Merge pull request #415 from toasted-nutbread/anki-marker-document-title

Anki marker document title
This commit is contained in:
toasted-nutbread 2020-04-07 19:00:58 -04:00 committed by GitHub
commit fa68a87736
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 94 additions and 20 deletions

View File

@ -156,6 +156,7 @@ Flashcard fields can be configured with the following steps:
`{cloze-prefix}` | Text for the containing `{sentence}` from the start up to the value of `{cloze-body}`. `{cloze-prefix}` | Text for the containing `{sentence}` from the start up to the value of `{cloze-body}`.
`{cloze-suffix}` | Text for the containing `{sentence}` from the value of `{cloze-body}` to the end. `{cloze-suffix}` | Text for the containing `{sentence}` from the value of `{cloze-body}` to the end.
`{dictionary}` | Name of the dictionary from which the card is being created (unavailable in *grouped* mode). `{dictionary}` | Name of the dictionary from which the card is being created (unavailable in *grouped* mode).
`{document-title}` | Title of the web page that the term appeared in.
`{expression}` | Term expressed as Kanji (will be displayed in Kana if Kanji is not available). `{expression}` | Term expressed as Kanji (will be displayed in Kana if Kanji is not available).
`{furigana}` | Term expressed as Kanji with Furigana displayed above it (e.g. <ruby>日本語<rt>にほんご</rt></ruby>). `{furigana}` | Term expressed as Kanji with Furigana displayed above it (e.g. <ruby>日本語<rt>にほんご</rt></ruby>).
`{furigana-plain}` | Term expressed as Kanji with Furigana displayed next to it in brackets (e.g. 日本語[にほんご]). `{furigana-plain}` | Term expressed as Kanji with Furigana displayed next to it in brackets (e.g. 日本語[にほんご]).
@ -175,6 +176,7 @@ Flashcard fields can be configured with the following steps:
`{cloze-prefix}` | Text for the containing `{sentence}` from the start up to the value of `{cloze-body}`. `{cloze-prefix}` | Text for the containing `{sentence}` from the start up to the value of `{cloze-body}`.
`{cloze-suffix}` | Text for the containing `{sentence}` from the value of `{cloze-body}` to the end. `{cloze-suffix}` | Text for the containing `{sentence}` from the value of `{cloze-body}` to the end.
`{dictionary}` | Name of the dictionary from which the card is being created. `{dictionary}` | Name of the dictionary from which the card is being created.
`{document-title}` | Title of the web page that the Kanji appeared in.
`{glossary}` | List of definitions for the Kanji. `{glossary}` | List of definitions for the Kanji.
`{kunyomi}` | Kunyomi (Japanese reading) for the Kanji expressed as Katakana. `{kunyomi}` | Kunyomi (Japanese reading) for the Kanji expressed as Katakana.
`{onyomi}` | Onyomi (Chinese reading) for the Kanji expressed as Hiragana. `{onyomi}` | Onyomi (Chinese reading) for the Kanji expressed as Hiragana.

View File

@ -158,4 +158,8 @@
<img src="{{definition.screenshotFileName}}" /> <img src="{{definition.screenshotFileName}}" />
{{/inline}} {{/inline}}
{{#*inline "document-title"}}
{{~context.document.title~}}
{{/inline}}
{{~> (lookup . "marker") ~}} {{~> (lookup . "marker") ~}}

View File

@ -21,7 +21,7 @@ class AnkiNoteBuilder {
this._renderTemplate = renderTemplate; this._renderTemplate = renderTemplate;
} }
async createNote(definition, mode, options, templates) { async createNote(definition, mode, context, options, templates) {
const isKanji = (mode === 'kanji'); const isKanji = (mode === 'kanji');
const tags = options.anki.tags; const tags = options.anki.tags;
const modeOptions = isKanji ? options.anki.kanji : options.anki.terms; const modeOptions = isKanji ? options.anki.kanji : options.anki.terms;
@ -35,7 +35,7 @@ class AnkiNoteBuilder {
}; };
for (const [fieldName, fieldValue] of modeOptionsFieldEntries) { for (const [fieldName, fieldValue] of modeOptionsFieldEntries) {
note.fields[fieldName] = await this.formatField(fieldValue, definition, mode, options, templates, null); note.fields[fieldName] = await this.formatField(fieldValue, definition, mode, context, options, templates, null);
} }
if (!isKanji && definition.audio) { if (!isKanji && definition.audio) {
@ -60,7 +60,7 @@ class AnkiNoteBuilder {
return note; return note;
} }
async formatField(field, definition, mode, options, templates, errors=null) { async formatField(field, definition, mode, context, options, templates, errors=null) {
const data = { const data = {
marker: null, marker: null,
definition, definition,
@ -69,7 +69,8 @@ class AnkiNoteBuilder {
modeTermKanji: mode === 'term-kanji', modeTermKanji: mode === 'term-kanji',
modeTermKana: mode === 'term-kana', modeTermKana: mode === 'term-kana',
modeKanji: mode === 'kanji', modeKanji: mode === 'kanji',
compactGlossaries: options.general.compactGlossaries compactGlossaries: options.general.compactGlossaries,
context
}; };
const pattern = /\{([\w-]+)\}/g; const pattern = /\{([\w-]+)\}/g;
return await AnkiNoteBuilder.stringReplaceAsync(field, pattern, async (g0, marker) => { return await AnkiNoteBuilder.stringReplaceAsync(field, pattern, async (g0, marker) => {

View File

@ -455,7 +455,7 @@ class Backend {
return results; return results;
} }
async _onApiDefinitionAdd({definition, mode, context, optionsContext}) { async _onApiDefinitionAdd({definition, mode, context, details, optionsContext}) {
const options = this.getOptions(optionsContext); const options = this.getOptions(optionsContext);
const templates = this.defaultAnkiFieldTemplates; const templates = this.defaultAnkiFieldTemplates;
@ -468,19 +468,19 @@ class Backend {
); );
} }
if (context && context.screenshot) { if (details && details.screenshot) {
await this._injectScreenshot( await this._injectScreenshot(
definition, definition,
options.anki.terms.fields, options.anki.terms.fields,
context.screenshot details.screenshot
); );
} }
const note = await this.ankiNoteBuilder.createNote(definition, mode, options, templates); const note = await this.ankiNoteBuilder.createNote(definition, mode, context, options, templates);
return this.anki.addNote(note); return this.anki.addNote(note);
} }
async _onApiDefinitionsAddable({definitions, modes, optionsContext}) { async _onApiDefinitionsAddable({definitions, modes, context, optionsContext}) {
const options = this.getOptions(optionsContext); const options = this.getOptions(optionsContext);
const templates = this.defaultAnkiFieldTemplates; const templates = this.defaultAnkiFieldTemplates;
const states = []; const states = [];
@ -489,7 +489,7 @@ class Backend {
const notes = []; const notes = [];
for (const definition of definitions) { for (const definition of definitions) {
for (const mode of modes) { for (const mode of modes) {
const note = await this.ankiNoteBuilder.createNote(definition, mode, options, templates); const note = await this.ankiNoteBuilder.createNote(definition, mode, context, options, templates);
notes.push(note); notes.push(note);
} }
} }

View File

@ -91,6 +91,15 @@ const profileOptionsVersionUpdates = [
if (utilStringHashCode(options.anki.fieldTemplates) === 1444379824) { if (utilStringHashCode(options.anki.fieldTemplates) === 1444379824) {
options.anki.fieldTemplates = null; options.anki.fieldTemplates = null;
} }
},
(options) => {
// Version 13 changes:
// Default anki field tempaltes updated to include {document-title}.
let fieldTemplates = options.anki.fieldTemplates;
if (typeof fieldTemplates === 'string') {
fieldTemplates += '\n\n{{#*inline "document-title"}}\n {{~context.document.title~}}\n{{/inline}}';
options.anki.fieldTemplates = fieldTemplates;
}
} }
]; ];

View File

@ -99,10 +99,15 @@ async function ankiTemplatesValidate(infoNode, field, mode, showSuccessResult, i
const definition = await ankiTemplatesValidateGetDefinition(text, optionsContext); const definition = await ankiTemplatesValidateGetDefinition(text, optionsContext);
if (definition !== null) { if (definition !== null) {
const options = await apiOptionsGet(optionsContext); const options = await apiOptionsGet(optionsContext);
const context = {
document: {
title: document.title
}
};
let templates = options.anki.fieldTemplates; let templates = options.anki.fieldTemplates;
if (typeof templates !== 'string') { templates = await apiGetDefaultAnkiFieldTemplates(); } if (typeof templates !== 'string') { templates = await apiGetDefaultAnkiFieldTemplates(); }
const ankiNoteBuilder = new AnkiNoteBuilder({renderTemplate: apiTemplateRender}); const ankiNoteBuilder = new AnkiNoteBuilder({renderTemplate: apiTemplateRender});
result = await ankiNoteBuilder.formatField(field, definition, mode, options, templates, exceptions); result = await ankiNoteBuilder.formatField(field, definition, mode, context, options, templates, exceptions);
} }
} catch (e) { } catch (e) {
exceptions.push(e); exceptions.push(e);

View File

@ -243,6 +243,7 @@ function ankiGetFieldMarkers(type) {
'cloze-prefix', 'cloze-prefix',
'cloze-suffix', 'cloze-suffix',
'dictionary', 'dictionary',
'document-title',
'expression', 'expression',
'furigana', 'furigana',
'furigana-plain', 'furigana-plain',
@ -258,6 +259,7 @@ function ankiGetFieldMarkers(type) {
return [ return [
'character', 'character',
'dictionary', 'dictionary',
'document-title',
'glossary', 'glossary',
'kunyomi', 'kunyomi',
'onyomi', 'onyomi',

View File

@ -162,6 +162,33 @@ class DisplayFloat extends Display {
setContentScale(scale) { setContentScale(scale) {
document.body.style.fontSize = `${scale}em`; document.body.style.fontSize = `${scale}em`;
} }
async getDocumentTitle() {
try {
const uniqueId = yomichan.generateId(16);
const promise = yomichan.getTemporaryListenerResult(
chrome.runtime.onMessage,
({action, params}, {resolve}) => {
if (
action === 'documentInformationBroadcast' &&
isObject(params) &&
params.uniqueId === uniqueId &&
params.frameId === 0
) {
resolve(params);
}
},
2000
);
apiForward('requestDocumentInformationBroadcast', {uniqueId});
const {title} = await promise;
return title;
} catch (e) {
return '';
}
}
} }
DisplayFloat.instance = new DisplayFloat(); DisplayFloat.instance = new DisplayFloat();

View File

@ -54,7 +54,8 @@ class Frontend extends TextScanner {
this._runtimeMessageHandlers = new Map([ this._runtimeMessageHandlers = new Map([
['popupSetVisibleOverride', ({visible}) => { this.popup.setVisibleOverride(visible); }], ['popupSetVisibleOverride', ({visible}) => { this.popup.setVisibleOverride(visible); }],
['rootPopupRequestInformationBroadcast', () => { this._broadcastRootPopupInformation(); }] ['rootPopupRequestInformationBroadcast', () => { this._broadcastRootPopupInformation(); }],
['requestDocumentInformationBroadcast', ({uniqueId}) => { this._broadcastDocumentInformation(uniqueId); }]
]); ]);
} }
@ -264,6 +265,14 @@ class Frontend extends TextScanner {
} }
} }
_broadcastDocumentInformation(uniqueId) {
apiForward('documentInformationBroadcast', {
uniqueId,
frameId: this.popup.frameId,
title: document.title
});
}
async _updatePopupPosition() { async _updatePopupPosition() {
const textSource = this.getCurrentTextSource(); const textSource = this.getCurrentTextSource();
if (textSource !== null && await this.popup.isVisible()) { if (textSource !== null && await this.popup.isVisible()) {

View File

@ -53,12 +53,12 @@ function apiKanjiFind(text, optionsContext) {
return _apiInvoke('kanjiFind', {text, optionsContext}); return _apiInvoke('kanjiFind', {text, optionsContext});
} }
function apiDefinitionAdd(definition, mode, context, optionsContext) { function apiDefinitionAdd(definition, mode, context, details, optionsContext) {
return _apiInvoke('definitionAdd', {definition, mode, context, optionsContext}); return _apiInvoke('definitionAdd', {definition, mode, context, details, optionsContext});
} }
function apiDefinitionsAddable(definitions, modes, optionsContext) { function apiDefinitionsAddable(definitions, modes, context, optionsContext) {
return _apiInvoke('definitionsAddable', {definitions, modes, optionsContext}); return _apiInvoke('definitionsAddable', {definitions, modes, context, optionsContext});
} }
function apiNoteView(noteId) { function apiNoteView(noteId) {

View File

@ -752,15 +752,16 @@ class Display {
try { try {
this.setSpinnerVisible(true); this.setSpinnerVisible(true);
const context = {}; const details = {};
if (this.noteUsesScreenshot(mode)) { if (this.noteUsesScreenshot(mode)) {
const screenshot = await this.getScreenshot(); const screenshot = await this.getScreenshot();
if (screenshot) { if (screenshot) {
context.screenshot = screenshot; details.screenshot = screenshot;
} }
} }
const noteId = await apiDefinitionAdd(definition, mode, context, this.getOptionsContext()); const context = await this._getNoteContext();
const noteId = await apiDefinitionAdd(definition, mode, context, details, this.getOptionsContext());
if (noteId) { if (noteId) {
const index = this.definitions.indexOf(definition); const index = this.definitions.indexOf(definition);
const adderButton = this.adderButtonFind(index, mode); const adderButton = this.adderButtonFind(index, mode);
@ -908,12 +909,17 @@ class Display {
async getDefinitionsAddable(definitions, modes) { async getDefinitionsAddable(definitions, modes) {
try { try {
return await apiDefinitionsAddable(definitions, modes, this.getOptionsContext()); const context = await this._getNoteContext();
return await apiDefinitionsAddable(definitions, modes, context, this.getOptionsContext());
} catch (e) { } catch (e) {
return []; return [];
} }
} }
async getDocumentTitle() {
return document.title;
}
static indexOf(nodeList, node) { static indexOf(nodeList, node) {
for (let i = 0, ii = nodeList.length; i < ii; ++i) { for (let i = 0, ii = nodeList.length; i < ii; ++i) {
if (nodeList[i] === node) { if (nodeList[i] === node) {
@ -934,6 +940,15 @@ class Display {
return (typeof key === 'string' ? (key.length === 1 ? key.toUpperCase() : key) : ''); return (typeof key === 'string' ? (key.length === 1 ? key.toUpperCase() : key) : '');
} }
async _getNoteContext() {
const documentTitle = await this.getDocumentTitle();
return {
document: {
title: documentTitle
}
};
}
async _getAudioUri(definition, source) { async _getAudioUri(definition, source) {
const optionsContext = this.getOptionsContext(); const optionsContext = this.getOptionsContext();
return await apiAudioGetUri(definition, source, optionsContext); return await apiAudioGetUri(definition, source, optionsContext);