@helping-ai-workflow/md2doc 3.2.1 → 3.4.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/README.md +33 -8
- package/THIRD-PARTY-NOTICES.md +44 -0
- package/lib/drawio.js +178 -0
- package/lib/editor/blockmap.js +225 -17
- package/lib/editor/cli.js +50 -1
- package/lib/editor/client.js +5376 -173
- package/lib/editor/inline-md.js +122 -12
- package/lib/editor/lineops.js +190 -3
- package/lib/editor/server.js +398 -6
- package/lib/editor/toolbar-model.js +118 -20
- package/lib/editor/wave-codec.js +2631 -0
- package/lib/editor/wave-geometry.js +320 -0
- package/lib/editor/wave-store.js +950 -0
- package/lib/editor/wave-ui.js +2124 -0
- package/lib/md2doc.js +874 -32
- package/package.json +14 -6
- package/vendor/drawio/LICENSE +201 -0
- package/vendor/drawio/NOTICE +109 -0
- package/vendor/drawio/README.md +25 -0
- package/vendor/drawio/viewer-static.min.js +8596 -0
|
@@ -11,11 +11,14 @@
|
|
|
11
11
|
else root.md2docToolbarModel = factory();
|
|
12
12
|
})(typeof self !== 'undefined' ? self : this, function () {
|
|
13
13
|
|
|
14
|
-
// The
|
|
15
|
-
// browser-reachable export path)
|
|
16
|
-
//
|
|
17
|
-
// between consecutive groups).
|
|
14
|
+
// The 23-button roster, spec §4 table (`export▾` moved out of scope — no
|
|
15
|
+
// browser-reachable export path) plus the v3.4.0 §3 `save` button. Seven
|
|
16
|
+
// groups, in render order; group order here doubles as separator placement
|
|
17
|
+
// for the caller (a separator sits between consecutive groups).
|
|
18
18
|
const BUTTON_DEFS = [
|
|
19
|
+
// file
|
|
20
|
+
{ id: 'save', group: 'file', label: '💾', icon: '💾', title: '儲存 (Ctrl+S)', toggle: false },
|
|
21
|
+
|
|
19
22
|
// history
|
|
20
23
|
{ id: 'undo', group: 'history', label: 'Undo', icon: '↶', title: '復原 (Ctrl+Z)', toggle: false },
|
|
21
24
|
{ id: 'redo', group: 'history', label: 'Redo', icon: '↷', title: '重做 (Ctrl+Y)', toggle: false },
|
|
@@ -29,11 +32,11 @@ const BUTTON_DEFS = [
|
|
|
29
32
|
{ id: 'check', group: 'block', label: '☑', icon: '☑', title: '待辦清單', toggle: false },
|
|
30
33
|
|
|
31
34
|
// inline
|
|
32
|
-
{ id: 'bold', group: 'inline', label: 'B', icon: 'B', title: '粗體', toggle:
|
|
33
|
-
{ id: 'italic', group: 'inline', label: 'I', icon: 'I', title: '斜體', toggle:
|
|
34
|
-
{ id: 'strike', group: 'inline', label: 'S', icon: 'S', title: '刪除線', toggle:
|
|
35
|
-
{ id: 'inline-code', group: 'inline', label: '`', icon: '`', title: '行內程式碼', toggle:
|
|
36
|
-
{ id: 'link', group: 'inline', label: '🔗', icon: '🔗', title: '連結', toggle:
|
|
35
|
+
{ id: 'bold', group: 'inline', label: 'B', icon: 'B', title: '粗體', toggle: true },
|
|
36
|
+
{ id: 'italic', group: 'inline', label: 'I', icon: 'I', title: '斜體', toggle: true },
|
|
37
|
+
{ id: 'strike', group: 'inline', label: 'S', icon: 'S', title: '刪除線', toggle: true },
|
|
38
|
+
{ id: 'inline-code', group: 'inline', label: '`', icon: '`', title: '行內程式碼', toggle: true },
|
|
39
|
+
{ id: 'link', group: 'inline', label: '🔗', icon: '🔗', title: '連結', toggle: true },
|
|
37
40
|
|
|
38
41
|
// indent
|
|
39
42
|
{ id: 'outdent', group: 'indent', label: '⇤', icon: '⇤', title: '減少縮排', toggle: false },
|
|
@@ -59,14 +62,53 @@ const BUTTON_DEFS = [
|
|
|
59
62
|
|
|
60
63
|
const BUTTONS = Object.freeze(BUTTON_DEFS.map((b) => Object.freeze(Object.assign({}, b))));
|
|
61
64
|
|
|
62
|
-
const GROUPS = Object.freeze(['history', 'block', 'inline', 'indent', 'insert', 'view']);
|
|
65
|
+
const GROUPS = Object.freeze(['file', 'history', 'block', 'inline', 'indent', 'insert', 'view']);
|
|
63
66
|
|
|
64
67
|
// Buttons that stay enabled even when there is no block at all (the zeroed
|
|
65
68
|
// state right after rerenderAll, before any block gets focus/selection).
|
|
66
|
-
|
|
69
|
+
// v3.4.0 §3: 'save' is here because unsaved changes have nothing to do with
|
|
70
|
+
// where the caret is — a document can be dirty with no block focused at all.
|
|
71
|
+
// NOTE (ruling P2): this entry does NOT currently change save's behaviour.
|
|
72
|
+
// `state.save.disabled = !dirty` below runs AFTER the no-block loop that
|
|
73
|
+
// reads this set, so it unconditionally overwrites whatever the no-block
|
|
74
|
+
// loop decided for 'save' — today the two never disagree in a way that
|
|
75
|
+
// shows through. This entry is kept anyway because it records the intent
|
|
76
|
+
// ("save must not be gateable on hasBlock") independently of where the
|
|
77
|
+
// `!dirty` line happens to sit; if that line is ever moved above the
|
|
78
|
+
// no-block loop, this set becomes the only thing still enforcing it. Do not
|
|
79
|
+
// delete it as "dead code" without re-checking that ordering.
|
|
80
|
+
const NO_BLOCK_ALLOWED = new Set(['undo', 'redo', 'outline', 'preview', 'save']);
|
|
81
|
+
|
|
82
|
+
// The mark element each inline button toggles. This module cannot ask the
|
|
83
|
+
// DOM which marks a selection sits in, so the client measures that and hands
|
|
84
|
+
// the answer in as `ctx.marks` — keyed by TAG, not by button id, because
|
|
85
|
+
// .ed-seltb carries a U button with no counterpart in this roster and both
|
|
86
|
+
// toolbars are painted from one measurement. The mapping from button to tag
|
|
87
|
+
// has to live somewhere, and here is where the button ids are.
|
|
88
|
+
const MARK_TAG_BY_ID = Object.freeze({
|
|
89
|
+
bold: 'STRONG',
|
|
90
|
+
italic: 'EM',
|
|
91
|
+
strike: 'DEL',
|
|
92
|
+
'inline-code': 'CODE',
|
|
93
|
+
link: 'A',
|
|
94
|
+
});
|
|
67
95
|
|
|
68
|
-
//
|
|
69
|
-
//
|
|
96
|
+
// The inline-format ids, read off the map above rather than retyped. The
|
|
97
|
+
// `hasSelection` rule below used to carry a hand-written copy of this list;
|
|
98
|
+
// the marks rule under it would have been a second.
|
|
99
|
+
const MARK_IDS = Object.freeze(Object.keys(MARK_TAG_BY_ID));
|
|
100
|
+
|
|
101
|
+
// The per-tag values `ctx.marks` may carry, and what each one means for the
|
|
102
|
+
// button that toggles that tag. The client's selectionMarkStates() is what
|
|
103
|
+
// produces them; the names are shared vocabulary, not a re-derivation.
|
|
104
|
+
// 'whole' the selection lies inside one mark of that tag
|
|
105
|
+
// 'partial' the selection overlaps marks of that tag without lying in one
|
|
106
|
+
// 'none' no mark of that tag is involved
|
|
107
|
+
// 'inert' pressing the button would do nothing at all
|
|
108
|
+
|
|
109
|
+
// ctx: {blockType, indent, headingDepth, inList, listOrdered, hasSelection,
|
|
110
|
+
// mode, marks}
|
|
111
|
+
// -> { [id]: {active: bool, mixed: bool, disabled: bool, label?: string} }
|
|
70
112
|
function deriveState(ctx) {
|
|
71
113
|
const c = ctx || {};
|
|
72
114
|
const blockType = c.blockType;
|
|
@@ -75,11 +117,12 @@ function deriveState(ctx) {
|
|
|
75
117
|
const inList = !!c.inList;
|
|
76
118
|
const listOrdered = !!c.listOrdered;
|
|
77
119
|
const mode = c.mode;
|
|
120
|
+
const dirty = !!c.dirty;
|
|
78
121
|
const hasBlock = blockType !== null && blockType !== undefined;
|
|
79
122
|
|
|
80
123
|
const state = {};
|
|
81
124
|
for (const b of BUTTONS) {
|
|
82
|
-
state[b.id] = { active: false, disabled: false };
|
|
125
|
+
state[b.id] = { active: false, mixed: false, disabled: false };
|
|
83
126
|
}
|
|
84
127
|
|
|
85
128
|
// v3.2.1: the mode button names the NEXT state, never the current one.
|
|
@@ -152,24 +195,79 @@ function deriveState(ctx) {
|
|
|
152
195
|
// refuse for reasons this ctx does not carry (a structural list gate, a
|
|
153
196
|
// block that owns no source line).
|
|
154
197
|
if (!c.hasSelection) {
|
|
155
|
-
for (const id of
|
|
198
|
+
for (const id of MARK_IDS) {
|
|
156
199
|
state[id].disabled = true;
|
|
157
200
|
}
|
|
158
201
|
}
|
|
159
202
|
|
|
203
|
+
// v3.3.0 (F2): what the selection is already marked as. Before this the ctx
|
|
204
|
+
// carried one boolean about the selection and nothing about its identity.
|
|
205
|
+
// MEASURED on the build before this change, reading the fixed toolbar's B
|
|
206
|
+
// over a selection wholly inside a <strong>, one straddling its edge, and one
|
|
207
|
+
// in plain text: aria-pressed came back null on all three — the buttons were
|
|
208
|
+
// `toggle: false`, so the client's aria-pressed write skipped them.
|
|
209
|
+
//
|
|
210
|
+
// `active` and `mixed` are separate booleans rather than one tri-state
|
|
211
|
+
// because `active` is what the toggle-roster test sweeps for and what the
|
|
212
|
+
// rest of this function already speaks. The client turns the pair into
|
|
213
|
+
// aria-pressed true / mixed / false.
|
|
214
|
+
//
|
|
215
|
+
// 'inert' disables. It is NOT the same question as `hasSelection`: that one
|
|
216
|
+
// asks whether the action would refuse the selection outright, this one asks
|
|
217
|
+
// whether the branch the action would actually take is a no-op. A press on
|
|
218
|
+
// an inert button leaves the document exactly as it was.
|
|
219
|
+
const marks = c.marks;
|
|
220
|
+
if (marks) {
|
|
221
|
+
for (const id of MARK_IDS) {
|
|
222
|
+
const m = marks[MARK_TAG_BY_ID[id]];
|
|
223
|
+
state[id].active = m === 'whole';
|
|
224
|
+
state[id].mixed = m === 'partial';
|
|
225
|
+
if (m === 'inert') state[id].disabled = true;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// v3.4.0 §3: the save button IS the save-state indicator — grey when the
|
|
230
|
+
// document matches disk, lit when it does not. The truth stays
|
|
231
|
+
// single-sourced: client.js's `documentIsDirty()` — since v3.4.0 batch3
|
|
232
|
+
// Task 7 that is `stack.isDirty() || burstHasUncommittedEdit() ||
|
|
233
|
+
// waveUnwrittenEdit` — arrives as ctx.dirty and is not recomputed here.
|
|
234
|
+
// This runs before the source-mode override below so that override can
|
|
235
|
+
// still give save its own exemption.
|
|
236
|
+
state.save.disabled = !dirty;
|
|
237
|
+
|
|
160
238
|
// mode 'source' is the full-document raw-source escape hatch: everything
|
|
161
239
|
// is disabled except the mode button that got you there (id 'preview',
|
|
162
|
-
// which in v3.2.1 is the edit <-> source toggle)
|
|
163
|
-
//
|
|
164
|
-
//
|
|
240
|
+
// which in v3.2.1 is the edit <-> source toggle), since T11-2 'outline'
|
|
241
|
+
// (☰), and since v3.4.0 §3 'save'. The mode button is still the ONLY way
|
|
242
|
+
// back to 'edit' — that has not changed. 'outline' stays reachable because
|
|
243
|
+
// it is what opens the sidebar drawer (toggleOutlineSidebar() in
|
|
244
|
+
// lib/editor/client.js), and the fixed `.sidebar-toggle` button that used
|
|
245
|
+
// to reach the drawer another way is hidden in edit mode entirely (see the
|
|
246
|
+
// T11-2 comment in lib/md2doc.js) — a disabled ☰ here would leave source
|
|
247
|
+
// mode with no way to open that drawer at all. 'save' stays reachable
|
|
248
|
+
// because source mode can still hold unsaved changes (the user can type
|
|
249
|
+
// into the source textarea) — see the per-button comment below for the
|
|
250
|
+
// exact shape of the exemption. This override still runs last and still
|
|
251
|
+
// overrides every disabled flag computed above for every other button; do
|
|
252
|
+
// not narrow it for anything but these three ids.
|
|
165
253
|
if (mode === 'source') {
|
|
166
254
|
for (const b of BUTTONS) {
|
|
167
|
-
state[b.id].disabled =
|
|
255
|
+
if (b.id === 'preview' || b.id === 'outline') { state[b.id].disabled = false; continue; }
|
|
256
|
+
// v3.4.0 §3: save is a third exemption, for the same shape of reason
|
|
257
|
+
// outline already had one — source mode can still hold unsaved
|
|
258
|
+
// changes (the user can type into the source textarea), so forcing
|
|
259
|
+
// the only save button grey here would make a mode that holds
|
|
260
|
+
// unsaved work with no way out of it. The exemption is from the MODE
|
|
261
|
+
// forcing it grey, not from being grey — a clean document still shows
|
|
262
|
+
// it disabled in source mode (see the `state.save.disabled = !dirty`
|
|
263
|
+
// line above, which already ran and is left untouched here).
|
|
264
|
+
if (b.id === 'save') continue;
|
|
265
|
+
state[b.id].disabled = true;
|
|
168
266
|
}
|
|
169
267
|
}
|
|
170
268
|
|
|
171
269
|
return state;
|
|
172
270
|
}
|
|
173
271
|
|
|
174
|
-
return { BUTTONS, GROUPS, deriveState };
|
|
272
|
+
return { BUTTONS, GROUPS, MARK_TAG_BY_ID, deriveState };
|
|
175
273
|
});
|