diff options
author | toasted-nutbread <toasted-nutbread@users.noreply.github.com> | 2021-03-31 18:17:28 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-03-31 18:17:28 -0400 |
commit | bcbd413e571d772a4438f57138169ad1a6a3b5a8 (patch) | |
tree | e8768f5e91e62759e2dce7179359aa83ff6e4d0f | |
parent | cbcfdcacaf68efb09e47932f5b14881b982aecd2 (diff) |
Definition collapsing when overflowing (#1575)
* Add double-down-chevron.svg
* Add options
* Update dictionary importers
* Update settings
* Add support for collapsible definitions
* Improve case when there is a very small amount of overflow
* Fix incorrect enabled state of newly imported dictionaries
-rw-r--r-- | ext/css/display.css | 71 | ||||
-rw-r--r-- | ext/css/material.css | 1 | ||||
-rw-r--r-- | ext/css/settings.css | 39 | ||||
-rw-r--r-- | ext/data/schemas/options-schema.json | 8 | ||||
-rw-r--r-- | ext/display-templates.html | 11 | ||||
-rw-r--r-- | ext/images/double-down-chevron.svg | 1 | ||||
-rw-r--r-- | ext/js/data/options-util.js | 4 | ||||
-rw-r--r-- | ext/js/display/display.js | 6 | ||||
-rw-r--r-- | ext/js/display/element-overflow-controller.js | 129 | ||||
-rw-r--r-- | ext/js/pages/settings/backup-controller.js | 2 | ||||
-rw-r--r-- | ext/js/pages/settings/collapsible-dictionary-controller.js | 159 | ||||
-rw-r--r-- | ext/js/pages/settings/dictionary-controller.js | 19 | ||||
-rw-r--r-- | ext/js/pages/settings/dictionary-import-controller.js | 11 | ||||
-rw-r--r-- | ext/js/pages/settings/settings-main.js | 4 | ||||
-rw-r--r-- | ext/popup.html | 1 | ||||
-rw-r--r-- | ext/search.html | 1 | ||||
-rw-r--r-- | ext/settings.html | 40 | ||||
-rw-r--r-- | resources/icons.svg | 26 | ||||
-rw-r--r-- | test/test-options-util.js | 17 |
19 files changed, 520 insertions, 30 deletions
diff --git a/ext/css/display.css b/ext/css/display.css index 887a04a6..c8669fa2 100644 --- a/ext/css/display.css +++ b/ext/css/display.css @@ -101,6 +101,9 @@ --animation-duration: 0.125s; --animation-duration2: calc(2 * var(--animation-duration)); + --collapsible-definition-line-count: 3; + --collapsible-definition-test-offset: 0.2em; + /* Colors */ --background-color: #ffffff; --glossary-image-background-color: #eeeeee; @@ -1154,6 +1157,74 @@ button.action-button[data-icon=source-term]::before { opacity: 0; white-space: pre-wrap; } +.definition-item { + display: list-item; + position: relative; +} +.definition-item-inner.collapsible.collapsed { + max-height: calc(1em * var(--collapsible-definition-line-count) * var(--line-height)); + overflow: hidden; +} +.definition-item-inner.collapse-test { + max-height: calc(1em * var(--collapsible-definition-line-count) * var(--line-height) + var(--collapsible-definition-test-offset)); + overflow: hidden; +} +.definition-item-inner { + display: flex; + flex-flow: row nowrap; +} +.definition-item-content { + flex: 1 1 auto; + background-color: transparent; + transition: background-color var(--animation-duration) ease-in-out; +} +button.definition-item-expansion-button { + --button-content-color: var(--text-color-light4); + --button-border-color: transparent; + --button-background-color: transparent; + + --button-hover-content-color: var(--text-color-light1); + --button-hover-border-color: var(--accent-color-lighter); + --button-hover-background-color: var(--accent-color-lighter); + + --button-active-content-color: var(--text-color); + --button-active-border-color: var(--accent-color-light); + --button-active-background-color: var(--accent-color-light); + + --button-padding-vertical: 0; + --button-padding-horizontal: 0.125em; + + flex: 0 0 auto; + order: 1; + border-radius: 0; + border: 0; +} +.definition-item-inner:not(.collapsible)>button.definition-item-expansion-button { + display: none; +} +button.definition-item-expansion-button:hover+.definition-item-content, +button.definition-item-expansion-button:active+.definition-item-content, +button.definition-item-expansion-button:focus+.definition-item-content { + background-color: var(--accent-color-transparent25); +} +button.definition-item-expansion-button:focus:not(:focus-visible)+.definition-item-content { + background-color: transparent; +} +button.definition-item-expansion-button:focus:hover+.definition-item-content, +button.definition-item-expansion-button:focus:active+.definition-item-content, +button.definition-item-expansion-button:focus:focus-visible+.definition-item-content { + background-color: var(--accent-color-transparent25); +} +.definition-item-expansion-button-icon { + transform: rotate(0deg); + width: calc(16em / var(--font-size-no-units)); + height: calc(16em / var(--font-size-no-units)); + background-color: var(--button-current-content-color); + transition: background-color var(--animation-duration) ease-in-out; +} +.definition-item-inner.collapsible:not(.collapsed)>button.definition-item-expansion-button>.definition-item-expansion-button-icon { + transform: rotate(180deg); +} /* Frequencies */ diff --git a/ext/css/material.css b/ext/css/material.css index 703f1268..08345769 100644 --- a/ext/css/material.css +++ b/ext/css/material.css @@ -234,6 +234,7 @@ .icon[data-icon=question-mark-thick] { --icon-image: url(/images/question-mark-thick.svg); } .icon[data-icon=left-chevron] { --icon-image: url(/images/left-chevron.svg); } .icon[data-icon=right-chevron] { --icon-image: url(/images/right-chevron.svg); } +.icon[data-icon=double-down-chevron] { --icon-image: url(/images/double-down-chevron.svg); } .icon[data-icon=plus-thick] { --icon-image: url(/images/plus-thick.svg); } .icon[data-icon=clipboard] { --icon-image: url(/images/clipboard.svg); } .icon[data-icon=key] { --icon-image: url(/images/key.svg); } diff --git a/ext/css/settings.css b/ext/css/settings.css index e2485925..22439409 100644 --- a/ext/css/settings.css +++ b/ext/css/settings.css @@ -47,6 +47,7 @@ --outline-item-icon-size: 32px; --input-short-width: calc(var(--input-width-large) / 2 - var(--padding) / 2); --input-short-height: 24px; + --input-medium-width: calc(var(--input-width-large) * 0.75); --fab-button-size: 56px; --fab-button-padding: 16px; --modal-width: 600px; @@ -657,6 +658,11 @@ input[type=number].short-width, select.short-width { width: var(--input-short-width); } +input[type=text].medium-width, +input[type=number].medium-width, +select.medium-width { + width: var(--input-medium-width); +} input[type=text].short-height, input[type=number].short-height, select.short-height { @@ -2125,6 +2131,39 @@ button.hotkey-list-item-enabled-button[data-scope-count='0'] { background-color: var(--warning-color); } +.collapsible-dictionary-list { + width: 100%; + display: flex; + flex-flow: column nowrap; + align-items: stretch; +} +.collapsible-dictionary-item { + display: flex; + flex-flow: row nowrap; + align-items: center; + margin-left: calc(var(--modal-padding-horizontal) * -1); + margin-right: calc(var(--modal-padding-horizontal) * -1); + padding: var(--settings-group-inner-horizontal-padding-fourth) var(--modal-padding-horizontal); +} +.collapsible-dictionary-item:not(:first-child) { + border-top: var(--thin-border-size) solid var(--separator-color2); +} +.collapsible-dictionary-cell { + display: flex; + flex-flow: row nowrap; + align-items: center; +} +.collapsible-dictionary-cell:first-of-type { + flex: 1 1 auto; +} +.collapsible-dictionary-cell:not(:first-of-type) { + flex: 0 0 auto; + margin-left: 1em; +} +.collapsible-dictionary-cell-label { + margin-left: 0.375em; +} + /* Generic layouts */ .margin-above { diff --git a/ext/data/schemas/options-schema.json b/ext/data/schemas/options-schema.json index 89e2d361..d829b392 100644 --- a/ext/data/schemas/options-schema.json +++ b/ext/data/schemas/options-schema.json @@ -732,7 +732,8 @@ "required": [ "priority", "enabled", - "allowSecondarySearches" + "allowSecondarySearches", + "definitionsCollapsible" ], "properties": { "priority": { @@ -746,6 +747,11 @@ "allowSecondarySearches": { "type": "boolean", "default": false + }, + "definitionsCollapsible": { + "type": "string", + "enum": ["not-collapsible", "expanded", "collapsed"], + "default": "not-collapsible" } } } diff --git a/ext/display-templates.html b/ext/display-templates.html index 20978d65..25beacb0 100644 --- a/ext/display-templates.html +++ b/ext/display-templates.html @@ -45,7 +45,16 @@ <div class="expression-tag-list tag-list"></div> </div> </div></template> -<template id="definition-item-template"><li class="definition-item"><div class="definition-tag-list tag-list"></div><div class="definition-disambiguation-list"></div><ul class="glossary-list"></ul></li></template> +<template id="definition-item-template" data-remove-whitespace-text="true"><li class="definition-item"> + <div class="definition-item-inner"> + <button class="definition-item-expansion-button"><div class="definition-item-expansion-button-icon icon" data-icon="double-down-chevron"></div></button> + <div class="definition-item-content"> + <div class="definition-tag-list tag-list"></div> + <div class="definition-disambiguation-list"></div> + <ul class="glossary-list"></ul> + </div> + </div> +</li></template> <template id="definition-disambiguation-template"><span class="definition-disambiguation"></span></template> <template id="glossary-item-template"><li class="glossary-item click-scannable"><span class="glossary-separator"> </span><span class="glossary"></span></li></template> <template id="glossary-item-image-template"><li class="glossary-item click-scannable" data-has-image="true"><span class="glossary-separator"> </span><span class="glossary"><a class="glossary-image-link" target="_blank" rel="noreferrer noopener"><span class="glossary-image-container"><span class="glossary-image-aspect-ratio-sizer"></span><img class="glossary-image" alt=""><span class="glossary-image-container-overlay"></span></span><span class="glossary-image-link-text">Image</span></a> <span class="glossary-image-description"></span></span></li></template> diff --git a/ext/images/double-down-chevron.svg b/ext/images/double-down-chevron.svg new file mode 100644 index 00000000..90684054 --- /dev/null +++ b/ext/images/double-down-chevron.svg @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M3 .5l-2 2 7 7 7-7-2-2-5 5-5-5zm0 6l-2 2 7 7 7-7-2-2-5 5-5-5z"/></svg>
\ No newline at end of file diff --git a/ext/js/data/options-util.js b/ext/js/data/options-util.js index 5b2e2bd3..ca122e89 100644 --- a/ext/js/data/options-util.js +++ b/ext/js/data/options-util.js @@ -755,9 +755,13 @@ class OptionsUtil { // Removed global option useSettingsV2. // Added part-of-speech field template. // Added an argument to hotkey inputs. + // Added definitionsCollapsible to dictionary options. await this._applyAnkiFieldTemplatesPatch(options, '/data/templates/anki-field-templates-upgrade-v10.handlebars'); delete options.global.useSettingsV2; for (const profile of options.profiles) { + for (const dictionaryOptions of Object.values(profile.options.dictionaries)) { + dictionaryOptions.definitionsCollapsible = 'not-collapsible'; + } for (const hotkey of profile.options.inputs.hotkeys) { switch (hotkey.action) { case 'previousEntry': diff --git a/ext/js/display/display.js b/ext/js/display/display.js index ee2448d6..b17a6168 100644 --- a/ext/js/display/display.js +++ b/ext/js/display/display.js @@ -23,6 +23,7 @@ * DisplayHistory * DisplayNotification * DocumentUtil + * ElementOverflowController * FrameEndpoint * Frontend * HotkeyHelpController @@ -114,6 +115,7 @@ class Display extends EventDispatcher { this._ankiNoteNotificationEventListeners = null; this._queryPostProcessor = null; this._optionToggleHotkeyHandler = new OptionToggleHotkeyHandler(this); + this._elementOverflowController = new ElementOverflowController(); this._hotkeyHandler.registerActions([ ['close', () => { this._onHotkeyClose(); }], @@ -305,6 +307,7 @@ class Display extends EventDispatcher { this._hotkeyHelpController.setOptions(options); this._displayGenerator.updateHotkeys(); this._hotkeyHelpController.setupNode(document.documentElement); + this._elementOverflowController.setOptions(options); this._queryParser.setOptions({ selectedParser: options.parsing.selectedParser, @@ -535,6 +538,7 @@ class Display extends EventDispatcher { this._hideAnkiNoteErrors(false); this._definitions = []; this._definitionNodes = []; + this._elementOverflowController.clearElements(); // Prepare const urlSearchParams = new URLSearchParams(location.search); @@ -966,6 +970,8 @@ class Display extends EventDispatcher { if (focusEntry === i) { this._focusEntry(i, false); } + + this._elementOverflowController.addElements(entry); } if (typeof scrollX === 'number' || typeof scrollY === 'number') { diff --git a/ext/js/display/element-overflow-controller.js b/ext/js/display/element-overflow-controller.js new file mode 100644 index 00000000..ccea1417 --- /dev/null +++ b/ext/js/display/element-overflow-controller.js @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2021 Yomichan Authors + * + * 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 <https://www.gnu.org/licenses/>. + */ + +class ElementOverflowController { + constructor() { + this._elements = []; + this._checkTimer = null; + this._eventListeners = new EventListenerCollection(); + this._windowEventListeners = new EventListenerCollection(); + this._dictionaries = new Map(); + this._updateBind = this._update.bind(this); + this._onWindowResizeBind = this._onWindowResize.bind(this); + this._onToggleButtonClickBind = this._onToggleButtonClick.bind(this); + } + + setOptions(options) { + this._dictionaries.clear(); + for (const [dictionary, {definitionsCollapsible}] of Object.entries(options.dictionaries)) { + let collapsible = false; + let collapsed = false; + switch (definitionsCollapsible) { + case 'expanded': + collapsible = true; + break; + case 'collapsed': + collapsible = true; + collapsed = true; + break; + } + if (!collapsible) { continue; } + this._dictionaries.set(dictionary, {collapsed}); + } + } + + addElements(entry) { + if (this._dictionaries.size === 0) { return; } + + const elements = entry.querySelectorAll('.definition-item-inner'); + let any = false; + for (const element of elements) { + const {dictionary} = element.parentNode.dataset; + const dictionaryInfo = this._dictionaries.get(dictionary); + if (typeof dictionaryInfo === 'undefined') { continue; } + + this._updateElement(element); + this._elements.push(element); + + if (dictionaryInfo.collapsed) { + element.classList.add('collapsed'); + } + + const button = element.querySelector('.definition-item-expansion-button'); + if (button !== null) { + this._eventListeners.addEventListener(button, 'click', this._onToggleButtonClickBind, false); + } + + any = true; + } + + if (any && this._windowEventListeners.size === 0) { + this._windowEventListeners.addEventListener(window, 'resize', this._onWindowResizeBind, false); + } + } + + clearElements() { + if (this._elements.length === 0) { return; } + this._elements = []; + this._windowEventListeners.removeAllEventListeners(); + } + + // Private + + _onWindowResize() { + if (this._checkTimer !== null) { + this._cancelIdleCallback(this._checkTimer); + } + this._checkTimer = this._requestIdleCallback(this._updateBind, 100); + } + + _onToggleButtonClick(e) { + const container = e.currentTarget.closest('.definition-item-inner'); + if (container === null) { return; } + container.classList.toggle('collapsed'); + } + + _update() { + for (const element of this._elements) { + this._updateElement(element); + } + } + + _updateElement(element) { + const {classList} = element; + classList.add('collapse-test'); + const collapsible = element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth; + classList.toggle('collapsible', collapsible); + classList.remove('collapse-test'); + } + + _requestIdleCallback(callback, timeout) { + if (typeof requestIdleCallback === 'function') { + return requestIdleCallback(callback, {timeout}); + } else { + return setTimeout(callback, timeout); + } + } + + _cancelIdleCallback(handle) { + if (typeof cancelIdleCallback === 'function') { + cancelIdleCallback(handle); + } else { + clearTimeout(handle); + } + } +} diff --git a/ext/js/pages/settings/backup-controller.js b/ext/js/pages/settings/backup-controller.js index 6f6614b6..933f0e2a 100644 --- a/ext/js/pages/settings/backup-controller.js +++ b/ext/js/pages/settings/backup-controller.js @@ -404,7 +404,7 @@ class BackupController { const dictionaries = await this._settingsController.getDictionaryInfo(); for (const {options: {dictionaries: optionsDictionaries}} of optionsFull.profiles) { for (const {title} of dictionaries) { - optionsDictionaries[title] = DictionaryController.createDefaultDictionarySettings(); + optionsDictionaries[title] = DictionaryController.createDefaultDictionarySettings(false); } } diff --git a/ext/js/pages/settings/collapsible-dictionary-controller.js b/ext/js/pages/settings/collapsible-dictionary-controller.js new file mode 100644 index 00000000..a3a1df62 --- /dev/null +++ b/ext/js/pages/settings/collapsible-dictionary-controller.js @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2021 Yomichan Authors + * + * 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 <https://www.gnu.org/licenses/>. + */ + +/* global + * ObjectPropertyAccessor + */ + +class CollapsibleDictionaryController { + constructor(settingsController) { + this._settingsController = settingsController; + this._getDictionaryInfoToken = null; + this._dictionaryInfoMap = new Map(); + this._container = null; + this._selects = []; + this._allSelect = null; + this._eventListeners = new EventListenerCollection(); + } + + async prepare() { + this._container = document.querySelector('#collapsible-dictionary-list'); + + await this._onDatabaseUpdated(); + await this._updateOptions(); + + yomichan.on('databaseUpdated', this._onDatabaseUpdated.bind(this)); + this._settingsController.on('optionsChanged', this._onOptionsChanged.bind(this)); + } + + // Private + + async _onDatabaseUpdated() { + const token = {}; + this._getDictionaryInfoToken = token; + const dictionaries = await this._settingsController.getDictionaryInfo(); + if (this._getDictionaryInfoToken !== token) { return; } + this._getDictionaryInfoToken = null; + + this._dictionaryInfoMap.clear(); + for (const entry of dictionaries) { + this._dictionaryInfoMap.set(entry.title, entry); + } + + await this._updateOptions(); + } + + _onOptionsChanged({options}) { + this._eventListeners.removeAllEventListeners(); + this._selects = []; + + const {dictionaries} = options; + + const fragment = document.createDocumentFragment(); + + this._setupAllSelect(fragment, options); + + for (const dictionary of Object.keys(dictionaries)) { + const dictionaryInfo = this._dictionaryInfoMap.get(dictionary); + if (typeof dictionaryInfo === 'undefined') { continue; } + + const select = this._addSelect(fragment, dictionary, `rev.${dictionaryInfo.revision}`); + select.dataset.setting = ObjectPropertyAccessor.getPathString(['dictionaries', dictionary, 'definitionsCollapsible']); + this._eventListeners.addEventListener(select, 'settingChanged', this._onDefinitionsCollapsibleChange.bind(this), false); + + this._selects.push(select); + } + + this._container.textContent = ''; + this._container.appendChild(fragment); + } + + _onDefinitionsCollapsibleChange() { + this._updateAllSelectFresh(); + } + + _onAllSelectChange(e) { + const {value} = e.currentTarget; + if (value === 'varies') { return; } + this._setDefinitionsCollapsibleAll(value); + } + + _setupAllSelect(fragment, options) { + const select = this._addSelect(fragment, 'All', ''); + + const option = document.createElement('option'); + option.value = 'varies'; + option.textContent = 'Varies'; + option.disabled = true; + select.appendChild(option); + + this._eventListeners.addEventListener(select, 'change', this._onAllSelectChange.bind(this), false); + + this._allSelect = select; + this._updateAllSelect(options); + } + + _addSelect(fragment, dictionary, version) { + const node = this._settingsController.instantiateTemplate('collapsible-dictionary-item'); + fragment.appendChild(node); + + const nameNode = node.querySelector('.dictionary-title'); + nameNode.textContent = dictionary; + + const versionNode = node.querySelector('.dictionary-version'); + versionNode.textContent = version; + + return node.querySelector('.definitions-collapsible'); + } + + async _updateOptions() { + const options = await this._settingsController.getOptions(); + this._onOptionsChanged({options}); + } + + async _updateAllSelectFresh() { + this._updateAllSelect(await this._settingsController.getOptions()); + } + + _updateAllSelect(options) { + let value = null; + let varies = false; + for (const {definitionsCollapsible} of Object.values(options.dictionaries)) { + if (value === null) { + value = definitionsCollapsible; + } else if (value !== definitionsCollapsible) { + varies = true; + break; + } + } + + this._allSelect.value = (varies || value === null ? 'varies' : value); + } + + async _setDefinitionsCollapsibleAll(value) { + const options = await this._settingsController.getOptions(); + const targets = []; + for (const dictionary of Object.keys(options.dictionaries)) { + const path = ObjectPropertyAccessor.getPathString(['dictionaries', dictionary, 'definitionsCollapsible']); + targets.push({action: 'set', path, value}); + } + await this._settingsController.modifyProfileSettings(targets); + for (const select of this._selects) { + select.value = value; + } + } +}
\ No newline at end of file diff --git a/ext/js/pages/settings/dictionary-controller.js b/ext/js/pages/settings/dictionary-controller.js index 17abfa13..65edcb67 100644 --- a/ext/js/pages/settings/dictionary-controller.js +++ b/ext/js/pages/settings/dictionary-controller.js @@ -248,6 +248,15 @@ class DictionaryController { this._updateDictionariesEnabledWarnings(options); } + static createDefaultDictionarySettings(enabled) { + return { + priority: 0, + enabled, + allowSecondarySearches: false, + definitionsCollapsible: 'not-collapsible' + }; + } + // Private _onOptionsChanged({options}) { @@ -535,7 +544,7 @@ class DictionaryController { targets.push({ action: 'set', path, - value: DictionaryController.createDefaultDictionarySettings() + value: DictionaryController.createDefaultDictionarySettings(false) }); } } @@ -548,12 +557,4 @@ class DictionaryController { _triggerStorageChanged() { yomichan.trigger('storageChanged'); } - - static createDefaultDictionarySettings() { - return { - enabled: false, - allowSecondarySearches: false, - priority: 0 - }; - } } diff --git a/ext/js/pages/settings/dictionary-import-controller.js b/ext/js/pages/settings/dictionary-import-controller.js index ce724263..afa45899 100644 --- a/ext/js/pages/settings/dictionary-import-controller.js +++ b/ext/js/pages/settings/dictionary-import-controller.js @@ -16,6 +16,7 @@ */ /* global + * DictionaryController * DictionaryDatabase * DictionaryImporter * ObjectPropertyAccessor @@ -212,7 +213,7 @@ class DictionaryImportController { const profileCount = optionsFull.profiles.length; for (let i = 0; i < profileCount; ++i) { const {options} = optionsFull.profiles[i]; - const value = this._createDictionaryOptions(); + const value = DictionaryController.createDefaultDictionarySettings(true); const path1 = ObjectPropertyAccessor.getPathString(['profiles', i, 'options', 'dictionaries', title]); targets.push({action: 'set', path: path1, value}); @@ -291,14 +292,6 @@ class DictionaryImportController { }); } - _createDictionaryOptions() { - return { - priority: 0, - enabled: true, - allowSecondarySearches: false - }; - } - _errorToString(error) { error = (typeof error.toString === 'function' ? error.toString() : `${error}`); diff --git a/ext/js/pages/settings/settings-main.js b/ext/js/pages/settings/settings-main.js index 3618836c..2560685c 100644 --- a/ext/js/pages/settings/settings-main.js +++ b/ext/js/pages/settings/settings-main.js @@ -20,6 +20,7 @@ * AnkiTemplatesController * AudioController * BackupController + * CollapsibleDictionaryController * DictionaryController * DictionaryImportController * DocumentFocusController @@ -146,6 +147,9 @@ async function setupGenericSettingsController(genericSettingController) { const mecabController = new MecabController(); mecabController.prepare(); + const collapsibleDictionaryController = new CollapsibleDictionaryController(settingsController); + collapsibleDictionaryController.prepare(); + await Promise.all(preparePromises); document.documentElement.dataset.loaded = 'true'; diff --git a/ext/popup.html b/ext/popup.html index 36cff420..76a0032b 100644 --- a/ext/popup.html +++ b/ext/popup.html @@ -105,6 +105,7 @@ <script src="/js/display/display-notification.js"></script> <script src="/js/display/display-profile-selection.js"></script> <script src="/js/display/display-resizer.js"></script> +<script src="/js/display/element-overflow-controller.js"></script> <script src="/js/display/option-toggle-hotkey-handler.js"></script> <script src="/js/display/query-parser.js"></script> <script src="/js/dom/document-focus-controller.js"></script> diff --git a/ext/search.html b/ext/search.html index 4ef8860f..d67dca45 100644 --- a/ext/search.html +++ b/ext/search.html @@ -89,6 +89,7 @@ <script src="/js/display/display-generator.js"></script> <script src="/js/display/display-history.js"></script> <script src="/js/display/display-notification.js"></script> +<script src="/js/display/element-overflow-controller.js"></script> <script src="/js/display/option-toggle-hotkey-handler.js"></script> <script src="/js/display/query-parser.js"></script> <script src="/js/display/search-display-controller.js"></script> diff --git a/ext/settings.html b/ext/settings.html index 3de183db..712dda3f 100644 --- a/ext/settings.html +++ b/ext/settings.html @@ -737,6 +737,14 @@ </p> </div> </div> + <div class="settings-item settings-item-button advanced-only" data-modal-action="show,collapsible-dictionaries"><div class="settings-item-inner"> + <div class="settings-item-left"> + <div class="settings-item-label">Configure collapsible dictionaries…</div> + </div> + <div class="settings-item-right open-panel-button-container"> + <button class="icon-button"><span class="icon-button-inner"><span class="icon" data-icon="material-right-arrow"></span></span></button> + </div> + </div></div> <div class="settings-item settings-item-button" data-modal-action="show,custom-css"><div class="settings-item-inner"> <div class="settings-item-left"> <div class="settings-item-label">Configure custom CSS…</div> @@ -2095,6 +2103,24 @@ </div> </div></div> +<div id="collapsible-dictionaries-modal" class="modal" tabindex="-1" role="dialog" hidden><div class="modal-content"> + <div class="modal-header"> + <div class="modal-title">Collapsible Dictionaries</div> + <div class="modal-header-button-container"> + <div class="modal-header-button-group"> + <button class="icon-button modal-header-button" data-modal-action="expand"><span class="icon-button-inner"><span class="icon" data-icon="expand"></span></span></button> + <button class="icon-button modal-header-button" data-modal-action="collapse"><span class="icon-button-inner"><span class="icon" data-icon="collapse"></span></span></button> + </div> + </div> + </div> + <div class="modal-body"> + <div id="collapsible-dictionary-list" class="collapsible-dictionary-list"></div> + </div> + <div class="modal-footer"> + <button data-modal-action="hide">Close</button> + </div> +</div></div> + <!-- Dictionary templates --> <template id="dictionary-template"><div class="settings-item dictionary-item"> @@ -2187,6 +2213,19 @@ </div> </div></div></template> +<template id="collapsible-dictionary-item-template"><div class="collapsible-dictionary-item"> + <div class="collapsible-dictionary-cell"> + <span class="dictionary-info-label"><strong class="dictionary-title"></strong> <span class="light dictionary-version"></span></span> + </div> + <div class="collapsible-dictionary-cell"> + <select class="definitions-collapsible medium-width"> + <option value="not-collapsible">Not collapsible</option> + <option value="collapsed">Collapsed</option> + <option value="expanded">Expanded</option> + </select> + </div> +</div></template> + <!-- Custom CSS modal --> <div id="custom-css-modal" class="modal modal-left" tabindex="-1" role="dialog" hidden><div class="modal-content-container1"> @@ -3280,6 +3319,7 @@ <script src="/js/pages/settings/anki-templates-controller.js"></script> <script src="/js/pages/settings/audio-controller.js"></script> <script src="/js/pages/settings/backup-controller.js"></script> +<script src="/js/pages/settings/collapsible-dictionary-controller.js"></script> <script src="/js/pages/settings/dictionary-controller.js"></script> <script src="/js/pages/settings/dictionary-import-controller.js"></script> <script src="/js/pages/settings/extension-keyboard-shortcuts-controller.js"></script> diff --git a/resources/icons.svg b/resources/icons.svg index 9621d551..04690850 100644 --- a/resources/icons.svg +++ b/resources/icons.svg @@ -12,7 +12,7 @@ inkscape:export-ydpi="192" inkscape:export-xdpi="192" sodipodi:docname="icons.svg" - inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)" + inkscape:version="0.92.4 (5da689c313, 2019-01-14)" id="svg8" version="1.1" viewBox="0 0 16 16" @@ -26,17 +26,17 @@ inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="22.627417" - inkscape:cx="8.8042238" - inkscape:cy="5.555245" + inkscape:cx="8.8116705" + inkscape:cy="3.394636" inkscape:document-units="px" - inkscape:current-layer="layer51" + inkscape:current-layer="g5298" showgrid="true" units="px" inkscape:snap-center="true" inkscape:window-width="1920" inkscape:window-height="1017" inkscape:window-x="-8" - inkscape:window-y="-8" + inkscape:window-y="-2" inkscape:window-maximized="1" viewbox-height="16" showguides="true" @@ -1605,11 +1605,23 @@ <g inkscape:groupmode="layer" id="layer51" - inkscape:label="Key"> + inkscape:label="Key" + style="display:none"> <path id="path1091" style="fill:#000000;stroke-linecap:round;stroke-linejoin:round" d="M 10.662109,1 C 8.349293,1.0478398 6.4995053,2.9366893 6.5,5.25 6.5002349,5.62267 6.5494854,5.9936904 6.6464844,6.3535156 L 0.75,12.25 V 13.5 L 2,15 H 4 L 4.5,14.5 V 13 H 6 V 11.5 H 7.5 V 10 H 9 L 9.6464844,9.3535156 C 10.00631,9.4505146 10.37733,9.4997651 10.75,9.5 13.09721,9.5 15,7.5972102 15,5.25 15,2.9027898 13.09721,1 10.75,1 10.7207,0.99969706 10.6914,0.99969706 10.66211,1 Z M 11.5,3 C 12.328427,3 13,3.6715729 13,4.5 13,5.3284271 12.328427,6 11.5,6 10.671573,6 10,5.3284271 10,4.5 10,3.6715729 10.671573,3 11.5,3 Z M 6.75,7.75 7.5,8.5 2.75,13.25 2,12.5 Z" - sodipodi:nodetypes="scccccccccccccccsccscssscccccc" /> + sodipodi:nodetypes="scccccccccccccccsccscssscccccc" + inkscape:connector-curvature="0" /> + </g> + <g + style="display:inline" + inkscape:groupmode="layer" + id="g5298" + inkscape:label="Double Down Chevron"> + <path + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="M 3 0.5 L 1 2.5 L 8 9.5 L 15 2.5 L 13 0.5 L 8 5.5 L 3 0.5 z M 3 6.5 L 1 8.5 L 8 15.5 L 15 8.5 L 13 6.5 L 8 11.5 L 3 6.5 z " + id="path5300" /> </g> </svg> diff --git a/test/test-options-util.js b/test/test-options-util.js index 7b9e6e4b..ce12bca9 100644 --- a/test/test-options-util.js +++ b/test/test-options-util.js @@ -135,7 +135,13 @@ function createProfileOptionsTestData1() { convertKatakanaToHiragana: 'variant', collapseEmphaticSequences: 'false' }, - dictionaries: {}, + dictionaries: { + 'Test Dictionary': { + priority: 0, + enabled: true, + allowSecondarySearches: false + } + }, parsing: { enableScanningParser: true, enableMecabParser: false, @@ -401,7 +407,14 @@ function createProfileOptionsUpdatedTestData1() { groups: [] } }, - dictionaries: {}, + dictionaries: { + 'Test Dictionary': { + priority: 0, + enabled: true, + allowSecondarySearches: false, + definitionsCollapsible: 'not-collapsible' + } + }, parsing: { enableScanningParser: true, enableMecabParser: false, |