mirror of
https://github.com/BigMarc/grammargem.git
synced 2026-10-13 17:01:55 +02:00
Native SwiftUI menu-bar app: global hotkey → capture selected text (AX API / clipboard fallback) → grammar (Harper) / local AI (MLX) → replace in place. Free/paid entitlement gate + Lemon Squeezy device-cap licensing, $0 marginal cost per user. Heavyweight engines behind protocol stubs so it builds out of the box. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
38 lines
1.2 KiB
Swift
38 lines
1.2 KiB
Swift
import Foundation
|
|
|
|
/// The user's personal dictionary — names, brand terms, jargon GrammaGem should
|
|
/// never "correct". Feeds Harper's ignore-list and the LLM context. Capped at 25
|
|
/// entries on free, unlimited on paid (enforced by `FeatureGate`, not here).
|
|
@MainActor
|
|
final class PersonalDictionary: ObservableObject {
|
|
@Published private(set) var entries: [String]
|
|
|
|
private let key = "GrammaGem.personalDictionary"
|
|
private let defaults = UserDefaults.standard
|
|
|
|
init() {
|
|
entries = defaults.stringArray(forKey: key) ?? []
|
|
}
|
|
|
|
func contains(_ word: String) -> Bool {
|
|
entries.contains { $0.caseInsensitiveCompare(word) == .orderedSame }
|
|
}
|
|
|
|
/// Append a trimmed, de-duplicated entry. Cap enforcement happens upstream.
|
|
func add(_ word: String) {
|
|
let w = word.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
guard !w.isEmpty, !contains(w) else { return }
|
|
entries.append(w)
|
|
persist()
|
|
}
|
|
|
|
func remove(_ word: String) {
|
|
entries.removeAll { $0.caseInsensitiveCompare(word) == .orderedSame }
|
|
persist()
|
|
}
|
|
|
|
private func persist() {
|
|
defaults.set(entries, forKey: key)
|
|
}
|
|
}
|