@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.
Files changed (79) hide show
  1. package/dist/collaboration/useMdKitCollaboration.d.ts +1 -1
  2. package/dist/collaboration/useMdKitCollaboration.js +304 -18
  3. package/dist/core/checkpointPolicy.d.ts +25 -0
  4. package/dist/core/checkpointPolicy.js +45 -0
  5. package/dist/core/index.d.ts +2 -0
  6. package/dist/core/index.js +1 -0
  7. package/dist/document/MdKitConflictPanel.d.ts +1 -1
  8. package/dist/document/MdKitConflictPanel.js +1 -1
  9. package/dist/document/MdKitDocumentToolbar.d.ts +3 -3
  10. package/dist/document/MdKitDocumentToolbar.js +6 -8
  11. package/dist/document/documentTypes.d.ts +7 -0
  12. package/dist/document/useMdKitDocument.d.ts +1 -1
  13. package/dist/fastify.d.ts +3 -3
  14. package/dist/fastify.js +1 -1
  15. package/dist/index.d.ts +29 -25
  16. package/dist/index.js +14 -12
  17. package/dist/markdown/MarkdownBubbleMenu.js +1 -1
  18. package/dist/markdown/MdKitEditor.d.ts +2 -2
  19. package/dist/markdown/MdKitEditor.js +2 -2
  20. package/dist/markdown/MdKitView.js +1 -1
  21. package/dist/markdown/TiptapMarkdownSurface.d.ts +2 -2
  22. package/dist/markdown/TiptapMarkdownSurface.js +17 -26
  23. package/dist/markdown/createMdKitTiptapExtensions.d.ts +8 -0
  24. package/dist/markdown/createMdKitTiptapExtensions.js +27 -0
  25. package/dist/markdown/prepareMarkdownForEditorHydration.js +1 -1
  26. package/dist/markdown/preserveMarkdownWhitespace.js +1 -1
  27. package/dist/server.d.ts +2 -0
  28. package/dist/server.js +1 -0
  29. package/dist/theme/MdKitThemeEditor.d.ts +1 -1
  30. package/dist/transport/backend.d.ts +29 -0
  31. package/dist/transport/backend.js +93 -0
  32. package/dist/transport/fastify.d.ts +1 -1
  33. package/dist/transport/fastify.js +1 -1
  34. package/dist/transport/http.d.ts +1 -1
  35. package/dist/transport/index.d.ts +5 -5
  36. package/dist/transport/index.js +2 -2
  37. package/dist/transport/rest.d.ts +1 -1
  38. package/dist/transport/store.d.ts +1 -1
  39. package/dist/transport/trpcClient.d.ts +29 -8
  40. package/dist/transport/trpcServer.d.ts +1 -1
  41. package/dist/trpc/client.d.ts +3 -3
  42. package/dist/trpc/client.js +1 -1
  43. package/dist/trpc/server.d.ts +5 -3
  44. package/dist/trpc/server.js +2 -1
  45. package/dist/trpc.d.ts +3 -3
  46. package/dist/trpc.js +1 -1
  47. package/dist/versioning/VersionHistoryPanel.d.ts +2 -2
  48. package/dist/versioning/VersionHistoryPanel.js +1 -1
  49. package/dist/versioning/useMdKitDocumentVersions.d.ts +2 -1
  50. package/dist/versioning/useMdKitDocumentVersions.js +6 -6
  51. package/dist/yjs/MdKitMarkdownYjs.d.ts +12 -0
  52. package/dist/yjs/MdKitMarkdownYjs.js +40 -0
  53. package/dist/yjs/index.d.ts +2 -0
  54. package/dist/yjs/index.js +1 -0
  55. package/docs/.vitepress/cache/deps/@theme_index.js +275 -0
  56. package/docs/.vitepress/cache/deps/@theme_index.js.map +7 -0
  57. package/docs/.vitepress/cache/deps/_metadata.json +40 -0
  58. package/docs/.vitepress/cache/deps/chunk-PM3I3KHC.js +9719 -0
  59. package/docs/.vitepress/cache/deps/chunk-PM3I3KHC.js.map +7 -0
  60. package/docs/.vitepress/cache/deps/chunk-VSHFF4ZG.js +13018 -0
  61. package/docs/.vitepress/cache/deps/chunk-VSHFF4ZG.js.map +7 -0
  62. package/docs/.vitepress/cache/deps/package.json +3 -0
  63. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +4505 -0
  64. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +7 -0
  65. package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +583 -0
  66. package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +7 -0
  67. package/docs/.vitepress/cache/deps/vue.js +347 -0
  68. package/docs/.vitepress/cache/deps/vue.js.map +7 -0
  69. package/docs/.vitepress/config.ts +11 -0
  70. package/docs/api.md +153 -18
  71. package/docs/architecture.md +24 -9
  72. package/docs/collaboration-persistence.md +147 -0
  73. package/docs/index.md +139 -65
  74. package/docs/permissions.md +139 -0
  75. package/docs/rest.md +98 -0
  76. package/docs/shadcn.md +6 -3
  77. package/docs/styling.md +11 -10
  78. package/docs/use-cases.md +148 -0
  79. 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
- }), [collaborator]);
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 provider = useMemo(() => {
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
- return null;
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
- return new HocuspocusProvider({
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: () => setStatus("connected"),
37
- onDisconnect: () => setStatus("disconnected"),
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
- return token || "";
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
- }, [documentId, enabled, endpoint, getToken, roomName, ydoc]);
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
- provider.destroy();
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.setAwarenessField("user", {
63
- color: normalizedCollaborator.color,
64
- id: normalizedCollaborator.id,
65
- imageUrl: normalizedCollaborator.imageUrl || undefined,
66
- name: normalizedCollaborator.name,
67
- });
68
- }, [normalizedCollaborator, provider]);
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
+ };
@@ -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";
@@ -1 +1,2 @@
1
1
  export { createMdKitDocumentRecord, detectMdKitDocumentConflict, normalizeMdKitVersionToken, restoreMdKitDocumentVersion, writeMdKitDocumentRecord, } from "./documentEngine.js";
2
+ export { CheckpointPolicy, measureMdKitEditDistance, } from "./checkpointPolicy.js";
@@ -1,4 +1,4 @@
1
- import type { MdKitDocumentController } from "./useMdKitDocument";
1
+ import type { MdKitDocumentController } from "./useMdKitDocument.js";
2
2
  export type MdKitConflictPanelProps = {
3
3
  className?: string;
4
4
  document: MdKitDocumentController;
@@ -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: ["Collaboration ", collaboration ? collaboration.status : "off"] })] }), 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: [_jsx("button", { type: "button", disabled: isBusy ||
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 { createMdKitDocumentRecord, detectMdKitDocumentConflict, normalizeMdKitVersionToken, restoreMdKitDocumentVersion, writeMdKitDocumentRecord, } from "./core/documentEngine";
3
- export { useMdKitDocument } from "./document/useMdKitDocument";
4
- export { MdKitConflictPanel } from "./document/MdKitConflictPanel";
5
- export { MdKitDocumentToolbar } from "./document/MdKitDocumentToolbar";
6
- export { MdKitEditor } from "./markdown/MdKitEditor";
7
- export { MdKitView } from "./markdown/MdKitView";
8
- export { MdKitThemeEditor } from "./theme/MdKitThemeEditor";
9
- export { createMdKitRestAdapter } from "./transport/rest";
10
- export { createMdKitEditorThemeStyle, darkMdKitEditorTheme, defaultMdKitEditorTheme, } from "./theme/editorTheme";
11
- export { VersionHistoryPanel } from "./versioning/VersionHistoryPanel";
12
- export { useMdKitDocumentVersions } from "./versioning/useMdKitDocumentVersions";
13
- export type { CreateMdKitDocumentRecordInput, MdKitDocumentRecord, RestoreMdKitDocumentVersionInput, RestoreMdKitDocumentVersionResult, WriteMdKitDocumentRecordInput, WriteMdKitDocumentRecordResult, } from "./core/documentEngine";
14
- export type { MdKitCollaborationParticipant, MdKitCollaborationSession, MdKitCollaborationStatus, MdKitDocumentAdapter, MdKitDocumentSnapshot, MdKitDocumentVersionDetail, MdKitDocumentVersionSummary, MdKitDocumentVersionToken, MdKitDocumentWriteInput, MdKitDocumentWriteResult, } from "./document/documentTypes";
15
- export type { MdKitDocumentConflictDetails, MdKitDocumentController, } from "./document/useMdKitDocument";
16
- export type { MdKitConflictPanelProps } from "./document/MdKitConflictPanel";
17
- export type { MdKitDocumentToolbarProps } from "./document/MdKitDocumentToolbar";
18
- export type { MdKitEditorProps } from "./markdown/MdKitEditor";
19
- export type { MdKitEditorDebugEvent } from "./markdown/editorDebug";
20
- export type { MdKitViewProps } from "./markdown/MdKitView";
21
- export type { MdKitThemeEditorProps } from "./theme/MdKitThemeEditor";
22
- export type { CreateMdKitRestAdapterOptions } from "./transport/rest";
23
- export type { MdKitEditorTheme, MdKitEditorThemeStyle, } from "./theme/editorTheme";
24
- export type { MdKitDocumentVersionsController, UseMdKitDocumentVersionsOptions, } from "./versioning/useMdKitDocumentVersions";
25
- export type { VersionHistoryPanelProps } from "./versioning/VersionHistoryPanel";
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";