Make exclusion table cells editable (#379)
This commit is contained in:
parent
3ad47a4c8c
commit
da8260a028
@ -95,7 +95,7 @@ export class Details extends LitElement {
|
||||
@toggle=${this.onToggle}
|
||||
aria-disabled=${this.disabled ? "true" : "false"}
|
||||
>
|
||||
<summary>
|
||||
<summary tabindex=${this.disabled ? "-1" : "0"}>
|
||||
<div class="summary-content">
|
||||
<div class="title">
|
||||
<slot name="title"></slot>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { property, state } from "lit/decorators.js";
|
||||
import { msg, localized, str } from "@lit/localize";
|
||||
|
||||
import type { ExclusionRemoveEvent } from "./queue-exclusion-table";
|
||||
import type {
|
||||
ExclusionAddEvent,
|
||||
ExclusionChangeEvent,
|
||||
@ -92,7 +93,7 @@ export class ExclusionEditor extends LiteElement {
|
||||
return html`
|
||||
${this.config
|
||||
? html`<btrix-queue-exclusion-table
|
||||
?editable=${this.isActiveCrawl}
|
||||
?removable=${this.isActiveCrawl}
|
||||
.exclusions=${this.config.exclude || []}
|
||||
@on-remove=${this.deleteExclusion}
|
||||
>
|
||||
@ -144,12 +145,12 @@ export class ExclusionEditor extends LiteElement {
|
||||
}
|
||||
}
|
||||
|
||||
private async deleteExclusion(e: CustomEvent) {
|
||||
const { value } = e.detail;
|
||||
private async deleteExclusion(e: ExclusionRemoveEvent) {
|
||||
const { regex } = e.detail;
|
||||
|
||||
try {
|
||||
const data = await this.apiFetch(
|
||||
`/archives/${this.archiveId}/crawls/${this.crawlId}/exclusions?regex=${value}`,
|
||||
`/archives/${this.archiveId}/crawls/${this.crawlId}/exclusions?regex=${regex}`,
|
||||
this.authState!,
|
||||
{
|
||||
method: "DELETE",
|
||||
@ -158,7 +159,7 @@ export class ExclusionEditor extends LiteElement {
|
||||
|
||||
if (data.new_cid) {
|
||||
this.notify({
|
||||
message: msg(html`Removed exclusion: <code>${value}</code>`),
|
||||
message: msg(html`Removed exclusion: <code>${regex}</code>`),
|
||||
variant: "success",
|
||||
icon: "check2-circle",
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { state, property } from "lit/decorators.js";
|
||||
import { html as staticHtml, unsafeStatic } from "lit/static-html.js";
|
||||
import { msg, localized } from "@lit/localize";
|
||||
import { msg, localized, str } from "@lit/localize";
|
||||
import RegexColorize from "regex-colorize";
|
||||
|
||||
import type { CrawlConfig } from "../pages/archive/types";
|
||||
@ -8,10 +8,24 @@ import LiteElement, { html } from "../utils/LiteElement";
|
||||
import { regexEscape } from "../utils/string";
|
||||
import type { Exclusion } from "./queue-exclusion-form";
|
||||
|
||||
export type ExclusionRemoveEvent = CustomEvent<{
|
||||
export type ExclusionChangeEvent = CustomEvent<{
|
||||
index: number;
|
||||
regex: string;
|
||||
}>;
|
||||
|
||||
export type ExclusionRemoveEvent = CustomEvent<{
|
||||
index: number;
|
||||
regex: string;
|
||||
}>;
|
||||
|
||||
type SLInputElement = HTMLInputElement & { invalid: boolean };
|
||||
|
||||
const MIN_LENGTH = 2;
|
||||
|
||||
function formatValue(type: Exclusion["type"], value: Exclusion["value"]) {
|
||||
return type == "text" ? regexEscape(value) : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Crawl queue exclusion table
|
||||
*
|
||||
@ -23,6 +37,7 @@ export type ExclusionRemoveEvent = CustomEvent<{
|
||||
* </btrix-queue-exclusion-table>
|
||||
* ```
|
||||
*
|
||||
* @event on-change ExclusionChangeEvent
|
||||
* @event on-remove ExclusionRemoveEvent
|
||||
*/
|
||||
@localized()
|
||||
@ -30,18 +45,21 @@ export class QueueExclusionTable extends LiteElement {
|
||||
@property({ type: Array })
|
||||
exclusions?: CrawlConfig["exclude"];
|
||||
|
||||
@property({ type: Number })
|
||||
pageSize: number = 5;
|
||||
|
||||
@property({ type: Boolean })
|
||||
editable = false;
|
||||
|
||||
@property({ type: Boolean })
|
||||
removable = false;
|
||||
|
||||
@state()
|
||||
private results: Exclusion[] = [];
|
||||
|
||||
@state()
|
||||
private page: number = 1;
|
||||
|
||||
@state()
|
||||
private pageSize: number = 5;
|
||||
|
||||
@state()
|
||||
private exclusionToRemove?: string;
|
||||
|
||||
@ -51,7 +69,7 @@ export class QueueExclusionTable extends LiteElement {
|
||||
|
||||
willUpdate(changedProperties: Map<string, any>) {
|
||||
if (changedProperties.has("exclusions") && this.exclusions) {
|
||||
this.exclusionToRemove = "";
|
||||
this.exclusionToRemove = undefined;
|
||||
|
||||
const prevVal = changedProperties.get("exclusions");
|
||||
if (prevVal) {
|
||||
@ -86,45 +104,58 @@ export class QueueExclusionTable extends LiteElement {
|
||||
|
||||
render() {
|
||||
const [typeColClass, valueColClass, actionColClass] =
|
||||
this.getColumnClassNames(0, this.results.length);
|
||||
this.getColumnClassNames(0, this.results.length, true);
|
||||
|
||||
return html`<btrix-details open disabled>
|
||||
<h4 slot="title">${msg("Exclusions")}</h4>
|
||||
<div slot="summary-description">
|
||||
${this.total && this.total > this.pageSize
|
||||
? html`<btrix-pagination
|
||||
page=${this.page}
|
||||
size=${this.pageSize}
|
||||
totalCount=${this.total}
|
||||
@page-change=${(e: CustomEvent) => {
|
||||
this.page = e.detail.page;
|
||||
}}
|
||||
>
|
||||
</btrix-pagination>`
|
||||
: ""}
|
||||
</div>
|
||||
<table
|
||||
class="w-full leading-none border-separate"
|
||||
style="border-spacing: 0;"
|
||||
>
|
||||
<thead class="text-xs font-mono text-neutral-600 uppercase">
|
||||
<tr class="h-10 text-left">
|
||||
<th class="font-normal px-2 w-40 bg-slate-50 ${typeColClass}">
|
||||
${msg("Exclusion Type")}
|
||||
</th>
|
||||
<th class="font-normal px-2 bg-slate-50 ${valueColClass}">
|
||||
${msg("Exclusion Value")}
|
||||
</th>
|
||||
<th class="font-normal px-2 w-10 bg-slate-50 ${actionColClass}">
|
||||
<span class="sr-only">Row actions</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${this.results.map(this.renderItem)}
|
||||
</tbody>
|
||||
</table>
|
||||
</btrix-details> `;
|
||||
return html`
|
||||
<style>
|
||||
btrix-queue-exclusion-table sl-input {
|
||||
--sl-input-border-radius-medium: 0;
|
||||
--sl-input-font-family: var(--sl-font-mono);
|
||||
--sl-input-spacing-medium: var(--sl-spacing-small);
|
||||
}
|
||||
|
||||
btrix-queue-exclusion-table sl-input:not([invalid]) {
|
||||
--sl-input-border-width: 0;
|
||||
}
|
||||
</style>
|
||||
<btrix-details open disabled>
|
||||
<h4 slot="title">${msg("Exclusions")}</h4>
|
||||
<div slot="summary-description">
|
||||
${this.total && this.total > this.pageSize
|
||||
? html`<btrix-pagination
|
||||
page=${this.page}
|
||||
size=${this.pageSize}
|
||||
totalCount=${this.total}
|
||||
@page-change=${(e: CustomEvent) => {
|
||||
this.page = e.detail.page;
|
||||
}}
|
||||
>
|
||||
</btrix-pagination>`
|
||||
: ""}
|
||||
</div>
|
||||
<table
|
||||
class="w-full leading-none border-separate"
|
||||
style="border-spacing: 0;"
|
||||
>
|
||||
<thead class="text-xs font-mono text-neutral-600 uppercase">
|
||||
<tr class="h-10 text-left">
|
||||
<th class="font-normal px-2 w-40 bg-slate-50 ${typeColClass}">
|
||||
${msg("Exclusion Type")}
|
||||
</th>
|
||||
<th class="font-normal px-2 bg-slate-50 ${valueColClass}">
|
||||
${msg("Exclusion Value")}
|
||||
</th>
|
||||
<th class="font-normal px-2 w-10 bg-slate-50 ${actionColClass}">
|
||||
<span class="sr-only">Row actions</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${this.results.map(this.renderItem)}
|
||||
</tbody>
|
||||
</table>
|
||||
</btrix-details>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderItem = (
|
||||
@ -135,44 +166,135 @@ export class QueueExclusionTable extends LiteElement {
|
||||
const [typeColClass, valueColClass, actionColClass] =
|
||||
this.getColumnClassNames(index + 1, arr.length);
|
||||
|
||||
let typeLabel: string = exclusion.type;
|
||||
let value: any = exclusion.value;
|
||||
|
||||
switch (exclusion.type) {
|
||||
case "regex":
|
||||
typeLabel = msg("Regex");
|
||||
value = staticHtml`<span class="regex">${unsafeStatic(
|
||||
new RegexColorize().colorizeText(exclusion.value)
|
||||
)}</span>`;
|
||||
break;
|
||||
case "text":
|
||||
typeLabel = msg("Matches Text");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return html`
|
||||
<tr
|
||||
class="h-10 ${this.exclusionToRemove === value
|
||||
class="h-10 ${this.exclusionToRemove === exclusion.value
|
||||
? "text-neutral-200"
|
||||
: "text-neutral-600"}"
|
||||
>
|
||||
<td class="py-2 px-3 whitespace-nowrap ${typeColClass}">
|
||||
${typeLabel}
|
||||
<td class="whitespace-nowrap ${typeColClass}">
|
||||
${this.renderType({ exclusion, index })}
|
||||
</td>
|
||||
<td class="font-mono ${valueColClass}">
|
||||
${this.renderValue({ exclusion, index })}
|
||||
</td>
|
||||
<td class="p-2 font-mono ${valueColClass}">${value}</td>
|
||||
<td class="text-[1rem] text-center ${actionColClass}">
|
||||
<btrix-icon-button
|
||||
name="trash3"
|
||||
@click=${() => this.removeExclusion(exclusion)}
|
||||
@click=${() => this.removeExclusion(exclusion, index)}
|
||||
></btrix-icon-button>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
};
|
||||
|
||||
private getColumnClassNames(index: number, count: number) {
|
||||
private renderType({
|
||||
exclusion,
|
||||
index,
|
||||
}: {
|
||||
exclusion: Exclusion;
|
||||
index: number;
|
||||
}) {
|
||||
let typeLabel: string = exclusion.type;
|
||||
|
||||
if (exclusion.type === "text") typeLabel = msg("Matches Text");
|
||||
if (exclusion.type === "regex") typeLabel = msg("Regex");
|
||||
|
||||
if (this.editable) {
|
||||
return html`
|
||||
<sl-select
|
||||
placeholder=${msg("Select Type")}
|
||||
size="small"
|
||||
.value=${exclusion.type}
|
||||
@sl-hide=${this.stopProp}
|
||||
@sl-after-hide=${this.stopProp}
|
||||
@sl-select=${(e: Event) => {
|
||||
this.updateExclusion({
|
||||
type: (e.target as HTMLSelectElement).value as Exclusion["type"],
|
||||
value: exclusion.value,
|
||||
index,
|
||||
});
|
||||
}}
|
||||
hoist
|
||||
>
|
||||
<sl-menu-item value="text">${msg("Matches Text")}</sl-menu-item>
|
||||
<sl-menu-item value="regex">${msg("Regex")}</sl-menu-item>
|
||||
</sl-select>
|
||||
`;
|
||||
}
|
||||
|
||||
return typeLabel;
|
||||
}
|
||||
|
||||
private renderValue({
|
||||
exclusion,
|
||||
index,
|
||||
}: {
|
||||
exclusion: Exclusion;
|
||||
index: number;
|
||||
}) {
|
||||
let value: any = exclusion.value;
|
||||
|
||||
if (this.editable) {
|
||||
return html`
|
||||
<sl-input
|
||||
name="exclusion-${index}"
|
||||
placeholder=${msg("Enter value")}
|
||||
class="m-0"
|
||||
value=${exclusion.value}
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
minlength=${MIN_LENGTH}
|
||||
@sl-clear=${() => {
|
||||
this.updateExclusion({
|
||||
type: exclusion.type,
|
||||
value: "",
|
||||
index,
|
||||
});
|
||||
}}
|
||||
@sl-input=${(e: CustomEvent) => {
|
||||
const inputElem = e.target as SLInputElement;
|
||||
const validityMessage = this.getInputValidity(inputElem) || "";
|
||||
|
||||
inputElem.classList.remove("invalid");
|
||||
inputElem.setCustomValidity(validityMessage);
|
||||
|
||||
this.checkSiblingRowValidity(e);
|
||||
}}
|
||||
@sl-change=${(e: CustomEvent) => {
|
||||
const inputElem = e.target as SLInputElement;
|
||||
const values = this.getCurrentValues(inputElem);
|
||||
const params = {
|
||||
type: values.type || exclusion.type,
|
||||
value: values.value,
|
||||
index,
|
||||
};
|
||||
|
||||
if (inputElem.invalid) {
|
||||
inputElem.classList.add("invalid");
|
||||
}
|
||||
inputElem.reportValidity();
|
||||
|
||||
this.updateExclusion(params);
|
||||
}}
|
||||
></sl-input>
|
||||
`;
|
||||
}
|
||||
|
||||
if (exclusion.type === "regex") {
|
||||
value = staticHtml`<span class="regex">${unsafeStatic(
|
||||
new RegexColorize().colorizeText(exclusion.value)
|
||||
)}</span>`;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private getColumnClassNames(
|
||||
index: number,
|
||||
count: number,
|
||||
isHeader?: boolean
|
||||
) {
|
||||
let typeColClass = "border-t border-x";
|
||||
let valueColClass = "border-t border-r";
|
||||
let actionColClass = "border-t border-r";
|
||||
@ -180,7 +302,7 @@ export class QueueExclusionTable extends LiteElement {
|
||||
if (index === 0) {
|
||||
typeColClass += " rounded-tl";
|
||||
|
||||
if (this.editable) {
|
||||
if (this.removable) {
|
||||
actionColClass += " rounded-tr";
|
||||
} else {
|
||||
valueColClass += " rounded-tr";
|
||||
@ -190,7 +312,7 @@ export class QueueExclusionTable extends LiteElement {
|
||||
if (index === count) {
|
||||
typeColClass += " border-b rounded-bl";
|
||||
|
||||
if (this.editable) {
|
||||
if (this.removable) {
|
||||
valueColClass += " border-b";
|
||||
actionColClass += " border-b rounded-br";
|
||||
} else {
|
||||
@ -198,22 +320,125 @@ export class QueueExclusionTable extends LiteElement {
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.editable) {
|
||||
if (!this.removable) {
|
||||
actionColClass += " hidden";
|
||||
}
|
||||
|
||||
if (!isHeader) {
|
||||
if (this.editable) {
|
||||
typeColClass += " px-[3px]";
|
||||
} else {
|
||||
typeColClass += " py-2 px-3";
|
||||
valueColClass += " p-2";
|
||||
}
|
||||
}
|
||||
|
||||
return [typeColClass, valueColClass, actionColClass];
|
||||
}
|
||||
|
||||
private removeExclusion({ value, type }: Exclusion) {
|
||||
private getCurrentValues(inputElem: SLInputElement) {
|
||||
// Get latest exclusion type value from select
|
||||
const typeSelectElem = inputElem.closest("tr")?.querySelector("sl-select");
|
||||
const exclusionType = typeSelectElem?.value;
|
||||
return {
|
||||
type: exclusionType as Exclusion["type"],
|
||||
value: inputElem.value,
|
||||
};
|
||||
}
|
||||
|
||||
private getInputDuplicateValidity(inputElem: SLInputElement) {
|
||||
const siblingElems = inputElem
|
||||
.closest("table")
|
||||
?.querySelectorAll(`sl-input:not([name="${inputElem.name}"])`);
|
||||
if (!siblingElems) {
|
||||
console.debug("getInputDuplicateValidity no matching siblings");
|
||||
return;
|
||||
}
|
||||
const siblingValues = Array.from(siblingElems).map(
|
||||
(elem) => (elem as SLInputElement).value
|
||||
);
|
||||
const { type, value } = this.getCurrentValues(inputElem);
|
||||
const formattedValue = formatValue(type, value);
|
||||
if (siblingValues.includes(formattedValue)) {
|
||||
return msg("Exclusion already exists. Please edit or remove to continue");
|
||||
}
|
||||
}
|
||||
|
||||
private getInputValidity(inputElem: SLInputElement): string | void {
|
||||
const { type, value } = this.getCurrentValues(inputElem);
|
||||
if (!value) return;
|
||||
|
||||
if (value.length < MIN_LENGTH) {
|
||||
return msg(str`Please enter ${MIN_LENGTH} or more characters`);
|
||||
}
|
||||
|
||||
if (type === "regex") {
|
||||
try {
|
||||
// Check if valid regex
|
||||
new RegExp(value);
|
||||
} catch (err: any) {
|
||||
return msg(
|
||||
"Please enter a valid Regular Expression constructor pattern"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return this.getInputDuplicateValidity(inputElem);
|
||||
}
|
||||
|
||||
private checkSiblingRowValidity(e: CustomEvent) {
|
||||
// Check if any sibling inputs are now valid
|
||||
// after fixing duplicate values
|
||||
const inputElem = e.target as HTMLInputElement;
|
||||
const table = inputElem.closest("table") as HTMLTableElement;
|
||||
Array.from(table?.querySelectorAll("sl-input[invalid]")).map((elem) => {
|
||||
if (elem !== inputElem) {
|
||||
const validityMessage =
|
||||
this.getInputDuplicateValidity(elem as SLInputElement) || "";
|
||||
(elem as SLInputElement).setCustomValidity(validityMessage);
|
||||
(elem as SLInputElement).reportValidity();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private removeExclusion({ value, type }: Exclusion, index: number) {
|
||||
this.exclusionToRemove = value;
|
||||
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("on-remove", {
|
||||
detail: {
|
||||
regex: type == "text" ? regexEscape(value) : value,
|
||||
regex: formatValue(type, value),
|
||||
index,
|
||||
},
|
||||
}) as ExclusionRemoveEvent
|
||||
);
|
||||
}
|
||||
|
||||
private updateExclusion({
|
||||
type,
|
||||
value,
|
||||
index,
|
||||
}: {
|
||||
type: Exclusion["type"];
|
||||
value: Exclusion["value"];
|
||||
index: number;
|
||||
}) {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("on-change", {
|
||||
detail: {
|
||||
index,
|
||||
regex: formatValue(type, value),
|
||||
},
|
||||
}) as ExclusionChangeEvent
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,10 @@ import { state, property } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
import { msg, localized, str } from "@lit/localize";
|
||||
import { parse as yamlToJson, stringify as jsonToYaml } from "yaml";
|
||||
import compact from "lodash/fp/compact";
|
||||
import merge from "lodash/fp/merge";
|
||||
import flow from "lodash/fp/flow";
|
||||
import uniq from "lodash/fp/uniq";
|
||||
import ISO6391 from "iso-639-1";
|
||||
|
||||
import type { AuthState } from "../../utils/AuthService";
|
||||
@ -12,11 +15,18 @@ import type { InitialCrawlTemplate } from "./crawl-templates-new";
|
||||
import type { CrawlTemplate, CrawlConfig } from "./types";
|
||||
import { getUTCSchedule, humanizeSchedule } from "../../utils/cron";
|
||||
import "../../components/crawl-scheduler";
|
||||
import { ExclusionRemoveEvent } from "../../components/queue-exclusion-table";
|
||||
import { ExclusionAddEvent } from "../../components/queue-exclusion-form";
|
||||
import {
|
||||
ExclusionRemoveEvent,
|
||||
ExclusionChangeEvent,
|
||||
} from "../../components/queue-exclusion-table";
|
||||
|
||||
const SEED_URLS_MAX = 3;
|
||||
|
||||
// Show default empty editable rows
|
||||
const defaultExclusions = [""];
|
||||
|
||||
const trimExclusions = flow(uniq, compact);
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* ```ts
|
||||
@ -48,7 +58,7 @@ export class CrawlTemplatesDetail extends LiteElement {
|
||||
private configCode: string = "";
|
||||
|
||||
@state()
|
||||
private exclusions: CrawlConfig["exclude"] = [];
|
||||
private exclusions: CrawlConfig["exclude"] = defaultExclusions;
|
||||
|
||||
private browserLanguage: CrawlConfig["lang"] = null;
|
||||
|
||||
@ -69,12 +79,12 @@ export class CrawlTemplatesDetail extends LiteElement {
|
||||
if (this.isConfigCodeView) {
|
||||
this.configCode = jsonToYaml(
|
||||
merge(this.crawlTemplate.config, {
|
||||
exclude: this.exclusions,
|
||||
exclude: trimExclusions(this.exclusions),
|
||||
})
|
||||
);
|
||||
} else if (this.isConfigCodeView === false) {
|
||||
this.exclusions =
|
||||
(yamlToJson(this.configCode) as CrawlConfig).exclude || [];
|
||||
const exclude = (yamlToJson(this.configCode) as CrawlConfig).exclude;
|
||||
this.exclusions = exclude?.length ? exclude : defaultExclusions;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -94,7 +104,9 @@ export class CrawlTemplatesDetail extends LiteElement {
|
||||
this.isConfigCodeView = true;
|
||||
}
|
||||
this.configCode = jsonToYaml(this.crawlTemplate.config);
|
||||
this.exclusions = this.crawlTemplate.config.exclude || [];
|
||||
if (this.crawlTemplate.config.exclude?.length) {
|
||||
this.exclusions = this.crawlTemplate.config.exclude;
|
||||
}
|
||||
} catch (e: any) {
|
||||
this.notify({
|
||||
message:
|
||||
@ -1111,23 +1123,19 @@ export class CrawlTemplatesDetail extends LiteElement {
|
||||
return html`
|
||||
<btrix-queue-exclusion-table
|
||||
.exclusions=${this.exclusions}
|
||||
pageSize="50"
|
||||
editable
|
||||
@on-remove=${(e: ExclusionRemoveEvent) => {
|
||||
if (!this.exclusions) return;
|
||||
const { regex: value } = e.detail;
|
||||
this.exclusions = this.exclusions.filter((v) => v !== value);
|
||||
}}
|
||||
removable
|
||||
@on-remove=${this.handleRemoveRegex}
|
||||
@on-change=${this.handleChangeRegex}
|
||||
></btrix-queue-exclusion-table>
|
||||
<div class="mt-2">
|
||||
<btrix-queue-exclusion-form
|
||||
@on-add=${(e: ExclusionAddEvent) => {
|
||||
const { regex, onSuccess } = e.detail;
|
||||
this.exclusions = [...(this.exclusions || []), regex];
|
||||
onSuccess();
|
||||
}}
|
||||
>
|
||||
</btrix-queue-exclusion-form>
|
||||
</div>
|
||||
<sl-button
|
||||
class="w-full mt-1"
|
||||
@click=${() => (this.exclusions = [...(this.exclusions || []), ""])}
|
||||
>
|
||||
<sl-icon slot="prefix" name="plus-lg"></sl-icon>
|
||||
<span class="text-neutral-600">${msg("Add More")}</span>
|
||||
</sl-button>
|
||||
`;
|
||||
}
|
||||
|
||||
@ -1163,6 +1171,26 @@ export class CrawlTemplatesDetail extends LiteElement {
|
||||
});
|
||||
}
|
||||
|
||||
private handleRemoveRegex(e: ExclusionRemoveEvent) {
|
||||
const { index } = e.detail;
|
||||
if (!this.exclusions) {
|
||||
this.exclusions = defaultExclusions;
|
||||
} else {
|
||||
this.exclusions = [
|
||||
...this.exclusions.slice(0, index),
|
||||
...this.exclusions.slice(index + 1),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
private handleChangeRegex(e: ExclusionChangeEvent) {
|
||||
const { regex, index } = e.detail;
|
||||
|
||||
const nextExclusions = [...this.exclusions!];
|
||||
nextExclusions[index] = regex;
|
||||
this.exclusions = nextExclusions;
|
||||
}
|
||||
|
||||
private async handleSubmitEditName(e: SubmitEvent) {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.target as HTMLFormElement);
|
||||
@ -1185,7 +1213,13 @@ export class CrawlTemplatesDetail extends LiteElement {
|
||||
|
||||
private async handleSubmitEditConfiguration(e: SubmitEvent) {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.target as HTMLFormElement);
|
||||
const form = e.target as HTMLFormElement;
|
||||
|
||||
if (form.querySelector("[invalid]")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData(form);
|
||||
const profileId = (formData.get("browserProfile") as string) || null;
|
||||
|
||||
let config: CrawlConfig;
|
||||
@ -1203,7 +1237,7 @@ export class CrawlTemplatesDetail extends LiteElement {
|
||||
scopeType: formData.get("scopeType") as string,
|
||||
limit: pageLimit ? +pageLimit : 0,
|
||||
extraHops: formData.get("extraHopsOne") ? 1 : 0,
|
||||
exclude: this.exclusions,
|
||||
exclude: trimExclusions(this.exclusions),
|
||||
lang: this.browserLanguage,
|
||||
};
|
||||
}
|
||||
|
@ -2,10 +2,15 @@ import { state, property } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
import { msg, localized, str } from "@lit/localize";
|
||||
import { parse as yamlToJson, stringify as jsonToYaml } from "yaml";
|
||||
import compact from "lodash/fp/compact";
|
||||
import merge from "lodash/fp/merge";
|
||||
import flow from "lodash/fp/flow";
|
||||
import uniq from "lodash/fp/uniq";
|
||||
|
||||
import type { ExclusionAddEvent } from "../../components/queue-exclusion-form";
|
||||
import type { ExclusionRemoveEvent } from "../../components/queue-exclusion-table";
|
||||
import type {
|
||||
ExclusionRemoveEvent,
|
||||
ExclusionChangeEvent,
|
||||
} from "../../components/queue-exclusion-table";
|
||||
import type { AuthState } from "../../utils/AuthService";
|
||||
import LiteElement, { html } from "../../utils/LiteElement";
|
||||
import { ScheduleInterval, humanizeNextDate } from "../../utils/cron";
|
||||
@ -36,7 +41,8 @@ const defaultValue = {
|
||||
config: {
|
||||
seeds: [],
|
||||
scopeType: "prefix",
|
||||
exclude: [],
|
||||
// Show default empty editable rows
|
||||
exclude: Array.from({ length: 3 }).map(() => ""),
|
||||
},
|
||||
} as InitialCrawlTemplate;
|
||||
const hours = Array.from({ length: 12 }).map((x, i) => ({
|
||||
@ -48,6 +54,8 @@ const minutes = Array.from({ length: 60 }).map((x, i) => ({
|
||||
label: `${i}`.padStart(2, "0"),
|
||||
}));
|
||||
|
||||
const trimExclusions = flow(uniq, compact);
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* ```ts
|
||||
@ -129,7 +137,9 @@ export class CrawlTemplatesNew extends LiteElement {
|
||||
this.isConfigCodeView = true;
|
||||
}
|
||||
this.configCode = jsonToYaml(this.initialCrawlTemplate.config);
|
||||
this.exclusions = this.initialCrawlTemplate.config.exclude;
|
||||
if (this.initialCrawlTemplate.config.exclude?.length) {
|
||||
this.exclusions = this.initialCrawlTemplate.config.exclude;
|
||||
}
|
||||
this.browserProfileId = this.initialCrawlTemplate.profileid;
|
||||
super.connectedCallback();
|
||||
}
|
||||
@ -139,12 +149,14 @@ export class CrawlTemplatesNew extends LiteElement {
|
||||
if (this.isConfigCodeView) {
|
||||
this.configCode = jsonToYaml(
|
||||
merge(this.initialCrawlTemplate.config, {
|
||||
exclude: this.exclusions,
|
||||
exclude: trimExclusions(this.exclusions),
|
||||
})
|
||||
);
|
||||
} else if (this.isConfigCodeView === false) {
|
||||
this.exclusions =
|
||||
(yamlToJson(this.configCode) as CrawlConfig).exclude || [];
|
||||
const exclude = (yamlToJson(this.configCode) as CrawlConfig).exclude;
|
||||
this.exclusions = exclude?.length
|
||||
? exclude
|
||||
: defaultValue.config.exclude;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -483,16 +495,19 @@ export class CrawlTemplatesNew extends LiteElement {
|
||||
return html`
|
||||
<btrix-queue-exclusion-table
|
||||
.exclusions=${this.exclusions}
|
||||
pageSize="50"
|
||||
editable
|
||||
removable
|
||||
@on-remove=${this.handleRemoveRegex}
|
||||
@on-change=${this.handleChangeRegex}
|
||||
></btrix-queue-exclusion-table>
|
||||
<div class="mt-2">
|
||||
<btrix-queue-exclusion-form
|
||||
fieldErrorMessage=${this.exclusionFieldErrorMessage || ""}
|
||||
@on-add=${this.handleAddRegex}
|
||||
>
|
||||
</btrix-queue-exclusion-form>
|
||||
</div>
|
||||
<sl-button
|
||||
class="w-full mt-1"
|
||||
@click=${() => (this.exclusions = [...(this.exclusions || []), ""])}
|
||||
>
|
||||
<sl-icon slot="prefix" name="plus-lg"></sl-icon>
|
||||
<span class="text-neutral-600">${msg("Add More")}</span>
|
||||
</sl-button>
|
||||
`;
|
||||
}
|
||||
|
||||
@ -549,7 +564,7 @@ export class CrawlTemplatesNew extends LiteElement {
|
||||
scopeType: formData.get("scopeType") as string,
|
||||
limit: pageLimit ? +pageLimit : 0,
|
||||
extraHops: formData.get("extraHopsOne") ? 1 : 0,
|
||||
exclude: this.exclusions,
|
||||
exclude: trimExclusions(this.exclusions),
|
||||
};
|
||||
}
|
||||
|
||||
@ -557,26 +572,33 @@ export class CrawlTemplatesNew extends LiteElement {
|
||||
}
|
||||
|
||||
private handleRemoveRegex(e: ExclusionRemoveEvent) {
|
||||
const { regex } = e.detail;
|
||||
if (!this.exclusions || !regex) return;
|
||||
this.exclusions = this.exclusions.filter((v) => v !== regex);
|
||||
const { index } = e.detail;
|
||||
if (!this.exclusions) {
|
||||
this.exclusions = defaultValue.config.exclude;
|
||||
} else {
|
||||
this.exclusions = [
|
||||
...this.exclusions.slice(0, index),
|
||||
...this.exclusions.slice(index + 1),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
private handleAddRegex(e: ExclusionAddEvent) {
|
||||
this.exclusionFieldErrorMessage = "";
|
||||
const { regex, onSuccess } = e.detail;
|
||||
if (this.exclusions && this.exclusions.indexOf(regex) > -1) {
|
||||
this.exclusionFieldErrorMessage = msg("Exclusion already exists");
|
||||
return;
|
||||
}
|
||||
private handleChangeRegex(e: ExclusionChangeEvent) {
|
||||
const { regex, index } = e.detail;
|
||||
|
||||
this.exclusions = [...(this.exclusions || []), regex];
|
||||
onSuccess();
|
||||
const nextExclusions = [...this.exclusions!];
|
||||
nextExclusions[index] = regex;
|
||||
this.exclusions = nextExclusions;
|
||||
}
|
||||
|
||||
private async onSubmit(event: SubmitEvent) {
|
||||
event.preventDefault();
|
||||
if (!this.authState) return;
|
||||
const form = event.target as HTMLFormElement;
|
||||
|
||||
if (form.querySelector("[invalid]")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData(event.target as HTMLFormElement);
|
||||
const params = this.parseTemplate(formData);
|
||||
|
Loading…
Reference in New Issue
Block a user