2016-04-04 03:05:22 +00:00
/ *
2017-08-15 04:43:09 +00:00
* Copyright ( C ) 2016 - 2017 Alex Yatskov < alex @ foosoft . net >
2016-04-04 03:05:22 +00:00
* Author : Alex Yatskov < alex @ foosoft . net >
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* 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/>.
* /
2019-09-08 17:27:32 +00:00
async function getOptionsArray ( ) {
const optionsFull = await apiOptionsGetFull ( ) ;
return optionsFull . profiles . map ( profile => profile . options ) ;
}
2019-09-07 19:54:00 +00:00
async function formRead ( options ) {
2019-09-11 00:49:17 +00:00
options . general . enable = $ ( '#enable' ) . prop ( 'checked' ) ;
2019-09-07 19:54:00 +00:00
options . general . showGuide = $ ( '#show-usage-guide' ) . prop ( 'checked' ) ;
options . general . compactTags = $ ( '#compact-tags' ) . prop ( 'checked' ) ;
options . general . compactGlossaries = $ ( '#compact-glossaries' ) . prop ( 'checked' ) ;
options . general . autoPlayAudio = $ ( '#auto-play-audio' ) . prop ( 'checked' ) ;
options . general . resultOutputMode = $ ( '#result-output-mode' ) . val ( ) ;
options . general . audioSource = $ ( '#audio-playback-source' ) . val ( ) ;
options . general . audioVolume = parseFloat ( $ ( '#audio-playback-volume' ) . val ( ) ) ;
options . general . debugInfo = $ ( '#show-debug-info' ) . prop ( 'checked' ) ;
options . general . showAdvanced = $ ( '#show-advanced-options' ) . prop ( 'checked' ) ;
options . general . maxResults = parseInt ( $ ( '#max-displayed-results' ) . val ( ) , 10 ) ;
options . general . popupDisplayMode = $ ( '#popup-display-mode' ) . val ( ) ;
options . general . popupHorizontalTextPosition = $ ( '#popup-horizontal-text-position' ) . val ( ) ;
options . general . popupVerticalTextPosition = $ ( '#popup-vertical-text-position' ) . val ( ) ;
options . general . popupWidth = parseInt ( $ ( '#popup-width' ) . val ( ) , 10 ) ;
options . general . popupHeight = parseInt ( $ ( '#popup-height' ) . val ( ) , 10 ) ;
options . general . popupHorizontalOffset = parseInt ( $ ( '#popup-horizontal-offset' ) . val ( ) , 0 ) ;
options . general . popupVerticalOffset = parseInt ( $ ( '#popup-vertical-offset' ) . val ( ) , 10 ) ;
options . general . popupHorizontalOffset2 = parseInt ( $ ( '#popup-horizontal-offset2' ) . val ( ) , 0 ) ;
options . general . popupVerticalOffset2 = parseInt ( $ ( '#popup-vertical-offset2' ) . val ( ) , 10 ) ;
options . general . customPopupCss = $ ( '#custom-popup-css' ) . val ( ) ;
options . scanning . middleMouse = $ ( '#middle-mouse-button-scan' ) . prop ( 'checked' ) ;
options . scanning . touchInputEnabled = $ ( '#touch-input-enabled' ) . prop ( 'checked' ) ;
options . scanning . selectText = $ ( '#select-matched-text' ) . prop ( 'checked' ) ;
options . scanning . alphanumeric = $ ( '#search-alphanumeric' ) . prop ( 'checked' ) ;
options . scanning . autoHideResults = $ ( '#auto-hide-results' ) . prop ( 'checked' ) ;
options . scanning . deepDomScan = $ ( '#deep-dom-scan' ) . prop ( 'checked' ) ;
2019-09-27 04:33:33 +00:00
options . scanning . enablePopupSearch = $ ( '#enable-search-within-first-popup' ) . prop ( 'checked' ) ;
2019-09-07 19:54:00 +00:00
options . scanning . enableOnPopupExpressions = $ ( '#enable-scanning-of-popup-expressions' ) . prop ( 'checked' ) ;
options . scanning . enableOnSearchPage = $ ( '#enable-scanning-on-search-page' ) . prop ( 'checked' ) ;
options . scanning . delay = parseInt ( $ ( '#scan-delay' ) . val ( ) , 10 ) ;
options . scanning . length = parseInt ( $ ( '#scan-length' ) . val ( ) , 10 ) ;
options . scanning . modifier = $ ( '#scan-modifier-key' ) . val ( ) ;
options . scanning . popupNestingMaxDepth = parseInt ( $ ( '#popup-nesting-max-depth' ) . val ( ) , 10 ) ;
const optionsAnkiEnableOld = options . anki . enable ;
options . anki . enable = $ ( '#anki-enable' ) . prop ( 'checked' ) ;
2019-09-14 20:21:41 +00:00
options . anki . tags = utilBackgroundIsolate ( $ ( '#card-tags' ) . val ( ) . split ( /[,; ]+/ ) ) ;
2019-09-07 19:54:00 +00:00
options . anki . sentenceExt = parseInt ( $ ( '#sentence-detection-extent' ) . val ( ) , 10 ) ;
options . anki . server = $ ( '#interface-server' ) . val ( ) ;
options . anki . screenshot . format = $ ( '#screenshot-format' ) . val ( ) ;
options . anki . screenshot . quality = parseInt ( $ ( '#screenshot-quality' ) . val ( ) , 10 ) ;
options . anki . fieldTemplates = $ ( '#field-templates' ) . val ( ) ;
if ( optionsAnkiEnableOld && ! ankiErrorShown ( ) ) {
options . anki . terms . deck = $ ( '#anki-terms-deck' ) . val ( ) ;
options . anki . terms . model = $ ( '#anki-terms-model' ) . val ( ) ;
2019-09-14 20:21:41 +00:00
options . anki . terms . fields = utilBackgroundIsolate ( ankiFieldsToDict ( $ ( '#terms .anki-field-value' ) ) ) ;
2019-09-07 19:54:00 +00:00
options . anki . kanji . deck = $ ( '#anki-kanji-deck' ) . val ( ) ;
options . anki . kanji . model = $ ( '#anki-kanji-model' ) . val ( ) ;
2019-09-14 20:21:41 +00:00
options . anki . kanji . fields = utilBackgroundIsolate ( ankiFieldsToDict ( $ ( '#kanji .anki-field-value' ) ) ) ;
2017-07-17 02:29:43 +00:00
}
2017-03-03 05:01:49 +00:00
2019-09-07 19:54:00 +00:00
options . general . mainDictionary = $ ( '#dict-main' ) . val ( ) ;
2017-07-17 02:29:43 +00:00
$ ( '.dict-group' ) . each ( ( index , element ) => {
const dictionary = $ ( element ) ;
2019-09-14 20:21:41 +00:00
options . dictionaries [ dictionary . data ( 'title' ) ] = utilBackgroundIsolate ( {
2017-10-29 19:20:56 +00:00
priority : parseInt ( dictionary . find ( '.dict-priority' ) . val ( ) , 10 ) ,
enabled : dictionary . find ( '.dict-enabled' ) . prop ( 'checked' ) ,
allowSecondarySearches : dictionary . find ( '.dict-allow-secondary-searches' ) . prop ( 'checked' )
2019-09-14 20:21:41 +00:00
} ) ;
2017-03-03 05:01:49 +00:00
} ) ;
}
2019-09-08 15:23:04 +00:00
async function formWrite ( options ) {
2019-09-11 00:49:17 +00:00
$ ( '#enable' ) . prop ( 'checked' , options . general . enable ) ;
2019-09-08 15:23:04 +00:00
$ ( '#show-usage-guide' ) . prop ( 'checked' , options . general . showGuide ) ;
$ ( '#compact-tags' ) . prop ( 'checked' , options . general . compactTags ) ;
$ ( '#compact-glossaries' ) . prop ( 'checked' , options . general . compactGlossaries ) ;
$ ( '#auto-play-audio' ) . prop ( 'checked' , options . general . autoPlayAudio ) ;
$ ( '#result-output-mode' ) . val ( options . general . resultOutputMode ) ;
$ ( '#audio-playback-source' ) . val ( options . general . audioSource ) ;
$ ( '#audio-playback-volume' ) . val ( options . general . audioVolume ) ;
$ ( '#show-debug-info' ) . prop ( 'checked' , options . general . debugInfo ) ;
$ ( '#show-advanced-options' ) . prop ( 'checked' , options . general . showAdvanced ) ;
$ ( '#max-displayed-results' ) . val ( options . general . maxResults ) ;
$ ( '#popup-display-mode' ) . val ( options . general . popupDisplayMode ) ;
$ ( '#popup-horizontal-text-position' ) . val ( options . general . popupHorizontalTextPosition ) ;
$ ( '#popup-vertical-text-position' ) . val ( options . general . popupVerticalTextPosition ) ;
$ ( '#popup-width' ) . val ( options . general . popupWidth ) ;
$ ( '#popup-height' ) . val ( options . general . popupHeight ) ;
$ ( '#popup-horizontal-offset' ) . val ( options . general . popupHorizontalOffset ) ;
$ ( '#popup-vertical-offset' ) . val ( options . general . popupVerticalOffset ) ;
$ ( '#popup-horizontal-offset2' ) . val ( options . general . popupHorizontalOffset2 ) ;
$ ( '#popup-vertical-offset2' ) . val ( options . general . popupVerticalOffset2 ) ;
$ ( '#custom-popup-css' ) . val ( options . general . customPopupCss ) ;
$ ( '#middle-mouse-button-scan' ) . prop ( 'checked' , options . scanning . middleMouse ) ;
$ ( '#touch-input-enabled' ) . prop ( 'checked' , options . scanning . touchInputEnabled ) ;
$ ( '#select-matched-text' ) . prop ( 'checked' , options . scanning . selectText ) ;
$ ( '#search-alphanumeric' ) . prop ( 'checked' , options . scanning . alphanumeric ) ;
$ ( '#auto-hide-results' ) . prop ( 'checked' , options . scanning . autoHideResults ) ;
$ ( '#deep-dom-scan' ) . prop ( 'checked' , options . scanning . deepDomScan ) ;
2019-09-27 04:33:33 +00:00
$ ( '#enable-search-within-first-popup' ) . prop ( 'checked' , options . scanning . enablePopupSearch ) ;
2019-09-08 15:23:04 +00:00
$ ( '#enable-scanning-of-popup-expressions' ) . prop ( 'checked' , options . scanning . enableOnPopupExpressions ) ;
$ ( '#enable-scanning-on-search-page' ) . prop ( 'checked' , options . scanning . enableOnSearchPage ) ;
$ ( '#scan-delay' ) . val ( options . scanning . delay ) ;
$ ( '#scan-length' ) . val ( options . scanning . length ) ;
$ ( '#scan-modifier-key' ) . val ( options . scanning . modifier ) ;
$ ( '#popup-nesting-max-depth' ) . val ( options . scanning . popupNestingMaxDepth ) ;
$ ( '#anki-enable' ) . prop ( 'checked' , options . anki . enable ) ;
$ ( '#card-tags' ) . val ( options . anki . tags . join ( ' ' ) ) ;
$ ( '#sentence-detection-extent' ) . val ( options . anki . sentenceExt ) ;
$ ( '#interface-server' ) . val ( options . anki . server ) ;
$ ( '#screenshot-format' ) . val ( options . anki . screenshot . format ) ;
$ ( '#screenshot-quality' ) . val ( options . anki . screenshot . quality ) ;
$ ( '#field-templates' ) . val ( options . anki . fieldTemplates ) ;
try {
await dictionaryGroupsPopulate ( options ) ;
await formMainDictionaryOptionsPopulate ( options ) ;
} catch ( e ) {
dictionaryErrorsShow ( [ e ] ) ;
}
try {
await ankiDeckAndModelPopulate ( options ) ;
} catch ( e ) {
ankiErrorShow ( e ) ;
}
formUpdateVisibility ( options ) ;
}
2019-09-08 16:16:12 +00:00
function formSetupEventListeners ( ) {
$ ( '#dict-purge-link' ) . click ( utilAsync ( onDictionaryPurge ) ) ;
$ ( '#dict-file' ) . change ( utilAsync ( onDictionaryImport ) ) ;
$ ( '#field-templates-reset' ) . click ( utilAsync ( onAnkiFieldTemplatesReset ) ) ;
$ ( 'input, select, textarea' ) . not ( '.anki-model' ) . not ( '.profile-form *' ) . change ( utilAsync ( onFormOptionsChanged ) ) ;
$ ( '.anki-model' ) . change ( utilAsync ( onAnkiModelChanged ) ) ;
}
2017-07-17 02:55:33 +00:00
function formUpdateVisibility ( options ) {
2017-03-03 05:01:49 +00:00
const general = $ ( '#anki-general' ) ;
if ( options . anki . enable ) {
general . show ( ) ;
} else {
general . hide ( ) ;
}
const advanced = $ ( '.options-advanced' ) ;
if ( options . general . showAdvanced ) {
advanced . show ( ) ;
} else {
advanced . hide ( ) ;
}
2017-05-25 02:13:56 +00:00
2017-10-29 17:18:15 +00:00
const mainGroup = $ ( '#dict-main-group' ) ;
2017-10-24 13:23:13 +00:00
if ( options . general . resultOutputMode === 'merge' ) {
2017-10-29 17:18:15 +00:00
mainGroup . show ( ) ;
2017-10-24 13:23:13 +00:00
} else {
2017-10-29 17:18:15 +00:00
mainGroup . hide ( ) ;
2017-10-24 13:23:13 +00:00
}
2017-05-25 02:13:56 +00:00
const debug = $ ( '#debug' ) ;
if ( options . general . debugInfo ) {
2017-09-13 03:20:03 +00:00
const temp = utilIsolate ( options ) ;
temp . anki . fieldTemplates = '...' ;
const text = JSON . stringify ( temp , null , 4 ) ;
2017-05-25 02:13:56 +00:00
debug . html ( handlebarsEscape ( text ) ) ;
debug . show ( ) ;
} else {
debug . hide ( ) ;
}
2017-03-03 05:01:49 +00:00
}
2017-10-24 13:23:13 +00:00
async function formMainDictionaryOptionsPopulate ( options ) {
2017-10-29 17:18:15 +00:00
const select = $ ( '#dict-main' ) . empty ( ) ;
select . append ( $ ( '<option class="text-muted" value="">Not selected</option>' ) ) ;
2017-10-24 13:23:13 +00:00
2017-10-29 17:18:15 +00:00
let mainDictionary = '' ;
2019-08-22 23:44:31 +00:00
for ( const dictRow of toIterable ( await utilDatabaseSummarize ( ) ) ) {
2017-10-29 17:59:50 +00:00
if ( dictRow . sequenced ) {
2017-10-29 17:42:39 +00:00
select . append ( $ ( ` <option value=" ${ dictRow . title } "> ${ dictRow . title } </option> ` ) ) ;
if ( dictRow . title === options . general . mainDictionary ) {
mainDictionary = dictRow . title ;
}
2017-10-24 13:23:13 +00:00
}
}
2017-10-29 17:18:15 +00:00
select . val ( mainDictionary ) ;
2017-10-24 13:23:13 +00:00
}
2017-08-15 04:43:09 +00:00
async function onFormOptionsChanged ( e ) {
2017-10-29 17:18:15 +00:00
if ( ! e . originalEvent && ! e . isTrigger ) {
return ;
}
2017-03-05 23:54:03 +00:00
2019-09-07 19:59:10 +00:00
const optionsContext = getOptionsContext ( ) ;
const options = await apiOptionsGet ( optionsContext ) ;
2019-09-07 19:54:00 +00:00
const optionsAnkiEnableOld = options . anki . enable ;
const optionsAnkiServerOld = options . anki . server ;
await formRead ( options ) ;
2019-09-11 00:46:30 +00:00
await settingsSaveOptions ( ) ;
2019-09-07 19:54:00 +00:00
formUpdateVisibility ( options ) ;
2017-03-05 23:54:03 +00:00
2017-10-29 17:18:15 +00:00
try {
2017-07-17 02:29:43 +00:00
const ankiUpdated =
2019-09-07 19:54:00 +00:00
options . anki . enable !== optionsAnkiEnableOld ||
options . anki . server !== optionsAnkiServerOld ;
2017-07-17 02:29:43 +00:00
if ( ankiUpdated ) {
2017-08-16 03:58:50 +00:00
ankiSpinnerShow ( true ) ;
2019-09-07 19:54:00 +00:00
await ankiDeckAndModelPopulate ( options ) ;
2017-08-16 03:58:50 +00:00
ankiErrorShow ( ) ;
2017-07-17 02:29:43 +00:00
}
} catch ( e ) {
ankiErrorShow ( e ) ;
} finally {
ankiSpinnerShow ( false ) ;
}
2017-08-15 04:43:09 +00:00
}
2017-07-17 02:29:43 +00:00
2017-08-15 04:43:09 +00:00
async function onReady ( ) {
2019-09-28 17:42:48 +00:00
showExtensionInformation ( ) ;
2019-09-08 16:16:12 +00:00
formSetupEventListeners ( ) ;
2019-09-08 17:16:05 +00:00
await profileOptionsSetup ( ) ;
2019-05-04 16:57:55 +00:00
storageInfoInitialize ( ) ;
2019-09-11 00:46:30 +00:00
chrome . runtime . onMessage . addListener ( onMessage ) ;
2017-08-15 04:43:09 +00:00
}
2017-07-17 02:29:43 +00:00
2017-08-15 04:43:09 +00:00
$ ( document ) . ready ( utilAsync ( onReady ) ) ;
2017-03-03 05:01:49 +00:00
2019-09-11 00:46:30 +00:00
/ *
* Remote options updates
* /
function settingsGetSource ( ) {
return new Promise ( ( resolve ) => {
chrome . tabs . getCurrent ( ( tab ) => resolve ( ` settings ${ tab ? tab . id : '' } ` ) ) ;
} ) ;
}
async function settingsSaveOptions ( ) {
const source = await settingsGetSource ( ) ;
await apiOptionsSave ( source ) ;
}
async function onOptionsUpdate ( { source } ) {
const thisSource = await settingsGetSource ( ) ;
if ( source === thisSource ) { return ; }
const optionsContext = getOptionsContext ( ) ;
const options = await apiOptionsGet ( optionsContext ) ;
await formWrite ( options ) ;
}
function onMessage ( { action , params } ) {
if ( action === 'optionsUpdate' ) {
onOptionsUpdate ( params ) ;
}
}
2017-03-03 05:01:49 +00:00
/ *
* Dictionary
* /
2019-02-27 02:01:32 +00:00
function dictionaryErrorToString ( error ) {
if ( error . toString ) {
error = error . toString ( ) ;
} else {
error = ` ${ error } ` ;
}
for ( const [ match , subst ] of dictionaryErrorToString . overrides ) {
if ( error . includes ( match ) ) {
error = subst ;
break ;
}
}
return error ;
}
dictionaryErrorToString . overrides = [
[
'A mutation operation was attempted on a database that did not allow mutations.' ,
'Access to IndexedDB appears to be restricted. Firefox seems to require that the history preference is set to "Remember history" before IndexedDB use of any kind is allowed.'
] ,
[
'The operation failed for reasons unrelated to the database itself and not covered by any other error code.' ,
'Unable to access IndexedDB due to a possibly corrupt user profile. Try using the "Refresh Firefox" feature to reset your user profile.'
] ,
[
'BulkError' ,
'Unable to finish importing dictionary data into IndexedDB. This may indicate that you do not have sufficient disk space available to complete this operation.'
]
] ;
function dictionaryErrorsShow ( errors ) {
2017-03-03 05:01:49 +00:00
const dialog = $ ( '#dict-error' ) ;
2019-02-27 02:01:32 +00:00
dialog . show ( ) . text ( '' ) ;
if ( errors !== null && errors . length > 0 ) {
const uniqueErrors = { } ;
for ( let e of errors ) {
e = dictionaryErrorToString ( e ) ;
uniqueErrors [ e ] = uniqueErrors . hasOwnProperty ( e ) ? uniqueErrors [ e ] + 1 : 1 ;
2017-09-23 03:38:23 +00:00
}
2019-02-27 02:01:32 +00:00
for ( const e in uniqueErrors ) {
const count = uniqueErrors [ e ] ;
const div = document . createElement ( 'p' ) ;
if ( count > 1 ) {
div . textContent = ` ${ e } ` ;
const em = document . createElement ( 'em' ) ;
em . textContent = ` ( ${ count } ) ` ;
div . appendChild ( em ) ;
} else {
div . textContent = ` ${ e } ` ;
2017-09-23 03:38:23 +00:00
}
2019-02-27 02:01:32 +00:00
dialog . append ( $ ( div ) ) ;
2017-09-23 03:38:23 +00:00
}
2019-02-27 02:01:32 +00:00
dialog . show ( ) ;
2017-03-03 05:01:49 +00:00
} else {
dialog . hide ( ) ;
}
}
2017-03-05 23:54:03 +00:00
function dictionarySpinnerShow ( show ) {
2017-03-03 05:01:49 +00:00
const spinner = $ ( '#dict-spinner' ) ;
if ( show ) {
spinner . show ( ) ;
} else {
spinner . hide ( ) ;
}
}
2017-03-05 23:54:03 +00:00
function dictionaryGroupsSort ( ) {
const dictGroups = $ ( '#dict-groups' ) ;
const dictGroupChildren = dictGroups . children ( '.dict-group' ) . sort ( ( ca , cb ) => {
const pa = parseInt ( $ ( ca ) . find ( '.dict-priority' ) . val ( ) , 10 ) ;
const pb = parseInt ( $ ( cb ) . find ( '.dict-priority' ) . val ( ) , 10 ) ;
if ( pa < pb ) {
return 1 ;
} else if ( pa > pb ) {
return - 1 ;
} else {
return 0 ;
}
} ) ;
dictGroups . append ( dictGroupChildren ) ;
}
2017-07-17 02:29:43 +00:00
async function dictionaryGroupsPopulate ( options ) {
2017-03-03 05:01:49 +00:00
const dictGroups = $ ( '#dict-groups' ) . empty ( ) ;
const dictWarning = $ ( '#dict-warning' ) . hide ( ) ;
2019-08-22 23:44:31 +00:00
const dictRows = toIterable ( await utilDatabaseSummarize ( ) ) ;
2017-07-17 02:29:43 +00:00
if ( dictRows . length === 0 ) {
dictWarning . show ( ) ;
}
2017-03-05 23:54:03 +00:00
2019-08-22 23:44:31 +00:00
for ( const dictRow of toIterable ( dictRowsSort ( dictRows , options ) ) ) {
2017-10-29 19:20:56 +00:00
const dictOptions = options . dictionaries [ dictRow . title ] || {
enabled : false ,
priority : 0 ,
allowSecondarySearches : false
} ;
2017-09-09 19:59:49 +00:00
const dictHtml = await apiTemplateRender ( 'dictionary.html' , {
2017-10-29 19:20:56 +00:00
enabled : dictOptions . enabled ,
priority : dictOptions . priority ,
allowSecondarySearches : dictOptions . allowSecondarySearches ,
2017-07-17 02:29:43 +00:00
title : dictRow . title ,
version : dictRow . version ,
revision : dictRow . revision ,
2017-10-29 19:20:56 +00:00
outdated : dictRow . version < 3
2017-07-17 02:29:43 +00:00
} ) ;
2017-03-03 05:01:49 +00:00
2017-07-17 02:29:43 +00:00
dictGroups . append ( $ ( dictHtml ) ) ;
}
2017-03-03 05:01:49 +00:00
2017-07-17 02:55:33 +00:00
formUpdateVisibility ( options ) ;
2017-07-17 02:29:43 +00:00
2017-10-15 02:19:16 +00:00
$ ( '.dict-enabled, .dict-priority, .dict-allow-secondary-searches' ) . change ( e => {
2017-07-17 02:29:43 +00:00
dictionaryGroupsSort ( ) ;
2017-07-17 02:55:33 +00:00
onFormOptionsChanged ( e ) ;
2017-07-17 02:29:43 +00:00
} ) ;
2017-03-03 05:01:49 +00:00
}
2017-08-15 04:43:09 +00:00
async function onDictionaryPurge ( e ) {
2017-03-03 05:01:49 +00:00
e . preventDefault ( ) ;
2017-10-29 17:18:15 +00:00
const dictControls = $ ( '#dict-importer, #dict-groups, #dict-main-group' ) . hide ( ) ;
2017-09-23 02:39:05 +00:00
const dictProgress = $ ( '#dict-purge' ) . show ( ) ;
2017-03-03 05:01:49 +00:00
2017-07-17 02:29:43 +00:00
try {
2019-02-27 02:01:32 +00:00
dictionaryErrorsShow ( null ) ;
2017-07-17 02:29:43 +00:00
dictionarySpinnerShow ( true ) ;
2017-07-28 04:42:14 +00:00
await utilDatabasePurge ( ) ;
2019-09-08 17:27:32 +00:00
for ( const options of await getOptionsArray ( ) ) {
options . dictionaries = utilBackgroundIsolate ( { } ) ;
options . general . mainDictionary = '' ;
}
2019-09-11 00:46:30 +00:00
await settingsSaveOptions ( ) ;
2017-07-17 02:29:43 +00:00
2019-09-08 17:27:32 +00:00
const optionsContext = getOptionsContext ( ) ;
const options = await apiOptionsGet ( optionsContext ) ;
2017-07-17 02:29:43 +00:00
await dictionaryGroupsPopulate ( options ) ;
2017-10-24 13:23:13 +00:00
await formMainDictionaryOptionsPopulate ( options ) ;
2017-07-17 02:29:43 +00:00
} catch ( e ) {
2019-02-27 02:01:32 +00:00
dictionaryErrorsShow ( [ e ] ) ;
2017-07-17 02:29:43 +00:00
} finally {
2017-03-05 23:54:03 +00:00
dictionarySpinnerShow ( false ) ;
2017-07-17 02:29:43 +00:00
2017-03-03 05:01:49 +00:00
dictControls . show ( ) ;
dictProgress . hide ( ) ;
2019-02-27 02:03:34 +00:00
if ( storageEstimate . mostRecent !== null ) {
storageUpdateStats ( ) ;
}
2017-07-17 02:29:43 +00:00
}
2017-08-15 04:43:09 +00:00
}
2017-06-25 22:36:28 +00:00
2017-08-15 04:43:09 +00:00
async function onDictionaryImport ( e ) {
2017-06-25 22:43:29 +00:00
const dictFile = $ ( '#dict-file' ) ;
2017-07-17 02:29:43 +00:00
const dictControls = $ ( '#dict-importer' ) . hide ( ) ;
2017-06-25 22:36:28 +00:00
const dictProgress = $ ( '#dict-import-progress' ) . show ( ) ;
2017-07-17 02:29:43 +00:00
try {
2019-02-27 02:01:32 +00:00
dictionaryErrorsShow ( null ) ;
2017-07-17 02:29:43 +00:00
dictionarySpinnerShow ( true ) ;
2017-06-25 22:36:28 +00:00
2017-07-17 02:29:43 +00:00
const setProgress = percent => dictProgress . find ( '.progress-bar' ) . css ( 'width' , ` ${ percent } % ` ) ;
2019-02-27 02:03:34 +00:00
const updateProgress = ( total , current ) => {
setProgress ( current / total * 100.0 ) ;
if ( storageEstimate . mostRecent !== null && ! storageUpdateStats . isUpdating ) {
storageUpdateStats ( ) ;
}
} ;
2017-07-17 02:29:43 +00:00
setProgress ( 0.0 ) ;
2019-02-27 02:01:32 +00:00
const exceptions = [ ] ;
const summary = await utilDatabaseImport ( e . target . files [ 0 ] , updateProgress , exceptions ) ;
2019-09-08 17:27:32 +00:00
for ( const options of await getOptionsArray ( ) ) {
options . dictionaries [ summary . title ] = utilBackgroundIsolate ( {
enabled : true ,
priority : 0 ,
allowSecondarySearches : false
} ) ;
if ( summary . sequenced && options . general . mainDictionary === '' ) {
options . general . mainDictionary = summary . title ;
}
2017-10-24 13:23:13 +00:00
}
2019-09-08 17:27:32 +00:00
await settingsSaveOptions ( ) ;
2019-02-27 02:01:32 +00:00
if ( exceptions . length > 0 ) {
exceptions . push ( ` Dictionary may not have been imported properly: ${ exceptions . length } error ${ exceptions . length === 1 ? '' : 's' } reported. ` ) ;
dictionaryErrorsShow ( exceptions ) ;
}
2019-09-08 17:27:32 +00:00
const optionsContext = getOptionsContext ( ) ;
const options = await apiOptionsGet ( optionsContext ) ;
2017-07-17 02:29:43 +00:00
await dictionaryGroupsPopulate ( options ) ;
2017-10-24 13:23:13 +00:00
await formMainDictionaryOptionsPopulate ( options ) ;
2017-07-17 02:29:43 +00:00
} catch ( e ) {
2019-02-27 02:01:32 +00:00
dictionaryErrorsShow ( [ e ] ) ;
2017-07-17 02:29:43 +00:00
} finally {
2017-06-25 22:36:28 +00:00
dictionarySpinnerShow ( false ) ;
2017-07-17 02:29:43 +00:00
dictFile . val ( '' ) ;
dictControls . show ( ) ;
2017-06-25 22:36:28 +00:00
dictProgress . hide ( ) ;
2017-07-17 02:29:43 +00:00
}
2017-08-15 04:43:09 +00:00
}
2017-03-03 05:01:49 +00:00
2017-07-17 06:56:36 +00:00
2017-03-03 05:01:49 +00:00
/ *
* Anki
* /
2017-03-05 23:54:03 +00:00
function ankiSpinnerShow ( show ) {
2017-03-03 05:01:49 +00:00
const spinner = $ ( '#anki-spinner' ) ;
if ( show ) {
spinner . show ( ) ;
} else {
spinner . hide ( ) ;
}
}
2017-03-05 23:54:03 +00:00
function ankiErrorShow ( error ) {
2017-03-03 05:01:49 +00:00
const dialog = $ ( '#anki-error' ) ;
if ( error ) {
2017-09-23 02:39:05 +00:00
dialog . show ( ) . text ( error ) ;
2017-03-03 05:01:49 +00:00
}
else {
dialog . hide ( ) ;
2016-04-04 03:05:22 +00:00
}
2017-03-03 05:01:49 +00:00
}
2017-07-17 02:55:33 +00:00
function ankiErrorShown ( ) {
return $ ( '#anki-error' ) . is ( ':visible' ) ;
}
2017-03-03 05:01:49 +00:00
function ankiFieldsToDict ( selection ) {
const result = { } ;
selection . each ( ( index , element ) => {
result [ $ ( element ) . data ( 'field' ) ] = $ ( element ) . val ( ) ;
} ) ;
2016-04-04 03:05:22 +00:00
2017-03-03 05:01:49 +00:00
return result ;
2016-04-04 03:05:22 +00:00
}
2017-07-17 02:29:43 +00:00
async function ankiDeckAndModelPopulate ( options ) {
2017-03-03 05:01:49 +00:00
const ankiFormat = $ ( '#anki-format' ) . hide ( ) ;
2017-07-28 04:42:14 +00:00
const deckNames = await utilAnkiGetDeckNames ( ) ;
2017-07-17 02:29:43 +00:00
const ankiDeck = $ ( '.anki-deck' ) ;
ankiDeck . find ( 'option' ) . remove ( ) ;
deckNames . sort ( ) . forEach ( name => ankiDeck . append ( $ ( '<option/>' , { value : name , text : name } ) ) ) ;
2017-07-28 04:42:14 +00:00
const modelNames = await utilAnkiGetModelNames ( ) ;
2017-07-17 02:29:43 +00:00
const ankiModel = $ ( '.anki-model' ) ;
ankiModel . find ( 'option' ) . remove ( ) ;
modelNames . sort ( ) . forEach ( name => ankiModel . append ( $ ( '<option/>' , { value : name , text : name } ) ) ) ;
2017-03-03 05:01:49 +00:00
2017-08-19 18:23:19 +00:00
$ ( '#anki-terms-deck' ) . val ( options . anki . terms . deck ) ;
await ankiFieldsPopulate ( $ ( '#anki-terms-model' ) . val ( options . anki . terms . model ) , options ) ;
$ ( '#anki-kanji-deck' ) . val ( options . anki . kanji . deck ) ;
await ankiFieldsPopulate ( $ ( '#anki-kanji-model' ) . val ( options . anki . kanji . model ) , options ) ;
2017-07-17 02:29:43 +00:00
ankiFormat . show ( ) ;
}
async function ankiFieldsPopulate ( element , options ) {
2017-03-03 05:01:49 +00:00
const modelName = element . val ( ) ;
2017-07-09 22:23:11 +00:00
if ( ! modelName ) {
2017-07-17 02:29:43 +00:00
return ;
2017-03-03 05:01:49 +00:00
}
2017-07-17 02:29:43 +00:00
const tab = element . closest ( '.tab-pane' ) ;
const tabId = tab . attr ( 'id' ) ;
const container = tab . find ( 'tbody' ) . empty ( ) ;
2017-03-03 05:01:49 +00:00
const markers = {
2017-03-29 03:49:26 +00:00
'terms' : [
'audio' ,
'cloze-body' ,
'cloze-prefix' ,
'cloze-suffix' ,
'dictionary' ,
'expression' ,
'furigana' ,
2017-08-26 19:18:35 +00:00
'furigana-plain' ,
2017-03-29 03:49:26 +00:00
'glossary' ,
2017-07-16 08:05:23 +00:00
'glossary-brief' ,
2017-03-29 03:49:26 +00:00
'reading' ,
'sentence' ,
'tags' ,
2019-08-15 23:39:58 +00:00
'url' ,
'screenshot'
2017-03-29 03:49:26 +00:00
] ,
'kanji' : [
'character' ,
'dictionary' ,
'glossary' ,
'kunyomi' ,
'onyomi' ,
'sentence' ,
'tags' ,
'url'
]
2017-03-03 05:01:49 +00:00
} [ tabId ] || { } ;
2017-07-28 04:42:14 +00:00
for ( const name of await utilAnkiGetModelFieldNames ( modelName ) ) {
2017-07-17 02:29:43 +00:00
const value = options . anki [ tabId ] . fields [ name ] || '' ;
const html = Handlebars . templates [ 'model.html' ] ( { name , markers , value } ) ;
container . append ( $ ( html ) ) ;
}
2017-03-03 05:01:49 +00:00
2017-08-15 04:43:09 +00:00
tab . find ( '.anki-field-value' ) . change ( utilAsync ( onFormOptionsChanged ) ) ;
2017-07-17 02:55:33 +00:00
tab . find ( '.marker-link' ) . click ( onAnkiMarkerClicked ) ;
}
function onAnkiMarkerClicked ( e ) {
e . preventDefault ( ) ;
const link = e . target ;
$ ( link ) . closest ( '.input-group' ) . find ( '.anki-field-value' ) . val ( ` { ${ link . text } } ` ) . trigger ( 'change' ) ;
2016-04-04 03:05:22 +00:00
}
2017-08-15 04:43:09 +00:00
async function onAnkiModelChanged ( e ) {
2017-07-17 02:29:43 +00:00
try {
2017-08-15 04:43:09 +00:00
if ( ! e . originalEvent ) {
return ;
}
2017-07-17 02:29:43 +00:00
const element = $ ( this ) ;
2017-03-03 05:01:49 +00:00
const tab = element . closest ( '.tab-pane' ) ;
const tabId = tab . attr ( 'id' ) ;
2017-07-17 02:29:43 +00:00
2019-09-07 19:59:10 +00:00
const optionsContext = getOptionsContext ( ) ;
const options = await apiOptionsGet ( optionsContext ) ;
2019-09-07 19:54:00 +00:00
await formRead ( options ) ;
2019-09-14 20:21:41 +00:00
options . anki [ tabId ] . fields = utilBackgroundIsolate ( { } ) ;
2019-09-11 00:46:30 +00:00
await settingsSaveOptions ( ) ;
2017-07-17 02:29:43 +00:00
2017-08-16 03:58:50 +00:00
ankiSpinnerShow ( true ) ;
2019-09-07 19:54:00 +00:00
await ankiFieldsPopulate ( element , options ) ;
2017-08-16 03:58:50 +00:00
ankiErrorShow ( ) ;
2017-07-17 02:29:43 +00:00
} catch ( e ) {
ankiErrorShow ( e ) ;
} finally {
ankiSpinnerShow ( false ) ;
}
2017-08-15 04:43:09 +00:00
}
2017-09-06 20:18:06 +00:00
async function onAnkiFieldTemplatesReset ( e ) {
try {
e . preventDefault ( ) ;
2019-09-07 19:59:10 +00:00
const optionsContext = getOptionsContext ( ) ;
const options = await apiOptionsGet ( optionsContext ) ;
2019-09-07 23:50:58 +00:00
const fieldTemplates = profileOptionsGetDefaultFieldTemplates ( ) ;
2019-09-07 19:59:10 +00:00
options . anki . fieldTemplates = fieldTemplates ;
$ ( '#field-templates' ) . val ( fieldTemplates ) ;
2019-09-11 00:46:30 +00:00
await settingsSaveOptions ( ) ;
2017-09-06 20:18:06 +00:00
} catch ( e ) {
ankiErrorShow ( e ) ;
}
}
2019-05-04 16:57:55 +00:00
/ *
* Storage
* /
async function getBrowser ( ) {
2019-08-22 23:44:31 +00:00
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' ;
}
} catch ( e ) { }
return 'firefox' ;
2019-05-04 16:57:55 +00:00
} else {
2019-08-22 23:44:31 +00:00
return 'chrome' ;
2019-05-04 16:57:55 +00:00
}
}
function storageBytesToLabeledString ( size ) {
const base = 1000 ;
2019-08-30 00:45:07 +00:00
const labels = [ 'bytes' , 'KB' , 'MB' , 'GB' ] ;
2019-05-04 16:57:55 +00:00
let labelIndex = 0 ;
while ( size >= base ) {
size /= base ;
++ labelIndex ;
}
2019-02-27 02:03:34 +00:00
const label = size . toFixed ( 1 ) ;
2019-05-04 16:57:55 +00:00
return ` ${ label } ${ labels [ labelIndex ] } ` ;
}
async function storageEstimate ( ) {
try {
2019-02-27 02:03:34 +00:00
return ( storageEstimate . mostRecent = await navigator . storage . estimate ( ) ) ;
2019-05-04 16:57:55 +00:00
} catch ( e ) { }
return null ;
}
2019-02-27 02:03:34 +00:00
storageEstimate . mostRecent = null ;
2019-05-04 16:57:55 +00:00
async function storageInfoInitialize ( ) {
const browser = await getBrowser ( ) ;
2019-08-30 00:45:07 +00:00
const container = document . querySelector ( '#storage-info' ) ;
container . setAttribute ( 'data-browser' , browser ) ;
2019-05-04 16:57:55 +00:00
await storageShowInfo ( ) ;
2019-08-30 00:45:07 +00:00
container . classList . remove ( 'storage-hidden' ) ;
2019-05-04 16:57:55 +00:00
2019-08-30 00:45:07 +00:00
document . querySelector ( '#storage-refresh' ) . addEventListener ( 'click' , ( ) => storageShowInfo ( ) , false ) ;
2019-05-04 16:57:55 +00:00
}
2019-02-27 02:03:34 +00:00
async function storageUpdateStats ( ) {
storageUpdateStats . isUpdating = true ;
2019-05-04 16:57:55 +00:00
const estimate = await storageEstimate ( ) ;
const valid = ( estimate !== null ) ;
if ( valid ) {
2019-08-30 00:45:07 +00:00
document . querySelector ( '#storage-usage' ) . textContent = storageBytesToLabeledString ( estimate . usage ) ;
document . querySelector ( '#storage-quota' ) . textContent = storageBytesToLabeledString ( estimate . quota ) ;
2019-05-04 16:57:55 +00:00
}
2019-02-27 02:03:34 +00:00
storageUpdateStats . isUpdating = false ;
return valid ;
}
storageUpdateStats . isUpdating = false ;
async function storageShowInfo ( ) {
storageSpinnerShow ( true ) ;
const valid = await storageUpdateStats ( ) ;
2019-08-30 00:45:07 +00:00
document . querySelector ( '#storage-use' ) . classList . toggle ( 'storage-hidden' , ! valid ) ;
document . querySelector ( '#storage-error' ) . classList . toggle ( 'storage-hidden' , valid ) ;
2019-05-04 16:57:55 +00:00
storageSpinnerShow ( false ) ;
}
function storageSpinnerShow ( show ) {
const spinner = $ ( '#storage-spinner' ) ;
if ( show ) {
spinner . show ( ) ;
} else {
spinner . hide ( ) ;
}
}
2019-09-28 17:42:48 +00:00
/ *
* Information
* /
function showExtensionInformation ( ) {
const node = document . getElementById ( 'extension-info' ) ;
if ( node === null ) { return ; }
const manifest = chrome . runtime . getManifest ( ) ;
node . textContent = ` ${ manifest . name } v ${ manifest . version } ` ;
}