246 lines
7.0 KiB
TypeScript
246 lines
7.0 KiB
TypeScript
const DIALOG_STYLE_ID = "sces-batch-name-dialog-style";
|
||
|
||
const activeDialogs = new WeakMap<
|
||
Document,
|
||
{
|
||
input: HTMLInputElement;
|
||
promise: Promise<string | null>;
|
||
}
|
||
>();
|
||
|
||
export function promptForBatchName(document: Document): Promise<string | null> {
|
||
const existingDialog = activeDialogs.get(document);
|
||
if (existingDialog) {
|
||
existingDialog.input.focus();
|
||
existingDialog.input.select();
|
||
return existingDialog.promise;
|
||
}
|
||
|
||
ensureDialogStyles(document);
|
||
|
||
const dialogRoot = document.createElement("div");
|
||
dialogRoot.dataset.pluginBatchNameDialog = "root";
|
||
dialogRoot.setAttribute("role", "dialog");
|
||
dialogRoot.setAttribute("aria-modal", "true");
|
||
dialogRoot.setAttribute("aria-labelledby", "sces-batch-name-title");
|
||
applyOverlayStyles(dialogRoot);
|
||
|
||
const dialogPanel = document.createElement("div");
|
||
applyPanelStyles(dialogPanel);
|
||
|
||
const title = document.createElement("h2");
|
||
title.id = "sces-batch-name-title";
|
||
title.textContent = "提交批次";
|
||
applyTitleStyles(title);
|
||
|
||
const description = document.createElement("p");
|
||
description.textContent = "请输入批次名称,便于后续在系统中识别和追踪。";
|
||
applyDescriptionStyles(description);
|
||
|
||
const input = document.createElement("input");
|
||
input.type = "text";
|
||
input.dataset.pluginBatchNameInput = "input";
|
||
input.placeholder = "例如:618达人筛选第一批";
|
||
input.maxLength = 60;
|
||
applyInputStyles(input);
|
||
|
||
const errorText = document.createElement("p");
|
||
errorText.dataset.pluginBatchNameError = "text";
|
||
applyErrorStyles(errorText);
|
||
|
||
const buttonRow = document.createElement("div");
|
||
applyButtonRowStyles(buttonRow);
|
||
|
||
const cancelButton = document.createElement("button");
|
||
cancelButton.type = "button";
|
||
cancelButton.dataset.pluginBatchNameCancel = "button";
|
||
cancelButton.textContent = "取消";
|
||
applySecondaryButtonStyles(cancelButton);
|
||
|
||
const confirmButton = document.createElement("button");
|
||
confirmButton.type = "button";
|
||
confirmButton.dataset.pluginBatchNameConfirm = "button";
|
||
confirmButton.textContent = "确认提交";
|
||
applyPrimaryButtonStyles(confirmButton);
|
||
|
||
buttonRow.append(cancelButton, confirmButton);
|
||
dialogPanel.append(title, description, input, errorText, buttonRow);
|
||
dialogRoot.appendChild(dialogPanel);
|
||
document.body.appendChild(dialogRoot);
|
||
|
||
const dialogPromise = new Promise<string | null>((resolve) => {
|
||
const closeDialog = (value: string | null) => {
|
||
activeDialogs.delete(document);
|
||
dialogRoot.remove();
|
||
document.removeEventListener("keydown", handleDocumentKeydown, true);
|
||
resolve(value);
|
||
};
|
||
|
||
const submitValue = () => {
|
||
const value = input.value.trim();
|
||
if (!value) {
|
||
errorText.textContent = "请输入批次名称";
|
||
input.setAttribute("aria-invalid", "true");
|
||
input.focus();
|
||
return;
|
||
}
|
||
|
||
closeDialog(value);
|
||
};
|
||
|
||
const handleDocumentKeydown = (event: KeyboardEvent) => {
|
||
if (event.key === "Escape") {
|
||
event.preventDefault();
|
||
closeDialog(null);
|
||
return;
|
||
}
|
||
|
||
if (event.key === "Enter") {
|
||
event.preventDefault();
|
||
submitValue();
|
||
}
|
||
};
|
||
|
||
input.addEventListener("input", () => {
|
||
if (!input.value.trim()) {
|
||
return;
|
||
}
|
||
|
||
errorText.textContent = "";
|
||
input.removeAttribute("aria-invalid");
|
||
});
|
||
cancelButton.addEventListener("click", () => {
|
||
closeDialog(null);
|
||
});
|
||
confirmButton.addEventListener("click", () => {
|
||
submitValue();
|
||
});
|
||
dialogRoot.addEventListener("click", (event) => {
|
||
if (event.target === dialogRoot) {
|
||
closeDialog(null);
|
||
}
|
||
});
|
||
document.addEventListener("keydown", handleDocumentKeydown, true);
|
||
});
|
||
|
||
activeDialogs.set(document, {
|
||
input,
|
||
promise: dialogPromise
|
||
});
|
||
|
||
input.focus();
|
||
|
||
return dialogPromise;
|
||
}
|
||
|
||
function ensureDialogStyles(document: Document): void {
|
||
if (document.getElementById(DIALOG_STYLE_ID)) {
|
||
return;
|
||
}
|
||
|
||
const style = document.createElement("style");
|
||
style.id = DIALOG_STYLE_ID;
|
||
style.textContent = `
|
||
[data-plugin-batch-name-dialog="root"] {
|
||
animation: sces-batch-name-fade-in 0.16s ease;
|
||
}
|
||
|
||
@keyframes sces-batch-name-fade-in {
|
||
from {
|
||
opacity: 0;
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
}
|
||
}
|
||
`;
|
||
document.head.appendChild(style);
|
||
}
|
||
|
||
function applyOverlayStyles(root: HTMLElement): void {
|
||
root.style.position = "fixed";
|
||
root.style.inset = "0";
|
||
root.style.background = "rgba(15, 23, 42, 0.38)";
|
||
root.style.display = "flex";
|
||
root.style.alignItems = "center";
|
||
root.style.justifyContent = "center";
|
||
root.style.padding = "24px";
|
||
root.style.zIndex = "2147483647";
|
||
}
|
||
|
||
function applyPanelStyles(panel: HTMLElement): void {
|
||
panel.style.width = "min(420px, calc(100vw - 32px))";
|
||
panel.style.background = "#fffaf9";
|
||
panel.style.border = "1px solid rgba(127, 29, 45, 0.14)";
|
||
panel.style.borderRadius = "18px";
|
||
panel.style.boxShadow = "0 28px 70px rgba(15, 23, 42, 0.22)";
|
||
panel.style.padding = "24px";
|
||
panel.style.boxSizing = "border-box";
|
||
}
|
||
|
||
function applyTitleStyles(title: HTMLElement): void {
|
||
title.style.margin = "0";
|
||
title.style.color = "#4c0519";
|
||
title.style.fontSize = "20px";
|
||
title.style.fontWeight = "700";
|
||
title.style.lineHeight = "28px";
|
||
}
|
||
|
||
function applyDescriptionStyles(description: HTMLElement): void {
|
||
description.style.margin = "10px 0 0";
|
||
description.style.color = "#64748b";
|
||
description.style.fontSize = "13px";
|
||
description.style.lineHeight = "20px";
|
||
}
|
||
|
||
function applyInputStyles(input: HTMLInputElement): void {
|
||
input.style.width = "100%";
|
||
input.style.height = "42px";
|
||
input.style.marginTop = "18px";
|
||
input.style.padding = "0 14px";
|
||
input.style.boxSizing = "border-box";
|
||
input.style.border = "1px solid #d8c1c6";
|
||
input.style.borderRadius = "12px";
|
||
input.style.background = "#ffffff";
|
||
input.style.color = "#1f2937";
|
||
input.style.fontSize = "14px";
|
||
input.style.outline = "none";
|
||
}
|
||
|
||
function applyErrorStyles(errorText: HTMLElement): void {
|
||
errorText.style.minHeight = "20px";
|
||
errorText.style.margin = "8px 0 0";
|
||
errorText.style.color = "#b91c1c";
|
||
errorText.style.fontSize = "12px";
|
||
errorText.style.lineHeight = "18px";
|
||
}
|
||
|
||
function applyButtonRowStyles(buttonRow: HTMLElement): void {
|
||
buttonRow.style.display = "flex";
|
||
buttonRow.style.justifyContent = "flex-end";
|
||
buttonRow.style.gap = "10px";
|
||
buttonRow.style.marginTop = "18px";
|
||
}
|
||
|
||
function applySecondaryButtonStyles(button: HTMLButtonElement): void {
|
||
button.style.height = "36px";
|
||
button.style.padding = "0 16px";
|
||
button.style.border = "1px solid #d7dde6";
|
||
button.style.borderRadius = "10px";
|
||
button.style.background = "#ffffff";
|
||
button.style.color = "#334155";
|
||
button.style.fontWeight = "600";
|
||
button.style.cursor = "pointer";
|
||
}
|
||
|
||
function applyPrimaryButtonStyles(button: HTMLButtonElement): void {
|
||
button.style.height = "36px";
|
||
button.style.padding = "0 16px";
|
||
button.style.border = "1px solid #7f1d2d";
|
||
button.style.borderRadius = "10px";
|
||
button.style.background = "#7f1d2d";
|
||
button.style.color = "#ffffff";
|
||
button.style.fontWeight = "600";
|
||
button.style.cursor = "pointer";
|
||
}
|