From f0047a0eb96e1b60c3b871925a765682e21105d0 Mon Sep 17 00:00:00 2001 From: xiaoqidun Date: Sat, 31 Oct 2026 15:43:42 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E7=95=8C=E9=9D=A2=E8=AE=BE=E8=AE=A1):=20?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E6=9D=90=E8=B4=A8=E4=B8=8E=E5=BC=B9=E7=AA=97?= =?UTF-8?q?=E4=BA=A4=E4=BA=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/webui/index.html | 7 ++++ assets/webui/ofdgo.css | 77 ++++++++++++++++++++++++----------- assets/webui/ofdgo.js | 89 +++++++++++++++++++++++++---------------- 3 files changed, 115 insertions(+), 58 deletions(-) diff --git a/assets/webui/index.html b/assets/webui/index.html index 3e9f3cf..218c22a 100644 --- a/assets/webui/index.html +++ b/assets/webui/index.html @@ -203,6 +203,13 @@
项目仓库https://github.com/xiaoqidun/ofdgo
+ +
+

操作确认

+

+
+
+

关闭文档

diff --git a/assets/webui/ofdgo.css b/assets/webui/ofdgo.css index f3e38da..e1be214 100644 --- a/assets/webui/ofdgo.css +++ b/assets/webui/ofdgo.css @@ -13,6 +13,7 @@ --accent-soft: rgba(15, 123, 104, 0.12); --selected: #dfefeb; --accent-strong: #095c4e; + --accent-hover: #095c4e; --focus: #087f6d; --sidebar-width: clamp(220px, 20vw, 292px); --bar-bg: #fafbfc; @@ -30,7 +31,9 @@ --radius-dialog: 8px; --page-space: clamp(14px, 2.5vw, 28px); --shadow-flyout: 0 4px 12px rgba(31, 36, 40, 0.12); - --shadow-soft: 0 10px 28px rgba(31, 36, 40, 0.12); + --shadow-soft: 0 12px 32px rgba(31, 36, 40, 0.18); + --shadow-choice: 0 1px 4px rgb(31 36 40 / 10%); + --popup-line: color-mix(in srgb, var(--text) 18%, transparent); --shadow-page: 0 14px 30px rgba(31, 36, 40, 0.16); --base: #eef1f3; --text: #263238; @@ -48,7 +51,7 @@ --warning-soft: #fbefd3; --warning-line: #ded0a6; --success-line: #bdd8cc; - --backdrop: rgb(31 36 40 / 18%); + --backdrop: rgb(0 0 0 / 18%); --control-fill: var(--input); --panel-fill: var(--panel); --well-fill: var(--base); @@ -77,6 +80,7 @@ --accent-soft: rgba(101, 203, 182, 0.16); --selected: #24463e; --accent-strong: #65cbb6; + --accent-hover: #80d6c4; --focus: #65cbb6; --hover: #343e45; --pressed-overlay: rgb(255 255 255 / 8%); @@ -100,6 +104,7 @@ --backdrop: rgb(0 0 0 / 35%); --shadow-flyout: 0 4px 12px #0005; --shadow-soft: 0 12px 36px #0006, 0 2px 6px #0003; + --shadow-choice: 0 1px 4px rgb(0 0 0 / 24%); --shadow-page: 0 14px 30px #0005; --dropdown-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23e0e7eb' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m5 9 7 7 7-7'/%3E%3C/svg%3E"); } @@ -697,7 +702,7 @@ a:focus-visible { position: fixed; margin: 0; padding: 4px; - border: 1px solid var(--line); + border: 1px solid var(--popup-line); background: var(--popup-fill); color: var(--text); box-shadow: var(--shadow-flyout); @@ -830,11 +835,11 @@ a:focus-visible { } .mixed-color::-webkit-color-swatch { - background: repeating-linear-gradient(135deg, #fff 0 4px, #aeb7bc 4px 8px); + background: repeating-linear-gradient(135deg, var(--input) 0 4px, var(--line-strong) 4px 8px); } .mixed-color::-moz-color-swatch { - background: repeating-linear-gradient(135deg, #fff 0 4px, #aeb7bc 4px 8px); + background: repeating-linear-gradient(135deg, var(--input) 0 4px, var(--line-strong) 4px 8px); } .app-bar { @@ -1161,7 +1166,7 @@ button:not(:disabled):hover { overflow: auto; margin: auto; padding: 0; - border: 1px solid var(--line); + border: 1px solid var(--popup-line); border-radius: var(--radius-dialog); background: var(--popup-fill); color: var(--text); @@ -1240,6 +1245,8 @@ button:not(:disabled):hover { .confirm-message { margin: 0 0 16px; line-height: 1.5; + white-space: pre-line; + overflow-wrap: anywhere; } .confirm-dialog .form-actions { @@ -1247,6 +1254,10 @@ button:not(:disabled):hover { justify-content: end; } +#confirmPanel .form-actions { + grid-template-columns: repeat(2, max-content); +} + .confirm-dialog .form-actions button[hidden] { display: none; } @@ -1878,12 +1889,17 @@ button:not(:disabled):hover { white-space: nowrap; } -#createImageOrder input:not(:disabled):not(:checked) + span:hover { - background: var(--hover-fill); +.form-options input:not(:disabled):not(:checked) + span:hover { + background-color: var(--hover-fill); } -#createImageOrder input:disabled + span { +.form-options input:disabled + span { opacity: 0.48; + cursor: default; +} + +.form-options input:not(:disabled) + span:active { + background-image: linear-gradient(var(--pressed-overlay), var(--pressed-overlay)); } .info-rows textarea, @@ -2154,7 +2170,7 @@ button:not(:disabled):hover { } .form-actions button[type="submit"]:not(:disabled):hover { - background: var(--accent-strong); + background: var(--accent-hover); } .zoom-label { @@ -2187,6 +2203,10 @@ body[data-hide-meta] .meta-panel { display: none; } +.form-dialog, +.form-dialog *, +.font-listbox, +.dropdown-list, .navigation-content, .viewer-panel, .search-results, @@ -2197,6 +2217,10 @@ body[data-hide-meta] .meta-panel { scrollbar-color: var(--line-strong) transparent; } +:is(.form-dialog, .form-dialog *, .font-listbox, .dropdown-list, .navigation-content, .viewer-panel, .search-results, .editor-toolbar, .document-list, .meta-panel):hover { + scrollbar-color: var(--text-muted) transparent; +} + .page-list-panel, .meta-panel { min-height: 0; @@ -2453,7 +2477,7 @@ body[data-hide-meta] .meta-panel { height: 180px; margin-top: 12px; overflow: auto; - background: white; + background: var(--well-fill); } .resource-viewer[hidden] { @@ -2467,6 +2491,10 @@ body[data-hide-meta] .meta-panel { object-fit: contain; } +.resource-viewer img { + background: var(--page); +} + .resource-viewer audio { width: 100%; } @@ -3350,13 +3378,13 @@ button.outline-link:hover { line-height: 1; } -.thumb-paper.pending, -.thumb-paper.error { +.thumb-paper.pending { background: - linear-gradient(90deg, rgba(31, 36, 40, 0.04) 1px, transparent 1px), - linear-gradient(0deg, rgba(31, 36, 40, 0.04) 1px, transparent 1px), - #ffffff; + linear-gradient(90deg, var(--canvas-grid) 1px, transparent 1px), + linear-gradient(0deg, var(--canvas-grid) 1px, transparent 1px), + var(--panel); background-size: 12px 12px; + color: var(--text-muted); } .thumb-svg { @@ -3472,7 +3500,7 @@ button.outline-link:hover { gap: 8px 10px; width: min(420px, calc(100vw - 32px)); padding: 10px 12px; - border: 1px solid var(--line); + border: 1px solid var(--popup-line); border-radius: var(--radius-dialog); background: var(--popup-fill); box-shadow: var(--shadow-flyout); @@ -3584,8 +3612,11 @@ button.outline-link:hover { z-index: 1; } -.page-shell.error { - background: #fff8f7; +.page-shell.error, +.page-shell.error .page-placeholder, +.thumb-paper.error { + background: var(--danger-soft); + color: var(--danger); } .page-surface { @@ -4141,9 +4172,9 @@ audio.page-media { color: var(--text-muted); font-size: 13px; background: - linear-gradient(90deg, rgba(31, 36, 40, 0.04) 1px, transparent 1px), - linear-gradient(0deg, rgba(31, 36, 40, 0.04) 1px, transparent 1px), - #ffffff; + linear-gradient(90deg, var(--canvas-grid) 1px, transparent 1px), + linear-gradient(0deg, var(--canvas-grid) 1px, transparent 1px), + var(--panel); background-size: 18px 18px; } @@ -5219,7 +5250,7 @@ audio.page-media { background: var(--control-fill); color: var(--accent); border-color: var(--line); - box-shadow: 0 1px 4px #0001; + box-shadow: var(--shadow-choice); } .appearance-choices [data-pressed="true"]::after { diff --git a/assets/webui/ofdgo.js b/assets/webui/ofdgo.js index 1541c86..e1441ea 100644 --- a/assets/webui/ofdgo.js +++ b/assets/webui/ofdgo.js @@ -40,6 +40,7 @@ let wasmRequestID = 0; let wasmRecoveryTimer = 0; let credentialRequest = null; let documentDialogs = null; +let confirmationTask = Promise.resolve(); let cancelPageTouch = null; const workspace = { tabs: [], active: null, sequence: 0, nextID: 0, switching: false, closing: false, objectClipboard: null, textClipboard: null }; @@ -285,6 +286,8 @@ const el = { ofdInput: document.querySelector("#ofdInput"), ofdButton: document.querySelector("#ofdButton"), closeButton: document.querySelector("#closeButton"), + confirmPanel: document.querySelector("#confirmPanel"), + confirmMessage: document.querySelector("#confirmPanel .confirm-message"), closePanel: document.querySelector("#closePanel"), closeTitle: document.querySelector("#closePanel .form-title"), closeMessage: document.querySelector("#closePanel .confirm-message"), @@ -725,7 +728,7 @@ el.editorTools.addEventListener("scroll", () => fontPicker.position()); const canvasEditor = new CanvasEditor(el.viewerPanel, { textControls: [el.appearanceButton, el.appearancePanel, el.textAlign, el.textFont, el.textFontToggle, el.textFontAdd, el.textFontList, el.textSize, el.textColor, el.textLineHeight, el.textSpacing, el.textWrap, el.paragraphButton, el.paragraphPanel, textBackground, ...textFormatButtons, el.undoButton, el.redoButton, el.saveButton, el.saveAsButton, el.fontInput, document.querySelector("#textReflow")], - keepTextFocus: () => el.appearancePanel.open || fontPicker.open || el.closePanel.open || el.paragraphPanel.open || canvasEditor.input?.fontDialog, + keepTextFocus: () => el.appearancePanel.open || fontPicker.open || el.confirmPanel.open || el.closePanel.open || el.paragraphPanel.open || canvasEditor.input?.fontDialog, prepareTextCommit: flushEditorInputs, onTextFormat: changeRangeFormat, onRichInput: ensureRichInput, @@ -984,7 +987,7 @@ resourceElements.Delete.addEventListener("click", () => resourceTask(async () => const item = resourceManager.selected; if (!item) return; const message = item.kind === "Attachment" ? `删除附件“${item.name}”?相关附件链接将一并移除。` : `删除资源“${item.name}”?`; - if (window.confirm(message)) await changeManagedResource("delete", item); + if (await requestConfirmation(message)) await changeManagedResource("delete", item); })); for (const [button, action] of [[resourceElements.Bind, "bind"], [resourceElements.Unbind, "unbind"]]) { button.addEventListener("click", () => resourceTask(() => changeManagedResource(action, resourceManager.selected))); @@ -1024,6 +1027,7 @@ el.versionList.addEventListener("keydown", event => { event.preventDefault(); selectManagedVersion(manager.entries[Math.max(0, Math.min(index, manager.entries.length - 1))].id, true); }); +document.querySelector("#confirmCancel").addEventListener("click", () => el.confirmPanel.close("cancel")); el.documentClose.addEventListener("click", () => el.documentPanel.close()); el.documentPanel.addEventListener("cancel", event => { if (el.documentForm.inert) event.preventDefault(); }); el.documentPanel.addEventListener("close", () => { @@ -1040,9 +1044,9 @@ el.documentList.addEventListener("keydown", event => { selectManagedDocument(event.key === "Home" ? 0 : event.key === "End" ? count - 1 : Math.max(0, Math.min(count - 1, index + (event.key === "ArrowUp" ? -1 : 1))), true); }); -editorClick(el.documentDeleteButton, () => { +editorClick(el.documentDeleteButton, async () => { const index = managedDocumentIndex(), entry = state.doc.documents[index]; - if (window.confirm(`删除文档“${entry.title || "未命名"}”?`)) return changeDocument("ofdgoChangeDocument", null, "delete", index); + if (await requestConfirmation(`删除文档“${entry.title || "未命名"}”?`)) return changeDocument("ofdgoChangeDocument", null, "delete", index); }); editorClick(el.documentUpButton, () => { const index = managedDocumentIndex(); @@ -1366,7 +1370,7 @@ el.importForm.addEventListener("submit", async event => { const position = state.importDocumentMode ? managedDocumentIndex() + 1 : { before:state.pageIndex, after:state.pageIndex+1, first:0, last:state.doc.pageCount }[el.importPosition.value]; const options = []; if (state.importEncrypted) { - if (!window.confirm(state.doc.encryption?.encrypted ? "按目标文档的加密策略导入,是否继续?" : "目标未加密,导入内容将按明文保存,是否继续?")) return; + if (!await requestConfirmation(state.doc.encryption?.encrypted ? "按目标文档的加密策略导入,是否继续?" : "目标未加密,导入内容将按明文保存,是否继续?")) return; options.push(true); } const range = el.importRange.value === "custom" ? state.importDocumentMode ? selectedImportDocuments() : el.importPages.value : ""; @@ -1844,7 +1848,7 @@ document.querySelector("#objectActionsButton").addEventListener("click", async ( if (!item || item.scoped || item.items) return; const original = state.objectLinkOriginal; if ((el.objectLinkKind.value !== original.kind || el.objectLinkAddress.value !== original.address || el.objectLinkPage.value !== original.page) - && !window.confirm("链接尚未应用,放弃链接更改?")) return; + && !await requestConfirmation("链接尚未应用,放弃链接更改?")) return; syncObjectLink(item); settings.scope = "object"; settings.kind = "actions"; @@ -2174,7 +2178,7 @@ function handleKeyDown(event) { function formDialogOpen() { if (documentTab().print?.context) return true; - return el.appearancePanel.open || Boolean(sealPreview || resourceManager.pick) || el.aboutPanel.open || el.closePanel.open || el.attachmentDialog.open || settingsElements.Panel.open || fontBindingElements.Panel.open || el.signPanel.open || el.verifyPanel.open || el.credentialsPanel.open || el.encryptionPanel.open || batchElements.Panel.open || el.exportPanel.open || el.createPanel.open || el.insertPanel.open || el.pagePanel.open || el.paragraphPanel.open || el.importPanel.open || el.infoPanel.open || el.documentPanel.open + return el.confirmPanel.open || el.appearancePanel.open || Boolean(sealPreview || resourceManager.pick) || el.aboutPanel.open || el.closePanel.open || el.attachmentDialog.open || settingsElements.Panel.open || fontBindingElements.Panel.open || el.signPanel.open || el.verifyPanel.open || el.credentialsPanel.open || el.encryptionPanel.open || batchElements.Panel.open || el.exportPanel.open || el.createPanel.open || el.insertPanel.open || el.pagePanel.open || el.paragraphPanel.open || el.importPanel.open || el.infoPanel.open || el.documentPanel.open || resourceElements.Panel.open || el.structureDialog.open || el.versionPanel.open || el.batchPagesPanel.open || el.objectStylePanel.open || el.outlinePanel.open || el.objectBoundsPanel.open || el.annotationNote.open || el.annotationCreate.open || el.objectPicker.open; } @@ -2780,8 +2784,8 @@ async function closeWorkspace() { for (const tab of workspace.tabs) cancelDocumentPreparation(tab); await Promise.all(workspace.tabs.filter(tab => tab.state.saving && tab.saveCompletion).map(tab => tab.saveCompletion)); if (workspace.tabs.some(tab => tab.state.saving)) { setStatus("文档正在保存,请稍后关闭"); return; } - if (batch.running && !window.confirm("批量转换尚未完成,是否退出?")) return; - if (workspace.tabs.some(tab => tab.opening || tab.state.exporting) && !window.confirm("文档正在处理,是否取消并退出?")) return; + if (batch.running && !await requestConfirmation("批量转换尚未完成,是否退出?", null)) return; + if (workspace.tabs.some(tab => tab.opening || tab.state.exporting) && !await requestConfirmation("文档正在处理,是否取消并退出?", null)) return; const activeDirty = state.dirty || canvasEditor.textChanged() || canvasEditor.cropChanged() || canvasEditor.nudgeChanged(); const dirty = activeDirty || workspace.tabs.some(tab => tab.state.dirty); if (dirty && (workspace.switching || workspace.tabs.some(tab => tab.closing) || formDialogOpen() || (activeDirty && document.body.hasAttribute("data-busy")))) { @@ -3203,6 +3207,21 @@ function setDirty(dirty) { updateWorkspaceDirty(); } +function requestConfirmation(message, tab = documentTab()) { + const sequence = tab?.state.openSeq, scope = tab && documentPanelScope(tab.state); + const current = () => !tab || isForegroundDocument(tab) && sequence === tab.state.openSeq && scope === documentPanelScope(tab.state); + const task = confirmationTask.then(async () => { + if (!current()) return false; + el.confirmMessage.textContent = message; + el.confirmPanel.returnValue = "cancel"; + const choice = new Promise(resolve => el.confirmPanel.addEventListener("close", () => resolve(el.confirmPanel.returnValue), { once: true })); + documentDialogs.showApplication(el.confirmPanel); + return await choice === "confirm" && current(); + }); + confirmationTask = task.then(() => {}, () => {}); + return task; +} + async function requestCloseAction(title, message, save, tab = documentTab()) { const setup = () => { el.closeTitle.textContent = title; @@ -3412,7 +3431,7 @@ async function changeVersion(action) { } return; } - if (action === "delete" && (!entry.id || !window.confirm(`删除版本“${entry.name || entry.index}”?`))) return; + if (action === "delete" && (!entry.id || !await requestConfirmation(`删除版本“${entry.name || entry.index}”?`))) return; el.versionStatus.textContent = "正在处理版本"; if (await changeDocument("ofdgoChangeVersion", null, action, entry.id, el.versionName.value.trim())) { if (tab.closed || state.versionManagement !== manager) return; @@ -3508,7 +3527,7 @@ async function exportManagedVersion() { const manager = state.versionManagement; const entry = manager?.entries?.find(item => item.id === manager.id); if (!entry?.id || !state.ready || state.exporting || manager.loading || manager.failed || document.body.hasAttribute("data-busy")) return; - if (state.doc.signatureCount && !window.confirm("版本副本可能使原签名失效,是否继续?")) return; + if (state.doc.signatureCount && !await requestConfirmation("版本副本可能使原签名失效,是否继续?")) return; const originalDocument = state.doc, wasmSeq = state.wasmSeq; const name = `${baseFileName()}_${entry.name || `版本${entry.index}`}`.replace(/[\\/:*?"<>|\u0000-\u001f]/g, "_") + ".ofd"; state.exporting = true; @@ -3811,9 +3830,9 @@ async function toggleEditor() { } } -function confirmTextReflow(item) { +async function confirmTextReflow(item) { return canEditObject(item, "reflow") && (canEditObject(item, "layoutKnown") - || window.confirm("将重新排版原文,是否继续?")); + || await requestConfirmation("将重新排版原文,是否继续?")); } function openCreatePanel(insert = false, packageDocument = false) { @@ -4596,7 +4615,7 @@ async function ensureRichInput(reflow = false) { let rich = await loadRichText(editing.item, editing.face, tab, editing.fontResource, reflow); if (canvasEditor.input !== editing || !isForegroundDocument(tab)) return false; if (!rich && !reflow) { - if (!confirmTextReflow(editing.item)) return false; + if (!await confirmTextReflow(editing.item)) return false; rich = await loadRichText(editing.item, editing.face, tab, editing.fontResource, true); } if (canvasEditor.input !== editing || !isForegroundDocument(tab)) return false; @@ -4607,7 +4626,7 @@ async function ensureRichInput(reflow = false) { async function reflowCanvasText() { const editing = canvasEditor.input; if (document.body.hasAttribute("data-busy") || !editing?.source || editing.composing || editing.saving || !canEditObject(editing.item, "reflow")) return; - if (!window.confirm("将重新排版原文,是否继续?")) return; + if (!await requestConfirmation("将重新排版原文,是否继续?")) return; editing.reflow = true; canvasEditor.recordSourceText(); await canvasEditor.previewSourceText(); @@ -4668,7 +4687,7 @@ async function changeRangeFormat(key, value) { const item = canvasEditor.selected; if (!canvasEditor.input && item) { const reflow = !canEditObject(item, "layoutKnown"); - if (reflow && !confirmTextReflow(item)) return; + if (reflow && !await confirmTextReflow(item)) return; const current = currentTextStyle()[key]; const patch = { [key]: value ?? (key === "background" ? current ? "" : textBackground.value : current !== true) }; await changeDocument("ofdgoFormatText", { ...item, id: (item.items || [item]).map(member => member.id) }, JSON.stringify(patch), reflow); @@ -5054,7 +5073,7 @@ async function changeTextFace(font, key, value) { if (!editing && item?.type === "TextObject") { if (!key && font?.embedded && textFontRuns(item).every(run => run.format.font === font.id.slice(9))) return; const reflow = Boolean(key && !canEditObject(item, "layoutKnown")); - if (!canEditObject(item, "replaceFont") || reflow && !confirmTextReflow(item)) return; + if (!canEditObject(item, "replaceFont") || reflow && !await confirmTextReflow(item)) return; await changeDocument("ofdgoStyleTextFonts", { ...item, id: (item.items || [item]).map(member => member.id) }, async () => { const result = await prepareTextFaces(tab, textFontRuns(item), font, key, value); return { changes: JSON.stringify(result.changes), fonts: result.fonts, reflow }; @@ -5184,7 +5203,7 @@ async function changeTextAlignment() { const sequence = editing ? editing.alignSequence = (editing.alignSequence || 0) + 1 : 0; try { if (editing) { - if (!editing.rich && !editing.item.draft && !confirmTextReflow(editing.item)) return; + if (!editing.rich && !editing.item.draft && !await confirmTextReflow(editing.item)) return; if (!await ensureRichInput(!canEditObject(editing.item, "layoutKnown")) || !isForegroundDocument(tab) || canvasEditor.input !== editing || editing.alignSequence !== sequence) return; editing.rich.align(align); } else await saveTextParagraphs(currentTextStyle(), { align }); @@ -5201,7 +5220,7 @@ async function saveTextParagraphs(item, patch) { Object.assign(item, patch); return true; } - if (!confirmTextReflow(item)) return false; + if (!await confirmTextReflow(item)) return false; return changeDocument("ofdgoLayoutTexts", item, JSON.stringify(patch)); } @@ -5296,7 +5315,7 @@ async function changeTextStyle(color) { return; } if (size !== item.size || fill !== null) { - if (color ? !canEditObject(item, "paint") : !confirmTextReflow(item)) { + if (color ? !canEditObject(item, "paint") : !await confirmTextReflow(item)) { updateObjectControls(item, true); return; } @@ -6118,7 +6137,7 @@ async function refreshApplication() { const temporaryFonts = fontManager.userFonts.some((font) => font.source === "upload"); const dirty = state.dirty || workspace.tabs.some(tab => tab.state.dirty); const message = [dirty ? "文档尚未保存,刷新将丢失更改" : "刷新后需重新打开文件", temporaryFonts ? "未保存字体需重新添加" : ""].filter(Boolean).join("\n"); - if ((workspace.tabs.some(tab => tab.state.doc || tab.state.ofdBytes) || temporaryFonts) && !window.confirm(message)) { + if ((workspace.tabs.some(tab => tab.state.doc || tab.state.ofdBytes) || temporaryFonts) && !await requestConfirmation(message, null)) { return; } setBusy(true, "正在更新应用", null, "正在更新应用"); @@ -7531,10 +7550,10 @@ function restoreSettingsDraft() { if (body) body.scrollTop = draft.scroll; } -function closeDocumentSettings() { +async function closeDocumentSettings() { if (settings.busy) return; rememberSettingsDraft(); - if ((settings.drafts.size || [...invoicePanel.drafts.values()].some(draft => draft.dirty.size)) && !window.confirm(settings.invoice ? "发票尚未应用,放弃更改?" : "配置尚未应用,放弃更改?")) return; + if ((settings.drafts.size || [...invoicePanel.drafts.values()].some(draft => draft.dirty.size)) && !await requestConfirmation(settings.invoice ? "发票尚未应用,放弃更改?" : "配置尚未应用,放弃更改?")) return; settingsElements.Panel.close(); } @@ -7902,7 +7921,7 @@ function renderInvoiceSettings() { settingsButton("还原", () => resetInvoiceDraft(draft), !draft.dirty.size, "InvoiceReset"); if (draft.entry) settingsButton("删除", async () => { if (draft.dirty.size) throw new Error("请先应用或还原草稿"); - if (window.confirm("删除此来源中的发票数据?页面将保留" + invoiceOtherDraftWarning(draft))) await commitInvoice(draft, { operation: "delete" }); + if (await requestConfirmation("删除此来源中的发票数据?页面将保留" + invoiceOtherDraftWarning(draft))) await commitInvoice(draft, { operation: "delete" }); }, !draft.detail.capabilities?.delete, "InvoiceDelete"); } settingsElements.Status.textContent = draft.status || invoiceIssues(draft.detail.issues) || ""; @@ -7912,7 +7931,7 @@ function renderInvoiceSettings() { async function newInvoiceDraft() { const tab = documentTab(), state = tab.state, settings = tab.settings, invoicePanel = tab.invoice; if (!state.editing) return; - if (invoicePanel.drafts.get("new")?.dirty.size && !window.confirm("放弃尚未应用的新建发票?")) return; + if (invoicePanel.drafts.get("new")?.dirty.size && !await requestConfirmation("放弃尚未应用的新建发票?")) return; const format = invoicePanel.formats.find(value => value.create && value.standard !== invoiceReferenceStandard) || invoicePanel.formats.find(value => value.create); if (!format) throw new Error("暂无可新建票种"); const invoice = { kind: format.kind, standard: format.standard, version: format.version, buyer: {}, seller: {}, items: [], facts: [] }; @@ -8365,9 +8384,9 @@ function invoiceOtherDraftWarning(draft) { return [...invoicePanel.drafts.values()].some(value => value !== draft && value.entry && value.dirty.size && invoiceSourceKey(value.entry.source) === invoiceSourceKey(draft.entry.source)) ? "\n该来源其他发票的未应用修改也将放弃" : ""; } -function resetInvoiceDraft(draft) { +async function resetInvoiceDraft(draft) { const section = { xml: "xml", fields: draft.entry ? "fields" : "model", appearance: "bindings" }[invoicePanel.tab] || "model"; - if (!draft.dirty.has(section) || !window.confirm("放弃当前编辑项的未应用修改?")) return; + if (!draft.dirty.has(section) || !await requestConfirmation("放弃当前编辑项的未应用修改?")) return; if (section === "xml") draft.xml = draft.detail.xml || ""; else if (section === "fields") draft.changes.clear(); else if (section === "bindings") draft.bindings = structuredClone(draft.detail.bindings || []); @@ -8450,7 +8469,7 @@ async function applyInvoiceDraft(draft, synchronize = draft.synchronize) { request.synchronize = draft.invoice.standard === invoiceReferenceStandard || synchronize; if (!await prepareInvoiceRequest(draft, request)) return; if (!isForegroundDocument(tab)) return; - if (request.operation === "replace" && !window.confirm("替换此来源中的全部发票原文?" + invoiceOtherDraftWarning(draft))) return; + if (request.operation === "replace" && !await requestConfirmation("替换此来源中的全部发票原文?" + invoiceOtherDraftWarning(draft))) return; await commitInvoice(draft, request); } @@ -8992,7 +9011,7 @@ function renderTemplateSettings() { settingsPages(source, "SourcePage", settings.page); document.querySelector("#settingsSourcePage").closest("label").firstElementChild.textContent = "来源"; const write = async (operation, order = 0) => { - if (operation === "replace" && !window.confirm("替换模板内容?当前文档中引用此模板的页面将同步更新。")) return; + if (operation === "replace" && !await requestConfirmation("替换模板内容?当前文档中引用此模板的页面将同步更新。")) return; const selected = settings.selected; if (operation === "add") settings.selected = entries.length; if (!await commitSettings("ofdgoChangeTemplate", operation, ["add", "replace"].includes(operation) ? Number(settingsValue("SourcePage")) : settings.page, entry?.ID || "", settingsValue("Name"), ["bind", "order"].includes(operation) ? settingsValue("BindingOrder") : settingsValue("TemplateOrder"), order)) settings.selected = selected; @@ -9605,7 +9624,7 @@ async function previewManagedResource() { } const item = resourceManager.selected; if (!item?.file) return; - if (state.doc.encryption?.encrypted && !window.confirm("资源将按明文读取,是否继续?")) return; + if (state.doc.encryption?.encrypted && !await requestConfirmation("资源将按明文读取,是否继续?")) return; releaseResourcePreview(); const sequence = resourceManager.sequence, openSeq = state.openSeq; let result; @@ -9701,7 +9720,7 @@ async function downloadResource(attachment, api, label) { if (!state.doc || document.body.hasAttribute("data-busy")) { return; } - if (state.doc.encryption?.encrypted && !window.confirm(`${label}将按明文下载,是否继续?`)) return; + if (state.doc.encryption?.encrypted && !await requestConfirmation(`${label}将按明文下载,是否继续?`)) return; const openSeq = state.openSeq; const originalDocument = state.doc; state.exporting = true; @@ -10059,7 +10078,7 @@ async function signDocument(event) { el.signStatus.textContent = "请选择证书、私钥和信任根"; return; } - if (el.signMode.value === "replace" && !window.confirm("替换现有签名,是否继续?")) return; + if (el.signMode.value === "replace" && !await requestConfirmation("替换现有签名,是否继续?")) return; let key; const keyPassword = new TextEncoder().encode(el.signKeyPassword.value); const files = Object.fromEntries(["Key", "Certificate", "Roots", "Intermediates", "Seal"].map(name => [name, Array.from(el[`sign${name}`].files || [])])); @@ -10561,7 +10580,7 @@ async function runBatch() { options.credentials = await unlock; if (!options.credentials) throw new DOMException("已取消解锁", "AbortError"); const preservesEncryption = err.code === "pdfPassword" && format.value === "pdf" && compression.mode !== 0 && !options.pages; - if (!preservesEncryption && !window.confirm("转换结果将不保留原加密,是否继续?")) throw new DOMException("已取消转换", "AbortError"); + if (!preservesEncryption && (!await requestConfirmation("转换结果将不保留原加密,是否继续?", null) || batch.canceled)) throw new DOMException("已取消转换", "AbortError"); } } } catch (err) { @@ -10643,7 +10662,7 @@ async function exportFile(whole, indices = null, value = el.exportFormat.value, if (saving && !state.editorInfo && !state.ofdBytes) { return; } - if (!saving && state.doc.encryption?.encrypted && !window.confirm("此格式将输出明文,是否继续?")) return; + if (!saving && state.doc.encryption?.encrypted && !await requestConfirmation("此格式将输出明文,是否继续?")) return; const format = saving ? { value: "ofd", label: "OFD", extension: "ofd", mime: "application/ofd" } : exportFormatInfo(value); if (!format) { return; @@ -11817,7 +11836,7 @@ async function activateOpenActions() { const surface = pageView.pageShell(state.pageIndex)?.querySelector(".page-surface"); const target = pageLinkTarget(link, info, surface, task); if (!target) continue; - if ((target.external || link.attachment) && !window.confirm(link.attachment + if ((target.external || link.attachment) && !await requestConfirmation(link.attachment ? `文档请求下载附件,是否继续?\n${link.fileName || ""}` : `文档请求打开链接,是否继续?\n${target.href}`)) return; task.navigating = !!link.dest; if (await target.activate() === false) return;