1
0
mirror of https://github.com/koloml/furbooru-tagging-assistant.git synced 2025-12-23 23:02:58 +00:00

Merge pull request #52 from koloml/release/0.3.2

Release: 0.3.2
This commit is contained in:
2024-11-23 01:15:14 +04:00
committed by GitHub
6 changed files with 85 additions and 29 deletions

View File

@@ -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"

4
package-lock.json generated
View File

@@ -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"
},

View File

@@ -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",

View File

@@ -13,6 +13,10 @@ export class SearchWrapper extends BaseComponent {
#arePropertiesSuggestionsEnabled = false;
/** @type {"start"|"end"} */
#propertiesSuggestionsPosition = "start";
/** @type {HTMLElement|null} */
#cachedAutocompleteContainer = null;
/** @type {TermToken|QuotedTermToken|null} */
#lastTermToken = null;
build() {
this.#searchField = this.container.querySelector('input[name=q]');
@@ -94,6 +98,7 @@ export class SearchWrapper extends BaseComponent {
let searchValue = this.#searchField.value;
if (!searchValue) {
this.#lastTermToken = null;
return null;
}
@@ -103,16 +108,37 @@ 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;
}
/**
* 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.
@@ -121,12 +147,24 @@ 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 = 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,29 +277,12 @@ 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.
* @return {HTMLElement} Resulting element.
*/
static #renderTermSuggestion(suggestedTerm) {
#renderTermSuggestion(suggestedTerm) {
/** @type {HTMLElement} */
const suggestionItem = document.createElement('li');
suggestionItem.classList.add('autocomplete__item', 'autocomplete__item--property');
@@ -269,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.

View File

@@ -10,5 +10,9 @@ Promise.allSettled([
]).then(() => {
fullScreenViewerEnabled.subscribe(value => {
void miscSettings.setFullscreenViewerEnabled(value);
})
});
miscSettings.subscribe(settings => {
fullScreenViewerEnabled.set(settings.fullscreenViewer);
});
});

View File

@@ -21,4 +21,9 @@ Promise.allSettled([
searchPropertiesSuggestionsPosition.subscribe(value => {
void searchSettings.setPropertiesSuggestionsPosition(value);
});
searchSettings.subscribe(settings => {
searchPropertiesSuggestionsEnabled.set(settings.suggestProperties);
searchPropertiesSuggestionsPosition.set(settings.suggestPropertiesPosition);
});
})