diff options
Diffstat (limited to 'ext')
| -rw-r--r-- | ext/bg/js/settings/backup.js | 569 | ||||
| -rw-r--r-- | ext/bg/js/settings/main.js | 4 | ||||
| -rw-r--r-- | ext/bg/js/util.js | 9 | 
3 files changed, 292 insertions, 290 deletions
| diff --git a/ext/bg/js/settings/backup.js b/ext/bg/js/settings/backup.js index 5eb55502..4e104e6f 100644 --- a/ext/bg/js/settings/backup.js +++ b/ext/bg/js/settings/backup.js @@ -22,355 +22,366 @@   * utilBackend   * utilBackgroundIsolate   * utilIsolate - * utilReadFileArrayBuffer   */ -// Exporting - -let _settingsExportToken = null; -let _settingsExportRevoke = null; -const SETTINGS_EXPORT_CURRENT_VERSION = 0; - -function _getSettingsExportDateString(date, dateSeparator, dateTimeSeparator, timeSeparator, resolution) { -    const values = [ -        date.getUTCFullYear().toString(), -        dateSeparator, -        (date.getUTCMonth() + 1).toString().padStart(2, '0'), -        dateSeparator, -        date.getUTCDate().toString().padStart(2, '0'), -        dateTimeSeparator, -        date.getUTCHours().toString().padStart(2, '0'), -        timeSeparator, -        date.getUTCMinutes().toString().padStart(2, '0'), -        timeSeparator, -        date.getUTCSeconds().toString().padStart(2, '0') -    ]; -    return values.slice(0, resolution * 2 - 1).join(''); -} +class SettingsBackup { +    constructor() { +        this._settingsExportToken = null; +        this._settingsExportRevoke = null; +        this._currentVersion = 0; +    } -async function _getSettingsExportData(date) { -    const optionsFull = await api.optionsGetFull(); -    const environment = await api.getEnvironmentInfo(); -    const fieldTemplatesDefault = await api.getDefaultAnkiFieldTemplates(); +    prepare() { +        document.querySelector('#settings-export').addEventListener('click', this._onSettingsExportClick.bind(this), false); +        document.querySelector('#settings-import').addEventListener('click', this._onSettingsImportClick.bind(this), false); +        document.querySelector('#settings-import-file').addEventListener('change', this._onSettingsImportFileChange.bind(this), false); +        document.querySelector('#settings-reset').addEventListener('click', this._onSettingsResetClick.bind(this), false); +        document.querySelector('#settings-reset-modal-confirm').addEventListener('click', this._onSettingsResetConfirmClick.bind(this), false); +    } -    // Format options -    for (const {options} of optionsFull.profiles) { -        if (options.anki.fieldTemplates === fieldTemplatesDefault || !options.anki.fieldTemplates) { -            delete options.anki.fieldTemplates; // Default -        } +    // Private + +    _getSettingsExportDateString(date, dateSeparator, dateTimeSeparator, timeSeparator, resolution) { +        const values = [ +            date.getUTCFullYear().toString(), +            dateSeparator, +            (date.getUTCMonth() + 1).toString().padStart(2, '0'), +            dateSeparator, +            date.getUTCDate().toString().padStart(2, '0'), +            dateTimeSeparator, +            date.getUTCHours().toString().padStart(2, '0'), +            timeSeparator, +            date.getUTCMinutes().toString().padStart(2, '0'), +            timeSeparator, +            date.getUTCSeconds().toString().padStart(2, '0') +        ]; +        return values.slice(0, resolution * 2 - 1).join('');      } -    const data = { -        version: SETTINGS_EXPORT_CURRENT_VERSION, -        date: _getSettingsExportDateString(date, '-', ' ', ':', 6), -        url: chrome.runtime.getURL('/'), -        manifest: chrome.runtime.getManifest(), -        environment, -        userAgent: navigator.userAgent, -        options: optionsFull -    }; - -    return data; -} +    async _getSettingsExportData(date) { +        const optionsFull = await api.optionsGetFull(); +        const environment = await api.getEnvironmentInfo(); +        const fieldTemplatesDefault = await api.getDefaultAnkiFieldTemplates(); -function _saveBlob(blob, fileName) { -    if (typeof navigator === 'object' && typeof navigator.msSaveBlob === 'function') { -        if (navigator.msSaveBlob(blob)) { -            return; +        // Format options +        for (const {options} of optionsFull.profiles) { +            if (options.anki.fieldTemplates === fieldTemplatesDefault || !options.anki.fieldTemplates) { +                delete options.anki.fieldTemplates; // Default +            }          } -    } -    const blobUrl = URL.createObjectURL(blob); +        const data = { +            version: this._currentVersion, +            date: this._getSettingsExportDateString(date, '-', ' ', ':', 6), +            url: chrome.runtime.getURL('/'), +            manifest: chrome.runtime.getManifest(), +            environment, +            userAgent: navigator.userAgent, +            options: optionsFull +        }; -    const a = document.createElement('a'); -    a.href = blobUrl; -    a.download = fileName; -    a.rel = 'noopener'; -    a.target = '_blank'; +        return data; +    } -    const revoke = () => { -        URL.revokeObjectURL(blobUrl); -        a.href = ''; -        _settingsExportRevoke = null; -    }; -    _settingsExportRevoke = revoke; +    _saveBlob(blob, fileName) { +        if (typeof navigator === 'object' && typeof navigator.msSaveBlob === 'function') { +            if (navigator.msSaveBlob(blob)) { +                return; +            } +        } -    a.dispatchEvent(new MouseEvent('click')); -    setTimeout(revoke, 60000); -} +        const blobUrl = URL.createObjectURL(blob); -async function _onSettingsExportClick() { -    if (_settingsExportRevoke !== null) { -        _settingsExportRevoke(); -        _settingsExportRevoke = null; -    } +        const a = document.createElement('a'); +        a.href = blobUrl; +        a.download = fileName; +        a.rel = 'noopener'; +        a.target = '_blank'; -    const date = new Date(Date.now()); +        const revoke = () => { +            URL.revokeObjectURL(blobUrl); +            a.href = ''; +            this._settingsExportRevoke = null; +        }; +        this._settingsExportRevoke = revoke; -    const token = {}; -    _settingsExportToken = token; -    const data = await _getSettingsExportData(date); -    if (_settingsExportToken !== token) { -        // A new export has been started -        return; +        a.dispatchEvent(new MouseEvent('click')); +        setTimeout(revoke, 60000);      } -    _settingsExportToken = null; -    const fileName = `yomichan-settings-${_getSettingsExportDateString(date, '-', '-', '-', 6)}.json`; -    const blob = new Blob([JSON.stringify(data, null, 4)], {type: 'application/json'}); -    _saveBlob(blob, fileName); -} - - -// Importing +    async _onSettingsExportClick() { +        if (this._settingsExportRevoke !== null) { +            this._settingsExportRevoke(); +            this._settingsExportRevoke = null; +        } -async function _settingsImportSetOptionsFull(optionsFull) { -    return utilIsolate(utilBackend().setFullOptions( -        utilBackgroundIsolate(optionsFull) -    )); -} +        const date = new Date(Date.now()); -function _showSettingsImportError(error) { -    yomichan.logError(error); -    document.querySelector('#settings-import-error-modal-message').textContent = `${error}`; -    $('#settings-import-error-modal').modal('show'); -} +        const token = {}; +        this._settingsExportToken = token; +        const data = await this._getSettingsExportData(date); +        if (this._settingsExportToken !== token) { +            // A new export has been started +            return; +        } +        this._settingsExportToken = null; -async function _showSettingsImportWarnings(warnings) { -    const modalNode = $('#settings-import-warning-modal'); -    const buttons = document.querySelectorAll('.settings-import-warning-modal-import-button'); -    const messageContainer = document.querySelector('#settings-import-warning-modal-message'); -    if (modalNode.length === 0 || buttons.length === 0 || messageContainer === null) { -        return {result: false}; +        const fileName = `yomichan-settings-${this._getSettingsExportDateString(date, '-', '-', '-', 6)}.json`; +        const blob = new Blob([JSON.stringify(data, null, 4)], {type: 'application/json'}); +        this._saveBlob(blob, fileName);      } -    // Set message -    const fragment = document.createDocumentFragment(); -    for (const warning of warnings) { -        const node = document.createElement('li'); -        node.textContent = `${warning}`; -        fragment.appendChild(node); +    _readFileArrayBuffer(file) { +        return new Promise((resolve, reject) => { +            const reader = new FileReader(); +            reader.onload = () => resolve(reader.result); +            reader.onerror = () => reject(reader.error); +            reader.readAsArrayBuffer(file); +        });      } -    messageContainer.textContent = ''; -    messageContainer.appendChild(fragment); - -    // Show modal -    modalNode.modal('show'); - -    // Wait for modal to close -    return new Promise((resolve) => { -        const onButtonClick = (e) => { -            e.preventDefault(); -            complete({ -                result: true, -                sanitize: e.currentTarget.dataset.importSanitize === 'true' -            }); -            modalNode.modal('hide'); -        }; -        const onModalHide = () => { -            complete({result: false}); -        }; -        let completed = false; -        const complete = (result) => { -            if (completed) { return; } -            completed = true; +    // Importing -            modalNode.off('hide.bs.modal', onModalHide); -            for (const button of buttons) { -                button.removeEventListener('click', onButtonClick, false); -            } +    async _settingsImportSetOptionsFull(optionsFull) { +        return utilIsolate(utilBackend().setFullOptions( +            utilBackgroundIsolate(optionsFull) +        )); +    } -            resolve(result); -        }; +    _showSettingsImportError(error) { +        yomichan.logError(error); +        document.querySelector('#settings-import-error-modal-message').textContent = `${error}`; +        $('#settings-import-error-modal').modal('show'); +    } -        // Hook events -        modalNode.on('hide.bs.modal', onModalHide); -        for (const button of buttons) { -            button.addEventListener('click', onButtonClick, false); +    async _showSettingsImportWarnings(warnings) { +        const modalNode = $('#settings-import-warning-modal'); +        const buttons = document.querySelectorAll('.settings-import-warning-modal-import-button'); +        const messageContainer = document.querySelector('#settings-import-warning-modal-message'); +        if (modalNode.length === 0 || buttons.length === 0 || messageContainer === null) { +            return {result: false};          } -    }); -} -function _isLocalhostUrl(urlString) { -    try { -        const url = new URL(urlString); -        switch (url.hostname.toLowerCase()) { -            case 'localhost': -            case '127.0.0.1': -            case '[::1]': -                switch (url.protocol.toLowerCase()) { -                    case 'http:': -                    case 'https:': -                        return true; -                } -                break; +        // Set message +        const fragment = document.createDocumentFragment(); +        for (const warning of warnings) { +            const node = document.createElement('li'); +            node.textContent = `${warning}`; +            fragment.appendChild(node);          } -    } catch (e) { -        // NOP -    } -    return false; -} +        messageContainer.textContent = ''; +        messageContainer.appendChild(fragment); + +        // Show modal +        modalNode.modal('show'); + +        // Wait for modal to close +        return new Promise((resolve) => { +            const onButtonClick = (e) => { +                e.preventDefault(); +                complete({ +                    result: true, +                    sanitize: e.currentTarget.dataset.importSanitize === 'true' +                }); +                modalNode.modal('hide'); +            }; +            const onModalHide = () => { +                complete({result: false}); +            }; + +            let completed = false; +            const complete = (result) => { +                if (completed) { return; } +                completed = true; + +                modalNode.off('hide.bs.modal', onModalHide); +                for (const button of buttons) { +                    button.removeEventListener('click', onButtonClick, false); +                } -function _settingsImportSanitizeProfileOptions(options, dryRun) { -    const warnings = []; +                resolve(result); +            }; -    const anki = options.anki; -    if (isObject(anki)) { -        const fieldTemplates = anki.fieldTemplates; -        if (typeof fieldTemplates === 'string') { -            warnings.push('anki.fieldTemplates contains a non-default value'); -            if (!dryRun) { -                delete anki.fieldTemplates; -            } -        } -        const server = anki.server; -        if (typeof server === 'string' && server.length > 0 && !_isLocalhostUrl(server)) { -            warnings.push('anki.server uses a non-localhost URL'); -            if (!dryRun) { -                delete anki.server; +            // Hook events +            modalNode.on('hide.bs.modal', onModalHide); +            for (const button of buttons) { +                button.addEventListener('click', onButtonClick, false);              } -        } +        });      } -    const audio = options.audio; -    if (isObject(audio)) { -        const customSourceUrl = audio.customSourceUrl; -        if (typeof customSourceUrl === 'string' && customSourceUrl.length > 0 && !_isLocalhostUrl(customSourceUrl)) { -            warnings.push('audio.customSourceUrl uses a non-localhost URL'); -            if (!dryRun) { -                delete audio.customSourceUrl; +    _isLocalhostUrl(urlString) { +        try { +            const url = new URL(urlString); +            switch (url.hostname.toLowerCase()) { +                case 'localhost': +                case '127.0.0.1': +                case '[::1]': +                    switch (url.protocol.toLowerCase()) { +                        case 'http:': +                        case 'https:': +                            return true; +                    } +                    break;              } +        } catch (e) { +            // NOP          } +        return false;      } -    return warnings; -} - -function _settingsImportSanitizeOptions(optionsFull, dryRun) { -    const warnings = new Set(); +    _settingsImportSanitizeProfileOptions(options, dryRun) { +        const warnings = []; -    const profiles = optionsFull.profiles; -    if (Array.isArray(profiles)) { -        for (const profile of profiles) { -            if (!isObject(profile)) { continue; } -            const options = profile.options; -            if (!isObject(options)) { continue; } - -            const warnings2 = _settingsImportSanitizeProfileOptions(options, dryRun); -            for (const warning of warnings2) { -                warnings.add(warning); +        const anki = options.anki; +        if (isObject(anki)) { +            const fieldTemplates = anki.fieldTemplates; +            if (typeof fieldTemplates === 'string') { +                warnings.push('anki.fieldTemplates contains a non-default value'); +                if (!dryRun) { +                    delete anki.fieldTemplates; +                } +            } +            const server = anki.server; +            if (typeof server === 'string' && server.length > 0 && !this._isLocalhostUrl(server)) { +                warnings.push('anki.server uses a non-localhost URL'); +                if (!dryRun) { +                    delete anki.server; +                }              }          } -    } -    return warnings; -} +        const audio = options.audio; +        if (isObject(audio)) { +            const customSourceUrl = audio.customSourceUrl; +            if (typeof customSourceUrl === 'string' && customSourceUrl.length > 0 && !this._isLocalhostUrl(customSourceUrl)) { +                warnings.push('audio.customSourceUrl uses a non-localhost URL'); +                if (!dryRun) { +                    delete audio.customSourceUrl; +                } +            } +        } -function _utf8Decode(arrayBuffer) { -    try { -        return new TextDecoder('utf-8').decode(arrayBuffer); -    } catch (e) { -        const binaryString = String.fromCharCode.apply(null, new Uint8Array(arrayBuffer)); -        return decodeURIComponent(escape(binaryString)); +        return warnings;      } -} -async function _importSettingsFile(file) { -    const dataString = _utf8Decode(await utilReadFileArrayBuffer(file)); -    const data = JSON.parse(dataString); +    _settingsImportSanitizeOptions(optionsFull, dryRun) { +        const warnings = new Set(); -    // Type check -    if (!isObject(data)) { -        throw new Error(`Invalid data type: ${typeof data}`); -    } +        const profiles = optionsFull.profiles; +        if (Array.isArray(profiles)) { +            for (const profile of profiles) { +                if (!isObject(profile)) { continue; } +                const options = profile.options; +                if (!isObject(options)) { continue; } -    // Version check -    const version = data.version; -    if (!( -        typeof version === 'number' && -        Number.isFinite(version) && -        version === Math.floor(version) -    )) { -        throw new Error(`Invalid version: ${version}`); -    } +                const warnings2 = this._settingsImportSanitizeProfileOptions(options, dryRun); +                for (const warning of warnings2) { +                    warnings.add(warning); +                } +            } +        } -    if (!( -        version >= 0 && -        version <= SETTINGS_EXPORT_CURRENT_VERSION -    )) { -        throw new Error(`Unsupported version: ${version}`); +        return warnings;      } -    // Verify options exists -    let optionsFull = data.options; -    if (!isObject(optionsFull)) { -        throw new Error(`Invalid options type: ${typeof optionsFull}`); +    _utf8Decode(arrayBuffer) { +        try { +            return new TextDecoder('utf-8').decode(arrayBuffer); +        } catch (e) { +            const binaryString = String.fromCharCode.apply(null, new Uint8Array(arrayBuffer)); +            return decodeURIComponent(escape(binaryString)); +        }      } -    // Upgrade options -    optionsFull = optionsUpdateVersion(optionsFull, {}); +    async _importSettingsFile(file) { +        const dataString = this._utf8Decode(await this._readFileArrayBuffer(file)); +        const data = JSON.parse(dataString); -    // Check for warnings -    const sanitizationWarnings = _settingsImportSanitizeOptions(optionsFull, true); +        // Type check +        if (!isObject(data)) { +            throw new Error(`Invalid data type: ${typeof data}`); +        } -    // Show sanitization warnings -    if (sanitizationWarnings.size > 0) { -        const {result, sanitize} = await _showSettingsImportWarnings(sanitizationWarnings); -        if (!result) { return; } +        // Version check +        const version = data.version; +        if (!( +            typeof version === 'number' && +            Number.isFinite(version) && +            version === Math.floor(version) +        )) { +            throw new Error(`Invalid version: ${version}`); +        } -        if (sanitize !== false) { -            _settingsImportSanitizeOptions(optionsFull, false); +        if (!( +            version >= 0 && +            version <= this._currentVersion +        )) { +            throw new Error(`Unsupported version: ${version}`);          } -    } -    // Assign options -    await _settingsImportSetOptionsFull(optionsFull); +        // Verify options exists +        let optionsFull = data.options; +        if (!isObject(optionsFull)) { +            throw new Error(`Invalid options type: ${typeof optionsFull}`); +        } -    // Reload settings page -    window.location.reload(); -} +        // Upgrade options +        optionsFull = optionsUpdateVersion(optionsFull, {}); -function _onSettingsImportClick() { -    document.querySelector('#settings-import-file').click(); -} +        // Check for warnings +        const sanitizationWarnings = this._settingsImportSanitizeOptions(optionsFull, true); -function _onSettingsImportFileChange(e) { -    const files = e.target.files; -    if (files.length === 0) { return; } +        // Show sanitization warnings +        if (sanitizationWarnings.size > 0) { +            const {result, sanitize} = await this._showSettingsImportWarnings(sanitizationWarnings); +            if (!result) { return; } -    const file = files[0]; -    e.target.value = null; -    _importSettingsFile(file).catch(_showSettingsImportError); -} +            if (sanitize !== false) { +                this._settingsImportSanitizeOptions(optionsFull, false); +            } +        } +        // Assign options +        await this._settingsImportSetOptionsFull(optionsFull); -// Resetting +        // Reload settings page +        window.location.reload(); +    } -function _onSettingsResetClick() { -    $('#settings-reset-modal').modal('show'); -} +    _onSettingsImportClick() { +        document.querySelector('#settings-import-file').click(); +    } -async function _onSettingsResetConfirmClick() { -    $('#settings-reset-modal').modal('hide'); +    async _onSettingsImportFileChange(e) { +        const files = e.target.files; +        if (files.length === 0) { return; } -    // Get default options -    const optionsFull = optionsGetDefault(); +        const file = files[0]; +        e.target.value = null; +        try { +            await this._importSettingsFile(file); +        } catch (error) { +            this._showSettingsImportError(error); +        } +    } -    // Assign options -    await _settingsImportSetOptionsFull(optionsFull); +    // Resetting -    // Reload settings page -    window.location.reload(); -} +    _onSettingsResetClick() { +        $('#settings-reset-modal').modal('show'); +    } +    async _onSettingsResetConfirmClick() { +        $('#settings-reset-modal').modal('hide'); -// Setup +        // Get default options +        const optionsFull = optionsGetDefault(); -function backupInitialize() { -    document.querySelector('#settings-export').addEventListener('click', _onSettingsExportClick, false); -    document.querySelector('#settings-import').addEventListener('click', _onSettingsImportClick, false); -    document.querySelector('#settings-import-file').addEventListener('change', _onSettingsImportFileChange, false); -    document.querySelector('#settings-reset').addEventListener('click', _onSettingsResetClick, false); -    document.querySelector('#settings-reset-modal-confirm').addEventListener('click', _onSettingsResetConfirmClick, false); +        // Assign options +        await this._settingsImportSetOptionsFull(optionsFull); + +        // Reload settings page +        window.location.reload(); +    }  } diff --git a/ext/bg/js/settings/main.js b/ext/bg/js/settings/main.js index 60b9e008..f96167af 100644 --- a/ext/bg/js/settings/main.js +++ b/ext/bg/js/settings/main.js @@ -16,13 +16,13 @@   */  /* global + * SettingsBackup   * ankiInitialize   * ankiTemplatesInitialize   * ankiTemplatesUpdateValue   * api   * appearanceInitialize   * audioSettingsInitialize - * backupInitialize   * dictSettingsInitialize   * getOptionsContext   * onAnkiOptionsChanged @@ -302,7 +302,7 @@ async function onReady() {      await dictSettingsInitialize();      ankiInitialize();      ankiTemplatesInitialize(); -    backupInitialize(); +    new SettingsBackup().prepare();      storageInfoInitialize(); diff --git a/ext/bg/js/util.js b/ext/bg/js/util.js index 8f86e47a..edc19c6e 100644 --- a/ext/bg/js/util.js +++ b/ext/bg/js/util.js @@ -65,12 +65,3 @@ function utilBackend() {      }      return backend;  } - -function utilReadFileArrayBuffer(file) { -    return new Promise((resolve, reject) => { -        const reader = new FileReader(); -        reader.onload = () => resolve(reader.result); -        reader.onerror = () => reject(reader.error); -        reader.readAsArrayBuffer(file); -    }); -} |