react-embed-docs 0.4.0 → 0.5.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.
@@ -1 +1 @@
1
- {"version":3,"file":"DocumentEdit.d.ts","sourceRoot":"","sources":["../../../src/client/components/DocumentEdit.tsx"],"names":[],"mappings":"AAIA,OAAO,8BAA8B,CAAA;AAgBrC,UAAU,iBAAiB;CAAG;AAwU9B,wBAAgB,YAAY,CAAC,EAAE,EAAE,iBAAiB,2CA2RjD"}
1
+ {"version":3,"file":"DocumentEdit.d.ts","sourceRoot":"","sources":["../../../src/client/components/DocumentEdit.tsx"],"names":[],"mappings":"AAIA,OAAO,8BAA8B,CAAA;AAgBrC,UAAU,iBAAiB;CAAG;AAwU9B,wBAAgB,YAAY,CAAC,EAAE,EAAE,iBAAiB,2CAmYjD"}
@@ -4,7 +4,7 @@ import { BlockNoteView } from '@blocknote/mantine';
4
4
  import '@blocknote/mantine/style.css';
5
5
  import { useCreateBlockNote } from '@blocknote/react';
6
6
  import { Eye, ImageIcon, Loader2, Save, X } from 'lucide-react';
7
- import { useEffect, useState } from 'react';
7
+ import { useCallback, useEffect, useState } from 'react';
8
8
  import { useCreateDocumentMutation, useDocumentQuery, useUpdateDocumentMutation, } from '../hooks/useDocsQuery.js';
9
9
  import { useFileUpload } from '../hooks/useFileUpload.js';
10
10
  import { useTranslation } from '../hooks/useTranslation.js';
@@ -346,17 +346,94 @@ export function DocumentEdit({}) {
346
346
  const [isSlugManuallyEdited, setIsSlugManuallyEdited] = useState(false);
347
347
  const [emoji, setEmoji] = useState(undefined);
348
348
  const [cover, setCover] = useState(null);
349
+ const [previousCover, setPreviousCover] = useState(null);
349
350
  const [isSaving, setIsSaving] = useState(false);
350
351
  const [hasLoaded, setHasLoaded] = useState(false);
351
- // File upload hook
352
+ // Track image URLs for cleanup when deleted
353
+ const [previousImageUrls, setPreviousImageUrls] = useState(new Set());
354
+ // Helper to extract image URLs from blocks
355
+ const extractImageUrls = useCallback((blocks) => {
356
+ const urls = [];
357
+ for (const block of blocks) {
358
+ if (block.type === 'image' && block.props?.url) {
359
+ urls.push(block.props.url);
360
+ }
361
+ // Check nested content
362
+ if (block.children && block.children.length > 0) {
363
+ urls.push(...extractImageUrls(block.children));
364
+ }
365
+ }
366
+ return urls;
367
+ }, []);
368
+ // File upload hook for cover
352
369
  const { isUploading: isUploadingCover, uploadFile: uploadCover } = useFileUpload({
353
370
  maxSize: 5 * 1024 * 1024, // 5MB
354
371
  acceptedTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
355
372
  });
356
- // Initialize editor
373
+ // File upload hook for editor images
374
+ const { uploadFile: uploadEditorFile } = useFileUpload({
375
+ maxSize: 5 * 1024 * 1024, // 5MB
376
+ acceptedTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml'],
377
+ });
378
+ // Upload file handler for BlockNote editor
379
+ const handleUploadFile = useCallback(async (file) => {
380
+ const result = await uploadEditorFile(file);
381
+ return result.url;
382
+ }, [uploadEditorFile]);
383
+ // Initialize editor with file upload support
357
384
  const editor = useCreateBlockNote({
358
385
  initialContent: getDefaultContent(),
386
+ uploadFile: handleUploadFile,
359
387
  });
388
+ // Helper to extract file ID from URL
389
+ const extractFileIdFromUrl = useCallback((url) => {
390
+ // URL format: /api/docs/files/{id}
391
+ const match = url.match(/\/api\/docs\/files\/([a-zA-Z0-9_-]+)/);
392
+ return match?.[1] ?? null;
393
+ }, []);
394
+ // Soft delete file from server
395
+ const softDeleteFile = useCallback(async (fileId) => {
396
+ try {
397
+ const res = await fetch(`/api/docs/files/${fileId}`, {
398
+ method: 'DELETE',
399
+ });
400
+ if (!res.ok) {
401
+ console.error(`Failed to soft delete file ${fileId}:`, res.statusText);
402
+ }
403
+ }
404
+ catch (error) {
405
+ console.error(`Error soft deleting file ${fileId}:`, error);
406
+ }
407
+ }, []);
408
+ // Monitor editor changes and soft delete removed images
409
+ useEffect(() => {
410
+ if (!editor)
411
+ return;
412
+ const unsubscribe = editor.onChange(() => {
413
+ const currentBlocks = editor.document;
414
+ const currentImageUrls = new Set(extractImageUrls(currentBlocks));
415
+ // Find images that were in previous but not in current
416
+ for (const url of previousImageUrls) {
417
+ if (!currentImageUrls.has(url)) {
418
+ const fileId = extractFileIdFromUrl(url);
419
+ if (fileId) {
420
+ softDeleteFile(fileId);
421
+ }
422
+ }
423
+ }
424
+ setPreviousImageUrls(currentImageUrls);
425
+ });
426
+ return () => {
427
+ unsubscribe();
428
+ };
429
+ }, [editor, extractImageUrls, extractFileIdFromUrl, softDeleteFile, previousImageUrls]);
430
+ // Initialize previousImageUrls when document loads
431
+ useEffect(() => {
432
+ if (editor && hasLoaded && existingDoc) {
433
+ const urls = new Set(extractImageUrls(editor.document));
434
+ setPreviousImageUrls(urls);
435
+ }
436
+ }, [editor, hasLoaded, existingDoc, extractImageUrls]);
360
437
  // Load existing document data when available
361
438
  useEffect(() => {
362
439
  if (existingDoc && !hasLoaded) {
@@ -364,8 +441,10 @@ export function DocumentEdit({}) {
364
441
  setSlug(existingDoc.slug);
365
442
  if (existingDoc.emoji)
366
443
  setEmoji(existingDoc.emoji);
367
- if (existingDoc.cover)
444
+ if (existingDoc.cover) {
368
445
  setCover(existingDoc.cover);
446
+ setPreviousCover(existingDoc.cover);
447
+ }
369
448
  if (editor && existingDoc.content) {
370
449
  try {
371
450
  const content = typeof existingDoc.content === 'string'
@@ -405,6 +484,13 @@ export function DocumentEdit({}) {
405
484
  try {
406
485
  const content = editor.document;
407
486
  const finalSlug = slug.trim() || generateSlug(title);
487
+ // Check if cover was removed and soft delete the old file
488
+ if (previousCover && !cover) {
489
+ const fileId = extractFileIdFromUrl(previousCover);
490
+ if (fileId) {
491
+ await softDeleteFile(fileId);
492
+ }
493
+ }
408
494
  if (!existingDoc) {
409
495
  // Create new document (with parentId if it's a child document)
410
496
  const newDoc = await createMutation.mutateAsync({
@@ -431,6 +517,8 @@ export function DocumentEdit({}) {
431
517
  },
432
518
  });
433
519
  }
520
+ // Update previousCover after successful save
521
+ setPreviousCover(cover);
434
522
  }
435
523
  catch (error) {
436
524
  console.error('Failed to save document:', error);
@@ -17,25 +17,50 @@ export declare class FilesService {
17
17
  */
18
18
  upload(data: InsertFileUpload): Promise<File>;
19
19
  /**
20
- * Get a file by its ID
20
+ * Get a file by its ID (excludes soft-deleted files)
21
21
  * @param id - The file ID
22
22
  * @returns The file record or undefined if not found
23
23
  */
24
24
  getById(id: string): Promise<File | undefined>;
25
25
  /**
26
- * Delete a file by its ID
26
+ * Soft delete a file by its ID (sets deletedAt timestamp)
27
27
  * @param id - The file ID to delete
28
28
  * @throws Error if deletion fails
29
29
  */
30
30
  delete(id: string): Promise<void>;
31
31
  /**
32
- * List all files with optional pagination
33
- * @param options - Pagination options
32
+ * Hard delete a file by its ID (permanent deletion)
33
+ * @param id - The file ID to delete
34
+ * @throws Error if deletion fails
35
+ */
36
+ hardDelete(id: string): Promise<void>;
37
+ /**
38
+ * Restore a soft-deleted file
39
+ * @param id - The file ID to restore
40
+ * @throws Error if restoration fails
41
+ */
42
+ restore(id: string): Promise<void>;
43
+ /**
44
+ * List all non-deleted files with optional pagination
45
+ * @param options - Pagination options and includeDeleted flag
34
46
  * @returns Array of files and total count
35
47
  */
36
48
  list(options?: {
37
49
  limit?: number;
38
50
  offset?: number;
51
+ includeDeleted?: boolean;
52
+ }): Promise<{
53
+ files: File[];
54
+ total: number;
55
+ }>;
56
+ /**
57
+ * List soft-deleted files (for cleanup purposes)
58
+ * @param options - Pagination options
59
+ * @returns Array of deleted files and total count
60
+ */
61
+ listDeleted(options?: {
62
+ limit?: number;
63
+ offset?: number;
39
64
  }): Promise<{
40
65
  files: File[];
41
66
  total: number;
@@ -1 +1 @@
1
- {"version":3,"file":"FilesService.d.ts","sourceRoot":"","sources":["../../src/server/FilesService.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAC1D,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAA;AAC5B,OAAO,EAEL,KAAK,IAAI,EACV,MAAM,aAAa,CAAA;AAEpB;;;;GAIG;AACH,qBAAa,YAAY;IACX,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAAF,EAAE,EAAE,EAAE;IAEnC;;;;;OAKG;IACG,MAAM,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBnD;;;;OAIG;IACG,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC;IAQpD;;;;OAIG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWvC;;;;OAIG;IACG,IAAI,CAAC,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAkBzG"}
1
+ {"version":3,"file":"FilesService.d.ts","sourceRoot":"","sources":["../../src/server/FilesService.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAC1D,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAA;AAC5B,OAAO,EAEL,KAAK,IAAI,EACV,MAAM,aAAa,CAAA;AAEpB;;;;GAIG;AACH,qBAAa,YAAY;IACX,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAAF,EAAE,EAAE,EAAE;IAEnC;;;;;OAKG;IACG,MAAM,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBnD;;;;OAIG;IACG,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC;IAapD;;;;OAIG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYvC;;;;OAIG;IACG,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAW3C;;;;OAIG;IACG,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYxC;;;;OAIG;IACG,IAAI,CAAC,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAwBlI;;;;OAIG;IACG,WAAW,CAAC,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAoBhH"}
@@ -1,4 +1,4 @@
1
- import { eq, sql } from 'drizzle-orm';
1
+ import { eq, isNull, sql } from 'drizzle-orm';
2
2
  import { nanoid } from 'nanoid';
3
3
  import { filesTable } from './schema.js';
4
4
  /**
@@ -32,7 +32,7 @@ export class FilesService {
32
32
  return result;
33
33
  }
34
34
  /**
35
- * Get a file by its ID
35
+ * Get a file by its ID (excludes soft-deleted files)
36
36
  * @param id - The file ID
37
37
  * @returns The file record or undefined if not found
38
38
  */
@@ -40,14 +40,33 @@ export class FilesService {
40
40
  const file = await this.db.query.filesTable.findFirst({
41
41
  where: eq(filesTable.id, id),
42
42
  });
43
+ // Return undefined if file is soft-deleted
44
+ if (file?.deletedAt) {
45
+ return undefined;
46
+ }
43
47
  return file;
44
48
  }
45
49
  /**
46
- * Delete a file by its ID
50
+ * Soft delete a file by its ID (sets deletedAt timestamp)
47
51
  * @param id - The file ID to delete
48
52
  * @throws Error if deletion fails
49
53
  */
50
54
  async delete(id) {
55
+ const [result] = await this.db
56
+ .update(filesTable)
57
+ .set({ deletedAt: new Date() })
58
+ .where(eq(filesTable.id, id))
59
+ .returning();
60
+ if (!result) {
61
+ throw new Error('File not found');
62
+ }
63
+ }
64
+ /**
65
+ * Hard delete a file by its ID (permanent deletion)
66
+ * @param id - The file ID to delete
67
+ * @throws Error if deletion fails
68
+ */
69
+ async hardDelete(id) {
51
70
  const [result] = await this.db
52
71
  .delete(filesTable)
53
72
  .where(eq(filesTable.id, id))
@@ -57,21 +76,61 @@ export class FilesService {
57
76
  }
58
77
  }
59
78
  /**
60
- * List all files with optional pagination
61
- * @param options - Pagination options
79
+ * Restore a soft-deleted file
80
+ * @param id - The file ID to restore
81
+ * @throws Error if restoration fails
82
+ */
83
+ async restore(id) {
84
+ const [result] = await this.db
85
+ .update(filesTable)
86
+ .set({ deletedAt: null })
87
+ .where(eq(filesTable.id, id))
88
+ .returning();
89
+ if (!result) {
90
+ throw new Error('File not found');
91
+ }
92
+ }
93
+ /**
94
+ * List all non-deleted files with optional pagination
95
+ * @param options - Pagination options and includeDeleted flag
62
96
  * @returns Array of files and total count
63
97
  */
64
98
  async list(options = {}) {
65
- const { limit = 50, offset = 0 } = options;
99
+ const { limit = 50, offset = 0, includeDeleted = false } = options;
100
+ // Build where condition based on includeDeleted flag
101
+ const whereCondition = includeDeleted ? undefined : isNull(filesTable.deletedAt);
66
102
  const files = await this.db.query.filesTable.findMany({
103
+ where: whereCondition,
67
104
  limit,
68
105
  offset,
69
106
  orderBy: (files, { desc }) => [desc(files.createdAt)],
70
107
  });
71
- // Get total count
108
+ // Get total count (filtered by deleted status)
109
+ const totalResult = await this.db
110
+ .select({ count: sql `count(*)::int` })
111
+ .from(filesTable)
112
+ .where(whereCondition ?? sql `TRUE`);
113
+ const total = Number(totalResult[0]?.count ?? 0);
114
+ return { files, total };
115
+ }
116
+ /**
117
+ * List soft-deleted files (for cleanup purposes)
118
+ * @param options - Pagination options
119
+ * @returns Array of deleted files and total count
120
+ */
121
+ async listDeleted(options = {}) {
122
+ const { limit = 50, offset = 0 } = options;
123
+ const files = await this.db.query.filesTable.findMany({
124
+ where: sql `${filesTable.deletedAt} IS NOT NULL`,
125
+ limit,
126
+ offset,
127
+ orderBy: (files, { desc }) => [desc(files.deletedAt)],
128
+ });
129
+ // Get total count of deleted files
72
130
  const totalResult = await this.db
73
131
  .select({ count: sql `count(*)::int` })
74
- .from(filesTable);
132
+ .from(filesTable)
133
+ .where(sql `${filesTable.deletedAt} IS NOT NULL`);
75
134
  const total = Number(totalResult[0]?.count ?? 0);
76
135
  return { files, total };
77
136
  }
@@ -307,6 +307,18 @@ export declare const filesTable: import("drizzle-orm/pg-core").PgTableWithColumn
307
307
  enumValues: undefined;
308
308
  baseColumn: never;
309
309
  }, {}, {}>;
310
+ deletedAt: import("drizzle-orm/pg-core").PgColumn<{
311
+ name: "deletedAt";
312
+ tableName: "files";
313
+ dataType: "date";
314
+ columnType: "PgTimestamp";
315
+ data: Date;
316
+ driverParam: string;
317
+ notNull: false;
318
+ hasDefault: false;
319
+ enumValues: undefined;
320
+ baseColumn: never;
321
+ }, {}, {}>;
310
322
  };
311
323
  dialect: "pg";
312
324
  }>;
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/server/schema.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAErD;;;GAGG;AACH,eAAO,MAAM,eAAe,gDAAmB,CAAA;AAE/C;;;GAGG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2CxB,CAAA;AAEH;;;GAGG;AACH,eAAO,MAAM,iBAAiB;;;EAM3B,CAAA;AAEH;;;GAGG;AACH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAcpB,CAAA;AAEH;;;GAGG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuB/B,CAAA;AAEH;;;GAGG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAa/B,CAAA;AAEH;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,cAAc,CAAC,YAAY,CAAA;AAC/D,MAAM,MAAM,QAAQ,GAAG,OAAO,cAAc,CAAC,YAAY,CAAA;AACzD,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CAAA;AAEpD,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAC,YAAY,CAAA;AACvD,MAAM,MAAM,IAAI,GAAG,OAAO,UAAU,CAAC,YAAY,CAAA;AAEjD,MAAM,MAAM,qBAAqB,GAAG,OAAO,qBAAqB,CAAC,YAAY,CAAA;AAC7E,MAAM,MAAM,eAAe,GAAG,OAAO,qBAAqB,CAAC,YAAY,CAAA;AAEvE,MAAM,MAAM,sBAAsB,GAAG,OAAO,qBAAqB,CAAC,YAAY,CAAA;AAC9E,MAAM,MAAM,gBAAgB,GAAG,OAAO,qBAAqB,CAAC,YAAY,CAAA"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/server/schema.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAErD;;;GAGG;AACH,eAAO,MAAM,eAAe,gDAAmB,CAAA;AAE/C;;;GAGG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2CxB,CAAA;AAEH;;;GAGG;AACH,eAAO,MAAM,iBAAiB;;;EAM3B,CAAA;AAEH;;;GAGG;AACH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiBpB,CAAA;AAEH;;;GAGG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuB/B,CAAA;AAEH;;;GAGG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAa/B,CAAA;AAEH;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,cAAc,CAAC,YAAY,CAAA;AAC/D,MAAM,MAAM,QAAQ,GAAG,OAAO,cAAc,CAAC,YAAY,CAAA;AACzD,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CAAA;AAEpD,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAC,YAAY,CAAA;AACvD,MAAM,MAAM,IAAI,GAAG,OAAO,UAAU,CAAC,YAAY,CAAA;AAEjD,MAAM,MAAM,qBAAqB,GAAG,OAAO,qBAAqB,CAAC,YAAY,CAAA;AAC7E,MAAM,MAAM,eAAe,GAAG,OAAO,qBAAqB,CAAC,YAAY,CAAA;AAEvE,MAAM,MAAM,sBAAsB,GAAG,OAAO,qBAAqB,CAAC,YAAY,CAAA;AAC9E,MAAM,MAAM,gBAAgB,GAAG,OAAO,qBAAqB,CAAC,YAAY,CAAA"}
@@ -75,11 +75,14 @@ export const filesTable = docsTableSchema.table('files', {
75
75
  createdAt: timestamp('createdAt')
76
76
  .default(sql `NOW()`)
77
77
  .notNull(),
78
+ deletedAt: timestamp('deletedAt'), // Soft delete for files
78
79
  }, (table) => ({
79
80
  // Index for file lookups
80
81
  filenameIdx: index('filename_idx').on(table.filename),
81
82
  // Index for mime type filtering
82
83
  mimeTypeIdx: index('mime_type_idx').on(table.mimeType),
84
+ // Index for soft delete queries
85
+ filesDeletedAtIdx: index('files_deleted_at_idx').on(table.deletedAt),
83
86
  }));
84
87
  /**
85
88
  * Document versions table
@@ -0,0 +1,2 @@
1
+ ALTER TABLE "docs"."files" ADD COLUMN "deletedAt" timestamp;--> statement-breakpoint
2
+ CREATE INDEX IF NOT EXISTS "files_deleted_at_idx" ON "docs"."files" USING btree ("deletedAt");
@@ -0,0 +1,595 @@
1
+ {
2
+ "id": "cd711252-7226-4bb5-a58d-0867586c7463",
3
+ "prevId": "31106a8d-1ede-402b-9721-ead67be66c4d",
4
+ "version": "7",
5
+ "dialect": "postgresql",
6
+ "tables": {
7
+ "docs.document_presence": {
8
+ "name": "document_presence",
9
+ "schema": "docs",
10
+ "columns": {
11
+ "id": {
12
+ "name": "id",
13
+ "type": "uuid",
14
+ "primaryKey": true,
15
+ "notNull": true,
16
+ "default": "gen_random_uuid()"
17
+ },
18
+ "document_id": {
19
+ "name": "document_id",
20
+ "type": "varchar(21)",
21
+ "primaryKey": false,
22
+ "notNull": true
23
+ },
24
+ "user_id": {
25
+ "name": "user_id",
26
+ "type": "varchar(255)",
27
+ "primaryKey": false,
28
+ "notNull": true
29
+ },
30
+ "user_name": {
31
+ "name": "user_name",
32
+ "type": "varchar(255)",
33
+ "primaryKey": false,
34
+ "notNull": false
35
+ },
36
+ "user_color": {
37
+ "name": "user_color",
38
+ "type": "varchar(7)",
39
+ "primaryKey": false,
40
+ "notNull": false
41
+ },
42
+ "cursor_position": {
43
+ "name": "cursor_position",
44
+ "type": "jsonb",
45
+ "primaryKey": false,
46
+ "notNull": false
47
+ },
48
+ "selection": {
49
+ "name": "selection",
50
+ "type": "jsonb",
51
+ "primaryKey": false,
52
+ "notNull": false
53
+ },
54
+ "last_seen_at": {
55
+ "name": "last_seen_at",
56
+ "type": "timestamp",
57
+ "primaryKey": false,
58
+ "notNull": true,
59
+ "default": "NOW()"
60
+ }
61
+ },
62
+ "indexes": {
63
+ "presence_idx": {
64
+ "name": "presence_idx",
65
+ "columns": [
66
+ {
67
+ "expression": "document_id",
68
+ "isExpression": false,
69
+ "asc": true,
70
+ "nulls": "last"
71
+ },
72
+ {
73
+ "expression": "user_id",
74
+ "isExpression": false,
75
+ "asc": true,
76
+ "nulls": "last"
77
+ }
78
+ ],
79
+ "isUnique": false,
80
+ "concurrently": false,
81
+ "method": "btree",
82
+ "with": {}
83
+ },
84
+ "last_seen_idx": {
85
+ "name": "last_seen_idx",
86
+ "columns": [
87
+ {
88
+ "expression": "last_seen_at",
89
+ "isExpression": false,
90
+ "asc": true,
91
+ "nulls": "last"
92
+ }
93
+ ],
94
+ "isUnique": false,
95
+ "concurrently": false,
96
+ "method": "btree",
97
+ "with": {}
98
+ }
99
+ },
100
+ "foreignKeys": {
101
+ "document_presence_document_id_documents_id_fk": {
102
+ "name": "document_presence_document_id_documents_id_fk",
103
+ "tableFrom": "document_presence",
104
+ "tableTo": "documents",
105
+ "schemaTo": "docs",
106
+ "columnsFrom": [
107
+ "document_id"
108
+ ],
109
+ "columnsTo": [
110
+ "id"
111
+ ],
112
+ "onDelete": "no action",
113
+ "onUpdate": "no action"
114
+ }
115
+ },
116
+ "compositePrimaryKeys": {},
117
+ "uniqueConstraints": {}
118
+ },
119
+ "docs.document_versions": {
120
+ "name": "document_versions",
121
+ "schema": "docs",
122
+ "columns": {
123
+ "id": {
124
+ "name": "id",
125
+ "type": "uuid",
126
+ "primaryKey": true,
127
+ "notNull": true,
128
+ "default": "gen_random_uuid()"
129
+ },
130
+ "document_id": {
131
+ "name": "document_id",
132
+ "type": "varchar(21)",
133
+ "primaryKey": false,
134
+ "notNull": true
135
+ },
136
+ "version_number": {
137
+ "name": "version_number",
138
+ "type": "integer",
139
+ "primaryKey": false,
140
+ "notNull": true
141
+ },
142
+ "change_description": {
143
+ "name": "change_description",
144
+ "type": "text",
145
+ "primaryKey": false,
146
+ "notNull": false
147
+ },
148
+ "title": {
149
+ "name": "title",
150
+ "type": "varchar(255)",
151
+ "primaryKey": false,
152
+ "notNull": true
153
+ },
154
+ "content": {
155
+ "name": "content",
156
+ "type": "jsonb",
157
+ "primaryKey": false,
158
+ "notNull": true
159
+ },
160
+ "emoji": {
161
+ "name": "emoji",
162
+ "type": "varchar(10)",
163
+ "primaryKey": false,
164
+ "notNull": false
165
+ },
166
+ "cover": {
167
+ "name": "cover",
168
+ "type": "varchar(500)",
169
+ "primaryKey": false,
170
+ "notNull": false
171
+ },
172
+ "ydoc_state": {
173
+ "name": "ydoc_state",
174
+ "type": "text",
175
+ "primaryKey": false,
176
+ "notNull": false
177
+ },
178
+ "created_by": {
179
+ "name": "created_by",
180
+ "type": "varchar(255)",
181
+ "primaryKey": false,
182
+ "notNull": false
183
+ },
184
+ "created_by_name": {
185
+ "name": "created_by_name",
186
+ "type": "varchar(255)",
187
+ "primaryKey": false,
188
+ "notNull": false
189
+ },
190
+ "created_at": {
191
+ "name": "created_at",
192
+ "type": "timestamp",
193
+ "primaryKey": false,
194
+ "notNull": true,
195
+ "default": "NOW()"
196
+ }
197
+ },
198
+ "indexes": {
199
+ "document_version_idx": {
200
+ "name": "document_version_idx",
201
+ "columns": [
202
+ {
203
+ "expression": "document_id",
204
+ "isExpression": false,
205
+ "asc": true,
206
+ "nulls": "last"
207
+ },
208
+ {
209
+ "expression": "version_number",
210
+ "isExpression": false,
211
+ "asc": true,
212
+ "nulls": "last"
213
+ }
214
+ ],
215
+ "isUnique": false,
216
+ "concurrently": false,
217
+ "method": "btree",
218
+ "with": {}
219
+ },
220
+ "version_created_at_idx": {
221
+ "name": "version_created_at_idx",
222
+ "columns": [
223
+ {
224
+ "expression": "created_at",
225
+ "isExpression": false,
226
+ "asc": true,
227
+ "nulls": "last"
228
+ }
229
+ ],
230
+ "isUnique": false,
231
+ "concurrently": false,
232
+ "method": "btree",
233
+ "with": {}
234
+ }
235
+ },
236
+ "foreignKeys": {
237
+ "document_versions_document_id_documents_id_fk": {
238
+ "name": "document_versions_document_id_documents_id_fk",
239
+ "tableFrom": "document_versions",
240
+ "tableTo": "documents",
241
+ "schemaTo": "docs",
242
+ "columnsFrom": [
243
+ "document_id"
244
+ ],
245
+ "columnsTo": [
246
+ "id"
247
+ ],
248
+ "onDelete": "no action",
249
+ "onUpdate": "no action"
250
+ }
251
+ },
252
+ "compositePrimaryKeys": {},
253
+ "uniqueConstraints": {}
254
+ },
255
+ "docs.documents": {
256
+ "name": "documents",
257
+ "schema": "docs",
258
+ "columns": {
259
+ "id": {
260
+ "name": "id",
261
+ "type": "varchar(21)",
262
+ "primaryKey": true,
263
+ "notNull": true
264
+ },
265
+ "title": {
266
+ "name": "title",
267
+ "type": "varchar(255)",
268
+ "primaryKey": false,
269
+ "notNull": true
270
+ },
271
+ "slug": {
272
+ "name": "slug",
273
+ "type": "varchar(255)",
274
+ "primaryKey": false,
275
+ "notNull": true
276
+ },
277
+ "content": {
278
+ "name": "content",
279
+ "type": "jsonb",
280
+ "primaryKey": false,
281
+ "notNull": true,
282
+ "default": "'[]'"
283
+ },
284
+ "search_index": {
285
+ "name": "search_index",
286
+ "type": "text",
287
+ "primaryKey": false,
288
+ "notNull": false
289
+ },
290
+ "emoji": {
291
+ "name": "emoji",
292
+ "type": "varchar(10)",
293
+ "primaryKey": false,
294
+ "notNull": false
295
+ },
296
+ "cover": {
297
+ "name": "cover",
298
+ "type": "varchar(500)",
299
+ "primaryKey": false,
300
+ "notNull": false
301
+ },
302
+ "isPublished": {
303
+ "name": "isPublished",
304
+ "type": "boolean",
305
+ "primaryKey": false,
306
+ "notNull": true,
307
+ "default": true
308
+ },
309
+ "parentId": {
310
+ "name": "parentId",
311
+ "type": "varchar(21)",
312
+ "primaryKey": false,
313
+ "notNull": false
314
+ },
315
+ "order": {
316
+ "name": "order",
317
+ "type": "integer",
318
+ "primaryKey": false,
319
+ "notNull": true,
320
+ "default": 0
321
+ },
322
+ "authorId": {
323
+ "name": "authorId",
324
+ "type": "integer",
325
+ "primaryKey": false,
326
+ "notNull": false
327
+ },
328
+ "ydoc_state": {
329
+ "name": "ydoc_state",
330
+ "type": "text",
331
+ "primaryKey": false,
332
+ "notNull": false
333
+ },
334
+ "last_modified_by": {
335
+ "name": "last_modified_by",
336
+ "type": "varchar(255)",
337
+ "primaryKey": false,
338
+ "notNull": false
339
+ },
340
+ "last_modified_at": {
341
+ "name": "last_modified_at",
342
+ "type": "timestamp",
343
+ "primaryKey": false,
344
+ "notNull": false
345
+ },
346
+ "createdAt": {
347
+ "name": "createdAt",
348
+ "type": "timestamp",
349
+ "primaryKey": false,
350
+ "notNull": true,
351
+ "default": "NOW()"
352
+ },
353
+ "updatedAt": {
354
+ "name": "updatedAt",
355
+ "type": "timestamp",
356
+ "primaryKey": false,
357
+ "notNull": true,
358
+ "default": "NOW()"
359
+ },
360
+ "deletedAt": {
361
+ "name": "deletedAt",
362
+ "type": "timestamp",
363
+ "primaryKey": false,
364
+ "notNull": false
365
+ }
366
+ },
367
+ "indexes": {
368
+ "content_search_idx": {
369
+ "name": "content_search_idx",
370
+ "columns": [
371
+ {
372
+ "expression": "content",
373
+ "isExpression": false,
374
+ "asc": true,
375
+ "nulls": "last"
376
+ }
377
+ ],
378
+ "isUnique": false,
379
+ "concurrently": false,
380
+ "method": "gin",
381
+ "with": {}
382
+ },
383
+ "title_search_idx": {
384
+ "name": "title_search_idx",
385
+ "columns": [
386
+ {
387
+ "expression": "title",
388
+ "isExpression": false,
389
+ "asc": true,
390
+ "nulls": "last"
391
+ }
392
+ ],
393
+ "isUnique": false,
394
+ "concurrently": false,
395
+ "method": "btree",
396
+ "with": {}
397
+ },
398
+ "search_index_idx": {
399
+ "name": "search_index_idx",
400
+ "columns": [
401
+ {
402
+ "expression": "search_index",
403
+ "isExpression": false,
404
+ "asc": true,
405
+ "nulls": "last"
406
+ }
407
+ ],
408
+ "isUnique": false,
409
+ "concurrently": false,
410
+ "method": "btree",
411
+ "with": {}
412
+ },
413
+ "deleted_at_idx": {
414
+ "name": "deleted_at_idx",
415
+ "columns": [
416
+ {
417
+ "expression": "deletedAt",
418
+ "isExpression": false,
419
+ "asc": true,
420
+ "nulls": "last"
421
+ }
422
+ ],
423
+ "isUnique": false,
424
+ "concurrently": false,
425
+ "method": "btree",
426
+ "with": {}
427
+ },
428
+ "parent_id_idx": {
429
+ "name": "parent_id_idx",
430
+ "columns": [
431
+ {
432
+ "expression": "parentId",
433
+ "isExpression": false,
434
+ "asc": true,
435
+ "nulls": "last"
436
+ }
437
+ ],
438
+ "isUnique": false,
439
+ "concurrently": false,
440
+ "method": "btree",
441
+ "with": {}
442
+ },
443
+ "order_idx": {
444
+ "name": "order_idx",
445
+ "columns": [
446
+ {
447
+ "expression": "order",
448
+ "isExpression": false,
449
+ "asc": true,
450
+ "nulls": "last"
451
+ }
452
+ ],
453
+ "isUnique": false,
454
+ "concurrently": false,
455
+ "method": "btree",
456
+ "with": {}
457
+ }
458
+ },
459
+ "foreignKeys": {
460
+ "documents_parentId_documents_id_fk": {
461
+ "name": "documents_parentId_documents_id_fk",
462
+ "tableFrom": "documents",
463
+ "tableTo": "documents",
464
+ "schemaTo": "docs",
465
+ "columnsFrom": [
466
+ "parentId"
467
+ ],
468
+ "columnsTo": [
469
+ "id"
470
+ ],
471
+ "onDelete": "no action",
472
+ "onUpdate": "no action"
473
+ }
474
+ },
475
+ "compositePrimaryKeys": {},
476
+ "uniqueConstraints": {
477
+ "documents_slug_unique": {
478
+ "name": "documents_slug_unique",
479
+ "nullsNotDistinct": false,
480
+ "columns": [
481
+ "slug"
482
+ ]
483
+ }
484
+ }
485
+ },
486
+ "docs.files": {
487
+ "name": "files",
488
+ "schema": "docs",
489
+ "columns": {
490
+ "id": {
491
+ "name": "id",
492
+ "type": "varchar(21)",
493
+ "primaryKey": true,
494
+ "notNull": true
495
+ },
496
+ "filename": {
497
+ "name": "filename",
498
+ "type": "varchar(255)",
499
+ "primaryKey": false,
500
+ "notNull": true
501
+ },
502
+ "mimeType": {
503
+ "name": "mimeType",
504
+ "type": "varchar(100)",
505
+ "primaryKey": false,
506
+ "notNull": true
507
+ },
508
+ "size": {
509
+ "name": "size",
510
+ "type": "integer",
511
+ "primaryKey": false,
512
+ "notNull": true
513
+ },
514
+ "content": {
515
+ "name": "content",
516
+ "type": "text",
517
+ "primaryKey": false,
518
+ "notNull": true
519
+ },
520
+ "createdAt": {
521
+ "name": "createdAt",
522
+ "type": "timestamp",
523
+ "primaryKey": false,
524
+ "notNull": true,
525
+ "default": "NOW()"
526
+ },
527
+ "deletedAt": {
528
+ "name": "deletedAt",
529
+ "type": "timestamp",
530
+ "primaryKey": false,
531
+ "notNull": false
532
+ }
533
+ },
534
+ "indexes": {
535
+ "filename_idx": {
536
+ "name": "filename_idx",
537
+ "columns": [
538
+ {
539
+ "expression": "filename",
540
+ "isExpression": false,
541
+ "asc": true,
542
+ "nulls": "last"
543
+ }
544
+ ],
545
+ "isUnique": false,
546
+ "concurrently": false,
547
+ "method": "btree",
548
+ "with": {}
549
+ },
550
+ "mime_type_idx": {
551
+ "name": "mime_type_idx",
552
+ "columns": [
553
+ {
554
+ "expression": "mimeType",
555
+ "isExpression": false,
556
+ "asc": true,
557
+ "nulls": "last"
558
+ }
559
+ ],
560
+ "isUnique": false,
561
+ "concurrently": false,
562
+ "method": "btree",
563
+ "with": {}
564
+ },
565
+ "files_deleted_at_idx": {
566
+ "name": "files_deleted_at_idx",
567
+ "columns": [
568
+ {
569
+ "expression": "deletedAt",
570
+ "isExpression": false,
571
+ "asc": true,
572
+ "nulls": "last"
573
+ }
574
+ ],
575
+ "isUnique": false,
576
+ "concurrently": false,
577
+ "method": "btree",
578
+ "with": {}
579
+ }
580
+ },
581
+ "foreignKeys": {},
582
+ "compositePrimaryKeys": {},
583
+ "uniqueConstraints": {}
584
+ }
585
+ },
586
+ "enums": {},
587
+ "schemas": {
588
+ "docs": "docs"
589
+ },
590
+ "_meta": {
591
+ "columns": {},
592
+ "schemas": {},
593
+ "tables": {}
594
+ }
595
+ }
@@ -8,6 +8,13 @@
8
8
  "when": 1770028583768,
9
9
  "tag": "0000_gray_monster_badoon",
10
10
  "breakpoints": true
11
+ },
12
+ {
13
+ "idx": 1,
14
+ "version": "7",
15
+ "when": 1770609760944,
16
+ "tag": "0001_omniscient_fallen_one",
17
+ "breakpoints": true
11
18
  }
12
19
  ]
13
20
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-embed-docs",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Full-stack documentation system with BlockNote editor, hierarchical structure, and file uploads",
5
5
  "type": "module",
6
6
  "main": "./dist/server/index.js",