@surrealdb/ui 1.2.16 → 1.2.17

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 (149) hide show
  1. package/dist/assets/1299e4695ad0bd61.svg +5 -0
  2. package/dist/assets/408117e904653548.svg +48 -0
  3. package/dist/assets/b516b020352f6369.svg +1 -0
  4. package/dist/assets/dfde8de566c3c2b9.svg +11 -0
  5. package/dist/assets/e9eae7bc863f211b.svg +1 -0
  6. package/dist/assets/f74ae3707a5d418f.svg +1 -0
  7. package/dist/ui.d.ts +12 -0
  8. package/dist/ui.js +659 -647
  9. package/dist/ui.js.map +1 -1
  10. package/package.json +8 -2
  11. package/.claude/launch.json +0 -17
  12. package/.oxfmtrc.json +0 -27
  13. package/.oxlintrc.json +0 -23
  14. package/.picto-fix-png/Cursor.png +0 -0
  15. package/.picto-fix-png/DocumentGradient.png +0 -0
  16. package/.picto-fix-png/FileGradient.png +0 -0
  17. package/.picto-fix-png/HealthBubbleGradient.png +0 -0
  18. package/.picto-fix-png/LearnGradient.png +0 -0
  19. package/.picto-fix-png/MediaFileGradient.png +0 -0
  20. package/.zed/settings.json +0 -36
  21. package/AGENTS.md +0 -111
  22. package/CLAUDE.md +0 -111
  23. package/REVIEW.md +0 -81
  24. package/doc/code-style.md +0 -264
  25. package/doc/release.md +0 -102
  26. package/doc/stacker-plan.md +0 -219
  27. package/release-notes.md +0 -39
  28. package/tests/_setup/e2e-helpers.tsx +0 -169
  29. package/tests/_setup/markdown-classes.ts +0 -3
  30. package/tests/_setup/portable-stories.ts +0 -33
  31. package/tests/e2e/MarkdownEditor/__screenshots__/content-blocks.test.tsx/MarkdownEditor---content-blocks-continues-an-ordered-list-when-Enter-is-pressed-at-the-end-of-a-numbered-line-1.png +0 -0
  32. package/tests/e2e/MarkdownEditor/__screenshots__/content-blocks.test.tsx/MarkdownEditor---content-blocks-renders-a-diagram-for-the-fenced-mermaid-block-1.png +0 -0
  33. package/tests/e2e/MarkdownEditor/__screenshots__/content-blocks.test.tsx/MarkdownEditor---content-blocks-renders-a-horizontal-rule-as-a-separator-1.png +0 -0
  34. package/tests/e2e/MarkdownEditor/__screenshots__/content-blocks.test.tsx/MarkdownEditor---content-blocks-renders-a-read-only-table-preview-when-the-table-block-is-inactive-1.png +0 -0
  35. package/tests/e2e/MarkdownEditor/__screenshots__/content-blocks.test.tsx/MarkdownEditor---content-blocks-shows-blockquote-text-from-the-sample-document-1.png +0 -0
  36. package/tests/e2e/MarkdownEditor/__screenshots__/content-blocks.test.tsx/MarkdownEditor---content-blocks-shows-fenced-TypeScript-sample-code-in-the-document-1.png +0 -0
  37. package/tests/e2e/MarkdownEditor/__screenshots__/edits.test.tsx/MarkdownEditor---edits-auto-continues-a-bullet-list-when-Enter-is-pressed-at-the-end-of-a-list-item-1.png +0 -0
  38. package/tests/e2e/MarkdownEditor/__screenshots__/edits.test.tsx/MarkdownEditor---edits-expands-triple-backtick-into-a-fenced-code-block-with-the-caret-on-the-empty-body-line-1.png +0 -0
  39. package/tests/e2e/MarkdownEditor/__screenshots__/edits.test.tsx/MarkdownEditor---edits-reflects-typed-characters-in-the-underlying-document-1.png +0 -0
  40. package/tests/e2e/MarkdownEditor/__screenshots__/heading-fold.test.tsx/MarkdownEditor---heading-folds-viewer--folds-and-unfolds-a-heading-section-via-the-margin-control-1.png +0 -0
  41. package/tests/e2e/MarkdownEditor/__screenshots__/hybrid-widgets.test.tsx/MarkdownEditor---hybrid-widgets-clicking-a-callout-header-focuses-the-editor-and-parks-the-caret-inside-the-callout--REASONING--4--1.png +0 -0
  42. package/tests/e2e/MarkdownEditor/__screenshots__/hybrid-widgets.test.tsx/MarkdownEditor---hybrid-widgets-keeps-list-bullets-consistent-after-hopping-the-caret-across-items-several-times-1.png +0 -0
  43. package/tests/e2e/MarkdownEditor/__screenshots__/hybrid-widgets.test.tsx/MarkdownEditor---hybrid-widgets-reveals-heading-marks-when-the-caret-enters-the-heading-line-and-hides-them-when-it-leaves-1.png +0 -0
  44. package/tests/e2e/MarkdownEditor/__screenshots__/hybrid-widgets.test.tsx/MarkdownEditor---hybrid-widgets-shows-the-right-callout-title-when-moving-focus-between-consecutive-callouts-1.png +0 -0
  45. package/tests/e2e/MarkdownEditor/__screenshots__/hybrid-widgets.test.tsx/MarkdownEditor---preview-widgets-clicking-a-callout-header-focuses-the-editor-with-the-caret-in-that-callout-1.png +0 -0
  46. package/tests/e2e/MarkdownEditor/__screenshots__/hybrid-widgets.test.tsx/MarkdownEditor---preview-widgets-keeps-list-bullets-consistent-after-hopping-the-caret-across-items-several-times-1.png +0 -0
  47. package/tests/e2e/MarkdownEditor/__screenshots__/hybrid-widgets.test.tsx/MarkdownEditor---preview-widgets-shows-the-right-callout-title-when-moving-focus-between-consecutive-callouts-1.png +0 -0
  48. package/tests/e2e/MarkdownEditor/__screenshots__/jsx-block-content.test.tsx/MarkdownEditor---block-JSX-components-clicking-the-edit-source-action-selects-the-component-source-range-1.png +0 -0
  49. package/tests/e2e/MarkdownEditor/__screenshots__/jsx-block-content.test.tsx/MarkdownEditor---block-JSX-components-renders-block-components-via-JsxBlockWidget-with-an-edit-source-action-1.png +0 -0
  50. package/tests/e2e/MarkdownEditor/__screenshots__/jsx-block-content.test.tsx/MarkdownEditor---inline-JSX-rendering-block-content-keeps-multiple-inline-JSX-widgets-on-the-same-line-side-by-side-1.png +0 -0
  51. package/tests/e2e/MarkdownEditor/__screenshots__/jsx-highlight.test.tsx/MarkdownEditor---JSX-attribute-highlighting-does-not-let-the-nested-HTML-parser-mis-highlight-attributes-after-an-expression-1.png +0 -0
  52. package/tests/e2e/MarkdownEditor/__screenshots__/jsx-inline-click.test.tsx/MarkdownEditor---inline-JSX-click-reveal-reveals-the-raw-source-when-an-inline-component-badge-is-clicked-1.png +0 -0
  53. package/tests/e2e/MarkdownEditor/__screenshots__/jsx-inline-click.test.tsx/MarkdownEditor---inline-JSX-click-reveal-reveals-the-raw-source-when-an-inline-component-badge-is-clicked-2.png +0 -0
  54. package/tests/e2e/MarkdownEditor/__screenshots__/jsx-inline-click.test.tsx/MarkdownEditor---inline-JSX-click-reveal-reveals-the-raw-source-when-an-inline-component-badge-is-clicked-3.png +0 -0
  55. package/tests/e2e/MarkdownEditor/__screenshots__/jsx-selection.test.tsx/MarkdownEditor---JSX-selection-reveal-shows-JSX-widget-when-inactive-and-raw-source-when-caret-is-inside-the-tag-1.png +0 -0
  56. package/tests/e2e/MarkdownEditor/__screenshots__/media-sizing.test.tsx/MarkdownEditor---media-sizing-images-and-videos-do-not-exceed-the-editor-content-width-1.png +0 -0
  57. package/tests/e2e/MarkdownEditor/__screenshots__/media-sizing.test.tsx/MarkdownEditor---media-sizing-matches-MarkdownViewer-image-width-in-the-split-playground-1.png +0 -0
  58. package/tests/e2e/MarkdownEditor/__screenshots__/modes.test.tsx/MarkdownEditor---modes-standalone-MarkdownViewer-is-not-a-CodeMirror-surface-1.png +0 -0
  59. package/tests/e2e/MarkdownEditor/__screenshots__/modes.test.tsx/MarkdownEditor---modes-toggling-the-SegmentedControl-swaps-mode-without-remounting-the-editor-1.png +0 -0
  60. package/tests/e2e/MarkdownEditor/__screenshots__/regressions.test.tsx/MarkdownEditor---regressions-repeated-checkbox-toggles-do-not-duplicate-or-drift-task-markers-1.png +0 -0
  61. package/tests/e2e/MarkdownEditor/__screenshots__/regressions.test.tsx/MarkdownEditor---regressions-scroll-position-is-preserved-when-the-document-is-edited--chat-92584463--1.png +0 -0
  62. package/tests/e2e/MarkdownEditor/__screenshots__/regressions.test.tsx/MarkdownEditor---regressions-task-checkbox-toggles-between-checked-and-unchecked-in-the-source-document-1.png +0 -0
  63. package/tests/e2e/MarkdownEditor/__screenshots__/regressions.test.tsx/MarkdownEditor---regressions-toggle-via-checkbox-preserves-task-marker-widgets-visible-from-the-previous-caret-position-1.png +0 -0
  64. package/tests/e2e/MarkdownEditor/__screenshots__/slash-commands.test.tsx/MarkdownEditor---slash-commands-dismisses-on-Escape-and-leaves-the-slash-as-literal-text-1.png +0 -0
  65. package/tests/e2e/MarkdownEditor/__screenshots__/slash-commands.test.tsx/MarkdownEditor---slash-commands-highlights-the-slash-and-shows-an-Enter-command-placeholder-until-text-is-typed-1.png +0 -0
  66. package/tests/e2e/MarkdownEditor/__screenshots__/slash-commands.test.tsx/MarkdownEditor---slash-commands-opens-a-filtered--keyboard-navigable-menu-and-inserts-boilerplate-on-Enter-1.png +0 -0
  67. package/tests/e2e/MarkdownEditor/__screenshots__/undo-redo.test.tsx/MarkdownEditor---undo-and-redo-restores-undone-document-edits-via-redo-1.png +0 -0
  68. package/tests/e2e/MarkdownEditor/content-blocks.test.tsx +0 -153
  69. package/tests/e2e/MarkdownEditor/edits.test.tsx +0 -112
  70. package/tests/e2e/MarkdownEditor/heading-fold.test.tsx +0 -45
  71. package/tests/e2e/MarkdownEditor/hybrid-widgets.test.tsx +0 -193
  72. package/tests/e2e/MarkdownEditor/image-on-image.test.tsx +0 -73
  73. package/tests/e2e/MarkdownEditor/jsx-block-content.test.tsx +0 -243
  74. package/tests/e2e/MarkdownEditor/jsx-highlight.test.tsx +0 -69
  75. package/tests/e2e/MarkdownEditor/jsx-inline-badges.test.tsx +0 -60
  76. package/tests/e2e/MarkdownEditor/jsx-inline-click.test.tsx +0 -54
  77. package/tests/e2e/MarkdownEditor/jsx-selection.test.tsx +0 -44
  78. package/tests/e2e/MarkdownEditor/link-placeholder.test.tsx +0 -68
  79. package/tests/e2e/MarkdownEditor/media-align.test.tsx +0 -58
  80. package/tests/e2e/MarkdownEditor/media-edit.test.tsx +0 -64
  81. package/tests/e2e/MarkdownEditor/media-sizing.test.tsx +0 -124
  82. package/tests/e2e/MarkdownEditor/modes.test.tsx +0 -94
  83. package/tests/e2e/MarkdownEditor/onimage-parity.test.tsx +0 -50
  84. package/tests/e2e/MarkdownEditor/regressions.test.tsx +0 -223
  85. package/tests/e2e/MarkdownEditor/slash-commands.test.tsx +0 -100
  86. package/tests/e2e/MarkdownEditor/table-click.test.tsx +0 -48
  87. package/tests/e2e/MarkdownEditor/table-controls.test.tsx +0 -57
  88. package/tests/e2e/MarkdownEditor/table-format.test.tsx +0 -42
  89. package/tests/e2e/MarkdownEditor/undo-redo.test.tsx +0 -39
  90. package/tests/e2e/MarkdownViewer/__screenshots__/parity.test.tsx/MarkdownViewer---editor-parity-matches-computed-font-size-on-first-heading-1.png +0 -0
  91. package/tests/e2e/MarkdownViewer/__screenshots__/parity.test.tsx/MarkdownViewer---editor-parity-matches-counts-for-shared-structural-classes-1.png +0 -0
  92. package/tests/e2e/MarkdownViewer/__screenshots__/parity.test.tsx/MarkdownViewer---editor-parity-matches-visible-text-between-preview-editor--blurred--and-MarkdownViewer-1.png +0 -0
  93. package/tests/e2e/MarkdownViewer/__screenshots__/render.test.tsx/MarkdownViewer---render-exercises-shared-preview-class-names-without-mounting-CodeMirror-1.png +0 -0
  94. package/tests/e2e/MarkdownViewer/parity.test.tsx +0 -191
  95. package/tests/e2e/MarkdownViewer/render.test.tsx +0 -36
  96. package/tests/e2e/Stacker/stacker.test.tsx +0 -304
  97. package/tests/unit/Editor/helpers.test.ts +0 -42
  98. package/tests/unit/MarkdownConformance/error-isolation.test.tsx +0 -44
  99. package/tests/unit/MarkdownConformance/indentation.test.tsx +0 -30
  100. package/tests/unit/MarkdownConformance/inline-code-fuzz.test.tsx +0 -255
  101. package/tests/unit/MarkdownConformance/jsx-attributes.test.tsx +0 -57
  102. package/tests/unit/MarkdownConformance/jsx-fuzz.test.tsx +0 -165
  103. package/tests/unit/MarkdownConformance/links.test.tsx +0 -95
  104. package/tests/unit/MarkdownConformance/render-helpers.tsx +0 -88
  105. package/tests/unit/MarkdownEditor/code-info.test.ts +0 -63
  106. package/tests/unit/MarkdownEditor/decorations.test.ts +0 -490
  107. package/tests/unit/MarkdownEditor/editor-ready.test.ts +0 -36
  108. package/tests/unit/MarkdownEditor/html-descriptors.test.ts +0 -94
  109. package/tests/unit/MarkdownEditor/indented-fence.test.ts +0 -103
  110. package/tests/unit/MarkdownEditor/jsx-attr-scan.test.ts +0 -115
  111. package/tests/unit/MarkdownEditor/jsx-block-widget.test.ts +0 -92
  112. package/tests/unit/MarkdownEditor/jsx-tag-grammar.test.ts +0 -88
  113. package/tests/unit/MarkdownEditor/list-indent.test.ts +0 -95
  114. package/tests/unit/MarkdownEditor/slash-commands.test.ts +0 -213
  115. package/tests/unit/MarkdownEditor/table-format.test.ts +0 -83
  116. package/tests/unit/MarkdownEditor/table.test.ts +0 -119
  117. package/tests/unit/MarkdownEditor/triggers.test.ts +0 -244
  118. package/tests/unit/MarkdownEditor/widget-store.test.ts +0 -105
  119. package/tests/unit/MarkdownViewer/callout.test.tsx +0 -60
  120. package/tests/unit/MarkdownViewer/code-highlight.test.tsx +0 -61
  121. package/tests/unit/MarkdownViewer/code-title.test.tsx +0 -62
  122. package/tests/unit/MarkdownViewer/features.test.tsx +0 -164
  123. package/tests/unit/MarkdownViewer/headings.test.tsx +0 -40
  124. package/tests/unit/MarkdownViewer/indented-jsx-children.test.tsx +0 -98
  125. package/tests/unit/MarkdownViewer/jsx-block-nesting.test.tsx +0 -234
  126. package/tests/unit/MarkdownViewer/jsx.test.tsx +0 -211
  127. package/tests/unit/MarkdownViewer/list-bullets.test.tsx +0 -49
  128. package/tests/unit/MarkdownViewer/list-code.test.tsx +0 -65
  129. package/tests/unit/MarkdownViewer/preserve-newlines.test.tsx +0 -143
  130. package/tests/unit/MarkdownViewer/read-time.test.ts +0 -21
  131. package/tests/unit/MarkdownViewer/renderers.test.tsx +0 -95
  132. package/tests/unit/MarkdownViewer/runnable.test.tsx +0 -69
  133. package/tests/unit/MarkdownViewer/ssr.test.tsx +0 -94
  134. package/tests/unit/MarkdownViewer/syntax-highlighting.test.tsx +0 -74
  135. package/tests/unit/MarkdownViewer/tabs-fenced-code.test.tsx +0 -196
  136. package/tests/unit/MarkdownViewer/tabs.test.tsx +0 -116
  137. package/tests/unit/Stacker/drag.test.ts +0 -21
  138. package/tests/unit/Stacker/helpers.test.ts +0 -105
  139. package/tests/unit/Stacker/reducer.test.ts +0 -107
  140. package/tests/unit/Syntax/language.test.ts +0 -63
  141. package/tools/duplicate-vector-mappings.json +0 -143
  142. package/tools/glass-export-overrides.json +0 -8
  143. package/tools/icon-parser.ts +0 -78
  144. package/tools/integration-gradient-exports.json +0 -57
  145. package/tools/picto-compare.ts +0 -64
  146. package/tools/picto-merge-urls.ts +0 -43
  147. package/tools/picto-sync.ts +0 -630
  148. package/tools/picto-verify-gradients.ts +0 -86
  149. package/tools/vector-export-nodes.json +0 -977
@@ -1,103 +0,0 @@
1
- import { markdownSourceFromString, parseMarkdownTree } from "@src/lib/markdown";
2
- import { readCodeText } from "@src/lib/markdown/tree/code-node";
3
- import { describe, expect, it } from "vitest";
4
-
5
- /** Reconstructed body text of the (single) FencedCode node. */
6
- function fencedCodeBody(text: string): string | null {
7
- const tree = parseMarkdownTree(text);
8
- const src = markdownSourceFromString(text);
9
- let body: string | null = null;
10
- tree.iterate({
11
- enter: (n) => {
12
- if (n.name === "FencedCode" && body === null) body = readCodeText(n.node, src);
13
- },
14
- });
15
- return body;
16
- }
17
-
18
- /** Top-level node names of a parsed document. */
19
- function topLevelNodes(text: string): { name: string; text: string }[] {
20
- const tree = parseMarkdownTree(text);
21
- const src = markdownSourceFromString(text);
22
- const out: { name: string; text: string }[] = [];
23
- let ch = tree.topNode.firstChild;
24
- while (ch) {
25
- out.push({ name: ch.name, text: src.slice(ch.from, ch.to) });
26
- ch = ch.nextSibling;
27
- }
28
- return out;
29
- }
30
-
31
- /** The source text of the (single) FencedCode node, or null if none. */
32
- function fencedCodeText(text: string): string | null {
33
- const tree = parseMarkdownTree(text);
34
- const src = markdownSourceFromString(text);
35
- let found: string | null = null;
36
- tree.iterate({
37
- enter: (n) => {
38
- if (n.name === "FencedCode" && found === null) found = src.slice(n.from, n.to);
39
- },
40
- });
41
- return found;
42
- }
43
-
44
- describe("MarkdownEditor / indented fenced code closing", () => {
45
- it("closes a tab-indented fence and does not swallow following content", () => {
46
- const text = "\t```ts\n\tcode\n\t```\ntext";
47
- const fence = fencedCodeText(text);
48
- expect(fence).not.toBeNull();
49
- expect(fence).not.toContain("text");
50
- // The trailing paragraph survives as its own node.
51
- expect(
52
- topLevelNodes(text).some((n) => n.name === "Paragraph" && n.text.includes("text")),
53
- ).toBe(true);
54
- });
55
-
56
- it("closes a 4-space-indented fence and does not swallow following content", () => {
57
- const text = " ```ts\n code\n ```\ntext";
58
- const fence = fencedCodeText(text);
59
- expect(fence).not.toBeNull();
60
- expect(fence).not.toContain("text");
61
- });
62
-
63
- it("does not let an indented fence swallow the closing </Tabs> tag", () => {
64
- const text = [
65
- "<Tabs>",
66
- '\t<TabItem value="esm" label="ESM">',
67
- "\t```ts",
68
- "\tconst db = 1;",
69
- "\t```",
70
- "\t</TabItem>",
71
- "</Tabs>",
72
- ].join("\n");
73
- // </Tabs> and </TabItem> must be their own HTMLBlock nodes, not buried
74
- // inside a runaway FencedCode.
75
- const fence = fencedCodeText(text);
76
- expect(fence).not.toContain("</TabItem>");
77
- expect(fence).not.toContain("</Tabs>");
78
- const names = topLevelNodes(text).map((n) => n.name);
79
- expect(names.filter((n) => n === "HTMLBlock").length).toBeGreaterThanOrEqual(4);
80
- });
81
-
82
- it("still parses a flush fence normally (regression)", () => {
83
- const text = "```ts\ncode\n```\ntext";
84
- expect(fencedCodeText(text)).toBe("```ts\ncode\n```");
85
- });
86
-
87
- it("still parses a fence nested in a list item (regression)", () => {
88
- const text = ["- item", " ```js", " code;", " ```", "", "after"].join("\n");
89
- const fence = fencedCodeText(text);
90
- expect(fence).not.toBeNull();
91
- expect(fence).not.toContain("after");
92
- });
93
-
94
- // Matches MDX/micromark: exactly the opening fence's indentation is removed
95
- // from each content line; deeper indentation is preserved.
96
- it("strips the opening fence indent from content, preserving deeper indent", () => {
97
- const text =
98
- "\t```ts\n\timport { Surreal } from 'surrealdb';\n\t\tconst indented = 1;\n\t```";
99
- expect(fencedCodeBody(text)).toBe(
100
- "import { Surreal } from 'surrealdb';\n\tconst indented = 1;",
101
- );
102
- });
103
- });
@@ -1,115 +0,0 @@
1
- import {
2
- buildJsxTagHighlights,
3
- findJsonAttributeClose,
4
- parseAttributes,
5
- parseJsonAttribute,
6
- resolveAttributeValue,
7
- } from "@src/lib/markdown/grammar/html-attrs";
8
- import { describe, expect, it } from "vitest";
9
-
10
- describe("findJsonAttributeClose", () => {
11
- it("finds the closing brace for nested JSON objects and strings", () => {
12
- const json = '{"version": "2.0.0", "items": [{"id": 1}]}';
13
- const full = `{${json}}`;
14
- const bounds = findJsonAttributeClose(`${full} trailing`, 0);
15
- expect(bounds).toEqual({
16
- contentFrom: 1,
17
- contentTo: full.length - 1,
18
- closeBrace: full.length - 1,
19
- });
20
- });
21
-
22
- it("handles braces inside JSON strings", () => {
23
- const json = '{"note": "}"}';
24
- const full = `{${json}}`;
25
- const bounds = findJsonAttributeClose(full, 0);
26
- expect(bounds?.closeBrace).toBe(full.length - 1);
27
- });
28
- });
29
-
30
- describe("parseJsonAttribute", () => {
31
- it("parses JSON literals without evaluation", () => {
32
- expect(parseJsonAttribute("42")).toBe(42);
33
- expect(parseJsonAttribute('"hello"')).toBe("hello");
34
- expect(parseJsonAttribute('{"enabled": true}')).toEqual({ enabled: true });
35
- });
36
-
37
- it("returns undefined for invalid JSON", () => {
38
- expect(parseJsonAttribute("{ foo: 1 }")).toBeUndefined();
39
- expect(parseJsonAttribute("count")).toBeUndefined();
40
- });
41
- });
42
-
43
- describe("resolveAttributeValue", () => {
44
- it("resolves JSON attribute values", () => {
45
- expect(resolveAttributeValue({ type: "json", value: "100" })).toBe(100);
46
- expect(resolveAttributeValue({ type: "json", value: '"2.0.0"' })).toBe("2.0.0");
47
- });
48
- });
49
-
50
- describe("buildJsxTagHighlights", () => {
51
- const tag = '<Since v={"2.0.0"} t="true" b="false" />';
52
-
53
- it("covers the attribute section without gaps for the nested HTML parser", () => {
54
- const highlights = buildJsxTagHighlights(tag);
55
- const attrStart = tag.indexOf(" v=");
56
- const attrEnd = tag.lastIndexOf(">");
57
-
58
- const gaps = leftoverGaps(attrStart, attrEnd, highlights);
59
- expect(gaps).toEqual([]);
60
- });
61
-
62
- it("labels attribute names and quoted literals after a JSON value", () => {
63
- const highlights = buildJsxTagHighlights(tag);
64
- const names = textAtKinds(tag, highlights, "name");
65
- const strings = textAtKinds(tag, highlights, "string");
66
-
67
- expect(names).toEqual(["v", "t", "b"]);
68
- expect(strings).toEqual(['"true"', '"false"']);
69
- });
70
-
71
- it("matches parseAttributes JSON boundaries", () => {
72
- const { endIndex } = readTagNameForTest(tag);
73
- const attrString = tag.slice(endIndex, tag.lastIndexOf(">"));
74
- const attrs = parseAttributes(attrString);
75
- const expr = buildJsxTagHighlights(tag).find((h) => h.kind === "expression-content");
76
-
77
- expect(attrs.v).toEqual({ type: "json", value: '"2.0.0"' });
78
- expect(expr).toBeDefined();
79
- if (!expr) return;
80
- expect(tag.slice(expr.from, expr.to)).toBe('"2.0.0"');
81
- expect(attrs.t).toBe("true");
82
- expect(attrs.b).toBe("false");
83
- });
84
- });
85
-
86
- function readTagNameForTest(text: string): { endIndex: number } {
87
- let i = 1;
88
- while (i < text.length && !/[\s/>]/.test(text[i] ?? "")) i++;
89
- return { endIndex: i };
90
- }
91
-
92
- function leftoverGaps(
93
- from: number,
94
- to: number,
95
- spans: { from: number; to: number }[],
96
- ): { from: number; to: number }[] {
97
- const gaps: { from: number; to: number }[] = [];
98
- let pos = from;
99
-
100
- for (const span of spans) {
101
- if (span.from > pos) gaps.push({ from: pos, to: span.from });
102
- pos = Math.max(pos, span.to);
103
- }
104
-
105
- if (pos < to) gaps.push({ from: pos, to });
106
- return gaps;
107
- }
108
-
109
- function textAtKinds(
110
- text: string,
111
- highlights: ReturnType<typeof buildJsxTagHighlights>,
112
- kind: "name" | "string",
113
- ): string[] {
114
- return highlights.filter((h) => h.kind === kind).map((h) => text.slice(h.from, h.to));
115
- }
@@ -1,92 +0,0 @@
1
- import { EditorSelection, EditorState } from "@codemirror/state";
2
- import { type Decoration, type DecorationSet, EditorView } from "@codemirror/view";
3
- import { markdownModeField, setMarkdownMode } from "@src/lib/markdown";
4
- import { markdownDecorations } from "@src/lib/markdown/editor/decorations";
5
- import { setMarkdownEditorFocused } from "@src/lib/markdown/editor/decorations/editor-focus";
6
- import { JsxBlockWidget } from "@src/lib/markdown/editor/widgets/jsx-widget";
7
- import { createWidgetStore } from "@src/lib/markdown/editor/widgets/store";
8
- import { Callout } from "@src/lib/markdown/grammar/callout";
9
- import { createMarkdownLanguage } from "@src/lib/markdown/grammar/language";
10
- import { afterEach, describe, expect, it } from "vitest";
11
-
12
- const views: EditorView[] = [];
13
-
14
- afterEach(() => {
15
- for (const v of views.splice(0)) v.destroy();
16
- });
17
-
18
- /** Build a focused, preview-mode editor with the caret at `cursor`. */
19
- function preview(doc: string, cursor: number): EditorView {
20
- const state = EditorState.create({
21
- doc,
22
- extensions: [
23
- createMarkdownLanguage({ extensions: [Callout] }),
24
- markdownModeField,
25
- markdownDecorations({ widgetStore: createWidgetStore() }),
26
- ],
27
- });
28
- const parent = document.createElement("div");
29
- document.body.appendChild(parent);
30
- const view = new EditorView({ state, parent });
31
- views.push(view);
32
- view.dispatch({ effects: setMarkdownMode.of("preview") });
33
- view.focus();
34
- view.dispatch({ effects: setMarkdownEditorFocused.of(true) });
35
- view.dispatch({ selection: EditorSelection.cursor(cursor) });
36
- return view;
37
- }
38
-
39
- function jsxBlockRanges(view: EditorView): Array<{ from: number; to: number }> {
40
- const out: Array<{ from: number; to: number }> = [];
41
- for (const set of (view as unknown as { docView: { decorations: DecorationSet[] } }).docView
42
- .decorations) {
43
- const cursor = set.iter();
44
- while (cursor.value) {
45
- const widget = (cursor.value.spec as { widget?: Decoration }).widget;
46
- if (widget instanceof JsxBlockWidget) {
47
- out.push({ from: cursor.from, to: cursor.to });
48
- }
49
- cursor.next();
50
- }
51
- }
52
- return out;
53
- }
54
-
55
- const TABS = `<Tabs syncKey="demo">
56
- <TabItem label="A">
57
- Alpha para one.
58
-
59
- Alpha para two.
60
- </TabItem>
61
- <TabItem label="B">
62
- Beta para one.
63
- </TabItem>
64
- </Tabs>`;
65
-
66
- // Some prose before the block so the caret can sit fully outside it.
67
- const TABS_WITH_PROSE = `Intro line.\n\n${TABS}`;
68
-
69
- describe("MarkdownEditor / JSX block widget", () => {
70
- it("renders a multi-paragraph Tabs block as a single widget when the caret is outside", () => {
71
- const view = preview(TABS_WITH_PROSE, 0); // caret in the intro line
72
- const ranges = jsxBlockRanges(view);
73
- expect(ranges).toHaveLength(1);
74
- // Covers the whole <Tabs>…</Tabs> span, not a fragment of it.
75
- const tabsStart = TABS_WITH_PROSE.indexOf("<Tabs");
76
- expect(ranges[0]?.from).toBe(tabsStart);
77
- expect(ranges[0]?.to).toBe(TABS_WITH_PROSE.length);
78
- });
79
-
80
- it("reveals the entire block as source (no partial widgets) when the caret is inside", () => {
81
- // Caret inside the first tab's content.
82
- const inside = TABS_WITH_PROSE.indexOf("Alpha para one.") + 3;
83
- const view = preview(TABS_WITH_PROSE, inside);
84
- expect(jsxBlockRanges(view)).toHaveLength(0);
85
- });
86
-
87
- it("keeps nested TabItem tags raw when the caret is on the closing tag line", () => {
88
- const onClose = TABS_WITH_PROSE.indexOf("</Tabs>") + 1;
89
- const view = preview(TABS_WITH_PROSE, onClose);
90
- expect(jsxBlockRanges(view)).toHaveLength(0);
91
- });
92
- });
@@ -1,88 +0,0 @@
1
- import {
2
- JSX_ATTR_NAME_NODE,
3
- JSX_ATTR_STRING_NODE,
4
- JSX_EXPRESSION_CONTENT_NODE,
5
- JSX_EXPRESSION_MARK_NODE,
6
- } from "@src/lib/markdown/grammar/jsx-tag";
7
- import { createMarkdownLanguage } from "@src/lib/markdown/grammar/language";
8
- import { describe, expect, it } from "vitest";
9
-
10
- function parse(text: string) {
11
- const support = createMarkdownLanguage({ addKeymap: false, pasteURLAsLink: false });
12
- return support.language.parser.parse(text);
13
- }
14
-
15
- function nodeNames(text: string): string[] {
16
- const tree = parse(text);
17
- const names: string[] = [];
18
- tree.iterate({
19
- enter: (n) => {
20
- names.push(n.name);
21
- },
22
- });
23
- return names;
24
- }
25
-
26
- function htmlTagSpan(text: string): { from: number; to: number } | null {
27
- const tree = parse(text);
28
- let span: { from: number; to: number } | null = null;
29
- tree.iterate({
30
- enter: (n) => {
31
- if (n.name === "HTMLTag") span = { from: n.from, to: n.to };
32
- },
33
- });
34
- return span;
35
- }
36
-
37
- describe("JsxTag grammar extension", () => {
38
- it("keeps plain quoted attributes on the built-in HTMLTag parser", () => {
39
- expect(nodeNames('<Since v="1" />')).toEqual(["Document", "Paragraph", "HTMLTag"]);
40
- expect(nodeNames('<Since v="1" />')).not.toContain(JSX_EXPRESSION_CONTENT_NODE);
41
- });
42
-
43
- it("parses a full HTMLTag when the attribute value is a JSX expression", () => {
44
- const text = '<Component prop={"a"} />';
45
- expect(htmlTagSpan(text)).toEqual({ from: 0, to: text.length });
46
- expect(nodeNames(text)).toContain(JSX_EXPRESSION_CONTENT_NODE);
47
- expect(nodeNames(text)).toContain(JSX_EXPRESSION_MARK_NODE);
48
- });
49
-
50
- it("does not truncate at > inside a JSX expression", () => {
51
- const text = "<Component prop={foo > bar} />";
52
- expect(htmlTagSpan(text)).toEqual({ from: 0, to: text.length });
53
- });
54
-
55
- it("parses multiple JSX tags on one line", () => {
56
- const text = '<Tabs defaultValue={"a"}><TabItem label={x} /></Tabs>';
57
- const tree = parse(text);
58
- const tags: string[] = [];
59
- tree.iterate({
60
- enter: (n) => {
61
- if (n.name === "HTMLTag") tags.push(text.slice(n.from, n.to));
62
- },
63
- });
64
- expect(tags).toEqual(['<Tabs defaultValue={"a"}>', "<TabItem label={x} />", "</Tabs>"]);
65
- });
66
-
67
- it("highlights quoted attributes that follow a JSX expression", () => {
68
- const text = '<Since v={"2.0.0"} t="true" b="false" />';
69
- expect(htmlTagSpan(text)).toEqual({ from: 0, to: text.length });
70
-
71
- const tree = parse(text);
72
- const names = collectNodes(tree, text, JSX_ATTR_NAME_NODE);
73
- const strings = collectNodes(tree, text, JSX_ATTR_STRING_NODE);
74
-
75
- expect(names).toEqual(["v", "t", "b"]);
76
- expect(strings).toEqual(['"true"', '"false"']);
77
- });
78
- });
79
-
80
- function collectNodes(tree: ReturnType<typeof parse>, text: string, name: string): string[] {
81
- const out: string[] = [];
82
- tree.iterate({
83
- enter: (n) => {
84
- if (n.name === name) out.push(text.slice(n.from, n.to));
85
- },
86
- });
87
- return out;
88
- }
@@ -1,95 +0,0 @@
1
- import { EditorSelection, EditorState } from "@codemirror/state";
2
- import { EditorView } from "@codemirror/view";
3
- import { indentMarkdownList } from "@src/lib/markdown/editor/list-indent";
4
- import { Callout } from "@src/lib/markdown/grammar/callout";
5
- import { createMarkdownLanguage } from "@src/lib/markdown/grammar/language";
6
- import { afterEach, describe, expect, it } from "vitest";
7
-
8
- let view: EditorView;
9
-
10
- afterEach(() => {
11
- view?.destroy();
12
- });
13
-
14
- function createView(doc: string, cursor: number): EditorView {
15
- const state = EditorState.create({
16
- doc,
17
- selection: EditorSelection.cursor(cursor),
18
- extensions: [createMarkdownLanguage({ extensions: [Callout] })],
19
- });
20
- const parent = document.createElement("div");
21
- document.body.appendChild(parent);
22
- view = new EditorView({ state, parent });
23
- return view;
24
- }
25
-
26
- /** Index just after the marker + space of the line containing `needle`. */
27
- function cursorInItem(doc: string, needle: string): number {
28
- return doc.indexOf(needle) + 1;
29
- }
30
-
31
- describe("indentMarkdownList", () => {
32
- it("indents an item under its preceding sibling", () => {
33
- const doc = "- one\n- two";
34
- const v = createView(doc, cursorInItem(doc, "two"));
35
- const handled = indentMarkdownList(v, false);
36
- expect(handled).toBe(true);
37
- expect(v.state.doc.toString()).toBe("- one\n - two");
38
- });
39
-
40
- it("consumes Tab but does not change the first item (no preceding sibling)", () => {
41
- const doc = "- one\n- two";
42
- const v = createView(doc, cursorInItem(doc, "one"));
43
- const handled = indentMarkdownList(v, false);
44
- expect(handled).toBe(true);
45
- expect(v.state.doc.toString()).toBe(doc);
46
- });
47
-
48
- it("outdents a nested item to its parent level", () => {
49
- const doc = "- one\n - two";
50
- const v = createView(doc, cursorInItem(doc, "two"));
51
- const handled = indentMarkdownList(v, true);
52
- expect(handled).toBe(true);
53
- expect(v.state.doc.toString()).toBe("- one\n- two");
54
- });
55
-
56
- it("consumes Shift-Tab but does not outdent a top-level item", () => {
57
- const doc = "- one\n- two";
58
- const v = createView(doc, cursorInItem(doc, "two"));
59
- const handled = indentMarkdownList(v, true);
60
- expect(handled).toBe(true);
61
- expect(v.state.doc.toString()).toBe(doc);
62
- });
63
-
64
- it("returns false outside a list", () => {
65
- const doc = "just a paragraph";
66
- const v = createView(doc, 4);
67
- expect(indentMarkdownList(v, false)).toBe(false);
68
- expect(indentMarkdownList(v, true)).toBe(false);
69
- });
70
-
71
- it("indents ordered list items by the marker width", () => {
72
- const doc = "1. one\n2. two";
73
- const v = createView(doc, cursorInItem(doc, "two"));
74
- const handled = indentMarkdownList(v, false);
75
- expect(handled).toBe(true);
76
- expect(v.state.doc.toString()).toBe("1. one\n 2. two");
77
- });
78
-
79
- it("indents an item together with its nested children", () => {
80
- const doc = "- one\n- two\n - child";
81
- const v = createView(doc, cursorInItem(doc, "two"));
82
- const handled = indentMarkdownList(v, false);
83
- expect(handled).toBe(true);
84
- expect(v.state.doc.toString()).toBe("- one\n - two\n - child");
85
- });
86
-
87
- it("keeps the cursor with the indented text", () => {
88
- const doc = "- one\n- two";
89
- const start = doc.indexOf("two");
90
- const v = createView(doc, start);
91
- indentMarkdownList(v, false);
92
- // Two spaces inserted before the line shift the cursor right by two.
93
- expect(v.state.selection.main.head).toBe(start + 2);
94
- });
95
- });