feat(界面设计): 统一材质与弹窗交互

This commit is contained in:
xiaoqidun committed 2026-10-31 15:43:42 +08:00
1 parent 4278ed7845
commit f0047a0eb9
3 files changed
+115 -58

No files matched your search

+7
View File
@@ -203,6 +203,13 @@
<div class="about-row"><span>项目仓库</span><a href="https://github.com/xiaoqidun/ofdgo" target="_blank" rel="noreferrer">https://github.com/xiaoqidun/ofdgo</a></div>
</form>
</dialog>
<dialog id="confirmPanel" class="form-dialog confirm-dialog">
<form method="dialog">
<h2 id="confirmTitle" class="form-title">操作确认</h2>
<p class="confirm-message"></p>
<div class="form-actions"><button id="confirmCancel" class="button" type="button">取消</button><button class="button" type="submit" value="confirm" autofocus>确定</button></div>
</form>
</dialog>
<dialog id="closePanel" class="form-dialog confirm-dialog">
<form method="dialog">
<h2 id="closeTitle" class="form-title">关闭文档</h2>
+54 -23
View File
@@ -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 {
+54 -35
View File
@@ -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;