@a3s-lab/office 0.30.0 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -5
- package/dist/{0~7231.js → 0~4808.js} +190 -3
- package/dist/0~4980.js +1 -1
- package/dist/0~5050.js +10 -1
- package/dist/{0~7360.js → 0~7240.js} +245 -16
- package/dist/0~7614.js +1 -1
- package/dist/0~document-editor.js +1756 -178
- package/dist/0~spreadsheet-editor.js +2 -1
- package/dist/0~work-docx-export.js +215 -27
- package/dist/0~work-docx-import.js +27 -33
- package/dist/0~work-docx-large-document-import.js +1 -1
- package/dist/0~work-office-diagnostics.js +28 -7
- package/dist/0~work-pptx-import.js +1 -1
- package/dist/{1544.js → 3266.js} +17 -1862
- package/dist/4121.js +425 -11
- package/dist/4166.js +3848 -0
- package/dist/{6282.js → 4174.js} +812 -10
- package/dist/9333.js +1849 -0
- package/dist/core.js +5259 -4
- package/dist/index.js +3 -3
- package/dist/internal/features/work/editors/document-command-catalog.d.ts +27 -0
- package/dist/internal/features/work/editors/document-compare-dialog.d.ts +14 -0
- package/dist/internal/features/work/editors/document-dom-selection.d.ts +7 -0
- package/dist/internal/features/work/editors/document-editor-support.d.ts +1 -0
- package/dist/internal/features/work/editors/document-index-dialog.d.ts +10 -0
- package/dist/internal/features/work/editors/document-index-entry-dialog.d.ts +10 -0
- package/dist/internal/features/work/editors/document-references-ribbon.d.ts +19 -0
- package/dist/internal/features/work/editors/document-toolbar.d.ts +6 -1
- package/dist/internal/features/work/editors/use-document-comparison.d.ts +9 -0
- package/dist/internal/features/work/editors/use-document-insert-commands.d.ts +3 -0
- package/dist/internal/features/work/work-document-changes.d.ts +5 -0
- package/dist/internal/features/work/work-document-compare-diff.d.ts +38 -0
- package/dist/internal/features/work/work-document-compare-stability.d.ts +5 -0
- package/dist/internal/features/work/work-document-compare.d.ts +27 -0
- package/dist/internal/features/work/work-document-index-fields.d.ts +19 -0
- package/dist/internal/features/work/work-document-index-nodes.d.ts +32 -0
- package/dist/internal/features/work/work-document-index.d.ts +63 -0
- package/dist/internal/features/work/work-docx-import.d.ts +3 -1
- package/dist/internal/features/work/work-docx-index-export.d.ts +14 -0
- package/dist/internal/features/work/work-docx-index-import.d.ts +23 -0
- package/dist/office-kernel.wasm +0 -0
- package/dist/styles.css +490 -0
- package/docs/latest/en/browser-editor-architecture.md +37 -0
- package/package.json +9 -3
- package/dist/4104.js +0 -9336
|
@@ -6,8 +6,8 @@ import { Fragment as external_react_Fragment, forwardRef, useCallback, useEffect
|
|
|
6
6
|
import { Extension, createNodeFromContent } from "@tiptap/core";
|
|
7
7
|
import { NodeSelection, Plugin, PluginKey, TextSelection } from "@tiptap/pm/state";
|
|
8
8
|
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
|
9
|
-
import { AArrowDown, AArrowUp, ALargeSmall, AlignCenter, AlignJustify, AlignLeft, AlignRight, AlignVerticalJustifyCenter, AlignVerticalJustifyEnd, AlignVerticalJustifyStart, ArrowDown, ArrowUp, BetweenHorizontalEnd, BetweenHorizontalStart, BetweenVerticalEnd, BetweenVerticalStart, Bold, BookMarked, BookOpen, Bookmark, CaseUpper, Check, CheckCheck, CheckCircle2, ChevronDown, ChevronRight, ChevronUp, ClipboardCopy, ClipboardPaste, Cloud, Columns3, Eraser, Eye, FileDiff, FilePlus2, FileText, Globe2, Grid2X2, Hash, Highlighter, Image, IndentDecrease, IndentIncrease, Italic, Languages, Link2, List, ListChecks, ListOrdered, ListTree, MessageSquarePlus, MessageSquareReply, MessagesSquare, MoveVertical, Paintbrush, Palette, PanelBottom, PanelBottomOpen, PanelLeftOpen, PanelTop, PanelTopOpen, Pilcrow, PilcrowLeft, PilcrowRight, Plus, Quote, Redo2, RefreshCw, Replace, ReplaceAll, RotateCcw, Rows3, Ruler, Scan, Search, Settings2, Square, StretchHorizontal, Strikethrough, Subscript, Superscript, Table2, TableCellsMerge, TableCellsSplit, TableProperties, TextWrap, Trash2, TriangleAlert, Type, Underline, Undo2, Unlink, Unlink2, X, XCircle, ZoomIn, ZoomOut } from "lucide-react";
|
|
10
|
-
import { Node as model_Node } from "@tiptap/pm/model";
|
|
9
|
+
import { AArrowDown, AArrowUp, ALargeSmall, AlignCenter, AlignJustify, AlignLeft, AlignRight, AlignVerticalJustifyCenter, AlignVerticalJustifyEnd, AlignVerticalJustifyStart, ArrowDown, ArrowUp, BetweenHorizontalEnd, BetweenHorizontalStart, BetweenVerticalEnd, BetweenVerticalStart, Bold, BookMarked, BookOpen, Bookmark, CaseUpper, Check, CheckCheck, CheckCircle2, ChevronDown, ChevronRight, ChevronUp, ClipboardCopy, ClipboardPaste, Cloud, Columns3, Eraser, Eye, FileCheck2, FileDiff, FilePlus2, FileStack, FileText, FileUp, GitCompareArrows, Globe2, Grid2X2, Hash, Highlighter, Image, IndentDecrease, IndentIncrease, Italic, Languages, Link2, List, ListChecks, ListOrdered, ListTree, MessageSquarePlus, MessageSquareReply, MessagesSquare, MoveVertical, Paintbrush, Palette, PanelBottom, PanelBottomOpen, PanelLeftOpen, PanelTop, PanelTopOpen, Pilcrow, PilcrowLeft, PilcrowRight, Plus, Quote, Redo2, RefreshCw, Replace, ReplaceAll, RotateCcw, Rows3, Ruler, Scan, Search, Settings2, Square, StretchHorizontal, Strikethrough, Subscript, Superscript, Table2, TableCellsMerge, TableCellsSplit, TableProperties, Tags, TextWrap, Trash2, TriangleAlert, Type, Underline, Undo2, Unlink, Unlink2, X, XCircle, ZoomIn, ZoomOut } from "lucide-react";
|
|
10
|
+
import { Fragment as model_Fragment, Node as model_Node } from "@tiptap/pm/model";
|
|
11
11
|
import extension_color from "@tiptap/extension-color";
|
|
12
12
|
import { TableKit } from "@tiptap/extension-table";
|
|
13
13
|
import extension_text_align from "@tiptap/extension-text-align";
|
|
@@ -15,17 +15,19 @@ import font_size from "@tiptap/extension-text-style/font-size";
|
|
|
15
15
|
import starter_kit from "@tiptap/starter-kit";
|
|
16
16
|
import { BubbleMenu } from "@tiptap/react/menus";
|
|
17
17
|
import { TableMap, isInTable, selectedRect } from "@tiptap/pm/tables";
|
|
18
|
-
import {
|
|
18
|
+
import { closeHistory } from "@tiptap/pm/history";
|
|
19
|
+
import { documentTableSizing, transferChangedDocumentTextStatistics, documentPageBordersVisible, documentUnderlineStyle, DOCUMENT_PAGE_BORDER_EDGES, documentTableRowOptions, normalizeDocumentColumns, clampDocumentMargin, materializeLazyDocumentEditorRoot, documentKerningThresholdPoints, selectedDocumentIndexOptions, nextDocumentTabAlignment, DOCUMENT_BOOKMARK_DUPLICATE_MESSAGE, normalizedTabPosition, documentCitationCount, resolveDocumentPageBorders, documentLazyHtmlChunkFragment, DocumentFontFamily, normalizeDocumentHiddenText, documentLegacyTextEffectsConflict, DOCUMENT_CHUNK_VISIBLE_IDS_META, selectedDocumentIndexDraft, MAX_DOCUMENT_IMAGE_RELATIVE_HEIGHT, retainAnchoredDocumentComments, DocumentTableRowIdentity, work_document_page_margins_twipsToMillimeters, DEFAULT_DOCUMENT_TABLE_CELL_FORMAT, resolveWorkDocumentEditorInput, documentCharacterPositionPoints, documentBulletListStyle, collectDocumentTextLayoutParagraphs, readOfficeDocumentCollaboration as readWorkOfficeDocumentCollaboration, normalizeDocumentCharacterScalePercent, DOCUMENT_PARAGRAPH_FORMAT_ATTRIBUTES, documentOrderedListState, normalizeDocumentPageChrome, documentTextStatistics, documentPageMarginsForLayout, invalidateDocumentLazyHtmlProjection, documentTableCellFormat, normalizeDocumentPageGeometry, normalizeDocumentImageLayer, documentImageProperties, isValidDocumentCitationTag, sanitizeDocumentPageChromeHtml, pageTwipsToMillimeters, normalizeDocumentPageBorders, importWorkDocumentFile, DocumentParagraphFormatting, documentModelHasTrustedInitialIntegrityFeatures, normalizeDocumentHtml, documentInitialSectionLayout, activeDocumentSection, documentParagraphDirection, documentParagraphPagination, isDocumentCharacterFormatMark, documentModelUsesWindowing, documentPageChromeLegacyFields, documentChunkMountedIds, collectDocumentCommentAnchors, documentTextCaseKeyboardShortcuts, DocumentTextStyle, canChangeDocumentIndent, documentCharacterSpacingPoints, documentKerningIsEffective, DocumentSuperscript, documentSectionById, activeDocumentBookmark, DocumentEquation, documentLazyHtmlProjectionFingerprint, documentLazyHtmlProjection, normalizeDocumentParagraphIndent, updateDocumentGutterPosition, DocumentStrike, mountWorkLiveDocumentCapture, registerDocumentPageSurfaceGeometry, documentHiddenTextKeyboardShortcut, documentHasIndex, syncDocumentContentFromHtml, documentLegacyTextEffectsCss, editorDocumentCaptionTargets, resolveDocumentPageChrome, documentImageLayoutOptions, MAX_DOCUMENT_NUMBERING_START, resolveDocumentPageSize, editorDocumentBookmarkReferenceTargets, serializeWorkDocumentNode, DOCUMENT_CHUNK_PAGINATION_META, documentModelForContent, createOfficeDocumentCollaborationBinding as createWorkOfficeDocumentCollaborationBinding, documentLegacyTextEffectsFromTextStyleAttributes, normalizeDocumentEmphasisMark, selectedDocumentChunkId, documentCommentViews, documentParagraphSpacing, normalizeDocumentTabStops, patchDocumentLazyHtmlProjection, normalizeDocumentTextCase, documentUnderlineColor, documentBookmarkNameExists, collectDocumentChanges, DocumentImage, selectedDocumentTableOfContentsOptions, resolveDocumentPageMargins, updateDocumentPageMarginMode, serializeDocumentParagraphFormatting, canSetDocumentTableRowRepeatHeader, normalizeDocumentCharacterSpacingTwips, DocumentCharacterFormatting, millimetersToPixels as work_document_layout_millimetersToPixels, DOCUMENT_PARAGRAPH_CHANGE_ATTRIBUTES, serializeDocumentCharacterFormatting, DOCUMENT_CHUNK_HYDRATION_META, documentTabLeaderLabel, createSchemaDerivedWorkDocumentModel, updateDocumentCustomPageMillimeters, DocumentUnderline, documentCommentDraftRange, documentParagraphTabStops, documentTableHorizontalAlignment, documentUnderlineKeyboardShortcuts, documentParagraphIndent, DocumentScriptFontFormatting, canInsertDocumentComment, resolveAllDocumentChanges, documentHasTableOfContents, documentTextLayoutBatches, normalizeDocumentImageWrapSide, updateDocumentPageOrientation, documentCitationStyleDetails, validateDocumentBookmarkName, setCustomDocumentColumns, updateDocumentPageMarginMillimeters, documentCitationStyle, normalizeDocumentCharacterPositionHalfPoints, documentTransactionsOnlyHydrateChunks, normalizeDocumentKerningThresholdHalfPoints, forgetSourceBlob as forgetWorkSourceBlob, DEFAULT_DOCUMENT_TABLE_CELL_MARGINS, DOCUMENT_TABLE_STYLE_OPTIONS, updateDocumentColumnWidth, updateDocumentPaperSizePreset, documentPageMetrics, documentStrikeStyle, DocumentHighlight, createDocumentBibliography, DocumentSubscript, selectedDocumentIndexEntry, updateDocumentPageChromeVariant, updateDocumentMirrorMargins, activeDocumentTableStyle, measureDocumentLayoutBlocksIncrementally, documentWordLineHeightFactor, normalizeDocumentLegacyTextEffect, createWorkDocumentExtensions } from "./4174.js";
|
|
19
20
|
import { useOfficeTaskPaneEscape, normalizeOfficeFontFamily, OfficeCheckbox, useOfficeDialog, useOfficeTaskPaneModal, CommittedOfficeNumberField, officeFontFamilyLabel, officeFontFamilies, CollectionState, createOfficeKernelClient, OfficeNumberField } from "./0~7048.js";
|
|
20
21
|
import { moveOfficeMenuFocus, office_text_field_OfficeTextArea, Popover, officeOverlayPortalRoot, button_Button, useOfficePublishPresenceLocation, OfficeTiptapPresenceLayer, icon_button_IconButton, office_text_field_OfficeTextField, useDialogFocusScope, useOfficeEditorWheelZoom, office_text_field_OfficeFileInput } from "./0~6090.js";
|
|
21
22
|
import { WorkOfficePreviewBar, isWorkspaceContextMenuKeyboardEvent, Tabs, WorkOfficeZoomControls, WorkOfficeStatusBar, Dialog, WorkOfficeRibbon, workspaceContextMenuPosition, OfficeSelect, WorkspaceContextMenu, WorkOfficeRibbonGroup, WorkOfficeRibbonButton } from "./0~4595.js";
|
|
22
|
-
import { documentFontNameFromCssFamily, normalizeDocumentRunBorder, parseDocumentScriptFonts, parseDocumentRunShading, DOCUMENT_RUN_BORDER_STYLES, serializeDocumentRunBorder, documentScriptFontDirectFamily, serializeDocumentRunShading, patchDocumentScriptFonts, serializeDocumentScriptFonts, documentRunBorderIsVisible, cssDocumentFontFamily, isDocumentParagraphArtBorderStyle, visibleWorkDocumentOutlineItems, DEFAULT_DOCUMENT_TABLE_OF_CONTENTS_OPTIONS, createOfficeId as createWorkId, normalizeDocumentLanguageTag, currentWorkDocumentOutlineItem, normalizeDocumentNoProof, parseDocumentRunBorder, DocumentParagraphIdentity, parseDocumentProofingLanguages, collectWorkDocumentOutline, documentScriptFontSlotFromHint, documentBorderPresentation, documentScriptFontFamilyForRendering, normalizeDocumentRunShading, normalizeDocumentScriptFontSlot, documentScriptFontSegments, patchDocumentProofingLanguages, serializeDocumentProofingLanguages, documentRunShadingDomAttributes } from "./4121.js";
|
|
23
|
+
import { documentFontNameFromCssFamily, normalizeDocumentRunBorder, parseDocumentScriptFonts, parseDocumentRunShading, DOCUMENT_RUN_BORDER_STYLES, serializeDocumentRunBorder, documentScriptFontDirectFamily, serializeDocumentRunShading, patchDocumentScriptFonts, serializeDocumentScriptFonts, documentRunBorderIsVisible, cssDocumentFontFamily, isDocumentParagraphArtBorderStyle, visibleWorkDocumentOutlineItems, DEFAULT_DOCUMENT_TABLE_OF_CONTENTS_OPTIONS, createOfficeId as createWorkId, DEFAULT_DOCUMENT_INDEX_OPTIONS, normalizeDocumentLanguageTag, currentWorkDocumentOutlineItem, normalizeDocumentNoProof, parseDocumentRunBorder, DocumentParagraphIdentity, parseDocumentProofingLanguages, collectWorkDocumentOutline, documentScriptFontSlotFromHint, documentBorderPresentation, documentScriptFontFamilyForRendering, normalizeDocumentRunShading, normalizeDocumentScriptFontSlot, documentScriptFontSegments, patchDocumentProofingLanguages, serializeDocumentProofingLanguages, documentRunShadingDomAttributes } from "./4121.js";
|
|
23
24
|
import { normalizeDocumentHref, DOCUMENT_LINK_VALIDATION_MESSAGE } from "./0~work-document-links.js";
|
|
24
25
|
import { OfficeColorPicker, isOfficeShortcutBlocked } from "./0~3635.js";
|
|
25
26
|
import { OfficeTableInsertPopover, readOfficeFileAsDataUrl } from "./0~9445.js";
|
|
26
|
-
import { mergeOfficeTiptapExtensions,
|
|
27
|
+
import { mergeOfficeTiptapExtensions, documentHasRefreshableFields, documentCurrentPage, documentAgentMenuItems, documentPageCount, createWorkDocumentSelectionSnapshot, documentPlainTextAsHtml, createWorkDocumentSelectionAction, documentCustomSelectionMenuItems } from "./0~5050.js";
|
|
27
28
|
import { WorkEditorLoadingState, documentLayoutFontKey, useOfficeCollaborationLocationNavigator, OFFICE_DOCUMENT_LAYOUT_HEBREW_FONT_ID, useOfficeEditorInitialFocus, OFFICE_DOCUMENT_LAYOUT_FONT_ID, OFFICE_DOCUMENT_LAYOUT_ARABIC_FONT_ID, OFFICE_DOCUMENT_LAYOUT_LATIN_FONT_ID } from "./432.js";
|
|
28
29
|
import { useOfficeDraft, InlineNotice } from "./0~345.js";
|
|
30
|
+
import { showToast } from "./6489.js";
|
|
29
31
|
import { documentPageColor, normalizeDocumentPageColor } from "./0~work-document-page-color.js";
|
|
30
32
|
var document_editor_namespaceObject = {};
|
|
31
33
|
__webpack_require__.r(document_editor_namespaceObject);
|
|
@@ -7483,6 +7485,33 @@ const documentCommandCatalog = {
|
|
|
7483
7485
|
group: 'tableOfContents'
|
|
7484
7486
|
}
|
|
7485
7487
|
},
|
|
7488
|
+
markIndexEntry: {
|
|
7489
|
+
id: 'references.markIndexEntry',
|
|
7490
|
+
label: '标记索引项',
|
|
7491
|
+
location: {
|
|
7492
|
+
area: 'ribbon',
|
|
7493
|
+
tab: 'references',
|
|
7494
|
+
group: 'index'
|
|
7495
|
+
}
|
|
7496
|
+
},
|
|
7497
|
+
index: {
|
|
7498
|
+
id: 'references.index',
|
|
7499
|
+
label: '插入或自定义索引',
|
|
7500
|
+
location: {
|
|
7501
|
+
area: 'ribbon',
|
|
7502
|
+
tab: 'references',
|
|
7503
|
+
group: 'index'
|
|
7504
|
+
}
|
|
7505
|
+
},
|
|
7506
|
+
refreshIndex: {
|
|
7507
|
+
id: 'references.refreshIndex',
|
|
7508
|
+
label: '更新索引',
|
|
7509
|
+
location: {
|
|
7510
|
+
area: 'ribbon',
|
|
7511
|
+
tab: 'references',
|
|
7512
|
+
group: 'index'
|
|
7513
|
+
}
|
|
7514
|
+
},
|
|
7486
7515
|
refreshFields: {
|
|
7487
7516
|
id: 'references.refreshFields',
|
|
7488
7517
|
label: '更新域',
|
|
@@ -8069,6 +8098,28 @@ function DocumentStatusBar({ bibliographyCount, citationCount, currentPage, page
|
|
|
8069
8098
|
]
|
|
8070
8099
|
});
|
|
8071
8100
|
}
|
|
8101
|
+
function synchronizeDocumentEditorSelectionFromDom(editor) {
|
|
8102
|
+
if (editor.isDestroyed) return false;
|
|
8103
|
+
const { view } = editor;
|
|
8104
|
+
const domSelection = view.dom.ownerDocument.getSelection();
|
|
8105
|
+
if (!domSelection || domSelection.isCollapsed || 1 !== domSelection.rangeCount) return false;
|
|
8106
|
+
const range = domSelection.getRangeAt(0);
|
|
8107
|
+
if (!view.dom.contains(range.startContainer) || !view.dom.contains(range.endContainer)) return false;
|
|
8108
|
+
let from;
|
|
8109
|
+
let to;
|
|
8110
|
+
try {
|
|
8111
|
+
from = view.posAtDOM(range.startContainer, range.startOffset, 1);
|
|
8112
|
+
to = view.posAtDOM(range.endContainer, range.endOffset, -1);
|
|
8113
|
+
} catch {
|
|
8114
|
+
return false;
|
|
8115
|
+
}
|
|
8116
|
+
const maximum = editor.state.doc.content.size;
|
|
8117
|
+
if (from < 0 || to <= from || to > maximum) return false;
|
|
8118
|
+
const nextSelection = TextSelection.between(editor.state.doc.resolve(from), editor.state.doc.resolve(to), 1);
|
|
8119
|
+
if (nextSelection.empty || nextSelection.eq(editor.state.selection)) return false;
|
|
8120
|
+
view.dispatch(editor.state.tr.setSelection(nextSelection).setMeta('addToHistory', false));
|
|
8121
|
+
return true;
|
|
8122
|
+
}
|
|
8072
8123
|
const proofingStateOptions = [
|
|
8073
8124
|
{
|
|
8074
8125
|
value: 'mixed',
|
|
@@ -13137,6 +13188,158 @@ function PictureButton({ label, active = false, disabled = false, onClick, child
|
|
|
13137
13188
|
children: children
|
|
13138
13189
|
});
|
|
13139
13190
|
}
|
|
13191
|
+
function DocumentReferencesRibbon({ editor, citationsOpen, citationSourceCount, onInsertNote, onInsertCaption, onInsertCrossReference, onOpenTableOfContents, onRefreshTableOfContents, onOpenIndexEntry, onOpenIndex, onRefreshIndex, onToggleCitations, onRefreshFields }) {
|
|
13192
|
+
const hasTableOfContents = documentHasTableOfContents(editor);
|
|
13193
|
+
const hasIndex = documentHasIndex(editor);
|
|
13194
|
+
const canMarkIndexEntry = Boolean(selectedDocumentIndexDraft(editor));
|
|
13195
|
+
const hasRefreshableFields = documentHasRefreshableFields(editor);
|
|
13196
|
+
const refreshFieldsCommand = getDocumentCommandDefinition('refreshFields');
|
|
13197
|
+
return /*#__PURE__*/ jsxs(Fragment, {
|
|
13198
|
+
children: [
|
|
13199
|
+
/*#__PURE__*/ jsxs(WorkOfficeRibbonGroup, {
|
|
13200
|
+
label: "目录",
|
|
13201
|
+
priority: "high",
|
|
13202
|
+
children: [
|
|
13203
|
+
/*#__PURE__*/ jsx(ReferencesButton, {
|
|
13204
|
+
label: "插入或自定义目录",
|
|
13205
|
+
onClick: onOpenTableOfContents,
|
|
13206
|
+
children: /*#__PURE__*/ jsx(ListTree, {
|
|
13207
|
+
size: 19
|
|
13208
|
+
})
|
|
13209
|
+
}),
|
|
13210
|
+
/*#__PURE__*/ jsx(ReferencesButton, {
|
|
13211
|
+
label: "更新目录",
|
|
13212
|
+
disabled: !hasTableOfContents,
|
|
13213
|
+
title: hasTableOfContents ? '根据当前标题和页码更新目录' : '文档中没有可更新的目录',
|
|
13214
|
+
onClick: onRefreshTableOfContents,
|
|
13215
|
+
children: /*#__PURE__*/ jsx(RefreshCw, {
|
|
13216
|
+
size: 19
|
|
13217
|
+
})
|
|
13218
|
+
})
|
|
13219
|
+
]
|
|
13220
|
+
}),
|
|
13221
|
+
/*#__PURE__*/ jsxs(WorkOfficeRibbonGroup, {
|
|
13222
|
+
label: "脚注",
|
|
13223
|
+
priority: "high",
|
|
13224
|
+
children: [
|
|
13225
|
+
/*#__PURE__*/ jsx(ReferencesButton, {
|
|
13226
|
+
label: "插入脚注",
|
|
13227
|
+
onClick: ()=>onInsertNote('footnote'),
|
|
13228
|
+
children: /*#__PURE__*/ jsx("span", {
|
|
13229
|
+
className: "work-ribbon-glyph",
|
|
13230
|
+
children: "\xb9"
|
|
13231
|
+
})
|
|
13232
|
+
}),
|
|
13233
|
+
/*#__PURE__*/ jsx(ReferencesButton, {
|
|
13234
|
+
label: "插入尾注",
|
|
13235
|
+
onClick: ()=>onInsertNote('endnote'),
|
|
13236
|
+
children: /*#__PURE__*/ jsx("span", {
|
|
13237
|
+
className: "work-ribbon-glyph",
|
|
13238
|
+
children: "ⅰ"
|
|
13239
|
+
})
|
|
13240
|
+
})
|
|
13241
|
+
]
|
|
13242
|
+
}),
|
|
13243
|
+
/*#__PURE__*/ jsxs(WorkOfficeRibbonGroup, {
|
|
13244
|
+
label: "题注",
|
|
13245
|
+
children: [
|
|
13246
|
+
/*#__PURE__*/ jsx(ReferencesButton, {
|
|
13247
|
+
label: "插入图片题注",
|
|
13248
|
+
onClick: ()=>onInsertCaption('figure'),
|
|
13249
|
+
children: /*#__PURE__*/ jsx(Image, {
|
|
13250
|
+
size: 19
|
|
13251
|
+
})
|
|
13252
|
+
}),
|
|
13253
|
+
/*#__PURE__*/ jsx(ReferencesButton, {
|
|
13254
|
+
label: "插入表格题注",
|
|
13255
|
+
onClick: ()=>onInsertCaption('table'),
|
|
13256
|
+
children: /*#__PURE__*/ jsx(Table2, {
|
|
13257
|
+
size: 19
|
|
13258
|
+
})
|
|
13259
|
+
}),
|
|
13260
|
+
/*#__PURE__*/ jsx(ReferencesButton, {
|
|
13261
|
+
label: "插入交叉引用",
|
|
13262
|
+
onClick: onInsertCrossReference,
|
|
13263
|
+
children: /*#__PURE__*/ jsx(Link2, {
|
|
13264
|
+
size: 19
|
|
13265
|
+
})
|
|
13266
|
+
})
|
|
13267
|
+
]
|
|
13268
|
+
}),
|
|
13269
|
+
/*#__PURE__*/ jsx(WorkOfficeRibbonGroup, {
|
|
13270
|
+
label: "引文和书目",
|
|
13271
|
+
priority: "high",
|
|
13272
|
+
children: /*#__PURE__*/ jsx(ReferencesButton, {
|
|
13273
|
+
label: `文献库${citationSourceCount ? `(${citationSourceCount})` : ''}`,
|
|
13274
|
+
active: citationsOpen,
|
|
13275
|
+
onClick: onToggleCitations,
|
|
13276
|
+
children: /*#__PURE__*/ jsx(BookOpen, {
|
|
13277
|
+
size: 19
|
|
13278
|
+
})
|
|
13279
|
+
})
|
|
13280
|
+
}),
|
|
13281
|
+
/*#__PURE__*/ jsxs(WorkOfficeRibbonGroup, {
|
|
13282
|
+
label: "索引",
|
|
13283
|
+
priority: "high",
|
|
13284
|
+
children: [
|
|
13285
|
+
/*#__PURE__*/ jsx(ReferencesButton, {
|
|
13286
|
+
label: "标记索引项",
|
|
13287
|
+
disabled: !canMarkIndexEntry,
|
|
13288
|
+
title: canMarkIndexEntry ? '把当前选中文字标记为索引项' : '请先选择正文文字或已有索引项',
|
|
13289
|
+
onClick: onOpenIndexEntry,
|
|
13290
|
+
children: /*#__PURE__*/ jsx(Tags, {
|
|
13291
|
+
size: 19
|
|
13292
|
+
})
|
|
13293
|
+
}),
|
|
13294
|
+
/*#__PURE__*/ jsx(ReferencesButton, {
|
|
13295
|
+
label: "插入或自定义索引",
|
|
13296
|
+
onClick: onOpenIndex,
|
|
13297
|
+
children: /*#__PURE__*/ jsx(ListOrdered, {
|
|
13298
|
+
size: 19
|
|
13299
|
+
})
|
|
13300
|
+
}),
|
|
13301
|
+
/*#__PURE__*/ jsx(ReferencesButton, {
|
|
13302
|
+
label: "更新索引",
|
|
13303
|
+
disabled: !hasIndex,
|
|
13304
|
+
title: hasIndex ? '根据当前索引项和页码更新索引' : '文档中没有可更新的索引',
|
|
13305
|
+
onClick: onRefreshIndex,
|
|
13306
|
+
children: /*#__PURE__*/ jsx(RefreshCw, {
|
|
13307
|
+
size: 19
|
|
13308
|
+
})
|
|
13309
|
+
})
|
|
13310
|
+
]
|
|
13311
|
+
}),
|
|
13312
|
+
/*#__PURE__*/ jsx(WorkOfficeRibbonGroup, {
|
|
13313
|
+
label: "更新",
|
|
13314
|
+
priority: "low",
|
|
13315
|
+
children: /*#__PURE__*/ jsx(ReferencesButton, {
|
|
13316
|
+
label: "更新页码和日期",
|
|
13317
|
+
shortcut: refreshFieldsCommand.shortcut?.label,
|
|
13318
|
+
ariaKeyShortcuts: refreshFieldsCommand.shortcut?.aria,
|
|
13319
|
+
disabled: !hasRefreshableFields,
|
|
13320
|
+
title: hasRefreshableFields ? `更新页码和日期(${refreshFieldsCommand.shortcut?.label})` : '文档中没有可更新的页码或日期',
|
|
13321
|
+
onClick: onRefreshFields,
|
|
13322
|
+
children: /*#__PURE__*/ jsx(RefreshCw, {
|
|
13323
|
+
size: 19
|
|
13324
|
+
})
|
|
13325
|
+
})
|
|
13326
|
+
})
|
|
13327
|
+
]
|
|
13328
|
+
});
|
|
13329
|
+
}
|
|
13330
|
+
function ReferencesButton({ label, title, shortcut, ariaKeyShortcuts, active = false, disabled = false, onClick, children }) {
|
|
13331
|
+
return /*#__PURE__*/ jsx(WorkOfficeRibbonButton, {
|
|
13332
|
+
label: label,
|
|
13333
|
+
visibleLabel: label.replace(/(\d+)$/, ''),
|
|
13334
|
+
title: title ?? (shortcut ? `${label}(${shortcut})` : label),
|
|
13335
|
+
"aria-keyshortcuts": ariaKeyShortcuts,
|
|
13336
|
+
active: active,
|
|
13337
|
+
displayLabel: true,
|
|
13338
|
+
disabled: disabled,
|
|
13339
|
+
onClick: onClick,
|
|
13340
|
+
children: children
|
|
13341
|
+
});
|
|
13342
|
+
}
|
|
13140
13343
|
function selectedDocumentChangeIndex(changes, selection) {
|
|
13141
13344
|
const index = changes.findIndex((change)=>selection.from === selection.to ? change.from <= selection.from && selection.from <= change.to : change.from < selection.to && change.to > selection.from);
|
|
13142
13345
|
return index >= 0 ? index : null;
|
|
@@ -14942,7 +15145,7 @@ function headingLevelForShortcut(code, key) {
|
|
|
14942
15145
|
if ('3' === value) return 3;
|
|
14943
15146
|
return null;
|
|
14944
15147
|
}
|
|
14945
|
-
function DocumentToolbar({ editor, defaultRibbonCollapsed = false, reviewOnly = false, suggestionOnly = false, history, layoutOpen, layout, layoutFonts = [], navigationOpen, pageColor, showPageNumbers, showHiddenText, showRulers, spellcheckEnabled, viewMode, zoom, pageChromeEditor, pageChromeEditingPart, pageChromeShowPageNumber, onRequestImage, onPageChromeEditingPartChange, onClosePageChrome, onTogglePageChromePageNumber, onToggleLayout, onLayoutChange, onOpenLayout, onToggleNavigation, onTogglePageNumbers, onToggleHiddenText, onToggleRulers, onPageColorChange, onToggleSpellcheck, onViewModeChange, onZoomChange, onZoomFit, onInsertSection, onInsertNote, onInsertCaption, onInsertCrossReference, onOpenTableOfContents, citationsOpen, citationSourceCount, onToggleCitations, onInsertField, onRefreshFields, onRefreshTableOfContents, canInsertComment, onInsertComment, commentsOpen, commentCount, onToggleComments, trackChanges, changesOpen, changeCount, findReplaceMode, fileActions, onRibbonTabChange, onToggleTrackChanges, onToggleChanges, onDecideChange, onOpenWordCount, onOpenFindReplace }) {
|
|
15148
|
+
function DocumentToolbar({ editor, defaultRibbonCollapsed = false, reviewOnly = false, suggestionOnly = false, history, layoutOpen, layout, layoutFonts = [], navigationOpen, pageColor, showPageNumbers, showHiddenText, showRulers, spellcheckEnabled, viewMode, zoom, pageChromeEditor, pageChromeEditingPart, pageChromeShowPageNumber, onRequestImage, onPageChromeEditingPartChange, onClosePageChrome, onTogglePageChromePageNumber, onToggleLayout, onLayoutChange, onOpenLayout, onToggleNavigation, onTogglePageNumbers, onToggleHiddenText, onToggleRulers, onPageColorChange, onToggleSpellcheck, onViewModeChange, onZoomChange, onZoomFit, onInsertSection, onInsertNote, onInsertCaption, onInsertCrossReference, onOpenTableOfContents, onOpenIndexEntry, onOpenIndex, citationsOpen, citationSourceCount, onToggleCitations, onInsertField, onRefreshFields, onRefreshIndex, onRefreshTableOfContents, canInsertComment, onInsertComment, commentsOpen, commentCount, onToggleComments, trackChanges, changesOpen, changeCount, findReplaceMode, fileActions, onRibbonTabChange, onToggleTrackChanges, onToggleChanges, onOpenComparison, onDecideChange, onOpenWordCount, onOpenFindReplace }) {
|
|
14946
15149
|
const [activeTab, setActiveTab] = useState(reviewOnly ? 'review' : 'home');
|
|
14947
15150
|
const [fontDialogRequest, setFontDialogRequest] = useState(null);
|
|
14948
15151
|
const [proofingDialogRequest, setProofingDialogRequest] = useState(null);
|
|
@@ -14952,19 +15155,18 @@ function DocumentToolbar({ editor, defaultRibbonCollapsed = false, reviewOnly =
|
|
|
14952
15155
|
const tableSelected = editor.isActive('table');
|
|
14953
15156
|
const activeBookmark = activeDocumentBookmark(editor);
|
|
14954
15157
|
const hasRefreshableFields = documentHasRefreshableFields(editor);
|
|
14955
|
-
const hasTableOfContents = documentHasTableOfContents(editor);
|
|
14956
15158
|
const documentChanges = collectDocumentChanges(editor.state.doc);
|
|
14957
15159
|
const previousChangeIndex = adjacentDocumentChangeIndex(documentChanges, editor.state.selection, -1);
|
|
14958
15160
|
const nextChangeIndex = adjacentDocumentChangeIndex(documentChanges, editor.state.selection, 1);
|
|
14959
15161
|
const actionableChangeIndex = actionableDocumentChangeIndex(documentChanges, editor.state.selection);
|
|
14960
15162
|
const undoCommand = getDocumentCommandDefinition('undo');
|
|
14961
15163
|
const redoCommand = getDocumentCommandDefinition('redo');
|
|
14962
|
-
const refreshFieldsCommand = getDocumentCommandDefinition('refreshFields');
|
|
14963
15164
|
const spellingCommand = getDocumentCommandDefinition('spelling');
|
|
14964
15165
|
const insertCommentCommand = getDocumentCommandDefinition('insertComment');
|
|
14965
15166
|
const trackChangesCommand = getDocumentCommandDefinition('trackChanges');
|
|
14966
15167
|
const openFontDialog = useCallback((target)=>{
|
|
14967
15168
|
if (target.isDestroyed) return;
|
|
15169
|
+
synchronizeDocumentEditorSelectionFromDom(target);
|
|
14968
15170
|
const { from, to } = target.state.selection;
|
|
14969
15171
|
setFontDialogRequest({
|
|
14970
15172
|
editor: target,
|
|
@@ -14977,6 +15179,7 @@ function DocumentToolbar({ editor, defaultRibbonCollapsed = false, reviewOnly =
|
|
|
14977
15179
|
}, []);
|
|
14978
15180
|
const openProofingDialog = useCallback((target)=>{
|
|
14979
15181
|
if (target.isDestroyed) return;
|
|
15182
|
+
synchronizeDocumentEditorSelectionFromDom(target);
|
|
14980
15183
|
const { from, to } = target.state.selection;
|
|
14981
15184
|
setProofingDialogRequest({
|
|
14982
15185
|
editor: target,
|
|
@@ -15360,115 +15563,20 @@ function DocumentToolbar({ editor, defaultRibbonCollapsed = false, reviewOnly =
|
|
|
15360
15563
|
onPageColorChange: onPageColorChange,
|
|
15361
15564
|
onInsertSection: onInsertSection
|
|
15362
15565
|
}),
|
|
15363
|
-
references: reviewOnly ? null : /*#__PURE__*/
|
|
15364
|
-
|
|
15365
|
-
|
|
15366
|
-
|
|
15367
|
-
|
|
15368
|
-
|
|
15369
|
-
|
|
15370
|
-
|
|
15371
|
-
|
|
15372
|
-
|
|
15373
|
-
|
|
15374
|
-
|
|
15375
|
-
|
|
15376
|
-
|
|
15377
|
-
/*#__PURE__*/ jsx(document_toolbar_ToolbarButton, {
|
|
15378
|
-
label: "更新目录",
|
|
15379
|
-
displayLabel: true,
|
|
15380
|
-
disabled: !hasTableOfContents,
|
|
15381
|
-
title: hasTableOfContents ? '根据当前标题和页码更新目录' : '文档中没有可更新的目录',
|
|
15382
|
-
onClick: onRefreshTableOfContents,
|
|
15383
|
-
children: /*#__PURE__*/ jsx(RefreshCw, {
|
|
15384
|
-
size: 19
|
|
15385
|
-
})
|
|
15386
|
-
})
|
|
15387
|
-
]
|
|
15388
|
-
}),
|
|
15389
|
-
/*#__PURE__*/ jsxs(document_toolbar_RibbonGroup, {
|
|
15390
|
-
label: "脚注",
|
|
15391
|
-
priority: "high",
|
|
15392
|
-
children: [
|
|
15393
|
-
/*#__PURE__*/ jsx(document_toolbar_ToolbarButton, {
|
|
15394
|
-
label: "插入脚注",
|
|
15395
|
-
displayLabel: true,
|
|
15396
|
-
onClick: ()=>onInsertNote('footnote'),
|
|
15397
|
-
children: /*#__PURE__*/ jsx("span", {
|
|
15398
|
-
className: "work-ribbon-glyph",
|
|
15399
|
-
children: "\xb9"
|
|
15400
|
-
})
|
|
15401
|
-
}),
|
|
15402
|
-
/*#__PURE__*/ jsx(document_toolbar_ToolbarButton, {
|
|
15403
|
-
label: "插入尾注",
|
|
15404
|
-
displayLabel: true,
|
|
15405
|
-
onClick: ()=>onInsertNote('endnote'),
|
|
15406
|
-
children: /*#__PURE__*/ jsx("span", {
|
|
15407
|
-
className: "work-ribbon-glyph",
|
|
15408
|
-
children: "ⅰ"
|
|
15409
|
-
})
|
|
15410
|
-
})
|
|
15411
|
-
]
|
|
15412
|
-
}),
|
|
15413
|
-
/*#__PURE__*/ jsxs(document_toolbar_RibbonGroup, {
|
|
15414
|
-
label: "题注",
|
|
15415
|
-
children: [
|
|
15416
|
-
/*#__PURE__*/ jsx(document_toolbar_ToolbarButton, {
|
|
15417
|
-
label: "插入图片题注",
|
|
15418
|
-
displayLabel: true,
|
|
15419
|
-
onClick: ()=>onInsertCaption('figure'),
|
|
15420
|
-
children: /*#__PURE__*/ jsx(Image, {
|
|
15421
|
-
size: 19
|
|
15422
|
-
})
|
|
15423
|
-
}),
|
|
15424
|
-
/*#__PURE__*/ jsx(document_toolbar_ToolbarButton, {
|
|
15425
|
-
label: "插入表格题注",
|
|
15426
|
-
displayLabel: true,
|
|
15427
|
-
onClick: ()=>onInsertCaption('table'),
|
|
15428
|
-
children: /*#__PURE__*/ jsx(Table2, {
|
|
15429
|
-
size: 19
|
|
15430
|
-
})
|
|
15431
|
-
}),
|
|
15432
|
-
/*#__PURE__*/ jsx(document_toolbar_ToolbarButton, {
|
|
15433
|
-
label: "插入交叉引用",
|
|
15434
|
-
displayLabel: true,
|
|
15435
|
-
onClick: onInsertCrossReference,
|
|
15436
|
-
children: /*#__PURE__*/ jsx(Link2, {
|
|
15437
|
-
size: 19
|
|
15438
|
-
})
|
|
15439
|
-
})
|
|
15440
|
-
]
|
|
15441
|
-
}),
|
|
15442
|
-
/*#__PURE__*/ jsx(document_toolbar_RibbonGroup, {
|
|
15443
|
-
label: "引文和书目",
|
|
15444
|
-
priority: "high",
|
|
15445
|
-
children: /*#__PURE__*/ jsx(document_toolbar_ToolbarButton, {
|
|
15446
|
-
label: `文献库${citationSourceCount ? `(${citationSourceCount})` : ''}`,
|
|
15447
|
-
displayLabel: true,
|
|
15448
|
-
active: citationsOpen,
|
|
15449
|
-
onClick: onToggleCitations,
|
|
15450
|
-
children: /*#__PURE__*/ jsx(BookOpen, {
|
|
15451
|
-
size: 19
|
|
15452
|
-
})
|
|
15453
|
-
})
|
|
15454
|
-
}),
|
|
15455
|
-
/*#__PURE__*/ jsx(document_toolbar_RibbonGroup, {
|
|
15456
|
-
label: "更新",
|
|
15457
|
-
priority: "low",
|
|
15458
|
-
children: /*#__PURE__*/ jsx(document_toolbar_ToolbarButton, {
|
|
15459
|
-
label: "更新页码和日期",
|
|
15460
|
-
displayLabel: true,
|
|
15461
|
-
shortcut: refreshFieldsCommand.shortcut?.label,
|
|
15462
|
-
ariaKeyShortcuts: refreshFieldsCommand.shortcut?.aria,
|
|
15463
|
-
disabled: !hasRefreshableFields,
|
|
15464
|
-
title: hasRefreshableFields ? `更新页码和日期(${refreshFieldsCommand.shortcut?.label})` : '文档中没有可更新的页码或日期',
|
|
15465
|
-
onClick: onRefreshFields,
|
|
15466
|
-
children: /*#__PURE__*/ jsx(RefreshCw, {
|
|
15467
|
-
size: 19
|
|
15468
|
-
})
|
|
15469
|
-
})
|
|
15470
|
-
})
|
|
15471
|
-
]
|
|
15566
|
+
references: reviewOnly ? null : /*#__PURE__*/ jsx(DocumentReferencesRibbon, {
|
|
15567
|
+
editor: editor,
|
|
15568
|
+
citationsOpen: citationsOpen,
|
|
15569
|
+
citationSourceCount: citationSourceCount,
|
|
15570
|
+
onInsertNote: onInsertNote,
|
|
15571
|
+
onInsertCaption: onInsertCaption,
|
|
15572
|
+
onInsertCrossReference: onInsertCrossReference,
|
|
15573
|
+
onOpenTableOfContents: onOpenTableOfContents,
|
|
15574
|
+
onRefreshTableOfContents: onRefreshTableOfContents,
|
|
15575
|
+
onOpenIndexEntry: onOpenIndexEntry,
|
|
15576
|
+
onOpenIndex: onOpenIndex,
|
|
15577
|
+
onRefreshIndex: onRefreshIndex,
|
|
15578
|
+
onToggleCitations: onToggleCitations,
|
|
15579
|
+
onRefreshFields: onRefreshFields
|
|
15472
15580
|
}),
|
|
15473
15581
|
review: /*#__PURE__*/ jsxs(Fragment, {
|
|
15474
15582
|
children: [
|
|
@@ -15599,6 +15707,30 @@ function DocumentToolbar({ editor, defaultRibbonCollapsed = false, reviewOnly =
|
|
|
15599
15707
|
]
|
|
15600
15708
|
})
|
|
15601
15709
|
]
|
|
15710
|
+
}),
|
|
15711
|
+
!reviewOnly && !suggestionOnly && /*#__PURE__*/ jsxs(document_toolbar_RibbonGroup, {
|
|
15712
|
+
label: "比较",
|
|
15713
|
+
priority: "high",
|
|
15714
|
+
children: [
|
|
15715
|
+
/*#__PURE__*/ jsx(document_toolbar_ToolbarButton, {
|
|
15716
|
+
label: "比较文档",
|
|
15717
|
+
displayLabel: true,
|
|
15718
|
+
title: "将另一个版本转换为可接受或拒绝的修订",
|
|
15719
|
+
onClick: ()=>onOpenComparison('compare'),
|
|
15720
|
+
children: /*#__PURE__*/ jsx(GitCompareArrows, {
|
|
15721
|
+
size: 19
|
|
15722
|
+
})
|
|
15723
|
+
}),
|
|
15724
|
+
/*#__PURE__*/ jsx(document_toolbar_ToolbarButton, {
|
|
15725
|
+
label: "合并文档",
|
|
15726
|
+
displayLabel: true,
|
|
15727
|
+
title: "合入与当前基线一致的带修订审阅副本",
|
|
15728
|
+
onClick: ()=>onOpenComparison('combine'),
|
|
15729
|
+
children: /*#__PURE__*/ jsx(FileStack, {
|
|
15730
|
+
size: 19
|
|
15731
|
+
})
|
|
15732
|
+
})
|
|
15733
|
+
]
|
|
15602
15734
|
})
|
|
15603
15735
|
]
|
|
15604
15736
|
}),
|
|
@@ -15838,15 +15970,6 @@ function DocumentFieldSelect({ onInsertField }) {
|
|
|
15838
15970
|
}
|
|
15839
15971
|
});
|
|
15840
15972
|
}
|
|
15841
|
-
function documentHasRefreshableFields(editor) {
|
|
15842
|
-
let found = false;
|
|
15843
|
-
editor.state.doc.descendants((node)=>{
|
|
15844
|
-
if ('documentField' !== node.type.name) return !found;
|
|
15845
|
-
found = true;
|
|
15846
|
-
return false;
|
|
15847
|
-
});
|
|
15848
|
-
return found;
|
|
15849
|
-
}
|
|
15850
15973
|
function DocumentVerticalRuler({ layout, onLayoutChange }) {
|
|
15851
15974
|
const rulerRef = useRef(null);
|
|
15852
15975
|
const pageHeight = documentRulerPageHeight(layout);
|
|
@@ -16146,59 +16269,1105 @@ function commentActor(actor) {
|
|
|
16146
16269
|
author: CURRENT_COMMENT_AUTHOR
|
|
16147
16270
|
};
|
|
16148
16271
|
}
|
|
16149
|
-
|
|
16150
|
-
|
|
16151
|
-
|
|
16152
|
-
|
|
16272
|
+
const GAP_COST = 1;
|
|
16273
|
+
const ALIGNMENT_EPSILON = 1e-9;
|
|
16274
|
+
function boundedDocumentSequenceDiff(left, right, equal, maximumCells) {
|
|
16275
|
+
if ((left.length + 1) * (right.length + 1) > maximumCells) return null;
|
|
16276
|
+
const columns = right.length + 1;
|
|
16277
|
+
const CellArray = Math.max(left.length, right.length) <= 65535 ? Uint16Array : Uint32Array;
|
|
16278
|
+
const matrix = new CellArray((left.length + 1) * columns);
|
|
16279
|
+
for(let leftIndex = left.length - 1; leftIndex >= 0; leftIndex -= 1)for(let rightIndex = right.length - 1; rightIndex >= 0; rightIndex -= 1){
|
|
16280
|
+
const offset = leftIndex * columns + rightIndex;
|
|
16281
|
+
matrix[offset] = equal(left[leftIndex], right[rightIndex]) ? 1 + matrix[(leftIndex + 1) * columns + rightIndex + 1] : Math.max(matrix[(leftIndex + 1) * columns + rightIndex], matrix[leftIndex * columns + rightIndex + 1]);
|
|
16282
|
+
}
|
|
16283
|
+
const steps = [];
|
|
16284
|
+
let leftIndex = 0;
|
|
16285
|
+
let rightIndex = 0;
|
|
16286
|
+
while(leftIndex < left.length || rightIndex < right.length){
|
|
16287
|
+
if (leftIndex < left.length && rightIndex < right.length && equal(left[leftIndex], right[rightIndex])) {
|
|
16288
|
+
appendSequenceDiff(steps, 'equal', left[leftIndex], right[rightIndex]);
|
|
16289
|
+
leftIndex += 1;
|
|
16290
|
+
rightIndex += 1;
|
|
16291
|
+
continue;
|
|
16292
|
+
}
|
|
16293
|
+
const deleteScore = leftIndex < left.length ? matrix[(leftIndex + 1) * columns + rightIndex] : -1;
|
|
16294
|
+
const insertScore = rightIndex < right.length ? matrix[leftIndex * columns + rightIndex + 1] : -1;
|
|
16295
|
+
if (leftIndex < left.length && deleteScore >= insertScore) {
|
|
16296
|
+
appendSequenceDiff(steps, 'delete', left[leftIndex], null);
|
|
16297
|
+
leftIndex += 1;
|
|
16298
|
+
} else {
|
|
16299
|
+
appendSequenceDiff(steps, 'insert', null, right[rightIndex]);
|
|
16300
|
+
rightIndex += 1;
|
|
16301
|
+
}
|
|
16302
|
+
}
|
|
16303
|
+
return steps;
|
|
16304
|
+
}
|
|
16305
|
+
function boundedDocumentSequenceAlignment(left, right, substitutionCost, maximumCells) {
|
|
16306
|
+
if ((left.length + 1) * (right.length + 1) > maximumCells) return null;
|
|
16307
|
+
const columns = right.length + 1;
|
|
16308
|
+
const matrix = new Float64Array((left.length + 1) * columns);
|
|
16309
|
+
for(let leftIndex = 1; leftIndex <= left.length; leftIndex += 1)matrix[leftIndex * columns] = leftIndex * GAP_COST;
|
|
16310
|
+
for(let rightIndex = 1; rightIndex <= right.length; rightIndex += 1)matrix[rightIndex] = rightIndex * GAP_COST;
|
|
16311
|
+
for(let leftIndex = 1; leftIndex <= left.length; leftIndex += 1)for(let rightIndex = 1; rightIndex <= right.length; rightIndex += 1){
|
|
16312
|
+
const substitution = matrix[(leftIndex - 1) * columns + rightIndex - 1] + substitutionCost(left[leftIndex - 1], right[rightIndex - 1]);
|
|
16313
|
+
const deletion = matrix[(leftIndex - 1) * columns + rightIndex] + GAP_COST;
|
|
16314
|
+
const insertion = matrix[leftIndex * columns + rightIndex - 1] + GAP_COST;
|
|
16315
|
+
matrix[leftIndex * columns + rightIndex] = Math.min(substitution, deletion, insertion);
|
|
16316
|
+
}
|
|
16317
|
+
const reversed = [];
|
|
16318
|
+
let leftIndex = left.length;
|
|
16319
|
+
let rightIndex = right.length;
|
|
16320
|
+
while(leftIndex > 0 || rightIndex > 0){
|
|
16321
|
+
const current = matrix[leftIndex * columns + rightIndex];
|
|
16322
|
+
if (leftIndex > 0 && rightIndex > 0) {
|
|
16323
|
+
const cost = substitutionCost(left[leftIndex - 1], right[rightIndex - 1]);
|
|
16324
|
+
const substitution = matrix[(leftIndex - 1) * columns + rightIndex - 1] + cost;
|
|
16325
|
+
if (approximatelyEqual(current, substitution)) {
|
|
16326
|
+
reversed.push({
|
|
16327
|
+
kind: 0 === cost ? 'equal' : 'substitute',
|
|
16328
|
+
left: left[leftIndex - 1],
|
|
16329
|
+
right: right[rightIndex - 1]
|
|
16330
|
+
});
|
|
16331
|
+
leftIndex -= 1;
|
|
16332
|
+
rightIndex -= 1;
|
|
16333
|
+
continue;
|
|
16334
|
+
}
|
|
16335
|
+
}
|
|
16336
|
+
if (leftIndex > 0 && approximatelyEqual(current, matrix[(leftIndex - 1) * columns + rightIndex] + GAP_COST)) {
|
|
16337
|
+
reversed.push({
|
|
16338
|
+
kind: 'delete',
|
|
16339
|
+
left: left[leftIndex - 1],
|
|
16340
|
+
right: null
|
|
16341
|
+
});
|
|
16342
|
+
leftIndex -= 1;
|
|
16343
|
+
continue;
|
|
16344
|
+
}
|
|
16345
|
+
reversed.push({
|
|
16346
|
+
kind: 'insert',
|
|
16347
|
+
left: null,
|
|
16348
|
+
right: right[rightIndex - 1]
|
|
16153
16349
|
});
|
|
16154
|
-
|
|
16155
|
-
|
|
16156
|
-
|
|
16157
|
-
|
|
16158
|
-
|
|
16159
|
-
|
|
16160
|
-
|
|
16161
|
-
|
|
16162
|
-
|
|
16163
|
-
|
|
16164
|
-
|
|
16165
|
-
|
|
16166
|
-
|
|
16167
|
-
|
|
16168
|
-
|
|
16169
|
-
|
|
16170
|
-
|
|
16171
|
-
|
|
16172
|
-
|
|
16173
|
-
|
|
16174
|
-
|
|
16175
|
-
|
|
16176
|
-
|
|
16177
|
-
|
|
16178
|
-
|
|
16179
|
-
|
|
16180
|
-
|
|
16181
|
-
|
|
16182
|
-
|
|
16183
|
-
|
|
16184
|
-
|
|
16185
|
-
|
|
16186
|
-
|
|
16187
|
-
|
|
16188
|
-
|
|
16189
|
-
|
|
16190
|
-
|
|
16191
|
-
|
|
16192
|
-
|
|
16193
|
-
|
|
16194
|
-
|
|
16195
|
-
|
|
16196
|
-
|
|
16197
|
-
|
|
16198
|
-
|
|
16199
|
-
|
|
16200
|
-
|
|
16201
|
-
|
|
16350
|
+
rightIndex -= 1;
|
|
16351
|
+
}
|
|
16352
|
+
return reversed.reverse();
|
|
16353
|
+
}
|
|
16354
|
+
function appendSequenceDiff(changes, kind, left, right) {
|
|
16355
|
+
const previous = changes.at(-1);
|
|
16356
|
+
if (previous?.kind === kind) {
|
|
16357
|
+
if ('equal' === previous.kind) {
|
|
16358
|
+
previous.left.push(left);
|
|
16359
|
+
previous.right.push(right);
|
|
16360
|
+
} else if ('delete' === previous.kind) previous.left.push(left);
|
|
16361
|
+
else previous.right.push(right);
|
|
16362
|
+
return;
|
|
16363
|
+
}
|
|
16364
|
+
if ('equal' === kind) changes.push({
|
|
16365
|
+
kind,
|
|
16366
|
+
left: [
|
|
16367
|
+
left
|
|
16368
|
+
],
|
|
16369
|
+
right: [
|
|
16370
|
+
right
|
|
16371
|
+
]
|
|
16372
|
+
});
|
|
16373
|
+
else if ('delete' === kind) changes.push({
|
|
16374
|
+
kind,
|
|
16375
|
+
left: [
|
|
16376
|
+
left
|
|
16377
|
+
],
|
|
16378
|
+
right: []
|
|
16379
|
+
});
|
|
16380
|
+
else changes.push({
|
|
16381
|
+
kind,
|
|
16382
|
+
left: [],
|
|
16383
|
+
right: [
|
|
16384
|
+
right
|
|
16385
|
+
]
|
|
16386
|
+
});
|
|
16387
|
+
}
|
|
16388
|
+
function approximatelyEqual(left, right) {
|
|
16389
|
+
return Math.abs(left - right) <= ALIGNMENT_EPSILON;
|
|
16390
|
+
}
|
|
16391
|
+
function stableJson(value) {
|
|
16392
|
+
return JSON.stringify(sortJsonValue(value));
|
|
16393
|
+
}
|
|
16394
|
+
function stableHash(value) {
|
|
16395
|
+
let hash = 0x811c9dc5;
|
|
16396
|
+
for(let index = 0; index < value.length; index += 1){
|
|
16397
|
+
hash ^= value.charCodeAt(index);
|
|
16398
|
+
hash = Math.imul(hash, 0x01000193);
|
|
16399
|
+
}
|
|
16400
|
+
return (hash >>> 0).toString(16).padStart(8, '0');
|
|
16401
|
+
}
|
|
16402
|
+
function wordParagraphId(value) {
|
|
16403
|
+
const number = 0x7fffffff & Number.parseInt(stableHash(value), 16) || 1;
|
|
16404
|
+
return number.toString(16).toUpperCase().padStart(8, '0');
|
|
16405
|
+
}
|
|
16406
|
+
function normalizedDate(value) {
|
|
16407
|
+
const timestamp = Date.parse(value);
|
|
16408
|
+
return Number.isFinite(timestamp) ? new Date(timestamp).toISOString() : '1970-01-01T00:00:00.000Z';
|
|
16409
|
+
}
|
|
16410
|
+
function boundedMetadata(value, maximum) {
|
|
16411
|
+
return value.trim().slice(0, maximum);
|
|
16412
|
+
}
|
|
16413
|
+
function sortJsonValue(value) {
|
|
16414
|
+
if (Array.isArray(value)) return value.map(sortJsonValue);
|
|
16415
|
+
if (!value || 'object' != typeof value) return value;
|
|
16416
|
+
return Object.fromEntries(Object.entries(value).sort(([left], [right])=>left.localeCompare(right)).map(([name, child])=>[
|
|
16417
|
+
name,
|
|
16418
|
+
sortJsonValue(child)
|
|
16419
|
+
]));
|
|
16420
|
+
}
|
|
16421
|
+
const MAX_COMPARISON_BLOCKS = 1024;
|
|
16422
|
+
const MAX_COMPARISON_TEXT = 1000000;
|
|
16423
|
+
const MAX_BLOCK_ALIGNMENT_CELLS = 1100000;
|
|
16424
|
+
const MAX_INLINE_DIFF_CELLS = 1100000;
|
|
16425
|
+
const BLOCK_CHANGE_ATTRIBUTE_NAMES = [
|
|
16426
|
+
'blockChangeKind',
|
|
16427
|
+
'blockChangeId',
|
|
16428
|
+
'blockChangeActorId',
|
|
16429
|
+
'blockChangeAuthor',
|
|
16430
|
+
'blockChangeDate'
|
|
16431
|
+
];
|
|
16432
|
+
const PARAGRAPH_IDENTITY_ATTRIBUTE_NAMES = [
|
|
16433
|
+
'paragraphId',
|
|
16434
|
+
'textId'
|
|
16435
|
+
];
|
|
16436
|
+
const REVIEW_ONLY_MARK_NAMES = new Set([
|
|
16437
|
+
'documentChange',
|
|
16438
|
+
'documentComment'
|
|
16439
|
+
]);
|
|
16440
|
+
function applyDocumentComparison(editor, revisedContent, options) {
|
|
16441
|
+
const revisedDocument = parseComparisonDocument(editor, revisedContent);
|
|
16442
|
+
if (!revisedDocument) return unsupportedResult({
|
|
16443
|
+
code: 'invalid-revised-content',
|
|
16444
|
+
message: 'The selected file cannot be represented by the Writer schema.'
|
|
16445
|
+
});
|
|
16446
|
+
const plan = 'combine' === options.mode ? planDocumentCombine(editor.state.doc, revisedDocument) : planDocumentCompare(editor.state.doc, revisedDocument, options);
|
|
16447
|
+
if ('applied' !== plan.status || !plan.document) return plan;
|
|
16448
|
+
const transaction = editor.state.tr;
|
|
16449
|
+
closeHistory(transaction);
|
|
16450
|
+
transaction.replaceWith(0, editor.state.doc.content.size, plan.document.content).setMeta('documentComparison', {
|
|
16451
|
+
mode: options.mode,
|
|
16452
|
+
sourceName: boundedMetadata(options.sourceName, 512)
|
|
16453
|
+
});
|
|
16454
|
+
if (!transaction.docChanged) return {
|
|
16455
|
+
status: 'unchanged',
|
|
16456
|
+
summary: plan.summary,
|
|
16457
|
+
diagnostics: plan.diagnostics
|
|
16458
|
+
};
|
|
16459
|
+
editor.view.dispatch(transaction);
|
|
16460
|
+
return {
|
|
16461
|
+
status: 'applied',
|
|
16462
|
+
summary: plan.summary,
|
|
16463
|
+
diagnostics: plan.diagnostics
|
|
16464
|
+
};
|
|
16465
|
+
}
|
|
16466
|
+
function planDocumentCompare(current, revised, options) {
|
|
16467
|
+
if (collectDocumentChanges(current).length) return unsupportedResult({
|
|
16468
|
+
code: 'current-revisions-present',
|
|
16469
|
+
message: 'Resolve current revisions before comparing another version.'
|
|
16470
|
+
});
|
|
16471
|
+
if (collectDocumentChanges(revised).length) return unsupportedResult({
|
|
16472
|
+
code: 'revised-revisions-present',
|
|
16473
|
+
message: 'Use Combine for a reviewed copy that already contains revisions.'
|
|
16474
|
+
});
|
|
16475
|
+
const currentSections = documentSections(current);
|
|
16476
|
+
const revisedSections = documentSections(revised);
|
|
16477
|
+
if (!currentSections || !revisedSections || currentSections.length !== revisedSections.length) return unsupportedResult({
|
|
16478
|
+
code: 'section-layout-mismatch',
|
|
16479
|
+
message: 'Comparison requires the same bounded section structure.'
|
|
16480
|
+
});
|
|
16481
|
+
const limits = comparisonLimits(currentSections, revisedSections);
|
|
16482
|
+
if (limits) return unsupportedResult(limits);
|
|
16483
|
+
const diagnostics = [];
|
|
16484
|
+
const factory = comparisonIdentityFactory(current, revised, options);
|
|
16485
|
+
const comparedSections = [];
|
|
16486
|
+
for(let sectionIndex = 0; sectionIndex < currentSections.length; sectionIndex += 1){
|
|
16487
|
+
const currentSection = currentSections[sectionIndex];
|
|
16488
|
+
const revisedSection = revisedSections[sectionIndex];
|
|
16489
|
+
if (sectionLayoutSignature(currentSection) !== sectionLayoutSignature(revisedSection)) {
|
|
16490
|
+
diagnostics.push({
|
|
16491
|
+
code: 'section-layout-mismatch',
|
|
16492
|
+
message: 'Page and section layout differences are not approximated as text revisions.',
|
|
16493
|
+
section: sectionIndex
|
|
16494
|
+
});
|
|
16495
|
+
continue;
|
|
16496
|
+
}
|
|
16497
|
+
const blocks = compareSectionBlocks(currentSection, revisedSection, sectionIndex, factory, diagnostics);
|
|
16498
|
+
if (blocks) comparedSections.push(currentSection.type.create(currentSection.attrs, model_Fragment.fromArray(blocks)));
|
|
16499
|
+
}
|
|
16500
|
+
if (diagnostics.length) return {
|
|
16501
|
+
status: 'unsupported',
|
|
16502
|
+
summary: factory.summary,
|
|
16503
|
+
diagnostics
|
|
16504
|
+
};
|
|
16505
|
+
const document1 = current.type.create(current.attrs, comparedSections);
|
|
16506
|
+
if (document1.eq(current)) return {
|
|
16507
|
+
status: 'unchanged',
|
|
16508
|
+
summary: emptyComparisonSummary(),
|
|
16509
|
+
diagnostics: []
|
|
16510
|
+
};
|
|
16511
|
+
return {
|
|
16512
|
+
status: 'applied',
|
|
16513
|
+
document: document1,
|
|
16514
|
+
summary: factory.summary,
|
|
16515
|
+
diagnostics: []
|
|
16516
|
+
};
|
|
16517
|
+
}
|
|
16518
|
+
function planDocumentCombine(current, reviewed) {
|
|
16519
|
+
if (collectDocumentChanges(current).length) return unsupportedResult({
|
|
16520
|
+
code: 'current-revisions-present',
|
|
16521
|
+
message: 'Resolve current revisions before combining another reviewed copy.'
|
|
16522
|
+
});
|
|
16523
|
+
const reviewedChanges = collectDocumentChanges(reviewed);
|
|
16524
|
+
if (!reviewedChanges.length) return unsupportedResult({
|
|
16525
|
+
code: 'combine-without-revisions',
|
|
16526
|
+
message: 'The selected reviewed copy contains no revisions to combine.'
|
|
16527
|
+
});
|
|
16528
|
+
if (hasStructuralBlockRevisions(reviewed)) return unsupportedResult({
|
|
16529
|
+
code: 'combine-structural-revisions',
|
|
16530
|
+
message: 'This reviewed copy changes the paragraph tree; combine supports inline and formatting revisions.'
|
|
16531
|
+
});
|
|
16532
|
+
const baseline = resolveAllDocumentChanges(reviewed, 'reject');
|
|
16533
|
+
if (!baseline) return unsupportedResult({
|
|
16534
|
+
code: 'combine-resolution-invalid',
|
|
16535
|
+
message: 'The reviewed copy contains a malformed formatting revision.'
|
|
16536
|
+
});
|
|
16537
|
+
if (!simpleDocumentShape(current) || !simpleDocumentShape(baseline)) return unsupportedResult({
|
|
16538
|
+
code: 'changed-complex-structure',
|
|
16539
|
+
message: 'Combine currently accepts text-only paragraph and heading review copies.'
|
|
16540
|
+
});
|
|
16541
|
+
if (comparisonSemanticSignature(current) !== comparisonSemanticSignature(baseline)) return unsupportedResult({
|
|
16542
|
+
code: 'combine-baseline-mismatch',
|
|
16543
|
+
message: 'Rejecting the imported revisions does not reproduce the current document.'
|
|
16544
|
+
});
|
|
16545
|
+
const document1 = transferCombineIdentities(current, reviewed);
|
|
16546
|
+
if (!document1) return unsupportedResult({
|
|
16547
|
+
code: 'combine-structural-revisions',
|
|
16548
|
+
message: 'This reviewed copy changes the paragraph tree; combine supports inline and formatting revisions.'
|
|
16549
|
+
});
|
|
16550
|
+
return {
|
|
16551
|
+
status: 'applied',
|
|
16552
|
+
document: document1,
|
|
16553
|
+
summary: summarizeChanges(reviewedChanges),
|
|
16554
|
+
diagnostics: []
|
|
16555
|
+
};
|
|
16556
|
+
}
|
|
16557
|
+
function compareSectionBlocks(currentSection, revisedSection, sectionIndex, factory, diagnostics) {
|
|
16558
|
+
const currentBlocks = childNodes(currentSection);
|
|
16559
|
+
const revisedBlocks = childNodes(revisedSection);
|
|
16560
|
+
const alignment = boundedDocumentSequenceAlignment(currentBlocks, revisedBlocks, blockSubstitutionCost, MAX_BLOCK_ALIGNMENT_CELLS);
|
|
16561
|
+
if (!alignment) {
|
|
16562
|
+
diagnostics.push({
|
|
16563
|
+
code: 'comparison-limit-exceeded',
|
|
16564
|
+
message: 'The block-alignment matrix exceeds the bounded comparison limit.',
|
|
16565
|
+
section: sectionIndex
|
|
16566
|
+
});
|
|
16567
|
+
return null;
|
|
16568
|
+
}
|
|
16569
|
+
const result = [];
|
|
16570
|
+
for (const [blockIndex, step] of alignment.entries()){
|
|
16571
|
+
if ('equal' === step.kind) {
|
|
16572
|
+
result.push(step.left);
|
|
16573
|
+
continue;
|
|
16574
|
+
}
|
|
16575
|
+
if ('delete' === step.kind) {
|
|
16576
|
+
const deleted = structuralChangeBlock(step.left, 'deletion', factory, `section-${sectionIndex}-delete-${blockIndex}`);
|
|
16577
|
+
if (deleted) result.push(deleted);
|
|
16578
|
+
else diagnostics.push(structuralBlockDiagnostic(step.left, sectionIndex, blockIndex));
|
|
16579
|
+
continue;
|
|
16580
|
+
}
|
|
16581
|
+
if ('insert' === step.kind) {
|
|
16582
|
+
const inserted = structuralChangeBlock(step.right, 'insertion', factory, `section-${sectionIndex}-insert-${blockIndex}`);
|
|
16583
|
+
if (inserted) result.push(inserted);
|
|
16584
|
+
else diagnostics.push(structuralBlockDiagnostic(step.right, sectionIndex, blockIndex));
|
|
16585
|
+
continue;
|
|
16586
|
+
}
|
|
16587
|
+
const compared = comparePairedBlocks(step.left, step.right, factory);
|
|
16588
|
+
if (compared) {
|
|
16589
|
+
result.push(compared);
|
|
16590
|
+
continue;
|
|
16591
|
+
}
|
|
16592
|
+
const deleted = structuralChangeBlock(step.left, 'deletion', factory, `section-${sectionIndex}-replace-delete-${blockIndex}`);
|
|
16593
|
+
const inserted = structuralChangeBlock(step.right, 'insertion', factory, `section-${sectionIndex}-replace-insert-${blockIndex}`);
|
|
16594
|
+
if (deleted && inserted) result.push(deleted, inserted);
|
|
16595
|
+
else diagnostics.push(structuralBlockDiagnostic(step.left, sectionIndex, blockIndex));
|
|
16596
|
+
}
|
|
16597
|
+
return result;
|
|
16598
|
+
}
|
|
16599
|
+
function comparePairedBlocks(current, revised, factory) {
|
|
16600
|
+
if (!isSimpleTextBlock(current) || !isSimpleTextBlock(revised)) return null;
|
|
16601
|
+
if (current.type !== revised.type || blockStructuralSignature(current) !== blockStructuralSignature(revised)) return null;
|
|
16602
|
+
const inline = compareInlineContent(current, revised, factory);
|
|
16603
|
+
if (!inline) return null;
|
|
16604
|
+
const before = serializeDocumentParagraphFormatting(current.attrs);
|
|
16605
|
+
const after = serializeDocumentParagraphFormatting(revised.attrs);
|
|
16606
|
+
let attributes = {
|
|
16607
|
+
...current.attrs,
|
|
16608
|
+
...paragraphFormattingAttributes(revised.attrs)
|
|
16609
|
+
};
|
|
16610
|
+
if (before !== after) {
|
|
16611
|
+
const identity = factory.create('paragraph-formatting', before);
|
|
16612
|
+
attributes = {
|
|
16613
|
+
...attributes,
|
|
16614
|
+
paragraphChangeKind: 'paragraph-formatting',
|
|
16615
|
+
paragraphChangeId: identity.id,
|
|
16616
|
+
paragraphChangeActorId: identity.actorId ?? '',
|
|
16617
|
+
paragraphChangeAuthor: identity.author,
|
|
16618
|
+
paragraphChangeDate: identity.date,
|
|
16619
|
+
paragraphChangeBefore: before
|
|
16620
|
+
};
|
|
16621
|
+
}
|
|
16622
|
+
return current.type.create(attributes, inline);
|
|
16623
|
+
}
|
|
16624
|
+
function compareInlineContent(current, revised, factory) {
|
|
16625
|
+
const currentUnits = inlineUnits(current);
|
|
16626
|
+
const revisedUnits = inlineUnits(revised);
|
|
16627
|
+
if (!currentUnits || !revisedUnits) return null;
|
|
16628
|
+
const diff = boundedDocumentSequenceDiff(currentUnits, revisedUnits, (left, right)=>left.text === right.text, MAX_INLINE_DIFF_CELLS);
|
|
16629
|
+
const changes = diff ?? [
|
|
16630
|
+
{
|
|
16631
|
+
kind: 'delete',
|
|
16632
|
+
left: currentUnits,
|
|
16633
|
+
right: []
|
|
16634
|
+
},
|
|
16635
|
+
{
|
|
16636
|
+
kind: 'insert',
|
|
16637
|
+
left: [],
|
|
16638
|
+
right: revisedUnits
|
|
16639
|
+
}
|
|
16640
|
+
];
|
|
16641
|
+
const nodes = [];
|
|
16642
|
+
for (const change of changes){
|
|
16643
|
+
if ('delete' === change.kind) {
|
|
16644
|
+
appendRevisionUnits(nodes, change.left, 'deletion', factory.create('deletion'), current.type.schema);
|
|
16645
|
+
continue;
|
|
16646
|
+
}
|
|
16647
|
+
if ('insert' === change.kind) {
|
|
16648
|
+
appendRevisionUnits(nodes, change.right, 'insertion', factory.create('insertion'), current.type.schema);
|
|
16649
|
+
continue;
|
|
16650
|
+
}
|
|
16651
|
+
let formattingIdentity = null;
|
|
16652
|
+
let formattingSignature = '';
|
|
16653
|
+
for(let index = 0; index < change.left.length; index += 1){
|
|
16654
|
+
const left = change.left[index];
|
|
16655
|
+
const right = change.right[index];
|
|
16656
|
+
if (marksEqual(left.marks, right.marks)) {
|
|
16657
|
+
nodes.push(current.type.schema.text(left.text, [
|
|
16658
|
+
...left.marks
|
|
16659
|
+
]));
|
|
16660
|
+
formattingIdentity = null;
|
|
16661
|
+
formattingSignature = '';
|
|
16662
|
+
continue;
|
|
16663
|
+
}
|
|
16664
|
+
if (nonCharacterMarksEqual(left.marks, right.marks)) {
|
|
16665
|
+
const before = serializeDocumentCharacterFormatting(left.marks);
|
|
16666
|
+
const after = serializeDocumentCharacterFormatting(right.marks);
|
|
16667
|
+
const signature = `${before}\u0000${after}`;
|
|
16668
|
+
if (!formattingIdentity || formattingSignature !== signature) {
|
|
16669
|
+
formattingIdentity = factory.create('formatting', before);
|
|
16670
|
+
formattingSignature = signature;
|
|
16671
|
+
}
|
|
16672
|
+
nodes.push(current.type.schema.text(right.text, [
|
|
16673
|
+
...withoutReviewMarks(right.marks),
|
|
16674
|
+
comparisonMark(current, 'formatting', formattingIdentity, before)
|
|
16675
|
+
]));
|
|
16676
|
+
continue;
|
|
16677
|
+
}
|
|
16678
|
+
const deletion = factory.create('deletion');
|
|
16679
|
+
const insertion = factory.create('insertion');
|
|
16680
|
+
appendRevisionUnits(nodes, [
|
|
16681
|
+
left
|
|
16682
|
+
], 'deletion', deletion, current.type.schema);
|
|
16683
|
+
appendRevisionUnits(nodes, [
|
|
16684
|
+
right
|
|
16685
|
+
], 'insertion', insertion, current.type.schema);
|
|
16686
|
+
formattingIdentity = null;
|
|
16687
|
+
formattingSignature = '';
|
|
16688
|
+
}
|
|
16689
|
+
}
|
|
16690
|
+
return model_Fragment.fromArray(nodes);
|
|
16691
|
+
}
|
|
16692
|
+
function structuralChangeBlock(block, kind, factory, identityChannel) {
|
|
16693
|
+
if (!isSimpleTextBlock(block) || !block.textContent) return null;
|
|
16694
|
+
const identity = factory.create(kind);
|
|
16695
|
+
const paragraphIdentity = 'insertion' === kind ? factory.paragraphIdentity(identityChannel) : {
|
|
16696
|
+
paragraphId: block.attrs.paragraphId,
|
|
16697
|
+
textId: block.attrs.textId
|
|
16698
|
+
};
|
|
16699
|
+
const nodes = [];
|
|
16700
|
+
for (const unit of inlineUnits(block) ?? [])appendRevisionUnits(nodes, [
|
|
16701
|
+
unit
|
|
16702
|
+
], kind, identity, block.type.schema);
|
|
16703
|
+
return block.type.create({
|
|
16704
|
+
...block.attrs,
|
|
16705
|
+
...paragraphIdentity,
|
|
16706
|
+
blockChangeKind: kind,
|
|
16707
|
+
blockChangeId: identity.id,
|
|
16708
|
+
blockChangeActorId: identity.actorId ?? '',
|
|
16709
|
+
blockChangeAuthor: identity.author,
|
|
16710
|
+
blockChangeDate: identity.date
|
|
16711
|
+
}, model_Fragment.fromArray(nodes));
|
|
16712
|
+
}
|
|
16713
|
+
function appendRevisionUnits(target, units, kind, identity, schema) {
|
|
16714
|
+
for (const unit of units)target.push(schema.text(unit.text, [
|
|
16715
|
+
...withoutReviewMarks(unit.marks),
|
|
16716
|
+
schema.marks.documentChange.create({
|
|
16717
|
+
kind,
|
|
16718
|
+
id: identity.id,
|
|
16719
|
+
actorId: identity.actorId ?? '',
|
|
16720
|
+
author: identity.author,
|
|
16721
|
+
date: identity.date,
|
|
16722
|
+
before: ''
|
|
16723
|
+
})
|
|
16724
|
+
]));
|
|
16725
|
+
}
|
|
16726
|
+
function comparisonMark(node, kind, identity, before) {
|
|
16727
|
+
return node.type.schema.marks.documentChange.create({
|
|
16728
|
+
kind,
|
|
16729
|
+
id: identity.id,
|
|
16730
|
+
actorId: identity.actorId ?? '',
|
|
16731
|
+
author: identity.author,
|
|
16732
|
+
date: identity.date,
|
|
16733
|
+
before
|
|
16734
|
+
});
|
|
16735
|
+
}
|
|
16736
|
+
function parseComparisonDocument(editor, content) {
|
|
16737
|
+
try {
|
|
16738
|
+
const parsed = createNodeFromContent(content, editor.schema, {
|
|
16739
|
+
errorOnInvalidContent: true,
|
|
16740
|
+
slice: false
|
|
16741
|
+
});
|
|
16742
|
+
return parsed instanceof model_Node ? parsed : null;
|
|
16743
|
+
} catch {
|
|
16744
|
+
return null;
|
|
16745
|
+
}
|
|
16746
|
+
}
|
|
16747
|
+
function documentSections(document1) {
|
|
16748
|
+
const sections = childNodes(document1);
|
|
16749
|
+
return sections.every((node)=>'documentSection' === node.type.name) ? sections : null;
|
|
16750
|
+
}
|
|
16751
|
+
function comparisonLimits(current, revised) {
|
|
16752
|
+
const currentBlocks = current.reduce((sum, section)=>sum + section.childCount, 0);
|
|
16753
|
+
const revisedBlocks = revised.reduce((sum, section)=>sum + section.childCount, 0);
|
|
16754
|
+
const textLength = current.reduce((sum, section)=>sum + section.textContent.length, 0) + revised.reduce((sum, section)=>sum + section.textContent.length, 0);
|
|
16755
|
+
return currentBlocks > MAX_COMPARISON_BLOCKS || revisedBlocks > MAX_COMPARISON_BLOCKS || textLength > MAX_COMPARISON_TEXT ? {
|
|
16756
|
+
code: 'comparison-limit-exceeded',
|
|
16757
|
+
message: `Comparison accepts at most ${MAX_COMPARISON_BLOCKS} blocks per version and ${MAX_COMPARISON_TEXT} combined text characters.`
|
|
16758
|
+
} : null;
|
|
16759
|
+
}
|
|
16760
|
+
function blockSubstitutionCost(current, revised) {
|
|
16761
|
+
if (comparisonSemanticSignature(current) === comparisonSemanticSignature(revised)) return 0;
|
|
16762
|
+
if (!isSimpleTextBlock(current) || !isSimpleTextBlock(revised)) return 2.1;
|
|
16763
|
+
if (current.type !== revised.type) return 2.1;
|
|
16764
|
+
if (!current.textContent && !revised.textContent) return 0.8;
|
|
16765
|
+
return documentTextSimilarity(current.textContent, revised.textContent) >= 0.18 ? 0.8 : 2.1;
|
|
16766
|
+
}
|
|
16767
|
+
function documentTextSimilarity(left, right) {
|
|
16768
|
+
if (left === right) return 1;
|
|
16769
|
+
if (!left || !right) return 0;
|
|
16770
|
+
const maximum = Math.max(left.length, right.length);
|
|
16771
|
+
let prefix = 0;
|
|
16772
|
+
while(prefix < Math.min(left.length, right.length) && left[prefix] === right[prefix])prefix += 1;
|
|
16773
|
+
let suffix = 0;
|
|
16774
|
+
while(suffix < Math.min(left.length, right.length) - prefix && left[left.length - suffix - 1] === right[right.length - suffix - 1])suffix += 1;
|
|
16775
|
+
const boundaryScore = (prefix + suffix) / maximum;
|
|
16776
|
+
const leftWords = comparisonWords(left);
|
|
16777
|
+
const rightWords = comparisonWords(right);
|
|
16778
|
+
let shared = 0;
|
|
16779
|
+
for (const word of leftWords)if (rightWords.has(word)) shared += 1;
|
|
16780
|
+
const union = new Set([
|
|
16781
|
+
...leftWords,
|
|
16782
|
+
...rightWords
|
|
16783
|
+
]).size;
|
|
16784
|
+
return Math.max(boundaryScore, union ? shared / union : 0);
|
|
16785
|
+
}
|
|
16786
|
+
function comparisonWords(value) {
|
|
16787
|
+
return new Set((value.toLocaleLowerCase().match(/[\p{L}\p{N}_]+/gu) ?? []).slice(0, 256));
|
|
16788
|
+
}
|
|
16789
|
+
function isSimpleTextBlock(node) {
|
|
16790
|
+
if ('paragraph' !== node.type.name && 'heading' !== node.type.name) return false;
|
|
16791
|
+
let simple = true;
|
|
16792
|
+
node.forEach((child)=>{
|
|
16793
|
+
if (!child.isText || child.marks.some((mark)=>REVIEW_ONLY_MARK_NAMES.has(mark.type.name))) simple = false;
|
|
16794
|
+
});
|
|
16795
|
+
return simple;
|
|
16796
|
+
}
|
|
16797
|
+
function simpleDocumentShape(document1) {
|
|
16798
|
+
const sections = documentSections(document1);
|
|
16799
|
+
return Boolean(sections?.every((section)=>childNodes(section).every((block)=>isSimpleTextBlock(block))));
|
|
16800
|
+
}
|
|
16801
|
+
function hasStructuralBlockRevisions(document1) {
|
|
16802
|
+
let found = false;
|
|
16803
|
+
document1.descendants((node)=>{
|
|
16804
|
+
if (('paragraph' === node.type.name || 'heading' === node.type.name) && ('insertion' === node.attrs.blockChangeKind || 'deletion' === node.attrs.blockChangeKind)) found = true;
|
|
16805
|
+
return !found;
|
|
16806
|
+
});
|
|
16807
|
+
return found;
|
|
16808
|
+
}
|
|
16809
|
+
function inlineUnits(node) {
|
|
16810
|
+
if (!isSimpleTextBlock(node)) return null;
|
|
16811
|
+
const units = [];
|
|
16812
|
+
node.forEach((child)=>{
|
|
16813
|
+
const text = child.text ?? '';
|
|
16814
|
+
for (const part of text.match(/\s+|[\p{L}\p{N}_]+|[^\s\p{L}\p{N}_]/gu) ?? [])units.push({
|
|
16815
|
+
text: part,
|
|
16816
|
+
marks: child.marks
|
|
16817
|
+
});
|
|
16818
|
+
});
|
|
16819
|
+
return units;
|
|
16820
|
+
}
|
|
16821
|
+
function marksEqual(left, right) {
|
|
16822
|
+
const normalizedLeft = withoutReviewMarks(left);
|
|
16823
|
+
const normalizedRight = withoutReviewMarks(right);
|
|
16824
|
+
return normalizedLeft.length === normalizedRight.length && normalizedLeft.every((mark, index)=>mark.eq(normalizedRight[index]));
|
|
16825
|
+
}
|
|
16826
|
+
function nonCharacterMarksEqual(left, right) {
|
|
16827
|
+
const normalizedLeft = withoutReviewMarks(left).filter((mark)=>!isDocumentCharacterFormatMark(mark.type.name));
|
|
16828
|
+
const normalizedRight = withoutReviewMarks(right).filter((mark)=>!isDocumentCharacterFormatMark(mark.type.name));
|
|
16829
|
+
return normalizedLeft.length === normalizedRight.length && normalizedLeft.every((mark, index)=>mark.eq(normalizedRight[index]));
|
|
16830
|
+
}
|
|
16831
|
+
function withoutReviewMarks(marks) {
|
|
16832
|
+
return marks.filter((mark)=>!REVIEW_ONLY_MARK_NAMES.has(mark.type.name));
|
|
16833
|
+
}
|
|
16834
|
+
function blockStructuralSignature(node) {
|
|
16835
|
+
const ignored = new Set([
|
|
16836
|
+
...DOCUMENT_PARAGRAPH_FORMAT_ATTRIBUTES,
|
|
16837
|
+
...DOCUMENT_PARAGRAPH_CHANGE_ATTRIBUTES,
|
|
16838
|
+
...BLOCK_CHANGE_ATTRIBUTE_NAMES,
|
|
16839
|
+
...PARAGRAPH_IDENTITY_ATTRIBUTE_NAMES
|
|
16840
|
+
]);
|
|
16841
|
+
return stableJson({
|
|
16842
|
+
attrs: filteredAttributes(node.attrs, ignored),
|
|
16843
|
+
type: node.type.name
|
|
16844
|
+
});
|
|
16845
|
+
}
|
|
16846
|
+
function paragraphFormattingAttributes(attributes) {
|
|
16847
|
+
return Object.fromEntries(DOCUMENT_PARAGRAPH_FORMAT_ATTRIBUTES.map((name)=>[
|
|
16848
|
+
name,
|
|
16849
|
+
attributes[name]
|
|
16850
|
+
]));
|
|
16851
|
+
}
|
|
16852
|
+
function sectionLayoutSignature(section) {
|
|
16853
|
+
return stableJson(filteredAttributes(section.attrs, new Set([
|
|
16854
|
+
'id'
|
|
16855
|
+
])));
|
|
16856
|
+
}
|
|
16857
|
+
function comparisonSemanticSignature(node) {
|
|
16858
|
+
return stableJson(comparisonSemanticValue(node));
|
|
16859
|
+
}
|
|
16860
|
+
function comparisonSemanticValue(node) {
|
|
16861
|
+
const ignored = new Set([
|
|
16862
|
+
...DOCUMENT_PARAGRAPH_CHANGE_ATTRIBUTES,
|
|
16863
|
+
...BLOCK_CHANGE_ATTRIBUTE_NAMES,
|
|
16864
|
+
...PARAGRAPH_IDENTITY_ATTRIBUTE_NAMES,
|
|
16865
|
+
...'documentSection' === node.type.name ? [
|
|
16866
|
+
'id'
|
|
16867
|
+
] : []
|
|
16868
|
+
]);
|
|
16869
|
+
return {
|
|
16870
|
+
type: node.type.name,
|
|
16871
|
+
...node.isText ? {
|
|
16872
|
+
text: node.text
|
|
16873
|
+
} : {},
|
|
16874
|
+
...Object.keys(node.attrs).length ? {
|
|
16875
|
+
attrs: filteredAttributes(node.attrs, ignored)
|
|
16876
|
+
} : {},
|
|
16877
|
+
...node.marks.length ? {
|
|
16878
|
+
marks: withoutReviewMarks(node.marks).map((mark)=>mark.toJSON())
|
|
16879
|
+
} : {},
|
|
16880
|
+
...node.childCount ? {
|
|
16881
|
+
content: childNodes(node).map(comparisonSemanticValue)
|
|
16882
|
+
} : {}
|
|
16883
|
+
};
|
|
16884
|
+
}
|
|
16885
|
+
function transferCombineIdentities(current, reviewed) {
|
|
16886
|
+
if (current.type !== reviewed.type) return null;
|
|
16887
|
+
if ('paragraph' === reviewed.type.name || 'heading' === reviewed.type.name) return reviewed.type.create({
|
|
16888
|
+
...reviewed.attrs,
|
|
16889
|
+
paragraphId: current.attrs.paragraphId,
|
|
16890
|
+
textId: current.attrs.textId
|
|
16891
|
+
}, reviewed.content, reviewed.marks);
|
|
16892
|
+
if (current.childCount !== reviewed.childCount) return null;
|
|
16893
|
+
const content = [];
|
|
16894
|
+
for(let index = 0; index < current.childCount; index += 1){
|
|
16895
|
+
const currentChild = current.child(index);
|
|
16896
|
+
const reviewedChild = reviewed.child(index);
|
|
16897
|
+
const transferred = transferCombineIdentities(currentChild, reviewedChild);
|
|
16898
|
+
if (!transferred) return null;
|
|
16899
|
+
content.push(transferred);
|
|
16900
|
+
}
|
|
16901
|
+
const attributes = {
|
|
16902
|
+
...reviewed.attrs
|
|
16903
|
+
};
|
|
16904
|
+
if ('documentSection' === reviewed.type.name) attributes.id = current.attrs.id;
|
|
16905
|
+
return reviewed.isText ? reviewed : reviewed.type.create(attributes, model_Fragment.fromArray(content), reviewed.marks);
|
|
16906
|
+
}
|
|
16907
|
+
function comparisonIdentityFactory(current, revised, options) {
|
|
16908
|
+
const seed = stableHash(`${options.mode}\u0000${comparisonSemanticSignature(current)}\u0000${comparisonSemanticSignature(revised)}`);
|
|
16909
|
+
const author = boundedMetadata(options.author, 256) || 'A3S Work user';
|
|
16910
|
+
const date = normalizedDate(options.date);
|
|
16911
|
+
const summary = emptyComparisonSummary();
|
|
16912
|
+
let sequence = 0;
|
|
16913
|
+
return {
|
|
16914
|
+
summary,
|
|
16915
|
+
create (kind) {
|
|
16916
|
+
sequence += 1;
|
|
16917
|
+
if ('insertion' === kind) summary.insertions += 1;
|
|
16918
|
+
else if ('deletion' === kind) summary.deletions += 1;
|
|
16919
|
+
else if ('formatting' === kind) summary.formatting += 1;
|
|
16920
|
+
else summary.paragraphFormatting += 1;
|
|
16921
|
+
return {
|
|
16922
|
+
id: `compare-${seed}-${kind}-${sequence.toString(36)}`,
|
|
16923
|
+
author,
|
|
16924
|
+
date
|
|
16925
|
+
};
|
|
16926
|
+
},
|
|
16927
|
+
paragraphIdentity (channel) {
|
|
16928
|
+
return {
|
|
16929
|
+
paragraphId: wordParagraphId(`${seed}:${channel}:paragraph`),
|
|
16930
|
+
textId: wordParagraphId(`${seed}:${channel}:text`)
|
|
16931
|
+
};
|
|
16932
|
+
}
|
|
16933
|
+
};
|
|
16934
|
+
}
|
|
16935
|
+
function summarizeChanges(changes) {
|
|
16936
|
+
const summary = emptyComparisonSummary();
|
|
16937
|
+
for (const change of changes)if ('insertion' === change.kind) summary.insertions += 1;
|
|
16938
|
+
else if ('deletion' === change.kind) summary.deletions += 1;
|
|
16939
|
+
else if ('formatting' === change.kind) summary.formatting += 1;
|
|
16940
|
+
else summary.paragraphFormatting += 1;
|
|
16941
|
+
return summary;
|
|
16942
|
+
}
|
|
16943
|
+
function structuralBlockDiagnostic(block, section, blockIndex) {
|
|
16944
|
+
return 'paragraph' === block.type.name || 'heading' === block.type.name ? {
|
|
16945
|
+
code: 'empty-structural-change',
|
|
16946
|
+
message: 'An empty inserted or deleted paragraph cannot carry a reviewable text revision.',
|
|
16947
|
+
section,
|
|
16948
|
+
block: blockIndex
|
|
16949
|
+
} : {
|
|
16950
|
+
code: 'changed-complex-structure',
|
|
16951
|
+
message: `Changed ${block.type.name} content is not approximated as plain text.`,
|
|
16952
|
+
section,
|
|
16953
|
+
block: blockIndex
|
|
16954
|
+
};
|
|
16955
|
+
}
|
|
16956
|
+
function unsupportedResult(diagnostic) {
|
|
16957
|
+
return {
|
|
16958
|
+
status: 'unsupported',
|
|
16959
|
+
summary: emptyComparisonSummary(),
|
|
16960
|
+
diagnostics: [
|
|
16961
|
+
diagnostic
|
|
16962
|
+
]
|
|
16963
|
+
};
|
|
16964
|
+
}
|
|
16965
|
+
function emptyComparisonSummary() {
|
|
16966
|
+
return {
|
|
16967
|
+
deletions: 0,
|
|
16968
|
+
formatting: 0,
|
|
16969
|
+
insertions: 0,
|
|
16970
|
+
paragraphFormatting: 0
|
|
16971
|
+
};
|
|
16972
|
+
}
|
|
16973
|
+
function childNodes(node) {
|
|
16974
|
+
const children = [];
|
|
16975
|
+
node.forEach((child)=>{
|
|
16976
|
+
children.push(child);
|
|
16977
|
+
});
|
|
16978
|
+
return children;
|
|
16979
|
+
}
|
|
16980
|
+
function filteredAttributes(attributes, ignored) {
|
|
16981
|
+
return Object.fromEntries(Object.entries(attributes).filter(([name])=>!ignored.has(name)));
|
|
16982
|
+
}
|
|
16983
|
+
function DocumentCompareDialog({ initialMode, restoreFocusTarget, onApplied, onClose, onSubmit }) {
|
|
16984
|
+
const [mode, setMode] = useState(initialMode);
|
|
16985
|
+
const [file, setFile] = useState(null);
|
|
16986
|
+
const [author, setAuthor] = useState('审阅者');
|
|
16987
|
+
const [authorTouched, setAuthorTouched] = useState(false);
|
|
16988
|
+
const [busy, setBusy] = useState(false);
|
|
16989
|
+
const [result, setResult] = useState(null);
|
|
16990
|
+
const fileInputRef = useRef(null);
|
|
16991
|
+
const formId = useId();
|
|
16992
|
+
const authorId = useId();
|
|
16993
|
+
const submit = async (event)=>{
|
|
16994
|
+
event.preventDefault();
|
|
16995
|
+
if (!file || busy || 'compare' === mode && !author.trim()) return;
|
|
16996
|
+
setBusy(true);
|
|
16997
|
+
setResult(null);
|
|
16998
|
+
try {
|
|
16999
|
+
const next = await onSubmit({
|
|
17000
|
+
author: author.trim(),
|
|
17001
|
+
file,
|
|
17002
|
+
mode
|
|
17003
|
+
});
|
|
17004
|
+
setResult(next);
|
|
17005
|
+
if ('applied' === next.status) onApplied(next);
|
|
17006
|
+
} catch {
|
|
17007
|
+
setResult({
|
|
17008
|
+
status: 'unsupported',
|
|
17009
|
+
summary: {
|
|
17010
|
+
deletions: 0,
|
|
17011
|
+
formatting: 0,
|
|
17012
|
+
insertions: 0,
|
|
17013
|
+
paragraphFormatting: 0
|
|
17014
|
+
},
|
|
17015
|
+
diagnostics: [
|
|
17016
|
+
{
|
|
17017
|
+
code: 'invalid-revised-content',
|
|
17018
|
+
message: 'The selected file could not be imported.'
|
|
17019
|
+
}
|
|
17020
|
+
]
|
|
17021
|
+
});
|
|
17022
|
+
} finally{
|
|
17023
|
+
setBusy(false);
|
|
17024
|
+
}
|
|
17025
|
+
};
|
|
17026
|
+
const selectFile = (next)=>{
|
|
17027
|
+
setFile(next);
|
|
17028
|
+
setResult(null);
|
|
17029
|
+
if (!authorTouched) setAuthor(fileStem(next.name) || '审阅者');
|
|
17030
|
+
};
|
|
17031
|
+
return /*#__PURE__*/ jsx(Dialog, {
|
|
17032
|
+
title: "比较与合并文档",
|
|
17033
|
+
description: "把另一版本转换为可逐项接受或拒绝的 Writer 修订。",
|
|
17034
|
+
className: "work-document-compare-dialog",
|
|
17035
|
+
closeDisabled: busy,
|
|
17036
|
+
restoreFocusTarget: restoreFocusTarget,
|
|
17037
|
+
onClose: onClose,
|
|
17038
|
+
footer: /*#__PURE__*/ jsxs(Fragment, {
|
|
17039
|
+
children: [
|
|
17040
|
+
/*#__PURE__*/ jsx(button_Button, {
|
|
17041
|
+
tone: "quiet",
|
|
17042
|
+
disabled: busy,
|
|
17043
|
+
onClick: onClose,
|
|
17044
|
+
children: "取消"
|
|
17045
|
+
}),
|
|
17046
|
+
/*#__PURE__*/ jsx(button_Button, {
|
|
17047
|
+
tone: "primary",
|
|
17048
|
+
type: "submit",
|
|
17049
|
+
form: formId,
|
|
17050
|
+
disabled: !file || busy || 'compare' === mode && !author.trim(),
|
|
17051
|
+
children: busy ? '正在处理…' : 'compare' === mode ? '生成比较结果' : '合并修订'
|
|
17052
|
+
})
|
|
17053
|
+
]
|
|
17054
|
+
}),
|
|
17055
|
+
children: /*#__PURE__*/ jsxs("form", {
|
|
17056
|
+
id: formId,
|
|
17057
|
+
onSubmit: submit,
|
|
17058
|
+
children: [
|
|
17059
|
+
/*#__PURE__*/ jsxs("fieldset", {
|
|
17060
|
+
className: "work-document-compare-mode",
|
|
17061
|
+
children: [
|
|
17062
|
+
/*#__PURE__*/ jsx("legend", {
|
|
17063
|
+
children: "处理方式"
|
|
17064
|
+
}),
|
|
17065
|
+
/*#__PURE__*/ jsxs("label", {
|
|
17066
|
+
children: [
|
|
17067
|
+
/*#__PURE__*/ jsx("input", {
|
|
17068
|
+
type: "radio",
|
|
17069
|
+
name: "document-comparison-mode",
|
|
17070
|
+
value: "compare",
|
|
17071
|
+
checked: 'compare' === mode,
|
|
17072
|
+
onChange: ()=>{
|
|
17073
|
+
setMode('compare');
|
|
17074
|
+
setResult(null);
|
|
17075
|
+
}
|
|
17076
|
+
}),
|
|
17077
|
+
/*#__PURE__*/ jsxs("span", {
|
|
17078
|
+
children: [
|
|
17079
|
+
/*#__PURE__*/ jsx(GitCompareArrows, {
|
|
17080
|
+
size: 18,
|
|
17081
|
+
"aria-hidden": "true"
|
|
17082
|
+
}),
|
|
17083
|
+
/*#__PURE__*/ jsx("strong", {
|
|
17084
|
+
children: "比较"
|
|
17085
|
+
}),
|
|
17086
|
+
/*#__PURE__*/ jsx("small", {
|
|
17087
|
+
children: "当前文档作为原稿,导入文件作为修订稿。"
|
|
17088
|
+
})
|
|
17089
|
+
]
|
|
17090
|
+
})
|
|
17091
|
+
]
|
|
17092
|
+
}),
|
|
17093
|
+
/*#__PURE__*/ jsxs("label", {
|
|
17094
|
+
children: [
|
|
17095
|
+
/*#__PURE__*/ jsx("input", {
|
|
17096
|
+
type: "radio",
|
|
17097
|
+
name: "document-comparison-mode",
|
|
17098
|
+
value: "combine",
|
|
17099
|
+
checked: 'combine' === mode,
|
|
17100
|
+
onChange: ()=>{
|
|
17101
|
+
setMode('combine');
|
|
17102
|
+
setResult(null);
|
|
17103
|
+
}
|
|
17104
|
+
}),
|
|
17105
|
+
/*#__PURE__*/ jsxs("span", {
|
|
17106
|
+
children: [
|
|
17107
|
+
/*#__PURE__*/ jsx(FileCheck2, {
|
|
17108
|
+
size: 18,
|
|
17109
|
+
"aria-hidden": "true"
|
|
17110
|
+
}),
|
|
17111
|
+
/*#__PURE__*/ jsx("strong", {
|
|
17112
|
+
children: "合并"
|
|
17113
|
+
}),
|
|
17114
|
+
/*#__PURE__*/ jsx("small", {
|
|
17115
|
+
children: "导入带修订的审阅副本,并核验其原始基线。"
|
|
17116
|
+
})
|
|
17117
|
+
]
|
|
17118
|
+
})
|
|
17119
|
+
]
|
|
17120
|
+
})
|
|
17121
|
+
]
|
|
17122
|
+
}),
|
|
17123
|
+
/*#__PURE__*/ jsx(office_text_field_OfficeFileInput, {
|
|
17124
|
+
ref: fileInputRef,
|
|
17125
|
+
accept: ".docx,.html,.htm,.txt,application/vnd.openxmlformats-officedocument.wordprocessingml.document,text/html,text/plain",
|
|
17126
|
+
"aria-label": 'compare' === mode ? '选择修订版本文件' : '选择审阅副本文件',
|
|
17127
|
+
disabled: busy,
|
|
17128
|
+
onFileSelect: selectFile
|
|
17129
|
+
}),
|
|
17130
|
+
/*#__PURE__*/ jsxs("button", {
|
|
17131
|
+
type: "button",
|
|
17132
|
+
className: "work-document-compare-file",
|
|
17133
|
+
"data-autofocus": "true",
|
|
17134
|
+
disabled: busy,
|
|
17135
|
+
onClick: ()=>fileInputRef.current?.click(),
|
|
17136
|
+
children: [
|
|
17137
|
+
/*#__PURE__*/ jsx(FileUp, {
|
|
17138
|
+
size: 20,
|
|
17139
|
+
"aria-hidden": "true"
|
|
17140
|
+
}),
|
|
17141
|
+
/*#__PURE__*/ jsxs("span", {
|
|
17142
|
+
children: [
|
|
17143
|
+
/*#__PURE__*/ jsx("strong", {
|
|
17144
|
+
children: file?.name ?? ('compare' === mode ? '选择修订版本' : '选择带修订的审阅副本')
|
|
17145
|
+
}),
|
|
17146
|
+
/*#__PURE__*/ jsx("small", {
|
|
17147
|
+
children: file ? `${fileTypeLabel(file.name)} · ${formatFileSize(file.size)}` : '支持 DOCX、HTML 和 TXT;当前文档不会在预检失败时改变。'
|
|
17148
|
+
})
|
|
17149
|
+
]
|
|
17150
|
+
})
|
|
17151
|
+
]
|
|
17152
|
+
}),
|
|
17153
|
+
'compare' === mode && /*#__PURE__*/ jsxs("label", {
|
|
17154
|
+
className: "work-document-compare-author",
|
|
17155
|
+
htmlFor: authorId,
|
|
17156
|
+
children: [
|
|
17157
|
+
/*#__PURE__*/ jsx("span", {
|
|
17158
|
+
children: "修订者名称"
|
|
17159
|
+
}),
|
|
17160
|
+
/*#__PURE__*/ jsx(office_text_field_OfficeTextField, {
|
|
17161
|
+
id: authorId,
|
|
17162
|
+
"aria-label": "比较结果修订者名称",
|
|
17163
|
+
value: author,
|
|
17164
|
+
disabled: busy,
|
|
17165
|
+
maxLength: 256,
|
|
17166
|
+
onChange: (event)=>{
|
|
17167
|
+
setAuthorTouched(true);
|
|
17168
|
+
setAuthor(event.currentTarget.value);
|
|
17169
|
+
}
|
|
17170
|
+
}),
|
|
17171
|
+
/*#__PURE__*/ jsx("small", {
|
|
17172
|
+
children: "该名称会显示在生成的插入、删除与格式修订中。"
|
|
17173
|
+
})
|
|
17174
|
+
]
|
|
17175
|
+
}),
|
|
17176
|
+
/*#__PURE__*/ jsx(ComparisonBoundary, {
|
|
17177
|
+
mode: mode
|
|
17178
|
+
}),
|
|
17179
|
+
result?.status === 'unchanged' && /*#__PURE__*/ jsx("p", {
|
|
17180
|
+
className: "work-document-compare-status",
|
|
17181
|
+
role: "status",
|
|
17182
|
+
children: "两份文档在支持的比较范围内完全一致,没有生成修订。"
|
|
17183
|
+
}),
|
|
17184
|
+
result?.status === 'unsupported' && /*#__PURE__*/ jsxs("div", {
|
|
17185
|
+
className: "work-document-compare-errors",
|
|
17186
|
+
role: "alert",
|
|
17187
|
+
children: [
|
|
17188
|
+
/*#__PURE__*/ jsx("strong", {
|
|
17189
|
+
children: "无法安全处理这份文档"
|
|
17190
|
+
}),
|
|
17191
|
+
/*#__PURE__*/ jsx("ul", {
|
|
17192
|
+
children: result.diagnostics.map((diagnostic, index)=>/*#__PURE__*/ jsx("li", {
|
|
17193
|
+
children: comparisonDiagnosticText(diagnostic)
|
|
17194
|
+
}, `${diagnostic.code}-${index}`))
|
|
17195
|
+
})
|
|
17196
|
+
]
|
|
17197
|
+
})
|
|
17198
|
+
]
|
|
17199
|
+
})
|
|
17200
|
+
});
|
|
17201
|
+
}
|
|
17202
|
+
function ComparisonBoundary({ mode }) {
|
|
17203
|
+
return /*#__PURE__*/ jsxs("div", {
|
|
17204
|
+
className: "work-document-compare-boundary",
|
|
17205
|
+
children: [
|
|
17206
|
+
/*#__PURE__*/ jsx("strong", {
|
|
17207
|
+
children: 'compare' === mode ? '确定性比较边界' : '安全合并边界'
|
|
17208
|
+
}),
|
|
17209
|
+
/*#__PURE__*/ jsx("p", {
|
|
17210
|
+
children: 'compare' === mode ? '支持同一分节布局中的段落、标题、文字和格式差异;复杂对象或节布局变化会明确停止。' : '审阅副本必须包含修订,且拒绝全部修订后与当前文档一致;现有修订须先处理。'
|
|
17211
|
+
})
|
|
17212
|
+
]
|
|
17213
|
+
});
|
|
17214
|
+
}
|
|
17215
|
+
function comparisonDiagnosticText(diagnostic) {
|
|
17216
|
+
const location = void 0 === diagnostic.section ? '' : `(第 ${diagnostic.section + 1} 节${void 0 === diagnostic.block ? '' : `,差异块 ${diagnostic.block + 1}`})`;
|
|
17217
|
+
const messages = {
|
|
17218
|
+
'changed-complex-structure': '检测到表格、图片、列表或其他复杂结构变化,未降级为纯文字。',
|
|
17219
|
+
'combine-baseline-mismatch': '审阅副本的原始基线与当前文档不一致。',
|
|
17220
|
+
'combine-resolution-invalid': '审阅副本包含损坏或无法拒绝的格式修订。',
|
|
17221
|
+
'combine-structural-revisions': '审阅副本改变了段落树;当前合并路径只接受行内和格式修订。',
|
|
17222
|
+
'combine-without-revisions': '审阅副本中没有可合并的修订。',
|
|
17223
|
+
'comparison-limit-exceeded': '文档超过本地有界比较限制。',
|
|
17224
|
+
'current-revisions-present': '当前文档仍有未处理修订,请先接受或拒绝。',
|
|
17225
|
+
'empty-structural-change': '空段落的结构变化无法承载可审阅文字修订。',
|
|
17226
|
+
'invalid-revised-content': '文件无法转换为当前 Writer 文档模型。',
|
|
17227
|
+
'revised-revisions-present': '修订稿已经包含修订,请改用“合并”。',
|
|
17228
|
+
'section-layout-mismatch': '分节数量或页面布局不一致。',
|
|
17229
|
+
'unsupported-inline-review-state': '导入内容包含无法安全迁移的批注或审阅状态。'
|
|
17230
|
+
};
|
|
17231
|
+
return `${messages[diagnostic.code]}${location}`;
|
|
17232
|
+
}
|
|
17233
|
+
function fileStem(name) {
|
|
17234
|
+
return name.replace(/\.[^.]+$/, '').trim();
|
|
17235
|
+
}
|
|
17236
|
+
function fileTypeLabel(name) {
|
|
17237
|
+
return name.split('.').at(-1)?.toLocaleUpperCase() || '文件';
|
|
17238
|
+
}
|
|
17239
|
+
function formatFileSize(bytes) {
|
|
17240
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
17241
|
+
if (bytes < 1048576) return `${Math.max(0.1, bytes / 1024).toFixed(1)} KB`;
|
|
17242
|
+
return `${Math.max(0.1, bytes / 1048576).toFixed(1)} MB`;
|
|
17243
|
+
}
|
|
17244
|
+
function useDocumentComparison({ editor, onApplied }) {
|
|
17245
|
+
const [dialog, setDialog] = useState(null);
|
|
17246
|
+
const editorRef = useRef(editor);
|
|
17247
|
+
editorRef.current = editor;
|
|
17248
|
+
const open = (mode)=>{
|
|
17249
|
+
const current = editorRef.current;
|
|
17250
|
+
if (!current || current.isDestroyed) return;
|
|
17251
|
+
const active = document.activeElement;
|
|
17252
|
+
setDialog({
|
|
17253
|
+
mode,
|
|
17254
|
+
restoreFocusTarget: active instanceof HTMLElement && active.isConnected ? active : current.view.dom
|
|
17255
|
+
});
|
|
17256
|
+
};
|
|
17257
|
+
const close = ()=>setDialog(null);
|
|
17258
|
+
const submit = async (request)=>{
|
|
17259
|
+
const current = editorRef.current;
|
|
17260
|
+
if (!current || current.isDestroyed) return invalidComparisonResult();
|
|
17261
|
+
const extension = comparisonFileExtension(request.file.name);
|
|
17262
|
+
if (!extension) return invalidComparisonResult();
|
|
17263
|
+
const artifact = await importWorkDocumentFile(request.file, extension);
|
|
17264
|
+
try {
|
|
17265
|
+
if ('document' !== artifact.content.type) return invalidComparisonResult();
|
|
17266
|
+
const content = artifact.content;
|
|
17267
|
+
const model = documentModelForContent(content);
|
|
17268
|
+
const fallbackHtml = model ? content.html : normalizeDocumentHtml(content);
|
|
17269
|
+
const input = resolveWorkDocumentEditorInput(content, fallbackHtml, model);
|
|
17270
|
+
return applyDocumentComparison(current, input.source, {
|
|
17271
|
+
author: request.author,
|
|
17272
|
+
date: new Date().toISOString(),
|
|
17273
|
+
mode: request.mode,
|
|
17274
|
+
sourceName: request.file.name
|
|
17275
|
+
});
|
|
17276
|
+
} finally{
|
|
17277
|
+
forgetWorkSourceBlob(artifact.id);
|
|
17278
|
+
}
|
|
17279
|
+
};
|
|
17280
|
+
const applied = (result)=>{
|
|
17281
|
+
const count = result.summary.insertions + result.summary.deletions + result.summary.formatting + result.summary.paragraphFormatting;
|
|
17282
|
+
close();
|
|
17283
|
+
onApplied();
|
|
17284
|
+
showToast(`已生成 ${count} 项可审阅修订`, 'success');
|
|
17285
|
+
};
|
|
17286
|
+
return {
|
|
17287
|
+
dialog: dialog ? /*#__PURE__*/ jsx(DocumentCompareDialog, {
|
|
17288
|
+
initialMode: dialog.mode,
|
|
17289
|
+
restoreFocusTarget: ()=>dialog.restoreFocusTarget?.isConnected ? dialog.restoreFocusTarget : editorRef.current?.view.dom ?? null,
|
|
17290
|
+
onApplied: applied,
|
|
17291
|
+
onClose: close,
|
|
17292
|
+
onSubmit: submit
|
|
17293
|
+
}) : null,
|
|
17294
|
+
open
|
|
17295
|
+
};
|
|
17296
|
+
}
|
|
17297
|
+
function comparisonFileExtension(name) {
|
|
17298
|
+
const extension = name.split('.').at(-1)?.toLocaleLowerCase() ?? '';
|
|
17299
|
+
return 'docx' === extension || 'html' === extension || 'htm' === extension || 'txt' === extension ? extension : null;
|
|
17300
|
+
}
|
|
17301
|
+
function invalidComparisonResult() {
|
|
17302
|
+
return {
|
|
17303
|
+
status: 'unsupported',
|
|
17304
|
+
summary: {
|
|
17305
|
+
deletions: 0,
|
|
17306
|
+
formatting: 0,
|
|
17307
|
+
insertions: 0,
|
|
17308
|
+
paragraphFormatting: 0
|
|
17309
|
+
},
|
|
17310
|
+
diagnostics: [
|
|
17311
|
+
{
|
|
17312
|
+
code: 'invalid-revised-content',
|
|
17313
|
+
message: 'The selected file cannot be imported as a Writer document.'
|
|
17314
|
+
}
|
|
17315
|
+
]
|
|
17316
|
+
};
|
|
17317
|
+
}
|
|
17318
|
+
function DocumentTableOfContentsDialog({ editing, options, restoreFocusTarget, onCancel, onOptionsChange, onSubmit }) {
|
|
17319
|
+
const updateOptions = (update)=>onOptionsChange({
|
|
17320
|
+
...options,
|
|
17321
|
+
...update
|
|
17322
|
+
});
|
|
17323
|
+
return /*#__PURE__*/ jsxs(Dialog, {
|
|
17324
|
+
title: editing ? '自定义目录' : '插入目录',
|
|
17325
|
+
description: "从文档标题生成可更新目录,并保留原生 DOCX 目录域。",
|
|
17326
|
+
className: "work-document-table-of-contents-dialog",
|
|
17327
|
+
restoreFocusTarget: restoreFocusTarget,
|
|
17328
|
+
onClose: onCancel,
|
|
17329
|
+
footer: /*#__PURE__*/ jsxs(Fragment, {
|
|
17330
|
+
children: [
|
|
17331
|
+
/*#__PURE__*/ jsx(button_Button, {
|
|
17332
|
+
tone: "quiet",
|
|
17333
|
+
onClick: onCancel,
|
|
17334
|
+
children: "取消"
|
|
17335
|
+
}),
|
|
17336
|
+
/*#__PURE__*/ jsx(button_Button, {
|
|
17337
|
+
tone: "primary",
|
|
17338
|
+
onClick: onSubmit,
|
|
17339
|
+
children: editing ? '应用' : '插入目录'
|
|
17340
|
+
})
|
|
17341
|
+
]
|
|
17342
|
+
}),
|
|
17343
|
+
children: [
|
|
17344
|
+
/*#__PURE__*/ jsxs("div", {
|
|
17345
|
+
className: "work-document-table-of-contents-dialog-grid",
|
|
17346
|
+
children: [
|
|
17347
|
+
/*#__PURE__*/ jsxs("div", {
|
|
17348
|
+
className: "work-document-table-of-contents-dialog-field",
|
|
17349
|
+
children: [
|
|
17350
|
+
/*#__PURE__*/ jsx("span", {
|
|
17351
|
+
children: "起始标题级别"
|
|
17352
|
+
}),
|
|
17353
|
+
/*#__PURE__*/ jsx(OfficeSelect, {
|
|
17354
|
+
initialFocus: true,
|
|
17355
|
+
ariaLabel: "起始标题级别",
|
|
17356
|
+
value: String(options.minLevel),
|
|
17357
|
+
options: tableOfContentsLevelOptions,
|
|
17358
|
+
onValueChange: (value)=>{
|
|
17359
|
+
const minLevel = Number(value);
|
|
17360
|
+
updateOptions({
|
|
17361
|
+
minLevel,
|
|
17362
|
+
maxLevel: Math.max(minLevel, options.maxLevel)
|
|
17363
|
+
});
|
|
17364
|
+
}
|
|
17365
|
+
})
|
|
17366
|
+
]
|
|
17367
|
+
}),
|
|
17368
|
+
/*#__PURE__*/ jsxs("div", {
|
|
17369
|
+
className: "work-document-table-of-contents-dialog-field",
|
|
17370
|
+
children: [
|
|
16202
17371
|
/*#__PURE__*/ jsx("span", {
|
|
16203
17372
|
children: "结束标题级别"
|
|
16204
17373
|
}),
|
|
@@ -16322,6 +17491,320 @@ const tableOfContentsLeaderOptions = [
|
|
|
16322
17491
|
label: '无'
|
|
16323
17492
|
}
|
|
16324
17493
|
];
|
|
17494
|
+
function DocumentIndexDialog({ editing, options, restoreFocusTarget, onCancel, onOptionsChange, onSubmit }) {
|
|
17495
|
+
const update = (patch)=>onOptionsChange({
|
|
17496
|
+
...options,
|
|
17497
|
+
...patch
|
|
17498
|
+
});
|
|
17499
|
+
return /*#__PURE__*/ jsxs(Dialog, {
|
|
17500
|
+
title: editing ? '自定义索引' : '插入索引',
|
|
17501
|
+
description: "从已标记的索引项生成可更新索引,并保留原生 DOCX INDEX 域。",
|
|
17502
|
+
className: "work-document-index-dialog",
|
|
17503
|
+
restoreFocusTarget: restoreFocusTarget,
|
|
17504
|
+
onClose: onCancel,
|
|
17505
|
+
footer: /*#__PURE__*/ jsxs(Fragment, {
|
|
17506
|
+
children: [
|
|
17507
|
+
/*#__PURE__*/ jsx(button_Button, {
|
|
17508
|
+
tone: "quiet",
|
|
17509
|
+
onClick: onCancel,
|
|
17510
|
+
children: "取消"
|
|
17511
|
+
}),
|
|
17512
|
+
/*#__PURE__*/ jsx(button_Button, {
|
|
17513
|
+
tone: "primary",
|
|
17514
|
+
onClick: onSubmit,
|
|
17515
|
+
children: editing ? '应用' : '插入索引'
|
|
17516
|
+
})
|
|
17517
|
+
]
|
|
17518
|
+
}),
|
|
17519
|
+
children: [
|
|
17520
|
+
/*#__PURE__*/ jsxs("div", {
|
|
17521
|
+
className: "work-document-index-dialog-grid",
|
|
17522
|
+
children: [
|
|
17523
|
+
/*#__PURE__*/ jsxs("div", {
|
|
17524
|
+
className: "work-document-index-dialog-field",
|
|
17525
|
+
children: [
|
|
17526
|
+
/*#__PURE__*/ jsx("span", {
|
|
17527
|
+
children: "栏数"
|
|
17528
|
+
}),
|
|
17529
|
+
/*#__PURE__*/ jsx(OfficeSelect, {
|
|
17530
|
+
initialFocus: true,
|
|
17531
|
+
ariaLabel: "索引栏数",
|
|
17532
|
+
value: String(options.columns),
|
|
17533
|
+
options: indexColumnOptions,
|
|
17534
|
+
onValueChange: (columns)=>update({
|
|
17535
|
+
columns: Number(columns)
|
|
17536
|
+
})
|
|
17537
|
+
})
|
|
17538
|
+
]
|
|
17539
|
+
}),
|
|
17540
|
+
/*#__PURE__*/ jsxs("div", {
|
|
17541
|
+
className: "work-document-index-dialog-field",
|
|
17542
|
+
children: [
|
|
17543
|
+
/*#__PURE__*/ jsx("span", {
|
|
17544
|
+
children: "次索引项布局"
|
|
17545
|
+
}),
|
|
17546
|
+
/*#__PURE__*/ jsx(OfficeSelect, {
|
|
17547
|
+
ariaLabel: "索引布局",
|
|
17548
|
+
value: options.format,
|
|
17549
|
+
options: indexFormatOptions,
|
|
17550
|
+
onValueChange: (format)=>update({
|
|
17551
|
+
format
|
|
17552
|
+
})
|
|
17553
|
+
})
|
|
17554
|
+
]
|
|
17555
|
+
})
|
|
17556
|
+
]
|
|
17557
|
+
}),
|
|
17558
|
+
/*#__PURE__*/ jsxs("div", {
|
|
17559
|
+
className: "work-document-index-dialog-options",
|
|
17560
|
+
children: [
|
|
17561
|
+
/*#__PURE__*/ jsx(OfficeCheckbox, {
|
|
17562
|
+
ariaLabel: "页码右对齐",
|
|
17563
|
+
checked: options.rightAlignPageNumbers,
|
|
17564
|
+
onCheckedChange: (rightAlignPageNumbers)=>update({
|
|
17565
|
+
rightAlignPageNumbers
|
|
17566
|
+
}),
|
|
17567
|
+
children: "页码右对齐"
|
|
17568
|
+
}),
|
|
17569
|
+
/*#__PURE__*/ jsxs("div", {
|
|
17570
|
+
className: "work-document-index-dialog-field",
|
|
17571
|
+
children: [
|
|
17572
|
+
/*#__PURE__*/ jsx("span", {
|
|
17573
|
+
children: "前导符"
|
|
17574
|
+
}),
|
|
17575
|
+
/*#__PURE__*/ jsx(OfficeSelect, {
|
|
17576
|
+
ariaLabel: "索引前导符",
|
|
17577
|
+
value: options.leader,
|
|
17578
|
+
options: indexLeaderOptions,
|
|
17579
|
+
disabled: !options.rightAlignPageNumbers,
|
|
17580
|
+
onValueChange: (leader)=>update({
|
|
17581
|
+
leader
|
|
17582
|
+
})
|
|
17583
|
+
})
|
|
17584
|
+
]
|
|
17585
|
+
})
|
|
17586
|
+
]
|
|
17587
|
+
}),
|
|
17588
|
+
/*#__PURE__*/ jsxs("fieldset", {
|
|
17589
|
+
className: "work-document-index-dialog-preview",
|
|
17590
|
+
"aria-label": "索引预览",
|
|
17591
|
+
"data-index-format": options.format,
|
|
17592
|
+
"data-index-leader": options.leader,
|
|
17593
|
+
"data-index-right-align-page-numbers": String(options.rightAlignPageNumbers),
|
|
17594
|
+
children: [
|
|
17595
|
+
/*#__PURE__*/ jsx("legend", {
|
|
17596
|
+
children: "索引"
|
|
17597
|
+
}),
|
|
17598
|
+
/*#__PURE__*/ jsxs("span", {
|
|
17599
|
+
children: [
|
|
17600
|
+
"Architecture ",
|
|
17601
|
+
/*#__PURE__*/ jsx("i", {
|
|
17602
|
+
"aria-hidden": "true"
|
|
17603
|
+
}),
|
|
17604
|
+
" 1, 3"
|
|
17605
|
+
]
|
|
17606
|
+
}),
|
|
17607
|
+
/*#__PURE__*/ jsxs("span", {
|
|
17608
|
+
className: "nested",
|
|
17609
|
+
children: [
|
|
17610
|
+
"Runtime ",
|
|
17611
|
+
/*#__PURE__*/ jsx("i", {
|
|
17612
|
+
"aria-hidden": "true"
|
|
17613
|
+
}),
|
|
17614
|
+
" 5"
|
|
17615
|
+
]
|
|
17616
|
+
})
|
|
17617
|
+
]
|
|
17618
|
+
})
|
|
17619
|
+
]
|
|
17620
|
+
});
|
|
17621
|
+
}
|
|
17622
|
+
const indexColumnOptions = [
|
|
17623
|
+
{
|
|
17624
|
+
value: '1',
|
|
17625
|
+
label: '一栏'
|
|
17626
|
+
},
|
|
17627
|
+
{
|
|
17628
|
+
value: '2',
|
|
17629
|
+
label: '两栏'
|
|
17630
|
+
},
|
|
17631
|
+
{
|
|
17632
|
+
value: '3',
|
|
17633
|
+
label: '三栏'
|
|
17634
|
+
},
|
|
17635
|
+
{
|
|
17636
|
+
value: '4',
|
|
17637
|
+
label: '四栏'
|
|
17638
|
+
}
|
|
17639
|
+
];
|
|
17640
|
+
const indexFormatOptions = [
|
|
17641
|
+
{
|
|
17642
|
+
value: 'indented',
|
|
17643
|
+
label: '缩进式'
|
|
17644
|
+
},
|
|
17645
|
+
{
|
|
17646
|
+
value: 'run-in',
|
|
17647
|
+
label: '连续式'
|
|
17648
|
+
}
|
|
17649
|
+
];
|
|
17650
|
+
const indexLeaderOptions = [
|
|
17651
|
+
{
|
|
17652
|
+
value: 'dot',
|
|
17653
|
+
label: '点线(……)'
|
|
17654
|
+
},
|
|
17655
|
+
{
|
|
17656
|
+
value: 'dash',
|
|
17657
|
+
label: '短横线(----)'
|
|
17658
|
+
},
|
|
17659
|
+
{
|
|
17660
|
+
value: 'underline',
|
|
17661
|
+
label: '下划线(____)'
|
|
17662
|
+
},
|
|
17663
|
+
{
|
|
17664
|
+
value: 'none',
|
|
17665
|
+
label: '无'
|
|
17666
|
+
}
|
|
17667
|
+
];
|
|
17668
|
+
function DocumentIndexEntryDialog({ editing, value, restoreFocusTarget, onCancel, onChange, onSubmit }) {
|
|
17669
|
+
const update = (patch)=>onChange({
|
|
17670
|
+
...value,
|
|
17671
|
+
...patch
|
|
17672
|
+
});
|
|
17673
|
+
const crossReferenceEnabled = Boolean(value.crossReference);
|
|
17674
|
+
const mainEntryId = useId();
|
|
17675
|
+
const subEntryId = useId();
|
|
17676
|
+
const crossReferenceId = useId();
|
|
17677
|
+
return /*#__PURE__*/ jsxs(Dialog, {
|
|
17678
|
+
title: editing ? '编辑索引项' : '标记索引项',
|
|
17679
|
+
description: "标记主索引项、次索引项或交叉引用,并保留原生 DOCX XE 域。",
|
|
17680
|
+
className: "work-document-index-entry-dialog",
|
|
17681
|
+
restoreFocusTarget: restoreFocusTarget,
|
|
17682
|
+
onClose: onCancel,
|
|
17683
|
+
footer: /*#__PURE__*/ jsxs(Fragment, {
|
|
17684
|
+
children: [
|
|
17685
|
+
/*#__PURE__*/ jsx(button_Button, {
|
|
17686
|
+
tone: "quiet",
|
|
17687
|
+
onClick: onCancel,
|
|
17688
|
+
children: "取消"
|
|
17689
|
+
}),
|
|
17690
|
+
/*#__PURE__*/ jsx(button_Button, {
|
|
17691
|
+
tone: "primary",
|
|
17692
|
+
disabled: !value.mainEntry.trim(),
|
|
17693
|
+
onClick: onSubmit,
|
|
17694
|
+
children: editing ? '应用' : '标记'
|
|
17695
|
+
})
|
|
17696
|
+
]
|
|
17697
|
+
}),
|
|
17698
|
+
children: [
|
|
17699
|
+
/*#__PURE__*/ jsxs("div", {
|
|
17700
|
+
className: "work-document-index-entry-dialog-fields",
|
|
17701
|
+
children: [
|
|
17702
|
+
/*#__PURE__*/ jsxs("label", {
|
|
17703
|
+
htmlFor: mainEntryId,
|
|
17704
|
+
children: [
|
|
17705
|
+
/*#__PURE__*/ jsx("span", {
|
|
17706
|
+
children: "主索引项"
|
|
17707
|
+
}),
|
|
17708
|
+
/*#__PURE__*/ jsx(office_text_field_OfficeTextField, {
|
|
17709
|
+
id: mainEntryId,
|
|
17710
|
+
"data-autofocus": true,
|
|
17711
|
+
"aria-label": "主索引项",
|
|
17712
|
+
value: value.mainEntry,
|
|
17713
|
+
maxLength: 240,
|
|
17714
|
+
onChange: (event)=>update({
|
|
17715
|
+
mainEntry: event.target.value
|
|
17716
|
+
}),
|
|
17717
|
+
onKeyDown: (event)=>{
|
|
17718
|
+
if ('Enter' !== event.key || !value.mainEntry.trim()) return;
|
|
17719
|
+
event.preventDefault();
|
|
17720
|
+
onSubmit();
|
|
17721
|
+
}
|
|
17722
|
+
})
|
|
17723
|
+
]
|
|
17724
|
+
}),
|
|
17725
|
+
/*#__PURE__*/ jsxs("label", {
|
|
17726
|
+
htmlFor: subEntryId,
|
|
17727
|
+
children: [
|
|
17728
|
+
/*#__PURE__*/ jsx("span", {
|
|
17729
|
+
children: "次索引项"
|
|
17730
|
+
}),
|
|
17731
|
+
/*#__PURE__*/ jsx(office_text_field_OfficeTextField, {
|
|
17732
|
+
id: subEntryId,
|
|
17733
|
+
"aria-label": "次索引项",
|
|
17734
|
+
value: value.subEntry,
|
|
17735
|
+
maxLength: 240,
|
|
17736
|
+
placeholder: "可选",
|
|
17737
|
+
onChange: (event)=>update({
|
|
17738
|
+
subEntry: event.target.value
|
|
17739
|
+
})
|
|
17740
|
+
})
|
|
17741
|
+
]
|
|
17742
|
+
})
|
|
17743
|
+
]
|
|
17744
|
+
}),
|
|
17745
|
+
/*#__PURE__*/ jsxs("div", {
|
|
17746
|
+
className: "work-document-index-entry-dialog-mode",
|
|
17747
|
+
children: [
|
|
17748
|
+
/*#__PURE__*/ jsx(OfficeCheckbox, {
|
|
17749
|
+
ariaLabel: "使用交叉引用",
|
|
17750
|
+
checked: crossReferenceEnabled,
|
|
17751
|
+
onCheckedChange: (enabled)=>update({
|
|
17752
|
+
crossReference: enabled ? value.crossReference || '参见' : '',
|
|
17753
|
+
pageBold: enabled ? false : value.pageBold,
|
|
17754
|
+
pageItalic: enabled ? false : value.pageItalic
|
|
17755
|
+
}),
|
|
17756
|
+
children: "使用交叉引用"
|
|
17757
|
+
}),
|
|
17758
|
+
crossReferenceEnabled && /*#__PURE__*/ jsxs("label", {
|
|
17759
|
+
htmlFor: crossReferenceId,
|
|
17760
|
+
children: [
|
|
17761
|
+
/*#__PURE__*/ jsx("span", {
|
|
17762
|
+
children: "引用目标"
|
|
17763
|
+
}),
|
|
17764
|
+
/*#__PURE__*/ jsx(office_text_field_OfficeTextField, {
|
|
17765
|
+
id: crossReferenceId,
|
|
17766
|
+
"aria-label": "交叉引用目标",
|
|
17767
|
+
value: value.crossReference,
|
|
17768
|
+
maxLength: 240,
|
|
17769
|
+
placeholder: "例如:Architecture",
|
|
17770
|
+
onChange: (event)=>update({
|
|
17771
|
+
crossReference: event.target.value
|
|
17772
|
+
})
|
|
17773
|
+
})
|
|
17774
|
+
]
|
|
17775
|
+
})
|
|
17776
|
+
]
|
|
17777
|
+
}),
|
|
17778
|
+
/*#__PURE__*/ jsxs("fieldset", {
|
|
17779
|
+
className: "work-document-index-entry-dialog-page-style",
|
|
17780
|
+
disabled: crossReferenceEnabled,
|
|
17781
|
+
children: [
|
|
17782
|
+
/*#__PURE__*/ jsx("legend", {
|
|
17783
|
+
children: "当前页码格式"
|
|
17784
|
+
}),
|
|
17785
|
+
/*#__PURE__*/ jsx(OfficeCheckbox, {
|
|
17786
|
+
ariaLabel: "页码加粗",
|
|
17787
|
+
checked: value.pageBold,
|
|
17788
|
+
disabled: crossReferenceEnabled,
|
|
17789
|
+
onCheckedChange: (pageBold)=>update({
|
|
17790
|
+
pageBold
|
|
17791
|
+
}),
|
|
17792
|
+
children: "页码加粗"
|
|
17793
|
+
}),
|
|
17794
|
+
/*#__PURE__*/ jsx(OfficeCheckbox, {
|
|
17795
|
+
ariaLabel: "页码倾斜",
|
|
17796
|
+
checked: value.pageItalic,
|
|
17797
|
+
disabled: crossReferenceEnabled,
|
|
17798
|
+
onCheckedChange: (pageItalic)=>update({
|
|
17799
|
+
pageItalic
|
|
17800
|
+
}),
|
|
17801
|
+
children: "页码倾斜"
|
|
17802
|
+
})
|
|
17803
|
+
]
|
|
17804
|
+
})
|
|
17805
|
+
]
|
|
17806
|
+
});
|
|
17807
|
+
}
|
|
16325
17808
|
function useDocumentInsertCommands({ contentRef, editor, resolveFieldContext }) {
|
|
16326
17809
|
const officeDialog = useOfficeDialog();
|
|
16327
17810
|
const [insertDialog, setInsertDialog] = useState(null);
|
|
@@ -16428,6 +17911,37 @@ function useDocumentInsertCommands({ contentRef, editor, resolveFieldContext })
|
|
|
16428
17911
|
editor,
|
|
16429
17912
|
rememberInvoker
|
|
16430
17913
|
]);
|
|
17914
|
+
const openIndexEntry = useCallback(()=>{
|
|
17915
|
+
if (!editor) return;
|
|
17916
|
+
const value = selectedDocumentIndexDraft(editor);
|
|
17917
|
+
if (!value) return void officeDialog.notice({
|
|
17918
|
+
title: '请选择索引文字',
|
|
17919
|
+
description: '请先选择要标记的正文文字,或选中一个已有索引项。'
|
|
17920
|
+
});
|
|
17921
|
+
rememberInvoker();
|
|
17922
|
+
setInsertDialog({
|
|
17923
|
+
kind: 'indexEntry',
|
|
17924
|
+
editing: Boolean(selectedDocumentIndexEntry(editor)),
|
|
17925
|
+
value
|
|
17926
|
+
});
|
|
17927
|
+
}, [
|
|
17928
|
+
editor,
|
|
17929
|
+
officeDialog,
|
|
17930
|
+
rememberInvoker
|
|
17931
|
+
]);
|
|
17932
|
+
const openIndex = useCallback(()=>{
|
|
17933
|
+
if (!editor) return;
|
|
17934
|
+
rememberInvoker();
|
|
17935
|
+
const selected = selectedDocumentIndexOptions(editor);
|
|
17936
|
+
setInsertDialog({
|
|
17937
|
+
kind: 'index',
|
|
17938
|
+
editing: Boolean(selected),
|
|
17939
|
+
options: selected ?? DEFAULT_DOCUMENT_INDEX_OPTIONS
|
|
17940
|
+
});
|
|
17941
|
+
}, [
|
|
17942
|
+
editor,
|
|
17943
|
+
rememberInvoker
|
|
17944
|
+
]);
|
|
16431
17945
|
const refreshFields = useCallback(()=>editor?.commands.refreshDocumentFields(contentRef.current, {
|
|
16432
17946
|
resolveContext: resolveFieldContext ?? void 0
|
|
16433
17947
|
}) ?? false, [
|
|
@@ -16441,6 +17955,12 @@ function useDocumentInsertCommands({ contentRef, editor, resolveFieldContext })
|
|
|
16441
17955
|
editor,
|
|
16442
17956
|
resolveFieldContext
|
|
16443
17957
|
]);
|
|
17958
|
+
const refreshIndex = useCallback(()=>editor?.commands.refreshDocumentIndexes({
|
|
17959
|
+
resolveContext: resolveFieldContext ?? void 0
|
|
17960
|
+
}) ?? false, [
|
|
17961
|
+
editor,
|
|
17962
|
+
resolveFieldContext
|
|
17963
|
+
]);
|
|
16444
17964
|
const submitCaption = ()=>{
|
|
16445
17965
|
if (!editor || insertDialog?.kind !== 'caption') return;
|
|
16446
17966
|
const inserted = editor.chain().focus().insertDocumentCaption(insertDialog.captionKind, insertDialog.title.trim()).run();
|
|
@@ -16467,6 +17987,23 @@ function useDocumentInsertCommands({ contentRef, editor, resolveFieldContext })
|
|
|
16467
17987
|
invokerRef.current = editor.view.dom;
|
|
16468
17988
|
setInsertDialog(null);
|
|
16469
17989
|
};
|
|
17990
|
+
const submitIndexEntry = ()=>{
|
|
17991
|
+
if (!editor || insertDialog?.kind !== 'indexEntry') return;
|
|
17992
|
+
const applied = insertDialog.editing ? editor.commands.updateDocumentIndexEntry(insertDialog.value) : editor.commands.markDocumentIndexEntry(insertDialog.value);
|
|
17993
|
+
if (!applied) return;
|
|
17994
|
+
invokerRef.current = editor.view.dom;
|
|
17995
|
+
setInsertDialog(null);
|
|
17996
|
+
};
|
|
17997
|
+
const submitIndex = ()=>{
|
|
17998
|
+
if (!editor || insertDialog?.kind !== 'index') return;
|
|
17999
|
+
const buildOptions = {
|
|
18000
|
+
resolveContext: resolveFieldContext ?? void 0
|
|
18001
|
+
};
|
|
18002
|
+
const applied = insertDialog.editing ? editor.commands.updateDocumentIndex(insertDialog.options, buildOptions) : editor.commands.insertDocumentIndex(insertDialog.options, buildOptions);
|
|
18003
|
+
if (!applied) return;
|
|
18004
|
+
invokerRef.current = editor.view.dom;
|
|
18005
|
+
setInsertDialog(null);
|
|
18006
|
+
};
|
|
16470
18007
|
const moveReferenceSelection = (event, currentIndex)=>{
|
|
16471
18008
|
if (insertDialog?.kind !== 'crossReference') return;
|
|
16472
18009
|
let nextIndex = currentIndex;
|
|
@@ -16608,6 +18145,28 @@ function useDocumentInsertCommands({ contentRef, editor, resolveFieldContext })
|
|
|
16608
18145
|
options
|
|
16609
18146
|
}),
|
|
16610
18147
|
onSubmit: submitTableOfContents
|
|
18148
|
+
}),
|
|
18149
|
+
insertDialog?.kind === 'indexEntry' && /*#__PURE__*/ jsx(DocumentIndexEntryDialog, {
|
|
18150
|
+
editing: insertDialog.editing,
|
|
18151
|
+
value: insertDialog.value,
|
|
18152
|
+
restoreFocusTarget: ()=>invokerRef.current,
|
|
18153
|
+
onCancel: ()=>setInsertDialog(null),
|
|
18154
|
+
onChange: (value)=>setInsertDialog({
|
|
18155
|
+
...insertDialog,
|
|
18156
|
+
value
|
|
18157
|
+
}),
|
|
18158
|
+
onSubmit: submitIndexEntry
|
|
18159
|
+
}),
|
|
18160
|
+
insertDialog?.kind === 'index' && /*#__PURE__*/ jsx(DocumentIndexDialog, {
|
|
18161
|
+
editing: insertDialog.editing,
|
|
18162
|
+
options: insertDialog.options,
|
|
18163
|
+
restoreFocusTarget: ()=>invokerRef.current,
|
|
18164
|
+
onCancel: ()=>setInsertDialog(null),
|
|
18165
|
+
onOptionsChange: (options)=>setInsertDialog({
|
|
18166
|
+
...insertDialog,
|
|
18167
|
+
options
|
|
18168
|
+
}),
|
|
18169
|
+
onSubmit: submitIndex
|
|
16611
18170
|
})
|
|
16612
18171
|
]
|
|
16613
18172
|
});
|
|
@@ -16618,8 +18177,11 @@ function useDocumentInsertCommands({ contentRef, editor, resolveFieldContext })
|
|
|
16618
18177
|
insertField,
|
|
16619
18178
|
insertImage,
|
|
16620
18179
|
insertNote,
|
|
18180
|
+
openIndexEntry,
|
|
18181
|
+
openIndex,
|
|
16621
18182
|
openTableOfContents,
|
|
16622
18183
|
refreshFields,
|
|
18184
|
+
refreshIndex,
|
|
16623
18185
|
refreshTableOfContents
|
|
16624
18186
|
};
|
|
16625
18187
|
}
|
|
@@ -18396,6 +19958,13 @@ function DocumentEditorSurface({ artifactId, autoFocus = true, collaboration, co
|
|
|
18396
19958
|
requestEditorViewChange,
|
|
18397
19959
|
taskPane
|
|
18398
19960
|
]);
|
|
19961
|
+
const documentComparison = useDocumentComparison({
|
|
19962
|
+
editor,
|
|
19963
|
+
onApplied: ()=>{
|
|
19964
|
+
if (editor && !editor.isDestroyed) taskPaneInvokerRef.current = editor.view.dom;
|
|
19965
|
+
setTaskPane('changes');
|
|
19966
|
+
}
|
|
19967
|
+
});
|
|
18399
19968
|
const startCommentDraft = useCallback(async ()=>{
|
|
18400
19969
|
rememberTaskPaneInvoker();
|
|
18401
19970
|
if (!await requestEditorViewChange(null, false)) return;
|
|
@@ -18689,6 +20258,10 @@ function DocumentEditorSurface({ artifactId, autoFocus = true, collaboration, co
|
|
|
18689
20258
|
documentInsert.refreshTableOfContents();
|
|
18690
20259
|
restoreDocumentBodyFocus();
|
|
18691
20260
|
};
|
|
20261
|
+
const refreshDocumentIndex = ()=>{
|
|
20262
|
+
documentInsert.refreshIndex();
|
|
20263
|
+
restoreDocumentBodyFocus();
|
|
20264
|
+
};
|
|
18692
20265
|
const openSelectionContextMenu = (event)=>{
|
|
18693
20266
|
if (!canEditDocument || !getSelectionMenuItems && !onAgentRequest) return false;
|
|
18694
20267
|
const snapshot = createWorkDocumentSelectionSnapshot(editor, contentRef.current);
|
|
@@ -18786,11 +20359,14 @@ function DocumentEditorSurface({ artifactId, autoFocus = true, collaboration, co
|
|
|
18786
20359
|
onInsertCaption: documentInsert.insertCaption,
|
|
18787
20360
|
onInsertCrossReference: documentInsert.insertCrossReference,
|
|
18788
20361
|
onOpenTableOfContents: documentInsert.openTableOfContents,
|
|
20362
|
+
onOpenIndexEntry: documentInsert.openIndexEntry,
|
|
20363
|
+
onOpenIndex: documentInsert.openIndex,
|
|
18789
20364
|
citationsOpen: citationsOpen,
|
|
18790
20365
|
citationSourceCount: currentContent.bibliography?.sources.length ?? 0,
|
|
18791
20366
|
onToggleCitations: ()=>void toggleTaskPane('citations'),
|
|
18792
20367
|
onInsertField: documentInsert.insertField,
|
|
18793
20368
|
onRefreshFields: refreshDocumentFields,
|
|
20369
|
+
onRefreshIndex: refreshDocumentIndex,
|
|
18794
20370
|
onRefreshTableOfContents: refreshDocumentTableOfContents,
|
|
18795
20371
|
canInsertComment: documentComments.canInsert,
|
|
18796
20372
|
onInsertComment: ()=>void startCommentDraft(),
|
|
@@ -18811,6 +20387,7 @@ function DocumentEditorSurface({ artifactId, autoFocus = true, collaboration, co
|
|
|
18811
20387
|
restoreDocumentBodyFocus();
|
|
18812
20388
|
},
|
|
18813
20389
|
onToggleChanges: ()=>void toggleTaskPane('changes'),
|
|
20390
|
+
onOpenComparison: documentComparison.open,
|
|
18814
20391
|
onDecideChange: (change, decision)=>decideDocumentChanges([
|
|
18815
20392
|
change
|
|
18816
20393
|
], decision),
|
|
@@ -19138,6 +20715,7 @@ function DocumentEditorSurface({ artifactId, autoFocus = true, collaboration, co
|
|
|
19138
20715
|
onClose: ()=>setSelectionMenu(null)
|
|
19139
20716
|
}),
|
|
19140
20717
|
canEditDocument && documentInsert.dialog,
|
|
20718
|
+
canEditDocument && documentComparison.dialog,
|
|
19141
20719
|
!preview && taskPaneDialog.dialog,
|
|
19142
20720
|
!preview && statisticsOpen && /*#__PURE__*/ jsx(DocumentStatisticsDialog, {
|
|
19143
20721
|
pageCount: pageCount,
|