mirror of
https://github.com/koloml/philomena-tagging-assistant.git
synced 2026-11-07 16:18:19 +00:00
Merge pull request #122 from koloml/feature/removing-properties-suggestions
Properties Suggestions: Removing feature since it'll be integrated into Philomena
This commit is contained in:
9 files changed
-569
No files matched your search
@@ -41,17 +41,6 @@
|
||||
"src/styles/content/listing.scss"
|
||||
]
|
||||
},
|
||||
{
|
||||
"matches": [
|
||||
"*://*.furbooru.org/*"
|
||||
],
|
||||
"js": [
|
||||
"src/content/header.ts"
|
||||
],
|
||||
"css": [
|
||||
"src/styles/content/header.scss"
|
||||
]
|
||||
},
|
||||
{
|
||||
"matches": [
|
||||
"*://*.furbooru.org/images/*"
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { initializeSiteHeader } from "$lib/components/SiteHeaderWrapper";
|
||||
|
||||
const siteHeader = document.querySelector<HTMLElement>('.header');
|
||||
|
||||
if (siteHeader) {
|
||||
initializeSiteHeader(siteHeader);
|
||||
}
|
||||
@@ -1,429 +0,0 @@
|
||||
import { BaseComponent } from "$lib/components/base/BaseComponent";
|
||||
import { QueryLexer, QuotedTermToken, TermToken, Token } from "$lib/booru/search/QueryLexer";
|
||||
import SearchSettings, { type SuggestionsPosition } from "$lib/extension/settings/SearchSettings";
|
||||
|
||||
export class SearchWrapper extends BaseComponent {
|
||||
#searchField: HTMLInputElement | null = null;
|
||||
#lastParsedSearchValue: string | null = null;
|
||||
#cachedParsedQuery: Token[] = [];
|
||||
#searchSettings: SearchSettings = new SearchSettings();
|
||||
#arePropertiesSuggestionsEnabled: boolean = false;
|
||||
#propertiesSuggestionsPosition: SuggestionsPosition = "start";
|
||||
#cachedAutocompleteContainer: HTMLElement | null = null;
|
||||
#lastTermToken: TermToken | QuotedTermToken | null = null;
|
||||
|
||||
build() {
|
||||
this.#searchField = this.container.querySelector('input[name=q]');
|
||||
}
|
||||
|
||||
init() {
|
||||
if (this.#searchField) {
|
||||
this.#searchField.addEventListener('input', this.#onInputFindProperties.bind(this))
|
||||
}
|
||||
|
||||
this.#searchSettings.resolvePropertiesSuggestionsEnabled()
|
||||
.then(isEnabled => this.#arePropertiesSuggestionsEnabled = isEnabled);
|
||||
this.#searchSettings.resolvePropertiesSuggestionsPosition()
|
||||
.then(position => this.#propertiesSuggestionsPosition = position);
|
||||
|
||||
this.#searchSettings.subscribe(settings => {
|
||||
this.#arePropertiesSuggestionsEnabled = Boolean(settings.suggestProperties);
|
||||
this.#propertiesSuggestionsPosition = settings.suggestPropertiesPosition || "start";
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Catch the user input and execute suggestions logic.
|
||||
* @param event Source event to find the input element from.
|
||||
*/
|
||||
#onInputFindProperties(event: Event) {
|
||||
// Ignore events until option is enabled.
|
||||
if (!this.#arePropertiesSuggestionsEnabled || !(event.currentTarget instanceof HTMLInputElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentFragment = this.#findCurrentTagFragment();
|
||||
|
||||
if (!currentFragment) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#renderSuggestions(
|
||||
SearchWrapper.#resolveSuggestionsFromTerm(currentFragment),
|
||||
event.currentTarget
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the selection position in the search field.
|
||||
*/
|
||||
#getInputUserSelection(): number {
|
||||
if (!this.#searchField) {
|
||||
throw new Error('Missing search field!');
|
||||
}
|
||||
|
||||
return Math.min(
|
||||
this.#searchField.selectionStart ?? 0,
|
||||
this.#searchField.selectionEnd ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the search query and return the list of parsed tokens. Result will be cached for current search query.
|
||||
*/
|
||||
#resolveQueryTokens(): Token[] {
|
||||
if (!this.#searchField) {
|
||||
throw new Error('Missing search field!');
|
||||
}
|
||||
|
||||
const searchValue = this.#searchField.value;
|
||||
|
||||
if (searchValue === this.#lastParsedSearchValue && this.#cachedParsedQuery) {
|
||||
return this.#cachedParsedQuery;
|
||||
}
|
||||
|
||||
this.#lastParsedSearchValue = searchValue;
|
||||
this.#cachedParsedQuery = new QueryLexer(searchValue).parse();
|
||||
|
||||
return this.#cachedParsedQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the currently selected term.
|
||||
* @return Selected term or null if none found.
|
||||
*/
|
||||
#findCurrentTagFragment(): string | null {
|
||||
if (!this.#searchField) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let searchValue = this.#searchField.value;
|
||||
|
||||
if (!searchValue) {
|
||||
this.#lastTermToken = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
const token = SearchWrapper.#findActiveSearchTermPosition(
|
||||
this.#resolveQueryTokens(),
|
||||
this.#getInputUserSelection(),
|
||||
);
|
||||
|
||||
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 Resolved element or nothing.
|
||||
*/
|
||||
#resolveAutocompleteContainer(): HTMLElement | null {
|
||||
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 suggestions List of suggestion to render the popup from.
|
||||
* @param targetInput Target input to attach the popup to.
|
||||
*/
|
||||
#renderSuggestions(suggestions: string[], targetInput: HTMLInputElement) {
|
||||
const suggestedListItems = suggestions
|
||||
.map(suggestedTerm => this.#renderTermSuggestion(suggestedTerm));
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
const autocompleteContainer = this.#resolveAutocompleteContainer();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
const listContainer = autocompleteContainer.querySelector('ul');
|
||||
|
||||
if (!listContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (this.#propertiesSuggestionsPosition) {
|
||||
case "start":
|
||||
listContainer.prepend(...suggestedListItems);
|
||||
break;
|
||||
|
||||
case "end":
|
||||
listContainer.append(...suggestedListItems);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn("Invalid position for property suggestions!");
|
||||
}
|
||||
|
||||
const parentScrollTop = targetInput.parentElement?.scrollTop ?? 0;
|
||||
|
||||
autocompleteContainer.style.position = 'absolute';
|
||||
autocompleteContainer.style.left = `${targetInput.offsetLeft}px`;
|
||||
autocompleteContainer.style.top = `${targetInput.offsetTop + targetInput.offsetHeight - parentScrollTop}px`;
|
||||
|
||||
document.body.append(autocompleteContainer);
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Loosely estimate where current selected search term is located and return it if found.
|
||||
* @param tokens Search value to find the actively selected term from.
|
||||
* @param userSelectionIndex The index of the user selection.
|
||||
* @return Search term object or NULL if nothing found.
|
||||
*/
|
||||
static #findActiveSearchTermPosition(tokens: Token[], userSelectionIndex: number): Token | null {
|
||||
return tokens.find(
|
||||
token => token.index < userSelectionIndex && token.index + token.value.length >= userSelectionIndex
|
||||
) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Regular expression to search the properties' syntax.
|
||||
*/
|
||||
static #propertySearchTermHeadingRegExp = /^(?<name>[a-z\d_]+)(?<op_syntax>\.(?<op>[a-z]*))?(?<value_syntax>:(?<value>.*))?$/;
|
||||
|
||||
/**
|
||||
* Create a list of suggested elements using the input received from the user.
|
||||
* @param searchTermValue Original decoded term received from the user.
|
||||
* @return {string[]} List of suggestions. Could be empty.
|
||||
*/
|
||||
static #resolveSuggestionsFromTerm(searchTermValue: string): string[] {
|
||||
const suggestionsList: string[] = [];
|
||||
|
||||
this.#propertySearchTermHeadingRegExp.lastIndex = 0;
|
||||
const parsedResult = this.#propertySearchTermHeadingRegExp.exec(searchTermValue);
|
||||
|
||||
if (!parsedResult) {
|
||||
return suggestionsList;
|
||||
}
|
||||
|
||||
const propertyName = parsedResult.groups?.name;
|
||||
|
||||
if (!propertyName) {
|
||||
return suggestionsList;
|
||||
}
|
||||
|
||||
const propertyType = this.#properties.get(propertyName);
|
||||
const hasOperatorSyntax = Boolean(parsedResult.groups?.op_syntax);
|
||||
const hasValueSyntax = Boolean(parsedResult.groups?.value_syntax);
|
||||
|
||||
// No suggestions for values for now, maybe could add suggestions for namespaces like my:*
|
||||
if (hasValueSyntax && propertyType) {
|
||||
if (this.#typeValues.has(propertyType)) {
|
||||
const givenValue = parsedResult.groups?.value;
|
||||
const candidateValues = this.#typeValues.get(propertyType) || [];
|
||||
|
||||
for (let candidateValue of candidateValues) {
|
||||
if (givenValue && !candidateValue.startsWith(givenValue)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
suggestionsList.push(`${propertyName}${parsedResult.groups?.op_syntax ?? ''}:${candidateValue}`);
|
||||
}
|
||||
}
|
||||
|
||||
return suggestionsList;
|
||||
}
|
||||
|
||||
// If at least one dot placed, start suggesting operators
|
||||
if (hasOperatorSyntax && propertyType) {
|
||||
if (this.#typeOperators.has(propertyType)) {
|
||||
const operatorName = parsedResult.groups?.op;
|
||||
const candidateOperators = this.#typeOperators.get(propertyType) ?? [];
|
||||
|
||||
for (let candidateOperator of candidateOperators) {
|
||||
if (operatorName && !candidateOperator.startsWith(operatorName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
suggestionsList.push(`${propertyName}.${candidateOperator}:`);
|
||||
}
|
||||
}
|
||||
|
||||
return suggestionsList;
|
||||
}
|
||||
|
||||
// Otherwise, search for properties with names starting with the term
|
||||
for (let [candidateProperty] of this.#properties) {
|
||||
if (propertyName && !candidateProperty.startsWith(propertyName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
suggestionsList.push(candidateProperty);
|
||||
}
|
||||
|
||||
return suggestionsList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a single suggestion item and connect required events to interact with the user.
|
||||
* @param suggestedTerm Term to use for suggestion item.
|
||||
* @return Resulting element.
|
||||
*/
|
||||
#renderTermSuggestion(suggestedTerm: string): HTMLElement {
|
||||
const suggestionItem = document.createElement('li');
|
||||
suggestionItem.classList.add('autocomplete__item', 'autocomplete__item--property');
|
||||
suggestionItem.dataset.value = suggestedTerm;
|
||||
suggestionItem.innerText = suggestedTerm;
|
||||
|
||||
const propertyIcon = document.createElement('i');
|
||||
propertyIcon.classList.add('fa', 'fa-info-circle');
|
||||
suggestionItem.insertAdjacentElement('afterbegin', propertyIcon);
|
||||
|
||||
suggestionItem.addEventListener('mouseover', () => {
|
||||
SearchWrapper.#findAndResetSelectedSuggestion(suggestionItem);
|
||||
suggestionItem.classList.add('autocomplete__item--selected');
|
||||
});
|
||||
|
||||
suggestionItem.addEventListener('mouseout', () => {
|
||||
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 suggestedTerm Term to replace the value with.
|
||||
*/
|
||||
#replaceLastActiveTokenWithSuggestion(suggestedTerm: string) {
|
||||
if (!this.#lastTermToken || !this.#searchField) {
|
||||
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.
|
||||
* @param suggestedElement Target element to search from. If element is disconnected from the DOM, search will be
|
||||
* halted.
|
||||
*/
|
||||
static #findAndResetSelectedSuggestion(suggestedElement: HTMLElement) {
|
||||
if (!suggestedElement.parentElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let selectedElement of suggestedElement.parentElement.querySelectorAll('.autocomplete__item--selected')) {
|
||||
selectedElement.classList.remove('autocomplete__item--selected');
|
||||
}
|
||||
}
|
||||
|
||||
static #typeNumeric = Symbol();
|
||||
static #typeDate = Symbol();
|
||||
static #typeLiteral = Symbol();
|
||||
static #typePersonal = Symbol();
|
||||
static #typeBoolean = Symbol();
|
||||
|
||||
static #properties = new Map([
|
||||
['animated', SearchWrapper.#typeBoolean],
|
||||
['aspect_ratio', SearchWrapper.#typeNumeric],
|
||||
['body_type_tag_count', SearchWrapper.#typeNumeric],
|
||||
['character_tag_count', SearchWrapper.#typeNumeric],
|
||||
['comment_count', SearchWrapper.#typeNumeric],
|
||||
['content_fanmade_tag_count', SearchWrapper.#typeNumeric],
|
||||
['content_official_tag_count', SearchWrapper.#typeNumeric],
|
||||
['created_at', SearchWrapper.#typeDate],
|
||||
['description', SearchWrapper.#typeLiteral],
|
||||
['downvotes', SearchWrapper.#typeNumeric],
|
||||
['duration', SearchWrapper.#typeNumeric],
|
||||
['error_tag_count', SearchWrapper.#typeNumeric],
|
||||
['faved_by', SearchWrapper.#typeLiteral],
|
||||
['faved_by_id', SearchWrapper.#typeNumeric],
|
||||
['faves', SearchWrapper.#typeNumeric],
|
||||
['file_name', SearchWrapper.#typeLiteral],
|
||||
['first_seen_at', SearchWrapper.#typeDate],
|
||||
['height', SearchWrapper.#typeNumeric],
|
||||
['id', SearchWrapper.#typeNumeric],
|
||||
['oc_tag_count', SearchWrapper.#typeNumeric],
|
||||
['orig_sha512_hash', SearchWrapper.#typeLiteral],
|
||||
['original_format', SearchWrapper.#typeLiteral],
|
||||
['pixels', SearchWrapper.#typeNumeric],
|
||||
['rating_tag_count', SearchWrapper.#typeNumeric],
|
||||
['score', SearchWrapper.#typeNumeric],
|
||||
['sha512_hash', SearchWrapper.#typeLiteral],
|
||||
['size', SearchWrapper.#typeNumeric],
|
||||
['source_count', SearchWrapper.#typeNumeric],
|
||||
['source_url', SearchWrapper.#typeLiteral],
|
||||
['species_tag_count', SearchWrapper.#typeNumeric],
|
||||
['spoiler_tag_count', SearchWrapper.#typeNumeric],
|
||||
['tag_count', SearchWrapper.#typeNumeric],
|
||||
['updated_at', SearchWrapper.#typeDate],
|
||||
['uploader', SearchWrapper.#typeLiteral],
|
||||
['uploader_id', SearchWrapper.#typeNumeric],
|
||||
['upvotes', SearchWrapper.#typeNumeric],
|
||||
['width', SearchWrapper.#typeNumeric],
|
||||
['wilson_score', SearchWrapper.#typeNumeric],
|
||||
['my', SearchWrapper.#typePersonal],
|
||||
]);
|
||||
|
||||
static #comparisonOperators = ['gt', 'gte', 'lt', 'lte'];
|
||||
|
||||
static #typeOperators = new Map([
|
||||
[SearchWrapper.#typeNumeric, SearchWrapper.#comparisonOperators],
|
||||
[SearchWrapper.#typeDate, SearchWrapper.#comparisonOperators],
|
||||
]);
|
||||
|
||||
static #typeValues = new Map([
|
||||
[SearchWrapper.#typePersonal, [
|
||||
'comments',
|
||||
'faves',
|
||||
'posts',
|
||||
'uploads',
|
||||
'upvotes',
|
||||
'watched',
|
||||
]],
|
||||
[SearchWrapper.#typeBoolean, [
|
||||
'true',
|
||||
'false',
|
||||
]]
|
||||
]);
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { BaseComponent } from "$lib/components/base/BaseComponent";
|
||||
import { SearchWrapper } from "$lib/components/SearchWrapper";
|
||||
|
||||
class SiteHeaderWrapper extends BaseComponent {
|
||||
#searchWrapper: SearchWrapper | null = null;
|
||||
|
||||
build() {
|
||||
const searchForm = this.container.querySelector<HTMLElement>('.header__search');
|
||||
this.#searchWrapper = searchForm && new SearchWrapper(searchForm) || null;
|
||||
}
|
||||
|
||||
init() {
|
||||
if (this.#searchWrapper) {
|
||||
this.#searchWrapper.initialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function initializeSiteHeader(siteHeaderElement: HTMLElement) {
|
||||
new SiteHeaderWrapper(siteHeaderElement)
|
||||
.initialize();
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import CacheableSettings from "$lib/extension/base/CacheableSettings";
|
||||
|
||||
export type SuggestionsPosition = "start" | "end";
|
||||
|
||||
interface SearchSettingsFields {
|
||||
suggestProperties: boolean;
|
||||
suggestPropertiesPosition: SuggestionsPosition;
|
||||
}
|
||||
|
||||
export default class SearchSettings extends CacheableSettings<SearchSettingsFields> {
|
||||
constructor() {
|
||||
super("search");
|
||||
}
|
||||
|
||||
async resolvePropertiesSuggestionsEnabled() {
|
||||
return this._resolveSetting("suggestProperties", false);
|
||||
}
|
||||
|
||||
async resolvePropertiesSuggestionsPosition() {
|
||||
return this._resolveSetting("suggestPropertiesPosition", "start");
|
||||
}
|
||||
|
||||
async setPropertiesSuggestions(isEnabled: boolean) {
|
||||
return this._writeSetting("suggestProperties", isEnabled);
|
||||
}
|
||||
|
||||
async setPropertiesSuggestionsPosition(position: "start" | "end") {
|
||||
return this._writeSetting("suggestPropertiesPosition", position);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@
|
||||
<MenuItem href="/" icon="arrow-left">Back</MenuItem>
|
||||
<hr>
|
||||
<MenuItem href="/preferences/tags">Tagging</MenuItem>
|
||||
<MenuItem href="/preferences/search">Search</MenuItem>
|
||||
<MenuItem href="/preferences/misc">Misc & Tools</MenuItem>
|
||||
<hr>
|
||||
<MenuItem href="/preferences/debug">Debug</MenuItem>
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
<script>
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import FormContainer from "$components/ui/forms/FormContainer.svelte";
|
||||
import FormControl from "$components/ui/forms/FormControl.svelte";
|
||||
import { searchPropertiesSuggestionsEnabled, searchPropertiesSuggestionsPosition } from "$stores/preferences/search";
|
||||
import CheckboxField from "$components/ui/forms/CheckboxField.svelte";
|
||||
import SelectField from "$components/ui/forms/SelectField.svelte";
|
||||
|
||||
const propertiesPositions = {
|
||||
start: "At the start of the list",
|
||||
end: "At the end of the list",
|
||||
}
|
||||
</script>
|
||||
|
||||
<Menu>
|
||||
<MenuItem href="/preferences" icon="arrow-left">Back</MenuItem>
|
||||
<hr>
|
||||
</Menu>
|
||||
<FormContainer>
|
||||
<FormControl>
|
||||
<CheckboxField bind:checked={$searchPropertiesSuggestionsEnabled}>
|
||||
Auto-complete properties
|
||||
</CheckboxField>
|
||||
</FormControl>
|
||||
{#if $searchPropertiesSuggestionsEnabled}
|
||||
<FormControl label="Show completed properties:">
|
||||
<SelectField bind:value={$searchPropertiesSuggestionsPosition}
|
||||
options="{propertiesPositions}"></SelectField>
|
||||
</FormControl>
|
||||
{/if}
|
||||
</FormContainer>
|
||||
@@ -1,28 +0,0 @@
|
||||
import { type Writable, writable } from "svelte/store";
|
||||
import SearchSettings, { type SuggestionsPosition } from "$lib/extension/settings/SearchSettings";
|
||||
|
||||
export const searchPropertiesSuggestionsEnabled = writable(false);
|
||||
|
||||
export const searchPropertiesSuggestionsPosition: Writable<SuggestionsPosition> = writable('start');
|
||||
|
||||
const searchSettings = new SearchSettings();
|
||||
|
||||
Promise.allSettled([
|
||||
// First we wait for all properties to load and save
|
||||
searchSettings.resolvePropertiesSuggestionsEnabled().then(v => searchPropertiesSuggestionsEnabled.set(v)),
|
||||
searchSettings.resolvePropertiesSuggestionsPosition().then(v => searchPropertiesSuggestionsPosition.set(v))
|
||||
]).then(() => {
|
||||
// And then we can start reading value changes from the writable objects
|
||||
searchPropertiesSuggestionsEnabled.subscribe(value => {
|
||||
void searchSettings.setPropertiesSuggestions(value);
|
||||
});
|
||||
|
||||
searchPropertiesSuggestionsPosition.subscribe(value => {
|
||||
void searchSettings.setPropertiesSuggestionsPosition(value);
|
||||
});
|
||||
|
||||
searchSettings.subscribe(settings => {
|
||||
searchPropertiesSuggestionsEnabled.set(Boolean(settings.suggestProperties));
|
||||
searchPropertiesSuggestionsPosition.set(settings.suggestPropertiesPosition || 'start');
|
||||
});
|
||||
})
|
||||
@@ -1,9 +0,0 @@
|
||||
.autocomplete {
|
||||
&__item {
|
||||
&--property {
|
||||
i {
|
||||
margin-right: .5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user