doclific 0.1.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 (231) hide show
  1. package/.gitattributes +2 -0
  2. package/.prettierignore +5 -0
  3. package/.prettierrc +9 -0
  4. package/.vscode/settings.json +13 -0
  5. package/dist/bin/doclific.d.ts +3 -0
  6. package/dist/bin/doclific.d.ts.map +1 -0
  7. package/dist/bin/doclific.js +11 -0
  8. package/dist/core/codebase.js +31 -0
  9. package/dist/core/docs.js +75 -0
  10. package/dist/core/git.js +47 -0
  11. package/dist/server/index.d.ts +2 -0
  12. package/dist/server/index.d.ts.map +1 -0
  13. package/dist/server/index.js +46 -0
  14. package/dist/server/router.d.ts +9 -0
  15. package/dist/server/router.d.ts.map +1 -0
  16. package/dist/server/router.js +55 -0
  17. package/frontend/README.md +73 -0
  18. package/frontend/components.json +24 -0
  19. package/frontend/eslint.config.js +23 -0
  20. package/frontend/index.html +25 -0
  21. package/frontend/package-lock.json +15754 -0
  22. package/frontend/package.json +122 -0
  23. package/frontend/public/logo.svg +1 -0
  24. package/frontend/src/App.tsx +21 -0
  25. package/frontend/src/components/app-sidebar.tsx +393 -0
  26. package/frontend/src/components/editor/editor-base-kit.tsx +43 -0
  27. package/frontend/src/components/editor/editor-kit.tsx +93 -0
  28. package/frontend/src/components/editor/plugins/align-base-kit.tsx +16 -0
  29. package/frontend/src/components/editor/plugins/align-kit.tsx +18 -0
  30. package/frontend/src/components/editor/plugins/autoformat-kit.tsx +236 -0
  31. package/frontend/src/components/editor/plugins/basic-blocks-base-kit.tsx +35 -0
  32. package/frontend/src/components/editor/plugins/basic-blocks-kit.tsx +88 -0
  33. package/frontend/src/components/editor/plugins/basic-marks-base-kit.tsx +27 -0
  34. package/frontend/src/components/editor/plugins/basic-marks-kit.tsx +41 -0
  35. package/frontend/src/components/editor/plugins/basic-nodes-kit.tsx +6 -0
  36. package/frontend/src/components/editor/plugins/block-menu-kit.tsx +14 -0
  37. package/frontend/src/components/editor/plugins/block-placeholder-kit.tsx +17 -0
  38. package/frontend/src/components/editor/plugins/block-selection-kit.tsx +32 -0
  39. package/frontend/src/components/editor/plugins/callout-base-kit.tsx +7 -0
  40. package/frontend/src/components/editor/plugins/callout-kit.tsx +7 -0
  41. package/frontend/src/components/editor/plugins/code-block-base-kit.tsx +23 -0
  42. package/frontend/src/components/editor/plugins/code-block-kit.tsx +26 -0
  43. package/frontend/src/components/editor/plugins/codebase-kit.tsx +23 -0
  44. package/frontend/src/components/editor/plugins/column-base-kit.tsx +11 -0
  45. package/frontend/src/components/editor/plugins/column-kit.tsx +10 -0
  46. package/frontend/src/components/editor/plugins/comment-base-kit.tsx +7 -0
  47. package/frontend/src/components/editor/plugins/comment-kit.tsx +97 -0
  48. package/frontend/src/components/editor/plugins/cursor-overlay-kit.tsx +13 -0
  49. package/frontend/src/components/editor/plugins/date-base-kit.tsx +5 -0
  50. package/frontend/src/components/editor/plugins/date-kit.tsx +7 -0
  51. package/frontend/src/components/editor/plugins/discussion-kit.tsx +148 -0
  52. package/frontend/src/components/editor/plugins/dnd-kit.tsx +28 -0
  53. package/frontend/src/components/editor/plugins/docx-kit.tsx +6 -0
  54. package/frontend/src/components/editor/plugins/emoji-kit.tsx +13 -0
  55. package/frontend/src/components/editor/plugins/excalidraw-kit.tsx +9 -0
  56. package/frontend/src/components/editor/plugins/exit-break-kit.tsx +12 -0
  57. package/frontend/src/components/editor/plugins/floating-toolbar-kit.tsx +19 -0
  58. package/frontend/src/components/editor/plugins/font-base-kit.tsx +20 -0
  59. package/frontend/src/components/editor/plugins/font-kit.tsx +29 -0
  60. package/frontend/src/components/editor/plugins/indent-base-kit.tsx +19 -0
  61. package/frontend/src/components/editor/plugins/indent-kit.tsx +22 -0
  62. package/frontend/src/components/editor/plugins/line-height-base-kit.tsx +14 -0
  63. package/frontend/src/components/editor/plugins/line-height-kit.tsx +16 -0
  64. package/frontend/src/components/editor/plugins/link-base-kit.tsx +5 -0
  65. package/frontend/src/components/editor/plugins/link-kit.tsx +15 -0
  66. package/frontend/src/components/editor/plugins/list-base-kit.tsx +23 -0
  67. package/frontend/src/components/editor/plugins/list-kit.tsx +26 -0
  68. package/frontend/src/components/editor/plugins/markdown-kit.tsx +46 -0
  69. package/frontend/src/components/editor/plugins/math-base-kit.tsx +11 -0
  70. package/frontend/src/components/editor/plugins/math-kit.tsx +13 -0
  71. package/frontend/src/components/editor/plugins/media-base-kit.tsx +31 -0
  72. package/frontend/src/components/editor/plugins/media-kit.tsx +43 -0
  73. package/frontend/src/components/editor/plugins/mention-base-kit.tsx +7 -0
  74. package/frontend/src/components/editor/plugins/mention-kit.tsx +15 -0
  75. package/frontend/src/components/editor/plugins/slash-kit.tsx +18 -0
  76. package/frontend/src/components/editor/plugins/suggestion-base-kit.tsx +7 -0
  77. package/frontend/src/components/editor/plugins/suggestion-kit.tsx +90 -0
  78. package/frontend/src/components/editor/plugins/table-base-kit.tsx +20 -0
  79. package/frontend/src/components/editor/plugins/table-kit.tsx +22 -0
  80. package/frontend/src/components/editor/plugins/toc-base-kit.tsx +5 -0
  81. package/frontend/src/components/editor/plugins/toc-kit.tsx +14 -0
  82. package/frontend/src/components/editor/plugins/toggle-base-kit.tsx +7 -0
  83. package/frontend/src/components/editor/plugins/toggle-kit.tsx +11 -0
  84. package/frontend/src/components/editor/transforms.ts +194 -0
  85. package/frontend/src/components/markdown-to-slate-demo.tsx +50 -0
  86. package/frontend/src/components/mode-toggle.tsx +15 -0
  87. package/frontend/src/components/theme-provider.tsx +73 -0
  88. package/frontend/src/components/ui/alert-dialog.tsx +155 -0
  89. package/frontend/src/components/ui/align-toolbar-button.tsx +84 -0
  90. package/frontend/src/components/ui/avatar.tsx +51 -0
  91. package/frontend/src/components/ui/block-context-menu.tsx +199 -0
  92. package/frontend/src/components/ui/block-discussion.tsx +365 -0
  93. package/frontend/src/components/ui/block-draggable.tsx +512 -0
  94. package/frontend/src/components/ui/block-list-static.tsx +80 -0
  95. package/frontend/src/components/ui/block-list.tsx +87 -0
  96. package/frontend/src/components/ui/block-selection.tsx +42 -0
  97. package/frontend/src/components/ui/block-suggestion.tsx +473 -0
  98. package/frontend/src/components/ui/blockquote-node-static.tsx +11 -0
  99. package/frontend/src/components/ui/blockquote-node.tsx +13 -0
  100. package/frontend/src/components/ui/button.tsx +62 -0
  101. package/frontend/src/components/ui/calendar.tsx +218 -0
  102. package/frontend/src/components/ui/callout-node-static.tsx +36 -0
  103. package/frontend/src/components/ui/callout-node.tsx +63 -0
  104. package/frontend/src/components/ui/caption.tsx +63 -0
  105. package/frontend/src/components/ui/checkbox.tsx +30 -0
  106. package/frontend/src/components/ui/code-block-node-static.tsx +35 -0
  107. package/frontend/src/components/ui/code-block-node.tsx +287 -0
  108. package/frontend/src/components/ui/code-node-static.tsx +15 -0
  109. package/frontend/src/components/ui/code-node.tsx +17 -0
  110. package/frontend/src/components/ui/codebase-snippet-node.tsx +237 -0
  111. package/frontend/src/components/ui/column-node-static.tsx +29 -0
  112. package/frontend/src/components/ui/column-node.tsx +317 -0
  113. package/frontend/src/components/ui/command.tsx +182 -0
  114. package/frontend/src/components/ui/comment-node-static.tsx +15 -0
  115. package/frontend/src/components/ui/comment-node.tsx +45 -0
  116. package/frontend/src/components/ui/comment-toolbar-button.tsx +24 -0
  117. package/frontend/src/components/ui/comment.tsx +618 -0
  118. package/frontend/src/components/ui/context-menu.tsx +250 -0
  119. package/frontend/src/components/ui/cursor-overlay.tsx +66 -0
  120. package/frontend/src/components/ui/date-node-static.tsx +45 -0
  121. package/frontend/src/components/ui/date-node.tsx +93 -0
  122. package/frontend/src/components/ui/dialog.tsx +143 -0
  123. package/frontend/src/components/ui/dropdown-menu.tsx +255 -0
  124. package/frontend/src/components/ui/dynamic-icon.tsx +12 -0
  125. package/frontend/src/components/ui/editor-static.tsx +53 -0
  126. package/frontend/src/components/ui/editor.tsx +130 -0
  127. package/frontend/src/components/ui/emoji-node.tsx +69 -0
  128. package/frontend/src/components/ui/emoji-toolbar-button.tsx +628 -0
  129. package/frontend/src/components/ui/equation-node-static.tsx +98 -0
  130. package/frontend/src/components/ui/equation-node.tsx +235 -0
  131. package/frontend/src/components/ui/equation-toolbar-button.tsx +25 -0
  132. package/frontend/src/components/ui/excalidraw-node.tsx +36 -0
  133. package/frontend/src/components/ui/export-toolbar-button.tsx +174 -0
  134. package/frontend/src/components/ui/file-selector.tsx +339 -0
  135. package/frontend/src/components/ui/floating-toolbar-buttons.tsx +73 -0
  136. package/frontend/src/components/ui/floating-toolbar.tsx +85 -0
  137. package/frontend/src/components/ui/font-color-toolbar-button.tsx +831 -0
  138. package/frontend/src/components/ui/font-size-toolbar-button.tsx +152 -0
  139. package/frontend/src/components/ui/heading-node-static.tsx +68 -0
  140. package/frontend/src/components/ui/heading-node.tsx +58 -0
  141. package/frontend/src/components/ui/highlight-node-static.tsx +11 -0
  142. package/frontend/src/components/ui/highlight-node.tsx +13 -0
  143. package/frontend/src/components/ui/history-toolbar-button.tsx +50 -0
  144. package/frontend/src/components/ui/hr-node-static.tsx +20 -0
  145. package/frontend/src/components/ui/hr-node.tsx +33 -0
  146. package/frontend/src/components/ui/import-toolbar-button.tsx +97 -0
  147. package/frontend/src/components/ui/indent-toolbar-button.tsx +30 -0
  148. package/frontend/src/components/ui/inline-combobox.tsx +414 -0
  149. package/frontend/src/components/ui/input.tsx +21 -0
  150. package/frontend/src/components/ui/insert-toolbar-button.tsx +254 -0
  151. package/frontend/src/components/ui/kbd-node-static.tsx +15 -0
  152. package/frontend/src/components/ui/kbd-node.tsx +17 -0
  153. package/frontend/src/components/ui/layout-header.tsx +35 -0
  154. package/frontend/src/components/ui/line-height-toolbar-button.tsx +68 -0
  155. package/frontend/src/components/ui/link-node-static.tsx +21 -0
  156. package/frontend/src/components/ui/link-node.tsx +39 -0
  157. package/frontend/src/components/ui/link-toolbar-button.tsx +22 -0
  158. package/frontend/src/components/ui/link-toolbar.tsx +206 -0
  159. package/frontend/src/components/ui/list-toolbar-button.tsx +204 -0
  160. package/frontend/src/components/ui/mark-toolbar-button.tsx +19 -0
  161. package/frontend/src/components/ui/media-audio-node-static.tsx +17 -0
  162. package/frontend/src/components/ui/media-audio-node.tsx +39 -0
  163. package/frontend/src/components/ui/media-embed-node.tsx +136 -0
  164. package/frontend/src/components/ui/media-file-node-static.tsx +29 -0
  165. package/frontend/src/components/ui/media-file-node.tsx +47 -0
  166. package/frontend/src/components/ui/media-image-node-static.tsx +39 -0
  167. package/frontend/src/components/ui/media-image-node.tsx +80 -0
  168. package/frontend/src/components/ui/media-placeholder-node.tsx +249 -0
  169. package/frontend/src/components/ui/media-preview-dialog.tsx +152 -0
  170. package/frontend/src/components/ui/media-toolbar-button.tsx +225 -0
  171. package/frontend/src/components/ui/media-toolbar.tsx +115 -0
  172. package/frontend/src/components/ui/media-upload-toast.tsx +66 -0
  173. package/frontend/src/components/ui/media-video-node-static.tsx +30 -0
  174. package/frontend/src/components/ui/media-video-node.tsx +121 -0
  175. package/frontend/src/components/ui/mention-node-static.tsx +36 -0
  176. package/frontend/src/components/ui/mention-node.tsx +194 -0
  177. package/frontend/src/components/ui/mode-toolbar-button.tsx +123 -0
  178. package/frontend/src/components/ui/more-toolbar-button.tsx +80 -0
  179. package/frontend/src/components/ui/paragraph-node-static.tsx +13 -0
  180. package/frontend/src/components/ui/paragraph-node.tsx +15 -0
  181. package/frontend/src/components/ui/popover.tsx +46 -0
  182. package/frontend/src/components/ui/resize-handle.tsx +87 -0
  183. package/frontend/src/components/ui/separator.tsx +28 -0
  184. package/frontend/src/components/ui/sheet.tsx +139 -0
  185. package/frontend/src/components/ui/sidebar.tsx +726 -0
  186. package/frontend/src/components/ui/skeleton.tsx +13 -0
  187. package/frontend/src/components/ui/slash-node.tsx +233 -0
  188. package/frontend/src/components/ui/sonner.tsx +38 -0
  189. package/frontend/src/components/ui/suggestion-node-static.tsx +35 -0
  190. package/frontend/src/components/ui/suggestion-node.tsx +162 -0
  191. package/frontend/src/components/ui/suggestion-toolbar-button.tsx +25 -0
  192. package/frontend/src/components/ui/table-icons.tsx +862 -0
  193. package/frontend/src/components/ui/table-node-static.tsx +98 -0
  194. package/frontend/src/components/ui/table-node.tsx +656 -0
  195. package/frontend/src/components/ui/table-toolbar-button.tsx +264 -0
  196. package/frontend/src/components/ui/toc-node-static.tsx +92 -0
  197. package/frontend/src/components/ui/toc-node.tsx +55 -0
  198. package/frontend/src/components/ui/toggle-node-static.tsx +18 -0
  199. package/frontend/src/components/ui/toggle-node.tsx +36 -0
  200. package/frontend/src/components/ui/toggle-toolbar-button.tsx +22 -0
  201. package/frontend/src/components/ui/toolbar.tsx +387 -0
  202. package/frontend/src/components/ui/tooltip.tsx +59 -0
  203. package/frontend/src/components/ui/turn-into-toolbar-button.tsx +188 -0
  204. package/frontend/src/hooks/use-debounce.ts +18 -0
  205. package/frontend/src/hooks/use-is-touch-device.ts +24 -0
  206. package/frontend/src/hooks/use-mobile.ts +19 -0
  207. package/frontend/src/hooks/use-mounted.ts +11 -0
  208. package/frontend/src/hooks/use-upload-file.ts +128 -0
  209. package/frontend/src/index.css +128 -0
  210. package/frontend/src/layout.tsx +42 -0
  211. package/frontend/src/lib/markdown-joiner-transform.ts +239 -0
  212. package/frontend/src/lib/orpc.ts +13 -0
  213. package/frontend/src/lib/uploadthing.ts +19 -0
  214. package/frontend/src/lib/utils.ts +6 -0
  215. package/frontend/src/main.tsx +13 -0
  216. package/frontend/src/pages/editor.tsx +44 -0
  217. package/frontend/src/types/docs.d.ts +6 -0
  218. package/frontend/src/types/global.d.ts +9 -0
  219. package/frontend/src/types/router.d.ts +4 -0
  220. package/frontend/tsconfig.app.json +33 -0
  221. package/frontend/tsconfig.json +10 -0
  222. package/frontend/tsconfig.node.json +26 -0
  223. package/frontend/vite.config.ts +14 -0
  224. package/package.json +30 -0
  225. package/src/bin/doclific.ts +17 -0
  226. package/src/core/codebase.ts +39 -0
  227. package/src/core/docs.ts +90 -0
  228. package/src/core/git.ts +48 -0
  229. package/src/server/index.ts +55 -0
  230. package/src/server/router.ts +65 -0
  231. package/tsconfig.json +15 -0
@@ -0,0 +1,98 @@
1
+ import type { TEquationElement } from 'platejs';
2
+ import type { SlateElementProps } from 'platejs/static';
3
+
4
+ import { getEquationHtml } from '@platejs/math';
5
+ import { RadicalIcon } from 'lucide-react';
6
+ import { SlateElement } from 'platejs/static';
7
+
8
+ import { cn } from '@/lib/utils';
9
+
10
+ export function EquationElementStatic(
11
+ props: SlateElementProps<TEquationElement>
12
+ ) {
13
+ const { element } = props;
14
+
15
+ const html = getEquationHtml({
16
+ element,
17
+ options: {
18
+ displayMode: true,
19
+ errorColor: '#cc0000',
20
+ fleqn: false,
21
+ leqno: false,
22
+ macros: { '\\f': '#1f(#2)' },
23
+ output: 'htmlAndMathml',
24
+ strict: 'warn',
25
+ throwOnError: false,
26
+ trust: false,
27
+ },
28
+ });
29
+
30
+ return (
31
+ <SlateElement className="my-1" {...props}>
32
+ <div
33
+ className={cn(
34
+ 'group flex select-none items-center justify-center rounded-sm hover:bg-primary/10 data-[selected=true]:bg-primary/10',
35
+ element.texExpression.length === 0 ? 'bg-muted p-3 pr-9' : 'px-2 py-1'
36
+ )}
37
+ >
38
+ {element.texExpression.length > 0 ? (
39
+ <span
40
+ dangerouslySetInnerHTML={{
41
+ __html: html,
42
+ }}
43
+ />
44
+ ) : (
45
+ <div className="flex h-7 w-full items-center gap-2 whitespace-nowrap text-muted-foreground text-sm">
46
+ <RadicalIcon className="size-6 text-muted-foreground/80" />
47
+ <div>Add a Tex equation</div>
48
+ </div>
49
+ )}
50
+ </div>
51
+ {props.children}
52
+ </SlateElement>
53
+ );
54
+ }
55
+
56
+ export function InlineEquationElementStatic(
57
+ props: SlateElementProps<TEquationElement>
58
+ ) {
59
+ const html = getEquationHtml({
60
+ element: props.element,
61
+ options: {
62
+ displayMode: true,
63
+ errorColor: '#cc0000',
64
+ fleqn: false,
65
+ leqno: false,
66
+ macros: { '\\f': '#1f(#2)' },
67
+ output: 'htmlAndMathml',
68
+ strict: 'warn',
69
+ throwOnError: false,
70
+ trust: false,
71
+ },
72
+ });
73
+
74
+ return (
75
+ <SlateElement
76
+ {...props}
77
+ className="inline-block select-none rounded-sm [&_.katex-display]:my-0"
78
+ >
79
+ <div
80
+ className={cn(
81
+ 'after:-top-0.5 after:-left-1 after:absolute after:inset-0 after:z-1 after:h-[calc(100%)+4px] after:w-[calc(100%+8px)] after:rounded-sm after:content-[""]',
82
+ 'h-6',
83
+ props.element.texExpression.length === 0 &&
84
+ 'text-muted-foreground after:bg-neutral-500/10'
85
+ )}
86
+ >
87
+ <span
88
+ className={cn(
89
+ props.element.texExpression.length === 0 && 'hidden',
90
+ 'font-mono leading-none'
91
+ )}
92
+ dangerouslySetInnerHTML={{ __html: html }}
93
+ />
94
+ </div>
95
+ {props.children}
96
+ </SlateElement>
97
+ );
98
+ }
@@ -0,0 +1,235 @@
1
+ import React from 'react';
2
+ import TextareaAutosize, {
3
+ type TextareaAutosizeProps,
4
+ } from 'react-textarea-autosize';
5
+
6
+ import type { TEquationElement } from 'platejs';
7
+ import type { PlateElementProps } from 'platejs/react';
8
+
9
+ import { useEquationElement, useEquationInput } from '@platejs/math/react';
10
+ import { BlockSelectionPlugin } from '@platejs/selection/react';
11
+ import { CornerDownLeftIcon, RadicalIcon } from 'lucide-react';
12
+ import {
13
+ createPrimitiveComponent,
14
+ PlateElement,
15
+ useEditorRef,
16
+ useEditorSelector,
17
+ useElement,
18
+ useReadOnly,
19
+ useSelected,
20
+ } from 'platejs/react';
21
+
22
+ import { Button } from '@/components/ui/button';
23
+ import {
24
+ Popover,
25
+ PopoverContent,
26
+ PopoverTrigger,
27
+ } from '@/components/ui/popover';
28
+ import { cn } from '@/lib/utils';
29
+
30
+ export function EquationElement(props: PlateElementProps<TEquationElement>) {
31
+ const selected = useSelected();
32
+ const [open, setOpen] = React.useState(selected);
33
+ const katexRef = React.useRef<HTMLDivElement | null>(null);
34
+
35
+ useEquationElement({
36
+ element: props.element,
37
+ katexRef,
38
+ options: {
39
+ displayMode: true,
40
+ errorColor: '#cc0000',
41
+ fleqn: false,
42
+ leqno: false,
43
+ macros: { '\\f': '#1f(#2)' },
44
+ output: 'htmlAndMathml',
45
+ strict: 'warn',
46
+ throwOnError: false,
47
+ trust: false,
48
+ },
49
+ });
50
+
51
+ return (
52
+ <PlateElement className="my-1" {...props}>
53
+ <Popover open={open} onOpenChange={setOpen} modal={false}>
54
+ <PopoverTrigger asChild>
55
+ <div
56
+ className={cn(
57
+ 'group flex cursor-pointer select-none items-center justify-center rounded-sm hover:bg-primary/10 data-[selected=true]:bg-primary/10',
58
+ props.element.texExpression.length === 0
59
+ ? 'bg-muted p-3 pr-9'
60
+ : 'px-2 py-1'
61
+ )}
62
+ data-selected={selected}
63
+ contentEditable={false}
64
+ role="button"
65
+ >
66
+ {props.element.texExpression.length > 0 ? (
67
+ <span ref={katexRef} />
68
+ ) : (
69
+ <div className="flex h-7 w-full items-center gap-2 whitespace-nowrap text-muted-foreground text-sm">
70
+ <RadicalIcon className="size-6 text-muted-foreground/80" />
71
+ <div>Add a Tex equation</div>
72
+ </div>
73
+ )}
74
+ </div>
75
+ </PopoverTrigger>
76
+
77
+ <EquationPopoverContent
78
+ open={open}
79
+ placeholder={
80
+ 'f(x) = \\begin{cases}\n x^2, &\\quad x > 0 \\\\\n 0, &\\quad x = 0 \\\\\n -x^2, &\\quad x < 0\n\\end{cases}'
81
+ }
82
+ isInline={false}
83
+ setOpen={setOpen}
84
+ />
85
+ </Popover>
86
+
87
+ {props.children}
88
+ </PlateElement>
89
+ );
90
+ }
91
+
92
+ export function InlineEquationElement(
93
+ props: PlateElementProps<TEquationElement>
94
+ ) {
95
+ const element = props.element;
96
+ const katexRef = React.useRef<HTMLDivElement | null>(null);
97
+ const selected = useSelected();
98
+ const isCollapsed = useEditorSelector(
99
+ (editor) => editor.api.isCollapsed(),
100
+ []
101
+ );
102
+ const [open, setOpen] = React.useState(selected && isCollapsed);
103
+
104
+ React.useEffect(() => {
105
+ if (selected && isCollapsed) {
106
+ setOpen(true);
107
+ }
108
+ }, [selected, isCollapsed]);
109
+
110
+ useEquationElement({
111
+ element,
112
+ katexRef,
113
+ options: {
114
+ displayMode: true,
115
+ errorColor: '#cc0000',
116
+ fleqn: false,
117
+ leqno: false,
118
+ macros: { '\\f': '#1f(#2)' },
119
+ output: 'htmlAndMathml',
120
+ strict: 'warn',
121
+ throwOnError: false,
122
+ trust: false,
123
+ },
124
+ });
125
+
126
+ return (
127
+ <PlateElement
128
+ {...props}
129
+ className={cn(
130
+ 'mx-1 inline-block select-none rounded-sm [&_.katex-display]:my-0!'
131
+ )}
132
+ >
133
+ <Popover open={open} onOpenChange={setOpen} modal={false}>
134
+ <PopoverTrigger asChild>
135
+ <div
136
+ className={cn(
137
+ 'after:-top-0.5 after:-left-1 after:absolute after:inset-0 after:z-1 after:h-[calc(100%)+4px] after:w-[calc(100%+8px)] after:rounded-sm after:content-[""]',
138
+ 'h-6',
139
+ ((element.texExpression.length > 0 && open) || selected) &&
140
+ 'after:bg-brand/15',
141
+ element.texExpression.length === 0 &&
142
+ 'text-muted-foreground after:bg-neutral-500/10'
143
+ )}
144
+ contentEditable={false}
145
+ >
146
+ <span
147
+ ref={katexRef}
148
+ className={cn(
149
+ element.texExpression.length === 0 && 'hidden',
150
+ 'font-mono leading-none'
151
+ )}
152
+ />
153
+ {element.texExpression.length === 0 && (
154
+ <span>
155
+ <RadicalIcon className="mr-1 inline-block h-[19px] w-4 py-[1.5px] align-text-bottom" />
156
+ New equation
157
+ </span>
158
+ )}
159
+ </div>
160
+ </PopoverTrigger>
161
+
162
+ <EquationPopoverContent
163
+ className="my-auto"
164
+ open={open}
165
+ placeholder="E = mc^2"
166
+ setOpen={setOpen}
167
+ isInline
168
+ />
169
+ </Popover>
170
+
171
+ {props.children}
172
+ </PlateElement>
173
+ );
174
+ }
175
+
176
+ const EquationInput = createPrimitiveComponent(TextareaAutosize)({
177
+ propsHook: useEquationInput,
178
+ });
179
+
180
+ const EquationPopoverContent = ({
181
+ className,
182
+ isInline,
183
+ open,
184
+ setOpen,
185
+ ...props
186
+ }: {
187
+ isInline: boolean;
188
+ open: boolean;
189
+ setOpen: (open: boolean) => void;
190
+ } & TextareaAutosizeProps) => {
191
+ const editor = useEditorRef();
192
+ const readOnly = useReadOnly();
193
+ const element = useElement<TEquationElement>();
194
+
195
+ React.useEffect(() => {
196
+ if (isInline && open) {
197
+ setOpen(true);
198
+ }
199
+ }, [isInline, open, setOpen]);
200
+
201
+ if (readOnly) return null;
202
+
203
+ const onClose = () => {
204
+ setOpen(false);
205
+
206
+ if (isInline) {
207
+ editor.tf.select(element, { focus: true, next: true });
208
+ } else {
209
+ editor
210
+ .getApi(BlockSelectionPlugin)
211
+ .blockSelection.set(element.id as string);
212
+ }
213
+ };
214
+
215
+ return (
216
+ <PopoverContent
217
+ className="flex gap-2"
218
+ onEscapeKeyDown={(e) => {
219
+ e.preventDefault();
220
+ }}
221
+ contentEditable={false}
222
+ >
223
+ <EquationInput
224
+ className={cn('max-h-[50vh] grow resize-none p-2 text-sm', className)}
225
+ state={{ isInline, open, onClose }}
226
+ autoFocus
227
+ {...props}
228
+ />
229
+
230
+ <Button variant="secondary" className="px-3" onClick={onClose}>
231
+ Done <CornerDownLeftIcon className="size-3.5" />
232
+ </Button>
233
+ </PopoverContent>
234
+ );
235
+ };
@@ -0,0 +1,25 @@
1
+
2
+
3
+ import { insertInlineEquation } from '@platejs/math';
4
+ import { RadicalIcon } from 'lucide-react';
5
+ import { useEditorRef } from 'platejs/react';
6
+
7
+ import { ToolbarButton } from './toolbar';
8
+
9
+ export function InlineEquationToolbarButton(
10
+ props: React.ComponentProps<typeof ToolbarButton>
11
+ ) {
12
+ const editor = useEditorRef();
13
+
14
+ return (
15
+ <ToolbarButton
16
+ {...props}
17
+ onClick={() => {
18
+ insertInlineEquation(editor);
19
+ }}
20
+ tooltip="Mark as equation"
21
+ >
22
+ <RadicalIcon />
23
+ </ToolbarButton>
24
+ );
25
+ }
@@ -0,0 +1,36 @@
1
+
2
+
3
+ import type { TExcalidrawElement } from '@platejs/excalidraw';
4
+ import type { PlateElementProps } from 'platejs/react';
5
+
6
+ import { useExcalidrawElement } from '@platejs/excalidraw/react';
7
+ import { PlateElement, useReadOnly } from 'platejs/react';
8
+
9
+ import '@excalidraw/excalidraw/index.css';
10
+
11
+ export function ExcalidrawElement(
12
+ props: PlateElementProps<TExcalidrawElement>
13
+ ) {
14
+ const { children, element } = props;
15
+ const readOnly = useReadOnly();
16
+
17
+ const { Excalidraw, excalidrawProps } = useExcalidrawElement({
18
+ element,
19
+ });
20
+
21
+ return (
22
+ <PlateElement {...props}>
23
+ <div contentEditable={false}>
24
+ <div className="mx-auto aspect-video h-[600px] w-[min(100%,600px)] overflow-hidden rounded-sm border">
25
+ {Excalidraw && (
26
+ <Excalidraw
27
+ {...(excalidrawProps as any)}
28
+ viewModeEnabled={readOnly}
29
+ />
30
+ )}
31
+ </div>
32
+ </div>
33
+ {children}
34
+ </PlateElement>
35
+ );
36
+ }
@@ -0,0 +1,174 @@
1
+ import React from 'react';
2
+
3
+ import type { DropdownMenuProps } from '@radix-ui/react-dropdown-menu';
4
+
5
+ import { MarkdownPlugin } from '@platejs/markdown';
6
+ import { ArrowDownToLineIcon } from 'lucide-react';
7
+ import { createSlateEditor } from 'platejs';
8
+ import { useEditorRef } from 'platejs/react';
9
+ import { serializeHtml } from 'platejs/static';
10
+
11
+ import {
12
+ DropdownMenu,
13
+ DropdownMenuContent,
14
+ DropdownMenuGroup,
15
+ DropdownMenuItem,
16
+ DropdownMenuTrigger,
17
+ } from '@/components/ui/dropdown-menu';
18
+ import { BaseEditorKit } from '@/components/editor/editor-base-kit';
19
+
20
+ import { EditorStatic } from './editor-static';
21
+ import { ToolbarButton } from './toolbar';
22
+
23
+ const siteUrl = 'https://platejs.org';
24
+
25
+ export function ExportToolbarButton(props: DropdownMenuProps) {
26
+ const editor = useEditorRef();
27
+ const [open, setOpen] = React.useState(false);
28
+
29
+ const getCanvas = async () => {
30
+ const { default: html2canvas } = await import('html2canvas-pro');
31
+
32
+ const style = document.createElement('style');
33
+ document.head.append(style);
34
+
35
+ const canvas = await html2canvas(editor.api.toDOMNode(editor)!, {
36
+ onclone: (document: Document) => {
37
+ const editorElement = document.querySelector(
38
+ '[contenteditable="true"]'
39
+ );
40
+ if (editorElement) {
41
+ Array.from(editorElement.querySelectorAll('*')).forEach((element) => {
42
+ const existingStyle = element.getAttribute('style') || '';
43
+ element.setAttribute(
44
+ 'style',
45
+ `${existingStyle}; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif !important`
46
+ );
47
+ });
48
+ }
49
+ },
50
+ });
51
+ style.remove();
52
+
53
+ return canvas;
54
+ };
55
+
56
+ const downloadFile = async (url: string, filename: string) => {
57
+ const response = await fetch(url);
58
+
59
+ const blob = await response.blob();
60
+ const blobUrl = window.URL.createObjectURL(blob);
61
+
62
+ const link = document.createElement('a');
63
+ link.href = blobUrl;
64
+ link.download = filename;
65
+ document.body.append(link);
66
+ link.click();
67
+ link.remove();
68
+
69
+ // Clean up the blob URL
70
+ window.URL.revokeObjectURL(blobUrl);
71
+ };
72
+
73
+ const exportToPdf = async () => {
74
+ const canvas = await getCanvas();
75
+
76
+ const PDFLib = await import('pdf-lib');
77
+ const pdfDoc = await PDFLib.PDFDocument.create();
78
+ const page = pdfDoc.addPage([canvas.width, canvas.height]);
79
+ const imageEmbed = await pdfDoc.embedPng(canvas.toDataURL('PNG'));
80
+ const { height, width } = imageEmbed.scale(1);
81
+ page.drawImage(imageEmbed, {
82
+ height,
83
+ width,
84
+ x: 0,
85
+ y: 0,
86
+ });
87
+ const pdfBase64 = await pdfDoc.saveAsBase64({ dataUri: true });
88
+
89
+ await downloadFile(pdfBase64, 'plate.pdf');
90
+ };
91
+
92
+ const exportToImage = async () => {
93
+ const canvas = await getCanvas();
94
+ await downloadFile(canvas.toDataURL('image/png'), 'plate.png');
95
+ };
96
+
97
+ const exportToHtml = async () => {
98
+ const editorStatic = createSlateEditor({
99
+ plugins: BaseEditorKit,
100
+ value: editor.children,
101
+ });
102
+
103
+ const editorHtml = await serializeHtml(editorStatic, {
104
+ editorComponent: EditorStatic,
105
+ props: { style: { padding: '0 calc(50% - 350px)', paddingBottom: '' } },
106
+ });
107
+
108
+ const tailwindCss = `<link rel="stylesheet" href="${siteUrl}/tailwind.css">`;
109
+ const katexCss = `<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.18/dist/katex.css" integrity="sha384-9PvLvaiSKCPkFKB1ZsEoTjgnJn+O3KvEwtsz37/XrkYft3DTk2gHdYvd9oWgW3tV" crossorigin="anonymous">`;
110
+
111
+ const html = `<!DOCTYPE html>
112
+ <html lang="en">
113
+ <head>
114
+ <meta charset="utf-8" />
115
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
116
+ <meta name="color-scheme" content="light dark" />
117
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
118
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
119
+ <link
120
+ href="https://fonts.googleapis.com/css2?family=Inter:wght@400..700&family=JetBrains+Mono:wght@400..700&display=swap"
121
+ rel="stylesheet"
122
+ />
123
+ ${tailwindCss}
124
+ ${katexCss}
125
+ <style>
126
+ :root {
127
+ --font-sans: 'Inter', 'Inter Fallback';
128
+ --font-mono: 'JetBrains Mono', 'JetBrains Mono Fallback';
129
+ }
130
+ </style>
131
+ </head>
132
+ <body>
133
+ ${editorHtml}
134
+ </body>
135
+ </html>`;
136
+
137
+ const url = `data:text/html;charset=utf-8,${encodeURIComponent(html)}`;
138
+
139
+ await downloadFile(url, 'plate.html');
140
+ };
141
+
142
+ const exportToMarkdown = async () => {
143
+ const md = editor.getApi(MarkdownPlugin).markdown.serialize();
144
+ const url = `data:text/markdown;charset=utf-8,${encodeURIComponent(md)}`;
145
+ await downloadFile(url, 'plate.md');
146
+ };
147
+
148
+ return (
149
+ <DropdownMenu open={open} onOpenChange={setOpen} modal={false} {...props}>
150
+ <DropdownMenuTrigger asChild>
151
+ <ToolbarButton pressed={open} tooltip="Export" isDropdown>
152
+ <ArrowDownToLineIcon className="size-4" />
153
+ </ToolbarButton>
154
+ </DropdownMenuTrigger>
155
+
156
+ <DropdownMenuContent align="start">
157
+ <DropdownMenuGroup>
158
+ <DropdownMenuItem onSelect={exportToHtml}>
159
+ Export as HTML
160
+ </DropdownMenuItem>
161
+ <DropdownMenuItem onSelect={exportToPdf}>
162
+ Export as PDF
163
+ </DropdownMenuItem>
164
+ <DropdownMenuItem onSelect={exportToImage}>
165
+ Export as Image
166
+ </DropdownMenuItem>
167
+ <DropdownMenuItem onSelect={exportToMarkdown}>
168
+ Export as Markdown
169
+ </DropdownMenuItem>
170
+ </DropdownMenuGroup>
171
+ </DropdownMenuContent>
172
+ </DropdownMenu>
173
+ );
174
+ }