@meta-1/editor 0.0.29 → 1.0.1
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/package.json +47 -66
- package/src/components/notion-like/notion-like-editor-toolbar-floating.tsx +181 -0
- package/src/components/tiptap-extension/list-normalization-extension.ts +112 -0
- package/src/components/tiptap-extension/node-alignment-extension.ts +285 -0
- package/src/components/tiptap-extension/node-background-extension.ts +150 -0
- package/src/components/tiptap-extension/ui-state-extension.ts +97 -0
- package/src/components/tiptap-icons/add-col-left-icon.tsx +30 -0
- package/src/components/tiptap-icons/add-col-right-icon.tsx +30 -0
- package/src/components/tiptap-icons/add-row-bottom-icon.tsx +30 -0
- package/src/components/tiptap-icons/add-row-top-icon.tsx +30 -0
- package/src/components/tiptap-icons/ai-sparkles-icon.tsx +32 -0
- package/src/components/tiptap-icons/align-bottom-icon.tsx +28 -0
- package/src/components/tiptap-icons/align-center-icon.tsx +38 -0
- package/src/components/tiptap-icons/align-center-vertical-icon.tsx +34 -0
- package/src/components/tiptap-icons/align-end-vertical-icon.tsx +34 -0
- package/src/components/tiptap-icons/align-justify-icon.tsx +38 -0
- package/src/components/tiptap-icons/align-left-icon.tsx +38 -0
- package/src/components/tiptap-icons/align-middle-icon.tsx +55 -0
- package/src/components/tiptap-icons/align-right-icon.tsx +38 -0
- package/src/components/tiptap-icons/align-start-vertical-icon.tsx +32 -0
- package/src/components/tiptap-icons/align-top-icon.tsx +28 -0
- package/src/components/tiptap-icons/alignment-icon.tsx +72 -0
- package/src/components/tiptap-icons/arrow-down-a-z-icon.tsx +34 -0
- package/src/components/tiptap-icons/arrow-down-icon.tsx +24 -0
- package/src/components/tiptap-icons/arrow-down-to-line-icon.tsx +28 -0
- package/src/components/tiptap-icons/arrow-down-z-a-icon.tsx +34 -0
- package/src/components/tiptap-icons/arrow-left-icon.tsx +24 -0
- package/src/components/tiptap-icons/arrow-right-icon.tsx +26 -0
- package/src/components/tiptap-icons/arrow-up-icon.tsx +26 -0
- package/src/components/tiptap-icons/at-sign-icon.tsx +26 -0
- package/src/components/tiptap-icons/ban-icon.tsx +26 -0
- package/src/components/tiptap-icons/blockquote-icon.tsx +44 -0
- package/src/components/tiptap-icons/bold-icon.tsx +26 -0
- package/src/components/tiptap-icons/check-ai-icon.tsx +32 -0
- package/src/components/tiptap-icons/check-icon.tsx +26 -0
- package/src/components/tiptap-icons/chevron-down-icon.tsx +26 -0
- package/src/components/tiptap-icons/chevron-right-icon.tsx +26 -0
- package/src/components/tiptap-icons/clipboard-icon.tsx +24 -0
- package/src/components/tiptap-icons/close-icon.tsx +24 -0
- package/src/components/tiptap-icons/code-block-icon.tsx +38 -0
- package/src/components/tiptap-icons/code2-icon.tsx +32 -0
- package/src/components/tiptap-icons/complete-sentence-icon.tsx +44 -0
- package/src/components/tiptap-icons/copy-icon.tsx +32 -0
- package/src/components/tiptap-icons/corner-down-left-icon.tsx +26 -0
- package/src/components/tiptap-icons/external-link-icon.tsx +28 -0
- package/src/components/tiptap-icons/grip-4-icon.tsx +24 -0
- package/src/components/tiptap-icons/grip-vertical-icon.tsx +44 -0
- package/src/components/tiptap-icons/heading-five-icon.tsx +28 -0
- package/src/components/tiptap-icons/heading-four-icon.tsx +28 -0
- package/src/components/tiptap-icons/heading-one-icon.tsx +28 -0
- package/src/components/tiptap-icons/heading-six-icon.tsx +30 -0
- package/src/components/tiptap-icons/heading-three-icon.tsx +36 -0
- package/src/components/tiptap-icons/heading-two-icon.tsx +28 -0
- package/src/components/tiptap-icons/highlighter-icon.tsx +26 -0
- package/src/components/tiptap-icons/image-caption-icon.tsx +38 -0
- package/src/components/tiptap-icons/image-icon.tsx +26 -0
- package/src/components/tiptap-icons/image-plus-icon.tsx +26 -0
- package/src/components/tiptap-icons/italic-icon.tsx +24 -0
- package/src/components/tiptap-icons/languages-icon.tsx +34 -0
- package/src/components/tiptap-icons/link-icon.tsx +28 -0
- package/src/components/tiptap-icons/list-icon.tsx +56 -0
- package/src/components/tiptap-icons/list-ordered-icon.tsx +56 -0
- package/src/components/tiptap-icons/list-todo-icon.tsx +50 -0
- package/src/components/tiptap-icons/message-square-icon.tsx +26 -0
- package/src/components/tiptap-icons/message-square-plus-icon.tsx +32 -0
- package/src/components/tiptap-icons/mic-ai-icon.tsx +34 -0
- package/src/components/tiptap-icons/minus-icon.tsx +26 -0
- package/src/components/tiptap-icons/moon-star-icon.tsx +30 -0
- package/src/components/tiptap-icons/more-vertical-icon.tsx +38 -0
- package/src/components/tiptap-icons/move-horizontal-icon.tsx +24 -0
- package/src/components/tiptap-icons/paint-bucket-icon.tsx +32 -0
- package/src/components/tiptap-icons/plus-icon.tsx +24 -0
- package/src/components/tiptap-icons/plus-small-icon.tsx +24 -0
- package/src/components/tiptap-icons/redo2-icon.tsx +26 -0
- package/src/components/tiptap-icons/refresh-ai-icon.tsx +34 -0
- package/src/components/tiptap-icons/refresh-ccw-icon.tsx +28 -0
- package/src/components/tiptap-icons/repeat-2-icon.tsx +26 -0
- package/src/components/tiptap-icons/rotate-ccw-icon.tsx +24 -0
- package/src/components/tiptap-icons/simplify-2-icon.tsx +24 -0
- package/src/components/tiptap-icons/smile-ai-icon.tsx +38 -0
- package/src/components/tiptap-icons/smile-plus-icon.tsx +26 -0
- package/src/components/tiptap-icons/square-x-icon.tsx +26 -0
- package/src/components/tiptap-icons/stop-circle-2-icon.tsx +26 -0
- package/src/components/tiptap-icons/strike-icon.tsx +28 -0
- package/src/components/tiptap-icons/subscript-icon.tsx +38 -0
- package/src/components/tiptap-icons/summarize-text-icon.tsx +36 -0
- package/src/components/tiptap-icons/sun-icon.tsx +58 -0
- package/src/components/tiptap-icons/superscript-icon.tsx +38 -0
- package/src/components/tiptap-icons/table-cell-merge-icon.tsx +44 -0
- package/src/components/tiptap-icons/table-cell-split-icon.tsx +44 -0
- package/src/components/tiptap-icons/table-column-icon.tsx +26 -0
- package/src/components/tiptap-icons/table-header-column-icon.tsx +28 -0
- package/src/components/tiptap-icons/table-header-row-icon.tsx +26 -0
- package/src/components/tiptap-icons/table-icon.tsx +26 -0
- package/src/components/tiptap-icons/table-row-icon.tsx +26 -0
- package/src/components/tiptap-icons/text-color-small-icon.tsx +26 -0
- package/src/components/tiptap-icons/text-extend-icon.tsx +36 -0
- package/src/components/tiptap-icons/text-reduce-icon.tsx +32 -0
- package/src/components/tiptap-icons/trash-icon.tsx +26 -0
- package/src/components/tiptap-icons/type-icon.tsx +24 -0
- package/src/components/tiptap-icons/underline-icon.tsx +26 -0
- package/src/components/tiptap-icons/undo2-icon.tsx +26 -0
- package/src/components/tiptap-icons/x-icon.tsx +24 -0
- package/src/components/tiptap-node/blockquote-node/blockquote-node.css +18 -0
- package/src/components/tiptap-node/code-block-node/code-block-node.css +24 -0
- package/src/components/tiptap-node/heading-node/heading-node.css +33 -0
- package/src/components/tiptap-node/horizontal-rule-node/horizontal-rule-node-extension.ts +11 -0
- package/src/components/tiptap-node/horizontal-rule-node/horizontal-rule-node.css +12 -0
- package/src/components/tiptap-node/image-node/image-node-extension.ts +169 -0
- package/src/components/tiptap-node/image-node/image-node-floating.tsx +41 -0
- package/src/components/tiptap-node/image-node/image-node-view.css +60 -0
- package/src/components/tiptap-node/image-node/image-node-view.tsx +316 -0
- package/src/components/tiptap-node/image-node/image-node.css +28 -0
- package/src/components/tiptap-node/image-upload-node/image-upload-node-extension.ts +155 -0
- package/src/components/tiptap-node/image-upload-node/image-upload-node.css +141 -0
- package/src/components/tiptap-node/image-upload-node/image-upload-node.tsx +513 -0
- package/src/components/tiptap-node/image-upload-node/index.tsx +1 -0
- package/src/components/tiptap-node/list-node/list-node.css +127 -0
- package/src/components/tiptap-node/paragraph-node/paragraph-node.css +198 -0
- package/src/components/tiptap-node/table-node/extensions/table-handle/helpers/create-image.ts +273 -0
- package/src/components/tiptap-node/table-node/extensions/table-handle/index.ts +2 -0
- package/src/components/tiptap-node/table-node/extensions/table-handle/table-handle-plugin.ts +718 -0
- package/src/components/tiptap-node/table-node/extensions/table-handle/table-handle.ts +48 -0
- package/src/components/tiptap-node/table-node/extensions/table-node-extension.ts +226 -0
- package/src/components/tiptap-node/table-node/hooks/use-table-handle-state.ts +66 -0
- package/src/components/tiptap-node/table-node/lib/tiptap-table-utils.ts +1289 -0
- package/src/components/tiptap-node/table-node/styles/prosemirror-table.css +35 -0
- package/src/components/tiptap-node/table-node/styles/table-node.css +158 -0
- package/src/components/tiptap-node/table-node/ui/table-add-row-column-button/index.tsx +2 -0
- package/src/components/tiptap-node/table-node/ui/table-add-row-column-button/table-add-row-column-button.tsx +94 -0
- package/src/components/tiptap-node/table-node/ui/table-add-row-column-button/use-table-add-row-column.ts +325 -0
- package/src/components/tiptap-node/table-node/ui/table-align-cell-button/index.tsx +2 -0
- package/src/components/tiptap-node/table-node/ui/table-align-cell-button/table-align-cell-button.tsx +129 -0
- package/src/components/tiptap-node/table-node/ui/table-align-cell-button/use-table-align-cell.ts +528 -0
- package/src/components/tiptap-node/table-node/ui/table-alignment-menu/index.tsx +1 -0
- package/src/components/tiptap-node/table-node/ui/table-alignment-menu/table-alignment-menu.tsx +154 -0
- package/src/components/tiptap-node/table-node/ui/table-cell-handle-menu/index.tsx +1 -0
- package/src/components/tiptap-node/table-node/ui/table-cell-handle-menu/table-cell-handle-menu.css +62 -0
- package/src/components/tiptap-node/table-node/ui/table-cell-handle-menu/table-cell-handle-menu.tsx +212 -0
- package/src/components/tiptap-node/table-node/ui/table-clear-row-column-content-button/index.tsx +2 -0
- package/src/components/tiptap-node/table-node/ui/table-clear-row-column-content-button/table-clear-row-column-content-button.tsx +101 -0
- package/src/components/tiptap-node/table-node/ui/table-clear-row-column-content-button/use-table-clear-row-column-content.ts +423 -0
- package/src/components/tiptap-node/table-node/ui/table-delete-row-column-button/index.tsx +2 -0
- package/src/components/tiptap-node/table-node/ui/table-delete-row-column-button/table-delete-row-column-button.tsx +100 -0
- package/src/components/tiptap-node/table-node/ui/table-delete-row-column-button/use-table-delete-row-column.ts +243 -0
- package/src/components/tiptap-node/table-node/ui/table-duplicate-row-column-button/index.tsx +2 -0
- package/src/components/tiptap-node/table-node/ui/table-duplicate-row-column-button/table-duplicate-row-column-button.tsx +92 -0
- package/src/components/tiptap-node/table-node/ui/table-duplicate-row-column-button/use-table-duplicate-row-column.ts +357 -0
- package/src/components/tiptap-node/table-node/ui/table-extend-row-column-button/index.tsx +2 -0
- package/src/components/tiptap-node/table-node/ui/table-extend-row-column-button/table-extend-row-column-button.css +17 -0
- package/src/components/tiptap-node/table-node/ui/table-extend-row-column-button/table-extend-row-column-button.tsx +240 -0
- package/src/components/tiptap-node/table-node/ui/table-extend-row-column-button/use-table-extend-row-column.ts +118 -0
- package/src/components/tiptap-node/table-node/ui/table-fit-to-width-button/index.tsx +2 -0
- package/src/components/tiptap-node/table-node/ui/table-fit-to-width-button/table-fit-to-width-button.tsx +98 -0
- package/src/components/tiptap-node/table-node/ui/table-fit-to-width-button/use-table-fit-to-width.ts +223 -0
- package/src/components/tiptap-node/table-node/ui/table-handle/index.tsx +2 -0
- package/src/components/tiptap-node/table-node/ui/table-handle/table-handle.tsx +163 -0
- package/src/components/tiptap-node/table-node/ui/table-handle/use-table-handle-positioning.ts +255 -0
- package/src/components/tiptap-node/table-node/ui/table-handle-menu/index.tsx +1 -0
- package/src/components/tiptap-node/table-node/ui/table-handle-menu/table-handle-menu.css +39 -0
- package/src/components/tiptap-node/table-node/ui/table-handle-menu/table-handle-menu.tsx +681 -0
- package/src/components/tiptap-node/table-node/ui/table-header-row-column-button/index.tsx +2 -0
- package/src/components/tiptap-node/table-node/ui/table-header-row-column-button/table-header-row-column-button.tsx +99 -0
- package/src/components/tiptap-node/table-node/ui/table-header-row-column-button/use-table-header-row-column.ts +227 -0
- package/src/components/tiptap-node/table-node/ui/table-merge-split-cell-button/index.tsx +2 -0
- package/src/components/tiptap-node/table-node/ui/table-merge-split-cell-button/table-merge-split-cell-button.tsx +125 -0
- package/src/components/tiptap-node/table-node/ui/table-merge-split-cell-button/use-table-merge-split-cell.ts +267 -0
- package/src/components/tiptap-node/table-node/ui/table-move-row-column-button/index.tsx +2 -0
- package/src/components/tiptap-node/table-node/ui/table-move-row-column-button/table-move-row-column-button.tsx +123 -0
- package/src/components/tiptap-node/table-node/ui/table-move-row-column-button/use-table-move-row-column.ts +431 -0
- package/src/components/tiptap-node/table-node/ui/table-selection-overlay/index.tsx +1 -0
- package/src/components/tiptap-node/table-node/ui/table-selection-overlay/table-selection-overlay.tsx +483 -0
- package/src/components/tiptap-node/table-node/ui/table-selection-overlay/use-resize-overlay.ts +78 -0
- package/src/components/tiptap-node/table-node/ui/table-sort-row-column-button/index.tsx +2 -0
- package/src/components/tiptap-node/table-node/ui/table-sort-row-column-button/table-sort-row-column-button.tsx +100 -0
- package/src/components/tiptap-node/table-node/ui/table-sort-row-column-button/use-table-sort-row-column.ts +444 -0
- package/src/components/tiptap-node/table-node/ui/table-trigger-button/index.tsx +3 -0
- package/src/components/tiptap-node/table-node/ui/table-trigger-button/table-grid-selector.css +39 -0
- package/src/components/tiptap-node/table-node/ui/table-trigger-button/table-grid-selector.tsx +219 -0
- package/src/components/tiptap-node/table-node/ui/table-trigger-button/table-trigger-button.tsx +132 -0
- package/src/components/tiptap-node/table-node/ui/table-trigger-button/use-table-trigger.ts +166 -0
- package/src/components/tiptap-ui/blockquote-button/blockquote-button.tsx +125 -0
- package/src/components/tiptap-ui/blockquote-button/index.tsx +2 -0
- package/src/components/tiptap-ui/blockquote-button/use-blockquote.ts +246 -0
- package/src/components/tiptap-ui/code-block-button/code-block-button.tsx +100 -0
- package/src/components/tiptap-ui/code-block-button/index.tsx +2 -0
- package/src/components/tiptap-ui/code-block-button/use-code-block.ts +256 -0
- package/src/components/tiptap-ui/color-highlight-button/color-highlight-button.css +32 -0
- package/src/components/tiptap-ui/color-highlight-button/color-highlight-button.tsx +171 -0
- package/src/components/tiptap-ui/color-highlight-button/index.tsx +2 -0
- package/src/components/tiptap-ui/color-highlight-button/use-color-highlight.ts +296 -0
- package/src/components/tiptap-ui/color-highlight-popover/color-highlight-popover.tsx +211 -0
- package/src/components/tiptap-ui/color-highlight-popover/index.tsx +1 -0
- package/src/components/tiptap-ui/color-menu/color-menu.tsx +178 -0
- package/src/components/tiptap-ui/color-menu/index.tsx +1 -0
- package/src/components/tiptap-ui/color-text-button/color-text-button.css +31 -0
- package/src/components/tiptap-ui/color-text-button/color-text-button.tsx +150 -0
- package/src/components/tiptap-ui/color-text-button/index.tsx +2 -0
- package/src/components/tiptap-ui/color-text-button/use-color-text.ts +251 -0
- package/src/components/tiptap-ui/color-text-popover/color-text-popover.css +25 -0
- package/src/components/tiptap-ui/color-text-popover/color-text-popover.tsx +360 -0
- package/src/components/tiptap-ui/color-text-popover/index.tsx +2 -0
- package/src/components/tiptap-ui/color-text-popover/use-color-text-popover.ts +229 -0
- package/src/components/tiptap-ui/copy-anchor-link-button/copy-anchor-link-button.tsx +118 -0
- package/src/components/tiptap-ui/copy-anchor-link-button/index.tsx +3 -0
- package/src/components/tiptap-ui/copy-anchor-link-button/use-copy-anchor-link.ts +252 -0
- package/src/components/tiptap-ui/copy-anchor-link-button/use-scroll-to-hash.ts +128 -0
- package/src/components/tiptap-ui/copy-to-clipboard-button/copy-to-clipboard-button.tsx +116 -0
- package/src/components/tiptap-ui/copy-to-clipboard-button/index.tsx +2 -0
- package/src/components/tiptap-ui/copy-to-clipboard-button/use-copy-to-clipboard.ts +234 -0
- package/src/components/tiptap-ui/delete-node-button/delete-node-button.tsx +98 -0
- package/src/components/tiptap-ui/delete-node-button/index.tsx +2 -0
- package/src/components/tiptap-ui/delete-node-button/use-delete-node.ts +236 -0
- package/src/components/tiptap-ui/drag-context-menu/drag-context-menu-types.ts +28 -0
- package/src/components/tiptap-ui/drag-context-menu/drag-context-menu.css +17 -0
- package/src/components/tiptap-ui/drag-context-menu/drag-context-menu.tsx +413 -0
- package/src/components/tiptap-ui/drag-context-menu/index.tsx +2 -0
- package/src/components/tiptap-ui/duplicate-button/duplicate-button.tsx +114 -0
- package/src/components/tiptap-ui/duplicate-button/index.tsx +2 -0
- package/src/components/tiptap-ui/duplicate-button/use-duplicate.ts +208 -0
- package/src/components/tiptap-ui/emoji-dropdown-menu/emoji-dropdown-menu.tsx +103 -0
- package/src/components/tiptap-ui/emoji-dropdown-menu/index.tsx +1 -0
- package/src/components/tiptap-ui/emoji-menu/emoji-menu-utils.ts +36 -0
- package/src/components/tiptap-ui/emoji-menu/emoji-menu.css +30 -0
- package/src/components/tiptap-ui/emoji-menu/emoji-menu.tsx +142 -0
- package/src/components/tiptap-ui/emoji-menu/index.tsx +2 -0
- package/src/components/tiptap-ui/emoji-trigger-button/emoji-trigger-button.tsx +128 -0
- package/src/components/tiptap-ui/emoji-trigger-button/index.tsx +2 -0
- package/src/components/tiptap-ui/emoji-trigger-button/use-emoji-trigger.ts +315 -0
- package/src/components/tiptap-ui/heading-button/heading-button.tsx +127 -0
- package/src/components/tiptap-ui/heading-button/index.tsx +2 -0
- package/src/components/tiptap-ui/heading-button/use-heading.ts +321 -0
- package/src/components/tiptap-ui/image-align-button/image-align-button.tsx +114 -0
- package/src/components/tiptap-ui/image-align-button/index.tsx +2 -0
- package/src/components/tiptap-ui/image-align-button/use-image-align.ts +295 -0
- package/src/components/tiptap-ui/image-caption-button/image-caption-button.tsx +77 -0
- package/src/components/tiptap-ui/image-caption-button/index.tsx +2 -0
- package/src/components/tiptap-ui/image-caption-button/use-image-caption.ts +212 -0
- package/src/components/tiptap-ui/image-download-button/image-download-button.tsx +104 -0
- package/src/components/tiptap-ui/image-download-button/index.tsx +2 -0
- package/src/components/tiptap-ui/image-download-button/use-image-download.ts +364 -0
- package/src/components/tiptap-ui/image-upload-button/image-upload-button.tsx +133 -0
- package/src/components/tiptap-ui/image-upload-button/index.tsx +2 -0
- package/src/components/tiptap-ui/image-upload-button/use-image-upload.ts +192 -0
- package/src/components/tiptap-ui/link-popover/index.tsx +2 -0
- package/src/components/tiptap-ui/link-popover/link-popover.tsx +271 -0
- package/src/components/tiptap-ui/link-popover/use-link-popover.ts +286 -0
- package/src/components/tiptap-ui/list-button/index.tsx +2 -0
- package/src/components/tiptap-ui/list-button/list-button.tsx +123 -0
- package/src/components/tiptap-ui/list-button/use-list.ts +326 -0
- package/src/components/tiptap-ui/mark-button/index.tsx +2 -0
- package/src/components/tiptap-ui/mark-button/mark-button.tsx +110 -0
- package/src/components/tiptap-ui/mark-button/use-mark.ts +195 -0
- package/src/components/tiptap-ui/mention-dropdown-menu/index.tsx +1 -0
- package/src/components/tiptap-ui/mention-dropdown-menu/mention-dropdown-menu.tsx +212 -0
- package/src/components/tiptap-ui/mention-trigger-button/index.tsx +2 -0
- package/src/components/tiptap-ui/mention-trigger-button/mention-trigger-button.tsx +122 -0
- package/src/components/tiptap-ui/mention-trigger-button/use-mention-trigger.ts +339 -0
- package/src/components/tiptap-ui/move-node-button/index.tsx +2 -0
- package/src/components/tiptap-ui/move-node-button/move-node-button.tsx +120 -0
- package/src/components/tiptap-ui/move-node-button/use-move-node.ts +207 -0
- package/src/components/tiptap-ui/reset-all-formatting-button/index.tsx +2 -0
- package/src/components/tiptap-ui/reset-all-formatting-button/reset-all-formatting-button.tsx +126 -0
- package/src/components/tiptap-ui/reset-all-formatting-button/use-reset-all-formatting.ts +250 -0
- package/src/components/tiptap-ui/slash-command-trigger-button/index.tsx +2 -0
- package/src/components/tiptap-ui/slash-command-trigger-button/slash-command-trigger-button.tsx +128 -0
- package/src/components/tiptap-ui/slash-command-trigger-button/use-slash-command-trigger.ts +255 -0
- package/src/components/tiptap-ui/slash-dropdown-menu/index.tsx +2 -0
- package/src/components/tiptap-ui/slash-dropdown-menu/slash-dropdown-menu.css +33 -0
- package/src/components/tiptap-ui/slash-dropdown-menu/slash-dropdown-menu.tsx +159 -0
- package/src/components/tiptap-ui/slash-dropdown-menu/use-slash-dropdown-menu.ts +317 -0
- package/src/components/tiptap-ui/text-align-button/index.tsx +2 -0
- package/src/components/tiptap-ui/text-align-button/text-align-button.tsx +120 -0
- package/src/components/tiptap-ui/text-align-button/use-text-align.ts +224 -0
- package/src/components/tiptap-ui/text-button/index.tsx +2 -0
- package/src/components/tiptap-ui/text-button/text-button.tsx +117 -0
- package/src/components/tiptap-ui/text-button/use-text.ts +264 -0
- package/src/components/tiptap-ui/turn-into-dropdown/index.tsx +2 -0
- package/src/components/tiptap-ui/turn-into-dropdown/turn-into-dropdown.tsx +192 -0
- package/src/components/tiptap-ui/turn-into-dropdown/use-turn-into-dropdown.ts +260 -0
- package/src/components/tiptap-ui/undo-redo-button/index.tsx +2 -0
- package/src/components/tiptap-ui/undo-redo-button/undo-redo-button.tsx +126 -0
- package/src/components/tiptap-ui/undo-redo-button/use-undo-redo.ts +184 -0
- package/src/components/tiptap-ui-primitive/avatar/avatar.css +83 -0
- package/src/components/tiptap-ui-primitive/avatar/avatar.tsx +239 -0
- package/src/components/tiptap-ui-primitive/avatar/index.tsx +1 -0
- package/src/components/tiptap-ui-primitive/badge/badge-colors.css +358 -0
- package/src/components/tiptap-ui-primitive/badge/badge-group.css +18 -0
- package/src/components/tiptap-ui-primitive/badge/badge.css +93 -0
- package/src/components/tiptap-ui-primitive/badge/badge.tsx +46 -0
- package/src/components/tiptap-ui-primitive/badge/index.tsx +1 -0
- package/src/components/tiptap-ui-primitive/button/button-colors.css +6 -0
- package/src/components/tiptap-ui-primitive/button/button-group.css +17 -0
- package/src/components/tiptap-ui-primitive/button/button.css +428 -0
- package/src/components/tiptap-ui-primitive/button/button.tsx +116 -0
- package/src/components/tiptap-ui-primitive/button/index.tsx +1 -0
- package/src/components/tiptap-ui-primitive/card/card.css +42 -0
- package/src/components/tiptap-ui-primitive/card/card.tsx +79 -0
- package/src/components/tiptap-ui-primitive/card/index.tsx +1 -0
- package/src/components/tiptap-ui-primitive/combobox/combobox.css +15 -0
- package/src/components/tiptap-ui-primitive/combobox/combobox.tsx +73 -0
- package/src/components/tiptap-ui-primitive/combobox/index.tsx +1 -0
- package/src/components/tiptap-ui-primitive/dropdown-menu/dropdown-menu.css +49 -0
- package/src/components/tiptap-ui-primitive/dropdown-menu/dropdown-menu.tsx +98 -0
- package/src/components/tiptap-ui-primitive/dropdown-menu/index.tsx +1 -0
- package/src/components/tiptap-ui-primitive/input/index.tsx +1 -0
- package/src/components/tiptap-ui-primitive/input/input.css +26 -0
- package/src/components/tiptap-ui-primitive/input/input.tsx +24 -0
- package/src/components/tiptap-ui-primitive/label/index.tsx +1 -0
- package/src/components/tiptap-ui-primitive/label/label.css +9 -0
- package/src/components/tiptap-ui-primitive/label/label.tsx +42 -0
- package/src/components/tiptap-ui-primitive/menu/index.tsx +5 -0
- package/src/components/tiptap-ui-primitive/menu/menu-context.ts +19 -0
- package/src/components/tiptap-ui-primitive/menu/menu-hooks.ts +102 -0
- package/src/components/tiptap-ui-primitive/menu/menu-types.ts +56 -0
- package/src/components/tiptap-ui-primitive/menu/menu-utils.ts +64 -0
- package/src/components/tiptap-ui-primitive/menu/menu.css +49 -0
- package/src/components/tiptap-ui-primitive/menu/menu.tsx +235 -0
- package/src/components/tiptap-ui-primitive/popover/index.tsx +1 -0
- package/src/components/tiptap-ui-primitive/popover/popover.css +49 -0
- package/src/components/tiptap-ui-primitive/popover/popover.tsx +37 -0
- package/src/components/tiptap-ui-primitive/separator/index.tsx +1 -0
- package/src/components/tiptap-ui-primitive/separator/separator.css +19 -0
- package/src/components/tiptap-ui-primitive/separator/separator.tsx +33 -0
- package/src/components/tiptap-ui-primitive/sidebar/index.tsx +1 -0
- package/src/components/tiptap-ui-primitive/sidebar/sidebar.css +140 -0
- package/src/components/tiptap-ui-primitive/sidebar/sidebar.tsx +299 -0
- package/src/components/tiptap-ui-primitive/spacer/index.tsx +1 -0
- package/src/components/tiptap-ui-primitive/spacer/spacer.tsx +26 -0
- package/src/components/tiptap-ui-primitive/textarea-autosize/index.tsx +1 -0
- package/src/components/tiptap-ui-primitive/textarea-autosize/textarea-autosize.tsx +18 -0
- package/src/components/tiptap-ui-primitive/toolbar/index.tsx +1 -0
- package/src/components/tiptap-ui-primitive/toolbar/toolbar.css +65 -0
- package/src/components/tiptap-ui-primitive/toolbar/toolbar.tsx +123 -0
- package/src/components/tiptap-ui-primitive/tooltip/index.tsx +1 -0
- package/src/components/tiptap-ui-primitive/tooltip/tooltip.css +21 -0
- package/src/components/tiptap-ui-primitive/tooltip/tooltip.tsx +237 -0
- package/src/components/tiptap-ui-utils/floating-element/floating-element-utils.ts +23 -0
- package/src/components/tiptap-ui-utils/floating-element/floating-element.tsx +343 -0
- package/src/components/tiptap-ui-utils/floating-element/index.tsx +2 -0
- package/src/components/tiptap-ui-utils/suggestion-menu/index.tsx +3 -0
- package/src/components/tiptap-ui-utils/suggestion-menu/suggestion-menu-types.ts +91 -0
- package/src/components/tiptap-ui-utils/suggestion-menu/suggestion-menu-utils.ts +87 -0
- package/src/components/tiptap-ui-utils/suggestion-menu/suggestion-menu.tsx +240 -0
- package/src/content/index.tsx +27 -0
- package/src/content/style.css +12 -0
- package/src/contexts/ai-context.tsx +65 -0
- package/src/contexts/app-context.tsx +159 -0
- package/src/contexts/user-context.tsx +138 -0
- package/src/editor/collaboration/index.tsx +64 -0
- package/src/editor/index.tsx +144 -42
- package/src/hooks/use-composed-ref.ts +47 -0
- package/src/hooks/use-cursor-visibility.ts +69 -0
- package/src/hooks/use-editor.ts +237 -0
- package/src/hooks/use-element-rect.ts +165 -0
- package/src/hooks/use-floating-element.ts +101 -0
- package/src/hooks/use-floating-toolbar-visibility.ts +123 -0
- package/src/hooks/use-is-breakpoint.ts +37 -0
- package/src/hooks/use-isomorphic-layout-effect.ts +11 -0
- package/src/hooks/use-menu-navigation.ts +182 -0
- package/src/hooks/use-on-click-outside.ts +135 -0
- package/src/hooks/use-scrolling.ts +75 -0
- package/src/hooks/use-throttled-callback.ts +48 -0
- package/src/hooks/use-tiptap-editor.ts +49 -0
- package/src/hooks/use-ui-editor-state.ts +29 -0
- package/src/hooks/use-unmount.ts +21 -0
- package/src/hooks/use-window-size.ts +88 -0
- package/src/index.ts +4 -7
- package/src/lib/tiptap-advanced-utils.ts +362 -0
- package/src/lib/tiptap-collab-utils.ts +289 -0
- package/src/lib/tiptap-utils.ts +612 -0
- package/src/locales/en.json +123 -0
- package/src/locales/zh-CN.json +123 -0
- package/src/locales/zh-TW.json +123 -0
- package/src/styles/variables.css +92 -0
- package/README.md +0 -458
- package/src/editor/constants.tsx +0 -66
- package/src/editor/container.css +0 -46
- package/src/editor/control/character-count/index.tsx +0 -39
- package/src/editor/control/drag-handle/index.tsx +0 -85
- package/src/editor/control/drag-handle/use.content.actions.ts +0 -71
- package/src/editor/control/drag-handle/use.data.ts +0 -29
- package/src/editor/control/drag-handle/use.handle.id.ts +0 -6
- package/src/editor/control/index.tsx +0 -35
- package/src/editor/editor.css +0 -626
- package/src/editor/extension/block-quote-figure/BlockquoteFigure.ts +0 -73
- package/src/editor/extension/block-quote-figure/Quote/Quote.ts +0 -31
- package/src/editor/extension/block-quote-figure/Quote/index.ts +0 -1
- package/src/editor/extension/block-quote-figure/QuoteCaption/QuoteCaption.ts +0 -54
- package/src/editor/extension/block-quote-figure/QuoteCaption/index.ts +0 -1
- package/src/editor/extension/block-quote-figure/index.ts +0 -1
- package/src/editor/extension/document/index.ts +0 -5
- package/src/editor/extension/figcaption/Figcaption.ts +0 -90
- package/src/editor/extension/figcaption/index.ts +0 -1
- package/src/editor/extension/figure/Figure.ts +0 -62
- package/src/editor/extension/figure/index.ts +0 -1
- package/src/editor/extension/font-size/FontSize.ts +0 -64
- package/src/editor/extension/font-size/index.ts +0 -1
- package/src/editor/extension/global-drag-handle/clipboard-serializer.ts +0 -28
- package/src/editor/extension/global-drag-handle/index.ts +0 -377
- package/src/editor/extension/heading/index.ts +0 -13
- package/src/editor/extension/horizontal-rule/HorizontalRule.ts +0 -10
- package/src/editor/extension/horizontal-rule/index.ts +0 -1
- package/src/editor/extension/image/index.ts +0 -5
- package/src/editor/extension/image-block/ImageBlock.ts +0 -103
- package/src/editor/extension/image-block/components/ImageBlockMenu.tsx +0 -100
- package/src/editor/extension/image-block/components/ImageBlockView.tsx +0 -47
- package/src/editor/extension/image-block/components/ImageBlockWidth.tsx +0 -40
- package/src/editor/extension/image-block/index.ts +0 -1
- package/src/editor/extension/image-upload/ImageUpload.ts +0 -58
- package/src/editor/extension/image-upload/index.ts +0 -1
- package/src/editor/extension/image-upload/view/ImageUpload.tsx +0 -27
- package/src/editor/extension/image-upload/view/ImageUploader.tsx +0 -64
- package/src/editor/extension/image-upload/view/hooks.ts +0 -109
- package/src/editor/extension/image-upload/view/index.tsx +0 -1
- package/src/editor/extension/index.ts +0 -30
- package/src/editor/extension/link/Link.ts +0 -39
- package/src/editor/extension/link/index.ts +0 -1
- package/src/editor/extension/multi-column/Column.ts +0 -33
- package/src/editor/extension/multi-column/Columns.ts +0 -65
- package/src/editor/extension/multi-column/index.ts +0 -2
- package/src/editor/extension/multi-column/menus/ColumnsMenu.tsx +0 -82
- package/src/editor/extension/multi-column/menus/index.ts +0 -1
- package/src/editor/extension/selection/Selection.ts +0 -36
- package/src/editor/extension/selection/index.ts +0 -1
- package/src/editor/extension/slash-command/MenuList.tsx +0 -145
- package/src/editor/extension/slash-command/groups.ts +0 -153
- package/src/editor/extension/slash-command/index.ts +0 -277
- package/src/editor/extension/slash-command/types.ts +0 -25
- package/src/editor/extension/table/Cell.ts +0 -126
- package/src/editor/extension/table/Header.ts +0 -89
- package/src/editor/extension/table/Row.ts +0 -8
- package/src/editor/extension/table/Table.ts +0 -9
- package/src/editor/extension/table/index.ts +0 -4
- package/src/editor/extension/table/menus/TableColumn/index.tsx +0 -73
- package/src/editor/extension/table/menus/TableColumn/utils.ts +0 -38
- package/src/editor/extension/table/menus/TableRow/index.tsx +0 -74
- package/src/editor/extension/table/menus/TableRow/utils.ts +0 -38
- package/src/editor/extension/table/menus/index.tsx +0 -2
- package/src/editor/extension/table/utils.ts +0 -258
- package/src/editor/extension/task-item/index.ts +0 -1
- package/src/editor/extension/task-item/task-item.ts +0 -225
- package/src/editor/extension/task-list/index.ts +0 -1
- package/src/editor/extension/task-list/task-list.ts +0 -81
- package/src/editor/extension/trailing-node/index.ts +0 -1
- package/src/editor/extension/trailing-node/trailing-node.ts +0 -70
- package/src/editor/extension/unique-id/index.ts +0 -1
- package/src/editor/extension/unique-id/uniqueId.ts +0 -123
- package/src/editor/hooks.ts +0 -264
- package/src/editor/menus/LinkMenu/LinkMenu.tsx +0 -75
- package/src/editor/menus/LinkMenu/index.tsx +0 -1
- package/src/editor/menus/TextMenu/TextMenu.tsx +0 -193
- package/src/editor/menus/TextMenu/components/AIDropdown.tsx +0 -140
- package/src/editor/menus/TextMenu/components/ContentTypePicker.tsx +0 -76
- package/src/editor/menus/TextMenu/components/EditLinkPopover.tsx +0 -25
- package/src/editor/menus/TextMenu/components/FontFamilyPicker.tsx +0 -84
- package/src/editor/menus/TextMenu/components/FontSizePicker.tsx +0 -56
- package/src/editor/menus/TextMenu/hooks/useTextmenuCommands.ts +0 -96
- package/src/editor/menus/TextMenu/hooks/useTextmenuContentTypes.ts +0 -86
- package/src/editor/menus/TextMenu/hooks/useTextmenuStates.ts +0 -50
- package/src/editor/menus/TextMenu/index.tsx +0 -2
- package/src/editor/menus/types.ts +0 -21
- package/src/editor/panels/Colorpicker/ColorButton.tsx +0 -35
- package/src/editor/panels/Colorpicker/Colorpicker.tsx +0 -67
- package/src/editor/panels/Colorpicker/index.tsx +0 -2
- package/src/editor/panels/LinkEditorPanel/LinkEditorPanel.tsx +0 -76
- package/src/editor/panels/LinkEditorPanel/index.tsx +0 -1
- package/src/editor/panels/LinkPreviewPanel/LinkPreviewPanel.tsx +0 -32
- package/src/editor/panels/LinkPreviewPanel/index.tsx +0 -1
- package/src/editor/panels/index.tsx +0 -3
- package/src/editor/types.tsx +0 -38
- package/src/editor/ui/Button/Button.tsx +0 -70
- package/src/editor/ui/Button/index.tsx +0 -2
- package/src/editor/ui/Dropdown/Dropdown.tsx +0 -39
- package/src/editor/ui/Dropdown/index.tsx +0 -1
- package/src/editor/ui/Icon.tsx +0 -21
- package/src/editor/ui/Loader/Loader.tsx +0 -39
- package/src/editor/ui/Loader/index.ts +0 -1
- package/src/editor/ui/Loader/types.ts +0 -7
- package/src/editor/ui/Panel/index.tsx +0 -109
- package/src/editor/ui/PopoverMenu.tsx +0 -127
- package/src/editor/ui/Spinner/Spinner.tsx +0 -10
- package/src/editor/ui/Spinner/index.tsx +0 -1
- package/src/editor/ui/Surface.tsx +0 -27
- package/src/editor/ui/Textarea/Textarea.tsx +0 -20
- package/src/editor/ui/Textarea/index.tsx +0 -1
- package/src/editor/ui/Toggle/Toggle.tsx +0 -39
- package/src/editor/ui/Toggle/index.tsx +0 -1
- package/src/editor/ui/Toolbar.tsx +0 -107
- package/src/editor/ui/Tooltip/index.tsx +0 -77
- package/src/editor/ui/Tooltip/types.ts +0 -17
- package/src/editor/utils/cssVar.ts +0 -14
- package/src/editor/utils/getRenderContainer.ts +0 -39
- package/src/editor/utils/index.ts +0 -16
- package/src/editor/utils/isCustomNodeSelected.ts +0 -47
- package/src/editor/utils/isTextSelected.ts +0 -25
- package/src/editor/utils/locale.ts +0 -5
- package/src/editor/viewer/index.tsx +0 -26
- package/src/globals.css +0 -1
- package/src/locales/en-us.ts +0 -133
- package/src/locales/zh-cn.ts +0 -133
- package/src/locales/zh-tw.ts +0 -133
|
@@ -0,0 +1,1289 @@
|
|
|
1
|
+
import type { Editor } from "@tiptap/react"
|
|
2
|
+
import type { Node } from "@tiptap/pm/model"
|
|
3
|
+
import type { Command } from "@tiptap/pm/state"
|
|
4
|
+
import { Selection, type EditorState, type Transaction } from "@tiptap/pm/state"
|
|
5
|
+
import type { FindNodeResult, Rect } from "@tiptap/pm/tables"
|
|
6
|
+
import {
|
|
7
|
+
TableMap,
|
|
8
|
+
CellSelection,
|
|
9
|
+
findTable,
|
|
10
|
+
selectedRect,
|
|
11
|
+
cellAround,
|
|
12
|
+
selectionCell,
|
|
13
|
+
isInTable,
|
|
14
|
+
} from "@tiptap/pm/tables"
|
|
15
|
+
import { Mapping } from "@tiptap/pm/transform"
|
|
16
|
+
|
|
17
|
+
export const RESIZE_MIN_WIDTH = 35
|
|
18
|
+
export const EMPTY_CELL_WIDTH = 120
|
|
19
|
+
export const EMPTY_CELL_HEIGHT = 40
|
|
20
|
+
|
|
21
|
+
export type Orientation = "row" | "column"
|
|
22
|
+
export interface CellInfo extends FindNodeResult {
|
|
23
|
+
row: number
|
|
24
|
+
column: number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type CellCoordinates = {
|
|
28
|
+
row: number
|
|
29
|
+
col: number
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type SelectionReturnMode = "state" | "transaction" | "dispatch"
|
|
33
|
+
|
|
34
|
+
export type BaseSelectionOptions = { mode?: SelectionReturnMode }
|
|
35
|
+
export type DispatchSelectionOptions = {
|
|
36
|
+
mode: "dispatch"
|
|
37
|
+
dispatch: (tr: Transaction) => void
|
|
38
|
+
}
|
|
39
|
+
export type TransactionSelectionOptions = { mode: "transaction" }
|
|
40
|
+
export type StateSelectionOptions = { mode?: "state" }
|
|
41
|
+
|
|
42
|
+
export type TableInfo = {
|
|
43
|
+
map: TableMap
|
|
44
|
+
} & FindNodeResult
|
|
45
|
+
|
|
46
|
+
// ============================================================================
|
|
47
|
+
// HELPER CONSTANTS & UTILITIES
|
|
48
|
+
// ============================================================================
|
|
49
|
+
|
|
50
|
+
const EMPTY_CELLS_RESULT = { cells: [], mergedCells: [] }
|
|
51
|
+
|
|
52
|
+
export function isHTMLElement(n: unknown): n is HTMLElement {
|
|
53
|
+
return n instanceof HTMLElement
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type DomCellAroundResult =
|
|
57
|
+
| {
|
|
58
|
+
type: "cell"
|
|
59
|
+
domNode: HTMLElement
|
|
60
|
+
tbodyNode: HTMLTableSectionElement | null
|
|
61
|
+
}
|
|
62
|
+
| {
|
|
63
|
+
type: "wrapper"
|
|
64
|
+
domNode: HTMLElement
|
|
65
|
+
tbodyNode: HTMLTableSectionElement | null
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function safeClosest<T extends Element>(
|
|
69
|
+
start: Element | null,
|
|
70
|
+
selector: string
|
|
71
|
+
): T | null {
|
|
72
|
+
return (start?.closest?.(selector) as T | null) ?? null
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Walk up from an element until we find a TD/TH or the table wrapper.
|
|
77
|
+
* Returns the found element plus its tbody (if present).
|
|
78
|
+
*/
|
|
79
|
+
export function domCellAround(
|
|
80
|
+
target: Element
|
|
81
|
+
): DomCellAroundResult | undefined {
|
|
82
|
+
let current: Element | null = target
|
|
83
|
+
|
|
84
|
+
while (
|
|
85
|
+
current &&
|
|
86
|
+
current.tagName !== "TD" &&
|
|
87
|
+
current.tagName !== "TH" &&
|
|
88
|
+
!current.classList.contains("tableWrapper")
|
|
89
|
+
) {
|
|
90
|
+
if (current.classList.contains("ProseMirror")) return undefined
|
|
91
|
+
current = isHTMLElement(current.parentNode)
|
|
92
|
+
? (current.parentNode as Element)
|
|
93
|
+
: null
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!current) return undefined
|
|
97
|
+
|
|
98
|
+
if (current.tagName === "TD" || current.tagName === "TH") {
|
|
99
|
+
return {
|
|
100
|
+
type: "cell",
|
|
101
|
+
domNode: current as HTMLElement,
|
|
102
|
+
tbodyNode: safeClosest<HTMLTableSectionElement>(current, "tbody"),
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
type: "wrapper",
|
|
108
|
+
domNode: current as HTMLElement,
|
|
109
|
+
tbodyNode: (current as HTMLElement).querySelector("tbody"),
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Clamps a value between min and max bounds
|
|
115
|
+
*/
|
|
116
|
+
export function clamp(value: number, min: number, max: number): number {
|
|
117
|
+
return Math.max(min, Math.min(value, max))
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Validates if row/col indices are within table bounds
|
|
122
|
+
*/
|
|
123
|
+
function isWithinBounds(row: number, col: number, map: TableMap): boolean {
|
|
124
|
+
return row >= 0 && row < map.height && col >= 0 && col < map.width
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Resolves the index for a row or column based on current selection or provided value
|
|
129
|
+
*/
|
|
130
|
+
function resolveOrientationIndex(
|
|
131
|
+
state: EditorState,
|
|
132
|
+
table: TableInfo,
|
|
133
|
+
orientation: Orientation,
|
|
134
|
+
providedIndex?: number
|
|
135
|
+
): number | null {
|
|
136
|
+
if (typeof providedIndex === "number") {
|
|
137
|
+
return providedIndex
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (state.selection instanceof CellSelection) {
|
|
141
|
+
const rect = selectedRect(state)
|
|
142
|
+
return orientation === "row" ? rect.top : rect.left
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const $cell = cellAround(state.selection.$anchor) ?? selectionCell(state)
|
|
146
|
+
if (!$cell) return null
|
|
147
|
+
|
|
148
|
+
const rel = $cell.pos - table.start
|
|
149
|
+
const rect = table.map.findCell(rel)
|
|
150
|
+
return orientation === "row" ? rect.top : rect.left
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Creates a CellInfo object from position data
|
|
155
|
+
*/
|
|
156
|
+
function createCellInfo(
|
|
157
|
+
row: number,
|
|
158
|
+
column: number,
|
|
159
|
+
cellPos: number,
|
|
160
|
+
cellNode: Node
|
|
161
|
+
): CellInfo {
|
|
162
|
+
return {
|
|
163
|
+
row,
|
|
164
|
+
column,
|
|
165
|
+
pos: cellPos,
|
|
166
|
+
node: cellNode,
|
|
167
|
+
start: cellPos + 1,
|
|
168
|
+
depth: cellNode ? cellNode.content.size : 0,
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Checks if a cell is merged (has colspan or rowspan > 1)
|
|
174
|
+
*/
|
|
175
|
+
export function isCellMerged(node: Node | null): boolean {
|
|
176
|
+
if (!node) return false
|
|
177
|
+
const colspan = node.attrs.colspan ?? 1
|
|
178
|
+
const rowspan = node.attrs.rowspan ?? 1
|
|
179
|
+
return colspan > 1 || rowspan > 1
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Generic function to collect cells along a row or column
|
|
184
|
+
*/
|
|
185
|
+
function collectCells(
|
|
186
|
+
editor: Editor | null,
|
|
187
|
+
orientation: Orientation,
|
|
188
|
+
index?: number,
|
|
189
|
+
tablePos?: number
|
|
190
|
+
): { cells: CellInfo[]; mergedCells: CellInfo[] } {
|
|
191
|
+
if (!editor) return EMPTY_CELLS_RESULT
|
|
192
|
+
|
|
193
|
+
const { state } = editor
|
|
194
|
+
const table = getTable(editor, tablePos)
|
|
195
|
+
if (!table) return EMPTY_CELLS_RESULT
|
|
196
|
+
|
|
197
|
+
const tableStart = table.start
|
|
198
|
+
const tableNode = table.node
|
|
199
|
+
const map = table.map
|
|
200
|
+
|
|
201
|
+
const resolvedIndex = resolveOrientationIndex(
|
|
202
|
+
state,
|
|
203
|
+
table,
|
|
204
|
+
orientation,
|
|
205
|
+
index
|
|
206
|
+
)
|
|
207
|
+
if (resolvedIndex === null) return EMPTY_CELLS_RESULT
|
|
208
|
+
|
|
209
|
+
// Bounds check
|
|
210
|
+
const maxIndex = orientation === "row" ? map.height : map.width
|
|
211
|
+
if (resolvedIndex < 0 || resolvedIndex >= maxIndex) {
|
|
212
|
+
return EMPTY_CELLS_RESULT
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const cells: CellInfo[] = []
|
|
216
|
+
const mergedCells: CellInfo[] = []
|
|
217
|
+
const seenMerged = new Set<number>()
|
|
218
|
+
|
|
219
|
+
const iterationCount = orientation === "row" ? map.width : map.height
|
|
220
|
+
|
|
221
|
+
for (let i = 0; i < iterationCount; i++) {
|
|
222
|
+
const row = orientation === "row" ? resolvedIndex : i
|
|
223
|
+
const col = orientation === "row" ? i : resolvedIndex
|
|
224
|
+
const cellIndex = row * map.width + col
|
|
225
|
+
const mapCell = map.map[cellIndex]
|
|
226
|
+
|
|
227
|
+
if (mapCell === undefined) continue
|
|
228
|
+
|
|
229
|
+
const cellPos = tableStart + mapCell
|
|
230
|
+
const cellNode = tableNode.nodeAt(mapCell)
|
|
231
|
+
if (!cellNode) continue
|
|
232
|
+
|
|
233
|
+
const cell = createCellInfo(row, col, cellPos, cellNode)
|
|
234
|
+
|
|
235
|
+
if (isCellMerged(cellNode)) {
|
|
236
|
+
if (!seenMerged.has(cellPos)) {
|
|
237
|
+
mergedCells.push(cell)
|
|
238
|
+
seenMerged.add(cellPos)
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
cells.push(cell)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return { cells, mergedCells }
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Generic function to count empty cells from the end of a row or column
|
|
250
|
+
*/
|
|
251
|
+
function countEmptyCellsFromEnd(
|
|
252
|
+
editor: Editor,
|
|
253
|
+
tablePos: number,
|
|
254
|
+
orientation: Orientation
|
|
255
|
+
): number {
|
|
256
|
+
const table = getTable(editor, tablePos)
|
|
257
|
+
if (!table) return 0
|
|
258
|
+
|
|
259
|
+
const { doc } = editor.state
|
|
260
|
+
const maxIndex = orientation === "row" ? table.map.height : table.map.width
|
|
261
|
+
|
|
262
|
+
let emptyCount = 0
|
|
263
|
+
for (let idx = maxIndex - 1; idx >= 0; idx--) {
|
|
264
|
+
const seen = new Set<number>()
|
|
265
|
+
let isLineEmpty = true
|
|
266
|
+
|
|
267
|
+
const iterationCount =
|
|
268
|
+
orientation === "row" ? table.map.width : table.map.height
|
|
269
|
+
|
|
270
|
+
for (let i = 0; i < iterationCount; i++) {
|
|
271
|
+
const row = orientation === "row" ? idx : i
|
|
272
|
+
const col = orientation === "row" ? i : idx
|
|
273
|
+
const rel = table.map.positionAt(row, col, table.node)
|
|
274
|
+
|
|
275
|
+
if (seen.has(rel)) continue
|
|
276
|
+
seen.add(rel)
|
|
277
|
+
|
|
278
|
+
const abs = tablePos + 1 + rel
|
|
279
|
+
const cell = doc.nodeAt(abs)
|
|
280
|
+
if (!cell) continue
|
|
281
|
+
|
|
282
|
+
if (!isCellEmpty(cell)) {
|
|
283
|
+
isLineEmpty = false
|
|
284
|
+
break
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (isLineEmpty) emptyCount++
|
|
289
|
+
else break
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return emptyCount
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Get information about the table at the current selection or a specific position.
|
|
297
|
+
*
|
|
298
|
+
* If `tablePos` is provided, it looks for a table at that exact position.
|
|
299
|
+
* Otherwise, it finds the nearest table containing the current selection.
|
|
300
|
+
*
|
|
301
|
+
* Returns an object with:
|
|
302
|
+
* - `node`: the table node
|
|
303
|
+
* - `pos`: the position of the table in the document
|
|
304
|
+
* - `start`: the position just after the table node (where its content starts)
|
|
305
|
+
* - `map`: the `TableMap` for layout info (rows, columns, spans)
|
|
306
|
+
*
|
|
307
|
+
* If no table is found, returns null.
|
|
308
|
+
*/
|
|
309
|
+
export function getTable(editor: Editor | null, tablePos?: number) {
|
|
310
|
+
if (!editor) return null
|
|
311
|
+
|
|
312
|
+
let table = null
|
|
313
|
+
|
|
314
|
+
if (typeof tablePos === "number") {
|
|
315
|
+
const tableNode = editor.state.doc.nodeAt(tablePos)
|
|
316
|
+
if (tableNode?.type.name === "table") {
|
|
317
|
+
table = {
|
|
318
|
+
node: tableNode,
|
|
319
|
+
pos: tablePos,
|
|
320
|
+
start: tablePos + 1,
|
|
321
|
+
depth: editor.state.doc.resolve(tablePos).depth,
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (!table) {
|
|
327
|
+
const { state } = editor
|
|
328
|
+
const $from = state.doc.resolve(state.selection.from)
|
|
329
|
+
table = findTable($from)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (!table) return null
|
|
333
|
+
|
|
334
|
+
const tableMap = TableMap.get(table.node)
|
|
335
|
+
if (!tableMap) return null
|
|
336
|
+
|
|
337
|
+
return { ...table, map: tableMap }
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Checks if the current text selection is inside a table cell.
|
|
342
|
+
* @param state - The editor state to check
|
|
343
|
+
* @returns true if the selection is inside a table cell; false otherwise
|
|
344
|
+
*/
|
|
345
|
+
export function isSelectionInCell(state: EditorState): boolean {
|
|
346
|
+
const { selection } = state
|
|
347
|
+
const $from = selection.$from
|
|
348
|
+
|
|
349
|
+
for (let depth = $from.depth; depth > 0; depth--) {
|
|
350
|
+
const node = $from.node(depth)
|
|
351
|
+
if (node.type.name === "tableCell" || node.type.name === "tableHeader") {
|
|
352
|
+
return true
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return false
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Cells overlap a rectangle if any of the cells in the rectangle are merged
|
|
361
|
+
* with cells outside the rectangle.
|
|
362
|
+
*/
|
|
363
|
+
export function cellsOverlapRectangle(
|
|
364
|
+
{ width, height, map }: TableMap,
|
|
365
|
+
rect: Rect
|
|
366
|
+
) {
|
|
367
|
+
let indexTop = rect.top * width + rect.left,
|
|
368
|
+
indexLeft = indexTop
|
|
369
|
+
let indexBottom = (rect.bottom - 1) * width + rect.left,
|
|
370
|
+
indexRight = indexTop + (rect.right - rect.left - 1)
|
|
371
|
+
for (let i = rect.top; i < rect.bottom; i++) {
|
|
372
|
+
if (
|
|
373
|
+
(rect.left > 0 && map[indexLeft] == map[indexLeft - 1]) ||
|
|
374
|
+
(rect.right < width && map[indexRight] == map[indexRight + 1])
|
|
375
|
+
)
|
|
376
|
+
return true
|
|
377
|
+
indexLeft += width
|
|
378
|
+
indexRight += width
|
|
379
|
+
}
|
|
380
|
+
for (let i = rect.left; i < rect.right; i++) {
|
|
381
|
+
if (
|
|
382
|
+
(rect.top > 0 && map[indexTop] == map[indexTop - width]) ||
|
|
383
|
+
(rect.bottom < height && map[indexBottom] == map[indexBottom + width])
|
|
384
|
+
)
|
|
385
|
+
return true
|
|
386
|
+
indexTop++
|
|
387
|
+
indexBottom++
|
|
388
|
+
}
|
|
389
|
+
return false
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Runs a function while preserving the editor's selection.
|
|
394
|
+
* @param editor The Tiptap editor instance
|
|
395
|
+
* @param fn The function to run
|
|
396
|
+
* @returns True if the selection was successfully restored, false otherwise
|
|
397
|
+
*/
|
|
398
|
+
export function runPreservingCursor(editor: Editor, fn: () => void): boolean {
|
|
399
|
+
const view = editor.view
|
|
400
|
+
const startSel = view.state.selection
|
|
401
|
+
const bookmark = startSel.getBookmark()
|
|
402
|
+
|
|
403
|
+
const mapping = new Mapping()
|
|
404
|
+
const originalDispatch = view.dispatch
|
|
405
|
+
|
|
406
|
+
view.dispatch = (tr) => {
|
|
407
|
+
mapping.appendMapping(tr.mapping)
|
|
408
|
+
originalDispatch(tr)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
try {
|
|
412
|
+
fn()
|
|
413
|
+
} finally {
|
|
414
|
+
view.dispatch = originalDispatch
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
try {
|
|
418
|
+
const sel = bookmark.map(mapping).resolve(view.state.doc)
|
|
419
|
+
view.dispatch(view.state.tr.setSelection(sel))
|
|
420
|
+
return true
|
|
421
|
+
} catch {
|
|
422
|
+
// Fallback: if the exact spot vanished (e.g., cell deleted),
|
|
423
|
+
// go to the nearest valid position.
|
|
424
|
+
const mappedPos = mapping.map(startSel.from, -1)
|
|
425
|
+
const clamped = clamp(mappedPos, 0, view.state.doc.content.size)
|
|
426
|
+
const near = Selection.near(view.state.doc.resolve(clamped), -1)
|
|
427
|
+
view.dispatch(view.state.tr.setSelection(near))
|
|
428
|
+
return false
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Determines whether a table cell is effectively empty.
|
|
434
|
+
*
|
|
435
|
+
* A cell is considered empty when:
|
|
436
|
+
* - it has no children, or
|
|
437
|
+
* - it contains only whitespace text, or
|
|
438
|
+
* - it contains no text and no non-text leaf nodes (images, embeds, etc.)
|
|
439
|
+
*
|
|
440
|
+
* Early-outs as soon as any meaningful content is found.
|
|
441
|
+
*
|
|
442
|
+
* @param cellNode - The table cell node to check
|
|
443
|
+
* @returns true if the cell is empty; false otherwise
|
|
444
|
+
*/
|
|
445
|
+
export function isCellEmpty(cellNode: Node): boolean {
|
|
446
|
+
if (cellNode.childCount === 0) return true
|
|
447
|
+
|
|
448
|
+
let isEmpty = true
|
|
449
|
+
cellNode.descendants((n) => {
|
|
450
|
+
if (n.isText && n.text?.trim()) {
|
|
451
|
+
isEmpty = false
|
|
452
|
+
return false
|
|
453
|
+
}
|
|
454
|
+
if (n.isLeaf && !n.isText) {
|
|
455
|
+
isEmpty = false
|
|
456
|
+
return false
|
|
457
|
+
}
|
|
458
|
+
return true
|
|
459
|
+
})
|
|
460
|
+
|
|
461
|
+
return isEmpty
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Determine if the current selection is a full row or column selection.
|
|
466
|
+
*
|
|
467
|
+
* If the selection is a `CellSelection` that spans an entire row or column,
|
|
468
|
+
* returns an object indicating the type and index:
|
|
469
|
+
* - `{ type: "row", index: number }` for full row selections
|
|
470
|
+
* - `{ type: "column", index: number }` for full column selections
|
|
471
|
+
*
|
|
472
|
+
* If the selection is not a full row/column, or if no table is found, returns null.
|
|
473
|
+
*/
|
|
474
|
+
export function getTableSelectionType(
|
|
475
|
+
editor: Editor | null,
|
|
476
|
+
index?: number,
|
|
477
|
+
orientation?: Orientation,
|
|
478
|
+
tablePos?: number
|
|
479
|
+
): { orientation: Orientation; index: number } | null {
|
|
480
|
+
if (typeof index === "number" && orientation) {
|
|
481
|
+
return { orientation, index }
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (!editor) return null
|
|
485
|
+
|
|
486
|
+
const { state } = editor
|
|
487
|
+
|
|
488
|
+
const table = getTable(editor, tablePos)
|
|
489
|
+
if (!table) return null
|
|
490
|
+
|
|
491
|
+
if (state.selection instanceof CellSelection) {
|
|
492
|
+
const rect = selectedRect(state)
|
|
493
|
+
const width = rect.right - rect.left
|
|
494
|
+
const height = rect.bottom - rect.top
|
|
495
|
+
|
|
496
|
+
if (height === 1 && width >= 1) {
|
|
497
|
+
return { orientation: "row", index: rect.top }
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
if (width === 1 && height >= 1) {
|
|
501
|
+
return { orientation: "column", index: rect.left }
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
return null
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
return null
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Get all cells (and unique merged cells) in the selected row or column.
|
|
512
|
+
*
|
|
513
|
+
* - If `index` is provided, uses that row/column index.
|
|
514
|
+
* - If omitted, uses the first selected row/column based on current selection.
|
|
515
|
+
*
|
|
516
|
+
* Returns an object with:
|
|
517
|
+
* - `cells`: all cells in the row/column
|
|
518
|
+
* - `mergedCells`: only the unique cells that have rowspan/colspan > 1
|
|
519
|
+
*
|
|
520
|
+
* If no valid selection or index is found, returns empty arrays.
|
|
521
|
+
*/
|
|
522
|
+
export function getRowOrColumnCells(
|
|
523
|
+
editor: Editor | null,
|
|
524
|
+
index?: number,
|
|
525
|
+
orientation?: Orientation,
|
|
526
|
+
tablePos?: number
|
|
527
|
+
): {
|
|
528
|
+
cells: CellInfo[]
|
|
529
|
+
mergedCells: CellInfo[]
|
|
530
|
+
index?: number
|
|
531
|
+
orientation?: Orientation
|
|
532
|
+
tablePos?: number
|
|
533
|
+
} {
|
|
534
|
+
const emptyResult = {
|
|
535
|
+
cells: [],
|
|
536
|
+
mergedCells: [],
|
|
537
|
+
index: undefined,
|
|
538
|
+
orientation: undefined,
|
|
539
|
+
tablePos: undefined,
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (!editor) {
|
|
543
|
+
return emptyResult
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (
|
|
547
|
+
typeof index !== "number" &&
|
|
548
|
+
!(editor.state.selection instanceof CellSelection)
|
|
549
|
+
) {
|
|
550
|
+
return emptyResult
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
let finalIndex = index
|
|
554
|
+
let finalOrientation = orientation
|
|
555
|
+
|
|
556
|
+
if (
|
|
557
|
+
typeof finalIndex !== "number" ||
|
|
558
|
+
!finalOrientation ||
|
|
559
|
+
!["row", "column"].includes(finalOrientation)
|
|
560
|
+
) {
|
|
561
|
+
const selectionType = getTableSelectionType(editor)
|
|
562
|
+
if (!selectionType) return emptyResult
|
|
563
|
+
|
|
564
|
+
finalIndex = selectionType.index
|
|
565
|
+
finalOrientation = selectionType.orientation
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
const result = collectCells(editor, finalOrientation, finalIndex, tablePos)
|
|
569
|
+
return { ...result, index: finalIndex, orientation: finalOrientation }
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Collect cells (and unique merged cells) from a specific row.
|
|
574
|
+
* - If `rowIndex` is provided, scans that row.
|
|
575
|
+
* - If omitted, uses the first (topmost) selected row based on the current selection.
|
|
576
|
+
*/
|
|
577
|
+
export function getRowCells(
|
|
578
|
+
editor: Editor | null,
|
|
579
|
+
rowIndex?: number,
|
|
580
|
+
tablePos?: number
|
|
581
|
+
): { cells: CellInfo[]; mergedCells: CellInfo[] } {
|
|
582
|
+
return collectCells(editor, "row", rowIndex, tablePos)
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Collect cells (and unique merged cells) from the current table.
|
|
587
|
+
* - If `columnIndex` is provided, scans that column.
|
|
588
|
+
* - If omitted, uses the first (leftmost) selected column based on the current selection.
|
|
589
|
+
*/
|
|
590
|
+
export function getColumnCells(
|
|
591
|
+
editor: Editor | null,
|
|
592
|
+
columnIndex?: number,
|
|
593
|
+
tablePos?: number
|
|
594
|
+
): { cells: CellInfo[]; mergedCells: CellInfo[] } {
|
|
595
|
+
return collectCells(editor, "column", columnIndex, tablePos)
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* After moving a row or column, update the selection to the moved item.
|
|
600
|
+
*
|
|
601
|
+
* This ensures that after a move operation, the selection remains on the
|
|
602
|
+
* moved row or column, providing better user feedback.
|
|
603
|
+
*
|
|
604
|
+
* @param editor - The editor instance
|
|
605
|
+
* @param orientation - "row" or "column" indicating what was moved
|
|
606
|
+
* @param newIndex - The new index of the moved row/column
|
|
607
|
+
* @param tablePos - Optional position of the table in the document
|
|
608
|
+
*/
|
|
609
|
+
export function updateSelectionAfterAction(
|
|
610
|
+
editor: Editor,
|
|
611
|
+
orientation: Orientation,
|
|
612
|
+
newIndex: number,
|
|
613
|
+
tablePos?: number
|
|
614
|
+
): void {
|
|
615
|
+
try {
|
|
616
|
+
const table = getTable(editor, tablePos)
|
|
617
|
+
if (!table) return
|
|
618
|
+
|
|
619
|
+
const { state } = editor
|
|
620
|
+
const { map } = table
|
|
621
|
+
|
|
622
|
+
if (orientation === "row") {
|
|
623
|
+
if (newIndex >= 0 && newIndex < map.height) {
|
|
624
|
+
const startCol = 0
|
|
625
|
+
const endCol = map.width - 1
|
|
626
|
+
|
|
627
|
+
const startCellPos =
|
|
628
|
+
table.start + map.positionAt(newIndex, startCol, table.node)
|
|
629
|
+
const endCellPos =
|
|
630
|
+
table.start + map.positionAt(newIndex, endCol, table.node)
|
|
631
|
+
|
|
632
|
+
const $start = state.doc.resolve(startCellPos)
|
|
633
|
+
const $end = state.doc.resolve(endCellPos)
|
|
634
|
+
|
|
635
|
+
const newSelection = CellSelection.create(
|
|
636
|
+
state.doc,
|
|
637
|
+
$start.pos,
|
|
638
|
+
$end.pos
|
|
639
|
+
)
|
|
640
|
+
const tr = state.tr.setSelection(newSelection)
|
|
641
|
+
editor.view.dispatch(tr)
|
|
642
|
+
}
|
|
643
|
+
} else if (orientation === "column") {
|
|
644
|
+
if (newIndex >= 0 && newIndex < map.width) {
|
|
645
|
+
const startRow = 0
|
|
646
|
+
const endRow = map.height - 1
|
|
647
|
+
|
|
648
|
+
const startCellPos =
|
|
649
|
+
table.start + map.positionAt(startRow, newIndex, table.node)
|
|
650
|
+
const endCellPos =
|
|
651
|
+
table.start + map.positionAt(endRow, newIndex, table.node)
|
|
652
|
+
|
|
653
|
+
const $start = state.doc.resolve(startCellPos)
|
|
654
|
+
const $end = state.doc.resolve(endCellPos)
|
|
655
|
+
|
|
656
|
+
const newSelection = CellSelection.create(
|
|
657
|
+
state.doc,
|
|
658
|
+
$start.pos,
|
|
659
|
+
$end.pos
|
|
660
|
+
)
|
|
661
|
+
const tr = state.tr.setSelection(newSelection)
|
|
662
|
+
editor.view.dispatch(tr)
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
} catch (error) {
|
|
666
|
+
console.warn("Failed to update selection after move:", error)
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Returns a command that sets the given attributes to the given values,
|
|
672
|
+
* and is only available when the currently selected cell doesn't
|
|
673
|
+
* already have those attributes set to those values.
|
|
674
|
+
*
|
|
675
|
+
* @public
|
|
676
|
+
*/
|
|
677
|
+
export function setCellAttr(attrs: Record<string, unknown>): Command
|
|
678
|
+
export function setCellAttr(name: string, value: unknown): Command
|
|
679
|
+
export function setCellAttr(
|
|
680
|
+
nameOrAttrs: string | Record<string, unknown>,
|
|
681
|
+
value?: unknown
|
|
682
|
+
): Command {
|
|
683
|
+
return function (state, dispatch) {
|
|
684
|
+
if (!isInTable(state)) return false
|
|
685
|
+
const $cell = selectionCell(state)
|
|
686
|
+
|
|
687
|
+
const attrs =
|
|
688
|
+
typeof nameOrAttrs === "string" ? { [nameOrAttrs]: value } : nameOrAttrs
|
|
689
|
+
|
|
690
|
+
if (dispatch) {
|
|
691
|
+
const tr = state.tr
|
|
692
|
+
if (state.selection instanceof CellSelection) {
|
|
693
|
+
state.selection.forEachCell((node, pos) => {
|
|
694
|
+
const needsUpdate = Object.entries(attrs).some(
|
|
695
|
+
([name, val]) => node.attrs[name] !== val
|
|
696
|
+
)
|
|
697
|
+
|
|
698
|
+
if (needsUpdate) {
|
|
699
|
+
tr.setNodeMarkup(pos, null, {
|
|
700
|
+
...node.attrs,
|
|
701
|
+
...attrs,
|
|
702
|
+
})
|
|
703
|
+
}
|
|
704
|
+
})
|
|
705
|
+
} else {
|
|
706
|
+
const needsUpdate = Object.entries(attrs).some(
|
|
707
|
+
([name, val]) => $cell.nodeAfter!.attrs[name] !== val
|
|
708
|
+
)
|
|
709
|
+
|
|
710
|
+
if (needsUpdate) {
|
|
711
|
+
tr.setNodeMarkup($cell.pos, null, {
|
|
712
|
+
...$cell.nodeAfter!.attrs,
|
|
713
|
+
...attrs,
|
|
714
|
+
})
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
dispatch(tr)
|
|
718
|
+
}
|
|
719
|
+
return true
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* Counts how many consecutive empty rows exist at the bottom of a given table.
|
|
725
|
+
*
|
|
726
|
+
* This function:
|
|
727
|
+
* - Locates the exact table in the document via reference matching
|
|
728
|
+
* - Iterates from the last visual row upward
|
|
729
|
+
* - Deduplicates cells per row using `TableMap` (merged cells can repeat positions)
|
|
730
|
+
* - Treats a row as empty only if all its unique cells are empty by `isCellEmpty`
|
|
731
|
+
*
|
|
732
|
+
* @param editor - The editor whose document contains the table
|
|
733
|
+
* @param target - The table node instance to analyze (must be the same reference as in the doc)
|
|
734
|
+
* @returns The number of trailing empty rows (0 if table not found)
|
|
735
|
+
*/
|
|
736
|
+
export function countEmptyRowsFromEnd(
|
|
737
|
+
editor: Editor,
|
|
738
|
+
tablePos: number
|
|
739
|
+
): number {
|
|
740
|
+
return countEmptyCellsFromEnd(editor, tablePos, "row")
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Counts how many consecutive empty columns exist at the right edge of a given table.
|
|
745
|
+
*
|
|
746
|
+
* Similar to `countEmptyRowsFromEnd`, but scans by columns:
|
|
747
|
+
* - Iterates from the last visual column leftward
|
|
748
|
+
* - Deduplicates per-column cells using `TableMap`
|
|
749
|
+
* - A column is empty only if all unique cells in that column are empty
|
|
750
|
+
*
|
|
751
|
+
* @param editor - The editor whose document contains the table
|
|
752
|
+
* @param target - The table node instance to analyze (must be the same reference as in the doc)
|
|
753
|
+
* @returns The number of trailing empty columns (0 if table not found)
|
|
754
|
+
*/
|
|
755
|
+
export function countEmptyColumnsFromEnd(
|
|
756
|
+
editor: Editor,
|
|
757
|
+
tablePos: number
|
|
758
|
+
): number {
|
|
759
|
+
return countEmptyCellsFromEnd(editor, tablePos, "column")
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* Rounds a number with a symmetric "dead-zone" around integer boundaries,
|
|
764
|
+
* which makes drag/resize UX feel less jittery near thresholds.
|
|
765
|
+
*
|
|
766
|
+
* For example, with `margin = 0.3`:
|
|
767
|
+
* - values < n + 0.3 snap down to `n`
|
|
768
|
+
* - values > n + 0.7 snap up to `n + 1`
|
|
769
|
+
* - values in [n + 0.3, n + 0.7] fall back to `Math.round`
|
|
770
|
+
*
|
|
771
|
+
* @param num - The floating value to round
|
|
772
|
+
* @param margin - Half-width of the dead-zone around integer boundaries (default 0.3)
|
|
773
|
+
* @returns The rounded value using the dead-zone heuristic
|
|
774
|
+
*/
|
|
775
|
+
export function marginRound(num: number, margin = 0.3): number {
|
|
776
|
+
const floor = Math.floor(num)
|
|
777
|
+
const ceil = Math.ceil(num)
|
|
778
|
+
const lowerBound = floor + margin
|
|
779
|
+
const upperBound = ceil - margin
|
|
780
|
+
|
|
781
|
+
if (num < lowerBound) return floor
|
|
782
|
+
if (num > upperBound) return ceil
|
|
783
|
+
return Math.round(num)
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* Compares two DOMRect objects for equality.
|
|
788
|
+
*
|
|
789
|
+
* Treats `undefined` as a valid state, where two `undefined` rects are equal,
|
|
790
|
+
* and `undefined` is not equal to any defined rect.
|
|
791
|
+
*
|
|
792
|
+
* @param a - The first DOMRect or undefined
|
|
793
|
+
* @param b - The second DOMRect or undefined
|
|
794
|
+
* @returns true if both rects are equal or both are undefined; false otherwise
|
|
795
|
+
*/
|
|
796
|
+
export function rectEq(a?: DOMRect | null, b?: DOMRect | null): boolean {
|
|
797
|
+
if (!a && !b) return true
|
|
798
|
+
if (!a || !b) return false
|
|
799
|
+
return (
|
|
800
|
+
a.left === b.left &&
|
|
801
|
+
a.top === b.top &&
|
|
802
|
+
a.width === b.width &&
|
|
803
|
+
a.height === b.height
|
|
804
|
+
)
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/**
|
|
808
|
+
* Applies the transaction based on the specified mode
|
|
809
|
+
*/
|
|
810
|
+
function applySelectionWithMode(
|
|
811
|
+
state: EditorState,
|
|
812
|
+
transaction: Transaction,
|
|
813
|
+
options: BaseSelectionOptions | DispatchSelectionOptions
|
|
814
|
+
): EditorState | Transaction | void {
|
|
815
|
+
const mode: SelectionReturnMode = options.mode ?? "state"
|
|
816
|
+
|
|
817
|
+
switch (mode) {
|
|
818
|
+
case "dispatch": {
|
|
819
|
+
const dispatchOptions = options as DispatchSelectionOptions
|
|
820
|
+
if (typeof dispatchOptions.dispatch === "function") {
|
|
821
|
+
dispatchOptions.dispatch(transaction)
|
|
822
|
+
}
|
|
823
|
+
return
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
case "transaction":
|
|
827
|
+
return transaction
|
|
828
|
+
|
|
829
|
+
default: // "state"
|
|
830
|
+
return state.apply(transaction)
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* Create or apply a `CellSelection` inside a table.
|
|
836
|
+
*
|
|
837
|
+
* Depending on the `mode` option, this helper behaves differently:
|
|
838
|
+
*
|
|
839
|
+
* - `"state"` (default) → Returns a new `EditorState` with the selection applied.
|
|
840
|
+
* - `"transaction"` → Returns a `Transaction` with the selection set, without applying it.
|
|
841
|
+
* - `"dispatch"` → Immediately calls `dispatch(tr)` with the new selection.
|
|
842
|
+
*
|
|
843
|
+
* This allows you to reuse the same helper in commands, tests, or utilities
|
|
844
|
+
* without duplicating logic.
|
|
845
|
+
*
|
|
846
|
+
* Example:
|
|
847
|
+
* ```ts
|
|
848
|
+
* // Get new state
|
|
849
|
+
* const nextState = createTableCellSelection(state, tablePosition, { row: 1, col: 1 }, { row: 2, col: 3 })
|
|
850
|
+
*
|
|
851
|
+
* // Get transaction only
|
|
852
|
+
* const tr = createTableCellSelection(state, tablePosition, { row: 0, col: 0 }, { row: 0, col: 2 }, { mode: "transaction" })
|
|
853
|
+
*
|
|
854
|
+
* // Dispatch directly
|
|
855
|
+
* createTableCellSelection(state, tablePosition, { row: 1, col: 1 }, { row: 3, col: 2 }, { mode: "dispatch", dispatch })
|
|
856
|
+
* ```
|
|
857
|
+
*/
|
|
858
|
+
export function createTableCellSelection(
|
|
859
|
+
state: EditorState,
|
|
860
|
+
tablePosition: number,
|
|
861
|
+
startCell: CellCoordinates,
|
|
862
|
+
endCell?: CellCoordinates,
|
|
863
|
+
options?: StateSelectionOptions
|
|
864
|
+
): EditorState
|
|
865
|
+
export function createTableCellSelection(
|
|
866
|
+
state: EditorState,
|
|
867
|
+
tablePosition: number,
|
|
868
|
+
startCell: CellCoordinates,
|
|
869
|
+
endCell: CellCoordinates | undefined,
|
|
870
|
+
options: TransactionSelectionOptions
|
|
871
|
+
): Transaction
|
|
872
|
+
export function createTableCellSelection(
|
|
873
|
+
state: EditorState,
|
|
874
|
+
tablePosition: number,
|
|
875
|
+
startCell: CellCoordinates,
|
|
876
|
+
endCell: CellCoordinates | undefined,
|
|
877
|
+
options: DispatchSelectionOptions
|
|
878
|
+
): void
|
|
879
|
+
|
|
880
|
+
export function createTableCellSelection(
|
|
881
|
+
state: EditorState,
|
|
882
|
+
tablePosition: number,
|
|
883
|
+
startCell: CellCoordinates,
|
|
884
|
+
endCell: CellCoordinates = startCell,
|
|
885
|
+
options: BaseSelectionOptions | DispatchSelectionOptions = { mode: "state" }
|
|
886
|
+
): EditorState | Transaction | void {
|
|
887
|
+
const startCellPosition = getCellPosition(state, tablePosition, startCell)
|
|
888
|
+
const endCellPosition = getCellPosition(state, tablePosition, endCell)
|
|
889
|
+
|
|
890
|
+
if (!startCellPosition || !endCellPosition) {
|
|
891
|
+
return
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
const transaction = state.tr.setSelection(
|
|
895
|
+
new CellSelection(startCellPosition, endCellPosition)
|
|
896
|
+
)
|
|
897
|
+
|
|
898
|
+
return applySelectionWithMode(state, transaction, options)
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
/**
|
|
902
|
+
* Get the position of a cell inside a table by relative row/col indices.
|
|
903
|
+
* Returns the position *before* the cell, which is what `CellSelection` expects.
|
|
904
|
+
*/
|
|
905
|
+
export function getCellPosition(
|
|
906
|
+
state: EditorState,
|
|
907
|
+
tablePosition: number,
|
|
908
|
+
cellCoordinates: CellCoordinates
|
|
909
|
+
) {
|
|
910
|
+
const resolvedTablePosition = state.doc.resolve(tablePosition)
|
|
911
|
+
const resolvedRowPosition = state.doc.resolve(
|
|
912
|
+
resolvedTablePosition.posAtIndex(cellCoordinates.row) + 1
|
|
913
|
+
)
|
|
914
|
+
const resolvedColPosition = state.doc.resolve(
|
|
915
|
+
resolvedRowPosition.posAtIndex(cellCoordinates.col)
|
|
916
|
+
)
|
|
917
|
+
|
|
918
|
+
const $cell = cellAround(resolvedColPosition)
|
|
919
|
+
if (!$cell) return null
|
|
920
|
+
|
|
921
|
+
return resolvedColPosition
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
/**
|
|
925
|
+
* Selects table cells by their (row, col) coordinates.
|
|
926
|
+
*
|
|
927
|
+
* This function can be used in three modes:
|
|
928
|
+
* - `"state"` (default) → Returns a new `EditorState` with the selection applied, or null if failed.
|
|
929
|
+
* - `"transaction"` → Returns a `Transaction` with the selection set, or null if failed.
|
|
930
|
+
* - `"dispatch"` → Immediately dispatches the selection and returns boolean success status.
|
|
931
|
+
*
|
|
932
|
+
* @param editor - The editor instance
|
|
933
|
+
* @param tablePos - Position of the table in the document
|
|
934
|
+
* @param coords - Array of {row, col} coordinates to select
|
|
935
|
+
* @param options - Mode and dispatch options
|
|
936
|
+
*/
|
|
937
|
+
export function selectCellsByCoords(
|
|
938
|
+
editor: Editor | null,
|
|
939
|
+
tablePos: number,
|
|
940
|
+
coords: { row: number; col: number }[],
|
|
941
|
+
options?: StateSelectionOptions
|
|
942
|
+
): EditorState
|
|
943
|
+
export function selectCellsByCoords(
|
|
944
|
+
editor: Editor | null,
|
|
945
|
+
tablePos: number,
|
|
946
|
+
coords: { row: number; col: number }[],
|
|
947
|
+
options: TransactionSelectionOptions
|
|
948
|
+
): Transaction
|
|
949
|
+
export function selectCellsByCoords(
|
|
950
|
+
editor: Editor | null,
|
|
951
|
+
tablePos: number,
|
|
952
|
+
coords: { row: number; col: number }[],
|
|
953
|
+
options: DispatchSelectionOptions
|
|
954
|
+
): void
|
|
955
|
+
export function selectCellsByCoords(
|
|
956
|
+
editor: Editor | null,
|
|
957
|
+
tablePos: number,
|
|
958
|
+
coords: { row: number; col: number }[],
|
|
959
|
+
options: BaseSelectionOptions | DispatchSelectionOptions = { mode: "state" }
|
|
960
|
+
): EditorState | Transaction | void {
|
|
961
|
+
if (!editor) return
|
|
962
|
+
|
|
963
|
+
const table = getTable(editor, tablePos)
|
|
964
|
+
if (!table) return
|
|
965
|
+
|
|
966
|
+
const { state } = editor
|
|
967
|
+
const tableMap = table.map
|
|
968
|
+
|
|
969
|
+
const cleanedCoords = coords
|
|
970
|
+
.map((coord) => ({
|
|
971
|
+
row: clamp(coord.row, 0, tableMap.height - 1),
|
|
972
|
+
col: clamp(coord.col, 0, tableMap.width - 1),
|
|
973
|
+
}))
|
|
974
|
+
.filter((coord) => isWithinBounds(coord.row, coord.col, tableMap))
|
|
975
|
+
|
|
976
|
+
if (cleanedCoords.length === 0) {
|
|
977
|
+
return
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// --- Find the smallest rectangle that contains all our coordinates ---
|
|
981
|
+
const allRows = cleanedCoords.map((coord) => coord.row)
|
|
982
|
+
const topRow = Math.min(...allRows)
|
|
983
|
+
const bottomRow = Math.max(...allRows)
|
|
984
|
+
|
|
985
|
+
const allCols = cleanedCoords.map((coord) => coord.col)
|
|
986
|
+
const leftCol = Math.min(...allCols)
|
|
987
|
+
const rightCol = Math.max(...allCols)
|
|
988
|
+
|
|
989
|
+
// --- Convert visual coordinates to document positions ---
|
|
990
|
+
// Use TableMap.map array directly to handle merged cells correctly
|
|
991
|
+
const getCellPositionFromMap = (row: number, col: number): number | null => {
|
|
992
|
+
// TableMap.map is a flat array where each entry represents a cell
|
|
993
|
+
// For merged cells, the same offset appears multiple times
|
|
994
|
+
const cellOffset = tableMap.map[row * tableMap.width + col]
|
|
995
|
+
if (cellOffset === undefined) return null
|
|
996
|
+
|
|
997
|
+
// Convert the relative offset to an absolute position in the document
|
|
998
|
+
// tablePos + 1 skips the table opening tag
|
|
999
|
+
return tablePos + 1 + cellOffset
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// Anchor = where the selection starts (top-left of bounding box)
|
|
1003
|
+
const anchorPosition = getCellPositionFromMap(topRow, leftCol)
|
|
1004
|
+
if (anchorPosition === null) return
|
|
1005
|
+
|
|
1006
|
+
// Head = where the selection ends (usually bottom-right of bounding box)
|
|
1007
|
+
let headPosition = getCellPositionFromMap(bottomRow, rightCol)
|
|
1008
|
+
if (headPosition === null) return
|
|
1009
|
+
|
|
1010
|
+
// --- Handle edge case with merged cells ---
|
|
1011
|
+
// If anchor and head point to the same cell, we need to find a different head
|
|
1012
|
+
// This happens when selecting a single merged cell or when all coords point to one cell
|
|
1013
|
+
if (headPosition === anchorPosition) {
|
|
1014
|
+
let foundDifferentCell = false
|
|
1015
|
+
|
|
1016
|
+
// Search backwards from bottom-right to find a cell with a different position
|
|
1017
|
+
for (let row = bottomRow; row >= topRow && !foundDifferentCell; row--) {
|
|
1018
|
+
for (let col = rightCol; col >= leftCol && !foundDifferentCell; col--) {
|
|
1019
|
+
const candidatePosition = getCellPositionFromMap(row, col)
|
|
1020
|
+
|
|
1021
|
+
if (
|
|
1022
|
+
candidatePosition !== null &&
|
|
1023
|
+
candidatePosition !== anchorPosition
|
|
1024
|
+
) {
|
|
1025
|
+
headPosition = candidatePosition
|
|
1026
|
+
foundDifferentCell = true
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
try {
|
|
1033
|
+
const anchorRef = state.doc.resolve(anchorPosition)
|
|
1034
|
+
const headRef = state.doc.resolve(headPosition)
|
|
1035
|
+
|
|
1036
|
+
const cellSelection = new CellSelection(anchorRef, headRef)
|
|
1037
|
+
const transaction = state.tr.setSelection(cellSelection)
|
|
1038
|
+
|
|
1039
|
+
return applySelectionWithMode(state, transaction, options)
|
|
1040
|
+
} catch (error) {
|
|
1041
|
+
console.error("Failed to create cell selection:", error)
|
|
1042
|
+
return
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
/**
|
|
1047
|
+
* Select the cell at (row, col) using `cellAround` to respect merged cells.
|
|
1048
|
+
*
|
|
1049
|
+
* @param editor Tiptap editor
|
|
1050
|
+
* @param row Row index (0-based)
|
|
1051
|
+
* @param col Column index (0-based)
|
|
1052
|
+
* @param tablePos Optional absolute position of the table node
|
|
1053
|
+
* @param dispatch Optional dispatch; defaults to editor.view.dispatch
|
|
1054
|
+
*/
|
|
1055
|
+
export function selectCellAt({
|
|
1056
|
+
editor,
|
|
1057
|
+
row,
|
|
1058
|
+
col,
|
|
1059
|
+
tablePos,
|
|
1060
|
+
dispatch,
|
|
1061
|
+
}: {
|
|
1062
|
+
editor: Editor | null
|
|
1063
|
+
row: number
|
|
1064
|
+
col: number
|
|
1065
|
+
tablePos?: number
|
|
1066
|
+
dispatch?: (tr: Transaction) => void
|
|
1067
|
+
}): boolean {
|
|
1068
|
+
if (!editor) return false
|
|
1069
|
+
|
|
1070
|
+
const { state, view } = editor
|
|
1071
|
+
const found = getTable(editor, tablePos)
|
|
1072
|
+
if (!found) return false
|
|
1073
|
+
|
|
1074
|
+
// Bounds check
|
|
1075
|
+
if (!isWithinBounds(row, col, found.map)) {
|
|
1076
|
+
return false
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
const relCellPos = found.map.positionAt(row, col, found.node)
|
|
1080
|
+
const absCellPos = found.start + relCellPos
|
|
1081
|
+
|
|
1082
|
+
const $abs = state.doc.resolve(absCellPos)
|
|
1083
|
+
const $cell = cellAround($abs)
|
|
1084
|
+
const cellPos = $cell ? $cell.pos : absCellPos
|
|
1085
|
+
|
|
1086
|
+
const sel = CellSelection.create(state.doc, cellPos)
|
|
1087
|
+
|
|
1088
|
+
const doDispatch = dispatch ?? view?.dispatch
|
|
1089
|
+
if (!doDispatch) return false
|
|
1090
|
+
|
|
1091
|
+
doDispatch(state.tr.setSelection(sel))
|
|
1092
|
+
return true
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
/**
|
|
1096
|
+
* Selects a boundary cell of the table based on orientation.
|
|
1097
|
+
*
|
|
1098
|
+
* For row orientation, selects the bottom-left cell of the table.
|
|
1099
|
+
* For column orientation, selects the top-right cell of the table.
|
|
1100
|
+
*
|
|
1101
|
+
* This function accounts for merged cells to ensure the correct cell is selected.
|
|
1102
|
+
*
|
|
1103
|
+
* @param editor The Tiptap editor instance
|
|
1104
|
+
* @param tableNode The table node
|
|
1105
|
+
* @param tablePos The position of the table node in the document
|
|
1106
|
+
* @param orientation "row" to select bottom-left, "column" to select top-right
|
|
1107
|
+
* @returns true if the selection was successful; false otherwise
|
|
1108
|
+
*/
|
|
1109
|
+
export function selectLastCell(
|
|
1110
|
+
editor: Editor,
|
|
1111
|
+
tableNode: Node,
|
|
1112
|
+
tablePos: number,
|
|
1113
|
+
orientation: Orientation
|
|
1114
|
+
) {
|
|
1115
|
+
const map = TableMap.get(tableNode)
|
|
1116
|
+
const isRow = orientation === "row"
|
|
1117
|
+
|
|
1118
|
+
// For rows, select bottom-left cell; for columns, select top-right cell
|
|
1119
|
+
const row = isRow ? map.height - 1 : 0
|
|
1120
|
+
const col = isRow ? 0 : map.width - 1
|
|
1121
|
+
|
|
1122
|
+
// Calculate the index in the table map
|
|
1123
|
+
const index = row * map.width + col
|
|
1124
|
+
|
|
1125
|
+
// Get the actual cell position from the map (handles merged cells)
|
|
1126
|
+
const cellPos = map.map[index]
|
|
1127
|
+
if (!cellPos && cellPos !== 0) {
|
|
1128
|
+
console.warn("selectLastCell: cell position not found in map", {
|
|
1129
|
+
index,
|
|
1130
|
+
row,
|
|
1131
|
+
col,
|
|
1132
|
+
map,
|
|
1133
|
+
})
|
|
1134
|
+
return false
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
// Find the row and column of the actual cell
|
|
1138
|
+
const cellIndex = map.map.indexOf(cellPos)
|
|
1139
|
+
const actualRow = cellIndex >= 0 ? Math.floor(cellIndex / map.width) : 0
|
|
1140
|
+
const actualCol = cellIndex >= 0 ? cellIndex % map.width : 0
|
|
1141
|
+
|
|
1142
|
+
return selectCellAt({
|
|
1143
|
+
editor,
|
|
1144
|
+
row: actualRow,
|
|
1145
|
+
col: actualCol,
|
|
1146
|
+
tablePos,
|
|
1147
|
+
dispatch: editor.view.dispatch.bind(editor.view),
|
|
1148
|
+
})
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
/**
|
|
1152
|
+
* Get all (row, col) coordinates for a given row or column index.
|
|
1153
|
+
*
|
|
1154
|
+
* - If `orientation` is "row", returns all columns in that row.
|
|
1155
|
+
* - If `orientation` is "column", returns all rows in that column.
|
|
1156
|
+
*
|
|
1157
|
+
* Returns null if:
|
|
1158
|
+
* - the editor or table is not found
|
|
1159
|
+
* - the index is out of bounds
|
|
1160
|
+
*
|
|
1161
|
+
* @param editor The Tiptap editor instance
|
|
1162
|
+
* @param index The row or column index (0-based)
|
|
1163
|
+
* @param orientation "row" to get row coordinates, "column" for column coordinates
|
|
1164
|
+
* @param tablePos Optional position of the table node in the document
|
|
1165
|
+
* @returns Array of {row, col} objects or null if invalid
|
|
1166
|
+
*/
|
|
1167
|
+
export function getIndexCoordinates({
|
|
1168
|
+
editor,
|
|
1169
|
+
index,
|
|
1170
|
+
orientation,
|
|
1171
|
+
tablePos,
|
|
1172
|
+
}: {
|
|
1173
|
+
editor: Editor | null
|
|
1174
|
+
index: number
|
|
1175
|
+
orientation?: Orientation
|
|
1176
|
+
tablePos?: number
|
|
1177
|
+
}): { row: number; col: number }[] | null {
|
|
1178
|
+
if (!editor) return null
|
|
1179
|
+
|
|
1180
|
+
const table = getTable(editor, tablePos)
|
|
1181
|
+
if (!table) return null
|
|
1182
|
+
|
|
1183
|
+
const { map } = table
|
|
1184
|
+
const { width, height } = map
|
|
1185
|
+
|
|
1186
|
+
if (index < 0) return null
|
|
1187
|
+
if (orientation === "row" && index >= height) return null
|
|
1188
|
+
if (orientation === "column" && index >= width) return null
|
|
1189
|
+
|
|
1190
|
+
return orientation === "row"
|
|
1191
|
+
? Array.from({ length: map.width }, (_, col) => ({ row: index, col }))
|
|
1192
|
+
: Array.from({ length: map.height }, (_, row) => ({ row, col: index }))
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
/**
|
|
1196
|
+
* Given a DOM cell element, find its (row, col) indices within the table.
|
|
1197
|
+
*
|
|
1198
|
+
* This function:
|
|
1199
|
+
* - Locates the nearest ancestor table element
|
|
1200
|
+
* - Uses the editor's document model to resolve the cell's position
|
|
1201
|
+
* - Traverses up the node hierarchy to find the corresponding table cell node
|
|
1202
|
+
* - Uses `TableMap` to translate the cell's position into (row, col) indices
|
|
1203
|
+
*
|
|
1204
|
+
* Returns null if:
|
|
1205
|
+
* - the table or cell cannot be found in the editor's document
|
|
1206
|
+
* - any error occurs during position resolution
|
|
1207
|
+
*
|
|
1208
|
+
* @param cell The HTMLTableCellElement (td or th)
|
|
1209
|
+
* @param tableNode The table node in the ProseMirror document
|
|
1210
|
+
* @param editor The Tiptap editor instance
|
|
1211
|
+
* @returns An object with { rowIndex, colIndex } or null if not found
|
|
1212
|
+
*/
|
|
1213
|
+
export function getCellIndicesFromDOM(
|
|
1214
|
+
cell: HTMLTableCellElement,
|
|
1215
|
+
tableNode: Node | null,
|
|
1216
|
+
editor: Editor
|
|
1217
|
+
): { rowIndex: number; colIndex: number } | null {
|
|
1218
|
+
if (!tableNode) return null
|
|
1219
|
+
|
|
1220
|
+
try {
|
|
1221
|
+
const cellPos = editor.view.posAtDOM(cell, 0)
|
|
1222
|
+
const $cellPos = editor.view.state.doc.resolve(cellPos)
|
|
1223
|
+
|
|
1224
|
+
for (let d = $cellPos.depth; d > 0; d--) {
|
|
1225
|
+
const node = $cellPos.node(d)
|
|
1226
|
+
if (node.type.name === "tableCell" || node.type.name === "tableHeader") {
|
|
1227
|
+
const tableMap = TableMap.get(tableNode)
|
|
1228
|
+
const cellNodePos = $cellPos.before(d)
|
|
1229
|
+
const tableStart = $cellPos.start(d - 2)
|
|
1230
|
+
const cellOffset = cellNodePos - tableStart
|
|
1231
|
+
const cellIndex = tableMap.map.indexOf(cellOffset)
|
|
1232
|
+
|
|
1233
|
+
return {
|
|
1234
|
+
rowIndex: Math.floor(cellIndex / tableMap.width),
|
|
1235
|
+
colIndex: cellIndex % tableMap.width,
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
} catch (error) {
|
|
1240
|
+
console.warn("Could not get cell position:", error)
|
|
1241
|
+
}
|
|
1242
|
+
return null
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
/**
|
|
1246
|
+
* Given a DOM element inside a table, find the corresponding table node and its position.
|
|
1247
|
+
*
|
|
1248
|
+
* This function:
|
|
1249
|
+
* - Locates the nearest ancestor table element
|
|
1250
|
+
* - Uses the editor's document model to resolve the table's position
|
|
1251
|
+
* - Traverses up the node hierarchy to find the corresponding table node
|
|
1252
|
+
*
|
|
1253
|
+
* Returns null if:
|
|
1254
|
+
* - the table cannot be found in the editor's document
|
|
1255
|
+
* - any error occurs during position resolution
|
|
1256
|
+
*
|
|
1257
|
+
* @param tableElement The HTMLTableElement or an element inside it
|
|
1258
|
+
* @param editor The Tiptap editor instance
|
|
1259
|
+
* @returns An object with { node: tableNode, pos: tablePos } or null if not found
|
|
1260
|
+
*/
|
|
1261
|
+
export function getTableFromDOM(
|
|
1262
|
+
tableElement: HTMLElement,
|
|
1263
|
+
editor: Editor
|
|
1264
|
+
): { node: Node; pos: number } | null {
|
|
1265
|
+
try {
|
|
1266
|
+
const pos = editor.view.posAtDOM(tableElement, 0)
|
|
1267
|
+
const $pos = editor.view.state.doc.resolve(pos)
|
|
1268
|
+
|
|
1269
|
+
for (let d = $pos.depth; d >= 0; d--) {
|
|
1270
|
+
const node = $pos.node(d)
|
|
1271
|
+
if (isTableNode(node)) {
|
|
1272
|
+
return { node, pos: d === 0 ? 0 : $pos.before(d) }
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
} catch (error) {
|
|
1276
|
+
console.warn("Could not get table from DOM:", error)
|
|
1277
|
+
}
|
|
1278
|
+
return null
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
/**
|
|
1282
|
+
* Checks if a node is a table node
|
|
1283
|
+
*/
|
|
1284
|
+
export function isTableNode(node: Node | null | undefined): node is Node {
|
|
1285
|
+
return (
|
|
1286
|
+
!!node &&
|
|
1287
|
+
(node.type.name === "table" || node.type.spec.tableRole === "table")
|
|
1288
|
+
)
|
|
1289
|
+
}
|