feat(anki): 添加 Anki Connect 集成 - #1241
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d2901c2627
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0efb02f4f2
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
感谢 PR,请解决一下冲突,稍后我们会 review |
Thanks for the PR, please resolve the conflict and we will review it later |
|
请继续推进 PR |
Please keep pushing the PR |
Easydict 目前缺少将词典查询结果直接加入 Anki 的入口,用户需要在查词和制卡之间手动复制内容。这个功能面向词典类结果,避免把普通句子翻译默认纳入制卡流程。 新增 Anki Connect 配置、字段自动读取、结果卡片底部按钮以及添加成功或失败的 toast 和日志。配置项支持端点、牌组、笔记类型、正反面字段,并通过 AnkiConnect 读取模板字段以减少手动填错。 此修改让用户可以从词典结果直接创建 Anki 卡片,并在添加成功或失败时获得明确反馈。它也为后续扩展更细的字段映射或例句卡片支持保留了清晰入口。 ---------------------------------------------------------------------- feat(anki): add Anki Connect integration Easydict did not provide a direct path from dictionary lookup results to Anki cards, so users had to copy content manually between lookup and note creation. This feature targets dictionary-style results and keeps general sentence translation out of the default card flow. Add Anki Connect settings, automatic note-field loading, a result-card toolbar button, and toast plus log feedback for add success or failure. The settings cover endpoint, deck, note type, and front/back fields, while AnkiConnect field loading helps avoid manual field-name mistakes. This lets users create Anki cards directly from dictionary results with clear feedback when the add operation succeeds or fails. It also leaves a focused path for future field mapping or example-sentence card support.
Anki Connect 之前只能把结果写入固定的正反面字段,无法把音标、释义或词形等内容拆到独立字段。使用自定义 Anki 模板时,用户仍然需要在 Easydict 和 Anki 之间手动调整字段。 新增可持久化的 Easydict 字段映射,并在设置页提供 Anki 字段到 Easydict 内容的逐行选择。读取 Anki 模板字段后会保存字段列表,默认映射仍保持 Word 和 Full Result 两行。 这样用户可以为音标、释义、翻译、词形和相关词等内容配置单独的 Anki 字段。旧的 Front/Back 设置仍作为兜底,因此现有制卡流程可以继续使用。 ---------------------------------------------------------------------- feat(anki): add field mapping settings Anki Connect previously wrote results into fixed front and back fields, so phonetics, definitions, and word forms could not be sent to separate fields. Users with custom Anki note types still had to adjust field content manually between Easydict and Anki. Add persistent Easydict field mappings and expose row-by-row selection from Anki fields to Easydict content in settings. Loading fields from the Anki note type now stores the field list, while the default mapping still keeps Word and Full Result rows. This lets users map phonetics, definitions, translations, word forms, and related words into dedicated Anki fields. The old Front/Back settings remain as a fallback, so the existing card creation flow keeps working.
Anki Connect 集成已经可以从词典结果创建卡片,但字段配置仍然偏固定,用户很难确认每个 Easydict 字段最终会写入 Anki 的内容。之前的 review 也暴露了不同词典结果在序列化时容易丢失可见内容的问题。
新增基于模板的 Anki 字段映射和设置页预览,支持插入 {Word}、{Definition} 等 Easydict 变量,并把 Anki Connect 设置区域拆成独立的 SwiftUI section。模板渲染同时补齐 simpleWords、音标、翻译文本以及 Apple Dictionary 和 MDict 的完整 HTML 结果处理。
此修改让用户可以在设置中预览 Anki 字段输出,再从词典结果创建更准确的卡片。拆分后的设置代码也降低了高级设置页的体积,便于后续 review 和维护。
----------------------------------------------------------------------
feat(anki): add field template mapping preview
The Anki Connect integration could already create cards from dictionary results, but field configuration was still too fixed and users could not easily confirm what each Easydict field would send to Anki. Earlier review feedback also showed that some dictionary result shapes could lose visible content during serialization.
Add template-based Anki field mappings and a settings preview with insertable Easydict variables such as {Word} and {Definition}. Split the Anki Connect settings into a dedicated SwiftUI section, and harden template rendering for simpleWords, phonetics, translated text, and full Apple Dictionary or MDict HTML results.
This lets users preview rendered Anki field output before creating cards from dictionary results. The settings split also keeps the advanced tab smaller and easier to review and maintain.
Anki 设置页首次出现时会先写入 Front 和 Back fallback 映射。用户随后读取真实模板字段时,这些未改动的默认映射会阻止新字段自动生效,导致非 Front/Back 笔记类型仍用旧字段名提交。 在读取 Anki 字段后检测当前映射是否仍是默认 fallback 或旧的默认字段映射。如果用户没有自定义过映射,就用新读取到的字段重新生成默认映射;如果用户已经改过字段或模板,则保留现有配置。 这样首次配置非 Front/Back 模板时,读取字段后即可直接得到正确的 Anki 字段名。用户的手动映射不会被刷新操作覆盖。 ---------------------------------------------------------------------- fix(anki): refresh untouched default field mappings The Anki settings view writes Front and Back fallback mappings when it first appears. When users later fetch real model fields, those untouched defaults prevented the fetched fields from taking effect, so non-Front/Back note types could still submit stale field names. Detect whether the current mappings still match the fallback defaults or the previously generated default mappings after Anki fields are loaded. If the user has not customized them, regenerate defaults from the fetched fields; if the user has changed fields or templates, keep the existing configuration. This lets first-time setup for non-Front/Back models pick up the correct Anki field names immediately after fetching fields. Manual user mappings remain protected from refresh overwrites.
审计本次 Anki 变更时发现词典渲染模板和 WebView 管理器里有几处无功能意义的空白或单用 helper 改动。 移除这些噪音,让最终 diff 聚焦 Anki Connect、字段映射、预览和词典内容采集本身,减少 PR 审查负担。 ---------------------------------------------------------------------- chore(anki): remove unrelated rendering churn While auditing the Anki changes, a few dictionary rendering and WebView manager edits turned out to be whitespace or a single-use helper with no behavior impact. Remove that noise so the final diff stays focused on Anki Connect, field mapping, preview, and dictionary content collection, reducing review overhead.
0efb02f to
9ae8268
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9ae8268a57
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
ai review出来的problem处理就你直接点solve mark掉就行了 |
To deal with the problems that come out of the AI review, just click solve mark and it will be done. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 44a2b4626f
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| let entryHTML = result.htmlStrings?.joined(separator: "\n\n") | ||
| let renderedHTML = result.htmlString | ||
|
|
||
| return firstNonEmpty([innerText, entryHTML, renderedHTML]) |
There was a problem hiding this comment.
Keep DictionaryText plain for MDict
When an MDict result is mapped to {DictionaryText}, innerTexts and htmlStrings are unset, so this fallback returns the complete rendered htmlString. The supposedly plain-text variable therefore becomes identical to {DictionaryHTML} and can include the document's CSS and markup in the Anki field. Populate or extract MDict plain text and use that here instead of falling back to HTML.
Useful? React with 👍 / 👎.
| let part = simpleWord.part?.trimmed ?? "" | ||
| let prefix = [word, part].filter { !$0.isEmpty }.joined(separator: " ") | ||
| return prefix.isEmpty ? means : "\(prefix): \(means)" |
There was a problem hiding this comment.
Avoid prefixing Youdao parts of speech twice
For Youdao simple-word entries with a part of speech, the parser sets showPartMeans = true, which already embeds simpleWord.part into meansText; this code then adds the same part to prefix. Consequently {Definition} and {FullResult} contain values such as foo n.: n. bar. Build the definition from simpleWord.means or skip the extra prefix when the part is already included.
Useful? React with 👍 / 👎.
| let lines = partLines(from: wordResult.synonyms, title: "Synonyms") | ||
| + partLines(from: wordResult.antonyms, title: "Antonyms") | ||
| + partLines(from: wordResult.collocation, title: "Collocation") |
There was a problem hiding this comment.
Localize headings embedded in Anki fields
On every non-English locale, {Related} and {FullResult} still write the English headings Synonyms, Antonyms, and Collocation into generated cards, even though the existing result UI and String Catalog already localize all three labels. Resolve the existing static catalog keys before passing these titles to partLines.
AGENTS.md reference: AGENTS.md:L25-L25
Useful? React with 👍 / 👎.
| return (false, object, "\(error)") | ||
| } | ||
|
|
||
| return (true, object, NSLocalizedString("anki.connect.added", comment: "")) |
There was a problem hiding this comment.
Reject responses that omit the Anki result
If the configured endpoint or an intervening proxy returns an unrelated JSON object such as {}, this path reports success because no non-null error exists. addNote then shows the localized “added” toast and permanently disables the result-card button even though no note ID was returned. Validate the Anki response envelope and require the expected result type for each action before reporting success.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 47c094514e
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| rendered = rendered.replacingOccurrences( | ||
| of: field.templateToken, | ||
| with: value, | ||
| options: [.caseInsensitive] |
There was a problem hiding this comment.
Encode plain values for Anki HTML fields
When a mapping uses a generated multiline value such as {Definition} or {FullResult}, this inserts its plain \n separators directly into an Anki field, which Anki renders as HTML; the lines therefore collapse into one paragraph, and tag-like dictionary text is interpreted as markup even though the settings preview displays it literally. HTML-escape plain token values and convert their line breaks to <br>, while preserving intentional template markup and raw {DictionaryHTML} content.
Useful? React with 👍 / 👎.
| let shouldReplaceMappings = shouldReplaceMappingsAfterFetching() | ||
| ankiConnectModelFields = fields |
There was a problem hiding this comment.
Ignore stale field responses after model edits
When the user changes the Note Type while a field request is pending, the editable model field can move from—for example—Basic to Cloze, but this completion still stores the fields returned for Basic and may replace the mappings with them. Subsequent addNote requests then use field names that do not exist in the currently configured model. Capture the requested model and discard the response if it changed, or disable model editing until the request completes.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 85e55cf42f
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| result.translatedText, | ||
| result.copiedText, | ||
| dictionaryContentForBack(from: result), |
There was a problem hiding this comment.
Avoid duplicating dictionary content in FullResult
When an Apple Dictionary or MDict WebView has finished loading, fetchDictionaryHTMLTextIfNeeded stores the extracted plain text in both translatedResults and copiedText. This list then adds that text alongside dictionaryContentForBack, which returns the complete HTML for those services; uniqueText removes only the duplicate plain-text copy, leaving {FullResult} with both plain text and the same content as HTML. Cards added after WebView extraction therefore contain duplicated definitions and can become unnecessarily large; exclude the WebView-derived text for these services or use only the preferred rendered HTML.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Bot automatically translated this content.
当 Apple Dictionary 或 MDict WebView 完成加载时,“fetchDictionaryHTMLTextIfNeeded”会将提取的纯文本存储在“translatedResults”和“copiedText”中。 然后,该列表将该文本添加到“dictionaryContentForBack”旁边,返回这些服务的完整 HTML; uniqueText 仅删除重复的纯文本副本,留下 {FullResult} 包含纯文本和与 HTML 相同的内容。 因此,在 WebView 提取后添加的卡片包含重复的定义,并且可能会变得不必要的大; 排除这些服务的 WebView 派生文本或仅使用首选呈现的 HTML。
有用? 用 👍 / 👎 做出反应。
| let result = try await queryAnkiPreview(text: text, serviceID: serviceID) | ||
| previewValues = AnkiTemplateRenderer.previewValues(from: result) |
There was a problem hiding this comment.
Discard previews for edited inputs
During a slow preview query, the word field and service picker remain editable while only the query button is disabled. The request captures the old text and service before this await, but its completion unconditionally replaces the preview, so changing either control in flight can leave the UI showing input B with results from request A. Compare a request token and the captured inputs before updating state, or disable those controls until the request finishes.
Useful? React with 👍 / 👎.
|
你好,有很多 review 评论,请看一下。 |
Hello, there are many review comments, please take a look. |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
法典审查摘要此评论显示了针对此拉取请求的最新法典审查活动。
ℹ️ 关于 GitHub 中的 Codex您的团队已设置 Codex 来审查此存储库中的拉取请求。 当您 当任何审查正在进行时,Codex 会以 👀 做出反应,如果有建议则进行评论,并在所有审查结束且没有发现结果时以 👍 做出反应。 </详情> |
|
To use Codex here, create a Codex account and connect to github. |
要在此处使用 Codex,请创建 Codex 帐户并连接到 github。 |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 41d832035f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| BOOL shouldShowAnkiButton = MyConfiguration.shared.enableAnkiConnect && | ||
| EZResultHasAnkiContent(result) && | ||
| (result.wordResult || EZResultNeedsDictionaryHTMLHeight(result)); |
There was a problem hiding this comment.
Show the Anki action for translation-only results
For Google sentence translations and other non-dictionary services that populate translatedText without a wordResult, this final condition is false, so the Anki button never appears even though EZResultHasAnkiContent accepts that translation and the new renderer exposes a {Translation} mapping. Remove the word-result/dictionary-only restriction or otherwise include translation-only results so users can actually add these supported values.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
| EZResultHasAnkiContent(result) && | ||
| (result.wordResult || EZResultNeedsDictionaryHTMLHeight(result)); | ||
| if (shouldShowAnkiButton) { | ||
| EZSymbolImageButton *ankiButton = [EZSymbolImageButton buttonWithSybolImageName:@"plus.rectangle.on.rectangle"]; |
There was a problem hiding this comment.
Use SFSafeSymbols for the Anki button
Replace the hardcoded plus.rectangle.on.rectangle identifier with the repository's typed SFSafeSymbols representation (bridged through a Swift helper if necessary); the current string bypasses compile-time symbol validation and directly violates the required symbol API convention. docs/agents/development.mdL52-L55
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Bot automatically translated this content.
将硬编码的 plus.rectangle.on.rectangle 标识符替换为存储库的类型化 SFSafeSymbols 表示形式(如有必要,通过 Swift 助手进行桥接); 当前字符串绕过编译时符号验证并直接违反了所需的符号 API 约定。 docs/agents/development.mdL52-L55
有用吗? 用 👍 / 👎 做出反应。
Related issue: #773
Supersedes #1227
Easydict 目前缺少将词典查询结果直接加入 Anki 的入口,用户需要在查词和制卡之间手动复制内容。这个功能面向词典类结果,避免把普通句子翻译默认纳入制卡流程。
新增 Anki Connect 配置、结果卡片底部按钮以及添加成功或失败的 toast 和日志。配置项支持端点、牌组、笔记类型,并可以通过 AnkiConnect 自动读取模板字段,减少手动填错。
在此基础上,设置页支持更灵活的字段映射。用户可以把每个 Anki 字段配置为 Easydict 变量模板,例如 {Word}、{Phonetic}、{Definition}、{Translation}、{DictionaryText}、{DictionaryHTML} 等,而不是只能选择固定的正反面字段。
同时新增字段预览入口。用户可以输入一个单词,选择词典服务后预览 Easydict 各变量的实际值,以及最终会写入 Anki 各字段的渲染结果,从而在真正添加卡片前确认字段是否匹配。
本次也补强了词典结果序列化逻辑:保留 simpleWords、音标、翻译文本等可见内容,并在 Apple Dictionary / MDict 多词典结果中优先使用完整渲染 HTML,避免创建卡片时丢失页面中可见的词典 section。
此修改让用户可以从词典结果直接创建 Anki 卡片,并在添加成功或失败时获得明确反馈。它也为后续扩展更细的字段映射、例句卡片或 TTS 附件支持保留了清晰入口。
