Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions ext/css/display.css
Original file line number Diff line number Diff line change
Expand Up @@ -1995,6 +1995,13 @@ button.footer-notification-close-button {
.popup-menu-item-group[data-is-primary-card-audio=true]>.popup-menu-item-audio-button~.popup-menu-item-set-primary-audio-button:focus .popup-menu-item-icon {
opacity: 1;
}
.popup-menu-item-group[data-is-sentence-card-audio=true]>.popup-menu-item-audio-button~.popup-menu-item-set-primary-audio-button .popup-menu-item-icon,
.popup-menu-item-group[data-is-sentence-card-audio=true]>.popup-menu-item-audio-button~.popup-menu-item-set-primary-audio-button:hover .popup-menu-item-icon,
.popup-menu-item-group[data-is-sentence-card-audio=true]>.popup-menu-item-audio-button~.popup-menu-item-set-primary-audio-button:active .popup-menu-item-icon,
.popup-menu-item-group[data-is-sentence-card-audio=true]>.popup-menu-item-audio-button~.popup-menu-item-set-primary-audio-button:focus .popup-menu-item-icon {
opacity: 1;
background-color: #6495ed; /* cornflowerblue */
}


/* Anki errors */
Expand Down
3 changes: 3 additions & 0 deletions ext/data/schemas/custom-audio-list-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
},
"url": {
"type": "string"
},
"sentence": {
"type": "string"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{{#*inline "sample-sentence-audio"}}
{{~#if (hasMedia "sampleSentenceAudio")~}}
[sound:{{getMedia "sampleSentenceAudio"}}]
{{~/if~}}
{{/inline}}

{{#*inline "sample-sentence-text"}}
{{~#if (hasMedia "sampleSentenceText")}}{{{getMedia "sampleSentenceText"}}}{{/if~}}
{{/inline}}
10 changes: 10 additions & 0 deletions ext/data/templates/default-anki-field-templates.handlebars
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@
{{~/if~}}
{{/inline}}

{{#*inline "sample-sentence-audio"}}
{{~#if (hasMedia "sampleSentenceAudio")~}}
[sound:{{getMedia "sampleSentenceAudio"}}]
{{~/if~}}
{{/inline}}

{{#*inline "sample-sentence-text"}}
{{~#if (hasMedia "sampleSentenceText")}}{{{getMedia "sampleSentenceText"}}}{{/if~}}
{{/inline}}

{{#*inline "character"}}
{{~definition.character~}}
{{/inline}}
Expand Down
66 changes: 64 additions & 2 deletions ext/js/background/backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -730,12 +730,13 @@ export class Backend {
}

/** @type {import('api').ApiHandler<'injectAnkiNoteMedia'>} */
async _onApiInjectAnkiNoteMedia({timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails, dictionaryMediaDetails}) {
async _onApiInjectAnkiNoteMedia({timestamp, definitionDetails, audioDetails, sampleSentenceAudioDetails, screenshotDetails, clipboardDetails, dictionaryMediaDetails}) {
return await this._injectAnkNoteMedia(
this._anki,
timestamp,
definitionDetails,
audioDetails,
sampleSentenceAudioDetails,
screenshotDetails,
clipboardDetails,
dictionaryMediaDetails,
Expand Down Expand Up @@ -2261,16 +2262,19 @@ export class Backend {
* @param {number} timestamp
* @param {import('api').InjectAnkiNoteMediaDefinitionDetails} definitionDetails
* @param {?import('api').InjectAnkiNoteMediaAudioDetails} audioDetails
* @param {?import('api').InjectAnkiNoteMediaSampleSentenceAudioDetails} sampleSentenceAudioDetails
* @param {?import('api').InjectAnkiNoteMediaScreenshotDetails} screenshotDetails
* @param {?import('api').InjectAnkiNoteMediaClipboardDetails} clipboardDetails
* @param {import('api').InjectAnkiNoteMediaDictionaryMediaDetails[]} dictionaryMediaDetails
* @returns {Promise<import('api').ApiReturn<'injectAnkiNoteMedia'>>}
*/
async _injectAnkNoteMedia(ankiConnect, timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails, dictionaryMediaDetails) {
async _injectAnkNoteMedia(ankiConnect, timestamp, definitionDetails, audioDetails, sampleSentenceAudioDetails, screenshotDetails, clipboardDetails, dictionaryMediaDetails) {
let screenshotFileName = null;
let clipboardImageFileName = null;
let clipboardText = null;
let audioFileName = null;
let sampleSentenceAudioFileName = null;
let sampleSentenceText = null;
const errors = [];

try {
Expand Down Expand Up @@ -2305,6 +2309,14 @@ export class Backend {
errors.push(ExtensionError.serialize(e));
}

try {
if (sampleSentenceAudioDetails !== null) {
({sampleSentenceAudioFileName, sampleSentenceText} = await this._injectAnkiNoteSampleSentenceAudio(ankiConnect, timestamp, definitionDetails, sampleSentenceAudioDetails));
}
} catch (e) {
errors.push(ExtensionError.serialize(e));
}

/** @type {import('api').InjectAnkiNoteDictionaryMediaResult[]} */
let dictionaryMedia;
try {
Expand All @@ -2323,6 +2335,8 @@ export class Backend {
clipboardImageFileName,
clipboardText,
audioFileName,
sampleSentenceAudioFileName,
sampleSentenceText,
dictionaryMedia,
errors: errors,
};
Expand Down Expand Up @@ -2368,6 +2382,54 @@ export class Backend {
return await ankiConnect.storeMediaFile(fileName, data);
}

/**
* @param {AnkiConnect} ankiConnect
* @param {number} timestamp
* @param {import('api').InjectAnkiNoteMediaDefinitionDetails} definitionDetails
* @param {import('api').InjectAnkiNoteMediaSampleSentenceAudioDetails} details
* @returns {Promise<{sampleSentenceAudioFileName: ?string, sampleSentenceText: ?string}>}
*/
async _injectAnkiNoteSampleSentenceAudio(ankiConnect, timestamp, definitionDetails, details) {
/** @type {{sampleSentenceAudioFileName: ?string, sampleSentenceText: ?string}} */
const res = {sampleSentenceAudioFileName: null, sampleSentenceText: null};
if (definitionDetails.type !== 'term') { return res; }
const {term, reading} = definitionDetails;
if (term.length === 0 && reading.length === 0) { return res; }

const {sources, preferredAudioIndex, idleTimeout, languageSummary} = details.details;
let audio;
let text;
let data;
let contentType;
try {
({audio, text} = await this._audioDownloader.downloadSampleSentenceAudio(
sources,
preferredAudioIndex,
term,
reading,
idleTimeout,
languageSummary,
details.downloadAudio,
));
if (typeof text === 'string') { res.sampleSentenceText = text; }
if (!details.downloadAudio || audio === null) { return res; }
({data, contentType} = audio);
} catch (e) {
const error = this._getAudioDownloadError(e);
if (error !== null) { throw error; }
// No audio
log.logGenericError(e, 'log');
return res;
}

let extension = contentType !== null ? getFileExtensionFromAudioMediaType(contentType) : null;
if (extension === null) { extension = '.mp3'; }
let fileName = generateAnkiNoteMediaFileName('yomitan_sentence_audio', extension, timestamp);
fileName = fileName.replace(/\]/g, '');
res.sampleSentenceAudioFileName = await ankiConnect.storeMediaFile(fileName, data);
return res;
}

/**
* @param {AnkiConnect} ankiConnect
* @param {number} timestamp
Expand Down
5 changes: 3 additions & 2 deletions ext/js/comm/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,13 +125,14 @@ export class API {
* @param {import('api').ApiParam<'injectAnkiNoteMedia', 'timestamp'>} timestamp
* @param {import('api').ApiParam<'injectAnkiNoteMedia', 'definitionDetails'>} definitionDetails
* @param {import('api').ApiParam<'injectAnkiNoteMedia', 'audioDetails'>} audioDetails
* @param {import('api').ApiParam<'injectAnkiNoteMedia', 'sampleSentenceAudioDetails'>} sampleSentenceAudioDetails
* @param {import('api').ApiParam<'injectAnkiNoteMedia', 'screenshotDetails'>} screenshotDetails
* @param {import('api').ApiParam<'injectAnkiNoteMedia', 'clipboardDetails'>} clipboardDetails
* @param {import('api').ApiParam<'injectAnkiNoteMedia', 'dictionaryMediaDetails'>} dictionaryMediaDetails
* @returns {Promise<import('api').ApiReturn<'injectAnkiNoteMedia'>>}
*/
injectAnkiNoteMedia(timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails, dictionaryMediaDetails) {
return this._invoke('injectAnkiNoteMedia', {timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails, dictionaryMediaDetails});
injectAnkiNoteMedia(timestamp, definitionDetails, audioDetails, sampleSentenceAudioDetails, screenshotDetails, clipboardDetails, dictionaryMediaDetails) {
return this._invoke('injectAnkiNoteMedia', {timestamp, definitionDetails, audioDetails, sampleSentenceAudioDetails, screenshotDetails, clipboardDetails, dictionaryMediaDetails});
}

/**
Expand Down
30 changes: 29 additions & 1 deletion ext/js/data/anki-note-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,8 @@ export class AnkiNoteBuilder {

// Parse requirements
let injectAudio = false;
let injectSampleSentenceAudio = false;
let injectSampleSentenceText = false;
let injectScreenshot = false;
let injectClipboardImage = false;
let injectClipboardText = false;
Expand All @@ -407,6 +409,8 @@ export class AnkiNoteBuilder {
const {type} = requirement;
switch (type) {
case 'audio': injectAudio = true; break;
case 'sampleSentenceAudio': injectSampleSentenceAudio = true; break;
case 'sampleSentenceText': injectSampleSentenceText = true; break;
case 'screenshot': injectScreenshot = true; break;
case 'clipboardImage': injectClipboardImage = true; break;
case 'clipboardText': injectClipboardText = true; break;
Expand All @@ -430,6 +434,8 @@ export class AnkiNoteBuilder {
const dictionaryEntryDetails = this.getDictionaryEntryDetailsForNote(dictionaryEntry);
/** @type {?import('api').InjectAnkiNoteMediaAudioDetails} */
let audioDetails = null;
/** @type {?import('api').InjectAnkiNoteMediaSampleSentenceAudioDetails} */
let sampleSentenceAudioDetails = null;
/** @type {?import('api').InjectAnkiNoteMediaScreenshotDetails} */
let screenshotDetails = null;
/** @type {import('api').InjectAnkiNoteMediaClipboardDetails} */
Expand All @@ -441,6 +447,16 @@ export class AnkiNoteBuilder {
audioDetails = {sources, preferredAudioIndex, idleTimeout, languageSummary, enableDefaultAudioSources};
}
}
if ((injectSampleSentenceAudio || injectSampleSentenceText) && dictionaryEntryDetails.type !== 'kanji') {
const sampleSentenceAudioOptions = mediaOptions.sampleSentenceAudio;
if (typeof sampleSentenceAudioOptions === 'object' && sampleSentenceAudioOptions !== null) {
const {sources, preferredAudioIndex, idleTimeout, languageSummary, enableDefaultAudioSources} = sampleSentenceAudioOptions;
sampleSentenceAudioDetails = {
details: {sources, preferredAudioIndex, idleTimeout, languageSummary, enableDefaultAudioSources},
downloadAudio: injectSampleSentenceAudio,
};
}
}
if (injectScreenshot) {
const screenshotOptions = mediaOptions.screenshot;
if (typeof screenshotOptions === 'object' && screenshotOptions !== null) {
Expand All @@ -465,11 +481,21 @@ export class AnkiNoteBuilder {
timestamp,
dictionaryEntryDetails,
audioDetails,
sampleSentenceAudioDetails,
screenshotDetails,
clipboardDetails,
dictionaryMediaDetails,
);
const {audioFileName, screenshotFileName, clipboardImageFileName, clipboardText, dictionaryMedia: dictionaryMediaArray, errors} = injectedMedia;
const {
audioFileName,
sampleSentenceAudioFileName,
sampleSentenceText,
screenshotFileName,
clipboardImageFileName,
clipboardText,
dictionaryMedia: dictionaryMediaArray,
errors,
} = injectedMedia;
const textFurigana = textFuriganaPromise !== null ? await textFuriganaPromise : [];

// Format results
Expand All @@ -486,6 +512,8 @@ export class AnkiNoteBuilder {
}
const media = {
audio: (typeof audioFileName === 'string' ? {value: audioFileName} : void 0),
sampleSentenceAudio: (typeof sampleSentenceAudioFileName === 'string' ? {value: sampleSentenceAudioFileName} : void 0),
sampleSentenceText: (typeof sampleSentenceText === 'string' ? {value: sampleSentenceText} : void 0),
screenshot: (typeof screenshotFileName === 'string' ? {value: screenshotFileName} : void 0),
clipboardImage: (typeof clipboardImageFileName === 'string' ? {value: clipboardImageFileName} : void 0),
clipboardText: (typeof clipboardText === 'string' ? {value: clipboardText} : void 0),
Expand Down
2 changes: 2 additions & 0 deletions ext/js/data/anki-template-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ export function getStandardFieldMarkers(type, language = 'ja') {
'sentence',
'sentence-furigana',
'sentence-furigana-plain',
'sample-sentence-audio',
'sample-sentence-text',
'tags',
'url',
];
Expand Down
10 changes: 10 additions & 0 deletions ext/js/data/options-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,7 @@ export class OptionsUtil {
this._updateVersion69,
this._updateVersion70,
this._updateVersion71,
this._updateVersion72,
];
/* eslint-enable @typescript-eslint/unbound-method */
if (typeof targetVersion === 'number' && targetVersion < result.length) {
Expand Down Expand Up @@ -1795,6 +1796,15 @@ export class OptionsUtil {
options.global.dataTransmissionConsentShown = false;
}

/**
* - Added sample-sentence-audio and sample-sentence-text handlebars
* @type {import('options-util').UpdateFunction}
*/
async _updateVersion72(options) {
await this._applyAnkiFieldTemplatesPatch(options, '/data/templates/anki-field-templates-upgrade-v72.handlebars');
}


/**
* @param {string} url
* @returns {Promise<chrome.tabs.Tab>}
Expand Down
9 changes: 7 additions & 2 deletions ext/js/display/display-anki.js
Original file line number Diff line number Diff line change
Expand Up @@ -830,6 +830,8 @@ export class DisplayAnki {
const {type} = requirement;
switch (type) {
case 'audio':
case 'sampleSentenceAudio':
case 'sampleSentenceText':
case 'clipboardImage':
break;
default:
Expand Down Expand Up @@ -1008,6 +1010,7 @@ export class DisplayAnki {
const contentOrigin = this._display.getContentOrigin();
const details = this._ankiNoteBuilder.getDictionaryEntryDetailsForNote(dictionaryEntry);
const audioDetails = this._getAnkiNoteMediaAudioDetails(details);
const sampleSentenceAudioDetails = this._getAnkiNoteMediaAudioDetails(details, true);
const optionsContext = this._display.getOptionsContext();
const dictionaryStylesMap = this._ankiNoteBuilder.getDictionaryStylesMap(this._dictionaries);

Expand All @@ -1024,6 +1027,7 @@ export class DisplayAnki {
compactTags: this._compactTags,
mediaOptions: {
audio: audioDetails,
sampleSentenceAudio: sampleSentenceAudioDetails,
screenshot: {
format: this._screenshotFormat,
quality: this._screenshotQuality,
Expand Down Expand Up @@ -1063,11 +1067,12 @@ export class DisplayAnki {

/**
* @param {import('api').InjectAnkiNoteMediaDefinitionDetails} details
* @param {boolean} isSentence
* @returns {?import('anki-note-builder').AudioMediaOptions}
*/
_getAnkiNoteMediaAudioDetails(details) {
_getAnkiNoteMediaAudioDetails(details, isSentence = false) {
if (details.type !== 'term') { return null; }
const {sources, preferredAudioIndex, enableDefaultAudioSources} = this._displayAudio.getAnkiNoteMediaAudioDetails(details.term, details.reading);
const {sources, preferredAudioIndex, enableDefaultAudioSources} = this._displayAudio.getAnkiNoteMediaAudioDetails(details.term, details.reading, isSentence);
const languageSummary = this._display.getLanguageSummary();
return {
sources,
Expand Down
Loading