@multiplatform.one/rich-text 6.1.0 → 6.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -0
- package/dist/cjs/RichEditor.cjs +89 -9
- package/dist/cjs/RichEditor.native.js +444 -35
- package/dist/cjs/RichEditor.native.js.map +1 -1
- package/dist/cjs/RichTextEditor/index.cjs +20 -2
- package/dist/cjs/RichTextEditor/index.native.js +20 -2
- package/dist/cjs/RichTextEditor/index.native.js.map +1 -1
- package/dist/cjs/constants.cjs +52 -29
- package/dist/cjs/constants.native.js +59 -29
- package/dist/cjs/constants.native.js.map +1 -1
- package/dist/cjs/editor/BlockDragHandle.cjs +22 -7
- package/dist/cjs/editor/BlockDragHandle.native.js +41 -7
- package/dist/cjs/editor/BlockDragHandle.native.js.map +1 -1
- package/dist/cjs/editor/SlashCommands.cjs +257 -294
- package/dist/cjs/editor/SlashCommands.native.js +320 -341
- package/dist/cjs/editor/SlashCommands.native.js.map +1 -1
- package/dist/cjs/editor/TentapEditor.native.js +59 -12
- package/dist/cjs/editor/TentapEditor.native.js.map +1 -1
- package/dist/cjs/editor/TiptapEditor.cjs +143 -240
- package/dist/cjs/editor/TiptapEditor.native.js +163 -289
- package/dist/cjs/editor/TiptapEditor.native.js.map +1 -1
- package/dist/cjs/editor/blockAnchor.cjs +43 -0
- package/dist/cjs/editor/blockAnchor.native.js +68 -0
- package/dist/cjs/editor/blockAnchor.native.js.map +1 -0
- package/dist/cjs/editor/capabilities.cjs +37 -0
- package/dist/cjs/editor/capabilities.native.js +41 -0
- package/dist/cjs/editor/capabilities.native.js.map +1 -0
- package/dist/cjs/editor/codeBlockPreview.cjs +230 -0
- package/dist/cjs/editor/codeBlockPreview.native.js +265 -0
- package/dist/cjs/editor/codeBlockPreview.native.js.map +1 -0
- package/dist/cjs/editor/editorStyles.cjs +203 -34
- package/dist/cjs/editor/editorStyles.native.js +203 -34
- package/dist/cjs/editor/editorStyles.native.js.map +1 -1
- package/dist/cjs/editor/emoji.cjs +128 -0
- package/dist/cjs/editor/emoji.native.js +184 -0
- package/dist/cjs/editor/emoji.native.js.map +1 -0
- package/dist/cjs/editor/extensions.cjs +275 -0
- package/dist/cjs/editor/extensions.native.js +328 -0
- package/dist/cjs/editor/extensions.native.js.map +1 -0
- package/dist/cjs/editor/lazyKatex.cjs +78 -0
- package/dist/cjs/editor/lazyKatex.native.js +117 -0
- package/dist/cjs/editor/lazyKatex.native.js.map +1 -0
- package/dist/cjs/editor/markdown.cjs +216 -0
- package/dist/cjs/editor/markdown.native.js +278 -0
- package/dist/cjs/editor/markdown.native.js.map +1 -0
- package/dist/cjs/editor/mathematics.cjs +168 -0
- package/dist/cjs/editor/mathematics.native.js +202 -0
- package/dist/cjs/editor/mathematics.native.js.map +1 -0
- package/dist/cjs/editor/media.cjs +138 -0
- package/dist/cjs/editor/media.native.js +172 -0
- package/dist/cjs/editor/media.native.js.map +1 -0
- package/dist/cjs/editor/mentions.cjs +73 -0
- package/dist/cjs/editor/mentions.native.js +84 -0
- package/dist/cjs/editor/mentions.native.js.map +1 -0
- package/dist/cjs/editor/pasteCleanup.cjs +83 -0
- package/dist/cjs/editor/pasteCleanup.native.js +175 -0
- package/dist/cjs/editor/pasteCleanup.native.js.map +1 -0
- package/dist/cjs/editor/suggestionPopup.cjs +297 -0
- package/dist/cjs/editor/suggestionPopup.native.js +327 -0
- package/dist/cjs/editor/suggestionPopup.native.js.map +1 -0
- package/dist/cjs/index.cjs +5 -2
- package/dist/cjs/index.native.js +4 -1
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/preview.cjs +263 -0
- package/dist/cjs/preview.native.js +294 -0
- package/dist/cjs/preview.native.js.map +1 -0
- package/dist/cjs/shared/t.cjs +53 -0
- package/dist/cjs/shared/t.native.js +56 -0
- package/dist/cjs/shared/t.native.js.map +1 -0
- package/dist/cjs/shared/url.cjs +35 -0
- package/dist/cjs/shared/url.native.js +38 -0
- package/dist/cjs/shared/url.native.js.map +1 -0
- package/dist/cjs/styled.cjs +13 -6
- package/dist/cjs/styled.native.js +13 -6
- package/dist/cjs/styled.native.js.map +1 -1
- package/dist/cjs/toolbar/BubbleToolbar.cjs +19 -14
- package/dist/cjs/toolbar/BubbleToolbar.native.js +22 -15
- package/dist/cjs/toolbar/BubbleToolbar.native.js.map +1 -1
- package/dist/cjs/toolbar/CodeBlockBubble.cjs +59 -23
- package/dist/cjs/toolbar/CodeBlockBubble.native.js +71 -25
- package/dist/cjs/toolbar/CodeBlockBubble.native.js.map +1 -1
- package/dist/cjs/toolbar/ColorPicker.cjs +19 -54
- package/dist/cjs/toolbar/ColorPicker.native.js +81 -179
- package/dist/cjs/toolbar/ColorPicker.native.js.map +1 -1
- package/dist/cjs/toolbar/FixedToolbar.cjs +39 -13
- package/dist/cjs/toolbar/FixedToolbar.native.js +46 -54
- package/dist/cjs/toolbar/FixedToolbar.native.js.map +1 -1
- package/dist/cjs/toolbar/HeadingPicker.cjs +32 -39
- package/dist/cjs/toolbar/HeadingPicker.native.js +61 -51
- package/dist/cjs/toolbar/HeadingPicker.native.js.map +1 -1
- package/dist/cjs/toolbar/ImageBubble.cjs +20 -43
- package/dist/cjs/toolbar/ImageBubble.native.js +20 -43
- package/dist/cjs/toolbar/ImageBubble.native.js.map +1 -1
- package/dist/cjs/toolbar/ImageDialog.cjs +5 -4
- package/dist/cjs/toolbar/ImageDialog.native.js +76 -65
- package/dist/cjs/toolbar/ImageDialog.native.js.map +1 -1
- package/dist/cjs/toolbar/LinkBubble.cjs +23 -70
- package/dist/cjs/toolbar/LinkBubble.native.js +23 -70
- package/dist/cjs/toolbar/LinkBubble.native.js.map +1 -1
- package/dist/cjs/toolbar/LinkDialog.cjs +19 -18
- package/dist/cjs/toolbar/LinkDialog.native.js +86 -83
- package/dist/cjs/toolbar/LinkDialog.native.js.map +1 -1
- package/dist/cjs/toolbar/ListPicker.cjs +32 -43
- package/dist/cjs/toolbar/ListPicker.native.js +43 -44
- package/dist/cjs/toolbar/ListPicker.native.js.map +1 -1
- package/dist/cjs/toolbar/MathBubble.cjs +159 -0
- package/dist/cjs/toolbar/MathBubble.native.js +169 -0
- package/dist/cjs/toolbar/MathBubble.native.js.map +1 -0
- package/dist/cjs/toolbar/MoreDropdown.cjs +54 -93
- package/dist/cjs/toolbar/MoreDropdown.native.js +80 -93
- package/dist/cjs/toolbar/MoreDropdown.native.js.map +1 -1
- package/dist/cjs/toolbar/StatusBar.cjs +19 -10
- package/dist/cjs/toolbar/StatusBar.native.js +19 -10
- package/dist/cjs/toolbar/StatusBar.native.js.map +1 -1
- package/dist/cjs/toolbar/TableBubble.cjs +91 -96
- package/dist/cjs/toolbar/TableBubble.native.js +90 -73
- package/dist/cjs/toolbar/TableBubble.native.js.map +1 -1
- package/dist/cjs/toolbar/ToolbarButton.cjs +86 -21
- package/dist/cjs/toolbar/ToolbarButton.native.js +14 -4
- package/dist/cjs/toolbar/ToolbarButton.native.js.map +1 -1
- package/dist/cjs/toolbar/icons.cjs +111 -468
- package/dist/cjs/toolbar/shortcuts.cjs +73 -24
- package/dist/cjs/toolbar/shortcuts.native.js +73 -24
- package/dist/cjs/toolbar/shortcuts.native.js.map +1 -1
- package/dist/cjs/toolbar/useHorizontalScroll.cjs +25 -5
- package/dist/cjs/toolbar/useHorizontalScroll.native.js +25 -5
- package/dist/cjs/toolbar/useHorizontalScroll.native.js.map +1 -1
- package/dist/cjs/toolbar/useRovingFocus.cjs +80 -0
- package/dist/cjs/toolbar/useRovingFocus.native.js +113 -0
- package/dist/cjs/toolbar/useRovingFocus.native.js.map +1 -0
- package/dist/cjs/types.native.js.map +1 -1
- package/dist/esm/RichEditor.mjs +90 -10
- package/dist/esm/RichEditor.mjs.map +1 -1
- package/dist/esm/RichEditor.native.js +447 -38
- package/dist/esm/RichEditor.native.js.map +1 -1
- package/dist/esm/RichTextEditor/index.mjs +20 -2
- package/dist/esm/RichTextEditor/index.mjs.map +1 -1
- package/dist/esm/RichTextEditor/index.native.js +20 -2
- package/dist/esm/RichTextEditor/index.native.js.map +1 -1
- package/dist/esm/constants.mjs +47 -29
- package/dist/esm/constants.mjs.map +1 -1
- package/dist/esm/constants.native.js +54 -29
- package/dist/esm/constants.native.js.map +1 -1
- package/dist/esm/editor/BlockDragHandle.mjs +22 -7
- package/dist/esm/editor/BlockDragHandle.mjs.map +1 -1
- package/dist/esm/editor/BlockDragHandle.native.js +41 -7
- package/dist/esm/editor/BlockDragHandle.native.js.map +1 -1
- package/dist/esm/editor/SlashCommands.mjs +258 -295
- package/dist/esm/editor/SlashCommands.mjs.map +1 -1
- package/dist/esm/editor/SlashCommands.native.js +321 -342
- package/dist/esm/editor/SlashCommands.native.js.map +1 -1
- package/dist/esm/editor/TentapEditor.native.js +60 -13
- package/dist/esm/editor/TentapEditor.native.js.map +1 -1
- package/dist/esm/editor/TiptapEditor.mjs +145 -231
- package/dist/esm/editor/TiptapEditor.mjs.map +1 -1
- package/dist/esm/editor/TiptapEditor.native.js +165 -280
- package/dist/esm/editor/TiptapEditor.native.js.map +1 -1
- package/dist/esm/editor/blockAnchor.mjs +18 -0
- package/dist/esm/editor/blockAnchor.mjs.map +1 -0
- package/dist/esm/editor/blockAnchor.native.js +40 -0
- package/dist/esm/editor/blockAnchor.native.js.map +1 -0
- package/dist/esm/editor/capabilities.mjs +10 -0
- package/dist/esm/editor/capabilities.mjs.map +1 -0
- package/dist/esm/editor/capabilities.native.js +11 -0
- package/dist/esm/editor/capabilities.native.js.map +1 -0
- package/dist/esm/editor/codeBlockPreview.mjs +190 -0
- package/dist/esm/editor/codeBlockPreview.mjs.map +1 -0
- package/dist/esm/editor/codeBlockPreview.native.js +222 -0
- package/dist/esm/editor/codeBlockPreview.native.js.map +1 -0
- package/dist/esm/editor/editorStyles.mjs +203 -34
- package/dist/esm/editor/editorStyles.mjs.map +1 -1
- package/dist/esm/editor/editorStyles.native.js +203 -34
- package/dist/esm/editor/editorStyles.native.js.map +1 -1
- package/dist/esm/editor/emoji.mjs +88 -0
- package/dist/esm/editor/emoji.mjs.map +1 -0
- package/dist/esm/editor/emoji.native.js +141 -0
- package/dist/esm/editor/emoji.native.js.map +1 -0
- package/dist/esm/editor/extensions.mjs +236 -0
- package/dist/esm/editor/extensions.mjs.map +1 -0
- package/dist/esm/editor/extensions.native.js +286 -0
- package/dist/esm/editor/extensions.native.js.map +1 -0
- package/dist/esm/editor/lazyKatex.mjs +40 -0
- package/dist/esm/editor/lazyKatex.mjs.map +1 -0
- package/dist/esm/editor/lazyKatex.native.js +76 -0
- package/dist/esm/editor/lazyKatex.native.js.map +1 -0
- package/dist/esm/editor/markdown.mjs +179 -0
- package/dist/esm/editor/markdown.mjs.map +1 -0
- package/dist/esm/editor/markdown.native.js +238 -0
- package/dist/esm/editor/markdown.native.js.map +1 -0
- package/dist/esm/editor/mathematics.mjs +142 -0
- package/dist/esm/editor/mathematics.mjs.map +1 -0
- package/dist/esm/editor/mathematics.native.js +173 -0
- package/dist/esm/editor/mathematics.native.js.map +1 -0
- package/dist/esm/editor/media.mjs +113 -0
- package/dist/esm/editor/media.mjs.map +1 -0
- package/dist/esm/editor/media.native.js +144 -0
- package/dist/esm/editor/media.native.js.map +1 -0
- package/dist/esm/editor/mentions.mjs +37 -0
- package/dist/esm/editor/mentions.mjs.map +1 -0
- package/dist/esm/editor/mentions.native.js +45 -0
- package/dist/esm/editor/mentions.native.js.map +1 -0
- package/dist/esm/editor/pasteCleanup.mjs +57 -0
- package/dist/esm/editor/pasteCleanup.mjs.map +1 -0
- package/dist/esm/editor/pasteCleanup.native.js +146 -0
- package/dist/esm/editor/pasteCleanup.native.js.map +1 -0
- package/dist/esm/editor/suggestionPopup.mjs +268 -0
- package/dist/esm/editor/suggestionPopup.mjs.map +1 -0
- package/dist/esm/editor/suggestionPopup.native.js +295 -0
- package/dist/esm/editor/suggestionPopup.native.js.map +1 -0
- package/dist/esm/index.mjs +2 -1
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/index.native.js +2 -1
- package/dist/esm/index.native.js.map +1 -1
- package/dist/esm/preview.mjs +236 -0
- package/dist/esm/preview.mjs.map +1 -0
- package/dist/esm/preview.native.js +264 -0
- package/dist/esm/preview.native.js.map +1 -0
- package/dist/esm/shared/t.mjs +17 -0
- package/dist/esm/shared/t.mjs.map +1 -0
- package/dist/esm/shared/t.native.js +17 -0
- package/dist/esm/shared/t.native.js.map +1 -0
- package/dist/esm/shared/url.mjs +10 -0
- package/dist/esm/shared/url.mjs.map +1 -0
- package/dist/esm/shared/url.native.js +10 -0
- package/dist/esm/shared/url.native.js.map +1 -0
- package/dist/esm/styled.mjs +13 -6
- package/dist/esm/styled.mjs.map +1 -1
- package/dist/esm/styled.native.js +13 -6
- package/dist/esm/styled.native.js.map +1 -1
- package/dist/esm/toolbar/BubbleToolbar.mjs +20 -15
- package/dist/esm/toolbar/BubbleToolbar.mjs.map +1 -1
- package/dist/esm/toolbar/BubbleToolbar.native.js +23 -16
- package/dist/esm/toolbar/BubbleToolbar.native.js.map +1 -1
- package/dist/esm/toolbar/CodeBlockBubble.mjs +60 -24
- package/dist/esm/toolbar/CodeBlockBubble.mjs.map +1 -1
- package/dist/esm/toolbar/CodeBlockBubble.native.js +72 -26
- package/dist/esm/toolbar/CodeBlockBubble.native.js.map +1 -1
- package/dist/esm/toolbar/ColorPicker.mjs +21 -56
- package/dist/esm/toolbar/ColorPicker.mjs.map +1 -1
- package/dist/esm/toolbar/ColorPicker.native.js +82 -180
- package/dist/esm/toolbar/ColorPicker.native.js.map +1 -1
- package/dist/esm/toolbar/FixedToolbar.mjs +40 -14
- package/dist/esm/toolbar/FixedToolbar.mjs.map +1 -1
- package/dist/esm/toolbar/FixedToolbar.native.js +47 -55
- package/dist/esm/toolbar/FixedToolbar.native.js.map +1 -1
- package/dist/esm/toolbar/HeadingPicker.mjs +34 -41
- package/dist/esm/toolbar/HeadingPicker.mjs.map +1 -1
- package/dist/esm/toolbar/HeadingPicker.native.js +63 -53
- package/dist/esm/toolbar/HeadingPicker.native.js.map +1 -1
- package/dist/esm/toolbar/ImageBubble.mjs +20 -43
- package/dist/esm/toolbar/ImageBubble.mjs.map +1 -1
- package/dist/esm/toolbar/ImageBubble.native.js +20 -43
- package/dist/esm/toolbar/ImageBubble.native.js.map +1 -1
- package/dist/esm/toolbar/ImageDialog.mjs +5 -4
- package/dist/esm/toolbar/ImageDialog.mjs.map +1 -1
- package/dist/esm/toolbar/ImageDialog.native.js +75 -64
- package/dist/esm/toolbar/ImageDialog.native.js.map +1 -1
- package/dist/esm/toolbar/LinkBubble.mjs +23 -70
- package/dist/esm/toolbar/LinkBubble.mjs.map +1 -1
- package/dist/esm/toolbar/LinkBubble.native.js +23 -70
- package/dist/esm/toolbar/LinkBubble.native.js.map +1 -1
- package/dist/esm/toolbar/LinkDialog.mjs +17 -16
- package/dist/esm/toolbar/LinkDialog.mjs.map +1 -1
- package/dist/esm/toolbar/LinkDialog.native.js +84 -80
- package/dist/esm/toolbar/LinkDialog.native.js.map +1 -1
- package/dist/esm/toolbar/ListPicker.mjs +33 -44
- package/dist/esm/toolbar/ListPicker.mjs.map +1 -1
- package/dist/esm/toolbar/ListPicker.native.js +44 -45
- package/dist/esm/toolbar/ListPicker.native.js.map +1 -1
- package/dist/esm/toolbar/MathBubble.mjs +134 -0
- package/dist/esm/toolbar/MathBubble.mjs.map +1 -0
- package/dist/esm/toolbar/MathBubble.native.js +141 -0
- package/dist/esm/toolbar/MathBubble.native.js.map +1 -0
- package/dist/esm/toolbar/MoreDropdown.mjs +53 -92
- package/dist/esm/toolbar/MoreDropdown.mjs.map +1 -1
- package/dist/esm/toolbar/MoreDropdown.native.js +79 -92
- package/dist/esm/toolbar/MoreDropdown.native.js.map +1 -1
- package/dist/esm/toolbar/StatusBar.mjs +19 -10
- package/dist/esm/toolbar/StatusBar.mjs.map +1 -1
- package/dist/esm/toolbar/StatusBar.native.js +19 -10
- package/dist/esm/toolbar/StatusBar.native.js.map +1 -1
- package/dist/esm/toolbar/TableBubble.mjs +91 -96
- package/dist/esm/toolbar/TableBubble.mjs.map +1 -1
- package/dist/esm/toolbar/TableBubble.native.js +90 -73
- package/dist/esm/toolbar/TableBubble.native.js.map +1 -1
- package/dist/esm/toolbar/ToolbarButton.mjs +87 -22
- package/dist/esm/toolbar/ToolbarButton.mjs.map +1 -1
- package/dist/esm/toolbar/ToolbarButton.native.js +14 -4
- package/dist/esm/toolbar/ToolbarButton.native.js.map +1 -1
- package/dist/esm/toolbar/icons.mjs +109 -469
- package/dist/esm/toolbar/icons.mjs.map +1 -1
- package/dist/esm/toolbar/shortcuts.mjs +73 -24
- package/dist/esm/toolbar/shortcuts.mjs.map +1 -1
- package/dist/esm/toolbar/shortcuts.native.js +73 -24
- package/dist/esm/toolbar/shortcuts.native.js.map +1 -1
- package/dist/esm/toolbar/useHorizontalScroll.mjs +25 -6
- package/dist/esm/toolbar/useHorizontalScroll.mjs.map +1 -1
- package/dist/esm/toolbar/useHorizontalScroll.native.js +25 -6
- package/dist/esm/toolbar/useHorizontalScroll.native.js.map +1 -1
- package/dist/esm/toolbar/useRovingFocus.mjs +55 -0
- package/dist/esm/toolbar/useRovingFocus.mjs.map +1 -0
- package/dist/esm/toolbar/useRovingFocus.native.js +85 -0
- package/dist/esm/toolbar/useRovingFocus.native.js.map +1 -0
- package/dist/esm/types.mjs.map +1 -1
- package/dist/esm/types.native.js.map +1 -1
- package/dist/jsx/RichEditor.mjs +90 -10
- package/dist/jsx/RichEditor.mjs.map +1 -1
- package/dist/jsx/RichEditor.native.js +444 -35
- package/dist/jsx/RichEditor.native.js.map +1 -1
- package/dist/jsx/RichTextEditor/index.mjs +20 -2
- package/dist/jsx/RichTextEditor/index.mjs.map +1 -1
- package/dist/jsx/RichTextEditor/index.native.js +20 -2
- package/dist/jsx/RichTextEditor/index.native.js.map +1 -1
- package/dist/jsx/constants.mjs +47 -29
- package/dist/jsx/constants.mjs.map +1 -1
- package/dist/jsx/constants.native.js +59 -29
- package/dist/jsx/constants.native.js.map +1 -1
- package/dist/jsx/editor/BlockDragHandle.mjs +22 -7
- package/dist/jsx/editor/BlockDragHandle.mjs.map +1 -1
- package/dist/jsx/editor/BlockDragHandle.native.js +41 -7
- package/dist/jsx/editor/BlockDragHandle.native.js.map +1 -1
- package/dist/jsx/editor/SlashCommands.mjs +258 -295
- package/dist/jsx/editor/SlashCommands.mjs.map +1 -1
- package/dist/jsx/editor/SlashCommands.native.js +320 -341
- package/dist/jsx/editor/SlashCommands.native.js.map +1 -1
- package/dist/jsx/editor/TentapEditor.native.js +59 -12
- package/dist/jsx/editor/TentapEditor.native.js.map +1 -1
- package/dist/jsx/editor/TiptapEditor.mjs +145 -231
- package/dist/jsx/editor/TiptapEditor.mjs.map +1 -1
- package/dist/jsx/editor/TiptapEditor.native.js +163 -289
- package/dist/jsx/editor/TiptapEditor.native.js.map +1 -1
- package/dist/jsx/editor/blockAnchor.mjs +18 -0
- package/dist/jsx/editor/blockAnchor.mjs.map +1 -0
- package/dist/jsx/editor/blockAnchor.native.js +68 -0
- package/dist/jsx/editor/blockAnchor.native.js.map +1 -0
- package/dist/jsx/editor/capabilities.mjs +10 -0
- package/dist/jsx/editor/capabilities.mjs.map +1 -0
- package/dist/jsx/editor/capabilities.native.js +41 -0
- package/dist/jsx/editor/capabilities.native.js.map +1 -0
- package/dist/jsx/editor/codeBlockPreview.mjs +190 -0
- package/dist/jsx/editor/codeBlockPreview.mjs.map +1 -0
- package/dist/jsx/editor/codeBlockPreview.native.js +265 -0
- package/dist/jsx/editor/codeBlockPreview.native.js.map +1 -0
- package/dist/jsx/editor/editorStyles.mjs +203 -34
- package/dist/jsx/editor/editorStyles.mjs.map +1 -1
- package/dist/jsx/editor/editorStyles.native.js +203 -34
- package/dist/jsx/editor/editorStyles.native.js.map +1 -1
- package/dist/jsx/editor/emoji.mjs +88 -0
- package/dist/jsx/editor/emoji.mjs.map +1 -0
- package/dist/jsx/editor/emoji.native.js +184 -0
- package/dist/jsx/editor/emoji.native.js.map +1 -0
- package/dist/jsx/editor/extensions.mjs +236 -0
- package/dist/jsx/editor/extensions.mjs.map +1 -0
- package/dist/jsx/editor/extensions.native.js +328 -0
- package/dist/jsx/editor/extensions.native.js.map +1 -0
- package/dist/jsx/editor/lazyKatex.mjs +40 -0
- package/dist/jsx/editor/lazyKatex.mjs.map +1 -0
- package/dist/jsx/editor/lazyKatex.native.js +117 -0
- package/dist/jsx/editor/lazyKatex.native.js.map +1 -0
- package/dist/jsx/editor/markdown.mjs +179 -0
- package/dist/jsx/editor/markdown.mjs.map +1 -0
- package/dist/jsx/editor/markdown.native.js +278 -0
- package/dist/jsx/editor/markdown.native.js.map +1 -0
- package/dist/jsx/editor/mathematics.mjs +142 -0
- package/dist/jsx/editor/mathematics.mjs.map +1 -0
- package/dist/jsx/editor/mathematics.native.js +202 -0
- package/dist/jsx/editor/mathematics.native.js.map +1 -0
- package/dist/jsx/editor/media.mjs +113 -0
- package/dist/jsx/editor/media.mjs.map +1 -0
- package/dist/jsx/editor/media.native.js +172 -0
- package/dist/jsx/editor/media.native.js.map +1 -0
- package/dist/jsx/editor/mentions.mjs +37 -0
- package/dist/jsx/editor/mentions.mjs.map +1 -0
- package/dist/jsx/editor/mentions.native.js +84 -0
- package/dist/jsx/editor/mentions.native.js.map +1 -0
- package/dist/jsx/editor/pasteCleanup.mjs +57 -0
- package/dist/jsx/editor/pasteCleanup.mjs.map +1 -0
- package/dist/jsx/editor/pasteCleanup.native.js +175 -0
- package/dist/jsx/editor/pasteCleanup.native.js.map +1 -0
- package/dist/jsx/editor/suggestionPopup.mjs +268 -0
- package/dist/jsx/editor/suggestionPopup.mjs.map +1 -0
- package/dist/jsx/editor/suggestionPopup.native.js +327 -0
- package/dist/jsx/editor/suggestionPopup.native.js.map +1 -0
- package/dist/jsx/index.js +2 -1
- package/dist/jsx/index.js.map +1 -1
- package/dist/jsx/index.mjs +2 -1
- package/dist/jsx/index.mjs.map +1 -1
- package/dist/jsx/index.native.js +4 -1
- package/dist/jsx/index.native.js.map +1 -1
- package/dist/jsx/preview.mjs +236 -0
- package/dist/jsx/preview.mjs.map +1 -0
- package/dist/jsx/preview.native.js +294 -0
- package/dist/jsx/preview.native.js.map +1 -0
- package/dist/jsx/shared/t.mjs +17 -0
- package/dist/jsx/shared/t.mjs.map +1 -0
- package/dist/jsx/shared/t.native.js +56 -0
- package/dist/jsx/shared/t.native.js.map +1 -0
- package/dist/jsx/shared/url.mjs +10 -0
- package/dist/jsx/shared/url.mjs.map +1 -0
- package/dist/jsx/shared/url.native.js +38 -0
- package/dist/jsx/shared/url.native.js.map +1 -0
- package/dist/jsx/styled.mjs +13 -6
- package/dist/jsx/styled.mjs.map +1 -1
- package/dist/jsx/styled.native.js +13 -6
- package/dist/jsx/styled.native.js.map +1 -1
- package/dist/jsx/toolbar/BubbleToolbar.mjs +20 -15
- package/dist/jsx/toolbar/BubbleToolbar.mjs.map +1 -1
- package/dist/jsx/toolbar/BubbleToolbar.native.js +22 -15
- package/dist/jsx/toolbar/BubbleToolbar.native.js.map +1 -1
- package/dist/jsx/toolbar/CodeBlockBubble.mjs +60 -24
- package/dist/jsx/toolbar/CodeBlockBubble.mjs.map +1 -1
- package/dist/jsx/toolbar/CodeBlockBubble.native.js +71 -25
- package/dist/jsx/toolbar/CodeBlockBubble.native.js.map +1 -1
- package/dist/jsx/toolbar/ColorPicker.mjs +21 -56
- package/dist/jsx/toolbar/ColorPicker.mjs.map +1 -1
- package/dist/jsx/toolbar/ColorPicker.native.js +81 -179
- package/dist/jsx/toolbar/ColorPicker.native.js.map +1 -1
- package/dist/jsx/toolbar/FixedToolbar.mjs +40 -14
- package/dist/jsx/toolbar/FixedToolbar.mjs.map +1 -1
- package/dist/jsx/toolbar/FixedToolbar.native.js +46 -54
- package/dist/jsx/toolbar/FixedToolbar.native.js.map +1 -1
- package/dist/jsx/toolbar/HeadingPicker.mjs +34 -41
- package/dist/jsx/toolbar/HeadingPicker.mjs.map +1 -1
- package/dist/jsx/toolbar/HeadingPicker.native.js +61 -51
- package/dist/jsx/toolbar/HeadingPicker.native.js.map +1 -1
- package/dist/jsx/toolbar/ImageBubble.mjs +20 -43
- package/dist/jsx/toolbar/ImageBubble.mjs.map +1 -1
- package/dist/jsx/toolbar/ImageBubble.native.js +20 -43
- package/dist/jsx/toolbar/ImageBubble.native.js.map +1 -1
- package/dist/jsx/toolbar/ImageDialog.mjs +5 -4
- package/dist/jsx/toolbar/ImageDialog.mjs.map +1 -1
- package/dist/jsx/toolbar/ImageDialog.native.js +76 -65
- package/dist/jsx/toolbar/ImageDialog.native.js.map +1 -1
- package/dist/jsx/toolbar/LinkBubble.mjs +23 -70
- package/dist/jsx/toolbar/LinkBubble.mjs.map +1 -1
- package/dist/jsx/toolbar/LinkBubble.native.js +23 -70
- package/dist/jsx/toolbar/LinkBubble.native.js.map +1 -1
- package/dist/jsx/toolbar/LinkDialog.mjs +17 -16
- package/dist/jsx/toolbar/LinkDialog.mjs.map +1 -1
- package/dist/jsx/toolbar/LinkDialog.native.js +86 -83
- package/dist/jsx/toolbar/LinkDialog.native.js.map +1 -1
- package/dist/jsx/toolbar/ListPicker.mjs +33 -44
- package/dist/jsx/toolbar/ListPicker.mjs.map +1 -1
- package/dist/jsx/toolbar/ListPicker.native.js +43 -44
- package/dist/jsx/toolbar/ListPicker.native.js.map +1 -1
- package/dist/jsx/toolbar/MathBubble.mjs +134 -0
- package/dist/jsx/toolbar/MathBubble.mjs.map +1 -0
- package/dist/jsx/toolbar/MathBubble.native.js +169 -0
- package/dist/jsx/toolbar/MathBubble.native.js.map +1 -0
- package/dist/jsx/toolbar/MoreDropdown.mjs +53 -92
- package/dist/jsx/toolbar/MoreDropdown.mjs.map +1 -1
- package/dist/jsx/toolbar/MoreDropdown.native.js +80 -93
- package/dist/jsx/toolbar/MoreDropdown.native.js.map +1 -1
- package/dist/jsx/toolbar/StatusBar.mjs +19 -10
- package/dist/jsx/toolbar/StatusBar.mjs.map +1 -1
- package/dist/jsx/toolbar/StatusBar.native.js +19 -10
- package/dist/jsx/toolbar/StatusBar.native.js.map +1 -1
- package/dist/jsx/toolbar/TableBubble.mjs +91 -96
- package/dist/jsx/toolbar/TableBubble.mjs.map +1 -1
- package/dist/jsx/toolbar/TableBubble.native.js +90 -73
- package/dist/jsx/toolbar/TableBubble.native.js.map +1 -1
- package/dist/jsx/toolbar/ToolbarButton.mjs +87 -22
- package/dist/jsx/toolbar/ToolbarButton.mjs.map +1 -1
- package/dist/jsx/toolbar/ToolbarButton.native.js +14 -4
- package/dist/jsx/toolbar/ToolbarButton.native.js.map +1 -1
- package/dist/jsx/toolbar/icons.mjs +109 -469
- package/dist/jsx/toolbar/icons.mjs.map +1 -1
- package/dist/jsx/toolbar/shortcuts.mjs +73 -24
- package/dist/jsx/toolbar/shortcuts.mjs.map +1 -1
- package/dist/jsx/toolbar/shortcuts.native.js +73 -24
- package/dist/jsx/toolbar/shortcuts.native.js.map +1 -1
- package/dist/jsx/toolbar/useHorizontalScroll.mjs +25 -6
- package/dist/jsx/toolbar/useHorizontalScroll.mjs.map +1 -1
- package/dist/jsx/toolbar/useHorizontalScroll.native.js +25 -5
- package/dist/jsx/toolbar/useHorizontalScroll.native.js.map +1 -1
- package/dist/jsx/toolbar/useRovingFocus.mjs +55 -0
- package/dist/jsx/toolbar/useRovingFocus.mjs.map +1 -0
- package/dist/jsx/toolbar/useRovingFocus.native.js +113 -0
- package/dist/jsx/toolbar/useRovingFocus.native.js.map +1 -0
- package/dist/jsx/types.mjs.map +1 -1
- package/dist/jsx/types.native.js.map +1 -1
- package/package.json +28 -13
- package/src/RichEditor.native.tsx +471 -49
- package/src/RichEditor.stories.tsx +190 -0
- package/src/RichEditor.tsx +126 -7
- package/src/RichTextEditor/index.tsx +54 -3
- package/src/RichTextEditor/richTextEditor.spec.tsx +11 -4
- package/src/constants.ts +86 -24
- package/src/editor/BlockDragHandle.ts +30 -5
- package/src/editor/SlashCommands.tsx +238 -293
- package/src/editor/TentapEditor.native.tsx +81 -17
- package/src/editor/TiptapEditor.tsx +202 -265
- package/src/editor/blockAnchor.spec.ts +70 -0
- package/src/editor/blockAnchor.ts +42 -0
- package/src/editor/capabilities.spec.ts +190 -0
- package/src/editor/capabilities.ts +76 -0
- package/src/editor/codeBlockPreview.spec.ts +144 -0
- package/src/editor/codeBlockPreview.ts +266 -0
- package/src/editor/css.d.ts +2 -0
- package/src/editor/details.spec.ts +48 -0
- package/src/editor/editorStyles.spec.ts +25 -0
- package/src/editor/editorStyles.ts +218 -34
- package/src/editor/emoji.spec.ts +88 -0
- package/src/editor/emoji.ts +147 -0
- package/src/editor/emojilib.d.ts +16 -0
- package/src/editor/extensions.ts +315 -0
- package/src/editor/lazyKatex.spec.ts +38 -0
- package/src/editor/lazyKatex.ts +68 -0
- package/src/editor/markdown.spec.ts +196 -0
- package/src/editor/markdown.ts +249 -0
- package/src/editor/mathematics.spec.ts +97 -0
- package/src/editor/mathematics.ts +179 -0
- package/src/editor/media.spec.ts +121 -0
- package/src/editor/media.ts +133 -0
- package/src/editor/mentions.spec.ts +41 -0
- package/src/editor/mentions.ts +55 -0
- package/src/editor/pasteCleanup.spec.ts +84 -0
- package/src/editor/pasteCleanup.ts +124 -0
- package/src/editor/suggestionPopup.spec.tsx +105 -0
- package/src/editor/suggestionPopup.tsx +414 -0
- package/src/editor/tiptapEditor.spec.tsx +15 -8
- package/src/exports.spec.ts +4 -2
- package/src/index.ts +10 -1
- package/src/integration.spec.tsx +38 -9
- package/src/preview.spec.ts +130 -0
- package/src/preview.ts +287 -0
- package/src/richEditor.spec.tsx +10 -3
- package/src/shared/t.ts +25 -0
- package/src/shared/url.ts +16 -0
- package/src/styled.ts +13 -6
- package/src/toolbar/BubbleToolbar.tsx +48 -33
- package/src/toolbar/CodeBlockBubble.tsx +66 -28
- package/src/toolbar/ColorPicker.native.tsx +126 -0
- package/src/toolbar/ColorPicker.tsx +23 -67
- package/src/toolbar/FixedToolbar.native.tsx +51 -51
- package/src/toolbar/FixedToolbar.tsx +73 -21
- package/src/toolbar/HeadingPicker.tsx +46 -38
- package/src/toolbar/ImageBubble.tsx +26 -44
- package/src/toolbar/ImageDialog.native.tsx +97 -0
- package/src/toolbar/ImageDialog.tsx +5 -4
- package/src/toolbar/LinkBubble.tsx +27 -64
- package/src/toolbar/LinkDialog.native.tsx +117 -0
- package/src/toolbar/LinkDialog.tsx +28 -24
- package/src/toolbar/ListPicker.tsx +47 -43
- package/src/toolbar/MathBubble.tsx +140 -0
- package/src/toolbar/MoreDropdown.tsx +79 -62
- package/src/toolbar/StatusBar.tsx +13 -8
- package/src/toolbar/TableBubble.tsx +136 -95
- package/src/toolbar/ToolbarButton.native.tsx +14 -4
- package/src/toolbar/ToolbarButton.tsx +98 -19
- package/src/toolbar/bubbleToolbar.spec.tsx +2 -1
- package/src/toolbar/fixedToolbar.spec.tsx +4 -2
- package/src/toolbar/icons.tsx +113 -323
- package/src/toolbar/shortcuts.ts +77 -24
- package/src/toolbar/toolbarPrimitives.spec.tsx +29 -1
- package/src/toolbar/toolbarSubComponents.spec.tsx +4 -2
- package/src/toolbar/useHorizontalScroll.spec.ts +67 -0
- package/src/toolbar/useHorizontalScroll.tsx +39 -12
- package/src/toolbar/useRovingFocus.spec.tsx +100 -0
- package/src/toolbar/useRovingFocus.ts +82 -0
- package/src/types.spec.ts +40 -1
- package/src/types.ts +56 -1
- package/types/RichEditor.d.ts +51 -0
- package/types/RichEditor.d.ts.map +1 -0
- package/types/RichEditor.native.d.ts +59 -0
- package/types/RichEditor.native.d.ts.map +1 -0
- package/types/RichTextEditor/index.d.ts +77 -0
- package/types/RichTextEditor/index.d.ts.map +1 -0
- package/types/constants.d.ts +49 -0
- package/types/constants.d.ts.map +1 -0
- package/types/context.d.ts +10 -0
- package/types/context.d.ts.map +1 -0
- package/types/editor/BlockDragHandle.d.ts +8 -0
- package/types/editor/BlockDragHandle.d.ts.map +1 -0
- package/types/editor/MarkdownPaste.d.ts +8 -0
- package/types/editor/MarkdownPaste.d.ts.map +1 -0
- package/types/editor/SlashCommands.d.ts +24 -0
- package/types/editor/SlashCommands.d.ts.map +1 -0
- package/types/editor/TentapEditor.native.d.ts +21 -0
- package/types/editor/TentapEditor.native.d.ts.map +1 -0
- package/types/editor/TiptapEditor.d.ts +24 -0
- package/types/editor/TiptapEditor.d.ts.map +1 -0
- package/types/editor/blockAnchor.d.ts +25 -0
- package/types/editor/blockAnchor.d.ts.map +1 -0
- package/types/editor/capabilities.d.ts +29 -0
- package/types/editor/capabilities.d.ts.map +1 -0
- package/types/editor/codeBlockPreview.d.ts +51 -0
- package/types/editor/codeBlockPreview.d.ts.map +1 -0
- package/types/editor/editorStyles.d.ts +25 -0
- package/types/editor/editorStyles.d.ts.map +1 -0
- package/types/editor/emoji.d.ts +45 -0
- package/types/editor/emoji.d.ts.map +1 -0
- package/types/editor/extensions.d.ts +41 -0
- package/types/editor/extensions.d.ts.map +1 -0
- package/types/editor/index.d.ts +11 -0
- package/types/editor/index.d.ts.map +1 -0
- package/types/editor/lazyKatex.d.ts +33 -0
- package/types/editor/lazyKatex.d.ts.map +1 -0
- package/types/editor/markdown.d.ts +24 -0
- package/types/editor/markdown.d.ts.map +1 -0
- package/types/editor/mathematics.d.ts +44 -0
- package/types/editor/mathematics.d.ts.map +1 -0
- package/types/editor/media.d.ts +32 -0
- package/types/editor/media.d.ts.map +1 -0
- package/types/editor/mentions.d.ts +15 -0
- package/types/editor/mentions.d.ts.map +1 -0
- package/types/editor/pasteCleanup.d.ts +24 -0
- package/types/editor/pasteCleanup.d.ts.map +1 -0
- package/types/editor/suggestionPopup.d.ts +113 -0
- package/types/editor/suggestionPopup.d.ts.map +1 -0
- package/types/editor/types.d.ts +80 -0
- package/types/editor/types.d.ts.map +1 -0
- package/types/editor/useEditorState.d.ts +19 -0
- package/types/editor/useEditorState.d.ts.map +1 -0
- package/types/index.d.ts +12 -0
- package/types/index.d.ts.map +1 -0
- package/types/preview.d.ts +44 -0
- package/types/preview.d.ts.map +1 -0
- package/types/shared/t.d.ts +10 -0
- package/types/shared/t.d.ts.map +1 -0
- package/types/shared/url.d.ts +9 -0
- package/types/shared/url.d.ts.map +1 -0
- package/types/styled.d.ts +40 -0
- package/types/styled.d.ts.map +1 -0
- package/types/toolbar/BubbleToolbar.d.ts +25 -0
- package/types/toolbar/BubbleToolbar.d.ts.map +1 -0
- package/types/toolbar/CodeBlockBubble.d.ts +10 -0
- package/types/toolbar/CodeBlockBubble.d.ts.map +1 -0
- package/types/toolbar/ColorPicker.d.ts +15 -0
- package/types/toolbar/ColorPicker.d.ts.map +1 -0
- package/types/toolbar/ColorPicker.native.d.ts +20 -0
- package/types/toolbar/ColorPicker.native.d.ts.map +1 -0
- package/types/toolbar/FixedToolbar.d.ts +34 -0
- package/types/toolbar/FixedToolbar.d.ts.map +1 -0
- package/types/toolbar/FixedToolbar.native.d.ts +30 -0
- package/types/toolbar/FixedToolbar.native.d.ts.map +1 -0
- package/types/toolbar/HeadingPicker.d.ts +14 -0
- package/types/toolbar/HeadingPicker.d.ts.map +1 -0
- package/types/toolbar/ImageBubble.d.ts +10 -0
- package/types/toolbar/ImageBubble.d.ts.map +1 -0
- package/types/toolbar/ImageDialog.d.ts +12 -0
- package/types/toolbar/ImageDialog.d.ts.map +1 -0
- package/types/toolbar/ImageDialog.native.d.ts +15 -0
- package/types/toolbar/ImageDialog.native.d.ts.map +1 -0
- package/types/toolbar/LinkBubble.d.ts +10 -0
- package/types/toolbar/LinkBubble.d.ts.map +1 -0
- package/types/toolbar/LinkDialog.d.ts +20 -0
- package/types/toolbar/LinkDialog.d.ts.map +1 -0
- package/types/toolbar/LinkDialog.native.d.ts +18 -0
- package/types/toolbar/LinkDialog.native.d.ts.map +1 -0
- package/types/toolbar/ListPicker.d.ts +14 -0
- package/types/toolbar/ListPicker.d.ts.map +1 -0
- package/types/toolbar/MathBubble.d.ts +11 -0
- package/types/toolbar/MathBubble.d.ts.map +1 -0
- package/types/toolbar/MoreDropdown.d.ts +15 -0
- package/types/toolbar/MoreDropdown.d.ts.map +1 -0
- package/types/toolbar/ResizeHandle.d.ts +11 -0
- package/types/toolbar/ResizeHandle.d.ts.map +1 -0
- package/types/toolbar/StatusBar.d.ts +11 -0
- package/types/toolbar/StatusBar.d.ts.map +1 -0
- package/types/toolbar/TableBubble.d.ts +13 -0
- package/types/toolbar/TableBubble.d.ts.map +1 -0
- package/types/toolbar/ToolbarButton.d.ts +35 -0
- package/types/toolbar/ToolbarButton.d.ts.map +1 -0
- package/types/toolbar/ToolbarButton.native.d.ts +30 -0
- package/types/toolbar/ToolbarButton.native.d.ts.map +1 -0
- package/types/toolbar/ToolbarGroup.d.ts +14 -0
- package/types/toolbar/ToolbarGroup.d.ts.map +1 -0
- package/types/toolbar/icons.d.ts +52 -0
- package/types/toolbar/icons.d.ts.map +1 -0
- package/types/toolbar/icons.native.d.ts +41 -0
- package/types/toolbar/icons.native.d.ts.map +1 -0
- package/types/toolbar/shortcuts.d.ts +34 -0
- package/types/toolbar/shortcuts.d.ts.map +1 -0
- package/types/toolbar/useHorizontalScroll.d.ts +43 -0
- package/types/toolbar/useHorizontalScroll.d.ts.map +1 -0
- package/types/toolbar/useRovingFocus.d.ts +10 -0
- package/types/toolbar/useRovingFocus.d.ts.map +1 -0
- package/types/types.d.ts +147 -0
- package/types/types.d.ts.map +1 -0
- package/dist/cjs/RichEditor.stories.cjs +0 -327
- package/dist/cjs/RichEditor.stories.native.js +0 -342
- package/dist/cjs/RichEditor.stories.native.js.map +0 -1
- package/dist/esm/RichEditor.stories.mjs +0 -291
- package/dist/esm/RichEditor.stories.mjs.map +0 -1
- package/dist/esm/RichEditor.stories.native.js +0 -303
- package/dist/esm/RichEditor.stories.native.js.map +0 -1
- package/dist/jsx/RichEditor.stories.mjs +0 -291
- package/dist/jsx/RichEditor.stories.mjs.map +0 -1
- package/dist/jsx/RichEditor.stories.native.js +0 -342
- package/dist/jsx/RichEditor.stories.native.js.map +0 -1
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical HTML <-> Markdown conversion for the rich text editor (web).
|
|
3
|
+
*
|
|
4
|
+
* One implementation (LC-16) shared by the markdown source mode toggle and
|
|
5
|
+
* the markdown paste handler. GFM coverage beyond turndown's defaults:
|
|
6
|
+
* strikethrough, task lists (Tiptap's label-nested checkbox markup), and
|
|
7
|
+
* pipe tables. Marks with no markdown syntax (underline, sub/superscript,
|
|
8
|
+
* highlight, text color spans) survive as inline HTML so the
|
|
9
|
+
* rich -> markdown -> rich round-trip is lossless for every toolbar feature.
|
|
10
|
+
*
|
|
11
|
+
* GitLab-flavored extras (round-trip lossless):
|
|
12
|
+
* - `$x$` inline math <-> `<span data-type="inline-math" data-latex>`
|
|
13
|
+
* - `$$ ... $$` block math <-> code block with language "math"
|
|
14
|
+
* (``` math fences parse to the same node; `$$` is the canonical output)
|
|
15
|
+
* - ```mermaid fences <-> code block with language "mermaid" (turndown's
|
|
16
|
+
* fenced rule + marked's language classes handle this natively)
|
|
17
|
+
* - `<details>` collapsible sections stay raw HTML in markdown, exactly
|
|
18
|
+
* like GitLab renders them
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import TurndownService from "turndown";
|
|
22
|
+
import { Marked } from "marked";
|
|
23
|
+
|
|
24
|
+
// ===========================================================================
|
|
25
|
+
// HTML -> Markdown (turndown + GFM rules)
|
|
26
|
+
// ===========================================================================
|
|
27
|
+
|
|
28
|
+
function cellMarkdown(cell: HTMLElement, inline: TurndownService): string {
|
|
29
|
+
const md = inline.turndown(cell.innerHTML || "");
|
|
30
|
+
// GFM cells are single-line: collapse block breaks and escape pipes
|
|
31
|
+
return md.replace(/\n+/g, " ").replace(/\|/g, "\\|").trim();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function createTurndown(): TurndownService {
|
|
35
|
+
const service = new TurndownService({
|
|
36
|
+
headingStyle: "atx",
|
|
37
|
+
codeBlockStyle: "fenced",
|
|
38
|
+
bulletListMarker: "-",
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Marks markdown cannot express stay as inline HTML (round-trip lossless):
|
|
42
|
+
// underline, sub/superscript, highlight, and color-carrying spans.
|
|
43
|
+
// Collapsible <details> sections are raw HTML in markdown (GitLab
|
|
44
|
+
// behavior) — kept whole, children included.
|
|
45
|
+
service.keep((node) => {
|
|
46
|
+
const name = node.nodeName;
|
|
47
|
+
if (name === "U" || name === "SUB" || name === "SUP" || name === "MARK") return true;
|
|
48
|
+
if (name === "DETAILS") return true;
|
|
49
|
+
if (name === "SPAN") {
|
|
50
|
+
const style = (node as HTMLElement).getAttribute("style") ?? "";
|
|
51
|
+
return style.includes("color");
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
service.addRule("strikethrough", {
|
|
57
|
+
filter: ["del", "s"],
|
|
58
|
+
replacement: (content) => `~~${content}~~`,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Inline math node -> `$latex$` (GitLab-flavored markdown dollar math)
|
|
62
|
+
service.addRule("inlineMath", {
|
|
63
|
+
filter: (node) =>
|
|
64
|
+
node.nodeName === "SPAN" && (node as HTMLElement).getAttribute("data-type") === "inline-math",
|
|
65
|
+
replacement: (_content, node) => `$${(node as HTMLElement).getAttribute("data-latex") ?? ""}$`,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Math code block -> `$$` delimited block (canonical GLFM display math)
|
|
69
|
+
service.addRule("mathBlock", {
|
|
70
|
+
filter: (node) =>
|
|
71
|
+
node.nodeName === "PRE" &&
|
|
72
|
+
(node.firstChild as HTMLElement | null)?.nodeName === "CODE" &&
|
|
73
|
+
((node.firstChild as HTMLElement).getAttribute("class") ?? "").includes("language-math"),
|
|
74
|
+
replacement: (_content, node) => {
|
|
75
|
+
const code = (node.firstChild as HTMLElement).textContent ?? "";
|
|
76
|
+
return `\n\n$$\n${code.replace(/\n$/, "")}\n$$\n\n`;
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Tiptap task item: <li data-type="taskItem" data-checked="true">
|
|
81
|
+
// <label><input type="checkbox"><span></span></label><div><p>text</p></div>
|
|
82
|
+
service.addRule("taskListItem", {
|
|
83
|
+
filter: (node) =>
|
|
84
|
+
node.nodeName === "LI" && (node as HTMLElement).getAttribute("data-type") === "taskItem",
|
|
85
|
+
replacement: (content, node) => {
|
|
86
|
+
const checked = (node as HTMLElement).getAttribute("data-checked") === "true";
|
|
87
|
+
const text = content.replace(/^\s+|\s+$/g, "").replace(/\n{2,}/g, "\n");
|
|
88
|
+
return `- [${checked ? "x" : " "}] ${text}\n`;
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// The checkbox control inside the task item label contributes no text
|
|
93
|
+
service.addRule("taskListCheckbox", {
|
|
94
|
+
filter: (node) =>
|
|
95
|
+
(node.nodeName === "INPUT" && (node as HTMLInputElement).type === "checkbox") ||
|
|
96
|
+
(node.nodeName === "LABEL" && node.parentElement?.getAttribute("data-type") === "taskItem"),
|
|
97
|
+
replacement: () => "",
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// GFM pipe tables (simple grid: no colspan/rowspan)
|
|
101
|
+
service.addRule("table", {
|
|
102
|
+
filter: "table",
|
|
103
|
+
replacement: (_content, node) => {
|
|
104
|
+
const table = node as HTMLTableElement;
|
|
105
|
+
const rows = Array.from(table.querySelectorAll("tr"));
|
|
106
|
+
if (rows.length === 0) return "";
|
|
107
|
+
const inline = createInlineTurndown();
|
|
108
|
+
const grid = rows.map((row) =>
|
|
109
|
+
Array.from(row.querySelectorAll("th, td")).map((cell) =>
|
|
110
|
+
cellMarkdown(cell as HTMLElement, inline),
|
|
111
|
+
),
|
|
112
|
+
);
|
|
113
|
+
const cols = Math.max(...grid.map((r) => r.length));
|
|
114
|
+
const pad = (r: string[]) => {
|
|
115
|
+
const filled = [...r];
|
|
116
|
+
while (filled.length < cols) filled.push("");
|
|
117
|
+
return filled;
|
|
118
|
+
};
|
|
119
|
+
const line = (cells: string[]) => `| ${pad(cells).join(" | ")} |`;
|
|
120
|
+
const header = line(grid[0]!);
|
|
121
|
+
const divider = `| ${Array.from({ length: cols }, () => "---").join(" | ")} |`;
|
|
122
|
+
const body = grid.slice(1).map(line);
|
|
123
|
+
return `\n\n${[header, divider, ...body].join("\n")}\n\n`;
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
return service;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Inline-only converter for table cells (tables cannot nest in GFM cells) */
|
|
131
|
+
function createInlineTurndown(): TurndownService {
|
|
132
|
+
const service = new TurndownService({ codeBlockStyle: "fenced" });
|
|
133
|
+
service.keep((node) => {
|
|
134
|
+
const name = node.nodeName;
|
|
135
|
+
if (name === "U" || name === "SUB" || name === "SUP" || name === "MARK") return true;
|
|
136
|
+
if (name === "SPAN")
|
|
137
|
+
return ((node as HTMLElement).getAttribute("style") ?? "").includes("color");
|
|
138
|
+
return false;
|
|
139
|
+
});
|
|
140
|
+
service.addRule("strikethrough", {
|
|
141
|
+
filter: ["del", "s"],
|
|
142
|
+
replacement: (content) => `~~${content}~~`,
|
|
143
|
+
});
|
|
144
|
+
return service;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
let turndownSingleton: TurndownService | null = null;
|
|
148
|
+
|
|
149
|
+
/** Convert editor HTML to GFM markdown */
|
|
150
|
+
export function htmlToMarkdown(html: string): string {
|
|
151
|
+
if (!turndownSingleton) turndownSingleton = createTurndown();
|
|
152
|
+
return turndownSingleton.turndown(html);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ===========================================================================
|
|
156
|
+
// Markdown -> HTML (marked + math extensions + Tiptap task list upgrade)
|
|
157
|
+
// ===========================================================================
|
|
158
|
+
|
|
159
|
+
function escapeHtml(value: string): string {
|
|
160
|
+
return value
|
|
161
|
+
.replace(/&/g, "&")
|
|
162
|
+
.replace(/</g, "<")
|
|
163
|
+
.replace(/>/g, ">")
|
|
164
|
+
.replace(/"/g, """);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* `$$ ... $$` display math -> math code block. Accepts the delimiters on
|
|
169
|
+
* their own lines (canonical GLFM) or a single `$$x$$` line; both re-enter
|
|
170
|
+
* the editor as a code block with language "math".
|
|
171
|
+
*/
|
|
172
|
+
const blockMathExtension = {
|
|
173
|
+
name: "blockMath",
|
|
174
|
+
level: "block" as const,
|
|
175
|
+
start: (src: string) => src.match(/^\$\$/m)?.index,
|
|
176
|
+
tokenizer(src: string) {
|
|
177
|
+
const match =
|
|
178
|
+
src.match(/^\$\$[ \t]*\n([\s\S]*?)\n[ \t]*\$\$[ \t]*(?:\n+|$)/) ??
|
|
179
|
+
src.match(/^\$\$([^\n$]+?)\$\$[ \t]*(?:\n+|$)/);
|
|
180
|
+
if (!match) return undefined;
|
|
181
|
+
return { type: "blockMath", raw: match[0], latex: (match[1] ?? "").trim() };
|
|
182
|
+
},
|
|
183
|
+
renderer(token: { latex: string }) {
|
|
184
|
+
return `<pre><code class="language-math">${escapeHtml(token.latex)}</code></pre>\n`;
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* `$x^2$` inline math -> inline-math span. Content must not start or end
|
|
190
|
+
* with whitespace and cannot span lines, so prose like "$5 and $10" never
|
|
191
|
+
* false-positives (same heuristic GitLab/Pandoc use for dollar math).
|
|
192
|
+
*/
|
|
193
|
+
const inlineMathExtension = {
|
|
194
|
+
name: "inlineMath",
|
|
195
|
+
level: "inline" as const,
|
|
196
|
+
start: (src: string) => src.indexOf("$"),
|
|
197
|
+
tokenizer(src: string) {
|
|
198
|
+
const match = src.match(/^\$([^$\n])\$/) ?? src.match(/^\$([^$\n \t][^$\n]*?[^$\n \t])\$/);
|
|
199
|
+
if (!match) return undefined;
|
|
200
|
+
return { type: "inlineMath", raw: match[0], latex: match[1] ?? "" };
|
|
201
|
+
},
|
|
202
|
+
renderer(token: { latex: string }) {
|
|
203
|
+
const escaped = escapeHtml(token.latex);
|
|
204
|
+
return `<span data-type="inline-math" data-latex="${escaped}">${escaped}</span>`;
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
let markedSingleton: Marked | null = null;
|
|
209
|
+
|
|
210
|
+
/** Local marked instance so math extensions never leak into the global */
|
|
211
|
+
function getMarked(): Marked {
|
|
212
|
+
if (!markedSingleton) {
|
|
213
|
+
markedSingleton = new Marked({ gfm: true });
|
|
214
|
+
markedSingleton.use({ extensions: [blockMathExtension, inlineMathExtension] });
|
|
215
|
+
}
|
|
216
|
+
return markedSingleton;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* marked emits GFM task items as `<li><input type="checkbox" checked>`;
|
|
221
|
+
* Tiptap's TaskList/TaskItem parse `ul[data-type="taskList"] >
|
|
222
|
+
* li[data-type="taskItem"][data-checked]`. Upgrade in the DOM so the
|
|
223
|
+
* round-trip re-enters the editor as real task items.
|
|
224
|
+
*/
|
|
225
|
+
function upgradeTaskLists(html: string): string {
|
|
226
|
+
if (typeof document === "undefined" || !html.includes("checkbox")) return html;
|
|
227
|
+
const doc = new DOMParser().parseFromString(html, "text/html");
|
|
228
|
+
for (const input of Array.from(
|
|
229
|
+
doc.querySelectorAll<HTMLInputElement>('li input[type="checkbox"]'),
|
|
230
|
+
)) {
|
|
231
|
+
const li = input.closest("li");
|
|
232
|
+
if (!li) continue;
|
|
233
|
+
li.setAttribute("data-type", "taskItem");
|
|
234
|
+
li.setAttribute(
|
|
235
|
+
"data-checked",
|
|
236
|
+
input.checked || input.hasAttribute("checked") ? "true" : "false",
|
|
237
|
+
);
|
|
238
|
+
input.remove();
|
|
239
|
+
const list = li.closest("ul, ol");
|
|
240
|
+
if (list) list.setAttribute("data-type", "taskList");
|
|
241
|
+
}
|
|
242
|
+
return doc.body.innerHTML;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** Convert GFM markdown to HTML the Tiptap schema can ingest */
|
|
246
|
+
export function markdownToHtml(markdown: string): string {
|
|
247
|
+
const html = getMarked().parse(markdown, { async: false }) as string;
|
|
248
|
+
return upgradeTaskLists(html);
|
|
249
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { Editor } from "@tiptap/core";
|
|
3
|
+
import StarterKit from "@tiptap/starter-kit";
|
|
4
|
+
import { INLINE_MATH_INPUT_RULE, InlineMath } from "./mathematics";
|
|
5
|
+
|
|
6
|
+
function createEditor(content = "<p></p>") {
|
|
7
|
+
return new Editor({
|
|
8
|
+
element: document.createElement("div"),
|
|
9
|
+
extensions: [StarterKit, InlineMath],
|
|
10
|
+
content,
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function waitFor(check: () => boolean, timeoutMs = 3000): Promise<void> {
|
|
15
|
+
const start = Date.now();
|
|
16
|
+
while (!check()) {
|
|
17
|
+
if (Date.now() - start > timeoutMs) throw new Error("waitFor timed out");
|
|
18
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe("INLINE_MATH_INPUT_RULE", () => {
|
|
23
|
+
const matches = (text: string) => text.match(INLINE_MATH_INPUT_RULE);
|
|
24
|
+
|
|
25
|
+
it("matches dollar math at the end of typed text", () => {
|
|
26
|
+
const match = matches("energy $E=mc^2$");
|
|
27
|
+
expect(match?.[2]).toBe("E=mc^2");
|
|
28
|
+
const startOfLine = matches("$x^2$");
|
|
29
|
+
expect(startOfLine?.[2]).toBe("x^2");
|
|
30
|
+
const singleChar = matches("$x$");
|
|
31
|
+
expect(singleChar?.[2]).toBe("x");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("rejects price-like text and padded content", () => {
|
|
35
|
+
expect(matches("costs $5 and $10")).toBeNull();
|
|
36
|
+
expect(matches("$ x$")).toBeNull();
|
|
37
|
+
expect(matches("$x $")).toBeNull();
|
|
38
|
+
expect(matches("$$")).toBeNull();
|
|
39
|
+
// `$$x$$` is block math, not inline
|
|
40
|
+
expect(matches("$$x$$")).toBeNull();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe("InlineMath node", () => {
|
|
45
|
+
it("parses and serializes data-latex spans losslessly", () => {
|
|
46
|
+
const editor = createEditor(
|
|
47
|
+
'<p>see <span data-type="inline-math" data-latex="E=mc^2">E=mc^2</span></p>',
|
|
48
|
+
);
|
|
49
|
+
expect(editor.getHTML()).toContain('data-type="inline-math"');
|
|
50
|
+
expect(editor.getHTML()).toContain('data-latex="E=mc^2"');
|
|
51
|
+
editor.destroy();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("insertInlineMath inserts and selects the node (bubble opens)", () => {
|
|
55
|
+
const editor = createEditor();
|
|
56
|
+
editor.chain().focus().insertInlineMath({ latex: "a+b" }).run();
|
|
57
|
+
expect(editor.isActive("inlineMath")).toBe(true);
|
|
58
|
+
expect(editor.getAttributes("inlineMath").latex).toBe("a+b");
|
|
59
|
+
editor.destroy();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("updateInlineMath rewrites the selected node's latex", () => {
|
|
63
|
+
const editor = createEditor();
|
|
64
|
+
editor.chain().focus().insertInlineMath({ latex: "a" }).run();
|
|
65
|
+
expect(editor.commands.updateInlineMath({ latex: "b^2" })).toBe(true);
|
|
66
|
+
expect(editor.getAttributes("inlineMath").latex).toBe("b^2");
|
|
67
|
+
expect(editor.getHTML()).toContain('data-latex="b^2"');
|
|
68
|
+
editor.destroy();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("updateInlineMath is a no-op without a selected math node", () => {
|
|
72
|
+
const editor = createEditor("<p>plain</p>");
|
|
73
|
+
expect(editor.commands.updateInlineMath({ latex: "x" })).toBe(false);
|
|
74
|
+
editor.destroy();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("node view renders KaTeX markup for valid TeX", async () => {
|
|
78
|
+
const editor = createEditor('<p><span data-type="inline-math" data-latex="x^2">x^2</span></p>');
|
|
79
|
+
const chip = editor.view.dom.querySelector(".rt-math-inline");
|
|
80
|
+
expect(chip).not.toBeNull();
|
|
81
|
+
await waitFor(() => chip!.querySelector(".katex") !== null);
|
|
82
|
+
expect(chip!.getAttribute("aria-label")).toBe("x^2");
|
|
83
|
+
editor.destroy();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("node view shows the error state for invalid TeX", async () => {
|
|
87
|
+
const editor = createEditor(
|
|
88
|
+
'<p><span data-type="inline-math" data-latex="\\nope{">x</span></p>',
|
|
89
|
+
);
|
|
90
|
+
const chip = editor.view.dom.querySelector(".rt-math-inline");
|
|
91
|
+
await waitFor(() => chip!.classList.contains("rt-math-error"));
|
|
92
|
+
// raw source stays visible; the parse error rides the title
|
|
93
|
+
expect(chip!.textContent).toBe("\\nope{");
|
|
94
|
+
expect(chip!.getAttribute("title")).toMatch(/parse error/i);
|
|
95
|
+
editor.destroy();
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inline math node (`$x^2$`) rendered with lazy-loaded KaTeX — GitLab
|
|
3
|
+
* parity for dollar math in the rich text editor.
|
|
4
|
+
*
|
|
5
|
+
* - Typing `$latex$` converts to a rendered formula (input rule; prices
|
|
6
|
+
* like "$5 and $10" never convert — content cannot start/end with
|
|
7
|
+
* whitespace).
|
|
8
|
+
* - The node is an inline atom carrying the raw TeX in `data-latex`; the
|
|
9
|
+
* MathBubble edits it in place (updateInlineMath command).
|
|
10
|
+
* - Invalid TeX shows the raw source in an error style with the KaTeX
|
|
11
|
+
* parse message (Axiom 6: show the error, never blank).
|
|
12
|
+
* - Block math (`$$ ... $$`) is a code block with language "math" — see
|
|
13
|
+
* codeBlockPreview.ts — matching GitLab's ```math fence model.
|
|
14
|
+
*
|
|
15
|
+
* Serialization: `<span data-type="inline-math" data-latex="...">` <->
|
|
16
|
+
* `$...$` (editor/markdown.ts), so the source toggle is lossless.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { Node, mergeAttributes, nodeInputRule } from "@tiptap/core";
|
|
20
|
+
import { NodeSelection } from "@tiptap/pm/state";
|
|
21
|
+
import { t } from "../shared/t";
|
|
22
|
+
import { mountKatexHtml, renderKatexHtml } from "./lazyKatex";
|
|
23
|
+
|
|
24
|
+
export interface InlineMathOptions {
|
|
25
|
+
HTMLAttributes: Record<string, any>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
declare module "@tiptap/core" {
|
|
29
|
+
interface Commands<ReturnType> {
|
|
30
|
+
inlineMath: {
|
|
31
|
+
/** Inserts an inline math node and selects it (opens the MathBubble) */
|
|
32
|
+
insertInlineMath: (attributes?: { latex?: string }) => ReturnType;
|
|
33
|
+
/** Updates the latex of the selected inline math node */
|
|
34
|
+
updateInlineMath: (attributes: { latex: string }) => ReturnType;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Matches `$...$` as the last thing typed. match[1] is the dollar span
|
|
41
|
+
* (what the node replaces), match[2] the TeX. The character before the
|
|
42
|
+
* opening `$` must not be another `$` (that is block math), and content
|
|
43
|
+
* cannot start/end with whitespace or contain `$`/newlines.
|
|
44
|
+
*/
|
|
45
|
+
export const INLINE_MATH_INPUT_RULE = /(?:^|[^$])(\$([^$\n \t](?:[^$\n]*[^$\n \t])?)\$)$/;
|
|
46
|
+
|
|
47
|
+
export const InlineMath = Node.create<InlineMathOptions>({
|
|
48
|
+
name: "inlineMath",
|
|
49
|
+
inline: true,
|
|
50
|
+
group: "inline",
|
|
51
|
+
atom: true,
|
|
52
|
+
marks: "",
|
|
53
|
+
|
|
54
|
+
addOptions() {
|
|
55
|
+
return { HTMLAttributes: {} };
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
addAttributes() {
|
|
59
|
+
return {
|
|
60
|
+
latex: {
|
|
61
|
+
default: "",
|
|
62
|
+
parseHTML: (element) => element.getAttribute("data-latex") ?? element.textContent ?? "",
|
|
63
|
+
renderHTML: (attributes) => ({ "data-latex": attributes.latex }),
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
parseHTML() {
|
|
69
|
+
return [{ tag: 'span[data-type="inline-math"]' }];
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
renderHTML({ node, HTMLAttributes }) {
|
|
73
|
+
// raw TeX as text content: non-web surfaces (native preview, plain
|
|
74
|
+
// parsers) degrade to readable source
|
|
75
|
+
return [
|
|
76
|
+
"span",
|
|
77
|
+
mergeAttributes({ "data-type": "inline-math" }, this.options.HTMLAttributes, HTMLAttributes),
|
|
78
|
+
node.attrs.latex,
|
|
79
|
+
];
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
addCommands() {
|
|
83
|
+
return {
|
|
84
|
+
insertInlineMath:
|
|
85
|
+
(attributes) =>
|
|
86
|
+
({ tr, dispatch }) => {
|
|
87
|
+
if (dispatch) {
|
|
88
|
+
const pos = tr.selection.from;
|
|
89
|
+
tr.replaceSelectionWith(this.type.create({ latex: attributes?.latex ?? "" }), false);
|
|
90
|
+
tr.setSelection(NodeSelection.create(tr.doc, pos));
|
|
91
|
+
tr.scrollIntoView();
|
|
92
|
+
}
|
|
93
|
+
return true;
|
|
94
|
+
},
|
|
95
|
+
updateInlineMath:
|
|
96
|
+
(attributes) =>
|
|
97
|
+
({ tr, dispatch }) => {
|
|
98
|
+
const { selection } = tr;
|
|
99
|
+
if (!(selection instanceof NodeSelection) || selection.node.type !== this.type) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
if (dispatch) {
|
|
103
|
+
const pos = selection.from;
|
|
104
|
+
tr.setNodeMarkup(pos, undefined, { latex: attributes.latex });
|
|
105
|
+
tr.setSelection(NodeSelection.create(tr.doc, pos));
|
|
106
|
+
}
|
|
107
|
+
return true;
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
addInputRules() {
|
|
113
|
+
return [
|
|
114
|
+
nodeInputRule({
|
|
115
|
+
find: INLINE_MATH_INPUT_RULE,
|
|
116
|
+
type: this.type,
|
|
117
|
+
getAttributes: (match) => ({ latex: match[2] ?? "" }),
|
|
118
|
+
}),
|
|
119
|
+
];
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
addNodeView() {
|
|
123
|
+
return ({ node }) => {
|
|
124
|
+
const dom = document.createElement("span");
|
|
125
|
+
dom.className = "rt-math-inline";
|
|
126
|
+
dom.setAttribute("data-type", "inline-math");
|
|
127
|
+
dom.contentEditable = "false";
|
|
128
|
+
|
|
129
|
+
let currentLatex: string | null = null;
|
|
130
|
+
let destroyed = false;
|
|
131
|
+
|
|
132
|
+
const render = (latex: string) => {
|
|
133
|
+
if (destroyed || latex === currentLatex) return;
|
|
134
|
+
currentLatex = latex;
|
|
135
|
+
dom.setAttribute("data-latex", latex);
|
|
136
|
+
if (!latex.trim()) {
|
|
137
|
+
dom.classList.add("rt-math-empty");
|
|
138
|
+
dom.classList.remove("rt-math-error");
|
|
139
|
+
dom.removeAttribute("title");
|
|
140
|
+
dom.textContent = t("math");
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
dom.classList.remove("rt-math-empty");
|
|
144
|
+
// raw TeX stays visible while KaTeX loads (first render only)
|
|
145
|
+
dom.textContent = latex;
|
|
146
|
+
void renderKatexHtml(latex).then((result) => {
|
|
147
|
+
if (destroyed || currentLatex !== latex) return;
|
|
148
|
+
if (result.ok) {
|
|
149
|
+
mountKatexHtml(dom, result.html);
|
|
150
|
+
dom.classList.remove("rt-math-error");
|
|
151
|
+
dom.removeAttribute("title");
|
|
152
|
+
dom.setAttribute("role", "img");
|
|
153
|
+
dom.setAttribute("aria-label", latex);
|
|
154
|
+
} else {
|
|
155
|
+
// Axiom 6: raw source + parse message, never a blank chip
|
|
156
|
+
dom.textContent = latex;
|
|
157
|
+
dom.classList.add("rt-math-error");
|
|
158
|
+
dom.title = result.error;
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
render((node.attrs.latex as string) ?? "");
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
dom,
|
|
167
|
+
update: (updatedNode) => {
|
|
168
|
+
if (updatedNode.type.name !== "inlineMath") return false;
|
|
169
|
+
render((updatedNode.attrs.latex as string) ?? "");
|
|
170
|
+
return true;
|
|
171
|
+
},
|
|
172
|
+
ignoreMutation: () => true,
|
|
173
|
+
destroy: () => {
|
|
174
|
+
destroyed = true;
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
},
|
|
179
|
+
});
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media paste/drop upload behavior against a headless Tiptap editor.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, it, vi } from "vitest";
|
|
6
|
+
import { Editor } from "@tiptap/core";
|
|
7
|
+
import StarterKit from "@tiptap/starter-kit";
|
|
8
|
+
import Image from "@tiptap/extension-image";
|
|
9
|
+
import { MediaUpload } from "./media";
|
|
10
|
+
|
|
11
|
+
function createEditor() {
|
|
12
|
+
return new Editor({
|
|
13
|
+
element: document.createElement("div"),
|
|
14
|
+
extensions: [StarterKit, Image.configure({ inline: true, allowBase64: true }), MediaUpload],
|
|
15
|
+
content: "<p>start</p>",
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function firePaste(editor: Editor, files: File[]) {
|
|
20
|
+
const event = {
|
|
21
|
+
clipboardData: { files, types: ["Files"], getData: () => "" },
|
|
22
|
+
preventDefault: vi.fn(),
|
|
23
|
+
} as unknown as ClipboardEvent;
|
|
24
|
+
// someProp mirrors ProseMirror dispatch: handlers run in order until one
|
|
25
|
+
// claims the event by returning truthy
|
|
26
|
+
const handled = !!editor.view.someProp("handlePaste", (fn) =>
|
|
27
|
+
fn(editor.view, event, editor.state.tr.doc.slice(0)),
|
|
28
|
+
);
|
|
29
|
+
return { handled, event };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function waitFor(check: () => boolean, timeoutMs = 2000) {
|
|
33
|
+
const start = Date.now();
|
|
34
|
+
while (!check()) {
|
|
35
|
+
if (Date.now() - start > timeoutMs) throw new Error("waitFor timed out");
|
|
36
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
describe("MediaUpload", () => {
|
|
41
|
+
it("inserts an uploaded image URL when onImageUpload is wired", async () => {
|
|
42
|
+
const editor = createEditor();
|
|
43
|
+
const onImageUpload = vi.fn(async () => "https://cdn.example.com/pic.png");
|
|
44
|
+
editor.storage.mediaUpload.handlers = { onImageUpload };
|
|
45
|
+
|
|
46
|
+
const file = new File(["binary"], "pic.png", { type: "image/png" });
|
|
47
|
+
const { handled, event } = firePaste(editor, [file]);
|
|
48
|
+
|
|
49
|
+
expect(handled).toBe(true);
|
|
50
|
+
expect(event.preventDefault).toHaveBeenCalled();
|
|
51
|
+
await waitFor(() => editor.getHTML().includes("<img"));
|
|
52
|
+
expect(onImageUpload).toHaveBeenCalledWith(file);
|
|
53
|
+
expect(editor.getHTML()).toContain('src="https://cdn.example.com/pic.png"');
|
|
54
|
+
expect(editor.getHTML()).toContain('alt="pic"');
|
|
55
|
+
editor.destroy();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("falls back to base64 data URI without an image handler", async () => {
|
|
59
|
+
const editor = createEditor();
|
|
60
|
+
const file = new File(["binary"], "photo.jpg", { type: "image/jpeg" });
|
|
61
|
+
const { handled } = firePaste(editor, [file]);
|
|
62
|
+
|
|
63
|
+
expect(handled).toBe(true);
|
|
64
|
+
await waitFor(() => editor.getHTML().includes("<img"));
|
|
65
|
+
expect(editor.getHTML()).toContain('src="data:');
|
|
66
|
+
editor.destroy();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("inserts an attachment chip link for non-image files via onFileUpload", async () => {
|
|
70
|
+
const editor = createEditor();
|
|
71
|
+
const onFileUpload = vi.fn(async () => ({
|
|
72
|
+
url: "https://cdn.example.com/report.pdf",
|
|
73
|
+
name: "report.pdf",
|
|
74
|
+
}));
|
|
75
|
+
editor.storage.mediaUpload.handlers = { onFileUpload };
|
|
76
|
+
|
|
77
|
+
const file = new File(["%PDF"], "report.pdf", { type: "application/pdf" });
|
|
78
|
+
const { handled } = firePaste(editor, [file]);
|
|
79
|
+
|
|
80
|
+
expect(handled).toBe(true);
|
|
81
|
+
await waitFor(() => editor.getHTML().includes("<a"));
|
|
82
|
+
const html = editor.getHTML();
|
|
83
|
+
expect(html).toContain('href="https://cdn.example.com/report.pdf"');
|
|
84
|
+
expect(html).toContain("rt-attachment");
|
|
85
|
+
expect(html).toContain("report.pdf");
|
|
86
|
+
editor.destroy();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("ignores non-image files without a file handler (honest no-op)", () => {
|
|
90
|
+
const editor = createEditor();
|
|
91
|
+
const file = new File(["%PDF"], "report.pdf", { type: "application/pdf" });
|
|
92
|
+
const { handled } = firePaste(editor, [file]);
|
|
93
|
+
expect(handled).toBe(false);
|
|
94
|
+
expect(editor.getHTML()).not.toContain("<a");
|
|
95
|
+
editor.destroy();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("does not intercept text-only pastes", () => {
|
|
99
|
+
const editor = createEditor();
|
|
100
|
+
const { handled } = firePaste(editor, []);
|
|
101
|
+
expect(handled).toBeFalsy();
|
|
102
|
+
editor.destroy();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("survives a failing upload without crashing", async () => {
|
|
106
|
+
const editor = createEditor();
|
|
107
|
+
const onImageUpload = vi.fn(async () => {
|
|
108
|
+
throw new Error("upload broke");
|
|
109
|
+
});
|
|
110
|
+
editor.storage.mediaUpload.handlers = { onImageUpload };
|
|
111
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
112
|
+
|
|
113
|
+
const file = new File(["binary"], "pic.png", { type: "image/png" });
|
|
114
|
+
const { handled } = firePaste(editor, [file]);
|
|
115
|
+
expect(handled).toBe(true);
|
|
116
|
+
await waitFor(() => warn.mock.calls.length > 0);
|
|
117
|
+
expect(editor.getHTML()).not.toContain("<img");
|
|
118
|
+
warn.mockRestore();
|
|
119
|
+
editor.destroy();
|
|
120
|
+
});
|
|
121
|
+
});
|