@mp-lb/mdkit 0.2.3 → 0.2.4
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/collaboration/useMdKitCollaboration.d.ts +1 -1
- package/dist/collaboration/useMdKitCollaboration.js +304 -18
- package/dist/core/checkpointPolicy.d.ts +25 -0
- package/dist/core/checkpointPolicy.js +45 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +1 -0
- package/dist/document/MdKitConflictPanel.d.ts +1 -1
- package/dist/document/MdKitConflictPanel.js +1 -1
- package/dist/document/MdKitDocumentToolbar.d.ts +3 -3
- package/dist/document/MdKitDocumentToolbar.js +6 -8
- package/dist/document/documentTypes.d.ts +7 -0
- package/dist/document/useMdKitDocument.d.ts +1 -1
- package/dist/fastify.d.ts +3 -3
- package/dist/fastify.js +1 -1
- package/dist/index.d.ts +29 -25
- package/dist/index.js +14 -12
- package/dist/markdown/MarkdownBubbleMenu.js +1 -1
- package/dist/markdown/MdKitEditor.d.ts +2 -2
- package/dist/markdown/MdKitEditor.js +2 -2
- package/dist/markdown/MdKitView.js +1 -1
- package/dist/markdown/TiptapMarkdownSurface.d.ts +2 -2
- package/dist/markdown/TiptapMarkdownSurface.js +17 -26
- package/dist/markdown/createMdKitTiptapExtensions.d.ts +8 -0
- package/dist/markdown/createMdKitTiptapExtensions.js +27 -0
- package/dist/markdown/prepareMarkdownForEditorHydration.js +1 -1
- package/dist/markdown/preserveMarkdownWhitespace.js +1 -1
- package/dist/server.d.ts +2 -0
- package/dist/server.js +1 -0
- package/dist/theme/MdKitThemeEditor.d.ts +1 -1
- package/dist/transport/backend.d.ts +29 -0
- package/dist/transport/backend.js +93 -0
- package/dist/transport/fastify.d.ts +1 -1
- package/dist/transport/fastify.js +1 -1
- package/dist/transport/http.d.ts +1 -1
- package/dist/transport/index.d.ts +5 -5
- package/dist/transport/index.js +2 -2
- package/dist/transport/rest.d.ts +1 -1
- package/dist/transport/store.d.ts +1 -1
- package/dist/transport/trpcClient.d.ts +29 -8
- package/dist/transport/trpcServer.d.ts +1 -1
- package/dist/trpc/client.d.ts +3 -3
- package/dist/trpc/client.js +1 -1
- package/dist/trpc/server.d.ts +5 -3
- package/dist/trpc/server.js +2 -1
- package/dist/trpc.d.ts +3 -3
- package/dist/trpc.js +1 -1
- package/dist/versioning/VersionHistoryPanel.d.ts +2 -2
- package/dist/versioning/VersionHistoryPanel.js +1 -1
- package/dist/versioning/useMdKitDocumentVersions.d.ts +2 -1
- package/dist/versioning/useMdKitDocumentVersions.js +6 -6
- package/dist/yjs/MdKitMarkdownYjs.d.ts +12 -0
- package/dist/yjs/MdKitMarkdownYjs.js +40 -0
- package/dist/yjs/index.d.ts +2 -0
- package/dist/yjs/index.js +1 -0
- package/docs/.vitepress/cache/deps/@theme_index.js +275 -0
- package/docs/.vitepress/cache/deps/@theme_index.js.map +7 -0
- package/docs/.vitepress/cache/deps/_metadata.json +40 -0
- package/docs/.vitepress/cache/deps/chunk-PM3I3KHC.js +9719 -0
- package/docs/.vitepress/cache/deps/chunk-PM3I3KHC.js.map +7 -0
- package/docs/.vitepress/cache/deps/chunk-VSHFF4ZG.js +13018 -0
- package/docs/.vitepress/cache/deps/chunk-VSHFF4ZG.js.map +7 -0
- package/docs/.vitepress/cache/deps/package.json +3 -0
- package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +4505 -0
- package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +7 -0
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +583 -0
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +7 -0
- package/docs/.vitepress/cache/deps/vue.js +347 -0
- package/docs/.vitepress/cache/deps/vue.js.map +7 -0
- package/docs/.vitepress/config.ts +11 -0
- package/docs/api.md +153 -18
- package/docs/architecture.md +24 -9
- package/docs/collaboration-persistence.md +147 -0
- package/docs/index.md +139 -65
- package/docs/permissions.md +139 -0
- package/docs/rest.md +98 -0
- package/docs/shadcn.md +6 -3
- package/docs/styling.md +11 -10
- package/docs/use-cases.md +148 -0
- package/package.json +15 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MdKitCollaborationParticipant, MdKitCollaborationSession } from "../document/documentTypes";
|
|
1
|
+
import type { MdKitCollaborationParticipant, MdKitCollaborationSession } from "../document/documentTypes.js";
|
|
2
2
|
export type UseMdKitCollaborationOptions = {
|
|
3
3
|
collaborator: MdKitCollaborationParticipant;
|
|
4
4
|
documentId: string | null;
|
|
@@ -1,6 +1,50 @@
|
|
|
1
|
-
import { useEffect, useMemo, useState } from "react";
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
2
|
import { HocuspocusProvider } from "@hocuspocus/provider";
|
|
3
3
|
import * as Y from "yjs";
|
|
4
|
+
const isCollaborationDebugEnabled = () => {
|
|
5
|
+
if (globalThis.__MDKIT_COLLAB_DEBUG__ ===
|
|
6
|
+
true) {
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
if (typeof window === "undefined") {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
return window.localStorage.getItem("mdkit:collab-debug") === "true";
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
const stringifyDebugDetails = (details) => {
|
|
20
|
+
const seen = new WeakSet();
|
|
21
|
+
try {
|
|
22
|
+
return JSON.stringify(details, (_key, value) => {
|
|
23
|
+
if (typeof value === "bigint") {
|
|
24
|
+
return value.toString();
|
|
25
|
+
}
|
|
26
|
+
if (!value || typeof value !== "object") {
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
if (seen.has(value)) {
|
|
30
|
+
return "[Circular]";
|
|
31
|
+
}
|
|
32
|
+
seen.add(value);
|
|
33
|
+
return value;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
return JSON.stringify({
|
|
38
|
+
serializationError: error instanceof Error ? error.message : "Unable to serialize details",
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
const debugCollaboration = (event, details) => {
|
|
43
|
+
if (!isCollaborationDebugEnabled()) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
console.info(`MDKIT_COLLAB_DEBUG ${event} ${stringifyDebugDetails(details)}`);
|
|
47
|
+
};
|
|
4
48
|
const createColorFromId = (id) => {
|
|
5
49
|
let hash = 0;
|
|
6
50
|
for (let index = 0; index < id.length; index += 1) {
|
|
@@ -9,13 +53,84 @@ const createColorFromId = (id) => {
|
|
|
9
53
|
}
|
|
10
54
|
return `hsl(${Math.abs(hash) % 360}, 85%, 55%)`;
|
|
11
55
|
};
|
|
56
|
+
const parsePresence = (clientId, state, localClientId) => {
|
|
57
|
+
if (!state || typeof state !== "object") {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const user = "user" in state ? state.user : state;
|
|
61
|
+
if (!user || typeof user !== "object") {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const { color, id, imageUrl, name } = user;
|
|
65
|
+
if (typeof name !== "string") {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
clientId,
|
|
70
|
+
color: typeof color === "string"
|
|
71
|
+
? color
|
|
72
|
+
: createColorFromId(typeof id === "string" ? id : String(clientId)),
|
|
73
|
+
id: typeof id === "string" ? id : String(clientId),
|
|
74
|
+
imageUrl: typeof imageUrl === "string" ? imageUrl : undefined,
|
|
75
|
+
isLocal: clientId === localClientId,
|
|
76
|
+
name,
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
const summarizeAwarenessState = (clientId, state) => {
|
|
80
|
+
if (!state || typeof state !== "object") {
|
|
81
|
+
return {
|
|
82
|
+
clientId,
|
|
83
|
+
hasState: false,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const user = "user" in state ? state.user : state;
|
|
87
|
+
return {
|
|
88
|
+
clientId,
|
|
89
|
+
hasCursor: "cursor" in state,
|
|
90
|
+
hasState: true,
|
|
91
|
+
hasUser: !!user,
|
|
92
|
+
user,
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
const summarizeParticipants = (participants) => participants.map((participant) => ({
|
|
96
|
+
clientId: participant.clientId,
|
|
97
|
+
id: participant.id,
|
|
98
|
+
isLocal: participant.isLocal,
|
|
99
|
+
name: participant.name,
|
|
100
|
+
}));
|
|
101
|
+
const areParticipantsEqual = (left, right) => left.length === right.length &&
|
|
102
|
+
left.every((leftParticipant, index) => {
|
|
103
|
+
const rightParticipant = right[index];
|
|
104
|
+
return (rightParticipant &&
|
|
105
|
+
leftParticipant.clientId === rightParticipant.clientId &&
|
|
106
|
+
leftParticipant.color === rightParticipant.color &&
|
|
107
|
+
leftParticipant.id === rightParticipant.id &&
|
|
108
|
+
leftParticipant.imageUrl === rightParticipant.imageUrl &&
|
|
109
|
+
leftParticipant.isLocal === rightParticipant.isLocal &&
|
|
110
|
+
leftParticipant.name === rightParticipant.name);
|
|
111
|
+
});
|
|
112
|
+
const setProviderUser = (provider, collaborator) => {
|
|
113
|
+
provider.setAwarenessField("user", {
|
|
114
|
+
color: collaborator.color,
|
|
115
|
+
id: collaborator.id,
|
|
116
|
+
imageUrl: collaborator.imageUrl || undefined,
|
|
117
|
+
name: collaborator.name,
|
|
118
|
+
});
|
|
119
|
+
};
|
|
12
120
|
export const useMdKitCollaboration = (options) => {
|
|
13
121
|
const { collaborator, documentId, enabled = true, endpoint, getToken, resolveRoomName, } = options;
|
|
14
122
|
const [status, setStatus] = useState("disconnected");
|
|
123
|
+
const [participants, setParticipants] = useState([]);
|
|
124
|
+
const [provider, setProvider] = useState(null);
|
|
15
125
|
const normalizedCollaborator = useMemo(() => ({
|
|
16
126
|
...collaborator,
|
|
17
127
|
color: collaborator.color || createColorFromId(collaborator.id),
|
|
18
|
-
}), [
|
|
128
|
+
}), [
|
|
129
|
+
collaborator.color,
|
|
130
|
+
collaborator.id,
|
|
131
|
+
collaborator.imageUrl,
|
|
132
|
+
collaborator.name,
|
|
133
|
+
]);
|
|
19
134
|
const roomName = useMemo(() => {
|
|
20
135
|
if (!documentId) {
|
|
21
136
|
return "";
|
|
@@ -26,53 +141,223 @@ export const useMdKitCollaboration = (options) => {
|
|
|
26
141
|
void roomName;
|
|
27
142
|
return new Y.Doc();
|
|
28
143
|
}, [roomName]);
|
|
29
|
-
const
|
|
144
|
+
const setNextParticipants = useCallback((nextParticipants, source) => {
|
|
145
|
+
setParticipants((currentParticipants) => areParticipantsEqual(currentParticipants, nextParticipants)
|
|
146
|
+
? currentParticipants
|
|
147
|
+
: (() => {
|
|
148
|
+
debugCollaboration("participants_update", {
|
|
149
|
+
nextParticipants: summarizeParticipants(nextParticipants),
|
|
150
|
+
previousParticipants: summarizeParticipants(currentParticipants),
|
|
151
|
+
roomName,
|
|
152
|
+
source,
|
|
153
|
+
});
|
|
154
|
+
return nextParticipants;
|
|
155
|
+
})());
|
|
156
|
+
}, [roomName]);
|
|
157
|
+
const updateParticipantsFromProvider = useCallback((nextProvider, source) => {
|
|
158
|
+
const awareness = nextProvider?.awareness;
|
|
159
|
+
if (!awareness) {
|
|
160
|
+
debugCollaboration("awareness_missing", {
|
|
161
|
+
roomName,
|
|
162
|
+
source,
|
|
163
|
+
});
|
|
164
|
+
setNextParticipants([], source);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const rawStates = Array.from(awareness.getStates());
|
|
168
|
+
const nextParticipants = rawStates
|
|
169
|
+
.map(([clientId, state]) => parsePresence(clientId, state, ydoc.clientID))
|
|
170
|
+
.filter((presence) => Boolean(presence));
|
|
171
|
+
debugCollaboration("awareness_map_read", {
|
|
172
|
+
localClientId: ydoc.clientID,
|
|
173
|
+
parsedParticipants: summarizeParticipants(nextParticipants),
|
|
174
|
+
rawStates: rawStates.map(([clientId, state]) => summarizeAwarenessState(clientId, state)),
|
|
175
|
+
roomName,
|
|
176
|
+
source,
|
|
177
|
+
});
|
|
178
|
+
setNextParticipants(nextParticipants, source);
|
|
179
|
+
}, [roomName, setNextParticipants, ydoc.clientID]);
|
|
180
|
+
useEffect(() => {
|
|
30
181
|
if (!enabled || !documentId || !endpoint) {
|
|
31
|
-
|
|
182
|
+
debugCollaboration("provider_skipped", {
|
|
183
|
+
documentId,
|
|
184
|
+
enabled,
|
|
185
|
+
endpoint,
|
|
186
|
+
roomName,
|
|
187
|
+
});
|
|
188
|
+
setProvider(null);
|
|
189
|
+
setStatus("disconnected");
|
|
190
|
+
setNextParticipants([], "provider_skipped");
|
|
191
|
+
return;
|
|
32
192
|
}
|
|
33
|
-
|
|
193
|
+
debugCollaboration("provider_create", {
|
|
194
|
+
collaborator: normalizedCollaborator,
|
|
195
|
+
documentId,
|
|
196
|
+
endpoint,
|
|
197
|
+
localClientId: ydoc.clientID,
|
|
198
|
+
roomName,
|
|
199
|
+
});
|
|
200
|
+
let nextProvider = null;
|
|
201
|
+
nextProvider = new HocuspocusProvider({
|
|
34
202
|
document: ydoc,
|
|
35
203
|
name: roomName,
|
|
36
|
-
onConnect: () =>
|
|
37
|
-
|
|
204
|
+
onConnect: () => {
|
|
205
|
+
if (nextProvider) {
|
|
206
|
+
setProviderUser(nextProvider, normalizedCollaborator);
|
|
207
|
+
}
|
|
208
|
+
debugCollaboration("provider_connect", {
|
|
209
|
+
localClientId: ydoc.clientID,
|
|
210
|
+
roomName,
|
|
211
|
+
});
|
|
212
|
+
setStatus("connected");
|
|
213
|
+
globalThis.setTimeout(() => {
|
|
214
|
+
updateParticipantsFromProvider(nextProvider, "provider_connect");
|
|
215
|
+
}, 0);
|
|
216
|
+
},
|
|
217
|
+
onDisconnect: () => {
|
|
218
|
+
debugCollaboration("provider_disconnect", {
|
|
219
|
+
localClientId: ydoc.clientID,
|
|
220
|
+
roomName,
|
|
221
|
+
});
|
|
222
|
+
setStatus("disconnected");
|
|
223
|
+
},
|
|
38
224
|
onStatus: ({ status: nextStatus }) => {
|
|
225
|
+
debugCollaboration("provider_status", {
|
|
226
|
+
localClientId: ydoc.clientID,
|
|
227
|
+
roomName,
|
|
228
|
+
status: nextStatus,
|
|
229
|
+
});
|
|
39
230
|
if (nextStatus === "connecting") {
|
|
40
231
|
setStatus("connecting");
|
|
41
232
|
}
|
|
42
233
|
},
|
|
234
|
+
onAwarenessChange: ({ states }) => {
|
|
235
|
+
debugCollaboration("provider_awareness_change", {
|
|
236
|
+
localClientId: ydoc.clientID,
|
|
237
|
+
rawStates: states,
|
|
238
|
+
roomName,
|
|
239
|
+
});
|
|
240
|
+
updateParticipantsFromProvider(nextProvider, "provider_awareness_change");
|
|
241
|
+
},
|
|
43
242
|
token: async () => {
|
|
44
243
|
const token = getToken ? await getToken() : null;
|
|
45
|
-
|
|
244
|
+
debugCollaboration("provider_token", {
|
|
245
|
+
hasToken: !!token,
|
|
246
|
+
roomName,
|
|
247
|
+
source: getToken ? "custom" : "default",
|
|
248
|
+
});
|
|
249
|
+
return token || "notoken";
|
|
46
250
|
},
|
|
47
251
|
url: endpoint,
|
|
48
252
|
});
|
|
49
|
-
|
|
253
|
+
setProvider(nextProvider);
|
|
254
|
+
setStatus("connecting");
|
|
255
|
+
return () => {
|
|
256
|
+
nextProvider?.destroy();
|
|
257
|
+
nextProvider = null;
|
|
258
|
+
setProvider(null);
|
|
259
|
+
setStatus("disconnected");
|
|
260
|
+
setNextParticipants([], "provider_destroy");
|
|
261
|
+
};
|
|
262
|
+
}, [
|
|
263
|
+
documentId,
|
|
264
|
+
enabled,
|
|
265
|
+
endpoint,
|
|
266
|
+
getToken,
|
|
267
|
+
normalizedCollaborator,
|
|
268
|
+
roomName,
|
|
269
|
+
setNextParticipants,
|
|
270
|
+
updateParticipantsFromProvider,
|
|
271
|
+
ydoc,
|
|
272
|
+
]);
|
|
273
|
+
useEffect(() => {
|
|
274
|
+
debugCollaboration("session_options", {
|
|
275
|
+
collaborator: normalizedCollaborator,
|
|
276
|
+
documentId,
|
|
277
|
+
enabled,
|
|
278
|
+
endpoint,
|
|
279
|
+
localClientId: ydoc.clientID,
|
|
280
|
+
roomName,
|
|
281
|
+
});
|
|
282
|
+
}, [
|
|
283
|
+
documentId,
|
|
284
|
+
enabled,
|
|
285
|
+
endpoint,
|
|
286
|
+
normalizedCollaborator,
|
|
287
|
+
roomName,
|
|
288
|
+
ydoc.clientID,
|
|
289
|
+
]);
|
|
290
|
+
useEffect(() => {
|
|
291
|
+
debugCollaboration("session_state", {
|
|
292
|
+
isCollaborating: participants.some((participant) => !participant.isLocal),
|
|
293
|
+
participants: summarizeParticipants(participants),
|
|
294
|
+
roomName,
|
|
295
|
+
status: provider ? status : "disconnected",
|
|
296
|
+
});
|
|
297
|
+
}, [participants, provider, roomName, status]);
|
|
50
298
|
useEffect(() => {
|
|
51
299
|
if (!provider) {
|
|
52
300
|
return;
|
|
53
301
|
}
|
|
302
|
+
debugCollaboration("awareness_set_user", {
|
|
303
|
+
localClientId: ydoc.clientID,
|
|
304
|
+
roomName,
|
|
305
|
+
user: {
|
|
306
|
+
color: normalizedCollaborator.color,
|
|
307
|
+
id: normalizedCollaborator.id,
|
|
308
|
+
imageUrl: normalizedCollaborator.imageUrl || undefined,
|
|
309
|
+
name: normalizedCollaborator.name,
|
|
310
|
+
},
|
|
311
|
+
});
|
|
312
|
+
setProviderUser(provider, normalizedCollaborator);
|
|
313
|
+
updateParticipantsFromProvider(provider, "awareness_set_user");
|
|
314
|
+
}, [
|
|
315
|
+
normalizedCollaborator,
|
|
316
|
+
provider,
|
|
317
|
+
roomName,
|
|
318
|
+
updateParticipantsFromProvider,
|
|
319
|
+
ydoc.clientID,
|
|
320
|
+
]);
|
|
321
|
+
useEffect(() => {
|
|
322
|
+
const awareness = provider?.awareness;
|
|
323
|
+
if (!awareness) {
|
|
324
|
+
debugCollaboration("awareness_effect_missing", {
|
|
325
|
+
roomName,
|
|
326
|
+
});
|
|
327
|
+
setNextParticipants([], "awareness_effect_missing");
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
const handleAwarenessChange = () => updateParticipantsFromProvider(provider, "awareness_change_event");
|
|
331
|
+
awareness.on("change", handleAwarenessChange);
|
|
332
|
+
updateParticipantsFromProvider(provider, "awareness_change_subscribe");
|
|
54
333
|
return () => {
|
|
55
|
-
|
|
334
|
+
awareness.off("change", handleAwarenessChange);
|
|
56
335
|
};
|
|
57
|
-
}, [provider]);
|
|
336
|
+
}, [provider, roomName, setNextParticipants, updateParticipantsFromProvider]);
|
|
58
337
|
useEffect(() => {
|
|
59
338
|
if (!provider) {
|
|
60
339
|
return;
|
|
61
340
|
}
|
|
62
|
-
provider
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
341
|
+
const handleProviderAwareness = () => updateParticipantsFromProvider(provider, "provider_awareness_event");
|
|
342
|
+
provider.on("awarenessChange", handleProviderAwareness);
|
|
343
|
+
provider.on("awarenessUpdate", handleProviderAwareness);
|
|
344
|
+
updateParticipantsFromProvider(provider, "provider_awareness_subscribe");
|
|
345
|
+
return () => {
|
|
346
|
+
provider.off("awarenessChange", handleProviderAwareness);
|
|
347
|
+
provider.off("awarenessUpdate", handleProviderAwareness);
|
|
348
|
+
};
|
|
349
|
+
}, [provider, updateParticipantsFromProvider]);
|
|
69
350
|
return useMemo(() => {
|
|
70
351
|
if (!enabled || !documentId || !endpoint) {
|
|
71
352
|
return null;
|
|
72
353
|
}
|
|
354
|
+
const otherParticipants = participants.filter((participant) => !participant.isLocal);
|
|
73
355
|
return {
|
|
74
356
|
collaborator: normalizedCollaborator,
|
|
75
357
|
document: ydoc,
|
|
358
|
+
isCollaborating: otherParticipants.length > 0,
|
|
359
|
+
otherParticipants,
|
|
360
|
+
participants,
|
|
76
361
|
provider,
|
|
77
362
|
roomName,
|
|
78
363
|
status: provider ? status : "disconnected",
|
|
@@ -82,6 +367,7 @@ export const useMdKitCollaboration = (options) => {
|
|
|
82
367
|
enabled,
|
|
83
368
|
endpoint,
|
|
84
369
|
normalizedCollaborator,
|
|
370
|
+
participants,
|
|
85
371
|
provider,
|
|
86
372
|
roomName,
|
|
87
373
|
status,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { MdKitDocumentVersionDetail, MdKitDocumentWriteInput, MdKitDocumentWriteResult } from "../document/documentTypes.js";
|
|
2
|
+
export type MdKitCheckpointPolicyInput = {
|
|
3
|
+
currentContent: string;
|
|
4
|
+
documentId: string;
|
|
5
|
+
editDistance: number;
|
|
6
|
+
previousCheckpoint: MdKitDocumentVersionDetail | null;
|
|
7
|
+
previousCheckpointContent: string | null;
|
|
8
|
+
timeSinceLastCheckpointMs: number | null;
|
|
9
|
+
writeInput: MdKitDocumentWriteInput;
|
|
10
|
+
writeResult: MdKitDocumentWriteResult;
|
|
11
|
+
};
|
|
12
|
+
export type MdKitCheckpointPolicy = {
|
|
13
|
+
shouldCheckpoint(input: MdKitCheckpointPolicyInput): boolean | Promise<boolean>;
|
|
14
|
+
};
|
|
15
|
+
export type MdKitSmartCheckpointPolicyOptions = {
|
|
16
|
+
minEditDistance?: number;
|
|
17
|
+
minIntervalMs?: number;
|
|
18
|
+
};
|
|
19
|
+
export declare const measureMdKitEditDistance: (left: string, right: string) => number;
|
|
20
|
+
export declare const CheckpointPolicy: {
|
|
21
|
+
readonly always: () => MdKitCheckpointPolicy;
|
|
22
|
+
readonly function: (shouldCheckpoint: MdKitCheckpointPolicy["shouldCheckpoint"]) => MdKitCheckpointPolicy;
|
|
23
|
+
readonly never: () => MdKitCheckpointPolicy;
|
|
24
|
+
readonly smart: (options?: MdKitSmartCheckpointPolicyOptions) => MdKitCheckpointPolicy;
|
|
25
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const defaultMinEditDistance = 250;
|
|
2
|
+
const defaultMinIntervalMs = 5 * 60_000;
|
|
3
|
+
export const measureMdKitEditDistance = (left, right) => {
|
|
4
|
+
if (left === right) {
|
|
5
|
+
return 0;
|
|
6
|
+
}
|
|
7
|
+
if (left.length === 0) {
|
|
8
|
+
return right.length;
|
|
9
|
+
}
|
|
10
|
+
if (right.length === 0) {
|
|
11
|
+
return left.length;
|
|
12
|
+
}
|
|
13
|
+
let previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
14
|
+
let current = new Array(right.length + 1);
|
|
15
|
+
for (let leftIndex = 1; leftIndex <= left.length; leftIndex += 1) {
|
|
16
|
+
current[0] = leftIndex;
|
|
17
|
+
for (let rightIndex = 1; rightIndex <= right.length; rightIndex += 1) {
|
|
18
|
+
const cost = left[leftIndex - 1] === right[rightIndex - 1] ? 0 : 1;
|
|
19
|
+
current[rightIndex] = Math.min(current[rightIndex - 1] + 1, previous[rightIndex] + 1, previous[rightIndex - 1] + cost);
|
|
20
|
+
}
|
|
21
|
+
[previous, current] = [current, previous];
|
|
22
|
+
}
|
|
23
|
+
return previous[right.length] ?? 0;
|
|
24
|
+
};
|
|
25
|
+
export const CheckpointPolicy = {
|
|
26
|
+
always: () => ({
|
|
27
|
+
shouldCheckpoint: () => true,
|
|
28
|
+
}),
|
|
29
|
+
function: (shouldCheckpoint) => ({
|
|
30
|
+
shouldCheckpoint,
|
|
31
|
+
}),
|
|
32
|
+
never: () => ({
|
|
33
|
+
shouldCheckpoint: () => false,
|
|
34
|
+
}),
|
|
35
|
+
smart: (options = {}) => {
|
|
36
|
+
const minEditDistance = options.minEditDistance ?? defaultMinEditDistance;
|
|
37
|
+
const minIntervalMs = options.minIntervalMs ?? defaultMinIntervalMs;
|
|
38
|
+
return {
|
|
39
|
+
shouldCheckpoint: ({ editDistance, previousCheckpoint, timeSinceLastCheckpointMs, }) => !previousCheckpoint ||
|
|
40
|
+
editDistance >= minEditDistance ||
|
|
41
|
+
(timeSinceLastCheckpointMs !== null &&
|
|
42
|
+
timeSinceLastCheckpointMs >= minIntervalMs),
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
};
|
package/dist/core/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { createMdKitDocumentRecord, detectMdKitDocumentConflict, normalizeMdKitVersionToken, restoreMdKitDocumentVersion, writeMdKitDocumentRecord, } from "./documentEngine.js";
|
|
2
|
+
export { CheckpointPolicy, measureMdKitEditDistance, } from "./checkpointPolicy.js";
|
|
2
3
|
export type { CreateMdKitDocumentRecordInput, MdKitDocumentRecord, RestoreMdKitDocumentVersionInput, RestoreMdKitDocumentVersionResult, WriteMdKitDocumentRecordInput, WriteMdKitDocumentRecordResult, } from "./documentEngine.js";
|
|
4
|
+
export type { MdKitCheckpointPolicy, MdKitCheckpointPolicyInput, MdKitSmartCheckpointPolicyOptions, } from "./checkpointPolicy.js";
|
|
3
5
|
export type { MdKitDocumentSnapshot, MdKitDocumentVersionDetail, MdKitDocumentVersionSummary, MdKitDocumentVersionToken, MdKitDocumentWriteInput, MdKitDocumentWriteResult, } from "../document/documentTypes.js";
|
package/dist/core/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from "react";
|
|
3
|
-
import { joinClassNames } from "../ui/joinClassNames";
|
|
3
|
+
import { joinClassNames } from "../ui/joinClassNames.js";
|
|
4
4
|
export const MdKitConflictPanel = ({ className, document, title = "Document conflict", }) => {
|
|
5
5
|
const [pendingAction, setPendingAction] = useState(null);
|
|
6
6
|
const [activePreview, setActivePreview] = useState("remote");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { MdKitCollaborationSession } from "./documentTypes";
|
|
2
|
-
import type { MdKitDocumentController } from "./useMdKitDocument";
|
|
3
|
-
import type { MdKitDocumentVersionsController } from "../versioning/useMdKitDocumentVersions";
|
|
1
|
+
import type { MdKitCollaborationSession } from "./documentTypes.js";
|
|
2
|
+
import type { MdKitDocumentController } from "./useMdKitDocument.js";
|
|
3
|
+
import type { MdKitDocumentVersionsController } from "../versioning/useMdKitDocumentVersions.js";
|
|
4
4
|
export type MdKitDocumentToolbarProps = {
|
|
5
5
|
className?: string;
|
|
6
6
|
collaboration?: MdKitCollaborationSession | null;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from "react";
|
|
3
|
-
import { joinClassNames } from "../ui/joinClassNames";
|
|
3
|
+
import { joinClassNames } from "../ui/joinClassNames.js";
|
|
4
4
|
const formatUpdatedAt = (updatedAt) => {
|
|
5
5
|
if (!updatedAt) {
|
|
6
6
|
return "Never saved";
|
|
@@ -9,6 +9,7 @@ const formatUpdatedAt = (updatedAt) => {
|
|
|
9
9
|
};
|
|
10
10
|
export const MdKitDocumentToolbar = ({ className, collaboration, document, onOpenConflict, onOpenVersionHistory, showConflictActions = false, versions, }) => {
|
|
11
11
|
const [pendingAction, setPendingAction] = useState(null);
|
|
12
|
+
const otherCollaboratorCount = collaboration?.otherParticipants.length ?? 0;
|
|
12
13
|
const runAction = async (name, action) => {
|
|
13
14
|
setPendingAction(name);
|
|
14
15
|
try {
|
|
@@ -19,6 +20,7 @@ export const MdKitDocumentToolbar = ({ className, collaboration, document, onOpe
|
|
|
19
20
|
}
|
|
20
21
|
};
|
|
21
22
|
const hasVersionHistory = versions?.hasVersioning ?? false;
|
|
23
|
+
const hasActiveCollaborators = collaboration?.isCollaborating ?? false;
|
|
22
24
|
const isBusy = pendingAction !== null || document.saveStatus === "saving";
|
|
23
25
|
const status = document.conflict
|
|
24
26
|
? "Conflict"
|
|
@@ -33,16 +35,12 @@ export const MdKitDocumentToolbar = ({ className, collaboration, document, onOpe
|
|
|
33
35
|
: document.saveStatus === "saved"
|
|
34
36
|
? "Saved"
|
|
35
37
|
: "Idle";
|
|
36
|
-
return (_jsxs("div", { className: joinClassNames("mp-lb-mdkit-document-toolbar", className), "data-conflict": document.conflict ? "true" : undefined, "data-dirty": document.isDirty ? "true" : undefined, "data-save-status": document.saveStatus, "data-status": status.toLowerCase().replace(/\s+/g, "-"), children: [_jsxs("div", { className: "mp-lb-mdkit-document-toolbar-status", children: [_jsx("strong", { children: status }), _jsx("span", { children: formatUpdatedAt(document.updatedAt) }), _jsxs("span", { children: [
|
|
37
|
-
document.conflict ||
|
|
38
|
-
!hasVersionHistory ||
|
|
39
|
-
versions?.isLoading ||
|
|
40
|
-
!onOpenVersionHistory, onClick: () => void runAction("versions", async () => {
|
|
38
|
+
return (_jsxs("div", { className: joinClassNames("mp-lb-mdkit-document-toolbar", className), "data-conflict": document.conflict ? "true" : undefined, "data-dirty": document.isDirty ? "true" : undefined, "data-save-status": document.saveStatus, "data-status": status.toLowerCase().replace(/\s+/g, "-"), children: [_jsxs("div", { className: "mp-lb-mdkit-document-toolbar-status", children: [_jsx("strong", { children: status }), _jsx("span", { children: formatUpdatedAt(document.updatedAt) }), hasActiveCollaborators ? (_jsxs("span", { children: [otherCollaboratorCount + 1, " collaborators"] })) : null] }), document.error && !document.conflict ? (_jsx("div", { className: "mp-lb-mdkit-document-toolbar-error", children: document.error })) : null, _jsxs("div", { className: "mp-lb-mdkit-document-toolbar-actions", children: [hasVersionHistory && onOpenVersionHistory ? (_jsx("button", { type: "button", disabled: isBusy || document.conflict || versions?.isLoading, onClick: () => void runAction("versions", async () => {
|
|
41
39
|
await versions?.refresh();
|
|
42
|
-
await onOpenVersionHistory
|
|
40
|
+
await onOpenVersionHistory();
|
|
43
41
|
}), children: versions?.isLoading
|
|
44
42
|
? "Loading versions..."
|
|
45
|
-
: `Version ${String(document.version ?? "none")}` }), document.conflict && onOpenConflict ? (_jsx("button", { type: "button", className: "mp-lb-mdkit-document-toolbar-conflict-trigger", disabled: isBusy, onClick: () => void runAction("conflict", async () => {
|
|
43
|
+
: `Version ${String(document.version ?? "none")}` })) : null, document.conflict && onOpenConflict ? (_jsx("button", { type: "button", className: "mp-lb-mdkit-document-toolbar-conflict-trigger", disabled: isBusy, onClick: () => void runAction("conflict", async () => {
|
|
46
44
|
await onOpenConflict();
|
|
47
45
|
}), children: "Resolve conflict" })) : null] }), document.conflict && showConflictActions ? (_jsxs("div", { className: "mp-lb-mdkit-document-toolbar-conflict", children: [_jsx("span", { children: "Remote changes conflict with local edits." }), _jsx("button", { type: "button", disabled: isBusy, onClick: () => void runAction("reload", document.resync), children: "Keep remote" }), _jsx("button", { type: "button", disabled: isBusy, onClick: () => void runAction("overwrite", document.forceSave), children: "Keep local" })] })) : null] }));
|
|
48
46
|
};
|
|
@@ -48,9 +48,16 @@ export type MdKitCollaborationParticipant = {
|
|
|
48
48
|
color?: string;
|
|
49
49
|
imageUrl?: string;
|
|
50
50
|
};
|
|
51
|
+
export type MdKitCollaborationPresence = MdKitCollaborationParticipant & {
|
|
52
|
+
clientId: number;
|
|
53
|
+
isLocal: boolean;
|
|
54
|
+
};
|
|
51
55
|
export type MdKitCollaborationSession = {
|
|
52
56
|
collaborator: MdKitCollaborationParticipant;
|
|
53
57
|
document: Y.Doc;
|
|
58
|
+
isCollaborating: boolean;
|
|
59
|
+
otherParticipants: MdKitCollaborationPresence[];
|
|
60
|
+
participants: MdKitCollaborationPresence[];
|
|
54
61
|
provider: HocuspocusProvider | null;
|
|
55
62
|
roomName: string;
|
|
56
63
|
status: MdKitCollaborationStatus;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MdKitDocumentAdapter, MdKitDocumentVersionToken } from "./documentTypes";
|
|
1
|
+
import type { MdKitDocumentAdapter, MdKitDocumentVersionToken } from "./documentTypes.js";
|
|
2
2
|
export type UseMdKitDocumentOptions = {
|
|
3
3
|
adapter: Pick<MdKitDocumentAdapter, "readDocument" | "writeDocument" | "resyncDocument">;
|
|
4
4
|
debounceMs?: number;
|
package/dist/fastify.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { registerMdKitFastify } from "./transport/fastify";
|
|
2
|
-
export type { RegisterMdKitFastifyOptions } from "./transport/fastify";
|
|
3
|
-
export type { MdKitRestoreDocumentVersionInput, MdKitTransportStore, } from "./transport/store";
|
|
1
|
+
export { registerMdKitFastify } from "./transport/fastify.js";
|
|
2
|
+
export type { RegisterMdKitFastifyOptions } from "./transport/fastify.js";
|
|
3
|
+
export type { MdKitRestoreDocumentVersionInput, MdKitTransportStore, } from "./transport/store.js";
|
package/dist/fastify.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { registerMdKitFastify } from "./transport/fastify";
|
|
1
|
+
export { registerMdKitFastify } from "./transport/fastify.js";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,25 +1,29 @@
|
|
|
1
|
-
export { useMdKitCollaboration } from "./collaboration/useMdKitCollaboration";
|
|
2
|
-
export {
|
|
3
|
-
export {
|
|
4
|
-
export {
|
|
5
|
-
export {
|
|
6
|
-
export {
|
|
7
|
-
export {
|
|
8
|
-
export {
|
|
9
|
-
export {
|
|
10
|
-
export {
|
|
11
|
-
export {
|
|
12
|
-
export {
|
|
13
|
-
export
|
|
14
|
-
export
|
|
15
|
-
export type {
|
|
16
|
-
export type {
|
|
17
|
-
export type {
|
|
18
|
-
export type {
|
|
19
|
-
export type {
|
|
20
|
-
export type {
|
|
21
|
-
export type {
|
|
22
|
-
export type {
|
|
23
|
-
export type {
|
|
24
|
-
export type {
|
|
25
|
-
export type {
|
|
1
|
+
export { useMdKitCollaboration } from "./collaboration/useMdKitCollaboration.js";
|
|
2
|
+
export { CheckpointPolicy, measureMdKitEditDistance, } from "./core/checkpointPolicy.js";
|
|
3
|
+
export { createMdKitDocumentRecord, detectMdKitDocumentConflict, normalizeMdKitVersionToken, restoreMdKitDocumentVersion, writeMdKitDocumentRecord, } from "./core/documentEngine.js";
|
|
4
|
+
export { useMdKitDocument } from "./document/useMdKitDocument.js";
|
|
5
|
+
export { MdKitConflictPanel } from "./document/MdKitConflictPanel.js";
|
|
6
|
+
export { MdKitDocumentToolbar } from "./document/MdKitDocumentToolbar.js";
|
|
7
|
+
export { MdKitEditor } from "./markdown/MdKitEditor.js";
|
|
8
|
+
export { MdKitView } from "./markdown/MdKitView.js";
|
|
9
|
+
export { MdKitThemeEditor } from "./theme/MdKitThemeEditor.js";
|
|
10
|
+
export { createMdKitRestAdapter } from "./transport/rest.js";
|
|
11
|
+
export { createMdKitEditorThemeStyle, darkMdKitEditorTheme, defaultMdKitEditorTheme, } from "./theme/editorTheme.js";
|
|
12
|
+
export { VersionHistoryPanel } from "./versioning/VersionHistoryPanel.js";
|
|
13
|
+
export { useMdKitDocumentVersions } from "./versioning/useMdKitDocumentVersions.js";
|
|
14
|
+
export { yjs } from "./yjs/index.js";
|
|
15
|
+
export type { MdKitCheckpointPolicy, MdKitCheckpointPolicyInput, MdKitSmartCheckpointPolicyOptions, } from "./core/checkpointPolicy.js";
|
|
16
|
+
export type { CreateMdKitDocumentRecordInput, MdKitDocumentRecord, RestoreMdKitDocumentVersionInput, RestoreMdKitDocumentVersionResult, WriteMdKitDocumentRecordInput, WriteMdKitDocumentRecordResult, } from "./core/documentEngine.js";
|
|
17
|
+
export type { MdKitCollaborationParticipant, MdKitCollaborationSession, MdKitCollaborationStatus, MdKitDocumentAdapter, MdKitDocumentSnapshot, MdKitDocumentVersionDetail, MdKitDocumentVersionSummary, MdKitDocumentVersionToken, MdKitDocumentWriteInput, MdKitDocumentWriteResult, } from "./document/documentTypes.js";
|
|
18
|
+
export type { MdKitDocumentConflictDetails, MdKitDocumentController, } from "./document/useMdKitDocument.js";
|
|
19
|
+
export type { MdKitConflictPanelProps } from "./document/MdKitConflictPanel.js";
|
|
20
|
+
export type { MdKitDocumentToolbarProps } from "./document/MdKitDocumentToolbar.js";
|
|
21
|
+
export type { MdKitEditorProps } from "./markdown/MdKitEditor.js";
|
|
22
|
+
export type { MdKitEditorDebugEvent } from "./markdown/editorDebug.js";
|
|
23
|
+
export type { MdKitViewProps } from "./markdown/MdKitView.js";
|
|
24
|
+
export type { MdKitThemeEditorProps } from "./theme/MdKitThemeEditor.js";
|
|
25
|
+
export type { CreateMdKitRestAdapterOptions } from "./transport/rest.js";
|
|
26
|
+
export type { MdKitEditorTheme, MdKitEditorThemeStyle, } from "./theme/editorTheme.js";
|
|
27
|
+
export type { MdKitDocumentVersionsController, UseMdKitDocumentVersionsOptions, } from "./versioning/useMdKitDocumentVersions.js";
|
|
28
|
+
export type { VersionHistoryPanelProps } from "./versioning/VersionHistoryPanel.js";
|
|
29
|
+
export type { MdKitMarkdownYjsOptions } from "./yjs/index.js";
|