239 lines
6.6 KiB
TypeScript
239 lines
6.6 KiB
TypeScript
import { state, property } from "lit/decorators.js";
|
|
import { msg, localized, str } from "@lit/localize";
|
|
import debounce from "lodash/fp/debounce";
|
|
|
|
import LiteElement, { html } from "../utils/LiteElement";
|
|
import { regexEscape } from "../utils/string";
|
|
|
|
export type Exclusion = {
|
|
type: "text" | "regex";
|
|
value: string;
|
|
};
|
|
|
|
export type ExclusionChangeEvent = CustomEvent<{
|
|
value: string;
|
|
valid: boolean;
|
|
}>;
|
|
|
|
export type ExclusionAddEvent = CustomEvent<{
|
|
regex: string;
|
|
onSuccess: () => void;
|
|
}>;
|
|
|
|
const MIN_LENGTH = 2;
|
|
|
|
/**
|
|
* Crawl queue exclusion form
|
|
*
|
|
* Usage example:
|
|
* ```ts
|
|
* <btrix-queue-exclusion-form
|
|
* @on-change=${this.handleExclusionChange}
|
|
* @on-add=${this.handleExclusionAdd}
|
|
* ></btrix-queue-exclusion-form>
|
|
* ```
|
|
*
|
|
* @event on-change ExclusionChangeEvent
|
|
* @event on-add ExclusionAddEvent
|
|
*/
|
|
@localized()
|
|
export class QueueExclusionForm extends LiteElement {
|
|
@property({ type: Boolean })
|
|
isSubmitting = false;
|
|
|
|
@property({ type: String })
|
|
fieldErrorMessage = "";
|
|
|
|
@state()
|
|
private selectValue: Exclusion["type"] = "text";
|
|
|
|
@state()
|
|
private inputValue = "";
|
|
|
|
@state()
|
|
private isRegexInvalid = false;
|
|
|
|
async willUpdate(changedProperties: Map<string, any>) {
|
|
if (
|
|
changedProperties.get("selectValue") ||
|
|
(changedProperties.has("inputValue") &&
|
|
changedProperties.get("inputValue") !== undefined)
|
|
) {
|
|
this.fieldErrorMessage = "";
|
|
this.checkInputValidity();
|
|
this.dispatchChangeEvent();
|
|
}
|
|
}
|
|
|
|
disconnectedCallback(): void {
|
|
this.onInput.cancel();
|
|
super.disconnectedCallback();
|
|
}
|
|
|
|
render() {
|
|
return html`
|
|
<fieldset>
|
|
<div class="flex">
|
|
<div class="px-1 flex-0 w-40">
|
|
<sl-select
|
|
placeholder=${msg("Select Type")}
|
|
size="small"
|
|
value=${this.selectValue}
|
|
@sl-hide=${this.stopProp}
|
|
@sl-after-hide=${this.stopProp}
|
|
@sl-change=${(e: any) => {
|
|
this.selectValue = e.target.value;
|
|
}}
|
|
>
|
|
<sl-option value="text">${msg("Matches Text")}</sl-option>
|
|
<sl-option value="regex">${msg("Regex")}</sl-option>
|
|
</sl-select>
|
|
</div>
|
|
<div class="pl-1 flex-1 flex">
|
|
<div class="flex-1 mr-1 mb-2 md:mb-0">
|
|
<sl-input
|
|
class=${this.fieldErrorMessage ? "invalid" : ""}
|
|
size="small"
|
|
autocomplete="off"
|
|
minlength=${MIN_LENGTH}
|
|
placeholder=${this.selectValue === "text"
|
|
? "/skip-this-page"
|
|
: "example.com/skip.*"}
|
|
.value=${this.inputValue}
|
|
?disabled=${this.isSubmitting}
|
|
@keydown=${this.onKeyDown}
|
|
@sl-input=${this.onInput}
|
|
>
|
|
${this.fieldErrorMessage
|
|
? html`
|
|
<div slot="help-text">
|
|
${this.isRegexInvalid
|
|
? html`
|
|
<p class="text-danger">
|
|
${msg(
|
|
html`Regular Expression syntax error:
|
|
<code>${this.fieldErrorMessage}</code>`
|
|
)}
|
|
</p>
|
|
<p>
|
|
${msg(
|
|
html`Please enter a valid constructor string
|
|
pattern. See
|
|
<a
|
|
class="underline hover:no-underline"
|
|
href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp"
|
|
target="_blank"
|
|
rel="noopener noreferrer nofollow"
|
|
><code>RegExp</code> docs</a
|
|
>.`
|
|
)}
|
|
</p>
|
|
`
|
|
: html`<p class="text-danger">
|
|
${this.fieldErrorMessage}
|
|
</p>`}
|
|
</div>
|
|
`
|
|
: ""}
|
|
</sl-input>
|
|
</div>
|
|
<div class="flex-0 w-10 pt-1 text-center">
|
|
<btrix-button
|
|
variant="primary"
|
|
raised
|
|
icon
|
|
?disabled=${!this.inputValue ||
|
|
this.isRegexInvalid ||
|
|
this.isSubmitting}
|
|
?loading=${this.isSubmitting}
|
|
@click=${this.onButtonClick}
|
|
>
|
|
<sl-icon name="plus-lg"></sl-icon>
|
|
</btrix-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</fieldset>
|
|
`;
|
|
}
|
|
|
|
private onInput = debounce(200)((e: any) => {
|
|
this.inputValue = e.target.value;
|
|
}) as any;
|
|
|
|
private onButtonClick() {
|
|
this.handleAdd();
|
|
}
|
|
|
|
private onKeyDown = (e: KeyboardEvent) => {
|
|
e.stopPropagation();
|
|
if (e.key === "Enter") {
|
|
this.handleAdd();
|
|
}
|
|
};
|
|
|
|
private checkInputValidity(): void {
|
|
let isValid = true;
|
|
|
|
if (!this.inputValue || this.inputValue.length < MIN_LENGTH) {
|
|
isValid = false;
|
|
} else if (this.selectValue === "regex") {
|
|
try {
|
|
// Check if valid regex
|
|
new RegExp(this.inputValue);
|
|
} catch (err: any) {
|
|
this.fieldErrorMessage = err.message;
|
|
isValid = false;
|
|
}
|
|
}
|
|
|
|
this.isRegexInvalid = !isValid;
|
|
}
|
|
|
|
private async dispatchChangeEvent() {
|
|
await this.updateComplete;
|
|
this.dispatchEvent(
|
|
new CustomEvent("on-change", {
|
|
detail: {
|
|
value:
|
|
this.selectValue === "text"
|
|
? regexEscape(this.inputValue)
|
|
: this.inputValue,
|
|
valid: !this.isRegexInvalid,
|
|
},
|
|
}) as ExclusionChangeEvent
|
|
);
|
|
}
|
|
|
|
private async handleAdd() {
|
|
this.onInput.flush();
|
|
await this.updateComplete;
|
|
if (!this.inputValue) return;
|
|
|
|
let regex = this.inputValue;
|
|
if (this.selectValue === "text") {
|
|
regex = regexEscape(this.inputValue);
|
|
}
|
|
|
|
this.dispatchEvent(
|
|
new CustomEvent("on-add", {
|
|
detail: {
|
|
regex,
|
|
onSuccess: () => {
|
|
this.inputValue = "";
|
|
},
|
|
},
|
|
}) as ExclusionAddEvent
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Stop propgation of sl-select events.
|
|
* Prevents bug where sl-dialog closes when dropdown closes
|
|
* https://github.com/shoelace-style/shoelace/issues/170
|
|
*/
|
|
private stopProp(e: CustomEvent) {
|
|
e.stopPropagation();
|
|
}
|
|
}
|