@mp-lb/mdkit 0.3.1 → 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.
Files changed (160) hide show
  1. package/README.md +8 -2
  2. package/dist/collaboration/useMdKitCollaboration.d.ts +5 -0
  3. package/dist/collaboration/useMdKitCollaboration.d.ts.map +1 -0
  4. package/dist/collaboration/useMdKitCollaboration.js +4 -0
  5. package/dist/core/checkpointPolicy.d.ts +10 -0
  6. package/dist/core/checkpointPolicy.d.ts.map +1 -0
  7. package/dist/core/checkpointPolicy.js +9 -0
  8. package/dist/core/documentEngine.d.ts +1 -0
  9. package/dist/core/documentEngine.d.ts.map +1 -0
  10. package/dist/core/index.d.ts +1 -0
  11. package/dist/core/index.d.ts.map +1 -0
  12. package/dist/document/MdKitConflictPanel.d.ts +5 -0
  13. package/dist/document/MdKitConflictPanel.d.ts.map +1 -0
  14. package/dist/document/MdKitConflictPanel.js +4 -0
  15. package/dist/document/MdKitDocumentToolbar.d.ts +6 -0
  16. package/dist/document/MdKitDocumentToolbar.d.ts.map +1 -0
  17. package/dist/document/MdKitDocumentToolbar.js +5 -0
  18. package/dist/document/documentTypes.d.ts +6 -0
  19. package/dist/document/documentTypes.d.ts.map +1 -0
  20. package/dist/document/useMdKitDocument.d.ts +5 -0
  21. package/dist/document/useMdKitDocument.d.ts.map +1 -0
  22. package/dist/document/useMdKitDocument.js +4 -0
  23. package/dist/fastify.d.ts +1 -0
  24. package/dist/fastify.d.ts.map +1 -0
  25. package/dist/index.d.ts +4 -1
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/markdown/MarkdownBubbleMenu.d.ts +1 -0
  28. package/dist/markdown/MarkdownBubbleMenu.d.ts.map +1 -0
  29. package/dist/markdown/MarkdownPasteExtension.d.ts +1 -0
  30. package/dist/markdown/MarkdownPasteExtension.d.ts.map +1 -0
  31. package/dist/markdown/MarkdownSearchExtension.d.ts +1 -0
  32. package/dist/markdown/MarkdownSearchExtension.d.ts.map +1 -0
  33. package/dist/markdown/MarkdownSearchPanel.d.ts +1 -0
  34. package/dist/markdown/MarkdownSearchPanel.d.ts.map +1 -0
  35. package/dist/markdown/MdKitEditor.d.ts +11 -0
  36. package/dist/markdown/MdKitEditor.d.ts.map +1 -0
  37. package/dist/markdown/MdKitEditor.js +10 -2
  38. package/dist/markdown/MdKitView.d.ts +9 -1
  39. package/dist/markdown/MdKitView.d.ts.map +1 -0
  40. package/dist/markdown/MdKitView.js +7 -2
  41. package/dist/markdown/TiptapMarkdownSurface.d.ts +1 -0
  42. package/dist/markdown/TiptapMarkdownSurface.d.ts.map +1 -0
  43. package/dist/markdown/TiptapMarkdownSurface.js +10 -22
  44. package/dist/markdown/createMdKitTiptapExtensions.d.ts +1 -0
  45. package/dist/markdown/createMdKitTiptapExtensions.d.ts.map +1 -0
  46. package/dist/markdown/editorDebug.d.ts +1 -0
  47. package/dist/markdown/editorDebug.d.ts.map +1 -0
  48. package/dist/markdown/markdownFenceRanges.d.ts +1 -0
  49. package/dist/markdown/markdownFenceRanges.d.ts.map +1 -0
  50. package/dist/markdown/normalizeMarkdownSerialization.d.ts +1 -0
  51. package/dist/markdown/normalizeMarkdownSerialization.d.ts.map +1 -0
  52. package/dist/markdown/prepareMarkdownForEditorHydration.d.ts +1 -0
  53. package/dist/markdown/prepareMarkdownForEditorHydration.d.ts.map +1 -0
  54. package/dist/markdown/preserveMarkdownWhitespace.d.ts +1 -0
  55. package/dist/markdown/preserveMarkdownWhitespace.d.ts.map +1 -0
  56. package/dist/markdown/yamlFrontMatter.d.ts +1 -0
  57. package/dist/markdown/yamlFrontMatter.d.ts.map +1 -0
  58. package/dist/server.d.ts +1 -0
  59. package/dist/server.d.ts.map +1 -0
  60. package/dist/theme/MdKitThemeEditor.d.ts +5 -0
  61. package/dist/theme/MdKitThemeEditor.d.ts.map +1 -0
  62. package/dist/theme/MdKitThemeEditor.js +4 -0
  63. package/dist/theme/editorTheme.d.ts +1 -0
  64. package/dist/theme/editorTheme.d.ts.map +1 -0
  65. package/dist/theme/editorTheme.js +8 -8
  66. package/dist/transport/backend.d.ts +13 -0
  67. package/dist/transport/backend.d.ts.map +1 -0
  68. package/dist/transport/backend.js +6 -0
  69. package/dist/transport/fastify.d.ts +5 -0
  70. package/dist/transport/fastify.d.ts.map +1 -0
  71. package/dist/transport/fastify.js +4 -0
  72. package/dist/transport/http.d.ts +1 -0
  73. package/dist/transport/http.d.ts.map +1 -0
  74. package/dist/transport/index.d.ts +1 -0
  75. package/dist/transport/index.d.ts.map +1 -0
  76. package/dist/transport/rest.d.ts +6 -0
  77. package/dist/transport/rest.d.ts.map +1 -0
  78. package/dist/transport/rest.js +5 -0
  79. package/dist/transport/store.d.ts +1 -0
  80. package/dist/transport/store.d.ts.map +1 -0
  81. package/dist/transport/trpcClient.d.ts +8 -0
  82. package/dist/transport/trpcClient.d.ts.map +1 -0
  83. package/dist/transport/trpcClient.js +7 -0
  84. package/dist/transport/trpcServer.d.ts +6 -0
  85. package/dist/transport/trpcServer.d.ts.map +1 -0
  86. package/dist/transport/trpcServer.js +5 -0
  87. package/dist/trpc/client.d.ts +1 -0
  88. package/dist/trpc/client.d.ts.map +1 -0
  89. package/dist/trpc/server.d.ts +1 -0
  90. package/dist/trpc/server.d.ts.map +1 -0
  91. package/dist/trpc.d.ts +1 -0
  92. package/dist/trpc.d.ts.map +1 -0
  93. package/dist/ui/joinClassNames.d.ts +1 -0
  94. package/dist/ui/joinClassNames.d.ts.map +1 -0
  95. package/dist/versioning/VersionHistoryPanel.d.ts +5 -0
  96. package/dist/versioning/VersionHistoryPanel.d.ts.map +1 -0
  97. package/dist/versioning/VersionHistoryPanel.js +4 -0
  98. package/dist/versioning/useMdKitDocumentVersions.d.ts +5 -0
  99. package/dist/versioning/useMdKitDocumentVersions.d.ts.map +1 -0
  100. package/dist/versioning/useMdKitDocumentVersions.js +4 -0
  101. package/dist/yjs/MdKitMarkdownYjs.d.ts +1 -0
  102. package/dist/yjs/MdKitMarkdownYjs.d.ts.map +1 -0
  103. package/dist/yjs/index.d.ts +1 -0
  104. package/dist/yjs/index.d.ts.map +1 -0
  105. package/package.json +10 -12
  106. package/src/collaboration/useMdKitCollaboration.ts +528 -0
  107. package/src/core/checkpointPolicy.ts +107 -0
  108. package/src/core/documentEngine.ts +175 -0
  109. package/src/core/index.ts +33 -0
  110. package/src/document/MdKitConflictPanel.tsx +129 -0
  111. package/src/document/MdKitDocumentToolbar.tsx +141 -0
  112. package/src/document/documentTypes.ts +89 -0
  113. package/src/document/useMdKitDocument.ts +543 -0
  114. package/src/fastify.ts +6 -0
  115. package/src/index.ts +89 -0
  116. package/src/markdown/MarkdownBubbleMenu.tsx +271 -0
  117. package/src/markdown/MarkdownPasteExtension.ts +81 -0
  118. package/src/markdown/MarkdownSearchExtension.ts +77 -0
  119. package/src/markdown/MarkdownSearchPanel.tsx +98 -0
  120. package/src/markdown/MdKitEditor.tsx +75 -0
  121. package/src/markdown/MdKitView.tsx +80 -0
  122. package/src/markdown/TiptapMarkdownSurface.tsx +923 -0
  123. package/src/markdown/createMdKitTiptapExtensions.ts +42 -0
  124. package/src/markdown/editorDebug.ts +5 -0
  125. package/src/markdown/markdownFenceRanges.ts +68 -0
  126. package/src/markdown/normalizeMarkdownSerialization.ts +55 -0
  127. package/src/markdown/prepareMarkdownForEditorHydration.ts +23 -0
  128. package/src/markdown/preserveMarkdownWhitespace.ts +143 -0
  129. package/src/markdown/yamlFrontMatter.ts +135 -0
  130. package/src/server.ts +6 -0
  131. package/src/styles.css +125 -53
  132. package/src/theme/MdKitThemeEditor.tsx +134 -0
  133. package/src/theme/editorTheme.ts +72 -0
  134. package/src/transport/backend.ts +220 -0
  135. package/src/transport/fastify.ts +57 -0
  136. package/src/transport/http.ts +126 -0
  137. package/src/transport/index.ts +12 -0
  138. package/src/transport/rest.ts +80 -0
  139. package/src/transport/store.ts +45 -0
  140. package/src/transport/trpcClient.ts +90 -0
  141. package/src/transport/trpcServer.ts +66 -0
  142. package/src/trpc/client.ts +11 -0
  143. package/src/trpc/server.ts +12 -0
  144. package/src/trpc.ts +11 -0
  145. package/src/ui/joinClassNames.ts +3 -0
  146. package/src/versioning/VersionHistoryPanel.tsx +146 -0
  147. package/src/versioning/useMdKitDocumentVersions.ts +146 -0
  148. package/src/yjs/MdKitMarkdownYjs.ts +111 -0
  149. package/src/yjs/index.ts +8 -0
  150. package/docs/.vitepress/config.ts +0 -47
  151. package/docs/api.md +0 -512
  152. package/docs/architecture.md +0 -96
  153. package/docs/collaboration-persistence.md +0 -147
  154. package/docs/index.md +0 -341
  155. package/docs/permissions.md +0 -139
  156. package/docs/plain-text.md +0 -131
  157. package/docs/rest.md +0 -98
  158. package/docs/shadcn.md +0 -125
  159. package/docs/styling.md +0 -373
  160. package/docs/use-cases.md +0 -148
package/src/styles.css CHANGED
@@ -40,20 +40,53 @@
40
40
  --mp-lb-mdkit-accent-foreground: var(--primary-foreground, #ffffff);
41
41
  --mp-lb-mdkit-link: #4f46e5;
42
42
  --mp-lb-mdkit-font-family: inherit;
43
- --mp-lb-mdkit-font-size: 1rem;
44
- --mp-lb-mdkit-line-height: 1.55;
45
- --mp-lb-mdkit-surface-padding: 1rem;
46
- --mp-lb-mdkit-block-gap: 0.72em;
47
- --mp-lb-mdkit-tight-gap: 0.35em;
48
- --mp-lb-mdkit-section-gap: 1.25em;
43
+ --mp-lb-mdkit-font-size: 16px;
44
+ --mp-lb-mdkit-line-height: 24px;
45
+ --mp-lb-mdkit-surface-padding: 0px;
46
+ --mp-lb-mdkit-document-margin-block: clamp(1.5rem, 6vh, 4rem);
47
+ --mp-lb-mdkit-document-margin-inline: clamp(1rem, 4vw, 3rem);
48
+ --mp-lb-mdkit-fixed-width-edge-padding: clamp(1rem, 3vw, 2rem);
49
+ --mp-lb-mdkit-fixed-width-inline-padding: max(
50
+ var(--mp-lb-mdkit-fixed-width-edge-padding),
51
+ calc(50% - 36rem)
52
+ );
53
+ --mp-lb-mdkit-fixed-width-document-inline-padding: max(
54
+ var(--mp-lb-mdkit-document-margin-inline),
55
+ calc(50% - 36rem)
56
+ );
57
+ --mp-lb-mdkit-block-gap: 1px;
58
+ --mp-lb-mdkit-tight-gap: 1px;
59
+ --mp-lb-mdkit-section-gap: 1px;
49
60
  --mp-lb-mdkit-list-item-gap: 0.125rem;
50
- --mp-lb-mdkit-heading-font-weight: 650;
51
- --mp-lb-mdkit-heading-1-size: 1.5rem;
52
- --mp-lb-mdkit-heading-2-size: 1.25rem;
53
- --mp-lb-mdkit-heading-3-size: 1.125rem;
61
+ --mp-lb-mdkit-heading-1-size: 40px;
62
+ --mp-lb-mdkit-heading-1-weight: 700;
63
+ --mp-lb-mdkit-heading-1-line-height: 44px;
64
+ --mp-lb-mdkit-heading-1-margin-top: 50px;
65
+ --mp-lb-mdkit-heading-1-margin-bottom: 4px;
66
+ --mp-lb-mdkit-heading-2-size: 30px;
67
+ --mp-lb-mdkit-heading-2-weight: 600;
68
+ --mp-lb-mdkit-heading-2-line-height: 39px;
69
+ --mp-lb-mdkit-heading-2-margin-top: 32px;
70
+ --mp-lb-mdkit-heading-2-margin-bottom: 4px;
71
+ --mp-lb-mdkit-heading-3-size: 24px;
72
+ --mp-lb-mdkit-heading-3-weight: 600;
73
+ --mp-lb-mdkit-heading-3-line-height: 32px;
74
+ --mp-lb-mdkit-heading-3-margin-top: 22px;
75
+ --mp-lb-mdkit-heading-3-margin-bottom: 1px;
76
+ --mp-lb-mdkit-heading-4-size: 20px;
77
+ --mp-lb-mdkit-heading-4-line-height: 26px;
78
+ --mp-lb-mdkit-heading-4-margin-top: 16px;
79
+ --mp-lb-mdkit-heading-5-size: 18px;
80
+ --mp-lb-mdkit-heading-5-line-height: 22px;
81
+ --mp-lb-mdkit-heading-5-margin-top: 14px;
82
+ --mp-lb-mdkit-heading-6-size: 16px;
83
+ --mp-lb-mdkit-heading-6-line-height: 20px;
84
+ --mp-lb-mdkit-heading-6-margin-top: 12px;
85
+ --mp-lb-mdkit-heading-minor-weight: 600;
86
+ --mp-lb-mdkit-block-padding: 3px 2px;
54
87
  --mp-lb-mdkit-code-background: var(--mp-lb-mdkit-muted);
55
88
  --mp-lb-mdkit-code-radius: 0.35rem;
56
- --mp-lb-mdkit-code-block-radius: 0.75rem;
89
+ --mp-lb-mdkit-code-block-radius: 0.375rem;
57
90
  --mp-lb-mdkit-code-font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
58
91
  monospace;
59
92
  --mp-lb-mdkit-quote-border-color: var(--mp-lb-mdkit-border);
@@ -136,6 +169,20 @@
136
169
  padding: var(--mp-lb-mdkit-surface-padding);
137
170
  }
138
171
 
172
+ .mp-lb-mdkit-markdown-editor-document-margins .mp-lb-mdkit-editor-surface {
173
+ padding-block: var(--mp-lb-mdkit-document-margin-block);
174
+ padding-inline: var(--mp-lb-mdkit-document-margin-inline);
175
+ }
176
+
177
+ .mp-lb-mdkit-markdown-editor-fixed-width .mp-lb-mdkit-editor-surface {
178
+ padding-inline: var(--mp-lb-mdkit-fixed-width-inline-padding);
179
+ }
180
+
181
+ .mp-lb-mdkit-markdown-editor-fixed-width.mp-lb-mdkit-markdown-editor-document-margins
182
+ .mp-lb-mdkit-editor-surface {
183
+ padding-inline: var(--mp-lb-mdkit-fixed-width-document-inline-padding);
184
+ }
185
+
139
186
  .mp-lb-mdkit-search-panel {
140
187
  position: sticky;
141
188
  z-index: 5;
@@ -314,9 +361,17 @@
314
361
  }
315
362
 
316
363
  .mp-lb-mdkit-tiptap {
364
+ position: relative;
365
+ display: flex;
366
+ flex-direction: column;
317
367
  width: 100%;
318
368
  outline: none;
319
369
  line-height: var(--mp-lb-mdkit-line-height);
370
+ white-space: pre-wrap;
371
+ white-space: break-spaces;
372
+ word-wrap: break-word;
373
+ font-variant-ligatures: none;
374
+ font-feature-settings: "liga" 0;
320
375
  }
321
376
 
322
377
  .mp-lb-mdkit-markdown-editor-fill-height .mp-lb-mdkit-tiptap {
@@ -345,66 +400,72 @@
345
400
  flex: 0 0 auto;
346
401
  }
347
402
 
348
- .mp-lb-mdkit-tiptap
349
- :is(p, h1, h2, h3, h4, h5, h6, ul, ol, blockquote, pre, table) {
350
- margin: 0;
351
- }
352
-
353
- .mp-lb-mdkit-tiptap > * + * {
354
- margin-top: var(--mp-lb-mdkit-block-gap);
355
- }
356
-
357
403
  .mp-lb-mdkit-tiptap h1,
358
404
  .mp-lb-mdkit-tiptap h2,
359
405
  .mp-lb-mdkit-tiptap h3,
360
406
  .mp-lb-mdkit-tiptap h4,
361
407
  .mp-lb-mdkit-tiptap h5,
362
408
  .mp-lb-mdkit-tiptap h6 {
363
- font-weight: var(--mp-lb-mdkit-heading-font-weight);
409
+ padding: var(--mp-lb-mdkit-block-padding);
364
410
  }
365
411
 
366
412
  .mp-lb-mdkit-tiptap h1 {
413
+ margin: var(--mp-lb-mdkit-heading-1-margin-top) 0
414
+ var(--mp-lb-mdkit-heading-1-margin-bottom);
367
415
  font-size: var(--mp-lb-mdkit-heading-1-size);
368
- line-height: 1.25;
416
+ font-weight: var(--mp-lb-mdkit-heading-1-weight);
417
+ line-height: var(--mp-lb-mdkit-heading-1-line-height);
369
418
  }
370
419
 
371
420
  .mp-lb-mdkit-tiptap h2 {
421
+ margin: var(--mp-lb-mdkit-heading-2-margin-top) 0
422
+ var(--mp-lb-mdkit-heading-2-margin-bottom);
372
423
  font-size: var(--mp-lb-mdkit-heading-2-size);
373
- line-height: 1.3;
424
+ font-weight: var(--mp-lb-mdkit-heading-2-weight);
425
+ line-height: var(--mp-lb-mdkit-heading-2-line-height);
374
426
  }
375
427
 
376
- .mp-lb-mdkit-tiptap h3,
377
- .mp-lb-mdkit-tiptap h4,
378
- .mp-lb-mdkit-tiptap h5,
379
- .mp-lb-mdkit-tiptap h6 {
428
+ .mp-lb-mdkit-tiptap h3 {
429
+ margin: var(--mp-lb-mdkit-heading-3-margin-top) 0
430
+ var(--mp-lb-mdkit-heading-3-margin-bottom);
380
431
  font-size: var(--mp-lb-mdkit-heading-3-size);
381
- line-height: 1.35;
432
+ font-weight: var(--mp-lb-mdkit-heading-3-weight);
433
+ line-height: var(--mp-lb-mdkit-heading-3-line-height);
382
434
  }
383
435
 
384
- .mp-lb-mdkit-tiptap ul,
385
- .mp-lb-mdkit-tiptap ol {
386
- margin: 0;
387
- padding-left: 1.5rem;
436
+ .mp-lb-mdkit-tiptap h4 {
437
+ margin: var(--mp-lb-mdkit-heading-4-margin-top) 0 1px;
438
+ font-size: var(--mp-lb-mdkit-heading-4-size);
439
+ font-weight: var(--mp-lb-mdkit-heading-minor-weight);
440
+ line-height: var(--mp-lb-mdkit-heading-4-line-height);
388
441
  }
389
442
 
390
- .mp-lb-mdkit-tiptap
391
- > :is(h1, h2, h3)
392
- + :is(p, ul, ol, blockquote, pre, table) {
393
- margin-top: var(--mp-lb-mdkit-tight-gap);
443
+ .mp-lb-mdkit-tiptap h5 {
444
+ margin: var(--mp-lb-mdkit-heading-5-margin-top) 0 1px;
445
+ font-size: var(--mp-lb-mdkit-heading-5-size);
446
+ font-weight: var(--mp-lb-mdkit-heading-minor-weight);
447
+ line-height: var(--mp-lb-mdkit-heading-5-line-height);
394
448
  }
395
449
 
396
- .mp-lb-mdkit-tiptap > p + p {
397
- margin-top: var(--mp-lb-mdkit-block-gap);
450
+ .mp-lb-mdkit-tiptap h6 {
451
+ margin: var(--mp-lb-mdkit-heading-6-margin-top) 0 1px;
452
+ font-size: var(--mp-lb-mdkit-heading-6-size);
453
+ font-weight: var(--mp-lb-mdkit-heading-minor-weight);
454
+ line-height: var(--mp-lb-mdkit-heading-6-line-height);
398
455
  }
399
456
 
400
- .mp-lb-mdkit-tiptap > :is(p, ul, ol) + :is(ul, ol, p) {
401
- margin-top: var(--mp-lb-mdkit-tight-gap);
457
+ .mp-lb-mdkit-tiptap p {
458
+ margin: 1px 0 0;
459
+ padding: var(--mp-lb-mdkit-block-padding);
460
+ font-size: var(--mp-lb-mdkit-font-size);
461
+ line-height: var(--mp-lb-mdkit-line-height);
402
462
  }
403
463
 
404
- .mp-lb-mdkit-tiptap
405
- > :is(p, ul, ol, blockquote, pre, table, hr)
406
- + :is(h1, h2, h3) {
407
- margin-top: var(--mp-lb-mdkit-section-gap);
464
+ .mp-lb-mdkit-tiptap ul,
465
+ .mp-lb-mdkit-tiptap ol {
466
+ margin: 1px 0 0;
467
+ padding: var(--mp-lb-mdkit-block-padding);
468
+ padding-left: 1.5rem;
408
469
  }
409
470
 
410
471
  .mp-lb-mdkit-tiptap ul {
@@ -437,8 +498,8 @@
437
498
  }
438
499
 
439
500
  .mp-lb-mdkit-tiptap pre {
440
- margin: 0;
441
- padding: 0.75rem 0.875rem;
501
+ margin: 0.5rem 0;
502
+ padding: 2rem;
442
503
  overflow-x: auto;
443
504
  border: 1px solid var(--mp-lb-mdkit-border);
444
505
  border-radius: var(--mp-lb-mdkit-code-block-radius);
@@ -459,17 +520,28 @@
459
520
  }
460
521
 
461
522
  .mp-lb-mdkit-tiptap blockquote {
462
- margin: 0;
463
- padding-left: 0.875rem;
464
- border-left: 3px solid var(--mp-lb-mdkit-quote-border-color);
523
+ position: relative;
524
+ margin: 4px 0;
525
+ padding-block: 3px;
526
+ padding-inline: 1em 2px;
527
+ border-left: 0;
465
528
  color: var(--mp-lb-mdkit-muted-foreground);
466
529
  }
467
530
 
531
+ .mp-lb-mdkit-tiptap blockquote::before {
532
+ content: "";
533
+ position: absolute;
534
+ top: 2px;
535
+ bottom: 2px;
536
+ display: block;
537
+ border-inline-start: 0.25em solid var(--mp-lb-mdkit-quote-border-color);
538
+ inset-inline-start: 2px;
539
+ }
540
+
468
541
  .mp-lb-mdkit-tiptap hr {
469
- height: 1px;
470
- border: 0;
471
- background: var(--mp-lb-mdkit-border);
472
- margin: var(--mp-lb-mdkit-section-gap) 0;
542
+ border-width: 1px 0 0;
543
+ color: var(--mp-lb-mdkit-border);
544
+ margin: 13px 0;
473
545
  }
474
546
 
475
547
  .mp-lb-mdkit-tiptap img {
@@ -0,0 +1,134 @@
1
+ import type { MdKitEditorTheme } from "./editorTheme";
2
+
3
+ export type MdKitThemeEditorProps = {
4
+ className?: string;
5
+ onChange: (theme: MdKitEditorTheme) => void;
6
+ onReset?: () => void;
7
+ theme: MdKitEditorTheme;
8
+ };
9
+
10
+ /**
11
+ * Optional controls for editing an {@link MdKitEditorTheme}. Intended for theme
12
+ * builders, docs, and debug tools rather than production editing surfaces.
13
+ */
14
+ export const MdKitThemeEditor = ({
15
+ className,
16
+ onChange,
17
+ onReset,
18
+ theme,
19
+ }: MdKitThemeEditorProps) => {
20
+ const updateTheme = (patch: Partial<MdKitEditorTheme>) => {
21
+ onChange({
22
+ ...theme,
23
+ ...patch,
24
+ });
25
+ };
26
+
27
+ return (
28
+ <div className={["mp-lb-mdkit-theme-editor", className].filter(Boolean).join(" ")}>
29
+ <label>
30
+ <span>Background</span>
31
+ <input
32
+ type="color"
33
+ value={theme.background}
34
+ onChange={(event) => updateTheme({ background: event.target.value })}
35
+ />
36
+ </label>
37
+ <label>
38
+ <span>Text</span>
39
+ <input
40
+ type="color"
41
+ value={theme.foreground}
42
+ onChange={(event) => updateTheme({ foreground: event.target.value })}
43
+ />
44
+ </label>
45
+ <label>
46
+ <span>Link</span>
47
+ <input
48
+ type="color"
49
+ value={theme.link}
50
+ onChange={(event) => updateTheme({ link: event.target.value })}
51
+ />
52
+ </label>
53
+ <label>
54
+ <span>Code</span>
55
+ <input
56
+ type="color"
57
+ value={theme.codeBackground}
58
+ onChange={(event) =>
59
+ updateTheme({
60
+ codeBackground: event.target.value,
61
+ muted: event.target.value,
62
+ })
63
+ }
64
+ />
65
+ </label>
66
+ <label>
67
+ <span>Font size</span>
68
+ <input
69
+ type="range"
70
+ min="13"
71
+ max="22"
72
+ value={Number.parseInt(theme.fontSize, 10)}
73
+ onChange={(event) =>
74
+ updateTheme({ fontSize: `${event.target.value}px` })
75
+ }
76
+ />
77
+ </label>
78
+ <label>
79
+ <span>Font</span>
80
+ <select
81
+ value={theme.fontFamily}
82
+ onChange={(event) => updateTheme({ fontFamily: event.target.value })}
83
+ >
84
+ <option value="inherit">App default</option>
85
+ <option value="ui-serif, Georgia, Cambria, serif">Serif</option>
86
+ <option value="ui-sans-serif, system-ui, sans-serif">Sans</option>
87
+ <option value="ui-monospace, SFMono-Regular, Menlo, monospace">
88
+ Mono
89
+ </option>
90
+ </select>
91
+ </label>
92
+ <label>
93
+ <span>Line height</span>
94
+ <input
95
+ type="range"
96
+ min="1.2"
97
+ max="2.2"
98
+ step="0.1"
99
+ value={theme.lineHeight}
100
+ onChange={(event) => updateTheme({ lineHeight: event.target.value })}
101
+ />
102
+ </label>
103
+ <label>
104
+ <span>Padding</span>
105
+ <input
106
+ type="range"
107
+ min="0"
108
+ max="32"
109
+ value={Number.parseInt(theme.surfacePadding, 10)}
110
+ onChange={(event) =>
111
+ updateTheme({ surfacePadding: `${event.target.value}px` })
112
+ }
113
+ />
114
+ </label>
115
+ <label>
116
+ <span>Code radius</span>
117
+ <input
118
+ type="range"
119
+ min="0"
120
+ max="16"
121
+ value={Number.parseInt(theme.codeRadius, 10)}
122
+ onChange={(event) =>
123
+ updateTheme({ codeRadius: `${event.target.value}px` })
124
+ }
125
+ />
126
+ </label>
127
+ {onReset ? (
128
+ <button type="button" onClick={onReset}>
129
+ Reset style
130
+ </button>
131
+ ) : null}
132
+ </div>
133
+ );
134
+ };
@@ -0,0 +1,72 @@
1
+ import type { CSSProperties } from "react";
2
+
3
+ export type MdKitEditorTheme = {
4
+ background: string;
5
+ blockGap: string;
6
+ border: string;
7
+ codeBackground: string;
8
+ codeRadius: string;
9
+ fontFamily: string;
10
+ fontSize: string;
11
+ foreground: string;
12
+ lineHeight: string;
13
+ link: string;
14
+ muted: string;
15
+ mutedForeground: string;
16
+ surfacePadding: string;
17
+ };
18
+
19
+ export type MdKitEditorThemeStyle = CSSProperties &
20
+ Record<`--mp-lb-mdkit-${string}`, string>;
21
+
22
+ export const defaultMdKitEditorTheme: MdKitEditorTheme = {
23
+ background: "#ffffff",
24
+ blockGap: "1px",
25
+ border: "#d8dee8",
26
+ codeBackground: "#eef1f4",
27
+ codeRadius: "0.375rem",
28
+ fontFamily: "inherit",
29
+ fontSize: "16px",
30
+ foreground: "#18212f",
31
+ lineHeight: "1.5",
32
+ link: "#4f46e5",
33
+ muted: "#eef1f4",
34
+ mutedForeground: "#5b6472",
35
+ surfacePadding: "32px",
36
+ };
37
+
38
+ export const darkMdKitEditorTheme: MdKitEditorTheme = {
39
+ background: "#0b1220",
40
+ blockGap: "1px",
41
+ border: "#314158",
42
+ codeBackground: "#111827",
43
+ codeRadius: "0.375rem",
44
+ fontFamily: "inherit",
45
+ fontSize: "16px",
46
+ foreground: "#e5edf7",
47
+ lineHeight: "1.5",
48
+ link: "#38bdf8",
49
+ muted: "#172033",
50
+ mutedForeground: "#94a3b8",
51
+ surfacePadding: "32px",
52
+ };
53
+
54
+ export const createMdKitEditorThemeStyle = (
55
+ theme: MdKitEditorTheme,
56
+ ): MdKitEditorThemeStyle => ({
57
+ "--mp-lb-mdkit-background": theme.background,
58
+ "--mp-lb-mdkit-block-gap": theme.blockGap,
59
+ "--mp-lb-mdkit-border": theme.border,
60
+ "--mp-lb-mdkit-code-background": theme.codeBackground,
61
+ "--mp-lb-mdkit-code-radius": theme.codeRadius,
62
+ "--mp-lb-mdkit-code-block-radius": theme.codeRadius,
63
+ "--mp-lb-mdkit-font-family": theme.fontFamily,
64
+ "--mp-lb-mdkit-font-size": theme.fontSize,
65
+ "--mp-lb-mdkit-foreground": theme.foreground,
66
+ "--mp-lb-mdkit-line-height": theme.lineHeight,
67
+ "--mp-lb-mdkit-link": theme.link,
68
+ "--mp-lb-mdkit-muted": theme.muted,
69
+ "--mp-lb-mdkit-muted-foreground": theme.mutedForeground,
70
+ "--mp-lb-mdkit-quote-border-color": theme.border,
71
+ "--mp-lb-mdkit-surface-padding": theme.surfacePadding,
72
+ });
@@ -0,0 +1,220 @@
1
+ import type {
2
+ MdKitDocumentSnapshot,
3
+ MdKitDocumentVersionDetail,
4
+ MdKitDocumentVersionSummary,
5
+ MdKitDocumentVersionToken,
6
+ MdKitDocumentWriteInput,
7
+ MdKitDocumentWriteResult,
8
+ } from "../document/documentTypes";
9
+ import {
10
+ CheckpointPolicy,
11
+ measureMdKitEditDistance,
12
+ type MdKitCheckpointPolicy,
13
+ } from "../core/checkpointPolicy";
14
+ import type {
15
+ MdKitRestoreDocumentVersionInput,
16
+ MdKitTransportStore,
17
+ } from "./store";
18
+
19
+ export type MdKitCreateCheckpointInput = {
20
+ content: string;
21
+ documentId: string;
22
+ metadata?: unknown;
23
+ sourceRevision: MdKitDocumentVersionToken;
24
+ };
25
+
26
+ /**
27
+ * Application-owned persistence contract consumed by {@link createMdKitBackend}.
28
+ * Implement it with your database; mdkit calls `createCheckpoint` when the
29
+ * configured {@link CheckpointPolicy} triggers — the store never interprets the
30
+ * policy itself.
31
+ */
32
+ export type MdKitBackendStore = {
33
+ createCheckpoint?(
34
+ input: MdKitCreateCheckpointInput,
35
+ ): Promise<MdKitDocumentVersionSummary> | MdKitDocumentVersionSummary;
36
+ getLatestCheckpoint?(
37
+ documentId: string,
38
+ ): Promise<MdKitDocumentVersionDetail | null> | MdKitDocumentVersionDetail | null;
39
+ listDocumentVersions?(
40
+ documentId: string,
41
+ ): Promise<MdKitDocumentVersionSummary[]> | MdKitDocumentVersionSummary[];
42
+ readCollaborationState?(
43
+ documentName: string,
44
+ ): Promise<Uint8Array | null> | Uint8Array | null;
45
+ readDocument(
46
+ documentId: string,
47
+ ): Promise<MdKitDocumentSnapshot> | MdKitDocumentSnapshot;
48
+ readDocumentVersion?(input: {
49
+ documentId: string;
50
+ versionId: string;
51
+ }):
52
+ | Promise<MdKitDocumentVersionDetail | null>
53
+ | MdKitDocumentVersionDetail
54
+ | null;
55
+ resyncDocument?(
56
+ documentId: string,
57
+ ): Promise<MdKitDocumentSnapshot> | MdKitDocumentSnapshot;
58
+ restoreDocumentVersion?(
59
+ input: MdKitRestoreDocumentVersionInput,
60
+ ): Promise<MdKitDocumentWriteResult> | MdKitDocumentWriteResult;
61
+ writeCollaborationState?(
62
+ documentName: string,
63
+ state: Uint8Array,
64
+ ): Promise<void> | void;
65
+ writeDocument(
66
+ input: MdKitDocumentWriteInput,
67
+ ): Promise<MdKitDocumentWriteResult> | MdKitDocumentWriteResult;
68
+ };
69
+
70
+ export type CreateMdKitBackendOptions = {
71
+ checkpointPolicy?: MdKitCheckpointPolicy;
72
+ store: MdKitBackendStore;
73
+ };
74
+
75
+ const timestampMs = (value: string | null | undefined) => {
76
+ if (!value) {
77
+ return null;
78
+ }
79
+
80
+ const timestamp = Date.parse(value);
81
+ return Number.isFinite(timestamp) ? timestamp : null;
82
+ };
83
+
84
+ const successfulWriteRevision = (result: MdKitDocumentWriteResult) =>
85
+ "conflict" in result ? null : result.version;
86
+
87
+ const latestCheckpointFromList = async (
88
+ store: MdKitBackendStore,
89
+ documentId: string,
90
+ ) => {
91
+ const versions = await store.listDocumentVersions?.(documentId);
92
+ const latestSummary = versions?.at(-1);
93
+
94
+ if (!latestSummary || !store.readDocumentVersion) {
95
+ return null;
96
+ }
97
+
98
+ return store.readDocumentVersion({
99
+ documentId,
100
+ versionId: latestSummary.id,
101
+ });
102
+ };
103
+
104
+ const readLatestCheckpoint = async (
105
+ store: MdKitBackendStore,
106
+ documentId: string,
107
+ ) =>
108
+ (await store.getLatestCheckpoint?.(documentId)) ??
109
+ (await latestCheckpointFromList(store, documentId));
110
+
111
+ const maybeCreateCheckpoint = async (
112
+ store: MdKitBackendStore,
113
+ policy: MdKitCheckpointPolicy,
114
+ input: MdKitDocumentWriteInput,
115
+ result: MdKitDocumentWriteResult,
116
+ ) => {
117
+ const sourceRevision = successfulWriteRevision(result);
118
+
119
+ if (!store.createCheckpoint || sourceRevision === null) {
120
+ return;
121
+ }
122
+
123
+ const previousCheckpoint = await readLatestCheckpoint(store, input.documentId);
124
+ const previousCheckpointContent = previousCheckpoint?.content ?? null;
125
+ const editDistance = measureMdKitEditDistance(
126
+ previousCheckpointContent ?? "",
127
+ input.content,
128
+ );
129
+ const checkpointTimestamp = timestampMs(previousCheckpoint?.createdAt);
130
+ const timeSinceLastCheckpointMs =
131
+ checkpointTimestamp === null ? null : Date.now() - checkpointTimestamp;
132
+ const shouldCheckpoint = await policy.shouldCheckpoint({
133
+ currentContent: input.content,
134
+ documentId: input.documentId,
135
+ editDistance,
136
+ previousCheckpoint,
137
+ previousCheckpointContent,
138
+ timeSinceLastCheckpointMs,
139
+ writeInput: input,
140
+ writeResult: result,
141
+ });
142
+
143
+ if (!shouldCheckpoint) {
144
+ return;
145
+ }
146
+
147
+ await store.createCheckpoint({
148
+ content: input.content,
149
+ documentId: input.documentId,
150
+ sourceRevision,
151
+ });
152
+ };
153
+
154
+ const restoreWithStorePrimitives = async (
155
+ store: MdKitBackendStore,
156
+ input: MdKitRestoreDocumentVersionInput,
157
+ ) => {
158
+ if (!store.readDocumentVersion) {
159
+ if (!store.restoreDocumentVersion) {
160
+ throw new Error("Version restore is not supported");
161
+ }
162
+
163
+ return store.restoreDocumentVersion(input);
164
+ }
165
+
166
+ const restoredVersion = await store.readDocumentVersion(input);
167
+
168
+ if (!restoredVersion) {
169
+ throw new Error(`Version not found: ${input.versionId}`);
170
+ }
171
+
172
+ const current = await store.readDocument(input.documentId);
173
+ const latestCheckpoint = await readLatestCheckpoint(store, input.documentId);
174
+
175
+ if (
176
+ store.createCheckpoint &&
177
+ latestCheckpoint?.content !== current.content
178
+ ) {
179
+ await store.createCheckpoint({
180
+ content: current.content,
181
+ documentId: input.documentId,
182
+ sourceRevision: current.version,
183
+ });
184
+ }
185
+
186
+ return store.writeDocument({
187
+ baseVersion: current.version,
188
+ content: restoredVersion.content,
189
+ documentId: input.documentId,
190
+ force: true,
191
+ });
192
+ };
193
+
194
+ /**
195
+ * Wraps an application {@link MdKitBackendStore} with checkpoint-policy
196
+ * orchestration, producing the transport-ready surface the Fastify and tRPC
197
+ * helpers mount. The store owns persistence, auth, and metadata; this owns
198
+ * checkpoint timing and restore ordering.
199
+ */
200
+ export const createMdKitBackend = ({
201
+ checkpointPolicy = CheckpointPolicy.never(),
202
+ store,
203
+ }: CreateMdKitBackendOptions): MdKitTransportStore => ({
204
+ listDocumentVersions: (documentId) =>
205
+ store.listDocumentVersions?.(documentId) ?? [],
206
+ readCollaborationState: (documentName) =>
207
+ store.readCollaborationState?.(documentName) ?? null,
208
+ readDocument: (documentId) => store.readDocument(documentId),
209
+ readDocumentVersion: (input) => store.readDocumentVersion?.(input) ?? null,
210
+ resyncDocument: (documentId) =>
211
+ (store.resyncDocument ?? store.readDocument)(documentId),
212
+ restoreDocumentVersion: (input) => restoreWithStorePrimitives(store, input),
213
+ writeCollaborationState: (documentName, state) =>
214
+ store.writeCollaborationState?.(documentName, state),
215
+ writeDocument: async (input) => {
216
+ const result = await store.writeDocument(input);
217
+ await maybeCreateCheckpoint(store, checkpointPolicy, input, result);
218
+ return result;
219
+ },
220
+ });