@mp-lb/mdkit 0.0.1-main.4.1 → 0.1.0-main.5.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 (67) hide show
  1. package/dist/document/MdKitConflictPanel.js +4 -4
  2. package/dist/document/MdKitDocumentToolbar.js +3 -3
  3. package/dist/markdown/MarkdownBubbleMenu.d.ts +1 -1
  4. package/dist/markdown/MarkdownBubbleMenu.js +58 -8
  5. package/dist/markdown/MdKitEditor.js +1 -1
  6. package/dist/markdown/TiptapMarkdownSurface.js +5 -5
  7. package/dist/theme/MdKitThemeEditor.js +1 -1
  8. package/dist/theme/editorTheme.d.ts +1 -1
  9. package/dist/theme/editorTheme.js +15 -15
  10. package/dist/versioning/VersionHistoryPanel.js +2 -2
  11. package/docs/api.md +1 -1
  12. package/docs/shadcn.md +1 -1
  13. package/docs/styling.md +190 -71
  14. package/package.json +6 -2
  15. package/src/styles.css +271 -258
  16. package/dist/core/documentEngine.test.d.ts +0 -1
  17. package/dist/core/documentEngine.test.js +0 -119
  18. package/dist/document/useMdKitDocument.test.d.ts +0 -1
  19. package/dist/document/useMdKitDocument.test.js +0 -151
  20. package/dist/markdown/MdKitEditor.test.d.ts +0 -1
  21. package/dist/markdown/MdKitEditor.test.js +0 -126
  22. package/dist/markdown/normalizeMarkdownSerialization.test.d.ts +0 -1
  23. package/dist/markdown/normalizeMarkdownSerialization.test.js +0 -16
  24. package/dist/markdown/prepareMarkdownForEditorHydration.test.d.ts +0 -1
  25. package/dist/markdown/prepareMarkdownForEditorHydration.test.js +0 -13
  26. package/dist/markdown/preserveMarkdownWhitespace.test.d.ts +0 -1
  27. package/dist/markdown/preserveMarkdownWhitespace.test.js +0 -25
  28. package/dist/test/setup.d.ts +0 -1
  29. package/dist/test/setup.js +0 -13
  30. package/dist/versioning/useMdKitDocumentVersions.test.d.ts +0 -1
  31. package/dist/versioning/useMdKitDocumentVersions.test.js +0 -41
  32. package/docs/.vitepress/dist/404.html +0 -22
  33. package/docs/.vitepress/dist/api.html +0 -120
  34. package/docs/.vitepress/dist/architecture.html +0 -25
  35. package/docs/.vitepress/dist/assets/api.md.asncK3PQ.js +0 -96
  36. package/docs/.vitepress/dist/assets/api.md.asncK3PQ.lean.js +0 -1
  37. package/docs/.vitepress/dist/assets/app.BQvrHyG0.js +0 -1
  38. package/docs/.vitepress/dist/assets/architecture.md.BHQLarmZ.js +0 -1
  39. package/docs/.vitepress/dist/assets/architecture.md.BHQLarmZ.lean.js +0 -1
  40. package/docs/.vitepress/dist/assets/chunks/framework.RRduUuAx.js +0 -19
  41. package/docs/.vitepress/dist/assets/chunks/theme.CkCo6Nk1.js +0 -1
  42. package/docs/.vitepress/dist/assets/index.md.CITl-897.js +0 -137
  43. package/docs/.vitepress/dist/assets/index.md.CITl-897.lean.js +0 -1
  44. package/docs/.vitepress/dist/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
  45. package/docs/.vitepress/dist/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
  46. package/docs/.vitepress/dist/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
  47. package/docs/.vitepress/dist/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
  48. package/docs/.vitepress/dist/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
  49. package/docs/.vitepress/dist/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
  50. package/docs/.vitepress/dist/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
  51. package/docs/.vitepress/dist/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
  52. package/docs/.vitepress/dist/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
  53. package/docs/.vitepress/dist/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
  54. package/docs/.vitepress/dist/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
  55. package/docs/.vitepress/dist/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
  56. package/docs/.vitepress/dist/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
  57. package/docs/.vitepress/dist/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
  58. package/docs/.vitepress/dist/assets/shadcn.md.C3idOo2N.js +0 -57
  59. package/docs/.vitepress/dist/assets/shadcn.md.C3idOo2N.lean.js +0 -1
  60. package/docs/.vitepress/dist/assets/style.BtrGaL3i.css +0 -1
  61. package/docs/.vitepress/dist/assets/styling.md.B2C6kVFa.js +0 -91
  62. package/docs/.vitepress/dist/assets/styling.md.B2C6kVFa.lean.js +0 -1
  63. package/docs/.vitepress/dist/hashmap.json +0 -1
  64. package/docs/.vitepress/dist/index.html +0 -161
  65. package/docs/.vitepress/dist/shadcn.html +0 -81
  66. package/docs/.vitepress/dist/styling.html +0 -115
  67. package/docs/.vitepress/dist/vp-icons.css +0 -1
@@ -1 +0,0 @@
1
- export {};
@@ -1,119 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { createMdKitDocumentRecord, detectMdKitDocumentConflict, restoreMdKitDocumentVersion, writeMdKitDocumentRecord, } from "./documentEngine";
3
- const now = "2026-04-30T12:00:00.000Z";
4
- describe("documentEngine", () => {
5
- it("creates a current markdown snapshot with an initial version", () => {
6
- const record = createMdKitDocumentRecord({
7
- content: "# Initial",
8
- now,
9
- });
10
- expect(record.current).toEqual({
11
- content: "# Initial",
12
- updatedAt: now,
13
- version: "0",
14
- });
15
- expect(record.versions).toEqual([
16
- {
17
- content: "# Initial",
18
- createdAt: now,
19
- id: "0",
20
- label: "Initial",
21
- updatedAt: now,
22
- version: "0",
23
- },
24
- ]);
25
- });
26
- it("writes a new version when the base version matches", () => {
27
- const record = createMdKitDocumentRecord({ now });
28
- const written = writeMdKitDocumentRecord(record, {
29
- baseVersion: "0",
30
- content: "# Saved",
31
- now: "2026-04-30T12:01:00.000Z",
32
- });
33
- expect(written.result).toEqual({
34
- updatedAt: "2026-04-30T12:01:00.000Z",
35
- version: "1",
36
- });
37
- expect(written.record.current).toMatchObject({
38
- content: "# Saved",
39
- version: "1",
40
- });
41
- expect(written.record.versions.map((version) => version.id)).toEqual([
42
- "0",
43
- "1",
44
- ]);
45
- });
46
- it("returns a conflict when the base version is stale", () => {
47
- const record = createMdKitDocumentRecord({ now });
48
- const first = writeMdKitDocumentRecord(record, {
49
- baseVersion: "0",
50
- content: "first",
51
- now: "2026-04-30T12:01:00.000Z",
52
- });
53
- const second = writeMdKitDocumentRecord(first.record, {
54
- baseVersion: "0",
55
- content: "second",
56
- now: "2026-04-30T12:02:00.000Z",
57
- });
58
- expect(second.result).toEqual({
59
- conflict: true,
60
- updatedAt: "2026-04-30T12:01:00.000Z",
61
- version: "1",
62
- });
63
- expect(second.record).toBe(first.record);
64
- });
65
- it("can force-write a local version over a remote conflict", () => {
66
- const record = createMdKitDocumentRecord({ now });
67
- const first = writeMdKitDocumentRecord(record, {
68
- baseVersion: "0",
69
- content: "remote",
70
- now: "2026-04-30T12:01:00.000Z",
71
- });
72
- const forced = writeMdKitDocumentRecord(first.record, {
73
- baseVersion: "0",
74
- content: "local",
75
- force: true,
76
- label: "Force save",
77
- now: "2026-04-30T12:02:00.000Z",
78
- });
79
- expect(forced.record.current).toMatchObject({
80
- content: "local",
81
- version: "2",
82
- });
83
- expect(forced.record.versions.at(-1)).toMatchObject({
84
- content: "local",
85
- id: "2",
86
- label: "Force save",
87
- });
88
- });
89
- it("restores a saved version into the current snapshot", () => {
90
- const record = createMdKitDocumentRecord({ content: "initial", now });
91
- const first = writeMdKitDocumentRecord(record, {
92
- baseVersion: "0",
93
- content: "later",
94
- now: "2026-04-30T12:01:00.000Z",
95
- });
96
- const restored = restoreMdKitDocumentVersion(first.record, {
97
- now: "2026-04-30T12:02:00.000Z",
98
- versionId: "0",
99
- });
100
- expect(restored.record.current).toMatchObject({
101
- content: "initial",
102
- version: "2",
103
- });
104
- expect(restored.record.versions.at(-1)).toMatchObject({
105
- content: "initial",
106
- label: "Restore 0",
107
- });
108
- });
109
- it("detects conflict from base and current version tokens", () => {
110
- expect(detectMdKitDocumentConflict({
111
- baseVersion: "1",
112
- currentVersion: "2",
113
- })).toBe(true);
114
- expect(detectMdKitDocumentConflict({
115
- baseVersion: 1,
116
- currentVersion: "1",
117
- })).toBe(false);
118
- });
119
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,151 +0,0 @@
1
- import { act, renderHook, waitFor } from "@testing-library/react";
2
- import { afterEach, describe, expect, it, vi } from "vitest";
3
- import { useMdKitDocument } from "./useMdKitDocument";
4
- describe("useMdKitDocument", () => {
5
- afterEach(() => {
6
- vi.clearAllTimers();
7
- vi.useRealTimers();
8
- });
9
- it("marks autosave as pending during the debounce window", async () => {
10
- const adapter = {
11
- readDocument: vi.fn(async () => ({
12
- content: "Initial content",
13
- updatedAt: "2026-01-01T00:00:00.000Z",
14
- version: 1,
15
- })),
16
- resyncDocument: vi.fn(async () => ({
17
- content: "Initial content",
18
- updatedAt: "2026-01-01T00:00:00.000Z",
19
- version: 1,
20
- })),
21
- writeDocument: vi.fn(async () => ({
22
- updatedAt: "2026-01-01T00:00:01.000Z",
23
- version: 2,
24
- })),
25
- };
26
- const { result } = renderHook(() => useMdKitDocument({
27
- adapter,
28
- debounceMs: 250,
29
- documentId: "docs/example.md",
30
- pollMs: 0,
31
- }));
32
- await waitFor(() => {
33
- expect(result.current.value).toBe("Initial content");
34
- });
35
- vi.useFakeTimers();
36
- act(() => {
37
- result.current.setContent("Changed content");
38
- });
39
- expect(result.current.isDirty).toBe(true);
40
- expect(result.current.saveStatus).toBe("pending");
41
- expect(adapter.writeDocument).not.toHaveBeenCalled();
42
- await act(async () => {
43
- await vi.advanceTimersByTimeAsync(250);
44
- });
45
- expect(result.current.saveStatus).toBe("saved");
46
- expect(result.current.isDirty).toBe(false);
47
- expect(adapter.writeDocument).toHaveBeenCalledWith({
48
- baseVersion: 1,
49
- content: "Changed content",
50
- documentId: "docs/example.md",
51
- });
52
- });
53
- it("exposes remote, local, and base content when a save conflicts", async () => {
54
- const adapter = {
55
- readDocument: vi
56
- .fn()
57
- .mockResolvedValueOnce({
58
- content: "Base content",
59
- updatedAt: "2026-01-01T00:00:00.000Z",
60
- version: 1,
61
- })
62
- .mockResolvedValueOnce({
63
- content: "Remote content",
64
- updatedAt: "2026-01-01T00:00:02.000Z",
65
- version: 2,
66
- }),
67
- resyncDocument: vi.fn(),
68
- writeDocument: vi.fn(async () => ({
69
- conflict: true,
70
- updatedAt: "2026-01-01T00:00:02.000Z",
71
- version: 2,
72
- })),
73
- };
74
- const { result } = renderHook(() => useMdKitDocument({
75
- adapter,
76
- documentId: "docs/example.md",
77
- pollMs: 0,
78
- }));
79
- await waitFor(() => {
80
- expect(result.current.value).toBe("Base content");
81
- });
82
- act(() => {
83
- result.current.setContent("Local content");
84
- });
85
- await act(async () => {
86
- await result.current.saveNow();
87
- });
88
- expect(result.current.conflict).toBe(true);
89
- expect(result.current.conflictDetails).toEqual({
90
- baseContent: "Base content",
91
- localContent: "Local content",
92
- remoteContent: "Remote content",
93
- remoteUpdatedAt: "2026-01-01T00:00:02.000Z",
94
- remoteVersion: 2,
95
- });
96
- });
97
- it("force saves local content after a conflict", async () => {
98
- const adapter = {
99
- readDocument: vi
100
- .fn()
101
- .mockResolvedValueOnce({
102
- content: "Base content",
103
- updatedAt: "2026-01-01T00:00:00.000Z",
104
- version: 1,
105
- })
106
- .mockResolvedValueOnce({
107
- content: "Remote content",
108
- updatedAt: "2026-01-01T00:00:02.000Z",
109
- version: 2,
110
- }),
111
- resyncDocument: vi.fn(),
112
- writeDocument: vi
113
- .fn()
114
- .mockResolvedValueOnce({
115
- conflict: true,
116
- updatedAt: "2026-01-01T00:00:02.000Z",
117
- version: 2,
118
- })
119
- .mockResolvedValueOnce({
120
- updatedAt: "2026-01-01T00:00:03.000Z",
121
- version: 3,
122
- }),
123
- };
124
- const { result } = renderHook(() => useMdKitDocument({
125
- adapter,
126
- documentId: "docs/example.md",
127
- pollMs: 0,
128
- }));
129
- await waitFor(() => {
130
- expect(result.current.value).toBe("Base content");
131
- });
132
- act(() => {
133
- result.current.setContent("Local content");
134
- });
135
- await act(async () => {
136
- await result.current.saveNow();
137
- });
138
- expect(result.current.conflict).toBe(true);
139
- await act(async () => {
140
- await result.current.forceSave();
141
- });
142
- expect(result.current.conflict).toBe(false);
143
- expect(result.current.isDirty).toBe(false);
144
- expect(adapter.writeDocument).toHaveBeenLastCalledWith({
145
- baseVersion: null,
146
- content: "Local content",
147
- documentId: "docs/example.md",
148
- force: true,
149
- });
150
- });
151
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,126 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from "react";
3
- import { readFileSync } from "node:fs";
4
- import { resolve } from "node:path";
5
- import { act, fireEvent, render, screen, waitFor, } from "@testing-library/react";
6
- import { describe, expect, it, vi } from "vitest";
7
- import { MdKitEditor } from "./MdKitEditor";
8
- const firstMarkdown = "# Stored document\n\nOriginal paragraph.";
9
- const restoredMarkdown = "# Restored document\n\n- first\n- second";
10
- const TestHarness = () => {
11
- const [value, setValue] = useState(firstMarkdown);
12
- const [revision, setRevision] = useState(0);
13
- const coldRestore = () => {
14
- setValue(restoredMarkdown);
15
- setRevision((current) => current + 1);
16
- };
17
- return (_jsxs("div", { children: [_jsx("button", { type: "button", onClick: () => setValue(restoredMarkdown), children: "Replace from storage" }), _jsx("button", { type: "button", onClick: coldRestore, children: "Cold restore" }), _jsx(MdKitEditor, { instanceKey: revision, value: value, onChange: setValue })] }));
18
- };
19
- const editorText = (container) => {
20
- const editor = container.querySelector(".ProseMirror");
21
- if (!(editor instanceof HTMLElement)) {
22
- throw new Error("Expected TipTap to render a ProseMirror editor.");
23
- }
24
- return editor.textContent ?? "";
25
- };
26
- describe("MdKitEditor", () => {
27
- it("marks the editor as full-height when requested", async () => {
28
- const { container } = render(_jsx(MdKitEditor, { fillHeight: true, value: firstMarkdown, onChange: () => { } }));
29
- await waitFor(() => {
30
- expect(container.querySelector(".mdkit-markdown-editor-fill-height")).toBeTruthy();
31
- expect(editorText(container)).toContain("Stored document");
32
- });
33
- });
34
- it("keeps the editor root full-width without fill-height", async () => {
35
- const css = readFileSync(resolve(__dirname, "../styles.css"), "utf8");
36
- expect(css).toMatch(/\.mdkit-markdown-editor\s*{[^}]*width:\s*100%;/);
37
- });
38
- it("can render the editor as read-only", async () => {
39
- const { container } = render(_jsx(MdKitEditor, { readOnly: true, value: "Read-only document", onChange: () => { } }));
40
- const editor = await waitFor(() => {
41
- const element = container.querySelector(".ProseMirror");
42
- if (!(element instanceof HTMLElement)) {
43
- throw new Error("Expected TipTap to render a ProseMirror editor.");
44
- }
45
- return element;
46
- });
47
- expect(editor.getAttribute("contenteditable")).toBe("false");
48
- expect(container
49
- .querySelector(".mdkit-markdown-editor")
50
- ?.getAttribute("data-read-only")).toBe("true");
51
- });
52
- it("hydrates and restores from serialized markdown values", async () => {
53
- const { container } = render(_jsx(TestHarness, {}));
54
- await waitFor(() => {
55
- expect(editorText(container)).toContain("Stored document");
56
- expect(editorText(container)).toContain("Original paragraph.");
57
- });
58
- await act(async () => {
59
- screen.getByRole("button", { name: "Replace from storage" }).click();
60
- });
61
- await waitFor(() => {
62
- expect(editorText(container)).toContain("Restored document");
63
- expect(editorText(container)).toContain("first");
64
- expect(editorText(container)).toContain("second");
65
- });
66
- await act(async () => {
67
- screen.getByRole("button", { name: "Cold restore" }).click();
68
- });
69
- await waitFor(() => {
70
- expect(editorText(container)).toContain("Restored document");
71
- expect(editorText(container)).toContain("first");
72
- expect(editorText(container)).toContain("second");
73
- });
74
- });
75
- it("hydrates middle blank-line runs as visible empty editor paragraphs", async () => {
76
- const markdown = "Before paragraph.\n\nWhitespace probe:\n\n\n\n\nAfter paragraph.";
77
- const { container } = render(_jsx(MdKitEditor, { value: markdown, onChange: () => { } }));
78
- await waitFor(() => {
79
- expect(editorText(container)).toContain("Whitespace probe:");
80
- expect(editorText(container)).toContain("After paragraph.");
81
- });
82
- const paragraphs = Array.from(container.querySelectorAll(".ProseMirror p"));
83
- const whitespaceParagraphIndex = paragraphs.findIndex((paragraph) => paragraph.textContent === "Whitespace probe:");
84
- const afterParagraphIndex = paragraphs.findIndex((paragraph) => paragraph.textContent === "After paragraph.");
85
- expect(whitespaceParagraphIndex).toBeGreaterThanOrEqual(0);
86
- expect(afterParagraphIndex).toBeGreaterThan(whitespaceParagraphIndex);
87
- expect(afterParagraphIndex - whitespaceParagraphIndex).toBeGreaterThan(1);
88
- });
89
- it("keeps the same editor instance when controlled value changes", async () => {
90
- const { container, rerender } = render(_jsx(MdKitEditor, { value: "one", onChange: () => { } }));
91
- const editor = await waitFor(() => {
92
- const element = container.querySelector(".ProseMirror");
93
- if (!(element instanceof HTMLElement)) {
94
- throw new Error("Expected TipTap to render a ProseMirror editor.");
95
- }
96
- return element;
97
- });
98
- rerender(_jsx(MdKitEditor, { value: "one two", onChange: () => { } }));
99
- await waitFor(() => {
100
- expect(container.querySelector(".ProseMirror")).toBe(editor);
101
- expect(editorText(container)).toContain("one two");
102
- });
103
- });
104
- it("focuses the editor when the fill-height background is clicked", async () => {
105
- const onFocusChange = vi.fn();
106
- const { container } = render(_jsx(MdKitEditor, { fillHeight: true, value: "", onChange: () => { }, onFocusChange: onFocusChange }));
107
- const surface = await waitFor(() => {
108
- const element = container.querySelector(".hsk-editor-surface");
109
- if (!(element instanceof HTMLElement)) {
110
- throw new Error("Expected editor surface to render.");
111
- }
112
- return element;
113
- });
114
- const editor = container.querySelector(".ProseMirror");
115
- if (!(editor instanceof HTMLElement)) {
116
- throw new Error("Expected TipTap to render a ProseMirror editor.");
117
- }
118
- fireEvent.pointerDown(surface);
119
- fireEvent.pointerUp(surface);
120
- fireEvent.click(surface);
121
- await waitFor(() => {
122
- expect(document.activeElement).toBe(editor);
123
- expect(onFocusChange).toHaveBeenCalledWith(true);
124
- });
125
- });
126
- });
@@ -1,16 +0,0 @@
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-  \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  \n\n- real")).toBe("Paragraph  \n\n- real");
9
- });
10
- it("removes standalone TipTap non-breaking-space placeholder lines", () => {
11
- expect(normalizeMarkdownSerialization("- first\n\n \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 \n\n \n\n \n\nAfter")).toBe("Before\n\n\n\n\nAfter");
15
- });
16
- });
@@ -1,13 +0,0 @@
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 \n\n \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 \n\nAfter");
12
- });
13
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,25 +0,0 @@
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
- });
@@ -1 +0,0 @@
1
- import "@testing-library/jest-dom/vitest";
@@ -1,13 +0,0 @@
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
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,41 +0,0 @@
1
- import { act, renderHook, waitFor } from "@testing-library/react";
2
- import { describe, expect, it, vi } from "vitest";
3
- import { useMdKitDocumentVersions } from "./useMdKitDocumentVersions";
4
- describe("useMdKitDocumentVersions", () => {
5
- it("keeps the selected version preview open after loading it", async () => {
6
- const adapter = {
7
- listDocumentVersions: vi.fn(async () => [
8
- {
9
- createdAt: "2026-01-01T00:00:00.000Z",
10
- id: "1",
11
- label: "Version 1",
12
- version: 1,
13
- },
14
- ]),
15
- readDocumentVersion: vi.fn(async () => ({
16
- content: "Version one content",
17
- createdAt: "2026-01-01T00:00:00.000Z",
18
- id: "1",
19
- label: "Version 1",
20
- updatedAt: "2026-01-01T00:00:00.000Z",
21
- version: 1,
22
- })),
23
- };
24
- const { result } = renderHook(() => useMdKitDocumentVersions({
25
- adapter,
26
- documentId: "docs/example.md",
27
- }));
28
- await waitFor(() => {
29
- expect(result.current.versions).toHaveLength(1);
30
- });
31
- await act(async () => {
32
- await result.current.openVersion("1");
33
- });
34
- await waitFor(() => {
35
- expect(result.current.selectedVersionId).toBe("1");
36
- expect(result.current.selectedVersion?.content).toBe("Version one content");
37
- expect(result.current.isLoading).toBe(false);
38
- });
39
- expect(adapter.listDocumentVersions).toHaveBeenCalledTimes(1);
40
- });
41
- });
@@ -1,22 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en-US" dir="ltr">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width,initial-scale=1">
6
- <title>404 | Markdown Editor Kit</title>
7
- <meta name="description" content="Not Found">
8
- <meta name="generator" content="VitePress v1.6.4">
9
- <link rel="preload stylesheet" href="/assets/style.BtrGaL3i.css" as="style">
10
- <link rel="preload stylesheet" href="/vp-icons.css" as="style">
11
-
12
- <script type="module" src="/assets/app.BQvrHyG0.js"></script>
13
- <link rel="preload" href="/assets/inter-roman-latin.Di8DUHzh.woff2" as="font" type="font/woff2" crossorigin="">
14
- <script id="check-dark-mode">(()=>{const e=localStorage.getItem("vitepress-theme-appearance")||"auto",a=window.matchMedia("(prefers-color-scheme: dark)").matches;(!e||e==="auto"?a:e==="dark")&&document.documentElement.classList.add("dark")})();</script>
15
- <script id="check-mac-os">document.documentElement.classList.toggle("mac",/Mac|iPhone|iPod|iPad/i.test(navigator.platform));</script>
16
- </head>
17
- <body>
18
- <div id="app"></div>
19
- <script>window.__VP_HASH_MAP__=JSON.parse("{\"api.md\":\"asncK3PQ\",\"architecture.md\":\"BHQLarmZ\",\"index.md\":\"CITl-897\",\"shadcn.md\":\"C3idOo2N\",\"styling.md\":\"B2C6kVFa\"}");window.__VP_SITE_DATA__=JSON.parse("{\"lang\":\"en-US\",\"dir\":\"ltr\",\"title\":\"Markdown Editor Kit\",\"description\":\"Docs for the mdkit markdown editor package\",\"base\":\"/\",\"head\":[],\"router\":{\"prefetchLinks\":true},\"appearance\":true,\"themeConfig\":{\"nav\":[{\"text\":\"Quick Start\",\"link\":\"/\"},{\"text\":\"Styling\",\"link\":\"/styling\"},{\"text\":\"Shadcn\",\"link\":\"/shadcn\"},{\"text\":\"API\",\"link\":\"/api\"},{\"text\":\"Architecture\",\"link\":\"/architecture\"}],\"sidebar\":[{\"text\":\"Guide\",\"items\":[{\"text\":\"Quick Start\",\"link\":\"/\"},{\"text\":\"Styling\",\"link\":\"/styling\"},{\"text\":\"Shadcn Plugin\",\"link\":\"/shadcn\"},{\"text\":\"API Reference\",\"link\":\"/api\"},{\"text\":\"Architecture\",\"link\":\"/architecture\"}]}],\"socialLinks\":[{\"icon\":\"github\",\"link\":\"https://github.com/mp-lb/mdkit\"}]},\"locales\":{},\"scrollOffset\":134,\"cleanUrls\":true}");</script>
20
-
21
- </body>
22
- </html>