@helping-ai-workflow/md2doc 3.9.1 → 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 +229 -146
- 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
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/* Lucide icons v0.469.0 (ISC License, https://lucide.dev/license), only the ones
|
|
3
|
+
the editor draws. UMD like toolbar-model.js: require()-able in node for the
|
|
4
|
+
tests, injected into the edit page as window.md2docIcons (lib/editor/server.js). */
|
|
5
|
+
(function (root, factory) {
|
|
6
|
+
if (typeof module === 'object' && module.exports) module.exports = factory();
|
|
7
|
+
else root.md2docIcons = factory();
|
|
8
|
+
})(typeof self !== 'undefined' ? self : this, function () {
|
|
9
|
+
const BODY = {
|
|
10
|
+
"arrow-down-to-line": "<path d=\"M12 17V3\" /><path d=\"m6 11 6 6 6-6\" /><path d=\"M19 21H5\" />",
|
|
11
|
+
"arrow-up-to-line": "<path d=\"M5 3h14\" /><path d=\"m18 13-6-6-6 6\" /><path d=\"M12 7v14\" />",
|
|
12
|
+
"bold": "<path d=\"M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8\" />",
|
|
13
|
+
"check": "<path d=\"M20 6 9 17l-5-5\" />",
|
|
14
|
+
"circle-alert": "<circle cx=\"12\" cy=\"12\" r=\"10\" /><line x1=\"12\" x2=\"12\" y1=\"8\" y2=\"12\" /><line x1=\"12\" x2=\"12.01\" y1=\"16\" y2=\"16\" />",
|
|
15
|
+
"code": "<polyline points=\"16 18 22 12 16 6\" /><polyline points=\"8 6 2 12 8 18\" />",
|
|
16
|
+
"grip-vertical": "<circle cx=\"9\" cy=\"12\" r=\"1\" /><circle cx=\"9\" cy=\"5\" r=\"1\" /><circle cx=\"9\" cy=\"19\" r=\"1\" /><circle cx=\"15\" cy=\"12\" r=\"1\" /><circle cx=\"15\" cy=\"5\" r=\"1\" /><circle cx=\"15\" cy=\"19\" r=\"1\" />",
|
|
17
|
+
"heading": "<path d=\"M6 12h12\" /><path d=\"M6 20V4\" /><path d=\"M18 20V4\" />",
|
|
18
|
+
"image": "<rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" ry=\"2\" /><circle cx=\"9\" cy=\"9\" r=\"2\" /><path d=\"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21\" />",
|
|
19
|
+
"indent-decrease": "<path d=\"M21 12H11\" /><path d=\"M21 18H11\" /><path d=\"M21 6H11\" /><path d=\"m7 8-4 4 4 4\" />",
|
|
20
|
+
"indent-increase": "<path d=\"M21 12H11\" /><path d=\"M21 18H11\" /><path d=\"M21 6H11\" /><path d=\"m3 8 4 4-4 4\" />",
|
|
21
|
+
"info": "<circle cx=\"12\" cy=\"12\" r=\"10\" /><path d=\"M12 16v-4\" /><path d=\"M12 8h.01\" />",
|
|
22
|
+
"italic": "<line x1=\"19\" x2=\"10\" y1=\"4\" y2=\"4\" /><line x1=\"14\" x2=\"5\" y1=\"20\" y2=\"20\" /><line x1=\"15\" x2=\"9\" y1=\"4\" y2=\"20\" />",
|
|
23
|
+
"link": "<path d=\"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71\" /><path d=\"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71\" />",
|
|
24
|
+
"list": "<path d=\"M3 12h.01\" /><path d=\"M3 18h.01\" /><path d=\"M3 6h.01\" /><path d=\"M8 12h13\" /><path d=\"M8 18h13\" /><path d=\"M8 6h13\" />",
|
|
25
|
+
"list-checks": "<path d=\"m3 17 2 2 4-4\" /><path d=\"m3 7 2 2 4-4\" /><path d=\"M13 6h8\" /><path d=\"M13 12h8\" /><path d=\"M13 18h8\" />",
|
|
26
|
+
"list-ordered": "<path d=\"M10 12h11\" /><path d=\"M10 18h11\" /><path d=\"M10 6h11\" /><path d=\"M4 10h2\" /><path d=\"M4 6h1v4\" /><path d=\"M6 18H4c0-1 2-2 2-3s-1-1.5-2-1\" />",
|
|
27
|
+
"minus": "<path d=\"M5 12h14\" />",
|
|
28
|
+
"panel-left": "<rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" /><path d=\"M9 3v18\" />",
|
|
29
|
+
"plus": "<path d=\"M5 12h14\" /><path d=\"M12 5v14\" />",
|
|
30
|
+
"quote": "<path d=\"M16 3a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2 1 1 0 0 1 1 1v1a2 2 0 0 1-2 2 1 1 0 0 0-1 1v2a1 1 0 0 0 1 1 6 6 0 0 0 6-6V5a2 2 0 0 0-2-2z\" /><path d=\"M5 3a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2 1 1 0 0 1 1 1v1a2 2 0 0 1-2 2 1 1 0 0 0-1 1v2a1 1 0 0 0 1 1 6 6 0 0 0 6-6V5a2 2 0 0 0-2-2z\" />",
|
|
31
|
+
"redo-2": "<path d=\"m15 14 5-5-5-5\" /><path d=\"M20 9H9.5A5.5 5.5 0 0 0 4 14.5A5.5 5.5 0 0 0 9.5 20H13\" />",
|
|
32
|
+
"save": "<path d=\"M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z\" /><path d=\"M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7\" /><path d=\"M7 3v4a1 1 0 0 0 1 1h7\" />",
|
|
33
|
+
"square-code": "<path d=\"M10 9.5 8 12l2 2.5\" /><path d=\"m14 9.5 2 2.5-2 2.5\" /><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" />",
|
|
34
|
+
"strikethrough": "<path d=\"M16 4H9a3 3 0 0 0-2.83 4\" /><path d=\"M14 12a4 4 0 0 1 0 8H6\" /><line x1=\"4\" x2=\"20\" y1=\"12\" y2=\"12\" />",
|
|
35
|
+
"table": "<path d=\"M12 3v18\" /><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" /><path d=\"M3 9h18\" /><path d=\"M3 15h18\" />",
|
|
36
|
+
"underline": "<path d=\"M6 4v6a6 6 0 0 0 12 0V4\" /><line x1=\"4\" x2=\"20\" y1=\"20\" y2=\"20\" />",
|
|
37
|
+
"undo-2": "<path d=\"M9 14 4 9l5-5\" /><path d=\"M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11\" />",
|
|
38
|
+
"x": "<path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />"
|
|
39
|
+
};
|
|
40
|
+
const NAMES = Object.freeze(Object.keys(BODY));
|
|
41
|
+
function svg(name, size) {
|
|
42
|
+
const s = size || 16;
|
|
43
|
+
return '<svg class="ed-ico" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="' + s + '" height="' + s + '" ' +
|
|
44
|
+
'fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" ' +
|
|
45
|
+
'aria-hidden="true" focusable="false">' + BODY[name] + '</svg>';
|
|
46
|
+
}
|
|
47
|
+
return { NAMES, svg };
|
|
48
|
+
});
|
package/lib/editor/server.js
CHANGED
|
@@ -43,12 +43,12 @@ const SELECTION_SRC = fs.readFileSync(path.join(__dirname, 'selection.js'), 'utf
|
|
|
43
43
|
// client.js reads window.md2docCellRange.
|
|
44
44
|
const CELL_RANGE_SRC = fs.readFileSync(path.join(__dirname, 'cell-range.js'), 'utf8');
|
|
45
45
|
// v3.1.0 Task E: the four Phase-1 modules behind the visible toolbar, the
|
|
46
|
-
// paste pipeline
|
|
47
|
-
// source escape hatch. Same "pure module, no dependency on any other editor
|
|
46
|
+
// paste pipeline and the image-drop asset endpoint (the whole-document
|
|
47
|
+
// source escape hatch went with v3.10.0). Same "pure module, no dependency on any other editor
|
|
48
48
|
// module" property as indent-clamp.js / convert-md.js / selection.js above —
|
|
49
49
|
// each only has to land before client.js reads its window global.
|
|
50
50
|
const TOOLBAR_MODEL_SRC = fs.readFileSync(path.join(__dirname, 'toolbar-model.js'), 'utf8');
|
|
51
|
-
const
|
|
51
|
+
const ICONS_SRC = fs.readFileSync(path.join(__dirname, 'icons.js'), 'utf8');
|
|
52
52
|
const ASSET_SRC = fs.readFileSync(path.join(__dirname, 'asset.js'), 'utf8');
|
|
53
53
|
// turndown's BROWSER build. node's `require('turndown')` resolves to
|
|
54
54
|
// lib/turndown.cjs.js, which is not loadable as a plain <script> tag — the
|
|
@@ -736,8 +736,8 @@ async function createEditorServer({
|
|
|
736
736
|
`<script>${PATCHMAP_SRC}</script>\n` +
|
|
737
737
|
`<script>${SELECTION_SRC}</script>\n` +
|
|
738
738
|
`<script>${CELL_RANGE_SRC}</script>\n` +
|
|
739
|
+
`<script>${ICONS_SRC}</script>\n` +
|
|
739
740
|
`<script>${TOOLBAR_MODEL_SRC}</script>\n` +
|
|
740
|
-
`<script>${DOCSOURCE_SRC}</script>\n` +
|
|
741
741
|
`<script>${ASSET_SRC}</script>\n` +
|
|
742
742
|
`<script>${TURNDOWN_SRC}</script>\n` +
|
|
743
743
|
`<script>${PASTE_MD_SRC}</script>\n` +
|
|
@@ -982,6 +982,16 @@ async function createEditorServer({
|
|
|
982
982
|
} catch (e) {
|
|
983
983
|
fileId = undefined; drawioClientId = undefined; drawioAck = undefined;
|
|
984
984
|
}
|
|
985
|
+
// v3.10.0: the client compares this with its own baseline to notice an
|
|
986
|
+
// edit made outside the browser. A header, not a body: the bare 204
|
|
987
|
+
// ("nothing stale") is part of the drawio contract and stays bodiless.
|
|
988
|
+
const pingFile = (typeof fileId === 'number') ? absFiles[fileId] : undefined;
|
|
989
|
+
// Async on purpose: this route fires every 10s from every open tab, and
|
|
990
|
+
// the drawio fast path below is pinned to ZERO fs.statSync calls for a
|
|
991
|
+
// document with no drawio references (test/editor-server.test.js (a)).
|
|
992
|
+
if (pingFile) {
|
|
993
|
+
try { res.setHeader('X-Md2doc-Mtime', String((await fs.promises.stat(pingFile)).mtimeMs)); } catch (e) { /* file gone: no header */ }
|
|
994
|
+
}
|
|
985
995
|
// Ack FIRST, then re-check: a client that has just confirmed it
|
|
986
996
|
// applied version N must get a 204 on this same beat unless the file
|
|
987
997
|
// has moved on past N since.
|
|
@@ -11,53 +11,47 @@
|
|
|
11
11
|
else root.md2docToolbarModel = factory();
|
|
12
12
|
})(typeof self !== 'undefined' ? self : this, function () {
|
|
13
13
|
|
|
14
|
-
// The
|
|
15
|
-
// browser-reachable export path) plus the v3.4.0 §3 `save` button.
|
|
14
|
+
// The 22-button roster, spec §4 table (`export▾` moved out of scope — no
|
|
15
|
+
// browser-reachable export path) plus the v3.4.0 §3 `save` button.
|
|
16
|
+
// v3.10.0 removed the edit/source mode button with source mode itself. Seven
|
|
16
17
|
// groups, in render order; group order here doubles as separator placement
|
|
17
18
|
// for the caller (a separator sits between consecutive groups).
|
|
18
19
|
const BUTTON_DEFS = [
|
|
19
20
|
// file
|
|
20
|
-
{ id: 'save', group: 'file', label: '💾', icon: '
|
|
21
|
+
{ id: 'save', group: 'file', label: '💾', icon: 'save', title: '儲存 (Ctrl+S)', toggle: false },
|
|
21
22
|
|
|
22
23
|
// history
|
|
23
|
-
{ id: 'undo', group: 'history', label: 'Undo', icon: '
|
|
24
|
-
{ id: 'redo', group: 'history', label: 'Redo', icon: '
|
|
24
|
+
{ id: 'undo', group: 'history', label: 'Undo', icon: 'undo-2', title: '復原 (Ctrl+Z)', toggle: false },
|
|
25
|
+
{ id: 'redo', group: 'history', label: 'Redo', icon: 'redo-2', title: '重做 (Ctrl+Y)', toggle: false },
|
|
25
26
|
|
|
26
27
|
// block
|
|
27
|
-
{ id: 'headings', group: 'block', label: 'H', icon: '
|
|
28
|
-
{ id: 'quote', group: 'block', label: '❝', icon: '
|
|
29
|
-
{ id: 'code', group: 'block', label: '</>', icon: '
|
|
30
|
-
{ id: 'list', group: 'block', label: '•', icon: '
|
|
31
|
-
{ id: 'ordered-list', group: 'block', label: '1.', icon: '
|
|
32
|
-
{ id: 'check', group: 'block', label: '☑', icon: '
|
|
28
|
+
{ id: 'headings', group: 'block', label: 'H', icon: 'heading', title: '標題', toggle: true },
|
|
29
|
+
{ id: 'quote', group: 'block', label: '❝', icon: 'quote', title: '引用', toggle: true },
|
|
30
|
+
{ id: 'code', group: 'block', label: '</>', icon: 'square-code', title: '程式碼區塊', toggle: true },
|
|
31
|
+
{ id: 'list', group: 'block', label: '•', icon: 'list', title: '項目符號列表', toggle: true },
|
|
32
|
+
{ id: 'ordered-list', group: 'block', label: '1.', icon: 'list-ordered', title: '編號列表', toggle: true },
|
|
33
|
+
{ id: 'check', group: 'block', label: '☑', icon: 'list-checks', title: '待辦清單', toggle: false },
|
|
33
34
|
|
|
34
35
|
// inline
|
|
35
|
-
{ id: 'bold', group: 'inline', label: 'B', icon: '
|
|
36
|
-
{ id: 'italic', group: 'inline', label: 'I', icon: '
|
|
37
|
-
{ id: 'strike', group: 'inline', label: 'S', icon: '
|
|
38
|
-
{ id: 'inline-code', group: 'inline', label: '`', icon: '
|
|
39
|
-
{ id: 'link', group: 'inline', label: '🔗', icon: '
|
|
36
|
+
{ id: 'bold', group: 'inline', label: 'B', icon: 'bold', title: '粗體', toggle: true },
|
|
37
|
+
{ id: 'italic', group: 'inline', label: 'I', icon: 'italic', title: '斜體', toggle: true },
|
|
38
|
+
{ id: 'strike', group: 'inline', label: 'S', icon: 'strikethrough', title: '刪除線', toggle: true },
|
|
39
|
+
{ id: 'inline-code', group: 'inline', label: '`', icon: 'code', title: '行內程式碼', toggle: true },
|
|
40
|
+
{ id: 'link', group: 'inline', label: '🔗', icon: 'link', title: '連結', toggle: true },
|
|
40
41
|
|
|
41
42
|
// indent
|
|
42
|
-
{ id: 'outdent', group: 'indent', label: '⇤', icon: '
|
|
43
|
-
{ id: 'indent', group: 'indent', label: '⇥', icon: '
|
|
43
|
+
{ id: 'outdent', group: 'indent', label: '⇤', icon: 'indent-decrease', title: '減少縮排', toggle: false },
|
|
44
|
+
{ id: 'indent', group: 'indent', label: '⇥', icon: 'indent-increase', title: '增加縮排', toggle: false },
|
|
44
45
|
|
|
45
46
|
// insert
|
|
46
|
-
{ id: 'table', group: 'insert', label: '⊞', icon: '
|
|
47
|
-
{ id: 'insert-before', group: 'insert', label: '⬆', icon: '
|
|
48
|
-
{ id: 'insert-after', group: 'insert', label: '⬇', icon: '
|
|
49
|
-
{ id: 'line', group: 'insert', label: '―', icon: '
|
|
50
|
-
{ id: 'image', group: 'insert', label: '🖼', icon: '
|
|
47
|
+
{ id: 'table', group: 'insert', label: '⊞', icon: 'table', title: '插入表格', toggle: false },
|
|
48
|
+
{ id: 'insert-before', group: 'insert', label: '⬆', icon: 'arrow-up-to-line', title: '在上方插入區塊', toggle: false },
|
|
49
|
+
{ id: 'insert-after', group: 'insert', label: '⬇', icon: 'arrow-down-to-line', title: '在下方插入區塊', toggle: false },
|
|
50
|
+
{ id: 'line', group: 'insert', label: '―', icon: 'minus', title: '插入分隔線', toggle: false },
|
|
51
|
+
{ id: 'image', group: 'insert', label: '🖼', icon: 'image', title: '插入圖片', toggle: false },
|
|
51
52
|
|
|
52
53
|
// view
|
|
53
|
-
{ id: 'outline', group: 'view', label: '☰', icon: '
|
|
54
|
-
// v3.2.1: 這顆是 edit <-> source 的兩態切換(preview 模式已移除)。id 保留
|
|
55
|
-
// 為 'preview' 是刻意的 —— 每一支選擇器、journey 案例與 runtime 測試都指名
|
|
56
|
-
// 它,改名只換得一個更好聽的字串。label / title 不是烘死值:deriveState()
|
|
57
|
-
// 每次都依當前 mode 覆寫成【下一個】狀態的名字,這裡的值只是 edit 模式的
|
|
58
|
-
// 起始值。toggle: false —— 兩態下 aria-pressed 講不出真話(按鈕講的是去處,
|
|
59
|
-
// 不是目前所在),真話由會變的 label / title 承擔。
|
|
60
|
-
{ id: 'preview', group: 'view', label: 'M↓', icon: 'M↓', title: '切換到原始碼模式', toggle: false },
|
|
54
|
+
{ id: 'outline', group: 'view', label: '☰', icon: 'panel-left', title: '大綱', toggle: false },
|
|
61
55
|
];
|
|
62
56
|
|
|
63
57
|
const BUTTONS = Object.freeze(BUTTON_DEFS.map((b) => Object.freeze(Object.assign({}, b))));
|
|
@@ -77,7 +71,7 @@ const GROUPS = Object.freeze(['file', 'history', 'block', 'inline', 'indent', 'i
|
|
|
77
71
|
// `!dirty` line happens to sit; if that line is ever moved above the
|
|
78
72
|
// no-block loop, this set becomes the only thing still enforcing it. Do not
|
|
79
73
|
// delete it as "dead code" without re-checking that ordering.
|
|
80
|
-
const NO_BLOCK_ALLOWED = new Set(['undo', 'redo', 'outline', '
|
|
74
|
+
const NO_BLOCK_ALLOWED = new Set(['undo', 'redo', 'outline', 'save']);
|
|
81
75
|
|
|
82
76
|
// The mark element each inline button toggles. This module cannot ask the
|
|
83
77
|
// DOM which marks a selection sits in, so the client measures that and hands
|
|
@@ -107,7 +101,7 @@ const MARK_IDS = Object.freeze(Object.keys(MARK_TAG_BY_ID));
|
|
|
107
101
|
// 'inert' pressing the button would do nothing at all
|
|
108
102
|
|
|
109
103
|
// ctx: {blockType, indent, headingDepth, inList, listOrdered, hasSelection,
|
|
110
|
-
//
|
|
104
|
+
// marks, dirty}
|
|
111
105
|
// -> { [id]: {active: bool, mixed: bool, disabled: bool, label?: string} }
|
|
112
106
|
function deriveState(ctx) {
|
|
113
107
|
const c = ctx || {};
|
|
@@ -116,7 +110,6 @@ function deriveState(ctx) {
|
|
|
116
110
|
const headingDepth = c.headingDepth;
|
|
117
111
|
const inList = !!c.inList;
|
|
118
112
|
const listOrdered = !!c.listOrdered;
|
|
119
|
-
const mode = c.mode;
|
|
120
113
|
const dirty = !!c.dirty;
|
|
121
114
|
const hasBlock = blockType !== null && blockType !== undefined;
|
|
122
115
|
|
|
@@ -125,19 +118,6 @@ function deriveState(ctx) {
|
|
|
125
118
|
state[b.id] = { active: false, mixed: false, disabled: false };
|
|
126
119
|
}
|
|
127
120
|
|
|
128
|
-
// v3.2.1: the mode button names the NEXT state, never the current one.
|
|
129
|
-
// Under the old tri-state cycle it was permanently labelled 「預覽」 and
|
|
130
|
-
// reported aria-pressed=true in BOTH non-edit modes, so pressing it twice
|
|
131
|
-
// landed in source with no grips and an interface that said nothing about
|
|
132
|
-
// where you were — the reported "stuck" defect. It carries no `active`
|
|
133
|
-
// now: with two states the honest signal is the label, and the current
|
|
134
|
-
// mode is spelled out in .ed-toolbar-status by the client.
|
|
135
|
-
// It stays enabled in source mode via the mode==='source' override below
|
|
136
|
-
// (do not gate this on inList/hasBlock — it is the escape hatch OUT of
|
|
137
|
-
// those states).
|
|
138
|
-
state.preview.label = mode === 'source' ? '✎' : 'M↓';
|
|
139
|
-
state.preview.title = mode === 'source' ? '切換到編輯模式' : '切換到原始碼模式';
|
|
140
|
-
|
|
141
121
|
// headings: dropdown whose label tracks the current heading depth.
|
|
142
122
|
state.headings.label = blockType === 'heading' ? 'H' + headingDepth : 'H';
|
|
143
123
|
state.headings.active = blockType === 'heading';
|
|
@@ -231,41 +211,8 @@ function deriveState(ctx) {
|
|
|
231
211
|
// single-sourced: client.js's `documentIsDirty()` — since v3.4.0 batch3
|
|
232
212
|
// Task 7 that is `stack.isDirty() || burstHasUncommittedEdit() ||
|
|
233
213
|
// 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
214
|
state.save.disabled = !dirty;
|
|
237
215
|
|
|
238
|
-
// mode 'source' is the full-document raw-source escape hatch: everything
|
|
239
|
-
// is disabled except the mode button that got you there (id 'preview',
|
|
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.
|
|
253
|
-
if (mode === 'source') {
|
|
254
|
-
for (const b of BUTTONS) {
|
|
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;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
216
|
return state;
|
|
270
217
|
}
|
|
271
218
|
|
package/lib/highlight.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// v3.10.0: syntax colour for fenced code, done once when the HTML is written —
|
|
3
|
+
// the reading page needs no script for it, and PDF gets it for free. Only the
|
|
4
|
+
// languages below are registered; anything else (or no language) stays plain
|
|
5
|
+
// text rather than guessing.
|
|
6
|
+
const hljs = require('highlight.js/lib/core');
|
|
7
|
+
|
|
8
|
+
const LANGS = {
|
|
9
|
+
verilog: require('highlight.js/lib/languages/verilog'),
|
|
10
|
+
python: require('highlight.js/lib/languages/python'),
|
|
11
|
+
bash: require('highlight.js/lib/languages/bash'),
|
|
12
|
+
c: require('highlight.js/lib/languages/c'),
|
|
13
|
+
cpp: require('highlight.js/lib/languages/cpp'),
|
|
14
|
+
json: require('highlight.js/lib/languages/json'),
|
|
15
|
+
yaml: require('highlight.js/lib/languages/yaml'),
|
|
16
|
+
tcl: require('highlight.js/lib/languages/tcl'),
|
|
17
|
+
makefile: require('highlight.js/lib/languages/makefile'),
|
|
18
|
+
javascript: require('highlight.js/lib/languages/javascript'),
|
|
19
|
+
diff: require('highlight.js/lib/languages/diff'),
|
|
20
|
+
plaintext: require('highlight.js/lib/languages/plaintext'),
|
|
21
|
+
};
|
|
22
|
+
for (const [name, def] of Object.entries(LANGS)) hljs.registerLanguage(name, def);
|
|
23
|
+
hljs.registerAliases(['systemverilog', 'sv', 'v'], { languageName: 'verilog' });
|
|
24
|
+
hljs.registerAliases(['sh', 'shell', 'zsh'], { languageName: 'bash' });
|
|
25
|
+
|
|
26
|
+
// `lang` is the fence's info string; only its first word names the language,
|
|
27
|
+
// case-insensitively. Returns escaped, coloured HTML, or null when the
|
|
28
|
+
// language is missing or not registered.
|
|
29
|
+
function highlightCode(code, lang) {
|
|
30
|
+
const name = String(lang || '').trim().split(/\s+/)[0].toLowerCase();
|
|
31
|
+
if (!name || !hljs.getLanguage(name)) return null;
|
|
32
|
+
return hljs.highlight(code, { language: name, ignoreIllegals: true }).value;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = { highlightCode };
|