@helping-ai-workflow/md2doc 2.11.1 → 2.12.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 +1650 -269
- package/lib/editor/indent-clamp.js +6 -1
- package/lib/editor/selection.js +204 -0
- package/lib/editor/server.js +7 -0
- package/lib/md2doc.js +50 -2
- package/package.json +2 -2
|
@@ -30,7 +30,12 @@
|
|
|
30
30
|
// §3.4's global convention). For a multi-block operation
|
|
31
31
|
// this is the SMALLEST old indent in the set — anchoring on
|
|
32
32
|
// the first member instead drives later members negative on
|
|
33
|
-
// a delete
|
|
33
|
+
// a delete (§3.4 rule 3's first worked failure). It is the
|
|
34
|
+
// bound for the blocks BELOW the set only; how far the set
|
|
35
|
+
// ITSELF may move is a separate question the caller answers
|
|
36
|
+
// (client.js batchIndentDelta(), which since the 2026-08-31
|
|
37
|
+
// D1 review takes the minimum head-room across ALL members,
|
|
38
|
+
// not the head-room of this shallowest one).
|
|
34
39
|
// opts.removed — the operated block(s) no longer exist.
|
|
35
40
|
// opts.operatedBecomes — the operated block is still there but is no
|
|
36
41
|
// longer a list item (§3.3 conversion); the value
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/* Spec §3.3 / §3.6 / §4.4 — block 多選的純模型.
|
|
3
|
+
UMD, same shape as indent-clamp.js: `require`-able in node for the unit
|
|
4
|
+
tests, and injected into the editor page as `window.md2docSelection`. */
|
|
5
|
+
(function (root, factory) {
|
|
6
|
+
if (typeof module === 'object' && module.exports) module.exports = factory();
|
|
7
|
+
else root.md2docSelection = factory();
|
|
8
|
+
})(typeof self !== 'undefined' ? self : this, function () {
|
|
9
|
+
|
|
10
|
+
// A selection's identity is a LINE RANGE — never block ids, never DOM nodes.
|
|
11
|
+
// buildBlockMap() renumbers every id from 0 on every render (blockmap.js:170)
|
|
12
|
+
// and every batch operation triggers a full rerenderAll(), so an id or an
|
|
13
|
+
// element held across a commit is a dangling reference into a document that
|
|
14
|
+
// no longer exists. Every post-commit recovery path already in client.js
|
|
15
|
+
// (blockElAtLine, reresolveBlockEl, focusBlockAtLine) goes through startLine
|
|
16
|
+
// for exactly this reason. Nothing in this file reads the DOM, and nothing
|
|
17
|
+
// outside it may hand this file an id.
|
|
18
|
+
//
|
|
19
|
+
// a selection — { anchorLine, focusLine }, or null for "nothing selected"
|
|
20
|
+
// a block — a record from buildBlockMap(): { id, type, startLine,
|
|
21
|
+
// endLine, ... }. Read here for startLine/endLine only;
|
|
22
|
+
// `id` is passed through untouched for the caller's own use.
|
|
23
|
+
// `blocks` — the whole live block list, in document order.
|
|
24
|
+
|
|
25
|
+
// ── the no-line exclusion ────────────────────────────────────────────
|
|
26
|
+
// buildBlockMap emits a block for the OUTER item of a same-line nest
|
|
27
|
+
// ("- - a" -> outer {startLine:3, endLine:2}, inner {startLine:3,
|
|
28
|
+
// endLine:3}), and its range is INVERTED because it owns no source line of
|
|
29
|
+
// its own. Such a block has no line for a range to touch, so it can never be
|
|
30
|
+
// a member of a line-range selection. This is the same predicate as
|
|
31
|
+
// blockOwnsNoLine() in client.js:928, which is the guard every structural
|
|
32
|
+
// path in the editor already checks before it writes bytes.
|
|
33
|
+
function ownsALine(b) {
|
|
34
|
+
return !!b && typeof b.startLine === 'number' && typeof b.endLine === 'number'
|
|
35
|
+
&& b.endLine >= b.startLine;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function lineOf(v) {
|
|
39
|
+
const n = Number(v);
|
|
40
|
+
return Number.isFinite(n) ? n : null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ── normalize ────────────────────────────────────────────────────────
|
|
44
|
+
// Orders anchor/focus into the {startLine, endLine} shape the block records
|
|
45
|
+
// and the UndoStack op shape already use, so the two never have to be
|
|
46
|
+
// mentally converted at a call site. Returns null rather than a NaN range
|
|
47
|
+
// for "no selection" — a NaN range compares false against everything and
|
|
48
|
+
// would silently produce an empty member set that looks like a real answer.
|
|
49
|
+
function normalize(sel) {
|
|
50
|
+
if (!sel) return null;
|
|
51
|
+
const a = lineOf(sel.anchorLine);
|
|
52
|
+
const f = lineOf(sel.focusLine);
|
|
53
|
+
if (a === null || f === null) return null;
|
|
54
|
+
return { startLine: Math.min(a, f), endLine: Math.max(a, f) };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ── membership ───────────────────────────────────────────────────────
|
|
58
|
+
// Membership is INTERSECTION, not containment: a multi-line block touched
|
|
59
|
+
// anywhere is wholly selected. Block selection has no partial state (§3.6 —
|
|
60
|
+
// the visual is a tint over whole blocks), so a code fence whose middle line
|
|
61
|
+
// is in range is in the set exactly as much as a one-line paragraph is.
|
|
62
|
+
// Returns block RECORDS in document order, never ids and never elements.
|
|
63
|
+
function membersOf(sel, blocks) {
|
|
64
|
+
const r = normalize(sel);
|
|
65
|
+
if (!r) return [];
|
|
66
|
+
return (blocks || []).filter(
|
|
67
|
+
(b) => ownsALine(b) && b.startLine <= r.endLine && b.endLine >= r.startLine);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// There is deliberately NO per-block `isSelected()` here. One existed until
|
|
71
|
+
// 2026-08-31 and never had a production caller in `lib/`: every paint and
|
|
72
|
+
// every operation asks membersOf() for the whole set at once, because a batch
|
|
73
|
+
// needs the set's contiguity and its index range, not N independent yes/no
|
|
74
|
+
// answers. Its assertions were all over SINGLE-line blocks, so the
|
|
75
|
+
// intersection-not-containment rule was pinned by membersOf()'s multi-line
|
|
76
|
+
// fixture alone and nothing was lost by removing it. A future caller that
|
|
77
|
+
// genuinely wants one block's answer should re-add it WITH that caller and
|
|
78
|
+
// with a multi-line fixture, rather than inherit an untested export.
|
|
79
|
+
|
|
80
|
+
// ── extendTo ─────────────────────────────────────────────────────────
|
|
81
|
+
// Shift+Click and a drag both move the FOCUS and keep the anchor where the
|
|
82
|
+
// gesture started; that is what makes a selection reversible by dragging
|
|
83
|
+
// back. With no prior selection there is no anchor to keep, so the gesture
|
|
84
|
+
// starts one collapsed at that line.
|
|
85
|
+
function extendTo(sel, line) {
|
|
86
|
+
const l = lineOf(line);
|
|
87
|
+
if (l === null) return sel || null;
|
|
88
|
+
const a = sel ? lineOf(sel.anchorLine) : null;
|
|
89
|
+
return { anchorLine: a === null ? l : a, focusLine: l };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ── stepFocus ────────────────────────────────────────────────────────
|
|
93
|
+
// §4.4's Shift+↑↓. Moves by BLOCK, not by line: blocks do not tile the
|
|
94
|
+
// document (blank separator lines belong to nothing), so a per-line step
|
|
95
|
+
// would park the focus on a line that owns no block and collapse the set to
|
|
96
|
+
// nothing on the way past. Blocks that own no source line are skipped for
|
|
97
|
+
// the same reason membersOf() excludes them — landing on one would leave a
|
|
98
|
+
// selection whose member set is empty.
|
|
99
|
+
//
|
|
100
|
+
// Clamped at both ends: stepping past either end is a no-op, never an error
|
|
101
|
+
// and never a wrap. `dir` is any negative number for up, anything else down.
|
|
102
|
+
function stepFocus(sel, blocks, dir) {
|
|
103
|
+
if (!sel) return sel || null;
|
|
104
|
+
const nav = (blocks || []).filter(ownsALine);
|
|
105
|
+
if (!nav.length) return sel;
|
|
106
|
+
const focus = lineOf(sel.focusLine);
|
|
107
|
+
if (focus === null) return sel;
|
|
108
|
+
const step = dir < 0 ? -1 : 1;
|
|
109
|
+
|
|
110
|
+
// The focus normally sits exactly on a block's startLine, because that is
|
|
111
|
+
// what this function and the gesture handlers write. If it does not (a
|
|
112
|
+
// caller resolved a raw click y-coordinate, say), take the nearest block
|
|
113
|
+
// at or after it, and the last block when the line is past the end.
|
|
114
|
+
let i = nav.findIndex((b) => b.startLine === focus);
|
|
115
|
+
if (i === -1) i = nav.findIndex((b) => b.startLine > focus);
|
|
116
|
+
if (i === -1) i = nav.length - 1;
|
|
117
|
+
|
|
118
|
+
const j = Math.max(0, Math.min(nav.length - 1, i + step));
|
|
119
|
+
return { anchorLine: sel.anchorLine, focusLine: nav[j].startLine };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ── §3.3's membership rule ───────────────────────────────────────────
|
|
123
|
+
// "grip 在選取集合內 → 作用整個集合;grip 在集合外 → 先把集合換成該單一
|
|
124
|
+
// block 再作用." One helper so every operation asks the question the same
|
|
125
|
+
// way instead of each menu handler re-deriving it.
|
|
126
|
+
//
|
|
127
|
+
// `opBlock` MUST be a record out of `blocks` — identity is by reference, not
|
|
128
|
+
// by id (this module never touches ids) and not by line tuple (a nest three
|
|
129
|
+
// deep like "- - - a" produces two structurally identical phantoms, so a
|
|
130
|
+
// tuple compare is genuinely ambiguous). A record from anywhere else answers
|
|
131
|
+
// 'single', which is the conservative direction: it operates on one block
|
|
132
|
+
// rather than silently on N.
|
|
133
|
+
//
|
|
134
|
+
// "By reference" is a TESTED contract, not a description of the current line
|
|
135
|
+
// of code. `id` is passed through untouched and never compared, so a shallow
|
|
136
|
+
// COPY of a member — same id, same lines, different object — is a different
|
|
137
|
+
// block here and answers 'single'. test/selection.test.js pins exactly that
|
|
138
|
+
// case, because it is the only fixture that separates this from
|
|
139
|
+
// `members.some((m) => m.id === opBlock.id)`: every other fixture in the
|
|
140
|
+
// suite hands in a record straight out of `blocks`, where the two agree.
|
|
141
|
+
//
|
|
142
|
+
// A selection of exactly the grip block is 'batch' with one member, not a
|
|
143
|
+
// fallthrough to 'single'. The two answers are byte-identical for a single
|
|
144
|
+
// block today, but a caller that special-cased size 1 would drift the moment
|
|
145
|
+
// batch and single paths diverge.
|
|
146
|
+
function resolveMembership(sel, blocks, opBlock) {
|
|
147
|
+
const members = membersOf(sel, blocks);
|
|
148
|
+
if (opBlock && members.indexOf(opBlock) !== -1) return { mode: 'batch', members };
|
|
149
|
+
return { mode: 'single', members: opBlock ? [opBlock] : [] };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ── collapseTo ───────────────────────────────────────────────────────
|
|
153
|
+
// §3.3: "操作後集合塌縮為「操作結果所涵蓋的行區間」". Every structural
|
|
154
|
+
// operation declares the line range it produced; rerenderAll() re-derives
|
|
155
|
+
// the member set from that range against the freshly built block list. §4.4:
|
|
156
|
+
// if the range does not resolve, the selection is CLEARED rather than left
|
|
157
|
+
// dangling — an operation that removed everything it touched declares an
|
|
158
|
+
// empty (inverted) range, and that is not a selection of one line.
|
|
159
|
+
function collapseTo(range) {
|
|
160
|
+
if (!range) return null;
|
|
161
|
+
const s = lineOf(range.startLine);
|
|
162
|
+
const e = lineOf(range.endLine);
|
|
163
|
+
if (s === null || e === null || e < s) return null;
|
|
164
|
+
return { anchorLine: s, focusLine: e };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ── spanIsContiguous ─────────────────────────────────────────────────
|
|
168
|
+
// Batch convert/delete/duplicate rewrite their single-item entry points to
|
|
169
|
+
// take ONE contiguous index range — a loop would re-render between items and
|
|
170
|
+
// invalidate every id in between, which is the defect class recorded at
|
|
171
|
+
// client.js:3298 and :4806. This is the gate that says a set can be
|
|
172
|
+
// expressed that way.
|
|
173
|
+
//
|
|
174
|
+
// Indices are taken over the WHOLE `blocks` list, phantoms included. A
|
|
175
|
+
// no-line phantom sitting between two members ('- a\n- - b\n- c\n' ->
|
|
176
|
+
// li{1,1} | phantom{2,1} | li{2,2} | li{3,3}) is never a member, but it IS
|
|
177
|
+
// in the DOM run a batch operation would slice, and every structural path
|
|
178
|
+
// already refuses a phantom (blockOwnsNoLine). Reporting false there hands
|
|
179
|
+
// the caller a refusal instead of a range it cannot honour.
|
|
180
|
+
function spanIsContiguous(members, blocks) {
|
|
181
|
+
const list = members || [];
|
|
182
|
+
if (list.length <= 1) {
|
|
183
|
+
// 0 members has no gap to find; the caller checks emptiness itself,
|
|
184
|
+
// because "nothing selected" and "a set with a hole in it" deserve
|
|
185
|
+
// different messages.
|
|
186
|
+
return list.every((m) => (blocks || []).indexOf(m) !== -1);
|
|
187
|
+
}
|
|
188
|
+
const idx = list.map((m) => (blocks || []).indexOf(m));
|
|
189
|
+
if (idx.some((i) => i < 0)) return false;
|
|
190
|
+
idx.sort((a, b) => a - b);
|
|
191
|
+
for (let k = 1; k < idx.length; k++) if (idx[k] !== idx[k - 1] + 1) return false;
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
normalize,
|
|
197
|
+
membersOf,
|
|
198
|
+
extendTo,
|
|
199
|
+
stepFocus,
|
|
200
|
+
resolveMembership,
|
|
201
|
+
collapseTo,
|
|
202
|
+
spanIsContiguous,
|
|
203
|
+
};
|
|
204
|
+
});
|
package/lib/editor/server.js
CHANGED
|
@@ -23,6 +23,12 @@ const INDENT_CLAMP_SRC = fs.readFileSync(path.join(__dirname, 'indent-clamp.js')
|
|
|
23
23
|
// indent-clamp.js above — it only has to land before client.js reads
|
|
24
24
|
// window.md2docConvertMd.
|
|
25
25
|
const CONVERT_MD_SRC = fs.readFileSync(path.join(__dirname, 'convert-md.js'), 'utf8');
|
|
26
|
+
// S3 spec §3.3/§3.6/§4.4: the pure block multi-select model — line-range
|
|
27
|
+
// normalization, membership, Shift+arrow stepping, the §3.3 grip rule and the
|
|
28
|
+
// post-operation collapse. Same "no dependency on any other editor module"
|
|
29
|
+
// property as indent-clamp.js and convert-md.js above; it only has to land
|
|
30
|
+
// before client.js reads window.md2docSelection.
|
|
31
|
+
const SELECTION_SRC = fs.readFileSync(path.join(__dirname, 'selection.js'), 'utf8');
|
|
26
32
|
|
|
27
33
|
function readJson(req, limitBytes = 50 * 1024 * 1024) {
|
|
28
34
|
return new Promise((resolve, reject) => {
|
|
@@ -159,6 +165,7 @@ async function createEditorServer({ files, idleTimeoutMs = 30000, clientJs = '',
|
|
|
159
165
|
`<script>${HISTORY_SRC}</script>\n` +
|
|
160
166
|
`<script>${INDENT_CLAMP_SRC}</script>\n` +
|
|
161
167
|
`<script>${CONVERT_MD_SRC}</script>\n` +
|
|
168
|
+
`<script>${SELECTION_SRC}</script>\n` +
|
|
162
169
|
`<script>${clientJs}</script>\n`;
|
|
163
170
|
// Splice at the LAST "</body>" — the document's real closing tag.
|
|
164
171
|
// The first occurrence can sit inside an inlined diagram bundle's JS
|
package/lib/md2doc.js
CHANGED
|
@@ -2050,6 +2050,36 @@ ${itemsHtml}
|
|
|
2050
2050
|
output, same precedent as the lightbox selectors above. */
|
|
2051
2051
|
.ed-block { position: relative; cursor: pointer; }
|
|
2052
2052
|
.ed-block:hover { outline: 1px dashed #b0b0b0; }
|
|
2053
|
+
/* S3: the block-level multi-select tint (spec §3.6). rgba, never opaque —
|
|
2054
|
+
§3.6 says the text underneath stays readable. #3b82f6 is the editor's
|
|
2055
|
+
established blue (.ed-wys-armed:focus, .ed-li-check[data-checked="1"],
|
|
2056
|
+
.ed-te-hl).
|
|
2057
|
+
|
|
2058
|
+
The descendant rules and their !important are not decoration, they are
|
|
2059
|
+
the whole problem: <pre> (#f6f8fa), <th> (#f6f8fa), the zebra stripe
|
|
2060
|
+
tr:nth-child(even) (#fafbfc) and the sticky first column (#ffffff) all
|
|
2061
|
+
carry OPAQUE backgrounds, and the sticky column additionally paints at
|
|
2062
|
+
z-index: 1 over anything drawn beneath it. Two of those beat a plain
|
|
2063
|
+
descendant selector on specificity alone — counted:
|
|
2064
|
+
.content table tbody tr:nth-child(even) td:first-child is (0,3,4)
|
|
2065
|
+
against this rule's (0,3,2), and .content table tbody td:first-child is
|
|
2066
|
+
(0,2,3) against the same (0,3,2) — so without !important the tint is
|
|
2067
|
+
invisible on exactly the blocks a user is most likely to select. .ed-te-hl
|
|
2068
|
+
needed the identical treatment for the identical reason. This ADDS rules
|
|
2069
|
+
rather than reordering the sticky/zebra/header set, which CLAUDE.md says
|
|
2070
|
+
wins by source order and must not be moved.
|
|
2071
|
+
(No backticks in this comment: it lives inside a JS template literal.)
|
|
2072
|
+
|
|
2073
|
+
:focus outline is suppressed because the roving tabindex below focuses a
|
|
2074
|
+
.ed-block for real (spec §4.4 wants a real focus holder, not <body>) and
|
|
2075
|
+
the tint — not a second ring — is the selection's affordance. */
|
|
2076
|
+
.ed-block.ed-selected { background: rgba(59, 130, 246, 0.15); }
|
|
2077
|
+
.ed-block.ed-selected pre,
|
|
2078
|
+
.ed-block.ed-selected th,
|
|
2079
|
+
.ed-block.ed-selected tr:nth-child(even),
|
|
2080
|
+
.ed-block.ed-selected tbody td:first-child,
|
|
2081
|
+
.ed-block.ed-selected thead th:first-child { background: rgba(59, 130, 246, 0.15) !important; }
|
|
2082
|
+
.ed-block.ed-selected:focus { outline: none; }
|
|
2053
2083
|
/* white-space: pre-line is LOAD-BEARING, not styling — do not relax it.
|
|
2054
2084
|
A hard-wrapped ("lazy continuation") list item's own content contains a
|
|
2055
2085
|
real newline, and its .ed-li-text is an editing host. Under the default
|
|
@@ -2111,7 +2141,9 @@ ${itemsHtml}
|
|
|
2111
2141
|
.ed-block:hover .ed-handle,
|
|
2112
2142
|
.ed-handle:focus { opacity: 1; }
|
|
2113
2143
|
.ed-handle:hover { background: rgba(0, 0, 0, 0.08); }
|
|
2114
|
-
/* The ⠿ handle's menu: 轉換成 › / 建立副本 / 刪除 / MD 原始碼 (spec §3.7)
|
|
2144
|
+
/* The ⠿ handle's menu: 轉換成 › / 建立副本 / 刪除 / MD 原始碼 (spec §3.7),
|
|
2145
|
+
each led by a 16px currentColor icon (v2.12.0; drawn in
|
|
2146
|
+
lib/editor/client.js's MENU_ICON_PATHS).
|
|
2115
2147
|
Dark translucent panel, bordered rows — same visual language as
|
|
2116
2148
|
.ed-seltb below.
|
|
2117
2149
|
|
|
@@ -2135,7 +2167,23 @@ ${itemsHtml}
|
|
|
2135
2167
|
background: rgba(255, 255, 255, 0.08); color: inherit;
|
|
2136
2168
|
font: inherit; font-size: 12px; line-height: 1;
|
|
2137
2169
|
text-align: left; white-space: nowrap; cursor: pointer;
|
|
2138
|
-
|
|
2170
|
+
/* v2.12.0: a flex ROW so the icon and the label share a baseline-free
|
|
2171
|
+
centre line and a fixed gap. The row is still one flex item of the
|
|
2172
|
+
column above, and align-items: stretch there keeps every row the same
|
|
2173
|
+
width, so S2's one-left-edge / monotonic-top invariants are untouched.
|
|
2174
|
+
The submenu's own buttons carry this class and no icon; a lone text node
|
|
2175
|
+
is an anonymous flex item at flex-start, i.e. exactly where text-align
|
|
2176
|
+
put it. */
|
|
2177
|
+
display: flex; align-items: center; gap: 8px;
|
|
2178
|
+
}
|
|
2179
|
+
/* Notion's own sizing. The stroke colour is NOT set here on purpose: the
|
|
2180
|
+
markup carries stroke="currentColor" (lib/editor/client.js), so the icon
|
|
2181
|
+
is whatever colour .ed-handle-menu's own color property is, and follows a
|
|
2182
|
+
retheme for free. A hex here would break that silently. (No backticks in
|
|
2183
|
+
this comment: it lives inside a JS template literal.)
|
|
2184
|
+
flex: 0 0 auto because a 16px-wide flex item next to a nowrap label
|
|
2185
|
+
would otherwise be the thing that shrinks. */
|
|
2186
|
+
.ed-menu-icon { flex: 0 0 auto; width: 16px; height: 16px; }
|
|
2139
2187
|
/* The 轉換成 submenu carries BOTH classes, so it inherits every rule above
|
|
2140
2188
|
and only overrides the horizontal offset. It is a CHILD of the menu, and
|
|
2141
2189
|
the menu is itself position:absolute, so the menu's own padding box is
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@helping-ai-workflow/md2doc",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.12.0",
|
|
4
4
|
"description": "Markdown → HTML / PDF renderer with WaveDrom, Mermaid, and Graphviz support",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"markdown",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"preinstall": "node scripts/preinstall.js",
|
|
39
|
-
"test": "node test/md2doc.test.js && node test/blockmap.test.js && node test/images.test.js && node test/scroll-anchor.test.js && node test/lightbox.test.js && node test/lightbox-anno.test.js && node test/lightbox-anno-style.test.js && node test/reader-panels.test.js && node test/cli.test.js && node test/code-operator.test.js && node test/convert-md.test.js && node test/render-api.test.js && node test/lineops.test.js && node test/indent-clamp.test.js && node test/editmode-render.test.js && node test/editmode-wavedrom-reinit.test.js && node test/editor-server.test.js && node test/cli-edit.test.js && node test/open-viewer.test.js && node test/editor-client.test.js && node test/editor-client-runtime.test.js && node test/roundtrip.test.js && node test/byte-stability.test.js && node test/editor-server-throw.test.js && node test/editor-reader-rebind.test.js && node test/inline-md.test.js && node test/table-md.test.js && node test/gate-compat.test.js && node test/history.test.js && node test/list-md.test.js"
|
|
39
|
+
"test": "node test/md2doc.test.js && node test/blockmap.test.js && node test/images.test.js && node test/scroll-anchor.test.js && node test/lightbox.test.js && node test/lightbox-anno.test.js && node test/lightbox-anno-style.test.js && node test/reader-panels.test.js && node test/cli.test.js && node test/code-operator.test.js && node test/convert-md.test.js && node test/render-api.test.js && node test/lineops.test.js && node test/indent-clamp.test.js && node test/selection.test.js && node test/editmode-render.test.js && node test/editmode-wavedrom-reinit.test.js && node test/editor-server.test.js && node test/cli-edit.test.js && node test/open-viewer.test.js && node test/editor-client.test.js && node test/editor-client-runtime.test.js && node test/roundtrip.test.js && node test/byte-stability.test.js && node test/editor-server-throw.test.js && node test/editor-reader-rebind.test.js && node test/inline-md.test.js && node test/table-md.test.js && node test/gate-compat.test.js && node test/history.test.js && node test/list-md.test.js"
|
|
40
40
|
},
|
|
41
41
|
"repository": {
|
|
42
42
|
"type": "git",
|