import Foundation /// Eine kurze Notiz. /// /// Kein Titel als eigenes Feld: wer eine Notiz schreibt, will schreiben und /// nicht erst benennen. Die Überschrift ist die erste Zeile — dieselbe /// Verabredung, die Apples Notizen seit jeher treffen. public struct Note: Identifiable, Equatable, Sendable, Codable { public let id: UUID public var text: String public var updatedAt: Date public init(id: UUID = UUID(), text: String = "", updatedAt: Date = Date()) { self.id = id self.text = text self.updatedAt = updatedAt } } /// Die Regeln der Notizen — ohne Dateisystem und ohne Uhr. public enum NoteRules { /// Wie viele Notizen höchstens. /// /// Die Kachel ist klein und die Liste soll überschaubar bleiben. Wer mehr /// braucht, braucht ein Notizprogramm und keine Notch. public static let limit = 50 /// Wie lang eine Überschrift höchstens ist. public static let titleLength = 60 /// Die erste nichtleere Zeile, gekürzt. Leer bleibt leer — daraus macht die /// Anzeige „Neue Notiz", und das ist ihre Sache, nicht unsere. public static func title(of note: Note) -> String { let line = note.text .split(whereSeparator: \.isNewline) .first { !$0.trimmingCharacters(in: .whitespaces).isEmpty } guard let line else { return "" } let trimmed = line.trimmingCharacters(in: .whitespaces) guard trimmed.count > titleLength else { return trimmed } return String(trimmed.prefix(titleLength)) + "…" } /// Ob eine Notiz überhaupt etwas enthält. /// /// Eine Notiz aus lauter Leerzeichen ist keine — sie entsteht, wenn man auf /// „Neu" drückt und es sich anders überlegt, und soll dann von selbst /// verschwinden statt als leere Zeile stehen zu bleiben. public static func isEmpty(_ note: Note) -> Bool { note.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty } /// Zuletzt geändert zuerst, mit fester Reihenfolge bei Gleichstand. public static func sorted(_ notes: [Note]) -> [Note] { notes.sorted { $0.updatedAt == $1.updatedAt ? $0.id.uuidString > $1.id.uuidString : $0.updatedAt > $1.updatedAt } } /// Nimmt eine geänderte Notiz auf: leere fallen weg, über der Grenze fällt /// die älteste heraus. public static func merge(_ notes: [Note], updating note: Note, limit: Int = limit) -> [Note] { var kept = notes.filter { $0.id != note.id } if !isEmpty(note) { kept.append(note) } let sorted = sorted(kept) return Array(sorted.prefix(limit)) } }