More display refactoring (#697)

* Remove some unnecessary _setQuery calls

* Add support for forcing the query parser to be visible or hidden

* Move _setEventListenersActive calls

* Remove URL for kanji links

* Refactor _setContentTermsOrKanji

* Move search query text assignment into Display

* Move title updates into Display

* Move popup close calls

* Prevent infinite loop of extension unload events
This commit is contained in:
toasted-nutbread 2020-08-01 16:22:00 -04:00 committed by GitHub
parent b52074b3f0
commit 1e839cd230
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 153 additions and 94 deletions

View File

@ -63,11 +63,14 @@ class DisplaySearch extends Display {
this.on('contentUpdating', this._onContentUpdating.bind(this)); this.on('contentUpdating', this._onContentUpdating.bind(this));
this.queryParserVisible = true;
this.setHistorySettings({useBrowserHistory: true}); this.setHistorySettings({useBrowserHistory: true});
const options = this.getOptions(); 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; document.documentElement.dataset.searchMode = mode;
@ -78,8 +81,6 @@ class DisplaySearch extends Display {
this._wanakanaEnable.checked = false; this._wanakanaEnable.checked = false;
} }
this._setQuery(query);
if (mode !== 'popup') { if (mode !== 'popup') {
if (options.general.enableClipboardMonitor === true) { if (options.general.enableClipboardMonitor === true) {
this._clipboardMonitorEnable.checked = true; this._clipboardMonitorEnable.checked = true;
@ -147,14 +148,24 @@ class DisplaySearch extends Display {
if (!this._isPrepared) { return; } if (!this._isPrepared) { return; }
const query = this._query.value; const query = this._query.value;
if (query) { if (query) {
this._setQuery(query);
this._onSearchQueryUpdated(query, false); this._onSearchQueryUpdated(query, false);
} }
} }
postProcessQuery(query) {
if (this._isWanakanaEnabled()) {
try {
query = wanakana.toKana(query);
} catch (e) {
// NOP
}
}
return query;
}
// Private // Private
_onContentUpdating({type, source, content, urlSearchParams}) { _onContentUpdating({type, content, source}) {
let animate = false; let animate = false;
let valid = false; let valid = false;
switch (type) { switch (type) {
@ -173,13 +184,8 @@ class DisplaySearch extends Display {
if (typeof source !== 'string') { source = ''; } if (typeof source !== 'string') { source = ''; }
let full = urlSearchParams.get('full'); this._query.value = source;
if (full === null) { full = source; }
this._closePopups();
this._setQuery(full);
this._setIntroVisible(!valid, animate); this._setIntroVisible(!valid, animate);
this._setTitleText(source);
this._updateSearchButton(); this._updateSearchButton();
} }
@ -289,19 +295,6 @@ class DisplaySearch extends Display {
return this._wanakanaEnable !== null && this._wanakanaEnable.checked; 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) { _setIntroVisible(visible, animate) {
if (this._introVisible === visible) { if (this._introVisible === visible) {
return; return;
@ -362,19 +355,6 @@ class DisplaySearch extends Display {
this._search.disabled = this._introVisible && (this._query === null || this._query.value.length === 0); 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() { async _prepareNestedPopups() {
let complete = false; let complete = false;
@ -401,8 +381,4 @@ class DisplaySearch extends Display {
await onOptionsUpdated(); await onOptionsUpdated();
} }
_closePopups() {
yomichan.trigger('closePopups');
}
} }

View File

@ -46,7 +46,7 @@
<div id="spinner" hidden><img src="/mixed/img/spinner.gif"></div> <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-select-container" class="input-group"></div>
<div id="query-parser-content"></div> <div id="query-parser-content"></div>
</div> </div>

View File

@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1" /> <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/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/icon19.png" sizes="19x19">
<link rel="icon" type="image/png" href="/mixed/img/icon32.png" sizes="32x32"> <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> <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></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-select-container" class="input-group"></div>
<div id="query-parser-content"></div> <div id="query-parser-content"></div>
</div> </div>

View File

@ -305,6 +305,7 @@ button.action-button {
border-bottom: 0.03571428em dashed var(--dark-border-color); /* 28px => 1px */ border-bottom: 0.03571428em dashed var(--dark-border-color); /* 28px => 1px */
color: var(--default-text-color); color: var(--default-text-color);
text-decoration: none; text-decoration: none;
cursor: pointer;
} }
.term-expression[data-frequency=popular]>.term-expression-text, .term-expression[data-frequency=popular]>.term-expression-text,

View File

@ -281,7 +281,6 @@ class DisplayGenerator {
_createKanjiLink(character) { _createKanjiLink(character) {
const node = document.createElement('a'); const node = document.createElement('a');
node.href = '#';
node.className = 'kanji-link'; node.className = 'kanji-link';
node.textContent = character; node.textContent = character;
return node; return node;

View File

@ -69,7 +69,12 @@ class Display extends EventDispatcher {
this._historyChangeIgnore = false; this._historyChangeIgnore = false;
this._historyHasChanged = false; this._historyHasChanged = false;
this._navigationHeader = document.querySelector('#navigation-header'); this._navigationHeader = document.querySelector('#navigation-header');
this._defaultTitle = 'Yomichan Search';
this._defaultTitleMaxLength = 1000;
this._fullQuery = ''; this._fullQuery = '';
this._queryParserVisible = false;
this._queryParserVisibleOverride = null;
this._queryParserContainer = document.querySelector('#query-parser-container');
this._queryParser = new QueryParser({ this._queryParser = new QueryParser({
getOptionsContext: this.getOptionsContext.bind(this), getOptionsContext: this.getOptionsContext.bind(this),
setSpinnerVisible: this.setSpinnerVisible.bind(this) setSpinnerVisible: this.setSpinnerVisible.bind(this)
@ -123,6 +128,15 @@ class Display extends EventDispatcher {
this._autoPlayAudioDelay = value; this._autoPlayAudioDelay = value;
} }
get queryParserVisible() {
return this._queryParserVisible;
}
set queryParserVisible(value) {
this._queryParserVisible = value;
this._updateQueryParserVisibility();
}
async prepare() { async prepare() {
this._setInteractive(true); this._setInteractive(true);
await this._displayGenerator.prepare(); await this._displayGenerator.prepare();
@ -322,10 +336,8 @@ class Display extends EventDispatcher {
return data; return data;
} }
setQueryParserText(text) { postProcessQuery(query) {
if (this._fullQuery === text) { return; } return query;
this._fullQuery = text;
this._queryParser.setText(text);
} }
// Message handlers // Message handlers
@ -371,6 +383,13 @@ class Display extends EventDispatcher {
let type = urlSearchParams.get('type'); let type = urlSearchParams.get('type');
if (type === null) { type = 'terms'; } 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; let asigned = false;
const eventArgs = {type, urlSearchParams, token}; const eventArgs = {type, urlSearchParams, token};
this._historyHasChanged = true; this._historyHasChanged = true;
@ -379,38 +398,8 @@ class Display extends EventDispatcher {
case 'terms': case 'terms':
case 'kanji': case 'kanji':
{ {
const source = urlSearchParams.get('query');
if (!source) { break; }
const isTerms = (type === 'terms'); const isTerms = (type === 'terms');
let {state, content} = this._history; asigned = await this._setContentTermsOrKanji(token, isTerms, urlSearchParams, eventArgs);
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);
} }
break; break;
case 'unloaded': case 'unloaded':
@ -419,19 +408,25 @@ class Display extends EventDispatcher {
eventArgs.content = content; eventArgs.content = content;
this.trigger('contentUpdating', eventArgs); this.trigger('contentUpdating', eventArgs);
this._setContentExtensionUnloaded(); this._setContentExtensionUnloaded();
asigned = true;
} }
break; break;
} }
if (!asigned) { const stale = (this._setContentToken !== token);
const {content} = this._history; if (!stale) {
eventArgs.type = 'clear'; if (!asigned) {
eventArgs.content = content; const {content} = this._history;
this.trigger('contentUpdating', eventArgs); eventArgs.type = 'clear';
this._clearContent(); eventArgs.content = content;
this.trigger('contentUpdating', eventArgs);
this._clearContent();
}
this._setEventListenersActive(true);
} }
eventArgs.stale = (this._setContentToken !== token); eventArgs.stale = stale;
this.trigger('contentUpdated', eventArgs); this.trigger('contentUpdated', eventArgs);
} catch (e) { } catch (e) {
this.onError(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; } if (typeof url !== 'string') { url = window.location.href; }
sentence = this._getValidSentenceData(sentence); sentence = this._getValidSentenceData(sentence);
this._setEventListenersActive(false);
this._definitions = definitions; this._definitions = definitions;
for (const definition of definitions) { for (const definition of definitions) {
@ -761,7 +791,7 @@ class Display extends EventDispatcher {
for (let i = 0, ii = definitions.length; i < ii; ++i) { for (let i = 0, ii = definitions.length; i < ii; ++i) {
if (i > 0) { if (i > 0) {
await promiseTimeout(1); await promiseTimeout(1);
if (this._setContentToken !== token) { return; } if (this._setContentToken !== token) { return true; }
} }
const entry = ( const entry = (
@ -791,8 +821,12 @@ class Display extends EventDispatcher {
this.autoPlayAudio(); 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 modes = isTerms ? ['term-kanji', 'term-kana'] : ['kanji'];
const states = await this._getDefinitionsAddable(definitions, modes); const states = await this._getDefinitionsAddable(definitions, modes);
if (this._setContentToken !== token) { return; } if (this._setContentToken !== token) { return; }
@ -813,11 +847,12 @@ class Display extends EventDispatcher {
this._updateNavigation(null, null); this._updateNavigation(null, null);
this._setNoContentVisible(false); this._setNoContentVisible(false);
this._setTitleText('');
} }
_clearContent() { _clearContent() {
this._setEventListenersActive(false);
this._container.textContent = ''; this._container.textContent = '';
this._setTitleText('');
} }
_setNoContentVisible(visible) { _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) { _updateNavigation(previous, next) {
if (this._navigationHeader === null) { return; } if (this._navigationHeader === null) { return; }
this._navigationHeader.hidden = !(previous || next); this._navigationHeader.hidden = !(previous || next);
@ -1177,6 +1234,25 @@ class Display extends EventDispatcher {
if (!wildcards) { if (!wildcards) {
params.wildcards = 'off'; params.wildcards = 'off';
} }
if (this._queryParserVisibleOverride !== null) {
params['full-visible'] = `${this._queryParserVisibleOverride}`;
}
return params; return params;
} }
_isQueryParserVisible() {
return (
this._queryParserVisibleOverride !== null ?
this._queryParserVisibleOverride :
this._queryParserVisible
);
}
_updateQueryParserVisibility() {
this._queryParserContainer.hidden = !this._isQueryParserVisible();
}
_closePopups() {
yomichan.trigger('closePopups');
}
} }

View File

@ -48,6 +48,7 @@ const yomichan = (() => {
} }
this._isExtensionUnloaded = false; this._isExtensionUnloaded = false;
this._isTriggeringExtensionUnloaded = false;
this._isReady = false; this._isReady = false;
const {promise, resolve} = deferPromise(); const {promise, resolve} = deferPromise();
@ -256,7 +257,13 @@ const yomichan = (() => {
triggerExtensionUnloaded() { triggerExtensionUnloaded() {
this._isExtensionUnloaded = true; this._isExtensionUnloaded = true;
this.trigger('extensionUnloaded'); if (this._isTriggeringExtensionUnloaded) { return; }
try {
this._isTriggeringExtensionUnloaded = true;
this.trigger('extensionUnloaded');
} finally {
this._isTriggeringExtensionUnloaded = false;
}
} }
// Private // Private