@abraca/dabra 1.8.2 → 2.0.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/abracadabra-provider.cjs +12722 -9050
- package/dist/abracadabra-provider.cjs.map +1 -1
- package/dist/abracadabra-provider.esm.js +12683 -9061
- package/dist/abracadabra-provider.esm.js.map +1 -1
- package/dist/index.d.ts +1485 -118
- package/package.json +1 -1
- package/src/AbracadabraBaseProvider.ts +51 -2
- package/src/AbracadabraClient.ts +516 -66
- package/src/AbracadabraProvider.ts +22 -7
- package/src/AbracadabraWS.ts +1 -1
- package/src/ChatClient.ts +193 -113
- package/src/ContentManager.ts +228 -0
- package/src/CryptoIdentityKeystore.ts +3 -3
- package/src/DocConverters.ts +1862 -0
- package/src/DocKeyManager.ts +60 -12
- package/src/DocTypes.ts +628 -0
- package/src/DocUtils.ts +89 -0
- package/src/DocumentManager.ts +319 -0
- package/src/E2EAbracadabraProvider.ts +189 -0
- package/src/EncryptedChatClient.ts +173 -0
- package/src/EncryptedY.ts +2 -2
- package/src/FileBlobStore.ts +10 -0
- package/src/IdentityDoc.ts +25 -0
- package/src/MetaManager.ts +100 -0
- package/src/MnemonicKeyDerivation.ts +4 -4
- package/src/NotificationsClient.ts +120 -98
- package/src/OutgoingMessages/SubdocMessage.ts +2 -2
- package/src/RpcClient.ts +659 -0
- package/src/TreeManager.ts +473 -0
- package/src/TreeTimestamps.ts +28 -25
- package/src/index.ts +71 -1
- package/src/messageRecord.ts +121 -0
- package/src/types.ts +174 -16
- package/src/webrtc/AbracadabraWebRTC.ts +2 -2
- package/src/webrtc/DataChannelRouter.ts +2 -2
- package/src/webrtc/E2EEChannel.ts +3 -3
- package/src/webrtc/FileTransferChannel.ts +9 -2
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContentManager — read/write document content via markdown ↔ Y.js conversion.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from `mcp/tools/content.ts` and `cli/commands/content.ts`.
|
|
5
|
+
*/
|
|
6
|
+
import * as Y from "yjs";
|
|
7
|
+
import type { PageMeta } from "./DocTypes.ts";
|
|
8
|
+
import { toPlain } from "./DocUtils.ts";
|
|
9
|
+
import {
|
|
10
|
+
yjsToMarkdown,
|
|
11
|
+
populateYDocFromMarkdown,
|
|
12
|
+
parseFrontmatter,
|
|
13
|
+
buildHeadingElement,
|
|
14
|
+
buildParagraphElement,
|
|
15
|
+
buildBulletListElement,
|
|
16
|
+
buildOrderedListElement,
|
|
17
|
+
buildTaskListElement,
|
|
18
|
+
buildCodeBlockElement,
|
|
19
|
+
buildBlockquoteElement,
|
|
20
|
+
buildHorizontalRuleElement,
|
|
21
|
+
buildBlocksFromMarkdown,
|
|
22
|
+
readBlocksFromFragment,
|
|
23
|
+
type DocumentBlock,
|
|
24
|
+
} from "./DocConverters.ts";
|
|
25
|
+
export type { DocumentBlock } from "./DocConverters.ts";
|
|
26
|
+
import type { DocumentManager } from "./DocumentManager.ts";
|
|
27
|
+
|
|
28
|
+
export interface DocumentContent {
|
|
29
|
+
label: string;
|
|
30
|
+
type?: string;
|
|
31
|
+
meta?: PageMeta;
|
|
32
|
+
title: string;
|
|
33
|
+
markdown: string;
|
|
34
|
+
children: Array<{
|
|
35
|
+
id: string;
|
|
36
|
+
label: string;
|
|
37
|
+
type?: string;
|
|
38
|
+
meta?: PageMeta;
|
|
39
|
+
}>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export class ContentManager {
|
|
43
|
+
constructor(private dm: DocumentManager) {}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Read document content as markdown.
|
|
47
|
+
* Returns the title extracted from the TipTap documentHeader, the markdown
|
|
48
|
+
* body, tree metadata, and immediate children.
|
|
49
|
+
*/
|
|
50
|
+
async read(docId: string): Promise<DocumentContent> {
|
|
51
|
+
const provider = await this.dm.getChildProvider(docId);
|
|
52
|
+
const fragment = provider.document.getXmlFragment("default");
|
|
53
|
+
|
|
54
|
+
const { title, markdown } = yjsToMarkdown(fragment);
|
|
55
|
+
|
|
56
|
+
// Get tree metadata + immediate children
|
|
57
|
+
const treeMap = this.dm.getTreeMap();
|
|
58
|
+
let label = title;
|
|
59
|
+
let type: string | undefined;
|
|
60
|
+
let meta: PageMeta | undefined;
|
|
61
|
+
const childrenWithOrder: Array<{
|
|
62
|
+
id: string;
|
|
63
|
+
label: string;
|
|
64
|
+
type?: string;
|
|
65
|
+
meta?: PageMeta;
|
|
66
|
+
order: number;
|
|
67
|
+
}> = [];
|
|
68
|
+
|
|
69
|
+
if (treeMap) {
|
|
70
|
+
const raw = treeMap.get(docId);
|
|
71
|
+
if (raw) {
|
|
72
|
+
const entry = toPlain(raw) as Record<string, unknown>;
|
|
73
|
+
label = (entry.label as string) || title;
|
|
74
|
+
type = entry.type as string | undefined;
|
|
75
|
+
meta = entry.meta as PageMeta | undefined;
|
|
76
|
+
}
|
|
77
|
+
// Collect immediate children sorted by order
|
|
78
|
+
treeMap.forEach((rawChild: unknown, id: string) => {
|
|
79
|
+
const value = toPlain(rawChild) as Record<string, unknown>;
|
|
80
|
+
if (value && value.parentId === docId) {
|
|
81
|
+
childrenWithOrder.push({
|
|
82
|
+
id,
|
|
83
|
+
label: (value.label as string) || "Untitled",
|
|
84
|
+
type: value.type as string | undefined,
|
|
85
|
+
meta: value.meta as PageMeta | undefined,
|
|
86
|
+
order: (value.order as number) ?? 0,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
childrenWithOrder.sort((a, b) => a.order - b.order);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const children = childrenWithOrder.map(({ id, label, type, meta }) => ({
|
|
94
|
+
id,
|
|
95
|
+
label,
|
|
96
|
+
type,
|
|
97
|
+
meta,
|
|
98
|
+
}));
|
|
99
|
+
|
|
100
|
+
return { label, type, meta, title, markdown, children };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Write markdown content to a document.
|
|
105
|
+
* Supports optional YAML frontmatter for title and metadata.
|
|
106
|
+
*
|
|
107
|
+
* @param mode - "replace" clears existing content first (default),
|
|
108
|
+
* "append" adds to the end.
|
|
109
|
+
*/
|
|
110
|
+
async write(
|
|
111
|
+
docId: string,
|
|
112
|
+
markdown: string,
|
|
113
|
+
mode: "replace" | "append" = "replace",
|
|
114
|
+
): Promise<void> {
|
|
115
|
+
const provider = await this.dm.getChildProvider(docId);
|
|
116
|
+
const doc = provider.document;
|
|
117
|
+
const fragment = doc.getXmlFragment("default");
|
|
118
|
+
|
|
119
|
+
// Parse optional frontmatter
|
|
120
|
+
const { title, meta, body } = parseFrontmatter(markdown);
|
|
121
|
+
|
|
122
|
+
// Update tree metadata if frontmatter provided title or meta
|
|
123
|
+
if (title || Object.keys(meta).length > 0) {
|
|
124
|
+
const treeMap = this.dm.getTreeMap();
|
|
125
|
+
const rootDoc = this.dm.rootDocument;
|
|
126
|
+
if (treeMap && rootDoc) {
|
|
127
|
+
const entry = treeMap.get(docId);
|
|
128
|
+
if (entry) {
|
|
129
|
+
rootDoc.transact(() => {
|
|
130
|
+
const updates: Record<string, unknown> = {
|
|
131
|
+
...entry,
|
|
132
|
+
updatedAt: Date.now(),
|
|
133
|
+
};
|
|
134
|
+
if (title) updates.label = title;
|
|
135
|
+
if (Object.keys(meta).length > 0) {
|
|
136
|
+
updates.meta = {
|
|
137
|
+
...(entry.meta ?? {}),
|
|
138
|
+
...meta,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
treeMap.set(docId, updates);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (mode === "replace") {
|
|
148
|
+
// Clear existing content
|
|
149
|
+
doc.transact(() => {
|
|
150
|
+
while (fragment.length > 0) {
|
|
151
|
+
fragment.delete(0);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Write new content
|
|
157
|
+
const contentToWrite = body || markdown;
|
|
158
|
+
const fallbackTitle = title || "Untitled";
|
|
159
|
+
populateYDocFromMarkdown(fragment, contentToWrite, fallbackTitle);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private async _appendElements(docId: string, els: Y.XmlElement[]): Promise<void> {
|
|
163
|
+
const provider = await this.dm.getChildProvider(docId);
|
|
164
|
+
const doc = provider.document;
|
|
165
|
+
const fragment = doc.getXmlFragment("default");
|
|
166
|
+
doc.transact(() => {
|
|
167
|
+
fragment.insert(fragment.length, els);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async appendHeading(docId: string, text: string, opts?: { level?: 1|2|3|4|5|6 }): Promise<void> {
|
|
172
|
+
return this._appendElements(docId, [buildHeadingElement(text, opts?.level)]);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async appendParagraph(docId: string, text: string): Promise<void> {
|
|
176
|
+
return this._appendElements(docId, [buildParagraphElement(text)]);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async appendBulletList(docId: string, items: string[]): Promise<void> {
|
|
180
|
+
return this._appendElements(docId, [buildBulletListElement(items)]);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async appendOrderedList(docId: string, items: string[]): Promise<void> {
|
|
184
|
+
return this._appendElements(docId, [buildOrderedListElement(items)]);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async appendTaskList(docId: string, items: Array<{ text: string; checked?: boolean }>): Promise<void> {
|
|
188
|
+
return this._appendElements(docId, [buildTaskListElement(items)]);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async appendCodeBlock(docId: string, code: string, opts?: { language?: string }): Promise<void> {
|
|
192
|
+
return this._appendElements(docId, [buildCodeBlockElement(code, opts?.language)]);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async appendBlockquote(docId: string, text: string): Promise<void> {
|
|
196
|
+
return this._appendElements(docId, [buildBlockquoteElement(text)]);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async appendHorizontalRule(docId: string): Promise<void> {
|
|
200
|
+
return this._appendElements(docId, [buildHorizontalRuleElement()]);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async appendMarkdown(docId: string, markdown: string): Promise<void> {
|
|
204
|
+
return this._appendElements(docId, buildBlocksFromMarkdown(markdown));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async getBlocks(docId: string): Promise<DocumentBlock[]> {
|
|
208
|
+
const provider = await this.dm.getChildProvider(docId);
|
|
209
|
+
return readBlocksFromFragment(provider.document.getXmlFragment("default"));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Get the raw Y.XmlFragment for a document (the 'default' fragment
|
|
214
|
+
* that TipTap uses for document content).
|
|
215
|
+
*/
|
|
216
|
+
async getFragment(docId: string): Promise<Y.XmlFragment> {
|
|
217
|
+
const provider = await this.dm.getChildProvider(docId);
|
|
218
|
+
return provider.document.getXmlFragment("default");
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Get the raw Y.Doc for a document (synced).
|
|
223
|
+
*/
|
|
224
|
+
async getDoc(docId: string): Promise<Y.Doc> {
|
|
225
|
+
const provider = await this.dm.getChildProvider(docId);
|
|
226
|
+
return provider.document;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
@@ -357,7 +357,7 @@ export class CryptoIdentityKeystore {
|
|
|
357
357
|
} else {
|
|
358
358
|
const all = await dbGetAll(db);
|
|
359
359
|
if (all.length > 0) {
|
|
360
|
-
await dbPut(db, all[0].key, { ...all[0].value, username });
|
|
360
|
+
await dbPut(db, String(all[0].key), { ...all[0].value, username });
|
|
361
361
|
}
|
|
362
362
|
}
|
|
363
363
|
} finally {
|
|
@@ -557,7 +557,7 @@ export class CryptoIdentityKeystore {
|
|
|
557
557
|
challenge: crypto.getRandomValues(new Uint8Array(32)),
|
|
558
558
|
rp: { id: rpId, name: rpName },
|
|
559
559
|
user: {
|
|
560
|
-
id: fromBase64url(kp.publicKeyB64),
|
|
560
|
+
id: fromBase64url(kp.publicKeyB64) as BufferSource,
|
|
561
561
|
name: kp.publicKeyB64.slice(0, 16),
|
|
562
562
|
displayName: kp.publicKeyB64.slice(0, 16),
|
|
563
563
|
},
|
|
@@ -663,7 +663,7 @@ export class CryptoIdentityKeystore {
|
|
|
663
663
|
|
|
664
664
|
if (credentialIdHint) {
|
|
665
665
|
allowCredentials.push({
|
|
666
|
-
id: fromBase64url(credentialIdHint),
|
|
666
|
+
id: fromBase64url(credentialIdHint) as BufferSource,
|
|
667
667
|
type: "public-key",
|
|
668
668
|
});
|
|
669
669
|
} else {
|