@mp-lb/mdkit 0.3.2 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +3 -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 +132 -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 {
@@ -325,6 +380,13 @@
325
380
  flex: 1 1 auto;
326
381
  }
327
382
 
383
+ .mp-lb-mdkit-markdown-editor-fill-height.mp-lb-mdkit-markdown-editor-document-margins
384
+ .mp-lb-mdkit-tiptap::after {
385
+ content: "";
386
+ display: block;
387
+ flex: 0 0 var(--mp-lb-mdkit-document-margin-block);
388
+ }
389
+
328
390
  .mp-lb-mdkit-markdown-view.mp-lb-mdkit-markdown-editor-fill-height
329
391
  .mp-lb-mdkit-editor-shell {
330
392
  height: auto;
@@ -345,66 +407,72 @@
345
407
  flex: 0 0 auto;
346
408
  }
347
409
 
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
410
  .mp-lb-mdkit-tiptap h1,
358
411
  .mp-lb-mdkit-tiptap h2,
359
412
  .mp-lb-mdkit-tiptap h3,
360
413
  .mp-lb-mdkit-tiptap h4,
361
414
  .mp-lb-mdkit-tiptap h5,
362
415
  .mp-lb-mdkit-tiptap h6 {
363
- font-weight: var(--mp-lb-mdkit-heading-font-weight);
416
+ padding: var(--mp-lb-mdkit-block-padding);
364
417
  }
365
418
 
366
419
  .mp-lb-mdkit-tiptap h1 {
420
+ margin: var(--mp-lb-mdkit-heading-1-margin-top) 0
421
+ var(--mp-lb-mdkit-heading-1-margin-bottom);
367
422
  font-size: var(--mp-lb-mdkit-heading-1-size);
368
- line-height: 1.25;
423
+ font-weight: var(--mp-lb-mdkit-heading-1-weight);
424
+ line-height: var(--mp-lb-mdkit-heading-1-line-height);
369
425
  }
370
426
 
371
427
  .mp-lb-mdkit-tiptap h2 {
428
+ margin: var(--mp-lb-mdkit-heading-2-margin-top) 0
429
+ var(--mp-lb-mdkit-heading-2-margin-bottom);
372
430
  font-size: var(--mp-lb-mdkit-heading-2-size);
373
- line-height: 1.3;
431
+ font-weight: var(--mp-lb-mdkit-heading-2-weight);
432
+ line-height: var(--mp-lb-mdkit-heading-2-line-height);
374
433
  }
375
434
 
376
- .mp-lb-mdkit-tiptap h3,
377
- .mp-lb-mdkit-tiptap h4,
378
- .mp-lb-mdkit-tiptap h5,
379
- .mp-lb-mdkit-tiptap h6 {
435
+ .mp-lb-mdkit-tiptap h3 {
436
+ margin: var(--mp-lb-mdkit-heading-3-margin-top) 0
437
+ var(--mp-lb-mdkit-heading-3-margin-bottom);
380
438
  font-size: var(--mp-lb-mdkit-heading-3-size);
381
- line-height: 1.35;
439
+ font-weight: var(--mp-lb-mdkit-heading-3-weight);
440
+ line-height: var(--mp-lb-mdkit-heading-3-line-height);
382
441
  }
383
442
 
384
- .mp-lb-mdkit-tiptap ul,
385
- .mp-lb-mdkit-tiptap ol {
386
- margin: 0;
387
- padding-left: 1.5rem;
443
+ .mp-lb-mdkit-tiptap h4 {
444
+ margin: var(--mp-lb-mdkit-heading-4-margin-top) 0 1px;
445
+ font-size: var(--mp-lb-mdkit-heading-4-size);
446
+ font-weight: var(--mp-lb-mdkit-heading-minor-weight);
447
+ line-height: var(--mp-lb-mdkit-heading-4-line-height);
388
448
  }
389
449
 
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);
450
+ .mp-lb-mdkit-tiptap h5 {
451
+ margin: var(--mp-lb-mdkit-heading-5-margin-top) 0 1px;
452
+ font-size: var(--mp-lb-mdkit-heading-5-size);
453
+ font-weight: var(--mp-lb-mdkit-heading-minor-weight);
454
+ line-height: var(--mp-lb-mdkit-heading-5-line-height);
394
455
  }
395
456
 
396
- .mp-lb-mdkit-tiptap > p + p {
397
- margin-top: var(--mp-lb-mdkit-block-gap);
457
+ .mp-lb-mdkit-tiptap h6 {
458
+ margin: var(--mp-lb-mdkit-heading-6-margin-top) 0 1px;
459
+ font-size: var(--mp-lb-mdkit-heading-6-size);
460
+ font-weight: var(--mp-lb-mdkit-heading-minor-weight);
461
+ line-height: var(--mp-lb-mdkit-heading-6-line-height);
398
462
  }
399
463
 
400
- .mp-lb-mdkit-tiptap > :is(p, ul, ol) + :is(ul, ol, p) {
401
- margin-top: var(--mp-lb-mdkit-tight-gap);
464
+ .mp-lb-mdkit-tiptap p {
465
+ margin: 1px 0 0;
466
+ padding: var(--mp-lb-mdkit-block-padding);
467
+ font-size: var(--mp-lb-mdkit-font-size);
468
+ line-height: var(--mp-lb-mdkit-line-height);
402
469
  }
403
470
 
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);
471
+ .mp-lb-mdkit-tiptap ul,
472
+ .mp-lb-mdkit-tiptap ol {
473
+ margin: 1px 0 0;
474
+ padding: var(--mp-lb-mdkit-block-padding);
475
+ padding-left: 1.5rem;
408
476
  }
409
477
 
410
478
  .mp-lb-mdkit-tiptap ul {
@@ -437,8 +505,8 @@
437
505
  }
438
506
 
439
507
  .mp-lb-mdkit-tiptap pre {
440
- margin: 0;
441
- padding: 0.75rem 0.875rem;
508
+ margin: 0.5rem 0;
509
+ padding: 2rem;
442
510
  overflow-x: auto;
443
511
  border: 1px solid var(--mp-lb-mdkit-border);
444
512
  border-radius: var(--mp-lb-mdkit-code-block-radius);
@@ -459,17 +527,28 @@
459
527
  }
460
528
 
461
529
  .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);
530
+ position: relative;
531
+ margin: 4px 0;
532
+ padding-block: 3px;
533
+ padding-inline: 1em 2px;
534
+ border-left: 0;
465
535
  color: var(--mp-lb-mdkit-muted-foreground);
466
536
  }
467
537
 
538
+ .mp-lb-mdkit-tiptap blockquote::before {
539
+ content: "";
540
+ position: absolute;
541
+ top: 2px;
542
+ bottom: 2px;
543
+ display: block;
544
+ border-inline-start: 0.25em solid var(--mp-lb-mdkit-quote-border-color);
545
+ inset-inline-start: 2px;
546
+ }
547
+
468
548
  .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;
549
+ border-width: 1px 0 0;
550
+ color: var(--mp-lb-mdkit-border);
551
+ margin: 13px 0;
473
552
  }
474
553
 
475
554
  .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
+ });