deepspace 0.7.0 → 0.8.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/CHANGELOG.md +30 -0
- package/README.md +22 -8
- package/dist/cli.js +10306 -5256
- package/dist/cli.js.map +1 -1
- package/dist/feature-installer.js +638 -0
- package/dist/feature-installer.js.map +1 -0
- package/dist/index.d.ts +107 -117
- package/dist/index.js +610 -779
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +403 -167
- package/dist/server.js +498 -367
- package/dist/server.js.map +1 -1
- package/dist/testing.d.ts +115 -0
- package/dist/testing.js +3 -3
- package/dist/testing.js.map +1 -1
- package/dist/worker.d.ts +397 -160
- package/dist/worker.js +506 -367
- package/dist/worker.js.map +1 -1
- package/features/ai-chat/feature.json +2 -0
- package/features/ai-chat/src/AiChatPage.tsx +2 -3
- package/features/ai-chat/src/ChatPanel.messages.tsx +344 -0
- package/features/ai-chat/src/ChatPanel.stream.ts +397 -0
- package/features/ai-chat/src/ChatPanel.tsx +158 -862
- package/features/ai-chat/src/__tests__/ChatPanel.stream.test.ts +163 -0
- package/features/canvas/src/CanvasListPage.tsx +1 -1
- package/features/docs/feature.json +4 -8
- package/features/docs/src/DocsEditorPage.tsx +33 -326
- package/features/docs/src/DocsListPage.tsx +33 -29
- package/features/docs/src/InviteDialog.tsx +11 -40
- package/features/docs/src/__tests__/docs-presence-access.test.ts +71 -0
- package/features/docs/src/docs-library-types.ts +28 -3
- package/features/docs/src/docs-ui.css +1 -4
- package/features/docs/src/use-docs-presence-access.ts +330 -0
- package/features/file-attachments/feature.json +2 -2
- package/features/file-attachments/src/AttachmentPreview.tsx +428 -0
- package/features/file-attachments/src/AttachmentsPage.tsx +100 -462
- package/features/file-attachments/src/useFilePreview.ts +54 -68
- package/features/file-manager/src/FileManagerPage.tsx +1 -1
- package/features/integration-test/src/integration.spec.ts +1 -1
- package/features/items/feature.json +1 -5
- package/features/items/src/ItemsPage.tsx +207 -135
- package/features/landing/feature.json +3 -0
- package/features/landing/src/LandingPage.tsx +4 -18
- package/features/leaderboard/feature.json +1 -5
- package/features/leaderboard/src/LeaderboardPage.tsx +333 -198
- package/features/messaging/feature.json +0 -4
- package/features/messaging/src/ChatMultiPage.tsx +1 -3
- package/features/messaging/src/ChatPage.tsx +1 -3
- package/features/messaging/src/components/chat/MessageActionSheet.tsx +40 -16
- package/features/search-bar/feature.json +8 -3
- package/features/search-bar/src/SearchOverlay.tsx +108 -252
- package/features/search-bar/src/__tests__/search-model.test.ts +40 -0
- package/features/search-bar/src/search-model.ts +223 -0
- package/features/tasks/src/TasksPage.tsx +3 -3
- package/features/topbar/src/Topbar.tsx +0 -17
- package/package.json +1 -2
- package/features/docs/src/docs-favorites.ts +0 -18
- package/features/docs/src/use-yjs-room-with-awareness.ts +0 -289
- package/features/file-attachments/src/attachments-constants.ts +0 -9
- package/features/items/src/items-constants.ts +0 -10
- package/features/leaderboard/src/leaderboard-constants.ts +0 -21
- package/features/messaging/src/components/chat/ReactionPicker.tsx +0 -2
- package/scripts/add-feature.cjs +0 -807
|
@@ -1,24 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* useFilePreview — Centralized file preview hook with dynamic imports.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* - Code/text → CodeMirror (dynamically imported)
|
|
7
|
-
* - CSV → PapaParse (dynamically imported)
|
|
8
|
-
* - DOCX → docx-preview (dynamically imported)
|
|
9
|
-
* - XLSX → ExcelJS (dynamically imported)
|
|
4
|
+
* Native image/video/audio need no loader. PDF, code/text, CSV, DOCX, and
|
|
5
|
+
* XLSX load their parser or renderer only when selected.
|
|
10
6
|
*
|
|
11
7
|
* Heavy libraries are only loaded when needed, keeping the base bundle small.
|
|
12
8
|
*
|
|
13
9
|
* @example
|
|
14
10
|
* ```tsx
|
|
15
|
-
* const
|
|
11
|
+
* const preview = useFilePreview({
|
|
16
12
|
* url: 'https://...',
|
|
17
13
|
* fileName: 'report.csv',
|
|
18
14
|
* mimeType: 'text/csv',
|
|
19
15
|
* })
|
|
20
|
-
*
|
|
21
|
-
* return <PreviewComponent />
|
|
22
16
|
* ```
|
|
23
17
|
*/
|
|
24
18
|
|
|
@@ -80,7 +74,6 @@ export interface XlsxSheet {
|
|
|
80
74
|
|
|
81
75
|
export interface XlsxData {
|
|
82
76
|
sheets: XlsxSheet[]
|
|
83
|
-
activeSheet: number
|
|
84
77
|
}
|
|
85
78
|
|
|
86
79
|
// ============================================================================
|
|
@@ -128,56 +121,56 @@ export function useFilePreview({ url, fileName, mimeType }: FilePreviewInput): F
|
|
|
128
121
|
const [docxModule, setDocxModule] = useState<typeof import('docx-preview') | null>(null)
|
|
129
122
|
const [pdfPages, setPdfPages] = useState<HTMLCanvasElement[] | null>(null)
|
|
130
123
|
|
|
131
|
-
// Load preview content based on category
|
|
132
124
|
useEffect(() => {
|
|
133
|
-
if (!previewable || !url) return
|
|
134
|
-
|
|
135
|
-
// Native browser previews (image, video, audio) don't need fetching
|
|
136
|
-
// PDF uses PDF.js now (rendered to canvas) to avoid iframe sandbox issues
|
|
137
|
-
if (category === 'image' || category === 'video' || category === 'audio') {
|
|
138
|
-
return
|
|
139
|
-
}
|
|
140
|
-
|
|
141
125
|
const controller = new AbortController()
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
126
|
+
const { signal } = controller
|
|
127
|
+
const needsContent = previewable
|
|
128
|
+
&& !!url
|
|
129
|
+
&& category !== 'image'
|
|
130
|
+
&& category !== 'video'
|
|
131
|
+
&& category !== 'audio'
|
|
132
|
+
|
|
133
|
+
// Clear every result before deciding whether this category needs an
|
|
134
|
+
// asynchronous loader. Without this reset, switching from a failed or
|
|
135
|
+
// loading document to a native image/video/audio preview retains the old
|
|
136
|
+
// error/loading state and hides the native preview indefinitely.
|
|
137
|
+
setIsLoading(needsContent)
|
|
145
138
|
setError(null)
|
|
146
139
|
setTextContent(null)
|
|
147
140
|
setCsvData(null)
|
|
141
|
+
setCodeEditorKit(null)
|
|
148
142
|
setXlsxData(null)
|
|
149
143
|
setDocxData(null)
|
|
150
144
|
setDocxModule(null)
|
|
151
145
|
setPdfPages(null)
|
|
152
146
|
|
|
147
|
+
if (!needsContent) return () => controller.abort()
|
|
148
|
+
|
|
153
149
|
async function loadPreview() {
|
|
154
150
|
try {
|
|
155
151
|
if (category === 'pdf') {
|
|
156
|
-
await loadPdfPreview(url,
|
|
152
|
+
await loadPdfPreview(url, signal, setPdfPages)
|
|
157
153
|
} else if (category === 'code' || category === 'text') {
|
|
158
|
-
await loadCodePreview(url, fileName,
|
|
154
|
+
await loadCodePreview(url, fileName, signal, setTextContent, setCodeEditorKit)
|
|
159
155
|
} else if (extension === 'csv' || extension === 'tsv') {
|
|
160
|
-
await loadCsvPreview(url,
|
|
156
|
+
await loadCsvPreview(url, signal, setCsvData)
|
|
161
157
|
} else if (extension === 'docx') {
|
|
162
|
-
await loadDocxPreview(url,
|
|
158
|
+
await loadDocxPreview(url, signal, setDocxData, setDocxModule)
|
|
163
159
|
} else if (extension === 'xlsx' || extension === 'xls') {
|
|
164
|
-
await loadXlsxPreview(url,
|
|
160
|
+
await loadXlsxPreview(url, signal, setXlsxData)
|
|
165
161
|
}
|
|
166
162
|
} catch (err) {
|
|
167
|
-
if (!
|
|
163
|
+
if (!signal.aborted && !(err instanceof DOMException && err.name === 'AbortError')) {
|
|
168
164
|
setError(err instanceof Error ? err.message : 'Failed to load preview')
|
|
169
165
|
}
|
|
170
166
|
} finally {
|
|
171
|
-
if (!
|
|
167
|
+
if (!signal.aborted) setIsLoading(false)
|
|
172
168
|
}
|
|
173
169
|
}
|
|
174
170
|
|
|
175
|
-
loadPreview()
|
|
171
|
+
void loadPreview()
|
|
176
172
|
|
|
177
|
-
return () =>
|
|
178
|
-
cancelled = true
|
|
179
|
-
controller.abort()
|
|
180
|
-
}
|
|
173
|
+
return () => controller.abort()
|
|
181
174
|
}, [url, fileName, mimeType, category, extension, previewable])
|
|
182
175
|
|
|
183
176
|
return {
|
|
@@ -209,7 +202,6 @@ async function loadCodePreview(
|
|
|
209
202
|
url: string,
|
|
210
203
|
fileName: string,
|
|
211
204
|
signal: AbortSignal,
|
|
212
|
-
cancelled: boolean,
|
|
213
205
|
setTextContent: (v: string) => void,
|
|
214
206
|
setCodeEditorKit: (v: CodeEditorKit) => void,
|
|
215
207
|
) {
|
|
@@ -220,11 +212,11 @@ async function loadCodePreview(
|
|
|
220
212
|
import('@codemirror/theme-one-dark').catch(() => null),
|
|
221
213
|
])
|
|
222
214
|
|
|
223
|
-
if (
|
|
215
|
+
if (signal.aborted) return
|
|
224
216
|
if (!response.ok) throw new Error(`HTTP ${response.status}`)
|
|
225
217
|
|
|
226
218
|
const text = await response.text()
|
|
227
|
-
if (
|
|
219
|
+
if (signal.aborted) return
|
|
228
220
|
|
|
229
221
|
// Pretty-print minified JSON before handing it to CodeMirror. Files like
|
|
230
222
|
// `package-lock.json` or build manifests are often shipped as one long
|
|
@@ -266,7 +258,6 @@ async function loadCodePreview(
|
|
|
266
258
|
async function loadCsvPreview(
|
|
267
259
|
url: string,
|
|
268
260
|
signal: AbortSignal,
|
|
269
|
-
cancelled: boolean,
|
|
270
261
|
setCsvData: (v: FilePreviewResult['csvData']) => void,
|
|
271
262
|
) {
|
|
272
263
|
const [response, Papa] = await Promise.all([
|
|
@@ -274,11 +265,11 @@ async function loadCsvPreview(
|
|
|
274
265
|
import('papaparse'),
|
|
275
266
|
])
|
|
276
267
|
|
|
277
|
-
if (
|
|
268
|
+
if (signal.aborted) return
|
|
278
269
|
if (!response.ok) throw new Error(`HTTP ${response.status}`)
|
|
279
270
|
|
|
280
271
|
const text = await response.text()
|
|
281
|
-
if (
|
|
272
|
+
if (signal.aborted) return
|
|
282
273
|
|
|
283
274
|
const result = Papa.default.parse<string[]>(text, { skipEmptyLines: true })
|
|
284
275
|
const allRows = result.data
|
|
@@ -298,7 +289,6 @@ async function loadCsvPreview(
|
|
|
298
289
|
async function loadDocxPreview(
|
|
299
290
|
url: string,
|
|
300
291
|
signal: AbortSignal,
|
|
301
|
-
cancelled: boolean,
|
|
302
292
|
setDocxData: (v: ArrayBuffer) => void,
|
|
303
293
|
setDocxModule: (v: typeof import('docx-preview')) => void,
|
|
304
294
|
) {
|
|
@@ -307,11 +297,11 @@ async function loadDocxPreview(
|
|
|
307
297
|
import('docx-preview'),
|
|
308
298
|
])
|
|
309
299
|
|
|
310
|
-
if (
|
|
300
|
+
if (signal.aborted) return
|
|
311
301
|
if (!response.ok) throw new Error(`HTTP ${response.status}`)
|
|
312
302
|
|
|
313
303
|
const arrayBuffer = await response.arrayBuffer()
|
|
314
|
-
if (
|
|
304
|
+
if (signal.aborted) return
|
|
315
305
|
|
|
316
306
|
setDocxData(arrayBuffer)
|
|
317
307
|
setDocxModule(docxPreviewModule)
|
|
@@ -320,7 +310,6 @@ async function loadDocxPreview(
|
|
|
320
310
|
async function loadXlsxPreview(
|
|
321
311
|
url: string,
|
|
322
312
|
signal: AbortSignal,
|
|
323
|
-
cancelled: boolean,
|
|
324
313
|
setXlsxData: (v: XlsxData) => void,
|
|
325
314
|
) {
|
|
326
315
|
const [response, ExcelJSModule] = await Promise.all([
|
|
@@ -328,16 +317,16 @@ async function loadXlsxPreview(
|
|
|
328
317
|
import('exceljs'),
|
|
329
318
|
])
|
|
330
319
|
|
|
331
|
-
if (
|
|
320
|
+
if (signal.aborted) return
|
|
332
321
|
if (!response.ok) throw new Error(`HTTP ${response.status}`)
|
|
333
322
|
|
|
334
323
|
const arrayBuffer = await response.arrayBuffer()
|
|
335
|
-
if (
|
|
324
|
+
if (signal.aborted) return
|
|
336
325
|
|
|
337
326
|
const ExcelJS = (ExcelJSModule as { default?: typeof import('exceljs') }).default ?? ExcelJSModule
|
|
338
327
|
const workbook = new ExcelJS.Workbook()
|
|
339
328
|
await workbook.xlsx.load(arrayBuffer)
|
|
340
|
-
if (
|
|
329
|
+
if (signal.aborted) return
|
|
341
330
|
|
|
342
331
|
// Bounded recursion: a malformed XLSX could in theory produce
|
|
343
332
|
// `{result: {result: {result: ...}}}` chains that blow the stack.
|
|
@@ -384,7 +373,7 @@ async function loadXlsxPreview(
|
|
|
384
373
|
return { name: ws.name, headers, rows, truncated }
|
|
385
374
|
})
|
|
386
375
|
|
|
387
|
-
setXlsxData({ sheets
|
|
376
|
+
setXlsxData({ sheets })
|
|
388
377
|
}
|
|
389
378
|
|
|
390
379
|
// ============================================================================
|
|
@@ -429,45 +418,42 @@ async function loadPdfJs(): Promise<typeof import('pdfjs-dist')> {
|
|
|
429
418
|
async function loadPdfPreview(
|
|
430
419
|
url: string,
|
|
431
420
|
signal: AbortSignal,
|
|
432
|
-
cancelled: boolean,
|
|
433
421
|
setPdfPages: (v: HTMLCanvasElement[]) => void,
|
|
434
|
-
setError: (v: string) => void,
|
|
435
422
|
) {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
if (cancelled) return
|
|
439
|
-
|
|
440
|
-
const response = await fetch(url, { signal })
|
|
441
|
-
if (cancelled) return
|
|
442
|
-
if (!response.ok) throw new Error(`HTTP ${response.status}`)
|
|
423
|
+
const pdfjsLib = await loadPdfJs()
|
|
424
|
+
if (signal.aborted) return
|
|
443
425
|
|
|
444
|
-
|
|
445
|
-
|
|
426
|
+
const response = await fetch(url, { signal })
|
|
427
|
+
if (signal.aborted) return
|
|
428
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`)
|
|
446
429
|
|
|
447
|
-
|
|
448
|
-
|
|
430
|
+
const arrayBuffer = await response.arrayBuffer()
|
|
431
|
+
if (signal.aborted) return
|
|
449
432
|
|
|
433
|
+
const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise
|
|
434
|
+
try {
|
|
450
435
|
const canvases: HTMLCanvasElement[] = []
|
|
451
436
|
const SCALE = 1.5
|
|
452
437
|
|
|
453
438
|
for (let i = 1; i <= pdf.numPages; i++) {
|
|
454
|
-
if (
|
|
439
|
+
if (signal.aborted) return
|
|
455
440
|
const page = await pdf.getPage(i)
|
|
441
|
+
if (signal.aborted) return
|
|
456
442
|
const viewport = page.getViewport({ scale: SCALE })
|
|
457
443
|
|
|
458
444
|
const canvas = document.createElement('canvas')
|
|
459
445
|
canvas.width = viewport.width
|
|
460
446
|
canvas.height = viewport.height
|
|
461
447
|
|
|
462
|
-
const ctx = canvas.getContext('2d')
|
|
448
|
+
const ctx = canvas.getContext('2d')
|
|
449
|
+
if (!ctx) throw new Error('Canvas rendering is not available')
|
|
463
450
|
await page.render({ canvasContext: ctx, viewport }).promise
|
|
451
|
+
if (signal.aborted) return
|
|
464
452
|
canvases.push(canvas)
|
|
465
453
|
}
|
|
466
454
|
|
|
467
|
-
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
setError(err instanceof Error ? err.message : 'Failed to load PDF')
|
|
471
|
-
}
|
|
455
|
+
setPdfPages(canvases)
|
|
456
|
+
} finally {
|
|
457
|
+
await pdf.destroy()
|
|
472
458
|
}
|
|
473
459
|
}
|
|
@@ -209,7 +209,7 @@ function PreviewModal({ file, imageUrl, onClose }: PreviewModalProps) {
|
|
|
209
209
|
|
|
210
210
|
return (
|
|
211
211
|
<Modal open={!!file} onClose={onClose} size="lg">
|
|
212
|
-
<Modal.Header
|
|
212
|
+
<Modal.Header>
|
|
213
213
|
<Modal.Title>{name}</Modal.Title>
|
|
214
214
|
</Modal.Header>
|
|
215
215
|
<Modal.Body>
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* signs in as a pre-created test account via the `users` fixture (from
|
|
7
7
|
* deepspace/testing), which handles sign-in caching, context creation,
|
|
8
8
|
* and cleanup. Create at least one test account first:
|
|
9
|
-
* npx deepspace test
|
|
9
|
+
* npx deepspace test accounts create --email integ@deepspace.test --password TestPass123! --name "Integration Tester"
|
|
10
10
|
*/
|
|
11
11
|
import { test, expect } from 'deepspace/testing'
|
|
12
12
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "Items",
|
|
4
4
|
"category": "data",
|
|
5
5
|
"description": "CRUD items with ownership, status toggling, and role-based access",
|
|
6
|
-
"details": "Reference implementation for basic CRUD with useQuery and
|
|
6
|
+
"details": "Reference implementation for basic CRUD with useQuery and confirmed mutations. Items have owner-based permissions (own items editable, others read-only), active/archived status toggling, and role-based create access.",
|
|
7
7
|
"files": [
|
|
8
8
|
{
|
|
9
9
|
"src": "src/ItemsPage.tsx",
|
|
@@ -12,10 +12,6 @@
|
|
|
12
12
|
{
|
|
13
13
|
"src": "src/items-schema.ts",
|
|
14
14
|
"dest": "src/schemas/items-schema.ts"
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
"src": "src/items-constants.ts",
|
|
18
|
-
"dest": "src/components/items/items-constants.ts"
|
|
19
15
|
}
|
|
20
16
|
],
|
|
21
17
|
"schema": {
|