@surrealdb/ui 1.2.12 → 1.2.13
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.
- package/.claude/launch.json +17 -0
- package/.oxfmtrc.json +27 -0
- package/.oxlintrc.json +23 -0
- package/.zed/settings.json +34 -34
- package/AGENTS.md +21 -19
- package/README.md +15 -10
- package/dist/ui.css +1 -1
- package/dist/ui.d.ts +234 -1
- package/dist/ui.js +8217 -7329
- package/dist/ui.js.map +1 -1
- package/doc/code-style.md +264 -0
- package/doc/stacker-plan.md +219 -0
- package/package.json +119 -116
- package/res/_mixins.scss +15 -11
- package/tests/_setup/e2e-helpers.tsx +85 -85
- package/tests/_setup/portable-stories.ts +11 -10
- package/tests/e2e/MarkdownEditor/content-blocks.test.tsx +140 -139
- package/tests/e2e/MarkdownEditor/edits.test.tsx +101 -100
- package/tests/e2e/MarkdownEditor/heading-fold.test.tsx +35 -34
- package/tests/e2e/MarkdownEditor/hybrid-widgets.test.tsx +178 -177
- package/tests/e2e/MarkdownEditor/image-on-image.test.tsx +50 -49
- package/tests/e2e/MarkdownEditor/jsx-block-content.test.tsx +195 -194
- package/tests/e2e/MarkdownEditor/jsx-highlight.test.tsx +47 -46
- package/tests/e2e/MarkdownEditor/jsx-inline-badges.test.tsx +35 -34
- package/tests/e2e/MarkdownEditor/jsx-inline-click.test.tsx +39 -38
- package/tests/e2e/MarkdownEditor/jsx-selection.test.tsx +28 -27
- package/tests/e2e/MarkdownEditor/link-placeholder.test.tsx +49 -48
- package/tests/e2e/MarkdownEditor/media-align.test.tsx +40 -39
- package/tests/e2e/MarkdownEditor/media-edit.test.tsx +49 -48
- package/tests/e2e/MarkdownEditor/media-sizing.test.tsx +108 -107
- package/tests/e2e/MarkdownEditor/modes.test.tsx +79 -78
- package/tests/e2e/MarkdownEditor/onimage-parity.test.tsx +29 -28
- package/tests/e2e/MarkdownEditor/regressions.test.tsx +194 -193
- package/tests/e2e/MarkdownEditor/slash-commands.test.tsx +81 -80
- package/tests/e2e/MarkdownEditor/table-click.test.tsx +36 -35
- package/tests/e2e/MarkdownEditor/table-controls.test.tsx +44 -43
- package/tests/e2e/MarkdownEditor/table-format.test.tsx +28 -27
- package/tests/e2e/MarkdownEditor/undo-redo.test.tsx +22 -21
- package/tests/e2e/MarkdownViewer/parity.test.tsx +144 -143
- package/tests/e2e/MarkdownViewer/render.test.tsx +20 -19
- package/tests/e2e/Stacker/stacker.test.tsx +304 -0
- package/tests/unit/Editor/helpers.test.ts +26 -26
- package/tests/unit/MarkdownConformance/error-isolation.test.tsx +26 -25
- package/tests/unit/MarkdownConformance/indentation.test.tsx +20 -19
- package/tests/unit/MarkdownConformance/inline-code-fuzz.test.tsx +255 -0
- package/tests/unit/MarkdownConformance/jsx-attributes.test.tsx +39 -38
- package/tests/unit/MarkdownConformance/jsx-fuzz.test.tsx +122 -122
- package/tests/unit/MarkdownConformance/links.test.tsx +78 -54
- package/tests/unit/MarkdownConformance/render-helpers.tsx +60 -60
- package/tests/unit/MarkdownEditor/code-info.test.ts +58 -58
- package/tests/unit/MarkdownEditor/decorations.test.ts +423 -421
- package/tests/unit/MarkdownEditor/editor-ready.test.ts +22 -22
- package/tests/unit/MarkdownEditor/html-descriptors.test.ts +70 -70
- package/tests/unit/MarkdownEditor/indented-fence.test.ts +80 -80
- package/tests/unit/MarkdownEditor/jsx-attr-scan.test.ts +87 -87
- package/tests/unit/MarkdownEditor/jsx-block-widget.test.ts +51 -51
- package/tests/unit/MarkdownEditor/jsx-tag-grammar.test.ts +63 -63
- package/tests/unit/MarkdownEditor/list-indent.test.ts +67 -67
- package/tests/unit/MarkdownEditor/slash-commands.test.ts +150 -150
- package/tests/unit/MarkdownEditor/table-format.test.ts +67 -67
- package/tests/unit/MarkdownEditor/table.test.ts +96 -96
- package/tests/unit/MarkdownEditor/triggers.test.ts +167 -167
- package/tests/unit/MarkdownEditor/widget-store.test.ts +90 -90
- package/tests/unit/MarkdownViewer/callout.test.tsx +42 -42
- package/tests/unit/MarkdownViewer/code-highlight.test.tsx +41 -41
- package/tests/unit/MarkdownViewer/code-title.test.tsx +45 -45
- package/tests/unit/MarkdownViewer/features.test.tsx +129 -129
- package/tests/unit/MarkdownViewer/headings.test.tsx +28 -28
- package/tests/unit/MarkdownViewer/indented-jsx-children.test.tsx +53 -53
- package/tests/unit/MarkdownViewer/jsx-block-nesting.test.tsx +137 -137
- package/tests/unit/MarkdownViewer/jsx.test.tsx +188 -188
- package/tests/unit/MarkdownViewer/list-bullets.test.tsx +31 -31
- package/tests/unit/MarkdownViewer/list-code.test.tsx +45 -45
- package/tests/unit/MarkdownViewer/preserve-newlines.test.tsx +109 -109
- package/tests/unit/MarkdownViewer/read-time.test.ts +13 -13
- package/tests/unit/MarkdownViewer/renderers.test.tsx +75 -75
- package/tests/unit/MarkdownViewer/runnable.test.tsx +51 -51
- package/tests/unit/MarkdownViewer/ssr.test.tsx +56 -55
- package/tests/unit/MarkdownViewer/syntax-highlighting.test.tsx +43 -42
- package/tests/unit/MarkdownViewer/tabs-fenced-code.test.tsx +120 -120
- package/tests/unit/MarkdownViewer/tabs.test.tsx +83 -82
- package/tests/unit/Stacker/drag.test.ts +21 -0
- package/tests/unit/Stacker/helpers.test.ts +105 -0
- package/tests/unit/Stacker/reducer.test.ts +107 -0
- package/tools/duplicate-vector-mappings.json +141 -141
- package/tools/glass-export-overrides.json +6 -6
- package/tools/icon-parser.ts +41 -40
- package/tools/integration-gradient-exports.json +55 -55
- package/tools/picto-compare.ts +32 -32
- package/tools/picto-merge-urls.ts +19 -19
- package/tools/picto-sync.ts +543 -543
- package/tools/picto-verify-gradients.ts +50 -50
- package/tools/vector-export-nodes.json +975 -975
|
@@ -1,71 +1,95 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
2
3
|
import { renderMarkdown, visibleText } from "./render-helpers";
|
|
3
4
|
|
|
4
5
|
describe("Conformance / autolinks", () => {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
it("keeps an absolute URL href unchanged", () => {
|
|
7
|
+
const html = renderMarkdown("Go https://example.com now");
|
|
8
|
+
expect(html).toContain('href="https://example.com"');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("adds https:// to a www. autolink", () => {
|
|
12
|
+
const html = renderMarkdown("Visit www.example.com today");
|
|
13
|
+
expect(html).toContain('href="https://www.example.com"');
|
|
14
|
+
});
|
|
9
15
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
it("adds mailto: to a bare email autolink", () => {
|
|
17
|
+
const html = renderMarkdown("Mail me@example.com please");
|
|
18
|
+
expect(html).toContain('href="mailto:me@example.com"');
|
|
19
|
+
});
|
|
14
20
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
it("renders an angle-bracket URL autolink as an anchor", () => {
|
|
22
|
+
const html = renderMarkdown("See <https://example.com> here");
|
|
23
|
+
expect(html).toContain('href="https://example.com"');
|
|
24
|
+
expect(visibleText(html)).toContain("https://example.com");
|
|
25
|
+
// The angle brackets are delimiters, not visible content.
|
|
26
|
+
expect(visibleText(html)).not.toContain("<https://example.com>");
|
|
27
|
+
});
|
|
19
28
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
it("renders an angle-bracket email autolink with mailto:", () => {
|
|
30
|
+
const html = renderMarkdown("Mail <me@example.com> ok");
|
|
31
|
+
expect(html).toContain('href="mailto:me@example.com"');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("does not linkify a URL inside inline code", () => {
|
|
35
|
+
const html = renderMarkdown("Use `https://example.com` verbatim");
|
|
36
|
+
expect(html).not.toContain('href="https://example.com"');
|
|
37
|
+
expect(html).toContain("https://example.com");
|
|
38
|
+
});
|
|
39
|
+
});
|
|
27
40
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
41
|
+
describe("Conformance / angle brackets in inline code", () => {
|
|
42
|
+
// Regression: `<…>` inside inline code was scanned as an HTML/JSX tag,
|
|
43
|
+
// which pushed the whole paragraph down the descriptor path and rendered
|
|
44
|
+
// every backtick literally while dropping the bracketed text.
|
|
45
|
+
it("keeps inline code intact when it contains an angle-bracket placeholder", () => {
|
|
46
|
+
const html = renderMarkdown(
|
|
47
|
+
"`.{..+collect}` returns the set; and `.{..+shortest=<target>}` returns the shortest path.",
|
|
48
|
+
);
|
|
49
|
+
expect(html).toContain("<code");
|
|
50
|
+
expect(html).toContain(".{..+collect}");
|
|
51
|
+
expect(html).toContain(".{..+shortest=<target>}");
|
|
52
|
+
// Backticks must not leak into the visible output.
|
|
53
|
+
expect(visibleText(html)).not.toContain("`");
|
|
54
|
+
expect(visibleText(html)).toContain(".{..+shortest=<target>}");
|
|
55
|
+
});
|
|
32
56
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
57
|
+
it("still pairs a real inline tag that sits beside inline code", () => {
|
|
58
|
+
const html = renderMarkdown("Press `Enter` then <strong>go</strong> now");
|
|
59
|
+
expect(html).toContain("<code");
|
|
60
|
+
expect(html).toContain("<strong>go</strong>");
|
|
61
|
+
});
|
|
38
62
|
});
|
|
39
63
|
|
|
40
64
|
describe("Conformance / reference-style links", () => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
65
|
+
it("resolves a full reference link [text][id]", () => {
|
|
66
|
+
const html = renderMarkdown("[click here][site] now\n\n[site]: https://example.com");
|
|
67
|
+
expect(html).toContain('href="https://example.com"');
|
|
68
|
+
expect(visibleText(html)).toContain("click here");
|
|
69
|
+
});
|
|
46
70
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
71
|
+
it("resolves a collapsed reference link [id][]", () => {
|
|
72
|
+
const html = renderMarkdown("[site][] now\n\n[site]: https://example.com");
|
|
73
|
+
expect(html).toContain('href="https://example.com"');
|
|
74
|
+
expect(visibleText(html)).toContain("site");
|
|
75
|
+
});
|
|
52
76
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
77
|
+
it("resolves a shortcut reference link [id]", () => {
|
|
78
|
+
const html = renderMarkdown("[site] now\n\n[site]: https://example.com");
|
|
79
|
+
expect(html).toContain('href="https://example.com"');
|
|
80
|
+
});
|
|
57
81
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
82
|
+
it("matches reference labels case-insensitively", () => {
|
|
83
|
+
const html = renderMarkdown("[Click][SITE] now\n\n[site]: https://example.com");
|
|
84
|
+
expect(html).toContain('href="https://example.com"');
|
|
85
|
+
});
|
|
62
86
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
87
|
+
it("emits the visible text (no anchor) when the reference is undefined", () => {
|
|
88
|
+
const html = renderMarkdown("see [missing][nope] end");
|
|
89
|
+
expect(html).not.toContain("href=");
|
|
90
|
+
const text = visibleText(html);
|
|
91
|
+
expect(text).toContain("missing");
|
|
92
|
+
// Reference brackets/label are not shown as literal text.
|
|
93
|
+
expect(text).not.toContain("[missing]");
|
|
94
|
+
});
|
|
71
95
|
});
|
|
@@ -8,35 +8,35 @@ import { renderToString } from "react-dom/server";
|
|
|
8
8
|
|
|
9
9
|
// jsdom lacks matchMedia, which Mantine touches during render.
|
|
10
10
|
if (typeof window !== "undefined" && typeof window.matchMedia !== "function") {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
11
|
+
Object.defineProperty(window, "matchMedia", {
|
|
12
|
+
writable: true,
|
|
13
|
+
value: (query: string) => ({
|
|
14
|
+
matches: false,
|
|
15
|
+
media: query,
|
|
16
|
+
onchange: null,
|
|
17
|
+
addListener: () => {},
|
|
18
|
+
removeListener: () => {},
|
|
19
|
+
addEventListener: () => {},
|
|
20
|
+
removeEventListener: () => {},
|
|
21
|
+
dispatchEvent: () => false,
|
|
22
|
+
}),
|
|
23
|
+
});
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
/** Render a `MarkdownViewer` to an HTML string inside a Mantine provider. */
|
|
27
27
|
export function renderMarkdown(content: string, props?: Partial<MarkdownViewerProps>): string {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
28
|
+
return renderToString(
|
|
29
|
+
<MantineProvider
|
|
30
|
+
theme={MANTINE_THEME}
|
|
31
|
+
cssVariablesResolver={v8CssVariablesResolver}
|
|
32
|
+
forceColorScheme="dark"
|
|
33
|
+
>
|
|
34
|
+
<MarkdownViewer
|
|
35
|
+
content={content}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
</MantineProvider>,
|
|
39
|
+
);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/**
|
|
@@ -45,44 +45,44 @@ export function renderMarkdown(content: string, props?: Partial<MarkdownViewerPr
|
|
|
45
45
|
* client (the environment where a throwing embedded component would blank the UI).
|
|
46
46
|
*/
|
|
47
47
|
export function renderMarkdownClient(
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
content: string,
|
|
49
|
+
props?: Partial<MarkdownViewerProps>,
|
|
50
50
|
): string {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
51
|
+
const container = document.createElement("div");
|
|
52
|
+
document.body.appendChild(container);
|
|
53
|
+
const root = createRoot(container);
|
|
54
|
+
act(() => {
|
|
55
|
+
root.render(
|
|
56
|
+
<MantineProvider
|
|
57
|
+
theme={MANTINE_THEME}
|
|
58
|
+
cssVariablesResolver={v8CssVariablesResolver}
|
|
59
|
+
forceColorScheme="dark"
|
|
60
|
+
>
|
|
61
|
+
<MarkdownViewer
|
|
62
|
+
content={content}
|
|
63
|
+
{...props}
|
|
64
|
+
/>
|
|
65
|
+
</MantineProvider>,
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
const html = container.innerHTML;
|
|
69
|
+
act(() => {
|
|
70
|
+
root.unmount();
|
|
71
|
+
});
|
|
72
|
+
container.remove();
|
|
73
|
+
return html;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
/** Strip all HTML tags and decode common entities, returning the visible text. */
|
|
77
77
|
export function visibleText(html: string): string {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
78
|
+
return html
|
|
79
|
+
.replace(/<style[^>]*>[\s\S]*?<\/style>/g, "")
|
|
80
|
+
.replace(/<[^>]+>/g, "")
|
|
81
|
+
.replace(/"/g, '"')
|
|
82
|
+
.replace(/'/g, "'")
|
|
83
|
+
.replace(/</g, "<")
|
|
84
|
+
.replace(/>/g, ">")
|
|
85
|
+
.replace(/&/g, "&")
|
|
86
|
+
.replace(/\s+/g, " ")
|
|
87
|
+
.trim();
|
|
88
88
|
}
|
|
@@ -2,62 +2,62 @@ import { codeInfoString, parseCodeInfo } from "@src/lib/markdown/tree/code-info"
|
|
|
2
2
|
import { describe, expect, it } from "vitest";
|
|
3
3
|
|
|
4
4
|
describe("parseCodeInfo", () => {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
5
|
+
it("returns an empty language and attributes for a blank info string", () => {
|
|
6
|
+
expect(parseCodeInfo("")).toEqual({ language: "", attributes: {} });
|
|
7
|
+
expect(parseCodeInfo(" ")).toEqual({ language: "", attributes: {} });
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("reads the language as the first token, lower-cased", () => {
|
|
11
|
+
expect(parseCodeInfo("TypeScript")).toEqual({
|
|
12
|
+
language: "typescript",
|
|
13
|
+
attributes: {},
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("resolves loose flags to true", () => {
|
|
18
|
+
const parsed = parseCodeInfo("surql runnable");
|
|
19
|
+
expect(parsed.language).toBe("surql");
|
|
20
|
+
expect(parsed.attributes.runnable).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("parses double- and single-quoted values", () => {
|
|
24
|
+
const parsed = parseCodeInfo(`js title="My Example" label='other'`);
|
|
25
|
+
expect(parsed.attributes.title).toBe("My Example");
|
|
26
|
+
expect(parsed.attributes.label).toBe("other");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("keeps an empty string value for empty quotes", () => {
|
|
30
|
+
const parsed = parseCodeInfo(`js title=""`);
|
|
31
|
+
expect(parsed.attributes.title).toBe("");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("parses bare unquoted values", () => {
|
|
35
|
+
const parsed = parseCodeInfo("ts height=200");
|
|
36
|
+
expect(parsed.attributes.height).toBe("200");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("parses braced expression values", () => {
|
|
40
|
+
const parsed = parseCodeInfo("surql runnable={SELECT 1}");
|
|
41
|
+
expect(parsed.attributes.runnable).toBe("{SELECT 1}");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("mixes loose flags and key/value pairs", () => {
|
|
45
|
+
const parsed = parseCodeInfo(`surql runnable title="Demo"`);
|
|
46
|
+
expect(parsed).toEqual({
|
|
47
|
+
language: "surql",
|
|
48
|
+
attributes: { runnable: true, title: "Demo" },
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("preserves attribute value casing", () => {
|
|
53
|
+
const parsed = parseCodeInfo(`surql runnable="SELECT 1"`);
|
|
54
|
+
expect(parsed.attributes.runnable).toBe("SELECT 1");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("codeInfoString returns string values and undefined for flags", () => {
|
|
58
|
+
const parsed = parseCodeInfo(`surql runnable title="Demo"`);
|
|
59
|
+
expect(codeInfoString(parsed, "title")).toBe("Demo");
|
|
60
|
+
expect(codeInfoString(parsed, "runnable")).toBeUndefined();
|
|
61
|
+
expect(codeInfoString(parsed, "missing")).toBeUndefined();
|
|
62
|
+
});
|
|
63
63
|
});
|