From 59f4b7995682bc0b1b4ef7e5f94791a586e6abd5 Mon Sep 17 00:00:00 2001 From: sua yoo Date: Tue, 18 Nov 2025 15:44:08 -0800 Subject: [PATCH 1/3] add load action --- .../src/pages/org/browser-profiles/profile.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/frontend/src/pages/org/browser-profiles/profile.ts b/frontend/src/pages/org/browser-profiles/profile.ts index 098900c5a9..dc33472519 100644 --- a/frontend/src/pages/org/browser-profiles/profile.ts +++ b/frontend/src/pages/org/browser-profiles/profile.ts @@ -669,6 +669,17 @@ export class BrowserProfilesProfilePage extends BtrixElement { (workflow) => html` + + void this.openBrowser({ url: workflow.firstSeed })} + > + + ${msg("Load Crawl Start URL")} + + ${when( this.appState.isCrawler, () => html` @@ -679,19 +690,8 @@ export class BrowserProfilesProfilePage extends BtrixElement { ${msg("Edit Workflow Settings")} - `, )} - - - ${msg("Go to Workflow")} - `, )} From b592eda22493c954e08b313a6c612917012f947b Mon Sep 17 00:00:00 2001 From: sua yoo Date: Wed, 19 Nov 2025 10:43:18 -0800 Subject: [PATCH 2/3] allows opening in new tab --- .../features/crawl-workflows/workflow-list.ts | 100 ++++++++++++------ 1 file changed, 69 insertions(+), 31 deletions(-) diff --git a/frontend/src/features/crawl-workflows/workflow-list.ts b/frontend/src/features/crawl-workflows/workflow-list.ts index 85ae28e311..24eca599dc 100644 --- a/frontend/src/features/crawl-workflows/workflow-list.ts +++ b/frontend/src/features/crawl-workflows/workflow-list.ts @@ -25,7 +25,7 @@ import { ShareableNotice } from "./templates/shareable-notice"; import { BtrixElement } from "@/classes/BtrixElement"; import type { OverflowDropdown } from "@/components/ui/overflow-dropdown"; -import { WorkflowTab } from "@/routes"; +import { OrgTab, WorkflowTab } from "@/routes"; import { noData } from "@/strings/ui"; import type { ListWorkflow } from "@/types/crawler"; import { humanizeSchedule } from "@/utils/cron"; @@ -40,15 +40,15 @@ export type WorkflowColumnName = | "actions"; const columnWidths = { - name: "minmax(18rem, 1fr)", + // TODO Consolidate with table.stylesheet.css + // https://github.com/webrecorder/browsertrix/issues/3001 + name: "[clickable-start] minmax(18rem, 1fr)", "latest-crawl": "minmax(15rem, 18rem)", "total-crawls": "minmax(6rem, 9rem)", modified: "minmax(12rem, 15rem)", - actions: "3rem", + actions: "[clickable-end] 3rem", } as const satisfies Record; -// postcss-lit-disable-next-line -const mediumBreakpointCss = css`30rem`; // postcss-lit-disable-next-line const largeBreakpointCss = css`60rem`; // postcss-lit-disable-next-line @@ -65,11 +65,6 @@ const rowCss = css` right: 0; } - @media only screen and (min-width: ${mediumBreakpointCss}) { - .row { - grid-template-columns: repeat(2, 1fr); - } - } @media only screen and (min-width: ${largeBreakpointCss}) { .row { grid-template-columns: var(--btrix-workflow-list-columns); @@ -252,6 +247,43 @@ export class WorkflowListItem extends BtrixElement { border-left: 1px solid var(--sl-panel-border-color); } } + + /* + * TODO Consolidate with table.stylesheet.css + * https://github.com/webrecorder/browsertrix/issues/3001 + */ + .rowClickTarget--cell { + display: grid; + grid-template-columns: subgrid; + white-space: nowrap; + overflow: hidden; + } + + .rowClickTarget { + max-width: 100%; + } + + .col sl-tooltip > *, + .col btrix-popover > *, + .col btrix-overflow-dropdown { + /* Place above .rowClickTarget::after overlay */ + z-index: 10; + position: relative; + } + + .rowClickTarget::after { + content: ""; + display: block; + position: absolute; + inset: 0; + grid-column: clickable-start / clickable-end; + } + + .rowClickTarget:focus-visible { + outline: var(--sl-focus-ring); + outline-offset: -0.25rem; + border-radius: 0.5rem; + } `, ]; @@ -271,13 +303,18 @@ export class WorkflowListItem extends BtrixElement { @query("btrix-overflow-dropdown") dropdownMenu!: OverflowDropdown; + @query("a") + private readonly anchor?: HTMLAnchorElement | null; + private readonly columnTemplate = { - name: () => - html`
-
+ name: () => { + const href = `/orgs/${this.orgSlugState}/${OrgTab.Workflows}/${this.workflow?.id}/${this.workflow?.lastCrawlState?.startsWith("failed") ? WorkflowTab.Logs : WorkflowTab.LatestCrawl}`; + + return html` +
${this.safeRender((workflow) => { if (workflow.schedule) { @@ -291,7 +328,8 @@ export class WorkflowListItem extends BtrixElement { return msg("---"); })}
-
`, +
`; + }, "latest-crawl": () => html`
${this.safeRender(this.renderLatestCrawl)}
`, "total-crawls": () => @@ -358,20 +396,7 @@ export class WorkflowListItem extends BtrixElement { } satisfies Record TemplateResult>; render() { - return html`
{ - if (e.target === this.dropdownMenu) { - return; - } - e.preventDefault(); - await this.updateComplete; - const failedStates = ["failed", "failed_not_logged_in"]; - const href = `/orgs/${this.orgSlugState}/workflows/${this.workflow?.id}/${failedStates.includes(this.workflow?.lastCrawlState || "") ? WorkflowTab.Logs : WorkflowTab.LatestCrawl}`; - this.navigate.to(href); - }} - > + return html`
${this.columns ? this.columns.map((col) => this.columnTemplate[col]()) : Object.values(this.columnTemplate).map((render) => render())} @@ -480,7 +505,7 @@ export class WorkflowListItem extends BtrixElement { return html` -
+
${status}
${duration}
@@ -495,7 +520,7 @@ export class WorkflowListItem extends BtrixElement { return html` -
+
${workflow.modifiedByName ? html`${nameSuffix} `; }; + + /* + * TODO Remove when refactored to `btrix-table` + * https://github.com/webrecorder/browsertrix/issues/3001 + */ + private readonly redirectEventToAnchor = (e: MouseEvent) => { + if (!e.defaultPrevented) { + const newEvent = new MouseEvent(e.type, e); + e.stopPropagation(); + + this.anchor?.dispatchEvent(newEvent); + } + }; } @customElement("btrix-workflow-list") From dc3e4a43030cf625e4a4ad6d69072a67a2a15636 Mon Sep 17 00:00:00 2001 From: sua yoo Date: Wed, 19 Nov 2025 11:59:04 -0800 Subject: [PATCH 3/3] show suggestions --- .../browser-profiles/start-browser-dialog.ts | 71 +++++++++++++++++-- frontend/src/pages/org/workflows-list.ts | 9 +-- frontend/src/theme.stylesheet.css | 7 +- frontend/src/types/workflow.ts | 7 ++ 4 files changed, 82 insertions(+), 12 deletions(-) diff --git a/frontend/src/features/browser-profiles/start-browser-dialog.ts b/frontend/src/features/browser-profiles/start-browser-dialog.ts index 8000adc9af..877f15acd3 100644 --- a/frontend/src/features/browser-profiles/start-browser-dialog.ts +++ b/frontend/src/features/browser-profiles/start-browser-dialog.ts @@ -1,5 +1,6 @@ import { consume } from "@lit/context"; import { localized, msg } from "@lit/localize"; +import { Task } from "@lit/task"; import type { SlButton, SlChangeEvent, @@ -11,6 +12,7 @@ import { html, nothing, type PropertyValues } from "lit"; import { customElement, property, query, state } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; import { when } from "lit/directives/when.js"; +import queryString from "query-string"; import { BtrixElement } from "@/classes/BtrixElement"; import type { Details } from "@/components/ui/details"; @@ -26,6 +28,7 @@ import { type OrgProxiesContext, } from "@/context/org-proxies"; import type { Profile } from "@/types/crawler"; +import type { WorkflowSearchValues } from "@/types/workflow"; type StartBrowserEventDetail = { url?: string; @@ -89,6 +92,48 @@ export class StartBrowserDialog extends BtrixElement { @query("#submit-button") private readonly submitButton?: SlButton | null; + // Get unique origins from workflow first seeds/crawl start URLs + private readonly workflowOrigins = new Task(this, { + task: async ([profile], { signal }) => { + if (!profile) return null; + + const query = queryString.stringify({ profileIds: profile.id }); + + try { + const { firstSeeds } = await this.api.fetch( + `/orgs/${this.orgId}/crawlconfigs/search-values?${query}`, + { signal }, + ); + + const profileUrls = profile.origins.map((origin) => new URL(origin)); + const originMap: { [url: string]: boolean } = {}; + + firstSeeds.forEach((seed) => { + const seedUrl = new URL(seed); + + // Only check domain names without www + if ( + profileUrls.some((url) => { + return ( + seedUrl.hostname.replace(/^www\./, "") === + url.hostname.replace(/^www\./, "") + ); + }) + ) { + return; + } + + originMap[seedUrl.origin] = true; + }); + + return Object.keys(originMap); + } catch (e) { + console.debug(e); + } + }, + args: () => [this.profile] as const, + }); + protected willUpdate(changedProperties: PropertyValues): void { if (changedProperties.has("initialUrl")) { this.loadUrl = this.initialUrl; @@ -238,6 +283,15 @@ export class StartBrowserDialog extends BtrixElement { } private readonly renderUrl = (profile: Profile) => { + const option = (url: string) => + html` +
${url}
+
`; + return html` + ${msg("New Site")} + ${msg("Saved Sites")} - ${profile.origins.map( - (url) => html` ${url} `, + ${profile.origins.map(option)} + ${when(this.workflowOrigins.value, (seeds) => + seeds.length + ? html` + + ${msg("Suggestions from Related Workflows")} + ${seeds.map(option)} + ` + : nothing, )} - - ${msg("New Site")}
diff --git a/frontend/src/pages/org/workflows-list.ts b/frontend/src/pages/org/workflows-list.ts index 88ad7a362b..4089fc2a5d 100644 --- a/frontend/src/pages/org/workflows-list.ts +++ b/frontend/src/pages/org/workflows-list.ts @@ -38,7 +38,7 @@ import { WorkflowTab } from "@/routes"; import { deleteConfirmation } from "@/strings/ui"; import type { APIPaginatedList, APIPaginationQuery } from "@/types/api"; import { type CrawlState } from "@/types/crawlState"; -import { type StorageSeedFile } from "@/types/workflow"; +import type { StorageSeedFile, WorkflowSearchValues } from "@/types/workflow"; import { isApiError } from "@/utils/api"; import { settingsForDuplicate } from "@/utils/crawl-workflows/settingsForDuplicate"; import { renderName } from "@/utils/crawler"; @@ -994,12 +994,7 @@ export class WorkflowsList extends BtrixElement { private async fetchConfigSearchValues() { try { - const data: { - crawlIds: string[]; - names: string[]; - descriptions: string[]; - firstSeeds: string[]; - } = await this.api.fetch( + const data = await this.api.fetch( `/orgs/${this.orgId}/crawlconfigs/search-values`, ); diff --git a/frontend/src/theme.stylesheet.css b/frontend/src/theme.stylesheet.css index 6fc831f214..dca3a7a062 100644 --- a/frontend/src/theme.stylesheet.css +++ b/frontend/src/theme.stylesheet.css @@ -237,11 +237,16 @@ } /* Adjust menu item hover and focus styles */ - sl-menu-item, + sl-option:not([aria-selected="true"]):not(:disabled), + sl-menu-item:not([disabled]), btrix-menu-item-link { @apply part-[base]:text-neutral-700 part-[base]:hover:bg-cyan-50/50 part-[base]:hover:text-cyan-700 part-[base]:focus-visible:bg-cyan-50/50; } + sl-option[aria-selected="true"] { + @apply part-[base]:bg-cyan-50 part-[base]:text-cyan-700; + } + /* Add menu item variants */ .menu-item-success { @apply part-[base]:text-success part-[base]:hover:bg-success-50 part-[base]:hover:text-success-700 part-[base]:focus-visible:bg-success-50; diff --git a/frontend/src/types/workflow.ts b/frontend/src/types/workflow.ts index e838981e3a..d34cf5bfb1 100644 --- a/frontend/src/types/workflow.ts +++ b/frontend/src/types/workflow.ts @@ -16,3 +16,10 @@ export type StorageSeedFile = StorageFile & { firstSeed: string; seedCount: number; }; + +export type WorkflowSearchValues = { + crawlIds: string[]; + names: string[]; + descriptions: string[]; + firstSeeds: string[]; +};