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
@@ -0,0 +1,84 @@
1
+ import type { Document, DocumentSummary, ListDocumentsFilters, ListDocumentsOptions, ListDocumentsResult, UpdateDocument } from '../../shared/types.js';
2
+ /**
3
+ * Query key factory for documents
4
+ * Provides consistent query keys for caching and invalidation
5
+ */
6
+ export declare const docsQueryKeys: {
7
+ all: readonly ["docs"];
8
+ lists: () => readonly ["docs", "list"];
9
+ list: (filters?: ListDocumentsFilters) => readonly ["docs", "list", ListDocumentsFilters];
10
+ trees: () => readonly ["docs", "tree"];
11
+ tree: () => readonly ["docs", "tree"];
12
+ details: () => readonly ["docs", "detail"];
13
+ detail: (id: string) => readonly ["docs", "detail", string];
14
+ slug: (slug: string) => readonly ["docs", "detail", "slug", string];
15
+ children: (parentId: string) => readonly ["docs", "children", string];
16
+ search: (query: string) => readonly ["docs", "search", string];
17
+ };
18
+ /**
19
+ * Hook to list/search documents with filters
20
+ * Supports pagination, sorting, and filtering by parent, published status, and search query
21
+ */
22
+ export declare function useDocumentsQuery(filters?: ListDocumentsFilters, options?: ListDocumentsOptions, queryOptions?: {
23
+ enabled?: boolean;
24
+ }): import("@tanstack/react-query").UseQueryResult<ListDocumentsResult, Error>;
25
+ /**
26
+ * Hook to get a single document by ID
27
+ */
28
+ export declare function useDocumentQuery(id: string | null | undefined, options?: {
29
+ enabled?: boolean;
30
+ }): import("@tanstack/react-query").UseQueryResult<Document, Error>;
31
+ /**
32
+ * Hook to get a document by slug
33
+ */
34
+ export declare function useDocumentBySlugQuery(slug: string, options?: {
35
+ enabled?: boolean;
36
+ }): import("@tanstack/react-query").UseQueryResult<Document, Error>;
37
+ /**
38
+ * Hook to create a new document
39
+ * Invalidates list queries on success
40
+ */
41
+ export declare function useCreateDocumentMutation(): import("@tanstack/react-query").UseMutationResult<Document, Error, {
42
+ slug: string;
43
+ title: string;
44
+ isPublished: boolean;
45
+ content?: any;
46
+ emoji?: string | undefined;
47
+ cover?: string | null | undefined;
48
+ authorId?: number | undefined;
49
+ parentSlugs?: string[] | undefined;
50
+ }, unknown>;
51
+ /**
52
+ * Hook to update an existing document
53
+ * Invalidates the specific document and list queries on success
54
+ */
55
+ export declare function useUpdateDocumentMutation(): import("@tanstack/react-query").UseMutationResult<Document, Error, {
56
+ id: string;
57
+ data: UpdateDocument;
58
+ }, unknown>;
59
+ /**
60
+ * Hook to soft delete a document
61
+ * Removes from cache and invalidates lists on success
62
+ */
63
+ export declare function useDeleteDocumentMutation(): import("@tanstack/react-query").UseMutationResult<void, Error, string, unknown>;
64
+ /**
65
+ * Hook to reorder a document (move to new parent and/or position)
66
+ * Implements optimistic updates for immediate UI feedback
67
+ */
68
+ export declare function useReorderDocumentMutation(): import("@tanstack/react-query").UseMutationResult<DocumentSummary, Error, {
69
+ id: string;
70
+ parentId: string | null;
71
+ order: number;
72
+ }, {
73
+ previousLists: [unknown, ListDocumentsResult | undefined][];
74
+ previousTrees: [unknown, DocumentSummary[] | undefined][];
75
+ }>;
76
+ /**
77
+ * Hook to update just the order field of a document
78
+ * Useful for drag-and-drop sorting within the same parent
79
+ */
80
+ export declare function useUpdateOrderMutation(): import("@tanstack/react-query").UseMutationResult<DocumentSummary, Error, {
81
+ id: string;
82
+ order: number;
83
+ }, unknown>;
84
+ //# sourceMappingURL=useDocsQuery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useDocsQuery.d.ts","sourceRoot":"","sources":["../../../src/client/hooks/useDocsQuery.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,QAAQ,EACR,eAAe,EACf,oBAAoB,EACpB,oBAAoB,EACpB,mBAAmB,EACnB,cAAc,EACf,MAAM,uBAAuB,CAAA;AAqF9B;;;GAGG;AACH,eAAO,MAAM,aAAa;;;qBAGR,oBAAoB;;;;iBAIvB,MAAM;iBACN,MAAM;yBACE,MAAM;oBACX,MAAM;CACvB,CAAA;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,GAAE,oBAAyB,EAClC,OAAO,GAAE,oBAAyB,EAClC,YAAY,GAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,8EAOzC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,mEAMlG;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,mEAMvF;AAED;;;GAGG;AACH,wBAAgB,yBAAyB;;;;;;;;;YAUxC;AAED;;;GAGG;AACH,wBAAgB,yBAAyB;QAKS,MAAM;UAAQ,cAAc;YAM7E;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,oFAUxC;AAED;;;GAGG;AACH,wBAAgB,0BAA0B;QAgClB,MAAM;cAAY,MAAM,GAAG,IAAI;WAAS,MAAM;;mBACtC,CAAC,OAAO,EAAE,mBAAmB,GAAG,SAAS,CAAC,EAAE;mBAAiB,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,SAAS,CAAC,EAAE;GAkBtI;AAED;;;GAGG;AACH,wBAAgB,sBAAsB;QAKmB,MAAM;WAAS,MAAM;YAM7E"}
@@ -0,0 +1,241 @@
1
+ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
2
+ /**
3
+ * API client for documents
4
+ */
5
+ const api = {
6
+ list: async (filters, options) => {
7
+ const params = new URLSearchParams();
8
+ if (filters?.search)
9
+ params.set('search', filters.search);
10
+ if (filters?.parentId !== undefined)
11
+ params.set('parentId', filters.parentId ?? 'null');
12
+ if (filters?.isPublished !== undefined)
13
+ params.set('isPublished', String(filters.isPublished));
14
+ if (options?.offset)
15
+ params.set('offset', String(options.offset));
16
+ if (options?.limit)
17
+ params.set('limit', String(options.limit));
18
+ if (options?.sortBy)
19
+ params.set('sortBy', String(options.sortBy));
20
+ if (options?.sortDir)
21
+ params.set('sortDir', options.sortDir);
22
+ const res = await fetch(`/api/docs?${params}`);
23
+ if (!res.ok)
24
+ throw new Error('Failed to fetch documents');
25
+ const data = await res.json();
26
+ return {
27
+ documents: data.items,
28
+ total: data.total,
29
+ };
30
+ },
31
+ getById: async (id) => {
32
+ const res = await fetch(`/api/docs/${id}`);
33
+ if (!res.ok)
34
+ throw new Error('Failed to fetch document');
35
+ return res.json();
36
+ },
37
+ getBySlug: async (slug) => {
38
+ const res = await fetch(`/api/docs/slug/${slug}`);
39
+ if (!res.ok)
40
+ throw new Error('Failed to fetch document');
41
+ return res.json();
42
+ },
43
+ create: async (data) => {
44
+ const res = await fetch('/api/docs', {
45
+ method: 'POST',
46
+ headers: { 'Content-Type': 'application/json' },
47
+ body: JSON.stringify(data),
48
+ });
49
+ if (!res.ok)
50
+ throw new Error('Failed to create document');
51
+ return res.json();
52
+ },
53
+ update: async (id, data) => {
54
+ const res = await fetch(`/api/docs/${id}`, {
55
+ method: 'PUT',
56
+ headers: { 'Content-Type': 'application/json' },
57
+ body: JSON.stringify(data),
58
+ });
59
+ if (!res.ok)
60
+ throw new Error('Failed to update document');
61
+ return res.json();
62
+ },
63
+ delete: async (id) => {
64
+ const res = await fetch(`/api/docs/${id}`, {
65
+ method: 'DELETE',
66
+ });
67
+ if (!res.ok)
68
+ throw new Error('Failed to delete document');
69
+ },
70
+ reorder: async (id, parentId, order) => {
71
+ const res = await fetch(`/api/docs/${id}/reorder`, {
72
+ method: 'POST',
73
+ headers: { 'Content-Type': 'application/json' },
74
+ body: JSON.stringify({ parentId, order }),
75
+ });
76
+ if (!res.ok)
77
+ throw new Error('Failed to reorder document');
78
+ return res.json();
79
+ },
80
+ updateOrder: async (id, order) => {
81
+ const res = await fetch(`/api/docs/${id}/order`, {
82
+ method: 'PATCH',
83
+ headers: { 'Content-Type': 'application/json' },
84
+ body: JSON.stringify({ order }),
85
+ });
86
+ if (!res.ok)
87
+ throw new Error('Failed to update document order');
88
+ return res.json();
89
+ },
90
+ };
91
+ /**
92
+ * Query key factory for documents
93
+ * Provides consistent query keys for caching and invalidation
94
+ */
95
+ export const docsQueryKeys = {
96
+ all: ['docs'],
97
+ lists: () => [...docsQueryKeys.all, 'list'],
98
+ list: (filters = {}) => [...docsQueryKeys.lists(), filters],
99
+ trees: () => [...docsQueryKeys.all, 'tree'],
100
+ tree: () => [...docsQueryKeys.trees()],
101
+ details: () => [...docsQueryKeys.all, 'detail'],
102
+ detail: (id) => [...docsQueryKeys.details(), id],
103
+ slug: (slug) => [...docsQueryKeys.details(), 'slug', slug],
104
+ children: (parentId) => [...docsQueryKeys.all, 'children', parentId],
105
+ search: (query) => [...docsQueryKeys.all, 'search', query],
106
+ };
107
+ /**
108
+ * Hook to list/search documents with filters
109
+ * Supports pagination, sorting, and filtering by parent, published status, and search query
110
+ */
111
+ export function useDocumentsQuery(filters = {}, options = {}, queryOptions = {}) {
112
+ return useQuery({
113
+ queryKey: docsQueryKeys.list(filters),
114
+ queryFn: () => api.list(filters, options),
115
+ ...queryOptions,
116
+ });
117
+ }
118
+ /**
119
+ * Hook to get a single document by ID
120
+ */
121
+ export function useDocumentQuery(id, options = {}) {
122
+ return useQuery({
123
+ queryKey: docsQueryKeys.detail(id),
124
+ queryFn: () => api.getById(id),
125
+ enabled: !!id && options.enabled !== false,
126
+ });
127
+ }
128
+ /**
129
+ * Hook to get a document by slug
130
+ */
131
+ export function useDocumentBySlugQuery(slug, options = {}) {
132
+ return useQuery({
133
+ queryKey: docsQueryKeys.slug(slug),
134
+ queryFn: () => api.getBySlug(slug),
135
+ enabled: !!slug && options.enabled !== false,
136
+ });
137
+ }
138
+ /**
139
+ * Hook to create a new document
140
+ * Invalidates list queries on success
141
+ */
142
+ export function useCreateDocumentMutation() {
143
+ const queryClient = useQueryClient();
144
+ return useMutation({
145
+ mutationFn: api.create,
146
+ onSuccess: () => {
147
+ queryClient.invalidateQueries({ queryKey: docsQueryKeys.lists() });
148
+ queryClient.invalidateQueries({ queryKey: docsQueryKeys.trees() });
149
+ },
150
+ });
151
+ }
152
+ /**
153
+ * Hook to update an existing document
154
+ * Invalidates the specific document and list queries on success
155
+ */
156
+ export function useUpdateDocumentMutation() {
157
+ const queryClient = useQueryClient();
158
+ return useMutation({
159
+ mutationFn: ({ id, data }) => api.update(id, data),
160
+ onSuccess: (_data, variables) => {
161
+ queryClient.invalidateQueries({ queryKey: docsQueryKeys.detail(variables.id) });
162
+ queryClient.invalidateQueries({ queryKey: docsQueryKeys.lists() });
163
+ queryClient.invalidateQueries({ queryKey: docsQueryKeys.trees() });
164
+ },
165
+ });
166
+ }
167
+ /**
168
+ * Hook to soft delete a document
169
+ * Removes from cache and invalidates lists on success
170
+ */
171
+ export function useDeleteDocumentMutation() {
172
+ const queryClient = useQueryClient();
173
+ return useMutation({
174
+ mutationFn: api.delete,
175
+ onSuccess: () => {
176
+ queryClient.invalidateQueries({ queryKey: docsQueryKeys.lists() });
177
+ queryClient.invalidateQueries({ queryKey: docsQueryKeys.trees() });
178
+ },
179
+ });
180
+ }
181
+ /**
182
+ * Hook to reorder a document (move to new parent and/or position)
183
+ * Implements optimistic updates for immediate UI feedback
184
+ */
185
+ export function useReorderDocumentMutation() {
186
+ const queryClient = useQueryClient();
187
+ return useMutation({
188
+ mutationFn: ({ id, parentId, order }) => api.reorder(id, parentId, order),
189
+ onMutate: async (variables) => {
190
+ const { id, parentId, order } = variables;
191
+ // Cancel outgoing refetches
192
+ await queryClient.cancelQueries({ queryKey: docsQueryKeys.lists() });
193
+ await queryClient.cancelQueries({ queryKey: docsQueryKeys.trees() });
194
+ // Snapshot previous values
195
+ const previousLists = queryClient.getQueriesData({ queryKey: docsQueryKeys.lists() });
196
+ const previousTrees = queryClient.getQueriesData({ queryKey: docsQueryKeys.trees() });
197
+ // Optimistically update all list caches
198
+ queryClient.setQueriesData({ queryKey: docsQueryKeys.lists() }, (old) => {
199
+ if (!old)
200
+ return old;
201
+ return {
202
+ ...old,
203
+ documents: old.documents.map((doc) => doc.id === id ? { ...doc, parentId, order } : doc),
204
+ };
205
+ });
206
+ // Return context for rollback
207
+ return { previousLists, previousTrees };
208
+ },
209
+ onError: (_err, _variables, context) => {
210
+ // Rollback on error
211
+ if (context) {
212
+ context.previousLists.forEach(([key, value]) => {
213
+ queryClient.setQueryData(key, value);
214
+ });
215
+ context.previousTrees.forEach(([key, value]) => {
216
+ queryClient.setQueryData(key, value);
217
+ });
218
+ }
219
+ },
220
+ onSettled: () => {
221
+ // Always refetch after error or success
222
+ queryClient.invalidateQueries({ queryKey: docsQueryKeys.lists() });
223
+ queryClient.invalidateQueries({ queryKey: docsQueryKeys.trees() });
224
+ },
225
+ });
226
+ }
227
+ /**
228
+ * Hook to update just the order field of a document
229
+ * Useful for drag-and-drop sorting within the same parent
230
+ */
231
+ export function useUpdateOrderMutation() {
232
+ const queryClient = useQueryClient();
233
+ return useMutation({
234
+ mutationFn: ({ id, order }) => api.updateOrder(id, order),
235
+ onSuccess: (_data, variables) => {
236
+ queryClient.invalidateQueries({ queryKey: docsQueryKeys.detail(variables.id) });
237
+ queryClient.invalidateQueries({ queryKey: docsQueryKeys.lists() });
238
+ queryClient.invalidateQueries({ queryKey: docsQueryKeys.trees() });
239
+ },
240
+ });
241
+ }
@@ -0,0 +1,31 @@
1
+ export type ExportFormat = 'docx' | 'pdf' | 'html' | 'markdown';
2
+ export interface UseExportOptions {
3
+ /** Base URL for API requests (default: '/api') */
4
+ baseUrl?: string;
5
+ }
6
+ export interface UseExportReturn {
7
+ exportDocument: (documentId: string, format: ExportFormat, filename?: string) => Promise<void>;
8
+ isExporting: boolean;
9
+ error: Error | null;
10
+ }
11
+ /**
12
+ * Hook for exporting documents to various formats
13
+ *
14
+ * @example
15
+ * ```tsx
16
+ * function DocumentView({ docId }: { docId: string }) {
17
+ * const { exportDocument, isExporting } = useExport()
18
+ *
19
+ * return (
20
+ * <button
21
+ * onClick={() => exportDocument(docId, 'docx')}
22
+ * disabled={isExporting}
23
+ * >
24
+ * Export to Word
25
+ * </button>
26
+ * )
27
+ * }
28
+ * ```
29
+ */
30
+ export declare function useExport(options?: UseExportOptions): UseExportReturn;
31
+ //# sourceMappingURL=useExport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useExport.d.ts","sourceRoot":"","sources":["../../../src/client/hooks/useExport.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,UAAU,CAAA;AAE/D,MAAM,WAAW,gBAAgB;IAC/B,kDAAkD;IAClD,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,cAAc,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC9F,WAAW,EAAE,OAAO,CAAA;IACpB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAA;CACpB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,SAAS,CAAC,OAAO,GAAE,gBAAqB,GAAG,eAAe,CAwDzE"}
@@ -0,0 +1,66 @@
1
+ import { useState, useCallback } from 'react';
2
+ /**
3
+ * Hook for exporting documents to various formats
4
+ *
5
+ * @example
6
+ * ```tsx
7
+ * function DocumentView({ docId }: { docId: string }) {
8
+ * const { exportDocument, isExporting } = useExport()
9
+ *
10
+ * return (
11
+ * <button
12
+ * onClick={() => exportDocument(docId, 'docx')}
13
+ * disabled={isExporting}
14
+ * >
15
+ * Export to Word
16
+ * </button>
17
+ * )
18
+ * }
19
+ * ```
20
+ */
21
+ export function useExport(options = {}) {
22
+ const { baseUrl = '/api' } = options;
23
+ const [isExporting, setIsExporting] = useState(false);
24
+ const [error, setError] = useState(null);
25
+ const exportDocument = useCallback(async (documentId, format, filename) => {
26
+ setIsExporting(true);
27
+ setError(null);
28
+ try {
29
+ const response = await fetch(`${baseUrl}/docs/${documentId}/export?format=${format}`);
30
+ if (!response.ok) {
31
+ const errorData = await response.json().catch(() => ({}));
32
+ throw new Error(errorData.message || `Export failed: ${response.statusText}`);
33
+ }
34
+ // Get the blob
35
+ const blob = await response.blob();
36
+ // Determine filename from Content-Disposition header or use default
37
+ const contentDisposition = response.headers.get('Content-Disposition');
38
+ const downloadedFilename = contentDisposition
39
+ ?.match(/filename="([^"]+)"/)?.[1]
40
+ || filename
41
+ || `document.${format === 'markdown' ? 'md' : format}`;
42
+ // Create download link
43
+ const url = window.URL.createObjectURL(blob);
44
+ const link = document.createElement('a');
45
+ link.href = url;
46
+ link.download = downloadedFilename;
47
+ document.body.appendChild(link);
48
+ link.click();
49
+ document.body.removeChild(link);
50
+ window.URL.revokeObjectURL(url);
51
+ }
52
+ catch (err) {
53
+ const exportError = err instanceof Error ? err : new Error('Export failed');
54
+ setError(exportError);
55
+ throw exportError;
56
+ }
57
+ finally {
58
+ setIsExporting(false);
59
+ }
60
+ }, [baseUrl]);
61
+ return {
62
+ exportDocument,
63
+ isExporting,
64
+ error,
65
+ };
66
+ }
@@ -0,0 +1,44 @@
1
+ export interface FileUploadOptions {
2
+ maxSize?: number;
3
+ acceptedTypes?: string[];
4
+ onProgress?: (progress: number) => void;
5
+ }
6
+ export interface FileUploadState {
7
+ isUploading: boolean;
8
+ progress: number;
9
+ error: Error | null;
10
+ }
11
+ export interface UploadResult {
12
+ id: string;
13
+ filename: string;
14
+ mimeType: string;
15
+ size: number;
16
+ url: string;
17
+ }
18
+ /**
19
+ * Hook for handling file uploads with base64 conversion
20
+ * Features:
21
+ * - File size and type validation
22
+ * - Upload progress tracking
23
+ * - Error handling
24
+ * - Base64 conversion
25
+ */
26
+ export declare function useFileUpload(options?: FileUploadOptions): {
27
+ isUploading: boolean;
28
+ progress: number;
29
+ error: Error | null;
30
+ uploadFile: (file: File) => Promise<UploadResult>;
31
+ reset: () => void;
32
+ };
33
+ /**
34
+ * Hook for handling multiple file uploads
35
+ */
36
+ export declare function useMultiFileUpload(options?: FileUploadOptions): {
37
+ isUploading: boolean;
38
+ progress: Record<string, number>;
39
+ errors: Record<string, Error>;
40
+ results: Record<string, UploadResult>;
41
+ uploadFiles: (files: File[]) => Promise<UploadResult[]>;
42
+ reset: () => void;
43
+ };
44
+ //# sourceMappingURL=useFileUpload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFileUpload.d.ts","sourceRoot":"","sources":["../../../src/client/hooks/useFileUpload.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;CACxC;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,OAAO,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAA;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;CACZ;AAuCD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,OAAO,GAAE,iBAAsB;;;;uBAQ5C,IAAI,KAAG,OAAO,CAAC,YAAY,CAAC;;EA+E5C;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,iBAAsB;;;;;yBAShD,IAAI,EAAE,KAAG,OAAO,CAAC,YAAY,EAAE,CAAC;;EA8EjD"}
@@ -0,0 +1,193 @@
1
+ import { useCallback, useState } from 'react';
2
+ /**
3
+ * Converts a File to base64 string
4
+ */
5
+ function fileToBase64(file) {
6
+ return new Promise((resolve, reject) => {
7
+ const reader = new FileReader();
8
+ reader.onload = () => {
9
+ const result = reader.result;
10
+ const base64 = result.split(',')[1];
11
+ resolve(base64);
12
+ };
13
+ reader.onerror = reject;
14
+ reader.readAsDataURL(file);
15
+ });
16
+ }
17
+ /**
18
+ * Validates file size
19
+ */
20
+ function validateFileSize(file, maxSize) {
21
+ return file.size <= maxSize;
22
+ }
23
+ /**
24
+ * Validates file type
25
+ */
26
+ function validateFileType(file, acceptedTypes) {
27
+ if (!acceptedTypes || acceptedTypes.length === 0)
28
+ return true;
29
+ return acceptedTypes.some((type) => {
30
+ if (type.includes('*')) {
31
+ const [category] = type.split('/');
32
+ return file.type.startsWith(`${category}/`);
33
+ }
34
+ return file.type === type;
35
+ });
36
+ }
37
+ /**
38
+ * Hook for handling file uploads with base64 conversion
39
+ * Features:
40
+ * - File size and type validation
41
+ * - Upload progress tracking
42
+ * - Error handling
43
+ * - Base64 conversion
44
+ */
45
+ export function useFileUpload(options = {}) {
46
+ const { maxSize = 5 * 1024 * 1024, acceptedTypes, onProgress } = options;
47
+ const [isUploading, setIsUploading] = useState(false);
48
+ const [progress, setProgress] = useState(0);
49
+ const [error, setError] = useState(null);
50
+ const uploadFile = useCallback(async (file) => {
51
+ setIsUploading(true);
52
+ setProgress(0);
53
+ setError(null);
54
+ try {
55
+ // Validate file size
56
+ if (!validateFileSize(file, maxSize)) {
57
+ throw new Error(`File too large (max ${(maxSize / 1024 / 1024).toFixed(1)}MB)`);
58
+ }
59
+ // Validate file type
60
+ if (!validateFileType(file, acceptedTypes)) {
61
+ throw new Error(`File type not accepted. Allowed: ${acceptedTypes?.join(', ') ?? 'all'}`);
62
+ }
63
+ // Simulate progress updates
64
+ const progressInterval = setInterval(() => {
65
+ setProgress((prev) => {
66
+ const next = prev + Math.random() * 20;
67
+ if (next >= 90) {
68
+ clearInterval(progressInterval);
69
+ return 90;
70
+ }
71
+ onProgress?.(next);
72
+ return next;
73
+ });
74
+ }, 100);
75
+ // Convert to base64
76
+ const base64Content = await fileToBase64(file);
77
+ // Upload to server
78
+ const res = await fetch('/api/docs/files', {
79
+ method: 'POST',
80
+ headers: { 'Content-Type': 'application/json' },
81
+ body: JSON.stringify({
82
+ filename: file.name,
83
+ mimeType: file.type,
84
+ content: base64Content,
85
+ }),
86
+ });
87
+ clearInterval(progressInterval);
88
+ if (!res.ok) {
89
+ const errorData = await res.json().catch(() => ({ message: 'Upload failed' }));
90
+ throw new Error(errorData.message || `Upload failed: ${res.statusText}`);
91
+ }
92
+ setProgress(100);
93
+ onProgress?.(100);
94
+ const result = await res.json();
95
+ return result;
96
+ }
97
+ catch (err) {
98
+ const error = err instanceof Error ? err : new Error(String(err));
99
+ setError(error);
100
+ throw error;
101
+ }
102
+ finally {
103
+ setIsUploading(false);
104
+ }
105
+ }, [maxSize, acceptedTypes, onProgress]);
106
+ const reset = useCallback(() => {
107
+ setIsUploading(false);
108
+ setProgress(0);
109
+ setError(null);
110
+ }, []);
111
+ return {
112
+ isUploading,
113
+ progress,
114
+ error,
115
+ uploadFile,
116
+ reset,
117
+ };
118
+ }
119
+ /**
120
+ * Hook for handling multiple file uploads
121
+ */
122
+ export function useMultiFileUpload(options = {}) {
123
+ const { maxSize = 5 * 1024 * 1024, acceptedTypes } = options;
124
+ const [isUploading, setIsUploading] = useState(false);
125
+ const [progress, setProgress] = useState({});
126
+ const [errors, setErrors] = useState({});
127
+ const [results, setResults] = useState({});
128
+ const uploadFiles = useCallback(async (files) => {
129
+ setIsUploading(true);
130
+ setProgress({});
131
+ setErrors({});
132
+ setResults({});
133
+ const uploadPromises = files.map(async (file) => {
134
+ try {
135
+ // Validate file size
136
+ if (file.size > maxSize) {
137
+ throw new Error(`File "${file.name}" too large (max ${(maxSize / 1024 / 1024).toFixed(1)}MB)`);
138
+ }
139
+ // Validate file type
140
+ if (acceptedTypes && !validateFileType(file, acceptedTypes)) {
141
+ throw new Error(`File "${file.name}" type not accepted`);
142
+ }
143
+ setProgress((prev) => ({ ...prev, [file.name]: 0 }));
144
+ // Convert to base64
145
+ const base64Content = await fileToBase64(file);
146
+ setProgress((prev) => ({ ...prev, [file.name]: 50 }));
147
+ // Upload to server
148
+ const res = await fetch('/api/files', {
149
+ method: 'POST',
150
+ headers: { 'Content-Type': 'application/json' },
151
+ body: JSON.stringify({
152
+ filename: file.name,
153
+ mimeType: file.type,
154
+ content: base64Content,
155
+ }),
156
+ });
157
+ if (!res.ok) {
158
+ throw new Error(`Failed to upload "${file.name}"`);
159
+ }
160
+ const result = await res.json();
161
+ setProgress((prev) => ({ ...prev, [file.name]: 100 }));
162
+ setResults((prev) => ({ ...prev, [file.name]: result }));
163
+ return result;
164
+ }
165
+ catch (err) {
166
+ const error = err instanceof Error ? err : new Error(String(err));
167
+ setErrors((prev) => ({ ...prev, [file.name]: error }));
168
+ throw error;
169
+ }
170
+ });
171
+ try {
172
+ const allResults = await Promise.all(uploadPromises);
173
+ return allResults;
174
+ }
175
+ finally {
176
+ setIsUploading(false);
177
+ }
178
+ }, [maxSize, acceptedTypes]);
179
+ const reset = useCallback(() => {
180
+ setIsUploading(false);
181
+ setProgress({});
182
+ setErrors({});
183
+ setResults({});
184
+ }, []);
185
+ return {
186
+ isUploading,
187
+ progress,
188
+ errors,
189
+ results,
190
+ uploadFiles,
191
+ reset,
192
+ };
193
+ }
@@ -0,0 +1,2 @@
1
+ export declare const useSystemTheme: () => "light" | "dark";
2
+ //# sourceMappingURL=useSystemTheme.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSystemTheme.d.ts","sourceRoot":"","sources":["../../../src/client/hooks/useSystemTheme.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,cAAc,wBAqB1B,CAAA"}