react-embed-docs 0.4.0 → 0.6.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.
- package/README.md +18 -0
- package/dist/client/components/DocumentEdit.d.ts.map +1 -1
- package/dist/client/components/DocumentEdit.js +95 -7
- package/dist/client/components/DocumentView.js +2 -2
- package/dist/client/components/Layout.d.ts.map +1 -1
- package/dist/client/components/Layout.js +23 -5
- package/dist/server/FilesService.d.ts +29 -4
- package/dist/server/FilesService.d.ts.map +1 -1
- package/dist/server/FilesService.js +67 -8
- package/dist/server/schema.d.ts +12 -0
- package/dist/server/schema.d.ts.map +1 -1
- package/dist/server/schema.js +3 -0
- package/drizzle/migrations/0001_omniscient_fallen_one.sql +2 -0
- package/drizzle/migrations/meta/0001_snapshot.json +595 -0
- package/drizzle/migrations/meta/_journal.json +7 -0
- package/package.json +1 -1
- package/styles/docs.css +130 -0
package/README.md
CHANGED
|
@@ -306,6 +306,24 @@ module.exports = {
|
|
|
306
306
|
|
|
307
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
308
|
|
|
309
|
+
### Mobile / Responsive
|
|
310
|
+
|
|
311
|
+
The components are fully responsive and mobile-friendly. For optimal mobile experience, ensure your HTML includes the viewport meta tag:
|
|
312
|
+
|
|
313
|
+
```html
|
|
314
|
+
<head>
|
|
315
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
|
|
316
|
+
</head>
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
**Mobile features:**
|
|
320
|
+
- Hamburger menu for sidebar navigation on screens < 768px
|
|
321
|
+
- Responsive typography that scales down on mobile
|
|
322
|
+
- Touch-friendly button and input sizes (min 44px touch targets)
|
|
323
|
+
- Collapsible sidebar that becomes an overlay on mobile
|
|
324
|
+
- Optimized search dropdown for small screens
|
|
325
|
+
- Flexible layouts that stack vertically on mobile
|
|
326
|
+
|
|
309
327
|
## Configuration
|
|
310
328
|
|
|
311
329
|
### Custom API Prefix
|
|
@@ -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,
|
|
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,2CAqYjD"}
|
|
@@ -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
|
-
//
|
|
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
|
-
//
|
|
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);
|
|
@@ -467,9 +555,9 @@ export function DocumentEdit({}) {
|
|
|
467
555
|
if (!isNewDocument && isLoadingDoc) {
|
|
468
556
|
return (_jsx("div", { className: "flex items-center justify-center min-h-100", children: _jsx(Loader2, { className: "h-8 w-8 animate-spin text-gray-400" }) }));
|
|
469
557
|
}
|
|
470
|
-
return (_jsxs("div", { className: "mx-auto h-full flex flex-col", children: [_jsxs("div", { className: "mb-6 shrink-0", children: [_jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { className: "flex items-center gap-2 flex-1", children: [_jsx(EmojiPicker, { value: emoji, onChange: setEmoji }), _jsx("input", { type: "text", value: title, onChange: (e) => setTitle(e.target.value), placeholder: t('editor.titlePlaceholder'), className: "text-2xl font-bold bg-transparent border-none outline-none px-0 flex-1 placeholder:text-gray-400" })] }), _jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [!isNewDocument && existingDoc && (_jsxs("button", { onClick: handleView, className: "px-3 py-1.5 text-sm border border-border rounded-md hover:bg-gray-50 flex items-center gap-2 transition-colors", children: [_jsx(Eye, { className: "h-4 w-4" }), t('editor.view')] })), _jsx("button", { onClick: handleSave, disabled: isSaving || createMutation.isPending || updateMutation.isPending, className: "px-3 py-1.5 text-sm bg-primary text-white rounded-md hover:bg-gray-800 disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2 transition-colors", children: isSaving ||
|
|
558
|
+
return (_jsxs("div", { className: "mx-auto h-full flex flex-col max-w-full sm:max-w-[90%] lg:max-w-[80%]", children: [_jsxs("div", { className: "mb-4 sm:mb-6 shrink-0", children: [_jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start justify-between gap-3 sm:gap-4", children: [_jsxs("div", { className: "flex items-center gap-2 flex-1 min-w-0", children: [_jsx(EmojiPicker, { value: emoji, onChange: setEmoji }), _jsx("input", { type: "text", value: title, onChange: (e) => setTitle(e.target.value), placeholder: t('editor.titlePlaceholder'), className: "text-xl sm:text-2xl font-bold bg-transparent border-none outline-none px-0 flex-1 placeholder:text-gray-400 min-w-0" })] }), _jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [!isNewDocument && existingDoc && (_jsxs("button", { onClick: handleView, className: "px-2 sm:px-3 py-1.5 text-sm border border-border rounded-md hover:bg-gray-50 flex items-center gap-1 sm:gap-2 transition-colors", children: [_jsx(Eye, { className: "h-4 w-4" }), _jsx("span", { className: "hidden sm:inline", children: t('editor.view') })] })), _jsx("button", { onClick: handleSave, disabled: isSaving || createMutation.isPending || updateMutation.isPending, className: "px-2 sm:px-3 py-1.5 text-sm bg-primary text-white rounded-md hover:bg-gray-800 disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-1 sm:gap-2 transition-colors", children: isSaving ||
|
|
471
559
|
createMutation.isPending ||
|
|
472
|
-
updateMutation.isPending ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), t('editor.saving')] })) : (_jsxs(_Fragment, { children: [_jsx(Save, { className: "h-4 w-4" }), isNewDocument ? t('editor.create') : t('editor.save')] })) })] })] }), _jsxs("div", { className: "text-sm text-gray-500 mt-2 flex items-center gap-2", children: [_jsxs("span", { className: "shrink-0", children: [t('editor.slug'), ":"] }), _jsx("input", { type: "text", value: slug, onChange: (e) => {
|
|
560
|
+
updateMutation.isPending ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), _jsx("span", { className: "hidden sm:inline", children: t('editor.saving') })] })) : (_jsxs(_Fragment, { children: [_jsx(Save, { className: "h-4 w-4" }), _jsx("span", { className: "hidden sm:inline", children: isNewDocument ? t('editor.create') : t('editor.save') })] })) })] })] }), _jsxs("div", { className: "text-sm text-gray-500 mt-2 flex items-center gap-2 flex-wrap", children: [_jsxs("span", { className: "shrink-0", children: [t('editor.slug'), ":"] }), _jsx("input", { type: "text", value: slug, onChange: (e) => {
|
|
473
561
|
setSlug(e.target.value);
|
|
474
562
|
setIsSlugManuallyEdited(true);
|
|
475
563
|
}, onBlur: (e) => {
|
|
@@ -477,5 +565,5 @@ export function DocumentEdit({}) {
|
|
|
477
565
|
setIsSlugManuallyEdited(false);
|
|
478
566
|
setSlug(generateSlug(title));
|
|
479
567
|
}
|
|
480
|
-
}, placeholder: generateSlug(title), className: "bg-transparent border-none outline-none px-0 flex-1 text-gray-500 placeholder:text-gray-400 font-mono text-sm" })] })] }), _jsx("div", { className: "mb-6 shrink-0", children: cover ? (_jsxs("div", { className: "relative w-full h-80 rounded-lg overflow-hidden bg-gray-100", children: [_jsx("img", { src: cover, alt: t('editor.coverAlt'), className: "w-full h-full object-cover" }), _jsx("button", { onClick: () => setCover(null), className: "absolute top-2 right-2 p-1.5 bg-secondary hover:bg-white rounded-md shadow-sm transition-colors", title: t('editor.removeCover'), children: _jsx(X, { className: "h-4 w-4" }) })] })) : (_jsxs("div", { onDrop: handleDrop, onDragOver: handleDragOver, className: "relative w-full h-32 border-2 border-dashed border-border rounded-lg bg-secondary hover:bg-gray-100 transition-colors cursor-pointer", children: [_jsx("input", { type: "file", accept: "image/jpeg,image/png,image/gif,image/webp", onChange: handleFileInput, className: "absolute inset-0 w-full h-full opacity-0 cursor-pointer" }), _jsx("div", { className: "flex flex-col items-center justify-center h-full gap-2 text-gray-400", children: isUploadingCover ? (_jsx(Loader2, { className: "h-6 w-6 animate-spin" })) : (_jsxs(_Fragment, { children: [_jsx(ImageIcon, { className: "h-6 w-6" }), _jsx("span", { className: "text-sm", children: t('editor.coverDropzone') }), _jsx("span", { className: "text-xs text-gray-400", children: t('editor.coverFormats') })] })) })] })) }), _jsx("div", { className: "flex-1 min-h-0 overflow-auto", children: _jsx("div", { className: "h-full", children: _jsx(BlockNoteView, { editor: editor, theme: blockNoteTheme[theme], className: "h-full" }) }) })] }));
|
|
568
|
+
}, placeholder: generateSlug(title), className: "bg-transparent border-none outline-none px-0 flex-1 text-gray-500 placeholder:text-gray-400 font-mono text-sm min-w-0" })] })] }), _jsx("div", { className: "mb-4 sm:mb-6 shrink-0", children: cover ? (_jsxs("div", { className: "relative w-full h-48 sm:h-64 lg:h-80 rounded-lg overflow-hidden bg-gray-100", children: [_jsx("img", { src: cover, alt: t('editor.coverAlt'), className: "w-full h-full object-cover" }), _jsx("button", { onClick: () => setCover(null), className: "absolute top-2 right-2 p-1.5 bg-secondary hover:bg-white rounded-md shadow-sm transition-colors", title: t('editor.removeCover'), children: _jsx(X, { className: "h-4 w-4" }) })] })) : (_jsxs("div", { onDrop: handleDrop, onDragOver: handleDragOver, className: "relative w-full h-24 sm:h-32 border-2 border-dashed border-border rounded-lg bg-secondary hover:bg-gray-100 transition-colors cursor-pointer", children: [_jsx("input", { type: "file", accept: "image/jpeg,image/png,image/gif,image/webp", onChange: handleFileInput, className: "absolute inset-0 w-full h-full opacity-0 cursor-pointer" }), _jsx("div", { className: "flex flex-col items-center justify-center h-full gap-1 sm:gap-2 text-gray-400", children: isUploadingCover ? (_jsx(Loader2, { className: "h-5 sm:h-6 w-5 sm:w-6 animate-spin" })) : (_jsxs(_Fragment, { children: [_jsx(ImageIcon, { className: "h-5 sm:h-6 w-5 sm:w-6" }), _jsx("span", { className: "text-xs sm:text-sm", children: t('editor.coverDropzone') }), _jsx("span", { className: "text-xs text-gray-400 hidden sm:inline", children: t('editor.coverFormats') })] })) })] })) }), _jsx("div", { className: "flex-1 min-h-0 overflow-auto", children: _jsx("div", { className: "h-full", children: _jsx(BlockNoteView, { editor: editor, theme: blockNoteTheme[theme], className: "h-full" }) }) })] }));
|
|
481
569
|
}
|
|
@@ -54,7 +54,7 @@ export function DocumentView({}) {
|
|
|
54
54
|
if (error || !doc) {
|
|
55
55
|
return (_jsx("div", { className: "flex items-center justify-center min-h-[400px]", children: _jsx("p", { className: "text-red-500", children: t('view.loadFailed') }) }));
|
|
56
56
|
}
|
|
57
|
-
return (_jsxs("div", { className: "max-w-[80%] mx-auto", children: [_jsxs("div", { className: "mb-8", children: [_jsxs("div", { className: "flex items-start justify-between gap-4 mb-4", children: [_jsxs("div", { className: "flex items-center gap-3", children: [doc.emoji && (_jsx("span", { className: "text-5xl leading-none", role: "img", "aria-label": "document emoji", children: doc.emoji })), _jsx("h1", { className: "text-3xl font-bold", children: doc.title })] }), _jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [doc && (_jsx(ExportButton, { documentId: doc.id, documentTitle: doc.title })), _jsxs("button", { onClick: handleEdit, className: "px-3 py-1.5 text-sm bg-secondary text-white rounded-md hover:bg-primary flex items-center gap-2 transition-colors", children: [_jsx(Edit, { className: "h-4 w-4" }), t('view.edit')] })] })] }), _jsxs("div", { className: "flex items-center gap-6 text-sm text-gray-500", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(User, { className: "h-4 w-4" }), _jsxs("span", { children: [t('view.authorId'), ": ", doc.authorId] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Clock, { className: "h-4 w-4" }), _jsxs("span", { children: [t('view.updated'), ' ', doc.updatedAt
|
|
57
|
+
return (_jsxs("div", { className: "max-w-full sm:max-w-[90%] lg:max-w-[80%] mx-auto", children: [_jsxs("div", { className: "mb-6 sm:mb-8", children: [_jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start justify-between gap-4 mb-4", children: [_jsxs("div", { className: "flex items-center gap-3", children: [doc.emoji && (_jsx("span", { className: "text-4xl sm:text-5xl leading-none", role: "img", "aria-label": "document emoji", children: doc.emoji })), _jsx("h1", { className: "text-2xl sm:text-3xl font-bold", children: doc.title })] }), _jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [doc && (_jsx(ExportButton, { documentId: doc.id, documentTitle: doc.title })), _jsxs("button", { onClick: handleEdit, className: "px-3 py-1.5 text-sm bg-secondary text-white rounded-md hover:bg-primary flex items-center gap-2 transition-colors", children: [_jsx(Edit, { className: "h-4 w-4" }), _jsx("span", { className: "hidden sm:inline", children: t('view.edit') })] })] })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-4 sm:gap-6 text-sm text-gray-500", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(User, { className: "h-4 w-4" }), _jsxs("span", { children: [t('view.authorId'), ": ", doc.authorId] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Clock, { className: "h-4 w-4" }), _jsxs("span", { children: [t('view.updated'), ' ', doc.updatedAt
|
|
58
58
|
? new Date(doc.updatedAt).toLocaleDateString()
|
|
59
|
-
: 'N/A'] })] })] })] }), doc.cover && (_jsx("div", { className: "mb-8 -mx-4 sm:-mx-8 lg:-mx-12", children: _jsx("div", { className: "relative w-full max-h-80 overflow-hidden", children: _jsx("img", { src: doc.cover, alt: doc.title, className: "w-full h-full object-cover" }) }) })), _jsx("div", { children: _jsx(BlockNoteView, { editor: editor, editable: false, theme: blockNoteTheme[theme], className: "[&_.bn-editor]:p-0 [&_.bn-editor]:px-0 [&_.bn-container]:max-w-none [&_.bn-editor]:!px-0" }) })] }));
|
|
59
|
+
: 'N/A'] })] })] })] }), doc.cover && (_jsx("div", { className: "mb-6 sm:mb-8 -mx-4 sm:-mx-8 lg:-mx-12", children: _jsx("div", { className: "relative w-full max-h-60 sm:max-h-80 overflow-hidden", children: _jsx("img", { src: doc.cover, alt: doc.title, className: "w-full h-full object-cover" }) }) })), _jsx("div", { children: _jsx(BlockNoteView, { editor: editor, editable: false, theme: blockNoteTheme[theme], className: "[&_.bn-editor]:p-0 [&_.bn-editor]:px-0 [&_.bn-container]:max-w-none [&_.bn-editor]:!px-0" }) })] }));
|
|
60
60
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Layout.d.ts","sourceRoot":"","sources":["../../../src/client/components/Layout.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Layout.d.ts","sourceRoot":"","sources":["../../../src/client/components/Layout.tsx"],"names":[],"mappings":"AAmBA,UAAU,eAAe;IACvB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC7B;AAmJD,wBAAgB,MAAM,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,eAAe,2CAoO/D"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { FileText, PanelLeftCloseIcon, PanelLeftOpenIcon, Plus, Search, } from 'lucide-react';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { FileText, Menu, PanelLeftCloseIcon, PanelLeftOpenIcon, Plus, Search, X, } from 'lucide-react';
|
|
4
4
|
import { useEffect, useRef, useState } from 'react';
|
|
5
5
|
import { useDocumentsQuery } from '../hooks/useDocsQuery.js';
|
|
6
6
|
import { useTranslation } from '../hooks/useTranslation.js';
|
|
@@ -133,6 +133,8 @@ export function Layout({ children, userAvatar }) {
|
|
|
133
133
|
const { t } = useTranslation();
|
|
134
134
|
const [expandedFolders, setExpandedFolders] = useState(new Set());
|
|
135
135
|
const [isSidebarOpen, setIsSidebarOpen] = useState(true);
|
|
136
|
+
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
|
137
|
+
const [isMobile, setIsMobile] = useState(false);
|
|
136
138
|
// Header search state
|
|
137
139
|
const [headerSearchQuery, setHeaderSearchQuery] = useState('');
|
|
138
140
|
const [showSearchResults, setShowSearchResults] = useState(false);
|
|
@@ -140,6 +142,22 @@ export function Layout({ children, userAvatar }) {
|
|
|
140
142
|
const searchInputRef = useRef(null);
|
|
141
143
|
const { data } = useDocumentsQuery();
|
|
142
144
|
const documents = data?.documents ?? [];
|
|
145
|
+
// Mobile detection
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
const checkMobile = () => {
|
|
148
|
+
setIsMobile(window.innerWidth < 768);
|
|
149
|
+
if (window.innerWidth < 768) {
|
|
150
|
+
setIsSidebarOpen(false);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
checkMobile();
|
|
154
|
+
window.addEventListener('resize', checkMobile);
|
|
155
|
+
return () => window.removeEventListener('resize', checkMobile);
|
|
156
|
+
}, []);
|
|
157
|
+
// Close mobile menu on document change
|
|
158
|
+
useEffect(() => {
|
|
159
|
+
setIsMobileMenuOpen(false);
|
|
160
|
+
}, [params.documentSlug]);
|
|
143
161
|
// Search query for header search
|
|
144
162
|
const { data: searchResultsData } = useDocumentsQuery(debouncedSearchQuery.length > 0 ? { search: debouncedSearchQuery } : {});
|
|
145
163
|
const searchResults = searchResultsData?.documents ?? [];
|
|
@@ -154,9 +172,9 @@ export function Layout({ children, userAvatar }) {
|
|
|
154
172
|
document.addEventListener('mousedown', handleClickOutside);
|
|
155
173
|
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
156
174
|
}, []);
|
|
157
|
-
return (_jsxs("div", { className: "flex h-screen w-full", children: [_jsx(Sidebar, { isOpen: isSidebarOpen, onToggle: setIsSidebarOpen, documents: documents }), _jsxs("main", { className: "flex-1 flex flex-col min-w-0 overflow-hidden", children: [params.mode === 'view' && (_jsxs("header", { className: "h-16 border-b border-border flex items-center justify-between px-6 shrink-0", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("button", { onClick: () => setIsSidebarOpen(!isSidebarOpen), className: "p-2 hover:bg-primary rounded-md transition-colors text-muted-foreground", title: isSidebarOpen
|
|
175
|
+
return (_jsxs("div", { className: "flex h-screen w-full", children: [!isMobile && (_jsx(Sidebar, { isOpen: isSidebarOpen, onToggle: setIsSidebarOpen, documents: documents })), isMobile && isMobileMenuOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 bg-black/50 z-40", onClick: () => setIsMobileMenuOpen(false) }), _jsxs("div", { className: "fixed left-0 top-0 h-full w-64 z-50 bg-background border-r border-border", children: [_jsxs("div", { className: "p-4 border-b border-border flex items-center justify-between", children: [_jsx("h2", { className: "font-semibold text-sm text-muted-foreground", children: t('sidebar.documents') }), _jsx("button", { onClick: () => setIsMobileMenuOpen(false), className: "p-2 hover:bg-primary rounded-md transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] }), _jsx(Sidebar, { isOpen: true, onToggle: () => { }, documents: documents, currentDocId: params.documentSlug })] })] })), _jsxs("main", { className: "flex-1 flex flex-col min-w-0 overflow-hidden", children: [params.mode === 'view' && (_jsxs("header", { className: "h-16 border-b border-border flex items-center justify-between px-4 sm:px-6 shrink-0", children: [_jsxs("div", { className: "flex items-center gap-3", children: [isMobile && (_jsx("button", { onClick: () => setIsMobileMenuOpen(true), className: "p-2 hover:bg-primary rounded-md transition-colors text-muted-foreground", title: t('layout.openSidebar'), children: _jsx(Menu, { className: "h-6 w-6" }) })), !isMobile && (_jsx("button", { onClick: () => setIsSidebarOpen(!isSidebarOpen), className: "p-2 hover:bg-primary rounded-md transition-colors text-muted-foreground", title: isSidebarOpen
|
|
158
176
|
? t('layout.closeSidebar')
|
|
159
|
-
: t('layout.openSidebar'), children: isSidebarOpen ? (_jsx(PanelLeftCloseIcon, { className: "h-6 w-6" })) : (_jsx(PanelLeftOpenIcon, { className: "h-6 w-6" })) }), _jsx(Breadcrumbs, { homeLabel: t('sidebar.documents') })] }), _jsxs("div", { className: "flex items-center gap-4 py-2", children: [_jsxs("div", { ref: searchInputRef, className: "relative w-64", children: [_jsx(Search, { className: "absolute left-2 top-2.5 h-4 w-4 z-10" }), _jsx("input", { type: "text", placeholder: t('layout.searchPlaceholder'), className: "w-full pl-8 pr-3 py-2 text-sm border border-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent", value: headerSearchQuery, onChange: (e) => {
|
|
177
|
+
: t('layout.openSidebar'), children: isSidebarOpen ? (_jsx(PanelLeftCloseIcon, { className: "h-6 w-6" })) : (_jsx(PanelLeftOpenIcon, { className: "h-6 w-6" })) })), _jsx(Breadcrumbs, { homeLabel: t('sidebar.documents') })] }), _jsxs("div", { className: "flex items-center gap-2 sm:gap-4 py-2", children: [_jsxs("div", { ref: searchInputRef, className: "relative w-40 sm:w-64", children: [_jsx(Search, { className: "absolute left-2 top-2.5 h-4 w-4 z-10" }), _jsx("input", { type: "text", placeholder: t('layout.searchPlaceholder'), className: "w-full pl-8 pr-3 py-2 text-sm border border-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent", value: headerSearchQuery, onChange: (e) => {
|
|
160
178
|
setHeaderSearchQuery(e.target.value);
|
|
161
179
|
setShowSearchResults(e.target.value.length > 0);
|
|
162
180
|
}, onFocus: () => {
|
|
@@ -172,5 +190,5 @@ export function Layout({ children, userAvatar }) {
|
|
|
172
190
|
setHeaderSearchQuery('');
|
|
173
191
|
setShowSearchResults(false);
|
|
174
192
|
}, className: "w-full px-3 py-2 text-left hover:bg-primary hover:text-primary-foreground transition-colors", children: _jsxs("div", { className: "flex items-start gap-2", children: [_jsx("div", { className: "shrink-0 mt-0.5", children: doc.emoji ? (_jsx("span", { className: "text-base", children: doc.emoji })) : (_jsx(FileText, { className: "h-4 w-4 text-muted-foreground" })) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "font-medium text-sm truncate", children: doc.title }), breadcrumb && (_jsx("div", { className: "text-xs text-muted-foreground truncate", children: breadcrumb }))] })] }) }, doc.id));
|
|
175
|
-
}) })) }))] }), _jsxs("button", { onClick: () => onCreate(), className: "px-4 py-2 bg-secondary text-white text-sm rounded-md hover:bg-gray-800 flex items-center gap-2 transition-colors", children: [_jsx(Plus, { className: "h-4 w-4" }), t('layout.create')] }), userAvatar] })] })), _jsx("div", { className: "flex-1 overflow-auto p-6", children: children })] })] }));
|
|
193
|
+
}) })) }))] }), _jsxs("button", { onClick: () => onCreate(), className: "px-3 sm:px-4 py-2 bg-secondary text-white text-sm rounded-md hover:bg-gray-800 flex items-center gap-2 transition-colors", children: [_jsx(Plus, { className: "h-4 w-4" }), _jsx("span", { className: "hidden sm:inline", children: t('layout.create') })] }), userAvatar] })] })), _jsx("div", { className: "flex-1 overflow-auto p-4 sm:p-6", children: children })] })] }));
|
|
176
194
|
}
|
|
@@ -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
|
-
*
|
|
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
|
-
*
|
|
33
|
-
* @param
|
|
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;
|
|
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
|
-
*
|
|
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
|
-
*
|
|
61
|
-
* @param
|
|
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
|
}
|
package/dist/server/schema.d.ts
CHANGED
|
@@ -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
|
|
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"}
|
package/dist/server/schema.js
CHANGED
|
@@ -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,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
|
+
}
|
package/package.json
CHANGED
package/styles/docs.css
CHANGED
|
@@ -978,4 +978,134 @@
|
|
|
978
978
|
.docs-content {
|
|
979
979
|
padding: 1rem;
|
|
980
980
|
}
|
|
981
|
+
|
|
982
|
+
/* Mobile typography adjustments */
|
|
983
|
+
.prose h1 {
|
|
984
|
+
font-size: 1.75rem;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
.prose h2 {
|
|
988
|
+
font-size: 1.375rem;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
.prose h3 {
|
|
992
|
+
font-size: 1.125rem;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
/* Mobile editor adjustments */
|
|
996
|
+
.docs-editor-container {
|
|
997
|
+
max-width: 100%;
|
|
998
|
+
padding: 0 0.5rem;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
.docs-editor-title-input {
|
|
1002
|
+
font-size: 1.25rem;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
/* Mobile cover adjustments */
|
|
1006
|
+
.docs-cover-image,
|
|
1007
|
+
.docs-cover-dropzone {
|
|
1008
|
+
max-height: 200px;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
/* Mobile button sizing */
|
|
1012
|
+
.docs-btn {
|
|
1013
|
+
padding: 0.375rem 0.75rem;
|
|
1014
|
+
font-size: 0.8125rem;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
/* Mobile tree item touch targets */
|
|
1018
|
+
.docs-tree-item {
|
|
1019
|
+
padding: 0.5rem 0.625rem;
|
|
1020
|
+
min-height: 2.5rem;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
.docs-tree-item-toggle,
|
|
1024
|
+
.docs-tree-item-drag-handle {
|
|
1025
|
+
min-width: 2rem;
|
|
1026
|
+
min-height: 2rem;
|
|
1027
|
+
display: flex;
|
|
1028
|
+
align-items: center;
|
|
1029
|
+
justify-content: center;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
/* Always show drag handle on mobile for better touch */
|
|
1033
|
+
.docs-tree-item-drag-handle {
|
|
1034
|
+
opacity: 1;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
/* Mobile card adjustments */
|
|
1038
|
+
.docs-list-card {
|
|
1039
|
+
padding: 0.875rem;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
/* Mobile emoji picker */
|
|
1043
|
+
.docs-emoji-picker-dropdown {
|
|
1044
|
+
width: 14rem;
|
|
1045
|
+
left: 50%;
|
|
1046
|
+
transform: translateX(-50%);
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
.docs-emoji-grid {
|
|
1050
|
+
grid-template-columns: repeat(6, 1fr);
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
/* Mobile dropdown adjustments */
|
|
1054
|
+
.docs-dropdown {
|
|
1055
|
+
max-width: calc(100vw - 2rem);
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
/* Mobile view title */
|
|
1059
|
+
.docs-view-heading {
|
|
1060
|
+
font-size: 1.5rem;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
.docs-view-emoji {
|
|
1064
|
+
font-size: 2rem;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
/* Small mobile devices */
|
|
1069
|
+
@media (max-width: 480px) {
|
|
1070
|
+
.docs-container {
|
|
1071
|
+
font-size: 0.875rem;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
.docs-content {
|
|
1075
|
+
padding: 0.75rem;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
.prose {
|
|
1079
|
+
line-height: 1.65;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
.prose p {
|
|
1083
|
+
margin-top: 0.75rem;
|
|
1084
|
+
margin-bottom: 0.75rem;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
.prose ul,
|
|
1088
|
+
.prose ol {
|
|
1089
|
+
padding-left: 1.25rem;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
/* Make inputs larger for mobile touch */
|
|
1093
|
+
.docs-input,
|
|
1094
|
+
.docs-sidebar-search-input {
|
|
1095
|
+
min-height: 2.5rem;
|
|
1096
|
+
font-size: 16px; /* Prevent zoom on iOS */
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
/* Touch-friendly buttons */
|
|
1100
|
+
.docs-btn {
|
|
1101
|
+
min-height: 2.25rem;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
/* Mobile search results */
|
|
1105
|
+
.docs-search-results {
|
|
1106
|
+
max-height: 60vh;
|
|
1107
|
+
left: -1rem;
|
|
1108
|
+
right: -1rem;
|
|
1109
|
+
margin-top: 0.5rem;
|
|
1110
|
+
}
|
|
981
1111
|
}
|