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,287 @@
1
+ import React from 'react';
2
+
3
+ import { formatCodeBlock, isLangSupported } from '@platejs/code-block';
4
+ import { BracesIcon, Check, CheckIcon, CopyIcon } from 'lucide-react';
5
+ import { type TCodeBlockElement, type TCodeSyntaxLeaf, NodeApi } from 'platejs';
6
+ import {
7
+ type PlateElementProps,
8
+ type PlateLeafProps,
9
+ PlateElement,
10
+ PlateLeaf,
11
+ } from 'platejs/react';
12
+ import { useEditorRef, useElement, useReadOnly } from 'platejs/react';
13
+
14
+ import { Button } from '@/components/ui/button';
15
+ import {
16
+ Command,
17
+ CommandEmpty,
18
+ CommandGroup,
19
+ CommandInput,
20
+ CommandItem,
21
+ CommandList,
22
+ } from '@/components/ui/command';
23
+ import {
24
+ Popover,
25
+ PopoverContent,
26
+ PopoverTrigger,
27
+ } from '@/components/ui/popover';
28
+ import { cn } from '@/lib/utils';
29
+
30
+ export function CodeBlockElement(props: PlateElementProps<TCodeBlockElement>) {
31
+ const { editor, element } = props;
32
+
33
+ return (
34
+ <PlateElement
35
+ className="py-1 **:[.hljs-addition]:bg-[#f0fff4] **:[.hljs-addition]:text-[#22863a] dark:**:[.hljs-addition]:bg-[#3c5743] dark:**:[.hljs-addition]:text-[#ceead5] **:[.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable]:text-[#005cc5] dark:**:[.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable]:text-[#6596cf] **:[.hljs-built\\\\_in,.hljs-symbol]:text-[#e36209] dark:**:[.hljs-built\\\\_in,.hljs-symbol]:text-[#c3854e] **:[.hljs-bullet]:text-[#735c0f] **:[.hljs-comment,.hljs-code,.hljs-formula]:text-[#6a737d] dark:**:[.hljs-comment,.hljs-code,.hljs-formula]:text-[#6a737d] **:[.hljs-deletion]:bg-[#ffeef0] **:[.hljs-deletion]:text-[#b31d28] dark:**:[.hljs-deletion]:bg-[#473235] dark:**:[.hljs-deletion]:text-[#e7c7cb] **:[.hljs-emphasis]:italic **:[.hljs-keyword,.hljs-doctag,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language\\\\_]:text-[#d73a49] dark:**:[.hljs-keyword,.hljs-doctag,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language\\\\_]:text-[#ee6960] **:[.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo]:text-[#22863a] dark:**:[.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo]:text-[#36a84f] **:[.hljs-regexp,.hljs-string,.hljs-meta_.hljs-string]:text-[#032f62] dark:**:[.hljs-regexp,.hljs-string,.hljs-meta_.hljs-string]:text-[#3593ff] **:[.hljs-section]:font-bold **:[.hljs-section]:text-[#005cc5] dark:**:[.hljs-section]:text-[#61a5f2] **:[.hljs-strong]:font-bold **:[.hljs-title,.hljs-title.class\\\\_,.hljs-title.class\\\\_.inherited\\\\_\\\\_,.hljs-title.function\\\\_]:text-[#6f42c1] dark:**:[.hljs-title,.hljs-title.class\\\\_,.hljs-title.class\\\\_.inherited\\\\_\\\\_,.hljs-title.function\\\\_]:text-[#a77bfa]"
36
+ {...props}
37
+ >
38
+ <div className="relative rounded-md bg-muted/50">
39
+ <pre className="overflow-x-auto p-8 pr-4 font-mono text-sm leading-[normal] [tab-size:2] print:break-inside-avoid">
40
+ <code>{props.children}</code>
41
+ </pre>
42
+
43
+ <div
44
+ className="absolute top-1 right-1 z-10 flex select-none gap-0.5"
45
+ contentEditable={false}
46
+ >
47
+ {isLangSupported(element.lang) && (
48
+ <Button
49
+ size="icon"
50
+ variant="ghost"
51
+ className="size-6 text-xs"
52
+ onClick={() => formatCodeBlock(editor, { element })}
53
+ title="Format code"
54
+ >
55
+ <BracesIcon className="!size-3.5 text-muted-foreground" />
56
+ </Button>
57
+ )}
58
+
59
+ <CodeBlockCombobox />
60
+
61
+ <CopyButton
62
+ size="icon"
63
+ variant="ghost"
64
+ className="size-6 gap-1 text-muted-foreground text-xs"
65
+ value={() => NodeApi.string(element)}
66
+ />
67
+ </div>
68
+ </div>
69
+ </PlateElement>
70
+ );
71
+ }
72
+
73
+ function CodeBlockCombobox() {
74
+ const [open, setOpen] = React.useState(false);
75
+ const readOnly = useReadOnly();
76
+ const editor = useEditorRef();
77
+ const element = useElement<TCodeBlockElement>();
78
+ const value = element.lang || 'plaintext';
79
+ const [searchValue, setSearchValue] = React.useState('');
80
+
81
+ const items = React.useMemo(
82
+ () =>
83
+ languages.filter(
84
+ (language) =>
85
+ !searchValue ||
86
+ language.label.toLowerCase().includes(searchValue.toLowerCase())
87
+ ),
88
+ [searchValue]
89
+ );
90
+
91
+ if (readOnly) return null;
92
+
93
+ return (
94
+ <Popover open={open} onOpenChange={setOpen}>
95
+ <PopoverTrigger asChild>
96
+ <Button
97
+ size="sm"
98
+ variant="ghost"
99
+ className="h-6 select-none justify-between gap-1 px-2 text-muted-foreground text-xs"
100
+ aria-expanded={open}
101
+ role="combobox"
102
+ >
103
+ {languages.find((language) => language.value === value)?.label ??
104
+ 'Plain Text'}
105
+ </Button>
106
+ </PopoverTrigger>
107
+ <PopoverContent
108
+ className="w-[200px] p-0"
109
+ onCloseAutoFocus={() => setSearchValue('')}
110
+ >
111
+ <Command shouldFilter={false}>
112
+ <CommandInput
113
+ className="h-9"
114
+ value={searchValue}
115
+ onValueChange={(value) => setSearchValue(value)}
116
+ placeholder="Search language..."
117
+ />
118
+ <CommandEmpty>No language found.</CommandEmpty>
119
+
120
+ <CommandList className="h-[344px] overflow-y-auto">
121
+ <CommandGroup>
122
+ {items.map((language) => (
123
+ <CommandItem
124
+ key={language.label}
125
+ className="cursor-pointer"
126
+ value={language.value}
127
+ onSelect={(value) => {
128
+ editor.tf.setNodes<TCodeBlockElement>(
129
+ { lang: value },
130
+ { at: element }
131
+ );
132
+ setSearchValue(value);
133
+ setOpen(false);
134
+ }}
135
+ >
136
+ <Check
137
+ className={cn(
138
+ value === language.value ? 'opacity-100' : 'opacity-0'
139
+ )}
140
+ />
141
+ {language.label}
142
+ </CommandItem>
143
+ ))}
144
+ </CommandGroup>
145
+ </CommandList>
146
+ </Command>
147
+ </PopoverContent>
148
+ </Popover>
149
+ );
150
+ }
151
+
152
+ function CopyButton({
153
+ value,
154
+ ...props
155
+ }: { value: (() => string) | string } & Omit<
156
+ React.ComponentProps<typeof Button>,
157
+ 'value'
158
+ >) {
159
+ const [hasCopied, setHasCopied] = React.useState(false);
160
+
161
+ React.useEffect(() => {
162
+ setTimeout(() => {
163
+ setHasCopied(false);
164
+ }, 2000);
165
+ }, [hasCopied]);
166
+
167
+ return (
168
+ <Button
169
+ onClick={() => {
170
+ void navigator.clipboard.writeText(
171
+ typeof value === 'function' ? value() : value
172
+ );
173
+ setHasCopied(true);
174
+ }}
175
+ {...props}
176
+ >
177
+ <span className="sr-only">Copy</span>
178
+ {hasCopied ? (
179
+ <CheckIcon className="!size-3" />
180
+ ) : (
181
+ <CopyIcon className="!size-3" />
182
+ )}
183
+ </Button>
184
+ );
185
+ }
186
+
187
+ export function CodeLineElement(props: PlateElementProps) {
188
+ return <PlateElement {...props} />;
189
+ }
190
+
191
+ export function CodeSyntaxLeaf(props: PlateLeafProps<TCodeSyntaxLeaf>) {
192
+ const tokenClassName = props.leaf.className as string;
193
+
194
+ return <PlateLeaf className={tokenClassName} {...props} />;
195
+ }
196
+
197
+ const languages: { label: string; value: string }[] = [
198
+ { label: 'Auto', value: 'auto' },
199
+ { label: 'Plain Text', value: 'plaintext' },
200
+ { label: 'ABAP', value: 'abap' },
201
+ { label: 'Agda', value: 'agda' },
202
+ { label: 'Arduino', value: 'arduino' },
203
+ { label: 'ASCII Art', value: 'ascii' },
204
+ { label: 'Assembly', value: 'x86asm' },
205
+ { label: 'Bash', value: 'bash' },
206
+ { label: 'BASIC', value: 'basic' },
207
+ { label: 'BNF', value: 'bnf' },
208
+ { label: 'C', value: 'c' },
209
+ { label: 'C#', value: 'csharp' },
210
+ { label: 'C++', value: 'cpp' },
211
+ { label: 'Clojure', value: 'clojure' },
212
+ { label: 'CoffeeScript', value: 'coffeescript' },
213
+ { label: 'Coq', value: 'coq' },
214
+ { label: 'CSS', value: 'css' },
215
+ { label: 'Dart', value: 'dart' },
216
+ { label: 'Dhall', value: 'dhall' },
217
+ { label: 'Diff', value: 'diff' },
218
+ { label: 'Docker', value: 'dockerfile' },
219
+ { label: 'EBNF', value: 'ebnf' },
220
+ { label: 'Elixir', value: 'elixir' },
221
+ { label: 'Elm', value: 'elm' },
222
+ { label: 'Erlang', value: 'erlang' },
223
+ { label: 'F#', value: 'fsharp' },
224
+ { label: 'Flow', value: 'flow' },
225
+ { label: 'Fortran', value: 'fortran' },
226
+ { label: 'Gherkin', value: 'gherkin' },
227
+ { label: 'GLSL', value: 'glsl' },
228
+ { label: 'Go', value: 'go' },
229
+ { label: 'GraphQL', value: 'graphql' },
230
+ { label: 'Groovy', value: 'groovy' },
231
+ { label: 'Haskell', value: 'haskell' },
232
+ { label: 'HCL', value: 'hcl' },
233
+ { label: 'HTML', value: 'html' },
234
+ { label: 'Idris', value: 'idris' },
235
+ { label: 'Java', value: 'java' },
236
+ { label: 'JavaScript', value: 'javascript' },
237
+ { label: 'JSON', value: 'json' },
238
+ { label: 'Julia', value: 'julia' },
239
+ { label: 'Kotlin', value: 'kotlin' },
240
+ { label: 'LaTeX', value: 'latex' },
241
+ { label: 'Less', value: 'less' },
242
+ { label: 'Lisp', value: 'lisp' },
243
+ { label: 'LiveScript', value: 'livescript' },
244
+ { label: 'LLVM IR', value: 'llvm' },
245
+ { label: 'Lua', value: 'lua' },
246
+ { label: 'Makefile', value: 'makefile' },
247
+ { label: 'Markdown', value: 'markdown' },
248
+ { label: 'Markup', value: 'markup' },
249
+ { label: 'MATLAB', value: 'matlab' },
250
+ { label: 'Mathematica', value: 'mathematica' },
251
+ { label: 'Mermaid', value: 'mermaid' },
252
+ { label: 'Nix', value: 'nix' },
253
+ { label: 'Notion Formula', value: 'notion' },
254
+ { label: 'Objective-C', value: 'objectivec' },
255
+ { label: 'OCaml', value: 'ocaml' },
256
+ { label: 'Pascal', value: 'pascal' },
257
+ { label: 'Perl', value: 'perl' },
258
+ { label: 'PHP', value: 'php' },
259
+ { label: 'PowerShell', value: 'powershell' },
260
+ { label: 'Prolog', value: 'prolog' },
261
+ { label: 'Protocol Buffers', value: 'protobuf' },
262
+ { label: 'PureScript', value: 'purescript' },
263
+ { label: 'Python', value: 'python' },
264
+ { label: 'R', value: 'r' },
265
+ { label: 'Racket', value: 'racket' },
266
+ { label: 'Reason', value: 'reasonml' },
267
+ { label: 'Ruby', value: 'ruby' },
268
+ { label: 'Rust', value: 'rust' },
269
+ { label: 'Sass', value: 'scss' },
270
+ { label: 'Scala', value: 'scala' },
271
+ { label: 'Scheme', value: 'scheme' },
272
+ { label: 'SCSS', value: 'scss' },
273
+ { label: 'Shell', value: 'shell' },
274
+ { label: 'Smalltalk', value: 'smalltalk' },
275
+ { label: 'Solidity', value: 'solidity' },
276
+ { label: 'SQL', value: 'sql' },
277
+ { label: 'Swift', value: 'swift' },
278
+ { label: 'TOML', value: 'toml' },
279
+ { label: 'TypeScript', value: 'typescript' },
280
+ { label: 'VB.Net', value: 'vbnet' },
281
+ { label: 'Verilog', value: 'verilog' },
282
+ { label: 'VHDL', value: 'vhdl' },
283
+ { label: 'Visual Basic', value: 'vbnet' },
284
+ { label: 'WebAssembly', value: 'wasm' },
285
+ { label: 'XML', value: 'xml' },
286
+ { label: 'YAML', value: 'yaml' },
287
+ ];
@@ -0,0 +1,15 @@
1
+ import type { SlateLeafProps } from 'platejs/static';
2
+
3
+ import { SlateLeaf } from 'platejs/static';
4
+
5
+ export function CodeLeafStatic(props: SlateLeafProps) {
6
+ return (
7
+ <SlateLeaf
8
+ {...props}
9
+ as="code"
10
+ className="whitespace-pre-wrap rounded-md bg-muted px-[0.3em] py-[0.2em] font-mono text-sm"
11
+ >
12
+ {props.children}
13
+ </SlateLeaf>
14
+ );
15
+ }
@@ -0,0 +1,17 @@
1
+
2
+
3
+ import type { PlateLeafProps } from 'platejs/react';
4
+
5
+ import { PlateLeaf } from 'platejs/react';
6
+
7
+ export function CodeLeaf(props: PlateLeafProps) {
8
+ return (
9
+ <PlateLeaf
10
+ {...props}
11
+ as="code"
12
+ className="whitespace-pre-wrap rounded-md bg-muted px-[0.3em] py-[0.2em] font-mono text-sm"
13
+ >
14
+ {props.children}
15
+ </PlateLeaf>
16
+ );
17
+ }
@@ -0,0 +1,237 @@
1
+ import type { PlateElementProps } from 'platejs/react';
2
+ import type { CodebaseSnippetElementType } from '@/components/editor/plugins/codebase-kit';
3
+ import { useQuery } from '@tanstack/react-query';
4
+ import { orpcTs } from '@/lib/orpc';
5
+ import { createHighlighter, type Highlighter } from 'shiki';
6
+ import { useEffect, useState } from 'react';
7
+ import { useTheme } from '@/components/theme-provider';
8
+ import { Copy, Settings } from 'lucide-react';
9
+ import { FileSelector } from './file-selector';
10
+ import { useEditorRef } from 'platejs/react';
11
+ import { toast } from 'sonner';
12
+
13
+ export function CodebaseSnippetElement({ attributes, children, element }: PlateElementProps<CodebaseSnippetElementType>) {
14
+ const { theme } = useTheme();
15
+ const editor = useEditorRef();
16
+ const [highlighter, setHighlighter] = useState<Highlighter | null>(null);
17
+ const [highlightedCode, setHighlightedCode] = useState<string>('');
18
+ const [fileSelectorOpen, setFileSelectorOpen] = useState(false);
19
+
20
+ const fileContents = useQuery({
21
+ ...orpcTs.codebase.getFileContents.queryOptions({
22
+ input: {
23
+ filePath: element.filePath || '',
24
+ },
25
+
26
+ }),
27
+ enabled: !!element.filePath,
28
+ })
29
+
30
+ const language = getLanguageFromPath(element.filePath);
31
+
32
+ useEffect(() => {
33
+ let mounted = true;
34
+
35
+ const initHighlighter = async () => {
36
+ try {
37
+ const h = await createHighlighter({
38
+ themes: ['github-dark', 'github-light'],
39
+ langs: [language, 'text'],
40
+ });
41
+ if (mounted) {
42
+ setHighlighter(h);
43
+ }
44
+ } catch (error) {
45
+ console.error('Failed to create highlighter:', error);
46
+ }
47
+ };
48
+
49
+ initHighlighter();
50
+
51
+ return () => {
52
+ mounted = false;
53
+ };
54
+ }, [language]);
55
+
56
+ useEffect(() => {
57
+ if (!highlighter || !fileContents.data) return;
58
+
59
+ const highlightCode = async () => {
60
+ const code = fileContents.data?.contents || '';
61
+ const startLine = element.lineStart ? parseInt(element.lineStart) : 1;
62
+ let endLine: number | undefined = undefined;
63
+ if (element.lineEnd) {
64
+ endLine = parseInt(element.lineEnd);
65
+ } else {
66
+ // If lineEnd is not specified, use the total number of lines as the maximum
67
+ endLine = code.split('\n').length;
68
+ }
69
+
70
+ let codeToHighlight = code;
71
+ let actualStartLine = startLine;
72
+ if (startLine !== undefined && endLine !== undefined) {
73
+ const lines = code.split('\n');
74
+ codeToHighlight = lines.slice(startLine - 1, endLine).join('\n');
75
+ actualStartLine = startLine;
76
+ } else if (startLine !== undefined) {
77
+ const lines = code.split('\n');
78
+ codeToHighlight = lines.slice(startLine - 1).join('\n');
79
+ actualStartLine = startLine;
80
+ }
81
+
82
+ try {
83
+ const html = highlighter.codeToHtml(codeToHighlight, {
84
+ lang: language,
85
+ theme: theme === 'dark' ? 'github-dark' : 'github-light',
86
+ });
87
+
88
+ // Extract the inner content from Shiki's HTML (it wraps in pre>code)
89
+ const tempDiv = document.createElement('div');
90
+ tempDiv.innerHTML = html;
91
+ const codeElement = tempDiv.querySelector('pre code') || tempDiv.querySelector('code');
92
+ const codeContent = codeElement ? codeElement.innerHTML : html;
93
+
94
+ // Split code into lines
95
+ const lines = codeToHighlight.split('\n');
96
+ const highlightedLines = codeContent.split('\n');
97
+
98
+ // Create line-by-line structure with numbers
99
+ const lineItems = lines.map((_, index) => {
100
+ const lineNumber = actualStartLine + index;
101
+ const highlightedLine = highlightedLines[index] || '';
102
+ return `<div class="flex gap-4 hover:bg-muted/50 transition-colors"><span class="inline-block w-10 text-right text-muted-foreground select-none flex-shrink-0">${lineNumber}</span><code class="flex-1">${highlightedLine || '\n'}</code></div>`;
103
+ }).join('');
104
+
105
+ // Wrap the highlighted code with line numbers
106
+ const codeWithLineNumbers = `<div class="overflow-x-auto"><div class="p-4 text-sm leading-normal font-mono m-0">${lineItems}</div></div>`;
107
+
108
+ setHighlightedCode(codeWithLineNumbers);
109
+ } catch (error) {
110
+ console.error('Failed to highlight code:', error);
111
+ const lines = codeToHighlight.split('\n');
112
+ const lineItems = lines.map((line, index) => {
113
+ const lineNumber = actualStartLine + index;
114
+ return `<div class="flex gap-4 hover:bg-muted/50 transition-colors"><span class="inline-block w-10 text-right text-muted-foreground select-none flex-shrink-0">${lineNumber}</span><code class="flex-1">${line || '\n'}</code></div>`;
115
+ }).join('');
116
+
117
+ const fallbackHtml = `<div class="overflow-x-auto"><pre class="p-4 text-sm leading-normal font-mono m-0">${lineItems}</pre></div>`;
118
+ setHighlightedCode(fallbackHtml);
119
+ }
120
+ };
121
+
122
+ highlightCode();
123
+ }, [highlighter, fileContents.data, language, theme, element.lineStart, element.lineEnd]);
124
+
125
+ const handleCopy = () => {
126
+ void navigator.clipboard.writeText(fileContents.data?.contents || '');
127
+ toast.success('Copied to clipboard');
128
+ }
129
+
130
+ return (
131
+ <div contentEditable={false} {...attributes} className="codebase-snippet my-4 rounded-lg border overflow-hidden">
132
+ <div className="bg-muted px-4 py-2 border-b flex justify-between items-center">
133
+ <div className='flex items-center gap-2'>
134
+ <a href={`vscode://file/${fileContents.data?.fullPath || ''}:${element.lineStart}`}>
135
+ <p className="text-sm font-medium text-muted-foreground">{element.filePath}</p>
136
+ </a>
137
+ <button
138
+ className='bg-transparent border-none p-0 m-0 cursor-pointer'
139
+ onClick={() => setFileSelectorOpen(true)}
140
+ >
141
+ <Settings className='size-4 text-muted-foreground' />
142
+ </button>
143
+ </div>
144
+ <button className='bg-transparent border-none p-0 m-0 cursor-pointer' onClick={handleCopy}>
145
+ <Copy className='size-4 text-muted-foreground' />
146
+ </button>
147
+ </div>
148
+ <div className="relative">
149
+ {fileContents.isLoading ? (
150
+ <div className="p-4 text-sm text-muted-foreground">Loading code...</div>
151
+ ) : highlightedCode ? (
152
+ <div
153
+ dangerouslySetInnerHTML={{ __html: highlightedCode }}
154
+ />
155
+ ) : (
156
+ <div className="p-4 text-sm text-muted-foreground">No code to display</div>
157
+ )}
158
+ </div>
159
+ <div className="bg-muted px-4 py-2 border-t flex justify-between items-center">
160
+ <p className="text-sm font-medium text-muted-foreground">Line {element.lineStart} - {element.lineEnd}</p>
161
+ </div>
162
+ {children}
163
+ <FileSelector
164
+ open={fileSelectorOpen}
165
+ onClose={() => setFileSelectorOpen(false)}
166
+ onSelect={(filePath, startLine, endLine) => {
167
+ editor.tf.setNodes(
168
+ {
169
+ filePath,
170
+ lineStart: String(startLine),
171
+ lineEnd: String(endLine),
172
+ },
173
+ { at: element }
174
+ );
175
+ }}
176
+ currentFilePath={element.filePath}
177
+ currentStartLine={element.lineStart}
178
+ currentEndLine={element.lineEnd}
179
+ />
180
+ </div>
181
+ )
182
+ }
183
+
184
+ function getLanguageFromPath(path?: string) {
185
+ if (!path) return 'text';
186
+
187
+ const ext = path.split('.').pop();
188
+
189
+ switch (ext) {
190
+ case 'ts':
191
+ return 'ts';
192
+ case 'tsx':
193
+ return 'tsx';
194
+ case 'jsx':
195
+ return 'jsx';
196
+ case 'js':
197
+ return 'javascript';
198
+ case 'json':
199
+ return 'json';
200
+ case 'go':
201
+ return 'go';
202
+ case 'java':
203
+ return 'java';
204
+ case 'kt':
205
+ return 'kotlin';
206
+ case 'php':
207
+ return 'php';
208
+ case 'rb':
209
+ return 'ruby';
210
+ case 'rs':
211
+ return 'rust';
212
+ case 'scala':
213
+ return 'scala';
214
+ case 'swift':
215
+ return 'swift';
216
+ case 'vb':
217
+ return 'vbnet';
218
+ case 'cs':
219
+ return 'csharp';
220
+ case 'py':
221
+ return 'python';
222
+ case 'md':
223
+ return 'markdown';
224
+ case 'sh':
225
+ return 'shell';
226
+ case 'css':
227
+ return 'css';
228
+ case 'html':
229
+ return 'html';
230
+ case 'xml':
231
+ return 'xml';
232
+ case 'yaml':
233
+ return 'yaml';
234
+ default:
235
+ return 'text';
236
+ }
237
+ }
@@ -0,0 +1,29 @@
1
+ import type { TColumnElement } from 'platejs';
2
+ import type { SlateElementProps } from 'platejs/static';
3
+
4
+ import { SlateElement } from 'platejs/static';
5
+
6
+ export function ColumnElementStatic(props: SlateElementProps<TColumnElement>) {
7
+ const { width } = props.element;
8
+
9
+ return (
10
+ <div className="group/column relative" style={{ width: width ?? '100%' }}>
11
+ <SlateElement
12
+ className="h-full px-2 pt-2 group-first/column:pl-0 group-last/column:pr-0"
13
+ {...props}
14
+ >
15
+ <div className="relative h-full border border-transparent p-1.5">
16
+ {props.children}
17
+ </div>
18
+ </SlateElement>
19
+ </div>
20
+ );
21
+ }
22
+
23
+ export function ColumnGroupElementStatic(props: SlateElementProps) {
24
+ return (
25
+ <SlateElement className="mb-2" {...props}>
26
+ <div className="flex size-full rounded">{props.children}</div>
27
+ </SlateElement>
28
+ );
29
+ }