grammargem/Sources/GrammarGem/System/SnippetStore.swift
BigMarc 3d749e8139 feat: Implement LiveMonitor for real-time grammar and spelling checks across applications
- Added LiveMonitor class to scan text areas in the frontmost window for grammar and spelling issues.
- Introduced DetectedField struct to represent text areas with issues, including suggestions for corrections.
- Implemented functionality to apply corrections and dismiss suggestions.
- Added Permissions class to manage Accessibility permissions required for monitoring.
- Created SnippetStore for managing text-expansion snippets, including persistence to UserDefaults.
- Developed TextCapture class to read and write text from the frontmost application using Accessibility API and clipboard fallback.
- Implemented TextFieldScanner to traverse the Accessibility tree and collect editable text elements.
- Added TextReplacementCoordinator to orchestrate the text correction process.
- Integrated UpdaterManager for handling application updates using Sparkle.
- Created comprehensive unit tests for various components, including grammar corrections and entitlement checks.
2026-06-18 16:49:55 +02:00

52 lines
1.5 KiB
Swift

import Foundation
/// A text-expansion snippet: type a short trigger, get a longer expansion.
struct Snippet: Codable, Identifiable, Equatable {
var id = UUID()
var trigger: String
var expansion: String
}
/// Local store for text-expansion snippets (a paid feature). Persisted to disk;
/// snippet contents never leave the device.
@MainActor
final class SnippetStore: ObservableObject {
@Published private(set) var snippets: [Snippet]
private let key = "GrammaGem.snippets"
private let d = UserDefaults.standard
init() {
if let raw = d.data(forKey: key),
let decoded = try? JSONDecoder().decode([Snippet].self, from: raw) {
snippets = decoded
} else {
snippets = []
}
}
func add(trigger: String, expansion: String) {
let t = trigger.trimmingCharacters(in: .whitespaces)
let e = expansion.trimmingCharacters(in: .whitespacesAndNewlines)
guard !t.isEmpty, !e.isEmpty else { return }
snippets.append(Snippet(trigger: t, expansion: e))
persist()
}
func remove(_ snippet: Snippet) {
snippets.removeAll { $0.id == snippet.id }
persist()
}
/// Resolve a trigger to its expansion (used by the snippet hotkey path).
func expansion(for trigger: String) -> String? {
snippets.first { $0.trigger == trigger }?.expansion
}
private func persist() {
if let raw = try? JSONEncoder().encode(snippets) {
d.set(raw, forKey: key)
}
}
}