69 lines
2.3 KiB
Swift
69 lines
2.3 KiB
Swift
import Foundation
|
|
|
|
/// The text of the Notitie editor, kept in one plain file. Saves are debounced and atomic.
|
|
@MainActor
|
|
public final class NoteStore: ObservableObject {
|
|
@Published public var text: String = "" {
|
|
didSet {
|
|
guard text != oldValue, !loading else { return }
|
|
changed()
|
|
}
|
|
}
|
|
public private(set) var lastError: String?
|
|
/// True when the file exists but could not be read: saving would overwrite the user's text, so it is switched off.
|
|
public private(set) var isReadOnly = false
|
|
|
|
private let fileURL: URL
|
|
private var saveTask: Task<Void, Never>?
|
|
private var loading = false
|
|
|
|
public init(fileURL: URL) {
|
|
self.fileURL = fileURL
|
|
}
|
|
|
|
public func load() {
|
|
guard FileManager.default.fileExists(atPath: fileURL.path) else { return }
|
|
loading = true
|
|
defer { loading = false }
|
|
do {
|
|
text = try String(contentsOf: fileURL, encoding: .utf8)
|
|
} catch {
|
|
isReadOnly = true
|
|
lastError = error.localizedDescription
|
|
}
|
|
}
|
|
|
|
/// Writes immediately (also call this when the app quits).
|
|
public func flush() {
|
|
saveTask?.cancel()
|
|
saveTask = nil
|
|
guard !isReadOnly else { return }
|
|
do {
|
|
try FileManager.default.createDirectory(
|
|
at: fileURL.deletingLastPathComponent(), withIntermediateDirectories: true)
|
|
try text.write(to: fileURL, atomically: true, encoding: .utf8)
|
|
lastError = nil
|
|
} catch {
|
|
lastError = error.localizedDescription
|
|
}
|
|
}
|
|
|
|
private func changed() {
|
|
saveTask?.cancel()
|
|
saveTask = Task { [weak self] in
|
|
try? await Task.sleep(for: .milliseconds(400))
|
|
guard !Task.isCancelled else { return }
|
|
self?.flush()
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Puts a space between dictations: the text before the cursor decides whether one is needed.
|
|
public enum DictationSpacing {
|
|
public static func prepared(_ text: String, characterBefore: Character?) -> String {
|
|
guard let before = characterBefore, !before.isWhitespace, let first = text.first else { return text }
|
|
if first.isWhitespace || ".,;:!?)]}\u{201D}\u{2019}".contains(first) { return text }
|
|
return " " + text
|
|
}
|
|
}
|