@cyberyozh/text-editor 0.1.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.
- package/README.md +102 -0
- package/dist/editor.css +2069 -0
- package/dist/editor.css.map +1 -0
- package/dist/editor.d.mts +45 -0
- package/dist/editor.d.ts +45 -0
- package/dist/editor.js +6155 -0
- package/dist/editor.js.map +1 -0
- package/dist/editor.mjs +6118 -0
- package/dist/editor.mjs.map +1 -0
- package/dist/render/client.d.mts +24 -0
- package/dist/render/client.d.ts +24 -0
- package/dist/render/client.js +98 -0
- package/dist/render/client.js.map +1 -0
- package/dist/render/client.mjs +74 -0
- package/dist/render/client.mjs.map +1 -0
- package/dist/render.d.mts +58 -0
- package/dist/render.d.ts +58 -0
- package/dist/render.js +2521 -0
- package/dist/render.js.map +1 -0
- package/dist/render.mjs +2484 -0
- package/dist/render.mjs.map +1 -0
- package/dist/styles/index.css +2068 -0
- package/package.json +92 -0
- package/src/editor/commands/items.ts +374 -0
- package/src/editor/commands/suggestion.tsx +146 -0
- package/src/editor/components/button/button.css +56 -0
- package/src/editor/components/button/button.tsx +35 -0
- package/src/editor/components/button/index.ts +1 -0
- package/src/editor/components/common-modal/common-modal.css +85 -0
- package/src/editor/components/common-modal/common-modal.tsx +58 -0
- package/src/editor/components/common-modal/index.ts +1 -0
- package/src/editor/components/index.ts +3 -0
- package/src/editor/components/input/index.ts +1 -0
- package/src/editor/components/input/input.css +43 -0
- package/src/editor/components/input/input.tsx +22 -0
- package/src/editor/context/dialog-command.tsx +47 -0
- package/src/editor/context/index.ts +2 -0
- package/src/editor/context/translations-context.tsx +28 -0
- package/src/editor/controls/bubble-menu-content.tsx +394 -0
- package/src/editor/controls/callout-menu.tsx +169 -0
- package/src/editor/controls/character-count.tsx +36 -0
- package/src/editor/controls/commands-list.tsx +117 -0
- package/src/editor/controls/drag-handle.tsx +98 -0
- package/src/editor/controls/emoji-picker.tsx +62 -0
- package/src/editor/controls/index.ts +5 -0
- package/src/editor/controls/table-menu.tsx +322 -0
- package/src/editor/dialogs/add-katex-dialog/add-katex-dialog.css +115 -0
- package/src/editor/dialogs/add-katex-dialog/add-katex-dialog.tsx +116 -0
- package/src/editor/dialogs/add-katex-dialog/index.ts +1 -0
- package/src/editor/dialogs/add-link-dialog/add-link-dialog.css +105 -0
- package/src/editor/dialogs/add-link-dialog/add-link-dialog.tsx +177 -0
- package/src/editor/dialogs/add-link-dialog/index.ts +1 -0
- package/src/editor/dialogs/add-video-dialog/add-video-dialog.css +146 -0
- package/src/editor/dialogs/add-video-dialog/add-video-dialog.tsx +157 -0
- package/src/editor/dialogs/add-video-dialog/index.ts +1 -0
- package/src/editor/dialogs/image-dialog/image-dialog.css +101 -0
- package/src/editor/dialogs/image-dialog/image-dialog.tsx +276 -0
- package/src/editor/dialogs/image-dialog/index.ts +1 -0
- package/src/editor/dialogs/index.ts +4 -0
- package/src/editor/editor.tsx +247 -0
- package/src/editor/handlers/create-image-upload-handler.ts +24 -0
- package/src/editor/handlers/dialog-handlers.ts +143 -0
- package/src/editor/handlers/index.ts +8 -0
- package/src/editor/index.ts +3 -0
- package/src/extensions/base/callout.ts +138 -0
- package/src/extensions/base/code-block.ts +24 -0
- package/src/extensions/base/compatibility.ts +493 -0
- package/src/extensions/base/copyable-text.ts +56 -0
- package/src/extensions/base/custom-quote.ts +174 -0
- package/src/extensions/base/figure.ts +141 -0
- package/src/extensions/base/index.ts +19 -0
- package/src/extensions/base/link.ts +18 -0
- package/src/extensions/base/myth-reality.ts +45 -0
- package/src/extensions/base/prompt.ts +109 -0
- package/src/extensions/base/video.ts +29 -0
- package/src/extensions/editor/callout.ts +43 -0
- package/src/extensions/editor/code-block.ts +9 -0
- package/src/extensions/editor/commands.ts +43 -0
- package/src/extensions/editor/copyable-text.ts +16 -0
- package/src/extensions/editor/custom-quote.ts +54 -0
- package/src/extensions/editor/figure.ts +20 -0
- package/src/extensions/editor/get-editor-extensions.ts +113 -0
- package/src/extensions/editor/index.ts +25 -0
- package/src/extensions/editor/prompt.ts +43 -0
- package/src/extensions/editor/video.ts +23 -0
- package/src/extensions/render/get-render-extensions.ts +74 -0
- package/src/extensions/render/index.ts +6 -0
- package/src/extensions/views/components/callout.tsx +70 -0
- package/src/extensions/views/components/code-block.tsx +73 -0
- package/src/extensions/views/components/copy-button.tsx +52 -0
- package/src/extensions/views/components/copyable-text.tsx +11 -0
- package/src/extensions/views/components/custom-quote.tsx +64 -0
- package/src/extensions/views/components/figure.tsx +63 -0
- package/src/extensions/views/components/index.ts +7 -0
- package/src/extensions/views/components/prompt.tsx +43 -0
- package/src/extensions/views/components/video.tsx +40 -0
- package/src/extensions/views/node-views/callout.tsx +51 -0
- package/src/extensions/views/node-views/code-block.tsx +30 -0
- package/src/extensions/views/node-views/copyable-text.tsx +10 -0
- package/src/extensions/views/node-views/custom-quote.tsx +66 -0
- package/src/extensions/views/node-views/figure.tsx +38 -0
- package/src/extensions/views/node-views/index.ts +7 -0
- package/src/extensions/views/node-views/prompt.tsx +35 -0
- package/src/extensions/views/node-views/video.tsx +19 -0
- package/src/icons/Check.tsx +12 -0
- package/src/icons/Copy.tsx +15 -0
- package/src/icons/DragIcon.tsx +10 -0
- package/src/icons/Edit.tsx +12 -0
- package/src/icons/Quote.tsx +10 -0
- package/src/icons/X.tsx +13 -0
- package/src/icons/color/TiptapCalloutError.tsx +25 -0
- package/src/icons/color/TiptapCalloutInfo.tsx +18 -0
- package/src/icons/color/TiptapCalloutSuccess.tsx +49 -0
- package/src/icons/color/TiptapCalloutWarning.tsx +28 -0
- package/src/icons/color/index.ts +4 -0
- package/src/icons/index.ts +60 -0
- package/src/icons/menu/AddImage.tsx +10 -0
- package/src/icons/menu/AlignCenter.tsx +10 -0
- package/src/icons/menu/AlignLeft.tsx +10 -0
- package/src/icons/menu/AlignRight.tsx +10 -0
- package/src/icons/menu/ArrowDown.tsx +13 -0
- package/src/icons/menu/ArrowLeft.tsx +13 -0
- package/src/icons/menu/ArrowRight.tsx +13 -0
- package/src/icons/menu/ArrowUp.tsx +13 -0
- package/src/icons/menu/Blockquote.tsx +7 -0
- package/src/icons/menu/Bold.tsx +10 -0
- package/src/icons/menu/BulletList.tsx +10 -0
- package/src/icons/menu/CalloutError.tsx +10 -0
- package/src/icons/menu/CalloutInfo.tsx +12 -0
- package/src/icons/menu/CalloutSuccess.tsx +10 -0
- package/src/icons/menu/CalloutWarning.tsx +12 -0
- package/src/icons/menu/ClearFormatting.tsx +10 -0
- package/src/icons/menu/Code.tsx +10 -0
- package/src/icons/menu/CodeBlock.tsx +10 -0
- package/src/icons/menu/Copyable.tsx +10 -0
- package/src/icons/menu/CustomQuote.tsx +17 -0
- package/src/icons/menu/DeleteColumn.tsx +17 -0
- package/src/icons/menu/DeleteRow.tsx +17 -0
- package/src/icons/menu/Details.tsx +10 -0
- package/src/icons/menu/H2.tsx +10 -0
- package/src/icons/menu/H3.tsx +10 -0
- package/src/icons/menu/H4.tsx +10 -0
- package/src/icons/menu/H5.tsx +10 -0
- package/src/icons/menu/H6.tsx +10 -0
- package/src/icons/menu/Highlight.tsx +13 -0
- package/src/icons/menu/HorizontalRule.tsx +7 -0
- package/src/icons/menu/Italic.tsx +10 -0
- package/src/icons/menu/Link.tsx +10 -0
- package/src/icons/menu/LinkOff.tsx +10 -0
- package/src/icons/menu/Math.tsx +10 -0
- package/src/icons/menu/MergeCells.tsx +10 -0
- package/src/icons/menu/MythReality.tsx +10 -0
- package/src/icons/menu/OrderedList.tsx +10 -0
- package/src/icons/menu/Prompt.tsx +18 -0
- package/src/icons/menu/Redo.tsx +14 -0
- package/src/icons/menu/SplitCells.tsx +7 -0
- package/src/icons/menu/Strikethrough.tsx +10 -0
- package/src/icons/menu/Subscript.tsx +10 -0
- package/src/icons/menu/Superscript.tsx +10 -0
- package/src/icons/menu/Table.tsx +14 -0
- package/src/icons/menu/TableColumnHeading.tsx +7 -0
- package/src/icons/menu/TableRowHeading.tsx +10 -0
- package/src/icons/menu/TextColor.tsx +13 -0
- package/src/icons/menu/Underline.tsx +10 -0
- package/src/icons/menu/Undo.tsx +14 -0
- package/src/icons/menu/Video.tsx +14 -0
- package/src/icons/menu/index.ts +49 -0
- package/src/render/index.ts +22 -0
- package/src/render/static-renderer-config.tsx +206 -0
- package/src/render/tiptap-interactivity-provider.tsx +78 -0
- package/src/render/tiptap-render.tsx +102 -0
- package/src/styles/components.css +10 -0
- package/src/styles/editor/base.css +39 -0
- package/src/styles/editor/callout-menu.css +81 -0
- package/src/styles/editor/controls.css +57 -0
- package/src/styles/editor/emoji-picker.css +113 -0
- package/src/styles/editor/index.css +9 -0
- package/src/styles/editor/menus.css +16 -0
- package/src/styles/editor/states.css +43 -0
- package/src/styles/editor/typography.css +165 -0
- package/src/styles/editor/utilities.css +71 -0
- package/src/styles/editor/variables.css +70 -0
- package/src/styles/extensions/callout.css +155 -0
- package/src/styles/extensions/code-block.css +109 -0
- package/src/styles/extensions/commands-list.css +83 -0
- package/src/styles/extensions/custom-quote.css +103 -0
- package/src/styles/extensions/details.css +87 -0
- package/src/styles/extensions/figure.css +66 -0
- package/src/styles/extensions/index.css +11 -0
- package/src/styles/extensions/math.css +32 -0
- package/src/styles/extensions/myth-reality.css +59 -0
- package/src/styles/extensions/prompt.css +75 -0
- package/src/styles/extensions/table.css +253 -0
- package/src/styles/extensions/video.css +53 -0
- package/src/styles/styles.css +3 -0
- package/src/translations/en.json +158 -0
- package/src/translations/ru.json +158 -0
- package/src/types/image-handler.ts +33 -0
- package/src/types/index.ts +1 -0
- package/src/utils/copy-to-clipboard.ts +23 -0
- package/src/utils/custom-emoji.ts +174 -0
- package/src/utils/detect-video-hosting.ts +106 -0
- package/src/utils/get-shared-extensions.ts +45 -0
- package/src/utils/get-translations.ts +25 -0
- package/src/utils/handle-exit-node.ts +61 -0
- package/src/utils/image-src-resolver.ts +74 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/migrate-legacy-callouts.ts +59 -0
- package/src/utils/pluralize.ts +38 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export { default as AddImage } from './AddImage';
|
|
2
|
+
export { default as AlignCenter } from './AlignCenter';
|
|
3
|
+
export { default as AlignLeft } from './AlignLeft';
|
|
4
|
+
export { default as AlignRight } from './AlignRight';
|
|
5
|
+
export { default as ArrowDown } from './ArrowDown';
|
|
6
|
+
export { default as ArrowLeft } from './ArrowLeft';
|
|
7
|
+
export { default as ArrowRight } from './ArrowRight';
|
|
8
|
+
export { default as ArrowUp } from './ArrowUp';
|
|
9
|
+
export { default as Blockquote } from './Blockquote';
|
|
10
|
+
export { default as Bold } from './Bold';
|
|
11
|
+
export { default as BulletList } from './BulletList';
|
|
12
|
+
export { default as CalloutError } from './CalloutError';
|
|
13
|
+
export { default as CalloutInfo } from './CalloutInfo';
|
|
14
|
+
export { default as CalloutSuccess } from './CalloutSuccess';
|
|
15
|
+
export { default as CalloutWarning } from './CalloutWarning';
|
|
16
|
+
export { default as ClearFormatting } from './ClearFormatting';
|
|
17
|
+
export { default as CodeBlock } from './CodeBlock';
|
|
18
|
+
export { default as Copyable } from './Copyable';
|
|
19
|
+
export { default as CustomQuote } from './CustomQuote';
|
|
20
|
+
export { default as DeleteColumn } from './DeleteColumn';
|
|
21
|
+
export { default as DeleteRow } from './DeleteRow';
|
|
22
|
+
export { default as Details } from './Details';
|
|
23
|
+
export { default as H2 } from './H2';
|
|
24
|
+
export { default as H3 } from './H3';
|
|
25
|
+
export { default as H4 } from './H4';
|
|
26
|
+
export { default as H5 } from './H5';
|
|
27
|
+
export { default as H6 } from './H6';
|
|
28
|
+
export { default as Highlight } from './Highlight';
|
|
29
|
+
export { default as HorizontalRule } from './HorizontalRule';
|
|
30
|
+
export { default as Italic } from './Italic';
|
|
31
|
+
export { default as LinkOff } from './LinkOff';
|
|
32
|
+
export { default as Link } from './Link';
|
|
33
|
+
export { default as Math } from './Math';
|
|
34
|
+
export { default as MergeCells } from './MergeCells';
|
|
35
|
+
export { default as MythReality } from './MythReality';
|
|
36
|
+
export { default as OrderedList } from './OrderedList';
|
|
37
|
+
export { default as Prompt } from './Prompt';
|
|
38
|
+
export { default as Redo } from './Redo';
|
|
39
|
+
export { default as SplitCells } from './SplitCells';
|
|
40
|
+
export { default as Strikethrough } from './Strikethrough';
|
|
41
|
+
export { default as Subscript } from './Subscript';
|
|
42
|
+
export { default as Superscript } from './Superscript';
|
|
43
|
+
export { default as TableColumnHeading } from './TableColumnHeading';
|
|
44
|
+
export { default as TableRowHeading } from './TableRowHeading';
|
|
45
|
+
export { default as Table } from './Table';
|
|
46
|
+
export { default as TextColor } from './TextColor';
|
|
47
|
+
export { default as Underline } from './Underline';
|
|
48
|
+
export { default as Undo } from './Undo';
|
|
49
|
+
export { default as Video } from './Video';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render entry point for @cyberyozh/tiptap
|
|
3
|
+
*
|
|
4
|
+
* Use this to render Tiptap JSON content as React elements (typically in Next.js Server Components).
|
|
5
|
+
* This entry point has NO client-side dependencies and is safe for server-side execution.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* // app/blog/[slug]/page.tsx (Server Component)
|
|
10
|
+
* import { TiptapRender } from '@cyberyozh/tiptap/render';
|
|
11
|
+
*
|
|
12
|
+
* export default function BlogPost({ article }) {
|
|
13
|
+
* return (
|
|
14
|
+
* <article>
|
|
15
|
+
* <h1>{article.title}</h1>
|
|
16
|
+
* <TiptapRender content={article.content} />
|
|
17
|
+
* </article>
|
|
18
|
+
* );
|
|
19
|
+
* }
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export { TiptapRender } from "./tiptap-render";
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for @tiptap/static-renderer for server-side rendering
|
|
3
|
+
* Provides React component mappings for custom extensions
|
|
4
|
+
*
|
|
5
|
+
* Node and mark mappings control how Tiptap JSON is rendered to React elements
|
|
6
|
+
* during server-side static rendering. These map directly to the extensions
|
|
7
|
+
* defined in extensions/ folder.
|
|
8
|
+
*
|
|
9
|
+
* Note: Node views (addNodeView) are NOT supported by static renderer.
|
|
10
|
+
* We provide custom React component mappings that replicate the NodeView output
|
|
11
|
+
* without using Tiptap's NodeViewWrapper/NodeViewContent APIs.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import katex from "katex";
|
|
15
|
+
import hljs from "highlight.js/lib/common";
|
|
16
|
+
import type { CalloutType } from "@/extensions/base/callout";
|
|
17
|
+
import {
|
|
18
|
+
Callout,
|
|
19
|
+
CopyableText,
|
|
20
|
+
CodeBlock,
|
|
21
|
+
CustomQuote,
|
|
22
|
+
CustomQuoteText,
|
|
23
|
+
CustomQuoteAuthor,
|
|
24
|
+
CustomQuoteSign,
|
|
25
|
+
PromptHeader,
|
|
26
|
+
PromptContent,
|
|
27
|
+
Video,
|
|
28
|
+
Figure,
|
|
29
|
+
} from "@/extensions/views/components";
|
|
30
|
+
|
|
31
|
+
export const getNodeMapping = (getImageById?: (fileId: string) => string | null) => ({
|
|
32
|
+
callout({ node, children }: any) {
|
|
33
|
+
return <Callout {...node.attrs}>{children}</Callout>;
|
|
34
|
+
},
|
|
35
|
+
codeBlock({ node, children }: any) {
|
|
36
|
+
const language = (node.attrs.language as string) || "";
|
|
37
|
+
|
|
38
|
+
let codeText = "";
|
|
39
|
+
if (Array.isArray(children)) {
|
|
40
|
+
codeText = children
|
|
41
|
+
.map((child: any) => {
|
|
42
|
+
if (child?.props?.node?.text) {
|
|
43
|
+
return child.props.node.text;
|
|
44
|
+
}
|
|
45
|
+
return "";
|
|
46
|
+
})
|
|
47
|
+
.join("");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let highlightedHtml = "";
|
|
51
|
+
try {
|
|
52
|
+
if (!language) {
|
|
53
|
+
highlightedHtml = hljs.highlightAuto(codeText).value;
|
|
54
|
+
} else {
|
|
55
|
+
highlightedHtml = hljs.highlight(codeText, { language }).value;
|
|
56
|
+
}
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.warn(`Failed to highlight code for language "${language}":`, error);
|
|
59
|
+
highlightedHtml = codeText
|
|
60
|
+
.replace(/&/g, "&")
|
|
61
|
+
.replace(/</g, "<")
|
|
62
|
+
.replace(/>/g, ">")
|
|
63
|
+
.replace(/"/g, """)
|
|
64
|
+
.replace(/'/g, "'");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<CodeBlock
|
|
69
|
+
language={language}
|
|
70
|
+
isEditable={false}
|
|
71
|
+
>
|
|
72
|
+
<code dangerouslySetInnerHTML={{ __html: highlightedHtml }} />
|
|
73
|
+
</CodeBlock>
|
|
74
|
+
);
|
|
75
|
+
},
|
|
76
|
+
customQuote({ children }: any) {
|
|
77
|
+
return <CustomQuote>{children}</CustomQuote>;
|
|
78
|
+
},
|
|
79
|
+
customQuoteText({ children }: any) {
|
|
80
|
+
return <CustomQuoteText>{children}</CustomQuoteText>;
|
|
81
|
+
},
|
|
82
|
+
customQuoteAuthor({ children }: any) {
|
|
83
|
+
return <CustomQuoteAuthor>{children}</CustomQuoteAuthor>;
|
|
84
|
+
},
|
|
85
|
+
customQuoteSign({ children }: any) {
|
|
86
|
+
return <CustomQuoteSign>{children}</CustomQuoteSign>;
|
|
87
|
+
},
|
|
88
|
+
promptHeader({ children }: any) {
|
|
89
|
+
return <PromptHeader>{children}</PromptHeader>;
|
|
90
|
+
},
|
|
91
|
+
promptContent({ children }: any) {
|
|
92
|
+
return <PromptContent>{children}</PromptContent>;
|
|
93
|
+
},
|
|
94
|
+
video({ node }: any) {
|
|
95
|
+
return <Video src={node.attrs.src} />;
|
|
96
|
+
},
|
|
97
|
+
figure({ node }: any) {
|
|
98
|
+
return <Figure {...node.attrs} getImageById={getImageById} />;
|
|
99
|
+
},
|
|
100
|
+
image({ node }: any) {
|
|
101
|
+
return <Figure {...node.attrs} getImageById={getImageById} />;
|
|
102
|
+
},
|
|
103
|
+
inlineMath({ node }: any) {
|
|
104
|
+
const latex = node.attrs.latex || "";
|
|
105
|
+
let html = "";
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
html = katex.renderToString(latex, {
|
|
109
|
+
throwOnError: false,
|
|
110
|
+
output: "html",
|
|
111
|
+
});
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.warn("KaTeX rendering error for inline math:", error);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<span
|
|
118
|
+
className="react-renderer node-inlineMath"
|
|
119
|
+
dangerouslySetInnerHTML={{ __html: html || latex }}
|
|
120
|
+
/>
|
|
121
|
+
);
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
// Block math (KaTeX) - from @tiptap/extension-mathematics
|
|
125
|
+
blockMath({ node }: any) {
|
|
126
|
+
const latex = node.attrs.latex || "";
|
|
127
|
+
let html = "";
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
html = katex.renderToString(latex, {
|
|
131
|
+
throwOnError: false,
|
|
132
|
+
output: "html",
|
|
133
|
+
displayMode: true,
|
|
134
|
+
});
|
|
135
|
+
} catch (error) {
|
|
136
|
+
console.warn("KaTeX rendering error for block math:", error);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<div className="react-renderer node-blockMath">
|
|
141
|
+
<div
|
|
142
|
+
className="tiptap-mathematics-render"
|
|
143
|
+
data-type="math"
|
|
144
|
+
data-mode="block"
|
|
145
|
+
dangerouslySetInnerHTML={{ __html: html || latex }}
|
|
146
|
+
/>
|
|
147
|
+
</div>
|
|
148
|
+
);
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
inlineLatex({ node }: any) {
|
|
152
|
+
const latex = node.attrs.latex || "";
|
|
153
|
+
let html = "";
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
html = katex.renderToString(latex, {
|
|
157
|
+
throwOnError: false,
|
|
158
|
+
output: "html",
|
|
159
|
+
});
|
|
160
|
+
} catch (error) {
|
|
161
|
+
console.warn("KaTeX rendering error for inline latex:", error);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
<span
|
|
166
|
+
className="react-renderer node-inlineLatex inline-latex"
|
|
167
|
+
dangerouslySetInnerHTML={{ __html: html || latex }}
|
|
168
|
+
/>
|
|
169
|
+
);
|
|
170
|
+
},
|
|
171
|
+
/*
|
|
172
|
+
COMPATABILITY
|
|
173
|
+
*/
|
|
174
|
+
calloutAlert({ node, children }: any) {
|
|
175
|
+
const type = (node.attrs.type as CalloutType) || "info";
|
|
176
|
+
return <Callout type={type}>{children}</Callout>;
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
calloutTip({ node, children }: any) {
|
|
180
|
+
const type = (node.attrs.type as CalloutType) || "info";
|
|
181
|
+
return <Callout type={type}>{children}</Callout>;
|
|
182
|
+
},
|
|
183
|
+
customQuoteV2({ children }: any) {
|
|
184
|
+
return <CustomQuote>{children}</CustomQuote>;
|
|
185
|
+
},
|
|
186
|
+
promptV2Header({ children }: any) {
|
|
187
|
+
return <PromptHeader>{children}</PromptHeader>;
|
|
188
|
+
},
|
|
189
|
+
promptV2Content({ children }: any) {
|
|
190
|
+
return <PromptContent>{children}</PromptContent>;
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
export const nodeMapping = getNodeMapping();
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Mark mappings for static renderer
|
|
198
|
+
*
|
|
199
|
+
* Standard marks (bold, italic, code, link, etc.) are handled automatically.
|
|
200
|
+
* Custom marks can be mapped here if needed for special rendering.
|
|
201
|
+
*/
|
|
202
|
+
export const markMapping = {
|
|
203
|
+
"copyable-text"({ children }: any) {
|
|
204
|
+
return <CopyableText>{children}</CopyableText>;
|
|
205
|
+
},
|
|
206
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect } from "react";
|
|
4
|
+
import { copyToClipboard } from "../utils/copy-to-clipboard";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Client component that initializes Tiptap interactivity
|
|
8
|
+
* Use this in your layout to enable interactive features for server-rendered content
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```tsx
|
|
12
|
+
* // app/layout.tsx (Server Component)
|
|
13
|
+
* import { CalloutInteractivityProvider } from '@cyberyozh/tiptap/render';
|
|
14
|
+
*
|
|
15
|
+
* export default function Layout({ children }) {
|
|
16
|
+
* return (
|
|
17
|
+
* <html>
|
|
18
|
+
* <body>
|
|
19
|
+
* <CalloutInteractivityProvider />
|
|
20
|
+
* {children}
|
|
21
|
+
* </body>
|
|
22
|
+
* </html>
|
|
23
|
+
* );
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function TiptapInteractivityProvider() {
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
function findCopyContent(button: HTMLElement): Element | null {
|
|
30
|
+
const parent = button.closest(":has([data-copy-content])");
|
|
31
|
+
return parent?.querySelector("[data-copy-content]") || null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function showSuccessIcon(button: HTMLElement) {
|
|
35
|
+
const defaultIcon = button.querySelector('[data-copy-icon="default"]') as HTMLElement;
|
|
36
|
+
const successIcon = button.querySelector('[data-copy-icon="success"]') as HTMLElement;
|
|
37
|
+
|
|
38
|
+
if (defaultIcon && successIcon) {
|
|
39
|
+
defaultIcon.style.display = "none";
|
|
40
|
+
successIcon.style.display = "flex";
|
|
41
|
+
|
|
42
|
+
setTimeout(() => {
|
|
43
|
+
defaultIcon.style.display = "flex";
|
|
44
|
+
successIcon.style.display = "none";
|
|
45
|
+
}, 1500);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function handleCopy(e: MouseEvent) {
|
|
50
|
+
const target = e.target as HTMLElement;
|
|
51
|
+
const button = target.closest('[data-copy-action="copy"]') as HTMLElement;
|
|
52
|
+
|
|
53
|
+
if (button) {
|
|
54
|
+
const contentElement = findCopyContent(button);
|
|
55
|
+
|
|
56
|
+
if (contentElement) {
|
|
57
|
+
const textContent = contentElement.textContent?.trim() || "";
|
|
58
|
+
|
|
59
|
+
copyToClipboard(textContent)
|
|
60
|
+
.then(() => {
|
|
61
|
+
showSuccessIcon(button);
|
|
62
|
+
})
|
|
63
|
+
.catch((error) => {
|
|
64
|
+
console.error("❌ Failed to copy:", error);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
document.addEventListener("click", handleCopy);
|
|
71
|
+
|
|
72
|
+
return () => {
|
|
73
|
+
document.removeEventListener("click", handleCopy);
|
|
74
|
+
};
|
|
75
|
+
}, []);
|
|
76
|
+
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { renderToReactElement } from "@tiptap/static-renderer/pm/react";
|
|
2
|
+
import { ReactNode } from "react";
|
|
3
|
+
import { getRenderExtensions } from "../extensions/render";
|
|
4
|
+
import { getNodeMapping, markMapping } from "./static-renderer-config";
|
|
5
|
+
|
|
6
|
+
import "highlight.js/styles/github-dark.css";
|
|
7
|
+
import "katex/dist/katex.min.css";
|
|
8
|
+
|
|
9
|
+
type JSONContent = Record<string, any>;
|
|
10
|
+
|
|
11
|
+
interface TiptapRenderProps {
|
|
12
|
+
/**
|
|
13
|
+
* Tiptap JSON content (parsed object or JSON string)
|
|
14
|
+
*/
|
|
15
|
+
content: JSONContent | string;
|
|
16
|
+
/**
|
|
17
|
+
* Function to resolve image URLs from file IDs
|
|
18
|
+
* Used for static rendering of figure nodes with file references
|
|
19
|
+
*/
|
|
20
|
+
getImageById?: (fileId: string) => string | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function renderTiptapToReact(
|
|
24
|
+
content: JSONContent | string,
|
|
25
|
+
getImageById?: (fileId: string) => string | null
|
|
26
|
+
): ReactNode {
|
|
27
|
+
let parsedContent: JSONContent;
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
if (typeof content === "string") {
|
|
31
|
+
parsedContent = JSON.parse(content);
|
|
32
|
+
} else {
|
|
33
|
+
parsedContent = content;
|
|
34
|
+
}
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.warn("Failed to parse Tiptap content:", error);
|
|
37
|
+
parsedContent = { type: "doc", content: [{ type: "paragraph" }] };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const rendered = renderToReactElement({
|
|
41
|
+
extensions: getRenderExtensions(),
|
|
42
|
+
content: parsedContent,
|
|
43
|
+
options: {
|
|
44
|
+
nodeMapping: getNodeMapping(getImageById),
|
|
45
|
+
markMapping,
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div
|
|
51
|
+
className="tiptap"
|
|
52
|
+
data-tiptap-render
|
|
53
|
+
>
|
|
54
|
+
{rendered}
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Server component that renders Tiptap JSON content as React
|
|
61
|
+
*
|
|
62
|
+
* Designed for Next.js Server Components. Converts Tiptap JSON to rendered React elements
|
|
63
|
+
* without any client-side dependencies.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```tsx
|
|
67
|
+
* // app/blog/[slug]/page.tsx (Server Component)
|
|
68
|
+
* import { TiptapRender } from '@cyberyozh/tiptap/server';
|
|
69
|
+
*
|
|
70
|
+
* export default function BlogPost({ article }) {
|
|
71
|
+
* return (
|
|
72
|
+
* <article>
|
|
73
|
+
* <h1>{article.title}</h1>
|
|
74
|
+
* <TiptapRender content={article.content} lang="en" />
|
|
75
|
+
* </article>
|
|
76
|
+
* );
|
|
77
|
+
* }
|
|
78
|
+
* ```
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```tsx
|
|
82
|
+
* // With custom image resolver
|
|
83
|
+
* import { TiptapRender } from '@cyberyozh/tiptap/server';
|
|
84
|
+
*
|
|
85
|
+
* const getImageById = (fileId: string) => `/api/images/${fileId}`;
|
|
86
|
+
*
|
|
87
|
+
* export default function BlogPost({ article }) {
|
|
88
|
+
* return (
|
|
89
|
+
* <article>
|
|
90
|
+
* <TiptapRender
|
|
91
|
+
* content={article.content}
|
|
92
|
+
* lang="en"
|
|
93
|
+
* getImageById={getImageById}
|
|
94
|
+
* />
|
|
95
|
+
* </article>
|
|
96
|
+
* );
|
|
97
|
+
* }
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
export function TiptapRender({ content, getImageById }: TiptapRenderProps) {
|
|
101
|
+
return renderTiptapToReact(content, getImageById);
|
|
102
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/* UI Components */
|
|
2
|
+
@import "../editor/components/button/button.css";
|
|
3
|
+
@import "../editor/components/input/input.css";
|
|
4
|
+
@import "../editor/components/common-modal/common-modal.css";
|
|
5
|
+
|
|
6
|
+
/* Dialog Styles */
|
|
7
|
+
@import "../editor/dialogs/add-katex-dialog/add-katex-dialog.css";
|
|
8
|
+
@import "../editor/dialogs/add-link-dialog/add-link-dialog.css";
|
|
9
|
+
@import "../editor/dialogs/add-video-dialog/add-video-dialog.css";
|
|
10
|
+
@import "../editor/dialogs/image-dialog/image-dialog.css";
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
.tiptap-content-wrapper {
|
|
2
|
+
position: relative;
|
|
3
|
+
margin: 0 !important;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.tiptap {
|
|
7
|
+
padding: var(--tiptap-spacing);
|
|
8
|
+
border: 1px solid var(--tiptap-color-border);
|
|
9
|
+
border-radius: var(--tiptap-radius);
|
|
10
|
+
font-size: var(--tiptap-font-size);
|
|
11
|
+
font-family: var(--tiptap-font-family);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.tiptap:focus {
|
|
15
|
+
border-color: var(--tiptap-color-primary-light);
|
|
16
|
+
outline: none;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.tiptap-content-wrapper.has-error .tiptap {
|
|
20
|
+
border-color: var(--tiptap-color-primary-error);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.tiptap-error-message {
|
|
24
|
+
margin: 8px 0 0 0 !important;
|
|
25
|
+
color: var(--tiptap-color-primary-error);
|
|
26
|
+
font-size: 14px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.tiptap[data-tiptap-render],
|
|
30
|
+
.tiptap[contenteditable="false"] {
|
|
31
|
+
padding: 0;
|
|
32
|
+
border: none;
|
|
33
|
+
outline: none;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.tiptap[data-tiptap-render] [data-placeholder].is-empty,
|
|
37
|
+
.tiptap[contenteditable="false"] [data-placeholder].is-empty {
|
|
38
|
+
display: none;
|
|
39
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
.tiptap__drag-handle-color-menu {
|
|
2
|
+
display: flex;
|
|
3
|
+
z-index: 1000;
|
|
4
|
+
position: absolute;
|
|
5
|
+
top: 0;
|
|
6
|
+
left: 100%;
|
|
7
|
+
flex-direction: column;
|
|
8
|
+
flex-wrap: wrap;
|
|
9
|
+
width: 196px;
|
|
10
|
+
margin-left: 8px;
|
|
11
|
+
padding: 8px;
|
|
12
|
+
gap: 4px;
|
|
13
|
+
border: 1px solid var(--tiptap-color-primary-light);
|
|
14
|
+
border-radius: 8px;
|
|
15
|
+
background: var(--tiptap-bg-surface-menu);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.tiptap__color-menu-title {
|
|
19
|
+
display: flex;
|
|
20
|
+
align-items: center;
|
|
21
|
+
height: 32px;
|
|
22
|
+
padding: 4px 8px;
|
|
23
|
+
border-radius: 8px;
|
|
24
|
+
font-weight: 400;
|
|
25
|
+
font-size: 15px;
|
|
26
|
+
line-height: 1.4;
|
|
27
|
+
white-space: nowrap;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.tiptap__color-menu-row {
|
|
31
|
+
display: flex;
|
|
32
|
+
flex-wrap: wrap;
|
|
33
|
+
justify-content: space-between;
|
|
34
|
+
gap: 4px;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.tiptap__color-option {
|
|
38
|
+
display: flex;
|
|
39
|
+
align-items: center;
|
|
40
|
+
justify-content: center;
|
|
41
|
+
width: 40px;
|
|
42
|
+
height: 40px;
|
|
43
|
+
padding: 4px;
|
|
44
|
+
border: none;
|
|
45
|
+
border-radius: 8px;
|
|
46
|
+
border-color: transparent;
|
|
47
|
+
outline: none;
|
|
48
|
+
background: var(--tiptap-bg-surface-menu);
|
|
49
|
+
cursor: pointer;
|
|
50
|
+
transition: border 0.15s ease;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.tiptap__color-option:hover {
|
|
54
|
+
border: 1px solid var(--tiptap-color-primary-light);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.tiptap__color-swatch {
|
|
58
|
+
display: flex;
|
|
59
|
+
align-items: center;
|
|
60
|
+
justify-content: center;
|
|
61
|
+
width: 29px;
|
|
62
|
+
padding: 4px 8px;
|
|
63
|
+
border-radius: 4px;
|
|
64
|
+
font-weight: 400;
|
|
65
|
+
font-size: 15px;
|
|
66
|
+
line-height: 1.4;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.tiptap__color-menu-custom {
|
|
70
|
+
display: flex;
|
|
71
|
+
align-items: center;
|
|
72
|
+
gap: 8px;
|
|
73
|
+
margin-top: 8px;
|
|
74
|
+
padding-top: 8px;
|
|
75
|
+
border-top: 1px solid var(--tiptap-color-primary-light);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.tiptap__color-menu-custom-label {
|
|
79
|
+
font-size: 14px;
|
|
80
|
+
white-space: nowrap;
|
|
81
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
.tiptap-controls-row {
|
|
2
|
+
display: flex;
|
|
3
|
+
gap: 12px;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.tiptap-controls-row:not(:last-child) {
|
|
7
|
+
margin-bottom: 12px;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.tiptap-controls-group {
|
|
11
|
+
display: flex;
|
|
12
|
+
width: fit-content;
|
|
13
|
+
padding: 6px;
|
|
14
|
+
gap: 12px;
|
|
15
|
+
border: 1px solid var(--tiptap-color-primary-subtle);
|
|
16
|
+
border-radius: 4px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.bubble-menu .tiptap-controls-group {
|
|
20
|
+
padding: 4px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.tiptap-control-button {
|
|
24
|
+
display: flex;
|
|
25
|
+
align-items: center;
|
|
26
|
+
justify-content: center;
|
|
27
|
+
padding: 0;
|
|
28
|
+
border: none;
|
|
29
|
+
border-radius: 4px;
|
|
30
|
+
background-color: transparent;
|
|
31
|
+
color: var(--tiptap-color-primary);
|
|
32
|
+
cursor: pointer;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.tiptap-control-button svg {
|
|
36
|
+
width: 24px;
|
|
37
|
+
height: 24px;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.bubble-menu .tiptap-control-button svg {
|
|
41
|
+
width: 18px;
|
|
42
|
+
height: 18px;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.tiptap-control-button:disabled {
|
|
46
|
+
color: var(--tiptap-color-text-disabled);
|
|
47
|
+
cursor: not-allowed;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.tiptap-control-button:hover {
|
|
51
|
+
outline: 1px solid var(--tiptap-color-primary-light);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.tiptap-control-button.is-active {
|
|
55
|
+
background-color: var(--tiptap-color-primary);
|
|
56
|
+
color: var(--tiptap-state-control-active-color);
|
|
57
|
+
}
|