diff options
Diffstat (limited to 'ext/mixed/js')
| -rw-r--r-- | ext/mixed/js/api.js | 6 | ||||
| -rw-r--r-- | ext/mixed/js/audio.js | 9 | ||||
| -rw-r--r-- | ext/mixed/js/core.js | 45 | ||||
| -rw-r--r-- | ext/mixed/js/display-generator.js | 26 | ||||
| -rw-r--r-- | ext/mixed/js/display.js | 131 | ||||
| -rw-r--r-- | ext/mixed/js/text-scanner.js | 24 | 
6 files changed, 121 insertions, 120 deletions
| diff --git a/ext/mixed/js/api.js b/ext/mixed/js/api.js index 0b1e7e4f..86bdc73c 100644 --- a/ext/mixed/js/api.js +++ b/ext/mixed/js/api.js @@ -58,15 +58,15 @@ function apiDefinitionAdd(definition, mode, context, optionsContext) {  }  function apiDefinitionsAddable(definitions, modes, optionsContext) { -    return _apiInvoke('definitionsAddable', {definitions, modes, optionsContext}).catch(() => null); +    return _apiInvoke('definitionsAddable', {definitions, modes, optionsContext});  }  function apiNoteView(noteId) {      return _apiInvoke('noteView', {noteId});  } -function apiTemplateRender(template, data, dynamic) { -    return _apiInvoke('templateRender', {data, template, dynamic}); +function apiTemplateRender(template, data) { +    return _apiInvoke('templateRender', {data, template});  }  function apiAudioGetUrl(definition, source, optionsContext) { diff --git a/ext/mixed/js/audio.js b/ext/mixed/js/audio.js index 76a3e7da..47db5c75 100644 --- a/ext/mixed/js/audio.js +++ b/ext/mixed/js/audio.js @@ -114,8 +114,11 @@ function audioGetFromUrl(url, willDownload) {  async function audioGetFromSources(expression, sources, optionsContext, willDownload, cache=null) {      const key = `${expression.expression}:${expression.reading}`; -    if (cache !== null && hasOwn(cache, expression)) { -        return cache[key]; +    if (cache !== null) { +        const cacheValue = cache.get(expression); +        if (typeof cacheValue !== 'undefined') { +            return cacheValue; +        }      }      for (let i = 0, ii = sources.length; i < ii; ++i) { @@ -133,7 +136,7 @@ async function audioGetFromSources(expression, sources, optionsContext, willDown              }              const result = {audio, url, source};              if (cache !== null) { -                cache[key] = result; +                cache.set(key, result);              }              return result;          } catch (e) { diff --git a/ext/mixed/js/core.js b/ext/mixed/js/core.js index ca9e98e5..330a30fb 100644 --- a/ext/mixed/js/core.js +++ b/ext/mixed/js/core.js @@ -113,11 +113,7 @@ function toIterable(value) {      if (value !== null && typeof value === 'object') {          const length = value.length;          if (typeof length === 'number' && Number.isFinite(length)) { -            const array = []; -            for (let i = 0; i < length; ++i) { -                array.push(value[i]); -            } -            return array; +            return Array.from(value);          }      } @@ -240,6 +236,29 @@ class EventDispatcher {      }  } +class EventListenerCollection { +    constructor() { +        this._eventListeners = []; +    } + +    get size() { +        return this._eventListeners.length; +    } + +    addEventListener(node, type, listener, options) { +        node.addEventListener(type, listener, options); +        this._eventListeners.push([node, type, listener, options]); +    } + +    removeAllEventListeners() { +        if (this._eventListeners.length === 0) { return; } +        for (const [node, type, listener, options] of this._eventListeners) { +            node.removeEventListener(type, listener, options); +        } +        this._eventListeners = []; +    } +} +  /*   * Default message handlers @@ -252,7 +271,7 @@ const yomichan = (() => {              this._messageHandlers = new Map([                  ['getUrl', this._onMessageGetUrl.bind(this)], -                ['optionsUpdate', this._onMessageOptionsUpdate.bind(this)], +                ['optionsUpdated', this._onMessageOptionsUpdated.bind(this)],                  ['zoomChanged', this._onMessageZoomChanged.bind(this)]              ]); @@ -261,6 +280,16 @@ const yomichan = (() => {          // Public +        generateId(length) { +            const array = new Uint8Array(length); +            window.crypto.getRandomValues(array); +            let id = ''; +            for (const value of array) { +                id += value.toString(16).padStart(2, '0'); +            } +            return id; +        } +          triggerOrphaned(error) {              this.trigger('orphaned', {error});          } @@ -280,8 +309,8 @@ const yomichan = (() => {              return {url: window.location.href};          } -        _onMessageOptionsUpdate({source}) { -            this.trigger('optionsUpdate', {source}); +        _onMessageOptionsUpdated({source}) { +            this.trigger('optionsUpdated', {source});          }          _onMessageZoomChanged({oldZoomFactor, newZoomFactor}) { diff --git a/ext/mixed/js/display-generator.js b/ext/mixed/js/display-generator.js index 3617e546..46f3d17e 100644 --- a/ext/mixed/js/display-generator.js +++ b/ext/mixed/js/display-generator.js @@ -20,9 +20,6 @@  class DisplayGenerator {      constructor() { -        this._isInitialized = false; -        this._initializationPromise = null; -          this._termEntryTemplate = null;          this._termExpressionTemplate = null;          this._termDefinitionItemTemplate = null; @@ -41,18 +38,10 @@ class DisplayGenerator {          this._tagFrequencyTemplate = null;      } -    isInitialized() { -        return this._isInitialized; -    } - -    initialize() { -        if (this._isInitialized) { -            return Promise.resolve(); -        } -        if (this._initializationPromise === null) { -            this._initializationPromise = this._initializeInternal(); -        } -        return this._initializationPromise; +    async prepare() { +        const html = await apiGetDisplayTemplatesHtml(); +        const doc = new DOMParser().parseFromString(html, 'text/html'); +        this._setTemplates(doc);      }      createTermEntry(details) { @@ -304,13 +293,6 @@ class DisplayGenerator {          return node;      } -    async _initializeInternal() { -        const html = await apiGetDisplayTemplatesHtml(); -        const doc = new DOMParser().parseFromString(html, 'text/html'); -        this._setTemplates(doc); -        this._isInitialized = true; -    } -      _setTemplates(doc) {          this._termEntryTemplate = doc.querySelector('#term-entry-template');          this._termExpressionTemplate = doc.querySelector('#term-expression-template'); diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index b18e275d..8113260c 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -32,11 +32,11 @@ class Display {          this.index = 0;          this.audioPlaying = null;          this.audioFallback = null; -        this.audioCache = {}; +        this.audioCache = new Map();          this.styleNode = null; -        this.eventListeners = []; -        this.persistentEventListeners = []; +        this.eventListeners = new EventListenerCollection(); +        this.persistentEventListeners = new EventListenerCollection();          this.interactive = false;          this.eventListenersActive = false;          this.clickScanPrevent = false; @@ -48,6 +48,13 @@ class Display {          this.setInteractive(true);      } +    async prepare(options=null) { +        const displayGeneratorPromise = this.displayGenerator.prepare(); +        const updateOptionsPromise = this.updateOptions(options); +        await Promise.all([displayGeneratorPromise, updateOptionsPromise]); +        yomichan.on('optionsUpdated', () => this.updateOptions(null)); +    } +      onError(_error) {          throw new Error('Override me');      } @@ -179,13 +186,15 @@ class Display {          e.preventDefault();          const link = e.currentTarget;          const entry = link.closest('.entry'); -        const definitionIndex = this.entryIndexFind(entry); +        const index = this.entryIndexFind(entry); +        if (index < 0 || index >= this.definitions.length) { return; } +          const expressionIndex = Display.indexOf(entry.querySelectorAll('.term-expression .action-play-audio'), link);          this.audioPlay( -            this.definitions[definitionIndex], +            this.definitions[index],              // expressionIndex is used in audioPlay to detect result output mode              Math.max(expressionIndex, this.options.general.resultOutputMode === 'merge' ? 0 : -1), -            definitionIndex +            index          );      } @@ -193,6 +202,8 @@ class Display {          e.preventDefault();          const link = e.currentTarget;          const index = this.entryIndexFind(link); +        if (index < 0 || index >= this.definitions.length) { return; } +          this.noteAdd(this.definitions[index], link.dataset.mode);      } @@ -243,15 +254,6 @@ class Display {          throw new Error('Override me');      } -    isInitialized() { -        return this.options !== null; -    } - -    async initialize(options=null) { -        await this.updateOptions(options); -        yomichan.on('optionsUpdate', () => this.updateOptions(null)); -    } -      async updateOptions(options) {          this.options = options ? options : await apiOptionsGet(this.getOptionsContext());          this.updateDocumentOptions(this.options); @@ -299,13 +301,23 @@ class Display {          this.interactive = interactive;          if (interactive) { -            Display.addEventListener(this.persistentEventListeners, document, 'keydown', this.onKeyDown.bind(this), false); -            Display.addEventListener(this.persistentEventListeners, document, 'wheel', this.onWheel.bind(this), {passive: false}); -            Display.addEventListener(this.persistentEventListeners, document.querySelector('.action-previous'), 'click', this.onSourceTermView.bind(this)); -            Display.addEventListener(this.persistentEventListeners, document.querySelector('.action-next'), 'click', this.onNextTermView.bind(this)); -            Display.addEventListener(this.persistentEventListeners, document.querySelector('.navigation-header'), 'wheel', this.onHistoryWheel.bind(this), {passive: false}); +            const actionPrevious = document.querySelector('.action-previous'); +            const actionNext = document.querySelector('.action-next'); +            const navigationHeader = document.querySelector('.navigation-header'); + +            this.persistentEventListeners.addEventListener(document, 'keydown', this.onKeyDown.bind(this), false); +            this.persistentEventListeners.addEventListener(document, 'wheel', this.onWheel.bind(this), {passive: false}); +            if (actionPrevious !== null) { +                this.persistentEventListeners.addEventListener(actionPrevious, 'click', this.onSourceTermView.bind(this)); +            } +            if (actionNext !== null) { +                this.persistentEventListeners.addEventListener(actionNext, 'click', this.onNextTermView.bind(this)); +            } +            if (navigationHeader !== null) { +                this.persistentEventListeners.addEventListener(navigationHeader, 'wheel', this.onHistoryWheel.bind(this), {passive: false}); +            }          } else { -            Display.clearEventListeners(this.persistentEventListeners); +            this.persistentEventListeners.removeAllEventListeners();          }          this.setEventListenersActive(this.eventListenersActive);      } @@ -316,23 +328,23 @@ class Display {          this.eventListenersActive = active;          if (active) { -            this.addEventListeners('.action-add-note', 'click', this.onNoteAdd.bind(this)); -            this.addEventListeners('.action-view-note', 'click', this.onNoteView.bind(this)); -            this.addEventListeners('.action-play-audio', 'click', this.onAudioPlay.bind(this)); -            this.addEventListeners('.kanji-link', 'click', this.onKanjiLookup.bind(this)); +            this.addMultipleEventListeners('.action-add-note', 'click', this.onNoteAdd.bind(this)); +            this.addMultipleEventListeners('.action-view-note', 'click', this.onNoteView.bind(this)); +            this.addMultipleEventListeners('.action-play-audio', 'click', this.onAudioPlay.bind(this)); +            this.addMultipleEventListeners('.kanji-link', 'click', this.onKanjiLookup.bind(this));              if (this.options.scanning.enablePopupSearch) { -                this.addEventListeners('.term-glossary-item, .tag', 'mouseup', this.onGlossaryMouseUp.bind(this)); -                this.addEventListeners('.term-glossary-item, .tag', 'mousedown', this.onGlossaryMouseDown.bind(this)); -                this.addEventListeners('.term-glossary-item, .tag', 'mousemove', this.onGlossaryMouseMove.bind(this)); +                this.addMultipleEventListeners('.term-glossary-item, .tag', 'mouseup', this.onGlossaryMouseUp.bind(this)); +                this.addMultipleEventListeners('.term-glossary-item, .tag', 'mousedown', this.onGlossaryMouseDown.bind(this)); +                this.addMultipleEventListeners('.term-glossary-item, .tag', 'mousemove', this.onGlossaryMouseMove.bind(this));              }          } else { -            Display.clearEventListeners(this.eventListeners); +            this.eventListeners.removeAllEventListeners();          }      } -    addEventListeners(selector, type, listener, options) { +    addMultipleEventListeners(selector, type, listener, options) {          for (const node of this.container.querySelectorAll(selector)) { -            Display.addEventListener(this.eventListeners, node, type, listener, options); +            this.eventListeners.addEventListener(node, type, listener, options);          }      } @@ -362,7 +374,6 @@ class Display {      async setContentTerms(definitions, context, token) {          if (!context) { throw new Error('Context expected'); } -        if (!this.isInitialized()) { return; }          this.setEventListenersActive(false); @@ -370,11 +381,6 @@ class Display {              window.focus();          } -        if (!this.displayGenerator.isInitialized()) { -            await this.displayGenerator.initialize(); -            if (this.setContentToken !== token) { return; } -        } -          this.definitions = definitions;          if (context.disableHistory) {              delete context.disableHistory; @@ -418,7 +424,7 @@ class Display {          this.setEventListenersActive(true); -        const states = await apiDefinitionsAddable(definitions, ['term-kanji', 'term-kana'], this.getOptionsContext()); +        const states = await this.getDefinitionsAddable(definitions, ['term-kanji', 'term-kana']);          if (this.setContentToken !== token) { return; }          this.updateAdderButtons(states); @@ -426,7 +432,6 @@ class Display {      async setContentKanji(definitions, context, token) {          if (!context) { throw new Error('Context expected'); } -        if (!this.isInitialized()) { return; }          this.setEventListenersActive(false); @@ -434,11 +439,6 @@ class Display {              window.focus();          } -        if (!this.displayGenerator.isInitialized()) { -            await this.displayGenerator.initialize(); -            if (this.setContentToken !== token) { return; } -        } -          this.definitions = definitions;          if (context.disableHistory) {              delete context.disableHistory; @@ -460,7 +460,7 @@ class Display {          for (let i = 0, ii = definitions.length; i < ii; ++i) {              if (i > 0) { -                await promiseTimeout(0); +                await promiseTimeout(1);                  if (this.setContentToken !== token) { return; }              } @@ -473,7 +473,7 @@ class Display {          this.setEventListenersActive(true); -        const states = await apiDefinitionsAddable(definitions, ['kanji'], this.getOptionsContext()); +        const states = await this.getDefinitionsAddable(definitions, ['kanji']);          if (this.setContentToken !== token) { return; }          this.updateAdderButtons(states); @@ -512,6 +512,8 @@ class Display {      }      autoPlayAudio() { +        if (this.definitions.length === 0) { return; } +          this.audioPlay(this.definitions[0], this.firstExpressionIndex, 0);      } @@ -611,9 +613,12 @@ class Display {      }      noteTryAdd(mode) { -        const button = this.adderButtonFind(this.index, mode); +        const index = this.index; +        if (index < 0 || index >= this.definitions.length) { return; } + +        const button = this.adderButtonFind(index, mode);          if (button !== null && !button.classList.contains('disabled')) { -            this.noteAdd(this.definitions[this.index], mode); +            this.noteAdd(this.definitions[index], mode);          }      } @@ -712,7 +717,7 @@ class Display {      async getScreenshot() {          try {              await this.setPopupVisibleOverride(false); -            await Display.delay(1); // Wait for popup to be hidden. +            await promiseTimeout(1); // Wait for popup to be hidden.              const {format, quality} = this.options.anki.screenshot;              const dataUrl = await apiScreenshotGet({format, quality}); @@ -781,8 +786,12 @@ class Display {          return entry !== null ? entry.querySelector('.action-play-audio>img') : null;      } -    static delay(time) { -        return new Promise((resolve) => setTimeout(resolve, time)); +    async getDefinitionsAddable(definitions, modes) { +        try { +            return await apiDefinitionsAddable(definitions, modes, this.getOptionsContext()); +        } catch (e) { +            return []; +        }      }      static indexOf(nodeList, node) { @@ -794,19 +803,6 @@ class Display {          return -1;      } -    static addEventListener(eventListeners, object, type, listener, options) { -        if (object === null) { return; } -        object.addEventListener(type, listener, options); -        eventListeners.push([object, type, listener, options]); -    } - -    static clearEventListeners(eventListeners) { -        for (const [object, type, listener, options] of eventListeners) { -            object.removeEventListener(type, listener, options); -        } -        eventListeners.length = 0; -    } -      static getElementTop(element) {          const elementRect = element.getBoundingClientRect();          const documentRect = document.documentElement.getBoundingClientRect(); @@ -915,9 +911,12 @@ Display._onKeyDownHandlers = new Map([      ['P', (self, e) => {          if (e.altKey) { -            const entry = self.getEntry(self.index); +            const index = self.index; +            if (index < 0 || index >= self.definitions.length) { return; } + +            const entry = self.getEntry(index);              if (entry !== null && entry.dataset.type === 'term') { -                self.audioPlay(self.definitions[self.index], self.firstExpressionIndex, self.index); +                self.audioPlay(self.definitions[index], self.firstExpressionIndex, index);              }              return true;          } diff --git a/ext/mixed/js/text-scanner.js b/ext/mixed/js/text-scanner.js index e6da1e5e..aa10bbaf 100644 --- a/ext/mixed/js/text-scanner.js +++ b/ext/mixed/js/text-scanner.js @@ -31,7 +31,7 @@ class TextScanner {          this.options = null;          this.enabled = false; -        this.eventListeners = []; +        this.eventListeners = new EventListenerCollection();          this.primaryTouchIdentifier = null;          this.preventNextContextMenu = false; @@ -229,7 +229,7 @@ class TextScanner {              }          } else {              if (this.enabled) { -                this.clearEventListeners(); +                this.eventListeners.removeAllEventListeners();                  this.enabled = false;              }              this.onSearchClear(false); @@ -237,13 +237,13 @@ class TextScanner {      }      hookEvents() { -        let eventListeners = this.getMouseEventListeners(); +        let eventListenerInfos = this.getMouseEventListeners();          if (this.options.scanning.touchInputEnabled) { -            eventListeners = eventListeners.concat(this.getTouchEventListeners()); +            eventListenerInfos = eventListenerInfos.concat(this.getTouchEventListeners());          } -        for (const [node, type, listener, options] of eventListeners) { -            this.addEventListener(node, type, listener, options); +        for (const [node, type, listener, options] of eventListenerInfos) { +            this.eventListeners.addEventListener(node, type, listener, options);          }      } @@ -268,18 +268,6 @@ class TextScanner {          ];      } -    addEventListener(node, type, listener, options) { -        node.addEventListener(type, listener, options); -        this.eventListeners.push([node, type, listener, options]); -    } - -    clearEventListeners() { -        for (const [node, type, listener, options] of this.eventListeners) { -            node.removeEventListener(type, listener, options); -        } -        this.eventListeners = []; -    } -      setOptions(options) {          this.options = options;          this.setEnabled(this.options.general.enable); |