@abreen/tada 1.19.5 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -2
- package/build/bundle.ts +1 -27
- package/build/source-records.ts +2 -2
- package/build/toc-plugin.ts +31 -16
- package/build/types.d.ts +1 -3
- package/build/utils/derive-theme.ts +14 -61
- package/build/utils/literate-java.ts +1 -24
- package/build/utils/markdown.ts +1 -35
- package/init/content/lectures/01/Pair.java.md +5 -4
- package/init/content/lectures/index.md +5 -2
- package/init/content/markdown.md +22 -62
- package/package.json +1 -1
- package/src/_alerts.scss +5 -4
- package/src/_base.scss +22 -13
- package/src/code/_index.scss +5 -3
- package/src/header/_index.scss +9 -0
- package/src/header/index.ts +1 -1
- package/src/navigate/runtime.ts +0 -5
- package/src/print/_index.scss +5 -7
- package/src/toc/index.ts +7 -32
- package/src/toc/model.ts +24 -6
- package/templates/_theme.scss +23 -6
package/README.md
CHANGED
|
@@ -62,8 +62,6 @@ A static site generator. The successor to Presto.
|
|
|
62
62
|
* Highlights the heading currently being viewed
|
|
63
63
|
|
|
64
64
|
- Extended Markdown syntax
|
|
65
|
-
* `<<< details ... <<<` renders a collapsible box
|
|
66
|
-
* `::: section ... :::` renders a special section with a fancy background
|
|
67
65
|
* `!!! note ... !!!` and `!!! warning ... !!!` render alert boxes
|
|
68
66
|
* `??? question ... ???` renders a Q&A section; answer is hidden until click
|
|
69
67
|
- To display clickable multiple choice options, use a [task list][task-list]
|
package/build/bundle.ts
CHANGED
|
@@ -3,11 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import { compileTemplate } from './lodash-template';
|
|
4
4
|
import * as sass from 'sass';
|
|
5
5
|
import { getPackageDir, toPosix } from './utils/paths';
|
|
6
|
-
import {
|
|
7
|
-
deriveTheme,
|
|
8
|
-
deriveLinkHue,
|
|
9
|
-
deriveTraceLineActiveHue,
|
|
10
|
-
} from './utils/derive-theme';
|
|
6
|
+
import { deriveTheme } from './utils/derive-theme';
|
|
11
7
|
import type { PluginBuilder } from 'bun';
|
|
12
8
|
import type { SiteVariables } from './types';
|
|
13
9
|
import timezones from '../src/timezone/timezones.json' with { type: 'json' };
|
|
@@ -38,14 +34,6 @@ function getBundleNaming(): string {
|
|
|
38
34
|
return `[name].bundle.tada-${version}.[ext]`;
|
|
39
35
|
}
|
|
40
36
|
|
|
41
|
-
function formatCssNumber(value: number, precision = 4): string {
|
|
42
|
-
if (Number.isInteger(value)) {
|
|
43
|
-
return String(value);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return value.toFixed(precision).replace(/\.?0+$/, '');
|
|
47
|
-
}
|
|
48
|
-
|
|
49
37
|
// Returns the rendered SCSS source of the theme module (`config/theme`)
|
|
50
38
|
function renderThemeScss(siteVariables: SiteVariables): string {
|
|
51
39
|
const templatePath = path.join(getPackageDir(), 'templates/_theme.scss');
|
|
@@ -54,14 +42,6 @@ function renderThemeScss(siteVariables: SiteVariables): string {
|
|
|
54
42
|
const tintHue = siteVariables.tintHue ?? 190;
|
|
55
43
|
const tintAmount = siteVariables.tintAmount ?? 100;
|
|
56
44
|
|
|
57
|
-
const linkHue = formatCssNumber(deriveLinkHue(tintHue));
|
|
58
|
-
const linkColor = `hsl(${linkHue}deg 66% 38%)`;
|
|
59
|
-
const linkColorHover = `hsl(${linkHue}deg 56% 49%)`;
|
|
60
|
-
const linkColorDark = `hsl(${linkHue}deg 50% 72%)`;
|
|
61
|
-
const linkColorHoverDark = `hsl(${linkHue}deg 40% 80%)`;
|
|
62
|
-
const traceLineActiveHue = formatCssNumber(deriveTraceLineActiveHue(tintHue));
|
|
63
|
-
const bgTraceLineActive = `hsl(${traceLineActiveHue}deg 100% 86%)`;
|
|
64
|
-
const bgTraceLineActiveDark = `hsl(${traceLineActiveHue}deg 90% 18%)`;
|
|
65
45
|
const customFontFaces = renderCustomFontFaceScss(siteVariables.fontOverrides);
|
|
66
46
|
const customFontTuning = renderCustomFontTuningScss(
|
|
67
47
|
siteVariables.fontOverrides,
|
|
@@ -71,12 +51,6 @@ function renderThemeScss(siteVariables: SiteVariables): string {
|
|
|
71
51
|
...theme,
|
|
72
52
|
tintHue,
|
|
73
53
|
tintAmount,
|
|
74
|
-
linkColor,
|
|
75
|
-
linkColorHover,
|
|
76
|
-
linkColorDark,
|
|
77
|
-
linkColorHoverDark,
|
|
78
|
-
bgTraceLineActive,
|
|
79
|
-
bgTraceLineActiveDark,
|
|
80
54
|
serifFontStack: getSerifFontStack(siteVariables.fontOverrides),
|
|
81
55
|
serifMonoFontStack: getSerifMonoFontStack(siteVariables.fontOverrides),
|
|
82
56
|
serifFontFeatureSettings: renderFontFeatureSettings(
|
package/build/source-records.ts
CHANGED
|
@@ -203,7 +203,7 @@ export function createPublicRecord(
|
|
|
203
203
|
};
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
-
|
|
206
|
+
function collectSourceOutputs(
|
|
207
207
|
assets: Asset[],
|
|
208
208
|
traceOutputs: ReadonlyMap<string, string>,
|
|
209
209
|
): Map<string, OutputContent> {
|
|
@@ -223,7 +223,7 @@ function cloneHtmlOutputAnalysis(
|
|
|
223
223
|
return { outgoingTargets: new Set(analysis.outgoingTargets) };
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
-
|
|
226
|
+
function collectSourceHtmlAnalysis(
|
|
227
227
|
assets: Asset[],
|
|
228
228
|
): Map<string, HtmlOutputAnalysis> {
|
|
229
229
|
const htmlAnalysisByOutputPath = new Map<string, HtmlOutputAnalysis>();
|
package/build/toc-plugin.ts
CHANGED
|
@@ -23,6 +23,14 @@ interface AlertItem {
|
|
|
23
23
|
|
|
24
24
|
type TocItem = HeadingItem | DinkusItem | AlertItem;
|
|
25
25
|
|
|
26
|
+
// Net <details> elements an HTML block opens. Markdown between a raw <details>
|
|
27
|
+
// and its </details> is parsed as separate tokens, so track the nesting here.
|
|
28
|
+
function detailsDepthChange(html: string): number {
|
|
29
|
+
const opened = html.match(/<details[\s>]/gi)?.length ?? 0;
|
|
30
|
+
const closed = html.match(/<\/details\s*>/gi)?.length ?? 0;
|
|
31
|
+
return opened - closed;
|
|
32
|
+
}
|
|
33
|
+
|
|
26
34
|
export function tocPlugin(md: MarkdownIt): void {
|
|
27
35
|
md.core.ruler.push('toc_collector', state => {
|
|
28
36
|
if (!state.env) {
|
|
@@ -31,14 +39,27 @@ export function tocPlugin(md: MarkdownIt): void {
|
|
|
31
39
|
|
|
32
40
|
const tokens = state.tokens;
|
|
33
41
|
const items: TocItem[] = [];
|
|
34
|
-
|
|
42
|
+
let containerDepth = 0;
|
|
43
|
+
let detailsDepth = 0;
|
|
35
44
|
const usedAlertIds = new Map<string, number>();
|
|
36
45
|
|
|
37
46
|
for (let i = 0; i < tokens.length; i++) {
|
|
38
47
|
const token = tokens[i];
|
|
39
48
|
|
|
40
|
-
//
|
|
49
|
+
// Nothing inside a <details> is listed
|
|
50
|
+
if (token.type === 'html_block') {
|
|
51
|
+
detailsDepth = Math.max(
|
|
52
|
+
0,
|
|
53
|
+
detailsDepth + detailsDepthChange(token.content),
|
|
54
|
+
);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Headings (included at any container depth, but not inside a <details>)
|
|
41
59
|
if (token.type === 'heading_open') {
|
|
60
|
+
if (detailsDepth > 0) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
42
63
|
const inline = tokens[i + 1];
|
|
43
64
|
if (!inline || inline.type !== 'inline') {
|
|
44
65
|
continue;
|
|
@@ -56,22 +77,20 @@ export function tocPlugin(md: MarkdownIt): void {
|
|
|
56
77
|
continue;
|
|
57
78
|
}
|
|
58
79
|
|
|
59
|
-
// Thematic breaks / dinkuses (
|
|
80
|
+
// Thematic breaks / dinkuses (not inside an alert, question, or <details>)
|
|
60
81
|
if (
|
|
61
82
|
token.type === 'hr' &&
|
|
62
|
-
|
|
83
|
+
containerDepth === 0 &&
|
|
84
|
+
detailsDepth === 0 &&
|
|
63
85
|
!state.env?.slides
|
|
64
86
|
) {
|
|
65
87
|
items.push({ kind: 'dinkus' });
|
|
66
88
|
continue;
|
|
67
89
|
}
|
|
68
90
|
|
|
69
|
-
// Alerts (
|
|
91
|
+
// Alerts (not inside another alert, question, or <details>)
|
|
70
92
|
// Must be checked before generic container tracking below
|
|
71
93
|
if (token.type === 'container_alert_open') {
|
|
72
|
-
const depth = containerStack.length;
|
|
73
|
-
const parentIsSection = depth === 1 && containerStack[0] === 'section';
|
|
74
|
-
|
|
75
94
|
const match = token.info
|
|
76
95
|
.trim()
|
|
77
96
|
.match(/^(note|warning)(?:\s+"(.+)"|\s+(.+))?$/);
|
|
@@ -81,7 +100,7 @@ export function tocPlugin(md: MarkdownIt): void {
|
|
|
81
100
|
// Assign IDs to every alert, including those excluded from the TOC.
|
|
82
101
|
const id = deduplicateId(usedAlertIds, textToId(title || type));
|
|
83
102
|
token.attrSet('id', id);
|
|
84
|
-
if (
|
|
103
|
+
if (containerDepth === 0 && detailsDepth === 0) {
|
|
85
104
|
const displayTitle = title
|
|
86
105
|
? md.utils.escapeHtml(curlyQuote(title))
|
|
87
106
|
: type === 'warning'
|
|
@@ -90,23 +109,19 @@ export function tocPlugin(md: MarkdownIt): void {
|
|
|
90
109
|
items.push({ kind: 'alert', type, title: displayTitle, id });
|
|
91
110
|
}
|
|
92
111
|
}
|
|
93
|
-
// Fall through to
|
|
112
|
+
// Fall through to count the alert as a container
|
|
94
113
|
}
|
|
95
114
|
|
|
96
115
|
// Track container nesting
|
|
97
116
|
if (token.type.startsWith('container_') && token.type.endsWith('_open')) {
|
|
98
|
-
|
|
99
|
-
'container_'.length,
|
|
100
|
-
-'_open'.length,
|
|
101
|
-
);
|
|
102
|
-
containerStack.push(containerType);
|
|
117
|
+
containerDepth++;
|
|
103
118
|
continue;
|
|
104
119
|
}
|
|
105
120
|
if (
|
|
106
121
|
token.type.startsWith('container_') &&
|
|
107
122
|
token.type.endsWith('_close')
|
|
108
123
|
) {
|
|
109
|
-
|
|
124
|
+
containerDepth--;
|
|
110
125
|
continue;
|
|
111
126
|
}
|
|
112
127
|
}
|
package/build/types.d.ts
CHANGED
|
@@ -217,10 +217,8 @@ export interface LiterateCodeBlock {
|
|
|
217
217
|
hidden: boolean;
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
-
/** Result from
|
|
220
|
+
/** Result from extractLiterateJavaCode */
|
|
221
221
|
export interface LiterateJavaParseResult {
|
|
222
|
-
pageVariables: Record<string, unknown>;
|
|
223
|
-
content: string;
|
|
224
222
|
javaSource: string;
|
|
225
223
|
codeBlocks: LiterateCodeBlock[];
|
|
226
224
|
visibleBlockIndices: number[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { parse, oklch,
|
|
2
|
-
import type { Oklch
|
|
1
|
+
import { parse, oklch, toGamut, formatHex, wcagContrast } from 'culori';
|
|
2
|
+
import type { Oklch } from 'culori';
|
|
3
3
|
import type { DerivedTheme } from '../types';
|
|
4
4
|
|
|
5
5
|
// OKLCH lightness range for the theme color used as backgrounds/outlines
|
|
@@ -14,36 +14,17 @@ const LIGHT_TEXT_L_MAX = 0.5;
|
|
|
14
14
|
const DARK_TEXT_L_MIN = 0.7;
|
|
15
15
|
const DARK_TEXT_L_MAX = 0.82;
|
|
16
16
|
|
|
17
|
-
const LINK_ANCHOR_HUE = 212;
|
|
18
|
-
const LINK_TINT_BLEND = 0.05;
|
|
19
|
-
const TRACE_LINE_ACTIVE_ANCHOR_HUE = 52;
|
|
20
|
-
const TRACE_LINE_ACTIVE_TINT_BLEND = 0.1;
|
|
21
|
-
|
|
22
17
|
function clamp(v: number, min: number, max: number): number {
|
|
23
18
|
return Math.min(Math.max(v, min), max);
|
|
24
19
|
}
|
|
25
20
|
|
|
26
|
-
function linearize(c: number): number {
|
|
27
|
-
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function relativeLuminance({ r, g, b }: Rgb): number {
|
|
31
|
-
return 0.2126 * linearize(r) + 0.7152 * linearize(g) + 0.0722 * linearize(b);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function contrastRatio(l1: number, l2: number): number {
|
|
35
|
-
return (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
21
|
// Pick black or white text for use on the given background color.
|
|
39
|
-
// Prefer white unless black has substantially better contrast (1.
|
|
22
|
+
// Prefer white unless black has substantially better contrast (1.9x),
|
|
40
23
|
// which gives white text on saturated mid-tone colors like steelblue and tomato.
|
|
41
24
|
function pickTextColor(bgHex: string): '#fff' | '#000' {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const blackContrast = contrastRatio(bgLum, 0);
|
|
46
|
-
return blackContrast > whiteContrast * 1.9 ? '#000' : '#fff';
|
|
25
|
+
return wcagContrast(bgHex, '#000') > wcagContrast(bgHex, '#fff') * 1.9
|
|
26
|
+
? '#000'
|
|
27
|
+
: '#fff';
|
|
47
28
|
}
|
|
48
29
|
|
|
49
30
|
function toHex(oklchColor: Oklch): string {
|
|
@@ -56,48 +37,20 @@ export function deriveTheme(cssColor: string): DerivedTheme {
|
|
|
56
37
|
throw new Error(`Invalid color: ${cssColor}`);
|
|
57
38
|
}
|
|
58
39
|
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const lightL = clamp(l, LIGHT_THEME_L_MIN, LIGHT_THEME_L_MAX);
|
|
65
|
-
const darkL = clamp(l, DARK_THEME_L_MIN, DARK_THEME_L_MAX);
|
|
40
|
+
// Keep the hue and chroma of the theme color, clamping only its lightness
|
|
41
|
+
const base = oklch(parsed);
|
|
42
|
+
const withLightness = (min: number, max: number) =>
|
|
43
|
+
toHex({ ...base, l: clamp(base.l, min, max) });
|
|
66
44
|
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
const themeColorLight = toHex({ mode: 'oklch', l: lightL, c, h });
|
|
71
|
-
const themeColorDark = toHex({ mode: 'oklch', l: darkL, c, h });
|
|
45
|
+
const themeColorLight = withLightness(LIGHT_THEME_L_MIN, LIGHT_THEME_L_MAX);
|
|
46
|
+
const themeColorDark = withLightness(DARK_THEME_L_MIN, DARK_THEME_L_MAX);
|
|
72
47
|
|
|
73
48
|
return {
|
|
74
49
|
themeColorLight,
|
|
75
50
|
themeColorDark,
|
|
76
|
-
themeColorTextLight:
|
|
77
|
-
themeColorTextDark:
|
|
51
|
+
themeColorTextLight: withLightness(LIGHT_TEXT_L_MIN, LIGHT_TEXT_L_MAX),
|
|
52
|
+
themeColorTextDark: withLightness(DARK_TEXT_L_MIN, DARK_TEXT_L_MAX),
|
|
78
53
|
textOnThemeLight: pickTextColor(themeColorLight),
|
|
79
54
|
textOnThemeDark: pickTextColor(themeColorDark),
|
|
80
55
|
};
|
|
81
56
|
}
|
|
82
|
-
|
|
83
|
-
export function deriveLinkHue(tintHue: number): number {
|
|
84
|
-
const diff = ((tintHue - LINK_ANCHOR_HUE + 540) % 360) - 180;
|
|
85
|
-
return (LINK_ANCHOR_HUE + diff * LINK_TINT_BLEND + 360) % 360;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export function deriveTraceLineActiveHue(tintHue: number): number {
|
|
89
|
-
const diff = ((tintHue - TRACE_LINE_ACTIVE_ANCHOR_HUE + 540) % 360) - 180;
|
|
90
|
-
return (
|
|
91
|
-
(TRACE_LINE_ACTIVE_ANCHOR_HUE + diff * TRACE_LINE_ACTIVE_TINT_BLEND + 360) %
|
|
92
|
-
360
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export function getTextOnColor(cssColor: string): '#fff' | '#000' {
|
|
97
|
-
const parsed = parse(cssColor);
|
|
98
|
-
if (!parsed) {
|
|
99
|
-
throw new Error(`Invalid color: ${cssColor}`);
|
|
100
|
-
}
|
|
101
|
-
const gamutMapped = formatHex(toGamut('rgb', 'oklch')(parsed));
|
|
102
|
-
return pickTextColor(gamutMapped);
|
|
103
|
-
}
|
|
@@ -3,11 +3,8 @@ import type Token from 'markdown-it/lib/token.mjs';
|
|
|
3
3
|
import os from 'os';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { execFileSync } from 'child_process';
|
|
6
|
-
import { createMarkdown } from './markdown';
|
|
7
6
|
import { makeLogger } from '../log';
|
|
8
|
-
import { parseFrontMatterAndContent } from './front-matter';
|
|
9
7
|
import type {
|
|
10
|
-
SiteVariables,
|
|
11
8
|
LiterateJavaParseResult,
|
|
12
9
|
LiterateCodeBlock,
|
|
13
10
|
LiterateRunnerEntry,
|
|
@@ -50,29 +47,9 @@ let runnerClassDir: string | null = null;
|
|
|
50
47
|
|
|
51
48
|
const MAIN_PATTERN = /\bvoid\s+main\s*\(/m;
|
|
52
49
|
|
|
53
|
-
export function parseLiterateJava(
|
|
54
|
-
rawContent: string,
|
|
55
|
-
siteVariables: SiteVariables,
|
|
56
|
-
): LiterateJavaParseResult {
|
|
57
|
-
const { pageVariables, content } = parseFrontMatterAndContent(
|
|
58
|
-
rawContent,
|
|
59
|
-
'.md',
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
const md = createMarkdown(siteVariables, {
|
|
63
|
-
validatorOptions: { enabled: false },
|
|
64
|
-
});
|
|
65
|
-
const tokens = md.parse(content, {});
|
|
66
|
-
|
|
67
|
-
return { pageVariables, content, ...extractLiterateJavaCode(tokens) };
|
|
68
|
-
}
|
|
69
|
-
|
|
70
50
|
export function extractLiterateJavaCode(
|
|
71
51
|
tokens: Token[],
|
|
72
|
-
):
|
|
73
|
-
LiterateJavaParseResult,
|
|
74
|
-
'javaSource' | 'codeBlocks' | 'visibleBlockIndices'
|
|
75
|
-
> {
|
|
52
|
+
): LiterateJavaParseResult {
|
|
76
53
|
const codeBlocks: LiterateCodeBlock[] = [];
|
|
77
54
|
let javaLine = 1;
|
|
78
55
|
|
package/build/utils/markdown.ts
CHANGED
|
@@ -19,7 +19,6 @@ import katexPlugin from './katex';
|
|
|
19
19
|
import markdownPartialsPlugin from './markdown-partials';
|
|
20
20
|
import type { RenderDependencyCollector, SiteVariables } from '../types';
|
|
21
21
|
|
|
22
|
-
const DETAILS_PATTERN = /^details\s+(.*)$/;
|
|
23
22
|
const ALERT_PATTERN = /^(note|warning)(?:\s+"(.+)"|\s+(.+))?$/;
|
|
24
23
|
const QUESTION_PATTERN = /^question\s+(.+)$/;
|
|
25
24
|
const MULTIPLE_CHOICE_MARKER_LENGTH = 4;
|
|
@@ -65,40 +64,7 @@ export function createMarkdown(
|
|
|
65
64
|
.use(superlinkPlugin, siteVariables)
|
|
66
65
|
.use(tocPlugin)
|
|
67
66
|
.use(columnsPlugin)
|
|
68
|
-
.use(katexPlugin)
|
|
69
|
-
.use(markdownItContainer, 'details', {
|
|
70
|
-
marker: '<',
|
|
71
|
-
validate: function (params: string) {
|
|
72
|
-
return DETAILS_PATTERN.test(params.trim());
|
|
73
|
-
},
|
|
74
|
-
|
|
75
|
-
render: function (tokens: Token[], idx: number) {
|
|
76
|
-
const m = tokens[idx].info.trim().match(DETAILS_PATTERN);
|
|
77
|
-
|
|
78
|
-
if (tokens[idx].nesting === 1) {
|
|
79
|
-
return (
|
|
80
|
-
'<details><summary>' +
|
|
81
|
-
markdown.renderInline(m![1]) +
|
|
82
|
-
'</summary><div class="content">\n'
|
|
83
|
-
);
|
|
84
|
-
} else {
|
|
85
|
-
return '</div></details>\n';
|
|
86
|
-
}
|
|
87
|
-
},
|
|
88
|
-
})
|
|
89
|
-
.use(markdownItContainer, 'section', {
|
|
90
|
-
marker: ':',
|
|
91
|
-
validate: function (params: string) {
|
|
92
|
-
return !!params.trim().match(/^section$/);
|
|
93
|
-
},
|
|
94
|
-
render: function (tokens: Token[], idx: number) {
|
|
95
|
-
if (tokens[idx].nesting === 1) {
|
|
96
|
-
return '<section>\n';
|
|
97
|
-
} else {
|
|
98
|
-
return '</section>\n';
|
|
99
|
-
}
|
|
100
|
-
},
|
|
101
|
-
});
|
|
67
|
+
.use(katexPlugin);
|
|
102
68
|
|
|
103
69
|
if (filePath && options.templateParams) {
|
|
104
70
|
markdown.use(markdownPartialsPlugin, {
|
|
@@ -159,11 +159,12 @@ Please note:
|
|
|
159
159
|
To provide some confidence that our `Pair` class works, let's write some tests.
|
|
160
160
|
|
|
161
161
|
<!---
|
|
162
|
-
Code blocks inside of Tada's special containers, like alerts,
|
|
163
|
-
|
|
162
|
+
Code blocks inside of Tada's special containers, like alerts, or inside raw HTML
|
|
163
|
+
elements, like details, are included in the final .java file.
|
|
164
164
|
-->
|
|
165
165
|
|
|
166
|
-
|
|
166
|
+
<details>
|
|
167
|
+
<summary>The <code>test()</code> helper method</summary>
|
|
167
168
|
|
|
168
169
|
```
|
|
169
170
|
private static void test(boolean condition) {
|
|
@@ -179,7 +180,7 @@ sections, etc. are included in the final .java file.
|
|
|
179
180
|
}
|
|
180
181
|
```
|
|
181
182
|
|
|
182
|
-
|
|
183
|
+
</details>
|
|
183
184
|
|
|
184
185
|
<!---
|
|
185
186
|
When Tada renders a literate code block, it trims leading whitespace common to
|
|
@@ -10,9 +10,12 @@ One-hour lectures are held weekly at <time datetime="17:40">5:40 PM</time>.
|
|
|
10
10
|
Each lecture is recorded and made available by password-protected link.
|
|
11
11
|
Return to this page to access the recordings.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
<details>
|
|
14
|
+
<summary>More details</summary>
|
|
15
|
+
|
|
14
16
|
Here is a sample collapsible with more information.
|
|
15
|
-
|
|
17
|
+
|
|
18
|
+
</details>
|
|
16
19
|
|
|
17
20
|
Following lecture, an optional office hour is held, during which you may ask
|
|
18
21
|
questions and get hints for the problem sets.
|
package/init/content/markdown.md
CHANGED
|
@@ -202,24 +202,6 @@ headings, so you may [link directly to a definition](#binary-tree):
|
|
|
202
202
|
```
|
|
203
203
|
|
|
204
204
|
|
|
205
|
-
### Collapsible section
|
|
206
|
-
|
|
207
|
-
Use
|
|
208
|
-
|
|
209
|
-
```
|
|
210
|
-
<<< details Title of *collapsible*
|
|
211
|
-
Here's the content that is visible when expanded.
|
|
212
|
-
<<<
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
to create a collapsible section (the `<details>` element):
|
|
216
|
-
|
|
217
|
-
<<< details Title of *collapsible*
|
|
218
|
-
Here's the content that is visible when expanded. (When the page is being
|
|
219
|
-
printed, all collapsibles are automatically opened.)
|
|
220
|
-
<<<
|
|
221
|
-
|
|
222
|
-
|
|
223
205
|
### Alerts
|
|
224
206
|
|
|
225
207
|
Brightly colored boxes that call attention to specific warnings or information.
|
|
@@ -304,45 +286,6 @@ becomes a multiple choice question with clickable options:
|
|
|
304
286
|
???
|
|
305
287
|
|
|
306
288
|
|
|
307
|
-
### Generic section
|
|
308
|
-
|
|
309
|
-
Break up a long page by introducing a `<section>` (a standard HTML 5 element)
|
|
310
|
-
which must start with a heading. Generic sections are displayed with a slightly
|
|
311
|
-
different background.
|
|
312
|
-
|
|
313
|
-
Use
|
|
314
|
-
|
|
315
|
-
```
|
|
316
|
-
::: section
|
|
317
|
-
|
|
318
|
-
### Submitting your work
|
|
319
|
-
|
|
320
|
-
Here are the steps to follow when you are ready to submit your work for
|
|
321
|
-
this problem set.
|
|
322
|
-
|
|
323
|
-
...
|
|
324
|
-
|
|
325
|
-
:::
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
to create:
|
|
329
|
-
|
|
330
|
-
::: section
|
|
331
|
-
|
|
332
|
-
### Submitting your work
|
|
333
|
-
|
|
334
|
-
Here are the steps to follow when you are ready to submit your work for
|
|
335
|
-
this problem set.
|
|
336
|
-
|
|
337
|
-
1. Step one
|
|
338
|
-
2. Step two
|
|
339
|
-
3. Step three
|
|
340
|
-
4. Step four
|
|
341
|
-
|
|
342
|
-
Email the files you changed to the course staff.
|
|
343
|
-
|
|
344
|
-
:::
|
|
345
|
-
|
|
346
289
|
### Two-column layout
|
|
347
290
|
|
|
348
291
|
Use
|
|
@@ -383,16 +326,33 @@ Use
|
|
|
383
326
|
[KaTeX][The library behind math support](https://katex.org/)
|
|
384
327
|
```
|
|
385
328
|
|
|
386
|
-
to
|
|
329
|
+
to render a large, button-styled link with a title and description:
|
|
387
330
|
|
|
388
331
|
[Home][Back to the front page](index.html)
|
|
389
332
|
[Markdown][You are here](markdown.html)
|
|
390
333
|
[KaTeX][The library behind math support](https://katex.org/)
|
|
391
334
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
335
|
+
The syntax is an extension of the normal link syntax, but with a second set of
|
|
336
|
+
square brackets `[ ]` for the superlink description.
|
|
337
|
+
|
|
338
|
+
Because superlinks are block elements, don't use them in paragraphs or headings.
|
|
339
|
+
Instead, separate them from surrrounding content with blank lines.
|
|
340
|
+
|
|
341
|
+
Combine superlinks with [two-column layout](#two-column-layout) to achieve a row
|
|
342
|
+
or grid of links:
|
|
343
|
+
|
|
344
|
+
+++
|
|
345
|
+
|
|
346
|
+
[First][Back to the front page](index.html)
|
|
347
|
+
[Second][Back to the front page](index.html)
|
|
348
|
+
[Third][Back to the front page](index.html)
|
|
349
|
+
|
|
350
|
+
+++
|
|
351
|
+
|
|
352
|
+
[Fourth][You are here](https://example.com)
|
|
353
|
+
[Fifth][You are here](markdown.html)
|
|
354
|
+
|
|
355
|
+
+++
|
|
396
356
|
|
|
397
357
|
---
|
|
398
358
|
|
package/package.json
CHANGED
package/src/_alerts.scss
CHANGED
|
@@ -29,10 +29,11 @@
|
|
|
29
29
|
color: var(--fg-color);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
background:
|
|
32
|
+
// Inline code keeps its padding. A border replaces the background chip,
|
|
33
|
+
// which would clash with the alert's own background.
|
|
34
|
+
:is(tt, code):not(pre *) {
|
|
35
|
+
background: none;
|
|
36
|
+
border: 1px solid var(--fg-color);
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
a:not(.superlink) {
|
package/src/_base.scss
CHANGED
|
@@ -122,14 +122,6 @@ pre {
|
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
details .content > *:first-child {
|
|
126
|
-
margin-top: 0;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
details .content > *:last-child {
|
|
130
|
-
margin-bottom: 0;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
125
|
details summary {
|
|
134
126
|
padding: var(--details-padding);
|
|
135
127
|
cursor: pointer;
|
|
@@ -146,11 +138,6 @@ main details:focus-within {
|
|
|
146
138
|
outline-offset: 1px;
|
|
147
139
|
}
|
|
148
140
|
|
|
149
|
-
details .content {
|
|
150
|
-
padding: 0 var(--details-padding) var(--details-padding)
|
|
151
|
-
var(--details-padding);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
141
|
@media (hover: hover) {
|
|
155
142
|
details summary:hover {
|
|
156
143
|
color: var(--fg2-color);
|
|
@@ -164,11 +151,33 @@ details summary:active {
|
|
|
164
151
|
}
|
|
165
152
|
|
|
166
153
|
main details {
|
|
154
|
+
padding: 0 var(--details-padding);
|
|
167
155
|
margin-bottom: 1rem;
|
|
168
156
|
border: 2px solid var(--fg-color);
|
|
169
157
|
border-radius: var(--border-radius);
|
|
170
158
|
}
|
|
171
159
|
|
|
160
|
+
main details[open] {
|
|
161
|
+
padding-bottom: var(--details-padding);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/*
|
|
165
|
+
* Plain <details> in content has no .content wrapper, and its body can be bare
|
|
166
|
+
* text, so the details element supplies the inset. The summary spans the full
|
|
167
|
+
* width so its whole row stays clickable.
|
|
168
|
+
*/
|
|
169
|
+
main details > summary {
|
|
170
|
+
margin: 0 calc(-1 * var(--details-padding));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
main details > summary + * {
|
|
174
|
+
margin-top: 0;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
main details > :last-child {
|
|
178
|
+
margin-bottom: 0;
|
|
179
|
+
}
|
|
180
|
+
|
|
172
181
|
input[type='text'],
|
|
173
182
|
input[type='search'] {
|
|
174
183
|
padding: 4px;
|
package/src/code/_index.scss
CHANGED
|
@@ -168,9 +168,11 @@ body.code .code-prose {
|
|
|
168
168
|
color: inherit;
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
code
|
|
172
|
-
|
|
173
|
-
|
|
171
|
+
// The prose block is on the same background as the inline code chip, so a
|
|
172
|
+
// border delineates inline code instead
|
|
173
|
+
:is(tt, code):not(pre *) {
|
|
174
|
+
background: none;
|
|
175
|
+
border: 1px solid var(--fg2-color);
|
|
174
176
|
}
|
|
175
177
|
}
|
|
176
178
|
}
|
package/src/header/_index.scss
CHANGED
|
@@ -171,6 +171,15 @@ header .content {
|
|
|
171
171
|
position: relative;
|
|
172
172
|
box-sizing: border-box;
|
|
173
173
|
width: 100cqw;
|
|
174
|
+
padding: 0 var(--details-padding) var(--details-padding);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
header .content > :first-child {
|
|
178
|
+
margin-top: 0;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
header .content > :last-child {
|
|
182
|
+
margin-bottom: 0;
|
|
174
183
|
}
|
|
175
184
|
|
|
176
185
|
header summary .logo {
|
package/src/header/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getElement } from '../util';
|
|
2
2
|
import { globals } from '../globals';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
type HeaderTitleState = 'site' | 'page';
|
|
5
5
|
|
|
6
6
|
export interface HeadingGeometry {
|
|
7
7
|
/** Bottom edge of the page heading block (breadcrumbs, h1, and info) */
|
package/src/navigate/runtime.ts
CHANGED
|
@@ -103,11 +103,6 @@ export function closeHeaderDetails(document: Document): void {
|
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
export interface NavigationEventDetail {
|
|
107
|
-
path: string;
|
|
108
|
-
validators: ResponseValidators;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
106
|
function dispatchNavigationEvent(
|
|
112
107
|
window: Window,
|
|
113
108
|
validators: ResponseValidators,
|
package/src/print/_index.scss
CHANGED
|
@@ -46,18 +46,16 @@
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
.code-body pre,
|
|
49
|
-
.code-prose-content code,
|
|
50
49
|
.file-header tt,
|
|
51
50
|
.file-header code {
|
|
52
51
|
border: none;
|
|
53
52
|
}
|
|
54
53
|
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
.
|
|
59
|
-
|
|
60
|
-
padding: var(--mono-padding);
|
|
54
|
+
// Alerts and code prose draw a themed border around inline code on screen.
|
|
55
|
+
// Print uses the same light border as other inline code.
|
|
56
|
+
.alert .content :is(tt, code):not(pre *),
|
|
57
|
+
body.code .code-prose .code-prose-content :is(tt, code):not(pre *) {
|
|
58
|
+
border-color: #ccc;
|
|
61
59
|
}
|
|
62
60
|
|
|
63
61
|
.footnotes {
|
package/src/toc/index.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { debounce } from '../util';
|
|
2
2
|
import {
|
|
3
3
|
alertToTableItem,
|
|
4
|
+
getHeadingsAndAlerts,
|
|
4
5
|
getHighlightIndexes,
|
|
5
6
|
headingToTableItem,
|
|
6
7
|
switchCurrent,
|
|
7
|
-
type Dinkus,
|
|
8
8
|
} from './model';
|
|
9
9
|
|
|
10
10
|
const LATENCY_MS = 50;
|
|
@@ -40,26 +40,6 @@ function scrollIfNeeded(element: HTMLElement, doc: Document) {
|
|
|
40
40
|
container.scrollTo({ top: desiredScrollTop });
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
function getHeadingsAndAlerts(
|
|
44
|
-
parent: HTMLElement,
|
|
45
|
-
): (HTMLHeadingElement | HTMLDivElement)[] {
|
|
46
|
-
return Array.from(
|
|
47
|
-
parent.querySelectorAll(
|
|
48
|
-
'.body h1, .body h2, .body h3, .body h4, .body h5, .body h6, :is(.body, .page-content) > div.alert, .body section > div.alert',
|
|
49
|
-
),
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function getTocElements(
|
|
54
|
-
parent: HTMLElement,
|
|
55
|
-
): (HTMLHeadingElement | HTMLDivElement | HTMLHRElement)[] {
|
|
56
|
-
return Array.from(
|
|
57
|
-
parent.querySelectorAll(
|
|
58
|
-
'.body h1, .body h2, .body h3, .body h4, .body h5, .body h6, :is(.body, .page-content) > div.alert, .body section > div.alert, :is(.body, .page-content) > hr',
|
|
59
|
-
),
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
43
|
/* Calculate how much to offset scroll calculations based on floating header */
|
|
64
44
|
function getHeaderOffset(doc: Document) {
|
|
65
45
|
const element = doc.querySelector('header details summary');
|
|
@@ -156,17 +136,12 @@ export default (window: Window) => {
|
|
|
156
136
|
}
|
|
157
137
|
|
|
158
138
|
const headingsAndAlerts = getHeadingsAndAlerts(window.document.body);
|
|
159
|
-
const items =
|
|
160
|
-
.map(el =>
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
return alertToTableItem(el as HTMLElement);
|
|
166
|
-
} else {
|
|
167
|
-
return headingToTableItem(el as HTMLHeadingElement);
|
|
168
|
-
}
|
|
169
|
-
})
|
|
139
|
+
const items = headingsAndAlerts
|
|
140
|
+
.map(el =>
|
|
141
|
+
el.tagName === 'DIV'
|
|
142
|
+
? alertToTableItem(el)
|
|
143
|
+
: headingToTableItem(el as HTMLHeadingElement),
|
|
144
|
+
)
|
|
170
145
|
.filter(obj => obj != null);
|
|
171
146
|
|
|
172
147
|
const highlightIndexes = getHighlightIndexes(items);
|
package/src/toc/model.ts
CHANGED
|
@@ -3,18 +3,36 @@ type AlertType = 'warning' | 'note';
|
|
|
3
3
|
|
|
4
4
|
export type Alert = { type: AlertType; title: string };
|
|
5
5
|
export type Heading = { level: HeadingLevel; innerHtml: string; id: string };
|
|
6
|
-
export type Dinkus = { type: 'dinkus' };
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
const HEADINGS = '.body h1, .body h2, .body h3, .body h4, .body h5, .body h6';
|
|
8
|
+
|
|
9
|
+
function isNestedBlock(el: Element): boolean {
|
|
10
|
+
return el.parentElement?.closest('.alert, .question') != null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Like the build-time TOC, nothing inside a <details> is listed, and alerts are
|
|
14
|
+
// only listed outside of another alert or question. Headings are listed at any
|
|
15
|
+
// other depth.
|
|
16
|
+
export function getHeadingsAndAlerts(
|
|
17
|
+
parent: ParentNode,
|
|
18
|
+
): (HTMLHeadingElement | HTMLDivElement)[] {
|
|
19
|
+
return Array.from(
|
|
20
|
+
parent.querySelectorAll<HTMLHeadingElement | HTMLDivElement>(
|
|
21
|
+
`${HEADINGS}, .body div.alert`,
|
|
22
|
+
),
|
|
23
|
+
).filter(
|
|
24
|
+
el =>
|
|
25
|
+
el.closest('details') == null &&
|
|
26
|
+
(!el.matches('div.alert') || !isNestedBlock(el)),
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function getHighlightIndexes(items: (Heading | Alert)[]) {
|
|
9
31
|
const indexes: (number | null)[] = [];
|
|
10
32
|
let currentHeadingIndex: number | null = null;
|
|
11
33
|
let tocIndex = 0;
|
|
12
34
|
|
|
13
35
|
items.forEach(item => {
|
|
14
|
-
if (!('level' in item) && item.type === 'dinkus') {
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
36
|
if ('level' in item) {
|
|
19
37
|
currentHeadingIndex = tocIndex;
|
|
20
38
|
}
|
package/templates/_theme.scss
CHANGED
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
/* TADA_CUSTOM_FONT_TUNING */
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
// Lean $anchor up to $max toward --tint-hue, most when the tint is 90deg away
|
|
12
|
+
@function lean-toward-tint($anchor, $max) {
|
|
13
|
+
@return calc(#{$anchor} + #{$max} * sin(var(--tint-hue) - #{$anchor}));
|
|
14
|
+
}
|
|
15
|
+
|
|
11
16
|
:root {
|
|
12
17
|
--theme-color: <%= themeColorLight %>;
|
|
13
18
|
--theme-color-text: <%= themeColorTextLight %>;
|
|
@@ -28,8 +33,11 @@
|
|
|
28
33
|
|
|
29
34
|
// Secondary foreground color (lighter)
|
|
30
35
|
--fg2-color: hsl(var(--tint-hue) calc(8% * var(--tint-amount)) 45%);
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
|
|
37
|
+
// Link color; the @supports block below leans the hue toward the tint
|
|
38
|
+
--link-hue: 216deg;
|
|
39
|
+
--link-color: hsl(var(--link-hue) 66% 38%);
|
|
40
|
+
--link-color-hover: hsl(var(--link-hue) 56% 49%);
|
|
33
41
|
|
|
34
42
|
// Main background color (the color of the page background)
|
|
35
43
|
--bg-color: hsl(var(--tint-hue) calc(65% * var(--tint-amount)) 97%);
|
|
@@ -44,7 +52,8 @@
|
|
|
44
52
|
--bg2-color-translucent: hsl(
|
|
45
53
|
var(--tint-hue) calc(55% * var(--tint-amount)) 92% / 66%
|
|
46
54
|
);
|
|
47
|
-
--
|
|
55
|
+
--trace-line-active-hue: 52deg;
|
|
56
|
+
--bg-trace-line-active: hsl(var(--trace-line-active-hue) 100% 86%);
|
|
48
57
|
--box-shadow: 0 2px 9px
|
|
49
58
|
hsl(var(--tint-hue) calc(85% * var(--tint-amount)) 10% / 25%);
|
|
50
59
|
--backdrop-filter: blur(10px);
|
|
@@ -82,6 +91,14 @@
|
|
|
82
91
|
color-scheme: light dark;
|
|
83
92
|
}
|
|
84
93
|
|
|
94
|
+
// Browsers without CSS sin() keep the fixed hues above
|
|
95
|
+
@supports (scale: sin(90deg)) {
|
|
96
|
+
:root {
|
|
97
|
+
--link-hue: #{lean-toward-tint(216deg, 9deg)};
|
|
98
|
+
--trace-line-active-hue: #{lean-toward-tint(52deg, 18deg)};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
85
102
|
:root[data-font-preference='serif'] {
|
|
86
103
|
--font: var(--serif-font);
|
|
87
104
|
--mono-font: var(--serif-mono-font);
|
|
@@ -110,8 +127,8 @@
|
|
|
110
127
|
var(--tint-hue) calc(85% * var(--tint-amount)) 90% / 33%
|
|
111
128
|
);
|
|
112
129
|
--fg2-color: hsl(var(--tint-hue) calc(10% * var(--tint-amount)) 60%);
|
|
113
|
-
--link-color:
|
|
114
|
-
--link-color-hover:
|
|
130
|
+
--link-color: hsl(var(--link-hue) 50% 72%);
|
|
131
|
+
--link-color-hover: hsl(var(--link-hue) 40% 80%);
|
|
115
132
|
--bg-color: hsl(var(--tint-hue) calc(35% * var(--tint-amount)) 5%);
|
|
116
133
|
--bg-color-translucent: hsl(
|
|
117
134
|
var(--tint-hue) calc(35% * var(--tint-amount)) 10% / 66%
|
|
@@ -120,7 +137,7 @@
|
|
|
120
137
|
--bg2-color-translucent: hsl(
|
|
121
138
|
var(--tint-hue) calc(40% * var(--tint-amount)) 12% / 66%
|
|
122
139
|
);
|
|
123
|
-
--bg-trace-line-active:
|
|
140
|
+
--bg-trace-line-active: hsl(var(--trace-line-active-hue) 90% 18%);
|
|
124
141
|
--box-shadow: 0 3px 8px
|
|
125
142
|
hsl(var(--tint-hue) calc(33% * var(--tint-amount)) 8% / 70%);
|
|
126
143
|
--link-active-color: #fff;
|