diff options
-rw-r--r-- | ext/bg/js/search.js | 58 | ||||
-rw-r--r-- | ext/bg/search.html | 2 | ||||
-rw-r--r-- | ext/fg/float.html | 4 | ||||
-rw-r--r-- | ext/mixed/css/display.css | 1 | ||||
-rw-r--r-- | ext/mixed/js/display-generator.js | 1 | ||||
-rw-r--r-- | ext/mixed/js/display.js | 172 | ||||
-rw-r--r-- | ext/mixed/js/yomichan.js | 9 |
7 files changed, 153 insertions, 94 deletions
diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index fc857368..d95fd5e4 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -63,11 +63,14 @@ class DisplaySearch extends Display { this.on('contentUpdating', this._onContentUpdating.bind(this)); + this.queryParserVisible = true; this.setHistorySettings({useBrowserHistory: true}); const options = this.getOptions(); - const {queryParams: {query='', mode=''}} = parseUrl(window.location.href); + const urlSearchParams = new URLSearchParams(location.search); + let mode = urlSearchParams.get('mode'); + if (mode === null) { mode = ''; } document.documentElement.dataset.searchMode = mode; @@ -78,8 +81,6 @@ class DisplaySearch extends Display { this._wanakanaEnable.checked = false; } - this._setQuery(query); - if (mode !== 'popup') { if (options.general.enableClipboardMonitor === true) { this._clipboardMonitorEnable.checked = true; @@ -147,14 +148,24 @@ class DisplaySearch extends Display { if (!this._isPrepared) { return; } const query = this._query.value; if (query) { - this._setQuery(query); this._onSearchQueryUpdated(query, false); } } + postProcessQuery(query) { + if (this._isWanakanaEnabled()) { + try { + query = wanakana.toKana(query); + } catch (e) { + // NOP + } + } + return query; + } + // Private - _onContentUpdating({type, source, content, urlSearchParams}) { + _onContentUpdating({type, content, source}) { let animate = false; let valid = false; switch (type) { @@ -173,13 +184,8 @@ class DisplaySearch extends Display { if (typeof source !== 'string') { source = ''; } - let full = urlSearchParams.get('full'); - if (full === null) { full = source; } - - this._closePopups(); - this._setQuery(full); + this._query.value = source; this._setIntroVisible(!valid, animate); - this._setTitleText(source); this._updateSearchButton(); } @@ -289,19 +295,6 @@ class DisplaySearch extends Display { return this._wanakanaEnable !== null && this._wanakanaEnable.checked; } - _setQuery(query) { - let interpretedQuery = query; - if (this._isWanakanaEnabled()) { - try { - interpretedQuery = wanakana.toKana(query); - } catch (e) { - // NOP - } - } - this._query.value = interpretedQuery; - this.setQueryParserText(interpretedQuery); - } - _setIntroVisible(visible, animate) { if (this._introVisible === visible) { return; @@ -362,19 +355,6 @@ class DisplaySearch extends Display { this._search.disabled = this._introVisible && (this._query === null || this._query.value.length === 0); } - _setTitleText(text) { - // Chrome limits title to 1024 characters - if (text.length > 1000) { - text = text.substring(0, 1000) + '...'; - } - - if (text.length === 0) { - document.title = 'Yomichan Search'; - } else { - document.title = `${text} - Yomichan Search`; - } - } - async _prepareNestedPopups() { let complete = false; @@ -401,8 +381,4 @@ class DisplaySearch extends Display { await onOptionsUpdated(); } - - _closePopups() { - yomichan.trigger('closePopups'); - } } diff --git a/ext/bg/search.html b/ext/bg/search.html index 653a708f..eb85e368 100644 --- a/ext/bg/search.html +++ b/ext/bg/search.html @@ -46,7 +46,7 @@ <div id="spinner" hidden><img src="/mixed/img/spinner.gif"></div> - <div class="scan-disable"> + <div class="scan-disable" id="query-parser-container"> <div id="query-parser-select-container" class="input-group"></div> <div id="query-parser-content"></div> </div> diff --git a/ext/fg/float.html b/ext/fg/float.html index 427a7e57..17378713 100644 --- a/ext/fg/float.html +++ b/ext/fg/float.html @@ -3,7 +3,7 @@ <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1" /> - <title></title> + <title>Yomichan Search</title> <link rel="icon" type="image/png" href="/mixed/img/icon16.png" sizes="16x16"> <link rel="icon" type="image/png" href="/mixed/img/icon19.png" sizes="19x19"> <link rel="icon" type="image/png" href="/mixed/img/icon32.png" sizes="32x32"> @@ -21,7 +21,7 @@ <button class="action-button action-next" data-icon="source-term" title="Next term (Alt + F)"></button> </div></div><div class="navigation-header-spacer"></div> - <div class="scan-disable" hidden> + <div class="scan-disable" id="query-parser-container" hidden> <div id="query-parser-select-container" class="input-group"></div> <div id="query-parser-content"></div> </div> diff --git a/ext/mixed/css/display.css b/ext/mixed/css/display.css index 703cef1c..f5d9403a 100644 --- a/ext/mixed/css/display.css +++ b/ext/mixed/css/display.css @@ -305,6 +305,7 @@ button.action-button { border-bottom: 0.03571428em dashed var(--dark-border-color); /* 28px => 1px */ color: var(--default-text-color); text-decoration: none; + cursor: pointer; } .term-expression[data-frequency=popular]>.term-expression-text, diff --git a/ext/mixed/js/display-generator.js b/ext/mixed/js/display-generator.js index 276b37de..2b0b5b1c 100644 --- a/ext/mixed/js/display-generator.js +++ b/ext/mixed/js/display-generator.js @@ -281,7 +281,6 @@ class DisplayGenerator { _createKanjiLink(character) { const node = document.createElement('a'); - node.href = '#'; node.className = 'kanji-link'; node.textContent = character; return node; diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index a9f217b4..40febadc 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -69,7 +69,12 @@ class Display extends EventDispatcher { this._historyChangeIgnore = false; this._historyHasChanged = false; this._navigationHeader = document.querySelector('#navigation-header'); + this._defaultTitle = 'Yomichan Search'; + this._defaultTitleMaxLength = 1000; this._fullQuery = ''; + this._queryParserVisible = false; + this._queryParserVisibleOverride = null; + this._queryParserContainer = document.querySelector('#query-parser-container'); this._queryParser = new QueryParser({ getOptionsContext: this.getOptionsContext.bind(this), setSpinnerVisible: this.setSpinnerVisible.bind(this) @@ -123,6 +128,15 @@ class Display extends EventDispatcher { this._autoPlayAudioDelay = value; } + get queryParserVisible() { + return this._queryParserVisible; + } + + set queryParserVisible(value) { + this._queryParserVisible = value; + this._updateQueryParserVisibility(); + } + async prepare() { this._setInteractive(true); await this._displayGenerator.prepare(); @@ -322,10 +336,8 @@ class Display extends EventDispatcher { return data; } - setQueryParserText(text) { - if (this._fullQuery === text) { return; } - this._fullQuery = text; - this._queryParser.setText(text); + postProcessQuery(query) { + return query; } // Message handlers @@ -371,6 +383,13 @@ class Display extends EventDispatcher { let type = urlSearchParams.get('type'); if (type === null) { type = 'terms'; } + const fullVisible = urlSearchParams.get('full-visible'); + this._queryParserVisibleOverride = (fullVisible === null ? null : (fullVisible !== 'false')); + this._updateQueryParserVisibility(); + + this._closePopups(); + this._setEventListenersActive(false); + let asigned = false; const eventArgs = {type, urlSearchParams, token}; this._historyHasChanged = true; @@ -379,38 +398,8 @@ class Display extends EventDispatcher { case 'terms': case 'kanji': { - const source = urlSearchParams.get('query'); - if (!source) { break; } - const isTerms = (type === 'terms'); - let {state, content} = this._history; - let changeHistory = false; - if (!isObject(content)) { - content = {}; - changeHistory = true; - } - if (!isObject(state)) { - state = {}; - changeHistory = true; - } - - let {definitions} = content; - if (!Array.isArray(definitions)) { - definitions = await this._findDefinitions(isTerms, source, urlSearchParams); - if (this._setContentToken !== token) { return; } - content.definitions = definitions; - changeHistory = true; - } - - if (changeHistory) { - this._historyStateUpdate(state, content); - } - - asigned = true; - eventArgs.source = source; - eventArgs.content = content; - this.trigger('contentUpdating', eventArgs); - await this._setContentTermsOrKanji(token, isTerms, definitions, state); + asigned = await this._setContentTermsOrKanji(token, isTerms, urlSearchParams, eventArgs); } break; case 'unloaded': @@ -419,19 +408,25 @@ class Display extends EventDispatcher { eventArgs.content = content; this.trigger('contentUpdating', eventArgs); this._setContentExtensionUnloaded(); + asigned = true; } break; } - if (!asigned) { - const {content} = this._history; - eventArgs.type = 'clear'; - eventArgs.content = content; - this.trigger('contentUpdating', eventArgs); - this._clearContent(); + const stale = (this._setContentToken !== token); + if (!stale) { + if (!asigned) { + const {content} = this._history; + eventArgs.type = 'clear'; + eventArgs.content = content; + this.trigger('contentUpdating', eventArgs); + this._clearContent(); + } + + this._setEventListenersActive(true); } - eventArgs.stale = (this._setContentToken !== token); + eventArgs.stale = stale; this.trigger('contentUpdated', eventArgs); } catch (e) { this.onError(e); @@ -739,12 +734,47 @@ class Display extends EventDispatcher { } } - async _setContentTermsOrKanji(token, isTerms, definitions, {sentence=null, url=null, focusEntry=null, scrollX=null, scrollY=null}) { + async _setContentTermsOrKanji(token, isTerms, urlSearchParams, eventArgs) { + let source = urlSearchParams.get('query'); + if (!source) { return false; } + + let {state, content} = this._history; + let changeHistory = false; + if (!isObject(content)) { + content = {}; + changeHistory = true; + } + if (!isObject(state)) { + state = {}; + changeHistory = true; + } + + source = this.postProcessQuery(source); + let full = urlSearchParams.get('full'); + full = (full === null ? source : this.postProcessQuery(full)); + this._setQueryParserText(full); + this._setTitleText(source); + + let {definitions} = content; + if (!Array.isArray(definitions)) { + definitions = await this._findDefinitions(isTerms, source, urlSearchParams); + if (this._setContentToken !== token) { return true; } + content.definitions = definitions; + changeHistory = true; + } + + if (changeHistory) { + this._historyStateUpdate(state, content); + } + + eventArgs.source = source; + eventArgs.content = content; + this.trigger('contentUpdating', eventArgs); + + let {sentence=null, url=null, focusEntry=null, scrollX=null, scrollY=null} = state; if (typeof url !== 'string') { url = window.location.href; } sentence = this._getValidSentenceData(sentence); - this._setEventListenersActive(false); - this._definitions = definitions; for (const definition of definitions) { @@ -761,7 +791,7 @@ class Display extends EventDispatcher { for (let i = 0, ii = definitions.length; i < ii; ++i) { if (i > 0) { await promiseTimeout(1); - if (this._setContentToken !== token) { return; } + if (this._setContentToken !== token) { return true; } } const entry = ( @@ -791,8 +821,12 @@ class Display extends EventDispatcher { this.autoPlayAudio(); } - this._setEventListenersActive(true); + this._setContentTermsOrKanjiUpdateAdderButtons(token, isTerms, definitions); + return true; + } + + async _setContentTermsOrKanjiUpdateAdderButtons(token, isTerms, definitions) { const modes = isTerms ? ['term-kanji', 'term-kana'] : ['kanji']; const states = await this._getDefinitionsAddable(definitions, modes); if (this._setContentToken !== token) { return; } @@ -813,11 +847,12 @@ class Display extends EventDispatcher { this._updateNavigation(null, null); this._setNoContentVisible(false); + this._setTitleText(''); } _clearContent() { - this._setEventListenersActive(false); this._container.textContent = ''; + this._setTitleText(''); } _setNoContentVisible(visible) { @@ -828,6 +863,28 @@ class Display extends EventDispatcher { } } + _setQueryParserText(text) { + if (this._fullQuery === text) { return; } + this._fullQuery = text; + if (!this._isQueryParserVisible()) { return; } + this._queryParser.setText(text); + } + + _setTitleText(text) { + // Chrome limits title to 1024 characters + const ellipsis = '...'; + const maxLength = this._defaultTitleMaxLength - this._defaultTitle.length; + if (text.length > maxLength) { + text = `${text.substring(0, Math.max(0, maxLength - maxLength))}${ellipsis}`; + } + + document.title = ( + text.length === 0 ? + this._defaultTitle : + `${text} - ${this._defaultTitle}` + ); + } + _updateNavigation(previous, next) { if (this._navigationHeader === null) { return; } this._navigationHeader.hidden = !(previous || next); @@ -1177,6 +1234,25 @@ class Display extends EventDispatcher { if (!wildcards) { params.wildcards = 'off'; } + if (this._queryParserVisibleOverride !== null) { + params['full-visible'] = `${this._queryParserVisibleOverride}`; + } return params; } + + _isQueryParserVisible() { + return ( + this._queryParserVisibleOverride !== null ? + this._queryParserVisibleOverride : + this._queryParserVisible + ); + } + + _updateQueryParserVisibility() { + this._queryParserContainer.hidden = !this._isQueryParserVisible(); + } + + _closePopups() { + yomichan.trigger('closePopups'); + } } diff --git a/ext/mixed/js/yomichan.js b/ext/mixed/js/yomichan.js index 6eba68b2..2de82274 100644 --- a/ext/mixed/js/yomichan.js +++ b/ext/mixed/js/yomichan.js @@ -48,6 +48,7 @@ const yomichan = (() => { } this._isExtensionUnloaded = false; + this._isTriggeringExtensionUnloaded = false; this._isReady = false; const {promise, resolve} = deferPromise(); @@ -256,7 +257,13 @@ const yomichan = (() => { triggerExtensionUnloaded() { this._isExtensionUnloaded = true; - this.trigger('extensionUnloaded'); + if (this._isTriggeringExtensionUnloaded) { return; } + try { + this._isTriggeringExtensionUnloaded = true; + this.trigger('extensionUnloaded'); + } finally { + this._isTriggeringExtensionUnloaded = false; + } } // Private |