@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,186 @@
|
|
|
1
|
+
import { OBJECT_REPLACEMENT_CHARACTER } from '@prosekit/core'
|
|
2
|
+
import {
|
|
3
|
+
Plugin,
|
|
4
|
+
type EditorState,
|
|
5
|
+
type Transaction,
|
|
6
|
+
} from '@prosekit/pm/state'
|
|
7
|
+
import {
|
|
8
|
+
Decoration,
|
|
9
|
+
DecorationSet,
|
|
10
|
+
} from '@prosekit/pm/view'
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
getPluginState,
|
|
14
|
+
getTrMeta,
|
|
15
|
+
pluginKey,
|
|
16
|
+
setTrMeta,
|
|
17
|
+
type PredictionPluginMatching,
|
|
18
|
+
type PredictionPluginState,
|
|
19
|
+
} from './autocomplete-helpers'
|
|
20
|
+
import type { AutocompleteRule } from './autocomplete-rule'
|
|
21
|
+
|
|
22
|
+
export function createAutocompletePlugin({
|
|
23
|
+
getRules,
|
|
24
|
+
}: {
|
|
25
|
+
getRules: () => AutocompleteRule[]
|
|
26
|
+
}): Plugin {
|
|
27
|
+
return new Plugin<PredictionPluginState>({
|
|
28
|
+
key: pluginKey,
|
|
29
|
+
|
|
30
|
+
state: {
|
|
31
|
+
init: (): PredictionPluginState => {
|
|
32
|
+
return { ignores: [], matching: null }
|
|
33
|
+
},
|
|
34
|
+
apply: (
|
|
35
|
+
tr: Transaction,
|
|
36
|
+
prevValue: PredictionPluginState,
|
|
37
|
+
oldState: EditorState,
|
|
38
|
+
newState: EditorState,
|
|
39
|
+
): PredictionPluginState => {
|
|
40
|
+
const meta = getTrMeta(tr)
|
|
41
|
+
|
|
42
|
+
// No changes
|
|
43
|
+
if (
|
|
44
|
+
!tr.docChanged
|
|
45
|
+
&& oldState.selection.eq(newState.selection)
|
|
46
|
+
&& !meta
|
|
47
|
+
) {
|
|
48
|
+
return prevValue
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Receiving a meta means that we are ignoring a match
|
|
52
|
+
if (meta) {
|
|
53
|
+
let ignores = prevValue.ignores
|
|
54
|
+
if (!ignores.includes(meta.ignore)) {
|
|
55
|
+
ignores = [...ignores, meta.ignore]
|
|
56
|
+
}
|
|
57
|
+
return { matching: null, ignores }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Calculate the new ignores
|
|
61
|
+
const ignoreSet = new Set(prevValue.ignores.map(pos => tr.mapping.map(pos)))
|
|
62
|
+
|
|
63
|
+
// Calculate the new matching
|
|
64
|
+
let matching = calcPluginStateMatching(newState, getRules())
|
|
65
|
+
|
|
66
|
+
// Check if the matching should be ignored
|
|
67
|
+
if (matching && ignoreSet.has(matching.from)) {
|
|
68
|
+
matching = null
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Return the new matching and ignores
|
|
72
|
+
return { matching, ignores: Array.from(ignoreSet) }
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
view: () => ({
|
|
77
|
+
update: (view, prevState) => {
|
|
78
|
+
const prevValue = getPluginState(prevState)
|
|
79
|
+
const currValue = getPluginState(view.state)
|
|
80
|
+
|
|
81
|
+
if (
|
|
82
|
+
prevValue?.matching
|
|
83
|
+
&& prevValue.matching.rule !== currValue?.matching?.rule
|
|
84
|
+
) {
|
|
85
|
+
// Deactivate the previous rule
|
|
86
|
+
prevValue.matching.rule.onLeave?.()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (
|
|
90
|
+
currValue?.matching
|
|
91
|
+
&& !currValue.ignores.includes(currValue.matching.from)
|
|
92
|
+
) {
|
|
93
|
+
// Activate the current rule
|
|
94
|
+
|
|
95
|
+
const { from, to, match, rule } = currValue.matching
|
|
96
|
+
|
|
97
|
+
const textContent = view.state.doc.textBetween(
|
|
98
|
+
from,
|
|
99
|
+
to,
|
|
100
|
+
null,
|
|
101
|
+
OBJECT_REPLACEMENT_CHARACTER,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
const deleteMatch = () => {
|
|
105
|
+
if (
|
|
106
|
+
view.state.doc.textBetween(
|
|
107
|
+
from,
|
|
108
|
+
to,
|
|
109
|
+
null,
|
|
110
|
+
OBJECT_REPLACEMENT_CHARACTER,
|
|
111
|
+
) === textContent
|
|
112
|
+
) {
|
|
113
|
+
view.dispatch(view.state.tr.delete(from, to))
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const ignoreMatch = () => {
|
|
118
|
+
view.dispatch(
|
|
119
|
+
setTrMeta(view.state.tr, { ignore: from }),
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
rule.onMatch({
|
|
124
|
+
state: view.state,
|
|
125
|
+
match,
|
|
126
|
+
from,
|
|
127
|
+
to,
|
|
128
|
+
deleteMatch,
|
|
129
|
+
ignoreMatch,
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
}),
|
|
134
|
+
|
|
135
|
+
props: {
|
|
136
|
+
decorations: (state: EditorState) => {
|
|
137
|
+
const pluginState = getPluginState(state)
|
|
138
|
+
if (pluginState?.matching) {
|
|
139
|
+
const { from, to } = pluginState.matching
|
|
140
|
+
const deco = Decoration.inline(from, to, {
|
|
141
|
+
class: 'prosemirror-prediction-match',
|
|
142
|
+
})
|
|
143
|
+
return DecorationSet.create(state.doc, [deco])
|
|
144
|
+
}
|
|
145
|
+
return null
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const MAX_MATCH = 200
|
|
152
|
+
|
|
153
|
+
function calcPluginStateMatching(
|
|
154
|
+
state: EditorState,
|
|
155
|
+
rules: AutocompleteRule[],
|
|
156
|
+
): PredictionPluginMatching | null {
|
|
157
|
+
const $pos = state.selection.$from
|
|
158
|
+
|
|
159
|
+
const parentOffset: number = $pos.parentOffset
|
|
160
|
+
|
|
161
|
+
const textBefore: string = $pos.parent.textBetween(
|
|
162
|
+
Math.max(0, parentOffset - MAX_MATCH),
|
|
163
|
+
parentOffset,
|
|
164
|
+
null,
|
|
165
|
+
OBJECT_REPLACEMENT_CHARACTER,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
for (const rule of rules) {
|
|
169
|
+
if (!rule.canMatch({ state })) {
|
|
170
|
+
continue
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
rule.regex.lastIndex = 0
|
|
174
|
+
const match = rule.regex.exec(textBefore)
|
|
175
|
+
if (!match) {
|
|
176
|
+
continue
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const to = $pos.pos
|
|
180
|
+
const from = to - textBefore.length + match.index
|
|
181
|
+
|
|
182
|
+
return { rule, match, from, to }
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return null
|
|
186
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { EditorState } from '@prosekit/pm/state'
|
|
2
|
+
|
|
3
|
+
import { defaultCanMatch } from './autocomplete-helpers'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Options for the {@link MatchHandler} callback.
|
|
7
|
+
*/
|
|
8
|
+
export interface MatchHandlerOptions {
|
|
9
|
+
/**
|
|
10
|
+
* The editor state.
|
|
11
|
+
*/
|
|
12
|
+
state: EditorState
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The result of `RegExp.exec`.
|
|
16
|
+
*/
|
|
17
|
+
match: RegExpExecArray
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The start position of the matched text.
|
|
21
|
+
*/
|
|
22
|
+
from: number
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The end position of the matched text.
|
|
26
|
+
*/
|
|
27
|
+
to: number
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Call this function to ignore the match. You probably want to call this
|
|
31
|
+
* function when the user presses the `Escape` key.
|
|
32
|
+
*/
|
|
33
|
+
ignoreMatch: () => void
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Call this function to delete the matched text. For example, in a slash
|
|
37
|
+
* menu, you might want to delete the matched text first then do something
|
|
38
|
+
* else when the user presses the `Enter` key.
|
|
39
|
+
*/
|
|
40
|
+
deleteMatch: () => void
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A callback that is called when the rule starts to match, and also on
|
|
45
|
+
* subsequent updates while the rule continues to match.
|
|
46
|
+
*/
|
|
47
|
+
export type MatchHandler = (options: MatchHandlerOptions) => void
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Options for the {@link CanMatchPredicate} callback.
|
|
51
|
+
*/
|
|
52
|
+
export interface CanMatchOptions {
|
|
53
|
+
/**
|
|
54
|
+
* The editor state.
|
|
55
|
+
*/
|
|
56
|
+
state: EditorState
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* A predicate to determine if the rule can be applied in the current editor state.
|
|
61
|
+
*/
|
|
62
|
+
export type CanMatchPredicate = (options: CanMatchOptions) => boolean
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Options for creating an {@link AutocompleteRule}
|
|
66
|
+
*/
|
|
67
|
+
export interface AutocompleteRuleOptions {
|
|
68
|
+
/**
|
|
69
|
+
* The regular expression to match against the text before the cursor. The
|
|
70
|
+
* last match before the cursor is used.
|
|
71
|
+
*
|
|
72
|
+
* For a slash menu, you might use `/(?<!\S)\/(|\S.*)$/u`.
|
|
73
|
+
* For a mention, you might use `/@\w*$/`
|
|
74
|
+
*/
|
|
75
|
+
regex: RegExp
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* A callback that is called when the rule starts to match, and also on
|
|
79
|
+
* subsequent updates while the rule continues to match.
|
|
80
|
+
*/
|
|
81
|
+
onEnter: MatchHandler
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* A callback that is called when the rule stops matching.
|
|
85
|
+
*/
|
|
86
|
+
onLeave?: VoidFunction
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* A predicate to determine if the rule can be applied in the current editor
|
|
90
|
+
* state. If not provided, it defaults to only allowing matches in empty
|
|
91
|
+
* selections that are not inside a code block or code mark.
|
|
92
|
+
*/
|
|
93
|
+
canMatch?: CanMatchPredicate
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* An autocomplete rule that can be used to create an autocomplete extension.
|
|
98
|
+
*
|
|
99
|
+
* @public
|
|
100
|
+
*/
|
|
101
|
+
export class AutocompleteRule {
|
|
102
|
+
/** @internal */
|
|
103
|
+
readonly regex: RegExp
|
|
104
|
+
/** @internal */
|
|
105
|
+
readonly onMatch: MatchHandler
|
|
106
|
+
/** @internal */
|
|
107
|
+
readonly onLeave?: VoidFunction
|
|
108
|
+
/** @internal */
|
|
109
|
+
readonly canMatch: (options: { state: EditorState }) => boolean
|
|
110
|
+
|
|
111
|
+
constructor(options: AutocompleteRuleOptions) {
|
|
112
|
+
this.regex = options.regex
|
|
113
|
+
this.onMatch = options.onEnter
|
|
114
|
+
this.onLeave = options.onLeave
|
|
115
|
+
this.canMatch = options.canMatch ?? defaultCanMatch
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import {
|
|
2
|
+
canUseRegexLookbehind,
|
|
3
|
+
union,
|
|
4
|
+
} from '@prosekit/core'
|
|
5
|
+
import {
|
|
6
|
+
describe,
|
|
7
|
+
expect,
|
|
8
|
+
it,
|
|
9
|
+
vi,
|
|
10
|
+
} from 'vitest'
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
defineTestExtension,
|
|
14
|
+
setupTestFromExtension,
|
|
15
|
+
} from '../testing'
|
|
16
|
+
import {
|
|
17
|
+
inputText,
|
|
18
|
+
pressKey,
|
|
19
|
+
} from '../testing/keyboard'
|
|
20
|
+
|
|
21
|
+
import { defineAutocomplete } from './autocomplete'
|
|
22
|
+
import {
|
|
23
|
+
AutocompleteRule,
|
|
24
|
+
type MatchHandler,
|
|
25
|
+
type MatchHandlerOptions,
|
|
26
|
+
} from './autocomplete-rule'
|
|
27
|
+
|
|
28
|
+
function setupSlashMenu() {
|
|
29
|
+
const regex = canUseRegexLookbehind() ? /(?<!\S)\/(|\S.*)$/u : /\/(|\S.*)$/u
|
|
30
|
+
|
|
31
|
+
const onEnter = vi.fn<MatchHandler>()
|
|
32
|
+
const onLeave = vi.fn<VoidFunction>()
|
|
33
|
+
|
|
34
|
+
const rule = new AutocompleteRule({ regex, onEnter, onLeave })
|
|
35
|
+
const extension = union(defineTestExtension(), defineAutocomplete(rule))
|
|
36
|
+
const { editor, n, m } = setupTestFromExtension(extension)
|
|
37
|
+
|
|
38
|
+
const doc = n.doc(n.paragraph('<a>'))
|
|
39
|
+
editor.set(doc)
|
|
40
|
+
|
|
41
|
+
const getOnEnterOptions = (): MatchHandlerOptions => {
|
|
42
|
+
const parameters = onEnter.mock.calls.at(-1)
|
|
43
|
+
const options = parameters?.[0]
|
|
44
|
+
if (!options) {
|
|
45
|
+
throw new Error('No onEnter options found')
|
|
46
|
+
}
|
|
47
|
+
return options
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return { editor, n, m, onEnter, onLeave, getOnEnterOptions }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
describe('defineAutocomplete', () => {
|
|
54
|
+
it('can trigger onEnter', async () => {
|
|
55
|
+
const { onEnter, onLeave } = setupSlashMenu()
|
|
56
|
+
|
|
57
|
+
expect(onEnter).not.toHaveBeenCalled()
|
|
58
|
+
expect(onLeave).not.toHaveBeenCalled()
|
|
59
|
+
|
|
60
|
+
await inputText('/')
|
|
61
|
+
expect(onEnter).toHaveBeenCalledTimes(1)
|
|
62
|
+
|
|
63
|
+
await inputText('order')
|
|
64
|
+
expect(onEnter).toHaveBeenCalledTimes(6)
|
|
65
|
+
|
|
66
|
+
await inputText(' ')
|
|
67
|
+
expect(onEnter).toHaveBeenCalledTimes(7)
|
|
68
|
+
|
|
69
|
+
await inputText('list')
|
|
70
|
+
expect(onEnter).toHaveBeenCalledTimes(11)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('can trigger onLeave', async () => {
|
|
74
|
+
const { onEnter, onLeave } = setupSlashMenu()
|
|
75
|
+
|
|
76
|
+
expect(onEnter).not.toHaveBeenCalled()
|
|
77
|
+
expect(onLeave).not.toHaveBeenCalled()
|
|
78
|
+
|
|
79
|
+
// Slash menu should be triggered when typing "/"
|
|
80
|
+
await inputText('/')
|
|
81
|
+
expect(onEnter).toHaveBeenCalledTimes(1)
|
|
82
|
+
expect(onLeave).toHaveBeenCalledTimes(0)
|
|
83
|
+
|
|
84
|
+
// Slash menu should not be triggered when typing "/ "
|
|
85
|
+
await pressKey('Space')
|
|
86
|
+
expect(onEnter).toHaveBeenCalledTimes(1)
|
|
87
|
+
expect(onLeave).toHaveBeenCalledTimes(1)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('can delete the matched text', async () => {
|
|
91
|
+
const { editor, onEnter, getOnEnterOptions } = setupSlashMenu()
|
|
92
|
+
|
|
93
|
+
expect(onEnter).not.toHaveBeenCalled()
|
|
94
|
+
|
|
95
|
+
await inputText('/')
|
|
96
|
+
expect(onEnter).toHaveBeenCalledTimes(1)
|
|
97
|
+
|
|
98
|
+
const options = getOnEnterOptions()
|
|
99
|
+
expect(editor.state.doc.textContent).toBe('/')
|
|
100
|
+
options.deleteMatch()
|
|
101
|
+
expect(editor.state.doc.textContent).toBe('')
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('can ignore the match', async () => {
|
|
105
|
+
const { editor, onEnter, onLeave, getOnEnterOptions } = setupSlashMenu()
|
|
106
|
+
|
|
107
|
+
expect(onEnter).not.toHaveBeenCalled()
|
|
108
|
+
|
|
109
|
+
await inputText('/')
|
|
110
|
+
expect(onEnter).toHaveBeenCalledTimes(1)
|
|
111
|
+
expect(onLeave).toHaveBeenCalledTimes(0)
|
|
112
|
+
expect(editor.state.doc.textContent).toBe('/')
|
|
113
|
+
|
|
114
|
+
// Typing should trigger autocomplete
|
|
115
|
+
await inputText('a')
|
|
116
|
+
expect(onEnter).toHaveBeenCalledTimes(2)
|
|
117
|
+
expect(onLeave).toHaveBeenCalledTimes(0)
|
|
118
|
+
expect(editor.state.doc.textContent).toBe('/a')
|
|
119
|
+
|
|
120
|
+
// Call `ignoreMatch` to dismiss the match
|
|
121
|
+
const options = getOnEnterOptions()
|
|
122
|
+
options.ignoreMatch()
|
|
123
|
+
expect(onEnter).toHaveBeenCalledTimes(2)
|
|
124
|
+
expect(onLeave).toHaveBeenCalledTimes(1)
|
|
125
|
+
|
|
126
|
+
// Typing should not trigger autocomplete anymore
|
|
127
|
+
await inputText('a')
|
|
128
|
+
expect(onEnter).toHaveBeenCalledTimes(2)
|
|
129
|
+
expect(onLeave).toHaveBeenCalledTimes(1)
|
|
130
|
+
expect(editor.state.doc.textContent).toBe('/aa')
|
|
131
|
+
})
|
|
132
|
+
})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineFacet,
|
|
3
|
+
defineFacetPayload,
|
|
4
|
+
pluginFacet,
|
|
5
|
+
type Extension,
|
|
6
|
+
type PluginPayload,
|
|
7
|
+
} from '@prosekit/core'
|
|
8
|
+
|
|
9
|
+
import { createAutocompletePlugin } from './autocomplete-plugin'
|
|
10
|
+
import type { AutocompleteRule } from './autocomplete-rule'
|
|
11
|
+
|
|
12
|
+
export function defineAutocomplete(rule: AutocompleteRule): Extension {
|
|
13
|
+
return defineFacetPayload(autocompleteFacet, [rule])
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const autocompleteFacet = defineFacet<AutocompleteRule, PluginPayload>({
|
|
17
|
+
reduce: () => {
|
|
18
|
+
let rules: AutocompleteRule[] = []
|
|
19
|
+
const getRules = () => rules
|
|
20
|
+
const plugin = createAutocompletePlugin({ getRules })
|
|
21
|
+
|
|
22
|
+
return function reducer(inputs) {
|
|
23
|
+
rules = inputs
|
|
24
|
+
return plugin
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
parent: pluginFacet,
|
|
28
|
+
singleton: true,
|
|
29
|
+
})
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineCommands,
|
|
3
|
+
insertNode,
|
|
4
|
+
toggleWrap,
|
|
5
|
+
wrap,
|
|
6
|
+
type Extension,
|
|
7
|
+
} from '@prosekit/core'
|
|
8
|
+
|
|
9
|
+
export type BlockquoteCommandsExtension = Extension<{
|
|
10
|
+
Commands: {
|
|
11
|
+
setBlockquote: []
|
|
12
|
+
insertBlockquote: []
|
|
13
|
+
toggleBlockquote: []
|
|
14
|
+
}
|
|
15
|
+
}>
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
export function defineBlockquoteCommands(): BlockquoteCommandsExtension {
|
|
21
|
+
return defineCommands({
|
|
22
|
+
setBlockquote: () => {
|
|
23
|
+
return wrap({ type: 'blockquote' })
|
|
24
|
+
},
|
|
25
|
+
insertBlockquote: () => {
|
|
26
|
+
return insertNode({ type: 'blockquote' })
|
|
27
|
+
},
|
|
28
|
+
toggleBlockquote: () => {
|
|
29
|
+
return toggleWrap({ type: 'blockquote' })
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { PlainExtension } from '@prosekit/core'
|
|
2
|
+
|
|
3
|
+
import { defineWrappingInputRule } from '../input-rule'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Wraps the text block in a blockquote when `>` is typed at the start of a new
|
|
7
|
+
* line followed by a space.
|
|
8
|
+
*/
|
|
9
|
+
export function defineBlockquoteInputRule(): PlainExtension {
|
|
10
|
+
return defineWrappingInputRule({
|
|
11
|
+
regex: /^>\s/,
|
|
12
|
+
type: 'blockquote',
|
|
13
|
+
})
|
|
14
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {
|
|
2
|
+
describe,
|
|
3
|
+
expect,
|
|
4
|
+
it,
|
|
5
|
+
} from 'vitest'
|
|
6
|
+
|
|
7
|
+
import { setupTest } from '../testing'
|
|
8
|
+
import { pressKey } from '../testing/keyboard'
|
|
9
|
+
|
|
10
|
+
describe('blockquote keymap', () => {
|
|
11
|
+
it('should wrap paragraph into blockquote with shortcut', async () => {
|
|
12
|
+
const { editor, n } = setupTest()
|
|
13
|
+
|
|
14
|
+
const doc1 = n.doc(n.p('hel<a>lo'))
|
|
15
|
+
editor.set(doc1)
|
|
16
|
+
|
|
17
|
+
await pressKey('mod-shift-b')
|
|
18
|
+
|
|
19
|
+
const doc2 = n.doc(n.blockquote(n.p('hello')))
|
|
20
|
+
expect(editor.state.doc.toJSON()).toEqual(doc2.toJSON())
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('should lift blockquote up with shortcut', async () => {
|
|
24
|
+
const { editor, n } = setupTest()
|
|
25
|
+
const doc1 = n.doc(n.blockquote(n.p('hello')))
|
|
26
|
+
editor.set(doc1)
|
|
27
|
+
|
|
28
|
+
await pressKey('mod-shift-b')
|
|
29
|
+
|
|
30
|
+
const doc2 = n.doc(n.p('hello'))
|
|
31
|
+
expect(editor.state.doc.toJSON()).toEqual(doc2.toJSON())
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('should unset blockquote when press backspace at the beginning of blockquote', async () => {
|
|
35
|
+
const { editor, n } = setupTest()
|
|
36
|
+
const doc1 = n.doc(n.blockquote(n.p('<a>hello')))
|
|
37
|
+
|
|
38
|
+
editor.set(doc1)
|
|
39
|
+
|
|
40
|
+
await pressKey('Backspace')
|
|
41
|
+
|
|
42
|
+
const doc2 = n.doc(n.p('hello'))
|
|
43
|
+
expect(editor.state.doc.toJSON()).toEqual(doc2.toJSON())
|
|
44
|
+
})
|
|
45
|
+
})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineKeymap,
|
|
3
|
+
isAtBlockStart,
|
|
4
|
+
toggleWrap,
|
|
5
|
+
type PlainExtension,
|
|
6
|
+
} from '@prosekit/core'
|
|
7
|
+
import { joinBackward } from '@prosekit/pm/commands'
|
|
8
|
+
import type { Command } from '@prosekit/pm/state'
|
|
9
|
+
|
|
10
|
+
function toggleBlockquoteKeybinding(): Command {
|
|
11
|
+
return toggleWrap({ type: 'blockquote' })
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function backspaceUnsetBlockquote(): Command {
|
|
15
|
+
return (state, dispatch, view) => {
|
|
16
|
+
const $pos = isAtBlockStart(state, view)
|
|
17
|
+
if ($pos?.node(-1).type.name === 'blockquote') {
|
|
18
|
+
return joinBackward(state, dispatch, view)
|
|
19
|
+
}
|
|
20
|
+
return false
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
export function defineBlockquoteKeymap(): PlainExtension {
|
|
27
|
+
return defineKeymap({
|
|
28
|
+
'mod-shift-b': toggleBlockquoteKeybinding(),
|
|
29
|
+
'Backspace': backspaceUnsetBlockquote(),
|
|
30
|
+
})
|
|
31
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineNodeSpec,
|
|
3
|
+
type Extension,
|
|
4
|
+
} from '@prosekit/core'
|
|
5
|
+
import type { Attrs } from '@prosekit/pm/model'
|
|
6
|
+
|
|
7
|
+
export type BlockquoteSpecExtension = Extension<{
|
|
8
|
+
Nodes: {
|
|
9
|
+
blockquote: Attrs
|
|
10
|
+
}
|
|
11
|
+
}>
|
|
12
|
+
|
|
13
|
+
export function defineBlockquoteSpec(): BlockquoteSpecExtension {
|
|
14
|
+
return defineNodeSpec({
|
|
15
|
+
name: 'blockquote',
|
|
16
|
+
content: 'block+',
|
|
17
|
+
group: 'block',
|
|
18
|
+
defining: true,
|
|
19
|
+
parseDOM: [{ tag: 'blockquote' }],
|
|
20
|
+
toDOM() {
|
|
21
|
+
return ['blockquote', 0]
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
union,
|
|
3
|
+
type Union,
|
|
4
|
+
} from '@prosekit/core'
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
defineBlockquoteCommands,
|
|
8
|
+
type BlockquoteCommandsExtension,
|
|
9
|
+
} from './blockquote-commands'
|
|
10
|
+
import { defineBlockquoteInputRule } from './blockquote-input-rule'
|
|
11
|
+
import { defineBlockquoteKeymap } from './blockquote-keymap'
|
|
12
|
+
import {
|
|
13
|
+
defineBlockquoteSpec,
|
|
14
|
+
type BlockquoteSpecExtension,
|
|
15
|
+
} from './blockquote-spec'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
export type BlockquoteExtension = Union<
|
|
21
|
+
[BlockquoteSpecExtension, BlockquoteCommandsExtension]
|
|
22
|
+
>
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @public
|
|
26
|
+
*/
|
|
27
|
+
export function defineBlockquote(): BlockquoteExtension {
|
|
28
|
+
return union(
|
|
29
|
+
defineBlockquoteSpec(),
|
|
30
|
+
defineBlockquoteInputRule(),
|
|
31
|
+
defineBlockquoteCommands(),
|
|
32
|
+
defineBlockquoteKeymap(),
|
|
33
|
+
)
|
|
34
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export {
|
|
2
|
+
defineBlockquote,
|
|
3
|
+
type BlockquoteExtension,
|
|
4
|
+
} from './blockquote'
|
|
5
|
+
export {
|
|
6
|
+
defineBlockquoteCommands,
|
|
7
|
+
type BlockquoteCommandsExtension,
|
|
8
|
+
} from './blockquote-commands'
|
|
9
|
+
export { defineBlockquoteInputRule } from './blockquote-input-rule'
|
|
10
|
+
export { defineBlockquoteKeymap } from './blockquote-keymap'
|
|
11
|
+
export {
|
|
12
|
+
defineBlockquoteSpec,
|
|
13
|
+
type BlockquoteSpecExtension,
|
|
14
|
+
} from './blockquote-spec'
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineCommands,
|
|
3
|
+
toggleMark,
|
|
4
|
+
type Extension,
|
|
5
|
+
} from '@prosekit/core'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export type BoldCommandsExtension = Extension<{
|
|
11
|
+
Commands: {
|
|
12
|
+
toggleBold: []
|
|
13
|
+
}
|
|
14
|
+
}>
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
export function defineBoldCommands(): BoldCommandsExtension {
|
|
20
|
+
return defineCommands({
|
|
21
|
+
toggleBold: () => toggleMark({ type: 'bold' }),
|
|
22
|
+
})
|
|
23
|
+
}
|