@simple-mark/codemirror 0.3.4 → 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.
Files changed (3) hide show
  1. package/index.d.ts +8 -2
  2. package/index.js +85 -16
  3. 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 { LineLook, Theme } from "@simple-mark/model";
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 {
@@ -40,7 +40,7 @@ export declare function previews(): Extension;
40
40
  /** The theme the editor draws with. */
41
41
  export declare const markdownTheme: Facet<Theme, Theme>;
42
42
  /** A look as an inline CSS declaration list. */
43
- export declare function lookCss(look: LineLook, theme: Theme): string;
43
+ export declare function lookCss(look: ResolvedLineLook, theme: Theme): string;
44
44
  declare class MarkdownDecorations {
45
45
  decorations: DecorationSet;
46
46
  private theme;
@@ -49,6 +49,12 @@ declare class MarkdownDecorations {
49
49
  constructor(view: EditorView);
50
50
  update(update: ViewUpdate): void;
51
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;
52
58
  /** Draws runs of wide characters exactly two columns each, so table pipes line up. */
53
59
  private wideRuns;
54
60
  private lineDecoration;
package/index.js CHANGED
@@ -2,7 +2,7 @@ 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
8
  /** The URLs a resolver gives for `src`, in order; undefined while the host is still loading. */
@@ -129,12 +129,26 @@ function lookCss(look, theme) {
129
129
  if (look.monospace !== void 0) css.push(`font-family: ${look.monospace ? theme.monospaceFamily : theme.fontFamily}`);
130
130
  if (look.color !== void 0) css.push(`color: ${look.color}`);
131
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`);
132
133
  if (look.strike !== void 0 || look.underline !== void 0) {
133
134
  const lines = [look.strike && "line-through", look.underline && "underline"].filter(Boolean);
134
135
  css.push(`text-decoration: ${lines.join(" ") || "none"}`);
135
136
  }
137
+ const em = (size) => `${size / (look.fontScale ?? 1)}em`;
136
138
  if (look.lineBackground !== void 0) css.push(`background-color: ${look.lineBackground}`);
137
- if (look.borderLeft !== void 0) css.push(`box-shadow: inset ${3 / 15}em 0 0 ${look.borderLeft}`, `padding-left: ${12 / 15}em`);
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)}`);
138
152
  return css.join("; ");
139
153
  }
140
154
  const fontChecks = /* @__PURE__ */ new Map();
@@ -186,32 +200,86 @@ var MarkdownDecorations = class {
186
200
  const { doc } = view.state;
187
201
  const { from, to } = view.viewport;
188
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. */
189
204
  const lines = /* @__PURE__ */ new Map();
190
205
  for (const block of blocks) {
191
- const start = Math.max(block.from, from);
192
- let end = Math.min(block.to, to);
193
- if (end > start && doc.lineAt(end).from === end) end--;
194
- for (let pos = start; pos <= end;) {
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;) {
195
211
  const line = doc.lineAt(pos);
196
- let styles = lines.get(line.from);
197
- if (!styles) lines.set(line.from, styles = /* @__PURE__ */ new Set());
198
- styles.add(block.style);
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);
199
221
  pos = line.to + 1;
200
222
  }
201
223
  }
202
224
  const ranges = [];
203
- for (const [lineFrom, styles] of lines) ranges.push(this.lineDecoration([...styles]).range(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);
204
227
  for (const span of spans) ranges.push(this.markDecoration(span.styles).range(span.from, span.to));
205
- for (const [lineFrom, styles] of lines) {
228
+ for (const [lineFrom, { styles }] of lines) {
206
229
  const line = doc.lineAt(lineFrom);
207
230
  if (line.length && [...styles].some((s) => /^h[1-6]$/.test(s))) ranges.push(headingText.range(line.from, line.to));
208
231
  }
209
232
  if (this.fixWide) {
210
233
  const cursorLines = new Set(view.state.selection.ranges.map((r) => doc.lineAt(r.head).from));
211
- 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);
212
235
  }
213
236
  return Decoration.set(ranges, true);
214
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
+ }
215
283
  /** Draws runs of wide characters exactly two columns each, so table pipes line up. */
216
284
  wideRuns(line, ranges) {
217
285
  let start = -1;
@@ -241,13 +309,14 @@ var MarkdownDecorations = class {
241
309
  }
242
310
  flush(i);
243
311
  }
244
- lineDecoration(styles) {
245
- const key = `line:${styles.sort().join(" ")}`;
312
+ lineDecoration(entry) {
313
+ const styles = [...entry.styles].sort();
314
+ const key = `line:${styles.join(" ")}|${[...entry.first].sort().join(" ")}|${[...entry.last].sort().join(" ")}`;
246
315
  let deco = this.cache.get(key);
247
316
  if (!deco) {
248
317
  deco = Decoration.line({
249
318
  class: styles.map((s) => `sm-line-${s}`).join(" "),
250
- attributes: { style: lookCss(resolveLineLook(styles, this.theme), this.theme) }
319
+ attributes: { style: lookCss(resolveLineLook(styles, this.theme, entry), this.theme) }
251
320
  });
252
321
  this.cache.set(key, deco);
253
322
  }
@@ -258,7 +327,7 @@ var MarkdownDecorations = class {
258
327
  let deco = this.cache.get(key);
259
328
  if (!deco) {
260
329
  const linked = styles.includes("link") || styles.includes("url");
261
- const look = resolveLook([], styles, this.theme);
330
+ const { background: _background, radius: _radius, ...look } = resolveLook([], styles, this.theme);
262
331
  const keepLine = look.monospace !== void 0 || look.fontScale !== void 0;
263
332
  deco = Decoration.mark({
264
333
  class: styles.map((s) => `sm-${s}`).join(" "),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simple-mark/codemirror",
3
- "version": "0.3.4",
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.4",
32
- "@simple-mark/model": "^0.3.4"
31
+ "@simple-mark/math": "^0.3.5",
32
+ "@simple-mark/model": "^0.3.5"
33
33
  }
34
34
  }