@rimori/react-client 0.4.10 → 0.4.11-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -12,6 +12,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
|
12
12
|
import { useRimori } from '../../providers/PluginProvider';
|
|
13
13
|
import { Markdown } from 'tiptap-markdown';
|
|
14
14
|
import StarterKit from '@tiptap/starter-kit';
|
|
15
|
+
import { Paragraph } from '@tiptap/extension-paragraph';
|
|
15
16
|
import Table from '@tiptap/extension-table';
|
|
16
17
|
import TableCell from '@tiptap/extension-table-cell';
|
|
17
18
|
import TableHeader from '@tiptap/extension-table-header';
|
|
@@ -26,6 +27,28 @@ import { AiOutlineUnorderedList } from 'react-icons/ai';
|
|
|
26
27
|
import { LuClipboardPaste, LuHeading1, LuHeading2, LuHeading3, LuLink, LuUnlink } from 'react-icons/lu';
|
|
27
28
|
import { FaBold, FaCode, FaItalic, FaParagraph, FaStrikethrough } from 'react-icons/fa';
|
|
28
29
|
import { ImageUploadExtension, triggerImageUpload } from './ImageUploadExtension';
|
|
30
|
+
// Extends TipTap's Paragraph to serialize empty paragraphs as <p></p>.
|
|
31
|
+
// Standard markdown collapses consecutive blank lines, losing empty paragraph nodes.
|
|
32
|
+
// Since tiptap-markdown enables html:true by default, <p></p> survives the round-trip.
|
|
33
|
+
const ParagraphPreserveEmpty = Paragraph.extend({
|
|
34
|
+
addStorage() {
|
|
35
|
+
return {
|
|
36
|
+
markdown: {
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
38
|
+
serialize(state, node) {
|
|
39
|
+
if (node.childCount === 0) {
|
|
40
|
+
state.write('<p></p>');
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
state.renderInline(node);
|
|
44
|
+
}
|
|
45
|
+
state.closeBlock(node);
|
|
46
|
+
},
|
|
47
|
+
parse: {},
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
});
|
|
29
52
|
function getMarkdown(editor) {
|
|
30
53
|
return editor.storage.markdown.getMarkdown();
|
|
31
54
|
}
|
|
@@ -160,7 +183,8 @@ export const MarkdownEditor = ({ content, editable, className, onUpdate, labels,
|
|
|
160
183
|
return data.url;
|
|
161
184
|
}), [storage]);
|
|
162
185
|
const extensions = useMemo(() => [
|
|
163
|
-
StarterKit,
|
|
186
|
+
StarterKit.configure({ paragraph: false }),
|
|
187
|
+
ParagraphPreserveEmpty,
|
|
164
188
|
Table.configure({ resizable: false }),
|
|
165
189
|
TableRow,
|
|
166
190
|
TableHeader,
|
|
@@ -184,7 +208,8 @@ export const MarkdownEditor = ({ content, editable, className, onUpdate, labels,
|
|
|
184
208
|
onUpdate === null || onUpdate === void 0 ? void 0 : onUpdate(markdown);
|
|
185
209
|
},
|
|
186
210
|
});
|
|
187
|
-
// Sync external content changes (e.g. AI autofill) without triggering update loop
|
|
211
|
+
// Sync external content changes (e.g. AI autofill) without triggering update loop.
|
|
212
|
+
// Pass false to setContent to prevent onUpdate from firing and re-normalizing the content.
|
|
188
213
|
useEffect(() => {
|
|
189
214
|
if (!editor)
|
|
190
215
|
return;
|
|
@@ -192,7 +217,7 @@ export const MarkdownEditor = ({ content, editable, className, onUpdate, labels,
|
|
|
192
217
|
if (incoming === lastEmittedRef.current)
|
|
193
218
|
return;
|
|
194
219
|
lastEmittedRef.current = incoming;
|
|
195
|
-
editor.commands.setContent(incoming);
|
|
220
|
+
editor.commands.setContent(incoming, false);
|
|
196
221
|
}, [editor, content]);
|
|
197
222
|
// Sync editable prop
|
|
198
223
|
useEffect(() => {
|
package/dist/style.css
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rimori/react-client",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.11-next.1",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"format": "prettier --write ."
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
|
-
"@rimori/client": "
|
|
27
|
+
"@rimori/client": "2.5.19-next.5",
|
|
28
28
|
"react": "^18.1.0",
|
|
29
29
|
"react-dom": "^18.1.0"
|
|
30
30
|
},
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"@tiptap/core": "^2.26.1",
|
|
33
33
|
"@tiptap/extension-image": "^2.26.1",
|
|
34
34
|
"@tiptap/extension-link": "^2.26.1",
|
|
35
|
+
"@tiptap/extension-paragraph": "^2.26.1",
|
|
35
36
|
"@tiptap/extension-table": "^2.26.1",
|
|
36
37
|
"@tiptap/extension-table-cell": "^2.26.1",
|
|
37
38
|
"@tiptap/extension-table-header": "^2.26.1",
|
|
@@ -46,7 +47,7 @@
|
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|
|
48
49
|
"@eslint/js": "^9.37.0",
|
|
49
|
-
"@rimori/client": "
|
|
50
|
+
"@rimori/client": "2.5.19-next.5",
|
|
50
51
|
"@types/react": "^18.3.21",
|
|
51
52
|
"eslint-config-prettier": "^10.1.8",
|
|
52
53
|
"eslint-plugin-prettier": "^5.5.4",
|
|
@@ -2,6 +2,7 @@ import { JSX, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
|
2
2
|
import { useRimori } from '../../providers/PluginProvider';
|
|
3
3
|
import { Markdown } from 'tiptap-markdown';
|
|
4
4
|
import StarterKit from '@tiptap/starter-kit';
|
|
5
|
+
import { Paragraph } from '@tiptap/extension-paragraph';
|
|
5
6
|
import Table from '@tiptap/extension-table';
|
|
6
7
|
import TableCell from '@tiptap/extension-table-cell';
|
|
7
8
|
import TableHeader from '@tiptap/extension-table-header';
|
|
@@ -28,6 +29,28 @@ import { LuClipboardPaste, LuHeading1, LuHeading2, LuHeading3, LuLink, LuUnlink
|
|
|
28
29
|
import { FaBold, FaCode, FaItalic, FaParagraph, FaStrikethrough } from 'react-icons/fa';
|
|
29
30
|
import { ImageUploadExtension, triggerImageUpload } from './ImageUploadExtension';
|
|
30
31
|
|
|
32
|
+
// Extends TipTap's Paragraph to serialize empty paragraphs as <p></p>.
|
|
33
|
+
// Standard markdown collapses consecutive blank lines, losing empty paragraph nodes.
|
|
34
|
+
// Since tiptap-markdown enables html:true by default, <p></p> survives the round-trip.
|
|
35
|
+
const ParagraphPreserveEmpty = Paragraph.extend({
|
|
36
|
+
addStorage() {
|
|
37
|
+
return {
|
|
38
|
+
markdown: {
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
40
|
+
serialize(state: any, node: any) {
|
|
41
|
+
if (node.childCount === 0) {
|
|
42
|
+
state.write('<p></p>');
|
|
43
|
+
} else {
|
|
44
|
+
state.renderInline(node);
|
|
45
|
+
}
|
|
46
|
+
state.closeBlock(node);
|
|
47
|
+
},
|
|
48
|
+
parse: {},
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
31
54
|
function getMarkdown(editor: Editor): string {
|
|
32
55
|
return (editor.storage as { markdown: { getMarkdown: () => string } }).markdown.getMarkdown();
|
|
33
56
|
}
|
|
@@ -559,7 +582,8 @@ export const MarkdownEditor = ({
|
|
|
559
582
|
|
|
560
583
|
const extensions = useMemo(
|
|
561
584
|
() => [
|
|
562
|
-
StarterKit,
|
|
585
|
+
StarterKit.configure({ paragraph: false }),
|
|
586
|
+
ParagraphPreserveEmpty,
|
|
563
587
|
Table.configure({ resizable: false }),
|
|
564
588
|
TableRow,
|
|
565
589
|
TableHeader,
|
|
@@ -587,13 +611,14 @@ export const MarkdownEditor = ({
|
|
|
587
611
|
},
|
|
588
612
|
});
|
|
589
613
|
|
|
590
|
-
// Sync external content changes (e.g. AI autofill) without triggering update loop
|
|
614
|
+
// Sync external content changes (e.g. AI autofill) without triggering update loop.
|
|
615
|
+
// Pass false to setContent to prevent onUpdate from firing and re-normalizing the content.
|
|
591
616
|
useEffect(() => {
|
|
592
617
|
if (!editor) return;
|
|
593
618
|
const incoming = content ?? '';
|
|
594
619
|
if (incoming === lastEmittedRef.current) return;
|
|
595
620
|
lastEmittedRef.current = incoming;
|
|
596
|
-
editor.commands.setContent(incoming);
|
|
621
|
+
editor.commands.setContent(incoming, false);
|
|
597
622
|
}, [editor, content]);
|
|
598
623
|
|
|
599
624
|
// Sync editable prop
|
package/src/style.scss
CHANGED