import type { MarketExportScope, MarketExportTarget } from "./types"; export interface PluginToolbarHandlers { onApplyFilter(): Promise | void; onApplySort(): Promise | void; onExport(): Promise | void; } export interface PluginToolbarDom { exportButton: HTMLButtonElement; exportCustomPagesInput: HTMLInputElement; exportRangeSelect: HTMLSelectElement; exportStatusText: HTMLElement; filterApplyButton: HTMLButtonElement; personalFilterInput: HTMLInputElement; root: HTMLElement; singleFilterInput: HTMLInputElement; sortApplyButton: HTMLButtonElement; sortDirectionSelect: HTMLSelectElement; sortFieldSelect: HTMLSelectElement; } export function ensurePluginToolbar( document: Document, handlers: PluginToolbarHandlers ): PluginToolbarDom { const existingRoot = document.querySelector( "[data-plugin-toolbar='root']" ) as HTMLElement | null; if (existingRoot) { return readToolbarDom(existingRoot); } const root = document.createElement("section"); root.dataset.pluginToolbar = "root"; const singleFilterInput = document.createElement("input"); singleFilterInput.type = "number"; singleFilterInput.step = "0.01"; singleFilterInput.dataset.pluginFilterSingle = "input"; const personalFilterInput = document.createElement("input"); personalFilterInput.type = "number"; personalFilterInput.step = "0.01"; personalFilterInput.dataset.pluginFilterPersonal = "input"; const filterApplyButton = document.createElement("button"); filterApplyButton.type = "button"; filterApplyButton.dataset.pluginFilterApply = "button"; filterApplyButton.textContent = "应用筛选"; const sortFieldSelect = document.createElement("select"); sortFieldSelect.dataset.pluginSortField = "select"; appendOption(sortFieldSelect, "", "不排序"); appendOption(sortFieldSelect, "singleVideoAfterSearchRate", "单视频看后搜率"); appendOption(sortFieldSelect, "personalVideoAfterSearchRate", "个人视频看后搜率"); const sortDirectionSelect = document.createElement("select"); sortDirectionSelect.dataset.pluginSortDirection = "select"; appendOption(sortDirectionSelect, "desc", "降序"); appendOption(sortDirectionSelect, "asc", "升序"); const sortApplyButton = document.createElement("button"); sortApplyButton.type = "button"; sortApplyButton.dataset.pluginSortApply = "button"; sortApplyButton.textContent = "应用排序"; const exportButton = document.createElement("button"); exportButton.type = "button"; exportButton.dataset.pluginExport = "button"; exportButton.textContent = "导出CSV"; const exportRangeSelect = document.createElement("select"); exportRangeSelect.dataset.pluginExportRange = "select"; appendOption(exportRangeSelect, "current", "当前页"); appendOption(exportRangeSelect, "first-5", "前5页"); appendOption(exportRangeSelect, "first-10", "前10页"); appendOption(exportRangeSelect, "all", "全部"); appendOption(exportRangeSelect, "custom", "自定义"); exportRangeSelect.value = "first-5"; const exportCustomPagesInput = document.createElement("input"); exportCustomPagesInput.type = "number"; exportCustomPagesInput.min = "1"; exportCustomPagesInput.step = "1"; exportCustomPagesInput.hidden = true; exportCustomPagesInput.dataset.pluginExportCustomPages = "input"; const exportStatusText = document.createElement("span"); exportStatusText.dataset.pluginExportStatus = "text"; root.append( singleFilterInput, personalFilterInput, filterApplyButton, sortFieldSelect, sortDirectionSelect, sortApplyButton, exportRangeSelect, exportCustomPagesInput, exportButton ); root.append(exportStatusText); document.body.prepend(root); filterApplyButton.addEventListener("click", () => { void handlers.onApplyFilter(); }); sortApplyButton.addEventListener("click", () => { void handlers.onApplySort(); }); exportButton.addEventListener("click", () => { void handlers.onExport(); }); exportRangeSelect.addEventListener("change", () => { syncCustomPagesInputVisibility({ exportButton, exportCustomPagesInput, exportRangeSelect, exportStatusText, filterApplyButton, personalFilterInput, root, singleFilterInput, sortApplyButton, sortDirectionSelect, sortFieldSelect }); }); const toolbarDom = { exportButton, exportCustomPagesInput, exportRangeSelect, exportStatusText, filterApplyButton, personalFilterInput, root, singleFilterInput, sortApplyButton, sortDirectionSelect, sortFieldSelect } satisfies PluginToolbarDom; syncCustomPagesInputVisibility(toolbarDom); return toolbarDom; } function appendOption( select: HTMLSelectElement, value: string, label: string ): void { const option = select.ownerDocument.createElement("option"); option.value = value; option.textContent = label; select.appendChild(option); } function readToolbarDom(root: HTMLElement): PluginToolbarDom { const toolbarDom = { exportButton: root.querySelector( '[data-plugin-export="button"]' ) as HTMLButtonElement, exportCustomPagesInput: root.querySelector( '[data-plugin-export-custom-pages="input"]' ) as HTMLInputElement, exportRangeSelect: root.querySelector( '[data-plugin-export-range="select"]' ) as HTMLSelectElement, exportStatusText: root.querySelector( '[data-plugin-export-status="text"]' ) as HTMLElement, filterApplyButton: root.querySelector( '[data-plugin-filter-apply="button"]' ) as HTMLButtonElement, personalFilterInput: root.querySelector( '[data-plugin-filter-personal="input"]' ) as HTMLInputElement, root, singleFilterInput: root.querySelector( '[data-plugin-filter-single="input"]' ) as HTMLInputElement, sortApplyButton: root.querySelector( '[data-plugin-sort-apply="button"]' ) as HTMLButtonElement, sortDirectionSelect: root.querySelector( '[data-plugin-sort-direction="select"]' ) as HTMLSelectElement, sortFieldSelect: root.querySelector( '[data-plugin-sort-field="select"]' ) as HTMLSelectElement } satisfies PluginToolbarDom; syncCustomPagesInputVisibility(toolbarDom); return toolbarDom; } export function readToolbarExportTarget( toolbar: PluginToolbarDom ): { error?: string; target?: MarketExportTarget } { const scope = toolbar.exportRangeSelect.value as MarketExportScope; if (scope === "all") { return { target: { mode: "all" } }; } if (scope === "current") { return { target: { mode: "count", pageCount: 1 } }; } if (scope === "first-5") { return { target: { mode: "count", pageCount: 5 } }; } if (scope === "first-10") { return { target: { mode: "count", pageCount: 10 } }; } const pageCount = Number(toolbar.exportCustomPagesInput.value); if (!Number.isInteger(pageCount) || pageCount < 1) { return { error: "请输入有效页数" }; } return { target: { mode: "count", pageCount } }; } export function setToolbarBusyState( toolbar: PluginToolbarDom, isBusy: boolean ): void { [ toolbar.exportButton, toolbar.filterApplyButton, toolbar.sortApplyButton, toolbar.singleFilterInput, toolbar.personalFilterInput, toolbar.sortFieldSelect, toolbar.sortDirectionSelect, toolbar.exportRangeSelect, toolbar.exportCustomPagesInput ].forEach((element) => { element.disabled = isBusy; }); } export function setToolbarExportStatus( toolbar: PluginToolbarDom, text: string ): void { toolbar.exportStatusText.textContent = text; } function syncCustomPagesInputVisibility(toolbar: PluginToolbarDom): void { toolbar.exportCustomPagesInput.hidden = toolbar.exportRangeSelect.value !== "custom"; }