Files
openless/Sources/OpenLessPersistence/DictionaryStore.swift
T
baiqing 88a5a97812 feat: 1.0.03 — 侧边栏重构 + 词汇表 chip 布局 + 风格页 + 关掉自动学词
- 侧边栏 4 → 6 项:首页 / 历史记录 / 词汇表 / 风格 / 帮助中心 / 设置
- 词典 → 词汇表,整页改 chip 流式布局:phrase + hitCount,hover ✕ 删除,
  批量输入(每行一个),重置统计 / 清除全部
- DictionaryEntry 加 hitCount + 兼容旧 dictionary.json 解码
- DictationCoordinator 在润色后扫描最终文本递增命中数;
  删除 learnTerms 自动学词路径
- 润色模式 → 风格 (StyleTab):4 张模式卡 + 启用开关;
  关闭时跳过 Ark 直接插入原文 (UserPreferences.polishEnabled)
- 帮助中心:快速上手 4 步、快捷键速查、FAQ、GitHub 外链
- 设置 Tab 合并 Volc ASR + Ark 凭据、录音、授权、隐私、关于 + 检查更新
- 胶囊:NSPanel 220×96, hasShadow=false;macOS 26 分支去掉 SwiftUI 阴影
  → 解决"红绿灯外圈黑边"
- 侧边栏视觉:strokeBorder 0.08 → 0.04、阴影 radius 16 → 6
  → 解决"灰色边栏 + 黑色卡顿"
- 首页底部新增今日概览 + 风格预览
2026-04-28 11:16:40 +08:00

114 lines
3.6 KiB
Swift

import Foundation
import OpenLessCore
public final class DictionaryStore: @unchecked Sendable {
private let fileURL: URL
private let lock = NSLock()
private let encoder = JSONEncoder()
private let decoder = JSONDecoder()
public init() {
encoder.dateEncodingStrategy = .iso8601
decoder.dateDecodingStrategy = .iso8601
let dir = FileManager.default
.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
.appendingPathComponent("OpenLess", isDirectory: true)
try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
self.fileURL = dir.appendingPathComponent("dictionary.json")
if !FileManager.default.fileExists(atPath: fileURL.path) {
FileManager.default.createFile(atPath: fileURL.path, contents: "[]".data(using: .utf8))
}
}
public func all() -> [DictionaryEntry] {
lock.lock()
defer { lock.unlock() }
return loadAll()
}
public func enabledEntries() -> [DictionaryEntry] {
all().filter { $0.enabled && !$0.trimmedPhrase.isEmpty }
}
public func upsert(_ entry: DictionaryEntry) {
lock.lock()
defer { lock.unlock() }
var entries = loadAll()
var updated = entry
updated.phrase = updated.trimmedPhrase
updated.updatedAt = Date()
if let index = entries.firstIndex(where: { $0.id == updated.id }) {
entries[index] = updated
} else {
entries.insert(updated, at: 0)
}
saveLocked(entries)
}
public func delete(id: UUID) {
lock.lock()
defer { lock.unlock() }
let entries = loadAll().filter { $0.id != id }
saveLocked(entries)
}
/// 扫描润色后文本:每个启用词条只要在文本里出现就 +1(同一次输出最多 +1)。
/// 返回这次新增命中的 phrase,方便日志。
@discardableResult
public func incrementHits(matching text: String) -> [String] {
let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { return [] }
lock.lock()
defer { lock.unlock() }
var entries = loadAll()
let lowerText = trimmed.lowercased()
var hit: [String] = []
for index in entries.indices {
guard entries[index].enabled else { continue }
let phrase = entries[index].trimmedPhrase
guard !phrase.isEmpty else { continue }
if lowerText.contains(phrase.lowercased()) {
entries[index].hitCount += 1
hit.append(phrase)
}
}
if !hit.isEmpty {
saveLocked(entries)
}
return hit
}
public func resetHits() {
lock.lock()
defer { lock.unlock() }
var entries = loadAll()
for index in entries.indices {
entries[index].hitCount = 0
}
saveLocked(entries)
}
public func clearAll() {
lock.lock()
defer { lock.unlock() }
saveLocked([])
}
private func loadAll() -> [DictionaryEntry] {
guard let data = try? Data(contentsOf: fileURL) else { return [] }
return (try? decoder.decode([DictionaryEntry].self, from: data)) ?? []
}
private func saveLocked(_ entries: [DictionaryEntry]) {
let sorted = entries.sorted { lhs, rhs in
if lhs.enabled != rhs.enabled { return lhs.enabled && !rhs.enabled }
return lhs.updatedAt > rhs.updatedAt
}
if let data = try? encoder.encode(sorted) {
try? data.write(to: fileURL, options: .atomic)
}
}
}