@openpresentation/opf-editor 0.26.7 → 0.26.8
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/source-wrap.js +49 -7
- package/dist/styles.css +6 -4
- package/package.json +7 -7
package/dist/source-wrap.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RangeSetBuilder, countColumn } from "@codemirror/state";
|
|
1
|
+
import { RangeSetBuilder, StateEffect, countColumn } from "@codemirror/state";
|
|
2
2
|
import { Decoration, ViewPlugin } from "@codemirror/view";
|
|
3
3
|
|
|
4
4
|
// A wrapped line continues under its own indentation, as VS Code's `wrappingIndent: "indent"` does (JSON, YAML and Markdown).
|
|
@@ -6,9 +6,14 @@ import { Decoration, ViewPlugin } from "@codemirror/view";
|
|
|
6
6
|
// text-indent, so the first line starts where it always did and every continuation line starts n columns in. n is the line's leading
|
|
7
7
|
// whitespace (a tab counts as the editor's tab size) plus the width of a list marker ("- ", "1. ") when the line starts one, so wrapped
|
|
8
8
|
// list items line up under their text, or plus two columns (one indent step) for any other line, so a continuation never looks like a new line.
|
|
9
|
+
// The hang is capped at 40% of the line's width, so a deeply nested line still leaves room for text on a phone. The cap is worked out here, in
|
|
10
|
+
// columns, from the measured width, and not in the stylesheet: a percentage in `padding-left` makes the style and layout of every line depend on
|
|
11
|
+
// its containing block, and replacing the text of a long document then took about 15 times as long (0.26.7).
|
|
9
12
|
|
|
10
|
-
/** The most columns a line can hang. The stylesheet defines `opf-hang-0` through `opf-hang-${MAX_HANG}
|
|
13
|
+
/** The most columns a line can hang. The stylesheet defines `opf-hang-0` through `opf-hang-${MAX_HANG}`. */
|
|
11
14
|
export const MAX_HANG = 32;
|
|
15
|
+
/** The share of the line's width a hang can take. */
|
|
16
|
+
export const HANG_SHARE = 0.4;
|
|
12
17
|
const LIST_MARKER = /^([-*+]|\d{1,9}[.)])[ \t]+/;
|
|
13
18
|
const OFFSET = 2;
|
|
14
19
|
|
|
@@ -22,9 +27,18 @@ export function hangColumns(text, tabSize = 4) {
|
|
|
22
27
|
return Math.min(columns, MAX_HANG);
|
|
23
28
|
}
|
|
24
29
|
|
|
30
|
+
/**
|
|
31
|
+
* The most columns a line can hang in a line `width` pixels wide whose columns are `columnWidth` pixels wide: 40% of the width, in whole
|
|
32
|
+
* columns, never above MAX_HANG. `MAX_HANG` when either size is unknown (zero, negative or not a number), for example while the editor is hidden.
|
|
33
|
+
*/
|
|
34
|
+
export function hangCap(width, columnWidth) {
|
|
35
|
+
if (!(width > 0) || !(columnWidth > 0)) return MAX_HANG;
|
|
36
|
+
return Math.max(0, Math.min(MAX_HANG, Math.floor((width * HANG_SHARE) / columnWidth)));
|
|
37
|
+
}
|
|
38
|
+
|
|
25
39
|
const decorations = Array.from({ length: MAX_HANG + 1 }, (_, columns) => Decoration.line({ class: `opf-hang-${columns}` }));
|
|
26
40
|
|
|
27
|
-
function build(view) {
|
|
41
|
+
function build(view, cap) {
|
|
28
42
|
const builder = new RangeSetBuilder();
|
|
29
43
|
const { doc, tabSize } = view.state;
|
|
30
44
|
let last = -1;
|
|
@@ -32,7 +46,7 @@ function build(view) {
|
|
|
32
46
|
for (let position = from; position <= to;) {
|
|
33
47
|
const line = doc.lineAt(position);
|
|
34
48
|
if (line.from > last) {
|
|
35
|
-
builder.add(line.from, line.from, decorations[hangColumns(line.text, tabSize)]);
|
|
49
|
+
builder.add(line.from, line.from, decorations[Math.min(hangColumns(line.text, tabSize), cap)]);
|
|
36
50
|
last = line.from;
|
|
37
51
|
}
|
|
38
52
|
position = line.to + 1;
|
|
@@ -41,14 +55,42 @@ function build(view) {
|
|
|
41
55
|
return builder.finish();
|
|
42
56
|
}
|
|
43
57
|
|
|
44
|
-
|
|
58
|
+
const setCap = StateEffect.define();
|
|
59
|
+
|
|
60
|
+
/** The width of the text area of a line, in pixels: the content box of the editor's content element, which is what a percentage would have resolved against. */
|
|
61
|
+
function lineWidth(view) {
|
|
62
|
+
const content = view.contentDOM, style = getComputedStyle(content);
|
|
63
|
+
return content.clientWidth - (parseFloat(style.paddingLeft) || 0) - (parseFloat(style.paddingRight) || 0);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The extension: line decorations for the visible lines (a line is decorated again when the document, the viewport, the tab size or the cap
|
|
68
|
+
* changes). The cap is measured in CodeMirror's measure cycle whenever the geometry changes, and the lines are decorated again only when it differs.
|
|
69
|
+
*/
|
|
45
70
|
export const hangingIndent = ViewPlugin.fromClass(
|
|
46
71
|
class {
|
|
47
72
|
constructor(view) {
|
|
48
|
-
this.
|
|
73
|
+
this.cap = MAX_HANG;
|
|
74
|
+
this.decorations = build(view, this.cap);
|
|
75
|
+
this.measure = {
|
|
76
|
+
key: this,
|
|
77
|
+
read: (target) => hangCap(lineWidth(target), target.defaultCharacterWidth),
|
|
78
|
+
write: (cap, target) => {
|
|
79
|
+
if (cap === this.cap) return;
|
|
80
|
+
// The measure cycle is an update in progress: the new cap is dispatched right after it, before the browser paints.
|
|
81
|
+
queueMicrotask(() => { if (!this.destroyed) target.dispatch({ effects: setCap.of(cap) }); });
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
view.requestMeasure(this.measure);
|
|
49
85
|
}
|
|
50
86
|
update(update) {
|
|
51
|
-
|
|
87
|
+
let capped = false;
|
|
88
|
+
for (const transaction of update.transactions) for (const effect of transaction.effects) if (effect.is(setCap)) { this.cap = effect.value; capped = true; }
|
|
89
|
+
if (update.geometryChanged || update.viewportChanged) update.view.requestMeasure(this.measure);
|
|
90
|
+
if (capped || update.docChanged || update.viewportChanged || update.startState.tabSize !== update.state.tabSize) this.decorations = build(update.view, this.cap);
|
|
91
|
+
}
|
|
92
|
+
destroy() {
|
|
93
|
+
this.destroyed = true;
|
|
52
94
|
}
|
|
53
95
|
},
|
|
54
96
|
{ decorations: (plugin) => plugin.decorations },
|
package/dist/styles.css
CHANGED
|
@@ -3555,8 +3555,10 @@ input.opf-lx-input[data-field="max"] { font-family: var(--opf-ui-mono); font-siz
|
|
|
3555
3555
|
|
|
3556
3556
|
/* A wrapped line continues under its own indentation (src/source-wrap.js, VS Code's wrappingIndent "indent"): `opf-hang-<n>` says how many
|
|
3557
3557
|
columns, the first line keeps its place (a negative text-indent of the same width) and every continuation line starts n columns in.
|
|
3558
|
-
The hang is capped at 40% of the line so a deeply nested line still leaves room for text at 320px
|
|
3559
|
-
|
|
3558
|
+
The hang is capped at 40% of the line so a deeply nested line still leaves room for text at 320px; the cap is applied to n in source-wrap.js,
|
|
3559
|
+
from the measured width, because a percentage here makes style and layout of every line very slow (replacing the text of an 848-line
|
|
3560
|
+
document took 8.5 s instead of 0.56 s). Text inside an inline widget does not inherit the negative indent. `--opf-line-start` is the line's own
|
|
3561
|
+
left padding (the source pane sets it to 2px). */
|
|
3560
3562
|
.cm-line.opf-hang-0 { --opf-hang: 0; }
|
|
3561
3563
|
.cm-line.opf-hang-1 { --opf-hang: 1; }
|
|
3562
3564
|
.cm-line.opf-hang-2 { --opf-hang: 2; }
|
|
@@ -3591,8 +3593,8 @@ input.opf-lx-input[data-field="max"] { font-family: var(--opf-ui-mono); font-siz
|
|
|
3591
3593
|
.cm-line.opf-hang-31 { --opf-hang: 31; }
|
|
3592
3594
|
.cm-line.opf-hang-32 { --opf-hang: 32; }
|
|
3593
3595
|
:is(.opf-ui, .opf-json, .opf-code) .cm-line {
|
|
3594
|
-
padding-left: calc(var(--opf-line-start, 6px) +
|
|
3595
|
-
text-indent: calc(-1 *
|
|
3596
|
+
padding-left: calc(var(--opf-line-start, 6px) + var(--opf-hang, 0) * 1ch);
|
|
3597
|
+
text-indent: calc(-1 * var(--opf-hang, 0) * 1ch);
|
|
3596
3598
|
}
|
|
3597
3599
|
|
|
3598
3600
|
:is(.opf-ui, .opf-json, .opf-code) .cm-line > * {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openpresentation/opf-editor",
|
|
3
|
-
"version": "0.26.
|
|
3
|
+
"version": "0.26.8",
|
|
4
4
|
"description": "Embeddable local OPF editor primitives built on the deterministic renderer.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -287,10 +287,10 @@
|
|
|
287
287
|
"@lezer/highlight": "1.2.5",
|
|
288
288
|
"jsonc-parser": "3.3.1",
|
|
289
289
|
"yaml": "^2.9.1",
|
|
290
|
-
"@openpresentation/opf": "^0.26.
|
|
290
|
+
"@openpresentation/opf": "^0.26.8"
|
|
291
291
|
},
|
|
292
292
|
"peerDependencies": {
|
|
293
|
-
"@openpresentation/opf-render": "^0.26.
|
|
293
|
+
"@openpresentation/opf-render": "^0.26.8"
|
|
294
294
|
},
|
|
295
295
|
"peerDependenciesMeta": {
|
|
296
296
|
"@openpresentation/opf-render": {
|
|
@@ -349,9 +349,9 @@
|
|
|
349
349
|
"pdf-lib": "1.17.1",
|
|
350
350
|
"pdfjs-dist": "6.4.299",
|
|
351
351
|
"playwright": "1.64.0",
|
|
352
|
-
"@openpresentation/gallery": "0.26.
|
|
353
|
-
"@openpresentation/opf-pptx": "0.26.
|
|
354
|
-
"@openpresentation/opf-render": "0.26.
|
|
352
|
+
"@openpresentation/gallery": "0.26.8",
|
|
353
|
+
"@openpresentation/opf-pptx": "0.26.8",
|
|
354
|
+
"@openpresentation/opf-render": "0.26.8"
|
|
355
355
|
},
|
|
356
356
|
"scripts": {
|
|
357
357
|
"build": "node scripts/build.mjs",
|
|
@@ -402,5 +402,5 @@
|
|
|
402
402
|
"test:export-menu-browser": "node test/export-menu-browser.mjs",
|
|
403
403
|
"test:agent-handoff-browser": "node test/agent-handoff-browser.mjs"
|
|
404
404
|
},
|
|
405
|
-
"gitHead": "
|
|
405
|
+
"gitHead": "bb6faa40bebc1654f607bf6a434c43b5e95d0b1e"
|
|
406
406
|
}
|