mirror of
https://github.com/BigMarc/grammargem.git
synced 2026-10-13 17:01:55 +02:00
- Management app: sidebar window with Dashboard (usage insights), Devices (slot usage, deactivate this Mac), Writing Modes (custom + app-aware map), Dictionary, Snippets, Shortcuts, AI Model, Privacy, General, License, About. - New local stores: Preferences (launch-at-login, appearance), UsageStats (privacy-friendly local insights), CustomModesStore, SnippetStore. - License model now tracks device name + activation usage/limit for the Devices screen. - README rewritten as a conversion-focused product page (no internal cost/setup). - Legal entity set to FounderGem LLC across README, LICENSE, Info.plist, About. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
52 lines
1.5 KiB
Swift
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)
|
|
}
|
|
}
|
|
}
|