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,339 @@
1
+ import { useState, useMemo } from "react";
2
+ import { ChevronRight, ChevronDown, File, Folder, X } from "lucide-react";
3
+ import { cn } from "@/lib/utils";
4
+ import { Button } from "@/components/ui/button";
5
+ import { useQuery, useQueryClient } from "@tanstack/react-query";
6
+ import { orpcTs } from "@/lib/orpc";
7
+
8
+ interface FileNode {
9
+ name: string;
10
+ path: string;
11
+ type: "file" | "directory";
12
+ children?: FileNode[];
13
+ }
14
+
15
+ interface FileSelectorProps {
16
+ open: boolean;
17
+ onClose: () => void;
18
+ onSelect: (filePath: string, startLine: number, endLine: number) => void;
19
+ currentFilePath?: string;
20
+ currentStartLine?: string;
21
+ currentEndLine?: string;
22
+ }
23
+
24
+ export function FileSelector({
25
+ open,
26
+ onClose,
27
+ onSelect,
28
+ currentFilePath,
29
+ currentStartLine,
30
+ currentEndLine,
31
+ }: FileSelectorProps) {
32
+ const queryClient = useQueryClient();
33
+ const [expandedDirs, setExpandedDirs] = useState<Set<string>>(new Set());
34
+ const [selectedFile, setSelectedFile] = useState<string>(currentFilePath || "");
35
+ const [startLineInput, setStartLineInput] = useState<string>(
36
+ currentStartLine || ""
37
+ );
38
+ const [endLineInput, setEndLineInput] = useState<string>(
39
+ currentEndLine || ""
40
+ );
41
+ const [folderContents, setFolderContents] = useState<Map<string, FileNode[]>>(new Map());
42
+
43
+ const rootFolderQuery = useQuery({
44
+ ...orpcTs.codebase.getFolderContents.queryOptions({
45
+ input: { filePath: "" },
46
+ }),
47
+ enabled: open,
48
+ });
49
+
50
+ // Merge all folder contents for rendering (includes root folder)
51
+ const allFolderContents = useMemo(() => {
52
+ const merged = new Map(folderContents);
53
+ if (rootFolderQuery.data) {
54
+ merged.set("", rootFolderQuery.data);
55
+ }
56
+ return merged;
57
+ }, [folderContents, rootFolderQuery.data]);
58
+
59
+ const fileContentQuery = useQuery({
60
+ ...orpcTs.codebase.getFileContents.queryOptions({
61
+ input: { filePath: selectedFile },
62
+ }),
63
+ enabled: open && !!selectedFile,
64
+ });
65
+
66
+ const fileContent = fileContentQuery.data?.contents || "";
67
+ const lineCount = fileContent.split("\n").length;
68
+
69
+ // Convert inputs to numbers for calculations, with defaults
70
+ const startLine = startLineInput === "" ? 1 : Math.max(1, parseInt(startLineInput) || 1);
71
+ const endLine = endLineInput === ""
72
+ ? (fileContentQuery.data && lineCount > 0 ? Math.min(50, lineCount) : 1)
73
+ : Math.min(lineCount, Math.max(startLine, parseInt(endLineInput) || lineCount));
74
+
75
+ const toggleExpanded = async (path: string) => {
76
+ const newExpanded = new Set(expandedDirs);
77
+ if (newExpanded.has(path)) {
78
+ newExpanded.delete(path);
79
+ } else {
80
+ newExpanded.add(path);
81
+ // Fetch folder contents if not already loaded
82
+ if (!folderContents.has(path)) {
83
+ try {
84
+ const data = await queryClient.fetchQuery(
85
+ orpcTs.codebase.getFolderContents.queryOptions({
86
+ input: { filePath: path },
87
+ })
88
+ );
89
+ setFolderContents((prev) => {
90
+ const newMap = new Map(prev);
91
+ newMap.set(path, data || []);
92
+ return newMap;
93
+ });
94
+ } catch (error) {
95
+ console.error("Failed to fetch folder contents:", error);
96
+ }
97
+ }
98
+ }
99
+ setExpandedDirs(newExpanded);
100
+ };
101
+
102
+ const handleSelectFile = (filePath: string) => {
103
+ setSelectedFile(filePath);
104
+ setStartLineInput("");
105
+ setEndLineInput("");
106
+ fileContentQuery.refetch();
107
+ };
108
+
109
+ const handleConfirm = () => {
110
+ if (selectedFile) {
111
+ onSelect(selectedFile, startLine, endLine);
112
+ onClose();
113
+ }
114
+ };
115
+
116
+ const renderFileTree = (nodes: FileNode[], depth = 0) => {
117
+ return (
118
+ <div>
119
+ {nodes.map((node) => {
120
+ const children = node.type === "directory" ? allFolderContents.get(node.path) : undefined;
121
+ const isLoading = node.type === "directory" && expandedDirs.has(node.path) && !allFolderContents.has(node.path);
122
+
123
+ return (
124
+ <div key={node.path}>
125
+ <div
126
+ className={cn(
127
+ "flex items-center gap-1 px-2 py-1.5 hover:bg-muted transition-colors cursor-pointer rounded text-sm",
128
+ selectedFile === node.path && node.type === "file"
129
+ ? "bg-primary/10"
130
+ : ""
131
+ )}
132
+ style={{ paddingLeft: `${depth * 16 + 8}px` }}
133
+ >
134
+ {node.type === "directory" ? (
135
+ <>
136
+ <button
137
+ onClick={() => toggleExpanded(node.path)}
138
+ className="flex items-center justify-center w-4 h-4 p-0"
139
+ disabled={isLoading}
140
+ >
141
+ {isLoading ? (
142
+ <div className="w-4 h-4 border-2 border-primary border-t-transparent rounded-full animate-spin" />
143
+ ) : expandedDirs.has(node.path) ? (
144
+ <ChevronDown className="w-4 h-4" />
145
+ ) : (
146
+ <ChevronRight className="w-4 h-4" />
147
+ )}
148
+ </button>
149
+ <Folder className="w-4 h-4 text-blue-500 flex-shrink-0" />
150
+ <span className="text-foreground">{node.name}</span>
151
+ </>
152
+ ) : (
153
+ <>
154
+ <div className="w-4" />
155
+ <File className="w-4 h-4 text-muted-foreground flex-shrink-0" />
156
+ <button
157
+ onClick={() => handleSelectFile(node.path)}
158
+ className="text-foreground hover:text-primary"
159
+ >
160
+ {node.name}
161
+ </button>
162
+ </>
163
+ )}
164
+ </div>
165
+ {node.type === "directory" &&
166
+ expandedDirs.has(node.path) &&
167
+ children && (
168
+ <>{renderFileTree(children, depth + 1)}</>
169
+ )}
170
+ </div>
171
+ );
172
+ })}
173
+ </div>
174
+ );
175
+ };
176
+
177
+ if (!open) return null;
178
+
179
+ return (
180
+ <div className="fixed inset-0 z-50 flex items-center justify-center">
181
+ {/* Backdrop */}
182
+ <div className="absolute inset-0 bg-black/50" onClick={onClose} />
183
+
184
+ {/* Modal */}
185
+ <div className="relative z-50 w-full max-w-4xl min-h-[80vh] max-h-[calc(100vh-2rem)] mx-4 bg-background rounded-lg shadow-xl flex flex-col overflow-hidden border">
186
+ {/* Header */}
187
+ <div className="flex items-center justify-between px-6 py-4 border-b">
188
+ <div>
189
+ <h2 className="text-lg font-semibold">Select File and Lines</h2>
190
+ <p className="text-sm text-muted-foreground">
191
+ Choose a file and specify the line range you want to display
192
+ </p>
193
+ </div>
194
+ <button
195
+ onClick={onClose}
196
+ className="p-1 hover:bg-muted rounded"
197
+ >
198
+ <X className="w-6 h-6" />
199
+ </button>
200
+ </div>
201
+
202
+ {/* Content */}
203
+ <div className="flex gap-4 flex-1 overflow-hidden p-6">
204
+ {/* File browser */}
205
+ <div className="flex-1 border rounded-lg overflow-y-auto bg-muted/50 p-2">
206
+ {rootFolderQuery.isLoading ? (
207
+ <div className="p-4 text-sm text-muted-foreground">
208
+ Loading files...
209
+ </div>
210
+ ) : (
211
+ renderFileTree(allFolderContents.get("") || [])
212
+ )}
213
+ </div>
214
+
215
+ {/* Preview and line selection */}
216
+ <div className="flex-1 flex flex-col gap-4">
217
+ {selectedFile ? (
218
+ <>
219
+ <div>
220
+ <p className="text-sm font-semibold mb-2">
221
+ Selected:{" "}
222
+ <span className="text-primary">{selectedFile}</span>
223
+ </p>
224
+ <p className="text-xs text-muted-foreground">
225
+ Total lines: {lineCount}
226
+ </p>
227
+ </div>
228
+
229
+ {/* Line range inputs */}
230
+ <div className="flex gap-4">
231
+ <div className="flex-1">
232
+ <label className="text-xs font-medium text-muted-foreground block mb-1">
233
+ Start Line
234
+ </label>
235
+ <input
236
+ type="text"
237
+ inputMode="numeric"
238
+ value={startLineInput}
239
+ onChange={(e) => {
240
+ const value = e.target.value;
241
+ // Allow empty string or valid numbers
242
+ if (value === "" || /^\d+$/.test(value)) {
243
+ setStartLineInput(value);
244
+ }
245
+ }}
246
+ onBlur={(e) => {
247
+ // Validate on blur - set to 1 if empty or invalid
248
+ const num = parseInt(e.target.value);
249
+ if (isNaN(num) || num < 1) {
250
+ setStartLineInput("");
251
+ } else if (num > lineCount) {
252
+ setStartLineInput(String(lineCount));
253
+ }
254
+ }}
255
+ className="w-full px-2 py-1 border rounded text-sm bg-background text-foreground"
256
+ placeholder="1"
257
+ />
258
+ </div>
259
+ <div className="flex-1">
260
+ <label className="text-xs font-medium text-muted-foreground block mb-1">
261
+ End Line
262
+ </label>
263
+ <input
264
+ type="text"
265
+ inputMode="numeric"
266
+ value={endLineInput}
267
+ onChange={(e) => {
268
+ const value = e.target.value;
269
+ // Allow empty string or valid numbers
270
+ if (value === "" || /^\d+$/.test(value)) {
271
+ setEndLineInput(value);
272
+ }
273
+ }}
274
+ onBlur={(e) => {
275
+ // Validate on blur
276
+ const num = parseInt(e.target.value);
277
+ if (isNaN(num) || num < startLine) {
278
+ setEndLineInput("");
279
+ } else if (num > lineCount) {
280
+ setEndLineInput(String(lineCount));
281
+ }
282
+ }}
283
+ className="w-full px-2 py-1 border rounded text-sm bg-background text-foreground"
284
+ placeholder={String(lineCount)}
285
+ />
286
+ </div>
287
+ </div>
288
+
289
+ {/* Preview */}
290
+ <div className="flex-1 flex flex-col">
291
+ <p className="text-xs font-medium text-muted-foreground mb-1">
292
+ Preview
293
+ </p>
294
+ <div className="flex-1 relative">
295
+ <div className="bg-muted rounded border p-2 overflow-y-auto absolute inset-0 text-xs font-mono">
296
+ {fileContentQuery.isLoading ? (
297
+ <p className="text-muted-foreground">Loading...</p>
298
+ ) : (
299
+ fileContent
300
+ .split("\n")
301
+ .slice(startLine - 1, endLine)
302
+ .map((line, idx) => (
303
+ <div
304
+ key={idx}
305
+ className="flex gap-2 text-foreground"
306
+ >
307
+ <span className="text-muted-foreground w-8 text-right flex-shrink-0">
308
+ {startLine + idx}
309
+ </span>
310
+ <span>{line}</span>
311
+ </div>
312
+ ))
313
+ )}
314
+ </div>
315
+ </div>
316
+ </div>
317
+ </>
318
+ ) : (
319
+ <div className="flex items-center justify-center h-full text-muted-foreground">
320
+ <p>Select a file to begin</p>
321
+ </div>
322
+ )}
323
+ </div>
324
+ </div>
325
+
326
+ {/* Footer */}
327
+ <div className="flex justify-end gap-2 px-6 py-4 border-t bg-muted/50">
328
+ <Button variant="outline" onClick={onClose}>
329
+ Cancel
330
+ </Button>
331
+ <Button onClick={handleConfirm} disabled={!selectedFile}>
332
+ Insert Code
333
+ </Button>
334
+ </div>
335
+ </div>
336
+ </div>
337
+ );
338
+ }
339
+
@@ -0,0 +1,73 @@
1
+
2
+
3
+ import {
4
+ BoldIcon,
5
+ Code2Icon,
6
+ ItalicIcon,
7
+ StrikethroughIcon,
8
+ UnderlineIcon,
9
+ } from 'lucide-react';
10
+ import { KEYS } from 'platejs';
11
+ import { useEditorReadOnly } from 'platejs/react';
12
+
13
+ import { CommentToolbarButton } from './comment-toolbar-button';
14
+ import { InlineEquationToolbarButton } from './equation-toolbar-button';
15
+ import { LinkToolbarButton } from './link-toolbar-button';
16
+ import { MarkToolbarButton } from './mark-toolbar-button';
17
+ import { MoreToolbarButton } from './more-toolbar-button';
18
+ import { SuggestionToolbarButton } from './suggestion-toolbar-button';
19
+ import { ToolbarGroup } from './toolbar';
20
+ import { TurnIntoToolbarButton } from './turn-into-toolbar-button';
21
+
22
+ export function FloatingToolbarButtons() {
23
+ const readOnly = useEditorReadOnly();
24
+
25
+ return (
26
+ <>
27
+ {!readOnly && (
28
+ <>
29
+ <ToolbarGroup>
30
+ <TurnIntoToolbarButton />
31
+
32
+ <MarkToolbarButton nodeType={KEYS.bold} tooltip="Bold (⌘+B)">
33
+ <BoldIcon />
34
+ </MarkToolbarButton>
35
+
36
+ <MarkToolbarButton nodeType={KEYS.italic} tooltip="Italic (⌘+I)">
37
+ <ItalicIcon />
38
+ </MarkToolbarButton>
39
+
40
+ <MarkToolbarButton
41
+ nodeType={KEYS.underline}
42
+ tooltip="Underline (⌘+U)"
43
+ >
44
+ <UnderlineIcon />
45
+ </MarkToolbarButton>
46
+
47
+ <MarkToolbarButton
48
+ nodeType={KEYS.strikethrough}
49
+ tooltip="Strikethrough (⌘+⇧+M)"
50
+ >
51
+ <StrikethroughIcon />
52
+ </MarkToolbarButton>
53
+
54
+ <MarkToolbarButton nodeType={KEYS.code} tooltip="Code (⌘+E)">
55
+ <Code2Icon />
56
+ </MarkToolbarButton>
57
+
58
+ <InlineEquationToolbarButton />
59
+
60
+ <LinkToolbarButton />
61
+ </ToolbarGroup>
62
+ </>
63
+ )}
64
+
65
+ <ToolbarGroup>
66
+ <CommentToolbarButton />
67
+ <SuggestionToolbarButton />
68
+
69
+ {!readOnly && <MoreToolbarButton />}
70
+ </ToolbarGroup>
71
+ </>
72
+ );
73
+ }
@@ -0,0 +1,85 @@
1
+
2
+
3
+ import {
4
+ type FloatingToolbarState,
5
+ flip,
6
+ offset,
7
+ useFloatingToolbar,
8
+ useFloatingToolbarState,
9
+ } from '@platejs/floating';
10
+ import { useComposedRef } from '@udecode/cn';
11
+ import { KEYS } from 'platejs';
12
+ import {
13
+ useEditorId,
14
+ useEventEditorValue,
15
+ usePluginOption,
16
+ } from 'platejs/react';
17
+
18
+ import { cn } from '@/lib/utils';
19
+
20
+ import { Toolbar } from './toolbar';
21
+
22
+ export function FloatingToolbar({
23
+ children,
24
+ className,
25
+ state,
26
+ ...props
27
+ }: React.ComponentProps<typeof Toolbar> & {
28
+ state?: FloatingToolbarState;
29
+ }) {
30
+ const editorId = useEditorId();
31
+ const focusedEditorId = useEventEditorValue('focus');
32
+ const isFloatingLinkOpen = !!usePluginOption({ key: KEYS.link }, 'mode');
33
+ const isAIChatOpen = usePluginOption({ key: KEYS.aiChat }, 'open');
34
+
35
+ const floatingToolbarState = useFloatingToolbarState({
36
+ editorId,
37
+ focusedEditorId,
38
+ hideToolbar: isFloatingLinkOpen || isAIChatOpen,
39
+ ...state,
40
+ floatingOptions: {
41
+ middleware: [
42
+ offset(12),
43
+ flip({
44
+ fallbackPlacements: [
45
+ 'top-start',
46
+ 'top-end',
47
+ 'bottom-start',
48
+ 'bottom-end',
49
+ ],
50
+ padding: 12,
51
+ }),
52
+ ],
53
+ placement: 'top',
54
+ ...state?.floatingOptions,
55
+ },
56
+ });
57
+
58
+ const {
59
+ clickOutsideRef,
60
+ hidden,
61
+ props: rootProps,
62
+ ref: floatingRef,
63
+ } = useFloatingToolbar(floatingToolbarState);
64
+
65
+ const ref = useComposedRef<HTMLDivElement>(props.ref, floatingRef);
66
+
67
+ if (hidden) return null;
68
+
69
+ return (
70
+ <div ref={clickOutsideRef}>
71
+ <Toolbar
72
+ {...props}
73
+ {...rootProps}
74
+ ref={ref}
75
+ className={cn(
76
+ 'scrollbar-hide absolute z-50 overflow-x-auto whitespace-nowrap rounded-md border bg-popover p-1 opacity-100 shadow-md print:hidden',
77
+ 'max-w-[80vw]',
78
+ className
79
+ )}
80
+ >
81
+ {children}
82
+ </Toolbar>
83
+ </div>
84
+ );
85
+ }