@simple-mark/codemirror 0.3.3 → 0.3.5
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/index.d.ts +18 -5
- package/index.js +112 -25
- package/package.json +3 -3
package/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EditorState, Extension, Facet, StateCommand } from "@codemirror/state";
|
|
2
2
|
import { DecorationSet, EditorView, ViewPlugin, ViewUpdate } from "@codemirror/view";
|
|
3
|
-
import {
|
|
3
|
+
import { ResolvedLineLook, Theme } from "@simple-mark/model";
|
|
4
4
|
//#region packages/codemirror/src/links.d.ts
|
|
5
5
|
/** What to open: a web address, or a path as written (an image, another document, …). */
|
|
6
6
|
interface LinkTarget {
|
|
@@ -22,11 +22,15 @@ export declare function links(): Extension;
|
|
|
22
22
|
//#region packages/codemirror/src/image-resolver.d.ts
|
|
23
23
|
/**
|
|
24
24
|
* Turns a path as written in the document into a URL to load, or several to try in order
|
|
25
|
-
* (the first that loads is shown), or null if it can't be loaded.
|
|
25
|
+
* (the first that loads is shown), or null if it can't be loaded. `undefined` means the host
|
|
26
|
+
* doesn't know yet (its file is still loading): the editor shows that it is loading, and the
|
|
27
|
+
* host calls `refreshImages` once it knows.
|
|
26
28
|
*/
|
|
27
|
-
type ImageResolver = (src: string) => string | readonly string[] | null;
|
|
29
|
+
type ImageResolver = (src: string) => string | readonly string[] | null | undefined;
|
|
28
30
|
/** How image (and file) paths are resolved; by default they are used as written. */
|
|
29
31
|
export declare const imageResolver: Facet<ImageResolver, ImageResolver>;
|
|
32
|
+
/** Resolves the images again, e.g. when files the resolver was waiting for have loaded. */
|
|
33
|
+
export declare function refreshImages(view: EditorView): void;
|
|
30
34
|
//#endregion
|
|
31
35
|
//#region packages/codemirror/src/previews.d.ts
|
|
32
36
|
/** Image and math previews; the source text always stays visible. */
|
|
@@ -36,7 +40,7 @@ export declare function previews(): Extension;
|
|
|
36
40
|
/** The theme the editor draws with. */
|
|
37
41
|
export declare const markdownTheme: Facet<Theme, Theme>;
|
|
38
42
|
/** A look as an inline CSS declaration list. */
|
|
39
|
-
export declare function lookCss(look:
|
|
43
|
+
export declare function lookCss(look: ResolvedLineLook, theme: Theme): string;
|
|
40
44
|
declare class MarkdownDecorations {
|
|
41
45
|
decorations: DecorationSet;
|
|
42
46
|
private theme;
|
|
@@ -45,6 +49,12 @@ declare class MarkdownDecorations {
|
|
|
45
49
|
constructor(view: EditorView);
|
|
46
50
|
update(update: ViewUpdate): void;
|
|
47
51
|
private build;
|
|
52
|
+
/**
|
|
53
|
+
* One element behind each run of neighbouring spans with the same background (inline code
|
|
54
|
+
* with its backticks), so the background has rounded corners only at its ends. The spans
|
|
55
|
+
* themselves draw no background. Being longer, a box wraps the spans' elements.
|
|
56
|
+
*/
|
|
57
|
+
private backgroundBoxes;
|
|
48
58
|
/** Draws runs of wide characters exactly two columns each, so table pipes line up. */
|
|
49
59
|
private wideRuns;
|
|
50
60
|
private lineDecoration;
|
|
@@ -84,7 +94,10 @@ export interface EditorOptions {
|
|
|
84
94
|
theme?: Theme;
|
|
85
95
|
/** Line ending to read and write ("\n", "\r\n"); by default the one `doc` uses. */
|
|
86
96
|
lineSeparator?: string;
|
|
87
|
-
/**
|
|
97
|
+
/**
|
|
98
|
+
* Turns an image path from the document into a URL to load (default: as written). Return
|
|
99
|
+
* undefined while the file is still loading, and call `refreshImages(view)` when it has.
|
|
100
|
+
*/
|
|
88
101
|
resolveImage?: ImageResolver;
|
|
89
102
|
/**
|
|
90
103
|
* Opens a followed link (Ctrl/Cmd+Click) or a clicked image preview. Default: web addresses
|
package/index.js
CHANGED
|
@@ -2,21 +2,27 @@ import { defaultKeymap, history, historyKeymap } from "@codemirror/commands";
|
|
|
2
2
|
import { commonmarkLanguage, markdown } from "@codemirror/lang-markdown";
|
|
3
3
|
import { EditorState, Facet, Prec, StateEffect, StateField } from "@codemirror/state";
|
|
4
4
|
import { Decoration, EditorView, ViewPlugin, WidgetType, hoverTooltip, keymap, showTooltip } from "@codemirror/view";
|
|
5
|
-
import { cellAt, charWidth, defaultTheme, headingAnchors, linkAt, linkKind, markdownExtensions, markdownStyles, mathSource, parseTable, previewBlocks, renderTable, resolveLineLook, resolveLook, rowLine } from "@simple-mark/model";
|
|
5
|
+
import { STROKES, cellAt, charWidth, defaultTheme, headingAnchors, linkAt, linkKind, markdownExtensions, markdownStyles, mathSource, parseTable, previewBlocks, renderTable, resolveLineLook, resolveLook, rowLine } from "@simple-mark/model";
|
|
6
6
|
import { ensureSyntaxTree, syntaxTree } from "@codemirror/language";
|
|
7
7
|
//#region packages/codemirror/src/image-resolver.ts
|
|
8
|
-
/** The URLs a resolver gives for `src`, in order. */
|
|
8
|
+
/** The URLs a resolver gives for `src`, in order; undefined while the host is still loading. */
|
|
9
9
|
function resolvedUrls(resolve, src) {
|
|
10
10
|
const result = resolve(src);
|
|
11
|
-
return result === null ? [] : typeof result === "string" ? [result] : [...result];
|
|
11
|
+
return result === void 0 ? void 0 : result === null ? [] : typeof result === "string" ? [result] : [...result];
|
|
12
12
|
}
|
|
13
13
|
/** How image (and file) paths are resolved; by default they are used as written. */
|
|
14
14
|
const imageResolver = Facet.define({ combine: (values) => values[0] ?? ((src) => src) });
|
|
15
|
+
/** Asks the editor to resolve its images again. */
|
|
16
|
+
const imagesChanged = StateEffect.define();
|
|
17
|
+
/** Resolves the images again, e.g. when files the resolver was waiting for have loaded. */
|
|
18
|
+
function refreshImages(view) {
|
|
19
|
+
view.dispatch({ effects: imagesChanged.of(null) });
|
|
20
|
+
}
|
|
15
21
|
//#endregion
|
|
16
22
|
//#region packages/codemirror/src/links.ts
|
|
17
23
|
/** In a browser: web addresses and paths (resolved like images) open in a new tab. */
|
|
18
24
|
const openInBrowser = (target, view) => {
|
|
19
|
-
const url = target.kind === "web" ? target.url : resolvedUrls(view.state.facet(imageResolver), target.url)[0];
|
|
25
|
+
const url = target.kind === "web" ? target.url : resolvedUrls(view.state.facet(imageResolver), target.url)?.[0];
|
|
20
26
|
if (url) window.open(url, "_blank", "noopener,noreferrer");
|
|
21
27
|
};
|
|
22
28
|
/** How links are opened. */
|
|
@@ -123,12 +129,26 @@ function lookCss(look, theme) {
|
|
|
123
129
|
if (look.monospace !== void 0) css.push(`font-family: ${look.monospace ? theme.monospaceFamily : theme.fontFamily}`);
|
|
124
130
|
if (look.color !== void 0) css.push(`color: ${look.color}`);
|
|
125
131
|
if (look.background !== void 0) css.push(`background-color: ${look.background}`);
|
|
132
|
+
if (look.radius !== void 0) css.push(`border-radius: ${look.radius}em`);
|
|
126
133
|
if (look.strike !== void 0 || look.underline !== void 0) {
|
|
127
134
|
const lines = [look.strike && "line-through", look.underline && "underline"].filter(Boolean);
|
|
128
135
|
css.push(`text-decoration: ${lines.join(" ") || "none"}`);
|
|
129
136
|
}
|
|
137
|
+
const em = (size) => `${size / (look.fontScale ?? 1)}em`;
|
|
130
138
|
if (look.lineBackground !== void 0) css.push(`background-color: ${look.lineBackground}`);
|
|
131
|
-
if (look.
|
|
139
|
+
if (look.radiusTop || look.radiusBottom) {
|
|
140
|
+
const [top, bottom] = [em(look.radiusTop ?? 0), em(look.radiusBottom ?? 0)];
|
|
141
|
+
css.push(`border-radius: ${top} ${top} ${bottom} ${bottom}`);
|
|
142
|
+
}
|
|
143
|
+
const shadows = [];
|
|
144
|
+
if (look.borderLeft !== void 0) shadows.push(`inset ${em(STROKES.bar)} 0 0 ${look.borderLeft}`);
|
|
145
|
+
if (look.borderBottom !== void 0) shadows.push(`inset 0 -${em(STROKES.border)} 0 ${look.borderBottom}`);
|
|
146
|
+
if (shadows.length) css.push(`box-shadow: ${shadows.join(", ")}`);
|
|
147
|
+
if (look.rule !== void 0) {
|
|
148
|
+
const half = em(STROKES.rule / 2);
|
|
149
|
+
css.push(`background-image: linear-gradient(transparent calc(50% - ${half}), ${look.rule} 0, ${look.rule} calc(50% + ${half}), transparent 0)`);
|
|
150
|
+
}
|
|
151
|
+
if (look.indent !== void 0) css.push(`padding-left: ${em(look.indent)}`);
|
|
132
152
|
return css.join("; ");
|
|
133
153
|
}
|
|
134
154
|
const fontChecks = /* @__PURE__ */ new Map();
|
|
@@ -180,32 +200,86 @@ var MarkdownDecorations = class {
|
|
|
180
200
|
const { doc } = view.state;
|
|
181
201
|
const { from, to } = view.viewport;
|
|
182
202
|
const { blocks, spans } = markdownStyles(syntaxTree(view.state), from, to);
|
|
203
|
+
/** Block styles of each line, and the styles whose block starts or ends there. */
|
|
183
204
|
const lines = /* @__PURE__ */ new Map();
|
|
184
205
|
for (const block of blocks) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
206
|
+
let blockEnd = block.to;
|
|
207
|
+
if (blockEnd > block.from && doc.lineAt(blockEnd).from === blockEnd) blockEnd--;
|
|
208
|
+
const firstLine = doc.lineAt(block.from).from;
|
|
209
|
+
const lastLine = doc.lineAt(blockEnd).from;
|
|
210
|
+
for (let pos = Math.max(block.from, from), end = Math.min(blockEnd, to); pos <= end;) {
|
|
189
211
|
const line = doc.lineAt(pos);
|
|
190
|
-
let
|
|
191
|
-
if (!
|
|
192
|
-
|
|
212
|
+
let entry = lines.get(line.from);
|
|
213
|
+
if (!entry) lines.set(line.from, entry = {
|
|
214
|
+
styles: /* @__PURE__ */ new Set(),
|
|
215
|
+
first: /* @__PURE__ */ new Set(),
|
|
216
|
+
last: /* @__PURE__ */ new Set()
|
|
217
|
+
});
|
|
218
|
+
entry.styles.add(block.style);
|
|
219
|
+
if (line.from === firstLine) entry.first.add(block.style);
|
|
220
|
+
if (line.from === lastLine) entry.last.add(block.style);
|
|
193
221
|
pos = line.to + 1;
|
|
194
222
|
}
|
|
195
223
|
}
|
|
196
224
|
const ranges = [];
|
|
197
|
-
for (const [lineFrom,
|
|
225
|
+
for (const [lineFrom, entry] of lines) ranges.push(this.lineDecoration(entry).range(lineFrom));
|
|
226
|
+
for (const box of this.backgroundBoxes(spans)) ranges.push(box);
|
|
198
227
|
for (const span of spans) ranges.push(this.markDecoration(span.styles).range(span.from, span.to));
|
|
199
|
-
for (const [lineFrom, styles] of lines) {
|
|
228
|
+
for (const [lineFrom, { styles }] of lines) {
|
|
200
229
|
const line = doc.lineAt(lineFrom);
|
|
201
230
|
if (line.length && [...styles].some((s) => /^h[1-6]$/.test(s))) ranges.push(headingText.range(line.from, line.to));
|
|
202
231
|
}
|
|
203
232
|
if (this.fixWide) {
|
|
204
233
|
const cursorLines = new Set(view.state.selection.ranges.map((r) => doc.lineAt(r.head).from));
|
|
205
|
-
for (const [lineFrom, styles] of lines) if (styles.has("table") && !cursorLines.has(lineFrom)) this.wideRuns(doc.lineAt(lineFrom), ranges);
|
|
234
|
+
for (const [lineFrom, { styles }] of lines) if (styles.has("table") && !cursorLines.has(lineFrom)) this.wideRuns(doc.lineAt(lineFrom), ranges);
|
|
206
235
|
}
|
|
207
236
|
return Decoration.set(ranges, true);
|
|
208
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* One element behind each run of neighbouring spans with the same background (inline code
|
|
240
|
+
* with its backticks), so the background has rounded corners only at its ends. The spans
|
|
241
|
+
* themselves draw no background. Being longer, a box wraps the spans' elements.
|
|
242
|
+
*/
|
|
243
|
+
backgroundBoxes(spans) {
|
|
244
|
+
const boxes = [];
|
|
245
|
+
let open = null;
|
|
246
|
+
const flush = () => {
|
|
247
|
+
if (!open) return;
|
|
248
|
+
const { background, radius, monospace } = open.look;
|
|
249
|
+
const look = {
|
|
250
|
+
background,
|
|
251
|
+
...radius !== void 0 ? { radius } : {},
|
|
252
|
+
...monospace !== void 0 ? { monospace } : {}
|
|
253
|
+
};
|
|
254
|
+
const key = `box:${JSON.stringify(look)}`;
|
|
255
|
+
let deco = this.cache.get(key);
|
|
256
|
+
if (!deco) {
|
|
257
|
+
const css = `${lookCss(look, this.theme)}; line-height: 0; -webkit-box-decoration-break: clone; box-decoration-break: clone`;
|
|
258
|
+
deco = Decoration.mark({
|
|
259
|
+
class: "sm-box",
|
|
260
|
+
attributes: { style: css }
|
|
261
|
+
});
|
|
262
|
+
this.cache.set(key, deco);
|
|
263
|
+
}
|
|
264
|
+
boxes.push(deco.range(open.from, open.to));
|
|
265
|
+
open = null;
|
|
266
|
+
};
|
|
267
|
+
for (const span of spans) {
|
|
268
|
+
const look = resolveLook([], span.styles, this.theme);
|
|
269
|
+
if (look.background === void 0) flush();
|
|
270
|
+
else if (open && open.to === span.from && open.look.background === look.background) open.to = span.to;
|
|
271
|
+
else {
|
|
272
|
+
flush();
|
|
273
|
+
open = {
|
|
274
|
+
from: span.from,
|
|
275
|
+
to: span.to,
|
|
276
|
+
look
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
flush();
|
|
281
|
+
return boxes;
|
|
282
|
+
}
|
|
209
283
|
/** Draws runs of wide characters exactly two columns each, so table pipes line up. */
|
|
210
284
|
wideRuns(line, ranges) {
|
|
211
285
|
let start = -1;
|
|
@@ -235,13 +309,14 @@ var MarkdownDecorations = class {
|
|
|
235
309
|
}
|
|
236
310
|
flush(i);
|
|
237
311
|
}
|
|
238
|
-
lineDecoration(
|
|
239
|
-
const
|
|
312
|
+
lineDecoration(entry) {
|
|
313
|
+
const styles = [...entry.styles].sort();
|
|
314
|
+
const key = `line:${styles.join(" ")}|${[...entry.first].sort().join(" ")}|${[...entry.last].sort().join(" ")}`;
|
|
240
315
|
let deco = this.cache.get(key);
|
|
241
316
|
if (!deco) {
|
|
242
317
|
deco = Decoration.line({
|
|
243
318
|
class: styles.map((s) => `sm-line-${s}`).join(" "),
|
|
244
|
-
attributes: { style: lookCss(resolveLineLook(styles, this.theme), this.theme) }
|
|
319
|
+
attributes: { style: lookCss(resolveLineLook(styles, this.theme, entry), this.theme) }
|
|
245
320
|
});
|
|
246
321
|
this.cache.set(key, deco);
|
|
247
322
|
}
|
|
@@ -252,7 +327,7 @@ var MarkdownDecorations = class {
|
|
|
252
327
|
let deco = this.cache.get(key);
|
|
253
328
|
if (!deco) {
|
|
254
329
|
const linked = styles.includes("link") || styles.includes("url");
|
|
255
|
-
const look = resolveLook([], styles, this.theme);
|
|
330
|
+
const { background: _background, radius: _radius, ...look } = resolveLook([], styles, this.theme);
|
|
256
331
|
const keepLine = look.monospace !== void 0 || look.fontScale !== void 0;
|
|
257
332
|
deco = Decoration.mark({
|
|
258
333
|
class: styles.map((s) => `sm-${s}`).join(" "),
|
|
@@ -687,7 +762,15 @@ function imageRef(image, resolve) {
|
|
|
687
762
|
alt: image.alt,
|
|
688
763
|
problem: `No definition for ${image.raw}`
|
|
689
764
|
};
|
|
690
|
-
const
|
|
765
|
+
const urls = resolvedUrls(resolve, image.src);
|
|
766
|
+
if (urls === void 0) return {
|
|
767
|
+
raw: image.raw,
|
|
768
|
+
src: null,
|
|
769
|
+
alt: image.alt,
|
|
770
|
+
problem: `Loading ${image.raw}…`,
|
|
771
|
+
loading: true
|
|
772
|
+
};
|
|
773
|
+
const [src, ...fallbacks] = urls;
|
|
691
774
|
if (src === void 0) return {
|
|
692
775
|
raw: image.raw,
|
|
693
776
|
src: null,
|
|
@@ -732,14 +815,14 @@ var ImageWidget = class extends WidgetType {
|
|
|
732
815
|
if (--waiting === 0) rememberHeight(view, this.key, dom);
|
|
733
816
|
};
|
|
734
817
|
for (const image of this.images) {
|
|
735
|
-
const problem = (text) => {
|
|
818
|
+
const problem = (text, className = "sm-image-error") => {
|
|
736
819
|
const span = document.createElement("span");
|
|
737
|
-
span.className =
|
|
820
|
+
span.className = className;
|
|
738
821
|
span.textContent = text;
|
|
739
822
|
return span;
|
|
740
823
|
};
|
|
741
824
|
if (image.src === null) {
|
|
742
|
-
dom.appendChild(problem(image.problem ?? `Can't load ${image.raw}
|
|
825
|
+
dom.appendChild(problem(image.problem ?? `Can't load ${image.raw}`, image.loading ? "sm-image-loading" : "sm-image-error"));
|
|
743
826
|
continue;
|
|
744
827
|
}
|
|
745
828
|
waiting++;
|
|
@@ -801,7 +884,7 @@ function blockPreviews(state) {
|
|
|
801
884
|
const blockPreviewField = StateField.define({
|
|
802
885
|
create: blockPreviews,
|
|
803
886
|
update(previews, tr) {
|
|
804
|
-
return tr.docChanged || syntaxTree(tr.state) !== syntaxTree(tr.startState) || tr.state.facet(imageResolver) !== tr.startState.facet(imageResolver) || tr.effects.some((effect) => effect.is(refreshEstimates)) ? blockPreviews(tr.state) : previews;
|
|
887
|
+
return tr.docChanged || syntaxTree(tr.state) !== syntaxTree(tr.startState) || tr.state.facet(imageResolver) !== tr.startState.facet(imageResolver) || tr.effects.some((effect) => effect.is(refreshEstimates) || effect.is(imagesChanged)) ? blockPreviews(tr.state) : previews;
|
|
805
888
|
},
|
|
806
889
|
provide: (field) => EditorView.decorations.from(field)
|
|
807
890
|
});
|
|
@@ -863,6 +946,10 @@ const previewTheme = EditorView.theme({
|
|
|
863
946
|
color: "#cf222e",
|
|
864
947
|
fontSize: "0.9em"
|
|
865
948
|
},
|
|
949
|
+
".sm-image-loading": {
|
|
950
|
+
color: "#818b98",
|
|
951
|
+
fontSize: "0.9em"
|
|
952
|
+
},
|
|
866
953
|
".sm-math-preview": {
|
|
867
954
|
textAlign: "center",
|
|
868
955
|
padding: previewSpacing,
|
|
@@ -1143,4 +1230,4 @@ function createEditor(parent, options = {}) {
|
|
|
1143
1230
|
});
|
|
1144
1231
|
}
|
|
1145
1232
|
//#endregion
|
|
1146
|
-
export { createEditor, followLink, imageResolver, insertRowBelow, keepLineSeparator, linkOpener, links, lookCss, markdownDecorations, markdownTheme, nextCell, previews, previousCell, simpleMark, tableEditing };
|
|
1233
|
+
export { createEditor, followLink, imageResolver, insertRowBelow, keepLineSeparator, linkOpener, links, lookCss, markdownDecorations, markdownTheme, nextCell, previews, previousCell, refreshImages, simpleMark, tableEditing };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simple-mark/codemirror",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"description": "CodeMirror 6 editor that styles Markdown without hiding its syntax.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"@codemirror/state": "^6.7.6",
|
|
29
29
|
"@codemirror/view": "^6.43.15",
|
|
30
30
|
"@lezer/common": "^1.5.3",
|
|
31
|
-
"@simple-mark/math": "^0.3.
|
|
32
|
-
"@simple-mark/model": "^0.3.
|
|
31
|
+
"@simple-mark/math": "^0.3.5",
|
|
32
|
+
"@simple-mark/model": "^0.3.5"
|
|
33
33
|
}
|
|
34
34
|
}
|