// // HistoryStore.swift // 划词侠 plugin // // Persists last 20 actions in UserDefaults under a single JSON key. // Cheap, durable, no external deps. Records actionLabel + input // preview + output preview + timestamp. // import Foundation struct HistoryEntry: Codable, Identifiable, Equatable { var id: UUID var actionLabel: String // e.g. "翻译·中文" / "总结" / "改语气·正式" var inputPreview: String // truncated to 80 chars var outputPreview: String // truncated to 120 chars var timestamp: Date init(id: UUID = UUID(), actionLabel: String, inputPreview: String, outputPreview: String, timestamp: Date = Date()) { self.id = id self.actionLabel = actionLabel self.inputPreview = inputPreview self.outputPreview = outputPreview self.timestamp = timestamp } } enum HistoryStore { private static let key = "com.mioisland.plugin.clipper.history.v1" private static let maxEntries = 20 static func load() -> [HistoryEntry] { guard let data = UserDefaults.standard.data(forKey: key) else { return [] } return (try? JSONDecoder().decode([HistoryEntry].self, from: data)) ?? [] } static func save(_ entries: [HistoryEntry]) { guard let data = try? JSONEncoder().encode(entries) else { return } UserDefaults.standard.set(data, forKey: key) } static func append(_ entry: HistoryEntry) -> [HistoryEntry] { var list = load() list.insert(entry, at: 0) if list.count > maxEntries { list = Array(list.prefix(maxEntries)) } save(list) return list } static func truncate(_ s: String, max: Int) -> String { let collapsed = s.replacingOccurrences(of: "\n", with: " ") if collapsed.count <= max { return collapsed } let i = collapsed.index(collapsed.startIndex, offsetBy: max) return String(collapsed[..