@portone/docx-editor 0.1.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/CHANGELOG.md +7 -0
- package/CONTRIBUTING.md +63 -0
- package/LICENSE +203 -0
- package/README.md +48 -0
- package/assets/editor.png +0 -0
- package/dist/DocxEditor.d.ts +123 -0
- package/dist/DocxEditor.js +348 -0
- package/dist/core.d.ts +36 -0
- package/dist/core.js +38 -0
- package/dist/docx/comments/constants.d.ts +8 -0
- package/dist/docx/comments/constants.js +20 -0
- package/dist/docx/comments/model.d.ts +37 -0
- package/dist/docx/comments/model.js +104 -0
- package/dist/docx/comments/reading.d.ts +36 -0
- package/dist/docx/comments/reading.js +149 -0
- package/dist/docx/comments/writing.d.ts +11 -0
- package/dist/docx/comments/writing.js +327 -0
- package/dist/docx/comments.d.ts +3 -0
- package/dist/docx/comments.js +4 -0
- package/dist/docx/container.d.ts +12 -0
- package/dist/docx/container.js +130 -0
- package/dist/docx/documentSettings.d.ts +4 -0
- package/dist/docx/documentSettings.js +14 -0
- package/dist/docx/exportDocx.d.ts +15 -0
- package/dist/docx/exportDocx.js +163 -0
- package/dist/docx/exportRefs.d.ts +16 -0
- package/dist/docx/exportRefs.js +10 -0
- package/dist/docx/formatting/direct.d.ts +30 -0
- package/dist/docx/formatting/direct.js +201 -0
- package/dist/docx/formatting/effectiveParagraph.d.ts +15 -0
- package/dist/docx/formatting/effectiveParagraph.js +81 -0
- package/dist/docx/formatting/styles.d.ts +67 -0
- package/dist/docx/formatting/styles.js +165 -0
- package/dist/docx/formatting/tabStops.d.ts +8 -0
- package/dist/docx/formatting/tabStops.js +65 -0
- package/dist/docx/formatting.d.ts +4 -0
- package/dist/docx/formatting.js +4 -0
- package/dist/docx/headersFooters.d.ts +36 -0
- package/dist/docx/headersFooters.js +218 -0
- package/dist/docx/hyperlink.d.ts +75 -0
- package/dist/docx/hyperlink.js +71 -0
- package/dist/docx/importDocx.d.ts +21 -0
- package/dist/docx/importDocx.js +263 -0
- package/dist/docx/importParagraph.d.ts +32 -0
- package/dist/docx/importParagraph.js +308 -0
- package/dist/docx/importTable.d.ts +9 -0
- package/dist/docx/importTable.js +275 -0
- package/dist/docx/media.d.ts +60 -0
- package/dist/docx/media.js +213 -0
- package/dist/docx/notes.d.ts +22 -0
- package/dist/docx/notes.js +71 -0
- package/dist/docx/pageGeometry.d.ts +75 -0
- package/dist/docx/pageGeometry.js +88 -0
- package/dist/docx/paraProps.d.ts +74 -0
- package/dist/docx/paraProps.js +167 -0
- package/dist/docx/propsXml.d.ts +49 -0
- package/dist/docx/propsXml.js +302 -0
- package/dist/docx/relationships.d.ts +42 -0
- package/dist/docx/relationships.js +94 -0
- package/dist/docx/runProps.d.ts +51 -0
- package/dist/docx/runProps.js +207 -0
- package/dist/docx/scan.d.ts +20 -0
- package/dist/docx/scan.js +110 -0
- package/dist/docx/sdt.d.ts +56 -0
- package/dist/docx/sdt.js +104 -0
- package/dist/docx/sdtProps.d.ts +18 -0
- package/dist/docx/sdtProps.js +11 -0
- package/dist/docx/serializeBlock.d.ts +4 -0
- package/dist/docx/serializeBlock.js +30 -0
- package/dist/docx/serializeParagraph.d.ts +15 -0
- package/dist/docx/serializeParagraph.js +208 -0
- package/dist/docx/serializeTable.d.ts +12 -0
- package/dist/docx/serializeTable.js +134 -0
- package/dist/docx/session.d.ts +81 -0
- package/dist/docx/session.js +79 -0
- package/dist/docx/tableFormatting/editing.d.ts +64 -0
- package/dist/docx/tableFormatting/editing.js +318 -0
- package/dist/docx/tableFormatting/reading.d.ts +96 -0
- package/dist/docx/tableFormatting/reading.js +221 -0
- package/dist/docx/tableFormatting.d.ts +2 -0
- package/dist/docx/tableFormatting.js +3 -0
- package/dist/docx/tableTemplate.d.ts +22 -0
- package/dist/docx/tableTemplate.js +111 -0
- package/dist/docx/theme.d.ts +40 -0
- package/dist/docx/theme.js +61 -0
- package/dist/docx/uniqueControls.d.ts +14 -0
- package/dist/docx/uniqueControls.js +62 -0
- package/dist/download.d.ts +57 -0
- package/dist/download.js +44 -0
- package/dist/editor/clipboard/images.d.ts +14 -0
- package/dist/editor/clipboard/images.js +158 -0
- package/dist/editor/clipboard/inlineFormatting.d.ts +21 -0
- package/dist/editor/clipboard/inlineFormatting.js +232 -0
- package/dist/editor/commands/breakCommands.d.ts +8 -0
- package/dist/editor/commands/breakCommands.js +19 -0
- package/dist/editor/commands/canRunCommand.d.ts +6 -0
- package/dist/editor/commands/canRunCommand.js +10 -0
- package/dist/editor/commands/commentCommands.d.ts +3 -0
- package/dist/editor/commands/commentCommands.js +4 -0
- package/dist/editor/commands/comments/editing.d.ts +21 -0
- package/dist/editor/commands/comments/editing.js +305 -0
- package/dist/editor/commands/comments/model.d.ts +31 -0
- package/dist/editor/commands/comments/model.js +0 -0
- package/dist/editor/commands/comments/reading.d.ts +8 -0
- package/dist/editor/commands/comments/reading.js +58 -0
- package/dist/editor/commands/formatting/editing.d.ts +32 -0
- package/dist/editor/commands/formatting/editing.js +138 -0
- package/dist/editor/commands/formatting/reading.d.ts +57 -0
- package/dist/editor/commands/formatting/reading.js +82 -0
- package/dist/editor/commands/formatting/shared.d.ts +32 -0
- package/dist/editor/commands/formatting/shared.js +53 -0
- package/dist/editor/commands/formattingCommands.d.ts +2 -0
- package/dist/editor/commands/formattingCommands.js +3 -0
- package/dist/editor/commands/historyCommands.d.ts +11 -0
- package/dist/editor/commands/historyCommands.js +16 -0
- package/dist/editor/commands/indentCommands.d.ts +15 -0
- package/dist/editor/commands/indentCommands.js +44 -0
- package/dist/editor/commands/index.d.ts +82 -0
- package/dist/editor/commands/index.js +171 -0
- package/dist/editor/commands/linkCommands.d.ts +56 -0
- package/dist/editor/commands/linkCommands.js +146 -0
- package/dist/editor/commands/listCommands.d.ts +58 -0
- package/dist/editor/commands/listCommands.js +140 -0
- package/dist/editor/commands/lockCommands.d.ts +47 -0
- package/dist/editor/commands/lockCommands.js +198 -0
- package/dist/editor/commands/noteQueries.d.ts +12 -0
- package/dist/editor/commands/noteQueries.js +29 -0
- package/dist/editor/commands/paragraphCommands.d.ts +59 -0
- package/dist/editor/commands/paragraphCommands.js +113 -0
- package/dist/editor/commands/spacingCommands.d.ts +23 -0
- package/dist/editor/commands/spacingCommands.js +48 -0
- package/dist/editor/commands/tabCommands.d.ts +5 -0
- package/dist/editor/commands/tabCommands.js +65 -0
- package/dist/editor/createEditor.d.ts +59 -0
- package/dist/editor/createEditor.js +160 -0
- package/dist/editor/documentStyles.d.ts +60 -0
- package/dist/editor/documentStyles.js +80 -0
- package/dist/editor/externalClipboard.d.ts +11 -0
- package/dist/editor/externalClipboard.js +323 -0
- package/dist/editor/imageFiles.d.ts +74 -0
- package/dist/editor/imageFiles.js +173 -0
- package/dist/editor/insertImage.d.ts +31 -0
- package/dist/editor/insertImage.js +35 -0
- package/dist/editor/insertTable.d.ts +18 -0
- package/dist/editor/insertTable.js +44 -0
- package/dist/editor/paragraphEdits.d.ts +42 -0
- package/dist/editor/paragraphEdits.js +59 -0
- package/dist/editor/plainText.d.ts +3 -0
- package/dist/editor/plainText.js +50 -0
- package/dist/editor/plugins/bookmarkProtection.d.ts +3 -0
- package/dist/editor/plugins/bookmarkProtection.js +28 -0
- package/dist/editor/plugins/columnResize.d.ts +27 -0
- package/dist/editor/plugins/columnResize.js +192 -0
- package/dist/editor/plugins/commentDecorations.d.ts +3 -0
- package/dist/editor/plugins/commentDecorations.js +23 -0
- package/dist/editor/plugins/commentReservations.d.ts +5 -0
- package/dist/editor/plugins/commentReservations.js +26 -0
- package/dist/editor/plugins/dropCaret.d.ts +10 -0
- package/dist/editor/plugins/dropCaret.js +11 -0
- package/dist/editor/plugins/imagePaste.d.ts +8 -0
- package/dist/editor/plugins/imagePaste.js +265 -0
- package/dist/editor/plugins/keymap.d.ts +5 -0
- package/dist/editor/plugins/keymap.js +94 -0
- package/dist/editor/plugins/linkPanel.d.ts +23 -0
- package/dist/editor/plugins/linkPanel.js +39 -0
- package/dist/editor/plugins/listInputRules.d.ts +14 -0
- package/dist/editor/plugins/listInputRules.js +36 -0
- package/dist/editor/plugins/lockedContent.d.ts +6 -0
- package/dist/editor/plugins/lockedContent.js +41 -0
- package/dist/editor/plugins/noteProtection.d.ts +3 -0
- package/dist/editor/plugins/noteProtection.js +25 -0
- package/dist/editor/plugins/numberingDecorations.d.ts +42 -0
- package/dist/editor/plugins/numberingDecorations.js +110 -0
- package/dist/editor/plugins/rowResize.d.ts +15 -0
- package/dist/editor/plugins/rowResize.js +196 -0
- package/dist/editor/plugins/styledParagraphs.d.ts +15 -0
- package/dist/editor/plugins/styledParagraphs.js +65 -0
- package/dist/editor/plugins/tabCaret.d.ts +3 -0
- package/dist/editor/plugins/tabCaret.js +126 -0
- package/dist/editor/plugins/tabDecorations.d.ts +11 -0
- package/dist/editor/plugins/tabDecorations.js +87 -0
- package/dist/editor/plugins/tabLayout.d.ts +16 -0
- package/dist/editor/plugins/tabLayout.js +328 -0
- package/dist/editor/plugins/tabPointer.d.ts +7 -0
- package/dist/editor/plugins/tabPointer.js +95 -0
- package/dist/editor/plugins/tableContextMenu.d.ts +21 -0
- package/dist/editor/plugins/tableContextMenu.js +88 -0
- package/dist/editor/plugins/textContextMenu.d.ts +15 -0
- package/dist/editor/plugins/textContextMenu.js +90 -0
- package/dist/editor/views/imageResize.d.ts +53 -0
- package/dist/editor/views/imageResize.js +167 -0
- package/dist/editor/views/runMarkView.d.ts +15 -0
- package/dist/editor/views/runMarkView.js +9 -0
- package/dist/editor/views/tableView.d.ts +24 -0
- package/dist/editor/views/tableView.js +32 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +38 -0
- package/dist/model/format.d.ts +240 -0
- package/dist/model/format.js +305 -0
- package/dist/model/tabStops.d.ts +17 -0
- package/dist/model/tabStops.js +22 -0
- package/dist/numbering/listTemplate.d.ts +29 -0
- package/dist/numbering/listTemplate.js +83 -0
- package/dist/numbering/markers.d.ts +24 -0
- package/dist/numbering/markers.js +93 -0
- package/dist/numbering/parseNumbering.d.ts +65 -0
- package/dist/numbering/parseNumbering.js +116 -0
- package/dist/numbering/writeNumbering.d.ts +13 -0
- package/dist/numbering/writeNumbering.js +49 -0
- package/dist/ooxml/errors.d.ts +39 -0
- package/dist/ooxml/errors.js +21 -0
- package/dist/ooxml/image.d.ts +88 -0
- package/dist/ooxml/image.js +106 -0
- package/dist/ooxml/tabStops.d.ts +3 -0
- package/dist/ooxml/tabStops.js +51 -0
- package/dist/ooxml/units.d.ts +37 -0
- package/dist/ooxml/units.js +116 -0
- package/dist/ooxml/xml.d.ts +36 -0
- package/dist/ooxml/xml.js +89 -0
- package/dist/page/PageGuides.d.ts +16 -0
- package/dist/page/PageGuides.js +114 -0
- package/dist/page/measureBlocks.d.ts +25 -0
- package/dist/page/measureBlocks.js +80 -0
- package/dist/page/pageDecorations.d.ts +36 -0
- package/dist/page/pageDecorations.js +211 -0
- package/dist/page/pageLayout.d.ts +176 -0
- package/dist/page/pageLayout.js +157 -0
- package/dist/page/tableMeasurements.d.ts +10 -0
- package/dist/page/tableMeasurements.js +115 -0
- package/dist/page/usePageLayout.d.ts +57 -0
- package/dist/page/usePageLayout.js +141 -0
- package/dist/schema/docxSchema.d.ts +9 -0
- package/dist/schema/docxSchema.js +863 -0
- package/dist/schema/index.d.ts +2 -0
- package/dist/schema/index.js +9 -0
- package/dist/schema/locks.d.ts +112 -0
- package/dist/schema/locks.js +211 -0
- package/dist/schema/rendering.d.ts +6 -0
- package/dist/schema/rendering.js +46 -0
- package/dist/styles/classNames.d.ts +214 -0
- package/dist/styles/classNames.js +208 -0
- package/dist/styles/colors.d.ts +11 -0
- package/dist/styles/colors.js +14 -0
- package/dist/styles/fontSizes.d.ts +6 -0
- package/dist/styles/fontSizes.js +20 -0
- package/dist/styles/fontStack.d.ts +55 -0
- package/dist/styles/fontStack.js +262 -0
- package/dist/styles/fonts.d.ts +6 -0
- package/dist/styles/fonts.js +11 -0
- package/dist/styles/inlineStyle.d.ts +67 -0
- package/dist/styles/inlineStyle.js +212 -0
- package/dist/styles/lineSpacings.d.ts +11 -0
- package/dist/styles/lineSpacings.js +10 -0
- package/dist/styles.css +1650 -0
- package/dist/table/cellBorders.d.ts +12 -0
- package/dist/table/cellBorders.js +9 -0
- package/dist/table/cellFormatting.d.ts +57 -0
- package/dist/table/cellFormatting.js +173 -0
- package/dist/table/columns.d.ts +25 -0
- package/dist/table/columns.js +155 -0
- package/dist/table/commands.d.ts +19 -0
- package/dist/table/commands.js +63 -0
- package/dist/table/format.d.ts +35 -0
- package/dist/table/format.js +26 -0
- package/dist/table/gridBorders.d.ts +42 -0
- package/dist/table/gridBorders.js +192 -0
- package/dist/table/index.d.ts +16 -0
- package/dist/table/index.js +52 -0
- package/dist/table/merge.d.ts +36 -0
- package/dist/table/merge.js +125 -0
- package/dist/table/resize.d.ts +55 -0
- package/dist/table/resize.js +66 -0
- package/dist/table/rowResize.d.ts +14 -0
- package/dist/table/rowResize.js +44 -0
- package/dist/table/rows.d.ts +32 -0
- package/dist/table/rows.js +89 -0
- package/dist/table/widths.d.ts +69 -0
- package/dist/table/widths.js +97 -0
- package/dist/ui/AlignMenu.d.ts +15 -0
- package/dist/ui/AlignMenu.js +64 -0
- package/dist/ui/CellStyleGroup.d.ts +15 -0
- package/dist/ui/CellStyleGroup.js +232 -0
- package/dist/ui/ColorPicker.d.ts +13 -0
- package/dist/ui/ColorPicker.js +165 -0
- package/dist/ui/CommentsPanel.d.ts +15 -0
- package/dist/ui/CommentsPanel.js +348 -0
- package/dist/ui/FontFamilySelect.d.ts +14 -0
- package/dist/ui/FontFamilySelect.js +59 -0
- package/dist/ui/FontSizeSelect.d.ts +11 -0
- package/dist/ui/FontSizeSelect.js +49 -0
- package/dist/ui/InsertImageButton.d.ts +9 -0
- package/dist/ui/InsertImageButton.js +47 -0
- package/dist/ui/LinkCard.d.ts +12 -0
- package/dist/ui/LinkCard.js +127 -0
- package/dist/ui/LinkPanel.d.ts +9 -0
- package/dist/ui/LinkPanel.js +121 -0
- package/dist/ui/MenuChoice.d.ts +11 -0
- package/dist/ui/MenuChoice.js +32 -0
- package/dist/ui/NotesPanel.d.ts +6 -0
- package/dist/ui/NotesPanel.js +33 -0
- package/dist/ui/ParagraphStyleSelect.d.ts +11 -0
- package/dist/ui/ParagraphStyleSelect.js +62 -0
- package/dist/ui/Popover.d.ts +22 -0
- package/dist/ui/Popover.js +74 -0
- package/dist/ui/SpacingMenu.d.ts +12 -0
- package/dist/ui/SpacingMenu.js +54 -0
- package/dist/ui/TableMenu.d.ts +12 -0
- package/dist/ui/TableMenu.js +112 -0
- package/dist/ui/TableSizePicker.d.ts +9 -0
- package/dist/ui/TableSizePicker.js +53 -0
- package/dist/ui/TextMenu.d.ts +15 -0
- package/dist/ui/TextMenu.js +194 -0
- package/dist/ui/Toolbar.d.ts +19 -0
- package/dist/ui/Toolbar.js +448 -0
- package/dist/ui/ToolbarButton.d.ts +23 -0
- package/dist/ui/ToolbarButton.js +56 -0
- package/dist/ui/Tooltip.d.ts +19 -0
- package/dist/ui/Tooltip.js +47 -0
- package/dist/ui/ZoomSelect.d.ts +7 -0
- package/dist/ui/ZoomSelect.js +44 -0
- package/dist/ui/comments/CommentComposer.d.ts +17 -0
- package/dist/ui/comments/CommentComposer.js +59 -0
- package/dist/ui/comments/useCommentRailLayout.d.ts +18 -0
- package/dist/ui/comments/useCommentRailLayout.js +183 -0
- package/dist/ui/keyboardWalk.d.ts +22 -0
- package/dist/ui/keyboardWalk.js +70 -0
- package/dist/ui/openAddress.d.ts +11 -0
- package/dist/ui/openAddress.js +16 -0
- package/dist/ui/panelPlacement.d.ts +51 -0
- package/dist/ui/panelPlacement.js +71 -0
- package/dist/ui/presets.d.ts +11 -0
- package/dist/ui/presets.js +0 -0
- package/dist/ui/rovingFocus.d.ts +20 -0
- package/dist/ui/rovingFocus.js +61 -0
- package/dist/ui/runCommand.d.ts +5 -0
- package/dist/ui/runCommand.js +13 -0
- package/dist/ui/useDismiss.d.ts +6 -0
- package/dist/ui/useDismiss.js +31 -0
- package/dist/ui/useFitWidthZoom.d.ts +2 -0
- package/dist/ui/useFitWidthZoom.js +26 -0
- package/dist/ui/useGridKeyboard.d.ts +12 -0
- package/dist/ui/useGridKeyboard.js +90 -0
- package/dist/ui/useMenuKeyboard.d.ts +12 -0
- package/dist/ui/useMenuKeyboard.js +56 -0
- package/dist/ui/zoom.d.ts +4 -0
- package/dist/ui/zoom.js +21 -0
- package/package.json +120 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
// src/editor/plugins/tabLayout.ts
|
|
2
|
+
import { Plugin } from "prosemirror-state";
|
|
3
|
+
import { DEFAULT_TAB_STOP_PT } from "../../docx/documentSettings.js";
|
|
4
|
+
import { toParagraphFormat } from "../../model/format.js";
|
|
5
|
+
import { docxSchema } from "../../schema/index.js";
|
|
6
|
+
import { editorClassNames, editorCssVariables } from "../../styles/classNames.js";
|
|
7
|
+
import { documentDefaultTabStopPt } from "../documentStyles.js";
|
|
8
|
+
import { setTabWidths, tabWidths } from "./tabDecorations.js";
|
|
9
|
+
var PX_PER_PT = 96 / 72;
|
|
10
|
+
var POSITION_EPSILON_PT = 0.01;
|
|
11
|
+
var WIDTH_EPSILON_PX = 0.1;
|
|
12
|
+
var NATURAL_WIDTH_PX = 1e5;
|
|
13
|
+
var MAX_LAYOUT_PASSES = 4;
|
|
14
|
+
function nextTabTarget(currentPt, stops, defaultStopPt) {
|
|
15
|
+
const custom = stops.find(
|
|
16
|
+
(stop) => stop.align !== "bar" && stop.positionPt > currentPt + POSITION_EPSILON_PT
|
|
17
|
+
);
|
|
18
|
+
if (custom && custom.align !== "bar") {
|
|
19
|
+
return {
|
|
20
|
+
align: custom.align === "num" ? "start" : custom.align,
|
|
21
|
+
targetPt: custom.positionPt
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const interval = defaultStopPt > 0 ? defaultStopPt : DEFAULT_TAB_STOP_PT;
|
|
25
|
+
return {
|
|
26
|
+
align: "start",
|
|
27
|
+
targetPt: (Math.floor(currentPt / interval) + 1) * interval
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function alignedPrefix(align, segment) {
|
|
31
|
+
if (align === "center") return segment.wholePt / 2;
|
|
32
|
+
if (align === "end") return segment.wholePt;
|
|
33
|
+
if (align === "decimal") return segment.decimalPrefixPt;
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
36
|
+
function tabAdvancePt(currentPt, target, segment) {
|
|
37
|
+
return Math.max(
|
|
38
|
+
0,
|
|
39
|
+
target.targetPt - currentPt - alignedPrefix(target.align, segment)
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
function numeric(value) {
|
|
43
|
+
const parsed = Number.parseFloat(value);
|
|
44
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
45
|
+
}
|
|
46
|
+
function computedStyle(element) {
|
|
47
|
+
return element.ownerDocument.defaultView?.getComputedStyle(element) ?? getComputedStyle(element);
|
|
48
|
+
}
|
|
49
|
+
function scaleOf(view) {
|
|
50
|
+
const layer = view.dom.closest(`.${editorClassNames.pageLayer}`);
|
|
51
|
+
const parsed = Number.parseFloat(layer ? computedStyle(layer).zoom : "");
|
|
52
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 1;
|
|
53
|
+
}
|
|
54
|
+
function paragraphLayoutContext(view, slot) {
|
|
55
|
+
const $pos = view.state.doc.resolve(view.posAtDOM(slot, 0));
|
|
56
|
+
for (let depth = $pos.depth; depth > 0; depth -= 1) {
|
|
57
|
+
const node = $pos.node(depth);
|
|
58
|
+
if (node.type === docxSchema.nodes.paragraph) {
|
|
59
|
+
return { format: toParagraphFormat(node.attrs.format) ?? {}, node };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
function resetSlot(slot) {
|
|
65
|
+
slot.removeAttribute("data-tab-layout");
|
|
66
|
+
slot.style.removeProperty(editorCssVariables.tabWidth);
|
|
67
|
+
}
|
|
68
|
+
function cloneParagraph(paragraph, widthPx, resetTabs) {
|
|
69
|
+
const clone = paragraph.cloneNode(true);
|
|
70
|
+
const computed = computedStyle(paragraph);
|
|
71
|
+
for (let index = 0; index < computed.length; index += 1) {
|
|
72
|
+
const property = computed.item(index);
|
|
73
|
+
clone.style.setProperty(
|
|
74
|
+
property,
|
|
75
|
+
computed.getPropertyValue(property),
|
|
76
|
+
computed.getPropertyPriority(property)
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
clone.style.position = "fixed";
|
|
80
|
+
clone.style.inset = "0 auto auto -200000px";
|
|
81
|
+
clone.style.boxSizing = "border-box";
|
|
82
|
+
clone.style.width = `${widthPx}px`;
|
|
83
|
+
clone.style.maxWidth = "none";
|
|
84
|
+
clone.style.textAlign = "start";
|
|
85
|
+
clone.style.visibility = "hidden";
|
|
86
|
+
clone.style.pointerEvents = "none";
|
|
87
|
+
clone.style.zoom = "1";
|
|
88
|
+
clone.setAttribute("aria-hidden", "true");
|
|
89
|
+
if (resetTabs) {
|
|
90
|
+
for (const slot of clone.querySelectorAll(
|
|
91
|
+
`.${editorClassNames.tabSlot}`
|
|
92
|
+
)) {
|
|
93
|
+
resetSlot(slot);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return clone;
|
|
97
|
+
}
|
|
98
|
+
function rangeAfter(slot, paragraph) {
|
|
99
|
+
const boundaries = Array.from(
|
|
100
|
+
paragraph.querySelectorAll(`.${editorClassNames.tabSlot}, br`)
|
|
101
|
+
);
|
|
102
|
+
const next = boundaries[boundaries.indexOf(slot) + 1];
|
|
103
|
+
const range = paragraph.ownerDocument.createRange();
|
|
104
|
+
range.setStartAfter(slot);
|
|
105
|
+
if (next) range.setEndBefore(next);
|
|
106
|
+
else range.setEnd(paragraph, paragraph.childNodes.length);
|
|
107
|
+
return range;
|
|
108
|
+
}
|
|
109
|
+
function rangeWidth(range) {
|
|
110
|
+
const rects = Array.from(range.getClientRects()).filter(
|
|
111
|
+
(rect) => rect.width > 0 || rect.height > 0
|
|
112
|
+
);
|
|
113
|
+
if (rects.length === 0) return 0;
|
|
114
|
+
return Math.max(...rects.map((rect) => rect.right)) - Math.min(...rects.map((rect) => rect.left));
|
|
115
|
+
}
|
|
116
|
+
function decimalSeparator(node, paragraph) {
|
|
117
|
+
const language = node.parentElement?.closest("[lang]")?.lang || paragraph.lang || paragraph.ownerDocument.documentElement.lang || void 0;
|
|
118
|
+
try {
|
|
119
|
+
const parts = new Intl.NumberFormat(language).formatToParts(1.1);
|
|
120
|
+
return parts.find((part) => part.type === "decimal")?.value ?? ".";
|
|
121
|
+
} catch {
|
|
122
|
+
return ".";
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function decimalPrefix(range, paragraph) {
|
|
126
|
+
const walker = paragraph.ownerDocument.createTreeWalker(
|
|
127
|
+
paragraph,
|
|
128
|
+
paragraph.ownerDocument.defaultView?.NodeFilter.SHOW_TEXT ?? 4
|
|
129
|
+
);
|
|
130
|
+
let node = walker.nextNode();
|
|
131
|
+
while (node) {
|
|
132
|
+
if (range.intersectsNode(node)) {
|
|
133
|
+
const at = node.textContent?.indexOf(decimalSeparator(node, paragraph)) ?? -1;
|
|
134
|
+
if (at >= 0) {
|
|
135
|
+
const prefix = range.cloneRange();
|
|
136
|
+
prefix.setEnd(node, at);
|
|
137
|
+
return prefix;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
node = walker.nextNode();
|
|
141
|
+
}
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
function segmentMetrics(slot, paragraph) {
|
|
145
|
+
const whole = rangeAfter(slot, paragraph);
|
|
146
|
+
return {
|
|
147
|
+
wholePt: rangeWidth(whole) / PX_PER_PT,
|
|
148
|
+
decimalPrefixPt: rangeWidth(decimalPrefix(whole, paragraph) ?? whole) / PX_PER_PT
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function currentPositionPt(paragraph, slot) {
|
|
152
|
+
const paragraphRect = paragraph.getBoundingClientRect();
|
|
153
|
+
const slotRect = slot.getBoundingClientRect();
|
|
154
|
+
const style = computedStyle(paragraph);
|
|
155
|
+
const rtl = style.direction === "rtl";
|
|
156
|
+
const margin = numeric(rtl ? style.marginRight : style.marginLeft);
|
|
157
|
+
const origin = rtl ? paragraphRect.right + margin : paragraphRect.left - margin;
|
|
158
|
+
return (rtl ? origin - slotRect.right : slotRect.left - origin) / PX_PER_PT;
|
|
159
|
+
}
|
|
160
|
+
function applyWidth(slot, widthPx) {
|
|
161
|
+
slot.setAttribute("data-tab-layout", "");
|
|
162
|
+
slot.style.setProperty(editorCssVariables.tabWidth, `${widthPx}px`);
|
|
163
|
+
}
|
|
164
|
+
function slotPosition(slot) {
|
|
165
|
+
const parsed = Number.parseInt(slot.dataset.tabPosition ?? "", 10);
|
|
166
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
167
|
+
}
|
|
168
|
+
function measureParagraph(paragraph, actualSlots, format, defaultStopPt, scale, widths) {
|
|
169
|
+
const paragraphWidth = paragraph.getBoundingClientRect().width / scale;
|
|
170
|
+
if (paragraphWidth <= 0) return;
|
|
171
|
+
const measured = cloneParagraph(paragraph, paragraphWidth, false);
|
|
172
|
+
const natural = cloneParagraph(paragraph, NATURAL_WIDTH_PX, true);
|
|
173
|
+
paragraph.ownerDocument.body.append(measured, natural);
|
|
174
|
+
try {
|
|
175
|
+
const measuredSlots = Array.from(
|
|
176
|
+
measured.querySelectorAll(`.${editorClassNames.tabSlot}`)
|
|
177
|
+
);
|
|
178
|
+
const naturalSlots = Array.from(
|
|
179
|
+
natural.querySelectorAll(`.${editorClassNames.tabSlot}`)
|
|
180
|
+
);
|
|
181
|
+
for (let pass = 0; pass < MAX_LAYOUT_PASSES; pass += 1) {
|
|
182
|
+
let changed = false;
|
|
183
|
+
measuredSlots.forEach((slot, index) => {
|
|
184
|
+
const actual = actualSlots[index];
|
|
185
|
+
const naturalSlot = naturalSlots[index];
|
|
186
|
+
if (!actual || !naturalSlot) return;
|
|
187
|
+
const position = slotPosition(actual);
|
|
188
|
+
if (position === null) return;
|
|
189
|
+
const currentPt = currentPositionPt(measured, slot);
|
|
190
|
+
const segment = segmentMetrics(naturalSlot, natural);
|
|
191
|
+
const target = nextTabTarget(
|
|
192
|
+
currentPt,
|
|
193
|
+
format.tabStops ?? [],
|
|
194
|
+
defaultStopPt
|
|
195
|
+
);
|
|
196
|
+
const widthPx = tabAdvancePt(currentPt, target, segment) * PX_PER_PT;
|
|
197
|
+
const previous = numeric(
|
|
198
|
+
slot.style.getPropertyValue(editorCssVariables.tabWidth)
|
|
199
|
+
);
|
|
200
|
+
if (!slot.hasAttribute("data-tab-layout") || Math.abs(previous - widthPx) > WIDTH_EPSILON_PX) {
|
|
201
|
+
changed = true;
|
|
202
|
+
}
|
|
203
|
+
widths.set(position, widthPx);
|
|
204
|
+
applyWidth(slot, widthPx);
|
|
205
|
+
});
|
|
206
|
+
if (!changed) break;
|
|
207
|
+
}
|
|
208
|
+
} finally {
|
|
209
|
+
measured.remove();
|
|
210
|
+
natural.remove();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function sameWidths(left, right) {
|
|
214
|
+
if (left.size !== right.size) return false;
|
|
215
|
+
for (const [position, width] of left) {
|
|
216
|
+
const other = right.get(position);
|
|
217
|
+
if (other === void 0 || Math.abs(other - width) > WIDTH_EPSILON_PX) {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
function measureTabs(view, cache, generation) {
|
|
224
|
+
const scale = scaleOf(view);
|
|
225
|
+
const defaultStopPt = documentDefaultTabStopPt(view.state);
|
|
226
|
+
const widths = new Map(tabWidths(view.state));
|
|
227
|
+
const paragraphs = /* @__PURE__ */ new Map();
|
|
228
|
+
for (const slot of view.dom.querySelectorAll(
|
|
229
|
+
`.${editorClassNames.tabSlot}`
|
|
230
|
+
)) {
|
|
231
|
+
const closest = slot.closest(`.${editorClassNames.paragraph}`);
|
|
232
|
+
if (!closest) continue;
|
|
233
|
+
const paragraph = closest;
|
|
234
|
+
const entries = paragraphs.get(paragraph);
|
|
235
|
+
if (entries) entries.push(slot);
|
|
236
|
+
else paragraphs.set(paragraph, [slot]);
|
|
237
|
+
}
|
|
238
|
+
for (const [paragraph, slots] of paragraphs) {
|
|
239
|
+
const context = paragraphLayoutContext(view, slots[0]);
|
|
240
|
+
if (!context) continue;
|
|
241
|
+
const paragraphWidth = paragraph.getBoundingClientRect().width / scale;
|
|
242
|
+
const cached = cache.get(context.node);
|
|
243
|
+
if (cached && cached.generation === generation && cached.defaultStopPt === defaultStopPt && cached.scale === scale && Math.abs(cached.paragraphWidth - paragraphWidth) <= WIDTH_EPSILON_PX && cached.widths.length === slots.length) {
|
|
244
|
+
slots.forEach((slot, index) => {
|
|
245
|
+
const position = slotPosition(slot);
|
|
246
|
+
const width = cached.widths[index];
|
|
247
|
+
if (position !== null && width !== void 0)
|
|
248
|
+
widths.set(position, width);
|
|
249
|
+
});
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
measureParagraph(
|
|
253
|
+
paragraph,
|
|
254
|
+
slots,
|
|
255
|
+
context.format,
|
|
256
|
+
defaultStopPt,
|
|
257
|
+
scale,
|
|
258
|
+
widths
|
|
259
|
+
);
|
|
260
|
+
cache.set(context.node, {
|
|
261
|
+
defaultStopPt,
|
|
262
|
+
generation,
|
|
263
|
+
paragraphWidth,
|
|
264
|
+
scale,
|
|
265
|
+
widths: slots.map((slot) => {
|
|
266
|
+
const position = slotPosition(slot);
|
|
267
|
+
return position === null ? 0 : widths.get(position) ?? 0;
|
|
268
|
+
})
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
return widths;
|
|
272
|
+
}
|
|
273
|
+
function tabLayout() {
|
|
274
|
+
return new Plugin({
|
|
275
|
+
view(view) {
|
|
276
|
+
let frame = 0;
|
|
277
|
+
let live = true;
|
|
278
|
+
let generation = 0;
|
|
279
|
+
const cache = /* @__PURE__ */ new WeakMap();
|
|
280
|
+
const ownerWindow = view.dom.ownerDocument.defaultView;
|
|
281
|
+
const requestFrame = ownerWindow?.requestAnimationFrame.bind(ownerWindow);
|
|
282
|
+
const cancelFrame = ownerWindow?.cancelAnimationFrame.bind(ownerWindow);
|
|
283
|
+
const schedule = () => {
|
|
284
|
+
if (!live || frame !== 0) return;
|
|
285
|
+
frame = (requestFrame ?? requestAnimationFrame)(() => {
|
|
286
|
+
frame = 0;
|
|
287
|
+
const widths = measureTabs(view, cache, generation);
|
|
288
|
+
if (sameWidths(widths, tabWidths(view.state))) return;
|
|
289
|
+
view.dispatch(
|
|
290
|
+
setTabWidths(view.state.tr, widths).setMeta("addToHistory", false)
|
|
291
|
+
);
|
|
292
|
+
});
|
|
293
|
+
};
|
|
294
|
+
const ResizeObserverClass = ownerWindow?.ResizeObserver;
|
|
295
|
+
const resize = typeof ResizeObserverClass === "undefined" ? null : new ResizeObserverClass(schedule);
|
|
296
|
+
resize?.observe(view.dom);
|
|
297
|
+
const onResize = () => schedule();
|
|
298
|
+
ownerWindow?.addEventListener("resize", onResize);
|
|
299
|
+
const fonts = view.dom.ownerDocument.fonts;
|
|
300
|
+
const onFontsChanged = () => {
|
|
301
|
+
generation += 1;
|
|
302
|
+
schedule();
|
|
303
|
+
};
|
|
304
|
+
fonts?.addEventListener("loadingdone", onFontsChanged);
|
|
305
|
+
fonts?.addEventListener("loadingerror", onFontsChanged);
|
|
306
|
+
fonts?.ready.then(onFontsChanged);
|
|
307
|
+
schedule();
|
|
308
|
+
return {
|
|
309
|
+
update(next, previous) {
|
|
310
|
+
if (!next.state.doc.eq(previous.doc)) schedule();
|
|
311
|
+
},
|
|
312
|
+
destroy() {
|
|
313
|
+
live = false;
|
|
314
|
+
if (frame !== 0) (cancelFrame ?? cancelAnimationFrame)(frame);
|
|
315
|
+
resize?.disconnect();
|
|
316
|
+
ownerWindow?.removeEventListener("resize", onResize);
|
|
317
|
+
fonts?.removeEventListener("loadingdone", onFontsChanged);
|
|
318
|
+
fonts?.removeEventListener("loadingerror", onFontsChanged);
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
export {
|
|
325
|
+
nextTabTarget,
|
|
326
|
+
tabAdvancePt,
|
|
327
|
+
tabLayout
|
|
328
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Plugin } from "prosemirror-state";
|
|
2
|
+
type TabSide = "before" | "after";
|
|
3
|
+
/** Maps the visual half of a tab to its logical side in the text flow. */
|
|
4
|
+
export declare function tabSideAtX(left: number, right: number, clientX: number, direction: "ltr" | "rtl"): TabSide;
|
|
5
|
+
/** Gives a tab deterministic click and drag selection boundaries. */
|
|
6
|
+
export declare function tabPointer(): Plugin;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// src/editor/plugins/tabPointer.ts
|
|
2
|
+
import { Plugin, TextSelection } from "prosemirror-state";
|
|
3
|
+
import { editorClassNames } from "../../styles/classNames.js";
|
|
4
|
+
function tabSideAtX(left, right, clientX, direction) {
|
|
5
|
+
const leftHalf = clientX < (left + right) / 2;
|
|
6
|
+
return leftHalf === (direction === "ltr") ? "before" : "after";
|
|
7
|
+
}
|
|
8
|
+
function tabSlot(target) {
|
|
9
|
+
if (!target || typeof target.closest !== "function") return null;
|
|
10
|
+
return target.closest(
|
|
11
|
+
`.${editorClassNames.tabSlot}`
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
function tabPosition(view, slot, clientX) {
|
|
15
|
+
const paragraph = slot.closest(`.${editorClassNames.paragraph}`);
|
|
16
|
+
const direction = paragraph && paragraph.ownerDocument.defaultView?.getComputedStyle(paragraph).direction === "rtl" ? "rtl" : "ltr";
|
|
17
|
+
const rect = slot.getBoundingClientRect();
|
|
18
|
+
const side = tabSideAtX(rect.left, rect.right, clientX, direction);
|
|
19
|
+
return view.posAtDOM(slot, side === "before" ? 0 : slot.childNodes.length);
|
|
20
|
+
}
|
|
21
|
+
function pointerPosition(view, event) {
|
|
22
|
+
const direct = tabSlot(event.target);
|
|
23
|
+
const ownerDocument = view.dom.ownerDocument;
|
|
24
|
+
const pointed = typeof ownerDocument.elementFromPoint === "function" ? ownerDocument.elementFromPoint(event.clientX, event.clientY) : null;
|
|
25
|
+
const slot = direct ?? tabSlot(pointed);
|
|
26
|
+
if (slot && view.dom.contains(slot)) {
|
|
27
|
+
return tabPosition(view, slot, event.clientX);
|
|
28
|
+
}
|
|
29
|
+
return view.posAtCoords({ left: event.clientX, top: event.clientY })?.pos ?? null;
|
|
30
|
+
}
|
|
31
|
+
function select(view, anchor, head) {
|
|
32
|
+
const size = view.state.doc.content.size;
|
|
33
|
+
const boundedAnchor = Math.max(0, Math.min(anchor, size));
|
|
34
|
+
const boundedHead = Math.max(0, Math.min(head, size));
|
|
35
|
+
const selection = TextSelection.between(
|
|
36
|
+
view.state.doc.resolve(boundedAnchor),
|
|
37
|
+
view.state.doc.resolve(boundedHead)
|
|
38
|
+
);
|
|
39
|
+
view.dispatch(view.state.tr.setSelection(selection));
|
|
40
|
+
}
|
|
41
|
+
function tabPointer() {
|
|
42
|
+
let stopDragging = null;
|
|
43
|
+
return new Plugin({
|
|
44
|
+
props: {
|
|
45
|
+
handleDOMEvents: {
|
|
46
|
+
mousedown(view, event) {
|
|
47
|
+
if (event.button !== 0) return false;
|
|
48
|
+
const slot = tabSlot(event.target);
|
|
49
|
+
if (!slot || !view.dom.contains(slot)) return false;
|
|
50
|
+
event.preventDefault();
|
|
51
|
+
view.focus();
|
|
52
|
+
const head = tabPosition(view, slot, event.clientX);
|
|
53
|
+
const anchor = event.shiftKey ? view.state.selection.anchor : head;
|
|
54
|
+
select(view, anchor, head);
|
|
55
|
+
stopDragging?.();
|
|
56
|
+
const onMove = (move) => {
|
|
57
|
+
const next = pointerPosition(view, move);
|
|
58
|
+
if (next === null) return;
|
|
59
|
+
move.preventDefault();
|
|
60
|
+
select(view, anchor, next);
|
|
61
|
+
};
|
|
62
|
+
const onUp = (up) => {
|
|
63
|
+
const next = pointerPosition(view, up);
|
|
64
|
+
if (next !== null) select(view, anchor, next);
|
|
65
|
+
stopDragging?.();
|
|
66
|
+
};
|
|
67
|
+
const ownerDocument = view.dom.ownerDocument;
|
|
68
|
+
stopDragging = () => {
|
|
69
|
+
ownerDocument.removeEventListener("mousemove", onMove, true);
|
|
70
|
+
ownerDocument.removeEventListener("mouseup", onUp, true);
|
|
71
|
+
stopDragging = null;
|
|
72
|
+
};
|
|
73
|
+
ownerDocument.addEventListener("mousemove", onMove, true);
|
|
74
|
+
ownerDocument.addEventListener("mouseup", onUp, true);
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
view: (view) => {
|
|
80
|
+
const ownerWindow = view.dom.ownerDocument.defaultView;
|
|
81
|
+
const onBlur = () => stopDragging?.();
|
|
82
|
+
ownerWindow?.addEventListener("blur", onBlur);
|
|
83
|
+
return {
|
|
84
|
+
destroy() {
|
|
85
|
+
ownerWindow?.removeEventListener("blur", onBlur);
|
|
86
|
+
stopDragging?.();
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
export {
|
|
93
|
+
tabPointer,
|
|
94
|
+
tabSideAtX
|
|
95
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Right-clicking in a table cell brings up our own menu.
|
|
3
|
+
*
|
|
4
|
+
* This file does two things: block the browser's default menu, and report where the menu should
|
|
5
|
+
* stand. Drawing the menu is the React side's job.
|
|
6
|
+
*
|
|
7
|
+
* Only the click that asks for the row and column actions reaches this plugin: a click on
|
|
8
|
+
* selected text is taken first by the text menu (`editor/textContextMenu`), which stands ahead
|
|
9
|
+
* of this one and hands back a click with nothing selected inside a cell.
|
|
10
|
+
*/
|
|
11
|
+
import { type Command, type EditorState, Plugin } from "prosemirror-state";
|
|
12
|
+
/** Where the menu should stand (viewport coordinates) */
|
|
13
|
+
export interface TableMenuAnchor {
|
|
14
|
+
clientX: number;
|
|
15
|
+
clientY: number;
|
|
16
|
+
}
|
|
17
|
+
/** The anchor of the currently open table menu. Null when it is closed */
|
|
18
|
+
export declare function tableMenuAnchor(state: EditorState): TableMenuAnchor | null;
|
|
19
|
+
/** Closes the menu. Called on running an item, Escape, clicking outside, and scrolling */
|
|
20
|
+
export declare const closeTableMenu: Command;
|
|
21
|
+
export declare function tableContextMenu(): Plugin<TableMenuAnchor | null>;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// src/editor/plugins/tableContextMenu.ts
|
|
2
|
+
import {
|
|
3
|
+
Plugin,
|
|
4
|
+
PluginKey,
|
|
5
|
+
TextSelection
|
|
6
|
+
} from "prosemirror-state";
|
|
7
|
+
var CLOSE = "close";
|
|
8
|
+
var menuKey = new PluginKey("docxEditorTableMenu");
|
|
9
|
+
function isRecord(value) {
|
|
10
|
+
return typeof value === "object" && value !== null;
|
|
11
|
+
}
|
|
12
|
+
function toAnchor(value) {
|
|
13
|
+
if (!isRecord(value)) return null;
|
|
14
|
+
const { clientX, clientY } = value;
|
|
15
|
+
if (typeof clientX !== "number" || typeof clientY !== "number") return null;
|
|
16
|
+
return { clientX, clientY };
|
|
17
|
+
}
|
|
18
|
+
function tableMenuAnchor(state) {
|
|
19
|
+
return menuKey.getState(state) ?? null;
|
|
20
|
+
}
|
|
21
|
+
var closeTableMenu = (state, dispatch) => {
|
|
22
|
+
if (tableMenuAnchor(state) === null) return false;
|
|
23
|
+
if (dispatch) dispatch(state.tr.setMeta(menuKey, CLOSE));
|
|
24
|
+
return true;
|
|
25
|
+
};
|
|
26
|
+
function cellStart($pos) {
|
|
27
|
+
for (let depth = $pos.depth; depth > 0; depth -= 1) {
|
|
28
|
+
if ($pos.node(depth).type.spec.tableRole === "cell") {
|
|
29
|
+
return $pos.before(depth);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
function cellAtEvent(view, target) {
|
|
35
|
+
const node = target instanceof Node ? target : null;
|
|
36
|
+
const element = node instanceof Element ? node : node?.parentElement;
|
|
37
|
+
const cell = element?.closest("td");
|
|
38
|
+
if (!cell || !view.dom.contains(cell)) return null;
|
|
39
|
+
return cellStart(view.state.doc.resolve(view.posAtDOM(cell, 0)));
|
|
40
|
+
}
|
|
41
|
+
function selectionCovers(state, cellPos) {
|
|
42
|
+
return state.selection.ranges.some(
|
|
43
|
+
(range) => cellStart(range.$from) === cellPos
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
function openMenu(view, cellPos, anchor) {
|
|
47
|
+
const state = view.state;
|
|
48
|
+
const tr = state.tr.setMeta(menuKey, anchor);
|
|
49
|
+
if (!selectionCovers(state, cellPos)) {
|
|
50
|
+
tr.setSelection(TextSelection.near(tr.doc.resolve(cellPos + 1)));
|
|
51
|
+
}
|
|
52
|
+
view.dispatch(tr);
|
|
53
|
+
}
|
|
54
|
+
function tableContextMenu() {
|
|
55
|
+
return new Plugin({
|
|
56
|
+
key: menuKey,
|
|
57
|
+
state: {
|
|
58
|
+
init: () => null,
|
|
59
|
+
apply: (tr, current) => {
|
|
60
|
+
const meta = tr.getMeta(menuKey);
|
|
61
|
+
if (meta === CLOSE) return null;
|
|
62
|
+
const anchor = toAnchor(meta);
|
|
63
|
+
if (anchor) return anchor;
|
|
64
|
+
return tr.docChanged ? null : current;
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
props: {
|
|
68
|
+
handleDOMEvents: {
|
|
69
|
+
contextmenu(view, event) {
|
|
70
|
+
if (!view.editable) return false;
|
|
71
|
+
const cellPos = cellAtEvent(view, event.target);
|
|
72
|
+
if (cellPos === null) return false;
|
|
73
|
+
event.preventDefault();
|
|
74
|
+
openMenu(view, cellPos, {
|
|
75
|
+
clientX: event.clientX,
|
|
76
|
+
clientY: event.clientY
|
|
77
|
+
});
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
export {
|
|
85
|
+
closeTableMenu,
|
|
86
|
+
tableContextMenu,
|
|
87
|
+
tableMenuAnchor
|
|
88
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replaces the native text context menu and moves the selection to the clicked position. Unselected
|
|
3
|
+
* table cells are left to the table context-menu plugin.
|
|
4
|
+
*/
|
|
5
|
+
import { type Command, type EditorState, Plugin } from "prosemirror-state";
|
|
6
|
+
/** Where the menu should stand (viewport coordinates) */
|
|
7
|
+
export interface TextMenuAnchor {
|
|
8
|
+
clientX: number;
|
|
9
|
+
clientY: number;
|
|
10
|
+
}
|
|
11
|
+
/** The anchor of the currently open text menu. Null when it is closed */
|
|
12
|
+
export declare function textMenuAnchor(state: EditorState): TextMenuAnchor | null;
|
|
13
|
+
/** Closes the menu. Called on running an item, Escape, clicking outside, and scrolling */
|
|
14
|
+
export declare const closeTextMenu: Command;
|
|
15
|
+
export declare function textContextMenu(): Plugin<TextMenuAnchor | null>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// src/editor/plugins/textContextMenu.ts
|
|
2
|
+
import {
|
|
3
|
+
Plugin,
|
|
4
|
+
PluginKey,
|
|
5
|
+
TextSelection
|
|
6
|
+
} from "prosemirror-state";
|
|
7
|
+
import { CellSelection } from "prosemirror-tables";
|
|
8
|
+
var CLOSE = "close";
|
|
9
|
+
var menuKey = new PluginKey("docxEditorTextMenu");
|
|
10
|
+
function isRecord(value) {
|
|
11
|
+
return typeof value === "object" && value !== null;
|
|
12
|
+
}
|
|
13
|
+
function toAnchor(value) {
|
|
14
|
+
if (!isRecord(value)) return null;
|
|
15
|
+
const { clientX, clientY } = value;
|
|
16
|
+
if (typeof clientX !== "number" || typeof clientY !== "number") return null;
|
|
17
|
+
return { clientX, clientY };
|
|
18
|
+
}
|
|
19
|
+
function textMenuAnchor(state) {
|
|
20
|
+
return menuKey.getState(state) ?? null;
|
|
21
|
+
}
|
|
22
|
+
var closeTextMenu = (state, dispatch) => {
|
|
23
|
+
if (textMenuAnchor(state) === null) return false;
|
|
24
|
+
if (dispatch) dispatch(state.tr.setMeta(menuKey, CLOSE));
|
|
25
|
+
return true;
|
|
26
|
+
};
|
|
27
|
+
function isInEditor(view, target) {
|
|
28
|
+
return target instanceof Node && view.dom.contains(target);
|
|
29
|
+
}
|
|
30
|
+
function isInTableCell(view, target) {
|
|
31
|
+
const node = target instanceof Node ? target : null;
|
|
32
|
+
const element = node instanceof Element ? node : node?.parentElement;
|
|
33
|
+
const cell = element?.closest("td");
|
|
34
|
+
return !!cell && view.dom.contains(cell);
|
|
35
|
+
}
|
|
36
|
+
function clickedSpot(view, event) {
|
|
37
|
+
const found = view.posAtCoords({ left: event.clientX, top: event.clientY });
|
|
38
|
+
return found?.pos ?? view.state.selection.head;
|
|
39
|
+
}
|
|
40
|
+
function isInSelection(state, spot) {
|
|
41
|
+
const selection = state.selection;
|
|
42
|
+
return !selection.empty && selection.ranges.some(
|
|
43
|
+
(range) => range.$from.pos <= spot && spot <= range.$to.pos
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
function forTableMenu(view, target, spot) {
|
|
47
|
+
const aboutCells = view.state.selection instanceof CellSelection || !isInSelection(view.state, spot);
|
|
48
|
+
return aboutCells && isInTableCell(view, target);
|
|
49
|
+
}
|
|
50
|
+
function openMenu(state, event, spot) {
|
|
51
|
+
const tr = state.tr.setMeta(menuKey, {
|
|
52
|
+
clientX: event.clientX,
|
|
53
|
+
clientY: event.clientY
|
|
54
|
+
});
|
|
55
|
+
if (isInSelection(state, spot)) return tr;
|
|
56
|
+
return tr.setSelection(TextSelection.near(tr.doc.resolve(spot)));
|
|
57
|
+
}
|
|
58
|
+
function textContextMenu() {
|
|
59
|
+
return new Plugin({
|
|
60
|
+
key: menuKey,
|
|
61
|
+
state: {
|
|
62
|
+
init: () => null,
|
|
63
|
+
apply: (tr, current) => {
|
|
64
|
+
const meta = tr.getMeta(menuKey);
|
|
65
|
+
if (meta === CLOSE) return null;
|
|
66
|
+
const anchor = toAnchor(meta);
|
|
67
|
+
if (anchor) return anchor;
|
|
68
|
+
return tr.docChanged ? null : current;
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
props: {
|
|
72
|
+
handleDOMEvents: {
|
|
73
|
+
contextmenu(view, event) {
|
|
74
|
+
if (!view.editable) return false;
|
|
75
|
+
if (!isInEditor(view, event.target)) return false;
|
|
76
|
+
const spot = clickedSpot(view, event);
|
|
77
|
+
if (forTableMenu(view, event.target, spot)) return false;
|
|
78
|
+
event.preventDefault();
|
|
79
|
+
view.dispatch(openMenu(view.state, event, spot));
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
export {
|
|
87
|
+
closeTextMenu,
|
|
88
|
+
textContextMenu,
|
|
89
|
+
textMenuAnchor
|
|
90
|
+
};
|