diff options
author | Alex Yatskov <alex@foosoft.net> | 2020-01-04 11:54:54 -0800 |
---|---|---|
committer | Alex Yatskov <alex@foosoft.net> | 2020-01-04 11:54:54 -0800 |
commit | 2a12036ca305044291f1f4105d6a8d249848b210 (patch) | |
tree | 5cfd4a3d837bf99730233a805d72395c8c61fc07 /ext/bg/js/api.js | |
parent | 9105cb5618cfdd14c2bc37cd22db2b360fe8cd52 (diff) | |
parent | 174b92366577b0a638003b15e2d73fdc91cd62c3 (diff) |
Merge branch 'master' into testing
Diffstat (limited to 'ext/bg/js/api.js')
-rw-r--r-- | ext/bg/js/api.js | 498 |
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; -} |