react-embed-docs 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 (115) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +422 -0
  3. package/dist/client/components/Breadcrumbs.d.ts +21 -0
  4. package/dist/client/components/Breadcrumbs.d.ts.map +1 -0
  5. package/dist/client/components/Breadcrumbs.js +123 -0
  6. package/dist/client/components/DocsLayout.d.ts +20 -0
  7. package/dist/client/components/DocsLayout.d.ts.map +1 -0
  8. package/dist/client/components/DocsLayout.js +387 -0
  9. package/dist/client/components/DocumentContent.d.ts +5 -0
  10. package/dist/client/components/DocumentContent.d.ts.map +1 -0
  11. package/dist/client/components/DocumentContent.js +15 -0
  12. package/dist/client/components/DocumentEdit.d.ts +6 -0
  13. package/dist/client/components/DocumentEdit.d.ts.map +1 -0
  14. package/dist/client/components/DocumentEdit.js +153 -0
  15. package/dist/client/components/DocumentList.d.ts +5 -0
  16. package/dist/client/components/DocumentList.d.ts.map +1 -0
  17. package/dist/client/components/DocumentList.js +39 -0
  18. package/dist/client/components/DocumentProvider.d.ts +42 -0
  19. package/dist/client/components/DocumentProvider.d.ts.map +1 -0
  20. package/dist/client/components/DocumentProvider.js +47 -0
  21. package/dist/client/components/DocumentView.d.ts +6 -0
  22. package/dist/client/components/DocumentView.d.ts.map +1 -0
  23. package/dist/client/components/DocumentView.js +58 -0
  24. package/dist/client/components/DragOverlayItem.d.ts +5 -0
  25. package/dist/client/components/DragOverlayItem.d.ts.map +1 -0
  26. package/dist/client/components/DragOverlayItem.js +9 -0
  27. package/dist/client/components/EmojiPicker.d.ts +8 -0
  28. package/dist/client/components/EmojiPicker.d.ts.map +1 -0
  29. package/dist/client/components/EmojiPicker.js +48 -0
  30. package/dist/client/components/ExportButton.d.ts +22 -0
  31. package/dist/client/components/ExportButton.d.ts.map +1 -0
  32. package/dist/client/components/ExportButton.js +97 -0
  33. package/dist/client/components/Layout.d.ts +7 -0
  34. package/dist/client/components/Layout.d.ts.map +1 -0
  35. package/dist/client/components/Layout.js +172 -0
  36. package/dist/client/components/ReactEmbedDocs.d.ts +8 -0
  37. package/dist/client/components/ReactEmbedDocs.d.ts.map +1 -0
  38. package/dist/client/components/ReactEmbedDocs.js +8 -0
  39. package/dist/client/components/SearchInput.d.ts +2 -0
  40. package/dist/client/components/SearchInput.d.ts.map +1 -0
  41. package/dist/client/components/SearchInput.js +7 -0
  42. package/dist/client/components/Sidebar.d.ts +10 -0
  43. package/dist/client/components/Sidebar.d.ts.map +1 -0
  44. package/dist/client/components/Sidebar.js +176 -0
  45. package/dist/client/components/SortableTreeItem.d.ts +13 -0
  46. package/dist/client/components/SortableTreeItem.d.ts.map +1 -0
  47. package/dist/client/components/SortableTreeItem.js +24 -0
  48. package/dist/client/components/VersionHistory.d.ts +14 -0
  49. package/dist/client/components/VersionHistory.d.ts.map +1 -0
  50. package/dist/client/components/VersionHistory.js +102 -0
  51. package/dist/client/hooks/useCollaboration.d.ts +99 -0
  52. package/dist/client/hooks/useCollaboration.d.ts.map +1 -0
  53. package/dist/client/hooks/useCollaboration.js +180 -0
  54. package/dist/client/hooks/useDocsQuery.d.ts +84 -0
  55. package/dist/client/hooks/useDocsQuery.d.ts.map +1 -0
  56. package/dist/client/hooks/useDocsQuery.js +241 -0
  57. package/dist/client/hooks/useExport.d.ts +31 -0
  58. package/dist/client/hooks/useExport.d.ts.map +1 -0
  59. package/dist/client/hooks/useExport.js +66 -0
  60. package/dist/client/hooks/useFileUpload.d.ts +44 -0
  61. package/dist/client/hooks/useFileUpload.d.ts.map +1 -0
  62. package/dist/client/hooks/useFileUpload.js +193 -0
  63. package/dist/client/hooks/useSystemTheme.d.ts +2 -0
  64. package/dist/client/hooks/useSystemTheme.d.ts.map +1 -0
  65. package/dist/client/hooks/useSystemTheme.js +19 -0
  66. package/dist/client/hooks/useVersions.d.ts +105 -0
  67. package/dist/client/hooks/useVersions.d.ts.map +1 -0
  68. package/dist/client/hooks/useVersions.js +129 -0
  69. package/dist/client/index.d.ts +23 -0
  70. package/dist/client/index.d.ts.map +1 -0
  71. package/dist/client/index.js +18 -0
  72. package/dist/client/lib/blocknoteTheme.d.ts +13 -0
  73. package/dist/client/lib/blocknoteTheme.d.ts.map +1 -0
  74. package/dist/client/lib/blocknoteTheme.js +76 -0
  75. package/dist/client/lib/path.d.ts +8 -0
  76. package/dist/client/lib/path.d.ts.map +1 -0
  77. package/dist/client/lib/path.js +30 -0
  78. package/dist/client/providers/DocumentProvider.d.ts +1 -0
  79. package/dist/client/providers/DocumentProvider.d.ts.map +1 -0
  80. package/dist/client/providers/DocumentProvider.js +1 -0
  81. package/dist/server/CollaborationService.d.ts +134 -0
  82. package/dist/server/CollaborationService.d.ts.map +1 -0
  83. package/dist/server/CollaborationService.js +307 -0
  84. package/dist/server/DocsService.d.ts +115 -0
  85. package/dist/server/DocsService.d.ts.map +1 -0
  86. package/dist/server/DocsService.js +512 -0
  87. package/dist/server/ExportService.d.ts +106 -0
  88. package/dist/server/ExportService.d.ts.map +1 -0
  89. package/dist/server/ExportService.js +501 -0
  90. package/dist/server/FilesService.d.ts +44 -0
  91. package/dist/server/FilesService.d.ts.map +1 -0
  92. package/dist/server/FilesService.js +78 -0
  93. package/dist/server/VersioningService.d.ts +112 -0
  94. package/dist/server/VersioningService.d.ts.map +1 -0
  95. package/dist/server/VersioningService.js +264 -0
  96. package/dist/server/db.d.ts +7 -0
  97. package/dist/server/db.d.ts.map +1 -0
  98. package/dist/server/db.js +22 -0
  99. package/dist/server/index.d.ts +55 -0
  100. package/dist/server/index.d.ts.map +1 -0
  101. package/dist/server/index.js +36 -0
  102. package/dist/server/routes.d.ts +9 -0
  103. package/dist/server/routes.d.ts.map +1 -0
  104. package/dist/server/routes.js +483 -0
  105. package/dist/server/schema.d.ts +587 -0
  106. package/dist/server/schema.d.ts.map +1 -0
  107. package/dist/server/schema.js +126 -0
  108. package/dist/shared/types.d.ts +314 -0
  109. package/dist/shared/types.d.ts.map +1 -0
  110. package/dist/shared/types.js +48 -0
  111. package/drizzle/migrations/0000_gray_monster_badoon.sql +88 -0
  112. package/drizzle/migrations/meta/0000_snapshot.json +574 -0
  113. package/drizzle/migrations/meta/_journal.json +13 -0
  114. package/package.json +109 -0
  115. package/styles/docs.css +981 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nik Lucky
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,422 @@
1
+ # react-embed-docs
2
+
3
+ Full-stack documentation system with BlockNote editor, hierarchical structure, and file uploads.
4
+
5
+ ## Features
6
+
7
+ - 📝 **BlockNote Editor** - Notion-style block-based rich text editor
8
+ - 🌳 **Hierarchical Structure** - Parent-child document relationships with drag-drop reordering
9
+ - 🔍 **Full-Text Search** - Fast search with pre-built search index
10
+ - 🎨 **Customizable** - Tailwind CSS styling with dark mode support
11
+ - 📎 **File Uploads** - Built-in cover image support with PostgreSQL storage
12
+ - 😀 **Emoji Icons** - Document icons with emoji picker
13
+ - ⚡ **TypeScript** - Full type safety
14
+ - 🗄️ **PostgreSQL** - Reliable data storage with Drizzle ORM
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ # Using bun
20
+ bun install react-embed-docs
21
+
22
+ # Using npm
23
+ npm install react-embed-docs
24
+
25
+ # Using yarn
26
+ yarn add react-embed-docs
27
+ ```
28
+
29
+ ## Peer Dependencies
30
+
31
+ ```json
32
+ {
33
+ "react": "^18.0.0",
34
+ "react-dom": "^18.0.0",
35
+ "@blocknote/core": "^0.46.0",
36
+ "@blocknote/react": "^0.46.0",
37
+ "@blocknote/mantine": "^0.46.0",
38
+ "drizzle-orm": "^0.31.0",
39
+ "@tanstack/react-query": "^5.0.0",
40
+ "postgres": "^3.4.0"
41
+ }
42
+ ```
43
+
44
+ ## Quick Start
45
+
46
+ ### 1. Database Setup
47
+
48
+ Run the migrations to create the necessary tables:
49
+
50
+ ```typescript
51
+ import { drizzle } from 'drizzle-orm/postgres-js'
52
+ import postgres from 'postgres'
53
+ import { runMigrations } from 'react-embed-docs/server'
54
+
55
+ const connection = postgres(process.env.DATABASE_URL)
56
+ const db = drizzle(connection)
57
+
58
+ // Run migrations on startup
59
+ await runMigrations(db)
60
+ ```
61
+
62
+ ### 2. Server Setup (Hono)
63
+
64
+ ```typescript
65
+ import { Hono } from 'hono'
66
+ import { createDocsRouter, createFilesRouter } from 'react-embed-docs/server'
67
+
68
+ const app = new Hono()
69
+
70
+ // Add docs routes
71
+ app.route('/api/docs', createDocsRouter({ db }))
72
+ app.route('/api/files', createFilesRouter({ db }))
73
+
74
+ // Or combine both
75
+ import { createRouter } from 'react-embed-docs/server'
76
+ app.route('/api', createRouter({ db }))
77
+ ```
78
+
79
+ ### 3. Client Setup (React)
80
+
81
+ ```tsx
82
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
83
+ import { DocsLayout, DocumentEdit } from 'react-embed-docs/client'
84
+ import 'react-embed-docs/styles'
85
+
86
+ const queryClient = new QueryClient()
87
+
88
+ function App() {
89
+ return (
90
+ <QueryClientProvider client={queryClient}>
91
+ <DocsLayout
92
+ onNavigate={(id) => window.location.href = `/docs/${id}`}
93
+ userAvatar={<UserAvatar />} // Your custom avatar component
94
+ >
95
+ <DocumentEdit
96
+ docId="new"
97
+ onSave={(doc) => console.log('Saved:', doc)}
98
+ onNavigate={(id) => window.location.href = `/docs/${id}`}
99
+ />
100
+ </DocsLayout>
101
+ </QueryClientProvider>
102
+ )
103
+ }
104
+ ```
105
+
106
+ ## API Reference
107
+
108
+ ### Server Exports
109
+
110
+ ```typescript
111
+ import {
112
+ DocsService,
113
+ FilesService,
114
+ createDocsRouter,
115
+ createFilesRouter,
116
+ createRouter,
117
+ runMigrations,
118
+ documentsTable,
119
+ filesTable
120
+ } from 'react-embed-docs/server'
121
+ ```
122
+
123
+ #### DocsService
124
+
125
+ ```typescript
126
+ class DocsService {
127
+ // Queries
128
+ list(filters, options): Promise<ListDocumentsResult>
129
+ getById(id: string): Promise<Document | undefined>
130
+ getBySlug(slug: string): Promise<Document | undefined>
131
+ getChildren(parentId: string): Promise<Document[]>
132
+ getTree(): Promise<Document[]>
133
+
134
+ // Mutations
135
+ create(data: InsertDocument): Promise<Document>
136
+ update(id: string, data: UpdateDocument): Promise<Document | undefined>
137
+ delete(id: string): Promise<Document | undefined>
138
+
139
+ // Search
140
+ search(query: string, options): Promise<SearchResult[]>
141
+ searchText(query: string, options): Promise<SearchTextResult[]>
142
+
143
+ // Reordering
144
+ reorder(id: string, newParentId: string | null, newOrder: number): Promise<Document | undefined>
145
+ }
146
+ ```
147
+
148
+ ### Client Exports
149
+
150
+ ```typescript
151
+ import {
152
+ DocsLayout,
153
+ DocumentEdit,
154
+ DocumentView,
155
+ DocumentList,
156
+ EmojiPicker,
157
+ useDocumentsQuery,
158
+ useDocumentQuery,
159
+ useCreateDocumentMutation,
160
+ useUpdateDocumentMutation,
161
+ useDeleteDocumentMutation,
162
+ useReorderDocumentMutation,
163
+ useFileUpload
164
+ } from 'react-embed-docs/client'
165
+ ```
166
+
167
+ #### Components
168
+
169
+ **DocsLayout**
170
+ ```tsx
171
+ <DocsLayout
172
+ currentDocId?: string // Currently active document ID
173
+ onNavigate?: (id: string) => void // Navigation callback
174
+ userAvatar?: React.ReactNode // User avatar component for header
175
+ onSearch?: (query: string) => void // Search callback
176
+ >
177
+ {children}
178
+ </DocsLayout>
179
+ ```
180
+
181
+ **DocumentEdit**
182
+ ```tsx
183
+ <DocumentEdit
184
+ docId: string // 'new' or document ID
185
+ parentId?: string // Parent document ID (for creating child)
186
+ onSave?: (doc: Document) => void
187
+ onNavigate?: (id: string) => void
188
+ />
189
+ ```
190
+
191
+ **DocumentView**
192
+ ```tsx
193
+ <DocumentView
194
+ docId: string
195
+ onEdit?: (id: string) => void
196
+ onCreateChild?: (parentId: string) => void
197
+ onNavigate?: (id: string) => void
198
+ />
199
+ ```
200
+
201
+ #### Hooks
202
+
203
+ ```typescript
204
+ // Queries
205
+ const { data, isLoading } = useDocumentsQuery({ search?: string, parentId?: string })
206
+ const { data } = useDocumentQuery(docId)
207
+
208
+ // Mutations
209
+ const create = useCreateDocumentMutation()
210
+ const update = useUpdateDocumentMutation()
211
+ const delete = useDeleteDocumentMutation()
212
+ const reorder = useReorderDocumentMutation()
213
+
214
+ // File upload
215
+ const { isUploading, progress, error, uploadFile } = useFileUpload({
216
+ maxSize: 5 * 1024 * 1024, // 5MB
217
+ allowedTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp']
218
+ })
219
+ ```
220
+
221
+ ## Database Schema
222
+
223
+ ### Documents Table
224
+
225
+ | Column | Type | Description |
226
+ |--------|------|-------------|
227
+ | id | varchar(21) | Primary key (nanoid) |
228
+ | title | varchar(255) | Document title |
229
+ | slug | varchar(255) | URL-friendly unique slug |
230
+ | content | jsonb | BlockNote JSON content |
231
+ | searchIndex | text | Pre-built search text (title + content) |
232
+ | emoji | varchar(10) | Document icon emoji |
233
+ | cover | varchar(500) | Cover image file ID |
234
+ | isPublished | boolean | Published state |
235
+ | parentId | varchar(21) | Parent document ID (self-reference) |
236
+ | order | integer | Sort order within parent |
237
+ | authorId | integer | Document author (optional) |
238
+ | createdAt | timestamp | Creation time |
239
+ | updatedAt | timestamp | Last update time |
240
+ | deletedAt | timestamp | Soft delete timestamp |
241
+
242
+ ### Files Table
243
+
244
+ | Column | Type | Description |
245
+ |--------|------|-------------|
246
+ | id | varchar(21) | Primary key (nanoid) |
247
+ | filename | varchar(255) | Original filename |
248
+ | mimeType | varchar(100) | File MIME type |
249
+ | size | integer | File size in bytes |
250
+ | content | text | Base64 encoded file content |
251
+ | createdAt | timestamp | Upload time |
252
+
253
+ ## Styling
254
+
255
+ The package includes a complete Tailwind CSS stylesheet with all utilities. Import it in your app:
256
+
257
+ ```typescript
258
+ import 'react-embed-docs/styles'
259
+ ```
260
+
261
+ This imports a pre-built CSS bundle that includes all Tailwind utilities used by the components. **No additional Tailwind configuration needed.**
262
+
263
+ ### Using with your own Tailwind setup
264
+
265
+ If you prefer to process the styles through your own Tailwind build (e.g., for custom theming), use the source CSS instead:
266
+
267
+ ```typescript
268
+ import 'react-embed-docs/styles/source'
269
+ ```
270
+
271
+ Then add the package files to your Tailwind content config:
272
+
273
+ ```javascript
274
+ // tailwind.config.js
275
+ module.exports = {
276
+ content: [
277
+ './src/**/*.{js,ts,jsx,tsx}',
278
+ './node_modules/react-embed-docs/dist/client/**/*.js',
279
+ ],
280
+ }
281
+ ```
282
+
283
+ ### Tailwind CSS Safelist (legacy)
284
+
285
+ If you're using the source CSS and some classes aren't working, you can use the safelist:
286
+
287
+ ```javascript
288
+ // tailwind.config.js
289
+ module.exports = {
290
+ content: [
291
+ './src/**/*.{js,ts,jsx,tsx}',
292
+ // Add this to scan the safelist
293
+ './node_modules/react-embed-docs/styles/tailwind.safelist.js',
294
+ ],
295
+ safelist: [
296
+ // Option 1: Include all classes from the package
297
+ ...require('react-embed-docs/styles/tailwind.safelist.js'),
298
+
299
+ // Option 2: Or manually add specific classes you need
300
+ 'h-40', 'h-44', 'h-48', 'h-52', 'h-56', 'h-60',
301
+ ],
302
+ }
303
+ ```
304
+
305
+ ### Dark Mode
306
+
307
+ All components support dark mode through Tailwind's `dark:` variants. The components automatically adapt when you have `class="dark"` on your HTML element.
308
+
309
+ ## Configuration
310
+
311
+ ### Custom API Prefix
312
+
313
+ ```typescript
314
+ app.route('/custom-prefix', createDocsRouter({ db }))
315
+ ```
316
+
317
+ ### File Upload Limits
318
+
319
+ ```typescript
320
+ const { uploadFile } = useFileUpload({
321
+ maxSize: 10 * 1024 * 1024, // 10MB
322
+ allowedTypes: ['image/jpeg', 'image/png', 'image/webp', 'application/pdf']
323
+ })
324
+ ```
325
+
326
+ ### Authentication
327
+
328
+ The package doesn't enforce authentication, allowing you to integrate with your own auth system:
329
+
330
+ ```typescript
331
+ // Add your auth middleware before docs routes
332
+ app.use('/api/docs/*', yourAuthMiddleware)
333
+ app.route('/api/docs', createDocsRouter({ db }))
334
+ ```
335
+
336
+ ## Examples
337
+
338
+ ### Creating a New Document
339
+
340
+ ```typescript
341
+ const createMutation = useCreateDocumentMutation()
342
+
343
+ const handleCreate = async () => {
344
+ const doc = await createMutation.mutateAsync({
345
+ title: 'My New Document',
346
+ slug: 'my-new-document',
347
+ content: [], // BlockNote blocks
348
+ emoji: '📝',
349
+ isPublished: true
350
+ })
351
+
352
+ // Navigate to the new document
353
+ router.push(`/docs/${doc.id}`)
354
+ }
355
+ ```
356
+
357
+ ### Uploading a Cover Image
358
+
359
+ ```typescript
360
+ const { uploadFile } = useFileUpload()
361
+ const updateMutation = useUpdateDocumentMutation()
362
+
363
+ const handleCoverUpload = async (file: File) => {
364
+ const result = await uploadFile(file)
365
+ if (result) {
366
+ await updateMutation.mutateAsync({
367
+ id: docId,
368
+ cover: result.url // /api/files/{id}
369
+ })
370
+ }
371
+ }
372
+ ```
373
+
374
+ ### Searching Documents
375
+
376
+ ```typescript
377
+ const [searchQuery, setSearchQuery] = useState('')
378
+ const { data } = useDocumentsQuery({ search: searchQuery })
379
+
380
+ // Results include documents matching title or content
381
+ ```
382
+
383
+ ## Migration Guide
384
+
385
+ ### From Existing Implementation
386
+
387
+ If you're migrating from an existing docs implementation:
388
+
389
+ 1. **Database**: The schema is compatible but adds `searchIndex` column for faster search
390
+ 2. **Components**: Replace shadcn imports with `react-embed-docs/client`
391
+ 3. **Services**: Replace local services with `react-embed-docs/server`
392
+ 4. **Styling**: Import `react-embed-docs/styles` or copy CSS classes
393
+
394
+ ## Development
395
+
396
+ ```bash
397
+ # Install dependencies
398
+ bun install
399
+
400
+ # Build
401
+ bun run build
402
+
403
+ # Watch mode
404
+ bun run dev
405
+
406
+ # Run migrations
407
+ bun run migrate
408
+ ```
409
+
410
+ ## License
411
+
412
+ MIT License - see LICENSE file for details.
413
+
414
+ ## Contributing
415
+
416
+ Contributions are welcome! Please read our contributing guidelines before submitting PRs.
417
+
418
+ ## Support
419
+
420
+ - 📧 Email: support@example.com
421
+ - 🐛 Issues: [GitHub Issues](https://github.com/niklucky/react-embed-docs/issues)
422
+ - 📖 Documentation: [Full Documentation](https://github.com/niklucky/react-embed-docs#readme)
@@ -0,0 +1,21 @@
1
+ interface BreadcrumbDoc {
2
+ id: string;
3
+ title: string;
4
+ slug: string;
5
+ emoji?: string;
6
+ parentId?: string | null;
7
+ }
8
+ interface BreadcrumbsProps {
9
+ docId?: string;
10
+ /**
11
+ * Array of all documents to build paths locally without API call.
12
+ * If not provided, will fetch from API.
13
+ */
14
+ documents?: BreadcrumbDoc[];
15
+ onNavigate?: (path: string) => void;
16
+ homeLabel?: string;
17
+ className?: string;
18
+ }
19
+ export declare function Breadcrumbs({ docId, documents, onNavigate, homeLabel, className, }: BreadcrumbsProps): import("react/jsx-runtime").JSX.Element;
20
+ export {};
21
+ //# sourceMappingURL=Breadcrumbs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Breadcrumbs.d.ts","sourceRoot":"","sources":["../../../src/client/components/Breadcrumbs.tsx"],"names":[],"mappings":"AAKA,UAAU,aAAa;IACrB,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,UAAU,gBAAgB;IACxB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;OAGG;IACH,SAAS,CAAC,EAAE,aAAa,EAAE,CAAA;IAC3B,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAwCD,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,SAAS,EACT,UAAU,EACV,SAAkB,EAClB,SAAc,GACf,EAAE,gBAAgB,2CAmLlB"}
@@ -0,0 +1,123 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { ChevronRight, Home, Loader2 } from 'lucide-react';
4
+ import { useEffect, useMemo, useState } from 'react';
5
+ // Fetch breadcrumbs from API
6
+ async function fetchBreadcrumbs(docId) {
7
+ const response = await fetch(`/api/docs/${docId}/breadcrumbs`);
8
+ if (!response.ok) {
9
+ throw new Error('Failed to fetch breadcrumbs');
10
+ }
11
+ const data = await response.json();
12
+ return data.items;
13
+ }
14
+ // Build full path from root to document
15
+ function buildPath(docId, allDocs, basePath = '/docs') {
16
+ const pathParts = [];
17
+ const visited = new Set();
18
+ let currentId = docId;
19
+ while (currentId) {
20
+ // Prevent infinite loops from circular references
21
+ if (visited.has(currentId))
22
+ break;
23
+ visited.add(currentId);
24
+ const doc = allDocs.find((d) => d.id === currentId);
25
+ if (!doc)
26
+ break;
27
+ pathParts.unshift(doc.slug || doc.id);
28
+ currentId = allDocs.find((d) => d.id === currentId)?.parentId ?? null;
29
+ }
30
+ if (pathParts.length === 0)
31
+ return basePath;
32
+ return `${basePath}/${pathParts.join('/')}`;
33
+ }
34
+ export function Breadcrumbs({ docId, documents, onNavigate, homeLabel = 'Home', className = '', }) {
35
+ const [fetchedBreadcrumbs, setFetchedBreadcrumbs] = useState([]);
36
+ const [isLoading, setIsLoading] = useState(false);
37
+ const [error, setError] = useState(null);
38
+ // Use provided documents or fetch from API
39
+ const breadcrumbs = useMemo(() => {
40
+ if (documents && docId) {
41
+ // Build breadcrumbs from provided documents
42
+ const result = [];
43
+ const visited = new Set();
44
+ let currentId = docId;
45
+ while (currentId) {
46
+ if (visited.has(currentId))
47
+ break;
48
+ visited.add(currentId);
49
+ const doc = documents.find((d) => d.id === currentId);
50
+ if (!doc)
51
+ break;
52
+ result.unshift(doc);
53
+ currentId = documents.find((d) => d.id === currentId)?.parentId ?? null;
54
+ }
55
+ return result;
56
+ }
57
+ return fetchedBreadcrumbs;
58
+ }, [documents, docId, fetchedBreadcrumbs]);
59
+ useEffect(() => {
60
+ // If documents are provided, don't fetch
61
+ if (documents || !docId) {
62
+ return;
63
+ }
64
+ setIsLoading(true);
65
+ setError(null);
66
+ fetchBreadcrumbs(docId)
67
+ .then((items) => {
68
+ setFetchedBreadcrumbs(items);
69
+ setIsLoading(false);
70
+ })
71
+ .catch((err) => {
72
+ setError(err instanceof Error ? err.message : 'Failed to load breadcrumbs');
73
+ setIsLoading(false);
74
+ });
75
+ }, [docId, documents]);
76
+ const handleNavigate = (targetDocId) => {
77
+ if (!onNavigate)
78
+ return;
79
+ if (!targetDocId) {
80
+ // Navigate to root
81
+ onNavigate('/docs');
82
+ return;
83
+ }
84
+ // Build full path
85
+ const pathParts = [];
86
+ const visited = new Set();
87
+ let currentId = targetDocId;
88
+ const allDocs = documents || breadcrumbs;
89
+ while (currentId) {
90
+ if (visited.has(currentId))
91
+ break;
92
+ visited.add(currentId);
93
+ const doc = allDocs.find((d) => d.id === currentId);
94
+ if (!doc)
95
+ break;
96
+ pathParts.unshift(doc.slug || doc.id);
97
+ currentId = allDocs.find((d) => d.id === currentId)?.parentId ?? null;
98
+ }
99
+ if (pathParts.length === 0) {
100
+ onNavigate('/docs');
101
+ }
102
+ else {
103
+ onNavigate(`/docs/${pathParts.join('/')}`);
104
+ }
105
+ };
106
+ if (!docId) {
107
+ return (_jsx("nav", { className: `flex items-center gap-1 text-sm ${className}`, children: _jsxs("button", { onClick: () => handleNavigate(null), className: "flex items-center gap-1.5 text-gray-500 hover:text-gray-700 transition-colors", children: [_jsx(Home, { className: "h-4 w-4" }), _jsx("span", { children: homeLabel })] }) }));
108
+ }
109
+ if (isLoading && !documents) {
110
+ return (_jsxs("div", { className: `flex items-center gap-2 text-sm text-gray-400 ${className}`, children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), _jsx("span", { children: "Loading..." })] }));
111
+ }
112
+ if (error && !documents) {
113
+ return (_jsxs("div", { className: `flex items-center gap-1 text-sm text-gray-500 ${className}`, children: [_jsxs("button", { onClick: () => handleNavigate(null), className: "flex items-center gap-1.5 hover:text-gray-700 transition-colors", children: [_jsx(Home, { className: "h-4 w-4" }), _jsx("span", { children: homeLabel })] }), _jsx(ChevronRight, { className: "h-4 w-4 text-gray-400" }), _jsx("span", { className: "text-gray-400", children: "Error loading path" })] }));
114
+ }
115
+ if (breadcrumbs.length === 0) {
116
+ return (_jsx("nav", { className: `flex items-center gap-1 text-sm ${className}`, children: _jsxs("button", { onClick: () => handleNavigate(null), className: "flex items-center gap-1.5 text-gray-500 hover:text-gray-700 transition-colors", children: [_jsx(Home, { className: "h-4 w-4" }), _jsx("span", { children: homeLabel })] }) }));
117
+ }
118
+ return (_jsxs("nav", { className: `flex items-center gap-1 text-sm ${className}`, children: [_jsxs("button", { onClick: () => handleNavigate(null), className: "flex items-center gap-1.5 text-gray-500 hover:text-gray-700 transition-colors shrink-0", title: homeLabel, children: [_jsx(Home, { className: "h-4 w-4" }), _jsx("span", { className: "hidden sm:inline", children: homeLabel })] }), breadcrumbs.map((doc, index) => {
119
+ const isLast = index === breadcrumbs.length - 1;
120
+ const displayTitle = doc.title || 'Untitled';
121
+ return (_jsxs("span", { className: "flex items-center gap-1 min-w-0", children: [_jsx(ChevronRight, { className: "h-4 w-4 text-gray-400 shrink-0" }), isLast ? (_jsxs("span", { className: "font-medium text-gray-900 truncate max-w-[200px] sm:max-w-[300px] md:max-w-[400px]", title: displayTitle, children: [doc.emoji && _jsx("span", { className: "mr-1", children: doc.emoji }), displayTitle] })) : (_jsxs("button", { onClick: () => handleNavigate(doc.id), className: "text-gray-500 hover:text-gray-700 transition-colors truncate max-w-[150px] sm:max-w-[200px]", title: displayTitle, children: [doc.emoji && _jsx("span", { className: "mr-1", children: doc.emoji }), displayTitle] }))] }, doc.id));
122
+ })] }));
123
+ }
@@ -0,0 +1,20 @@
1
+ import { DocumentSummary } from '../../shared/types.js';
2
+ interface DocsLayoutProps {
3
+ theme?: 'light' | 'dark';
4
+ children: React.ReactNode;
5
+ currentDocId?: string;
6
+ /**
7
+ * Called when navigating to a document.
8
+ * `path` is the full path from root to the document (e.g., "/docs/parent/child")
9
+ */
10
+ onNavigate?: (path: string) => void;
11
+ /**
12
+ * Base path for document URLs (default: "/docs")
13
+ */
14
+ basePath?: string;
15
+ userAvatar?: React.ReactNode;
16
+ onSearch?: (query: string) => DocumentSummary[];
17
+ }
18
+ export declare function DocsLayout({ children, currentDocId, onNavigate, basePath, userAvatar, onSearch, }: DocsLayoutProps): import("react/jsx-runtime").JSX.Element;
19
+ export {};
20
+ //# sourceMappingURL=DocsLayout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DocsLayout.d.ts","sourceRoot":"","sources":["../../../src/client/components/DocsLayout.tsx"],"names":[],"mappings":"AAoCA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAavD,UAAU,eAAe;IACvB,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;IACxB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC5B,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,eAAe,EAAE,CAAA;CAChD;AAsQD,wBAAgB,UAAU,CAAC,EACzB,QAAQ,EACR,YAAY,EACZ,UAAU,EACV,QAAkB,EAClB,UAAU,EACV,QAAQ,GACT,EAAE,eAAe,2CAgcjB"}