@helping-ai-workflow/md2doc 3.9.0 → 3.10.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/lib/editor/client.js +260 -262
- package/lib/editor/icons.js +48 -0
- package/lib/editor/server.js +14 -4
- package/lib/editor/toolbar-model.js +27 -80
- package/lib/highlight.js +35 -0
- package/lib/md2doc.js +267 -154
- package/lib/theme/runtime.js +80 -2
- package/lib/theme/tokens.js +74 -5
- package/package.json +4 -3
- package/lib/editor/docsource.js +0 -115
package/lib/md2doc.js
CHANGED
|
@@ -22,6 +22,7 @@ const fs = require('fs');
|
|
|
22
22
|
const path = require('path');
|
|
23
23
|
const drawio = require('./drawio.js');
|
|
24
24
|
const { applyReaderTheme } = require('./theme/tokens');
|
|
25
|
+
const { highlightCode } = require('./highlight');
|
|
25
26
|
|
|
26
27
|
// How long to let client-side WaveDrom / Mermaid scripts render before we
|
|
27
28
|
// snapshot the DOM (PDF print, or --bake-svg inert-SVG bake).
|
|
@@ -1033,9 +1034,16 @@ ${itemsHtml}
|
|
|
1033
1034
|
const b64 = Buffer.from(code, 'utf8').toString('base64');
|
|
1034
1035
|
return '\n' + GRAPHVIZ_PLACEHOLDER.html([b64]) + '\n';
|
|
1035
1036
|
}
|
|
1036
|
-
// Default:
|
|
1037
|
+
// Default: fenced code. v3.10.0 colours registered languages here, at
|
|
1038
|
+
// render time (lib/highlight.js); an unknown or missing language stays
|
|
1039
|
+
// plain escaped text.
|
|
1040
|
+
const coloured = highlightCode(code, lang);
|
|
1041
|
+
// The info string goes into an attribute: escape it (an info string such
|
|
1042
|
+
// as x" onmouseover=… must stay text). Ordinary language names are unchanged.
|
|
1043
|
+
const langAttr = escapeHtml(lang);
|
|
1044
|
+
if (coloured !== null) return `<pre><code class="language-${langAttr} hljs">${coloured}</code></pre>\n`;
|
|
1037
1045
|
const escaped = code.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
|
1038
|
-
return `<pre><code class="language-${
|
|
1046
|
+
return `<pre><code class="language-${langAttr}">${escaped}</code></pre>\n`;
|
|
1039
1047
|
};
|
|
1040
1048
|
|
|
1041
1049
|
renderer.heading = function(token) {
|
|
@@ -1070,7 +1078,18 @@ ${itemsHtml}
|
|
|
1070
1078
|
const baseListitem = renderer.listitem.bind(renderer);
|
|
1071
1079
|
renderer.listitem = function(token) {
|
|
1072
1080
|
appendSectionText(flattenTokenText(token.tokens));
|
|
1073
|
-
|
|
1081
|
+
const html = baseListitem(token);
|
|
1082
|
+
if (!token.task) return html;
|
|
1083
|
+
// v3.10.0: a task item drops its bullet and keeps its own text in a span,
|
|
1084
|
+
// so a checked item's strike-through stops at its own words — CSS
|
|
1085
|
+
// text-decoration cannot be undone on a nested child list. Shapes: tight
|
|
1086
|
+
// (text then an optional block child — a nested list, blockquote, code,
|
|
1087
|
+
// table…), loose (inside a <p>), leaf. The span ends at whichever comes
|
|
1088
|
+
// first: a block child's opening tag, the loose item's </p>, or </li>.
|
|
1089
|
+
const cls = token.checked ? 'task-item is-checked' : 'task-item';
|
|
1090
|
+
const m = html.match(/^<li>([\s\S]*?<input[^>]*>)([\s\S]*?)(<(?:ul|ol|blockquote|pre|table|div|p|h[1-6])[\s>][\s\S]*|<\/p>[\s\S]*|<\/li>\s*)$/);
|
|
1091
|
+
if (!m) return html.replace(/^<li>/, '<li class="' + cls + '">');
|
|
1092
|
+
return '<li class="' + cls + '">' + m[1] + ' <span class="task-text">' + m[2].trim() + '</span>' + m[3];
|
|
1074
1093
|
};
|
|
1075
1094
|
|
|
1076
1095
|
const baseBlockquote = renderer.blockquote.bind(renderer);
|
|
@@ -1305,7 +1324,10 @@ ${itemsHtml}
|
|
|
1305
1324
|
var __md2docWavedromSeq = 0;
|
|
1306
1325
|
window.__md2docInitDiagrams = function (rootEl) {
|
|
1307
1326
|
rootEl = rootEl || document;
|
|
1308
|
-
|
|
1327
|
+
// v3.10.0: with the theme runtime on the page (lib/theme/runtime.js), new
|
|
1328
|
+
// mermaid hosts are drawn through it, in the theme on screen, so a block
|
|
1329
|
+
// rebuilt after an edit does not come back light (or recoloured) in dark.
|
|
1330
|
+
if (typeof mermaid !== 'undefined' && !(window.__md2docThemeMermaid && window.__md2docThemeMermaid())) {
|
|
1309
1331
|
mermaid.initialize({ startOnLoad: false, ${MERMAID_CONFIG_JS} });
|
|
1310
1332
|
var mermaidNodes = rootEl.querySelectorAll ? rootEl.querySelectorAll('.mermaid') : [];
|
|
1311
1333
|
var pending = [];
|
|
@@ -1434,6 +1456,17 @@ ${itemsHtml}
|
|
|
1434
1456
|
'\n .ed-block[data-list-type="ol"][data-indent="' + d + '"] > .ed-li-marker::before' +
|
|
1435
1457
|
' { content: counter(ed-ol-' + d + ') "."; }';
|
|
1436
1458
|
}
|
|
1459
|
+
// v3.10.0: bullets change by depth like the reader's disc / circle / square,
|
|
1460
|
+
// cycling every three levels. Task items keep their own content:none rule
|
|
1461
|
+
// (same specificity, later in the sheet, so it still wins).
|
|
1462
|
+
const ED_UL_GLYPHS = ['\\2022', '\\25E6', '\\25AA'];
|
|
1463
|
+
// Depth-agnostic fallback first (deeper than ED_OL_MAX_DEPTH keeps a •
|
|
1464
|
+
// rather than going blank); the per-depth rules outrank it.
|
|
1465
|
+
let edUlMarkerCss = '\n .ed-block[data-list-type="ul"] > .ed-li-marker::before { content: "' + ED_UL_GLYPHS[0] + '"; }';
|
|
1466
|
+
for (let d = 0; d <= ED_OL_MAX_DEPTH; d++) {
|
|
1467
|
+
edUlMarkerCss += '\n .ed-block[data-list-type="ul"][data-indent="' + d + '"] > .ed-li-marker::before' +
|
|
1468
|
+
' { content: "' + ED_UL_GLYPHS[d % 3] + '"; }';
|
|
1469
|
+
}
|
|
1437
1470
|
|
|
1438
1471
|
const editModeLayoutCss = `
|
|
1439
1472
|
/* 56px, not 48px — spec §4.2's hit-test conflict 1 (gutter vs
|
|
@@ -1463,8 +1496,16 @@ ${itemsHtml}
|
|
|
1463
1496
|
comment above .ed-handle below. That still leaves 6px of clearance from
|
|
1464
1497
|
splitterRight (contentLeft-56), so the conflict-1 guard still holds.
|
|
1465
1498
|
Guarded by the elementFromPoint(splitter.right - 2, y) assertion §4.2
|
|
1466
|
-
asks for, in test/editor-client-runtime.test.js.
|
|
1467
|
-
|
|
1499
|
+
asks for, in test/editor-client-runtime.test.js.
|
|
1500
|
+
|
|
1501
|
+
v3.10.0: 62px. The B look makes the buttons 24px; with --ed-gutter-gap
|
|
1502
|
+
2px and the 10px shift the pair spans [contentLeft-60, contentLeft-12],
|
|
1503
|
+
and at 56px of padding the + hung back over the splitter again (the
|
|
1504
|
+
spec's gap 0 already did: measured 1400x900, splitter [324, 356], +
|
|
1505
|
+
[354, 378], elementFromPoint(355, +'s y) === .ed-insert). 62px restores
|
|
1506
|
+
2px of clearance: re-measured + [358, 382], ⠿ [382, 406], row grip
|
|
1507
|
+
[408, 428], contentLeft 418. */
|
|
1508
|
+
.content { padding-left: 62px; }
|
|
1468
1509
|
|
|
1469
1510
|
/* S1: every block is one full-width row, whatever its type or depth. The
|
|
1470
1511
|
indent is a property of the item's own MARKER, never of its box, so all
|
|
@@ -1510,7 +1551,7 @@ ${itemsHtml}
|
|
|
1510
1551
|
margin-left: calc(var(--ed-indent, 0) * 1.6em);
|
|
1511
1552
|
width: 1.2em;
|
|
1512
1553
|
display: flex; justify-content: flex-end;
|
|
1513
|
-
color:
|
|
1554
|
+
color: var(--md-muted); -webkit-user-select: none; user-select: none;
|
|
1514
1555
|
}
|
|
1515
1556
|
/* min-width: 0 so an unbreakable token inside an item cannot widen the row
|
|
1516
1557
|
past its siblings — that would break the equal-width guarantee above. */
|
|
@@ -1525,7 +1566,7 @@ ${itemsHtml}
|
|
|
1525
1566
|
.ed-block[data-block-type="li"] > .ed-editing { flex: 1 1 auto; min-width: 0; }
|
|
1526
1567
|
/* column-gap already provides the marker/checkbox separation. */
|
|
1527
1568
|
.ed-block[data-block-type="li"] > .ed-li-check { flex: 0 0 auto; margin-right: 0; }
|
|
1528
|
-
|
|
1569
|
+
${edUlMarkerCss}${edOlCounterCss}
|
|
1529
1570
|
/* A BULLETED task item's checkbox IS its marker, so the • is suppressed and
|
|
1530
1571
|
the marker box collapses (the indent lives on its margin and survives).
|
|
1531
1572
|
An ORDERED task item keeps its number — that is what GFM renders — and
|
|
@@ -1554,8 +1595,8 @@ ${itemsHtml}
|
|
|
1554
1595
|
whatever --ed-gutter-shift moved the pair by. Writing any of them as a
|
|
1555
1596
|
second literal is how the corridor comes back. */
|
|
1556
1597
|
:root {
|
|
1557
|
-
--ed-gutter-btn:
|
|
1558
|
-
--ed-gutter-gap:
|
|
1598
|
+
--ed-gutter-btn: 24px;
|
|
1599
|
+
--ed-gutter-gap: 2px;
|
|
1559
1600
|
--ed-gutter-w: calc(var(--ed-gutter-btn) * 2 + var(--ed-gutter-gap));
|
|
1560
1601
|
}
|
|
1561
1602
|
/* v3.0.1: the pair moves LEFT by --ed-gutter-shift so the table row grip
|
|
@@ -1567,15 +1608,21 @@ ${itemsHtml}
|
|
|
1567
1608
|
tableLeft=412; a 10px shift lands ⠿=[380,398] +=[362,380], grip
|
|
1568
1609
|
(straddling, 20px wide, centred on tableLeft) = [402,422], leaving
|
|
1569
1610
|
exactly --ed-gutter-gap (4px) between the ⠿'s right edge (398) and the
|
|
1570
|
-
grip's left edge (402), and +'s left edge (362) stays well clear of 0.
|
|
1611
|
+
grip's left edge (402), and +'s left edge (362) stays well clear of 0.
|
|
1612
|
+
v3.10.0 re-measured (same page, 24px buttons, --ed-gutter-gap 2px, 62px
|
|
1613
|
+
content padding): ⠿=[382,406] +=[358,382] tableLeft=418, grip=[408,428].
|
|
1614
|
+
The spec asked for gap 0; that left the ⠿ abutting the grip, and on CI's
|
|
1615
|
+
fractional layout the grip took the ⠿'s last half-pixel column (§4.2
|
|
1616
|
+
conflict 2, editor-client-runtime: x 73.5 in a [50, 74] box answered
|
|
1617
|
+
ROW-GRIP). 2px is the clearance that survives sub-pixel snapping. */
|
|
1571
1618
|
:root { --ed-gutter-shift: 10px; }
|
|
1572
1619
|
.ed-handle {
|
|
1573
1620
|
left: calc(-1 * (var(--ed-gutter-btn) + var(--ed-gutter-gap) + var(--ed-gutter-shift)));
|
|
1574
|
-
top:
|
|
1621
|
+
top: var(--ed-pair-top, 0px);
|
|
1575
1622
|
width: var(--ed-gutter-btn);
|
|
1576
1623
|
}
|
|
1577
1624
|
.ed-insert {
|
|
1578
|
-
left: calc(-1 * (var(--ed-gutter-w) + var(--ed-gutter-shift))); top:
|
|
1625
|
+
left: calc(-1 * (var(--ed-gutter-w) + var(--ed-gutter-shift))); top: var(--ed-pair-top, 0px);
|
|
1579
1626
|
width: var(--ed-gutter-btn);
|
|
1580
1627
|
}
|
|
1581
1628
|
/* v2.11.1: the gutter's HOVER ZONE, and it is the reason the pair could move
|
|
@@ -1705,22 +1752,6 @@ ${itemsHtml}
|
|
|
1705
1752
|
/* The reader bar is gone in edit mode, so its 52px anchor offset goes too
|
|
1706
1753
|
(it moved scrollIntoView landings under the edit toolbar). */
|
|
1707
1754
|
.content [id] { scroll-margin-top: 0; }
|
|
1708
|
-
/* T11-2 step 5: enterSourceMode() (lib/editor/client.js) sets
|
|
1709
|
-
contentEl.hidden = true and mounts a plain textarea in its place, and
|
|
1710
|
-
setDocMode() writes docMode onto body as data-ed-mode. Measured on that
|
|
1711
|
-
build: with ten TOC links listed, clicking the last one moved neither
|
|
1712
|
-
the (hidden) reading position nor the textarea's own scrollTop
|
|
1713
|
-
(3924 before and after) — the click target was a normal anchor jump /
|
|
1714
|
-
scrollIntoView() reaching into a subtree rooted at .content, and
|
|
1715
|
-
.content's own display:none is what absorbed it; the same subtree
|
|
1716
|
-
relationship holds for a search result's jump target. Hiding the TOC
|
|
1717
|
-
nav and the search-results panel while data-ed-mode="source" removes
|
|
1718
|
-
the two controls whose only reachable target sits in that hidden
|
|
1719
|
-
subtree; the search input row (.reader-tools) is left as-is — driving
|
|
1720
|
-
it against the textarea's own line numbers instead is a separate
|
|
1721
|
-
feasibility question, not answered here. */
|
|
1722
|
-
body[data-ed-mode="source"] .toc,
|
|
1723
|
-
body[data-ed-mode="source"] .search-results { display: none !important; }
|
|
1724
1755
|
|
|
1725
1756
|
/* ── v3.4.0 batch3 Task 6: the wavedrom editing surface ───────────────
|
|
1726
1757
|
Both of these are mounted on document.body by lib/editor/client.js, and
|
|
@@ -1735,10 +1766,10 @@ ${itemsHtml}
|
|
|
1735
1766
|
toolbar's own band, because the editor is modal over the document. */
|
|
1736
1767
|
.ed-wave-edit-btn {
|
|
1737
1768
|
position: fixed; z-index: 12; padding: 3px 10px; font-size: 12px;
|
|
1738
|
-
border: 1px solid
|
|
1739
|
-
color:
|
|
1769
|
+
border: 1px solid var(--md-border); border-radius: 4px; background: var(--md-ed-surface);
|
|
1770
|
+
color: var(--md-strong); cursor: pointer; box-shadow: 0 1px 3px var(--md-ed-shadow);
|
|
1740
1771
|
}
|
|
1741
|
-
.ed-wave-edit-btn:hover { background:
|
|
1772
|
+
.ed-wave-edit-btn:hover { background: var(--md-ed-surface); border-color: var(--md-border); }
|
|
1742
1773
|
.ed-wave-overlay {
|
|
1743
1774
|
position: fixed; inset: 0; z-index: 998; background: rgba(0,0,0,.45);
|
|
1744
1775
|
display: flex; align-items: center; justify-content: center;
|
|
@@ -2783,6 +2814,24 @@ const html = `<!DOCTYPE html>
|
|
|
2783
2814
|
overflow-wrap: anywhere;
|
|
2784
2815
|
word-break: break-word;
|
|
2785
2816
|
}
|
|
2817
|
+
/* v3.10.0: task items. The checkbox is the marker, so no bullet; a checked
|
|
2818
|
+
item's OWN text is struck — child selectors only, because a descendant
|
|
2819
|
+
rule would strike the nested child items too (text-decoration cannot be
|
|
2820
|
+
undone further down). */
|
|
2821
|
+
.content ul > li.task-item { list-style: none; }
|
|
2822
|
+
.content li.task-item > input[type="checkbox"],
|
|
2823
|
+
.content li.task-item > p > input[type="checkbox"] {
|
|
2824
|
+
appearance: none; -webkit-appearance: none; width: 16px; height: 16px; margin: 0 6px 0 0;
|
|
2825
|
+
vertical-align: -3px; border: 1.5px solid var(--md-muted); border-radius: 4px; background: transparent; }
|
|
2826
|
+
/* In a bulleted list the checkbox takes the bullet's place; an ORDERED task
|
|
2827
|
+
item keeps its number (what GFM renders, and what the editor does). */
|
|
2828
|
+
.content ul > li.task-item > input[type="checkbox"],
|
|
2829
|
+
.content ul > li.task-item > p > input[type="checkbox"] { margin-left: -22px; }
|
|
2830
|
+
.content li.task-item.is-checked > input[type="checkbox"],
|
|
2831
|
+
.content li.task-item.is-checked > p > input[type="checkbox"] {
|
|
2832
|
+
border-color: var(--md-accent); background: var(--md-accent) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E") center / 12px no-repeat; }
|
|
2833
|
+
.content li.task-item.is-checked > .task-text,
|
|
2834
|
+
.content li.task-item.is-checked > p > .task-text { color: var(--md-muted); text-decoration: line-through; }
|
|
2786
2835
|
.content > * { max-width: 100%; }
|
|
2787
2836
|
.content iframe, .content video, .content canvas { max-width: 100%; height: auto; }
|
|
2788
2837
|
.doc-meta {
|
|
@@ -2837,6 +2886,16 @@ const html = `<!DOCTYPE html>
|
|
|
2837
2886
|
line-height: 1.45;
|
|
2838
2887
|
}
|
|
2839
2888
|
pre code { background: none; padding: 0; font-size: 0.875em; }
|
|
2889
|
+
/* v3.10.0: fenced-code syntax colour, written at render time by
|
|
2890
|
+
lib/highlight.js (no script on the page). Colours are the syn-* roles. */
|
|
2891
|
+
.hljs-keyword, .hljs-selector-tag, .hljs-literal { color: var(--md-syn-keyword); }
|
|
2892
|
+
.hljs-number, .hljs-attr, .hljs-attribute { color: var(--md-syn-number); }
|
|
2893
|
+
.hljs-comment, .hljs-quote { color: var(--md-syn-comment); font-style: italic; }
|
|
2894
|
+
.hljs-string, .hljs-regexp { color: var(--md-syn-string); }
|
|
2895
|
+
.hljs-type, .hljs-built_in, .hljs-meta { color: var(--md-syn-type); }
|
|
2896
|
+
.hljs-title, .hljs-variable, .hljs-params, .hljs-section { color: var(--md-syn-title); }
|
|
2897
|
+
.hljs-addition { color: var(--md-syn-string); }
|
|
2898
|
+
.hljs-deletion { color: var(--md-syn-keyword); }
|
|
2840
2899
|
.content :not(pre) > code {
|
|
2841
2900
|
background: none;
|
|
2842
2901
|
padding: 0;
|
|
@@ -3222,11 +3281,10 @@ ${usesDrawio ? ` /* v3.4.0 §4: a document with no drawio reference must not pa
|
|
|
3222
3281
|
above), so shipping this unconditionally is inert on normal HTML
|
|
3223
3282
|
output, same precedent as the lightbox selectors above. */
|
|
3224
3283
|
.ed-block { position: relative; cursor: pointer; }
|
|
3225
|
-
.ed-block:hover { outline: 1px dashed #b0b0b0; }
|
|
3226
3284
|
/* S3: the block-level multi-select tint (spec §3.6). rgba, never opaque —
|
|
3227
|
-
§3.6 says the text underneath stays readable.
|
|
3228
|
-
|
|
3229
|
-
.ed-te-hl).
|
|
3285
|
+
§3.6 says the text underneath stays readable. Since v3.10.0 the tint is
|
|
3286
|
+
the --md-ed-sel role, the reader accent at low alpha, shared with
|
|
3287
|
+
.ed-te-hl (focus rings and the checked box use --md-accent itself).
|
|
3230
3288
|
|
|
3231
3289
|
The descendant rules and their !important are not decoration, they are
|
|
3232
3290
|
the whole problem: <pre> (#f6f8fa) and the sticky first column (#ffffff)
|
|
@@ -3245,12 +3303,12 @@ ${usesDrawio ? ` /* v3.4.0 §4: a document with no drawio reference must not pa
|
|
|
3245
3303
|
:focus outline is suppressed because the roving tabindex below focuses a
|
|
3246
3304
|
.ed-block for real (spec §4.4 wants a real focus holder, not <body>) and
|
|
3247
3305
|
the tint — not a second ring — is the selection's affordance. */
|
|
3248
|
-
.ed-block.ed-selected { background:
|
|
3306
|
+
.ed-block.ed-selected { background: var(--md-ed-sel); }
|
|
3249
3307
|
.ed-block.ed-selected pre,
|
|
3250
3308
|
.ed-block.ed-selected th,
|
|
3251
3309
|
.ed-block.ed-selected tr:nth-child(even),
|
|
3252
3310
|
.ed-block.ed-selected tbody td:first-child,
|
|
3253
|
-
.ed-block.ed-selected thead th:first-child { background:
|
|
3311
|
+
.ed-block.ed-selected thead th:first-child { background: var(--md-ed-sel) !important; }
|
|
3254
3312
|
.ed-block.ed-selected:focus { outline: none; }
|
|
3255
3313
|
/* white-space: pre-line is LOAD-BEARING, not styling — do not relax it.
|
|
3256
3314
|
A hard-wrapped ("lazy continuation") list item's own content contains a
|
|
@@ -3270,9 +3328,10 @@ ${usesDrawio ? ` /* v3.4.0 §4: a document with no drawio reference must not pa
|
|
|
3270
3328
|
Task 5's and live in editModeLayoutCss (edit-mode only), fed by the hooks
|
|
3271
3329
|
renderEditModeList() emits here. */
|
|
3272
3330
|
.ed-block[data-block-type="li"] { cursor: text; }
|
|
3273
|
-
.ed-li-check { display: inline-block; width:
|
|
3274
|
-
border:
|
|
3275
|
-
.ed-li-check[data-checked="1"] {
|
|
3331
|
+
.ed-li-check { display: inline-block; width: 16px; height: 16px; margin-right: 6px; box-sizing: border-box;
|
|
3332
|
+
border: 1.5px solid var(--md-ed-glyph); border-radius: 4px; vertical-align: middle; cursor: pointer; }
|
|
3333
|
+
.ed-li-check[data-checked="1"] { border-color: var(--md-accent); background: var(--md-accent) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E") center / 12px no-repeat; }
|
|
3334
|
+
.ed-block[data-task="1"]:has(> .ed-li-check[data-checked="1"]) > .ed-li-text { color: var(--md-muted); text-decoration: line-through; }
|
|
3276
3335
|
/* Phase 3 Task 2: always-on paragraph/heading/list editing. Every eligible
|
|
3277
3336
|
block's content element is contenteditable from the moment it renders
|
|
3278
3337
|
(armEditables() in the client runtime) — no persistent outline (that
|
|
@@ -3283,8 +3342,8 @@ ${usesDrawio ? ` /* v3.4.0 §4: a document with no drawio reference must not pa
|
|
|
3283
3342
|
toggling. */
|
|
3284
3343
|
.ed-wys-armed { cursor: text; }
|
|
3285
3344
|
.ed-wys-armed:focus {
|
|
3286
|
-
outline: 2px solid
|
|
3287
|
-
caret-color:
|
|
3345
|
+
outline: 2px solid var(--md-accent); outline-offset: 2px;
|
|
3346
|
+
caret-color: var(--md-accent);
|
|
3288
3347
|
}
|
|
3289
3348
|
/* Task 5: table cells armed PERMANENTLY (one edit surface per CELL, not
|
|
3290
3349
|
per table root — see armEditables()'s 'table' branch in the client
|
|
@@ -3295,8 +3354,8 @@ ${usesDrawio ? ` /* v3.4.0 §4: a document with no drawio reference must not pa
|
|
|
3295
3354
|
table's horizontal edges. */
|
|
3296
3355
|
.ed-wys-cell { cursor: text; }
|
|
3297
3356
|
.ed-wys-cell:focus {
|
|
3298
|
-
outline: 2px solid
|
|
3299
|
-
caret-color:
|
|
3357
|
+
outline: 2px solid var(--md-accent); outline-offset: -2px;
|
|
3358
|
+
caret-color: var(--md-accent);
|
|
3300
3359
|
}
|
|
3301
3360
|
/* Phase 3 Task 2: the ⠿ block-actions handle — one real per-block node
|
|
3302
3361
|
(not a floating, JS-repositioned element like .ed-seltb/.ed-tb-insert),
|
|
@@ -3304,10 +3363,10 @@ ${usesDrawio ? ` /* v3.4.0 §4: a document with no drawio reference must not pa
|
|
|
3304
3363
|
visibility toggle (opacity), never display or pointer-events, so a
|
|
3305
3364
|
script-driven click still reaches it even without a real hover. */
|
|
3306
3365
|
.ed-handle {
|
|
3307
|
-
position: absolute; left: -22px; top: 0; width:
|
|
3366
|
+
position: absolute; left: -22px; top: 0; width: 24px; height: 24px;
|
|
3308
3367
|
display: flex; align-items: center; justify-content: center;
|
|
3309
3368
|
padding: 0; margin: 0; border: none; border-radius: 4px;
|
|
3310
|
-
background: transparent; color:
|
|
3369
|
+
background: transparent; color: var(--md-ed-glyph); font-size: 13px; line-height: 1;
|
|
3311
3370
|
cursor: pointer; opacity: 0; transition: opacity .12s ease, background .12s ease;
|
|
3312
3371
|
}
|
|
3313
3372
|
.ed-block:hover .ed-handle,
|
|
@@ -3323,7 +3382,7 @@ ${usesDrawio ? ` /* v3.4.0 §4: a document with no drawio reference must not pa
|
|
|
3323
3382
|
a recorded, unfixed residue. */
|
|
3324
3383
|
.ed-block.ed-keep-lit .ed-handle,
|
|
3325
3384
|
.ed-block.ed-keep-lit .ed-insert { opacity: 1; }
|
|
3326
|
-
.ed-handle:hover { background:
|
|
3385
|
+
.ed-handle:hover { background: var(--md-ed-hover); color: var(--md-strong); }
|
|
3327
3386
|
/* The ⠿ handle's menu: 轉換成 › / 建立副本 / 刪除 / MD 原始碼 (spec §3.7),
|
|
3328
3387
|
each led by a 16px currentColor icon (v2.12.0; drawn in
|
|
3329
3388
|
lib/editor/client.js's MENU_ICON_PATHS).
|
|
@@ -3341,8 +3400,8 @@ ${usesDrawio ? ` /* v3.4.0 §4: a document with no drawio reference must not pa
|
|
|
3341
3400
|
position: absolute; top: -4px; left: -4px; z-index: 6;
|
|
3342
3401
|
display: flex; flex-direction: column; align-items: stretch; gap: 2px;
|
|
3343
3402
|
padding: 4px; border-radius: 8px;
|
|
3344
|
-
background:
|
|
3345
|
-
box-shadow: 0
|
|
3403
|
+
background: var(--md-ed-surface); color: var(--md-strong);
|
|
3404
|
+
box-shadow: 0 0 0 1px var(--md-ed-ring), 0 8px 24px var(--md-ed-shadow);
|
|
3346
3405
|
}
|
|
3347
3406
|
.ed-handle-menu-btn {
|
|
3348
3407
|
/* v3.0.1: the icon sits FLUSH LEFT and the label sits one space-width
|
|
@@ -3350,8 +3409,8 @@ ${usesDrawio ? ` /* v3.4.0 §4: a document with no drawio reference must not pa
|
|
|
3350
3409
|
The right padding is untouched — it is what keeps the 轉換成 row's
|
|
3351
3410
|
chevron off the panel edge. */
|
|
3352
3411
|
min-width: 132px; height: 26px; padding: 0 10px 0 6px;
|
|
3353
|
-
border: 1px solid
|
|
3354
|
-
background:
|
|
3412
|
+
border: 1px solid transparent; border-radius: 6px;
|
|
3413
|
+
background: transparent; color: inherit;
|
|
3355
3414
|
font: inherit; font-size: 12px; line-height: 1;
|
|
3356
3415
|
text-align: left; white-space: nowrap; cursor: pointer;
|
|
3357
3416
|
/* v2.12.0: a flex ROW so the icon and the label share a baseline-free
|
|
@@ -3406,7 +3465,7 @@ ${usesDrawio ? ` /* v3.4.0 §4: a document with no drawio reference must not pa
|
|
|
3406
3465
|
tighter than the list at 18px per row. (No backticks in this comment:
|
|
3407
3466
|
it lives inside a JS template literal.) */
|
|
3408
3467
|
.ed-handle-submenu { left: 100%; margin-left: 4px; max-height: calc(100vh - 8px); overflow-y: auto; }
|
|
3409
|
-
.ed-handle-menu-btn:hover { background:
|
|
3468
|
+
.ed-handle-menu-btn:hover { background: var(--md-ed-hover); }
|
|
3410
3469
|
.ed-handle-menu-btn[hidden] { display: none; }
|
|
3411
3470
|
/* §10-gap fix: block-level INSERT — the + button sits NEXT TO the ⠿
|
|
3412
3471
|
handle in the left gutter, same node/visibility-toggle shape as
|
|
@@ -3430,32 +3489,32 @@ ${usesDrawio ? ` /* v3.4.0 §4: a document with no drawio reference must not pa
|
|
|
3430
3489
|
than deleted because it is the correct fallback if the gutter padding is
|
|
3431
3490
|
ever conditioned differently. */
|
|
3432
3491
|
.ed-insert {
|
|
3433
|
-
position: absolute; left: -22px;
|
|
3492
|
+
position: absolute; left: -22px; width: 24px; height: 24px;
|
|
3434
3493
|
display: flex; align-items: center; justify-content: center;
|
|
3435
3494
|
padding: 0; margin: 0; border: none; border-radius: 4px;
|
|
3436
|
-
background: transparent; color:
|
|
3495
|
+
background: transparent; color: var(--md-ed-glyph); font-size: 13px; line-height: 1;
|
|
3437
3496
|
cursor: pointer; opacity: 0; transition: opacity .12s ease, background .12s ease;
|
|
3438
3497
|
}
|
|
3439
3498
|
.ed-block:hover .ed-insert,
|
|
3440
3499
|
.ed-insert:focus { opacity: 1; }
|
|
3441
|
-
.ed-insert:hover { background:
|
|
3500
|
+
.ed-insert:hover { background: var(--md-ed-hover); color: var(--md-strong); }
|
|
3442
3501
|
/* The + button's small menu: 段落/標題/清單/表格/程式碼 — same dark
|
|
3443
3502
|
translucent pill language as .ed-handle-menu above. */
|
|
3444
3503
|
.ed-insert-menu {
|
|
3445
3504
|
position: absolute; top: -4px; left: -4px; z-index: 6;
|
|
3446
3505
|
display: flex; align-items: center; gap: 4px;
|
|
3447
3506
|
padding: 4px 6px; border-radius: 8px;
|
|
3448
|
-
background:
|
|
3449
|
-
box-shadow: 0
|
|
3507
|
+
background: var(--md-ed-surface); color: var(--md-strong);
|
|
3508
|
+
box-shadow: 0 0 0 1px var(--md-ed-ring), 0 8px 24px var(--md-ed-shadow);
|
|
3450
3509
|
}
|
|
3451
3510
|
.ed-insert-menu-btn {
|
|
3452
3511
|
min-width: 28px; height: 26px; padding: 0 8px;
|
|
3453
|
-
border: 1px solid
|
|
3454
|
-
background:
|
|
3512
|
+
border: 1px solid transparent; border-radius: 6px;
|
|
3513
|
+
background: transparent; color: inherit;
|
|
3455
3514
|
font: inherit; font-size: 12px; line-height: 1;
|
|
3456
3515
|
white-space: nowrap; cursor: pointer;
|
|
3457
3516
|
}
|
|
3458
|
-
.ed-insert-menu-btn:hover { background:
|
|
3517
|
+
.ed-insert-menu-btn:hover { background: var(--md-ed-hover); }
|
|
3459
3518
|
/* Task 5: hover-edge column/row insert bubbles — a SINGLETON pair of "+"
|
|
3460
3519
|
buttons (client runtime repositions them via getBoundingClientRect(),
|
|
3461
3520
|
never creates more than these two). Visual language deliberately
|
|
@@ -3468,11 +3527,11 @@ ${usesDrawio ? ` /* v3.4.0 §4: a document with no drawio reference must not pa
|
|
|
3468
3527
|
position: fixed; z-index: 8;
|
|
3469
3528
|
width: 18px; height: 18px; padding: 0; margin: 0;
|
|
3470
3529
|
display: flex; align-items: center; justify-content: center;
|
|
3471
|
-
border: 1px solid
|
|
3472
|
-
background:
|
|
3473
|
-
cursor: pointer; box-shadow: 0 1px
|
|
3530
|
+
border: 1px solid var(--md-accent); border-radius: 50%;
|
|
3531
|
+
background: var(--md-ed-surface); color: var(--md-accent); font-size: 13px; line-height: 1;
|
|
3532
|
+
cursor: pointer; box-shadow: 0 0 0 1px var(--md-ed-ring), 0 2px 6px var(--md-ed-shadow);
|
|
3474
3533
|
}
|
|
3475
|
-
.ed-tb-insert:hover { background:
|
|
3534
|
+
.ed-tb-insert:hover { background: var(--md-accent); color: var(--md-ed-surface); }
|
|
3476
3535
|
.ed-tb-insert[hidden] { display: none; }
|
|
3477
3536
|
/* Task 6: table edge-click menus (delete/align) + row drag-reorder.
|
|
3478
3537
|
'.ed-te-hl' marks the currently-selected column's or row's CELLS
|
|
@@ -3487,32 +3546,32 @@ ${usesDrawio ? ` /* v3.4.0 §4: a document with no drawio reference must not pa
|
|
|
3487
3546
|
relabeled/repositioned per click rather than rebuilt. '.ed-te-drop-
|
|
3488
3547
|
indicator' is the singleton line shown while dragging a row.
|
|
3489
3548
|
'.ed-te-row-dragging' dims the row actually being dragged. */
|
|
3490
|
-
.ed-te-hl { background:
|
|
3549
|
+
.ed-te-hl { background: var(--md-ed-sel) !important; }
|
|
3491
3550
|
/* Table cell-range selection: the painted rectangle, and the table that
|
|
3492
3551
|
holds one (its caret and native selection are hidden while it stands). */
|
|
3493
|
-
.ed-cell-range { background:
|
|
3552
|
+
.ed-cell-range { background: var(--md-ed-range) !important; }
|
|
3494
3553
|
.ed-cell-range-active { caret-color: transparent; }
|
|
3495
3554
|
.ed-cell-range-active ::selection { background: transparent; }
|
|
3496
3555
|
.ed-te-menu {
|
|
3497
3556
|
position: fixed; z-index: 12;
|
|
3498
3557
|
display: flex; align-items: center; gap: 4px;
|
|
3499
3558
|
padding: 4px 6px; border-radius: 8px;
|
|
3500
|
-
background:
|
|
3501
|
-
box-shadow: 0
|
|
3559
|
+
background: var(--md-ed-surface); color: var(--md-strong);
|
|
3560
|
+
box-shadow: 0 0 0 1px var(--md-ed-ring), 0 8px 24px var(--md-ed-shadow);
|
|
3502
3561
|
}
|
|
3503
3562
|
.ed-te-menu[hidden] { display: none; }
|
|
3504
3563
|
.ed-te-menu-btn {
|
|
3505
3564
|
min-width: 28px; height: 26px; padding: 0 8px;
|
|
3506
|
-
border: 1px solid
|
|
3507
|
-
background:
|
|
3565
|
+
border: 1px solid transparent; border-radius: 6px;
|
|
3566
|
+
background: transparent; color: inherit;
|
|
3508
3567
|
font: inherit; font-size: 12px; line-height: 1;
|
|
3509
3568
|
white-space: nowrap; cursor: pointer;
|
|
3510
3569
|
}
|
|
3511
|
-
.ed-te-menu-btn:hover { background:
|
|
3570
|
+
.ed-te-menu-btn:hover { background: var(--md-ed-hover); }
|
|
3512
3571
|
.ed-te-menu-btn[hidden] { display: none; }
|
|
3513
3572
|
.ed-te-drop-indicator {
|
|
3514
3573
|
position: fixed; z-index: 8; height: 3px; border-radius: 2px;
|
|
3515
|
-
background:
|
|
3574
|
+
background: var(--md-accent); pointer-events: none;
|
|
3516
3575
|
}
|
|
3517
3576
|
.ed-te-drop-indicator[hidden] { display: none; }
|
|
3518
3577
|
.ed-te-row-dragging { opacity: 0.4; }
|
|
@@ -3527,7 +3586,7 @@ ${usesDrawio ? ` /* v3.4.0 §4: a document with no drawio reference must not pa
|
|
|
3527
3586
|
the BLOCK line rather than table chrome that happened to be on screen. */
|
|
3528
3587
|
.ed-block-drop-indicator {
|
|
3529
3588
|
position: fixed; z-index: 8; height: 3px; border-radius: 2px;
|
|
3530
|
-
background:
|
|
3589
|
+
background: var(--md-accent); pointer-events: none;
|
|
3531
3590
|
}
|
|
3532
3591
|
.ed-block-drop-indicator[hidden] { display: none; }
|
|
3533
3592
|
/* S4 Task 2b (user request, 2026-09-01): the drag's feedback on the SOURCE
|
|
@@ -3579,9 +3638,9 @@ ${usesDrawio ? ` /* v3.4.0 §4: a document with no drawio reference must not pa
|
|
|
3579
3638
|
transition: background .12s ease;
|
|
3580
3639
|
}
|
|
3581
3640
|
.ed-te-grip[hidden] { display: none; }
|
|
3582
|
-
.ed-te-grip:hover { background:
|
|
3583
|
-
.ed-te-grip-dot { width: 3px; height: 3px; border-radius: 50%; background:
|
|
3584
|
-
.ed-te-grip:hover .ed-te-grip-dot { background:
|
|
3641
|
+
.ed-te-grip:hover { background: var(--md-ed-hover); }
|
|
3642
|
+
.ed-te-grip-dot { width: 3px; height: 3px; border-radius: 50%; background: var(--md-ed-glyph); }
|
|
3643
|
+
.ed-te-grip:hover .ed-te-grip-dot { background: var(--md-muted); }
|
|
3585
3644
|
/* spec §4.2 hit-test conflict 2: the row grip's width is a TOKEN because
|
|
3586
3645
|
lib/editor/client.js's updateTableEdgeGrips() reads it (via
|
|
3587
3646
|
gripRowWidthPx()) to CENTRE the grip on the table's left border —
|
|
@@ -3611,17 +3670,17 @@ ${usesDrawio ? ` /* v3.4.0 §4: a document with no drawio reference must not pa
|
|
|
3611
3670
|
position: fixed; z-index: 15;
|
|
3612
3671
|
display: flex; align-items: center; gap: 4px;
|
|
3613
3672
|
padding: 4px 6px; border-radius: 8px;
|
|
3614
|
-
background:
|
|
3615
|
-
box-shadow: 0
|
|
3673
|
+
background: var(--md-ed-surface); color: var(--md-strong);
|
|
3674
|
+
box-shadow: 0 0 0 1px var(--md-ed-ring), 0 8px 24px var(--md-ed-shadow);
|
|
3616
3675
|
}
|
|
3617
3676
|
.ed-seltb-btn {
|
|
3618
3677
|
min-width: 28px; height: 26px; padding: 0 8px;
|
|
3619
|
-
border: 1px solid
|
|
3620
|
-
background:
|
|
3678
|
+
border: 1px solid transparent; border-radius: 6px;
|
|
3679
|
+
background: transparent; color: inherit;
|
|
3621
3680
|
font: inherit; font-size: 13px; line-height: 1;
|
|
3622
3681
|
white-space: nowrap; cursor: pointer;
|
|
3623
3682
|
}
|
|
3624
|
-
.ed-seltb-btn:hover:not([aria-disabled="true"]) { background:
|
|
3683
|
+
.ed-seltb-btn:hover:not([aria-disabled="true"]) { background: var(--md-ed-hover); }
|
|
3625
3684
|
/* v3.3.0 (F2): the four states these buttons report about the selection.
|
|
3626
3685
|
'true' (the selection is entirely inside a mark of this type) is the
|
|
3627
3686
|
filled treatment the fixed toolbar already uses. 'mixed' (the selection
|
|
@@ -3631,17 +3690,18 @@ ${usesDrawio ? ` /* v3.4.0 §4: a document with no drawio reference must not pa
|
|
|
3631
3690
|
rule above. INERT is aria-disabled, never the disabled DOM property; see
|
|
3632
3691
|
paintSelToolbarMarks() in lib/editor/client.js for why. */
|
|
3633
3692
|
.ed-seltb-btn[aria-pressed="true"] {
|
|
3634
|
-
background:
|
|
3693
|
+
background: var(--md-active-bg); color: var(--md-active-fg); border-color: transparent;
|
|
3635
3694
|
}
|
|
3636
3695
|
.ed-seltb-btn[aria-pressed="mixed"] {
|
|
3637
|
-
background:
|
|
3638
|
-
border-style: dashed; border-color:
|
|
3696
|
+
background: transparent;
|
|
3697
|
+
border-style: dashed; border-color: var(--md-border);
|
|
3639
3698
|
}
|
|
3640
3699
|
.ed-seltb-btn[aria-disabled="true"] { opacity: 0.38; cursor: default; }
|
|
3641
|
-
.ed-seltb
|
|
3642
|
-
.ed-seltb-
|
|
3643
|
-
.ed-seltb-
|
|
3644
|
-
.ed-seltb-
|
|
3700
|
+
.ed-seltb { padding: 3px; gap: 2px; }
|
|
3701
|
+
.ed-seltb-btn { width: 30px; height: 30px; min-width: 0; padding: 0; display: inline-grid; place-items: center; color: var(--md-muted); }
|
|
3702
|
+
.ed-seltb-btn:hover:not([aria-disabled="true"]) { color: var(--md-strong); }
|
|
3703
|
+
.ed-seltb-sep { width: 1px; height: 18px; margin: 0 3px; background: var(--md-rule); align-self: center; }
|
|
3704
|
+
.ed-ico { display: block; flex: none; }
|
|
3645
3705
|
/* v3.1.0 §4: the always-visible editing toolbar. Its numeric language is
|
|
3646
3706
|
.lightbox-bar's (8px/12px padding, 6px gap, 13px type) over .ed-seltb's
|
|
3647
3707
|
dark pill, so the two floating surfaces read as one family.
|
|
@@ -3684,7 +3744,9 @@ ${usesDrawio ? ` /* v3.4.0 §4: a document with no drawio reference must not pa
|
|
|
3684
3744
|
save included, still reaches an empty unreachable list and an empty
|
|
3685
3745
|
neverClear list at all three — this change does not trade the T11-2
|
|
3686
3746
|
no-scroll guarantee for the F12 with-scroll one. */
|
|
3687
|
-
|
|
3747
|
+
/* v3.10.0 B 外觀: buttons grew to 32x32 (+~4px each) and separators to
|
|
3748
|
+
6px margins, so the gap went 3px -> 0 to keep outline (hamburger) inside 800px. */
|
|
3749
|
+
gap: 0; padding: 8px 12px;
|
|
3688
3750
|
/* F12, both halves measured on this build at 900px viewport height.
|
|
3689
3751
|
justify-content was center. Centering an overflowing flex row splits
|
|
3690
3752
|
the overflow across BOTH ends, and the leading end of a scroll
|
|
@@ -3704,9 +3766,13 @@ ${usesDrawio ? ` /* v3.4.0 §4: a document with no drawio reference must not pa
|
|
|
3704
3766
|
scrollLeft 0 after this change, [outline, preview] still overlap the
|
|
3705
3767
|
slot at a 900px viewport and [check, bold, italic, strike] do at 420px.
|
|
3706
3768
|
What makes that harmless is the slot's own pointer-events: none, in
|
|
3707
|
-
the rule further down — the load-bearing line, not this one.
|
|
3708
|
-
|
|
3709
|
-
|
|
3769
|
+
the rule further down — the load-bearing line, not this one.
|
|
3770
|
+
v3.10.0: 260px. The slot now carries the save state, and its longest
|
|
3771
|
+
text (the dirty one, "有未儲存的變更 · Ctrl+S 儲存" after a dot) is far
|
|
3772
|
+
wider than the old 7rem mode name. */
|
|
3773
|
+
padding-right: 260px;
|
|
3774
|
+
background: var(--md-ed-chrome); color: var(--md-muted);
|
|
3775
|
+
border-bottom: 1px solid var(--md-rule);
|
|
3710
3776
|
/* F12, second half: before this the bar scrolled but nothing on screen
|
|
3711
3777
|
said so, so a button parked outside the viewport looked absent rather
|
|
3712
3778
|
than off to one side. Two edge gradients, painted only on the side
|
|
@@ -3730,32 +3796,34 @@ ${usesDrawio ? ` /* v3.4.0 §4: a document with no drawio reference must not pa
|
|
|
3730
3796
|
than light — darkening the ground a translucent button sits on is what
|
|
3731
3797
|
reads as content passing under an edge, where a light wash behind the
|
|
3732
3798
|
same button would not. */
|
|
3733
|
-
--ed-tb-edge-l:
|
|
3734
|
-
--ed-tb-edge-r:
|
|
3799
|
+
--ed-tb-edge-l: transparent;
|
|
3800
|
+
--ed-tb-edge-r: transparent;
|
|
3735
3801
|
background-image:
|
|
3736
|
-
linear-gradient(to right, var(--ed-tb-edge-l),
|
|
3737
|
-
linear-gradient(to left, var(--ed-tb-edge-r),
|
|
3802
|
+
linear-gradient(to right, var(--ed-tb-edge-l), transparent),
|
|
3803
|
+
linear-gradient(to left, var(--ed-tb-edge-r), transparent);
|
|
3738
3804
|
background-repeat: no-repeat;
|
|
3739
3805
|
background-position: left center, right center;
|
|
3740
3806
|
background-size: 26px 100%, 26px 100%;
|
|
3741
3807
|
font-size: 13px;
|
|
3742
|
-
box-shadow:
|
|
3808
|
+
box-shadow: none;
|
|
3743
3809
|
}
|
|
3744
|
-
.ed-toolbar[data-ed-tb-overflow~="left"] { --ed-tb-edge-l:
|
|
3745
|
-
.ed-toolbar[data-ed-tb-overflow~="right"] { --ed-tb-edge-r:
|
|
3810
|
+
.ed-toolbar[data-ed-tb-overflow~="left"] { --ed-tb-edge-l: var(--md-ed-shadow); }
|
|
3811
|
+
.ed-toolbar[data-ed-tb-overflow~="right"] { --ed-tb-edge-r: var(--md-ed-shadow); }
|
|
3746
3812
|
.ed-toolbar-sep {
|
|
3747
|
-
flex: 0 0 auto; width: 1px; height:
|
|
3748
|
-
background:
|
|
3813
|
+
flex: 0 0 auto; width: 1px; height: 20px; margin: 0 6px;
|
|
3814
|
+
background: var(--md-rule);
|
|
3749
3815
|
}
|
|
3750
3816
|
.ed-toolbar-btn {
|
|
3751
3817
|
flex: 0 0 auto;
|
|
3752
|
-
min-width:
|
|
3753
|
-
|
|
3754
|
-
|
|
3818
|
+
min-width: 32px; height: 32px; padding: 0;
|
|
3819
|
+
display: inline-grid; place-items: center; color: var(--md-muted);
|
|
3820
|
+
border: 0 solid transparent; border-radius: 6px;
|
|
3821
|
+
background: transparent;
|
|
3755
3822
|
font: inherit; font-size: 13px; line-height: 1;
|
|
3756
3823
|
white-space: nowrap; cursor: pointer;
|
|
3757
3824
|
}
|
|
3758
|
-
.ed-toolbar-btn:hover:not(:disabled) { background:
|
|
3825
|
+
.ed-toolbar-btn:hover:not(:disabled) { background: var(--md-ed-hover); color: var(--md-strong); }
|
|
3826
|
+
.ed-toolbar-btn .ed-ico { width: 18px; height: 18px; }
|
|
3759
3827
|
/* F12: where the toolbar's keyboard cursor is. An outline rather than a
|
|
3760
3828
|
background so it stays legible on a button that is already pressed-in
|
|
3761
3829
|
(aria-pressed paints that one nearly white). Claim no more for it than a
|
|
@@ -3764,21 +3832,18 @@ ${usesDrawio ? ` /* v3.4.0 §4: a document with no drawio reference must not pa
|
|
|
3764
3832
|
follow, and what this cue does for a screen reader has not been measured
|
|
3765
3833
|
here. What it does do is let the row be walked without a mouse. */
|
|
3766
3834
|
.ed-toolbar-btn[data-ed-tb-cursor] {
|
|
3767
|
-
outline: 2px solid
|
|
3835
|
+
outline: 2px solid var(--md-accent); outline-offset: 1px;
|
|
3768
3836
|
}
|
|
3769
3837
|
.ed-toolbar-btn:disabled { opacity: 0.38; cursor: default; }
|
|
3770
3838
|
.ed-toolbar-btn[aria-pressed="true"] {
|
|
3771
|
-
background:
|
|
3839
|
+
background: var(--md-active-bg); color: var(--md-active-fg); border-color: transparent;
|
|
3772
3840
|
}
|
|
3773
3841
|
/* v3.3.0 (F2): the middle state, matching .ed-seltb-btn[aria-pressed="mixed"]
|
|
3774
3842
|
above so the two bars answer the same question the same way. */
|
|
3775
3843
|
.ed-toolbar-btn[aria-pressed="mixed"] {
|
|
3776
|
-
background:
|
|
3777
|
-
border-style: dashed; border-color:
|
|
3844
|
+
background: transparent;
|
|
3845
|
+
border-width: 1px; border-style: dashed; border-color: var(--md-border);
|
|
3778
3846
|
}
|
|
3779
|
-
.ed-toolbar-b { font-weight: bold; }
|
|
3780
|
-
.ed-toolbar-i { font-style: italic; }
|
|
3781
|
-
.ed-toolbar-s { text-decoration: line-through; }
|
|
3782
3847
|
/* H1 fix — and its reason has since moved, so both readings are kept here
|
|
3783
3848
|
rather than one silently standing in for the other. The brief's literal
|
|
3784
3849
|
margin-left: auto (a flex-flow item) was measured on v3.2.0 to break
|
|
@@ -3824,9 +3889,13 @@ ${usesDrawio ? ` /* v3.4.0 §4: a document with no drawio reference must not pa
|
|
|
3824
3889
|
regression, this time on content someone can actually see and try to
|
|
3825
3890
|
click. */
|
|
3826
3891
|
.ed-toolbar-status { position: fixed; top: 0; right: 12px;
|
|
3827
|
-
height: var(--ed-toolbar-h);
|
|
3828
|
-
min-width: 7rem;
|
|
3892
|
+
height: var(--ed-toolbar-h); display: flex; align-items: center; gap: 6px;
|
|
3893
|
+
min-width: 7rem; justify-content: flex-end; font-size: 13px; color: var(--md-muted);
|
|
3829
3894
|
pointer-events: none; }
|
|
3895
|
+
.ed-toolbar-status[data-state="clean"] .ed-ico { color: var(--md-ed-ok); }
|
|
3896
|
+
.ed-toolbar-status[data-state="failed"] { color: var(--md-ed-err); }
|
|
3897
|
+
.ed-status-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--md-ed-dirty); display: inline-block; }
|
|
3898
|
+
.ed-toolbar-btn[data-ed-tb="save"][aria-pressed="true"] { color: var(--md-accent); background: var(--md-active-bg); }
|
|
3830
3899
|
/* The H▾ dropdown: same singleton-panel idiom as .ed-insert-menu, but
|
|
3831
3900
|
anchored to a fixed bar rather than moved into a block, so it is
|
|
3832
3901
|
positioned in viewport coordinates by the client runtime.
|
|
@@ -3839,8 +3908,8 @@ ${usesDrawio ? ` /* v3.4.0 §4: a document with no drawio reference must not pa
|
|
|
3839
3908
|
position: fixed; z-index: 102;
|
|
3840
3909
|
display: flex; flex-direction: column; gap: 2px;
|
|
3841
3910
|
padding: 4px; border-radius: 8px;
|
|
3842
|
-
background:
|
|
3843
|
-
box-shadow: 0
|
|
3911
|
+
background: var(--md-ed-surface); color: var(--md-strong);
|
|
3912
|
+
box-shadow: 0 0 0 1px var(--md-ed-ring), 0 8px 24px var(--md-ed-shadow);
|
|
3844
3913
|
max-height: calc(100vh - 8px); overflow-y: auto;
|
|
3845
3914
|
}
|
|
3846
3915
|
.ed-toolbar-menu[hidden] { display: none; }
|
|
@@ -3849,7 +3918,7 @@ ${usesDrawio ? ` /* v3.4.0 §4: a document with no drawio reference must not pa
|
|
|
3849
3918
|
border: none; border-radius: 4px; background: transparent; color: inherit;
|
|
3850
3919
|
font: inherit; font-size: 13px; line-height: 1.4; cursor: pointer;
|
|
3851
3920
|
}
|
|
3852
|
-
.ed-toolbar-menu-btn:hover { background:
|
|
3921
|
+
.ed-toolbar-menu-btn:hover { background: var(--md-ed-hover); }
|
|
3853
3922
|
/* backlog #7 (Task 9 review, M1): the panel's OWN keyboard cursor
|
|
3854
3923
|
(lib/editor/client.js's toolbarMenuIndex / data-ed-tb-menu-cursor) had
|
|
3855
3924
|
no matching rule at all — the fix made the six items operable by
|
|
@@ -3859,32 +3928,24 @@ ${usesDrawio ? ` /* v3.4.0 §4: a document with no drawio reference must not pa
|
|
|
3859
3928
|
over :hover's own background, and over nothing at all when neither
|
|
3860
3929
|
applies). */
|
|
3861
3930
|
.ed-toolbar-menu-btn[data-ed-tb-menu-cursor] {
|
|
3862
|
-
outline: 2px solid
|
|
3863
|
-
background:
|
|
3864
|
-
}
|
|
3865
|
-
/* 追加 4: the whole-document source escape hatch. Replaces the rendered
|
|
3866
|
-
content column for the duration of source mode; the document's own
|
|
3867
|
-
lines[] is untouched until the mode is left (see docsource.js). */
|
|
3868
|
-
.ed-source {
|
|
3869
|
-
display: block; width: 100%; box-sizing: border-box;
|
|
3870
|
-
min-height: 70vh; padding: 12px;
|
|
3871
|
-
font-family: monospace; font-size: 13px; line-height: 1.6;
|
|
3872
|
-
border: 1px solid #d0d7de; border-radius: 8px; resize: vertical;
|
|
3873
|
-
white-space: pre; overflow-wrap: normal;
|
|
3931
|
+
outline: 2px solid var(--md-accent); outline-offset: -2px;
|
|
3932
|
+
background: var(--md-ed-hover);
|
|
3874
3933
|
}
|
|
3875
3934
|
.ed-editing { position: relative; }
|
|
3876
3935
|
.ed-raw {
|
|
3877
|
-
display: block; width: 100%; font-family: monospace; font-size: 13px;
|
|
3936
|
+
display: block; width: 100%; font-family: "Cascadia Mono", Consolas, ui-monospace, monospace; font-size: 13px;
|
|
3878
3937
|
min-height: 3em; box-sizing: border-box; padding: 6px;
|
|
3879
|
-
border: 1px solid
|
|
3938
|
+
border: 1px solid var(--md-border); border-radius: 6px;
|
|
3939
|
+
background: var(--md-ed-field); color: var(--md-strong); resize: vertical;
|
|
3880
3940
|
}
|
|
3881
3941
|
.ed-controls { display: flex; gap: 6px; margin-top: 4px; }
|
|
3882
3942
|
.ed-controls button {
|
|
3883
3943
|
font-size: 13px; padding: 2px 10px; cursor: pointer;
|
|
3884
|
-
border: 1px solid
|
|
3944
|
+
border: 1px solid var(--md-border); border-radius: 6px;
|
|
3945
|
+
background: var(--md-ed-surface); color: var(--md-strong);
|
|
3885
3946
|
}
|
|
3886
|
-
.ed-commit { color:
|
|
3887
|
-
.ed-cancel { color:
|
|
3947
|
+
.ed-commit { color: var(--md-ed-ok); }
|
|
3948
|
+
.ed-cancel { color: var(--md-ed-err); }
|
|
3888
3949
|
/* backlog #8: 'top: 0' used to put this banner directly OVER .ed-toolbar
|
|
3889
3950
|
(same top-of-viewport band, and z-index 999 against the bar's 101 wins
|
|
3890
3951
|
the paint) — MEASURED (task-10-toolbar-probe.js): all 23 toolbar
|
|
@@ -3921,16 +3982,38 @@ ${usesDrawio ? ` /* v3.4.0 §4: a document with no drawio reference must not pa
|
|
|
3921
3982
|
land nearby as a side effect of their own trigger's position, not a
|
|
3922
3983
|
shared clamp — see showBanner()'s parallel comment in
|
|
3923
3984
|
lib/editor/client.js. Closed regardless, so a banner can never land on
|
|
3924
|
-
top of one of those instead.
|
|
3985
|
+
top of one of those instead.
|
|
3986
|
+
v3.10.0 (two message levels): only the ERROR card floors at the token
|
|
3987
|
+
now — its top is --ed-toolbar-h plus 10px, a centred card rather than a
|
|
3988
|
+
full-width band — and a NOTICE sits at the bottom of the viewport, so it
|
|
3989
|
+
never reaches the toolbar's band at all. */
|
|
3990
|
+
/* Centred with left/right + auto margins and a fit-content width, NOT
|
|
3991
|
+
left: 50% + translateX(-50%): a fixed box anchored at left: 50% may only
|
|
3992
|
+
grow into the right half of the viewport, so the 760px cap never applied
|
|
3993
|
+
and a long message wrapped into a card half the viewport wide and several
|
|
3994
|
+
times as tall (measured 800x600: 400px wide, y 54-284, covering the
|
|
3995
|
+
waveform editor's group buttons). */
|
|
3925
3996
|
.ed-conflict {
|
|
3926
|
-
position: fixed;
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3997
|
+
position: fixed; left: 16px; right: 16px; margin: 0 auto; width: fit-content; z-index: 999;
|
|
3998
|
+
display: flex; align-items: center; gap: 10px;
|
|
3999
|
+
max-width: 760px; box-sizing: border-box;
|
|
4000
|
+
padding: 10px 10px 10px 14px; border-radius: 8px; font-size: 14px; line-height: 1.5;
|
|
4001
|
+
}
|
|
4002
|
+
.ed-conflict[data-level="error"] {
|
|
4003
|
+
top: calc(var(--ed-toolbar-h) + 10px);
|
|
4004
|
+
background: var(--md-ed-err-bg); color: var(--md-ed-err-ink);
|
|
4005
|
+
box-shadow: inset 3px 0 0 var(--md-ed-err), 0 0 0 1px var(--md-ed-ring), 0 8px 24px var(--md-ed-shadow);
|
|
4006
|
+
}
|
|
4007
|
+
.ed-conflict[data-level="notice"] {
|
|
4008
|
+
bottom: 28px; background: var(--md-ed-surface); color: var(--md-strong);
|
|
4009
|
+
box-shadow: 0 0 0 1px var(--md-ed-ring), 0 8px 24px var(--md-ed-shadow);
|
|
4010
|
+
}
|
|
4011
|
+
.ed-msg-ico { display: grid; color: var(--md-muted); }
|
|
4012
|
+
.ed-conflict[data-level="error"] .ed-msg-ico { color: var(--md-ed-err); }
|
|
4013
|
+
.ed-msg-text { flex: 1 1 auto; }
|
|
4014
|
+
.ed-conflict button { font: inherit; font-size: 13px; font-weight: 600; border: 0; border-radius: 6px;
|
|
4015
|
+
padding: 4px 12px; cursor: pointer; background: var(--md-ed-err); color: var(--md-ed-surface); }
|
|
4016
|
+
.ed-conflict button.ed-msg-x { background: transparent; color: inherit; padding: 4px; display: grid; }
|
|
3934
4017
|
${opts.editMode ? editModeLayoutCss : ''}
|
|
3935
4018
|
</style>
|
|
3936
4019
|
${usesMath ? buildKatexStyleTag() : ''}
|
|
@@ -4668,11 +4751,19 @@ ${mermaidInitTag}`)
|
|
|
4668
4751
|
var anchorTop = 0;
|
|
4669
4752
|
var anchorFrame = 0;
|
|
4670
4753
|
var suppressAnchorCapture = false;
|
|
4754
|
+
// Column width the anchor was last captured/held at; -1 until the column
|
|
4755
|
+
// ResizeObserver below is installed. See captureScrollAnchor's first guard.
|
|
4756
|
+
var lastAnchorColW = -1;
|
|
4671
4757
|
var lastLayoutWidth = document.documentElement.clientWidth;
|
|
4672
4758
|
var lastPixelRatio = window.devicePixelRatio;
|
|
4673
4759
|
|
|
4674
4760
|
function captureScrollAnchor() {
|
|
4675
4761
|
if (suppressAnchorCapture || !contentEl) return;
|
|
4762
|
+
// A column reflow the ResizeObserver has not delivered yet: during a
|
|
4763
|
+
// splitter drag the next pointermove lands before holdScrollAnchor's
|
|
4764
|
+
// trailing capture frame, so capturing now would pin a post-reflow node
|
|
4765
|
+
// and the observer's restore would then find nothing to correct.
|
|
4766
|
+
if (lastAnchorColW >= 0 && contentEl.clientWidth !== lastAnchorColW) return;
|
|
4676
4767
|
var rect = contentEl.getBoundingClientRect();
|
|
4677
4768
|
var hit = document.elementFromPoint(rect.left + rect.width / 2, ANCHOR_LINE);
|
|
4678
4769
|
var node = (hit && hit.closest) ? hit.closest('main.content > *') : null;
|
|
@@ -4724,14 +4815,7 @@ ${mermaidInitTag}`)
|
|
|
4724
4815
|
});
|
|
4725
4816
|
}, { passive: true });
|
|
4726
4817
|
|
|
4727
|
-
|
|
4728
|
-
var width = document.documentElement.clientWidth;
|
|
4729
|
-
var ratio = window.devicePixelRatio;
|
|
4730
|
-
// A height-only change (a mobile browser hiding its toolbar, a devtools
|
|
4731
|
-
// dock) reflows nothing, so correcting the scroll would only jerk the page.
|
|
4732
|
-
if (width === lastLayoutWidth && ratio === lastPixelRatio) return;
|
|
4733
|
-
lastLayoutWidth = width;
|
|
4734
|
-
lastPixelRatio = ratio;
|
|
4818
|
+
function holdScrollAnchor() {
|
|
4735
4819
|
// Hold the anchor across the whole reflow: the scrollTo below fires scroll
|
|
4736
4820
|
// events that would otherwise re-capture a mid-reflow position.
|
|
4737
4821
|
suppressAnchorCapture = true;
|
|
@@ -4743,8 +4827,37 @@ ${mermaidInitTag}`)
|
|
|
4743
4827
|
captureScrollAnchor();
|
|
4744
4828
|
});
|
|
4745
4829
|
});
|
|
4830
|
+
}
|
|
4831
|
+
|
|
4832
|
+
window.addEventListener('resize', function () {
|
|
4833
|
+
var width = document.documentElement.clientWidth;
|
|
4834
|
+
var ratio = window.devicePixelRatio;
|
|
4835
|
+
// A height-only change (a mobile browser hiding its toolbar, a devtools
|
|
4836
|
+
// dock) reflows nothing, so correcting the scroll would only jerk the page.
|
|
4837
|
+
if (width === lastLayoutWidth && ratio === lastPixelRatio) return;
|
|
4838
|
+
lastLayoutWidth = width;
|
|
4839
|
+
lastPixelRatio = ratio;
|
|
4840
|
+
holdScrollAnchor();
|
|
4746
4841
|
});
|
|
4747
4842
|
|
|
4843
|
+
// The column also narrows and widens with no window resize at all: a
|
|
4844
|
+
// sidebar-splitter drag, its double-click reset and the TOC collapse toggle
|
|
4845
|
+
// only move --md2doc-sidebar-w / data-toc-collapsed, so the 'resize' listener
|
|
4846
|
+
// above never hears about them and the reflow slid the page by thousands of
|
|
4847
|
+
// px. Watch the column itself. Width only: its height changes whenever an
|
|
4848
|
+
// image or diagram finishes loading, which reflows nothing above the anchor.
|
|
4849
|
+
// The first callback (fired by observe()) sees the starting width and does
|
|
4850
|
+
// nothing, so a load that lands on a #hash is never pulled back.
|
|
4851
|
+
if (contentEl && typeof ResizeObserver === 'function') {
|
|
4852
|
+
lastAnchorColW = contentEl.clientWidth;
|
|
4853
|
+
new ResizeObserver(function () {
|
|
4854
|
+
var w = contentEl.clientWidth;
|
|
4855
|
+
if (w === lastAnchorColW) return;
|
|
4856
|
+
lastAnchorColW = w;
|
|
4857
|
+
holdScrollAnchor();
|
|
4858
|
+
}).observe(contentEl);
|
|
4859
|
+
}
|
|
4860
|
+
|
|
4748
4861
|
captureScrollAnchor();
|
|
4749
4862
|
|
|
4750
4863
|
// ── drawio sheet bar ─────────────────────────────────────────────────────
|
|
@@ -5919,8 +6032,8 @@ ${usesDrawioMultiPage ? ` // v3.4.0 batch2 Task 6: wired through a NAMED, reusa
|
|
|
5919
6032
|
await drawioBrowser.closeIfOpen();
|
|
5920
6033
|
}
|
|
5921
6034
|
// v3.9.0 dark mode: a post-pass over the finished reader HTML (lib/theme/).
|
|
5922
|
-
//
|
|
5923
|
-
if (!opts.
|
|
6035
|
+
// v3.10.0: edit mode is themed too. noTheme is an internal switch for the light-lock test.
|
|
6036
|
+
if (!opts.noTheme) bakedHtml = applyReaderTheme(bakedHtml);
|
|
5924
6037
|
return {
|
|
5925
6038
|
html: bakedHtml, bodyHtml: bakedBody, parts, blocks,
|
|
5926
6039
|
// v3.4.0 batch2 Task 6: an added field, not a shape change to the four
|