2026-04-21 17:10:06 +08:00

283 lines
7.8 KiB
TypeScript

import type { MarketExportScope, MarketExportTarget } from "./types";
export interface PluginToolbarHandlers {
onApplyFilter(): Promise<void> | void;
onApplySort(): Promise<void> | void;
onExport(): Promise<void> | 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";
}