@mp-lb/mdkit 0.3.2 → 0.3.3
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/README.md +8 -2
- package/dist/collaboration/useMdKitCollaboration.d.ts +5 -0
- package/dist/collaboration/useMdKitCollaboration.d.ts.map +1 -0
- package/dist/collaboration/useMdKitCollaboration.js +4 -0
- package/dist/core/checkpointPolicy.d.ts +10 -0
- package/dist/core/checkpointPolicy.d.ts.map +1 -0
- package/dist/core/checkpointPolicy.js +9 -0
- package/dist/core/documentEngine.d.ts +1 -0
- package/dist/core/documentEngine.d.ts.map +1 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/document/MdKitConflictPanel.d.ts +5 -0
- package/dist/document/MdKitConflictPanel.d.ts.map +1 -0
- package/dist/document/MdKitConflictPanel.js +4 -0
- package/dist/document/MdKitDocumentToolbar.d.ts +6 -0
- package/dist/document/MdKitDocumentToolbar.d.ts.map +1 -0
- package/dist/document/MdKitDocumentToolbar.js +5 -0
- package/dist/document/documentTypes.d.ts +6 -0
- package/dist/document/documentTypes.d.ts.map +1 -0
- package/dist/document/useMdKitDocument.d.ts +5 -0
- package/dist/document/useMdKitDocument.d.ts.map +1 -0
- package/dist/document/useMdKitDocument.js +4 -0
- package/dist/fastify.d.ts +1 -0
- package/dist/fastify.d.ts.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -0
- package/dist/markdown/MarkdownBubbleMenu.d.ts +1 -0
- package/dist/markdown/MarkdownBubbleMenu.d.ts.map +1 -0
- package/dist/markdown/MarkdownPasteExtension.d.ts +1 -0
- package/dist/markdown/MarkdownPasteExtension.d.ts.map +1 -0
- package/dist/markdown/MarkdownSearchExtension.d.ts +1 -0
- package/dist/markdown/MarkdownSearchExtension.d.ts.map +1 -0
- package/dist/markdown/MarkdownSearchPanel.d.ts +1 -0
- package/dist/markdown/MarkdownSearchPanel.d.ts.map +1 -0
- package/dist/markdown/MdKitEditor.d.ts +11 -0
- package/dist/markdown/MdKitEditor.d.ts.map +1 -0
- package/dist/markdown/MdKitEditor.js +10 -2
- package/dist/markdown/MdKitView.d.ts +9 -1
- package/dist/markdown/MdKitView.d.ts.map +1 -0
- package/dist/markdown/MdKitView.js +7 -2
- package/dist/markdown/TiptapMarkdownSurface.d.ts +1 -0
- package/dist/markdown/TiptapMarkdownSurface.d.ts.map +1 -0
- package/dist/markdown/TiptapMarkdownSurface.js +3 -22
- package/dist/markdown/createMdKitTiptapExtensions.d.ts +1 -0
- package/dist/markdown/createMdKitTiptapExtensions.d.ts.map +1 -0
- package/dist/markdown/editorDebug.d.ts +1 -0
- package/dist/markdown/editorDebug.d.ts.map +1 -0
- package/dist/markdown/markdownFenceRanges.d.ts +1 -0
- package/dist/markdown/markdownFenceRanges.d.ts.map +1 -0
- package/dist/markdown/normalizeMarkdownSerialization.d.ts +1 -0
- package/dist/markdown/normalizeMarkdownSerialization.d.ts.map +1 -0
- package/dist/markdown/prepareMarkdownForEditorHydration.d.ts +1 -0
- package/dist/markdown/prepareMarkdownForEditorHydration.d.ts.map +1 -0
- package/dist/markdown/preserveMarkdownWhitespace.d.ts +1 -0
- package/dist/markdown/preserveMarkdownWhitespace.d.ts.map +1 -0
- package/dist/markdown/yamlFrontMatter.d.ts +1 -0
- package/dist/markdown/yamlFrontMatter.d.ts.map +1 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/theme/MdKitThemeEditor.d.ts +5 -0
- package/dist/theme/MdKitThemeEditor.d.ts.map +1 -0
- package/dist/theme/MdKitThemeEditor.js +4 -0
- package/dist/theme/editorTheme.d.ts +1 -0
- package/dist/theme/editorTheme.d.ts.map +1 -0
- package/dist/theme/editorTheme.js +8 -8
- package/dist/transport/backend.d.ts +13 -0
- package/dist/transport/backend.d.ts.map +1 -0
- package/dist/transport/backend.js +6 -0
- package/dist/transport/fastify.d.ts +5 -0
- package/dist/transport/fastify.d.ts.map +1 -0
- package/dist/transport/fastify.js +4 -0
- package/dist/transport/http.d.ts +1 -0
- package/dist/transport/http.d.ts.map +1 -0
- package/dist/transport/index.d.ts +1 -0
- package/dist/transport/index.d.ts.map +1 -0
- package/dist/transport/rest.d.ts +6 -0
- package/dist/transport/rest.d.ts.map +1 -0
- package/dist/transport/rest.js +5 -0
- package/dist/transport/store.d.ts +1 -0
- package/dist/transport/store.d.ts.map +1 -0
- package/dist/transport/trpcClient.d.ts +8 -0
- package/dist/transport/trpcClient.d.ts.map +1 -0
- package/dist/transport/trpcClient.js +7 -0
- package/dist/transport/trpcServer.d.ts +6 -0
- package/dist/transport/trpcServer.d.ts.map +1 -0
- package/dist/transport/trpcServer.js +5 -0
- package/dist/trpc/client.d.ts +1 -0
- package/dist/trpc/client.d.ts.map +1 -0
- package/dist/trpc/server.d.ts +1 -0
- package/dist/trpc/server.d.ts.map +1 -0
- package/dist/trpc.d.ts +1 -0
- package/dist/trpc.d.ts.map +1 -0
- package/dist/ui/joinClassNames.d.ts +1 -0
- package/dist/ui/joinClassNames.d.ts.map +1 -0
- package/dist/versioning/VersionHistoryPanel.d.ts +5 -0
- package/dist/versioning/VersionHistoryPanel.d.ts.map +1 -0
- package/dist/versioning/VersionHistoryPanel.js +4 -0
- package/dist/versioning/useMdKitDocumentVersions.d.ts +5 -0
- package/dist/versioning/useMdKitDocumentVersions.d.ts.map +1 -0
- package/dist/versioning/useMdKitDocumentVersions.js +4 -0
- package/dist/yjs/MdKitMarkdownYjs.d.ts +1 -0
- package/dist/yjs/MdKitMarkdownYjs.d.ts.map +1 -0
- package/dist/yjs/index.d.ts +1 -0
- package/dist/yjs/index.d.ts.map +1 -0
- package/package.json +10 -12
- package/src/collaboration/useMdKitCollaboration.ts +528 -0
- package/src/core/checkpointPolicy.ts +107 -0
- package/src/core/documentEngine.ts +175 -0
- package/src/core/index.ts +33 -0
- package/src/document/MdKitConflictPanel.tsx +129 -0
- package/src/document/MdKitDocumentToolbar.tsx +141 -0
- package/src/document/documentTypes.ts +89 -0
- package/src/document/useMdKitDocument.ts +543 -0
- package/src/fastify.ts +6 -0
- package/src/index.ts +89 -0
- package/src/markdown/MarkdownBubbleMenu.tsx +271 -0
- package/src/markdown/MarkdownPasteExtension.ts +81 -0
- package/src/markdown/MarkdownSearchExtension.ts +77 -0
- package/src/markdown/MarkdownSearchPanel.tsx +98 -0
- package/src/markdown/MdKitEditor.tsx +75 -0
- package/src/markdown/MdKitView.tsx +80 -0
- package/src/markdown/TiptapMarkdownSurface.tsx +923 -0
- package/src/markdown/createMdKitTiptapExtensions.ts +42 -0
- package/src/markdown/editorDebug.ts +5 -0
- package/src/markdown/markdownFenceRanges.ts +68 -0
- package/src/markdown/normalizeMarkdownSerialization.ts +55 -0
- package/src/markdown/prepareMarkdownForEditorHydration.ts +23 -0
- package/src/markdown/preserveMarkdownWhitespace.ts +143 -0
- package/src/markdown/yamlFrontMatter.ts +135 -0
- package/src/server.ts +6 -0
- package/src/styles.css +125 -53
- package/src/theme/MdKitThemeEditor.tsx +134 -0
- package/src/theme/editorTheme.ts +72 -0
- package/src/transport/backend.ts +220 -0
- package/src/transport/fastify.ts +57 -0
- package/src/transport/http.ts +126 -0
- package/src/transport/index.ts +12 -0
- package/src/transport/rest.ts +80 -0
- package/src/transport/store.ts +45 -0
- package/src/transport/trpcClient.ts +90 -0
- package/src/transport/trpcServer.ts +66 -0
- package/src/trpc/client.ts +11 -0
- package/src/trpc/server.ts +12 -0
- package/src/trpc.ts +11 -0
- package/src/ui/joinClassNames.ts +3 -0
- package/src/versioning/VersionHistoryPanel.tsx +146 -0
- package/src/versioning/useMdKitDocumentVersions.ts +146 -0
- package/src/yjs/MdKitMarkdownYjs.ts +111 -0
- package/src/yjs/index.ts +8 -0
- package/docs/.vitepress/config.ts +0 -47
- package/docs/api.md +0 -512
- package/docs/architecture.md +0 -96
- package/docs/collaboration-persistence.md +0 -147
- package/docs/index.md +0 -341
- package/docs/permissions.md +0 -139
- package/docs/plain-text.md +0 -131
- package/docs/rest.md +0 -98
- package/docs/shadcn.md +0 -125
- package/docs/styling.md +0 -373
- package/docs/use-cases.md +0 -148
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
|
|
2
|
+
import { createMdKitHttpHandlers } from "./http";
|
|
3
|
+
import type { MdKitTransportStore } from "./store";
|
|
4
|
+
|
|
5
|
+
export type RegisterMdKitFastifyOptions = {
|
|
6
|
+
prefix?: string;
|
|
7
|
+
store: MdKitTransportStore;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const send = async (
|
|
11
|
+
reply: FastifyReply,
|
|
12
|
+
response: Promise<{ body: unknown; status: number }>,
|
|
13
|
+
) => {
|
|
14
|
+
const { body, status } = await response;
|
|
15
|
+
return reply.status(status).send(body);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const toRequest = (request: FastifyRequest) => ({
|
|
19
|
+
body: request.body,
|
|
20
|
+
params: request.params as Record<string, unknown>,
|
|
21
|
+
query: request.query as Record<string, unknown>,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Registers the mdkit REST endpoints on a Fastify app under `prefix`, backed by
|
|
26
|
+
* a transport store (typically from `createMdKitBackend`).
|
|
27
|
+
*/
|
|
28
|
+
export const registerMdKitFastify = async (
|
|
29
|
+
app: FastifyInstance,
|
|
30
|
+
{ prefix = "", store }: RegisterMdKitFastifyOptions,
|
|
31
|
+
) => {
|
|
32
|
+
const handlers = createMdKitHttpHandlers(store);
|
|
33
|
+
|
|
34
|
+
app.get(`${prefix}/documents`, (request, reply) =>
|
|
35
|
+
send(reply, handlers.readDocument(toRequest(request))),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
app.put(`${prefix}/documents`, (request, reply) =>
|
|
39
|
+
send(reply, handlers.writeDocument(toRequest(request))),
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
app.post(`${prefix}/documents/resync`, (request, reply) =>
|
|
43
|
+
send(reply, handlers.resyncDocument(toRequest(request))),
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
app.get(`${prefix}/versions`, (request, reply) =>
|
|
47
|
+
send(reply, handlers.listDocumentVersions(toRequest(request))),
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
app.get(`${prefix}/versions/:versionId`, (request, reply) =>
|
|
51
|
+
send(reply, handlers.readDocumentVersion(toRequest(request))),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
app.post(`${prefix}/versions/:versionId/restore`, (request, reply) =>
|
|
55
|
+
send(reply, handlers.restoreDocumentVersion(toRequest(request))),
|
|
56
|
+
);
|
|
57
|
+
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
MdKitDocumentVersionToken,
|
|
3
|
+
MdKitDocumentWriteInput,
|
|
4
|
+
} from "../document/documentTypes";
|
|
5
|
+
import type {
|
|
6
|
+
MdKitRestoreDocumentVersionInput,
|
|
7
|
+
MdKitTransportStore,
|
|
8
|
+
} from "./store";
|
|
9
|
+
|
|
10
|
+
export type MdKitHttpRequest = {
|
|
11
|
+
body?: unknown;
|
|
12
|
+
params?: Record<string, unknown>;
|
|
13
|
+
query?: Record<string, unknown>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type MdKitHttpResponse<T = unknown> = {
|
|
17
|
+
body: T;
|
|
18
|
+
status: number;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type MdKitHttpHandlers = ReturnType<typeof createMdKitHttpHandlers>;
|
|
22
|
+
|
|
23
|
+
const readString = (
|
|
24
|
+
source: Record<string, unknown> | undefined,
|
|
25
|
+
key: string,
|
|
26
|
+
): string => {
|
|
27
|
+
const value = source?.[key];
|
|
28
|
+
|
|
29
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
30
|
+
throw new Error(`Missing mdkit ${key}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return value;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const readVersionToken = (value: unknown): MdKitDocumentVersionToken => {
|
|
37
|
+
if (
|
|
38
|
+
value === null ||
|
|
39
|
+
typeof value === "string" ||
|
|
40
|
+
typeof value === "number"
|
|
41
|
+
) {
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
throw new Error("Invalid mdkit baseVersion");
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const readWriteInput = (request: MdKitHttpRequest): MdKitDocumentWriteInput => {
|
|
49
|
+
const body = request.body as Record<string, unknown> | null | undefined;
|
|
50
|
+
|
|
51
|
+
if (!body || typeof body !== "object") {
|
|
52
|
+
throw new Error("Missing mdkit write body");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const content = body.content;
|
|
56
|
+
|
|
57
|
+
if (typeof content !== "string") {
|
|
58
|
+
throw new Error("Invalid mdkit content");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
baseVersion: readVersionToken(body.baseVersion),
|
|
63
|
+
content,
|
|
64
|
+
documentId: readString(request.query, "documentId"),
|
|
65
|
+
force: body.force === true,
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const notFound = (message: string): MdKitHttpResponse<{ error: string }> => ({
|
|
70
|
+
body: { error: message },
|
|
71
|
+
status: 404,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
export const createMdKitHttpHandlers = (store: MdKitTransportStore) => ({
|
|
75
|
+
readDocument: async (request: MdKitHttpRequest) => ({
|
|
76
|
+
body: await store.readDocument(readString(request.query, "documentId")),
|
|
77
|
+
status: 200,
|
|
78
|
+
}),
|
|
79
|
+
writeDocument: async (request: MdKitHttpRequest) => {
|
|
80
|
+
const body = await store.writeDocument(readWriteInput(request));
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
body,
|
|
84
|
+
status: "conflict" in body ? 409 : 200,
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
resyncDocument: async (request: MdKitHttpRequest) => ({
|
|
88
|
+
body: await (store.resyncDocument ?? store.readDocument)(
|
|
89
|
+
readString(request.query, "documentId"),
|
|
90
|
+
),
|
|
91
|
+
status: 200,
|
|
92
|
+
}),
|
|
93
|
+
listDocumentVersions: async (request: MdKitHttpRequest) => ({
|
|
94
|
+
body: {
|
|
95
|
+
versions: await (store.listDocumentVersions?.(
|
|
96
|
+
readString(request.query, "documentId"),
|
|
97
|
+
) ?? []),
|
|
98
|
+
},
|
|
99
|
+
status: 200,
|
|
100
|
+
}),
|
|
101
|
+
readDocumentVersion: async (request: MdKitHttpRequest) => {
|
|
102
|
+
const version = await store.readDocumentVersion?.({
|
|
103
|
+
documentId: readString(request.query, "documentId"),
|
|
104
|
+
versionId: readString(request.params, "versionId"),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return version
|
|
108
|
+
? ({ body: version, status: 200 } as const)
|
|
109
|
+
: notFound("Version not found");
|
|
110
|
+
},
|
|
111
|
+
restoreDocumentVersion: async (request: MdKitHttpRequest) => {
|
|
112
|
+
if (!store.restoreDocumentVersion) {
|
|
113
|
+
return notFound("Version restore is not supported");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const input: MdKitRestoreDocumentVersionInput = {
|
|
117
|
+
documentId: readString(request.query, "documentId"),
|
|
118
|
+
versionId: readString(request.params, "versionId"),
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
body: await store.restoreDocumentVersion(input),
|
|
123
|
+
status: 200,
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { createMdKitHttpHandlers } from "./http";
|
|
2
|
+
export { createMdKitRestAdapter } from "./rest";
|
|
3
|
+
export type {
|
|
4
|
+
MdKitHttpHandlers,
|
|
5
|
+
MdKitHttpRequest,
|
|
6
|
+
MdKitHttpResponse,
|
|
7
|
+
} from "./http";
|
|
8
|
+
export type { CreateMdKitRestAdapterOptions } from "./rest";
|
|
9
|
+
export type {
|
|
10
|
+
MdKitRestoreDocumentVersionInput,
|
|
11
|
+
MdKitTransportStore,
|
|
12
|
+
} from "./store";
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
MdKitDocumentAdapter,
|
|
3
|
+
MdKitDocumentVersionSummary,
|
|
4
|
+
} from "../document/documentTypes";
|
|
5
|
+
|
|
6
|
+
export type CreateMdKitRestAdapterOptions = {
|
|
7
|
+
baseUrl: string;
|
|
8
|
+
fetch?: typeof fetch;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const trimTrailingSlash = (value: string) => value.replace(/\/+$/, "");
|
|
12
|
+
|
|
13
|
+
const documentQuery = (documentId: string) =>
|
|
14
|
+
`documentId=${encodeURIComponent(documentId)}`;
|
|
15
|
+
|
|
16
|
+
const readJson = async <T>(response: Response): Promise<T> => {
|
|
17
|
+
const body = (await response.json()) as T;
|
|
18
|
+
|
|
19
|
+
if (!response.ok && response.status !== 409) {
|
|
20
|
+
throw new Error(`MdKit REST request failed: ${response.status}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return body;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Builds an {@link MdKitDocumentAdapter} that talks to the mdkit REST endpoint
|
|
28
|
+
* shape. Restore is not part of the adapter contract, so REST restore needs a
|
|
29
|
+
* separate call from application code.
|
|
30
|
+
*/
|
|
31
|
+
export const createMdKitRestAdapter = ({
|
|
32
|
+
baseUrl,
|
|
33
|
+
fetch: fetchImpl = fetch,
|
|
34
|
+
}: CreateMdKitRestAdapterOptions): MdKitDocumentAdapter => {
|
|
35
|
+
const url = trimTrailingSlash(baseUrl);
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
readDocument: async (documentId) =>
|
|
39
|
+
readJson(
|
|
40
|
+
await fetchImpl(`${url}/documents?${documentQuery(documentId)}`),
|
|
41
|
+
),
|
|
42
|
+
writeDocument: async (input) =>
|
|
43
|
+
readJson(
|
|
44
|
+
await fetchImpl(`${url}/documents?${documentQuery(input.documentId)}`, {
|
|
45
|
+
body: JSON.stringify(input),
|
|
46
|
+
headers: { "Content-Type": "application/json" },
|
|
47
|
+
method: "PUT",
|
|
48
|
+
}),
|
|
49
|
+
),
|
|
50
|
+
resyncDocument: async (documentId) =>
|
|
51
|
+
readJson(
|
|
52
|
+
await fetchImpl(
|
|
53
|
+
`${url}/documents/resync?${documentQuery(documentId)}`,
|
|
54
|
+
{
|
|
55
|
+
method: "POST",
|
|
56
|
+
},
|
|
57
|
+
),
|
|
58
|
+
),
|
|
59
|
+
listDocumentVersions: async (documentId) => {
|
|
60
|
+
const body = await readJson<{ versions: MdKitDocumentVersionSummary[] }>(
|
|
61
|
+
await fetchImpl(`${url}/versions?${documentQuery(documentId)}`),
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
return body.versions;
|
|
65
|
+
},
|
|
66
|
+
readDocumentVersion: async ({ documentId, versionId }) => {
|
|
67
|
+
const response = await fetchImpl(
|
|
68
|
+
`${url}/versions/${encodeURIComponent(versionId)}?${documentQuery(
|
|
69
|
+
documentId,
|
|
70
|
+
)}`,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
if (response.status === 404) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return readJson(response);
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
MdKitDocumentSnapshot,
|
|
3
|
+
MdKitDocumentVersionDetail,
|
|
4
|
+
MdKitDocumentVersionSummary,
|
|
5
|
+
MdKitDocumentWriteInput,
|
|
6
|
+
MdKitDocumentWriteResult,
|
|
7
|
+
} from "../document/documentTypes";
|
|
8
|
+
|
|
9
|
+
export type MdKitTransportStore = {
|
|
10
|
+
readDocument(
|
|
11
|
+
documentId: string,
|
|
12
|
+
): Promise<MdKitDocumentSnapshot> | MdKitDocumentSnapshot;
|
|
13
|
+
writeDocument(
|
|
14
|
+
input: MdKitDocumentWriteInput,
|
|
15
|
+
): Promise<MdKitDocumentWriteResult> | MdKitDocumentWriteResult;
|
|
16
|
+
resyncDocument?(
|
|
17
|
+
documentId: string,
|
|
18
|
+
): Promise<MdKitDocumentSnapshot> | MdKitDocumentSnapshot;
|
|
19
|
+
listDocumentVersions?(
|
|
20
|
+
documentId: string,
|
|
21
|
+
): Promise<MdKitDocumentVersionSummary[]> | MdKitDocumentVersionSummary[];
|
|
22
|
+
readDocumentVersion?(input: {
|
|
23
|
+
documentId: string;
|
|
24
|
+
versionId: string;
|
|
25
|
+
}):
|
|
26
|
+
| Promise<MdKitDocumentVersionDetail | null>
|
|
27
|
+
| MdKitDocumentVersionDetail
|
|
28
|
+
| null;
|
|
29
|
+
restoreDocumentVersion?(input: {
|
|
30
|
+
documentId: string;
|
|
31
|
+
versionId: string;
|
|
32
|
+
}): Promise<MdKitDocumentWriteResult> | MdKitDocumentWriteResult;
|
|
33
|
+
readCollaborationState?(
|
|
34
|
+
documentName: string,
|
|
35
|
+
): Promise<Uint8Array | null> | Uint8Array | null;
|
|
36
|
+
writeCollaborationState?(
|
|
37
|
+
documentName: string,
|
|
38
|
+
state: Uint8Array,
|
|
39
|
+
): Promise<void> | void;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type MdKitRestoreDocumentVersionInput = {
|
|
43
|
+
documentId: string;
|
|
44
|
+
versionId: string;
|
|
45
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { createTRPCProxyClient, httpBatchLink } from "@trpc/client";
|
|
2
|
+
import type {
|
|
3
|
+
MdKitDocumentAdapter,
|
|
4
|
+
MdKitDocumentSnapshot,
|
|
5
|
+
MdKitDocumentVersionDetail,
|
|
6
|
+
MdKitDocumentVersionSummary,
|
|
7
|
+
MdKitDocumentWriteInput,
|
|
8
|
+
MdKitDocumentWriteResult,
|
|
9
|
+
} from "../document/documentTypes";
|
|
10
|
+
import type { MdKitTrpcRouter } from "./trpcServer";
|
|
11
|
+
|
|
12
|
+
export type CreateMdKitTrpcClientAdapterOptions = {
|
|
13
|
+
url: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type MdKitTrpcQuery<Input, Output> = {
|
|
17
|
+
query(input: Input): Promise<Output>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type MdKitTrpcMutation<Input, Output> = {
|
|
21
|
+
mutate(input: Input): Promise<Output>;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type MdKitDocumentInput = {
|
|
25
|
+
documentId: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type MdKitVersionInput = {
|
|
29
|
+
documentId: string;
|
|
30
|
+
versionId: string;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type MdKitTrpcClient = {
|
|
34
|
+
listDocumentVersions: MdKitTrpcQuery<
|
|
35
|
+
MdKitDocumentInput,
|
|
36
|
+
{ versions: MdKitDocumentVersionSummary[] }
|
|
37
|
+
>;
|
|
38
|
+
readDocument: MdKitTrpcQuery<MdKitDocumentInput, MdKitDocumentSnapshot>;
|
|
39
|
+
readDocumentVersion: MdKitTrpcQuery<
|
|
40
|
+
MdKitVersionInput,
|
|
41
|
+
MdKitDocumentVersionDetail | null
|
|
42
|
+
>;
|
|
43
|
+
resyncDocument: MdKitTrpcMutation<MdKitDocumentInput, MdKitDocumentSnapshot>;
|
|
44
|
+
writeDocument: MdKitTrpcMutation<
|
|
45
|
+
MdKitDocumentWriteInput,
|
|
46
|
+
MdKitDocumentWriteResult
|
|
47
|
+
>;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type CreateMdKitTrpcAdapterOptions = {
|
|
51
|
+
client: MdKitTrpcClient;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Turns an mdkit tRPC client (standalone or a nested sub-client of your app
|
|
56
|
+
* router) into an {@link MdKitDocumentAdapter} for the document hooks.
|
|
57
|
+
*/
|
|
58
|
+
export const createMdKitTrpcAdapter = ({
|
|
59
|
+
client,
|
|
60
|
+
}: CreateMdKitTrpcAdapterOptions): MdKitDocumentAdapter => ({
|
|
61
|
+
listDocumentVersions: async (documentId) => {
|
|
62
|
+
const body = await client.listDocumentVersions.query({ documentId });
|
|
63
|
+
return body.versions;
|
|
64
|
+
},
|
|
65
|
+
readDocument: (documentId) => client.readDocument.query({ documentId }),
|
|
66
|
+
readDocumentVersion: (input) => client.readDocumentVersion.query(input),
|
|
67
|
+
resyncDocument: (documentId) => client.resyncDocument.mutate({ documentId }),
|
|
68
|
+
writeDocument: (input) => client.writeDocument.mutate(input),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Creates a typed tRPC proxy client for the mdkit router at `url`.
|
|
73
|
+
*/
|
|
74
|
+
export const createMdKitTrpcClient = ({
|
|
75
|
+
url,
|
|
76
|
+
}: CreateMdKitTrpcClientAdapterOptions) =>
|
|
77
|
+
createTRPCProxyClient<MdKitTrpcRouter>({
|
|
78
|
+
links: [
|
|
79
|
+
httpBatchLink({
|
|
80
|
+
url,
|
|
81
|
+
}),
|
|
82
|
+
],
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
export const createMdKitTrpcClientAdapter = ({
|
|
86
|
+
url,
|
|
87
|
+
}: CreateMdKitTrpcClientAdapterOptions) =>
|
|
88
|
+
createMdKitTrpcAdapter({
|
|
89
|
+
client: createMdKitTrpcClient({ url }),
|
|
90
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { initTRPC } from "@trpc/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import type { MdKitTransportStore } from "./store";
|
|
4
|
+
|
|
5
|
+
const t = initTRPC.create();
|
|
6
|
+
|
|
7
|
+
const documentInput = z.object({
|
|
8
|
+
documentId: z.string(),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const versionInput = z.object({
|
|
12
|
+
documentId: z.string(),
|
|
13
|
+
versionId: z.string(),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const versionToken = z.union([z.string(), z.number()]).nullable();
|
|
17
|
+
|
|
18
|
+
const writeInput = z.object({
|
|
19
|
+
baseVersion: versionToken,
|
|
20
|
+
content: z.string(),
|
|
21
|
+
documentId: z.string(),
|
|
22
|
+
force: z.boolean().optional(),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Builds a tRPC router exposing document read/write/resync, checkpoint
|
|
27
|
+
* list/read, and restore over a transport store (typically from
|
|
28
|
+
* `createMdKitBackend`). Mount it standalone or nested in an app router.
|
|
29
|
+
*/
|
|
30
|
+
export const createMdKitTrpcRouter = (store: MdKitTransportStore) =>
|
|
31
|
+
t.router({
|
|
32
|
+
listDocumentVersions: t.procedure
|
|
33
|
+
.input(documentInput)
|
|
34
|
+
.query(async ({ input }) => ({
|
|
35
|
+
versions: await (store.listDocumentVersions?.(input.documentId) ?? []),
|
|
36
|
+
})),
|
|
37
|
+
readDocument: t.procedure
|
|
38
|
+
.input(documentInput)
|
|
39
|
+
.query(({ input }) => store.readDocument(input.documentId)),
|
|
40
|
+
readDocumentVersion: t.procedure.input(versionInput).query(
|
|
41
|
+
({ input }) =>
|
|
42
|
+
store.readDocumentVersion?.({
|
|
43
|
+
documentId: input.documentId,
|
|
44
|
+
versionId: input.versionId,
|
|
45
|
+
}) ?? null,
|
|
46
|
+
),
|
|
47
|
+
resyncDocument: t.procedure
|
|
48
|
+
.input(documentInput)
|
|
49
|
+
.mutation(({ input }) =>
|
|
50
|
+
(store.resyncDocument ?? store.readDocument)(input.documentId),
|
|
51
|
+
),
|
|
52
|
+
restoreDocumentVersion: t.procedure
|
|
53
|
+
.input(versionInput)
|
|
54
|
+
.mutation(({ input }) => {
|
|
55
|
+
if (!store.restoreDocumentVersion) {
|
|
56
|
+
throw new Error("Version restore is not supported");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return store.restoreDocumentVersion(input);
|
|
60
|
+
}),
|
|
61
|
+
writeDocument: t.procedure
|
|
62
|
+
.input(writeInput)
|
|
63
|
+
.mutation(({ input }) => store.writeDocument(input)),
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
export type MdKitTrpcRouter = ReturnType<typeof createMdKitTrpcRouter>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export {
|
|
2
|
+
createMdKitTrpcAdapter,
|
|
3
|
+
createMdKitTrpcClient,
|
|
4
|
+
createMdKitTrpcClientAdapter,
|
|
5
|
+
} from "../transport/trpcClient";
|
|
6
|
+
export type {
|
|
7
|
+
CreateMdKitTrpcAdapterOptions,
|
|
8
|
+
CreateMdKitTrpcClientAdapterOptions,
|
|
9
|
+
MdKitTrpcClient,
|
|
10
|
+
} from "../transport/trpcClient";
|
|
11
|
+
export type { MdKitTrpcRouter } from "../transport/trpcServer";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { createMdKitBackend } from "../transport/backend";
|
|
2
|
+
export { createMdKitTrpcRouter } from "../transport/trpcServer";
|
|
3
|
+
export type {
|
|
4
|
+
CreateMdKitBackendOptions,
|
|
5
|
+
MdKitBackendStore,
|
|
6
|
+
MdKitCreateCheckpointInput,
|
|
7
|
+
} from "../transport/backend";
|
|
8
|
+
export type { MdKitTrpcRouter } from "../transport/trpcServer";
|
|
9
|
+
export type {
|
|
10
|
+
MdKitRestoreDocumentVersionInput,
|
|
11
|
+
MdKitTransportStore,
|
|
12
|
+
} from "../transport/store";
|
package/src/trpc.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export {
|
|
2
|
+
createMdKitTrpcAdapter,
|
|
3
|
+
createMdKitTrpcClient,
|
|
4
|
+
createMdKitTrpcClientAdapter,
|
|
5
|
+
} from "./transport/trpcClient";
|
|
6
|
+
export type {
|
|
7
|
+
CreateMdKitTrpcAdapterOptions,
|
|
8
|
+
CreateMdKitTrpcClientAdapterOptions,
|
|
9
|
+
MdKitTrpcClient,
|
|
10
|
+
} from "./transport/trpcClient";
|
|
11
|
+
export type { MdKitTrpcRouter } from "./transport/trpcServer";
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import type { MdKitDocumentVersionDetail } from "../document/documentTypes";
|
|
3
|
+
import type { MdKitDocumentVersionsController } from "./useMdKitDocumentVersions";
|
|
4
|
+
import { joinClassNames } from "../ui/joinClassNames";
|
|
5
|
+
|
|
6
|
+
export type VersionHistoryPanelProps = {
|
|
7
|
+
className?: string;
|
|
8
|
+
controller: MdKitDocumentVersionsController;
|
|
9
|
+
onRestoreVersion?: (
|
|
10
|
+
version: MdKitDocumentVersionDetail,
|
|
11
|
+
) => Promise<void> | void;
|
|
12
|
+
title?: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const getVersionLabel = (
|
|
16
|
+
version: Pick<MdKitDocumentVersionDetail, "id" | "label" | "version">,
|
|
17
|
+
) => {
|
|
18
|
+
if (version.label) {
|
|
19
|
+
return version.label;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (version.version != null) {
|
|
23
|
+
return `Version ${String(version.version)}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return version.id.slice(0, 8);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Renders checkpoint history from {@link useMdKitDocumentVersions} and invokes
|
|
31
|
+
* the host's restore handler for a chosen checkpoint.
|
|
32
|
+
*/
|
|
33
|
+
export const VersionHistoryPanel = ({
|
|
34
|
+
className,
|
|
35
|
+
controller,
|
|
36
|
+
onRestoreVersion,
|
|
37
|
+
title = "Version history",
|
|
38
|
+
}: VersionHistoryPanelProps) => {
|
|
39
|
+
const [isRestoring, setIsRestoring] = useState(false);
|
|
40
|
+
|
|
41
|
+
const restoreSelectedVersion = async () => {
|
|
42
|
+
if (!controller.selectedVersion || !onRestoreVersion) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
setIsRestoring(true);
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
await onRestoreVersion(controller.selectedVersion);
|
|
50
|
+
} finally {
|
|
51
|
+
setIsRestoring(false);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<aside className={joinClassNames("mp-lb-mdkit-version-history-panel", className)}>
|
|
57
|
+
<div className="mp-lb-mdkit-version-history-header">
|
|
58
|
+
<div>
|
|
59
|
+
<h2 className="mp-lb-mdkit-version-history-title">{title}</h2>
|
|
60
|
+
<p className="mp-lb-mdkit-version-history-subtitle">
|
|
61
|
+
Browse saved revisions and restore one when you need it.
|
|
62
|
+
</p>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
{!controller.hasVersioning ? (
|
|
66
|
+
<div className="mp-lb-mdkit-version-history-empty">
|
|
67
|
+
This adapter does not expose version history.
|
|
68
|
+
</div>
|
|
69
|
+
) : null}
|
|
70
|
+
{controller.error ? (
|
|
71
|
+
<div className="mp-lb-mdkit-version-history-error">{controller.error}</div>
|
|
72
|
+
) : null}
|
|
73
|
+
{controller.hasVersioning ? (
|
|
74
|
+
<div className="mp-lb-mdkit-version-history-layout">
|
|
75
|
+
<div className="mp-lb-mdkit-version-history-list" role="list">
|
|
76
|
+
{controller.versions.length === 0 ? (
|
|
77
|
+
<div className="mp-lb-mdkit-version-history-empty">
|
|
78
|
+
No revisions have been recorded for this document yet.
|
|
79
|
+
</div>
|
|
80
|
+
) : (
|
|
81
|
+
controller.versions.map((version) => (
|
|
82
|
+
<button
|
|
83
|
+
key={version.id}
|
|
84
|
+
type="button"
|
|
85
|
+
className={joinClassNames(
|
|
86
|
+
"mp-lb-mdkit-version-history-item",
|
|
87
|
+
controller.selectedVersionId === version.id &&
|
|
88
|
+
"mp-lb-mdkit-version-history-item-active",
|
|
89
|
+
)}
|
|
90
|
+
onClick={() => void controller.openVersion(version.id)}
|
|
91
|
+
>
|
|
92
|
+
<span className="mp-lb-mdkit-version-history-item-title">
|
|
93
|
+
{getVersionLabel(version)}
|
|
94
|
+
</span>
|
|
95
|
+
<span className="mp-lb-mdkit-version-history-item-meta">
|
|
96
|
+
{new Date(version.createdAt).toLocaleString()}
|
|
97
|
+
</span>
|
|
98
|
+
</button>
|
|
99
|
+
))
|
|
100
|
+
)}
|
|
101
|
+
</div>
|
|
102
|
+
<div className="mp-lb-mdkit-version-history-preview">
|
|
103
|
+
{controller.selectedVersion ? (
|
|
104
|
+
<>
|
|
105
|
+
<div className="mp-lb-mdkit-version-history-preview-header">
|
|
106
|
+
<div>
|
|
107
|
+
<h3 className="mp-lb-mdkit-version-history-preview-title">
|
|
108
|
+
{getVersionLabel(controller.selectedVersion)}
|
|
109
|
+
</h3>
|
|
110
|
+
<p className="mp-lb-mdkit-version-history-item-meta">
|
|
111
|
+
{new Date(
|
|
112
|
+
controller.selectedVersion.createdAt,
|
|
113
|
+
).toLocaleString()}
|
|
114
|
+
</p>
|
|
115
|
+
</div>
|
|
116
|
+
{onRestoreVersion ? (
|
|
117
|
+
<button
|
|
118
|
+
type="button"
|
|
119
|
+
className="mp-lb-mdkit-panel-primary-action"
|
|
120
|
+
disabled={isRestoring}
|
|
121
|
+
onClick={() => void restoreSelectedVersion()}
|
|
122
|
+
>
|
|
123
|
+
{isRestoring ? "Restoring..." : "Restore"}
|
|
124
|
+
</button>
|
|
125
|
+
) : null}
|
|
126
|
+
</div>
|
|
127
|
+
<pre className="mp-lb-mdkit-version-history-code">
|
|
128
|
+
{controller.selectedVersion.content}
|
|
129
|
+
</pre>
|
|
130
|
+
</>
|
|
131
|
+
) : (
|
|
132
|
+
<div className="mp-lb-mdkit-version-history-empty">
|
|
133
|
+
Select a saved revision to preview it here.
|
|
134
|
+
</div>
|
|
135
|
+
)}
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
) : null}
|
|
139
|
+
{controller.isLoading ? (
|
|
140
|
+
<div className="mp-lb-mdkit-version-history-meta">
|
|
141
|
+
Loading version data...
|
|
142
|
+
</div>
|
|
143
|
+
) : null}
|
|
144
|
+
</aside>
|
|
145
|
+
);
|
|
146
|
+
};
|