diff options
Diffstat (limited to 'ext')
-rw-r--r-- | ext/css/settings.css | 7 | ||||
-rw-r--r-- | ext/data/templates/anki-field-templates-upgrade-v21.handlebars | 161 | ||||
-rw-r--r-- | ext/data/templates/default-anki-field-templates.handlebars | 54 | ||||
-rw-r--r-- | ext/js/background/backend.js | 20 | ||||
-rw-r--r-- | ext/js/data/options-util.js | 35 | ||||
-rw-r--r-- | ext/js/pages/welcome-main.js | 7 | ||||
-rw-r--r-- | ext/js/templates/sandbox/anki-template-renderer.js | 54 | ||||
-rw-r--r-- | ext/welcome.html | 27 |
8 files changed, 284 insertions, 81 deletions
diff --git a/ext/css/settings.css b/ext/css/settings.css index eaebc3af..a2618d88 100644 --- a/ext/css/settings.css +++ b/ext/css/settings.css @@ -2167,6 +2167,13 @@ button.hotkey-list-item-enabled-button[data-scope-count='0'] { display: none; } +.warn-custom-templates-notification { + border: 1px solid var(--danger-color); +} +:root:not([data-warn-custom-templates=true]) .warn-custom-templates-notification { + display: none; +} + .test-anki-note-viewer-container { margin-top: 0.85em; display: flex; diff --git a/ext/data/templates/anki-field-templates-upgrade-v21.handlebars b/ext/data/templates/anki-field-templates-upgrade-v21.handlebars new file mode 100644 index 00000000..33c4dc6c --- /dev/null +++ b/ext/data/templates/anki-field-templates-upgrade-v21.handlebars @@ -0,0 +1,161 @@ +{{<<<<<<<}} +{{#formatGlossary ../dictionary}}{{{.}}}{{/formatGlossary}} +{{=======}} +{{formatGlossary ../dictionary .}} +{{>>>>>>>}} + +{{<<<<<<<}} +{{~#furigana}}{{{.}}}{{/furigana~}} +{{=======}} +{{~furigana .~}} +{{>>>>>>>}} + +{{<<<<<<<}} +{{#furigana}}{{{definition}}}{{/furigana}} +{{=======}} +{{furigana definition}} +{{>>>>>>>}} + +{{<<<<<<<}} +{{~#furigana expression reading~}}{{~/furigana~}} +{{=======}} +{{~furigana expression reading~}} +{{>>>>>>>}} + +{{<<<<<<<}} +{{~#furigana expression reading}}{{/furigana~}} +{{=======}} +{{~furigana expression reading~}} +{{>>>>>>>}} + +{{<<<<<<<}} +{{~#furiganaPlain}}{{{.}}}{{/furiganaPlain~}} +{{=======}} +{{~furiganaPlain .~}} +{{>>>>>>>}} + +{{<<<<<<<}} +{{#furiganaPlain}}{{{definition}}}{{/furiganaPlain}} +{{=======}} +{{furiganaPlain definition}} +{{>>>>>>>}} + +{{<<<<<<<}} +{{~#furiganaPlain expression reading~}}{{~/furiganaPlain~}} +{{=======}} +{{~furiganaPlain expression reading~}} +{{>>>>>>>}} + +{{<<<<<<<}} +{{~#furiganaPlain expression reading}}{{/furiganaPlain~}} +{{=======}} +{{~furiganaPlain expression reading~}} +{{>>>>>>>}} + +{{<<<<<<<}} +{{#getMedia "audio"}}{{/getMedia}} +{{=======}} +{{getMedia "audio"}} +{{>>>>>>>}} + +{{<<<<<<<}} +{{#getMedia "screenshot"}}{{/getMedia}} +{{=======}} +{{getMedia "screenshot"}} +{{>>>>>>>}} + +{{<<<<<<<}} +{{#getMedia "clipboardImage"}}{{/getMedia}} +{{=======}} +{{getMedia "clipboardImage"}} +{{>>>>>>>}} + +{{<<<<<<<}} +{{#getMedia "clipboardText"}}{{/getMedia}} +{{=======}} +{{getMedia "clipboardText"}} +{{>>>>>>>}} + +{{<<<<<<<}} +{{#getMedia "selectionText"}}{{/getMedia}} +{{=======}} +{{getMedia "selectionText"}} +{{>>>>>>>}} + +{{<<<<<<<}} +{{#getMedia "textFurigana" definition.cloze.sentence escape=false}}{{/getMedia}} +{{=======}} +{{getMedia "textFurigana" definition.cloze.sentence escape=false}} +{{>>>>>>>}} + +{{<<<<<<<}} +{{~#pronunciation format=format reading=reading downstepPosition=position nasalPositions=nasalPositions devoicePositions=devoicePositions~}}{{~/pronunciation~}} +{{=======}} +{{~pronunciation format=format reading=reading downstepPosition=position nasalPositions=nasalPositions devoicePositions=devoicePositions~}} +{{>>>>>>>}} + +{{<<<<<<<}} +{{~#set "any" false}}{{/set~}} +{{=======}} +{{~set "any" false~}} +{{>>>>>>>}} + +{{<<<<<<<}} +{{~#set "any" true}}{{/set~}} +{{=======}} +{{~set "any" true~}} +{{>>>>>>>}} + +{{<<<<<<<}} +{{~#set "previousDictionary" dictionary~}}{{~/set~}} +{{=======}} +{{~set "previousDictionary" dictionary~}} +{{>>>>>>>}} + +{{<<<<<<<}} +{{~#set "exclusive" (spread exclusiveExpressions exclusiveReadings)}}{{/set~}} +{{=======}} +{{~set "exclusive" (spread exclusiveExpressions exclusiveReadings)~}} +{{>>>>>>>}} + +{{<<<<<<<}} +{{~#set "separator" ""~}}{{/set~}} +{{=======}} +{{~set "separator" ""~}} +{{>>>>>>>}} + +{{<<<<<<<}} +{{~#get "separator"}}{{/get~}} +{{=======}} +{{~get "separator"~}} +{{>>>>>>>}} + +{{<<<<<<<}} +{{~#set "found" false}}{{/set~}} +{{=======}} +{{~set "found" false~}} +{{>>>>>>>}} + +{{<<<<<<<}} +{{~#set "found" true}}{{/set~}} +{{=======}} +{{~set "found" true~}} +{{>>>>>>>}} + +{{<<<<<<<}} +{{~#set "first" true}}{{/set~}} +{{=======}} +{{~set "first" true~}} +{{>>>>>>>}} + +{{<<<<<<<}} +{{~#set "first" false~}}{{~/set~}} +{{=======}} +{{~set "first" false~}} +{{>>>>>>>}} + +{{<<<<<<<}} +{{~#set (concat "used_" .) true~}}{{~/set~}} +{{=======}} +{{~set (concat "used_" .) true~}} +{{>>>>>>>}} diff --git a/ext/data/templates/default-anki-field-templates.handlebars b/ext/data/templates/default-anki-field-templates.handlebars index 31d5d13f..d94f6d70 100644 --- a/ext/data/templates/default-anki-field-templates.handlebars +++ b/ext/data/templates/default-anki-field-templates.handlebars @@ -1,19 +1,19 @@ {{#*inline "glossary-single"}} {{~#unless brief~}} {{~#scope~}} - {{~#set "any" false}}{{/set~}} + {{~set "any" false~}} {{~#each definitionTags~}} {{~#if (op "||" (op "!" @root.compactTags) (op "!" redundant))~}} {{~#if (get "any")}}, {{else}}<i>({{/if~}} {{name}} - {{~#set "any" true}}{{/set~}} + {{~set "any" true~}} {{~/if~}} {{~/each~}} {{~#unless noDictionaryTag~}} {{~#if (op "||" (op "!" @root.compactTags) (op "!==" dictionary (get "previousDictionary")))~}} {{~#if (get "any")}}, {{else}}<i>({{/if~}} {{dictionary}} - {{~#set "any" true}}{{/set~}} + {{~set "any" true~}} {{~/if~}} {{~/unless~}} {{~#if (get "any")}})</i> {{/if~}} @@ -21,18 +21,18 @@ {{~#if only~}}({{#each only}}{{.}}{{#unless @last}}, {{/unless}}{{/each}} only) {{/if~}} {{~/unless~}} {{~#if (op "<=" glossary.length 1)~}} - {{#each glossary}}{{#formatGlossary ../dictionary}}{{{.}}}{{/formatGlossary}}{{/each}} + {{#each glossary}}{{formatGlossary ../dictionary .}}{{/each}} {{~else if @root.compactGlossaries~}} - {{#each glossary}}{{#formatGlossary ../dictionary}}{{{.}}}{{/formatGlossary}}{{#unless @last}} | {{/unless}}{{/each}} + {{#each glossary}}{{formatGlossary ../dictionary .}}{{#unless @last}} | {{/unless}}{{/each}} {{~else~}} - <ul>{{#each glossary}}<li>{{#formatGlossary ../dictionary}}{{{.}}}{{/formatGlossary}}</li>{{/each}}</ul> + <ul>{{#each glossary}}<li>{{formatGlossary ../dictionary .}}</li>{{/each}}</ul> {{~/if~}} - {{~#set "previousDictionary" dictionary~}}{{~/set~}} + {{~set "previousDictionary" dictionary~}} {{/inline}} {{#*inline "audio"}} {{~#if (hasMedia "audio")~}} - [sound:{{#getMedia "audio"}}{{/getMedia}}] + [sound:{{getMedia "audio"}}] {{~/if~}} {{/inline}} @@ -78,22 +78,22 @@ {{#*inline "furigana"}} {{~#if merge~}} {{~#each definition.expressions~}} - <span class="expression-{{termFrequency}}">{{~#furigana}}{{{.}}}{{/furigana~}}</span> + <span class="expression-{{termFrequency}}">{{~furigana .~}}</span> {{~#unless @last}}、{{/unless~}} {{~/each~}} {{~else~}} - {{#furigana}}{{{definition}}}{{/furigana}} + {{furigana definition}} {{~/if~}} {{/inline}} {{#*inline "furigana-plain"}} {{~#if merge~}} {{~#each definition.expressions~}} - <span class="expression-{{termFrequency}}">{{~#furiganaPlain}}{{{.}}}{{/furiganaPlain~}}</span> + <span class="expression-{{termFrequency}}">{{~furiganaPlain .~}}</span> {{~#unless @last}}、{{/unless~}} {{~/each~}} {{~else~}} - {{#furiganaPlain}}{{{definition}}}{{/furiganaPlain}} + {{furiganaPlain definition}} {{~/if~}} {{/inline}} @@ -174,7 +174,7 @@ {{#*inline "screenshot"}} {{~#if (hasMedia "screenshot")~}} - <img src="{{#getMedia "screenshot"}}{{/getMedia}}" /> + <img src="{{getMedia "screenshot"}}" /> {{~/if~}} {{/inline}} @@ -184,16 +184,16 @@ {{! Pitch Accents }} {{#*inline "pitch-accent-item"}} - {{~#pronunciation format=format reading=reading downstepPosition=position nasalPositions=nasalPositions devoicePositions=devoicePositions~}}{{~/pronunciation~}} + {{~pronunciation format=format reading=reading downstepPosition=position nasalPositions=nasalPositions devoicePositions=devoicePositions~}} {{/inline}} {{#*inline "pitch-accent-item-disambiguation"}} {{~#scope~}} - {{~#set "exclusive" (spread exclusiveExpressions exclusiveReadings)}}{{/set~}} + {{~set "exclusive" (spread exclusiveExpressions exclusiveReadings)~}} {{~#if (op ">" (property (get "exclusive") "length") 0)~}} - {{~#set "separator" ""~}}{{/set~}} + {{~set "separator" ""~}} <em>({{#each (get "exclusive")~}} - {{~#get "separator"}}{{/get~}}{{{.}}} + {{~get "separator"~}}{{{.}}} {{~/each}} only) </em> {{~/if~}} {{~/scope~}} @@ -231,12 +231,12 @@ {{#*inline "clipboard-image"}} {{~#if (hasMedia "clipboardImage")~}} - <img src="{{#getMedia "clipboardImage"}}{{/getMedia}}" /> + <img src="{{getMedia "clipboardImage"}}" /> {{~/if~}} {{/inline}} {{#*inline "clipboard-text"}} - {{~#if (hasMedia "clipboardText")}}{{#getMedia "clipboardText"}}{{/getMedia}}{{/if~}} + {{~#if (hasMedia "clipboardText")}}{{getMedia "clipboardText"}}{{/if~}} {{/inline}} {{#*inline "conjugation"}} @@ -255,7 +255,7 @@ <li> {{~#if (op "!==" ../definition.type "kanji")~}} {{~#if (op "||" (op ">" ../uniqueExpressions.length 1) (op ">" ../uniqueReadings.length 1))~}}( - {{~#furigana expression reading~}}{{~/furigana~}} + {{~furigana expression reading~}} ) {{/if~}} {{~/if~}} {{~dictionary}}: {{frequency~}} @@ -267,10 +267,10 @@ {{#*inline "stroke-count"}} {{~#scope~}} - {{~#set "found" false}}{{/set~}} + {{~set "found" false~}} {{~#each definition.stats.misc~}} {{~#if (op "===" name "strokes")~}} - {{~#set "found" true}}{{/set~}} + {{~set "found" true~}} Stroke count: {{value}} {{~/if~}} {{~/each~}} @@ -295,14 +295,14 @@ {{#*inline "part-of-speech"}} {{~#scope~}} {{~#if (op "!==" definition.type "kanji")~}} - {{~#set "first" true}}{{/set~}} + {{~set "first" true~}} {{~#each definition.expressions~}} {{~#each wordClasses~}} {{~#unless (get (concat "used_" .))~}} {{~> part-of-speech-pretty . ~}} {{~#unless (get "first")}}, {{/unless~}} - {{~#set (concat "used_" .) true~}}{{~/set~}} - {{~#set "first" false~}}{{~/set~}} + {{~set (concat "used_" .) true~}} + {{~set "first" false~}} {{~/unless~}} {{~/each~}} {{~/each~}} @@ -316,13 +316,13 @@ {{/inline}} {{#*inline "selection-text"}} - {{~#if (hasMedia "selectionText")}}{{#getMedia "selectionText"}}{{/getMedia}}{{/if~}} + {{~#if (hasMedia "selectionText")}}{{getMedia "selectionText"}}{{/if~}} {{/inline}} {{#*inline "sentence-furigana"}} {{~#if definition.cloze~}} {{~#if (hasMedia "textFurigana" definition.cloze.sentence)~}} - {{#getMedia "textFurigana" definition.cloze.sentence escape=false}}{{/getMedia}} + {{getMedia "textFurigana" definition.cloze.sentence escape=false}} {{~else~}} {{definition.cloze.sentence}} {{~/if~}} diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index 308ae4d5..8e8e6945 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -2127,20 +2127,12 @@ class Backend { } async _openWelcomeGuidePageOnce() { - if (isObject(chrome.storage) && isObject(chrome.storage.session)) { - // Chrome - chrome.storage.session.get(['openedWelcomePage']).then((result) => { - if (!result.openedWelcomePage) { - this._openWelcomeGuidePage(); - chrome.storage.session.set({'openedWelcomePage': true}); - } - }); - } else { - // Firefox (storage.session is not supported yet) - // NOTE: This means that the welcome page will repeatedly open in Firefox - // until they support storage.session. - this._openWelcomeGuidePage(); - } + chrome.storage.session.get(['openedWelcomePage']).then((result) => { + if (!result.openedWelcomePage) { + this._openWelcomeGuidePage(); + chrome.storage.session.set({'openedWelcomePage': true}); + } + }); } async _openWelcomeGuidePage() { diff --git a/ext/js/data/options-util.js b/ext/js/data/options-util.js index 2674701f..1f2ffb05 100644 --- a/ext/js/data/options-util.js +++ b/ext/js/data/options-util.js @@ -470,7 +470,8 @@ class OptionsUtil { {async: false, update: this._updateVersion17.bind(this)}, {async: false, update: this._updateVersion18.bind(this)}, {async: false, update: this._updateVersion19.bind(this)}, - {async: false, update: this._updateVersion20.bind(this)} + {async: false, update: this._updateVersion20.bind(this)}, + {async: true, update: this._updateVersion21.bind(this)} ]; if (typeof targetVersion === 'number' && targetVersion < result.length) { result.splice(targetVersion); @@ -997,4 +998,36 @@ class OptionsUtil { } return options; } + + async _updateVersion21(options) { + await this._applyAnkiFieldTemplatesPatch(options, '/data/templates/anki-field-templates-upgrade-v21.handlebars'); + + let customTemplates = false; + for (const {options: profileOptions} of options.profiles) { + if (profileOptions.anki.fieldTemplates !== null) { + customTemplates = true; + } + } + + if (customTemplates && isObject(chrome.storage)) { + chrome.storage.session.set({'needsCustomTemplatesWarning': true}); + await this._createTab(chrome.runtime.getURL('/welcome.html')); + chrome.storage.session.set({'openedWelcomePage': true}); + } + + return options; + } + + _createTab(url) { + return new Promise((resolve, reject) => { + chrome.tabs.create({url}, (tab) => { + const e = chrome.runtime.lastError; + if (e) { + reject(new Error(e.message)); + } else { + resolve(tab); + } + }); + }); + } } diff --git a/ext/js/pages/welcome-main.js b/ext/js/pages/welcome-main.js index 521ce2c2..8039dae5 100644 --- a/ext/js/pages/welcome-main.js +++ b/ext/js/pages/welcome-main.js @@ -58,6 +58,13 @@ async function setupGenericSettingsController(genericSettingController) { setupEnvironmentInfo(); + chrome.storage.session.get({'needsCustomTemplatesWarning': false}).then((result) => { + if (result.needsCustomTemplatesWarning) { + document.documentElement.dataset.warnCustomTemplates = 'true'; + chrome.storage.session.remove(['needsCustomTemplatesWarning']); + } + }); + const preparePromises = []; const modalController = new ModalController(); diff --git a/ext/js/templates/sandbox/anki-template-renderer.js b/ext/js/templates/sandbox/anki-template-renderer.js index 789f0942..766c7798 100644 --- a/ext/js/templates/sandbox/anki-template-renderer.js +++ b/ext/js/templates/sandbox/anki-template-renderer.js @@ -68,9 +68,7 @@ class AnkiTemplateRenderer { ['dumpObject', this._dumpObject.bind(this)], ['furigana', this._furigana.bind(this)], ['furiganaPlain', this._furiganaPlain.bind(this)], - ['kanjiLinks', this._kanjiLinks.bind(this)], ['multiLine', this._multiLine.bind(this)], - ['sanitizeCssClass', this._sanitizeCssClass.bind(this)], ['regexReplace', this._regexReplace.bind(this)], ['regexMatch', this._regexMatch.bind(this)], ['mergeTags', this._mergeTags.bind(this)], @@ -132,10 +130,14 @@ class AnkiTemplateRenderer { return Handlebars.Utils.escapeExpression(text); } + _safeString(text) { + return new Handlebars.SafeString(text); + } + // Template helpers - _dumpObject(context, options) { - const dump = JSON.stringify(options.fn(context), null, 4); + _dumpObject(context, object) { + const dump = JSON.stringify(object, null, 4); return this._escape(dump); } @@ -145,14 +147,16 @@ class AnkiTemplateRenderer { let result = ''; for (const {text, reading: reading2} of segs) { - if (reading2.length > 0) { - result += `<ruby>${text}<rt>${reading2}</rt></ruby>`; + const safeText = this._escape(text); + const safeReading = this._escape(reading2); + if (safeReading.length > 0) { + result += `<ruby>${safeText}<rt>${safeReading}</rt></ruby>`; } else { - result += text; + result += safeText; } } - return result; + return this._safeString(result); } _furiganaPlain(context, ...args) { @@ -173,29 +177,16 @@ class AnkiTemplateRenderer { } _getFuriganaExpressionAndReading(context, ...args) { - const options = args[args.length - 1]; if (args.length >= 3) { return {expression: args[0], reading: args[1]}; - } else { - const {expression, reading} = options.fn(context); + } else if (args.length === 2) { + const {expression, reading} = args[0]; return {expression, reading}; + } else { + return void 0; } } - _kanjiLinks(context, options) { - const jp = this._japaneseUtil; - let result = ''; - for (const c of options.fn(context)) { - if (jp.isCodePointKanji(c.codePointAt(0))) { - result += `<a href="#" class="kanji-link">${c}</a>`; - } else { - result += c; - } - } - - return result; - } - _stringToMultiLineHtml(string) { return string.split('\n').join('<br>'); } @@ -204,10 +195,6 @@ class AnkiTemplateRenderer { return this._stringToMultiLineHtml(options.fn(context)); } - _sanitizeCssClass(context, options) { - return options.fn(context).replace(/[^_a-z0-9\u00a0-\uffff]/ig, '_'); - } - _regexReplace(context, ...args) { // Usage: // {{#regexReplace regex string [flags] [content]...}}content{{/regexReplace}} @@ -219,7 +206,7 @@ class AnkiTemplateRenderer { const options = args[argCount]; let value = typeof options.fn === 'function' ? options.fn(context) : ''; if (argCount > 3) { - value = `${args.slice(3).join('')}${value}`; + value = `${args.slice(3, -1).join('')}${value}`; } if (argCount > 1) { try { @@ -243,7 +230,7 @@ class AnkiTemplateRenderer { const options = args[argCount]; let value = typeof options.fn === 'function' ? options.fn(context) : ''; if (argCount > 2) { - value = `${args.slice(2).join('')}${value}`; + value = `${args.slice(2, -1).join('')}${value}`; } if (argCount > 0) { try { @@ -490,7 +477,7 @@ class AnkiTemplateRenderer { this._normalizeHtml(container, styleApplier, datasetKeyIgnorePattern); const result = container.innerHTML; container.textContent = ''; - return result; + return this._safeString(result); } _normalizeHtml(root, styleApplier, datasetKeyIgnorePattern) { @@ -543,9 +530,8 @@ class AnkiTemplateRenderer { return instance; } - _formatGlossary(context, dictionary, options) { + _formatGlossary(context, dictionary, content, options) { const data = options.data.root; - const content = options.fn(context); if (typeof content === 'string') { return this._stringToMultiLineHtml(this._escape(content)); } if (!(typeof content === 'object' && content !== null)) { return ''; } switch (content.type) { diff --git a/ext/welcome.html b/ext/welcome.html index 14e98367..56167866 100644 --- a/ext/welcome.html +++ b/ext/welcome.html @@ -25,6 +25,19 @@ <h1>Welcome to Yomitan!</h1> + <!-- Notifications --> + <div class="settings-group settings-group-top-margin warn-custom-templates-notification"> + <div class="settings-item"> + <div class="settings-item-inner settings-item-inner-wrappable"><div class="settings-item-left"><div class="settings-item-label"> + <p> + There are custom Anki templates in your settings. Note that <a href="https://github.com/themoeway/yomitan#custom-templates" target="_blank" rel="noopener noreferrer">some syntax has changed from previous versions of Yomitan.</a> + Please ensure that your custom templates are using the updated syntax. + </p> + </div></div></div> + </div> + </div> + + <!-- Content --> <h2>Here are some basics to get started</h2> <div class="settings-group"> <div class="settings-item"> @@ -49,10 +62,9 @@ <div class="settings-item"> <div class="settings-item-inner"><div class="settings-item-left"><div class="settings-item-label"> Yomitan requires one or more dictionaries to be installed in order to look up terms, kanji, and other information. - Several downloadable dictionaries can be found on the <a href="https://github.com/themoeway/yomitan#dictionaries" target="_blank" rel="noopener noreferrer">Yomitan homepage</a>, - allowing you to choose the dictionaries most relevant for you. + Several downloadable dictionaries can be found on the <a href="https://github.com/themoeway/yomitan#dictionaries" target="_blank" rel="noopener noreferrer">Yomitan homepage</a>. Dictionaries can be configured using the button below, - or later from the the <a href="/settings.html" rel="noopener">Settings</a> page. + or later from the <a href="/settings.html" rel="noopener">Settings</a> page. </div></div></div> <div class="settings-item-children settings-item-children-group"> <div class="settings-item settings-item-button" data-modal-action="show,dictionaries"><div class="settings-item-inner"> @@ -67,12 +79,17 @@ </div> <div class="settings-item"> <div class="settings-item-inner"><div class="settings-item-left"><div class="settings-item-label"> - You can also import an exported collection of dictionaries to migrate from a different device or browser from the <a href="/settings.html#!backup">Backup section of the Settings</a> page. + You can also import an exported collection of dictionaries from the <a href="/settings.html#!backup">Backup section of the Settings</a> page. <br><br> - If you are migrating from Yomichan, you may be particularly interested in migrating your data from Yomichan into Yomitan. + If you are migrating from Yomichan, you may be interested in importing your data into Yomitan. Please follow instructions from <a href="https://github.com/themoeway/yomitan#migrating-from-yomichan" target="_blank" rel="noopener noreferrer">Yomitan's README</a> for that. + + <br><br> + + If you are using or planning to use custom templates for Anki note creation, note that <a href="https://github.com/themoeway/yomitan#custom-templates" target="_blank" rel="noopener noreferrer">some syntax has changed from Yomichan and Yomibaba.</a> + Please ensure that your custom templates are using the updated syntax. </div></div></div> </div> <div class="settings-item"> |