@surrealdb/ui 1.2.11 → 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/assets/245b1ccaa4927818.svg +3 -0
- package/dist/assets/333e1fe6e67ca4a2.svg +10 -0
- package/dist/assets/4b0e10c655814886.svg +21 -0
- package/dist/assets/9932da008ea5dd32.svg +3 -0
- package/dist/assets/a372168d5789daa2.svg +4 -0
- package/dist/assets/b140e44ee4ae7e35.svg +5 -0
- package/dist/assets/e028075817d928e4.svg +3 -0
- package/dist/assets/ee1065ad5a8f7f14.svg +6 -0
- package/dist/ui.css +1 -1
- package/dist/ui.d.ts +579 -1
- package/dist/ui.js +11553 -8988
- 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 -115
- 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,119 +1,119 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
gfmTableAddColumn,
|
|
3
|
+
gfmTableAddRow,
|
|
4
|
+
gfmTableRemoveColumn,
|
|
5
|
+
gfmTableRemoveRow,
|
|
6
|
+
locateGfmTableCells,
|
|
7
|
+
parseGfmTable,
|
|
8
|
+
serialiseGfmTable,
|
|
9
|
+
type TableModel,
|
|
10
10
|
} from "@src/lib/markdown/tree/gfm-table";
|
|
11
11
|
import { describe, expect, it } from "vitest";
|
|
12
12
|
|
|
13
13
|
describe("GFM table", () => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
it("parses simple GFM tables", () => {
|
|
15
|
+
const source = "| a | b |\n| - | - |\n| 1 | 2 |";
|
|
16
|
+
const model = parseGfmTable(source);
|
|
17
|
+
expect(model).not.toBeNull();
|
|
18
|
+
expect(model?.headers).toEqual(["a", "b"]);
|
|
19
|
+
expect(model?.rows).toEqual([["1", "2"]]);
|
|
20
|
+
});
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
it("respects alignment markers", () => {
|
|
23
|
+
const source = "| a | b | c |\n| :- | :-: | -: |\n| 1 | 2 | 3 |";
|
|
24
|
+
const model = parseGfmTable(source);
|
|
25
|
+
expect(model?.alignments).toEqual(["left", "center", "right"]);
|
|
26
|
+
});
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
it("round trips a table preserving content", () => {
|
|
29
|
+
const source = "| Name | Score |\n| :--- | ----: |\n| Ari | 12 |\n| Nova | 8 |";
|
|
30
|
+
const model = parseGfmTable(source);
|
|
31
|
+
if (!model) throw new Error("expected parse");
|
|
32
|
+
const serialised = serialiseGfmTable(model);
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
const reparsed = parseGfmTable(serialised);
|
|
35
|
+
expect(reparsed).not.toBeNull();
|
|
36
|
+
expect(reparsed?.headers).toEqual(model.headers);
|
|
37
|
+
expect(reparsed?.alignments).toEqual(model.alignments);
|
|
38
|
+
expect(reparsed?.rows).toEqual(model.rows);
|
|
39
|
+
});
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
it("returns null for non-table input", () => {
|
|
42
|
+
expect(parseGfmTable("just a paragraph")).toBeNull();
|
|
43
|
+
});
|
|
44
44
|
});
|
|
45
45
|
|
|
46
46
|
describe("locateGfmTableCells", () => {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
47
|
+
it("locates the content start offset of each cell", () => {
|
|
48
|
+
const source = "| a | b |\n| - | - |\n| 1 | 2 |";
|
|
49
|
+
const cells = locateGfmTableCells(source);
|
|
50
|
+
expect(cells).not.toBeNull();
|
|
51
|
+
// Header content starts: 'a' at 2, 'b' at 6.
|
|
52
|
+
expect(cells?.headerOffsets).toEqual([2, 6]);
|
|
53
|
+
expect(source[cells?.headerOffsets[0] ?? -1]).toBe("a");
|
|
54
|
+
expect(source[cells?.headerOffsets[1] ?? -1]).toBe("b");
|
|
55
|
+
// Body row content starts: '1' and '2'.
|
|
56
|
+
expect(source[cells?.rowOffsets[0]?.[0] ?? -1]).toBe("1");
|
|
57
|
+
expect(source[cells?.rowOffsets[0]?.[1] ?? -1]).toBe("2");
|
|
58
|
+
});
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
60
|
+
it("handles ragged padding and missing outer pipes", () => {
|
|
61
|
+
const source = "Name | Score\n--- | ---\nAri | 12";
|
|
62
|
+
const cells = locateGfmTableCells(source);
|
|
63
|
+
expect(cells).not.toBeNull();
|
|
64
|
+
expect(source.slice(cells?.headerOffsets[0] ?? 0, (cells?.headerOffsets[0] ?? 0) + 4)).toBe(
|
|
65
|
+
"Name",
|
|
66
|
+
);
|
|
67
|
+
expect(source.slice(cells?.headerOffsets[1] ?? 0, (cells?.headerOffsets[1] ?? 0) + 5)).toBe(
|
|
68
|
+
"Score",
|
|
69
|
+
);
|
|
70
|
+
expect(source[cells?.rowOffsets[0]?.[1] ?? -1]).toBe("1");
|
|
71
|
+
});
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
it("returns null for non-table input", () => {
|
|
74
|
+
expect(locateGfmTableCells("just a paragraph")).toBeNull();
|
|
75
|
+
});
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
describe("GFM table edits", () => {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
const base: TableModel = {
|
|
80
|
+
headers: ["a", "b"],
|
|
81
|
+
alignments: ["default", "default"],
|
|
82
|
+
rows: [["1", "2"]],
|
|
83
|
+
};
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
it("adds an empty trailing column", () => {
|
|
86
|
+
const next = gfmTableAddColumn(base);
|
|
87
|
+
expect(next.headers).toEqual(["a", "b", ""]);
|
|
88
|
+
expect(next.alignments).toHaveLength(3);
|
|
89
|
+
expect(next.rows).toEqual([["1", "2", ""]]);
|
|
90
|
+
});
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
92
|
+
it("removes the last column but keeps at least one", () => {
|
|
93
|
+
const next = gfmTableRemoveColumn(base);
|
|
94
|
+
expect(next.headers).toEqual(["a"]);
|
|
95
|
+
expect(next.rows).toEqual([["1"]]);
|
|
96
|
+
const single: TableModel = { headers: ["only"], alignments: ["default"], rows: [["x"]] };
|
|
97
|
+
expect(gfmTableRemoveColumn(single)).toEqual(single);
|
|
98
|
+
});
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
100
|
+
it("adds an empty trailing row sized to the columns", () => {
|
|
101
|
+
const next = gfmTableAddRow(base);
|
|
102
|
+
expect(next.rows).toEqual([
|
|
103
|
+
["1", "2"],
|
|
104
|
+
["", ""],
|
|
105
|
+
]);
|
|
106
|
+
});
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
108
|
+
it("removes the last row and is a no-op with no rows", () => {
|
|
109
|
+
expect(gfmTableRemoveRow(base).rows).toEqual([]);
|
|
110
|
+
const empty: TableModel = { headers: ["a"], alignments: ["default"], rows: [] };
|
|
111
|
+
expect(gfmTableRemoveRow(empty)).toEqual(empty);
|
|
112
|
+
});
|
|
113
113
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
114
|
+
it("round-trips edits through serialise/parse", () => {
|
|
115
|
+
const widened = serialiseGfmTable(gfmTableAddColumn(base));
|
|
116
|
+
const reparsed = parseGfmTable(widened);
|
|
117
|
+
expect(reparsed?.headers).toEqual(["a", "b", ""]);
|
|
118
|
+
});
|
|
119
119
|
});
|
|
@@ -6,30 +6,30 @@ import { createMarkdownLanguage } from "@src/lib/markdown/grammar/language";
|
|
|
6
6
|
import { describe, expect, it } from "vitest";
|
|
7
7
|
|
|
8
8
|
function createView(doc: string, cursor?: number | { anchor: number; head: number }): EditorView {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
9
|
+
const state = EditorState.create({
|
|
10
|
+
doc,
|
|
11
|
+
extensions: [
|
|
12
|
+
createMarkdownLanguage({ extensions: [Callout] }),
|
|
13
|
+
markdownSnippetHandler,
|
|
14
|
+
markdownPairKeymap,
|
|
15
|
+
],
|
|
16
|
+
selection:
|
|
17
|
+
typeof cursor === "number"
|
|
18
|
+
? EditorSelection.cursor(cursor)
|
|
19
|
+
: cursor
|
|
20
|
+
? EditorSelection.range(cursor.anchor, cursor.head)
|
|
21
|
+
: undefined,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const parent = document.createElement("div");
|
|
25
|
+
document.body.appendChild(parent);
|
|
26
|
+
|
|
27
|
+
return new EditorView({ state, parent });
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
/** Dispatch a Backspace through the registered key handlers. */
|
|
31
31
|
function backspace(view: EditorView): boolean {
|
|
32
|
-
|
|
32
|
+
return runScopeHandlers(view, new KeyboardEvent("keydown", { key: "Backspace" }), "editor");
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
/**
|
|
@@ -39,206 +39,206 @@ function backspace(view: EditorView): boolean {
|
|
|
39
39
|
* normal keystroke.
|
|
40
40
|
*/
|
|
41
41
|
function type(view: EditorView, text: string): void {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
42
|
+
const main = view.state.selection.main;
|
|
43
|
+
const handlers = view.state.facet(EditorView.inputHandler);
|
|
44
|
+
const buildTransaction = () => view.state.update(view.state.replaceSelection(text));
|
|
45
|
+
for (const handler of handlers) {
|
|
46
|
+
if (handler(view, main.from, main.to, text, buildTransaction)) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
view.dispatch(buildTransaction());
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
describe("markdownSnippetHandler", () => {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
it("expands triple backtick into a fenced code block with caret on the empty content line", () => {
|
|
55
|
+
const view = createView("``", 2);
|
|
56
|
+
type(view, "`");
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
expect(view.state.doc.toString()).toBe("```\n\n```");
|
|
59
|
+
expect(view.state.selection.main.empty).toBe(true);
|
|
60
|
+
// Caret on the empty middle line, after "```\n".
|
|
61
|
+
expect(view.state.selection.main.from).toBe(4);
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
view.destroy();
|
|
64
|
+
});
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
it("preserves leading indent when expanding a fence inside a nested context", () => {
|
|
67
|
+
const view = createView(" ``", 4);
|
|
68
|
+
type(view, "`");
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
expect(view.state.doc.toString()).toBe(" ```\n \n ```");
|
|
71
|
+
// Caret on the indented content line.
|
|
72
|
+
expect(view.state.selection.main.from).toBe(8);
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
view.destroy();
|
|
75
|
+
});
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
77
|
+
it("expands a triple backtick typed one keystroke at a time", () => {
|
|
78
|
+
const view = createView("", 0);
|
|
79
|
+
type(view, "`");
|
|
80
|
+
type(view, "`");
|
|
81
|
+
type(view, "`");
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
expect(view.state.doc.toString()).toBe("```\n\n```");
|
|
84
|
+
expect(view.state.selection.main.from).toBe(4);
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
view.destroy();
|
|
87
|
+
});
|
|
88
88
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
it("does not expand a fence from a single backtick after text", () => {
|
|
90
|
+
const view = createView("abc", 3);
|
|
91
|
+
type(view, "`");
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
// A lone backtick must auto-pair, never clobber the preceding text.
|
|
94
|
+
expect(view.state.doc.toString()).toBe("abc``");
|
|
95
|
+
expect(view.state.selection.main.from).toBe(4);
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
view.destroy();
|
|
98
|
+
});
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
it("does not expand a fence when the backticks are not at the line start", () => {
|
|
101
|
+
const view = createView("x ``", 4);
|
|
102
|
+
type(view, "`");
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
expect(view.state.doc.toString()).toBe("x ```");
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
view.destroy();
|
|
107
|
+
});
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
it("does not expand a fence when there is content after the cursor on the same line", () => {
|
|
110
|
+
const view = createView("``rest", 2);
|
|
111
|
+
type(view, "`");
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
expect(view.state.doc.toString()).toBe("```rest");
|
|
114
114
|
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
view.destroy();
|
|
116
|
+
});
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
it("does not expand a fence when typing inside an existing fenced code block", () => {
|
|
119
|
+
const view = createView("```js\nlet x``\n```", 13);
|
|
120
|
+
type(view, "`");
|
|
121
121
|
|
|
122
|
-
|
|
122
|
+
expect(view.state.doc.toString()).toBe("```js\nlet x```\n```");
|
|
123
123
|
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
view.destroy();
|
|
125
|
+
});
|
|
126
126
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
127
|
+
it("wraps a non-empty selection with backticks", () => {
|
|
128
|
+
const view = createView("hello world", { anchor: 0, head: 5 });
|
|
129
|
+
type(view, "`");
|
|
130
130
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
expect(view.state.doc.toString()).toBe("`hello` world");
|
|
132
|
+
expect(view.state.selection.main.from).toBe(1);
|
|
133
|
+
expect(view.state.selection.main.to).toBe(6);
|
|
134
134
|
|
|
135
|
-
|
|
136
|
-
|
|
135
|
+
view.destroy();
|
|
136
|
+
});
|
|
137
137
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
138
|
+
it("wraps a selection with asterisks for italic and again for bold", () => {
|
|
139
|
+
const view = createView("hello world", { anchor: 0, head: 5 });
|
|
140
|
+
type(view, "*");
|
|
141
|
+
expect(view.state.doc.toString()).toBe("*hello* world");
|
|
142
142
|
|
|
143
|
-
|
|
144
|
-
|
|
143
|
+
type(view, "*");
|
|
144
|
+
expect(view.state.doc.toString()).toBe("**hello** world");
|
|
145
145
|
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
view.destroy();
|
|
147
|
+
});
|
|
148
148
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
149
|
+
it("wraps a selection with tildes for strikethrough", () => {
|
|
150
|
+
const view = createView("hello world", { anchor: 0, head: 5 });
|
|
151
|
+
type(view, "~");
|
|
152
|
+
type(view, "~");
|
|
153
153
|
|
|
154
|
-
|
|
154
|
+
expect(view.state.doc.toString()).toBe("~~hello~~ world");
|
|
155
155
|
|
|
156
|
-
|
|
157
|
-
|
|
156
|
+
view.destroy();
|
|
157
|
+
});
|
|
158
158
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
159
|
+
it("wraps a selection as a markdown link with caret between the parens", () => {
|
|
160
|
+
const view = createView("hello world", { anchor: 0, head: 5 });
|
|
161
|
+
type(view, "[");
|
|
162
162
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
163
|
+
expect(view.state.doc.toString()).toBe("[hello]() world");
|
|
164
|
+
expect(view.state.selection.main.empty).toBe(true);
|
|
165
|
+
expect(view.state.selection.main.from).toBe(8);
|
|
166
166
|
|
|
167
|
-
|
|
168
|
-
|
|
167
|
+
view.destroy();
|
|
168
|
+
});
|
|
169
169
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
170
|
+
it("does not wrap when the selection is inside a fenced code block", () => {
|
|
171
|
+
const view = createView("```js\nlet x = 1\n```", { anchor: 6, head: 11 });
|
|
172
|
+
type(view, "*");
|
|
173
173
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
174
|
+
const doc = view.state.doc.toString();
|
|
175
|
+
expect(doc).not.toContain("*let x *");
|
|
176
|
+
expect(doc).not.toContain("*let x ");
|
|
177
177
|
|
|
178
|
-
|
|
179
|
-
|
|
178
|
+
view.destroy();
|
|
179
|
+
});
|
|
180
180
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
181
|
+
it("auto-pairs a lone backtick for inline code", () => {
|
|
182
|
+
const view = createView("run ", 4);
|
|
183
|
+
type(view, "`");
|
|
184
184
|
|
|
185
|
-
|
|
186
|
-
|
|
185
|
+
expect(view.state.doc.toString()).toBe("run ``");
|
|
186
|
+
expect(view.state.selection.main.from).toBe(5);
|
|
187
187
|
|
|
188
|
-
|
|
189
|
-
|
|
188
|
+
view.destroy();
|
|
189
|
+
});
|
|
190
190
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
191
|
+
it("auto-pairs and steps over brackets", () => {
|
|
192
|
+
const view = createView("", 0);
|
|
193
|
+
type(view, "(");
|
|
194
|
+
expect(view.state.doc.toString()).toBe("()");
|
|
195
|
+
expect(view.state.selection.main.from).toBe(1);
|
|
196
196
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
197
|
+
type(view, ")");
|
|
198
|
+
expect(view.state.doc.toString()).toBe("()");
|
|
199
|
+
expect(view.state.selection.main.from).toBe(2);
|
|
200
200
|
|
|
201
|
-
|
|
202
|
-
|
|
201
|
+
view.destroy();
|
|
202
|
+
});
|
|
203
203
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
204
|
+
it("does not double a closing bracket that was not auto-paired", () => {
|
|
205
|
+
const view = createView("foo", 3);
|
|
206
|
+
type(view, ")");
|
|
207
207
|
|
|
208
|
-
|
|
208
|
+
expect(view.state.doc.toString()).toBe("foo)");
|
|
209
209
|
|
|
210
|
-
|
|
211
|
-
|
|
210
|
+
view.destroy();
|
|
211
|
+
});
|
|
212
212
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
213
|
+
it("suppresses emphasis auto-pairing directly in front of a word", () => {
|
|
214
|
+
const view = createView("word", 0);
|
|
215
|
+
type(view, "*");
|
|
216
216
|
|
|
217
|
-
|
|
218
|
-
|
|
217
|
+
expect(view.state.doc.toString()).toBe("*word");
|
|
218
|
+
expect(view.state.selection.main.from).toBe(1);
|
|
219
219
|
|
|
220
|
-
|
|
221
|
-
|
|
220
|
+
view.destroy();
|
|
221
|
+
});
|
|
222
222
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
223
|
+
it("deletes an empty auto-inserted pair with a single backspace", () => {
|
|
224
|
+
const view = createView("", 0);
|
|
225
|
+
type(view, "(");
|
|
226
|
+
expect(view.state.doc.toString()).toBe("()");
|
|
227
227
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
228
|
+
backspace(view);
|
|
229
|
+
expect(view.state.doc.toString()).toBe("");
|
|
230
|
+
expect(view.state.selection.main.from).toBe(0);
|
|
231
231
|
|
|
232
|
-
|
|
233
|
-
|
|
232
|
+
view.destroy();
|
|
233
|
+
});
|
|
234
234
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
235
|
+
it("leaves a non-empty pair to the default backspace", () => {
|
|
236
|
+
const view = createView("(x)", 2);
|
|
237
|
+
// The pair handler declines (no empty pair around the caret), so the
|
|
238
|
+
// default backspace binding is left to run.
|
|
239
|
+
expect(backspace(view)).toBe(false);
|
|
240
|
+
expect(view.state.doc.toString()).toBe("(x)");
|
|
241
241
|
|
|
242
|
-
|
|
243
|
-
|
|
242
|
+
view.destroy();
|
|
243
|
+
});
|
|
244
244
|
});
|