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, |