@charcuterie/ui 3.2.0 → 3.4.0
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/dist/Field/Field.d.ts +1 -1
- package/dist/Field/Field.d.ts.map +1 -1
- package/dist/Field/Field.js +19 -1
- package/dist/Field/Field.js.map +1 -1
- package/dist/MarkdownEditor/MarkdownEditor.d.ts.map +1 -1
- package/dist/MarkdownEditor/MarkdownEditor.js +21 -8
- package/dist/MarkdownEditor/MarkdownEditor.js.map +1 -1
- package/dist/MarkdownEditor/markdownCommands.d.ts +32 -0
- package/dist/MarkdownEditor/markdownCommands.d.ts.map +1 -1
- package/dist/MarkdownEditor/markdownCommands.js +47 -0
- package/dist/MarkdownEditor/markdownCommands.js.map +1 -1
- package/dist/MarkdownEditor/markdownSpans.d.ts.map +1 -1
- package/dist/MarkdownEditor/markdownSpans.js +30 -0
- package/dist/MarkdownEditor/markdownSpans.js.map +1 -1
- package/dist/Select/Select.d.ts +43 -2
- package/dist/Select/Select.d.ts.map +1 -1
- package/dist/Select/Select.js +27 -2
- package/dist/Select/Select.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/markdownEditorCodeMirror/MarkdownEditorCodeMirror.d.ts +93 -0
- package/dist/markdownEditorCodeMirror/MarkdownEditorCodeMirror.d.ts.map +1 -0
- package/dist/markdownEditorCodeMirror/MarkdownEditorCodeMirror.js +545 -0
- package/dist/markdownEditorCodeMirror/MarkdownEditorCodeMirror.js.map +1 -0
- package/dist/markdownEditorCodeMirror/index.d.ts +16 -0
- package/dist/markdownEditorCodeMirror/index.d.ts.map +1 -0
- package/dist/markdownEditorCodeMirror/index.js +14 -0
- package/dist/markdownEditorCodeMirror/index.js.map +1 -0
- package/dist/markdownEditorCodeMirror/livePreview.d.ts +40 -0
- package/dist/markdownEditorCodeMirror/livePreview.d.ts.map +1 -0
- package/dist/markdownEditorCodeMirror/livePreview.js +428 -0
- package/dist/markdownEditorCodeMirror/livePreview.js.map +1 -0
- package/dist/markdownEditorCodeMirror/livePreviewRanges.d.ts +128 -0
- package/dist/markdownEditorCodeMirror/livePreviewRanges.d.ts.map +1 -0
- package/dist/markdownEditorCodeMirror/livePreviewRanges.js +345 -0
- package/dist/markdownEditorCodeMirror/livePreviewRanges.js.map +1 -0
- package/package.json +36 -1
- package/src/Field/Field.mdx +2 -2
- package/src/Field/Field.stories.tsx +9 -5
- package/src/Field/Field.test.tsx +43 -6
- package/src/Field/Field.tsx +19 -1
- package/src/MarkdownEditor/MarkdownEditor.tsx +33 -7
- package/src/MarkdownEditor/markdownCommands.test.ts +55 -0
- package/src/MarkdownEditor/markdownCommands.ts +60 -0
- package/src/MarkdownEditor/markdownSpans.test.ts +65 -0
- package/src/MarkdownEditor/markdownSpans.ts +35 -0
- package/src/Select/Select.mdx +27 -1
- package/src/Select/Select.stories.tsx +6 -1
- package/src/Select/Select.tsx +43 -2
- package/src/index.ts +5 -0
- package/src/markdownEditorCodeMirror/MarkdownEditorCodeMirror.mdx +144 -0
- package/src/markdownEditorCodeMirror/MarkdownEditorCodeMirror.stories.tsx +147 -0
- package/src/markdownEditorCodeMirror/MarkdownEditorCodeMirror.tsx +866 -0
- package/src/markdownEditorCodeMirror/index.ts +26 -0
- package/src/markdownEditorCodeMirror/livePreview.ts +535 -0
- package/src/markdownEditorCodeMirror/livePreviewRanges.test.ts +431 -0
- package/src/markdownEditorCodeMirror/livePreviewRanges.ts +581 -0
- package/src/sourceRules.test.ts +20 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@charcuterie/ui/markdown-editor-codemirror` — the opt-in live
|
|
3
|
+
* preview surface.
|
|
4
|
+
*
|
|
5
|
+
* Its own entry point, and every CodeMirror package it needs is an
|
|
6
|
+
* **optional** peer, so a consumer that never imports this subpath
|
|
7
|
+
* installs none of it and bundles none of it. Same packaging as
|
|
8
|
+
* `@charcuterie/ui/react-router`, and the same reason: the capacity
|
|
9
|
+
* is worth having and is not worth charging everybody for.
|
|
10
|
+
*/
|
|
11
|
+
export { livePreview, livePreviewRawModeField, setLivePreviewRawMode, } from "./livePreview.ts";
|
|
12
|
+
export type { LivePreviewLineKind, LivePreviewMarkKind, LivePreviewRange, LivePreviewSelection, ToLivePreviewRangesOptions, } from "./livePreviewRanges.ts";
|
|
13
|
+
export { toLivePreviewRanges } from "./livePreviewRanges.ts";
|
|
14
|
+
export type { MarkdownEditorCodeMirrorProps } from "./MarkdownEditorCodeMirror.tsx";
|
|
15
|
+
export { MarkdownEditorCodeMirror } from "./MarkdownEditorCodeMirror.tsx";
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/markdownEditorCodeMirror/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACL,WAAW,EACX,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,kBAAkB,CAAA;AACzB,YAAY,EACV,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,EACpB,0BAA0B,GAC3B,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAC5D,YAAY,EAAE,6BAA6B,EAAE,MAAM,gCAAgC,CAAA;AACnF,OAAO,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAA"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@charcuterie/ui/markdown-editor-codemirror` — the opt-in live
|
|
3
|
+
* preview surface.
|
|
4
|
+
*
|
|
5
|
+
* Its own entry point, and every CodeMirror package it needs is an
|
|
6
|
+
* **optional** peer, so a consumer that never imports this subpath
|
|
7
|
+
* installs none of it and bundles none of it. Same packaging as
|
|
8
|
+
* `@charcuterie/ui/react-router`, and the same reason: the capacity
|
|
9
|
+
* is worth having and is not worth charging everybody for.
|
|
10
|
+
*/
|
|
11
|
+
export { livePreview, livePreviewRawModeField, setLivePreviewRawMode, } from "./livePreview.js";
|
|
12
|
+
export { toLivePreviewRanges } from "./livePreviewRanges.js";
|
|
13
|
+
export { MarkdownEditorCodeMirror } from "./MarkdownEditorCodeMirror.js";
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/markdownEditorCodeMirror/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACL,WAAW,EACX,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,kBAAkB,CAAA;AAQzB,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAE5D,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAAgC,CAAA"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The CodeMirror half of live preview: descriptors → decorations,
|
|
3
|
+
* two widgets, and a theme that reads Charcuterie tokens.
|
|
4
|
+
*
|
|
5
|
+
* `livePreviewRanges.ts` decided *what* to decorate and is pure.
|
|
6
|
+
* This file owns everything that needs `@codemirror/view`, which is
|
|
7
|
+
* also everything that cannot be tested in this package's Node-only
|
|
8
|
+
* Vitest project — so it is kept deliberately thin and mechanical.
|
|
9
|
+
*
|
|
10
|
+
* ### Why the theme uses `var(--color-…)` and not Tailwind classes
|
|
11
|
+
*
|
|
12
|
+
* Every other visual in this package is a Tailwind utility reading
|
|
13
|
+
* a token. CodeMirror is the exception, and for a structural
|
|
14
|
+
* reason: it generates its own scoped class names and injects its
|
|
15
|
+
* own stylesheet, so the DOM these decorations land on is not DOM
|
|
16
|
+
* Tailwind ever scans. Referencing the custom properties directly
|
|
17
|
+
* keeps a scheme flip a repaint — the same behaviour, one layer
|
|
18
|
+
* lower — and avoids a `safelist` that would rot the first time a
|
|
19
|
+
* class here changed.
|
|
20
|
+
*
|
|
21
|
+
* The properties below are the generated token names, not invented
|
|
22
|
+
* ones. That distinction has already cost this fleet a release:
|
|
23
|
+
* Docket shipped invisible priority bars from `--color-danger-9`, a
|
|
24
|
+
* scale that does not exist, and nothing errored. A custom property
|
|
25
|
+
* that resolves to nothing paints nothing, silently.
|
|
26
|
+
*/
|
|
27
|
+
import type { Extension } from "@codemirror/state";
|
|
28
|
+
import { StateField } from "@codemirror/state";
|
|
29
|
+
/** Toggle the "edit Markdown" mode. */
|
|
30
|
+
export declare const setLivePreviewRawMode: import("@codemirror/state").StateEffectType<boolean>;
|
|
31
|
+
/**
|
|
32
|
+
* Raw mode as editor state rather than a React prop threaded down.
|
|
33
|
+
*
|
|
34
|
+
* It has to live here because the decoration plugin reads it on
|
|
35
|
+
* every update, and because a keybinding should be able to flip it
|
|
36
|
+
* without a round trip through React.
|
|
37
|
+
*/
|
|
38
|
+
export declare const livePreviewRawModeField: StateField<boolean>;
|
|
39
|
+
export declare const livePreview: () => Extension;
|
|
40
|
+
//# sourceMappingURL=livePreview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"livePreview.d.ts","sourceRoot":"","sources":["../../src/markdownEditorCodeMirror/livePreview.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAS,MAAM,mBAAmB,CAAA;AACzD,OAAO,EAAe,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAqB3D,uCAAuC;AACvC,eAAO,MAAM,qBAAqB,sDACH,CAAA;AAE/B;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,qBAchC,CAAA;AAqcJ,eAAO,MAAM,WAAW,QAAO,SAM9B,CAAA"}
|
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The CodeMirror half of live preview: descriptors → decorations,
|
|
3
|
+
* two widgets, and a theme that reads Charcuterie tokens.
|
|
4
|
+
*
|
|
5
|
+
* `livePreviewRanges.ts` decided *what* to decorate and is pure.
|
|
6
|
+
* This file owns everything that needs `@codemirror/view`, which is
|
|
7
|
+
* also everything that cannot be tested in this package's Node-only
|
|
8
|
+
* Vitest project — so it is kept deliberately thin and mechanical.
|
|
9
|
+
*
|
|
10
|
+
* ### Why the theme uses `var(--color-…)` and not Tailwind classes
|
|
11
|
+
*
|
|
12
|
+
* Every other visual in this package is a Tailwind utility reading
|
|
13
|
+
* a token. CodeMirror is the exception, and for a structural
|
|
14
|
+
* reason: it generates its own scoped class names and injects its
|
|
15
|
+
* own stylesheet, so the DOM these decorations land on is not DOM
|
|
16
|
+
* Tailwind ever scans. Referencing the custom properties directly
|
|
17
|
+
* keeps a scheme flip a repaint — the same behaviour, one layer
|
|
18
|
+
* lower — and avoids a `safelist` that would rot the first time a
|
|
19
|
+
* class here changed.
|
|
20
|
+
*
|
|
21
|
+
* The properties below are the generated token names, not invented
|
|
22
|
+
* ones. That distinction has already cost this fleet a release:
|
|
23
|
+
* Docket shipped invisible priority bars from `--color-danger-9`, a
|
|
24
|
+
* scale that does not exist, and nothing errored. A custom property
|
|
25
|
+
* that resolves to nothing paints nothing, silently.
|
|
26
|
+
*/
|
|
27
|
+
import { syntaxTree } from "@codemirror/language";
|
|
28
|
+
import { StateEffect, StateField } from "@codemirror/state";
|
|
29
|
+
import { Decoration, EditorView, ViewPlugin, WidgetType, } from "@codemirror/view";
|
|
30
|
+
import { toLivePreviewRanges } from "./livePreviewRanges.js";
|
|
31
|
+
/** Marks a rendered link so the click handler can find its target. */
|
|
32
|
+
const URL_ATTRIBUTE = "data-charcuterie-url";
|
|
33
|
+
/** Toggle the "edit Markdown" mode. */
|
|
34
|
+
export const setLivePreviewRawMode = StateEffect.define();
|
|
35
|
+
/**
|
|
36
|
+
* Raw mode as editor state rather than a React prop threaded down.
|
|
37
|
+
*
|
|
38
|
+
* It has to live here because the decoration plugin reads it on
|
|
39
|
+
* every update, and because a keybinding should be able to flip it
|
|
40
|
+
* without a round trip through React.
|
|
41
|
+
*/
|
|
42
|
+
export const livePreviewRawModeField = StateField.define({
|
|
43
|
+
create: () => false,
|
|
44
|
+
update: (isRawMode, transaction) => {
|
|
45
|
+
let isNextRawMode = isRawMode;
|
|
46
|
+
for (const effect of transaction.effects) {
|
|
47
|
+
if (effect.is(setLivePreviewRawMode)) {
|
|
48
|
+
isNextRawMode = effect.value;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return isNextRawMode;
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
const LINE_CLASSES = {
|
|
55
|
+
blockquote: "cm-md-blockquote",
|
|
56
|
+
code: "cm-md-codeblock",
|
|
57
|
+
heading1: "cm-md-heading1",
|
|
58
|
+
heading2: "cm-md-heading2",
|
|
59
|
+
heading3: "cm-md-heading3",
|
|
60
|
+
heading4: "cm-md-heading4",
|
|
61
|
+
heading5: "cm-md-heading5",
|
|
62
|
+
heading6: "cm-md-heading6",
|
|
63
|
+
table: "cm-md-table",
|
|
64
|
+
};
|
|
65
|
+
const MARK_CLASSES = {
|
|
66
|
+
autolink: "cm-md-link",
|
|
67
|
+
code: "cm-md-code",
|
|
68
|
+
emphasis: "cm-md-emphasis",
|
|
69
|
+
linkText: "cm-md-link",
|
|
70
|
+
strikethrough: "cm-md-strikethrough",
|
|
71
|
+
strong: "cm-md-strong",
|
|
72
|
+
url: "cm-md-url",
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* An image, standing in for its own markup.
|
|
76
|
+
*
|
|
77
|
+
* `eq` compares by URL *and* alt so scrolling past an image reuses
|
|
78
|
+
* the same `<img>` instead of refetching it, while an edit to
|
|
79
|
+
* either still rebuilds. Without it every keystroke elsewhere in
|
|
80
|
+
* the document flashes every image on screen.
|
|
81
|
+
*/
|
|
82
|
+
class ImageWidget extends WidgetType {
|
|
83
|
+
url;
|
|
84
|
+
alt;
|
|
85
|
+
constructor(url, alt) {
|
|
86
|
+
super();
|
|
87
|
+
this.url = url;
|
|
88
|
+
this.alt = alt;
|
|
89
|
+
}
|
|
90
|
+
eq(other) {
|
|
91
|
+
return other.url === this.url && other.alt === this.alt;
|
|
92
|
+
}
|
|
93
|
+
toDOM(view) {
|
|
94
|
+
const image = document.createElement("img");
|
|
95
|
+
image.setAttribute("src", this.url);
|
|
96
|
+
image.setAttribute("alt", this.alt);
|
|
97
|
+
image.className = "cm-md-image";
|
|
98
|
+
// The document's height is unknown until the image decodes, and
|
|
99
|
+
// CodeMirror caches line heights. Without a remeasure the lines
|
|
100
|
+
// after a tall image overlap it until something else forces a
|
|
101
|
+
// layout.
|
|
102
|
+
image.addEventListener("load", () => {
|
|
103
|
+
view.requestMeasure();
|
|
104
|
+
});
|
|
105
|
+
return image;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* A task checkbox, standing in for `[ ]` / `[x]`.
|
|
110
|
+
*
|
|
111
|
+
* The position is read back from the DOM with `posAtDOM` rather
|
|
112
|
+
* than captured in the constructor: a widget outlives the document
|
|
113
|
+
* offsets it was built at, and a stale `from` writes the checkbox
|
|
114
|
+
* into whatever moved into that slot.
|
|
115
|
+
*/
|
|
116
|
+
class TaskWidget extends WidgetType {
|
|
117
|
+
isChecked;
|
|
118
|
+
label;
|
|
119
|
+
constructor(isChecked, label) {
|
|
120
|
+
super();
|
|
121
|
+
this.isChecked = isChecked;
|
|
122
|
+
this.label = label;
|
|
123
|
+
}
|
|
124
|
+
eq(other) {
|
|
125
|
+
return (other.isChecked === this.isChecked &&
|
|
126
|
+
other.label === this.label);
|
|
127
|
+
}
|
|
128
|
+
toDOM(view) {
|
|
129
|
+
const checkbox = document.createElement("input");
|
|
130
|
+
checkbox.type = "checkbox";
|
|
131
|
+
checkbox.checked = this.isChecked;
|
|
132
|
+
checkbox.className = "cm-md-task";
|
|
133
|
+
/**
|
|
134
|
+
* The item's own text, as the checkbox's name.
|
|
135
|
+
*
|
|
136
|
+
* A bare `<input type="checkbox">` inside a `contenteditable`
|
|
137
|
+
* has no wrapping `<label>` to inherit from and no `for`
|
|
138
|
+
* pointing at it, so without this it announces as an unlabelled
|
|
139
|
+
* checkbox — and a task list is exactly where "checkbox,
|
|
140
|
+
* checked" with no name is useless. The text is right there on
|
|
141
|
+
* the line; using it means the widget says what the sighted
|
|
142
|
+
* reader sees.
|
|
143
|
+
*/
|
|
144
|
+
checkbox.setAttribute("aria-label", this.label === "" ? "Task" : this.label);
|
|
145
|
+
checkbox.addEventListener("mousedown", (event) => {
|
|
146
|
+
// The checkbox owns the click; without this CodeMirror also
|
|
147
|
+
// moves the caret into the widget's slot on the way past.
|
|
148
|
+
event.stopPropagation();
|
|
149
|
+
});
|
|
150
|
+
checkbox.addEventListener("change", () => {
|
|
151
|
+
const from = view.posAtDOM(checkbox);
|
|
152
|
+
view.dispatch({
|
|
153
|
+
changes: {
|
|
154
|
+
from,
|
|
155
|
+
insert: this.isChecked ? "[ ]" : "[x]",
|
|
156
|
+
to: from + 3,
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
return checkbox;
|
|
161
|
+
}
|
|
162
|
+
ignoreEvent() {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const toDecorations = (view) => {
|
|
167
|
+
const isRawMode = view.state.field(livePreviewRawModeField, false);
|
|
168
|
+
const text = view.state.doc.toString();
|
|
169
|
+
/**
|
|
170
|
+
* An unfocused editor reveals nothing.
|
|
171
|
+
*
|
|
172
|
+
* Without this the caret's default resting place — offset 0 —
|
|
173
|
+
* counts as "inside" whatever construct starts the document, so a
|
|
174
|
+
* task whose description opens with a heading rendered its `#` on
|
|
175
|
+
* load and no other heading's. It looked like a concealment bug
|
|
176
|
+
* and was really a focus bug: there is no caret in the document
|
|
177
|
+
* until someone puts one there.
|
|
178
|
+
*/
|
|
179
|
+
const selections = view.hasFocus
|
|
180
|
+
? view.state.selection.ranges.map((range) => ({
|
|
181
|
+
from: range.from,
|
|
182
|
+
to: range.to,
|
|
183
|
+
}))
|
|
184
|
+
: [];
|
|
185
|
+
const decorations = [];
|
|
186
|
+
for (const visible of view.visibleRanges) {
|
|
187
|
+
for (const range of toLivePreviewRanges({
|
|
188
|
+
from: visible.from,
|
|
189
|
+
isRawMode: isRawMode ?? false,
|
|
190
|
+
selections,
|
|
191
|
+
text,
|
|
192
|
+
to: visible.to,
|
|
193
|
+
tree: syntaxTree(view.state),
|
|
194
|
+
})) {
|
|
195
|
+
switch (range.type) {
|
|
196
|
+
case "image": {
|
|
197
|
+
decorations.push(Decoration.replace({
|
|
198
|
+
widget: new ImageWidget(range.url, range.alt),
|
|
199
|
+
}).range(range.from, range.to));
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
case "line": {
|
|
203
|
+
decorations.push(Decoration.line({
|
|
204
|
+
class: LINE_CLASSES[range.lineKind],
|
|
205
|
+
}).range(range.from));
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
case "mark": {
|
|
209
|
+
decorations.push(Decoration.mark({
|
|
210
|
+
attributes: range.url
|
|
211
|
+
? { [URL_ATTRIBUTE]: range.url }
|
|
212
|
+
: undefined,
|
|
213
|
+
class: MARK_CLASSES[range.markKind],
|
|
214
|
+
}).range(range.from, range.to));
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
case "marker": {
|
|
218
|
+
decorations.push(range.isConcealed
|
|
219
|
+
? Decoration.replace({}).range(range.from, range.to)
|
|
220
|
+
: Decoration.mark({
|
|
221
|
+
class: "cm-md-marker",
|
|
222
|
+
}).range(range.from, range.to));
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
case "task": {
|
|
226
|
+
decorations.push(Decoration.replace({
|
|
227
|
+
widget: new TaskWidget(range.isChecked, range.label),
|
|
228
|
+
}).range(range.from, range.to));
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// `true` sorts. The tree walk emits in tree order, which is not
|
|
235
|
+
// the same as decoration order once line decorations and inline
|
|
236
|
+
// replacements interleave, and an unsorted set throws.
|
|
237
|
+
return Decoration.set(decorations, true);
|
|
238
|
+
};
|
|
239
|
+
const livePreviewPlugin = ViewPlugin.fromClass(class {
|
|
240
|
+
decorations;
|
|
241
|
+
constructor(view) {
|
|
242
|
+
this.decorations = toDecorations(view);
|
|
243
|
+
}
|
|
244
|
+
update(update) {
|
|
245
|
+
// Selection is in the list because concealment depends on the
|
|
246
|
+
// caret: moving it with an arrow key changes what is painted
|
|
247
|
+
// even though the document did not change.
|
|
248
|
+
if (update.docChanged ||
|
|
249
|
+
update.selectionSet ||
|
|
250
|
+
update.viewportChanged ||
|
|
251
|
+
// Focus is a decoration input now — see `toDecorations`.
|
|
252
|
+
update.focusChanged ||
|
|
253
|
+
update.transactions.some((transaction) => transaction.effects.some((effect) => effect.is(setLivePreviewRawMode)))) {
|
|
254
|
+
this.decorations = toDecorations(update.view);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}, {
|
|
258
|
+
decorations: (plugin) => plugin.decorations,
|
|
259
|
+
});
|
|
260
|
+
/**
|
|
261
|
+
* Open the link under the pointer.
|
|
262
|
+
*
|
|
263
|
+
* Plain click, not Ctrl+click, and that is Obsidian's behaviour
|
|
264
|
+
* rather than VS Code's — in a surface where the markup is hidden,
|
|
265
|
+
* a link that looks like a link should act like one. The cost is
|
|
266
|
+
* that clicking link *text* cannot place the caret there; arrow
|
|
267
|
+
* into it from the edge, or use the raw-markdown toggle. That is
|
|
268
|
+
* the same trade Obsidian makes and it is documented in the mdx.
|
|
269
|
+
*
|
|
270
|
+
* `noopener` is not optional: `target="_blank"` without it hands
|
|
271
|
+
* the opened page a live `window.opener` back into this one.
|
|
272
|
+
*/
|
|
273
|
+
const openLinkOnClick = EditorView.domEventHandlers({
|
|
274
|
+
mousedown: (event) => {
|
|
275
|
+
const target = event.target;
|
|
276
|
+
if (!(target instanceof HTMLElement)) {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
const url = target
|
|
280
|
+
.closest(`[${URL_ATTRIBUTE}]`)
|
|
281
|
+
?.getAttribute(URL_ATTRIBUTE);
|
|
282
|
+
if (!url) {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
event.preventDefault();
|
|
286
|
+
window.open(url, "_blank", "noopener,noreferrer");
|
|
287
|
+
return true;
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
/**
|
|
291
|
+
* The paint.
|
|
292
|
+
*
|
|
293
|
+
* Headings scale here — which is the one thing the textarea
|
|
294
|
+
* version could never do, because a `<textarea>` and a layer behind
|
|
295
|
+
* it only stay registered while every glyph keeps its advance
|
|
296
|
+
* width. CodeMirror owns the caret, so size is free.
|
|
297
|
+
*/
|
|
298
|
+
const livePreviewTheme = EditorView.theme({
|
|
299
|
+
/**
|
|
300
|
+
* Prose is proportional, and only code is monospaced.
|
|
301
|
+
*
|
|
302
|
+
* The textarea sibling has no choice here — its painted layer only
|
|
303
|
+
* stays registered with the caret while every glyph keeps its
|
|
304
|
+
* advance width, which in practice means one monospaced family for
|
|
305
|
+
* the whole document. This surface has no such constraint, and a
|
|
306
|
+
* description full of prose set in a code font is the single
|
|
307
|
+
* biggest thing that makes an editor *feel* like a code editor
|
|
308
|
+
* rather than like a document.
|
|
309
|
+
*
|
|
310
|
+
* It has to be `.cm-scroller`, not `&`. CodeMirror's own base
|
|
311
|
+
* theme sets `font-family: monospace` there, and a rule on the
|
|
312
|
+
* wrapper loses to it — silently, and it looks like the token
|
|
313
|
+
* failed to resolve rather than like a specificity problem.
|
|
314
|
+
*/
|
|
315
|
+
".cm-scroller": {
|
|
316
|
+
fontFamily: "var(--font-sans)",
|
|
317
|
+
},
|
|
318
|
+
/**
|
|
319
|
+
* …except in raw mode, where monospace is the honest choice:
|
|
320
|
+
* "Markdown source" is a *source* view, and source is code.
|
|
321
|
+
*/
|
|
322
|
+
"&.cm-md-raw .cm-scroller": {
|
|
323
|
+
fontFamily: "var(--font-mono)",
|
|
324
|
+
},
|
|
325
|
+
".cm-md-blockquote": {
|
|
326
|
+
borderInlineStart: "3px solid var(--color-intent-accent-border)",
|
|
327
|
+
color: "var(--color-content-secondary)",
|
|
328
|
+
paddingInlineStart: "0.75em",
|
|
329
|
+
},
|
|
330
|
+
".cm-md-code": {
|
|
331
|
+
color: "var(--color-intent-info-content)",
|
|
332
|
+
fontFamily: "var(--font-mono)",
|
|
333
|
+
},
|
|
334
|
+
".cm-md-codeblock": {
|
|
335
|
+
backgroundColor: "var(--color-surface-sunken)",
|
|
336
|
+
fontFamily: "var(--font-mono)",
|
|
337
|
+
},
|
|
338
|
+
".cm-md-emphasis": {
|
|
339
|
+
fontStyle: "italic",
|
|
340
|
+
},
|
|
341
|
+
".cm-md-heading1": {
|
|
342
|
+
fontSize: "1.75em",
|
|
343
|
+
fontWeight: "700",
|
|
344
|
+
lineHeight: "1.25",
|
|
345
|
+
},
|
|
346
|
+
".cm-md-heading2": {
|
|
347
|
+
fontSize: "1.5em",
|
|
348
|
+
fontWeight: "700",
|
|
349
|
+
lineHeight: "1.3",
|
|
350
|
+
},
|
|
351
|
+
".cm-md-heading3": {
|
|
352
|
+
fontSize: "1.25em",
|
|
353
|
+
fontWeight: "600",
|
|
354
|
+
lineHeight: "1.35",
|
|
355
|
+
},
|
|
356
|
+
".cm-md-heading4": {
|
|
357
|
+
fontSize: "1.1em",
|
|
358
|
+
fontWeight: "600",
|
|
359
|
+
},
|
|
360
|
+
".cm-md-heading5": {
|
|
361
|
+
fontWeight: "600",
|
|
362
|
+
},
|
|
363
|
+
".cm-md-heading6": {
|
|
364
|
+
color: "var(--color-content-secondary)",
|
|
365
|
+
fontWeight: "600",
|
|
366
|
+
},
|
|
367
|
+
".cm-md-image": {
|
|
368
|
+
borderRadius: "var(--radius-md)",
|
|
369
|
+
display: "inline-block",
|
|
370
|
+
maxWidth: "100%",
|
|
371
|
+
verticalAlign: "top",
|
|
372
|
+
},
|
|
373
|
+
".cm-md-link": {
|
|
374
|
+
color: "var(--color-intent-accent-content)",
|
|
375
|
+
cursor: "pointer",
|
|
376
|
+
textDecoration: "underline",
|
|
377
|
+
},
|
|
378
|
+
/**
|
|
379
|
+
* The dimmed marker.
|
|
380
|
+
*
|
|
381
|
+
* A token role, never `opacity`. Fading text over a themed
|
|
382
|
+
* background composites to a colour nobody audited, and axe
|
|
383
|
+
* measures the composited value — `content-secondary` at 60% came
|
|
384
|
+
* out at 3.77:1 in the sibling component and failed AA.
|
|
385
|
+
*/
|
|
386
|
+
".cm-md-marker": {
|
|
387
|
+
color: "var(--color-content-muted)",
|
|
388
|
+
},
|
|
389
|
+
".cm-md-strikethrough": {
|
|
390
|
+
color: "var(--color-content-secondary)",
|
|
391
|
+
textDecoration: "line-through",
|
|
392
|
+
},
|
|
393
|
+
".cm-md-strong": {
|
|
394
|
+
fontWeight: "700",
|
|
395
|
+
},
|
|
396
|
+
".cm-md-table": {
|
|
397
|
+
fontFamily: "var(--font-mono)",
|
|
398
|
+
},
|
|
399
|
+
".cm-md-task": {
|
|
400
|
+
accentColor: "var(--color-intent-accent-solid)",
|
|
401
|
+
marginInlineEnd: "0.35em",
|
|
402
|
+
verticalAlign: "middle",
|
|
403
|
+
},
|
|
404
|
+
".cm-md-url": {
|
|
405
|
+
color: "var(--color-content-muted)",
|
|
406
|
+
},
|
|
407
|
+
});
|
|
408
|
+
/**
|
|
409
|
+
* Everything the live-preview surface needs, as one extension.
|
|
410
|
+
*
|
|
411
|
+
* Order matters only in that the theme must be present for the
|
|
412
|
+
* decoration classes to mean anything; CodeMirror resolves the rest.
|
|
413
|
+
*/
|
|
414
|
+
/**
|
|
415
|
+
* Raw mode, as a class on the editor, so the theme above can react
|
|
416
|
+
* to it in CSS instead of every rule becoming a decoration.
|
|
417
|
+
*/
|
|
418
|
+
const rawModeClass = EditorView.editorAttributes.of((view) => view.state.field(livePreviewRawModeField, false)
|
|
419
|
+
? { class: "cm-md-raw" }
|
|
420
|
+
: null);
|
|
421
|
+
export const livePreview = () => [
|
|
422
|
+
livePreviewRawModeField,
|
|
423
|
+
livePreviewPlugin,
|
|
424
|
+
livePreviewTheme,
|
|
425
|
+
openLinkOnClick,
|
|
426
|
+
rawModeClass,
|
|
427
|
+
];
|
|
428
|
+
//# sourceMappingURL=livePreview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"livePreview.js","sourceRoot":"","sources":["../../src/markdownEditorCodeMirror/livePreview.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown → the ranges a live-preview surface decorates, as pure
|
|
3
|
+
* tree-in / descriptors-out.
|
|
4
|
+
*
|
|
5
|
+
* This is the half of the CodeMirror editor that decides what a
|
|
6
|
+
* document *looks* like, and it is deliberately the half with no
|
|
7
|
+
* `@codemirror/view` import in it. Descriptors are plain
|
|
8
|
+
* `{ from, to, type }` objects; turning them into `Decoration`s is
|
|
9
|
+
* `livePreview.ts`'s job. The split buys two things:
|
|
10
|
+
*
|
|
11
|
+
* - **These tests run in Node.** `packages/ui`'s Vitest project is
|
|
12
|
+
* `.ts`-only and has no DOM, on purpose. A test here parses real
|
|
13
|
+
* markdown with the real GFM parser and asserts real offsets —
|
|
14
|
+
* no jsdom, no mount, no story.
|
|
15
|
+
* - **The runtime stays incremental.** Tests hand in
|
|
16
|
+
* `markdownLanguage.parser.parse(text)`; the view hands in
|
|
17
|
+
* `syntaxTree(state)`, which CodeMirror has already reparsed
|
|
18
|
+
* incrementally. Same function, same offsets, and the expensive
|
|
19
|
+
* path is never the one under test.
|
|
20
|
+
*
|
|
21
|
+
* ### What separates this from `markdownSpans.ts`
|
|
22
|
+
*
|
|
23
|
+
* The sibling `MarkdownEditor` paints *behind* a `<textarea>`, so
|
|
24
|
+
* every span it emits has to be metric-neutral — same glyph
|
|
25
|
+
* advances, or the caret shears away from the letters. That is the
|
|
26
|
+
* constraint this module does **not** have. CodeMirror owns the
|
|
27
|
+
* caret and reflows around decorations, so a heading can genuinely
|
|
28
|
+
* be larger, `**` can genuinely be hidden, and an image can
|
|
29
|
+
* genuinely replace its own markup.
|
|
30
|
+
*
|
|
31
|
+
* That is the entire reason this subpath exists, and it is also why
|
|
32
|
+
* it is a subpath: the capability costs ~176 KB gz of optional peer
|
|
33
|
+
* and only a consumer that opts in should pay it.
|
|
34
|
+
*
|
|
35
|
+
* ### The one invariant
|
|
36
|
+
*
|
|
37
|
+
* **Concealment never touches the document.** Every descriptor here
|
|
38
|
+
* describes a *view* over an unmodified markdown string. Hiding
|
|
39
|
+
* `**` does not delete it; replacing `` with an image
|
|
40
|
+
* does not rewrite it. The stored value stays byte-for-byte what
|
|
41
|
+
* the user typed, which is the same no-HTML-in-the-data guarantee
|
|
42
|
+
* the textarea version gets structurally — kept here by never
|
|
43
|
+
* having a serialiser at all.
|
|
44
|
+
*/
|
|
45
|
+
import type { Tree } from "@lezer/common";
|
|
46
|
+
/**
|
|
47
|
+
* A block-level treatment applied to a whole line.
|
|
48
|
+
*
|
|
49
|
+
* Headings carry their level because size is the point: this is the
|
|
50
|
+
* "block-level type scaling" the textarea version had to give up.
|
|
51
|
+
*/
|
|
52
|
+
export type LivePreviewLineKind = "blockquote" | "code" | "heading1" | "heading2" | "heading3" | "heading4" | "heading5" | "heading6" | "table";
|
|
53
|
+
/** An inline treatment applied to a character range. */
|
|
54
|
+
export type LivePreviewMarkKind = "autolink" | "code" | "emphasis" | "linkText" | "strikethrough" | "strong" | "url";
|
|
55
|
+
export type LivePreviewRange = {
|
|
56
|
+
from: number;
|
|
57
|
+
to: number;
|
|
58
|
+
} & ({
|
|
59
|
+
/** A rendered image, standing in for its own ``. */
|
|
60
|
+
alt: string;
|
|
61
|
+
type: "image";
|
|
62
|
+
url: string;
|
|
63
|
+
} | {
|
|
64
|
+
/** A rendered task checkbox, standing in for `[ ]` / `[x]`. */
|
|
65
|
+
isChecked: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* The item's own text, which becomes the checkbox's
|
|
68
|
+
* accessible name — a checkbox inside a `contenteditable` has
|
|
69
|
+
* no `<label>` to inherit one from.
|
|
70
|
+
*/
|
|
71
|
+
label: string;
|
|
72
|
+
type: "task";
|
|
73
|
+
} | {
|
|
74
|
+
/**
|
|
75
|
+
* Syntax the reader does not need: `**`, `#`, `[`, `](…)`.
|
|
76
|
+
*
|
|
77
|
+
* `isConcealed` is the caret's answer, not the parser's. When
|
|
78
|
+
* the selection is inside the construct — or the whole
|
|
79
|
+
* surface is in raw mode — the marker stays in the flow and
|
|
80
|
+
* is merely dimmed, which is what makes the syntax editable
|
|
81
|
+
* at all.
|
|
82
|
+
*/
|
|
83
|
+
isConcealed: boolean;
|
|
84
|
+
type: "marker";
|
|
85
|
+
} | {
|
|
86
|
+
lineKind: LivePreviewLineKind;
|
|
87
|
+
type: "line";
|
|
88
|
+
} | {
|
|
89
|
+
markKind: LivePreviewMarkKind;
|
|
90
|
+
type: "mark";
|
|
91
|
+
/** Present on `linkText` and `autolink`; the click target. */
|
|
92
|
+
url?: string;
|
|
93
|
+
});
|
|
94
|
+
/** A selection range, in the two fields this module needs. */
|
|
95
|
+
export type LivePreviewSelection = {
|
|
96
|
+
from: number;
|
|
97
|
+
to: number;
|
|
98
|
+
};
|
|
99
|
+
export type ToLivePreviewRangesOptions = {
|
|
100
|
+
/**
|
|
101
|
+
* Limit the walk to a slice of the document — the view passes its
|
|
102
|
+
* visible ranges, so a long description costs what is on screen
|
|
103
|
+
* rather than what is stored. Omit both and the whole tree is
|
|
104
|
+
* walked, which is what the tests do.
|
|
105
|
+
*/
|
|
106
|
+
from?: number;
|
|
107
|
+
/**
|
|
108
|
+
* Raw mode — the "edit Markdown" toggle.
|
|
109
|
+
*
|
|
110
|
+
* Nothing is concealed, nothing is replaced by a widget, and no
|
|
111
|
+
* line is rescaled. Syntax colouring stays, because the point of
|
|
112
|
+
* the toggle is to see the markup, not to lose the highlighting.
|
|
113
|
+
*/
|
|
114
|
+
isRawMode?: boolean;
|
|
115
|
+
selections: readonly LivePreviewSelection[];
|
|
116
|
+
text: string;
|
|
117
|
+
to?: number;
|
|
118
|
+
tree: Tree;
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Every decoration the document wants, in tree order.
|
|
122
|
+
*
|
|
123
|
+
* Order is not sorted here on purpose — `Decoration.set(ranges,
|
|
124
|
+
* true)` sorts, and doing it twice is the sort of thing that looks
|
|
125
|
+
* free until a document is long.
|
|
126
|
+
*/
|
|
127
|
+
export declare const toLivePreviewRanges: ({ from, isRawMode, selections, text, to, tree, }: ToLivePreviewRangesOptions) => LivePreviewRange[];
|
|
128
|
+
//# sourceMappingURL=livePreviewRanges.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"livePreviewRanges.d.ts","sourceRoot":"","sources":["../../src/markdownEditorCodeMirror/livePreviewRanges.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,OAAO,KAAK,EAAc,IAAI,EAAE,MAAM,eAAe,CAAA;AAErD;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAC3B,YAAY,GACZ,MAAM,GACN,UAAU,GACV,UAAU,GACV,UAAU,GACV,UAAU,GACV,UAAU,GACV,UAAU,GACV,OAAO,CAAA;AAEX,wDAAwD;AACxD,MAAM,MAAM,mBAAmB,GAC3B,UAAU,GACV,MAAM,GACN,UAAU,GACV,UAAU,GACV,eAAe,GACf,QAAQ,GACR,KAAK,CAAA;AAET,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,CAAA;CACX,GAAG,CACA;IACE,+DAA+D;IAC/D,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,OAAO,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;CACZ,GACD;IACE,+DAA+D;IAC/D,SAAS,EAAE,OAAO,CAAA;IAClB;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;CACb,GACD;IACE;;;;;;;;OAQG;IACH,WAAW,EAAE,OAAO,CAAA;IACpB,IAAI,EAAE,QAAQ,CAAA;CACf,GACD;IACE,QAAQ,EAAE,mBAAmB,CAAA;IAC7B,IAAI,EAAE,MAAM,CAAA;CACb,GACD;IACE,QAAQ,EAAE,mBAAmB,CAAA;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,8DAA8D;IAC9D,GAAG,CAAC,EAAE,MAAM,CAAA;CACb,CACJ,CAAA;AAED,8DAA8D;AAC9D,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,CAAA;CACX,CAAA;AAED,MAAM,MAAM,0BAA0B,GAAG;IACvC;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,UAAU,EAAE,SAAS,oBAAoB,EAAE,CAAA;IAC3C,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,IAAI,CAAA;CACX,CAAA;AAgID;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,GAAI,kDAOjC,0BAA0B,KAAG,gBAAgB,EAoS/C,CAAA"}
|