summaryrefslogtreecommitdiff
path: root/ext/bg/js/api.js
diff options
context:
space:
mode:
Diffstat (limited to 'ext/bg/js/api.js')
-rw-r--r--ext/bg/js/api.js498
1 files changed, 23 insertions, 475 deletions
diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js
index b489b8d2..906aaa30 100644
--- a/ext/bg/js/api.js
+++ b/ext/bg/js/api.js
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016-2017 Alex Yatskov <alex@foosoft.net>
+ * Copyright (C) 2019-2020 Alex Yatskov <alex@foosoft.net>
* Author: Alex Yatskov <alex@foosoft.net>
*
* This program is free software: you can redistribute it and/or modify
@@ -13,491 +13,39 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-function apiOptionsGet(optionsContext) {
- return utilBackend().getOptions(optionsContext);
+function apiTemplateRender(template, data, dynamic) {
+ return _apiInvoke('templateRender', {data, template, dynamic});
}
-async function apiOptionsSet(changedOptions, optionsContext, source) {
- const options = await apiOptionsGet(optionsContext);
-
- function getValuePaths(obj) {
- const valuePaths = [];
- const nodes = [{obj, path: []}];
- while (nodes.length > 0) {
- const node = nodes.pop();
- for (const key of Object.keys(node.obj)) {
- const path = node.path.concat(key);
- const obj = node.obj[key];
- if (obj !== null && typeof obj === 'object') {
- nodes.unshift({obj, path});
- } else {
- valuePaths.push([obj, path]);
- }
- }
- }
- return valuePaths;
- }
-
- function modifyOption(path, value, options) {
- let pivot = options;
- for (const key of path.slice(0, -1)) {
- if (!hasOwn(pivot, key)) {
- return false;
- }
- pivot = pivot[key];
- }
- pivot[path[path.length - 1]] = value;
- return true;
- }
-
- for (const [value, path] of getValuePaths(changedOptions)) {
- modifyOption(path, value, options);
- }
-
- await apiOptionsSave(source);
-}
-
-function apiOptionsGetFull() {
- return utilBackend().getFullOptions();
-}
-
-async function apiOptionsSave(source) {
- const backend = utilBackend();
- const options = await apiOptionsGetFull();
- await optionsSave(options);
- backend.onOptionsUpdated(source);
-}
-
-async function apiTermsFind(text, details, optionsContext) {
- const options = await apiOptionsGet(optionsContext);
- const [definitions, length] = await utilBackend().translator.findTerms(text, details, options);
- definitions.splice(options.general.maxResults);
- return {length, definitions};
+function apiAudioGetUrl(definition, source, optionsContext) {
+ return _apiInvoke('audioGetUrl', {definition, source, optionsContext});
}
-async function apiTextParse(text, optionsContext) {
- const options = await apiOptionsGet(optionsContext);
- const translator = utilBackend().translator;
-
- const results = [];
- while (text.length > 0) {
- const term = [];
- const [definitions, sourceLength] = await translator.findTermsInternal(
- text.slice(0, options.scanning.length),
- dictEnabledSet(options),
- options.scanning.alphanumeric,
- {}
- );
- if (definitions.length > 0) {
- dictTermsSort(definitions);
- const {expression, reading} = definitions[0];
- const source = text.slice(0, sourceLength);
- for (const {text, furigana} of jpDistributeFuriganaInflected(expression, reading, source)) {
- const reading = jpConvertReading(text, furigana, options.parsing.readingMode);
- term.push({text, reading});
- }
- text = text.slice(source.length);
- } else {
- const reading = jpConvertReading(text[0], null, options.parsing.readingMode);
- term.push({text: text[0], reading});
- text = text.slice(1);
- }
- results.push(term);
- }
- return results;
-}
-
-async function apiTextParseMecab(text, optionsContext) {
- const options = await apiOptionsGet(optionsContext);
- const mecab = utilBackend().mecab;
-
- const results = {};
- const rawResults = await mecab.parseText(text);
- for (const mecabName in rawResults) {
- const result = [];
- for (const parsedLine of rawResults[mecabName]) {
- for (const {expression, reading, source} of parsedLine) {
- const term = [];
- if (expression !== null && reading !== null) {
- for (const {text, furigana} of jpDistributeFuriganaInflected(
- expression,
- jpKatakanaToHiragana(reading),
- source
- )) {
- const reading = jpConvertReading(text, furigana, options.parsing.readingMode);
- term.push({text, reading});
+function _apiInvoke(action, params={}) {
+ const data = {action, params};
+ return new Promise((resolve, reject) => {
+ try {
+ const callback = (response) => {
+ if (response !== null && typeof response === 'object') {
+ if (typeof response.error !== 'undefined') {
+ reject(jsonToError(response.error));
+ } else {
+ resolve(response.result);
}
} else {
- const reading = jpConvertReading(source, null, options.parsing.readingMode);
- term.push({text: source, reading});
- }
- result.push(term);
- }
- result.push([{text: '\n'}]);
- }
- results[mecabName] = result;
- }
- return results;
-}
-
-async function apiKanjiFind(text, optionsContext) {
- const options = await apiOptionsGet(optionsContext);
- const definitions = await utilBackend().translator.findKanji(text, options);
- definitions.splice(options.general.maxResults);
- return definitions;
-}
-
-async function apiDefinitionAdd(definition, mode, context, optionsContext) {
- const options = await apiOptionsGet(optionsContext);
-
- if (mode !== 'kanji') {
- await audioInject(
- definition,
- options.anki.terms.fields,
- options.audio.sources,
- optionsContext
- );
- }
-
- if (context && context.screenshot) {
- await apiInjectScreenshot(
- definition,
- options.anki.terms.fields,
- context.screenshot
- );
- }
-
- const note = await dictNoteFormat(definition, mode, options);
- return utilBackend().anki.addNote(note);
-}
-
-async function apiDefinitionsAddable(definitions, modes, optionsContext) {
- const options = await apiOptionsGet(optionsContext);
- const states = [];
-
- try {
- const notes = [];
- for (const definition of definitions) {
- for (const mode of modes) {
- const note = await dictNoteFormat(definition, mode, options);
- notes.push(note);
- }
- }
-
- const cannotAdd = [];
- const anki = utilBackend().anki;
- const results = await anki.canAddNotes(notes);
- for (let resultBase = 0; resultBase < results.length; resultBase += modes.length) {
- const state = {};
- for (let modeOffset = 0; modeOffset < modes.length; ++modeOffset) {
- const index = resultBase + modeOffset;
- const result = results[index];
- const info = {canAdd: result};
- state[modes[modeOffset]] = info;
- if (!result) {
- cannotAdd.push([notes[index], info]);
- }
- }
-
- states.push(state);
- }
-
- if (cannotAdd.length > 0) {
- const noteIdsArray = await anki.findNoteIds(cannotAdd.map((e) => e[0]));
- for (let i = 0, ii = Math.min(cannotAdd.length, noteIdsArray.length); i < ii; ++i) {
- const noteIds = noteIdsArray[i];
- if (noteIds.length > 0) {
- cannotAdd[i][1].noteId = noteIds[0];
+ const message = response === null ? 'Unexpected null response' : `Unexpected response of type ${typeof response}`;
+ reject(new Error(`${message} (${JSON.stringify(data)})`));
}
- }
- }
- } catch (e) {
- // NOP
- }
-
- return states;
-}
-
-async function apiNoteView(noteId) {
- return utilBackend().anki.guiBrowse(`nid:${noteId}`);
-}
-
-async function apiTemplateRender(template, data, dynamic) {
- if (dynamic) {
- return handlebarsRenderDynamic(template, data);
- } else {
- return handlebarsRenderStatic(template, data);
- }
-}
-
-async function apiCommandExec(command, params) {
- const handlers = apiCommandExec.handlers;
- if (hasOwn(handlers, command)) {
- const handler = handlers[command];
- handler(params);
- }
-}
-apiCommandExec.handlers = {
- search: async (params) => {
- const url = chrome.runtime.getURL('/bg/search.html');
- if (!(params && params.newTab)) {
- try {
- const tab = await apiFindTab(1000, (url2) => (
- url2 !== null &&
- url2.startsWith(url) &&
- (url2.length === url.length || url2[url.length] === '?' || url2[url.length] === '#')
- ));
- if (tab !== null) {
- await apiFocusTab(tab);
- return;
- }
- } catch (e) {
- // NOP
- }
- }
- chrome.tabs.create({url});
- },
-
- help: () => {
- chrome.tabs.create({url: 'https://foosoft.net/projects/yomichan/'});
- },
-
- options: (params) => {
- if (!(params && params.newTab)) {
- chrome.runtime.openOptionsPage();
- } else {
- const manifest = chrome.runtime.getManifest();
- const url = chrome.runtime.getURL(manifest.options_ui.page);
- chrome.tabs.create({url});
- }
- },
-
- toggle: async () => {
- const optionsContext = {
- depth: 0,
- url: window.location.href
- };
- const options = await apiOptionsGet(optionsContext);
- options.general.enable = !options.general.enable;
- await apiOptionsSave('popup');
- }
-};
-
-async function apiAudioGetUrl(definition, source, optionsContext) {
- return audioGetUrl(definition, source, optionsContext);
-}
-
-async function apiInjectScreenshot(definition, fields, screenshot) {
- let usesScreenshot = false;
- for (const name in fields) {
- if (fields[name].includes('{screenshot}')) {
- usesScreenshot = true;
- break;
- }
- }
-
- if (!usesScreenshot) {
- return;
- }
-
- const dateToString = (date) => {
- const year = date.getUTCFullYear();
- const month = date.getUTCMonth().toString().padStart(2, '0');
- const day = date.getUTCDate().toString().padStart(2, '0');
- const hours = date.getUTCHours().toString().padStart(2, '0');
- const minutes = date.getUTCMinutes().toString().padStart(2, '0');
- const seconds = date.getUTCSeconds().toString().padStart(2, '0');
- return `${year}-${month}-${day}-${hours}-${minutes}-${seconds}`;
- };
-
- const now = new Date(Date.now());
- const filename = `yomichan_browser_screenshot_${definition.reading}_${dateToString(now)}.${screenshot.format}`;
- const data = screenshot.dataUrl.replace(/^data:[\w\W]*?,/, '');
-
- try {
- await utilBackend().anki.storeMediaFile(filename, data);
- } catch (e) {
- return;
- }
-
- definition.screenshotFileName = filename;
-}
-
-function apiScreenshotGet(options, sender) {
- if (!(sender && sender.tab)) {
- return Promise.resolve();
- }
-
- const windowId = sender.tab.windowId;
- return new Promise((resolve) => {
- chrome.tabs.captureVisibleTab(windowId, options, (dataUrl) => resolve(dataUrl));
- });
-}
-
-function apiForward(action, params, sender) {
- if (!(sender && sender.tab)) {
- return Promise.resolve();
- }
-
- const tabId = sender.tab.id;
- return new Promise((resolve) => {
- chrome.tabs.sendMessage(tabId, {action, params}, (response) => resolve(response));
- });
-}
-
-function apiFrameInformationGet(sender) {
- const frameId = sender.frameId;
- return Promise.resolve({frameId});
-}
-
-function apiInjectStylesheet(css, sender) {
- if (!sender.tab) {
- return Promise.reject(new Error('Invalid tab'));
- }
-
- const tabId = sender.tab.id;
- const frameId = sender.frameId;
- const details = {
- code: css,
- runAt: 'document_start',
- cssOrigin: 'user',
- allFrames: false
- };
- if (typeof frameId === 'number') {
- details.frameId = frameId;
- }
-
- return new Promise((resolve, reject) => {
- chrome.tabs.insertCSS(tabId, details, () => {
- const e = chrome.runtime.lastError;
- if (e) {
- reject(new Error(e.message));
- } else {
- resolve();
- }
- });
- });
-}
-
-async function apiGetEnvironmentInfo() {
- const browser = await apiGetBrowser();
- const platform = await new Promise((resolve) => chrome.runtime.getPlatformInfo(resolve));
- return {
- browser,
- platform: {
- os: platform.os
- }
- };
-}
-
-async function apiGetBrowser() {
- if (EXTENSION_IS_BROWSER_EDGE) {
- return 'edge';
- }
- if (typeof browser !== 'undefined') {
- try {
- const info = await browser.runtime.getBrowserInfo();
- if (info.name === 'Fennec') {
- return 'firefox-mobile';
- }
+ };
+ const backend = window.yomichanBackend;
+ backend.onMessage({action, params}, null, callback);
} catch (e) {
- // NOP
+ reject(e);
+ yomichan.triggerOrphaned(e);
}
- return 'firefox';
- } else {
- return 'chrome';
- }
-}
-
-function apiGetTabUrl(tab) {
- return new Promise((resolve) => {
- chrome.tabs.sendMessage(tab.id, {action: 'getUrl'}, {frameId: 0}, (response) => {
- let url = null;
- if (!chrome.runtime.lastError) {
- url = (response !== null && typeof response === 'object' && !Array.isArray(response) ? response.url : null);
- if (url !== null && typeof url !== 'string') {
- url = null;
- }
- }
- resolve({tab, url});
- });
});
}
-
-async function apiFindTab(timeout, checkUrl) {
- // This function works around the need to have the "tabs" permission to access tab.url.
- const tabs = await new Promise((resolve) => chrome.tabs.query({}, resolve));
- let matchPromiseResolve = null;
- const matchPromise = new Promise((resolve) => { matchPromiseResolve = resolve; });
-
- const checkTabUrl = ({tab, url}) => {
- if (checkUrl(url, tab)) {
- matchPromiseResolve(tab);
- }
- };
-
- const promises = [];
- for (const tab of tabs) {
- const promise = apiGetTabUrl(tab);
- promise.then(checkTabUrl);
- promises.push(promise);
- }
-
- const racePromises = [
- matchPromise,
- Promise.all(promises).then(() => null)
- ];
- if (typeof timeout === 'number') {
- racePromises.push(new Promise((resolve) => setTimeout(() => resolve(null), timeout)));
- }
-
- return await Promise.race(racePromises);
-}
-
-async function apiFocusTab(tab) {
- await new Promise((resolve, reject) => {
- chrome.tabs.update(tab.id, {active: true}, () => {
- const e = chrome.runtime.lastError;
- if (e) { reject(e); }
- else { resolve(); }
- });
- });
-
- if (!(typeof chrome.windows === 'object' && chrome.windows !== null)) {
- // Windows not supported (e.g. on Firefox mobile)
- return;
- }
-
- try {
- const tabWindow = await new Promise((resolve) => {
- chrome.windows.get(tab.windowId, {}, (tabWindow) => {
- const e = chrome.runtime.lastError;
- if (e) { reject(e); }
- else { resolve(tabWindow); }
- });
- });
- if (!tabWindow.focused) {
- await new Promise((resolve, reject) => {
- chrome.windows.update(tab.windowId, {focused: true}, () => {
- const e = chrome.runtime.lastError;
- if (e) { reject(e); }
- else { resolve(); }
- });
- });
- }
- } catch (e) {
- // Edge throws exception for no reason here.
- }
-}
-
-async function apiClipboardGet() {
- const clipboardPasteTarget = utilBackend().clipboardPasteTarget;
- clipboardPasteTarget.innerText = '';
- clipboardPasteTarget.focus();
- document.execCommand('paste');
- return clipboardPasteTarget.innerText;
-}