@openpresentation/opf-editor 0.26.4 → 0.26.6
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/README.md +8 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.js +9 -2
- package/dist/json-editor.d.ts +20 -0
- package/dist/json-editor.js +189 -91
- package/dist/source-a11y.js +18 -1
- package/dist/source-check.js +90 -0
- package/dist/source-checker.js +130 -0
- package/dist/source-completions.js +232 -22
- package/dist/source-details.js +17 -0
- package/dist/source-diagnostics.js +3 -2
- package/dist/source-editing.js +53 -33
- package/dist/source-fixes-syntax.js +334 -0
- package/dist/source-fixes-text.js +75 -2
- package/dist/source-fixes.js +25 -1
- package/dist/source-folding.js +173 -0
- package/dist/source-hints.js +376 -0
- package/dist/source-keymap.js +369 -0
- package/dist/source-markdown.js +53 -17
- package/dist/source-messages.js +21 -205
- package/dist/source-navigation-model.js +426 -0
- package/dist/source-navigation-read.js +269 -0
- package/dist/source-navigation-ui.js +244 -0
- package/dist/source-navigation.js +217 -0
- package/dist/source-outline.js +112 -0
- package/dist/source-pane.d.ts +63 -0
- package/dist/source-pane.js +240 -45
- package/dist/source-problems.js +3 -0
- package/dist/source-references.js +274 -0
- package/dist/source-shortcuts.js +106 -0
- package/dist/source-snippets.js +20 -0
- package/dist/source-symbols.js +156 -0
- package/dist/source-undo-groups.js +46 -0
- package/dist/source-worker.d.ts +7 -0
- package/dist/source-worker.js +30 -0
- package/dist/source-yaml.js +94 -9
- package/dist/styles.css +288 -0
- package/package.json +12 -7
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// The worker entry of the source pane's text checks (`createSourcePane(el, { worker })`). A host bundles this file as its own worker script
|
|
2
|
+
// (`@openpresentation/opf-editor/source-worker`), for example `new Worker(new URL("@openpresentation/opf-editor/source-worker", import.meta.url), { type: "module" })`
|
|
3
|
+
// with a bundler that understands that form, or builds it as an extra entry point and serves it next to the page. It does the same work as the pane's
|
|
4
|
+
// own inline path (`./source-check.js`), off the main thread. Requests run in order, but when several checks of one kind are waiting only the newest runs
|
|
5
|
+
// (the older ones are answered with `skipped`): the pane drops stale replies anyway.
|
|
6
|
+
import { createCheckService } from "./source-check.js";
|
|
7
|
+
|
|
8
|
+
const service = createCheckService();
|
|
9
|
+
let queue = [];
|
|
10
|
+
let scheduled = false;
|
|
11
|
+
|
|
12
|
+
function drain() {
|
|
13
|
+
scheduled = false;
|
|
14
|
+
const work = queue;
|
|
15
|
+
queue = [];
|
|
16
|
+
// The last check and the last syntax request win; catalogs messages always run, in order, and before the checks that follow them.
|
|
17
|
+
const newest = new Map();
|
|
18
|
+
work.forEach((message, index) => { if (message.type === "check" || message.type === "syntax") newest.set(message.type, index); });
|
|
19
|
+
work.forEach((message, index) => {
|
|
20
|
+
if ((message.type === "check" || message.type === "syntax") && newest.get(message.type) !== index) { self.postMessage({ id: message.id, ok: true, skipped: true }); return; }
|
|
21
|
+
const reply = service.handle(message);
|
|
22
|
+
try { self.postMessage(reply); } catch (error) { self.postMessage({ id: message.id, ok: false, error: `The result could not be sent: ${error?.message ?? error}` }); }
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
self.onmessage = (event) => {
|
|
27
|
+
queue.push(event.data);
|
|
28
|
+
// Messages that arrive while a check runs queue behind it; draining on the next task lets everything already waiting be seen together.
|
|
29
|
+
if (!scheduled) { scheduled = true; setTimeout(drain, 0); }
|
|
30
|
+
};
|
package/dist/source-yaml.js
CHANGED
|
@@ -220,16 +220,40 @@ function inComment(line, column) {
|
|
|
220
220
|
return false;
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
+
/** The quote character of a quoted scalar that `value` opens and does not close, or null (a quoted scalar may run over several lines). */
|
|
224
|
+
export function openQuote(value) {
|
|
225
|
+
const text = value.trimStart();
|
|
226
|
+
const quote = text[0];
|
|
227
|
+
if (quote !== '"' && quote !== "'") return null;
|
|
228
|
+
return closesQuote(text.slice(1), quote) ? null : quote;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** Whether `text` (the rest of a quoted scalar) holds its closing quote. */
|
|
232
|
+
function closesQuote(text, quote) {
|
|
233
|
+
for (let index = 0; index < text.length; index++) {
|
|
234
|
+
if (quote === '"' && text[index] === "\\") { index++; continue; }
|
|
235
|
+
if (text[index] === quote) {
|
|
236
|
+
if (quote === "'" && text[index + 1] === "'") { index++; continue; }
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
|
|
223
243
|
/**
|
|
224
|
-
* Walk the lines before `stop` (a line index), applying each to `stack`. Lines inside a block scalar
|
|
225
|
-
* lines, comments and document markers change nothing. Returns `{ block, flow }`: the column a block scalar's text must be
|
|
226
|
-
* the depth of an unclosed flow collection (0 when none)
|
|
244
|
+
* Walk the lines before `stop` (a line index), applying each to `stack`. Lines inside a block scalar, a flow collection or a quoted scalar that span
|
|
245
|
+
* lines, blank lines, comments and document markers change nothing. Returns `{ block, flow, quote, open }`: the column a block scalar's text must be
|
|
246
|
+
* indented past (or -1), the depth of an unclosed flow collection (0 when none), the quote of an unclosed quoted scalar (or null) at line `stop`, so
|
|
247
|
+
* the caller knows whether that line is inside any of them, and for a flow collection `open`: the line it began on and where it sits in the stack.
|
|
227
248
|
*/
|
|
228
249
|
function walk(lines, stop, stack) {
|
|
229
250
|
let block = -1;
|
|
230
251
|
let flow = 0;
|
|
252
|
+
let quote = null;
|
|
253
|
+
let open = null;
|
|
231
254
|
for (let number = 0; number < stop; number++) {
|
|
232
255
|
const text = lines[number].text;
|
|
256
|
+
if (quote) { if (closesQuote(text, quote)) quote = null; continue; }
|
|
233
257
|
if (text.trim() === "") continue;
|
|
234
258
|
const indent = text.length - text.trimStart().length;
|
|
235
259
|
if (block >= 0) {
|
|
@@ -244,11 +268,14 @@ function walk(lines, stop, stack) {
|
|
|
244
268
|
const value = entry ? parsed.rest.slice(entry.valueStart) : parsed.rest;
|
|
245
269
|
const shape = entry ? shapeOfValue(value) : (parsed.markers.length ? shapeOfValue(value) : undefined);
|
|
246
270
|
if (!entry && !parsed.markers.length) continue;
|
|
247
|
-
enter(stack, parsed, entry, shape);
|
|
271
|
+
const placed = enter(stack, parsed, entry, shape);
|
|
248
272
|
if (shape?.block) block = entry ? parsed.restStart + entry.keyStart : parsed.markers.at(-1);
|
|
249
|
-
else if (shape?.flow)
|
|
273
|
+
else if (shape?.flow) {
|
|
274
|
+
flow = flowDepth(value, 0);
|
|
275
|
+
open = flow > 0 ? { number, parsed, entry, value, placed } : null;
|
|
276
|
+
} else if (shape && "scalar" in shape) quote = openQuote(value);
|
|
250
277
|
}
|
|
251
|
-
return { block, flow };
|
|
278
|
+
return { block, flow, quote, open };
|
|
252
279
|
}
|
|
253
280
|
|
|
254
281
|
// ---- flow collections on one line --------------------------------------------------------------------------------------------------
|
|
@@ -401,7 +428,13 @@ function keyToken(text, line, restStart, position) {
|
|
|
401
428
|
* colon yet. Options: `explicit` (Ctrl+Space also takes an empty place), `prefix` (the path of the node the text is the body of, for YAML in a fence of
|
|
402
429
|
* a Markdown slide), `exclude` (keys never offered, the `slides` of front matter).
|
|
403
430
|
*/
|
|
404
|
-
export function yamlContextAt(text, position,
|
|
431
|
+
export function yamlContextAt(text, position, options = {}) {
|
|
432
|
+
const context = readContext(text, position, options);
|
|
433
|
+
// The text the context was read from, and the path it is the body of: the references the value lists offer are looked up in it.
|
|
434
|
+
return context ? { ...context, sourceText: text, prefix: options.prefix ?? [] } : null;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function readContext(text, position, { explicit = false, prefix = [], exclude = [] }) {
|
|
405
438
|
const lines = linesOf(text);
|
|
406
439
|
const number = lineIndex(lines, position);
|
|
407
440
|
const line = lines[number];
|
|
@@ -414,8 +447,10 @@ export function yamlContextAt(text, position, { explicit = false, prefix = [], e
|
|
|
414
447
|
const state = walk(lines, number, stack);
|
|
415
448
|
const lineIndent = lineText.length - lineText.trimStart().length;
|
|
416
449
|
if (state.block >= 0 && (trimmedLine === "" ? column > state.block : lineIndent > state.block)) return null;
|
|
417
|
-
if (state.flow > 0) return null;
|
|
418
450
|
const before = lineText.slice(0, column);
|
|
451
|
+
// A line inside a quoted scalar that began above: text, in which only `{{` of a variable is completed.
|
|
452
|
+
if (state.quote) return continuationContext({ text, line, position, before, explicit });
|
|
453
|
+
if (state.flow > 0) return flowContinuation({ text, lines, number, line, position, column, explicit, prefix, exclude, open: state.open });
|
|
419
454
|
const parsed = readMarkers(before);
|
|
420
455
|
// The key is read from the whole line: a colon after the caret that ends the key (`top:le|ft: x`) is not the colon before it.
|
|
421
456
|
const whole = readMarkers(lineText);
|
|
@@ -425,6 +460,53 @@ export function yamlContextAt(text, position, { explicit = false, prefix = [], e
|
|
|
425
460
|
return entry ? valueContext({ ...shared, entry }) : keyOrItemContext(shared);
|
|
426
461
|
}
|
|
427
462
|
|
|
463
|
+
const VARIABLE_OPEN = /\{\{[A-Za-z0-9._-]*$/;
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* A line of text that continues a scalar begun on a line above (a quoted scalar left open, or a sentence that runs on): nothing to complete in it
|
|
467
|
+
* except the name of a variable after `{{`. The context is a value in the text sense (`multiline`), with no path.
|
|
468
|
+
*/
|
|
469
|
+
function continuationContext({ text, line, position, before, explicit }) {
|
|
470
|
+
if (!VARIABLE_OPEN.test(before)) return null;
|
|
471
|
+
let end = position;
|
|
472
|
+
while (end < line.end && /[A-Za-z0-9._-]/.test(text[end])) end++;
|
|
473
|
+
const start = line.start + (line.text.length - line.text.trimStart().length);
|
|
474
|
+
return { format: "yaml", mode: "value", multiline: true, token: { quoted: true, closed: false, start, end, from: start, to: end }, path: [], key: undefined, existing: new Set(), document: {}, itemStart: false, flow: false, needsSpace: false, column: 0, indent: 0, explicit };
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// The text of a line without its comment (a `#` after a space, outside quotes).
|
|
478
|
+
function withoutComment(text) {
|
|
479
|
+
let quote = null;
|
|
480
|
+
for (let index = 0; index < text.length; index++) {
|
|
481
|
+
const character = text[index];
|
|
482
|
+
if (quote) {
|
|
483
|
+
if (quote === '"' && character === "\\") index++;
|
|
484
|
+
else if (character === quote) quote = null;
|
|
485
|
+
} else if (character === '"' || character === "'") {
|
|
486
|
+
if (index === 0 || /[\s[{,:-]/.test(text[index - 1])) quote = character;
|
|
487
|
+
} else if (character === "#" && (index === 0 || isSpace(text[index - 1]))) return text.slice(0, index);
|
|
488
|
+
}
|
|
489
|
+
return text;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* A caret on a line inside a flow collection that began on an earlier line (`tags: [a,` / ` b, |]`, `{theme: minimal,` / ` |}`): the lines from
|
|
494
|
+
* the opening one to the caret are read as one text, so the containers open at the caret and the keys and items before it are known as on one line.
|
|
495
|
+
*/
|
|
496
|
+
function flowContinuation({ text, lines, number, line, position, column, explicit, prefix, exclude, open }) {
|
|
497
|
+
if (!open) return null;
|
|
498
|
+
const first = lines[open.number].text;
|
|
499
|
+
const start = open.entry ? open.parsed.restStart + open.entry.valueStart : open.parsed.restStart;
|
|
500
|
+
const middle = lines.slice(open.number + 1, number).map((item) => withoutComment(item.text).trim());
|
|
501
|
+
const joined = [withoutComment(first.slice(start)).trimStart(), ...middle, line.text.slice(0, column).trimStart()].join(" ");
|
|
502
|
+
const flow = flowAt(joined, joined.length);
|
|
503
|
+
if (!flow) return null;
|
|
504
|
+
const { map, seq } = open.placed;
|
|
505
|
+
const frame = open.entry ? map : seq;
|
|
506
|
+
const base = open.entry ? [...map.path, open.entry.key] : [...seq.path, seq.count - 1];
|
|
507
|
+
return flowContext({ text, line, position, flow, base, frame, lines, explicit, prefix, exclude });
|
|
508
|
+
}
|
|
509
|
+
|
|
428
510
|
function valueContext({ text, lines, line, position, parsed, entry, explicit, prefix, stack, lineIndent, exclude }) {
|
|
429
511
|
const lineText = line.text;
|
|
430
512
|
let valueFrom = line.start + parsed.restStart + entry.valueStart;
|
|
@@ -465,7 +547,10 @@ function keyOrItemContext({ text, lines, number, line, position, parsed, explici
|
|
|
465
547
|
}
|
|
466
548
|
const quoteOpen = rest[0] === '"' || rest[0] === "'";
|
|
467
549
|
// The word before the caret is a plain key (or the start of a scalar item) or an unfinished quoted key; anything else (a sentence) is prose.
|
|
468
|
-
if (!quoteOpen && rest !== "" && !/^[A-Za-z0-9_.$+:-]+$/.test(rest))
|
|
550
|
+
if (!quoteOpen && rest !== "" && !/^[A-Za-z0-9_.$+:-]+$/.test(rest)) {
|
|
551
|
+
// A sentence that runs on from a scalar above: only a variable (`{{`) is completed in it.
|
|
552
|
+
return parsed.markers.length ? null : continuationContext({ text, line, position, before: lineText.slice(0, position - line.start), explicit });
|
|
553
|
+
}
|
|
469
554
|
let seq = null;
|
|
470
555
|
for (const col of parsed.markers) { seq = stack.seqAt(col); seq.items[seq.count - 1] = { open: true }; }
|
|
471
556
|
const frame = stack.mapAt(parsed.restStart);
|
package/dist/styles.css
CHANGED
|
@@ -3639,6 +3639,13 @@ input.opf-lx-input[data-field="max"] { font-family: var(--opf-ui-mono); font-siz
|
|
|
3639
3639
|
opacity: 0.85;
|
|
3640
3640
|
}
|
|
3641
3641
|
|
|
3642
|
+
:root :is(.opf-ui, .opf-json, .opf-code) .cm-tooltip-autocomplete > .opf-completion-footer {
|
|
3643
|
+
padding: 4px 8px;
|
|
3644
|
+
font: 12px/1.4 var(--opf-ui-font);
|
|
3645
|
+
color: var(--opf-ui-muted-fg);
|
|
3646
|
+
border-top: 1px solid var(--opf-ui-border);
|
|
3647
|
+
}
|
|
3648
|
+
|
|
3642
3649
|
:root :is(.opf-ui, .opf-json, .opf-code) .cm-tooltip-autocomplete .cm-completionMatchedText {
|
|
3643
3650
|
font-weight: 600;
|
|
3644
3651
|
text-decoration: none;
|
|
@@ -3700,6 +3707,26 @@ input.opf-lx-input[data-field="max"] { font-family: var(--opf-ui-mono); font-siz
|
|
|
3700
3707
|
.opf-fix[aria-selected="true"] { background: var(--opf-ui-accent); color: var(--opf-ui-accent-fg); }
|
|
3701
3708
|
:root :is(.opf-ui, .opf-json, .opf-code) .cm-tooltip .cm-completionInfo { white-space: normal; }
|
|
3702
3709
|
|
|
3710
|
+
/* The shape hint (Cmd/Ctrl+Shift+Space): the fields of the object around the caret, each set, next or optional. The state is a word and a mark, not only a color. */
|
|
3711
|
+
.cm-tooltip.opf-hint { display: flex; flex-direction: column; min-width: 22em; max-width: 38em; padding: 4px 0; font: 12.5px var(--opf-ui-font); white-space: normal; }
|
|
3712
|
+
.opf-hint-head { display: flex; justify-content: space-between; gap: 24px; padding: 2px 12px 5px; border-bottom: 1px solid var(--opf-ui-border); }
|
|
3713
|
+
.opf-hint-title { font: 600 12px var(--opf-ui-mono); }
|
|
3714
|
+
.opf-hint-aside { color: var(--opf-ui-muted-fg); font-size: 11.5px; white-space: nowrap; }
|
|
3715
|
+
.opf-hint-rows { display: flex; flex-direction: column; margin: 0; padding: 3px 0 0; list-style: none; }
|
|
3716
|
+
.opf-hint-row { display: grid; grid-template-columns: 8em minmax(0, 1fr) 5.5em; gap: 12px; align-items: baseline; padding: 1px 12px; }
|
|
3717
|
+
.opf-hint-key { font: 12px var(--opf-ui-mono); overflow-wrap: anywhere; }
|
|
3718
|
+
.opf-hint-description { color: var(--opf-ui-muted-fg); }
|
|
3719
|
+
.opf-hint-state { text-align: right; color: var(--opf-ui-muted-fg); font-size: 11.5px; }
|
|
3720
|
+
.opf-hint-row[data-state="set"] .opf-hint-state::before { content: "\2713\00a0"; }
|
|
3721
|
+
.opf-hint-row[data-state="next"] { background: color-mix(in srgb, var(--opf-ui-accent) 12%, transparent); box-shadow: inset 2px 0 0 var(--opf-ui-accent); }
|
|
3722
|
+
.opf-hint-row[data-state="next"] .opf-hint-key,
|
|
3723
|
+
.opf-hint-row[data-state="next"] .opf-hint-state { color: var(--opf-ui-accent-strong); font-weight: 600; }
|
|
3724
|
+
.opf-hint-row[data-state="next"] .opf-hint-state::before { content: "\25B6\00a0"; }
|
|
3725
|
+
/* No animation either way; the rule is explicit so a later one cannot add motion for someone who asked for less. */
|
|
3726
|
+
@media (prefers-reduced-motion: reduce) {
|
|
3727
|
+
.cm-tooltip.opf-hint, .cm-tooltip.opf-hint * { animation: none !important; transition: none !important; }
|
|
3728
|
+
}
|
|
3729
|
+
|
|
3703
3730
|
/* Ctrl+Space with the list open hides the documentation panel (and shows it again). */
|
|
3704
3731
|
.cm-editor.opf-docs-off .cm-completionInfo { display: none; }
|
|
3705
3732
|
|
|
@@ -4007,6 +4034,208 @@ input.opf-lx-input[data-field="max"] { font-family: var(--opf-ui-mono); font-siz
|
|
|
4007
4034
|
.opf-present-button { transition: none; }
|
|
4008
4035
|
}
|
|
4009
4036
|
|
|
4037
|
+
/* shortcuts.css */
|
|
4038
|
+
/* The keyboard shortcuts dialog (src/source-shortcuts.js). Only --opf-ui-* tokens, no color literals (test/styles.mjs). */
|
|
4039
|
+
.opf-shortcuts,
|
|
4040
|
+
.opf-shortcuts *,
|
|
4041
|
+
.opf-shortcuts *::before,
|
|
4042
|
+
.opf-shortcuts *::after { box-sizing: border-box; }
|
|
4043
|
+
.opf-shortcuts [hidden] { display: none !important; }
|
|
4044
|
+
.opf-shortcuts {
|
|
4045
|
+
position: fixed;
|
|
4046
|
+
inset: 0;
|
|
4047
|
+
z-index: 70;
|
|
4048
|
+
display: grid;
|
|
4049
|
+
grid-template-columns: minmax(0, 640px);
|
|
4050
|
+
justify-content: center;
|
|
4051
|
+
align-content: center;
|
|
4052
|
+
padding: 16px;
|
|
4053
|
+
background: color-mix(in srgb, var(--opf-ui-fg) 32%, transparent);
|
|
4054
|
+
font-family: var(--opf-ui-font);
|
|
4055
|
+
font-size: 13px;
|
|
4056
|
+
line-height: 1.5;
|
|
4057
|
+
color: var(--opf-ui-fg);
|
|
4058
|
+
}
|
|
4059
|
+
.opf-shortcuts .opf-shortcuts-dialog {
|
|
4060
|
+
display: flex;
|
|
4061
|
+
flex-direction: column;
|
|
4062
|
+
gap: 8px;
|
|
4063
|
+
width: 100%;
|
|
4064
|
+
max-height: min(720px, calc(100vh - 32px));
|
|
4065
|
+
padding: 16px 18px 12px;
|
|
4066
|
+
overflow: hidden;
|
|
4067
|
+
background: var(--opf-ui-surface);
|
|
4068
|
+
border: 1px solid var(--opf-ui-border);
|
|
4069
|
+
border-radius: var(--opf-ui-radius-lg);
|
|
4070
|
+
box-shadow: var(--opf-ui-shadow-pop);
|
|
4071
|
+
}
|
|
4072
|
+
.opf-shortcuts .opf-shortcuts-dialog:focus { outline: none; }
|
|
4073
|
+
.opf-shortcuts .opf-shortcuts-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
|
4074
|
+
.opf-shortcuts .opf-shortcuts-title { margin: 0; font-size: 16px; font-weight: 600; letter-spacing: -0.01em; }
|
|
4075
|
+
.opf-shortcuts .opf-shortcuts-help { margin: 0; color: var(--opf-ui-muted-fg); }
|
|
4076
|
+
.opf-shortcuts .opf-shortcuts-close {
|
|
4077
|
+
display: grid;
|
|
4078
|
+
place-items: center;
|
|
4079
|
+
width: 30px;
|
|
4080
|
+
height: 30px;
|
|
4081
|
+
border: 0;
|
|
4082
|
+
border-radius: var(--opf-ui-radius);
|
|
4083
|
+
background: transparent;
|
|
4084
|
+
color: var(--opf-ui-muted-fg);
|
|
4085
|
+
font: inherit;
|
|
4086
|
+
font-size: 20px;
|
|
4087
|
+
line-height: 1;
|
|
4088
|
+
cursor: pointer;
|
|
4089
|
+
}
|
|
4090
|
+
.opf-shortcuts .opf-shortcuts-close:hover { background: var(--opf-ui-muted); color: var(--opf-ui-fg); }
|
|
4091
|
+
.opf-shortcuts .opf-shortcuts-search {
|
|
4092
|
+
width: 100%;
|
|
4093
|
+
min-height: 34px;
|
|
4094
|
+
padding: 4px 10px;
|
|
4095
|
+
border: 1px solid var(--opf-ui-border);
|
|
4096
|
+
border-radius: var(--opf-ui-radius-md);
|
|
4097
|
+
background: var(--opf-ui-bg);
|
|
4098
|
+
color: inherit;
|
|
4099
|
+
font: inherit;
|
|
4100
|
+
}
|
|
4101
|
+
.opf-shortcuts button:focus-visible,
|
|
4102
|
+
.opf-shortcuts input:focus-visible,
|
|
4103
|
+
.opf-shortcuts .opf-shortcuts-list:focus-visible { outline: 2px solid var(--opf-ui-ring); outline-offset: 2px; }
|
|
4104
|
+
.opf-shortcuts .opf-shortcuts-status { min-height: 18px; color: var(--opf-ui-muted-fg); font-size: 12px; }
|
|
4105
|
+
.opf-shortcuts .opf-shortcuts-list { flex: 1; min-height: 120px; overflow: auto; padding-right: 4px; }
|
|
4106
|
+
.opf-shortcuts .opf-shortcuts-group { margin: 0 0 12px; }
|
|
4107
|
+
.opf-shortcuts .opf-shortcuts-group-title { margin: 8px 0 4px; font-size: 11px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--opf-ui-muted-fg); }
|
|
4108
|
+
.opf-shortcuts .opf-shortcuts-row { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; padding: 4px 0; border-bottom: 1px solid var(--opf-ui-border); }
|
|
4109
|
+
.opf-shortcuts .opf-shortcuts-name { min-width: 0; }
|
|
4110
|
+
.opf-shortcuts .opf-shortcuts-when { color: var(--opf-ui-muted-fg); font-size: 12px; }
|
|
4111
|
+
.opf-shortcuts .opf-shortcuts-keys { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 4px; flex: none; max-width: 55%; }
|
|
4112
|
+
.opf-shortcuts .opf-shortcuts-or { color: var(--opf-ui-muted-fg); font-size: 11px; padding: 0 2px; align-self: center; }
|
|
4113
|
+
.opf-shortcuts .opf-shortcuts-key {
|
|
4114
|
+
display: inline-block;
|
|
4115
|
+
min-width: 22px;
|
|
4116
|
+
padding: 1px 6px;
|
|
4117
|
+
border: 1px solid var(--opf-ui-border-strong);
|
|
4118
|
+
border-bottom-width: 2px;
|
|
4119
|
+
border-radius: var(--opf-ui-radius-sm);
|
|
4120
|
+
background: var(--opf-ui-muted);
|
|
4121
|
+
font: 12px var(--opf-ui-mono);
|
|
4122
|
+
text-align: center;
|
|
4123
|
+
white-space: nowrap;
|
|
4124
|
+
}
|
|
4125
|
+
.opf-shortcuts .opf-shortcuts-none { color: var(--opf-ui-muted-fg); }
|
|
4126
|
+
@media (forced-colors: active) {
|
|
4127
|
+
.opf-shortcuts .opf-shortcuts-dialog,
|
|
4128
|
+
.opf-shortcuts .opf-shortcuts-key { border-color: CanvasText; }
|
|
4129
|
+
}
|
|
4130
|
+
|
|
4131
|
+
/* The symbol picker (src/source-outline.js) */
|
|
4132
|
+
.opf-quickpick {
|
|
4133
|
+
position: absolute;
|
|
4134
|
+
top: 6px;
|
|
4135
|
+
left: 50%;
|
|
4136
|
+
z-index: 40;
|
|
4137
|
+
width: min(480px, calc(100% - 24px));
|
|
4138
|
+
transform: translateX(-50%);
|
|
4139
|
+
display: flex;
|
|
4140
|
+
flex-direction: column;
|
|
4141
|
+
gap: 2px;
|
|
4142
|
+
padding: 6px;
|
|
4143
|
+
background: var(--opf-ui-surface);
|
|
4144
|
+
color: var(--opf-ui-fg);
|
|
4145
|
+
border: 1px solid var(--opf-ui-border);
|
|
4146
|
+
border-radius: var(--opf-ui-radius-lg);
|
|
4147
|
+
box-shadow: var(--opf-ui-shadow-pop);
|
|
4148
|
+
font: 13px var(--opf-ui-font);
|
|
4149
|
+
}
|
|
4150
|
+
.opf-quickpick-input { width: 100%; min-height: 30px; padding: 3px 8px; border: 1px solid var(--opf-ui-border); border-radius: var(--opf-ui-radius-md); background: var(--opf-ui-bg); color: inherit; font: inherit; }
|
|
4151
|
+
.opf-quickpick-input:focus-visible { outline: 2px solid var(--opf-ui-ring); outline-offset: 1px; }
|
|
4152
|
+
.opf-quickpick-list { margin: 0; padding: 2px 0 0; list-style: none; max-height: 280px; overflow: auto; }
|
|
4153
|
+
.opf-quickpick-item { display: flex; align-items: baseline; gap: 8px; min-height: 26px; padding: 3px 10px; border-radius: var(--opf-ui-radius-sm); cursor: pointer; }
|
|
4154
|
+
.opf-quickpick-item.is-selected { background: var(--opf-ui-accent); color: var(--opf-ui-accent-fg); }
|
|
4155
|
+
.opf-quickpick-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
4156
|
+
.opf-quickpick-detail { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; opacity: 0.75; }
|
|
4157
|
+
.opf-quickpick-kind { margin-left: auto; font-size: 11px; opacity: 0.75; flex: none; }
|
|
4158
|
+
.opf-quickpick-more { padding: 4px 10px; color: var(--opf-ui-muted-fg); font-size: 12px; }
|
|
4159
|
+
.opf-quickpick-status { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
|
|
4160
|
+
@media (forced-colors: active) { .opf-quickpick-item.is-selected { outline: 2px solid Highlight; } }
|
|
4161
|
+
|
|
4162
|
+
/* source-navigation.css */
|
|
4163
|
+
/*
|
|
4164
|
+
* Go to definition, peek, references and rename (source-navigation.js): one block widget under the line of the symbol. Tokens only. The declaration is
|
|
4165
|
+
* marked by a word, an arrow and a heavier line as well as by color, and nothing animates.
|
|
4166
|
+
*/
|
|
4167
|
+
.opf-sym {
|
|
4168
|
+
box-sizing: border-box;
|
|
4169
|
+
margin: 4px 0 6px 12px;
|
|
4170
|
+
padding: 6px 10px 8px;
|
|
4171
|
+
border: 1px solid var(--opf-ui-border-strong);
|
|
4172
|
+
border-left: 3px solid var(--opf-ui-accent);
|
|
4173
|
+
border-radius: var(--opf-ui-radius);
|
|
4174
|
+
background: var(--opf-ui-surface);
|
|
4175
|
+
color: var(--opf-ui-fg);
|
|
4176
|
+
box-shadow: var(--opf-ui-shadow-pop);
|
|
4177
|
+
font: 12px/1.45 var(--opf-ui-font);
|
|
4178
|
+
text-align: start;
|
|
4179
|
+
white-space: normal;
|
|
4180
|
+
cursor: default;
|
|
4181
|
+
}
|
|
4182
|
+
.opf-sym:focus-visible,
|
|
4183
|
+
.opf-sym-list:focus-visible { outline: 2px solid var(--opf-ui-ring); outline-offset: 1px; }
|
|
4184
|
+
.opf-sym-head { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
|
|
4185
|
+
.opf-sym-title { flex: 1 1 auto; min-width: 0; font-weight: 600; overflow-wrap: anywhere; }
|
|
4186
|
+
.opf-sym-where { color: var(--opf-ui-muted-fg); }
|
|
4187
|
+
.opf-sym-close {
|
|
4188
|
+
flex: none;
|
|
4189
|
+
min-height: 24px;
|
|
4190
|
+
padding: 0 8px;
|
|
4191
|
+
border: 1px solid var(--opf-ui-border);
|
|
4192
|
+
border-radius: var(--opf-ui-radius-sm);
|
|
4193
|
+
background: var(--opf-ui-muted-2);
|
|
4194
|
+
color: var(--opf-ui-fg);
|
|
4195
|
+
font: inherit;
|
|
4196
|
+
cursor: pointer;
|
|
4197
|
+
}
|
|
4198
|
+
.opf-sym-close:hover { background: var(--opf-ui-muted); }
|
|
4199
|
+
.opf-sym-close:focus-visible,
|
|
4200
|
+
.opf-sym-input:focus-visible { outline: 2px solid var(--opf-ui-ring); outline-offset: 1px; }
|
|
4201
|
+
.opf-sym-lines, .opf-sym-list { margin: 6px 0 0; padding: 0; list-style: none; max-height: 260px; overflow: auto; }
|
|
4202
|
+
.opf-sym-line, .opf-sym-ref { display: flex; align-items: baseline; gap: 8px; min-width: 0; padding: 1px 4px; border-radius: var(--opf-ui-radius-sm); }
|
|
4203
|
+
.opf-sym-no { flex: none; min-width: 3ch; color: var(--opf-ui-muted-fg); font-family: var(--opf-ui-mono); text-align: end; }
|
|
4204
|
+
.opf-sym-mark { flex: none; width: 1ch; color: var(--opf-ui-accent-strong); }
|
|
4205
|
+
.opf-sym-text { flex: 1 1 auto; min-width: 0; font-family: var(--opf-ui-mono); white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
4206
|
+
.opf-sym-line.is-target { background: var(--opf-ui-muted-2); font-weight: 600; }
|
|
4207
|
+
.opf-sym-ref { cursor: pointer; }
|
|
4208
|
+
.opf-sym-ref:hover { background: var(--opf-ui-muted-2); }
|
|
4209
|
+
.opf-sym-ref[aria-selected="true"] { background: var(--opf-ui-accent); color: var(--opf-ui-accent-fg); }
|
|
4210
|
+
.opf-sym-ref[aria-selected="true"] .opf-sym-no,
|
|
4211
|
+
.opf-sym-ref[aria-selected="true"] .opf-sym-badge { color: inherit; }
|
|
4212
|
+
.opf-sym-badge { flex: none; min-width: 6.5em; color: var(--opf-ui-muted-fg); font-size: 11px; }
|
|
4213
|
+
.opf-sym-ref.is-declaration .opf-sym-badge { font-weight: 600; }
|
|
4214
|
+
.opf-sym-hint { margin: 6px 0 0; color: var(--opf-ui-muted-fg); font-size: 11px; }
|
|
4215
|
+
.opf-sym-field { display: flex; align-items: center; gap: 8px; margin-top: 6px; }
|
|
4216
|
+
.opf-sym-label { flex: none; color: var(--opf-ui-muted-fg); }
|
|
4217
|
+
.opf-sym-input {
|
|
4218
|
+
flex: 1 1 auto;
|
|
4219
|
+
min-width: 0;
|
|
4220
|
+
min-height: var(--opf-ui-control-h);
|
|
4221
|
+
padding: 0 8px;
|
|
4222
|
+
border: 1px solid var(--opf-ui-border-strong);
|
|
4223
|
+
border-radius: var(--opf-ui-radius-sm);
|
|
4224
|
+
background: var(--opf-ui-bg);
|
|
4225
|
+
color: var(--opf-ui-fg);
|
|
4226
|
+
font: 13px var(--opf-ui-mono);
|
|
4227
|
+
}
|
|
4228
|
+
.opf-sym-input[aria-invalid="true"] { border-color: var(--opf-ui-danger); }
|
|
4229
|
+
.opf-sym-message { min-height: 1.45em; margin-top: 4px; }
|
|
4230
|
+
.opf-sym-message.is-error { color: var(--opf-ui-danger); font-weight: 600; }
|
|
4231
|
+
.opf-sym-message.is-error::before { content: "Error: "; }
|
|
4232
|
+
.opf-sym-preview { margin-top: 6px; }
|
|
4233
|
+
.opf-sym-image { display: block; max-width: 100%; max-height: 160px; border: 1px solid var(--opf-ui-border); border-radius: var(--opf-ui-radius-sm); }
|
|
4234
|
+
@media (forced-colors: active) {
|
|
4235
|
+
.opf-sym { border: 1px solid CanvasText; }
|
|
4236
|
+
.opf-sym-ref[aria-selected="true"] { outline: 2px solid Highlight; }
|
|
4237
|
+
}
|
|
4238
|
+
|
|
4010
4239
|
/* source-pane.css */
|
|
4011
4240
|
/* Source pane (RR-98). Only --opf-ui-* tokens (src/styles/tokens.css). */
|
|
4012
4241
|
.opf-source {
|
|
@@ -4268,6 +4497,25 @@ input.opf-lx-input[data-field="max"] { font-family: var(--opf-ui-mono); font-siz
|
|
|
4268
4497
|
cursor: pointer;
|
|
4269
4498
|
}
|
|
4270
4499
|
.opf-source-finding-fix:hover { background: var(--opf-ui-muted); }
|
|
4500
|
+
.opf-source-finding-details {
|
|
4501
|
+
min-height: 22px;
|
|
4502
|
+
padding: 0 8px;
|
|
4503
|
+
border: 1px solid transparent;
|
|
4504
|
+
border-radius: var(--opf-ui-radius-sm);
|
|
4505
|
+
background: transparent;
|
|
4506
|
+
color: var(--opf-ui-muted-fg);
|
|
4507
|
+
font-size: 12px;
|
|
4508
|
+
text-decoration: underline;
|
|
4509
|
+
cursor: pointer;
|
|
4510
|
+
}
|
|
4511
|
+
.opf-source-finding-details:hover { background: var(--opf-ui-muted); }
|
|
4512
|
+
.opf-source-finding-details[aria-expanded="true"] { text-decoration: none; background: var(--opf-ui-muted); }
|
|
4513
|
+
.opf-source-finding-explain { display: flex; flex-direction: column; gap: 4px; margin: 0 6px 6px 28px; padding: 6px 8px; border-left: 2px solid var(--opf-ui-border); color: var(--opf-ui-fg); font-size: 12px; }
|
|
4514
|
+
.opf-source-finding-explain[hidden] { display: none; }
|
|
4515
|
+
.opf-source-finding-explain-line { margin: 0; overflow-wrap: anywhere; }
|
|
4516
|
+
.opf-source-finding-explain-example { font: 11px var(--opf-ui-mono); overflow-wrap: anywhere; white-space: pre-wrap; }
|
|
4517
|
+
.opf-source-finding-explain-example code { padding: 1px 4px; border-radius: var(--opf-ui-radius-sm); background: var(--opf-ui-muted); }
|
|
4518
|
+
.opf-source-finding-explain-link { color: var(--opf-ui-muted-fg); }
|
|
4271
4519
|
.opf-source-finding-more { padding: 3px 6px; color: var(--opf-ui-muted-fg); font-size: 12px; }
|
|
4272
4520
|
|
|
4273
4521
|
.opf-source-status { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
|
|
@@ -4300,6 +4548,46 @@ input.opf-lx-input[data-field="max"] { font-family: var(--opf-ui-mono); font-siz
|
|
|
4300
4548
|
.opf-source-confirm button:hover { background: var(--opf-ui-muted); }
|
|
4301
4549
|
.opf-source-confirm-accept { font-weight: 600; }
|
|
4302
4550
|
|
|
4551
|
+
/* Fold gutter (src/source-folding.js) */
|
|
4552
|
+
.opf-source-code .cm-foldGutter { width: 16px; }
|
|
4553
|
+
.opf-source-code .cm-foldGutter .cm-gutterElement { display: flex; align-items: center; justify-content: center; padding: 0; cursor: pointer; }
|
|
4554
|
+
.opf-source-code .cm-foldGutter .opf-fold-marker { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 100%; color: var(--opf-ui-muted-fg); opacity: 0; transition: opacity var(--opf-ui-ease); }
|
|
4555
|
+
.opf-source-code .cm-foldGutter .opf-fold-marker.is-folded,
|
|
4556
|
+
.opf-source-code:hover .cm-foldGutter .opf-fold-marker,
|
|
4557
|
+
.opf-source-code .cm-foldGutter .opf-fold-marker:hover { opacity: 1; }
|
|
4558
|
+
.opf-source-code .cm-foldGutter .opf-fold-marker:hover { color: var(--opf-ui-fg); }
|
|
4559
|
+
.opf-source-code .cm-foldPlaceholder { border: 0; background: var(--opf-ui-muted); color: var(--opf-ui-muted-fg); border-radius: var(--opf-ui-radius-sm); padding: 0 5px; margin: 0 2px; cursor: pointer; }
|
|
4560
|
+
@media (prefers-reduced-motion: reduce) { .opf-source-code .cm-foldGutter .opf-fold-marker { transition: none; } }
|
|
4561
|
+
@media (forced-colors: active) { .opf-source-code .cm-foldGutter .opf-fold-marker { opacity: 1; } }
|
|
4562
|
+
|
|
4563
|
+
/* Outline panel (opt-in `outline` option) */
|
|
4564
|
+
.opf-source-body { display: flex; flex: 1; min-height: 0; min-width: 0; }
|
|
4565
|
+
.opf-source-body > .opf-source-code { flex: 1; min-width: 0; }
|
|
4566
|
+
.opf-source-outline { flex: none; width: 220px; max-width: 40%; overflow: auto; border-right: 1px solid var(--opf-ui-code-border); padding: 4px 0; }
|
|
4567
|
+
.opf-source-outline-list { margin: 0; padding: 0; list-style: none; }
|
|
4568
|
+
.opf-source-outline-item { display: flex; align-items: baseline; gap: 6px; width: 100%; min-height: 26px; padding: 2px 8px; border: 0; background: transparent; color: var(--opf-ui-fg); text-align: left; font: inherit; cursor: pointer; }
|
|
4569
|
+
.opf-source-outline-item:hover { background: var(--opf-ui-muted); }
|
|
4570
|
+
.opf-source-outline-item:focus-visible { outline: 2px solid var(--opf-ui-ring); outline-offset: -2px; }
|
|
4571
|
+
.opf-source-outline-item[aria-current="true"] { background: var(--opf-ui-muted); font-weight: 600; }
|
|
4572
|
+
.opf-source-outline-item.is-section { color: var(--opf-ui-muted-fg); font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em; }
|
|
4573
|
+
.opf-source-outline-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
4574
|
+
.opf-source-outline-detail { margin-left: auto; flex: none; max-width: 45%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; color: var(--opf-ui-muted-fg); }
|
|
4575
|
+
.opf-source-outline-empty { padding: 8px 10px; color: var(--opf-ui-muted-fg); }
|
|
4576
|
+
@media (forced-colors: active) { .opf-source-outline-item[aria-current="true"] { outline: 1px solid Highlight; } }
|
|
4577
|
+
|
|
4578
|
+
/* Forced colors (Windows high contrast): the selection of a list is an outline and a problem keeps its underline and a different shape per severity. */
|
|
4579
|
+
@media (forced-colors: active) {
|
|
4580
|
+
.opf-source-code .cm-tooltip-autocomplete > ul > li[aria-selected],
|
|
4581
|
+
.cm-tooltip .opf-fix[aria-selected="true"] { outline: 2px solid Highlight; outline-offset: -2px; forced-color-adjust: none; background: Canvas; color: CanvasText; }
|
|
4582
|
+
.opf-source-code .cm-lintRange-error { text-decoration: underline wavy CanvasText; }
|
|
4583
|
+
.opf-source-code .cm-lintRange-warning { text-decoration: underline dashed CanvasText; }
|
|
4584
|
+
.opf-source-code .cm-lintRange-info { text-decoration: underline dotted CanvasText; }
|
|
4585
|
+
.opf-source-finding { border: 1px solid CanvasText; }
|
|
4586
|
+
}
|
|
4587
|
+
@media (prefers-reduced-motion: reduce) {
|
|
4588
|
+
.cm-tooltip, .cm-tooltip * { animation: none !important; transition: none !important; }
|
|
4589
|
+
}
|
|
4590
|
+
|
|
4303
4591
|
/* thumbnails.css */
|
|
4304
4592
|
/* Thumbnail strip (RR-98). Uses only --opf-ui-* tokens. */
|
|
4305
4593
|
.opf-thumbs {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openpresentation/opf-editor",
|
|
3
|
-
"version": "0.26.
|
|
3
|
+
"version": "0.26.6",
|
|
4
4
|
"description": "Embeddable local OPF editor primitives built on the deterministic renderer.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -231,6 +231,10 @@
|
|
|
231
231
|
"types": "./dist/annotations.d.ts",
|
|
232
232
|
"default": "./dist/annotations.js"
|
|
233
233
|
},
|
|
234
|
+
"./source-worker": {
|
|
235
|
+
"types": "./dist/source-worker.d.ts",
|
|
236
|
+
"default": "./dist/source-worker.js"
|
|
237
|
+
},
|
|
234
238
|
"./source-pane": {
|
|
235
239
|
"types": "./dist/source-pane.d.ts",
|
|
236
240
|
"default": "./dist/source-pane.js"
|
|
@@ -283,10 +287,10 @@
|
|
|
283
287
|
"@lezer/highlight": "1.2.5",
|
|
284
288
|
"jsonc-parser": "3.3.1",
|
|
285
289
|
"yaml": "^2.9.1",
|
|
286
|
-
"@openpresentation/opf": "^0.26.
|
|
290
|
+
"@openpresentation/opf": "^0.26.6"
|
|
287
291
|
},
|
|
288
292
|
"peerDependencies": {
|
|
289
|
-
"@openpresentation/opf-render": "^0.26.
|
|
293
|
+
"@openpresentation/opf-render": "^0.26.6"
|
|
290
294
|
},
|
|
291
295
|
"peerDependenciesMeta": {
|
|
292
296
|
"@openpresentation/opf-render": {
|
|
@@ -345,9 +349,9 @@
|
|
|
345
349
|
"pdf-lib": "1.17.1",
|
|
346
350
|
"pdfjs-dist": "6.4.299",
|
|
347
351
|
"playwright": "1.64.0",
|
|
348
|
-
"@openpresentation/
|
|
349
|
-
"@openpresentation/opf-
|
|
350
|
-
"@openpresentation/
|
|
352
|
+
"@openpresentation/opf-pptx": "0.26.6",
|
|
353
|
+
"@openpresentation/opf-render": "0.26.6",
|
|
354
|
+
"@openpresentation/gallery": "0.26.6"
|
|
351
355
|
},
|
|
352
356
|
"scripts": {
|
|
353
357
|
"build": "node scripts/build.mjs",
|
|
@@ -388,6 +392,7 @@
|
|
|
388
392
|
"test:source-keyboard-json-browser": "node test/source-keyboard-json-browser.mjs",
|
|
389
393
|
"test:source-keyboard-yaml-browser": "node test/source-keyboard-yaml-browser.mjs",
|
|
390
394
|
"test:source-keyboard-markdown-browser": "node test/source-keyboard-markdown-browser.mjs",
|
|
395
|
+
"test:source-diagnostics-browser": "node test/source-diagnostics-browser.mjs",
|
|
391
396
|
"test:layout-editor-browser": "node test/layout-editor-browser.mjs",
|
|
392
397
|
"test:export-license-browser": "node test/export-license-browser.mjs",
|
|
393
398
|
"test:chart-options-browser": "node test/chart-options-browser.mjs",
|
|
@@ -397,5 +402,5 @@
|
|
|
397
402
|
"test:export-menu-browser": "node test/export-menu-browser.mjs",
|
|
398
403
|
"test:agent-handoff-browser": "node test/agent-handoff-browser.mjs"
|
|
399
404
|
},
|
|
400
|
-
"gitHead": "
|
|
405
|
+
"gitHead": "66937576155a3c10ec86ee3327921b51f0c141ae"
|
|
401
406
|
}
|