@blocknote/core 0.24.1 → 0.25.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/dist/blocknote.cjs +12 -0
- package/dist/blocknote.cjs.map +1 -0
- package/dist/blocknote.js +5028 -3444
- package/dist/blocknote.js.map +1 -1
- package/dist/comments.cjs +2 -0
- package/dist/comments.cjs.map +1 -0
- package/dist/comments.js +593 -0
- package/dist/comments.js.map +1 -0
- package/dist/style.css +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/webpack-stats.json +1 -1
- package/package.json +39 -26
- package/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap +1022 -378
- package/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap +730 -270
- package/src/api/blockManipulation/commands/moveBlocks/__snapshots__/moveBlocks.test.ts.snap +3100 -1260
- package/src/api/blockManipulation/commands/removeBlocks/__snapshots__/removeBlocks.test.ts.snap +438 -162
- package/src/api/blockManipulation/commands/replaceBlocks/__snapshots__/replaceBlocks.test.ts.snap +1168 -432
- package/src/api/blockManipulation/commands/splitBlock/__snapshots__/splitBlock.test.ts.snap +930 -378
- package/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap +2485 -1015
- package/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts +28 -1
- package/src/api/blockManipulation/commands/updateBlock/updateBlock.ts +1 -1
- package/src/api/blockManipulation/selections/__snapshots__/selection.test.ts.snap +292 -108
- package/src/api/blockManipulation/setupTestEnv.ts +14 -1
- package/src/api/blockManipulation/tables/tables.test.ts +1987 -0
- package/src/api/blockManipulation/tables/tables.ts +887 -0
- package/src/api/clipboard/__snapshots__/external/pasteEndOfParagraph.html +66 -24
- package/src/api/clipboard/__snapshots__/external/pasteEndOfParagraphText.html +66 -24
- package/src/api/clipboard/__snapshots__/external/pasteImage.html +66 -24
- package/src/api/clipboard/__snapshots__/external/pasteParagraphInCustomBlock.html +66 -24
- package/src/api/clipboard/__snapshots__/external/pasteTable.html +132 -48
- package/src/api/clipboard/__snapshots__/external/pasteTableInExistingTable.html +136 -44
- package/src/api/clipboard/fromClipboard/handleFileInsertion.ts +36 -14
- package/src/api/clipboard/toClipboard/copyExtension.ts +2 -3
- package/src/api/exporters/html/__snapshots__/table/headerCols/external.html +1 -0
- package/src/api/exporters/html/__snapshots__/table/headerCols/internal.html +1 -0
- package/src/api/exporters/html/__snapshots__/table/headerRows/external.html +1 -0
- package/src/api/exporters/html/__snapshots__/table/headerRows/internal.html +1 -0
- package/src/api/exporters/html/__snapshots__/table/headersRows/external.html +1 -0
- package/src/api/exporters/html/__snapshots__/table/headersRows/internal.html +1 -0
- package/src/api/exporters/html/__snapshots__/table/mixedCellColors/external.html +1 -0
- package/src/api/exporters/html/__snapshots__/table/mixedCellColors/internal.html +1 -0
- package/src/api/exporters/html/__snapshots__/table/mixedRowspansAndColspans/external.html +1 -0
- package/src/api/exporters/html/__snapshots__/table/mixedRowspansAndColspans/internal.html +1 -0
- package/src/api/exporters/markdown/__snapshots__/table/headerCols/markdown.md +4 -0
- package/src/api/exporters/markdown/__snapshots__/table/headerRows/markdown.md +4 -0
- package/src/api/exporters/markdown/__snapshots__/table/mixedCellColors/markdown.md +5 -0
- package/src/api/exporters/markdown/__snapshots__/table/mixedRowspansAndColspans/markdown.md +5 -0
- package/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap +985 -20
- package/src/api/nodeConversions/blockToNode.ts +63 -20
- package/src/api/nodeConversions/nodeToBlock.ts +75 -13
- package/src/api/parsers/html/__snapshots__/parse-notion-html.json +145 -54
- package/src/api/testUtil/cases/defaultSchema.ts +782 -9
- package/src/api/testUtil/partialBlockTestUtil.ts +39 -4
- package/src/blocks/TableBlockContent/TableBlockContent.ts +11 -5
- package/src/blocks/defaultBlockTypeGuards.ts +8 -0
- package/src/comments/index.ts +9 -0
- package/src/comments/models/User.ts +8 -0
- package/src/comments/threadstore/DefaultThreadStoreAuth.ts +106 -0
- package/src/comments/threadstore/ThreadStore.ts +134 -0
- package/src/comments/threadstore/ThreadStoreAuth.ts +13 -0
- package/src/comments/threadstore/TipTapThreadStore.ts +292 -0
- package/src/comments/threadstore/yjs/RESTYjsThreadStore.ts +144 -0
- package/src/comments/threadstore/yjs/YjsThreadStore.test.ts +294 -0
- package/src/comments/threadstore/yjs/YjsThreadStore.ts +340 -0
- package/src/comments/threadstore/yjs/YjsThreadStoreBase.ts +48 -0
- package/src/comments/threadstore/yjs/yjsHelpers.ts +121 -0
- package/src/comments/types.ts +117 -0
- package/src/editor/Block.css +16 -8
- package/src/editor/BlockNoteEditor.ts +269 -92
- package/src/editor/BlockNoteExtensions.ts +24 -1
- package/src/editor/BlockNoteTipTapEditor.ts +5 -1
- package/src/editor/editor.css +17 -0
- package/src/extensions/BackgroundColor/BackgroundColorExtension.ts +1 -1
- package/src/extensions/Comments/CommentMark.ts +61 -0
- package/src/extensions/Comments/CommentsPlugin.ts +301 -0
- package/src/extensions/Comments/userstore/UserStore.ts +72 -0
- package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +16 -10
- package/src/extensions/LinkToolbar/LinkToolbarPlugin.ts +3 -3
- package/src/extensions/ShowSelection/ShowSelectionPlugin.ts +52 -0
- package/src/extensions/SideMenu/SideMenuPlugin.ts +22 -9
- package/src/extensions/TableHandles/TableHandlesPlugin.ts +409 -57
- package/src/extensions/TextAlignment/TextAlignmentExtension.ts +2 -0
- package/src/extensions/TextColor/TextColorExtension.ts +1 -1
- package/src/extensions/UniqueID/UniqueID.ts +8 -3
- package/src/i18n/locales/ar.ts +23 -0
- package/src/i18n/locales/de.ts +15 -0
- package/src/i18n/locales/en.ts +25 -1
- package/src/i18n/locales/es.ts +16 -1
- package/src/i18n/locales/fr.ts +23 -0
- package/src/i18n/locales/hr.ts +18 -0
- package/src/i18n/locales/index.ts +1 -0
- package/src/i18n/locales/is.ts +24 -1
- package/src/i18n/locales/it.ts +21 -0
- package/src/i18n/locales/ja.ts +23 -0
- package/src/i18n/locales/ko.ts +23 -0
- package/src/i18n/locales/nl.ts +23 -0
- package/src/i18n/locales/no.ts +346 -0
- package/src/i18n/locales/pl.ts +23 -0
- package/src/i18n/locales/pt.ts +23 -0
- package/src/i18n/locales/ru.ts +23 -0
- package/src/i18n/locales/uk.ts +23 -0
- package/src/i18n/locales/vi.ts +23 -0
- package/src/i18n/locales/zh.ts +23 -0
- package/src/index.ts +6 -4
- package/src/schema/blocks/types.ts +32 -2
- package/src/util/browser.ts +1 -1
- package/src/util/table.ts +107 -0
- package/types/src/api/blockManipulation/tables/tables.d.ts +343 -0
- package/types/src/api/blockManipulation/tables/tables.test.d.ts +1 -0
- package/types/src/api/clipboard/toClipboard/copyExtension.d.ts +1 -1
- package/types/src/blocks/TableBlockContent/TableBlockContent.d.ts +1 -2
- package/types/src/blocks/defaultBlockTypeGuards.d.ts +3 -0
- package/types/src/comments/index.d.ts +9 -0
- package/types/src/comments/models/User.d.ts +8 -0
- package/types/src/comments/threadstore/DefaultThreadStoreAuth.d.ts +47 -0
- package/types/src/comments/threadstore/ThreadStore.d.ts +121 -0
- package/types/src/comments/threadstore/ThreadStoreAuth.d.ts +12 -0
- package/types/src/comments/threadstore/TipTapThreadStore.d.ts +97 -0
- package/types/src/comments/threadstore/yjs/RESTYjsThreadStore.d.ts +83 -0
- package/types/src/comments/threadstore/yjs/YjsThreadStore.d.ts +79 -0
- package/types/src/comments/threadstore/yjs/YjsThreadStore.test.d.ts +1 -0
- package/types/src/comments/threadstore/yjs/YjsThreadStoreBase.d.ts +15 -0
- package/types/src/comments/threadstore/yjs/yjsHelpers.d.ts +13 -0
- package/types/src/comments/types.d.ts +109 -0
- package/types/src/editor/BlockNoteEditor.d.ts +146 -66
- package/types/src/editor/BlockNoteExtensions.d.ts +4 -0
- package/types/src/extensions/Collaboration/createCollaborationExtensions.d.ts +1 -1
- package/types/src/extensions/Comments/CommentMark.d.ts +2 -0
- package/types/src/extensions/Comments/CommentsPlugin.d.ts +49 -0
- package/types/src/extensions/Comments/userstore/UserStore.d.ts +31 -0
- package/types/src/extensions/FormattingToolbar/FormattingToolbarPlugin.d.ts +1 -1
- package/types/src/extensions/ShowSelection/ShowSelectionPlugin.d.ts +15 -0
- package/types/src/extensions/SideMenu/SideMenuPlugin.d.ts +1 -0
- package/types/src/extensions/TableHandles/TableHandlesPlugin.d.ts +66 -1
- package/types/src/i18n/locales/de.d.ts +15 -0
- package/types/src/i18n/locales/en.d.ts +20 -0
- package/types/src/i18n/locales/es.d.ts +15 -0
- package/types/src/i18n/locales/hr.d.ts +18 -0
- package/types/src/i18n/locales/index.d.ts +1 -0
- package/types/src/i18n/locales/it.d.ts +21 -0
- package/types/src/i18n/locales/no.d.ts +2 -0
- package/types/src/index.d.ts +5 -4
- package/types/src/pm-nodes/BlockContainer.d.ts +2 -2
- package/types/src/pm-nodes/BlockGroup.d.ts +2 -2
- package/types/src/schema/blocks/types.d.ts +23 -2
- package/types/src/util/browser.d.ts +1 -1
- package/types/src/util/table.d.ts +12 -0
- package/dist/blocknote.umd.cjs +0 -11
- package/dist/blocknote.umd.cjs.map +0 -1
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import * as Y from "yjs";
|
|
2
|
+
import { CommentBody } from "../../types.js";
|
|
3
|
+
import { ThreadStoreAuth } from "../ThreadStoreAuth.js";
|
|
4
|
+
import { YjsThreadStoreBase } from "./YjsThreadStoreBase.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* This is a REST-based implementation of the YjsThreadStoreBase.
|
|
8
|
+
* It Reads data directly from the underlying document (same as YjsThreadStore),
|
|
9
|
+
* but for Writes, it sends data to a REST API that should:
|
|
10
|
+
* - check the user has the correct permissions to make the desired changes
|
|
11
|
+
* - apply the updates to the underlying Yjs document
|
|
12
|
+
*
|
|
13
|
+
* (see https://github.com/TypeCellOS/BlockNote-demo-nextjs-hocuspocus)
|
|
14
|
+
*
|
|
15
|
+
* The reason we still use the Yjs document as underlying storage is that it makes it easy to
|
|
16
|
+
* sync updates in real-time to other collaborators.
|
|
17
|
+
* (but technically, you could also implement a different storage altogether
|
|
18
|
+
* and not store the thread related data in the Yjs document)
|
|
19
|
+
*/
|
|
20
|
+
export class RESTYjsThreadStore extends YjsThreadStoreBase {
|
|
21
|
+
constructor(
|
|
22
|
+
private readonly BASE_URL: string,
|
|
23
|
+
private readonly headers: Record<string, string>,
|
|
24
|
+
threadsYMap: Y.Map<any>,
|
|
25
|
+
auth: ThreadStoreAuth
|
|
26
|
+
) {
|
|
27
|
+
super(threadsYMap, auth);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private doRequest = async (path: string, method: string, body?: any) => {
|
|
31
|
+
const response = await fetch(`${this.BASE_URL}${path}`, {
|
|
32
|
+
method,
|
|
33
|
+
body: JSON.stringify(body),
|
|
34
|
+
headers: {
|
|
35
|
+
"Content-Type": "application/json",
|
|
36
|
+
...this.headers,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
throw new Error(`Failed to ${method} ${path}: ${response.statusText}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return response.json();
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
public addThreadToDocument = async (options: {
|
|
48
|
+
threadId: string;
|
|
49
|
+
selection: {
|
|
50
|
+
prosemirror: {
|
|
51
|
+
head: number;
|
|
52
|
+
anchor: number;
|
|
53
|
+
};
|
|
54
|
+
yjs: {
|
|
55
|
+
head: any;
|
|
56
|
+
anchor: any;
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
}) => {
|
|
60
|
+
const { threadId, ...rest } = options;
|
|
61
|
+
return this.doRequest(`/${threadId}/addToDocument`, "POST", rest);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
public createThread = async (options: {
|
|
65
|
+
initialComment: {
|
|
66
|
+
body: CommentBody;
|
|
67
|
+
metadata?: any;
|
|
68
|
+
};
|
|
69
|
+
metadata?: any;
|
|
70
|
+
}) => {
|
|
71
|
+
return this.doRequest("", "POST", options);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
public addComment = (options: {
|
|
75
|
+
comment: {
|
|
76
|
+
body: CommentBody;
|
|
77
|
+
metadata?: any;
|
|
78
|
+
};
|
|
79
|
+
threadId: string;
|
|
80
|
+
}) => {
|
|
81
|
+
const { threadId, ...rest } = options;
|
|
82
|
+
return this.doRequest(`/${threadId}/comments`, "POST", rest);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
public updateComment = (options: {
|
|
86
|
+
comment: {
|
|
87
|
+
body: CommentBody;
|
|
88
|
+
metadata?: any;
|
|
89
|
+
};
|
|
90
|
+
threadId: string;
|
|
91
|
+
commentId: string;
|
|
92
|
+
}) => {
|
|
93
|
+
const { threadId, commentId, ...rest } = options;
|
|
94
|
+
return this.doRequest(`/${threadId}/comments/${commentId}`, "PUT", rest);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
public deleteComment = (options: {
|
|
98
|
+
threadId: string;
|
|
99
|
+
commentId: string;
|
|
100
|
+
softDelete?: boolean;
|
|
101
|
+
}) => {
|
|
102
|
+
const { threadId, commentId, ...rest } = options;
|
|
103
|
+
return this.doRequest(
|
|
104
|
+
`/${threadId}/comments/${commentId}?soft=${!!rest.softDelete}`,
|
|
105
|
+
"DELETE"
|
|
106
|
+
);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
public deleteThread = (options: { threadId: string }) => {
|
|
110
|
+
return this.doRequest(`/${options.threadId}`, "DELETE");
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
public resolveThread = (options: { threadId: string }) => {
|
|
114
|
+
return this.doRequest(`/${options.threadId}/resolve`, "POST");
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
public unresolveThread = (options: { threadId: string }) => {
|
|
118
|
+
return this.doRequest(`/${options.threadId}/unresolve`, "POST");
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
public addReaction = (options: {
|
|
122
|
+
threadId: string;
|
|
123
|
+
commentId: string;
|
|
124
|
+
emoji: string;
|
|
125
|
+
}) => {
|
|
126
|
+
const { threadId, commentId, ...rest } = options;
|
|
127
|
+
return this.doRequest(
|
|
128
|
+
`/${threadId}/comments/${commentId}/reactions`,
|
|
129
|
+
"POST",
|
|
130
|
+
rest
|
|
131
|
+
);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
public deleteReaction = (options: {
|
|
135
|
+
threadId: string;
|
|
136
|
+
commentId: string;
|
|
137
|
+
emoji: string;
|
|
138
|
+
}) => {
|
|
139
|
+
return this.doRequest(
|
|
140
|
+
`/${options.threadId}/comments/${options.commentId}/reactions/${options.emoji}`,
|
|
141
|
+
"DELETE"
|
|
142
|
+
);
|
|
143
|
+
};
|
|
144
|
+
}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import * as Y from "yjs";
|
|
3
|
+
import { CommentBody } from "../../types.js";
|
|
4
|
+
import { DefaultThreadStoreAuth } from "../DefaultThreadStoreAuth.js";
|
|
5
|
+
import { YjsThreadStore } from "./YjsThreadStore.js";
|
|
6
|
+
|
|
7
|
+
// Mock UUID to generate sequential IDs
|
|
8
|
+
let mockUuidCounter = 0;
|
|
9
|
+
vi.mock("uuid", () => ({
|
|
10
|
+
v4: () => `mocked-uuid-${++mockUuidCounter}`,
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
describe("YjsThreadStore", () => {
|
|
14
|
+
let store: YjsThreadStore;
|
|
15
|
+
let doc: Y.Doc;
|
|
16
|
+
let threadsYMap: Y.Map<any>;
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
// Reset mocks and create fresh instances
|
|
20
|
+
vi.clearAllMocks();
|
|
21
|
+
mockUuidCounter = 0;
|
|
22
|
+
doc = new Y.Doc();
|
|
23
|
+
threadsYMap = doc.getMap("threads");
|
|
24
|
+
|
|
25
|
+
store = new YjsThreadStore(
|
|
26
|
+
"test-user",
|
|
27
|
+
threadsYMap,
|
|
28
|
+
new DefaultThreadStoreAuth("test-user", "editor")
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("createThread", () => {
|
|
33
|
+
it("creates a thread with initial comment", async () => {
|
|
34
|
+
const initialComment = {
|
|
35
|
+
body: "Test comment" as CommentBody,
|
|
36
|
+
metadata: { extra: "metadatacomment" },
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const thread = await store.createThread({
|
|
40
|
+
initialComment,
|
|
41
|
+
metadata: { extra: "metadatathread" },
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
expect(thread).toMatchObject({
|
|
45
|
+
type: "thread",
|
|
46
|
+
id: "mocked-uuid-2",
|
|
47
|
+
resolved: false,
|
|
48
|
+
metadata: { extra: "metadatathread" },
|
|
49
|
+
comments: [
|
|
50
|
+
{
|
|
51
|
+
type: "comment",
|
|
52
|
+
id: "mocked-uuid-1",
|
|
53
|
+
userId: "test-user",
|
|
54
|
+
body: "Test comment",
|
|
55
|
+
metadata: { extra: "metadatacomment" },
|
|
56
|
+
reactions: [],
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("addComment", () => {
|
|
64
|
+
it("adds a comment to existing thread", async () => {
|
|
65
|
+
// First create a thread
|
|
66
|
+
const thread = await store.createThread({
|
|
67
|
+
initialComment: {
|
|
68
|
+
body: "Initial comment" as CommentBody,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Add new comment
|
|
73
|
+
const comment = await store.addComment({
|
|
74
|
+
threadId: thread.id,
|
|
75
|
+
comment: {
|
|
76
|
+
body: "New comment" as CommentBody,
|
|
77
|
+
metadata: { test: "metadata" },
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
expect(comment).toMatchObject({
|
|
82
|
+
type: "comment",
|
|
83
|
+
id: "mocked-uuid-3",
|
|
84
|
+
userId: "test-user",
|
|
85
|
+
body: "New comment",
|
|
86
|
+
metadata: { test: "metadata" },
|
|
87
|
+
reactions: [],
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Verify thread has both comments
|
|
91
|
+
const updatedThread = store.getThread(thread.id);
|
|
92
|
+
expect(updatedThread.comments).toHaveLength(2);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("throws error for non-existent thread", async () => {
|
|
96
|
+
await expect(
|
|
97
|
+
store.addComment({
|
|
98
|
+
threadId: "non-existent",
|
|
99
|
+
comment: {
|
|
100
|
+
body: "Test comment" as CommentBody,
|
|
101
|
+
},
|
|
102
|
+
})
|
|
103
|
+
).rejects.toThrow("Thread not found");
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("updateComment", () => {
|
|
108
|
+
it("updates existing comment", async () => {
|
|
109
|
+
const thread = await store.createThread({
|
|
110
|
+
initialComment: {
|
|
111
|
+
body: "Initial comment" as CommentBody,
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
await store.updateComment({
|
|
116
|
+
threadId: thread.id,
|
|
117
|
+
commentId: thread.comments[0].id,
|
|
118
|
+
comment: {
|
|
119
|
+
body: "Updated comment" as CommentBody,
|
|
120
|
+
metadata: { updatedMetadata: true },
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const updatedThread = store.getThread(thread.id);
|
|
125
|
+
expect(updatedThread.comments[0]).toMatchObject({
|
|
126
|
+
body: "Updated comment",
|
|
127
|
+
metadata: { updatedMetadata: true },
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe("deleteComment", () => {
|
|
133
|
+
it("soft deletes a comment", async () => {
|
|
134
|
+
const thread = await store.createThread({
|
|
135
|
+
initialComment: {
|
|
136
|
+
body: "Test comment" as CommentBody,
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
await store.deleteComment({
|
|
141
|
+
threadId: thread.id,
|
|
142
|
+
commentId: thread.comments[0].id,
|
|
143
|
+
softDelete: true,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const updatedThread = store.getThread(thread.id);
|
|
147
|
+
expect(updatedThread.comments[0].deletedAt).toBeDefined();
|
|
148
|
+
expect(updatedThread.comments[0].body).toBeUndefined();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("hard deletes a comment (deletes thread)", async () => {
|
|
152
|
+
const thread = await store.createThread({
|
|
153
|
+
initialComment: {
|
|
154
|
+
body: "Test comment" as CommentBody,
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
await store.deleteComment({
|
|
159
|
+
threadId: thread.id,
|
|
160
|
+
commentId: thread.comments[0].id,
|
|
161
|
+
softDelete: false,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Thread should be deleted since it was the only comment
|
|
165
|
+
expect(() => store.getThread(thread.id)).toThrow("Thread not found");
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe("resolveThread", () => {
|
|
170
|
+
it("resolves a thread", async () => {
|
|
171
|
+
const thread = await store.createThread({
|
|
172
|
+
initialComment: {
|
|
173
|
+
body: "Test comment" as CommentBody,
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
await store.resolveThread({ threadId: thread.id });
|
|
178
|
+
|
|
179
|
+
const updatedThread = store.getThread(thread.id);
|
|
180
|
+
expect(updatedThread.resolved).toBe(true);
|
|
181
|
+
expect(updatedThread.resolvedUpdatedAt).toBeDefined();
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
describe("unresolveThread", () => {
|
|
186
|
+
it("unresolves a thread", async () => {
|
|
187
|
+
const thread = await store.createThread({
|
|
188
|
+
initialComment: {
|
|
189
|
+
body: "Test comment" as CommentBody,
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
await store.resolveThread({ threadId: thread.id });
|
|
194
|
+
await store.unresolveThread({ threadId: thread.id });
|
|
195
|
+
|
|
196
|
+
const updatedThread = store.getThread(thread.id);
|
|
197
|
+
expect(updatedThread.resolved).toBe(false);
|
|
198
|
+
expect(updatedThread.resolvedUpdatedAt).toBeDefined();
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
describe("getThreads", () => {
|
|
203
|
+
it("returns all threads", async () => {
|
|
204
|
+
await store.createThread({
|
|
205
|
+
initialComment: {
|
|
206
|
+
body: "Thread 1" as CommentBody,
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
await store.createThread({
|
|
211
|
+
initialComment: {
|
|
212
|
+
body: "Thread 2" as CommentBody,
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const threads = store.getThreads();
|
|
217
|
+
expect(threads.size).toBe(2);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe("deleteThread", () => {
|
|
222
|
+
it("deletes an entire thread", async () => {
|
|
223
|
+
const thread = await store.createThread({
|
|
224
|
+
initialComment: {
|
|
225
|
+
body: "Test comment" as CommentBody,
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
await store.deleteThread({ threadId: thread.id });
|
|
230
|
+
|
|
231
|
+
// Verify thread is deleted
|
|
232
|
+
expect(() => store.getThread(thread.id)).toThrow("Thread not found");
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
describe("reactions", () => {
|
|
237
|
+
it("adds a reaction to a comment", async () => {
|
|
238
|
+
const thread = await store.createThread({
|
|
239
|
+
initialComment: {
|
|
240
|
+
body: "Test comment" as CommentBody,
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
await store.addReaction({
|
|
245
|
+
threadId: thread.id,
|
|
246
|
+
commentId: thread.comments[0].id,
|
|
247
|
+
emoji: "👍",
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
expect(store.getThread(thread.id).comments[0].reactions).toHaveLength(1);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("deletes a reaction from a comment", async () => {
|
|
254
|
+
const thread = await store.createThread({
|
|
255
|
+
initialComment: {
|
|
256
|
+
body: "Test comment" as CommentBody,
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
await store.addReaction({
|
|
261
|
+
threadId: thread.id,
|
|
262
|
+
commentId: thread.comments[0].id,
|
|
263
|
+
emoji: "👍",
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
expect(store.getThread(thread.id).comments[0].reactions).toHaveLength(1);
|
|
267
|
+
|
|
268
|
+
await store.deleteReaction({
|
|
269
|
+
threadId: thread.id,
|
|
270
|
+
commentId: thread.comments[0].id,
|
|
271
|
+
emoji: "👍",
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
expect(store.getThread(thread.id).comments[0].reactions).toHaveLength(0);
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
describe("subscribe", () => {
|
|
279
|
+
it("calls callback when threads change", async () => {
|
|
280
|
+
const callback = vi.fn();
|
|
281
|
+
const unsubscribe = store.subscribe(callback);
|
|
282
|
+
|
|
283
|
+
await store.createThread({
|
|
284
|
+
initialComment: {
|
|
285
|
+
body: "Test comment" as CommentBody,
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
expect(callback).toHaveBeenCalled();
|
|
290
|
+
|
|
291
|
+
unsubscribe();
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
});
|