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";
|
|
}
|