@critical-path/svelte 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.
- package/CHANGELOG.md +12 -0
- package/README.md +52 -2
- package/dist/activity-state.svelte.d.ts +27 -0
- package/dist/activity-state.svelte.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +134 -2
- package/package.json +3 -3
- package/src/activity-state.svelte.ts +156 -0
- package/src/index.test.ts +57 -2
- package/src/index.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @critical-path/svelte
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 1811922: Add attachment URL validation against large data URIs, sanitize undefined properties in FirebaseStore, provide direct attachment upload routes & SDK methods, and introduce TaskActivityState for combined comment & attachment threads.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [1811922]
|
|
12
|
+
- @critical-path/core@0.8.0
|
|
13
|
+
- @critical-path/client@0.4.0
|
|
14
|
+
|
|
3
15
|
## 0.4.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> **Svelte 5 Runes Reactive Integrations for Critical Path.**
|
|
4
4
|
|
|
5
|
-
`@critical-path/svelte` provides Svelte 5 Runes reactive state classes and factories (`ProjectState`, `TaskState`) for building project management UIs in Svelte 5 and SvelteKit applications.
|
|
5
|
+
`@critical-path/svelte` provides Svelte 5 Runes reactive state classes and factories (`ProjectState`, `TaskState`, `WorkflowState`, `CommentState`, `AttachmentState`, `TaskActivityState`) for building project management UIs in Svelte 5 and SvelteKit applications.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -16,7 +16,9 @@ pnpm add @critical-path/svelte svelte@^5.0.0
|
|
|
16
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
19
|
-
## 💡 Usage
|
|
19
|
+
## 💡 Usage Examples (Svelte 5 Runes)
|
|
20
|
+
|
|
21
|
+
### 1. Projects & Tasks
|
|
20
22
|
|
|
21
23
|
```svelte
|
|
22
24
|
<script lang="ts">
|
|
@@ -50,8 +52,56 @@ pnpm add @critical-path/svelte svelte@^5.0.0
|
|
|
50
52
|
{/if}
|
|
51
53
|
```
|
|
52
54
|
|
|
55
|
+
### 2. Unified Task Activity & Threaded Discussions (`TaskActivityState`)
|
|
56
|
+
|
|
57
|
+
Combines threaded comments with inline attachments (`attachment.commentId === comment.id`) and standalone attachments in a single reactive store:
|
|
58
|
+
|
|
59
|
+
```svelte
|
|
60
|
+
<script lang="ts">
|
|
61
|
+
import { onMount } from 'svelte';
|
|
62
|
+
import { createCriticalPathClient, createTaskActivityState } from '@critical-path/svelte';
|
|
63
|
+
|
|
64
|
+
const client = createCriticalPathClient({ baseUrl: '/api/critical-path' });
|
|
65
|
+
const activityState = createTaskActivityState(client, 'task_1');
|
|
66
|
+
|
|
67
|
+
onMount(() => {
|
|
68
|
+
activityState.fetch();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
async function handleSend(text: string, fileUrl?: string) {
|
|
72
|
+
await activityState.addComment(
|
|
73
|
+
{ content: text, authorId: 'user_1', authorType: 'user' },
|
|
74
|
+
fileUrl ? [{ filename: 'upload.png', url: fileUrl, uploaderId: 'user_1', mimeType: 'image/png', sizeBytes: 1024 }] : []
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
</script>
|
|
78
|
+
|
|
79
|
+
{#each activityState.threads as thread}
|
|
80
|
+
<div class="comment">
|
|
81
|
+
<p><strong>{thread.authorId}</strong>: {thread.content}</p>
|
|
82
|
+
|
|
83
|
+
<!-- Inline comment attachments -->
|
|
84
|
+
{#if thread.attachments.length > 0}
|
|
85
|
+
<div class="attachments">
|
|
86
|
+
{#each thread.attachments as att}
|
|
87
|
+
<a href={att.url} target="_blank">{att.filename}</a>
|
|
88
|
+
{/each}
|
|
89
|
+
</div>
|
|
90
|
+
{/if}
|
|
91
|
+
|
|
92
|
+
<!-- Threaded replies -->
|
|
93
|
+
{#each thread.replies as reply}
|
|
94
|
+
<div class="reply">
|
|
95
|
+
<p>↪ {reply.authorId}: {reply.content}</p>
|
|
96
|
+
</div>
|
|
97
|
+
{/each}
|
|
98
|
+
</div>
|
|
99
|
+
{/each}
|
|
100
|
+
```
|
|
101
|
+
|
|
53
102
|
---
|
|
54
103
|
|
|
55
104
|
## 📄 License
|
|
56
105
|
|
|
57
106
|
MIT © [Critical Path](https://github.com/Pixerate/Critical-Path)
|
|
107
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { CriticalPathClient } from '@critical-path/client';
|
|
2
|
+
import type { Comment, Attachment } from '@critical-path/core';
|
|
3
|
+
export interface ThreadedCommentWithAttachments extends Comment {
|
|
4
|
+
attachments: Attachment[];
|
|
5
|
+
replies: ThreadedCommentWithAttachments[];
|
|
6
|
+
}
|
|
7
|
+
export declare class TaskActivityState {
|
|
8
|
+
private client;
|
|
9
|
+
taskId?: string | undefined;
|
|
10
|
+
comments: Comment[];
|
|
11
|
+
attachments: Attachment[];
|
|
12
|
+
loading: boolean;
|
|
13
|
+
error: Error | null;
|
|
14
|
+
threads: ThreadedCommentWithAttachments[];
|
|
15
|
+
standaloneAttachments: Attachment[];
|
|
16
|
+
constructor(client: CriticalPathClient, taskId?: string | undefined);
|
|
17
|
+
fetch(taskId?: string): Promise<void>;
|
|
18
|
+
addComment(input: Omit<Comment, 'id' | 'taskId' | 'createdAt' | 'updatedAt'>, attachmentInputs?: Array<Omit<Attachment, 'id' | 'taskId' | 'commentId' | 'createdAt' | 'updatedAt'>>): Promise<{
|
|
19
|
+
comment: Comment;
|
|
20
|
+
attachments: Attachment[];
|
|
21
|
+
}>;
|
|
22
|
+
addAttachment(input: Omit<Attachment, 'id' | 'taskId' | 'createdAt' | 'updatedAt'>): Promise<Attachment>;
|
|
23
|
+
deleteComment(id: string): Promise<void>;
|
|
24
|
+
deleteAttachment(id: string): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
export declare function createTaskActivityState(client: CriticalPathClient, taskId?: string): TaskActivityState;
|
|
27
|
+
//# sourceMappingURL=activity-state.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"activity-state.svelte.d.ts","sourceRoot":"","sources":["../src/activity-state.svelte.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAE/D,MAAM,WAAW,8BAA+B,SAAQ,OAAO;IAC7D,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,OAAO,EAAE,8BAA8B,EAAE,CAAC;CAC3C;AAED,qBAAa,iBAAiB;IA6ChB,OAAO,CAAC,MAAM;IAA6B,MAAM,CAAC,EAAE,MAAM;IA5CtE,QAAQ,YAAyB;IACjC,WAAW,eAA4B;IACvC,OAAO,UAA0B;IACjC,KAAK,eAA8B;IAEnC,OAAO,mCAiCJ;IAEH,qBAAqB,eAElB;gBAEiB,MAAM,EAAE,kBAAkB,EAAS,MAAM,CAAC,EAAE,MAAM,YAAA;IAEhE,KAAK,CAAC,MAAM,CAAC,EAAE,MAAM;IAwBrB,UAAU,CACd,KAAK,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,GAAG,QAAQ,GAAG,WAAW,GAAG,WAAW,CAAC,EACjE,gBAAgB,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,GAAG,QAAQ,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,CAAC,CAAC;;;;IAiCjG,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,GAAG,QAAQ,GAAG,WAAW,GAAG,WAAW,CAAC;IAelF,aAAa,CAAC,EAAE,EAAE,MAAM;IAWxB,gBAAgB,CAAC,EAAE,EAAE,MAAM;CAUlC;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,kBAAkB,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAEtG"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,KAAK,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE/E,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,aAAa,GAAG,kBAAkB,CAEnF;AAED,cAAc,2BAA2B,CAAC;AAC1C,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,8BAA8B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,KAAK,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE/E,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,aAAa,GAAG,kBAAkB,CAEnF;AAED,cAAc,2BAA2B,CAAC;AAC1C,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,4BAA4B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -383,9 +383,141 @@ function d(e, t) {
|
|
|
383
383
|
return new u(e, t);
|
|
384
384
|
}
|
|
385
385
|
//#endregion
|
|
386
|
+
//#region src/activity-state.svelte.ts
|
|
387
|
+
var f = class {
|
|
388
|
+
client;
|
|
389
|
+
taskId;
|
|
390
|
+
#e = t.state(t.proxy([]));
|
|
391
|
+
get comments() {
|
|
392
|
+
return t.get(this.#e);
|
|
393
|
+
}
|
|
394
|
+
set comments(e) {
|
|
395
|
+
t.set(this.#e, e, !0);
|
|
396
|
+
}
|
|
397
|
+
#t = t.state(t.proxy([]));
|
|
398
|
+
get attachments() {
|
|
399
|
+
return t.get(this.#t);
|
|
400
|
+
}
|
|
401
|
+
set attachments(e) {
|
|
402
|
+
t.set(this.#t, e, !0);
|
|
403
|
+
}
|
|
404
|
+
#n = t.state(!1);
|
|
405
|
+
get loading() {
|
|
406
|
+
return t.get(this.#n);
|
|
407
|
+
}
|
|
408
|
+
set loading(e) {
|
|
409
|
+
t.set(this.#n, e, !0);
|
|
410
|
+
}
|
|
411
|
+
#r = t.state(null);
|
|
412
|
+
get error() {
|
|
413
|
+
return t.get(this.#r);
|
|
414
|
+
}
|
|
415
|
+
set error(e) {
|
|
416
|
+
t.set(this.#r, e, !0);
|
|
417
|
+
}
|
|
418
|
+
#i = t.derived(() => {
|
|
419
|
+
let e = /* @__PURE__ */ new Map(), t = [], n = /* @__PURE__ */ new Map();
|
|
420
|
+
for (let e of this.attachments) e.commentId && (n.has(e.commentId) || n.set(e.commentId, []), n.get(e.commentId).push(e));
|
|
421
|
+
for (let t of this.comments) e.set(t.id, {
|
|
422
|
+
...t,
|
|
423
|
+
attachments: n.get(t.id) || [],
|
|
424
|
+
replies: []
|
|
425
|
+
});
|
|
426
|
+
for (let n of this.comments) {
|
|
427
|
+
let r = e.get(n.id);
|
|
428
|
+
n.parentId && e.has(n.parentId) ? e.get(n.parentId).replies.push(r) : t.push(r);
|
|
429
|
+
}
|
|
430
|
+
return t;
|
|
431
|
+
});
|
|
432
|
+
get threads() {
|
|
433
|
+
return t.get(this.#i);
|
|
434
|
+
}
|
|
435
|
+
set threads(e) {
|
|
436
|
+
t.set(this.#i, e);
|
|
437
|
+
}
|
|
438
|
+
#a = t.derived(() => this.attachments.filter((e) => !e.commentId));
|
|
439
|
+
get standaloneAttachments() {
|
|
440
|
+
return t.get(this.#a);
|
|
441
|
+
}
|
|
442
|
+
set standaloneAttachments(e) {
|
|
443
|
+
t.set(this.#a, e);
|
|
444
|
+
}
|
|
445
|
+
constructor(e, t) {
|
|
446
|
+
this.client = e, this.taskId = t;
|
|
447
|
+
}
|
|
448
|
+
async fetch(e) {
|
|
449
|
+
let t = e || this.taskId;
|
|
450
|
+
if (!t) {
|
|
451
|
+
this.comments = [], this.attachments = [];
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
this.taskId = t, this.loading = !0, this.error = null;
|
|
455
|
+
try {
|
|
456
|
+
let [e, n] = await Promise.all([this.client.getComments(t), this.client.getAttachments({ taskId: t })]);
|
|
457
|
+
this.comments = e, this.attachments = n;
|
|
458
|
+
} catch (e) {
|
|
459
|
+
this.error = e instanceof Error ? e : Error(String(e));
|
|
460
|
+
} finally {
|
|
461
|
+
this.loading = !1;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
async addComment(e, t) {
|
|
465
|
+
if (!this.taskId) throw Error("TaskActivityState requires a taskId to add comments.");
|
|
466
|
+
try {
|
|
467
|
+
let n = await this.client.addComment({
|
|
468
|
+
...e,
|
|
469
|
+
taskId: this.taskId
|
|
470
|
+
}), r = [];
|
|
471
|
+
return t && t.length > 0 && (r = await Promise.all(t.map((e) => this.client.createAttachment({
|
|
472
|
+
...e,
|
|
473
|
+
taskId: this.taskId,
|
|
474
|
+
commentId: n.id
|
|
475
|
+
})))), this.comments = [...this.comments, n], r.length > 0 && (this.attachments = [...this.attachments, ...r]), {
|
|
476
|
+
comment: n,
|
|
477
|
+
attachments: r
|
|
478
|
+
};
|
|
479
|
+
} catch (e) {
|
|
480
|
+
let t = e instanceof Error ? e : Error(String(e));
|
|
481
|
+
throw this.error = t, t;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
async addAttachment(e) {
|
|
485
|
+
if (!this.taskId) throw Error("TaskActivityState requires a taskId to add attachments.");
|
|
486
|
+
try {
|
|
487
|
+
let t = await this.client.createAttachment({
|
|
488
|
+
...e,
|
|
489
|
+
taskId: this.taskId
|
|
490
|
+
});
|
|
491
|
+
return this.attachments = [t, ...this.attachments], t;
|
|
492
|
+
} catch (e) {
|
|
493
|
+
let t = e instanceof Error ? e : Error(String(e));
|
|
494
|
+
throw this.error = t, t;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
async deleteComment(e) {
|
|
498
|
+
try {
|
|
499
|
+
await this.client.deleteComment(e), this.comments = this.comments.filter((t) => t.id !== e);
|
|
500
|
+
} catch (e) {
|
|
501
|
+
let t = e instanceof Error ? e : Error(String(e));
|
|
502
|
+
throw this.error = t, t;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
async deleteAttachment(e) {
|
|
506
|
+
try {
|
|
507
|
+
await this.client.deleteAttachment(e), this.attachments = this.attachments.filter((t) => t.id !== e);
|
|
508
|
+
} catch (e) {
|
|
509
|
+
let t = e instanceof Error ? e : Error(String(e));
|
|
510
|
+
throw this.error = t, t;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
function p(e, t) {
|
|
515
|
+
return new f(e, t);
|
|
516
|
+
}
|
|
517
|
+
//#endregion
|
|
386
518
|
//#region src/index.ts
|
|
387
|
-
function
|
|
519
|
+
function m(t) {
|
|
388
520
|
return new e(t);
|
|
389
521
|
}
|
|
390
522
|
//#endregion
|
|
391
|
-
export { u as AttachmentState, c as CommentState, n as ProjectState, i as TaskState, o as WorkflowState, d as createAttachmentState, l as createCommentState,
|
|
523
|
+
export { u as AttachmentState, c as CommentState, n as ProjectState, f as TaskActivityState, i as TaskState, o as WorkflowState, d as createAttachmentState, l as createCommentState, m as createCriticalPathClient, r as createProjectState, p as createTaskActivityState, a as createTaskState, s as createWorkflowState };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@critical-path/svelte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Svelte 5 Runes state and reactive integrations for Critical Path headless project management framework",
|
|
5
5
|
"author": "Jack James",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@critical-path/client": "0.
|
|
22
|
-
"@critical-path/core": "0.
|
|
21
|
+
"@critical-path/client": "0.4.0",
|
|
22
|
+
"@critical-path/core": "0.8.0"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
25
|
"svelte": "^5.0.0"
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/// <reference types="svelte" />
|
|
2
|
+
import type { CriticalPathClient } from '@critical-path/client';
|
|
3
|
+
import type { Comment, Attachment } from '@critical-path/core';
|
|
4
|
+
|
|
5
|
+
export interface ThreadedCommentWithAttachments extends Comment {
|
|
6
|
+
attachments: Attachment[];
|
|
7
|
+
replies: ThreadedCommentWithAttachments[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class TaskActivityState {
|
|
11
|
+
comments = $state<Comment[]>([]);
|
|
12
|
+
attachments = $state<Attachment[]>([]);
|
|
13
|
+
loading = $state<boolean>(false);
|
|
14
|
+
error = $state<Error | null>(null);
|
|
15
|
+
|
|
16
|
+
threads = $derived.by(() => {
|
|
17
|
+
const map = new Map<string, ThreadedCommentWithAttachments>();
|
|
18
|
+
const roots: ThreadedCommentWithAttachments[] = [];
|
|
19
|
+
|
|
20
|
+
// Group attachments by commentId
|
|
21
|
+
const attachmentsByComment = new Map<string, Attachment[]>();
|
|
22
|
+
for (const a of this.attachments) {
|
|
23
|
+
if (a.commentId) {
|
|
24
|
+
if (!attachmentsByComment.has(a.commentId)) {
|
|
25
|
+
attachmentsByComment.set(a.commentId, []);
|
|
26
|
+
}
|
|
27
|
+
attachmentsByComment.get(a.commentId)!.push(a);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
for (const c of this.comments) {
|
|
32
|
+
map.set(c.id, {
|
|
33
|
+
...c,
|
|
34
|
+
attachments: attachmentsByComment.get(c.id) || [],
|
|
35
|
+
replies: []
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
for (const c of this.comments) {
|
|
40
|
+
const threaded = map.get(c.id)!;
|
|
41
|
+
if (c.parentId && map.has(c.parentId)) {
|
|
42
|
+
map.get(c.parentId)!.replies.push(threaded);
|
|
43
|
+
} else {
|
|
44
|
+
roots.push(threaded);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return roots;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
standaloneAttachments = $derived.by(() => {
|
|
52
|
+
return this.attachments.filter((a) => !a.commentId);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
constructor(private client: CriticalPathClient, public taskId?: string) {}
|
|
56
|
+
|
|
57
|
+
async fetch(taskId?: string) {
|
|
58
|
+
const targetTaskId = taskId || this.taskId;
|
|
59
|
+
if (!targetTaskId) {
|
|
60
|
+
this.comments = [];
|
|
61
|
+
this.attachments = [];
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
this.taskId = targetTaskId;
|
|
65
|
+
this.loading = true;
|
|
66
|
+
this.error = null;
|
|
67
|
+
try {
|
|
68
|
+
const [comments, attachments] = await Promise.all([
|
|
69
|
+
this.client.getComments(targetTaskId),
|
|
70
|
+
this.client.getAttachments({ taskId: targetTaskId })
|
|
71
|
+
]);
|
|
72
|
+
this.comments = comments;
|
|
73
|
+
this.attachments = attachments;
|
|
74
|
+
} catch (err) {
|
|
75
|
+
this.error = err instanceof Error ? err : new Error(String(err));
|
|
76
|
+
} finally {
|
|
77
|
+
this.loading = false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async addComment(
|
|
82
|
+
input: Omit<Comment, 'id' | 'taskId' | 'createdAt' | 'updatedAt'>,
|
|
83
|
+
attachmentInputs?: Array<Omit<Attachment, 'id' | 'taskId' | 'commentId' | 'createdAt' | 'updatedAt'>>
|
|
84
|
+
) {
|
|
85
|
+
if (!this.taskId) {
|
|
86
|
+
throw new Error('TaskActivityState requires a taskId to add comments.');
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
const comment = await this.client.addComment({ ...input, taskId: this.taskId });
|
|
90
|
+
let createdAttachments: Attachment[] = [];
|
|
91
|
+
|
|
92
|
+
if (attachmentInputs && attachmentInputs.length > 0) {
|
|
93
|
+
createdAttachments = await Promise.all(
|
|
94
|
+
attachmentInputs.map((att) =>
|
|
95
|
+
this.client.createAttachment({
|
|
96
|
+
...att,
|
|
97
|
+
taskId: this.taskId,
|
|
98
|
+
commentId: comment.id
|
|
99
|
+
})
|
|
100
|
+
)
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
this.comments = [...this.comments, comment];
|
|
105
|
+
if (createdAttachments.length > 0) {
|
|
106
|
+
this.attachments = [...this.attachments, ...createdAttachments];
|
|
107
|
+
}
|
|
108
|
+
return { comment, attachments: createdAttachments };
|
|
109
|
+
} catch (err) {
|
|
110
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
111
|
+
this.error = errorObj;
|
|
112
|
+
throw errorObj;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async addAttachment(input: Omit<Attachment, 'id' | 'taskId' | 'createdAt' | 'updatedAt'>) {
|
|
117
|
+
if (!this.taskId) {
|
|
118
|
+
throw new Error('TaskActivityState requires a taskId to add attachments.');
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
const created = await this.client.createAttachment({ ...input, taskId: this.taskId });
|
|
122
|
+
this.attachments = [created, ...this.attachments];
|
|
123
|
+
return created;
|
|
124
|
+
} catch (err) {
|
|
125
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
126
|
+
this.error = errorObj;
|
|
127
|
+
throw errorObj;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async deleteComment(id: string) {
|
|
132
|
+
try {
|
|
133
|
+
await this.client.deleteComment(id);
|
|
134
|
+
this.comments = this.comments.filter((c) => c.id !== id);
|
|
135
|
+
} catch (err) {
|
|
136
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
137
|
+
this.error = errorObj;
|
|
138
|
+
throw errorObj;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async deleteAttachment(id: string) {
|
|
143
|
+
try {
|
|
144
|
+
await this.client.deleteAttachment(id);
|
|
145
|
+
this.attachments = this.attachments.filter((a) => a.id !== id);
|
|
146
|
+
} catch (err) {
|
|
147
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
148
|
+
this.error = errorObj;
|
|
149
|
+
throw errorObj;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function createTaskActivityState(client: CriticalPathClient, taskId?: string): TaskActivityState {
|
|
155
|
+
return new TaskActivityState(client, taskId);
|
|
156
|
+
}
|
package/src/index.test.ts
CHANGED
|
@@ -10,10 +10,12 @@ import {
|
|
|
10
10
|
CommentState,
|
|
11
11
|
createCommentState,
|
|
12
12
|
AttachmentState,
|
|
13
|
-
createAttachmentState
|
|
13
|
+
createAttachmentState,
|
|
14
|
+
TaskActivityState,
|
|
15
|
+
createTaskActivityState
|
|
14
16
|
} from './index.js';
|
|
15
17
|
import type { CriticalPathClient } from '@critical-path/client';
|
|
16
|
-
import type { Project, Task, Workflow } from '@critical-path/core';
|
|
18
|
+
import type { Project, Task, Workflow, Comment, Attachment } from '@critical-path/core';
|
|
17
19
|
|
|
18
20
|
describe('@critical-path/svelte Svelte 5 Runes Test Suite', () => {
|
|
19
21
|
it('exports client factory and Svelte 5 Runes state factories', () => {
|
|
@@ -21,9 +23,15 @@ describe('@critical-path/svelte Svelte 5 Runes Test Suite', () => {
|
|
|
21
23
|
expect(createProjectState).toBeDefined();
|
|
22
24
|
expect(createTaskState).toBeDefined();
|
|
23
25
|
expect(createWorkflowState).toBeDefined();
|
|
26
|
+
expect(createCommentState).toBeDefined();
|
|
27
|
+
expect(createAttachmentState).toBeDefined();
|
|
28
|
+
expect(createTaskActivityState).toBeDefined();
|
|
24
29
|
expect(ProjectState).toBeDefined();
|
|
25
30
|
expect(TaskState).toBeDefined();
|
|
26
31
|
expect(WorkflowState).toBeDefined();
|
|
32
|
+
expect(CommentState).toBeDefined();
|
|
33
|
+
expect(AttachmentState).toBeDefined();
|
|
34
|
+
expect(TaskActivityState).toBeDefined();
|
|
27
35
|
});
|
|
28
36
|
|
|
29
37
|
describe('WorkflowState', () => {
|
|
@@ -252,4 +260,51 @@ describe('@critical-path/svelte Svelte 5 Runes Test Suite', () => {
|
|
|
252
260
|
expect(attachmentState.data).toEqual([]);
|
|
253
261
|
});
|
|
254
262
|
});
|
|
263
|
+
|
|
264
|
+
describe('TaskActivityState', () => {
|
|
265
|
+
it('fetches comments and attachments and unifies them into threads with attachments', async () => {
|
|
266
|
+
const mockComment: Comment = {
|
|
267
|
+
id: 'cmt_1',
|
|
268
|
+
taskId: 'task_1',
|
|
269
|
+
content: 'Comment with attachment',
|
|
270
|
+
authorId: 'u1',
|
|
271
|
+
authorType: 'user',
|
|
272
|
+
createdAt: '2026-01-01',
|
|
273
|
+
updatedAt: '2026-01-01'
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const mockAttachment: Attachment = {
|
|
277
|
+
id: 'att_1',
|
|
278
|
+
taskId: 'task_1',
|
|
279
|
+
commentId: 'cmt_1',
|
|
280
|
+
filename: 'screenshot.png',
|
|
281
|
+
mimeType: 'image/png',
|
|
282
|
+
sizeBytes: 2048,
|
|
283
|
+
url: 'https://storage.example.com/screenshot.png',
|
|
284
|
+
uploaderId: 'u1',
|
|
285
|
+
uploaderType: 'user',
|
|
286
|
+
createdAt: '2026-01-01',
|
|
287
|
+
updatedAt: '2026-01-01'
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const mockClient = {
|
|
291
|
+
getComments: vi.fn().mockResolvedValue([mockComment]),
|
|
292
|
+
getAttachments: vi.fn().mockResolvedValue([mockAttachment]),
|
|
293
|
+
addComment: vi.fn().mockResolvedValue(mockComment),
|
|
294
|
+
createAttachment: vi.fn().mockResolvedValue(mockAttachment),
|
|
295
|
+
deleteComment: vi.fn().mockResolvedValue(true),
|
|
296
|
+
deleteAttachment: vi.fn().mockResolvedValue(true)
|
|
297
|
+
} as unknown as CriticalPathClient;
|
|
298
|
+
|
|
299
|
+
const activityState = createTaskActivityState(mockClient, 'task_1');
|
|
300
|
+
await activityState.fetch();
|
|
301
|
+
|
|
302
|
+
expect(activityState.comments).toEqual([mockComment]);
|
|
303
|
+
expect(activityState.attachments).toEqual([mockAttachment]);
|
|
304
|
+
expect(activityState.threads).toHaveLength(1);
|
|
305
|
+
expect(activityState.threads[0].attachments).toEqual([mockAttachment]);
|
|
306
|
+
expect(activityState.standaloneAttachments).toHaveLength(0);
|
|
307
|
+
});
|
|
308
|
+
});
|
|
255
309
|
});
|
|
310
|
+
|