diff options
Diffstat (limited to 'ext/js/display')
| -rw-r--r-- | ext/js/display/display.js | 6 | ||||
| -rw-r--r-- | ext/js/display/element-overflow-controller.js | 129 | 
2 files changed, 135 insertions, 0 deletions
| 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); +        } +    } +} |