@prosekit/extensions 0.11.4 → 0.11.6
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/dist/commit/style.css +2 -0
- package/dist/commit/style.css.map +1 -0
- package/dist/commit/style.js +1 -0
- package/dist/drop-indicator-E7nCfdnR.js +58 -0
- package/dist/drop-indicator-E7nCfdnR.js.map +1 -0
- package/dist/enter-rule-RdhEA900.js +2 -1
- package/dist/enter-rule-RdhEA900.js.map +1 -0
- package/dist/file-DVUhe5KJ.js +134 -0
- package/dist/file-DVUhe5KJ.js.map +1 -0
- package/dist/gap-cursor/style.css +2 -0
- package/dist/gap-cursor/style.css.map +1 -0
- package/dist/gap-cursor/style.js +1 -0
- package/dist/index-DY6lIIYV.d.ts +134 -0
- package/dist/index-DY6lIIYV.d.ts.map +1 -0
- package/dist/{input-rule-Gji4N7Oe.js → input-rule-B17tpW4m.js} +3 -3
- package/dist/input-rule-B17tpW4m.js.map +1 -0
- package/dist/list/style.css +2 -0
- package/dist/list/style.css.map +1 -0
- package/dist/list/style.js +1 -0
- package/dist/loro/style.css +2 -0
- package/dist/loro/style.css.map +1 -0
- package/dist/loro/style.js +1 -0
- package/dist/{mark-rule-D7zaa32n.js → mark-rule-CGmswjQ_.js} +3 -3
- package/dist/mark-rule-CGmswjQ_.js.map +1 -0
- package/dist/{paste-rule-Cca3n5TA.js → paste-rule-BIztzELg.js} +5 -15
- package/dist/paste-rule-BIztzELg.js.map +1 -0
- package/dist/placeholder/style.css +2 -0
- package/dist/placeholder/style.css.map +1 -0
- package/dist/placeholder/style.js +1 -0
- package/dist/prosekit-extensions-autocomplete.d.ts +2 -1
- package/dist/prosekit-extensions-autocomplete.d.ts.map +1 -0
- package/dist/prosekit-extensions-autocomplete.js +2 -9
- package/dist/prosekit-extensions-autocomplete.js.map +1 -0
- package/dist/prosekit-extensions-blockquote.d.ts +2 -1
- package/dist/prosekit-extensions-blockquote.d.ts.map +1 -0
- package/dist/prosekit-extensions-blockquote.js +4 -4
- package/dist/prosekit-extensions-blockquote.js.map +1 -0
- package/dist/prosekit-extensions-bold.d.ts +2 -1
- package/dist/prosekit-extensions-bold.d.ts.map +1 -0
- package/dist/prosekit-extensions-bold.js +3 -2
- package/dist/prosekit-extensions-bold.js.map +1 -0
- package/dist/prosekit-extensions-code-block.d.ts +3 -2
- package/dist/prosekit-extensions-code-block.d.ts.map +1 -0
- package/dist/prosekit-extensions-code-block.js +5 -6
- package/dist/prosekit-extensions-code-block.js.map +1 -0
- package/dist/prosekit-extensions-code.d.ts +2 -1
- package/dist/prosekit-extensions-code.d.ts.map +1 -0
- package/dist/prosekit-extensions-code.js +3 -2
- package/dist/prosekit-extensions-code.js.map +1 -0
- package/dist/prosekit-extensions-commit.d.ts +2 -1
- package/dist/prosekit-extensions-commit.d.ts.map +1 -0
- package/dist/prosekit-extensions-commit.js +11 -13
- package/dist/prosekit-extensions-commit.js.map +1 -0
- package/dist/prosekit-extensions-doc.d.ts +2 -1
- package/dist/prosekit-extensions-doc.d.ts.map +1 -0
- package/dist/prosekit-extensions-doc.js +2 -1
- package/dist/prosekit-extensions-doc.js.map +1 -0
- package/dist/prosekit-extensions-drop-cursor.d.ts +2 -1
- package/dist/prosekit-extensions-drop-cursor.d.ts.map +1 -0
- package/dist/prosekit-extensions-drop-cursor.js +2 -1
- package/dist/prosekit-extensions-drop-cursor.js.map +1 -0
- package/dist/prosekit-extensions-drop-indicator.d.ts +5 -107
- package/dist/prosekit-extensions-drop-indicator.d.ts.map +1 -0
- package/dist/prosekit-extensions-drop-indicator.js +1 -1
- package/dist/prosekit-extensions-enter-rule.d.ts +2 -1
- package/dist/prosekit-extensions-enter-rule.d.ts.map +1 -0
- package/dist/prosekit-extensions-file.d.ts +2 -125
- package/dist/prosekit-extensions-file.js +1 -139
- package/dist/prosekit-extensions-gap-cursor.d.ts +2 -2
- package/dist/prosekit-extensions-gap-cursor.d.ts.map +1 -0
- package/dist/prosekit-extensions-gap-cursor.js +2 -1
- package/dist/prosekit-extensions-gap-cursor.js.map +1 -0
- package/dist/prosekit-extensions-hard-break.d.ts +2 -5
- package/dist/prosekit-extensions-hard-break.d.ts.map +1 -0
- package/dist/prosekit-extensions-hard-break.js +2 -1
- package/dist/prosekit-extensions-hard-break.js.map +1 -0
- package/dist/prosekit-extensions-heading.d.ts +2 -1
- package/dist/prosekit-extensions-heading.d.ts.map +1 -0
- package/dist/prosekit-extensions-heading.js +5 -6
- package/dist/prosekit-extensions-heading.js.map +1 -0
- package/dist/prosekit-extensions-horizontal-rule.d.ts +2 -1
- package/dist/prosekit-extensions-horizontal-rule.d.ts.map +1 -0
- package/dist/prosekit-extensions-horizontal-rule.js +5 -6
- package/dist/prosekit-extensions-horizontal-rule.js.map +1 -0
- package/dist/prosekit-extensions-image.d.ts +80 -3
- package/dist/prosekit-extensions-image.d.ts.map +1 -0
- package/dist/prosekit-extensions-image.js +90 -10
- package/dist/prosekit-extensions-image.js.map +1 -0
- package/dist/prosekit-extensions-input-rule.d.ts +2 -1
- package/dist/prosekit-extensions-input-rule.d.ts.map +1 -0
- package/dist/prosekit-extensions-input-rule.js +1 -1
- package/dist/prosekit-extensions-italic.d.ts +2 -1
- package/dist/prosekit-extensions-italic.d.ts.map +1 -0
- package/dist/prosekit-extensions-italic.js +3 -2
- package/dist/prosekit-extensions-italic.js.map +1 -0
- package/dist/prosekit-extensions-link.d.ts +2 -1
- package/dist/prosekit-extensions-link.d.ts.map +1 -0
- package/dist/prosekit-extensions-link.js +6 -5
- package/dist/prosekit-extensions-link.js.map +1 -0
- package/dist/prosekit-extensions-list.d.ts +22 -21
- package/dist/prosekit-extensions-list.d.ts.map +1 -0
- package/dist/prosekit-extensions-list.js +7 -8
- package/dist/prosekit-extensions-list.js.map +1 -0
- package/dist/prosekit-extensions-loro.d.ts +14 -13
- package/dist/prosekit-extensions-loro.d.ts.map +1 -0
- package/dist/prosekit-extensions-loro.js +2 -1
- package/dist/prosekit-extensions-loro.js.map +1 -0
- package/dist/prosekit-extensions-mark-rule.d.ts +2 -1
- package/dist/prosekit-extensions-mark-rule.d.ts.map +1 -0
- package/dist/prosekit-extensions-mark-rule.js +1 -1
- package/dist/prosekit-extensions-mention.d.ts +2 -1
- package/dist/prosekit-extensions-mention.d.ts.map +1 -0
- package/dist/prosekit-extensions-mention.js +2 -1
- package/dist/prosekit-extensions-mention.js.map +1 -0
- package/dist/prosekit-extensions-mod-click-prevention.d.ts +2 -1
- package/dist/prosekit-extensions-mod-click-prevention.d.ts.map +1 -0
- package/dist/prosekit-extensions-mod-click-prevention.js +2 -1
- package/dist/prosekit-extensions-mod-click-prevention.js.map +1 -0
- package/dist/prosekit-extensions-paragraph.d.ts +2 -5
- package/dist/prosekit-extensions-paragraph.d.ts.map +1 -0
- package/dist/prosekit-extensions-paragraph.js +2 -1
- package/dist/prosekit-extensions-paragraph.js.map +1 -0
- package/dist/prosekit-extensions-paste-rule.d.ts +2 -1
- package/dist/prosekit-extensions-paste-rule.d.ts.map +1 -0
- package/dist/prosekit-extensions-paste-rule.js +1 -1
- package/dist/prosekit-extensions-placeholder.d.ts +2 -1
- package/dist/prosekit-extensions-placeholder.d.ts.map +1 -0
- package/dist/prosekit-extensions-placeholder.js +5 -5
- package/dist/prosekit-extensions-placeholder.js.map +1 -0
- package/dist/prosekit-extensions-readonly.d.ts +2 -1
- package/dist/prosekit-extensions-readonly.d.ts.map +1 -0
- package/dist/prosekit-extensions-readonly.js +2 -1
- package/dist/prosekit-extensions-readonly.js.map +1 -0
- package/dist/prosekit-extensions-search.d.ts +2 -1
- package/dist/prosekit-extensions-search.d.ts.map +1 -0
- package/dist/prosekit-extensions-search.js +3 -3
- package/dist/prosekit-extensions-search.js.map +1 -0
- package/dist/prosekit-extensions-strike.d.ts +2 -1
- package/dist/prosekit-extensions-strike.d.ts.map +1 -0
- package/dist/prosekit-extensions-strike.js +3 -2
- package/dist/prosekit-extensions-strike.js.map +1 -0
- package/dist/prosekit-extensions-table.d.ts +47 -114
- package/dist/prosekit-extensions-table.d.ts.map +1 -0
- package/dist/prosekit-extensions-table.js +2 -2
- package/dist/prosekit-extensions-text-align.d.ts +2 -1
- package/dist/prosekit-extensions-text-align.d.ts.map +1 -0
- package/dist/prosekit-extensions-text-align.js +2 -1
- package/dist/prosekit-extensions-text-align.js.map +1 -0
- package/dist/prosekit-extensions-text.d.ts +2 -1
- package/dist/prosekit-extensions-text.d.ts.map +1 -0
- package/dist/prosekit-extensions-text.js +2 -1
- package/dist/prosekit-extensions-text.js.map +1 -0
- package/dist/prosekit-extensions-underline.d.ts +2 -1
- package/dist/prosekit-extensions-underline.d.ts.map +1 -0
- package/dist/prosekit-extensions-underline.js +2 -1
- package/dist/prosekit-extensions-underline.js.map +1 -0
- package/dist/prosekit-extensions-virtual-selection.d.ts +2 -1
- package/dist/prosekit-extensions-virtual-selection.d.ts.map +1 -0
- package/dist/prosekit-extensions-virtual-selection.js +3 -3
- package/dist/prosekit-extensions-virtual-selection.js.map +1 -0
- package/dist/prosekit-extensions-yjs.d.ts +2 -1
- package/dist/prosekit-extensions-yjs.d.ts.map +1 -0
- package/dist/prosekit-extensions-yjs.js +2 -1
- package/dist/prosekit-extensions-yjs.js.map +1 -0
- package/dist/prosekit-extensions.js +1 -0
- package/dist/search/style.css +2 -0
- package/dist/search/style.css.map +1 -0
- package/dist/search/style.js +1 -0
- package/dist/{shiki-highlighter-chunk-DSPM0T27.d.ts → shiki-highlighter-chunk-Cwu1Jr9o.d.ts} +2 -1
- package/dist/shiki-highlighter-chunk-Cwu1Jr9o.d.ts.map +1 -0
- package/dist/shiki-highlighter-chunk.d.ts +1 -1
- package/dist/shiki-highlighter-chunk.js +3 -5
- package/dist/shiki-highlighter-chunk.js.map +1 -0
- package/dist/table/style.css +2 -0
- package/dist/table/style.css.map +1 -0
- package/dist/table/style.js +1 -0
- package/dist/table-BNwuK7xg.js +297 -0
- package/dist/table-BNwuK7xg.js.map +1 -0
- package/dist/virtual-selection/style.css +2 -0
- package/dist/virtual-selection/style.css.map +1 -0
- package/dist/virtual-selection/style.js +1 -0
- package/dist/yjs/style.css +2 -0
- package/dist/yjs/style.css.map +1 -0
- package/dist/yjs/style.js +1 -0
- package/package.json +12 -10
- package/src/autocomplete/autocomplete-helpers.ts +74 -0
- package/src/autocomplete/autocomplete-plugin.ts +186 -0
- package/src/autocomplete/autocomplete-rule.ts +117 -0
- package/src/autocomplete/autocomplete.spec.ts +132 -0
- package/src/autocomplete/autocomplete.ts +29 -0
- package/src/autocomplete/index.ts +9 -0
- package/src/blockquote/blockquote-commands.ts +32 -0
- package/src/blockquote/blockquote-input-rule.ts +14 -0
- package/src/blockquote/blockquote-keymap.spec.ts +45 -0
- package/src/blockquote/blockquote-keymap.ts +31 -0
- package/src/blockquote/blockquote-spec.ts +24 -0
- package/src/blockquote/blockquote.ts +34 -0
- package/src/blockquote/index.ts +14 -0
- package/src/bold/bold-commands.ts +23 -0
- package/src/bold/bold-input-rule.spec.ts +51 -0
- package/src/bold/bold-input-rule.ts +18 -0
- package/src/bold/bold-keymap.ts +14 -0
- package/src/bold/bold-spec.ts +53 -0
- package/src/bold/bold.ts +32 -0
- package/src/bold/index.ts +14 -0
- package/src/code/code-commands.ts +23 -0
- package/src/code/code-input-rule.ts +18 -0
- package/src/code/code-keymap.ts +14 -0
- package/src/code/code-spec.ts +28 -0
- package/src/code/code.ts +32 -0
- package/src/code/index.ts +14 -0
- package/src/code-block/code-block-commands.ts +44 -0
- package/src/code-block/code-block-highlight.ts +40 -0
- package/src/code-block/code-block-input-rule.ts +36 -0
- package/src/code-block/code-block-keymap.ts +61 -0
- package/src/code-block/code-block-shiki.ts +58 -0
- package/src/code-block/code-block-spec.spec.ts +164 -0
- package/src/code-block/code-block-spec.ts +71 -0
- package/src/code-block/code-block-types.ts +8 -0
- package/src/code-block/code-block.ts +46 -0
- package/src/code-block/index.ts +32 -0
- package/src/code-block/shiki-bundle.ts +8 -0
- package/src/code-block/shiki-highlighter-chunk.ts +84 -0
- package/src/code-block/shiki-highlighter.ts +22 -0
- package/src/code-block/shiki-parser.ts +36 -0
- package/src/commit/index.ts +330 -0
- package/src/commit/style.css +7 -0
- package/src/doc/index.ts +21 -0
- package/src/drop-cursor/drop-cursor.ts +46 -0
- package/src/drop-cursor/index.ts +5 -0
- package/src/drop-indicator/drop-indicator-facet.ts +62 -0
- package/src/drop-indicator/drop-indicator.ts +35 -0
- package/src/drop-indicator/index.ts +14 -0
- package/src/enter-rule/index.ts +241 -0
- package/src/file/file-drop-handler.ts +75 -0
- package/src/file/file-paste-handler.spec.ts +95 -0
- package/src/file/file-paste-handler.ts +59 -0
- package/src/file/file-upload.ts +140 -0
- package/src/file/helpers.ts +39 -0
- package/src/file/index.ts +16 -0
- package/src/gap-cursor/gap-cursor.ts +28 -0
- package/src/gap-cursor/index.ts +4 -0
- package/src/gap-cursor/style.css +25 -0
- package/src/hard-break/hard-break-commands.ts +31 -0
- package/src/hard-break/hard-break-keymap.spec.ts +45 -0
- package/src/hard-break/hard-break-keymap.ts +16 -0
- package/src/hard-break/hard-break-spec.ts +31 -0
- package/src/hard-break/hard-break.ts +32 -0
- package/src/hard-break/index.ts +13 -0
- package/src/heading/heading-commands.ts +37 -0
- package/src/heading/heading-input-rule.ts +22 -0
- package/src/heading/heading-keymap.spec.ts +53 -0
- package/src/heading/heading-keymap.ts +40 -0
- package/src/heading/heading-spec.ts +39 -0
- package/src/heading/heading-types.ts +3 -0
- package/src/heading/heading.ts +34 -0
- package/src/heading/index.ts +15 -0
- package/src/horizontal-rule/horizontal-rule-commands.spec.ts +61 -0
- package/src/horizontal-rule/horizontal-rule-commands.ts +37 -0
- package/src/horizontal-rule/horizontal-rule-input-rule.spec.ts +61 -0
- package/src/horizontal-rule/horizontal-rule-input-rule.ts +26 -0
- package/src/horizontal-rule/horizontal-rule-spec.ts +21 -0
- package/src/horizontal-rule/horizontal-rule.ts +29 -0
- package/src/horizontal-rule/index.ts +14 -0
- package/src/image/image-commands.ts +36 -0
- package/src/image/image-spec.ts +72 -0
- package/src/image/image-upload-handler.ts +156 -0
- package/src/image/image.ts +25 -0
- package/src/image/index.ts +22 -0
- package/src/index.ts +1 -0
- package/src/input-rule/index.ts +237 -0
- package/src/italic/index.ts +14 -0
- package/src/italic/italic-commands.spec.ts +75 -0
- package/src/italic/italic-commands.ts +23 -0
- package/src/italic/italic-input-rule.spec.ts +25 -0
- package/src/italic/italic-input-rule.ts +18 -0
- package/src/italic/italic-keymap.ts +14 -0
- package/src/italic/italic-spec.ts +35 -0
- package/src/italic/italic.ts +34 -0
- package/src/link/index.spec.ts +88 -0
- package/src/link/index.ts +156 -0
- package/src/link/link-paste-rule.spec.ts +194 -0
- package/src/link/link-paste-rule.ts +22 -0
- package/src/link/link-regex.spec.ts +82 -0
- package/src/link/link-regex.ts +79 -0
- package/src/link/link-types.ts +8 -0
- package/src/list/index.ts +25 -0
- package/src/list/list-commands.ts +61 -0
- package/src/list/list-drop-indicator.ts +37 -0
- package/src/list/list-input-rules.ts +14 -0
- package/src/list/list-keymap.spec.ts +39 -0
- package/src/list/list-keymap.ts +48 -0
- package/src/list/list-plugins.ts +35 -0
- package/src/list/list-serializer.ts +38 -0
- package/src/list/list-spec.ts +60 -0
- package/src/list/list-types.spec.ts +10 -0
- package/src/list/list-types.ts +23 -0
- package/src/list/list.spec.ts +134 -0
- package/src/list/list.ts +38 -0
- package/src/list/style.css +128 -0
- package/src/loro/index.ts +17 -0
- package/src/loro/loro-commands.ts +27 -0
- package/src/loro/loro-cursor-plugin.ts +28 -0
- package/src/loro/loro-keymap.ts +23 -0
- package/src/loro/loro-sync-plugin.ts +14 -0
- package/src/loro/loro-undo-plugin.ts +12 -0
- package/src/loro/loro.ts +75 -0
- package/src/loro/style.css +33 -0
- package/src/mark-rule/apply.ts +129 -0
- package/src/mark-rule/index.ts +2 -0
- package/src/mark-rule/mark-rule.spec.ts +123 -0
- package/src/mark-rule/mark-rule.ts +48 -0
- package/src/mark-rule/range.ts +107 -0
- package/src/mark-rule/types.ts +30 -0
- package/src/mention/index.ts +90 -0
- package/src/mod-click-prevention/index.ts +35 -0
- package/src/paragraph/index.ts +7 -0
- package/src/paragraph/paragraph-commands.ts +29 -0
- package/src/paragraph/paragraph-keymap.ts +15 -0
- package/src/paragraph/paragraph-spec.ts +31 -0
- package/src/paragraph/paragraph.ts +37 -0
- package/src/paste-rule/index.ts +10 -0
- package/src/paste-rule/mark-paste-rule.spec.ts +112 -0
- package/src/paste-rule/mark-paste-rule.ts +194 -0
- package/src/paste-rule/paste-rule-plugin.ts +53 -0
- package/src/paste-rule/paste-rule.spec.ts +96 -0
- package/src/paste-rule/paste-rule.ts +60 -0
- package/src/paste-rule/split-text-by-regex.spec.ts +97 -0
- package/src/paste-rule/split-text-by-regex.ts +44 -0
- package/src/placeholder/index.ts +113 -0
- package/src/placeholder/style.css +7 -0
- package/src/readonly/index.ts +22 -0
- package/src/search/index.ts +140 -0
- package/src/search/style.css +13 -0
- package/src/strike/index.ts +101 -0
- package/src/table/index.ts +53 -0
- package/src/table/style.css +42 -0
- package/src/table/table-commands/delete-cell-selection.spec.ts +41 -0
- package/src/table/table-commands/delete-cell-selection.ts +1 -0
- package/src/table/table-commands/exit-table.spec.ts +45 -0
- package/src/table/table-commands/exit-table.ts +49 -0
- package/src/table/table-commands/insert-table.spec.ts +39 -0
- package/src/table/table-commands/insert-table.ts +80 -0
- package/src/table/table-commands/move-table-column.spec.ts +618 -0
- package/src/table/table-commands/move-table-column.ts +4 -0
- package/src/table/table-commands/move-table-row.spec.ts +380 -0
- package/src/table/table-commands/move-table-row.ts +4 -0
- package/src/table/table-commands/select-table-cell.spec.ts +34 -0
- package/src/table/table-commands/select-table-cell.ts +35 -0
- package/src/table/table-commands/select-table-column.spec.ts +33 -0
- package/src/table/table-commands/select-table-column.ts +39 -0
- package/src/table/table-commands/select-table-row.spec.ts +32 -0
- package/src/table/table-commands/select-table-row.ts +39 -0
- package/src/table/table-commands/select-table.spec.ts +36 -0
- package/src/table/table-commands/select-table.ts +50 -0
- package/src/table/table-commands.ts +110 -0
- package/src/table/table-drop-indicator.ts +40 -0
- package/src/table/table-plugins.ts +15 -0
- package/src/table/table-spec.spec.ts +113 -0
- package/src/table/table-spec.ts +109 -0
- package/src/table/table-utils.ts +16 -0
- package/src/table/table.ts +49 -0
- package/src/table/test-utils.ts +28 -0
- package/src/testing/clipboard.ts +58 -0
- package/src/testing/format-html.ts +5 -0
- package/src/testing/index.ts +161 -0
- package/src/testing/keyboard.ts +36 -0
- package/src/testing/markdown.ts +23 -0
- package/src/text/index.ts +24 -0
- package/src/text-align/index.ts +133 -0
- package/src/types/assert-type-equal.ts +8 -0
- package/src/underline/index.ts +83 -0
- package/src/virtual-selection/index.ts +100 -0
- package/src/virtual-selection/style.css +5 -0
- package/src/yjs/index.ts +22 -0
- package/src/yjs/style.css +31 -0
- package/src/yjs/yjs-commands.ts +27 -0
- package/src/yjs/yjs-cursor-plugin.ts +25 -0
- package/src/yjs/yjs-keymap.ts +23 -0
- package/src/yjs/yjs-sync-plugin.ts +23 -0
- package/src/yjs/yjs-undo-plugin.ts +87 -0
- package/src/yjs/yjs.ts +84 -0
- package/dist/drop-indicator-dB9rZn8e.js +0 -267
- package/dist/table-CPI9ZxbK.js +0 -760
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ProseMirrorNode,
|
|
3
|
+
ResolvedPos,
|
|
4
|
+
} from '@prosekit/pm/model'
|
|
5
|
+
import type {
|
|
6
|
+
EditorState,
|
|
7
|
+
Transaction,
|
|
8
|
+
} from '@prosekit/pm/state'
|
|
9
|
+
import type { ProsemirrorNode } from 'prosemirror-flat-list'
|
|
10
|
+
|
|
11
|
+
function getSpanTextRanges($from: ResolvedPos, $to: ResolvedPos) {
|
|
12
|
+
const nodeRange = $from.blockRange($to)
|
|
13
|
+
if (!nodeRange) {
|
|
14
|
+
return []
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const stack: Array<[start: number, node: ProseMirrorNode]> = []
|
|
18
|
+
let start = nodeRange.start
|
|
19
|
+
|
|
20
|
+
for (let i = nodeRange.startIndex; i < nodeRange.endIndex; i++) {
|
|
21
|
+
const child = nodeRange.parent.child(i)
|
|
22
|
+
stack.push([start, child])
|
|
23
|
+
start += child.nodeSize
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const ranges: Array<[number, number]> = []
|
|
27
|
+
|
|
28
|
+
while (stack.length > 0) {
|
|
29
|
+
const [start, node] = stack.pop()!
|
|
30
|
+
if (node.type.spec.code) {
|
|
31
|
+
continue
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (node.type.isTextblock) {
|
|
35
|
+
ranges.push([start + 1, start + 1 + node.content.size])
|
|
36
|
+
continue
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
node.forEach((child, offset) => {
|
|
40
|
+
stack.push([start + offset + 1, child])
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return ranges
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getInlineTextRange(
|
|
48
|
+
$from: ResolvedPos,
|
|
49
|
+
$to: ResolvedPos,
|
|
50
|
+
): [number, number] {
|
|
51
|
+
return [$from.start(), $to.end()]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getTextRanges(
|
|
55
|
+
doc: ProsemirrorNode,
|
|
56
|
+
from: number,
|
|
57
|
+
to: number,
|
|
58
|
+
): Array<[number, number]> {
|
|
59
|
+
const $from = doc.resolve(from)
|
|
60
|
+
const $to = doc.resolve(to)
|
|
61
|
+
|
|
62
|
+
if ($from.sameParent($to) && $from.parent.isTextblock) {
|
|
63
|
+
return [getInlineTextRange($from, $to)]
|
|
64
|
+
} else {
|
|
65
|
+
const nodeRange = $from.blockRange($to)
|
|
66
|
+
if (!nodeRange) {
|
|
67
|
+
return []
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return getSpanTextRanges($from, $to)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function getMapRange(
|
|
75
|
+
transactions: readonly Transaction[],
|
|
76
|
+
oldState: EditorState,
|
|
77
|
+
newState: EditorState,
|
|
78
|
+
) {
|
|
79
|
+
let lo = oldState.selection.from
|
|
80
|
+
let hi = oldState.selection.to
|
|
81
|
+
|
|
82
|
+
for (const tr of transactions) {
|
|
83
|
+
for (const map of tr.mapping.maps) {
|
|
84
|
+
lo = map.map(lo)
|
|
85
|
+
hi = map.map(hi)
|
|
86
|
+
|
|
87
|
+
map.forEach((_oldStart, _oldEnd, newStart, newEnd) => {
|
|
88
|
+
lo = Math.min(lo, hi, newStart)
|
|
89
|
+
hi = Math.max(lo, hi, newEnd)
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
lo = Math.min(lo, hi, newState.selection.from)
|
|
95
|
+
hi = Math.min(lo, hi, newState.selection.to)
|
|
96
|
+
|
|
97
|
+
return [lo, hi] as const
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function getCheckRanges(
|
|
101
|
+
transactions: readonly Transaction[],
|
|
102
|
+
oldState: EditorState,
|
|
103
|
+
newState: EditorState,
|
|
104
|
+
): Array<[number, number]> {
|
|
105
|
+
const [from, to] = getMapRange(transactions, oldState, newState)
|
|
106
|
+
return getTextRanges(newState.doc, from, to)
|
|
107
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Attrs,
|
|
3
|
+
MarkType,
|
|
4
|
+
} from '@prosekit/pm/model'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The options for {@link defineMarkRule}.
|
|
8
|
+
*
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
11
|
+
export interface MarkRuleOptions {
|
|
12
|
+
/**
|
|
13
|
+
* The regular expression to match against. It must has a `g` flag to match
|
|
14
|
+
* all instances of the mark.
|
|
15
|
+
*/
|
|
16
|
+
regex: RegExp
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The mark type to apply to the matched text.
|
|
20
|
+
*/
|
|
21
|
+
type: string | MarkType
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Attributes to set on the mark. If a function is provided, it will be called
|
|
25
|
+
* with the matched result from the regular expression.
|
|
26
|
+
*
|
|
27
|
+
* @default null
|
|
28
|
+
*/
|
|
29
|
+
attrs?: Attrs | null | ((match: RegExpMatchArray) => Attrs | null)
|
|
30
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineCommands,
|
|
3
|
+
defineNodeSpec,
|
|
4
|
+
insertNode,
|
|
5
|
+
union,
|
|
6
|
+
type Extension,
|
|
7
|
+
type Union,
|
|
8
|
+
} from '@prosekit/core'
|
|
9
|
+
|
|
10
|
+
export interface MentionAttrs {
|
|
11
|
+
id: string
|
|
12
|
+
value: string
|
|
13
|
+
kind: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
export type MentionSpecExtension = Extension<{
|
|
20
|
+
Nodes: {
|
|
21
|
+
mention: MentionAttrs
|
|
22
|
+
}
|
|
23
|
+
}>
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
export function defineMentionSpec(): MentionSpecExtension {
|
|
29
|
+
return defineNodeSpec<'mention', MentionAttrs>({
|
|
30
|
+
name: 'mention',
|
|
31
|
+
atom: true,
|
|
32
|
+
group: 'inline',
|
|
33
|
+
attrs: {
|
|
34
|
+
id: { validate: 'string' },
|
|
35
|
+
value: { validate: 'string' },
|
|
36
|
+
kind: { default: '', validate: 'string' },
|
|
37
|
+
},
|
|
38
|
+
inline: true,
|
|
39
|
+
leafText: (node) => (node.attrs as MentionAttrs).value.toString(),
|
|
40
|
+
parseDOM: [
|
|
41
|
+
{
|
|
42
|
+
tag: `span[data-mention]`,
|
|
43
|
+
getAttrs: (dom: HTMLElement): MentionAttrs => ({
|
|
44
|
+
id: dom.getAttribute('data-id') || '',
|
|
45
|
+
kind: dom.getAttribute('data-mention') || '',
|
|
46
|
+
value: dom.textContent || '',
|
|
47
|
+
}),
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
toDOM(node) {
|
|
51
|
+
return [
|
|
52
|
+
'span',
|
|
53
|
+
{
|
|
54
|
+
'data-id': (node.attrs as MentionAttrs).id.toString(),
|
|
55
|
+
'data-mention': (node.attrs as MentionAttrs).kind.toString(),
|
|
56
|
+
},
|
|
57
|
+
(node.attrs as MentionAttrs).value.toString(),
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @internal
|
|
65
|
+
*/
|
|
66
|
+
export type MentionCommandsExtension = Extension<{
|
|
67
|
+
Commands: {
|
|
68
|
+
insertMention: [attrs: MentionAttrs]
|
|
69
|
+
}
|
|
70
|
+
}>
|
|
71
|
+
|
|
72
|
+
export function defineMentionCommands(): MentionCommandsExtension {
|
|
73
|
+
return defineCommands({
|
|
74
|
+
insertMention: (attrs: MentionAttrs) => {
|
|
75
|
+
return insertNode({ type: 'mention', attrs })
|
|
76
|
+
},
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @internal
|
|
82
|
+
*/
|
|
83
|
+
export type MentionExtension = Union<[MentionSpecExtension, MentionCommandsExtension]>
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @public
|
|
87
|
+
*/
|
|
88
|
+
export function defineMention(): MentionExtension {
|
|
89
|
+
return union(defineMentionSpec(), defineMentionCommands())
|
|
90
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
definePlugin,
|
|
3
|
+
isApple,
|
|
4
|
+
type PlainExtension,
|
|
5
|
+
} from '@prosekit/core'
|
|
6
|
+
import {
|
|
7
|
+
Plugin,
|
|
8
|
+
PluginKey,
|
|
9
|
+
} from '@prosekit/pm/state'
|
|
10
|
+
import type { EditorView } from '@prosekit/pm/view'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
export type ModClickPreventionExtension = PlainExtension
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* By default, clicking a node while holding the mod key will select the node. This
|
|
19
|
+
* extension disables that behavior.
|
|
20
|
+
*
|
|
21
|
+
* @public
|
|
22
|
+
*/
|
|
23
|
+
export function defineModClickPrevention(): ModClickPreventionExtension {
|
|
24
|
+
return definePlugin(new Plugin({ key, props: { handleClick } }))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const key = new PluginKey('prosekit-mod-click-prevention')
|
|
28
|
+
|
|
29
|
+
function handleClick(_view: EditorView, _pos: number, event: MouseEvent) {
|
|
30
|
+
return !!event[selectNodeModifier]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const selectNodeModifier: 'metaKey' | 'ctrlKey' = isApple
|
|
34
|
+
? 'metaKey'
|
|
35
|
+
: 'ctrlKey'
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { defineParagraph } from './paragraph'
|
|
2
|
+
export type { ParagraphExtension } from './paragraph'
|
|
3
|
+
export { defineParagraphCommands } from './paragraph-commands'
|
|
4
|
+
export type { ParagraphCommandsExtension } from './paragraph-commands'
|
|
5
|
+
export { defineParagraphKeymap } from './paragraph-keymap'
|
|
6
|
+
export { defineParagraphSpec } from './paragraph-spec'
|
|
7
|
+
export type { ParagraphSpecExtension } from './paragraph-spec'
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineCommands,
|
|
3
|
+
setBlockType,
|
|
4
|
+
type Extension,
|
|
5
|
+
} from '@prosekit/core'
|
|
6
|
+
import type { Command } from '@prosekit/pm/state'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
export type ParagraphCommandsExtension = Extension<{
|
|
12
|
+
Commands: {
|
|
13
|
+
setParagraph: []
|
|
14
|
+
}
|
|
15
|
+
}>
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
export function setParagraph(): Command {
|
|
21
|
+
return setBlockType({ type: 'paragraph' })
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export function defineParagraphCommands(): ParagraphCommandsExtension {
|
|
28
|
+
return defineCommands({ setParagraph })
|
|
29
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineKeymap,
|
|
3
|
+
type PlainExtension,
|
|
4
|
+
} from '@prosekit/core'
|
|
5
|
+
|
|
6
|
+
import { setParagraph } from './paragraph-commands'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
export function defineParagraphKeymap(): PlainExtension {
|
|
12
|
+
return defineKeymap({
|
|
13
|
+
'mod-alt-0': setParagraph(),
|
|
14
|
+
})
|
|
15
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineNodeSpec,
|
|
3
|
+
type Extension,
|
|
4
|
+
} from '@prosekit/core'
|
|
5
|
+
import type { Attrs } from '@prosekit/pm/model'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export type ParagraphSpecExtension = Extension<{
|
|
11
|
+
Nodes: {
|
|
12
|
+
paragraph: Attrs
|
|
13
|
+
}
|
|
14
|
+
}>
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @internal
|
|
18
|
+
*
|
|
19
|
+
* Defines a paragraph node spec.
|
|
20
|
+
*/
|
|
21
|
+
export function defineParagraphSpec(): ParagraphSpecExtension {
|
|
22
|
+
return defineNodeSpec({
|
|
23
|
+
name: 'paragraph',
|
|
24
|
+
content: 'inline*',
|
|
25
|
+
group: 'block',
|
|
26
|
+
parseDOM: [{ tag: 'p' }],
|
|
27
|
+
toDOM() {
|
|
28
|
+
return ['p', 0]
|
|
29
|
+
},
|
|
30
|
+
})
|
|
31
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Priority,
|
|
3
|
+
union,
|
|
4
|
+
withPriority,
|
|
5
|
+
type Union,
|
|
6
|
+
} from '@prosekit/core'
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
defineParagraphCommands,
|
|
10
|
+
type ParagraphCommandsExtension,
|
|
11
|
+
} from './paragraph-commands'
|
|
12
|
+
import { defineParagraphKeymap } from './paragraph-keymap'
|
|
13
|
+
import {
|
|
14
|
+
defineParagraphSpec,
|
|
15
|
+
type ParagraphSpecExtension,
|
|
16
|
+
} from './paragraph-spec'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
export type ParagraphExtension = Union<[ParagraphSpecExtension, ParagraphCommandsExtension]>
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @public
|
|
25
|
+
*
|
|
26
|
+
* Defines a paragraph node.
|
|
27
|
+
*
|
|
28
|
+
* The paragraph node spec has the highest priority, because it should be the
|
|
29
|
+
* default block node for most cases.
|
|
30
|
+
*/
|
|
31
|
+
export function defineParagraph(): ParagraphExtension {
|
|
32
|
+
return union(
|
|
33
|
+
withPriority(defineParagraphSpec(), Priority.highest),
|
|
34
|
+
defineParagraphCommands(),
|
|
35
|
+
defineParagraphKeymap(),
|
|
36
|
+
)
|
|
37
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineMarkSpec,
|
|
3
|
+
union,
|
|
4
|
+
} from '@prosekit/core'
|
|
5
|
+
import {
|
|
6
|
+
describe,
|
|
7
|
+
expect,
|
|
8
|
+
it,
|
|
9
|
+
} from 'vitest'
|
|
10
|
+
|
|
11
|
+
import { defineBold } from '../bold'
|
|
12
|
+
import { defineDoc } from '../doc'
|
|
13
|
+
import { defineParagraph } from '../paragraph'
|
|
14
|
+
import { setupTestFromExtension } from '../testing'
|
|
15
|
+
import {
|
|
16
|
+
pasteHTML,
|
|
17
|
+
pasteText,
|
|
18
|
+
} from '../testing/clipboard'
|
|
19
|
+
import { defineText } from '../text'
|
|
20
|
+
|
|
21
|
+
import type { MarkPasteRuleOptions } from './mark-paste-rule'
|
|
22
|
+
import { defineMarkPasteRule } from './mark-paste-rule'
|
|
23
|
+
|
|
24
|
+
function setup(options?: Partial<MarkPasteRuleOptions>) {
|
|
25
|
+
return setupTestFromExtension(union(
|
|
26
|
+
defineDoc(),
|
|
27
|
+
defineText(),
|
|
28
|
+
defineParagraph(),
|
|
29
|
+
defineBold(),
|
|
30
|
+
defineMarkSpec({
|
|
31
|
+
name: 'testMark',
|
|
32
|
+
attrs: {
|
|
33
|
+
value: { validate: 'string' },
|
|
34
|
+
},
|
|
35
|
+
parseDOM: [{ tag: 'span[data-test]' }],
|
|
36
|
+
toDOM: () => ['span', { 'data-test': 'true' }, 0],
|
|
37
|
+
}),
|
|
38
|
+
defineMarkPasteRule({
|
|
39
|
+
type: 'testMark',
|
|
40
|
+
regex: /@(\w+)/g,
|
|
41
|
+
getAttrs: (match: RegExpExecArray) => ({ value: match[1] }),
|
|
42
|
+
...options,
|
|
43
|
+
}),
|
|
44
|
+
))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
describe('defineMarkPasteRule', () => {
|
|
48
|
+
it('should match simple patterns and create marks', () => {
|
|
49
|
+
const { editor, n, m } = setup()
|
|
50
|
+
editor.set(n.doc(n.paragraph('<a>')))
|
|
51
|
+
|
|
52
|
+
pasteHTML(editor.view, '<p>Hello @alice and @bob</p>')
|
|
53
|
+
|
|
54
|
+
expect(editor.view.state.doc.toJSON()).toEqual(
|
|
55
|
+
n.doc(n.paragraph(
|
|
56
|
+
'Hello ',
|
|
57
|
+
m.testMark({ value: 'alice' }, '@alice'),
|
|
58
|
+
' and ',
|
|
59
|
+
m.testMark({ value: 'bob' }, '@bob'),
|
|
60
|
+
)).toJSON(),
|
|
61
|
+
)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('should not match plain text', () => {
|
|
65
|
+
const { editor, n } = setup()
|
|
66
|
+
editor.set(n.doc(n.paragraph('<a>')))
|
|
67
|
+
|
|
68
|
+
pasteText(editor.view, 'Hello @alice and @bob')
|
|
69
|
+
|
|
70
|
+
expect(editor.view.state.doc.toJSON()).toEqual(
|
|
71
|
+
n.doc(n.paragraph('Hello @alice and @bob')).toJSON(),
|
|
72
|
+
)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('should skip processing when attrs returns false', () => {
|
|
76
|
+
const { editor, n, m } = setup({
|
|
77
|
+
getAttrs: (match: RegExpExecArray) => {
|
|
78
|
+
return match[1] === 'skip' ? false : { value: match[1] }
|
|
79
|
+
},
|
|
80
|
+
})
|
|
81
|
+
editor.set(n.doc(n.paragraph('<a>')))
|
|
82
|
+
|
|
83
|
+
pasteHTML(editor.view, '<p>Tags: @good @skip @also</p>')
|
|
84
|
+
|
|
85
|
+
expect(editor.view.state.doc.toJSON()).toEqual(
|
|
86
|
+
n.doc(n.paragraph(
|
|
87
|
+
'Tags: ',
|
|
88
|
+
m.testMark({ value: 'good' }, '@good'),
|
|
89
|
+
' @skip ',
|
|
90
|
+
m.testMark({ value: 'also' }, '@also'),
|
|
91
|
+
)).toJSON(),
|
|
92
|
+
)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('should use shouldSkip to skip bold text', () => {
|
|
96
|
+
const { editor, n, m } = setup({
|
|
97
|
+
shouldSkip: (node) => node.marks.some(mark => mark.type.name === 'bold'),
|
|
98
|
+
})
|
|
99
|
+
editor.set(n.doc(n.paragraph('<a>')))
|
|
100
|
+
|
|
101
|
+
pasteHTML(editor.view, '<p>Hello <strong>@alice</strong> and @bob</p>')
|
|
102
|
+
|
|
103
|
+
expect(editor.view.state.doc.toJSON()).toEqual(
|
|
104
|
+
n.doc(n.paragraph(
|
|
105
|
+
'Hello ',
|
|
106
|
+
m.bold('@alice'), // Skipped due to shouldSkip
|
|
107
|
+
' and ',
|
|
108
|
+
m.testMark({ value: 'bob' }, '@bob'), // Processed normally
|
|
109
|
+
)).toJSON(),
|
|
110
|
+
)
|
|
111
|
+
})
|
|
112
|
+
})
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getMarkType,
|
|
3
|
+
type PlainExtension,
|
|
4
|
+
} from '@prosekit/core'
|
|
5
|
+
import type {
|
|
6
|
+
Attrs,
|
|
7
|
+
MarkType,
|
|
8
|
+
ProseMirrorNode,
|
|
9
|
+
} from '@prosekit/pm/model'
|
|
10
|
+
import {
|
|
11
|
+
Fragment,
|
|
12
|
+
Slice,
|
|
13
|
+
} from '@prosekit/pm/model'
|
|
14
|
+
|
|
15
|
+
import { definePasteRule } from './paste-rule'
|
|
16
|
+
import { splitTextByRegex } from './split-text-by-regex'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The options for {@link defineMarkPasteRule}.
|
|
20
|
+
*
|
|
21
|
+
* @public
|
|
22
|
+
*/
|
|
23
|
+
export interface MarkPasteRuleOptions {
|
|
24
|
+
/**
|
|
25
|
+
* The regular expression to match against. It must have a `g` flag to match
|
|
26
|
+
* all instances of the mark.
|
|
27
|
+
*/
|
|
28
|
+
regex: RegExp
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The mark type to apply to the matched text.
|
|
32
|
+
*/
|
|
33
|
+
type: string | MarkType
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A function used to compute attributes to set on the mark created by this
|
|
37
|
+
* rule. When it returns `false`, the rule won't match. When it returns `null`
|
|
38
|
+
* or `undefined`, that is interpreted as an empty/default set of attributes.
|
|
39
|
+
* @default null
|
|
40
|
+
*/
|
|
41
|
+
getAttrs?: (match: RegExpExecArray) => Attrs | null | undefined | false
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Optional function to determine if a text node should be skipped.
|
|
45
|
+
* Default behavior: skip code nodes and nodes that already have the target mark.
|
|
46
|
+
*/
|
|
47
|
+
shouldSkip?: (node: ProseMirrorNode) => boolean
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Defines a paste rule that applies marks based on regex patterns.
|
|
52
|
+
*
|
|
53
|
+
* @public
|
|
54
|
+
*/
|
|
55
|
+
export function defineMarkPasteRule(options: MarkPasteRuleOptions): PlainExtension {
|
|
56
|
+
return definePasteRule({
|
|
57
|
+
handler: ({ slice, view, plain }) => {
|
|
58
|
+
if (plain) {
|
|
59
|
+
return slice
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const markType = getMarkType(view.state.schema, options.type)
|
|
63
|
+
|
|
64
|
+
return replaceMarkInSlice({
|
|
65
|
+
markType,
|
|
66
|
+
regex: options.regex,
|
|
67
|
+
getAttrs: options.getAttrs,
|
|
68
|
+
shouldSkip: options.shouldSkip,
|
|
69
|
+
}, slice)
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface MarkPasteRuleHandlerOptions {
|
|
75
|
+
markType: MarkType
|
|
76
|
+
regex: RegExp
|
|
77
|
+
getAttrs?: (match: RegExpExecArray) => Attrs | null | undefined | false
|
|
78
|
+
shouldSkip?: (node: ProseMirrorNode) => boolean
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function replaceMarkInSlice(options: MarkPasteRuleHandlerOptions, slice: Slice): Slice {
|
|
82
|
+
const newFragment = replaceMarkInFragment(options, slice.content)
|
|
83
|
+
if (!newFragment) {
|
|
84
|
+
return slice
|
|
85
|
+
}
|
|
86
|
+
return new Slice(newFragment, slice.openStart, slice.openEnd)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function replaceMarkInFragment(options: MarkPasteRuleHandlerOptions, fragment: Fragment): Fragment | undefined {
|
|
90
|
+
let changed = false
|
|
91
|
+
let children: ProseMirrorNode[] = []
|
|
92
|
+
|
|
93
|
+
for (const child of fragment.content) {
|
|
94
|
+
const newChild = replaceMarkInNode(options, child)
|
|
95
|
+
if (newChild) {
|
|
96
|
+
changed = true
|
|
97
|
+
}
|
|
98
|
+
children.push(newChild || child)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (changed) {
|
|
102
|
+
return Fragment.from(children)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function replaceMarkInNode(options: MarkPasteRuleHandlerOptions, node: ProseMirrorNode): ProseMirrorNode | undefined {
|
|
109
|
+
if (node.type.spec.code) {
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
if (node.type.isInline) {
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
if (node.type.isTextblock) {
|
|
116
|
+
return replaceMarkInTextblockNode(options, node)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const newChildren = replaceMarkInFragment(options, node.content)
|
|
120
|
+
if (!newChildren) {
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
return node.copy(newChildren)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function replaceMarkInTextblockNode(options: MarkPasteRuleHandlerOptions, node: ProseMirrorNode): ProseMirrorNode | undefined {
|
|
127
|
+
const newChildren: ProseMirrorNode[] = []
|
|
128
|
+
let changed = false
|
|
129
|
+
|
|
130
|
+
for (const inlineNode of node.content.content) {
|
|
131
|
+
const newInlineNodes = replaceMarkInInlineNode(options, inlineNode)
|
|
132
|
+
if (newInlineNodes) {
|
|
133
|
+
changed = true
|
|
134
|
+
newChildren.push(...newInlineNodes)
|
|
135
|
+
} else {
|
|
136
|
+
newChildren.push(inlineNode)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (changed) {
|
|
140
|
+
return node.copy(Fragment.from(newChildren))
|
|
141
|
+
}
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function replaceMarkInInlineNode(options: MarkPasteRuleHandlerOptions, node: ProseMirrorNode): ProseMirrorNode[] | undefined {
|
|
146
|
+
const text = node.text
|
|
147
|
+
if (!text) {
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const { markType, shouldSkip } = options
|
|
152
|
+
|
|
153
|
+
// Use custom skip logic if provided, otherwise use default
|
|
154
|
+
if (shouldSkip) {
|
|
155
|
+
if (shouldSkip(node)) {
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
} else {
|
|
159
|
+
// Default skip logic: skip if already has the target mark or has code mark
|
|
160
|
+
if (node.marks.some((mark) => mark.type === markType)) {
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
if (node.marks.some((mark) => mark.type.spec.code)) {
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const chunks = splitTextByRegex(text, options.regex)
|
|
169
|
+
if (!chunks) {
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const schema = node.type.schema
|
|
174
|
+
const nodes: ProseMirrorNode[] = []
|
|
175
|
+
|
|
176
|
+
for (const [text, match] of chunks) {
|
|
177
|
+
if (!text) {
|
|
178
|
+
continue
|
|
179
|
+
}
|
|
180
|
+
if (match) {
|
|
181
|
+
const attrs = options.getAttrs?.(match) ?? null
|
|
182
|
+
if (attrs !== false) {
|
|
183
|
+
const mark = markType.create(attrs)
|
|
184
|
+
nodes.push(schema.text(text, [...node.marks, mark]))
|
|
185
|
+
} else {
|
|
186
|
+
nodes.push(schema.text(text, node.marks))
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
nodes.push(schema.text(text, node.marks))
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return nodes
|
|
194
|
+
}
|