From 80ba4671f51d824a345fc0a26cc5187656744bd9 Mon Sep 17 00:00:00 2001 From: KoloMl Date: Thu, 14 Nov 2024 04:40:43 +0400 Subject: [PATCH 1/4] Fixed autocomplete popup duplication --- src/lib/components/SearchWrapper.js | 53 ++++++++++++++++++----------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/src/lib/components/SearchWrapper.js b/src/lib/components/SearchWrapper.js index 217c645..4e4378e 100644 --- a/src/lib/components/SearchWrapper.js +++ b/src/lib/components/SearchWrapper.js @@ -13,6 +13,8 @@ export class SearchWrapper extends BaseComponent { #arePropertiesSuggestionsEnabled = false; /** @type {"start"|"end"} */ #propertiesSuggestionsPosition = "start"; + /** @type {HTMLElement|null} */ + #cachedAutocompleteContainer = null; build() { this.#searchField = this.container.querySelector('input[name=q]'); @@ -113,6 +115,24 @@ export class SearchWrapper extends BaseComponent { return searchValue; } + /** + * Resolve the autocomplete container from the document. Once resolved, it can be safely reused without breaking + * anything. Assuming refactored autocomplete handler is still implemented the way it is. + * + * This means, that properties will only be suggested once actual autocomplete logic was activated. + * + * @return {HTMLElement|null} Resolved element or nothing. + */ + #resolveAutocompleteContainer() { + if (this.#cachedAutocompleteContainer) { + return this.#cachedAutocompleteContainer; + } + + this.#cachedAutocompleteContainer = document.querySelector('.autocomplete'); + + return this.#cachedAutocompleteContainer; + } + /** * Render the list of suggestions into the existing popup or create and populate a new one. * @param {string[]} suggestions List of suggestion to render the popup from. @@ -124,9 +144,21 @@ export class SearchWrapper extends BaseComponent { .map(suggestedTerm => SearchWrapper.#renderTermSuggestion(suggestedTerm)); requestAnimationFrame(() => { - const autocompleteContainer = document.querySelector('.autocomplete') ?? SearchWrapper.#renderAutocompleteContainer(); + const autocompleteContainer = this.#resolveAutocompleteContainer(); - for (let existingTerm of autocompleteContainer.querySelectorAll('.autocomplete__item--property')) { + if (!autocompleteContainer) { + return; + } + + // Since the autocomplete popup was refactored to re-use the same element over and over again, we need to remove + // the options from the popup manually when autocomplete was removed from the DOM, since site is not doing that. + const termsToRemove = autocompleteContainer.isConnected + // Only removing properties when element is still connected to the DOM (popup is used by the website) + ? autocompleteContainer.querySelectorAll('.autocomplete__item--property') + // Remove everything if popup was disconnected from the DOM. + : autocompleteContainer.querySelectorAll('.autocomplete__item') + + for (let existingTerm of termsToRemove) { existingTerm.remove(); } @@ -239,23 +271,6 @@ export class SearchWrapper extends BaseComponent { return suggestionsList; } - /** - * Render a new autocomplete container similar to the one generated by website. Might be sensitive to the updates - * made to the Philomena. - * @return {HTMLElement} - */ - static #renderAutocompleteContainer() { - const autocompleteContainer = document.createElement('div'); - autocompleteContainer.className = 'autocomplete'; - - const innerListContainer = document.createElement('ul'); - innerListContainer.className = 'autocomplete__list'; - - autocompleteContainer.append(innerListContainer); - - return autocompleteContainer; - } - /** * Render a single suggestion item and connect required events to interact with the user. * @param {string} suggestedTerm Term to use for suggestion item. From c0139d06386bb2d57fb07d5c995a2d533f8fee6d Mon Sep 17 00:00:00 2001 From: KoloMl Date: Thu, 14 Nov 2024 05:06:27 +0400 Subject: [PATCH 2/4] Fixed properties not being clickable with mouse --- src/lib/components/SearchWrapper.js | 42 +++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/src/lib/components/SearchWrapper.js b/src/lib/components/SearchWrapper.js index 4e4378e..43720db 100644 --- a/src/lib/components/SearchWrapper.js +++ b/src/lib/components/SearchWrapper.js @@ -15,6 +15,8 @@ export class SearchWrapper extends BaseComponent { #propertiesSuggestionsPosition = "start"; /** @type {HTMLElement|null} */ #cachedAutocompleteContainer = null; + /** @type {TermToken|QuotedTermToken|null} */ + #lastTermToken = null; build() { this.#searchField = this.container.querySelector('input[name=q]'); @@ -96,6 +98,7 @@ export class SearchWrapper extends BaseComponent { let searchValue = this.#searchField.value; if (!searchValue) { + this.#lastTermToken = null; return null; } @@ -105,13 +108,16 @@ export class SearchWrapper extends BaseComponent { ); if (token instanceof TermToken) { + this.#lastTermToken = token; return token.value; } if (token instanceof QuotedTermToken) { + this.#lastTermToken = token; return token.decodedValue; } + this.#lastTermToken = null; return searchValue; } @@ -141,7 +147,7 @@ export class SearchWrapper extends BaseComponent { #renderSuggestions(suggestions, targetInput) { /** @type {HTMLElement[]} */ const suggestedListItems = suggestions - .map(suggestedTerm => SearchWrapper.#renderTermSuggestion(suggestedTerm)); + .map(suggestedTerm => this.#renderTermSuggestion(suggestedTerm)); requestAnimationFrame(() => { const autocompleteContainer = this.#resolveAutocompleteContainer(); @@ -276,7 +282,7 @@ export class SearchWrapper extends BaseComponent { * @param {string} suggestedTerm Term to use for suggestion item. * @return {HTMLElement} Resulting element. */ - static #renderTermSuggestion(suggestedTerm) { + #renderTermSuggestion(suggestedTerm) { /** @type {HTMLElement} */ const suggestionItem = document.createElement('li'); suggestionItem.classList.add('autocomplete__item', 'autocomplete__item--property'); @@ -284,17 +290,43 @@ export class SearchWrapper extends BaseComponent { suggestionItem.innerText = suggestedTerm; suggestionItem.addEventListener('mouseover', () => { - this.#findAndResetSelectedSuggestion(suggestionItem); + SearchWrapper.#findAndResetSelectedSuggestion(suggestionItem); suggestionItem.classList.add('autocomplete__item--selected'); }); suggestionItem.addEventListener('mouseout', () => { - this.#findAndResetSelectedSuggestion(suggestionItem); - }) + SearchWrapper.#findAndResetSelectedSuggestion(suggestionItem); + }); + + suggestionItem.addEventListener('click', () => { + this.#replaceLastActiveTokenWithSuggestion(suggestedTerm); + }); return suggestionItem; } + /** + * Automatically replace the last active token stored in the variable with the new value. + * @param {string} suggestedTerm Term to replace the value with. + */ + #replaceLastActiveTokenWithSuggestion(suggestedTerm) { + if (!this.#lastTermToken) { + return; + } + + const searchQuery = this.#searchField.value; + const beforeToken = searchQuery.substring(0, this.#lastTermToken.index); + const afterToken = searchQuery.substring(this.#lastTermToken.index + this.#lastTermToken.value.length); + + let replacementValue = suggestedTerm; + + if (replacementValue.includes('"')) { + replacementValue = `"${QuotedTermToken.encode(replacementValue)}"` + } + + this.#searchField.value = beforeToken + replacementValue + afterToken; + } + /** * Find the selected suggestion item(s) and unselect them. Similar to the logic implemented by the Philomena's * front-end. From a8f0f161214b80187ac91fc0c3a62e1b02aecdf6 Mon Sep 17 00:00:00 2001 From: KoloMl Date: Sat, 23 Nov 2024 00:47:56 +0400 Subject: [PATCH 3/4] Fixed missing backward synchronization from browser storage to stores --- src/stores/misc-preferences.js | 6 +++++- src/stores/search-preferences.js | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/stores/misc-preferences.js b/src/stores/misc-preferences.js index c2dcb04..22eb309 100644 --- a/src/stores/misc-preferences.js +++ b/src/stores/misc-preferences.js @@ -10,5 +10,9 @@ Promise.allSettled([ ]).then(() => { fullScreenViewerEnabled.subscribe(value => { void miscSettings.setFullscreenViewerEnabled(value); - }) + }); + + miscSettings.subscribe(settings => { + fullScreenViewerEnabled.set(settings.fullscreenViewer); + }); }); diff --git a/src/stores/search-preferences.js b/src/stores/search-preferences.js index f485261..5d01486 100644 --- a/src/stores/search-preferences.js +++ b/src/stores/search-preferences.js @@ -21,4 +21,9 @@ Promise.allSettled([ searchPropertiesSuggestionsPosition.subscribe(value => { void searchSettings.setPropertiesSuggestionsPosition(value); }); + + searchSettings.subscribe(settings => { + searchPropertiesSuggestionsEnabled.set(settings.suggestProperties); + searchPropertiesSuggestionsPosition.set(settings.suggestPropertiesPosition); + }); }) From b645a1ca7a5b409ec673c02968dcc096710c26cf Mon Sep 17 00:00:00 2001 From: KoloMl Date: Sat, 23 Nov 2024 01:09:31 +0400 Subject: [PATCH 4/4] Bumped version to 0.3.2 --- manifest.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/manifest.json b/manifest.json index 8ca34e3..d88089d 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "name": "Furbooru Tagging Assistant", "description": "Experimental extension with a set of tools to make the tagging faster and easier. Made specifically for Furbooru.", - "version": "0.3.1", + "version": "0.3.2", "browser_specific_settings": { "gecko": { "id": "furbooru-tagging-assistant@thecore.city" diff --git a/package-lock.json b/package-lock.json index 82dbc12..c78ff81 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "furbooru-tagging-assistant", - "version": "0.3.1", + "version": "0.3.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "furbooru-tagging-assistant", - "version": "0.3.1", + "version": "0.3.2", "dependencies": { "lz-string": "^1.5.0" }, diff --git a/package.json b/package.json index 32762e5..92044cd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "furbooru-tagging-assistant", - "version": "0.3.1", + "version": "0.3.2", "private": true, "scripts": { "build": "npm run build:popup && npm run build:extension",