diff --git a/frontend/src/components/tag-input.ts b/frontend/src/components/tag-input.ts index 64be1e80..7429e8fd 100644 --- a/frontend/src/components/tag-input.ts +++ b/frontend/src/components/tag-input.ts @@ -10,6 +10,9 @@ export type Tags = string[]; export type TagsChangeEvent = CustomEvent<{ tags: string[]; }>; +export type TagInputEvent = CustomEvent<{ + value: string; +}>; /** * Usage: @@ -20,6 +23,7 @@ export type TagsChangeEvent = CustomEvent<{ * > * ``` * + * @events tag-input * @events tags-change */ @localized() @@ -110,6 +114,9 @@ export class TagInput extends LitElement { @property({ type: Array }) initialTags?: Tags; + @property({ type: Array }) + tagOptions: Tags = []; + @property({ type: Boolean }) disabled = false; @@ -126,9 +133,6 @@ export class TagInput extends LitElement { @state() private dropdownIsOpen?: boolean; - @state() - private tagOptions: Tags = []; - @query("#input") private input?: HTMLInputElement; @@ -300,8 +304,14 @@ export class TagInput extends LitElement { this.inputValue = input.value; if (input.value.length) { this.dropdownIsOpen = true; - this.tagOptions = await this.getOptions(); + } else { + this.dropdownIsOpen = false; } + this.dispatchEvent( + new CustomEvent("tag-input", { + detail: { value: input.value }, + }) + ); }) as any; private onKeyup(e: KeyboardEvent) { @@ -346,10 +356,4 @@ export class TagInput extends LitElement { }) ); } - - private async getOptions() { - // TODO actual API call - // https://github.com/webrecorder/browsertrix-cloud/issues/453 - return []; - } } diff --git a/frontend/src/pages/org/crawl-config-editor.ts b/frontend/src/pages/org/crawl-config-editor.ts index 08ebdaa4..e7fdae48 100644 --- a/frontend/src/pages/org/crawl-config-editor.ts +++ b/frontend/src/pages/org/crawl-config-editor.ts @@ -18,6 +18,7 @@ import flow from "lodash/fp/flow"; import uniq from "lodash/fp/uniq"; import RegexColorize from "regex-colorize"; import ISO6391 from "iso-639-1"; +import Fuse from "fuse.js"; import LiteElement, { html } from "../../utils/LiteElement"; import { regexEscape } from "../../utils/string"; @@ -35,7 +36,11 @@ import type { ExclusionChangeEvent, } from "../../components/queue-exclusion-table"; import type { TimeInputChangeEvent } from "../../components/time-input"; -import type { Tags, TagsChangeEvent } from "../../components/tag-input"; +import type { + TagInputEvent, + Tags, + TagsChangeEvent, +} from "../../components/tag-input"; import type { CrawlConfigParams, Profile, @@ -195,6 +200,9 @@ export class CrawlConfigEditor extends LiteElement { @property({ type: Object }) initialCrawlConfig?: InitialCrawlConfig; + @state() + private tagOptions: string[] = []; + @state() private isSubmitting = false; @@ -210,6 +218,12 @@ export class CrawlConfigEditor extends LiteElement { @state() private serverError?: TemplateResult | string; + // For fuzzy search: + private fuse = new Fuse([], { + shouldSort: false, + threshold: 0.2, // stricter; default is 0.6 + }); + private get formHasError() { return ( !this.hasRequiredFields() || @@ -305,6 +319,9 @@ export class CrawlConfigEditor extends LiteElement { } } } + if (changedProperties.get("orgId") && this.orgId) { + this.fetchTags(); + } } async updated(changedProperties: Map) { @@ -332,6 +349,8 @@ export class CrawlConfigEditor extends LiteElement { "sl-input, sl-textarea, sl-select, sl-radio-group" ) as HTMLElement )?.focus(); + + this.fetchTags(); } private initializeEditor() { @@ -1317,6 +1336,8 @@ https://example.net`} html` this.updateFormState( { @@ -1704,6 +1725,28 @@ https://example.net`} `; } + private onTagInput = (e: TagInputEvent) => { + const { value } = e.detail; + if (!value) return; + this.tagOptions = this.fuse.search(value).map(({ item }) => item); + }; + + private async fetchTags() { + this.tagOptions = []; + try { + const tags = await this.apiFetch( + `/orgs/${this.orgId}/crawlconfigs/tags`, + this.authState! + ); + + // Update search/filter collection + this.fuse.setCollection(tags as any); + } catch (e) { + // Fail silently, since users can still enter tags + console.debug(e); + } + } + private parseConfig(): NewCrawlConfigParams { const config: NewCrawlConfigParams = { jobType: this.jobType || "custom",