Initial scaffold: context-aware notes app for iOS + macOS
XcodeGen project (Notes / NotesMac / NotesTests), Swift 6 strict concurrency, iCloud-document storage via IndieSync with a SwiftData cache, automatic capture context (location, place, time zone, device) on every note, context-based relevance ranking with a 'Right here, right now' shelf, tags, search, and the square.and.pencil-on-red app icon.
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
import Foundation
|
||||
import IndieSync
|
||||
import SwiftData
|
||||
|
||||
/// Document ↔ cache-entity mapping. Nothing else in the app knows both
|
||||
/// shapes. `upsert` is used only by the metadata observer and cache
|
||||
/// rebuilds — app code never writes the cache directly.
|
||||
enum NoteMapper {
|
||||
static func fetch(id: String, in context: ModelContext) -> NoteEntity? {
|
||||
var descriptor = FetchDescriptor<NoteEntity>(predicate: #Predicate { $0.id == id })
|
||||
descriptor.fetchLimit = 1
|
||||
return try? context.fetch(descriptor).first
|
||||
}
|
||||
|
||||
static func fetch(jsonRelativePath path: String, in context: ModelContext) -> [NoteEntity] {
|
||||
let descriptor = FetchDescriptor<NoteEntity>(predicate: #Predicate { $0.jsonRelativePath == path })
|
||||
return (try? context.fetch(descriptor)) ?? []
|
||||
}
|
||||
|
||||
static func upsert(_ note: Note, relativePath: String, into context: ModelContext) {
|
||||
let idString = note.meta.id.stringValue
|
||||
let entity: NoteEntity
|
||||
if let existing = fetch(id: idString, in: context) {
|
||||
entity = existing
|
||||
} else {
|
||||
entity = NoteEntity(id: idString, jsonRelativePath: relativePath)
|
||||
context.insert(entity)
|
||||
}
|
||||
// Always overwrite, not just on insert — the document just read is at
|
||||
// least as fresh as whatever the cache last held.
|
||||
entity.text = note.payload.text
|
||||
entity.sourceRaw = note.payload.source.rawValue
|
||||
entity.tags = note.payload.tags
|
||||
entity.createdAt = note.meta.createdAt
|
||||
entity.modifiedAt = note.meta.modifiedAt
|
||||
entity.jsonRelativePath = relativePath
|
||||
entity.context = note.payload.context
|
||||
}
|
||||
|
||||
/// Near-lossless reconstruction from the cache, used only as the offline
|
||||
/// fallback when the authoritative file can't be read.
|
||||
static func note(from entity: NoteEntity) -> Note? {
|
||||
guard let id = ULID(string: entity.id) else { return nil }
|
||||
return Note(
|
||||
meta: Note.Meta(
|
||||
id: id,
|
||||
schemaVersion: Note.currentSchemaVersion,
|
||||
createdAt: entity.createdAt,
|
||||
modifiedAt: entity.modifiedAt
|
||||
),
|
||||
payload: Note.Payload(
|
||||
text: entity.text,
|
||||
source: entity.source,
|
||||
tags: entity.tags,
|
||||
context: entity.context
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user