@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/util.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
export function debounce(fn: Function, time: number) {
|
|
2
|
+
let timer: number;
|
|
3
|
+
return (...args: any[]) => {
|
|
4
|
+
window.clearTimeout(timer);
|
|
5
|
+
timer = window.setTimeout(() => {
|
|
6
|
+
fn.apply(null, args);
|
|
7
|
+
}, time);
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function removeClass(el: HTMLElement, className: string) {
|
|
12
|
+
el.classList.remove(className);
|
|
13
|
+
if (!el.className) {
|
|
14
|
+
el.removeAttribute('class');
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getElement(
|
|
19
|
+
parent: Document | Element,
|
|
20
|
+
selector: string,
|
|
21
|
+
): HTMLElement {
|
|
22
|
+
const el = parent.querySelector(selector);
|
|
23
|
+
if (!el) {
|
|
24
|
+
throw new Error(`no element matching "${selector}"`);
|
|
25
|
+
}
|
|
26
|
+
return el as HTMLElement;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function applyBasePath(subPath: string): string {
|
|
30
|
+
if (!subPath.startsWith('/')) {
|
|
31
|
+
throw new Error('invalid internal path, must start with "/": ' + subPath);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let path = window.siteVariables.basePath || '/';
|
|
35
|
+
if (path.endsWith('/')) {
|
|
36
|
+
path = path.slice(0, -1);
|
|
37
|
+
}
|
|
38
|
+
return path + subPath;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function scheduleTask(fn: () => void) {
|
|
42
|
+
if (typeof window.requestIdleCallback === 'function') {
|
|
43
|
+
window.requestIdleCallback(fn);
|
|
44
|
+
} else {
|
|
45
|
+
return setTimeout(fn, 0);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function formatDuration(ms: number): string {
|
|
50
|
+
const sign = ms < 0 ? '-' : '';
|
|
51
|
+
const absMs = Math.abs(ms);
|
|
52
|
+
|
|
53
|
+
const roundTo = (val: number, decimals: number) => {
|
|
54
|
+
const factor = Math.pow(10, decimals);
|
|
55
|
+
return Math.round(val * factor) / factor;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const formatSecondsFromMs = (msVal: number): string => {
|
|
59
|
+
const sec = msVal / 1000;
|
|
60
|
+
// Ensure fixed width 8 for seconds:
|
|
61
|
+
// <10s => 1 + '.' + 5 + 's' = 8
|
|
62
|
+
// >=10s => 2 + '.' + 4 + 's' = 8
|
|
63
|
+
let decimals = sec < 10 ? 5 : 4;
|
|
64
|
+
let rounded = roundTo(sec, decimals);
|
|
65
|
+
|
|
66
|
+
// If rounding reaches 60, switch to minutes
|
|
67
|
+
if (rounded >= 60) {
|
|
68
|
+
return formatMinutesFromMs(msVal);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// If value was <10 but rounding crossed into >=10, drop one decimal to keep width 8
|
|
72
|
+
if (decimals === 5 && rounded >= 10) {
|
|
73
|
+
decimals = 4;
|
|
74
|
+
rounded = roundTo(sec, decimals);
|
|
75
|
+
if (rounded >= 60) {
|
|
76
|
+
return formatMinutesFromMs(msVal);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return `${rounded.toFixed(decimals)}s`;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const formatMinutesFromMs = (msVal: number): string => {
|
|
84
|
+
const totalSec = msVal / 1000;
|
|
85
|
+
let minutes = Math.floor(totalSec / 60);
|
|
86
|
+
const rawSeconds = totalSec - minutes * 60;
|
|
87
|
+
|
|
88
|
+
let decimals: number;
|
|
89
|
+
if (minutes < 10) {
|
|
90
|
+
decimals = rawSeconds < 10 ? 3 : 2;
|
|
91
|
+
} else {
|
|
92
|
+
decimals = rawSeconds < 10 ? 2 : 1;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let seconds = roundTo(rawSeconds, decimals);
|
|
96
|
+
|
|
97
|
+
if (seconds >= 60) {
|
|
98
|
+
minutes += 1;
|
|
99
|
+
// after carry, seconds is 0 and decimals depend on new minutes value
|
|
100
|
+
decimals = minutes < 10 ? 3 : 2;
|
|
101
|
+
seconds = 0;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return `${minutes}m${seconds.toFixed(decimals)}s`;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
if (absMs < 1000) {
|
|
108
|
+
// 0.0000ms - 999.99ms
|
|
109
|
+
let decimals = absMs < 10 ? 4 : absMs < 100 ? 3 : 2;
|
|
110
|
+
let val = roundTo(absMs, decimals);
|
|
111
|
+
if (val >= 1000) {
|
|
112
|
+
return sign + formatSecondsFromMs(absMs);
|
|
113
|
+
}
|
|
114
|
+
return sign + `${val.toFixed(decimals)}ms`;
|
|
115
|
+
} else if (absMs < 60000) {
|
|
116
|
+
// 0.0000s - 59.9999s (variable decimals based on magnitude)
|
|
117
|
+
return sign + formatSecondsFromMs(absMs);
|
|
118
|
+
} else {
|
|
119
|
+
// Minutes + seconds with precision based on minute/second magnitude
|
|
120
|
+
return sign + formatMinutesFromMs(absMs);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<aside class="author">
|
|
2
|
+
Written by
|
|
3
|
+
<% if (page.author.url) { %>
|
|
4
|
+
<a href="<%= applyBasePath(page.author.url) %>">
|
|
5
|
+
<% } %>
|
|
6
|
+
<span class="text"><%= page.author.name %></span>
|
|
7
|
+
<% if (page.author.avatar) { %>
|
|
8
|
+
<img
|
|
9
|
+
width="32"
|
|
10
|
+
height="32"
|
|
11
|
+
src="<%= applyBasePath(page.author.avatar) %>"
|
|
12
|
+
alt="Picture of <%= page.author.name %>"
|
|
13
|
+
class="avatar"
|
|
14
|
+
/>
|
|
15
|
+
<% } else { %>
|
|
16
|
+
<img
|
|
17
|
+
width="32"
|
|
18
|
+
height="32"
|
|
19
|
+
src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='32' height='32'><rect width='32' height='32'/></svg>"
|
|
20
|
+
alt=""
|
|
21
|
+
class="avatar"
|
|
22
|
+
/>
|
|
23
|
+
<% } %>
|
|
24
|
+
<% if (page.author.url) { %>
|
|
25
|
+
</a>
|
|
26
|
+
<% } %>
|
|
27
|
+
</aside>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<a class="button" href="<%= page.codeFilePath %>" download="<%= page.downloadName %>" data-pagefind-ignore>Download</a>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<div class="title-and-info" data-pagefind-body>
|
|
2
|
+
<% if (page.parent) { %>
|
|
3
|
+
<a class="breadcrumb" href="<%= applyBasePath(page.parent) %>" data-pagefind-ignore><%= page.parentLabel %></a>
|
|
4
|
+
<% } %>
|
|
5
|
+
<h1 data-pagefind-weight="10"><%= page.titleHtml %></h1>
|
|
6
|
+
|
|
7
|
+
<div class="info" data-pagefind-ignore>
|
|
8
|
+
<% if (page.published) { %>
|
|
9
|
+
<div class="publish-date">
|
|
10
|
+
<time datetime="<%= isoDate(page.published) %>">
|
|
11
|
+
<%= readableDate(page.published) %>
|
|
12
|
+
</time>
|
|
13
|
+
</div>
|
|
14
|
+
<% } %>
|
|
15
|
+
<% if (page.author) { %>
|
|
16
|
+
<%= render('_author.html') %>
|
|
17
|
+
<% } %>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<nav>
|
|
2
|
+
<% json('nav.json').forEach(function(section) { %>
|
|
3
|
+
<div>
|
|
4
|
+
<p><%= section.title %></p>
|
|
5
|
+
<ul>
|
|
6
|
+
<% section.links.forEach(function(link) { %>
|
|
7
|
+
<li>
|
|
8
|
+
<a
|
|
9
|
+
class="<%= cx({disabled: link.disabled, external: link.external}) %>"
|
|
10
|
+
<% if (!link.disabled) { %>href="<%= link.internal ? applyBasePath(link.internal) : link.external %>"<% } %>
|
|
11
|
+
>
|
|
12
|
+
<%= link.text %></a>
|
|
13
|
+
</li>
|
|
14
|
+
<% }) %>
|
|
15
|
+
</ul>
|
|
16
|
+
</div>
|
|
17
|
+
<% }) %>
|
|
18
|
+
</nav>
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--theme-hue: <%= themeHue %>deg;
|
|
3
|
+
--theme-saturation: <%= themeSaturation %>%;
|
|
4
|
+
--theme-lightness: <%= themeLightness %>%;
|
|
5
|
+
--theme-color: hsl(
|
|
6
|
+
var(--theme-hue) var(--theme-saturation) var(--theme-lightness)
|
|
7
|
+
);
|
|
8
|
+
--theme-color-text: hsl(
|
|
9
|
+
var(--theme-hue) clamp(30%, var(--theme-saturation), 70%)
|
|
10
|
+
clamp(25%, var(--theme-lightness), 42%)
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
// Background & foreground color tint
|
|
14
|
+
--tint-hue: <%= tintHue %>deg;
|
|
15
|
+
--tint-amount: <%= tintAmount / 100 %>;
|
|
16
|
+
|
|
17
|
+
--link-active-color: #000;
|
|
18
|
+
|
|
19
|
+
--warning-bg-color: hsl(36 95% 70%);
|
|
20
|
+
--note-bg-color: hsl(209 95% 75%);
|
|
21
|
+
|
|
22
|
+
// Main foreground color (the color of most text)
|
|
23
|
+
--fg-color: hsl(var(--tint-hue) calc(8% * var(--tint-amount)) 8%);
|
|
24
|
+
--fg-color-translucent: hsl(
|
|
25
|
+
var(--tint-hue) calc(70% * var(--tint-amount)) 8% / 33%
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
// Secondary foreground color (lighter)
|
|
29
|
+
--fg2-color: hsl(var(--tint-hue) calc(6% * var(--tint-amount)) 45%);
|
|
30
|
+
|
|
31
|
+
// Main background color (the color of the page background)
|
|
32
|
+
--bg-color: hsl(var(--tint-hue) calc(5% * var(--tint-amount)) 97%);
|
|
33
|
+
--bg-color-translucent: hsl(
|
|
34
|
+
var(--tint-hue) calc(30% * var(--tint-amount)) 95% / 66%
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
// Secondary background color (lighter)
|
|
38
|
+
--bg2-color: hsl(var(--tint-hue) calc(12% * var(--tint-amount)) 92%);
|
|
39
|
+
--bg2-color-translucent: hsl(
|
|
40
|
+
var(--tint-hue) calc(12% * var(--tint-amount)) 92% / 66%
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
--box-shadow: 0 2px 9px
|
|
44
|
+
hsl(var(--tint-hue) calc(85% * var(--tint-amount)) 10% / 25%);
|
|
45
|
+
--backdrop-filter: blur(10px);
|
|
46
|
+
|
|
47
|
+
--font:
|
|
48
|
+
'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica,
|
|
49
|
+
Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
|
50
|
+
--heading-font: var(--font);
|
|
51
|
+
--mono-font: 'Google Sans Code', Consolas, Courier, monospace;
|
|
52
|
+
|
|
53
|
+
--font-size: 16px;
|
|
54
|
+
--font-size-smaller: 15px;
|
|
55
|
+
--font-size-small: 13px;
|
|
56
|
+
|
|
57
|
+
--font-weight: 400;
|
|
58
|
+
--line-height: 1.7;
|
|
59
|
+
|
|
60
|
+
// Use em units to specify a size relative to normal body text
|
|
61
|
+
--mono-font-size: 0.94em;
|
|
62
|
+
--mono-line-height: 1.5;
|
|
63
|
+
--mono-border-radius: 0.375em;
|
|
64
|
+
|
|
65
|
+
color-scheme: light dark;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@media (prefers-color-scheme: dark) {
|
|
69
|
+
:root {
|
|
70
|
+
--theme-color-text: hsl(
|
|
71
|
+
var(--theme-hue) clamp(30%, var(--theme-saturation), 70%)
|
|
72
|
+
clamp(50%, calc(var(--theme-lightness) + 15%), 80%)
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
--fg-color: hsl(var(--tint-hue) calc(10% * var(--tint-amount)) 92%);
|
|
76
|
+
--fg-color-translucent: hsl(
|
|
77
|
+
var(--tint-hue) calc(85% * var(--tint-amount)) 90% / 33%
|
|
78
|
+
);
|
|
79
|
+
--fg2-color: hsl(var(--tint-hue) calc(6% * var(--tint-amount)) 60%);
|
|
80
|
+
--bg-color: hsl(var(--tint-hue) calc(3% * var(--tint-amount)) 5%);
|
|
81
|
+
--bg-color-translucent: hsl(
|
|
82
|
+
var(--tint-hue) calc(15% * var(--tint-amount)) 10% / 66%
|
|
83
|
+
);
|
|
84
|
+
--bg2-color: hsl(var(--tint-hue) calc(12% * var(--tint-amount)) 12%);
|
|
85
|
+
--bg2-color-translucent: hsl(
|
|
86
|
+
var(--tint-hue) calc(12% * var(--tint-amount)) 12% / 66%
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
--box-shadow: 0 3px 8px
|
|
90
|
+
hsl(var(--tint-hue) calc(33% * var(--tint-amount)) 8% / 70%);
|
|
91
|
+
|
|
92
|
+
--link-active-color: #fff;
|
|
93
|
+
|
|
94
|
+
--warning-bg-color: hsl(36 77% 27%);
|
|
95
|
+
--note-bg-color: hsl(209 90% 25%);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<% if (site.features.favicon) { %>
|
|
5
|
+
<link rel="icon" href="<%= applyBasePath('/favicon.ico') %>" sizes="any" />
|
|
6
|
+
<link rel="icon" type="image/svg+xml" href="<%= applyBasePath('/favicon.svg') %>" />
|
|
7
|
+
<link rel="icon" type="image/png" sizes="512x512" href="<%= applyBasePath('/favicon-512.png') %>" />
|
|
8
|
+
<link rel="icon" type="image/png" sizes="256x256" href="<%= applyBasePath('/favicon-256.png') %>" />
|
|
9
|
+
<link rel="icon" type="image/png" sizes="192x192" href="<%= applyBasePath('/favicon-192.png') %>" />
|
|
10
|
+
<link rel="icon" type="image/png" sizes="128x128" href="<%= applyBasePath('/favicon-128.png') %>" />
|
|
11
|
+
<link rel="icon" type="image/png" sizes="64x64" href="<%= applyBasePath('/favicon-64.png') %>" />
|
|
12
|
+
<link rel="icon" type="image/png" sizes="48x48" href="<%= applyBasePath('/favicon-48.png') %>" />
|
|
13
|
+
<link rel="icon" type="image/png" sizes="32x32" href="<%= applyBasePath('/favicon-32.png') %>" />
|
|
14
|
+
<link rel="icon" type="image/png" sizes="16x16" href="<%= applyBasePath('/favicon-16.png') %>" />
|
|
15
|
+
<link rel="apple-touch-icon" sizes="512x512" href="<%= applyBasePath('/favicon-512.png') %>" />
|
|
16
|
+
<link rel="apple-touch-icon" sizes="256x256" href="<%= applyBasePath('/favicon-256.png') %>" />
|
|
17
|
+
<link rel="manifest" href="<%= applyBasePath('/manifest.json') %>" />
|
|
18
|
+
<meta name="apple-mobile-web-app-title" content="<%= site.symbol %>">
|
|
19
|
+
<meta name="mobile-web-app-capable" content="yes">
|
|
20
|
+
<% } %>
|
|
21
|
+
<meta charset="UTF-8" />
|
|
22
|
+
<meta http-equiv="cache-control" content="no-cache" />
|
|
23
|
+
<meta http-equiv="expires" content="0" />
|
|
24
|
+
<meta http-equiv="pragma" content="no-cache" />
|
|
25
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
26
|
+
<% if (page.description) { %>
|
|
27
|
+
<meta name="description" content="<%= page.description %>" />
|
|
28
|
+
<% } %>
|
|
29
|
+
<meta property="og:title" content="<%= page.title %>" />
|
|
30
|
+
<% if (page.author) { %>
|
|
31
|
+
<meta name="author" content="<%= page.author.name %>" />
|
|
32
|
+
<meta property="og:author" content="<%= page.author.name %>" />
|
|
33
|
+
<% } %>
|
|
34
|
+
<title><%= page.title %><%= site.titlePostfix %></title>
|
|
35
|
+
<script type="speculationrules">
|
|
36
|
+
{
|
|
37
|
+
"prerender": [{
|
|
38
|
+
"where": {
|
|
39
|
+
"href_matches": "<%= applyBasePath('/') %>*"
|
|
40
|
+
},
|
|
41
|
+
"eagerness": "moderate"
|
|
42
|
+
}]
|
|
43
|
+
}
|
|
44
|
+
</script>
|
|
45
|
+
<noscript><style>
|
|
46
|
+
.question-a-body:not([data-revealed]) > * { color: unset; background-color: unset; }
|
|
47
|
+
.question-a-body:not([data-revealed]) > * * { color: unset; background-color: unset; }
|
|
48
|
+
.question-a-body::after { display: none; }
|
|
49
|
+
.question-a-body { cursor: unset; }
|
|
50
|
+
</style></noscript>
|
|
51
|
+
</head>
|
|
52
|
+
|
|
53
|
+
<body class="<%= page.template %><% if (page.tocHtml) { %> toc-is-active<% } %>">
|
|
54
|
+
<header data-pagefind-ignore>
|
|
55
|
+
<details>
|
|
56
|
+
<summary aria-label="Toggle site navigation">
|
|
57
|
+
<span class="logo" aria-hidden="true"><%= site.symbol %></span>
|
|
58
|
+
<span class="site-title" aria-hidden="true"><%= site.title %></span>
|
|
59
|
+
<div id="to-top-container" class="to-top-container"></div>
|
|
60
|
+
<% if (site.features.search) { %>
|
|
61
|
+
<div class="search-controls">
|
|
62
|
+
<input type="search" class="search quick-search"
|
|
63
|
+
autocomplete="off" name="quick-search"
|
|
64
|
+
placeholder="Search"
|
|
65
|
+
role="combobox"
|
|
66
|
+
aria-haspopup="listbox"
|
|
67
|
+
aria-autocomplete="list"
|
|
68
|
+
aria-expanded="false"
|
|
69
|
+
aria-controls="quick-search-results"
|
|
70
|
+
disabled />
|
|
71
|
+
<script>document.currentScript.previousElementSibling.disabled=false;</script>
|
|
72
|
+
<% if (site.features.search) { %>
|
|
73
|
+
<div class="results-container" role="region"
|
|
74
|
+
aria-label="Search results"
|
|
75
|
+
aria-live="polite" aria-hidden="true" inert>
|
|
76
|
+
<div class="results" style="display:none"></div>
|
|
77
|
+
</div>
|
|
78
|
+
<% } %>
|
|
79
|
+
</div>
|
|
80
|
+
<% } %>
|
|
81
|
+
</summary>
|
|
82
|
+
<div class="content">
|
|
83
|
+
<%= render('_nav.html') %>
|
|
84
|
+
</div>
|
|
85
|
+
</details>
|
|
86
|
+
</header>
|
|
87
|
+
<div class="container">
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "object",
|
|
3
|
+
"additionalProperties": {
|
|
4
|
+
"type": "object",
|
|
5
|
+
"required": ["name", "avatar"],
|
|
6
|
+
"properties": {
|
|
7
|
+
"name": { "type": "string" },
|
|
8
|
+
"avatar": { "type": "string", "pattern": "^\\/[-a-zA-Z0-9_./#]*$" },
|
|
9
|
+
"url": { "type": "string", "pattern": "^\\/[-a-zA-Z0-9_./#]*$" }
|
|
10
|
+
},
|
|
11
|
+
"additionalProperties": false
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<%= render('_top.html') %>
|
|
2
|
+
|
|
3
|
+
<div class="main-content">
|
|
4
|
+
<main class="body" data-pagefind-body>
|
|
5
|
+
<div class="file-header">
|
|
6
|
+
<h1 data-pagefind-weight="10"><%= page.titleHtml %></h1>
|
|
7
|
+
<%= render('_download.html') %>
|
|
8
|
+
</div>
|
|
9
|
+
<div class="code-body">
|
|
10
|
+
<%= content %>
|
|
11
|
+
</div>
|
|
12
|
+
<div class="code-scrollbar" aria-hidden="true">
|
|
13
|
+
<div></div>
|
|
14
|
+
</div>
|
|
15
|
+
<% if (page.tocItems && page.tocItems.length > 0) { %>
|
|
16
|
+
<div class="code-search-index" aria-hidden="true" inert>
|
|
17
|
+
<% page.tocItems.forEach(function(item) { %>
|
|
18
|
+
<section>
|
|
19
|
+
<h2 id="L<%= item.line %>"><%= item.name %></h2>
|
|
20
|
+
<p><%= item.label %> defined at line <%= item.line %>.</p>
|
|
21
|
+
</section>
|
|
22
|
+
<% }); %>
|
|
23
|
+
</div>
|
|
24
|
+
<% } %>
|
|
25
|
+
</main>
|
|
26
|
+
<% if (page.tocHtml) { %>
|
|
27
|
+
<nav class="toc"><%= page.tocHtml %></nav>
|
|
28
|
+
<% } %>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<%= render('_bottom.html') %>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<%= render('_top.html') %>
|
|
2
|
+
|
|
3
|
+
<div class="main-content">
|
|
4
|
+
<%= render('_heading.html') %>
|
|
5
|
+
<main class="body" data-pagefind-body>
|
|
6
|
+
<%= content %>
|
|
7
|
+
</main>
|
|
8
|
+
<% if (page.toc && page.tocHtml) { %>
|
|
9
|
+
<nav class="toc"><%= page.tocHtml %></nav>
|
|
10
|
+
<% } %>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<%= render('_bottom.html') %>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<%= render('_top.html') %>
|
|
2
|
+
|
|
3
|
+
<div class="main-content">
|
|
4
|
+
<%= render('_heading.html') %>
|
|
5
|
+
<main class="body" data-pagefind-body>
|
|
6
|
+
<div class="file-header">
|
|
7
|
+
<code><%= page.downloadName %></code><%= render('_download.html') %>
|
|
8
|
+
</div>
|
|
9
|
+
<%= content %>
|
|
10
|
+
</main>
|
|
11
|
+
<% if (page.toc && page.tocHtml) { %>
|
|
12
|
+
<nav class="toc"><%= page.tocHtml %></nav>
|
|
13
|
+
<% } %>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<%= render('_bottom.html') %>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "array",
|
|
3
|
+
"items": {
|
|
4
|
+
"type": "object",
|
|
5
|
+
"required": ["title", "links"],
|
|
6
|
+
"properties": {
|
|
7
|
+
"title": { "type": "string" },
|
|
8
|
+
"links": {
|
|
9
|
+
"type": "array",
|
|
10
|
+
"items": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"required": ["text"],
|
|
13
|
+
"properties": {
|
|
14
|
+
"text": { "type": "string" },
|
|
15
|
+
"internal": { "type": "string", "pattern": "^\\/.*$" },
|
|
16
|
+
"external": { "type": "string", "pattern": "^https?:\\/\\/.*$" },
|
|
17
|
+
"disabled": { "type": "boolean" },
|
|
18
|
+
"keywords": { "type": "array", "items": { "type": "string" } }
|
|
19
|
+
},
|
|
20
|
+
"oneOf": [{ "required": ["internal"] }, { "required": ["external"] }],
|
|
21
|
+
"additionalProperties": false
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"additionalProperties": false
|
|
26
|
+
}
|
|
27
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "@tsconfig/node23/tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"target": "ES6",
|
|
5
|
+
"lib": ["es2024", "dom"],
|
|
6
|
+
"module": "esnext",
|
|
7
|
+
"moduleResolution": "bundler",
|
|
8
|
+
"outDir": "./dist/",
|
|
9
|
+
"resolveJsonModule": true,
|
|
10
|
+
"strict": true,
|
|
11
|
+
"noUnusedLocals": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src", "types"],
|
|
14
|
+
"exclude": ["node_modules", "**/*.spec.ts", "**/*.test.ts"]
|
|
15
|
+
}
|
package/types/dev.ts
ADDED
package/types/sass.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare module '*.scss';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface TimezoneDef {
|
|
2
|
+
value: string;
|
|
3
|
+
label: string;
|
|
4
|
+
abbreviation: string;
|
|
5
|
+
offsetMinutes?: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface Window {
|
|
9
|
+
siteVariables: {
|
|
10
|
+
base: string;
|
|
11
|
+
basePath: string;
|
|
12
|
+
titlePostfix?: string;
|
|
13
|
+
defaultTimeZone: string;
|
|
14
|
+
timezones: TimezoneDef[];
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const { createApplyBasePath } = require('./utils/paths');
|
|
2
|
+
const { makeLogger } = require('./log');
|
|
3
|
+
|
|
4
|
+
const log = makeLogger(__filename);
|
|
5
|
+
|
|
6
|
+
module.exports = function externalLinks(md, siteVariables, options = {}) {
|
|
7
|
+
const applyBasePath = createApplyBasePath(siteVariables);
|
|
8
|
+
const rewriteCodeLinks = siteVariables.features?.code !== false;
|
|
9
|
+
|
|
10
|
+
function rewriteInternalHref(href) {
|
|
11
|
+
const match = href.match(/^([^?#]*)(.*)$/);
|
|
12
|
+
const pathname = match ? match[1] : href;
|
|
13
|
+
const suffix = match ? match[2] : '';
|
|
14
|
+
let modifiedPath = pathname;
|
|
15
|
+
|
|
16
|
+
if (rewriteCodeLinks) {
|
|
17
|
+
// Rewrite code file links to .html links
|
|
18
|
+
for (const ext of Object.keys(siteVariables.codeLanguages)) {
|
|
19
|
+
if (modifiedPath.endsWith(`.${ext}`)) {
|
|
20
|
+
modifiedPath = modifiedPath.replace(
|
|
21
|
+
new RegExp(`\\.${ext}$`),
|
|
22
|
+
'.html',
|
|
23
|
+
);
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return modifiedPath + suffix;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function checkAndApplyBasePath(token) {
|
|
33
|
+
if (token.type === 'link_open') {
|
|
34
|
+
const href = token.attrGet('href');
|
|
35
|
+
|
|
36
|
+
if (href.startsWith('/')) {
|
|
37
|
+
const modifiedHref = rewriteInternalHref(href);
|
|
38
|
+
const afterApply = applyBasePath(modifiedHref);
|
|
39
|
+
log.debug`Applying base path: ${href} -> ${afterApply}`;
|
|
40
|
+
token.attrSet('href', afterApply);
|
|
41
|
+
} else if (
|
|
42
|
+
!href.startsWith('#') &&
|
|
43
|
+
!href.startsWith('//') &&
|
|
44
|
+
!/^[a-zA-Z][a-zA-Z\d+.-]*:/.test(href)
|
|
45
|
+
) {
|
|
46
|
+
// Relative link: only rewrite code extensions, don't apply base path
|
|
47
|
+
const modifiedHref = rewriteInternalHref(href);
|
|
48
|
+
if (modifiedHref !== href) {
|
|
49
|
+
log.debug`Rewriting internal link: ${href} -> ${modifiedHref}`;
|
|
50
|
+
token.attrSet('href', modifiedHref);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
} else if (token.type === 'image') {
|
|
54
|
+
const src = token.attrGet('src');
|
|
55
|
+
if (src && src.startsWith('/')) {
|
|
56
|
+
const afterApply = applyBasePath(src);
|
|
57
|
+
log.debug`Applying base path to image: ${src} -> ${afterApply}`;
|
|
58
|
+
token.attrSet('src', afterApply);
|
|
59
|
+
}
|
|
60
|
+
} else if (token.type === 'html_block' || token.type === 'html_inline') {
|
|
61
|
+
token.content = token.content.replace(
|
|
62
|
+
/(<img\b[^>]*\bsrc=")(\/)([^"]*")/g,
|
|
63
|
+
(match, prefix, slash, rest) => {
|
|
64
|
+
const src = slash + rest.slice(0, -1);
|
|
65
|
+
const afterApply = applyBasePath(src);
|
|
66
|
+
log.debug`Applying base path to img tag src: ${src} -> ${afterApply}`;
|
|
67
|
+
return prefix + afterApply + '"';
|
|
68
|
+
},
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
token.children?.map(checkAndApplyBasePath);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
md.core.ruler.push('apply_base_path', state => {
|
|
76
|
+
state.tokens.map(checkAndApplyBasePath);
|
|
77
|
+
});
|
|
78
|
+
};
|