diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..81d6847 --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +TRACKING_ID=1234 + +build: deps + TRACKING_ID=$(TRACKING_ID) yarn build + +deps: package.json + yarn install diff --git a/background.js b/background.js index 4d9c033..2619508 100644 --- a/background.js +++ b/background.js @@ -5,6 +5,8 @@ import Options from './lib/options' import trackEvent from './lib/tracking' import { localStorage } from './lib/storage' import { generateUrls, parseResponse } from './lib/apiClient.mjs' +import { recordTranslation, getTranslationHistory } from './lib/history' +import options from './lib/options' const blockTimeoutMs = 30000 * 60 const browserAction = chrome[process.env.MANIFEST_V3 === 'true' ? 'action' : 'browserAction'] @@ -125,6 +127,10 @@ async function on_translation_response(data, word, tl, last_translation, isRever Object.assign(last_translation, translation) await localStorage.set('last_translation', last_translation) + if (await Options.store_translations()) { + recordTranslation(word, sl, tl, isReverseTranslate, data) + } + console.log('response: ', translation) return translation } @@ -148,7 +154,7 @@ async function detectLanguage(request) { async function contentScriptListener(request) { const except_urls = await Options.except_urls() const last_translation = await localStorage.get('last_translation') || {} - + console.debug('contentScriptListener Invoked', request) switch (request.handler) { case 'get_last_tat_sl_tl': console.log('get_last_tat_sl_tl') @@ -205,6 +211,22 @@ async function contentScriptListener(request) { await Options.disable_everywhere(0) } break + case 'exportTranslationHistory': + if (await options.store_translations()) { + let records = await getTranslationHistory() + let jsonRecords = JSON.stringify(records) + const blob = new Blob([jsonRecords], { type: 'application/json' }) + const url = URL.createObjectURL(blob) + + // Use the downloads API to save the file + + browser.downloads.download({ + url: url, + filename: 'translation_history.json', + saveAs: true // Prompts the user with the save dialog + }) + } + break default: console.error('Unknown request', JSON.stringify(request, null, 2)) return {} diff --git a/contentscript.js b/contentscript.js index 75b8fa4..bddcdf3 100644 --- a/contentscript.js +++ b/contentscript.js @@ -1,7 +1,6 @@ import $ from 'jquery' import { renderError, - modifierKeys, escape_html, formatTranslation } from './lib/transover_utils' @@ -18,6 +17,7 @@ if (process.env.MANIFEST_V3 === 'true') { let options let disable_on_this_page let disable_everywhere +let store_translations function copyToClipboard(text) { const input = document.createElement('input') @@ -174,9 +174,11 @@ async function loadOptions() { disable_on_this_page = ignoreThisPage(options) disable_everywhere = options.disable_everywhere + store_translations = options.store_translations chrome.runtime.sendMessage({ handler: 'setIcon', - disabled: disable_on_this_page || disable_everywhere + disabled: disable_on_this_page || disable_everywhere, + store_translations: store_translations }) } @@ -200,7 +202,7 @@ function processEvent(e) { return res } - function getExactTextNode(nodes, e) { + function getExactTextNode(_, e) { $(text_nodes).wrap('') let hit_text_node = document.elementFromPoint(e.clientX, e.clientY) @@ -388,7 +390,7 @@ function processEvent(e) { } } -function withOptionsSatisfied(e, do_stuff) { +function withOptionsSatisfied(_, do_stuff) { if (!options) return //respect 'translate only when alt pressed' option @@ -464,7 +466,7 @@ function speak({ text, lang }) { } $(document).keydown(e => { - if (e.keyCode === 27) { + if (e.key === 'Escape') { audio.pause() audio.removeAttribute('src') audio.load() @@ -481,7 +483,7 @@ function speak({ text, lang }) { $(document).keydown(function(e) { if (!options) return - if (modifierKeys[e.keyCode] == options.popup_show_trigger) { + if (e.key == options.popup_show_trigger) { show_popup_key_pressed = true const selection = window.getSelection().toString() @@ -505,7 +507,7 @@ $(document).keydown(function(e) { } // text-to-speech on ctrl press - if (!e.originalEvent.repeat && modifierKeys[e.keyCode] == options.tts_key && options.tts && $('transover-popup').length > 0) { + if (!e.originalEvent.repeat && e.key == options.tts_key && options.tts && $('transover-popup').length > 0) { chrome.runtime.sendMessage({ handler: 'trackEvent', event: { @@ -538,11 +540,11 @@ $(document).keydown(function(e) { } // Hide tat popup on escape - if (e.keyCode == 27) { + if (e.key == 'Escape') { removePopup('transover-type-and-translate-popup') } }).keyup(function(e) { - if (options && modifierKeys[e.keyCode] == options.popup_show_trigger) { + if (options && e.key == options.popup_show_trigger) { show_popup_key_pressed = false } }) @@ -666,6 +668,12 @@ window.addEventListener('message', function(e) { if (e.source != window) return + if (e.data.type == 'exportTranslationHistory') { + chrome.runtime.sendMessage({ + handler: 'exportTranslationHistory' + }) + } + if (e.data.type == 'transoverTranslate') { chrome.runtime.sendMessage({handler: 'translate', word: e.data.text, sl: e.data.sl, tl: e.data.tl}, function(response) { debug('tat response: ', response) diff --git a/lib/history.js b/lib/history.js new file mode 100644 index 0000000..0f16c7c --- /dev/null +++ b/lib/history.js @@ -0,0 +1,42 @@ +/* eslint-disable no-console */ +import Dexie from 'dexie' + +const db = new Dexie('transover_db') + +db.version(1).stores({ + translations: '++id,word,source_language,target_language,reverse_translation,is_sentence,gt_resp,created_at' +}) + +function hasWhiteSpace(s) { + return /\s/g.test(s) +} + +function isSentence(word) { + if (hasWhiteSpace(word) || word.length > 20) { + return true + } + return false +} + +export async function recordTranslation(word, sl, tl, isReverseTranslate, gtResponse) { + console.debug('recordTranslation:', arguments) + let id = await db.translations.add({ + word: word, + source_language: sl, + target_language: tl, + reverse_translation: isReverseTranslate, + is_sentence: isSentence(word), + gt_resp: gtResponse, + created_at: new Date().toISOString() + }) + + return id +} + +export async function getTranslations() { +} + + +export async function getTranslationHistory() { + return await db.translations.toArray() +} diff --git a/lib/options.js b/lib/options.js index 107244b..ad98f87 100644 --- a/lib/options.js +++ b/lib/options.js @@ -125,4 +125,10 @@ export default { } return parseInt( await localStorage.get('disable_everywhere') ) || 0 }, + store_translations: async function (arg) { + if (arg != undefined) { + return await localStorage.set('store_translations', arg) + } + return parseInt(await localStorage.get('store_translations')) || 1 + }, } diff --git a/lib/options_script.js b/lib/options_script.js index 0db71c5..6d308e3 100644 --- a/lib/options_script.js +++ b/lib/options_script.js @@ -43,6 +43,7 @@ async function save_options() { await Options.do_not_show_oops($('#do_not_show_oops:checked').val() ? 1 : 0) await Options.popup_show_trigger($('#word_key_only_key').val()) await Options.show_from_lang($('#show_from_lang:checked').val() ? 1 : 0) + await Options.store_translations($('#record:checked').val() ? 1 : 0) $('.flash').fadeIn().delay(3000).fadeOut() } @@ -154,7 +155,7 @@ async function fill_reverse_lang() { async function populate_popup_show_trigger() { const saved_popup_show_trigger = await Options.popup_show_trigger() - ;[...new Set(Object.values(modifierKeys))].forEach(function(key) { + modifierKeys.forEach(function(key) { $('#word_key_only_key, #selection_key_only_key').each(function() { $(this).append($('