neuphlo-editor 2.5.1 → 2.6.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.
Files changed (128) hide show
  1. package/dist/Markdown-BAW0cb-U.d.cts +153 -0
  2. package/dist/Markdown-BAW0cb-U.d.ts +153 -0
  3. package/dist/chunk-ABL4D65Z.js +1454 -0
  4. package/dist/chunk-ABL4D65Z.js.map +1 -0
  5. package/dist/chunk-Z2DR2FK3.js +1582 -0
  6. package/dist/chunk-Z2DR2FK3.js.map +1 -0
  7. package/dist/headless/components/editor-bubble-item.d.ts +9 -0
  8. package/dist/headless/components/editor-bubble-item.js +29 -0
  9. package/dist/headless/components/editor-bubble.d.ts +9 -0
  10. package/dist/headless/components/editor-bubble.js +9 -0
  11. package/dist/headless/components/editor-command-item.d.ts +3 -0
  12. package/dist/headless/components/editor-command-item.js +27 -0
  13. package/dist/headless/components/editor-command.d.ts +3 -0
  14. package/dist/headless/components/editor-command.js +136 -0
  15. package/dist/headless/components/editor.d.ts +24 -0
  16. package/dist/headless/components/editor.js +13 -0
  17. package/dist/headless/extensions/AISuggestion/AISuggestion.d.ts +82 -0
  18. package/dist/headless/extensions/AISuggestion/AISuggestion.js +244 -0
  19. package/dist/headless/extensions/AISuggestion/index.d.ts +2 -0
  20. package/dist/headless/extensions/AISuggestion/index.js +1 -0
  21. package/dist/headless/extensions/CodeBlock/CodeBlock.d.ts +1 -0
  22. package/dist/headless/extensions/CodeBlock/CodeBlock.js +6 -0
  23. package/dist/headless/extensions/CodeBlock/index.d.ts +1 -0
  24. package/dist/headless/extensions/CodeBlock/index.js +1 -0
  25. package/dist/headless/extensions/DragHandle/DragHandle.d.ts +9 -0
  26. package/dist/headless/extensions/DragHandle/DragHandle.js +47 -0
  27. package/dist/headless/extensions/DragHandle/index.d.ts +2 -0
  28. package/dist/headless/extensions/DragHandle/index.js +1 -0
  29. package/dist/headless/extensions/Image/Image.d.ts +6 -0
  30. package/dist/headless/extensions/Image/Image.js +197 -0
  31. package/dist/headless/extensions/ImageBlock/ImageBlock.d.ts +23 -0
  32. package/dist/headless/extensions/ImageBlock/ImageBlock.js +242 -0
  33. package/dist/headless/extensions/ImageBlock/index.d.ts +2 -0
  34. package/dist/headless/extensions/ImageBlock/index.js +1 -0
  35. package/dist/headless/extensions/Link/Link.d.ts +2 -0
  36. package/dist/headless/extensions/Link/Link.js +44 -0
  37. package/dist/headless/extensions/Link/index.d.ts +1 -0
  38. package/dist/headless/extensions/Link/index.js +1 -0
  39. package/dist/headless/extensions/Markdown.d.ts +9 -0
  40. package/dist/headless/extensions/Markdown.js +138 -0
  41. package/dist/headless/extensions/MarkdownPaste.d.ts +3 -0
  42. package/dist/headless/extensions/MarkdownPaste.js +168 -0
  43. package/dist/headless/extensions/Mention/index.d.ts +4 -0
  44. package/dist/headless/extensions/Mention/index.js +3 -0
  45. package/dist/headless/extensions/Mention/mention-command.d.ts +8 -0
  46. package/dist/headless/extensions/Mention/mention-command.js +165 -0
  47. package/dist/headless/extensions/Mention/mention-list.d.ts +8 -0
  48. package/dist/headless/extensions/Mention/mention-list.js +150 -0
  49. package/dist/headless/extensions/Mention/mention-node-view.d.ts +2 -0
  50. package/dist/headless/extensions/Mention/mention-node-view.js +92 -0
  51. package/dist/headless/extensions/Mention/mention.d.ts +46 -0
  52. package/dist/headless/extensions/Mention/mention.js +218 -0
  53. package/dist/headless/extensions/Table/index.d.ts +2 -0
  54. package/dist/headless/extensions/Table/index.js +2 -0
  55. package/dist/headless/extensions/VideoBlock/VideoBlock.d.ts +17 -0
  56. package/dist/headless/extensions/VideoBlock/VideoBlock.js +73 -0
  57. package/dist/headless/extensions/VideoBlock/index.d.ts +2 -0
  58. package/dist/headless/extensions/VideoBlock/index.js +1 -0
  59. package/dist/headless/extensions/extension-kit.d.ts +29 -0
  60. package/dist/headless/extensions/extension-kit.js +132 -0
  61. package/dist/headless/extensions/index.d.ts +16 -0
  62. package/dist/headless/extensions/index.js +12 -0
  63. package/dist/headless/extensions/slash-command.d.ts +24 -0
  64. package/dist/headless/extensions/slash-command.js +99 -0
  65. package/dist/headless/index.cjs +143 -2
  66. package/dist/headless/index.cjs.map +1 -1
  67. package/dist/headless/index.d.cts +3 -2
  68. package/dist/headless/index.d.ts +3 -2
  69. package/dist/headless/index.js +5 -1
  70. package/dist/headless/utils/atoms.d.ts +13 -0
  71. package/dist/headless/utils/atoms.js +5 -0
  72. package/dist/headless/utils/store.d.ts +2 -0
  73. package/dist/headless/utils/store.js +4 -0
  74. package/dist/highlight.css +0 -6
  75. package/dist/react/Editor.d.ts +43 -0
  76. package/dist/react/Editor.js +119 -0
  77. package/dist/react/TableOfContents.d.ts +7 -0
  78. package/dist/react/TableOfContents.js +96 -0
  79. package/dist/react/index.cjs +156 -9
  80. package/dist/react/index.cjs.map +1 -1
  81. package/dist/react/index.d.cts +5 -2
  82. package/dist/react/index.d.ts +5 -2
  83. package/dist/react/index.js +14 -4
  84. package/dist/react/index.js.map +1 -1
  85. package/dist/react/menus/DragHandle/BlockActionMenu.d.ts +7 -0
  86. package/dist/react/menus/DragHandle/BlockActionMenu.js +111 -0
  87. package/dist/react/menus/DragHandle/DragHandleMenu.d.ts +7 -0
  88. package/dist/react/menus/DragHandle/DragHandleMenu.js +97 -0
  89. package/dist/react/menus/ImageBlock/ImageBlockLoading.d.ts +2 -0
  90. package/dist/react/menus/ImageBlock/ImageBlockLoading.js +6 -0
  91. package/dist/react/menus/ImageBlock/ImageBlockMenu.d.ts +8 -0
  92. package/dist/react/menus/ImageBlock/ImageBlockMenu.js +75 -0
  93. package/dist/react/menus/ImageBlock/ImageBlockView.d.ts +10 -0
  94. package/dist/react/menus/ImageBlock/ImageBlockView.js +60 -0
  95. package/dist/react/menus/ImageBlock/ImageBlockWidth.d.ts +6 -0
  96. package/dist/react/menus/ImageBlock/ImageBlockWidth.js +29 -0
  97. package/dist/react/menus/ImageBlock/ImageResizeHandle.d.ts +8 -0
  98. package/dist/react/menus/ImageBlock/ImageResizeHandle.js +37 -0
  99. package/dist/react/menus/ImageBlock/ImageUploader.d.ts +7 -0
  100. package/dist/react/menus/ImageBlock/ImageUploader.js +73 -0
  101. package/dist/react/menus/ImageBlock/index.d.ts +4 -0
  102. package/dist/react/menus/ImageBlock/index.js +4 -0
  103. package/dist/react/menus/ImageMenu.d.ts +10 -0
  104. package/dist/react/menus/ImageMenu.js +77 -0
  105. package/dist/react/menus/ImagePopover.d.ts +8 -0
  106. package/dist/react/menus/ImagePopover.js +146 -0
  107. package/dist/react/menus/LinkMenu.d.ts +2 -0
  108. package/dist/react/menus/LinkMenu.js +69 -0
  109. package/dist/react/menus/MenuList.d.ts +10 -0
  110. package/dist/react/menus/MenuList.js +94 -0
  111. package/dist/react/menus/SlashMenu.d.ts +5 -0
  112. package/dist/react/menus/SlashMenu.js +156 -0
  113. package/dist/react/menus/TableMenu.d.ts +5 -0
  114. package/dist/react/menus/TableMenu.js +462 -0
  115. package/dist/react/menus/TaskItem/TaskItemView.d.ts +12 -0
  116. package/dist/react/menus/TaskItem/TaskItemView.js +11 -0
  117. package/dist/react/menus/TextMenu.d.ts +10 -0
  118. package/dist/react/menus/TextMenu.js +139 -0
  119. package/dist/react/menus/VideoBlock/VideoBlockMenu.d.ts +7 -0
  120. package/dist/react/menus/VideoBlock/VideoBlockMenu.js +72 -0
  121. package/dist/react/menus/VideoBlock/VideoBlockView.d.ts +10 -0
  122. package/dist/react/menus/VideoBlock/VideoBlockView.js +87 -0
  123. package/dist/react/menus/VideoBlock/index.d.ts +2 -0
  124. package/dist/react/menus/VideoBlock/index.js +2 -0
  125. package/dist/react/menus/index.d.ts +15 -0
  126. package/dist/react/menus/index.js +9 -0
  127. package/package.json +13 -9
  128. package/LICENSE +0 -21
@@ -0,0 +1,242 @@
1
+ import { mergeAttributes } from "@tiptap/core";
2
+ import { Image as TiptapImage } from "@tiptap/extension-image";
3
+ import { ReactNodeViewRenderer } from "@tiptap/react";
4
+ import { Plugin, PluginKey } from "@tiptap/pm/state";
5
+ export const ImageBlock = TiptapImage.extend({
6
+ name: "imageBlock",
7
+ group: "block",
8
+ defining: true,
9
+ isolating: true,
10
+ addOptions() {
11
+ return {
12
+ ...this.parent?.(),
13
+ inline: false,
14
+ };
15
+ },
16
+ addAttributes() {
17
+ return {
18
+ src: {
19
+ default: "",
20
+ parseHTML: (element) => element.getAttribute("src"),
21
+ renderHTML: (attributes) => ({
22
+ src: attributes.src,
23
+ }),
24
+ },
25
+ width: {
26
+ default: "100%",
27
+ parseHTML: (element) => element.getAttribute("data-width"),
28
+ renderHTML: (attributes) => ({
29
+ "data-width": attributes.width,
30
+ }),
31
+ },
32
+ align: {
33
+ default: "center",
34
+ parseHTML: (element) => element.getAttribute("data-align"),
35
+ renderHTML: (attributes) => ({
36
+ "data-align": attributes.align,
37
+ }),
38
+ },
39
+ alt: {
40
+ default: undefined,
41
+ parseHTML: (element) => element.getAttribute("alt"),
42
+ renderHTML: (attributes) => ({
43
+ alt: attributes.alt,
44
+ }),
45
+ },
46
+ loading: {
47
+ default: false,
48
+ parseHTML: () => false,
49
+ renderHTML: () => ({}),
50
+ },
51
+ };
52
+ },
53
+ parseHTML() {
54
+ return [
55
+ {
56
+ tag: 'img[src]:not([src^="data:"])',
57
+ getAttrs: (element) => {
58
+ const el = element;
59
+ return {
60
+ src: el.getAttribute("src"),
61
+ alt: el.getAttribute("alt"),
62
+ width: el.getAttribute("data-width") || "100%",
63
+ align: el.getAttribute("data-align") || "center",
64
+ };
65
+ },
66
+ },
67
+ ];
68
+ },
69
+ renderHTML({ HTMLAttributes }) {
70
+ return ["img", mergeAttributes(HTMLAttributes)];
71
+ },
72
+ addCommands() {
73
+ return {
74
+ setImageBlock: (attrs) => ({ commands }) => {
75
+ return commands.insertContent({
76
+ type: "imageBlock",
77
+ attrs: { src: attrs.src },
78
+ });
79
+ },
80
+ setImageBlockAt: (attrs) => ({ commands }) => {
81
+ return commands.insertContentAt(attrs.pos, {
82
+ type: "imageBlock",
83
+ attrs: { src: attrs.src },
84
+ });
85
+ },
86
+ setImageBlockAlign: (align) => ({ commands }) => commands.updateAttributes("imageBlock", { align }),
87
+ setImageBlockWidth: (width) => ({ commands }) => commands.updateAttributes("imageBlock", {
88
+ width: `${Math.max(0, Math.min(100, width))}%`,
89
+ }),
90
+ };
91
+ },
92
+ addNodeView() {
93
+ if (this.options.nodeView) {
94
+ return ReactNodeViewRenderer(this.options.nodeView);
95
+ }
96
+ // If no custom node view is provided, we fall back to default behavior
97
+ // but avoid the circular dependency require()
98
+ return null;
99
+ },
100
+ addProseMirrorPlugins() {
101
+ return [
102
+ new Plugin({
103
+ key: new PluginKey("imageBlockDrop"),
104
+ props: {
105
+ handleDOMEvents: {
106
+ drop: (view, event) => {
107
+ const hasFiles = event.dataTransfer &&
108
+ event.dataTransfer.files &&
109
+ event.dataTransfer.files.length;
110
+ if (!hasFiles) {
111
+ return false;
112
+ }
113
+ const images = Array.from(event.dataTransfer.files).filter((file) => /image/i.test(file.type));
114
+ if (images.length === 0) {
115
+ return false;
116
+ }
117
+ event.preventDefault();
118
+ const { schema } = view.state;
119
+ const coordinates = view.posAtCoords({
120
+ left: event.clientX,
121
+ top: event.clientY,
122
+ });
123
+ if (!coordinates)
124
+ return false;
125
+ images.forEach(async (image) => {
126
+ if (this.options.uploadImage) {
127
+ try {
128
+ // Insert placeholder first
129
+ const placeholderNode = schema.nodes.imageBlock.create({
130
+ src: "",
131
+ loading: true,
132
+ });
133
+ const placeholderTr = view.state.tr.insert(coordinates.pos, placeholderNode);
134
+ view.dispatch(placeholderTr);
135
+ // Upload and replace
136
+ const url = await this.options.uploadImage(image);
137
+ const node = schema.nodes.imageBlock.create({ src: url });
138
+ // Find and replace the placeholder
139
+ const currentState = view.state;
140
+ let foundPos = -1;
141
+ currentState.doc.descendants((node, pos) => {
142
+ if (node.type.name === "imageBlock" &&
143
+ node.attrs.loading) {
144
+ foundPos = pos;
145
+ return false;
146
+ }
147
+ });
148
+ if (foundPos !== -1) {
149
+ const transaction = view.state.tr.replaceWith(foundPos, foundPos + 1, node);
150
+ view.dispatch(transaction);
151
+ }
152
+ }
153
+ catch (error) {
154
+ console.error("Failed to upload image:", error);
155
+ // Remove placeholder on error
156
+ const currentState = view.state;
157
+ let foundPos = -1;
158
+ currentState.doc.descendants((node, pos) => {
159
+ if (node.type.name === "imageBlock" &&
160
+ node.attrs.loading) {
161
+ foundPos = pos;
162
+ return false;
163
+ }
164
+ });
165
+ if (foundPos !== -1) {
166
+ const transaction = view.state.tr.delete(foundPos, foundPos + 1);
167
+ view.dispatch(transaction);
168
+ }
169
+ }
170
+ }
171
+ });
172
+ return true;
173
+ },
174
+ paste: (view, event) => {
175
+ const hasFiles = event.clipboardData &&
176
+ event.clipboardData.files &&
177
+ event.clipboardData.files.length;
178
+ if (!hasFiles) {
179
+ return false;
180
+ }
181
+ const images = Array.from(event.clipboardData.files).filter((file) => /image/i.test(file.type));
182
+ if (images.length === 0) {
183
+ return false;
184
+ }
185
+ event.preventDefault();
186
+ images.forEach(async (image) => {
187
+ if (this.options.uploadImage) {
188
+ try {
189
+ // Insert placeholder first
190
+ const placeholderNode = view.state.schema.nodes.imageBlock.create({
191
+ src: "",
192
+ loading: true,
193
+ });
194
+ view.dispatch(view.state.tr.replaceSelectionWith(placeholderNode));
195
+ // Upload and replace
196
+ const url = await this.options.uploadImage(image);
197
+ const node = view.state.schema.nodes.imageBlock.create({
198
+ src: url,
199
+ });
200
+ // Find and replace the placeholder
201
+ const currentState = view.state;
202
+ let foundPos = -1;
203
+ currentState.doc.descendants((node, pos) => {
204
+ if (node.type.name === "imageBlock" &&
205
+ node.attrs.loading) {
206
+ foundPos = pos;
207
+ return false;
208
+ }
209
+ });
210
+ if (foundPos !== -1) {
211
+ const transaction = view.state.tr.replaceWith(foundPos, foundPos + 1, node);
212
+ view.dispatch(transaction);
213
+ }
214
+ }
215
+ catch (error) {
216
+ console.error("Failed to upload image:", error);
217
+ // Remove placeholder on error
218
+ const currentState = view.state;
219
+ let foundPos = -1;
220
+ currentState.doc.descendants((node, pos) => {
221
+ if (node.type.name === "imageBlock" &&
222
+ node.attrs.loading) {
223
+ foundPos = pos;
224
+ return false;
225
+ }
226
+ });
227
+ if (foundPos !== -1) {
228
+ const transaction = view.state.tr.delete(foundPos, foundPos + 1);
229
+ view.dispatch(transaction);
230
+ }
231
+ }
232
+ }
233
+ });
234
+ return true;
235
+ },
236
+ },
237
+ },
238
+ }),
239
+ ];
240
+ },
241
+ });
242
+ export default ImageBlock;
@@ -0,0 +1,2 @@
1
+ export { ImageBlock } from "./ImageBlock";
2
+ export type { ImageBlockOptions } from "./ImageBlock";
@@ -0,0 +1 @@
1
+ export { ImageBlock } from "./ImageBlock";
@@ -0,0 +1,2 @@
1
+ export declare const Link: import("@tiptap/core").Mark<import("@tiptap/extension-link").LinkOptions, any>;
2
+ export default Link;
@@ -0,0 +1,44 @@
1
+ import { mergeAttributes } from "@tiptap/core";
2
+ import TiptapLink from "@tiptap/extension-link";
3
+ import { Plugin } from "@tiptap/pm/state";
4
+ export const Link = TiptapLink.extend({
5
+ inclusive: false,
6
+ parseHTML() {
7
+ return [
8
+ {
9
+ tag: 'a[href]:not([data-type="button"]):not([href *= "javascript:" i])',
10
+ },
11
+ ];
12
+ },
13
+ renderHTML({ HTMLAttributes }) {
14
+ return [
15
+ "a",
16
+ mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
17
+ class: "link",
18
+ }),
19
+ 0,
20
+ ];
21
+ },
22
+ addProseMirrorPlugins() {
23
+ const { editor } = this;
24
+ return [
25
+ ...(this.parent?.() || []),
26
+ new Plugin({
27
+ props: {
28
+ handleKeyDown: (view, event) => {
29
+ const { selection } = editor.state;
30
+ if (event.key === "Escape" && selection.empty !== true) {
31
+ editor.commands.focus(selection.to, { scrollIntoView: false });
32
+ }
33
+ return false;
34
+ },
35
+ },
36
+ }),
37
+ ];
38
+ },
39
+ }).configure({
40
+ openOnClick: false,
41
+ autolink: true,
42
+ enableClickSelection: true,
43
+ });
44
+ export default Link;
@@ -0,0 +1 @@
1
+ export * from "./Link";
@@ -0,0 +1 @@
1
+ export * from "./Link";
@@ -0,0 +1,9 @@
1
+ import { Extension } from "@tiptap/core";
2
+ import { MarkdownSerializer } from "@tiptap/pm/markdown";
3
+ export declare function markdownToHtml(input: string | null | undefined): string;
4
+ export interface MarkdownStorage {
5
+ serializer: MarkdownSerializer | null;
6
+ getMarkdown: () => string;
7
+ }
8
+ export declare const Markdown: Extension<any, MarkdownStorage>;
9
+ export default Markdown;
@@ -0,0 +1,138 @@
1
+ import { Extension } from "@tiptap/core";
2
+ import { MarkdownSerializer } from "@tiptap/pm/markdown";
3
+ import { DOMSerializer } from "@tiptap/pm/model";
4
+ import MarkdownIt from "markdown-it";
5
+ const markdownIt = new MarkdownIt({ html: true, linkify: false, breaks: false });
6
+ export function markdownToHtml(input) {
7
+ if (!input)
8
+ return "";
9
+ return markdownIt.render(input);
10
+ }
11
+ function nodeToHtml(node, schema) {
12
+ if (typeof document === "undefined")
13
+ return "";
14
+ const dom = DOMSerializer.fromSchema(schema).serializeNode(node);
15
+ const container = document.createElement("div");
16
+ container.appendChild(dom);
17
+ return container.innerHTML;
18
+ }
19
+ const specificNodes = {
20
+ text: (state, node) => {
21
+ state.text(node.text ?? "");
22
+ },
23
+ paragraph: (state, node) => {
24
+ state.renderInline(node);
25
+ state.closeBlock(node);
26
+ },
27
+ heading: (state, node) => {
28
+ state.write(`${state.repeat("#", node.attrs.level)} `);
29
+ state.renderInline(node);
30
+ state.closeBlock(node);
31
+ },
32
+ blockquote: (state, node) => {
33
+ state.wrapBlock("> ", null, node, () => state.renderContent(node));
34
+ },
35
+ horizontalRule: (state, node) => {
36
+ state.write(node.attrs.markup || "---");
37
+ state.closeBlock(node);
38
+ },
39
+ bulletList: (state, node) => {
40
+ state.renderList(node, " ", () => `${node.attrs.bullet || "-"} `);
41
+ },
42
+ orderedList: (state, node) => {
43
+ const start = node.attrs.start || 1;
44
+ const maxWidth = String(start + node.childCount - 1).length;
45
+ const space = state.repeat(" ", maxWidth + 2);
46
+ state.renderList(node, space, (index) => {
47
+ const label = String(start + index);
48
+ return `${state.repeat(" ", maxWidth - label.length)}${label}. `;
49
+ });
50
+ },
51
+ listItem: (state, node) => {
52
+ state.renderContent(node);
53
+ },
54
+ codeBlock: (state, node) => {
55
+ state.write(`\`\`\`${node.attrs.language || ""}\n`);
56
+ state.text(node.textContent, false);
57
+ state.ensureNewLine();
58
+ state.write("```");
59
+ state.closeBlock(node);
60
+ },
61
+ hardBreak: (state, node, parent, index) => {
62
+ for (let i = index + 1; i < parent.childCount; i += 1) {
63
+ if (parent.child(i).type !== node.type) {
64
+ state.write("\\\n");
65
+ return;
66
+ }
67
+ }
68
+ },
69
+ };
70
+ const inlineHtmlNodes = new Set(["mention", "reference"]);
71
+ const specificMarks = {
72
+ bold: { open: "**", close: "**", mixable: true, expelEnclosingWhitespace: true },
73
+ italic: { open: "*", close: "*", mixable: true, expelEnclosingWhitespace: true },
74
+ strike: { open: "~~", close: "~~", mixable: true, expelEnclosingWhitespace: true },
75
+ underline: { open: "<u>", close: "</u>", mixable: true },
76
+ code: { open: "`", close: "`", escape: false },
77
+ link: {
78
+ open: "[",
79
+ close: (_state, mark) => {
80
+ const href = mark.attrs.href || "";
81
+ const title = mark.attrs.title ? ` "${mark.attrs.title}"` : "";
82
+ return `](${href}${title})`;
83
+ },
84
+ },
85
+ };
86
+ function buildSerializer(schema) {
87
+ const nodes = {};
88
+ for (const name of Object.keys(schema.nodes)) {
89
+ if (specificNodes[name]) {
90
+ nodes[name] = specificNodes[name];
91
+ continue;
92
+ }
93
+ const type = schema.nodes[name];
94
+ if (type.isText) {
95
+ nodes[name] = specificNodes.text;
96
+ continue;
97
+ }
98
+ if (type.isInline || inlineHtmlNodes.has(name)) {
99
+ nodes[name] = (state, node) => {
100
+ state.text(nodeToHtml(node, schema), false);
101
+ };
102
+ continue;
103
+ }
104
+ nodes[name] = (state, node) => {
105
+ state.write(nodeToHtml(node, schema));
106
+ state.closeBlock(node);
107
+ };
108
+ }
109
+ const marks = {};
110
+ for (const name of Object.keys(schema.marks)) {
111
+ marks[name] = specificMarks[name] || { open: "", close: "", mixable: true };
112
+ }
113
+ return new MarkdownSerializer(nodes, marks);
114
+ }
115
+ export const Markdown = Extension.create({
116
+ name: "markdown",
117
+ addStorage() {
118
+ return {
119
+ serializer: null,
120
+ getMarkdown: () => "",
121
+ };
122
+ },
123
+ onBeforeCreate() {
124
+ const storage = this.storage;
125
+ storage.serializer = buildSerializer(this.editor.schema);
126
+ storage.getMarkdown = () => {
127
+ try {
128
+ return storage.serializer.serialize(this.editor.state.doc, {
129
+ tightLists: true,
130
+ });
131
+ }
132
+ catch {
133
+ return this.editor.getHTML();
134
+ }
135
+ };
136
+ },
137
+ });
138
+ export default Markdown;
@@ -0,0 +1,3 @@
1
+ import { Extension } from "@tiptap/core";
2
+ export declare const MarkdownPaste: Extension<any, any>;
3
+ export default MarkdownPaste;
@@ -0,0 +1,168 @@
1
+ import { Extension } from "@tiptap/core";
2
+ import { Plugin, PluginKey } from "@tiptap/pm/state";
3
+ import { MarkdownParser, defaultMarkdownParser } from "@tiptap/pm/markdown";
4
+ const markdownPastePluginKey = new PluginKey("markdownPaste");
5
+ /**
6
+ * Heuristic: does the plain text look like it contains markdown syntax?
7
+ */
8
+ function looksLikeMarkdown(text) {
9
+ const patterns = [
10
+ /^#{1,6}\s/m, // headings
11
+ /^\s*[-*+]\s/m, // unordered list
12
+ /^\s*\d+\.\s/m, // ordered list
13
+ /^\s*>\s/m, // blockquote
14
+ /\|.+\|/m, // table
15
+ /^```/m, // fenced code block
16
+ /\*\*.+\*\*/, // bold
17
+ /\*.+\*/, // italic
18
+ /~~.+~~/, // strikethrough
19
+ /`[^`]+`/, // inline code
20
+ /^\s*---\s*$/m, // horizontal rule
21
+ /^\s*\*\*\*\s*$/m, // horizontal rule alt
22
+ /\[.+\]\(.+\)/, // links
23
+ /!\[.*\]\(.+\)/, // images
24
+ ];
25
+ // Need at least one markdown pattern match and it shouldn't be HTML
26
+ const hasMarkdown = patterns.some((p) => p.test(text));
27
+ const isHtml = /^<[a-z][\s\S]*>/i.test(text.trim());
28
+ return hasMarkdown && !isHtml;
29
+ }
30
+ /**
31
+ * Build a MarkdownParser configured for TipTap's schema node/mark names.
32
+ * Re-uses the markdown-it tokenizer from prosemirror-markdown's default parser.
33
+ */
34
+ function buildParser(schema) {
35
+ // Grab the markdown-it tokenizer instance from prosemirror-markdown's default parser
36
+ const md = defaultMarkdownParser.tokenizer;
37
+ const tokens = {};
38
+ // Block nodes
39
+ if (schema.nodes.paragraph)
40
+ tokens.paragraph = { block: "paragraph" };
41
+ if (schema.nodes.heading) {
42
+ tokens.heading = {
43
+ block: "heading",
44
+ getAttrs: (tok) => ({ level: Number(tok.tag.slice(1)) }),
45
+ };
46
+ }
47
+ if (schema.nodes.blockquote)
48
+ tokens.blockquote = { block: "blockquote" };
49
+ if (schema.nodes.bulletList)
50
+ tokens.bullet_list = { block: "bulletList" };
51
+ if (schema.nodes.orderedList) {
52
+ tokens.ordered_list = {
53
+ block: "orderedList",
54
+ getAttrs: (tok) => ({ start: Number(tok.attrGet("start") || 1) }),
55
+ };
56
+ }
57
+ if (schema.nodes.listItem)
58
+ tokens.list_item = { block: "listItem" };
59
+ if (schema.nodes.codeBlock) {
60
+ tokens.code_block = { block: "codeBlock", noCloseToken: true };
61
+ tokens.fence = {
62
+ block: "codeBlock",
63
+ getAttrs: (tok) => ({ language: tok.info || "" }),
64
+ noCloseToken: true,
65
+ };
66
+ }
67
+ if (schema.nodes.horizontalRule) {
68
+ tokens.hr = { node: "horizontalRule" };
69
+ }
70
+ if (schema.nodes.hardBreak) {
71
+ tokens.hardbreak = { node: "hardBreak" };
72
+ }
73
+ if (schema.nodes.image) {
74
+ tokens.image = {
75
+ node: "image",
76
+ getAttrs: (tok) => ({
77
+ src: tok.attrGet("src"),
78
+ title: tok.attrGet("title") || null,
79
+ alt: tok.children?.[0]?.content || null,
80
+ }),
81
+ };
82
+ }
83
+ // Table support — markdown-it needs the table plugin enabled
84
+ if (schema.nodes.table) {
85
+ tokens.table = { block: "table" };
86
+ tokens.thead = { ignore: true };
87
+ tokens.tbody = { ignore: true };
88
+ tokens.tr = { block: "tableRow" };
89
+ tokens.th = { block: "tableHeader" };
90
+ tokens.td = { block: "tableCell" };
91
+ }
92
+ // Marks
93
+ if (schema.marks.bold || schema.marks.strong) {
94
+ tokens.strong = { mark: schema.marks.bold ? "bold" : "strong" };
95
+ }
96
+ if (schema.marks.italic || schema.marks.em) {
97
+ tokens.em = { mark: schema.marks.italic ? "italic" : "em" };
98
+ }
99
+ if (schema.marks.code) {
100
+ tokens.code_inline = { mark: "code", noCloseToken: true };
101
+ }
102
+ if (schema.marks.link) {
103
+ tokens.link = {
104
+ mark: "link",
105
+ getAttrs: (tok) => ({
106
+ href: tok.attrGet("href"),
107
+ title: tok.attrGet("title") || null,
108
+ }),
109
+ };
110
+ }
111
+ if (schema.marks.strike || schema.marks.strikethrough) {
112
+ tokens.s = { mark: schema.marks.strike ? "strike" : "strikethrough" };
113
+ }
114
+ try {
115
+ return new MarkdownParser(schema, md, tokens);
116
+ }
117
+ catch {
118
+ return null;
119
+ }
120
+ }
121
+ export const MarkdownPaste = Extension.create({
122
+ name: "markdownPaste",
123
+ addProseMirrorPlugins() {
124
+ const schema = this.editor.schema;
125
+ let parser = null;
126
+ return [
127
+ new Plugin({
128
+ key: markdownPastePluginKey,
129
+ props: {
130
+ handlePaste(view, event) {
131
+ const clipboardData = event.clipboardData;
132
+ if (!clipboardData)
133
+ return false;
134
+ // If there's HTML content, let ProseMirror handle it normally
135
+ // (the browser/OS already converted rich content)
136
+ const html = clipboardData.getData("text/html");
137
+ if (html && html.trim().length > 0)
138
+ return false;
139
+ const text = clipboardData.getData("text/plain");
140
+ if (!text || !looksLikeMarkdown(text))
141
+ return false;
142
+ // Lazily build parser on first use
143
+ if (!parser) {
144
+ parser = buildParser(schema);
145
+ }
146
+ if (!parser)
147
+ return false;
148
+ try {
149
+ const doc = parser.parse(text);
150
+ if (!doc || doc.content.size === 0)
151
+ return false;
152
+ const { tr } = view.state;
153
+ const slice = doc.slice(0, doc.content.size);
154
+ tr.replaceSelection(slice);
155
+ view.dispatch(tr);
156
+ return true;
157
+ }
158
+ catch {
159
+ // Fall back to default paste behavior
160
+ return false;
161
+ }
162
+ },
163
+ },
164
+ }),
165
+ ];
166
+ },
167
+ });
168
+ export default MarkdownPaste;
@@ -0,0 +1,4 @@
1
+ export { createMentionExtension, renderMentionSuggestion } from "./mention";
2
+ export { MentionCommand } from "./mention-command";
3
+ export { MentionNodeView } from "./mention-node-view";
4
+ export type { MentionItem, MentionOptions } from "./mention";
@@ -0,0 +1,3 @@
1
+ export { createMentionExtension, renderMentionSuggestion } from "./mention";
2
+ export { MentionCommand } from "./mention-command";
3
+ export { MentionNodeView } from "./mention-node-view";
@@ -0,0 +1,8 @@
1
+ import type { MentionItem } from "./mention";
2
+ interface MentionCommandProps {
3
+ items: MentionItem[];
4
+ command: (item: MentionItem) => void;
5
+ query: string;
6
+ }
7
+ export declare const MentionCommand: import("react").ForwardRefExoticComponent<MentionCommandProps & import("react").RefAttributes<any>>;
8
+ export {};