@abreen/tada 1.0.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/LICENSE +21 -0
- package/README.md +290 -0
- package/bin/tada.js +361 -0
- package/config/authors.json +1 -0
- package/config/nav.json +28 -0
- package/content/index.md +19 -0
- package/content/lectures/01/Pair.java.md +296 -0
- package/content/lectures/01/Rectangle.java +80 -0
- package/content/lectures/01/demo.py +9 -0
- package/content/lectures/01/index.md +39 -0
- package/content/lectures/01/lecture1.pdf +0 -0
- package/content/lectures/index.md +25 -0
- package/content/markdown.md +379 -0
- package/content/problem_sets/index.md +6 -0
- package/fonts/google-sans-code/GoogleSansCodeVariable-Italic.ttf +0 -0
- package/fonts/google-sans-code/GoogleSansCodeVariable.ttf +0 -0
- package/fonts/google-sans-code/LICENSE.txt +93 -0
- package/fonts/inter/InterVariable-Italic.ttf +0 -0
- package/fonts/inter/InterVariable.ttf +0 -0
- package/fonts/inter/LICENSE.txt +92 -0
- package/package.json +70 -0
- package/public/avatars/alex.jpg +0 -0
- package/public/test.txt +1 -0
- package/src/_mixins.scss +4 -0
- package/src/anchor/README.md +6 -0
- package/src/anchor/index.ts +34 -0
- package/src/anchor/style.scss +48 -0
- package/src/code/README.md +5 -0
- package/src/code/index.ts +113 -0
- package/src/code/style.scss +101 -0
- package/src/code.scss +54 -0
- package/src/header/README.md +8 -0
- package/src/header/index.ts +43 -0
- package/src/header/style.scss +228 -0
- package/src/index.ts +73 -0
- package/src/layout.scss +144 -0
- package/src/literate/style.scss +60 -0
- package/src/print/README.md +4 -0
- package/src/print/index.ts +32 -0
- package/src/print/style.scss +82 -0
- package/src/question/README.md +3 -0
- package/src/question/index.ts +25 -0
- package/src/question/style.scss +116 -0
- package/src/search/README.md +6 -0
- package/src/search/index.ts +574 -0
- package/src/search/style.scss +217 -0
- package/src/style.scss +815 -0
- package/src/timezone/index.test.ts +100 -0
- package/src/timezone/index.ts +298 -0
- package/src/timezone/style.scss +16 -0
- package/src/timezone/timezones.json +58 -0
- package/src/toc/README.md +3 -0
- package/src/toc/index.ts +322 -0
- package/src/toc/style.scss +203 -0
- package/src/top/README.md +4 -0
- package/src/top/index.ts +75 -0
- package/src/util.ts +122 -0
- package/templates/_author.html +27 -0
- package/templates/_bottom.html +3 -0
- package/templates/_download.html +1 -0
- package/templates/_heading.html +19 -0
- package/templates/_nav.html +18 -0
- package/templates/_theme.scss +97 -0
- package/templates/_top.html +87 -0
- package/templates/authors.schema.json +13 -0
- package/templates/code.html +31 -0
- package/templates/default.html +13 -0
- package/templates/literate.html +16 -0
- package/templates/nav.schema.json +27 -0
- package/tsconfig.json +15 -0
- package/types/dev.ts +3 -0
- package/types/sass.d.ts +1 -0
- package/types/site-variables.d.ts +16 -0
- package/webpack/apply-base-path-plugin.js +78 -0
- package/webpack/build-state.js +97 -0
- package/webpack/code.test.js +162 -0
- package/webpack/colors.js +15 -0
- package/webpack/config.base.js +147 -0
- package/webpack/config.dev.js +23 -0
- package/webpack/config.prod.js +32 -0
- package/webpack/content-watch-plugin.js +153 -0
- package/webpack/deflist-id-plugin.js +62 -0
- package/webpack/external-links-plugin.js +37 -0
- package/webpack/features.js +5 -0
- package/webpack/flair.json +1 -0
- package/webpack/generate-content-assets-plugin.js +308 -0
- package/webpack/generate-favicon-plugin.js +198 -0
- package/webpack/generate-fonts-plugin.js +69 -0
- package/webpack/generate-manifest-plugin.js +116 -0
- package/webpack/globals.js +74 -0
- package/webpack/heading-subtitle-plugin.js +80 -0
- package/webpack/json-schema.js +19 -0
- package/webpack/log.js +143 -0
- package/webpack/markdown-plugins.test.js +203 -0
- package/webpack/pagefind-plugin.js +379 -0
- package/webpack/pagefind-plugin.test.js +131 -0
- package/webpack/pdf-text.js +163 -0
- package/webpack/print-flair-plugin.js +22 -0
- package/webpack/reachability.js +273 -0
- package/webpack/reachability.test.js +80 -0
- package/webpack/serve.js +104 -0
- package/webpack/site-variables.js +53 -0
- package/webpack/site.schema.json +67 -0
- package/webpack/templates.js +128 -0
- package/webpack/text-to-id.js +8 -0
- package/webpack/toc-plugin.js +167 -0
- package/webpack/util.js +49 -0
- package/webpack/utils/code.js +439 -0
- package/webpack/utils/content-files.js +147 -0
- package/webpack/utils/define-plugin.js +20 -0
- package/webpack/utils/file-types.js +26 -0
- package/webpack/utils/front-matter.js +57 -0
- package/webpack/utils/jdi-runner/LiterateRunner.class +0 -0
- package/webpack/utils/jdi-runner/LiterateRunner.java +241 -0
- package/webpack/utils/literate-java.js +153 -0
- package/webpack/utils/markdown.js +244 -0
- package/webpack/utils/parse-hsl.js +8 -0
- package/webpack/utils/paths.js +58 -0
- package/webpack/utils/render.js +466 -0
- package/webpack/utils/shiki-highlighter.js +26 -0
- package/webpack/validate-internal-links-plugin.js +155 -0
- package/webpack/watch-reachability-state.js +273 -0
- package/webpack/watch-reachability-state.test.js +198 -0
- package/webpack/watch-reload-client.js +54 -0
- package/webpack/watch.js +166 -0
package/src/toc/index.ts
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { debounce } from '../util';
|
|
2
|
+
|
|
3
|
+
const LATENCY_MS = 50;
|
|
4
|
+
|
|
5
|
+
type HeadingLevel = '1' | '2' | '3' | '4' | '5' | '6';
|
|
6
|
+
type AlertType = 'warning' | 'note';
|
|
7
|
+
|
|
8
|
+
type Alert = { type: AlertType; title: string };
|
|
9
|
+
type Heading = { level: HeadingLevel; innerHtml: string; id: string };
|
|
10
|
+
type Dinkus = { type: 'dinkus' };
|
|
11
|
+
|
|
12
|
+
function getContainer(parent: HTMLElement): HTMLElement | null {
|
|
13
|
+
return parent.querySelector('nav.toc');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function getCurrentListItem(parent: HTMLElement): HTMLLIElement | null {
|
|
17
|
+
return parent.querySelector('nav.toc .current');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function scrollIfNeeded(element: HTMLElement) {
|
|
21
|
+
const container = getContainer(document.body);
|
|
22
|
+
if (container == null) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const containerHasScrollbar = container.scrollHeight > container.clientHeight;
|
|
26
|
+
if (!containerHasScrollbar) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Calculate element center relative to container's scroll space
|
|
31
|
+
const elementRect = element.getBoundingClientRect();
|
|
32
|
+
const containerRect = container.getBoundingClientRect();
|
|
33
|
+
const elementCenter =
|
|
34
|
+
elementRect.top -
|
|
35
|
+
containerRect.top +
|
|
36
|
+
container.scrollTop +
|
|
37
|
+
elementRect.height / 2;
|
|
38
|
+
const desiredScrollTop = elementCenter - container.clientHeight / 2;
|
|
39
|
+
|
|
40
|
+
container.scrollTop = desiredScrollTop;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getHighlightIndexes(items: (Heading | Alert | Dinkus)[]) {
|
|
44
|
+
const indexes: (number | null)[] = [];
|
|
45
|
+
let currentHeadingIndex: number | null = null;
|
|
46
|
+
let tocIndex = 0;
|
|
47
|
+
|
|
48
|
+
items.forEach(item => {
|
|
49
|
+
if (!('level' in item) && item.type === 'dinkus') {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if ('level' in item) {
|
|
54
|
+
currentHeadingIndex = tocIndex;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
indexes.push(currentHeadingIndex ?? tocIndex);
|
|
58
|
+
tocIndex++;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return indexes;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getHeadingsAndAlerts(
|
|
65
|
+
parent: HTMLElement,
|
|
66
|
+
): (HTMLHeadingElement | HTMLDivElement)[] {
|
|
67
|
+
return Array.from(
|
|
68
|
+
parent.querySelectorAll(
|
|
69
|
+
'.body h1, .body h2, .body h3, .body h4, .body h5, .body h6, .body > div.alert, .body section > div.alert',
|
|
70
|
+
),
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function getTocElements(
|
|
75
|
+
parent: HTMLElement,
|
|
76
|
+
): (HTMLHeadingElement | HTMLDivElement | HTMLHRElement)[] {
|
|
77
|
+
return Array.from(
|
|
78
|
+
parent.querySelectorAll(
|
|
79
|
+
'.body h1, .body h2, .body h3, .body h4, .body h5, .body h6, .body > div.alert, .body section > div.alert, .body > hr',
|
|
80
|
+
),
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* Calculate how much to offset scroll calculations based on floating header */
|
|
85
|
+
function getHeaderOffset() {
|
|
86
|
+
const element = document.querySelector('header details summary');
|
|
87
|
+
if (!element) {
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return element.getBoundingClientRect().height;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function getViewportActivationPoint() {
|
|
95
|
+
const headerOffset = getHeaderOffset();
|
|
96
|
+
|
|
97
|
+
return headerOffset + (window.innerHeight - headerOffset) / 3;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function headingToTableItem(el: HTMLHeadingElement): Heading {
|
|
101
|
+
const level = el.tagName[1] as HeadingLevel;
|
|
102
|
+
|
|
103
|
+
const subtitle = el.querySelector('.heading-subtitle');
|
|
104
|
+
const subtitleText = subtitle?.textContent || '';
|
|
105
|
+
let mainText = el.textContent || '';
|
|
106
|
+
|
|
107
|
+
if (mainText.length > 0 && subtitleText.length > 0) {
|
|
108
|
+
mainText = mainText.replace(subtitleText, '').trim();
|
|
109
|
+
return {
|
|
110
|
+
level,
|
|
111
|
+
id: el.id,
|
|
112
|
+
innerHtml: `${mainText}: <span class="heading-subtitle">${subtitleText}</span>`,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return { level, id: el.id, innerHtml: el.innerHTML };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function alertToTableItem(el: HTMLElement): Alert | null {
|
|
119
|
+
const classes = el.className
|
|
120
|
+
.split(' ')
|
|
121
|
+
.map(cl => cl.trim())
|
|
122
|
+
.filter(cl => cl != 'alert');
|
|
123
|
+
|
|
124
|
+
const firstClass = classes[0];
|
|
125
|
+
if (firstClass === 'warning' || firstClass === 'note') {
|
|
126
|
+
let title = el.querySelector('.title')?.innerHTML;
|
|
127
|
+
if (!title) {
|
|
128
|
+
if (firstClass === 'warning') {
|
|
129
|
+
title = 'Warning';
|
|
130
|
+
} else {
|
|
131
|
+
title = 'Note';
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return { type: firstClass, title };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function switchCurrent(
|
|
142
|
+
oldCurrent: HTMLElement | null,
|
|
143
|
+
newCurrent: HTMLElement,
|
|
144
|
+
) {
|
|
145
|
+
if (oldCurrent) {
|
|
146
|
+
oldCurrent.classList.remove('current');
|
|
147
|
+
}
|
|
148
|
+
newCurrent.classList.add('current');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function wireAlertClickHandlers(
|
|
152
|
+
toc: HTMLElement,
|
|
153
|
+
headingsAndAlerts: (HTMLHeadingElement | HTMLDivElement)[],
|
|
154
|
+
items: (Heading | Alert | Dinkus)[],
|
|
155
|
+
) {
|
|
156
|
+
const alertLinks = toc.querySelectorAll('li.alert-item a');
|
|
157
|
+
let alertIdx = 0;
|
|
158
|
+
let scrollIdx = 0;
|
|
159
|
+
|
|
160
|
+
items.forEach(item => {
|
|
161
|
+
if (!('level' in item) && (item as Alert | Dinkus).type === 'dinkus') {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (!('level' in item)) {
|
|
166
|
+
// This is an alert item
|
|
167
|
+
const scrollEl = headingsAndAlerts[scrollIdx];
|
|
168
|
+
const link = alertLinks[alertIdx] as HTMLAnchorElement | undefined;
|
|
169
|
+
if (link && scrollEl) {
|
|
170
|
+
link.onclick = (e: MouseEvent) => {
|
|
171
|
+
e.preventDefault();
|
|
172
|
+
|
|
173
|
+
const titleElement = scrollEl.querySelector('.title');
|
|
174
|
+
const titleId = titleElement?.id || null;
|
|
175
|
+
|
|
176
|
+
if (titleId) {
|
|
177
|
+
history.replaceState(
|
|
178
|
+
null,
|
|
179
|
+
document.title,
|
|
180
|
+
`${window.location.pathname}#${titleId}`,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
scrollEl.scrollIntoView();
|
|
185
|
+
scrollEl.focus();
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
alertIdx++;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
scrollIdx++;
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export default (window: Window) => {
|
|
196
|
+
const toc = window.document.querySelector('nav.toc') as HTMLElement;
|
|
197
|
+
if (toc == null) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const isCodePage = window.document.body.classList.contains('code');
|
|
202
|
+
|
|
203
|
+
if (isCodePage) {
|
|
204
|
+
const elements: HTMLAnchorElement[] = Array.from(
|
|
205
|
+
toc.querySelectorAll('ol li a'),
|
|
206
|
+
);
|
|
207
|
+
if (elements.length === 0) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const codeLines: number[] = elements.map(a => {
|
|
212
|
+
const match = a.getAttribute('href')?.match(/^#L(\d+)$/);
|
|
213
|
+
return match ? parseInt(match[1], 10) : 0;
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const parseHash = (hash: string): number | null => {
|
|
217
|
+
const single = hash.match(/^#L(\d+)$/);
|
|
218
|
+
if (single) {
|
|
219
|
+
return parseInt(single[1], 10);
|
|
220
|
+
}
|
|
221
|
+
const range = hash.match(/^#L(\d+)-L(\d+)$/);
|
|
222
|
+
if (range) {
|
|
223
|
+
return parseInt(range[1], 10);
|
|
224
|
+
}
|
|
225
|
+
return null;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const updateFromHash = () => {
|
|
229
|
+
const line = parseHash(window.location.hash);
|
|
230
|
+
const existingItem = getCurrentListItem(document.body);
|
|
231
|
+
|
|
232
|
+
if (line == null) {
|
|
233
|
+
if (existingItem) {
|
|
234
|
+
existingItem.classList.remove('current');
|
|
235
|
+
}
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
let best = 0;
|
|
240
|
+
for (let i = 0; i < codeLines.length; i++) {
|
|
241
|
+
if (codeLines[i] <= line) {
|
|
242
|
+
best = i;
|
|
243
|
+
} else {
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const nextItem = elements[best]?.parentElement ?? null;
|
|
249
|
+
if (nextItem != null && nextItem !== existingItem) {
|
|
250
|
+
switchCurrent(existingItem, nextItem);
|
|
251
|
+
scrollIfNeeded(nextItem);
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
window.addEventListener('hashchange', updateFromHash);
|
|
256
|
+
updateFromHash();
|
|
257
|
+
|
|
258
|
+
return () => {
|
|
259
|
+
window.removeEventListener('hashchange', updateFromHash);
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Regular page: TOC is already rendered in the DOM
|
|
264
|
+
const elements: HTMLAnchorElement[] = Array.from(
|
|
265
|
+
toc.querySelectorAll('ol li a'),
|
|
266
|
+
);
|
|
267
|
+
if (elements.length === 0) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const headingsAndAlerts = getHeadingsAndAlerts(window.document.body);
|
|
272
|
+
const items = getTocElements(window.document.body)
|
|
273
|
+
.map(el => {
|
|
274
|
+
const tag = el.tagName.toLowerCase();
|
|
275
|
+
if (tag === 'hr') {
|
|
276
|
+
return { type: 'dinkus' } as Dinkus;
|
|
277
|
+
} else if (tag === 'div') {
|
|
278
|
+
return alertToTableItem(el as HTMLElement);
|
|
279
|
+
} else {
|
|
280
|
+
return headingToTableItem(el as HTMLHeadingElement);
|
|
281
|
+
}
|
|
282
|
+
})
|
|
283
|
+
.filter(obj => obj != null);
|
|
284
|
+
|
|
285
|
+
const highlightIndexes = getHighlightIndexes(items);
|
|
286
|
+
|
|
287
|
+
// Wire up click handlers for alert items
|
|
288
|
+
wireAlertClickHandlers(toc, headingsAndAlerts, items);
|
|
289
|
+
|
|
290
|
+
function handleScroll() {
|
|
291
|
+
const viewportActivationPoint = getViewportActivationPoint();
|
|
292
|
+
|
|
293
|
+
let i = 0;
|
|
294
|
+
for (let idx = 0; idx < headingsAndAlerts.length; idx++) {
|
|
295
|
+
const top = headingsAndAlerts[idx].getBoundingClientRect().top;
|
|
296
|
+
if (top <= viewportActivationPoint + 1) {
|
|
297
|
+
i = idx;
|
|
298
|
+
} else {
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const existingItem = getCurrentListItem(document.body);
|
|
304
|
+
const highlightIndex = highlightIndexes[i];
|
|
305
|
+
const nextItem =
|
|
306
|
+
highlightIndex == null ? null : elements[highlightIndex]?.parentElement;
|
|
307
|
+
|
|
308
|
+
if (nextItem != null && nextItem !== existingItem) {
|
|
309
|
+
switchCurrent(existingItem, nextItem);
|
|
310
|
+
scrollIfNeeded(nextItem);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
const debounced = debounce(handleScroll, LATENCY_MS);
|
|
314
|
+
window.addEventListener('scroll', debounced, { passive: true });
|
|
315
|
+
|
|
316
|
+
// Call after load to set current item
|
|
317
|
+
handleScroll();
|
|
318
|
+
|
|
319
|
+
return () => {
|
|
320
|
+
window.removeEventListener('scroll', debounced);
|
|
321
|
+
};
|
|
322
|
+
};
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
@import '../_mixins.scss';
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--highlight-hue: 59deg;
|
|
5
|
+
--highlight-color: hsl(var(--highlight-hue) 100% 50% / 50%);
|
|
6
|
+
// For animations
|
|
7
|
+
--highlight-color-transparent: hsl(var(--highlight-hue) 50% 90% / 0%);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.toc {
|
|
11
|
+
@include user-select(none);
|
|
12
|
+
|
|
13
|
+
line-height: 1.4;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
body.code .toc ol li:has(a) {
|
|
17
|
+
font-family: var(--mono-font);
|
|
18
|
+
font-size: var(--mono-font-size);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
body.code .toc ol a {
|
|
22
|
+
white-space: nowrap;
|
|
23
|
+
overflow: hidden;
|
|
24
|
+
text-overflow: ellipsis;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.toc ol::before {
|
|
28
|
+
content: 'On this page';
|
|
29
|
+
display: block;
|
|
30
|
+
font-family: var(--mono-font);
|
|
31
|
+
font-style: italic;
|
|
32
|
+
font-size: var(--font-size-small);
|
|
33
|
+
text-align: center;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.toc ol {
|
|
37
|
+
margin: 0;
|
|
38
|
+
padding: 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.toc ol li {
|
|
42
|
+
list-style-type: none;
|
|
43
|
+
overflow-wrap: break-word;
|
|
44
|
+
margin-bottom: 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.toc ol a {
|
|
48
|
+
text-decoration: none;
|
|
49
|
+
border: none;
|
|
50
|
+
display: block;
|
|
51
|
+
padding: 4px 6px;
|
|
52
|
+
color: var(--fg-color);
|
|
53
|
+
|
|
54
|
+
&:focus-visible {
|
|
55
|
+
outline-offset: -2px;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@media (hover: hover) {
|
|
60
|
+
.toc ol li:has(a):hover,
|
|
61
|
+
.toc ol li:has(a):hover a {
|
|
62
|
+
color: var(--fg2-color);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.toc ol li a:active {
|
|
66
|
+
color: var(--link-active-color);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@media (min-width: 900px) {
|
|
71
|
+
.toc ol li.current a {
|
|
72
|
+
background: var(--bg2-color);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.toc ol li:last-child {
|
|
76
|
+
margin-bottom: var(--gap);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.toc ol::before {
|
|
80
|
+
display: none;
|
|
81
|
+
content: '';
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.toc ol li.level1 {
|
|
86
|
+
font-size: var(--font-size-smaller);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.toc ol li.level2 {
|
|
90
|
+
font-size: var(--font-size-smaller);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.toc ol li.level3 {
|
|
94
|
+
font-size: var(--font-size-smaller);
|
|
95
|
+
margin-left: 10px;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.toc ol li.level4 {
|
|
99
|
+
font-size: var(--font-size-small);
|
|
100
|
+
font-weight: 450;
|
|
101
|
+
margin-left: 18px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.toc ol li.level5 {
|
|
105
|
+
font-size: var(--font-size-small);
|
|
106
|
+
font-weight: 450;
|
|
107
|
+
margin-left: 24px;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.toc ol li.level6 {
|
|
111
|
+
font-size: var(--font-size-small);
|
|
112
|
+
font-weight: 450;
|
|
113
|
+
margin-left: 32px;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.toc ol li.dinkus-item {
|
|
117
|
+
height: 8px;
|
|
118
|
+
margin: 4px 0;
|
|
119
|
+
pointer-events: none;
|
|
120
|
+
background-color: var(--fg2-color);
|
|
121
|
+
mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 160 12'><rect x='0' y='5.5' width='96' height='1' fill='black'/><path d='M 64,6 C 71,6 80,4.5 80,1 C 80,4.5 89,6 96,6 C 89,6 80,7.5 80,11 C 80,7.5 71,6 64,6 Z' fill='black'/><rect x='64' y='5.5' width='96' height='1' fill='black'/></svg>");
|
|
122
|
+
mask-size: 100% 100%;
|
|
123
|
+
mask-repeat: no-repeat;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.toc ol li.alert-item {
|
|
127
|
+
position: relative;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.toc ol li.alert-item.warning a::before,
|
|
131
|
+
.toc ol li.alert-item.note a::before {
|
|
132
|
+
display: inline-block;
|
|
133
|
+
height: 20px;
|
|
134
|
+
vertical-align: bottom;
|
|
135
|
+
margin-right: 4px;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.toc ol li.alert-item.warning a::before {
|
|
139
|
+
// fg-color
|
|
140
|
+
content: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24'><polygon points='12,2 22,20 2,20' fill='none' stroke='hsl(20deg 8% 8%)' stroke-width='2' stroke-linejoin='round'/><line x1='12' y1='9' x2='12' y2='13' stroke='hsl(20deg 8% 8%)' stroke-width='2' stroke-linecap='round'/><line x1='12' y1='16' x2='12' y2='16' stroke='hsl(20deg 8% 8%)' stroke-width='2' stroke-linecap='round'/></svg>");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
@media (prefers-color-scheme: dark) {
|
|
144
|
+
.toc ol li.alert-item.warning a::before {
|
|
145
|
+
// fg2-color
|
|
146
|
+
content: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24'><polygon points='12,2 22,20 2,20' fill='none' stroke='hsl(20deg 20% 90%)' stroke-width='2' stroke-linejoin='round'/><line x1='12' y1='9' x2='12' y2='13' stroke='hsl(20deg 20% 90%)' stroke-width='2' stroke-linecap='round'/><line x1='12' y1='16' x2='12' y2='16' stroke='hsl(20deg 20% 90%)' stroke-width='2' stroke-linecap='round'/></svg>");
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
@media (hover: hover) {
|
|
151
|
+
.toc ol li.alert-item.warning:hover a::before,
|
|
152
|
+
.toc ol li.alert-item.warning a:hover::before {
|
|
153
|
+
// fg2-color
|
|
154
|
+
content: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24'><polygon points='12,2 22,20 2,20' fill='none' stroke='hsl(20deg 6% 60%)' stroke-width='2' stroke-linejoin='round'/><line x1='12' y1='9' x2='12' y2='13' stroke='hsl(20deg 6% 60%)' stroke-width='2' stroke-linecap='round'/><line x1='12' y1='16' x2='12' y2='16' stroke='hsl(20deg 6% 60%)' stroke-width='2' stroke-linecap='round'/></svg>");
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
@media (hover: hover) and (prefers-color-scheme: dark) {
|
|
159
|
+
.toc ol li.alert-item.warning:hover a::before,
|
|
160
|
+
.toc ol li.alert-item.warning a:hover::before {
|
|
161
|
+
// fg2-color
|
|
162
|
+
content: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24'><polygon points='12,2 22,20 2,20' fill='none' stroke='hsl(20deg 6% 55%)' stroke-width='2' stroke-linejoin='round'/><line x1='12' y1='9' x2='12' y2='13' stroke='hsl(20deg 6% 55%)' stroke-width='2' stroke-linecap='round'/><line x1='12' y1='16' x2='12' y2='16' stroke='hsl(20deg 6% 55%)' stroke-width='2' stroke-linecap='round'/></svg>");
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.toc ol li.alert-item.note a::before {
|
|
167
|
+
// fg-color
|
|
168
|
+
content: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24'><circle cx='12' cy='12' r='10' fill='none' stroke='hsl(20deg 8% 8%)' stroke-width='2'/><line x1='12' y1='10' x2='12' y2='16' stroke='hsl(20deg 8% 8%)' stroke-width='2' stroke-linecap='round'/><line x1='12' y1='7' x2='12' y2='7' stroke='hsl(20deg 8% 8%)' stroke-width='2' stroke-linecap='round'/></svg>");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
@media (prefers-color-scheme: dark) {
|
|
172
|
+
.toc ol li.alert-item.note a::before {
|
|
173
|
+
// fg-color
|
|
174
|
+
content: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24'><circle cx='12' cy='12' r='10' fill='none' stroke='hsl(20deg 20% 90%)' stroke-width='2'/><line x1='12' y1='10' x2='12' y2='16' stroke='hsl(20deg 20% 90%)' stroke-width='2' stroke-linecap='round'/><line x1='12' y1='7' x2='12' y2='7' stroke='hsl(20deg 20% 90%)' stroke-width='2' stroke-linecap='round'/></svg>");
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
@media (hover: hover) {
|
|
179
|
+
.toc ol li.alert-item.note:hover a::before,
|
|
180
|
+
.toc ol li.alert-item.note a:hover::before {
|
|
181
|
+
// fg2-color
|
|
182
|
+
content: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24'><circle cx='12' cy='12' r='10' fill='none' stroke='hsl(20deg 6% 60%)' stroke-width='2'/><line x1='12' y1='10' x2='12' y2='16' stroke='hsl(20deg 6% 60%)' stroke-width='2' stroke-linecap='round'/><line x1='12' y1='7' x2='12' y2='7' stroke='hsl(20deg 6% 60%)' stroke-width='2' stroke-linecap='round'/></svg>");
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
@media (hover: hover) and (prefers-color-scheme: dark) {
|
|
187
|
+
.toc ol li.alert-item.note:hover a::before,
|
|
188
|
+
.toc ol li.alert-item.note a:hover::before {
|
|
189
|
+
// fg2-color
|
|
190
|
+
content: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24'><circle cx='12' cy='12' r='10' fill='none' stroke='hsl(20deg 6% 55%)' stroke-width='2'/><line x1='12' y1='10' x2='12' y2='16' stroke='hsl(20deg 6% 55%)' stroke-width='2' stroke-linecap='round'/><line x1='12' y1='7' x2='12' y2='7' stroke='hsl(20deg 6% 55%)' stroke-width='2' stroke-linecap='round'/></svg>");
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.toc ol li.label {
|
|
195
|
+
font-size: var(--font-size-smaller);
|
|
196
|
+
color: var(--fg2-color);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
@media print {
|
|
200
|
+
.toc {
|
|
201
|
+
display: none;
|
|
202
|
+
}
|
|
203
|
+
}
|
package/src/top/index.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { debounce, removeClass } from '../util';
|
|
2
|
+
|
|
3
|
+
/** Show the "Back to top" button once the user is past this scroll position */
|
|
4
|
+
const SHOW_THRESHOLD_PX = 250;
|
|
5
|
+
|
|
6
|
+
/** Debounce time (maximum amount of time to wait before updates) */
|
|
7
|
+
const LATENCY_MS = 50;
|
|
8
|
+
|
|
9
|
+
export default (window: Window) => {
|
|
10
|
+
const mountParent =
|
|
11
|
+
(window.document.getElementById(
|
|
12
|
+
'to-top-container',
|
|
13
|
+
) as HTMLElement | null) ?? document.body;
|
|
14
|
+
|
|
15
|
+
function createLink(parent: HTMLElement): HTMLAnchorElement {
|
|
16
|
+
const link = window.document.createElement('a');
|
|
17
|
+
link.href = '#';
|
|
18
|
+
link.className = 'button';
|
|
19
|
+
link.tabIndex = -1;
|
|
20
|
+
link.innerText = 'Back to top';
|
|
21
|
+
parent.appendChild(link);
|
|
22
|
+
return link;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const link = createLink(mountParent);
|
|
26
|
+
|
|
27
|
+
let isShowing = false;
|
|
28
|
+
|
|
29
|
+
link.onclick = e => {
|
|
30
|
+
e.preventDefault();
|
|
31
|
+
const cleanUrl = window.location.pathname + window.location.search;
|
|
32
|
+
if (window.location.hash) {
|
|
33
|
+
history.pushState(null, '', cleanUrl);
|
|
34
|
+
} else {
|
|
35
|
+
history.replaceState(null, '', cleanUrl);
|
|
36
|
+
}
|
|
37
|
+
window.scrollTo({ top: 0 });
|
|
38
|
+
const toc = window.document.querySelector('nav.toc') as HTMLElement | null;
|
|
39
|
+
if (toc) {
|
|
40
|
+
toc.scrollTop = 0;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
function show(link: HTMLAnchorElement) {
|
|
45
|
+
if (!isShowing) {
|
|
46
|
+
link.classList.add('is-visible');
|
|
47
|
+
link.tabIndex = 0;
|
|
48
|
+
}
|
|
49
|
+
isShowing = true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function hide(link: HTMLAnchorElement) {
|
|
53
|
+
if (isShowing) {
|
|
54
|
+
removeClass(link, 'is-visible');
|
|
55
|
+
link.tabIndex = -1;
|
|
56
|
+
}
|
|
57
|
+
isShowing = false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function updateVisibility() {
|
|
61
|
+
if (window.scrollY > SHOW_THRESHOLD_PX) {
|
|
62
|
+
show(link);
|
|
63
|
+
} else {
|
|
64
|
+
hide(link);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const debounced = debounce(updateVisibility, LATENCY_MS);
|
|
69
|
+
window.addEventListener('scroll', debounced, { passive: true });
|
|
70
|
+
updateVisibility();
|
|
71
|
+
|
|
72
|
+
return () => {
|
|
73
|
+
window.removeEventListener('scroll', debounced);
|
|
74
|
+
};
|
|
75
|
+
};
|