spb/prisme Public MIT
Navigateur iOS intelligent — chaque page comprise localement avant d'être affichée. SwiftUI · WebKit · Foundation Models, 100% on-device.
Swift 96.7%
JavaScript 3.3%
1//2// LibraryStore.swift3// Prisme4//5// Author: Simon-Pierre Boucher <contact@spboucher.ai>6//78import Foundation9import Observation10import SwiftData1112/// Owns excerpts and structured favourites. Separate store file from the13/// history — each SwiftData container needs its own URL or they fight14/// over the default store.15@MainActor16@Observable17final class LibraryStore {18 private let container: ModelContainer19 /// Bumped on every mutation so observing views refresh their fetches.20 private var revision = 02122 private var context: ModelContext { container.mainContext }2324 init() {25 let base = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]26 let schema = Schema([Excerpt.self, StructuredFavorite.self])27 do {28 let config = ModelConfiguration(url: base.appendingPathComponent("library.store"))29 container = try ModelContainer(for: schema, configurations: config)30 } catch {31 let memoryOnly = ModelConfiguration(isStoredInMemoryOnly: true)32 container = try! ModelContainer(for: schema, configurations: memoryOnly)33 }34 }3536 // MARK: - Excerpts3738 var excerpts: [Excerpt] {39 _ = revision40 let descriptor = FetchDescriptor<Excerpt>(41 sortBy: [SortDescriptor(\.savedAt, order: .reverse)]42 )43 return (try? context.fetch(descriptor)) ?? []44 }4546 func saveExcerpt(47 _ selection: SelectionExcerpt,48 url: URL?,49 pageTitle: String,50 containerID: UUID51 ) {52 context.insert(Excerpt(53 text: selection.text,54 sourceURLString: url?.absoluteString ?? "",55 sourceTitle: pageTitle,56 sourceHost: url?.host() ?? "",57 sectionTitle: selection.section,58 domPath: selection.domPath,59 containerID: containerID60 ))61 persist()62 }6364 func delete(_ excerpt: Excerpt) {65 context.delete(excerpt)66 persist()67 }6869 // MARK: - Structured favourites7071 var favorites: [StructuredFavorite] {72 _ = revision73 let descriptor = FetchDescriptor<StructuredFavorite>(74 sortBy: [SortDescriptor(\.savedAt, order: .reverse)]75 )76 return (try? context.fetch(descriptor)) ?? []77 }7879 func hasFavorite(for url: URL?) -> Bool {80 guard let urlString = url?.absoluteString else { return false }81 _ = revision82 var descriptor = FetchDescriptor<StructuredFavorite>(83 predicate: #Predicate { $0.urlString == urlString }84 )85 descriptor.fetchLimit = 186 return ((try? context.fetch(descriptor))?.first) != nil87 }8889 func saveFavorite(90 url: URL,91 title: String,92 gist: String,93 kindRaw: String?,94 outlineTitles: [String],95 isGenerated: Bool,96 containerID: UUID97 ) {98 let urlString = url.absoluteString99 var descriptor = FetchDescriptor<StructuredFavorite>(100 predicate: #Predicate { $0.urlString == urlString }101 )102 descriptor.fetchLimit = 1103 if let existing = (try? context.fetch(descriptor))?.first {104 existing.title = title105 existing.gist = gist106 existing.kindRaw = kindRaw107 existing.outlineTitles = outlineTitles108 existing.isGenerated = isGenerated109 existing.savedAt = Date()110 } else {111 context.insert(StructuredFavorite(112 urlString: urlString,113 host: url.host() ?? "",114 title: title,115 gist: gist,116 kindRaw: kindRaw,117 outlineTitles: outlineTitles,118 isGenerated: isGenerated,119 containerID: containerID120 ))121 }122 persist()123 }124125 func delete(_ favorite: StructuredFavorite) {126 context.delete(favorite)127 persist()128 }129130 private func persist() {131 try? context.save()132 revision += 1133 }134}135