diff options
-rw-r--r-- | ext/bg/css/settings.css | 31 | ||||
-rw-r--r-- | ext/bg/js/settings/dictionary-controller.js | 156 | ||||
-rw-r--r-- | ext/bg/js/settings/dictionary-import-controller.js | 22 | ||||
-rw-r--r-- | ext/bg/settings.html | 87 |
4 files changed, 149 insertions, 147 deletions
diff --git a/ext/bg/css/settings.css b/ext/bg/css/settings.css index 9ec2e964..b755b33b 100644 --- a/ext/bg/css/settings.css +++ b/ext/bg/css/settings.css @@ -23,7 +23,7 @@ html:root:not([data-options-anki-enable=true]) #anki-general, html:root:not([data-options-general-debug-info=true]) .debug, html:root:not([data-options-general-show-advanced=true]) .options-advanced, -html:root:not([data-options-general-result-output-mode=merge]) #dict-main-group { +html:root:not([data-options-general-result-output-mode=merge]) #dictionary-main-group { display: none; } @@ -309,23 +309,26 @@ input[type=checkbox].storage-button-checkbox { height: 320px; } -.dict-delete-table { +.dictionary-delete-table { display: table; width: 100%; } -.dict-delete-table>*:first-child { +.dictionary-delete-table>*:first-child { display: table-cell; vertical-align: middle; padding-right: 1em; } -.dict-delete-table>*:nth-child(n+2) { +.dictionary-delete-table>*:nth-child(n+2) { display: table-cell; width: 100%; vertical-align: middle; } -.dict-delete-table .progress { +.dictionary-delete-table .progress { margin: 0; } +.dictionary-delete-table>*[hidden] { + display: none; +} .error-data-show-button { display: inline-block; @@ -377,44 +380,44 @@ html:root[data-operating-system=openbsd] [data-hide-for-operating-system~=openbs display: none; } -#dict-groups { +#dictionary-list { display: flex; flex-flow: column; } -.dict-details-container { +.dictionary-details-container { margin: 0.5em 0; } -.dict-details-toggle-link { +.dictionary-details-toggle-link { cursor: pointer; } -.dict-details { +.dictionary-details { margin-left: 1em; } -.dict-details-table { +.dictionary-details-table { display: table; width: 100% } -.dict-details-entry { +.dictionary-details-entry { display: table-row; } -.dict-details-entry+.dict-details-entry>* { +.dictionary-details-entry+.dictionary-details-entry>* { padding-top: 0.25em; } -.dict-details-entry-label { +.dictionary-details-entry-label { display: table-cell; font-weight: bold; white-space: nowrap; padding-right: 0.5em; } -.dict-details-entry-info { +.dictionary-details-entry-info { display: table-cell; white-space: pre-line; } diff --git a/ext/bg/js/settings/dictionary-controller.js b/ext/bg/js/settings/dictionary-controller.js index 34a4b933..3db16979 100644 --- a/ext/bg/js/settings/dictionary-controller.js +++ b/ext/bg/js/settings/dictionary-controller.js @@ -27,15 +27,8 @@ class DictionaryEntry { this._dictionaryController = dictionaryController; this._node = node; this._dictionaryInfo = dictionaryInfo; - this._dictionaryTitle = dictionaryInfo.title; this._eventListeners = new EventListenerCollection(); - this._enabledCheckbox = node.querySelector('.dict-enabled'); - this._allowSecondarySearchesCheckbox = node.querySelector('.dict-allow-secondary-searches'); - this._priorityInput = node.querySelector('.dict-priority'); - this._deleteButton = node.querySelector('.dict-delete-button'); - this._detailsToggleLink = node.querySelector('.dict-details-toggle-link'); - this._detailsContainer = node.querySelector('.dict-details'); - this._detailsTable = node.querySelector('.dict-details-table'); + this._detailsContainer = null; } get node() { @@ -43,31 +36,42 @@ class DictionaryEntry { } get dictionaryTitle() { - return this._dictionaryTitle; + return this._dictionaryInfo.title; } prepare() { const node = this._node; - const dictionaryInfo = this._dictionaryInfo; - const {title, revision, prefixWildcardsSupported} = dictionaryInfo; + const {title, revision, prefixWildcardsSupported, version} = this._dictionaryInfo; - if (dictionaryInfo.version < 3) { - node.querySelector('.dict-outdated').hidden = false; - } + this._detailsContainer = node.querySelector('.dictionary-details'); + + const enabledCheckbox = node.querySelector('.dictionary-enabled'); + const allowSecondarySearchesCheckbox = node.querySelector('.dictionary-allow-secondary-searches'); + const priorityInput = node.querySelector('.dictionary-priority'); + const deleteButton = node.querySelector('.dictionary-delete-button'); + const detailsTable = node.querySelector('.dictionary-details-table'); + const detailsToggleLink = node.querySelector('.dictionary-details-toggle-link'); + const outdatedContainer = node.querySelector('.dictionary-outdated-notification'); + const titleNode = node.querySelector('.dictionary-title'); + const versionNode = node.querySelector('.dictionary-version'); + const wildcardSupportedCheckbox = node.querySelector('.dictionary-prefix-wildcard-searches-supported'); + + const hasDetails = this._setupDetails(detailsTable); - node.querySelector('.dict-title').textContent = title; - node.querySelector('.dict-revision').textContent = `rev.${revision}`; - node.querySelector('.dict-prefix-wildcard-searches-supported').checked = !!prefixWildcardsSupported; + titleNode.textContent = title; + versionNode.textContent = `rev.${revision}`; + wildcardSupportedCheckbox.checked = !!prefixWildcardsSupported; - this._setupDetails(dictionaryInfo); + outdatedContainer.hidden = (version >= 3); + detailsToggleLink.hidden = !hasDetails; - this._enabledCheckbox.dataset.setting = ObjectPropertyAccessor.getPathString(['dictionaries', title, 'enabled']); - this._allowSecondarySearchesCheckbox.dataset.setting = ObjectPropertyAccessor.getPathString(['dictionaries', title, 'allowSecondarySearches']); - this._priorityInput.dataset.setting = ObjectPropertyAccessor.getPathString(['dictionaries', title, 'priority']); + enabledCheckbox.dataset.setting = ObjectPropertyAccessor.getPathString(['dictionaries', title, 'enabled']); + allowSecondarySearchesCheckbox.dataset.setting = ObjectPropertyAccessor.getPathString(['dictionaries', title, 'allowSecondarySearches']); + priorityInput.dataset.setting = ObjectPropertyAccessor.getPathString(['dictionaries', title, 'priority']); - this._eventListeners.addEventListener(this._deleteButton, 'click', this._onDeleteButtonClicked.bind(this), false); - this._eventListeners.addEventListener(this._detailsToggleLink, 'click', this._onDetailsToggleLinkClicked.bind(this), false); - this._eventListeners.addEventListener(this._priorityInput, 'settingChanged', this._onPriorityChanged.bind(this), false); + this._eventListeners.addEventListener(deleteButton, 'click', this._onDeleteButtonClicked.bind(this), false); + this._eventListeners.addEventListener(detailsToggleLink, 'click', this._onDetailsToggleLinkClicked.bind(this), false); + this._eventListeners.addEventListener(priorityInput, 'settingChanged', this._onPriorityChanged.bind(this), false); } cleanup() { @@ -79,7 +83,7 @@ class DictionaryEntry { } setCounts(counts) { - const node = this._node.querySelector('.dict-counts'); + const node = this._node.querySelector('.dictionary-counts'); node.textContent = JSON.stringify({info: this._dictionaryInfo, counts}, null, 4); node.hidden = false; } @@ -88,7 +92,7 @@ class DictionaryEntry { _onDeleteButtonClicked(e) { e.preventDefault(); - this._dictionaryController.deleteDictionary(this._dictionaryTitle); + this._dictionaryController.deleteDictionary(this.dictionaryTitle); } _onDetailsToggleLinkClicked(e) { @@ -101,7 +105,7 @@ class DictionaryEntry { this._node.style.order = `${-value}`; } - _setupDetails(dictionaryInfo) { + _setupDetails(detailsTable) { const targets = [ ['Author', 'author'], ['URL', 'url'], @@ -109,37 +113,24 @@ class DictionaryEntry { ['Attribution', 'attribution'] ]; + const dictionaryInfo = this._dictionaryInfo; const fragment = document.createDocumentFragment(); - let count = 0; + let any = false; for (const [label, key] of targets) { const info = dictionaryInfo[key]; if (typeof info !== 'string') { continue; } - const n1 = document.createElement('div'); - n1.className = 'dict-details-entry'; - n1.dataset.type = key; - - const n2 = document.createElement('span'); - n2.className = 'dict-details-entry-label'; - n2.textContent = `${label}:`; - n1.appendChild(n2); - - const n3 = document.createElement('span'); - n3.className = 'dict-details-entry-info'; - n3.textContent = info; - n1.appendChild(n3); - - fragment.appendChild(n1); + const details = this._dictionaryController.instantiateTemplate('dictionary-details-entry'); + details.dataset.type = key; + details.querySelector('.dictionary-details-entry-label').textContent = `${label}:`; + details.querySelector('.dictionary-details-entry-info').textContent = info; + fragment.appendChild(details); - ++count; + any = true; } - if (count > 0) { - this._detailsTable.appendChild(fragment); - } else { - this._detailsContainer.hidden = true; - this._detailsToggleLink.hidden = true; - } + detailsTable.appendChild(fragment); + return any; } } @@ -152,7 +143,6 @@ class DictionaryController { this._databaseStateToken = null; this._checkingIntegrity = false; this._warningNode = null; - this._mainDictionarySelect = null; this._checkIntegrityButton = null; this._dictionaryEntryContainer = null; this._integrityExtraInfoContainer = null; @@ -162,16 +152,15 @@ class DictionaryController { } async prepare() { - this._warningNode = document.querySelector('#dict-warning'); - this._mainDictionarySelect = document.querySelector('#dict-main'); - this._checkIntegrityButton = document.querySelector('#dict-check-integrity'); - this._dictionaryEntryContainer = document.querySelector('#dict-groups'); - this._integrityExtraInfoContainer = document.querySelector('#dict-groups-extra'); - this._deleteDictionaryModal = this._modalController.getModal('dict-delete-modal'); + this._warningNode = document.querySelector('#dictionary-warning'); + this._checkIntegrityButton = document.querySelector('#dictionary-check-integrity'); + this._dictionaryEntryContainer = document.querySelector('#dictionary-list'); + this._integrityExtraInfoContainer = document.querySelector('#dictionary-list-extra'); + this._deleteDictionaryModal = this._modalController.getModal('dictionary-confirm-delete'); yomichan.on('databaseUpdated', this._onDatabaseUpdated.bind(this)); - document.querySelector('#dict-delete-confirm').addEventListener('click', this._onDictionaryConfirmDelete.bind(this), false); + document.querySelector('#dictionary-confirm-delete-button').addEventListener('click', this._onDictionaryConfirmDelete.bind(this), false); this._checkIntegrityButton.addEventListener('click', this._onCheckIntegrityButtonClick.bind(this), false); await this._onDatabaseUpdated(); @@ -181,10 +170,14 @@ class DictionaryController { if (this._isDeleting) { return; } const modal = this._deleteDictionaryModal; modal.node.dataset.dictionaryTitle = dictionaryTitle; - modal.node.querySelector('#dict-remove-modal-dict-name').textContent = dictionaryTitle; + modal.node.querySelector('#dictionary-confirm-delete-name').textContent = dictionaryTitle; modal.setVisible(true); } + instantiateTemplate(name) { + return this._settingsController.instantiateTemplate(name); + } + // Private async _onDatabaseUpdated() { @@ -227,25 +220,26 @@ class DictionaryController { } _updateMainDictionarySelectOptions(dictionaries) { - const fragment = document.createDocumentFragment(); + for (const select of document.querySelectorAll('[data-setting="general.mainDictionary"]')) { + const fragment = document.createDocumentFragment(); - let option = document.createElement('option'); - option.className = 'text-muted'; - option.value = ''; - option.textContent = 'Not selected'; - fragment.appendChild(option); - - for (const {title, sequenced} of dictionaries) { - if (!sequenced) { continue; } - option = document.createElement('option'); - option.value = title; - option.textContent = title; + let option = document.createElement('option'); + option.className = 'text-muted'; + option.value = ''; + option.textContent = 'Not selected'; fragment.appendChild(option); - } - const select = this._mainDictionarySelect; - select.textContent = ''; // Empty - select.appendChild(fragment); + for (const {title, sequenced} of dictionaries) { + if (!sequenced) { continue; } + option = document.createElement('option'); + option.value = title; + option.textContent = title; + fragment.appendChild(option); + } + + select.textContent = ''; // Empty + select.appendChild(fragment); + } } async _checkIntegrity() { @@ -294,12 +288,12 @@ class DictionaryController { } _createExtra(totalCounts, remainders, totalRemainder) { - const node = this._settingsController.instantiateTemplate('dict-extra'); + const node = this.instantiateTemplate('dictionary-extra'); this._integrityExtraInfoNode = node; - node.querySelector('.dict-total-count').textContent = `${totalRemainder} item${totalRemainder !== 1 ? 's' : ''}`; + node.querySelector('.dictionary-total-count').textContent = `${totalRemainder} item${totalRemainder !== 1 ? 's' : ''}`; - const n = node.querySelector('.dict-counts'); + const n = node.querySelector('.dictionary-counts'); n.textContent = JSON.stringify({counts: totalCounts, remainders}, null, 4); n.hidden = false; @@ -318,7 +312,7 @@ class DictionaryController { } _createDictionaryEntry(dictionary) { - const node = this._settingsController.instantiateTemplate('dict'); + const node = this.instantiateTemplate('dictionary'); this._dictionaryEntryContainer.appendChild(node); const entry = new DictionaryEntry(this, node, dictionary); @@ -334,7 +328,7 @@ class DictionaryController { const entry = this._dictionaryEntries[index]; const node = entry.node; - const progress = node.querySelector('.progress'); + const progress = node.querySelector('.progress-container'); const progressBar = node.querySelector('.progress-bar'); const prevention = this._settingsController.preventPageExit(); try { @@ -365,7 +359,7 @@ class DictionaryController { _setButtonsEnabled(value) { value = !value; - for (const node of document.querySelectorAll('.dictionary-modifying-input')) { + for (const node of document.querySelectorAll('.dictionary-database-mutating-input')) { node.disabled = value; } } diff --git a/ext/bg/js/settings/dictionary-import-controller.js b/ext/bg/js/settings/dictionary-import-controller.js index 2c954ef8..e0ee0ee3 100644 --- a/ext/bg/js/settings/dictionary-import-controller.js +++ b/ext/bg/js/settings/dictionary-import-controller.js @@ -51,17 +51,17 @@ class DictionaryImportController { } async prepare() { - this._purgeButton = document.querySelector('#dict-purge-button'); - this._purgeConfirmButton = document.querySelector('#dict-purge-confirm'); - this._importFileButton = document.querySelector('#dict-file-button'); - this._importFileInput = document.querySelector('#dict-file'); - this._purgeConfirmModal = this._modalController.getModal('dict-purge-modal'); - this._errorContainer = document.querySelector('#dict-error'); - this._spinner = document.querySelector('#dict-spinner'); - this._progressContainer = document.querySelector('#dict-import-progress'); + this._purgeButton = document.querySelector('#dictionary-delete-all-button'); + this._purgeConfirmButton = document.querySelector('#dictionary-confirm-delete-all-button'); + this._importFileButton = document.querySelector('#dictionary-import-file-button'); + this._importFileInput = document.querySelector('#dictionary-import-file-input'); + this._purgeConfirmModal = this._modalController.getModal('dictionary-confirm-delete-all'); + this._errorContainer = document.querySelector('#dictionary-error'); + this._spinner = document.querySelector('#dictionary-spinner'); + this._progressContainer = document.querySelector('#dictionary-import-progress-container'); this._progressBar = this._progressContainer.querySelector('.progress-bar'); - this._purgeNotification = document.querySelector('#dict-purge'); - this._importInfo = document.querySelector('#dict-import-info'); + this._purgeNotification = document.querySelector('#dictionary-delete-all-status'); + this._importInfo = document.querySelector('#dictionary-import-info'); this._purgeButton.addEventListener('click', this._onPurgeButtonClick.bind(this), false); this._purgeConfirmButton.addEventListener('click', this._onPurgeConfirmButtonClick.bind(this), false); @@ -301,7 +301,7 @@ class DictionaryImportController { _setButtonsEnabled(value) { value = !value; - for (const node of document.querySelectorAll('.dictionary-modifying-input')) { + for (const node of document.querySelectorAll('.dictionary-database-mutating-input')) { node.disabled = value; } } diff --git a/ext/bg/settings.html b/ext/bg/settings.html index 18e3e389..d513ab57 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -656,7 +656,7 @@ <div class="ignore-form-changes"> <div> - <img src="/mixed/img/spinner.gif" class="pull-right" id="dict-spinner" alt hidden> + <img src="/mixed/img/spinner.gif" class="pull-right" id="dictionary-spinner" alt hidden> <h3>Dictionaries</h3> </div> @@ -664,38 +664,38 @@ Yomichan can import and use a variety of dictionary formats. Unneeded dictionaries can be disabled. </p> - <div class="form-group" id="dict-main-group"> - <label for="dict-main">Main dictionary for merged mode</label> - <select class="form-control" id="dict-main" data-setting="general.mainDictionary"></select> + <div class="form-group" id="dictionary-main-group"> + <label for="dictionary-main">Main dictionary for merged mode</label> + <select class="form-control" id="dictionary-main" data-setting="general.mainDictionary"></select> </div> - <div class="text-danger" id="dict-purge" hidden>Dictionary data is being purged, please be patient...</div> - <div class="alert alert-warning" id="dict-warning" hidden>No dictionaries have been installed</div> - <div class="alert alert-danger" id="dict-error" hidden></div> + <div class="text-danger" id="dictionary-delete-all-status" hidden>Dictionary data is being purged, please be patient...</div> + <div class="alert alert-warning" id="dictionary-warning" hidden>No dictionaries have been installed</div> + <div class="alert alert-danger" id="dictionary-error" hidden></div> - <div id="dict-groups"></div> - <div id="dict-groups-extra"></div> + <div id="dictionary-list"></div> + <div id="dictionary-list-extra"></div> - <div id="dict-import-progress" hidden> + <div id="dictionary-import-progress-container" hidden> Dictionary data is being imported, please be patient... - <span id="dict-import-info" hidden></span> + <span id="dictionary-import-info" hidden></span> <div class="progress"> <div class="progress-bar progress-bar-striped" style="width: 0%"></div> </div> </div> - <div id="dict-importer"> + <div> <p class="help-block"> Select a dictionary to import for use below. Please visit the Yomichan homepage to <a href="https://foosoft.net/projects/yomichan" target="_blank" rel="noopener">download free dictionaries</a> for use with this extension and to learn about importing proprietary EPWING dictionaries. </p> <div> - <button class="btn btn-primary dictionary-modifying-input" id="dict-file-button">Import Dictionary</button> - <button class="btn btn-danger dictionary-modifying-input" id="dict-purge-button">Purge Database</button> - <button class="btn btn-default dictionary-modifying-input" id="dict-check-integrity">Check integrity</button> + <button class="btn btn-primary dictionary-database-mutating-input" id="dictionary-import-file-button">Import Dictionary</button> + <button class="btn btn-danger dictionary-database-mutating-input" id="dictionary-delete-all-button">Purge Database</button> + <button class="btn btn-default dictionary-database-mutating-input" id="dictionary-check-integrity">Check integrity</button> </div> - <div hidden><input type="file" id="dict-file" accept=".zip,application/zip" multiple></div> + <div hidden><input type="file" id="dictionary-import-file-input" accept=".zip,application/zip" multiple></div> </div> <div> @@ -710,7 +710,7 @@ </p> </div> - <div class="modal fade" tabindex="-1" role="dialog" id="dict-purge-modal"> + <div class="modal fade" tabindex="-1" role="dialog" id="dictionary-confirm-delete-all"> <div class="modal-dialog modal-dialog-centered"> <div class="modal-content"> <div class="modal-header"> @@ -722,13 +722,13 @@ </div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> - <button type="button" class="btn btn-danger" id="dict-purge-confirm">Purge Database</button> + <button type="button" class="btn btn-danger" id="dictionary-confirm-delete-all-button">Purge Database</button> </div> </div> </div> </div> - <div class="modal fade" tabindex="-1" role="dialog" id="dict-delete-modal"> + <div class="modal fade" tabindex="-1" role="dialog" id="dictionary-confirm-delete"> <div class="modal-dialog modal-dialog-centered"> <div class="modal-content"> <div class="modal-header"> @@ -736,58 +736,63 @@ <h4 class="modal-title">Confirm dictionary deletion</h4> </div> <div class="modal-body"> - Are you sure you want to delete the dictionary <em id="dict-remove-modal-dict-name"></em>? + Are you sure you want to delete the dictionary <em id="dictionary-confirm-delete-name"></em>? This operation may take some time and the responsiveness of this browser tab may be reduced. </div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> - <button type="button" class="btn btn-danger dictionary-modifying-input" id="dict-delete-confirm">Delete Dictionary</button> + <button type="button" class="btn btn-danger dictionary-database-mutating-input" id="dictionary-confirm-delete-button">Delete Dictionary</button> </div> </div> </div> </div> - <template id="dict-template"><div class="dict-group well well-sm"> - <h4><span class="text-muted glyphicon glyphicon-book"></span> <span class="dict-title"></span> <small class="dict-revision"></small></h4> - <p class="text-warning dict-outdated" hidden>This dictionary is outdated and may not support new extension features; please import the latest version.</p> + <template id="dictionary-template"><div class="well well-sm"> + <h4><span class="text-muted glyphicon glyphicon-book"></span> <span class="dictionary-title"></span> <small class="dictionary-version"></small></h4> + <p class="text-warning dictionary-outdated-notification" hidden>This dictionary is outdated and may not support new extension features; please import the latest version.</p> <div class="checkbox"> - <label><input type="checkbox" class="dict-enabled"> Enable search</label> + <label><input type="checkbox" class="dictionary-enabled"> Enable search</label> </div> <div class="checkbox options-advanced"> - <label><input type="checkbox" class="dict-allow-secondary-searches"> Allow secondary searches</label> + <label><input type="checkbox" class="dictionary-allow-secondary-searches"> Allow secondary searches</label> </div> - <div class="checkbox dict-prefix-wildcard-searches-supported-container"> - <label><input type="checkbox" class="dict-prefix-wildcard-searches-supported" disabled> Prefix wildcard searches supported</label> + <div class="checkbox"> + <label><input type="checkbox" class="dictionary-prefix-wildcard-searches-supported" disabled> Prefix wildcard searches supported</label> </div> <div class="form-group options-advanced"> - <label class="dict-result-priority-label">Result priority</label> - <input type="number" class="form-control dict-priority"> + <label>Result priority</label> + <input type="number" class="form-control dictionary-priority"> </div> - <div class="dict-details-container"> - <a class="dict-details-toggle-link">Details...</a> - <div class="dict-details" hidden><div class="dict-details-table"></div></div> + <div class="dictionary-details-container"> + <a class="dictionary-details-toggle-link">Details...</a> + <div class="dictionary-details" hidden><div class="dictionary-details-table"></div></div> </div> - <div class="dict-delete-table"> + <div class="dictionary-delete-table"> <div> - <button class="btn btn-default dict-delete-button dictionary-modifying-input">Delete Dictionary</button> + <button class="btn btn-default dictionary-delete-button dictionary-database-mutating-input">Delete Dictionary</button> </div> - <div> - <div class="progress" hidden> + <div class="progress-container" hidden> + <div class="progress"> <div class="progress-bar progress-bar-striped" style="width: 0%"></div> </div> </div> </div> - <pre class="debug dict-counts" hidden></pre> + <pre class="debug dictionary-counts" hidden></pre> + </div></template> + + <template id="dictionary-details-entry-template"><div class="dictionary-details-entry"> + <span class="dictionary-details-entry-label"></span> + <span class="dictionary-details-entry-info"></span> </div></template> - <template id="dict-extra-template"><div class="well well-sm"> - <h4><span class="text-muted glyphicon glyphicon-alert"></span> <span class="dict-title">Unassociated Data</span> <small class="dict-total-count"></small></h4> + <template id="dictionary-extra-template"><div class="well well-sm"> + <h4><span class="text-muted glyphicon glyphicon-alert"></span> <span class="dictionary-title">Unassociated Data</span> <small class="dictionary-total-count"></small></h4> <p class="text-warning"> The database contains extra data which is not associated with any installed dictionary. Purging the database can fix this issue. </p> - <pre class="debug dict-counts" hidden></pre> + <pre class="debug dictionary-counts" hidden></pre> </div></template> </div> |