@mp-lb/mdkit 0.0.1-main.2.1

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 (125) hide show
  1. package/README.md +132 -0
  2. package/dist/collaboration/useMdKitCollaboration.d.ts +10 -0
  3. package/dist/collaboration/useMdKitCollaboration.js +90 -0
  4. package/dist/core/documentEngine.d.ts +38 -0
  5. package/dist/core/documentEngine.js +95 -0
  6. package/dist/core/documentEngine.test.d.ts +1 -0
  7. package/dist/core/documentEngine.test.js +119 -0
  8. package/dist/core/index.d.ts +3 -0
  9. package/dist/core/index.js +1 -0
  10. package/dist/document/MdKitConflictPanel.d.ts +7 -0
  11. package/dist/document/MdKitConflictPanel.js +41 -0
  12. package/dist/document/MdKitDocumentToolbar.d.ts +13 -0
  13. package/dist/document/MdKitDocumentToolbar.js +48 -0
  14. package/dist/document/documentTypes.d.ts +57 -0
  15. package/dist/document/documentTypes.js +1 -0
  16. package/dist/document/useMdKitDocument.d.ts +33 -0
  17. package/dist/document/useMdKitDocument.js +396 -0
  18. package/dist/document/useMdKitDocument.test.d.ts +1 -0
  19. package/dist/document/useMdKitDocument.test.js +151 -0
  20. package/dist/fastify.d.ts +3 -0
  21. package/dist/fastify.js +1 -0
  22. package/dist/index.d.ts +23 -0
  23. package/dist/index.js +11 -0
  24. package/dist/markdown/MarkdownBubbleMenu.d.ts +6 -0
  25. package/dist/markdown/MarkdownBubbleMenu.js +29 -0
  26. package/dist/markdown/MdKitEditor.d.ts +25 -0
  27. package/dist/markdown/MdKitEditor.js +7 -0
  28. package/dist/markdown/MdKitEditor.test.d.ts +1 -0
  29. package/dist/markdown/MdKitEditor.test.js +126 -0
  30. package/dist/markdown/TiptapMarkdownSurface.d.ts +23 -0
  31. package/dist/markdown/TiptapMarkdownSurface.js +430 -0
  32. package/dist/markdown/editorDebug.d.ts +5 -0
  33. package/dist/markdown/editorDebug.js +1 -0
  34. package/dist/markdown/markdownFenceRanges.d.ts +6 -0
  35. package/dist/markdown/markdownFenceRanges.js +41 -0
  36. package/dist/markdown/normalizeMarkdownSerialization.d.ts +1 -0
  37. package/dist/markdown/normalizeMarkdownSerialization.js +34 -0
  38. package/dist/markdown/normalizeMarkdownSerialization.test.d.ts +1 -0
  39. package/dist/markdown/normalizeMarkdownSerialization.test.js +16 -0
  40. package/dist/markdown/prepareMarkdownForEditorHydration.d.ts +1 -0
  41. package/dist/markdown/prepareMarkdownForEditorHydration.js +12 -0
  42. package/dist/markdown/prepareMarkdownForEditorHydration.test.d.ts +1 -0
  43. package/dist/markdown/prepareMarkdownForEditorHydration.test.js +13 -0
  44. package/dist/markdown/preserveMarkdownWhitespace.d.ts +1 -0
  45. package/dist/markdown/preserveMarkdownWhitespace.js +86 -0
  46. package/dist/markdown/preserveMarkdownWhitespace.test.d.ts +1 -0
  47. package/dist/markdown/preserveMarkdownWhitespace.test.js +25 -0
  48. package/dist/test/setup.d.ts +1 -0
  49. package/dist/test/setup.js +13 -0
  50. package/dist/theme/MdKitThemeEditor.d.ts +8 -0
  51. package/dist/theme/MdKitThemeEditor.js +13 -0
  52. package/dist/theme/editorTheme.d.ts +20 -0
  53. package/dist/theme/editorTheme.js +47 -0
  54. package/dist/transport/fastify.d.ts +7 -0
  55. package/dist/transport/fastify.js +19 -0
  56. package/dist/transport/http.d.ts +43 -0
  57. package/dist/transport/http.js +80 -0
  58. package/dist/transport/index.d.ts +5 -0
  59. package/dist/transport/index.js +2 -0
  60. package/dist/transport/rest.d.ts +6 -0
  61. package/dist/transport/rest.js +34 -0
  62. package/dist/transport/store.d.ts +21 -0
  63. package/dist/transport/store.js +1 -0
  64. package/dist/transport/trpcClient.d.ts +81 -0
  65. package/dist/transport/trpcClient.js +21 -0
  66. package/dist/transport/trpcServer.d.ts +72 -0
  67. package/dist/transport/trpcServer.js +45 -0
  68. package/dist/trpc/client.d.ts +3 -0
  69. package/dist/trpc/client.js +1 -0
  70. package/dist/trpc/server.d.ts +3 -0
  71. package/dist/trpc/server.js +1 -0
  72. package/dist/trpc.d.ts +3 -0
  73. package/dist/trpc.js +1 -0
  74. package/dist/ui/joinClassNames.d.ts +1 -0
  75. package/dist/ui/joinClassNames.js +1 -0
  76. package/dist/versioning/VersionHistoryPanel.d.ts +9 -0
  77. package/dist/versioning/VersionHistoryPanel.js +29 -0
  78. package/dist/versioning/useMdKitDocumentVersions.d.ts +16 -0
  79. package/dist/versioning/useMdKitDocumentVersions.js +88 -0
  80. package/dist/versioning/useMdKitDocumentVersions.test.d.ts +1 -0
  81. package/dist/versioning/useMdKitDocumentVersions.test.js +41 -0
  82. package/docs/.vitepress/config.ts +34 -0
  83. package/docs/.vitepress/dist/404.html +22 -0
  84. package/docs/.vitepress/dist/api.html +120 -0
  85. package/docs/.vitepress/dist/architecture.html +25 -0
  86. package/docs/.vitepress/dist/assets/api.md.asncK3PQ.js +96 -0
  87. package/docs/.vitepress/dist/assets/api.md.asncK3PQ.lean.js +1 -0
  88. package/docs/.vitepress/dist/assets/app.BQvrHyG0.js +1 -0
  89. package/docs/.vitepress/dist/assets/architecture.md.BHQLarmZ.js +1 -0
  90. package/docs/.vitepress/dist/assets/architecture.md.BHQLarmZ.lean.js +1 -0
  91. package/docs/.vitepress/dist/assets/chunks/framework.RRduUuAx.js +19 -0
  92. package/docs/.vitepress/dist/assets/chunks/theme.CkCo6Nk1.js +1 -0
  93. package/docs/.vitepress/dist/assets/index.md.CITl-897.js +137 -0
  94. package/docs/.vitepress/dist/assets/index.md.CITl-897.lean.js +1 -0
  95. package/docs/.vitepress/dist/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
  96. package/docs/.vitepress/dist/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
  97. package/docs/.vitepress/dist/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
  98. package/docs/.vitepress/dist/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
  99. package/docs/.vitepress/dist/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
  100. package/docs/.vitepress/dist/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
  101. package/docs/.vitepress/dist/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
  102. package/docs/.vitepress/dist/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
  103. package/docs/.vitepress/dist/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
  104. package/docs/.vitepress/dist/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
  105. package/docs/.vitepress/dist/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
  106. package/docs/.vitepress/dist/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
  107. package/docs/.vitepress/dist/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
  108. package/docs/.vitepress/dist/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
  109. package/docs/.vitepress/dist/assets/shadcn.md.C3idOo2N.js +57 -0
  110. package/docs/.vitepress/dist/assets/shadcn.md.C3idOo2N.lean.js +1 -0
  111. package/docs/.vitepress/dist/assets/style.BtrGaL3i.css +1 -0
  112. package/docs/.vitepress/dist/assets/styling.md.B2C6kVFa.js +91 -0
  113. package/docs/.vitepress/dist/assets/styling.md.B2C6kVFa.lean.js +1 -0
  114. package/docs/.vitepress/dist/hashmap.json +1 -0
  115. package/docs/.vitepress/dist/index.html +161 -0
  116. package/docs/.vitepress/dist/shadcn.html +81 -0
  117. package/docs/.vitepress/dist/styling.html +115 -0
  118. package/docs/.vitepress/dist/vp-icons.css +1 -0
  119. package/docs/api.md +343 -0
  120. package/docs/architecture.md +67 -0
  121. package/docs/index.md +244 -0
  122. package/docs/shadcn.md +118 -0
  123. package/docs/styling.md +247 -0
  124. package/package.json +105 -0
  125. package/src/styles.css +676 -0
@@ -0,0 +1,34 @@
1
+ const placeholderLinePattern = /^[ \t]*(?: |\u00a0)[ \t]*$/;
2
+ const normalizePlaceholderParagraphs = (markdown) => {
3
+ const lines = markdown.split("\n");
4
+ const normalizedLines = [];
5
+ let index = 0;
6
+ while (index < lines.length) {
7
+ const line = lines[index] ?? "";
8
+ if (line !== "" && !placeholderLinePattern.test(line)) {
9
+ normalizedLines.push(line);
10
+ index += 1;
11
+ continue;
12
+ }
13
+ const start = index;
14
+ let placeholderCount = 0;
15
+ while (index < lines.length &&
16
+ ((lines[index] ?? "") === "" ||
17
+ placeholderLinePattern.test(lines[index] ?? ""))) {
18
+ if (placeholderLinePattern.test(lines[index] ?? "")) {
19
+ placeholderCount += 1;
20
+ }
21
+ index += 1;
22
+ }
23
+ if (placeholderCount === 0) {
24
+ normalizedLines.push(...lines.slice(start, index));
25
+ continue;
26
+ }
27
+ normalizedLines.push(...Array.from({ length: placeholderCount + 1 }, () => ""));
28
+ }
29
+ const normalizedMarkdown = normalizedLines.join("\n");
30
+ return placeholderLinePattern.test(normalizedMarkdown.trim())
31
+ ? ""
32
+ : normalizedMarkdown;
33
+ };
34
+ export const normalizeMarkdownSerialization = (markdown) => normalizePlaceholderParagraphs(markdown.replace(/^([ \t]*(?:[-*+]|\d+[.)])[ \t]+)(?:&nbsp;|\u00a0)[ \t]*$/gm, "$1"));
@@ -0,0 +1,16 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { normalizeMarkdownSerialization } from "./normalizeMarkdownSerialization";
3
+ describe("normalizeMarkdownSerialization", () => {
4
+ it("removes TipTap non-breaking-space placeholders from empty list items", () => {
5
+ expect(normalizeMarkdownSerialization("- first\n- &nbsp;\n- third\n\n1. first\n2. \u00a0")).toBe("- first\n- \n- third\n\n1. first\n2. ");
6
+ });
7
+ it("does not remove non-breaking-space text outside empty list items", () => {
8
+ expect(normalizeMarkdownSerialization("Paragraph &nbsp;\n\n- real")).toBe("Paragraph &nbsp;\n\n- real");
9
+ });
10
+ it("removes standalone TipTap non-breaking-space placeholder lines", () => {
11
+ expect(normalizeMarkdownSerialization("- first\n\n&nbsp;\n\n\u00a0")).toBe("- first\n\n\n");
12
+ });
13
+ it("normalizes hydrated empty paragraphs back to the original newline run", () => {
14
+ expect(normalizeMarkdownSerialization("Before\n\n&nbsp;\n\n&nbsp;\n\n&nbsp;\n\nAfter")).toBe("Before\n\n\n\n\nAfter");
15
+ });
16
+ });
@@ -0,0 +1 @@
1
+ export declare const prepareMarkdownForEditorHydration: (markdown: string) => string;
@@ -0,0 +1,12 @@
1
+ import { getMarkdownFenceRanges, isInsideMarkdownFence, } from "./markdownFenceRanges";
2
+ const expandedBlankLineRunPattern = /\n{3,}/g;
3
+ export const prepareMarkdownForEditorHydration = (markdown) => {
4
+ const fenceRanges = getMarkdownFenceRanges(markdown);
5
+ return markdown.replace(expandedBlankLineRunPattern, (newlineRun, offset) => {
6
+ if (isInsideMarkdownFence(offset, fenceRanges)) {
7
+ return newlineRun;
8
+ }
9
+ const emptyParagraphs = Array.from({ length: newlineRun.length - 2 }, () => "&nbsp;").join("\n\n");
10
+ return `\n\n${emptyParagraphs}\n\n`;
11
+ });
12
+ };
@@ -0,0 +1,13 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { prepareMarkdownForEditorHydration } from "./prepareMarkdownForEditorHydration";
3
+ describe("prepareMarkdownForEditorHydration", () => {
4
+ it("converts expanded blank-line runs into empty paragraphs for TipTap", () => {
5
+ expect(prepareMarkdownForEditorHydration("Before\n\n\n\nAfter")).toBe("Before\n\n&nbsp;\n\n&nbsp;\n\nAfter");
6
+ });
7
+ it("keeps normal block separators unchanged", () => {
8
+ expect(prepareMarkdownForEditorHydration("Before\n\nAfter")).toBe("Before\n\nAfter");
9
+ });
10
+ it("does not convert blank lines inside fenced code blocks", () => {
11
+ expect(prepareMarkdownForEditorHydration("Before\n\n```ts\nconst one = 1;\n\n\nconst two = 2;\n```\n\n\nAfter")).toBe("Before\n\n```ts\nconst one = 1;\n\n\nconst two = 2;\n```\n\n&nbsp;\n\nAfter");
12
+ });
13
+ });
@@ -0,0 +1 @@
1
+ export declare const preserveMarkdownWhitespace: (previousRawMarkdown: string, nextSerializedMarkdown: string) => string;
@@ -0,0 +1,86 @@
1
+ import { getMarkdownFenceRanges, isInsideMarkdownFence, } from "./markdownFenceRanges";
2
+ const tokenizeMarkdownBlocks = (markdown) => {
3
+ const ranges = getMarkdownFenceRanges(markdown);
4
+ const tokens = [];
5
+ const separatorPattern = /\n{2,}/g;
6
+ let contentStart = 0;
7
+ let match;
8
+ while ((match = separatorPattern.exec(markdown))) {
9
+ const separatorStart = match.index;
10
+ const separator = match[0] ?? "";
11
+ if (isInsideMarkdownFence(separatorStart, ranges)) {
12
+ continue;
13
+ }
14
+ tokens.push({
15
+ content: markdown.slice(contentStart, separatorStart),
16
+ separatorAfter: separator,
17
+ });
18
+ contentStart = separatorStart + separator.length;
19
+ }
20
+ tokens.push({
21
+ content: markdown.slice(contentStart),
22
+ separatorAfter: "",
23
+ });
24
+ return tokens;
25
+ };
26
+ const blockPairKey = (left, right) => `${left.content.trim()}\u0000${right.content.trim()}`;
27
+ const leadingNewlineRunPattern = /^\n+/;
28
+ const trailingNewlineRunPattern = /\n+$/;
29
+ const preserveOuterBlankLines = (previousRawMarkdown, nextSerializedMarkdown) => {
30
+ const previousLeading = previousRawMarkdown.match(leadingNewlineRunPattern);
31
+ const nextLeading = nextSerializedMarkdown.match(leadingNewlineRunPattern);
32
+ const previousTrailing = previousRawMarkdown.match(trailingNewlineRunPattern);
33
+ const nextTrailing = nextSerializedMarkdown.match(trailingNewlineRunPattern);
34
+ const isBodyUnchanged = previousRawMarkdown.trim() === nextSerializedMarkdown.trim();
35
+ let nextMarkdown = nextSerializedMarkdown;
36
+ if (isBodyUnchanged &&
37
+ previousLeading &&
38
+ previousLeading[0].length > (nextLeading?.[0].length ?? 0)) {
39
+ nextMarkdown = `${previousLeading[0]}${nextMarkdown.trimStart()}`;
40
+ }
41
+ if (isBodyUnchanged &&
42
+ previousTrailing &&
43
+ previousTrailing[0].length > (nextTrailing?.[0].length ?? 0)) {
44
+ nextMarkdown = `${nextMarkdown.trimEnd()}${previousTrailing[0]}`;
45
+ }
46
+ return nextMarkdown;
47
+ };
48
+ export const preserveMarkdownWhitespace = (previousRawMarkdown, nextSerializedMarkdown) => {
49
+ const outerPreservedMarkdown = preserveOuterBlankLines(previousRawMarkdown, nextSerializedMarkdown);
50
+ if (previousRawMarkdown === outerPreservedMarkdown ||
51
+ !previousRawMarkdown.includes("\n\n") ||
52
+ !outerPreservedMarkdown.includes("\n\n")) {
53
+ return outerPreservedMarkdown;
54
+ }
55
+ const previousTokens = tokenizeMarkdownBlocks(previousRawMarkdown);
56
+ const nextTokens = tokenizeMarkdownBlocks(outerPreservedMarkdown);
57
+ if (previousTokens.length < 2 || nextTokens.length < 2) {
58
+ return outerPreservedMarkdown;
59
+ }
60
+ const preservedSeparators = new Map();
61
+ for (let index = 0; index < previousTokens.length - 1; index += 1) {
62
+ const left = previousTokens[index];
63
+ const right = previousTokens[index + 1];
64
+ if (!left || !right || left.separatorAfter.length <= 2) {
65
+ continue;
66
+ }
67
+ preservedSeparators.set(blockPairKey(left, right), left.separatorAfter);
68
+ }
69
+ if (preservedSeparators.size === 0) {
70
+ return outerPreservedMarkdown;
71
+ }
72
+ return nextTokens
73
+ .map((token, index) => {
74
+ const nextToken = nextTokens[index + 1];
75
+ if (!nextToken || !token.separatorAfter) {
76
+ return token.content;
77
+ }
78
+ const preservedSeparator = preservedSeparators.get(blockPairKey(token, nextToken));
79
+ const separator = preservedSeparator &&
80
+ preservedSeparator.length > token.separatorAfter.length
81
+ ? preservedSeparator
82
+ : token.separatorAfter;
83
+ return `${token.content}${separator}`;
84
+ })
85
+ .join("");
86
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,25 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { preserveMarkdownWhitespace } from "./preserveMarkdownWhitespace";
3
+ describe("preserveMarkdownWhitespace", () => {
4
+ it("preserves expanded blank-line runs between unchanged blocks", () => {
5
+ expect(preserveMarkdownWhitespace("# Title\n\n\nFirst paragraph.\n\n\n\nSecond paragraph.", "# Title\n\nFirst paragraph.\n\nSecond paragraph.")).toBe("# Title\n\n\nFirst paragraph.\n\n\n\nSecond paragraph.");
6
+ });
7
+ it("preserves blank-line runs between unchanged list and paragraph blocks", () => {
8
+ expect(preserveMarkdownWhitespace("- first\n- second\n\n\nParagraph.", "- first\n- second\n\nParagraph.")).toBe("- first\n- second\n\n\nParagraph.");
9
+ });
10
+ it("preserves blank-line runs between unchanged blockquote and table blocks", () => {
11
+ expect(preserveMarkdownWhitespace("> Quoted text.\n\n\n| A | B |\n| - | - |\n| 1 | 2 |", "> Quoted text.\n\n| A | B |\n| - | - |\n| 1 | 2 |")).toBe("> Quoted text.\n\n\n| A | B |\n| - | - |\n| 1 | 2 |");
12
+ });
13
+ it("preserves leading and trailing blank-line runs when the body is unchanged", () => {
14
+ expect(preserveMarkdownWhitespace("\n\n# Title\n\nParagraph.\n\n\n", "# Title\n\nParagraph.")).toBe("\n\n# Title\n\nParagraph.\n\n\n");
15
+ });
16
+ it("keeps serialized markdown when adjacent block content changed", () => {
17
+ expect(preserveMarkdownWhitespace("# Title\n\n\nFirst paragraph.", "# Title\n\nChanged paragraph.")).toBe("# Title\n\nChanged paragraph.");
18
+ });
19
+ it("does not preserve separators from inside fenced code blocks", () => {
20
+ expect(preserveMarkdownWhitespace("```ts\nconst a = 1;\n\n\nconst b = 2;\n```\n\n\nAfter.", "```ts\nconst a = 1;\n\nconst b = 2;\n```\n\nAfter.")).toBe("```ts\nconst a = 1;\n\nconst b = 2;\n```\n\nAfter.");
21
+ });
22
+ it("preserves separators around unchanged fenced code blocks", () => {
23
+ expect(preserveMarkdownWhitespace("Before.\n\n\n```ts\nconst a = 1;\n\nconst b = 2;\n```\n\n\nAfter.", "Before.\n\n```ts\nconst a = 1;\n\nconst b = 2;\n```\n\nAfter.")).toBe("Before.\n\n\n```ts\nconst a = 1;\n\nconst b = 2;\n```\n\n\nAfter.");
24
+ });
25
+ });
@@ -0,0 +1 @@
1
+ import "@testing-library/jest-dom/vitest";
@@ -0,0 +1,13 @@
1
+ import "@testing-library/jest-dom/vitest";
2
+ if (!window.matchMedia) {
3
+ window.matchMedia = () => ({
4
+ addEventListener: () => undefined,
5
+ addListener: () => undefined,
6
+ dispatchEvent: () => false,
7
+ matches: false,
8
+ media: "",
9
+ onchange: null,
10
+ removeEventListener: () => undefined,
11
+ removeListener: () => undefined,
12
+ });
13
+ }
@@ -0,0 +1,8 @@
1
+ import type { MdKitEditorTheme } from "./editorTheme";
2
+ export type MdKitThemeEditorProps = {
3
+ className?: string;
4
+ onChange: (theme: MdKitEditorTheme) => void;
5
+ onReset?: () => void;
6
+ theme: MdKitEditorTheme;
7
+ };
8
+ export declare const MdKitThemeEditor: ({ className, onChange, onReset, theme, }: MdKitThemeEditorProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export const MdKitThemeEditor = ({ className, onChange, onReset, theme, }) => {
3
+ const updateTheme = (patch) => {
4
+ onChange({
5
+ ...theme,
6
+ ...patch,
7
+ });
8
+ };
9
+ return (_jsxs("div", { className: ["hsk-theme-editor", className].filter(Boolean).join(" "), children: [_jsxs("label", { children: [_jsx("span", { children: "Background" }), _jsx("input", { type: "color", value: theme.background, onChange: (event) => updateTheme({ background: event.target.value }) })] }), _jsxs("label", { children: [_jsx("span", { children: "Text" }), _jsx("input", { type: "color", value: theme.foreground, onChange: (event) => updateTheme({ foreground: event.target.value }) })] }), _jsxs("label", { children: [_jsx("span", { children: "Link" }), _jsx("input", { type: "color", value: theme.link, onChange: (event) => updateTheme({ link: event.target.value }) })] }), _jsxs("label", { children: [_jsx("span", { children: "Code" }), _jsx("input", { type: "color", value: theme.codeBackground, onChange: (event) => updateTheme({
10
+ codeBackground: event.target.value,
11
+ muted: event.target.value,
12
+ }) })] }), _jsxs("label", { children: [_jsx("span", { children: "Font size" }), _jsx("input", { type: "range", min: "13", max: "22", value: Number.parseInt(theme.fontSize, 10), onChange: (event) => updateTheme({ fontSize: `${event.target.value}px` }) })] }), _jsxs("label", { children: [_jsx("span", { children: "Font" }), _jsxs("select", { value: theme.fontFamily, onChange: (event) => updateTheme({ fontFamily: event.target.value }), children: [_jsx("option", { value: "inherit", children: "App default" }), _jsx("option", { value: "ui-serif, Georgia, Cambria, serif", children: "Serif" }), _jsx("option", { value: "ui-sans-serif, system-ui, sans-serif", children: "Sans" }), _jsx("option", { value: "ui-monospace, SFMono-Regular, Menlo, monospace", children: "Mono" })] })] }), _jsxs("label", { children: [_jsx("span", { children: "Line height" }), _jsx("input", { type: "range", min: "1.2", max: "2.2", step: "0.1", value: theme.lineHeight, onChange: (event) => updateTheme({ lineHeight: event.target.value }) })] }), _jsxs("label", { children: [_jsx("span", { children: "Padding" }), _jsx("input", { type: "range", min: "0", max: "32", value: Number.parseInt(theme.surfacePadding, 10), onChange: (event) => updateTheme({ surfacePadding: `${event.target.value}px` }) })] }), _jsxs("label", { children: [_jsx("span", { children: "Code radius" }), _jsx("input", { type: "range", min: "0", max: "16", value: Number.parseInt(theme.codeRadius, 10), onChange: (event) => updateTheme({ codeRadius: `${event.target.value}px` }) })] }), onReset ? (_jsx("button", { type: "button", onClick: onReset, children: "Reset style" })) : null] }));
13
+ };
@@ -0,0 +1,20 @@
1
+ import type { CSSProperties } from "react";
2
+ export type MdKitEditorTheme = {
3
+ background: string;
4
+ blockGap: string;
5
+ border: string;
6
+ codeBackground: string;
7
+ codeRadius: string;
8
+ fontFamily: string;
9
+ fontSize: string;
10
+ foreground: string;
11
+ lineHeight: string;
12
+ link: string;
13
+ muted: string;
14
+ mutedForeground: string;
15
+ surfacePadding: string;
16
+ };
17
+ export type MdKitEditorThemeStyle = CSSProperties & Record<`--hsk-${string}`, string>;
18
+ export declare const defaultMdKitEditorTheme: MdKitEditorTheme;
19
+ export declare const darkMdKitEditorTheme: MdKitEditorTheme;
20
+ export declare const createMdKitEditorThemeStyle: (theme: MdKitEditorTheme) => MdKitEditorThemeStyle;
@@ -0,0 +1,47 @@
1
+ export const defaultMdKitEditorTheme = {
2
+ background: "#ffffff",
3
+ blockGap: "0.75rem",
4
+ border: "#d8dee8",
5
+ codeBackground: "#eef1f4",
6
+ codeRadius: "0.35rem",
7
+ fontFamily: "inherit",
8
+ fontSize: "16px",
9
+ foreground: "#18212f",
10
+ lineHeight: "1.7",
11
+ link: "#4f46e5",
12
+ muted: "#eef1f4",
13
+ mutedForeground: "#5b6472",
14
+ surfacePadding: "1rem",
15
+ };
16
+ export const darkMdKitEditorTheme = {
17
+ background: "#0b1220",
18
+ blockGap: "0.75rem",
19
+ border: "#314158",
20
+ codeBackground: "#111827",
21
+ codeRadius: "0.35rem",
22
+ fontFamily: "inherit",
23
+ fontSize: "16px",
24
+ foreground: "#e5edf7",
25
+ lineHeight: "1.7",
26
+ link: "#38bdf8",
27
+ muted: "#172033",
28
+ mutedForeground: "#94a3b8",
29
+ surfacePadding: "1rem",
30
+ };
31
+ export const createMdKitEditorThemeStyle = (theme) => ({
32
+ "--hsk-background": theme.background,
33
+ "--hsk-block-gap": theme.blockGap,
34
+ "--hsk-border": theme.border,
35
+ "--hsk-code-background": theme.codeBackground,
36
+ "--hsk-code-radius": theme.codeRadius,
37
+ "--hsk-code-block-radius": theme.codeRadius,
38
+ "--hsk-font-family": theme.fontFamily,
39
+ "--hsk-font-size": theme.fontSize,
40
+ "--hsk-foreground": theme.foreground,
41
+ "--hsk-line-height": theme.lineHeight,
42
+ "--hsk-link": theme.link,
43
+ "--hsk-muted": theme.muted,
44
+ "--hsk-muted-foreground": theme.mutedForeground,
45
+ "--hsk-quote-border-color": theme.border,
46
+ "--hsk-surface-padding": theme.surfacePadding,
47
+ });
@@ -0,0 +1,7 @@
1
+ import type { FastifyInstance } from "fastify";
2
+ import type { MdKitTransportStore } from "./store";
3
+ export type RegisterMdKitFastifyOptions = {
4
+ prefix?: string;
5
+ store: MdKitTransportStore;
6
+ };
7
+ export declare const registerMdKitFastify: (app: FastifyInstance, { prefix, store }: RegisterMdKitFastifyOptions) => Promise<void>;
@@ -0,0 +1,19 @@
1
+ import { createMdKitHttpHandlers } from "./http";
2
+ const send = async (reply, response) => {
3
+ const { body, status } = await response;
4
+ return reply.status(status).send(body);
5
+ };
6
+ const toRequest = (request) => ({
7
+ body: request.body,
8
+ params: request.params,
9
+ query: request.query,
10
+ });
11
+ export const registerMdKitFastify = async (app, { prefix = "", store }) => {
12
+ const handlers = createMdKitHttpHandlers(store);
13
+ app.get(`${prefix}/documents`, (request, reply) => send(reply, handlers.readDocument(toRequest(request))));
14
+ app.put(`${prefix}/documents`, (request, reply) => send(reply, handlers.writeDocument(toRequest(request))));
15
+ app.post(`${prefix}/documents/resync`, (request, reply) => send(reply, handlers.resyncDocument(toRequest(request))));
16
+ app.get(`${prefix}/versions`, (request, reply) => send(reply, handlers.listDocumentVersions(toRequest(request))));
17
+ app.get(`${prefix}/versions/:versionId`, (request, reply) => send(reply, handlers.readDocumentVersion(toRequest(request))));
18
+ app.post(`${prefix}/versions/:versionId/restore`, (request, reply) => send(reply, handlers.restoreDocumentVersion(toRequest(request))));
19
+ };
@@ -0,0 +1,43 @@
1
+ import type { MdKitTransportStore } from "./store";
2
+ export type MdKitHttpRequest = {
3
+ body?: unknown;
4
+ params?: Record<string, unknown>;
5
+ query?: Record<string, unknown>;
6
+ };
7
+ export type MdKitHttpResponse<T = unknown> = {
8
+ body: T;
9
+ status: number;
10
+ };
11
+ export type MdKitHttpHandlers = ReturnType<typeof createMdKitHttpHandlers>;
12
+ export declare const createMdKitHttpHandlers: (store: MdKitTransportStore) => {
13
+ readDocument: (request: MdKitHttpRequest) => Promise<{
14
+ body: import("..").MdKitDocumentSnapshot;
15
+ status: number;
16
+ }>;
17
+ writeDocument: (request: MdKitHttpRequest) => Promise<{
18
+ body: import("..").MdKitDocumentWriteResult;
19
+ status: number;
20
+ }>;
21
+ resyncDocument: (request: MdKitHttpRequest) => Promise<{
22
+ body: import("..").MdKitDocumentSnapshot;
23
+ status: number;
24
+ }>;
25
+ listDocumentVersions: (request: MdKitHttpRequest) => Promise<{
26
+ body: {
27
+ versions: import("..").MdKitDocumentVersionSummary[];
28
+ };
29
+ status: number;
30
+ }>;
31
+ readDocumentVersion: (request: MdKitHttpRequest) => Promise<MdKitHttpResponse<{
32
+ error: string;
33
+ }> | {
34
+ readonly body: import("..").MdKitDocumentVersionDetail;
35
+ readonly status: 200;
36
+ }>;
37
+ restoreDocumentVersion: (request: MdKitHttpRequest) => Promise<MdKitHttpResponse<{
38
+ error: string;
39
+ }> | {
40
+ body: import("..").MdKitDocumentWriteResult;
41
+ status: number;
42
+ }>;
43
+ };
@@ -0,0 +1,80 @@
1
+ const readString = (source, key) => {
2
+ const value = source?.[key];
3
+ if (typeof value !== "string" || value.length === 0) {
4
+ throw new Error(`Missing mdkit ${key}`);
5
+ }
6
+ return value;
7
+ };
8
+ const readVersionToken = (value) => {
9
+ if (value === null ||
10
+ typeof value === "string" ||
11
+ typeof value === "number") {
12
+ return value;
13
+ }
14
+ throw new Error("Invalid mdkit baseVersion");
15
+ };
16
+ const readWriteInput = (request) => {
17
+ const body = request.body;
18
+ if (!body || typeof body !== "object") {
19
+ throw new Error("Missing mdkit write body");
20
+ }
21
+ const content = body.content;
22
+ if (typeof content !== "string") {
23
+ throw new Error("Invalid mdkit content");
24
+ }
25
+ return {
26
+ baseVersion: readVersionToken(body.baseVersion),
27
+ content,
28
+ documentId: readString(request.query, "documentId"),
29
+ force: body.force === true,
30
+ };
31
+ };
32
+ const notFound = (message) => ({
33
+ body: { error: message },
34
+ status: 404,
35
+ });
36
+ export const createMdKitHttpHandlers = (store) => ({
37
+ readDocument: async (request) => ({
38
+ body: await store.readDocument(readString(request.query, "documentId")),
39
+ status: 200,
40
+ }),
41
+ writeDocument: async (request) => {
42
+ const body = await store.writeDocument(readWriteInput(request));
43
+ return {
44
+ body,
45
+ status: "conflict" in body ? 409 : 200,
46
+ };
47
+ },
48
+ resyncDocument: async (request) => ({
49
+ body: await (store.resyncDocument ?? store.readDocument)(readString(request.query, "documentId")),
50
+ status: 200,
51
+ }),
52
+ listDocumentVersions: async (request) => ({
53
+ body: {
54
+ versions: await (store.listDocumentVersions?.(readString(request.query, "documentId")) ?? []),
55
+ },
56
+ status: 200,
57
+ }),
58
+ readDocumentVersion: async (request) => {
59
+ const version = await store.readDocumentVersion?.({
60
+ documentId: readString(request.query, "documentId"),
61
+ versionId: readString(request.params, "versionId"),
62
+ });
63
+ return version
64
+ ? { body: version, status: 200 }
65
+ : notFound("Version not found");
66
+ },
67
+ restoreDocumentVersion: async (request) => {
68
+ if (!store.restoreDocumentVersion) {
69
+ return notFound("Version restore is not supported");
70
+ }
71
+ const input = {
72
+ documentId: readString(request.query, "documentId"),
73
+ versionId: readString(request.params, "versionId"),
74
+ };
75
+ return {
76
+ body: await store.restoreDocumentVersion(input),
77
+ status: 200,
78
+ };
79
+ },
80
+ });
@@ -0,0 +1,5 @@
1
+ export { createMdKitHttpHandlers } from "./http";
2
+ export { createMdKitRestAdapter } from "./rest";
3
+ export type { MdKitHttpHandlers, MdKitHttpRequest, MdKitHttpResponse, } from "./http";
4
+ export type { CreateMdKitRestAdapterOptions } from "./rest";
5
+ export type { MdKitRestoreDocumentVersionInput, MdKitTransportStore, } from "./store";
@@ -0,0 +1,2 @@
1
+ export { createMdKitHttpHandlers } from "./http";
2
+ export { createMdKitRestAdapter } from "./rest";
@@ -0,0 +1,6 @@
1
+ import type { MdKitDocumentAdapter } from "../document/documentTypes";
2
+ export type CreateMdKitRestAdapterOptions = {
3
+ baseUrl: string;
4
+ fetch?: typeof fetch;
5
+ };
6
+ export declare const createMdKitRestAdapter: ({ baseUrl, fetch: fetchImpl, }: CreateMdKitRestAdapterOptions) => MdKitDocumentAdapter;
@@ -0,0 +1,34 @@
1
+ const trimTrailingSlash = (value) => value.replace(/\/+$/, "");
2
+ const documentQuery = (documentId) => `documentId=${encodeURIComponent(documentId)}`;
3
+ const readJson = async (response) => {
4
+ const body = (await response.json());
5
+ if (!response.ok && response.status !== 409) {
6
+ throw new Error(`MdKit REST request failed: ${response.status}`);
7
+ }
8
+ return body;
9
+ };
10
+ export const createMdKitRestAdapter = ({ baseUrl, fetch: fetchImpl = fetch, }) => {
11
+ const url = trimTrailingSlash(baseUrl);
12
+ return {
13
+ readDocument: async (documentId) => readJson(await fetchImpl(`${url}/documents?${documentQuery(documentId)}`)),
14
+ writeDocument: async (input) => readJson(await fetchImpl(`${url}/documents?${documentQuery(input.documentId)}`, {
15
+ body: JSON.stringify(input),
16
+ headers: { "Content-Type": "application/json" },
17
+ method: "PUT",
18
+ })),
19
+ resyncDocument: async (documentId) => readJson(await fetchImpl(`${url}/documents/resync?${documentQuery(documentId)}`, {
20
+ method: "POST",
21
+ })),
22
+ listDocumentVersions: async (documentId) => {
23
+ const body = await readJson(await fetchImpl(`${url}/versions?${documentQuery(documentId)}`));
24
+ return body.versions;
25
+ },
26
+ readDocumentVersion: async ({ documentId, versionId }) => {
27
+ const response = await fetchImpl(`${url}/versions/${encodeURIComponent(versionId)}?${documentQuery(documentId)}`);
28
+ if (response.status === 404) {
29
+ return null;
30
+ }
31
+ return readJson(response);
32
+ },
33
+ };
34
+ };
@@ -0,0 +1,21 @@
1
+ import type { MdKitDocumentSnapshot, MdKitDocumentVersionDetail, MdKitDocumentVersionSummary, MdKitDocumentWriteInput, MdKitDocumentWriteResult } from "../document/documentTypes";
2
+ export type MdKitTransportStore = {
3
+ readDocument(documentId: string): Promise<MdKitDocumentSnapshot> | MdKitDocumentSnapshot;
4
+ writeDocument(input: MdKitDocumentWriteInput): Promise<MdKitDocumentWriteResult> | MdKitDocumentWriteResult;
5
+ resyncDocument?(documentId: string): Promise<MdKitDocumentSnapshot> | MdKitDocumentSnapshot;
6
+ listDocumentVersions?(documentId: string): Promise<MdKitDocumentVersionSummary[]> | MdKitDocumentVersionSummary[];
7
+ readDocumentVersion?(input: {
8
+ documentId: string;
9
+ versionId: string;
10
+ }): Promise<MdKitDocumentVersionDetail | null> | MdKitDocumentVersionDetail | null;
11
+ restoreDocumentVersion?(input: {
12
+ documentId: string;
13
+ versionId: string;
14
+ }): Promise<MdKitDocumentWriteResult> | MdKitDocumentWriteResult;
15
+ readCollaborationState?(documentName: string): Promise<Uint8Array | null> | Uint8Array | null;
16
+ writeCollaborationState?(documentName: string, state: Uint8Array): Promise<void> | void;
17
+ };
18
+ export type MdKitRestoreDocumentVersionInput = {
19
+ documentId: string;
20
+ versionId: string;
21
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,81 @@
1
+ import { createTRPCProxyClient } from "@trpc/client";
2
+ import type { MdKitDocumentAdapter } from "../document/documentTypes";
3
+ import type { MdKitTrpcRouter } from "./trpcServer";
4
+ export type CreateMdKitTrpcClientAdapterOptions = {
5
+ url: string;
6
+ };
7
+ export type CreateMdKitTrpcAdapterOptions = {
8
+ client: ReturnType<typeof createTRPCProxyClient<MdKitTrpcRouter>>;
9
+ };
10
+ export declare const createMdKitTrpcAdapter: ({ client, }: CreateMdKitTrpcAdapterOptions) => MdKitDocumentAdapter;
11
+ export declare const createMdKitTrpcClient: ({ url, }: CreateMdKitTrpcClientAdapterOptions) => import("@trpc/client").TRPCClient<import("@trpc/server").TRPCBuiltRouter<{
12
+ ctx: object;
13
+ meta: object;
14
+ errorShape: import("@trpc/server").TRPCDefaultErrorShape;
15
+ transformer: false;
16
+ }, import("@trpc/server").TRPCDecorateCreateRouterOptions<{
17
+ listDocumentVersions: import("@trpc/server").TRPCQueryProcedure<{
18
+ input: {
19
+ documentId: string;
20
+ };
21
+ output: {
22
+ versions: import("..").MdKitDocumentVersionSummary[];
23
+ };
24
+ meta: object;
25
+ }>;
26
+ readDocument: import("@trpc/server").TRPCQueryProcedure<{
27
+ input: {
28
+ documentId: string;
29
+ };
30
+ output: import("..").MdKitDocumentSnapshot;
31
+ meta: object;
32
+ }>;
33
+ readDocumentVersion: import("@trpc/server").TRPCQueryProcedure<{
34
+ input: {
35
+ documentId: string;
36
+ versionId: string;
37
+ };
38
+ output: import("..").MdKitDocumentVersionDetail | null;
39
+ meta: object;
40
+ }>;
41
+ resyncDocument: import("@trpc/server").TRPCMutationProcedure<{
42
+ input: {
43
+ documentId: string;
44
+ };
45
+ output: import("..").MdKitDocumentSnapshot;
46
+ meta: object;
47
+ }>;
48
+ restoreDocumentVersion: import("@trpc/server").TRPCMutationProcedure<{
49
+ input: {
50
+ documentId: string;
51
+ versionId: string;
52
+ };
53
+ output: {
54
+ version: import("..").MdKitDocumentVersionToken;
55
+ updatedAt?: string | null;
56
+ } | {
57
+ conflict: true;
58
+ version?: import("..").MdKitDocumentVersionToken;
59
+ updatedAt?: string | null;
60
+ };
61
+ meta: object;
62
+ }>;
63
+ writeDocument: import("@trpc/server").TRPCMutationProcedure<{
64
+ input: {
65
+ baseVersion: string | number | null;
66
+ content: string;
67
+ documentId: string;
68
+ force?: boolean | undefined;
69
+ };
70
+ output: {
71
+ version: import("..").MdKitDocumentVersionToken;
72
+ updatedAt?: string | null;
73
+ } | {
74
+ conflict: true;
75
+ version?: import("..").MdKitDocumentVersionToken;
76
+ updatedAt?: string | null;
77
+ };
78
+ meta: object;
79
+ }>;
80
+ }>>>;
81
+ export declare const createMdKitTrpcClientAdapter: ({ url, }: CreateMdKitTrpcClientAdapterOptions) => MdKitDocumentAdapter;