@chengzhao-dev/dsh-quarto-book 0.4.3 → 0.6.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.en.md +3 -3
- package/README.md +3 -3
- package/lib/tools/render-check.js +5 -2
- package/package.json +5 -2
- package/skills/designing-theme/SKILL.md +3 -2
- package/skills/designing-theme/assets/theme/css/base.css +216 -55
- package/skills/designing-theme/assets/theme/css/callouts.css +32 -23
- package/skills/designing-theme/assets/theme/css/code.css +84 -2
- package/skills/designing-theme/assets/theme/css/content.css +152 -36
- package/skills/designing-theme/assets/theme/css/landing.css +175 -54
- package/skills/designing-theme/assets/theme/css/nav.css +462 -35
- package/skills/designing-theme/assets/theme/css/sidebar.css +76 -31
- package/skills/designing-theme/assets/theme/css/tokens.css +34 -16
- package/skills/designing-theme/assets/theme/includes/fonts.html +1 -1
- package/skills/designing-theme/assets/theme/includes/footer.html +16 -7
- package/skills/designing-theme/assets/theme/includes/palette.html +420 -0
- package/skills/designing-theme/assets/theme/palettes/flexoki/brand-mark.svg +3 -0
- package/skills/designing-theme/assets/theme/palettes/flexoki/favicon-dark.svg +5 -0
- package/skills/designing-theme/assets/theme/palettes/flexoki/favicon-light.svg +5 -0
- package/skills/designing-theme/assets/theme/palettes/flexoki/highlight-dark.theme +214 -0
- package/skills/designing-theme/assets/theme/palettes/flexoki/highlight-light.theme +214 -0
- package/skills/designing-theme/assets/theme/palettes/flexoki/meta.md +53 -0
- package/skills/designing-theme/assets/theme/palettes/flexoki/theme-dark.scss +43 -0
- package/skills/designing-theme/assets/theme/palettes/flexoki/theme-light.scss +40 -0
- package/skills/designing-theme/assets/theme/palettes/flexoki/tokens.css +156 -0
- package/skills/designing-theme/assets/theme/palettes/hill-light/brand-mark.svg +3 -0
- package/skills/designing-theme/assets/theme/palettes/hill-light/favicon-dark.svg +5 -0
- package/skills/designing-theme/assets/theme/palettes/hill-light/favicon-light.svg +5 -0
- package/skills/designing-theme/assets/theme/palettes/hill-light/meta.md +78 -0
- package/skills/designing-theme/assets/theme/palettes/hill-light/syntax.css +150 -0
- package/skills/designing-theme/assets/theme/palettes/hill-light/tokens.css +322 -0
- package/skills/designing-theme/assets/theme/palettes/solarized/brand-mark.svg +3 -0
- package/skills/designing-theme/assets/theme/palettes/solarized/favicon-dark.svg +5 -0
- package/skills/designing-theme/assets/theme/palettes/solarized/favicon-light.svg +5 -0
- package/skills/designing-theme/assets/theme/palettes/solarized/highlight-dark.theme +214 -0
- package/skills/designing-theme/assets/theme/palettes/solarized/highlight-light.theme +214 -0
- package/skills/designing-theme/assets/theme/palettes/solarized/meta.md +66 -0
- package/skills/designing-theme/assets/theme/palettes/solarized/syntax.css +67 -0
- package/skills/designing-theme/assets/theme/palettes/solarized/theme-dark.scss +43 -0
- package/skills/designing-theme/assets/theme/palettes/solarized/theme-light.scss +41 -0
- package/skills/designing-theme/assets/theme/palettes/solarized/tokens.css +154 -0
- package/skills/designing-theme/knowledge/reading-surface.md +27 -15
- package/skills/designing-theme/references/theme-spine.md +2 -2
- package/skills/designing-theme/references/theme-system.md +82 -36
- package/skills/designing-theme/scripts/checking-layout.py +306 -48
- package/skills/designing-theme/scripts/measuring-pages.mjs +40 -3
- package/skills/writing-quarto/examples/01-language-scope.md +3 -3
- package/skills/writing-quarto/examples/02-structure-shape.md +5 -5
- package/skills/writing-quarto/examples/03-block-after.md +2 -2
- package/skills/writing-quarto/examples/03-excerpt-lead.md +6 -6
- package/skills/writing-quarto/examples/03-layout-list.md +15 -15
- package/skills/writing-quarto/examples/03-qmd-elements.md +4 -4
- package/skills/writing-quarto/examples/04-paragraph-cases.md +5 -5
- package/skills/writing-quarto/examples/04-paragraph-opening.md +3 -3
- package/skills/writing-quarto/examples/05-sentence-brevity.md +5 -5
- package/skills/writing-quarto/examples/05-sentence-frame.md +6 -6
- package/skills/writing-quarto/examples/05-sentence-reference.md +5 -5
- package/skills/writing-quarto/examples/06-wording-cases.md +8 -8
- package/skills/writing-quarto/examples/08-appendix.md +4 -4
- package/skills/writing-quarto/quarto/basics.md +24 -2
- package/skills/writing-quarto/quarto/comments.md +43 -0
- package/skills/writing-quarto/quarto/coords.md +4 -4
- package/skills/writing-quarto/quarto/encoding.md +35 -0
- package/skills/writing-quarto/quarto/mermaid.md +1 -1
- package/skills/writing-quarto/quarto/prose.md +86 -0
- package/skills/writing-quarto/quarto/render.md +17 -16
- package/skills/writing-quarto/quarto/terminal.md +3 -3
- package/skills/writing-quarto/scripts/checking-dom.py +20 -8
- package/skills/writing-quarto/scripts/verifying-content.py +12 -3
- package/skills/writing-quarto/steps/01-task.md +6 -6
- package/skills/writing-quarto/steps/02-structure.md +19 -19
- package/skills/writing-quarto/steps/03-blocks.md +40 -40
- package/skills/writing-quarto/steps/04-paragraphs.md +55 -55
- package/skills/writing-quarto/steps/05-sentences.md +42 -43
- package/skills/writing-quarto/steps/06-wording.md +9 -11
- package/skills/writing-quarto/steps/07-layout.md +21 -17
- package/skills/writing-quarto/steps/08-reread.md +9 -13
- package/templates/quarto-book-project/SKILL.md +1 -1
- package/skills/designing-theme/assets/theme/assets/brand-mark.svg +0 -4
- package/skills/designing-theme/assets/theme/assets/favicon.svg +0 -9
- package/skills/designing-theme/assets/theme/palettes/github/highlight-dark.theme +0 -213
- package/skills/designing-theme/assets/theme/palettes/github/highlight-light.theme +0 -213
- package/skills/designing-theme/assets/theme/palettes/github/meta.md +0 -38
- package/skills/designing-theme/assets/theme/palettes/github/theme-dark.scss +0 -35
- package/skills/designing-theme/assets/theme/palettes/github/theme-light.scss +0 -34
- package/skills/designing-theme/assets/theme/palettes/github/tokens.css +0 -160
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
<!-- 色板与时辰选择:Flexoki 是编译默认;Solarized 与 Hill Light(hill-light)只切
|
|
2
|
+
html[data-palette],Hill Light 另有 html[data-mode] 的四种时辰(晓雾/午荫/暮烟/夜月)。
|
|
3
|
+
首屏在 head 内恢复选择,避免先画 Flexoki 再跳色;控件在导航栏工具区末尾。
|
|
4
|
+
favicon 是外部 SVG,读不到 CSS 变量和 data-palette,所以按色板与明暗改 href。 -->
|
|
5
|
+
<script>
|
|
6
|
+
(function () {
|
|
7
|
+
var PALETTE_KEY = 'book-palette';
|
|
8
|
+
var MODE_KEY = 'book-hill-mode';
|
|
9
|
+
var PALETTES = ['flexoki', 'solarized', 'hill-light'];
|
|
10
|
+
var MODES = ['dawn', 'noon', 'dusk', 'night'];
|
|
11
|
+
var MODE_LABELS = {
|
|
12
|
+
dawn: ['晓雾', '溪山晓雾 · 清晨'],
|
|
13
|
+
noon: ['午荫', '溪山午荫 · 正午'],
|
|
14
|
+
dusk: ['暮烟', '溪山暮烟 · 黄昏'],
|
|
15
|
+
night: ['夜月', '溪山夜月 · 夜晚']
|
|
16
|
+
};
|
|
17
|
+
var PALETTE_LABELS = { flexoki: 'Flexoki', solarized: 'Solarized', 'hill-light': 'Hill Light' };
|
|
18
|
+
var root = document.documentElement;
|
|
19
|
+
|
|
20
|
+
function read(key) {
|
|
21
|
+
try {
|
|
22
|
+
return window.localStorage.getItem(key);
|
|
23
|
+
} catch (e) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function write(key, value) {
|
|
28
|
+
try {
|
|
29
|
+
window.localStorage.setItem(key, value);
|
|
30
|
+
} catch (e) {}
|
|
31
|
+
}
|
|
32
|
+
function isPalette(value) {
|
|
33
|
+
return PALETTES.indexOf(value) !== -1;
|
|
34
|
+
}
|
|
35
|
+
function isMode(value) {
|
|
36
|
+
return MODES.indexOf(value) !== -1;
|
|
37
|
+
}
|
|
38
|
+
// 山光的默认时辰按本地时间落点(与 hill-light 官方 head 脚本一致):
|
|
39
|
+
// 06–11 晓雾、11–17 午荫、17–20 暮烟,其余夜月。
|
|
40
|
+
function byHour() {
|
|
41
|
+
var hour = new Date().getHours();
|
|
42
|
+
if (hour >= 6 && hour < 11) return 'dawn';
|
|
43
|
+
if (hour >= 11 && hour < 17) return 'noon';
|
|
44
|
+
if (hour >= 17 && hour < 20) return 'dusk';
|
|
45
|
+
return 'night';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
var initialPalette = isPalette(read(PALETTE_KEY)) ? read(PALETTE_KEY) : 'flexoki';
|
|
49
|
+
var initialMode = isMode(read(MODE_KEY)) ? read(MODE_KEY) : byHour();
|
|
50
|
+
if (initialPalette !== 'flexoki') root.setAttribute('data-palette', initialPalette);
|
|
51
|
+
if (initialPalette === 'hill-light') root.setAttribute('data-mode', initialMode);
|
|
52
|
+
|
|
53
|
+
// 基座镜像:Quarto 只编译两套 Bootstrap(light/dark),靠 localStorage 的
|
|
54
|
+
// quarto-color-scheme 决定首帧用哪套。山光的明暗由时辰决定,所以 head 里先把
|
|
55
|
+
// 时辰翻成 sentinel(夜月 alternate,其余 default),Quarto 的 before-body 脚本
|
|
56
|
+
// 读到的就是山光要的基座;只镜像山光,Flexoki/Solarized 的明暗仍由读者与圆钮决定。
|
|
57
|
+
// file:// 下 Quarto 不写 localStorage,镜像也会被浏览器拒绝(write 已 try/catch),
|
|
58
|
+
// 此时由 mount 后的 syncBase 对账兜底,可能闪一帧。
|
|
59
|
+
function mirrorQuartoScheme() {
|
|
60
|
+
if (initialPalette !== 'hill-light') return;
|
|
61
|
+
if (location.protocol !== 'http:' && location.protocol !== 'https:') return;
|
|
62
|
+
write('quarto-color-scheme', initialMode === 'night' ? 'alternate' : 'default');
|
|
63
|
+
}
|
|
64
|
+
mirrorQuartoScheme();
|
|
65
|
+
|
|
66
|
+
function palette() {
|
|
67
|
+
var value = root.getAttribute('data-palette');
|
|
68
|
+
return isPalette(value) ? value : 'flexoki';
|
|
69
|
+
}
|
|
70
|
+
function mode() {
|
|
71
|
+
var value = root.getAttribute('data-mode');
|
|
72
|
+
return isMode(value) ? value : initialMode;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 明暗先看 Quarto 已写好的 body class,再看它存的偏好;都没有时退回系统偏好。
|
|
76
|
+
// file:// 下 Quarto 不写 localStorage,页脚观察器会在 body class 变化后再同步一次。
|
|
77
|
+
function quartoIsDark() {
|
|
78
|
+
if (document.body) {
|
|
79
|
+
if (document.body.classList.contains('quarto-dark')) return true;
|
|
80
|
+
if (document.body.classList.contains('quarto-light')) return false;
|
|
81
|
+
}
|
|
82
|
+
var scheme = read('quarto-color-scheme');
|
|
83
|
+
if (scheme === 'alternate' || scheme === 'dark') return true;
|
|
84
|
+
if (scheme === 'default' || scheme === 'light') return false;
|
|
85
|
+
return !!(window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
|
86
|
+
}
|
|
87
|
+
// 山光的明暗由时辰决定:夜月是唯一的暗色时辰。
|
|
88
|
+
function isDark() {
|
|
89
|
+
return palette() === 'hill-light' ? mode() === 'night' : quartoIsDark();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 只替换 Quarto 写好的相对路径里的色板与明暗两段,保留 ./ 或 ../../ 前缀。
|
|
93
|
+
function syncFavicon() {
|
|
94
|
+
var link = document.querySelector('link[rel~="icon"]');
|
|
95
|
+
if (!link) return;
|
|
96
|
+
var href = link.getAttribute('href') || '';
|
|
97
|
+
var next = href.replace(
|
|
98
|
+
/(palettes\/)(flexoki|solarized|hill-light)(\/favicon-)(light|dark)(\.svg)/,
|
|
99
|
+
'$1' + palette() + '$3' + (isDark() ? 'dark' : 'light') + '$5'
|
|
100
|
+
);
|
|
101
|
+
if (next !== href) link.setAttribute('href', next);
|
|
102
|
+
}
|
|
103
|
+
window.__bookSyncFavicon = syncFavicon;
|
|
104
|
+
syncFavicon();
|
|
105
|
+
|
|
106
|
+
// 基座对账:山光夜月必须落在 Quarto 的 dark 基座上(body.quarto-dark 的规则
|
|
107
|
+
// 才生效),其余时辰落在 light 基座。只在状态不一致时调用 QuartoToggleColorScheme,
|
|
108
|
+
// 避免把读者在其他色板的明暗偏好翻掉;切走山光时不动基座(保持读者当前的明暗)。
|
|
109
|
+
function syncBase() {
|
|
110
|
+
if (palette() !== 'hill-light') return;
|
|
111
|
+
if (typeof window.quartoToggleColorScheme !== 'function') return;
|
|
112
|
+
if (!document.body) return;
|
|
113
|
+
var cls = document.body.classList;
|
|
114
|
+
if (!cls.contains('quarto-dark') && !cls.contains('quarto-light')) return;
|
|
115
|
+
if ((mode() === 'night') !== cls.contains('quarto-dark')) {
|
|
116
|
+
window.quartoToggleColorScheme();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
window.__bookSyncBase = syncBase;
|
|
120
|
+
|
|
121
|
+
function applyPalette(value) {
|
|
122
|
+
if (value === 'flexoki') root.removeAttribute('data-palette');
|
|
123
|
+
else root.setAttribute('data-palette', value);
|
|
124
|
+
// 山光需要 data-mode;切走时清掉,避免其他色板误吃四时令牌。
|
|
125
|
+
if (value === 'hill-light') root.setAttribute('data-mode', mode());
|
|
126
|
+
else root.removeAttribute('data-mode');
|
|
127
|
+
write(PALETTE_KEY, value);
|
|
128
|
+
syncFavicon();
|
|
129
|
+
syncBase();
|
|
130
|
+
document.dispatchEvent(new CustomEvent('book-palette-change', { detail: value }));
|
|
131
|
+
}
|
|
132
|
+
function applyMode(value) {
|
|
133
|
+
root.setAttribute('data-mode', value);
|
|
134
|
+
write(MODE_KEY, value);
|
|
135
|
+
syncFavicon();
|
|
136
|
+
syncBase();
|
|
137
|
+
document.dispatchEvent(new CustomEvent('book-palette-change', { detail: 'hill-light' }));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 单选组键盘语义:方向键在段间移动并即时选中,Home/End 到两端;
|
|
141
|
+
// 只有选中段进 Tab 序列(roving tabindex),与原生 radio 一致。
|
|
142
|
+
function bindKeys(wrap, buttons, current, activate) {
|
|
143
|
+
wrap.addEventListener('keydown', function (event) {
|
|
144
|
+
var index = buttons.findIndex(function (button) {
|
|
145
|
+
return button.dataset.key === current();
|
|
146
|
+
});
|
|
147
|
+
if (index === -1) index = 0;
|
|
148
|
+
var next = null;
|
|
149
|
+
if (event.key === 'ArrowRight' || event.key === 'ArrowDown') {
|
|
150
|
+
next = (index + 1) % buttons.length;
|
|
151
|
+
} else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') {
|
|
152
|
+
next = (index - 1 + buttons.length) % buttons.length;
|
|
153
|
+
} else if (event.key === 'Home') {
|
|
154
|
+
next = 0;
|
|
155
|
+
} else if (event.key === 'End') {
|
|
156
|
+
next = buttons.length - 1;
|
|
157
|
+
}
|
|
158
|
+
if (next === null) return;
|
|
159
|
+
event.preventDefault();
|
|
160
|
+
activate(buttons[next].dataset.key);
|
|
161
|
+
buttons[next].focus();
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function makeSegment(label) {
|
|
166
|
+
var wrap = document.createElement('div');
|
|
167
|
+
wrap.className = 'palette-picker';
|
|
168
|
+
wrap.setAttribute('role', 'radiogroup');
|
|
169
|
+
wrap.setAttribute('aria-label', label);
|
|
170
|
+
var segment = document.createElement('div');
|
|
171
|
+
segment.className = 'palette-segment';
|
|
172
|
+
wrap.appendChild(segment);
|
|
173
|
+
return { wrap: wrap, segment: segment };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 色板下拉:三段文字胶囊在 Hill Light 出现后还要并排多一段四时辰,顶栏右端被挤到
|
|
177
|
+
// 近 190px,窄屏只能把名字全裁掉。改成「当前色板」按钮 + 列表:闭合态只占色点与名字,
|
|
178
|
+
// 展开后三套色板一次看全。
|
|
179
|
+
// 语义取 select-only combobox:按钮带着当前值(这是菜单按钮做不到的),面板是 listbox,
|
|
180
|
+
// 焦点留在按钮上、用 aria-activedescendant 指当前项——APG 的原生键位(方向键、Home/End、
|
|
181
|
+
// 首字母、Enter 提交、Esc 无提交关闭)就是照这个模型写的。
|
|
182
|
+
function makeMenu(label) {
|
|
183
|
+
var wrap = document.createElement('div');
|
|
184
|
+
wrap.className = 'palette-picker palette-menu';
|
|
185
|
+
var trigger = document.createElement('button');
|
|
186
|
+
trigger.type = 'button';
|
|
187
|
+
trigger.className = 'palette-trigger';
|
|
188
|
+
trigger.setAttribute('role', 'combobox');
|
|
189
|
+
trigger.setAttribute('aria-haspopup', 'listbox');
|
|
190
|
+
trigger.setAttribute('aria-controls', 'book-palette-list');
|
|
191
|
+
trigger.setAttribute('aria-expanded', 'false');
|
|
192
|
+
trigger.setAttribute('aria-autocomplete', 'none');
|
|
193
|
+
trigger.title = label;
|
|
194
|
+
trigger.innerHTML =
|
|
195
|
+
'<span class="palette-swatch" aria-hidden="true"></span>' +
|
|
196
|
+
'<span class="palette-trigger-label"></span>' +
|
|
197
|
+
'<svg class="palette-caret" viewBox="0 0 10 10" aria-hidden="true">' +
|
|
198
|
+
'<path d="M2 3.6 5 6.6 8 3.6" fill="none" stroke="currentColor" stroke-width="1.4" ' +
|
|
199
|
+
'stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
|
200
|
+
var list = document.createElement('div');
|
|
201
|
+
list.className = 'palette-menu-list';
|
|
202
|
+
list.id = 'book-palette-list';
|
|
203
|
+
list.setAttribute('role', 'listbox');
|
|
204
|
+
list.setAttribute('aria-label', label);
|
|
205
|
+
list.hidden = true;
|
|
206
|
+
wrap.appendChild(trigger);
|
|
207
|
+
wrap.appendChild(list);
|
|
208
|
+
return { wrap: wrap, trigger: trigger, list: list };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// 四时辰图标:12px 格内的极简线性图。晓雾/午荫/暮烟/夜月是气氛词,圆点或首字都
|
|
212
|
+
// 传达不了差别,所以缺口用图形补:破晓的日头、正午的烈日、擦山的低日、夜月的弯月。
|
|
213
|
+
// 颜色一律 currentColor,跟分段控件的选中态一起变。
|
|
214
|
+
var MODE_ICONS = {
|
|
215
|
+
dawn:
|
|
216
|
+
'<svg viewBox="0 0 12 12" aria-hidden="true">' +
|
|
217
|
+
'<path d="M1.6 8.6h8.8" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/>' +
|
|
218
|
+
'<path d="M3.3 8.6a2.7 2.7 0 0 1 5.4 0" fill="none" stroke="currentColor" stroke-width="1.2"/>' +
|
|
219
|
+
'<path d="M6 2.3v1.2M3.5 4.1l.85.85M8.5 4.1l-.85.85" stroke="currentColor" ' +
|
|
220
|
+
'stroke-width="1.2" stroke-linecap="round"/></svg>',
|
|
221
|
+
noon:
|
|
222
|
+
'<svg viewBox="0 0 12 12" aria-hidden="true">' +
|
|
223
|
+
'<circle cx="6" cy="6" r="2.1" fill="none" stroke="currentColor" stroke-width="1.2"/>' +
|
|
224
|
+
'<path d="M6 1.4v1.1M6 9.5v1.1M1.4 6h1.1M9.5 6h1.1M2.75 2.75l.8.8M8.45 8.45l.8.8' +
|
|
225
|
+
'M9.25 2.75l-.8.8M3.55 8.45l-.8.8" stroke="currentColor" stroke-width="1.2" ' +
|
|
226
|
+
'stroke-linecap="round"/></svg>',
|
|
227
|
+
dusk:
|
|
228
|
+
'<svg viewBox="0 0 12 12" aria-hidden="true">' +
|
|
229
|
+
'<path d="M1.6 8.6h8.8" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/>' +
|
|
230
|
+
'<path d="M3.3 8.6a2.7 2.7 0 0 1 5.4 0" fill="none" stroke="currentColor" stroke-width="1.2"/>' +
|
|
231
|
+
'<path d="M2.4 4.9h1.7M7.9 4.9h1.7" stroke="currentColor" stroke-width="1.2" ' +
|
|
232
|
+
'stroke-linecap="round"/></svg>',
|
|
233
|
+
night:
|
|
234
|
+
'<svg viewBox="0 0 12 12" aria-hidden="true">' +
|
|
235
|
+
'<path d="M8.4 2.5a3.9 3.9 0 1 0 1.2 5.6 4.8 4.8 0 0 1-1.2-5.6Z" fill="currentColor"/></svg>'
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
function mount() {
|
|
239
|
+
var tools = document.querySelector('.quarto-navbar-tools.tools-end');
|
|
240
|
+
if (!tools || tools.querySelector('.palette-picker')) return;
|
|
241
|
+
|
|
242
|
+
var paletteGroup = makeMenu('选择色板');
|
|
243
|
+
var paletteButtons = PALETTES.map(function (key) {
|
|
244
|
+
var button = document.createElement('div');
|
|
245
|
+
button.className = 'palette-menu-item';
|
|
246
|
+
button.id = 'book-palette-opt-' + key;
|
|
247
|
+
button.setAttribute('role', 'option');
|
|
248
|
+
button.setAttribute('aria-selected', 'false');
|
|
249
|
+
button.dataset.key = key;
|
|
250
|
+
button.innerHTML =
|
|
251
|
+
'<span class="palette-swatch" aria-hidden="true"></span>' +
|
|
252
|
+
'<span class="palette-menu-label">' + PALETTE_LABELS[key] + '</span>' +
|
|
253
|
+
'<svg class="palette-check" viewBox="0 0 12 12" aria-hidden="true">' +
|
|
254
|
+
'<path d="M2.4 6.3 4.7 8.6 9.6 3.7" fill="none" stroke="currentColor" stroke-width="1.6" ' +
|
|
255
|
+
'stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
|
256
|
+
button.addEventListener('click', function () {
|
|
257
|
+
applyPalette(key);
|
|
258
|
+
setMenu(false, true);
|
|
259
|
+
});
|
|
260
|
+
paletteGroup.list.appendChild(button);
|
|
261
|
+
return button;
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// 时辰仍用分段控件:四项是同一维度上的并列值,一眼看全比再开一层菜单快。
|
|
265
|
+
var modeGroup = makeSegment('选择时辰');
|
|
266
|
+
modeGroup.wrap.classList.add('mode-picker');
|
|
267
|
+
modeGroup.wrap.title = '选择时辰';
|
|
268
|
+
var modeButtons = MODES.map(function (key) {
|
|
269
|
+
var button = document.createElement('button');
|
|
270
|
+
button.type = 'button';
|
|
271
|
+
button.className = 'palette-segment-option';
|
|
272
|
+
button.setAttribute('role', 'radio');
|
|
273
|
+
button.dataset.key = key;
|
|
274
|
+
button.innerHTML = MODE_ICONS[key] + '<span class="palette-mode-label">' + MODE_LABELS[key][0] + '</span>';
|
|
275
|
+
button.setAttribute('aria-label', MODE_LABELS[key][1]);
|
|
276
|
+
button.title = MODE_LABELS[key][1];
|
|
277
|
+
button.addEventListener('click', function () {
|
|
278
|
+
applyMode(key);
|
|
279
|
+
});
|
|
280
|
+
modeGroup.segment.appendChild(button);
|
|
281
|
+
return button;
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
var activeIndex = -1;
|
|
285
|
+
|
|
286
|
+
function menuOpen() {
|
|
287
|
+
return !paletteGroup.list.hidden;
|
|
288
|
+
}
|
|
289
|
+
function currentIndex() {
|
|
290
|
+
var value = palette();
|
|
291
|
+
for (var i = 0; i < paletteButtons.length; i++) {
|
|
292
|
+
if (paletteButtons[i].dataset.key === value) return i;
|
|
293
|
+
}
|
|
294
|
+
return 0;
|
|
295
|
+
}
|
|
296
|
+
function setActive(index) {
|
|
297
|
+
activeIndex = (index + paletteButtons.length) % paletteButtons.length;
|
|
298
|
+
paletteButtons.forEach(function (button, i) {
|
|
299
|
+
button.classList.toggle('is-active', i === activeIndex);
|
|
300
|
+
});
|
|
301
|
+
paletteGroup.trigger.setAttribute('aria-activedescendant', paletteButtons[activeIndex].id);
|
|
302
|
+
}
|
|
303
|
+
function clearActive() {
|
|
304
|
+
activeIndex = -1;
|
|
305
|
+
paletteButtons.forEach(function (button) {
|
|
306
|
+
button.classList.remove('is-active');
|
|
307
|
+
});
|
|
308
|
+
paletteGroup.trigger.removeAttribute('aria-activedescendant');
|
|
309
|
+
}
|
|
310
|
+
function setMenu(open, refocus) {
|
|
311
|
+
paletteGroup.list.hidden = !open;
|
|
312
|
+
paletteGroup.trigger.setAttribute('aria-expanded', open ? 'true' : 'false');
|
|
313
|
+
if (open) {
|
|
314
|
+
setActive(currentIndex());
|
|
315
|
+
} else {
|
|
316
|
+
clearActive();
|
|
317
|
+
if (refocus) paletteGroup.trigger.focus();
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
paletteGroup.trigger.addEventListener('click', function () {
|
|
322
|
+
setMenu(!menuOpen(), false);
|
|
323
|
+
});
|
|
324
|
+
paletteGroup.trigger.addEventListener('keydown', function (event) {
|
|
325
|
+
var key = event.key;
|
|
326
|
+
if (!menuOpen()) {
|
|
327
|
+
if (key === 'ArrowDown' || key === 'ArrowUp' || key === 'Enter' || key === ' ') {
|
|
328
|
+
event.preventDefault();
|
|
329
|
+
setMenu(true, false);
|
|
330
|
+
}
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (key === 'ArrowDown') {
|
|
334
|
+
event.preventDefault();
|
|
335
|
+
setActive(activeIndex + 1);
|
|
336
|
+
} else if (key === 'ArrowUp') {
|
|
337
|
+
event.preventDefault();
|
|
338
|
+
setActive(activeIndex - 1);
|
|
339
|
+
} else if (key === 'Home') {
|
|
340
|
+
event.preventDefault();
|
|
341
|
+
setActive(0);
|
|
342
|
+
} else if (key === 'End') {
|
|
343
|
+
event.preventDefault();
|
|
344
|
+
setActive(paletteButtons.length - 1);
|
|
345
|
+
} else if (key === 'Enter' || key === ' ') {
|
|
346
|
+
event.preventDefault();
|
|
347
|
+
if (activeIndex >= 0) applyPalette(paletteButtons[activeIndex].dataset.key);
|
|
348
|
+
setMenu(false, true);
|
|
349
|
+
} else if (key === 'Escape') {
|
|
350
|
+
event.preventDefault();
|
|
351
|
+
setMenu(false, true);
|
|
352
|
+
} else if (key === 'Tab') {
|
|
353
|
+
setMenu(false, false);
|
|
354
|
+
} else if (key.length === 1) {
|
|
355
|
+
// 首字母跳转:三套色板的首字母正好是 f / s / h。
|
|
356
|
+
var letter = key.toLowerCase();
|
|
357
|
+
var hit = paletteButtons.filter(function (button) {
|
|
358
|
+
return PALETTE_LABELS[button.dataset.key].toLowerCase().charAt(0) === letter;
|
|
359
|
+
})[0];
|
|
360
|
+
if (hit) {
|
|
361
|
+
event.preventDefault();
|
|
362
|
+
setActive(paletteButtons.indexOf(hit));
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
document.addEventListener('click', function (event) {
|
|
367
|
+
if (menuOpen() && !paletteGroup.wrap.contains(event.target)) setMenu(false, false);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
function syncPalette() {
|
|
371
|
+
var value = palette();
|
|
372
|
+
var name = PALETTE_LABELS[value];
|
|
373
|
+
paletteGroup.trigger.dataset.key = value;
|
|
374
|
+
paletteGroup.trigger.querySelector('.palette-trigger-label').textContent = name;
|
|
375
|
+
// 无障碍名带上可见的当前色板名,满足 2.5.3(Label in Name)。
|
|
376
|
+
paletteGroup.trigger.setAttribute('aria-label', '选择色板,当前 ' + name);
|
|
377
|
+
paletteButtons.forEach(function (button) {
|
|
378
|
+
button.setAttribute('aria-selected', button.dataset.key === value ? 'true' : 'false');
|
|
379
|
+
});
|
|
380
|
+
modeGroup.wrap.hidden = value !== 'hill-light';
|
|
381
|
+
}
|
|
382
|
+
function syncMode() {
|
|
383
|
+
var value = mode();
|
|
384
|
+
modeButtons.forEach(function (button) {
|
|
385
|
+
var checked = button.dataset.key === value;
|
|
386
|
+
button.setAttribute('aria-checked', checked ? 'true' : 'false');
|
|
387
|
+
button.tabIndex = checked ? 0 : -1;
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
bindKeys(modeGroup.wrap, modeButtons, mode, applyMode);
|
|
392
|
+
|
|
393
|
+
syncPalette();
|
|
394
|
+
syncMode();
|
|
395
|
+
tools.appendChild(paletteGroup.wrap);
|
|
396
|
+
tools.appendChild(modeGroup.wrap);
|
|
397
|
+
|
|
398
|
+
document.addEventListener('book-palette-change', function () {
|
|
399
|
+
syncPalette();
|
|
400
|
+
syncMode();
|
|
401
|
+
syncBase();
|
|
402
|
+
});
|
|
403
|
+
// 返回页(bfcache)时 body class 可能被 Quarto 复原,再对账一次。
|
|
404
|
+
window.addEventListener('pageshow', function (event) {
|
|
405
|
+
if (event.persisted) syncBase();
|
|
406
|
+
});
|
|
407
|
+
syncBase();
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (document.readyState === 'loading') {
|
|
411
|
+
document.addEventListener('DOMContentLoaded', function () {
|
|
412
|
+
syncFavicon();
|
|
413
|
+
mount();
|
|
414
|
+
});
|
|
415
|
+
} else {
|
|
416
|
+
syncFavicon();
|
|
417
|
+
mount();
|
|
418
|
+
}
|
|
419
|
+
})();
|
|
420
|
+
</script>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="书">
|
|
2
|
+
<path fill="#000000" fill-rule="evenodd" d="M32 18c-6-5-15-7-24-6v36c9-1 18 1 24 6 6-5 15-7 24-6V12c-9-1-18 1-24 6Zm-2 3v29c-5-3-11-4-17-4V16c6 0 12 2 17 5Zm4 0c5-3 11-5 17-5v30c-6 0-12 1-17 4V21Z"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="书">
|
|
2
|
+
<rect width="64" height="64" rx="14" fill="#100F0F"/>
|
|
3
|
+
<path fill="#FFFCF0" fill-rule="evenodd" d="M32 18c-6-5-15-7-24-6v36c9-1 18 1 24 6 6-5 15-7 24-6V12c-9-1-18 1-24 6Zm-2 3v29c-5-3-11-4-17-4V16c6 0 12 2 17 5Zm4 0c5-3 11-5 17-5v30c-6 0-12 1-17 4V21Z"/>
|
|
4
|
+
<rect x="30" y="20" width="4" height="32" fill="#3AA99F"/>
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="书">
|
|
2
|
+
<rect width="64" height="64" rx="14" fill="#FFFCF0"/>
|
|
3
|
+
<path fill="#100F0F" fill-rule="evenodd" d="M32 18c-6-5-15-7-24-6v36c9-1 18 1 24 6 6-5 15-7 24-6V12c-9-1-18 1-24 6Zm-2 3v29c-5-3-11-4-17-4V16c6 0 12 2 17 5Zm4 0c5-3 11-5 17-5v30c-6 0-12 1-17 4V21Z"/>
|
|
4
|
+
<rect x="30" y="20" width="4" height="32" fill="#24837B"/>
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
{
|
|
2
|
+
"metadata": {
|
|
3
|
+
"name": "Quarto Book Flexoki Dark",
|
|
4
|
+
"revision": 1,
|
|
5
|
+
"license": "SPDX-License-Identifier: MIT",
|
|
6
|
+
"copyright": [
|
|
7
|
+
"SPDX-FileCopyrightText: Steph Ango (Flexoki, MIT)",
|
|
8
|
+
"SPDX-FileCopyrightText: 2020 Waqar Ahmed <waqar.17a@gmail.com> (skylighting 结构基础)"
|
|
9
|
+
]
|
|
10
|
+
},
|
|
11
|
+
"background-color": "#1c1b1a",
|
|
12
|
+
"editor-colors": {
|
|
13
|
+
"BackgroundColor": "#1c1b1a",
|
|
14
|
+
"BracketMatching": "#343331",
|
|
15
|
+
"CodeFolding": "#1c1b1a",
|
|
16
|
+
"CurrentLine": "#1c1b1a",
|
|
17
|
+
"CurrentLineNumber": "#cecdc3",
|
|
18
|
+
"IconBorder": "#1c1b1a",
|
|
19
|
+
"IndentationLine": "#343331",
|
|
20
|
+
"LineNumbers": "#878580",
|
|
21
|
+
"MarkBookmark": "#4385be",
|
|
22
|
+
"MarkBreakpointActive": "#d14d41",
|
|
23
|
+
"MarkBreakpointDisabled": "#8b7ec8",
|
|
24
|
+
"MarkBreakpointReached": "#d0a215",
|
|
25
|
+
"MarkError": "#d14d41",
|
|
26
|
+
"MarkExecution": "#8b7ec8",
|
|
27
|
+
"MarkWarning": "#da702c",
|
|
28
|
+
"ModifiedLines": "#d14d41",
|
|
29
|
+
"ReplaceHighlight": "#879a39",
|
|
30
|
+
"SavedLines": "#879a39",
|
|
31
|
+
"SearchHighlight": "#d0a215",
|
|
32
|
+
"Separator": "#343331",
|
|
33
|
+
"SpellChecking": "#d14d41",
|
|
34
|
+
"TabMarker": "#575653",
|
|
35
|
+
"TextSelection": "#343331",
|
|
36
|
+
"WordWrapMarker": "#343331"
|
|
37
|
+
},
|
|
38
|
+
"text-styles": {
|
|
39
|
+
"Alert": {
|
|
40
|
+
"bold": true,
|
|
41
|
+
"selected-text-color": "#d14d41",
|
|
42
|
+
"text-color": "#d14d41"
|
|
43
|
+
},
|
|
44
|
+
"Annotation": {
|
|
45
|
+
"selected-text-color": "#878580",
|
|
46
|
+
"text-color": "#878580"
|
|
47
|
+
},
|
|
48
|
+
"Attribute": {
|
|
49
|
+
"selected-text-color": "#d0a215",
|
|
50
|
+
"text-color": "#d0a215"
|
|
51
|
+
},
|
|
52
|
+
"BaseN": {
|
|
53
|
+
"selected-text-color": "#8b7ec8",
|
|
54
|
+
"text-color": "#8b7ec8"
|
|
55
|
+
},
|
|
56
|
+
"BuiltIn": {
|
|
57
|
+
"selected-text-color": "#ce5d97",
|
|
58
|
+
"text-color": "#ce5d97"
|
|
59
|
+
},
|
|
60
|
+
"Char": {
|
|
61
|
+
"selected-text-color": "#3aa99f",
|
|
62
|
+
"text-color": "#3aa99f"
|
|
63
|
+
},
|
|
64
|
+
"Comment": {
|
|
65
|
+
"selected-text-color": "#878580",
|
|
66
|
+
"text-color": "#878580"
|
|
67
|
+
},
|
|
68
|
+
"CommentVar": {
|
|
69
|
+
"selected-text-color": "#878580",
|
|
70
|
+
"text-color": "#878580"
|
|
71
|
+
},
|
|
72
|
+
"Constant": {
|
|
73
|
+
"selected-text-color": "#8b7ec8",
|
|
74
|
+
"text-color": "#8b7ec8"
|
|
75
|
+
},
|
|
76
|
+
"ControlFlow": {
|
|
77
|
+
"selected-text-color": "#879a39",
|
|
78
|
+
"text-color": "#879a39"
|
|
79
|
+
},
|
|
80
|
+
"DataType": {
|
|
81
|
+
"selected-text-color": "#d0a215",
|
|
82
|
+
"text-color": "#d0a215"
|
|
83
|
+
},
|
|
84
|
+
"DecVal": {
|
|
85
|
+
"selected-text-color": "#8b7ec8",
|
|
86
|
+
"text-color": "#8b7ec8"
|
|
87
|
+
},
|
|
88
|
+
"Documentation": {
|
|
89
|
+
"selected-text-color": "#878580",
|
|
90
|
+
"text-color": "#878580"
|
|
91
|
+
},
|
|
92
|
+
"Error": {
|
|
93
|
+
"selected-text-color": "#d14d41",
|
|
94
|
+
"text-color": "#d14d41",
|
|
95
|
+
"underline": true
|
|
96
|
+
},
|
|
97
|
+
"Extension": {
|
|
98
|
+
"bold": true,
|
|
99
|
+
"selected-text-color": "#d14d41",
|
|
100
|
+
"text-color": "#d14d41"
|
|
101
|
+
},
|
|
102
|
+
"Float": {
|
|
103
|
+
"selected-text-color": "#8b7ec8",
|
|
104
|
+
"text-color": "#8b7ec8"
|
|
105
|
+
},
|
|
106
|
+
"Function": {
|
|
107
|
+
"selected-text-color": "#da702c",
|
|
108
|
+
"text-color": "#da702c",
|
|
109
|
+
"bold": true
|
|
110
|
+
},
|
|
111
|
+
"Import": {
|
|
112
|
+
"selected-text-color": "#d14d41",
|
|
113
|
+
"text-color": "#d14d41"
|
|
114
|
+
},
|
|
115
|
+
"Information": {
|
|
116
|
+
"selected-text-color": "#878580",
|
|
117
|
+
"text-color": "#878580"
|
|
118
|
+
},
|
|
119
|
+
"Keyword": {
|
|
120
|
+
"selected-text-color": "#879a39",
|
|
121
|
+
"text-color": "#879a39"
|
|
122
|
+
},
|
|
123
|
+
"Normal": {
|
|
124
|
+
"selected-text-color": "#cecdc3",
|
|
125
|
+
"text-color": "#cecdc3"
|
|
126
|
+
},
|
|
127
|
+
"Operator": {
|
|
128
|
+
"selected-text-color": "#d14d41",
|
|
129
|
+
"text-color": "#d14d41"
|
|
130
|
+
},
|
|
131
|
+
"Others": {
|
|
132
|
+
"selected-text-color": "#8b7ec8",
|
|
133
|
+
"text-color": "#8b7ec8"
|
|
134
|
+
},
|
|
135
|
+
"Preprocessor": {
|
|
136
|
+
"selected-text-color": "#4385be",
|
|
137
|
+
"text-color": "#4385be"
|
|
138
|
+
},
|
|
139
|
+
"RegionMarker": {
|
|
140
|
+
"selected-text-color": "#878580",
|
|
141
|
+
"text-color": "#878580"
|
|
142
|
+
},
|
|
143
|
+
"SpecialChar": {
|
|
144
|
+
"selected-text-color": "#8b7ec8",
|
|
145
|
+
"text-color": "#8b7ec8"
|
|
146
|
+
},
|
|
147
|
+
"SpecialString": {
|
|
148
|
+
"selected-text-color": "#3aa99f",
|
|
149
|
+
"text-color": "#3aa99f"
|
|
150
|
+
},
|
|
151
|
+
"String": {
|
|
152
|
+
"selected-text-color": "#3aa99f",
|
|
153
|
+
"text-color": "#3aa99f"
|
|
154
|
+
},
|
|
155
|
+
"Variable": {
|
|
156
|
+
"selected-text-color": "#4385be",
|
|
157
|
+
"text-color": "#4385be"
|
|
158
|
+
},
|
|
159
|
+
"VerbatimString": {
|
|
160
|
+
"selected-text-color": "#3aa99f",
|
|
161
|
+
"text-color": "#3aa99f"
|
|
162
|
+
},
|
|
163
|
+
"Warning": {
|
|
164
|
+
"selected-text-color": "#da702c",
|
|
165
|
+
"text-color": "#da702c"
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
"custom-styles": {
|
|
169
|
+
"INI Files": {
|
|
170
|
+
"Section": {
|
|
171
|
+
"selected-text-color": "#8b7ec8",
|
|
172
|
+
"text-color": "#8b7ec8"
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
"Python": {
|
|
176
|
+
"Builtin Function": {
|
|
177
|
+
"selected-text-color": "#ce5d97",
|
|
178
|
+
"text-color": "#ce5d97"
|
|
179
|
+
},
|
|
180
|
+
"Import": {
|
|
181
|
+
"selected-text-color": "#d14d41",
|
|
182
|
+
"text-color": "#d14d41"
|
|
183
|
+
},
|
|
184
|
+
"Special Variable": {
|
|
185
|
+
"selected-text-color": "#4385be",
|
|
186
|
+
"text-color": "#4385be"
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
"Rust": {
|
|
190
|
+
"Attribute": {
|
|
191
|
+
"selected-text-color": "#d0a215",
|
|
192
|
+
"text-color": "#d0a215"
|
|
193
|
+
},
|
|
194
|
+
"Macro": {
|
|
195
|
+
"selected-text-color": "#4385be",
|
|
196
|
+
"text-color": "#4385be"
|
|
197
|
+
},
|
|
198
|
+
"Self": {
|
|
199
|
+
"selected-text-color": "#ce5d97",
|
|
200
|
+
"text-color": "#ce5d97"
|
|
201
|
+
},
|
|
202
|
+
"Trait": {
|
|
203
|
+
"selected-text-color": "#da702c",
|
|
204
|
+
"text-color": "#da702c"
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
"XML": {
|
|
208
|
+
"Element": {
|
|
209
|
+
"selected-text-color": "#4385be",
|
|
210
|
+
"text-color": "#4385be"
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|