@abreen/tada 1.2.0 → 1.3.2
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 +1 -1
- package/bin/tada.ts +28 -1
- package/build/globals.test.ts +4 -3
- package/build/globals.ts +9 -1
- package/build/markdown-plugins.test.ts +1 -3
- package/build/serve.ts +17 -5
- package/build/site-variables.ts +1 -1
- package/build/templates.ts +9 -1
- package/build/utils/jdi-runner/LiterateRunner.java +13 -0
- package/build/utils/literate-java.ts +13 -2
- package/build/utils/render.ts +15 -7
- package/build/watch.ts +36 -15
- package/init/content/index.md +3 -2
- package/init/content/labs/00/VowelCounter.java.md +129 -0
- package/init/content/labs/00/index.md +29 -0
- package/init/content/labs/index.md +9 -0
- package/init/content/lectures/01/Pair.java.md +1 -0
- package/init/content/markdown.md +0 -2
- package/package.json +11 -1
- package/schema/site.schema.json +2 -3
- package/src/_base.scss +58 -16
- package/src/_layout.scss +8 -10
- package/src/question/style.scss +8 -3
- package/src/search/style.scss +2 -6
- package/src/style.scss +11 -44
- package/src/timezone/index.ts +4 -19
- package/templates/_top.html +1 -0
- package/build/utils/jdi-runner/LiterateRunner.class +0 -0
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ A static site generator. The successor to Presto.
|
|
|
28
28
|
* `<<< details ... <<<` renders a collapsible box
|
|
29
29
|
* `::: section ... :::` renders a special section with a fancy background
|
|
30
30
|
* `!!! note ... !!!` and `!!! warning ... !!!` render alert boxes
|
|
31
|
-
* `???
|
|
31
|
+
* `??? question ... ???` renders a Q & A section; answers hidden until click
|
|
32
32
|
* Special heading subtitles with `## Heading # A subtitle here`
|
|
33
33
|
- Automatically generated favicon
|
|
34
34
|
* Text, color, font and font weight taken from config file
|
package/bin/tada.ts
CHANGED
|
@@ -116,6 +116,21 @@ function printUsage() {
|
|
|
116
116
|
console.log(' --tint-amount, --default-time-zone, --prod-base,');
|
|
117
117
|
console.log(' --prod-base-path]');
|
|
118
118
|
continue;
|
|
119
|
+
} else if (cmd === 'watch') {
|
|
120
|
+
console.log(` ${cmd.padEnd(18)} ${desc}`);
|
|
121
|
+
console.log(
|
|
122
|
+
' [--port <n>] HTTP server port (default: 8080)',
|
|
123
|
+
);
|
|
124
|
+
console.log(
|
|
125
|
+
' [--ws-port <n>] WebSocket port for live reload (default: 35729)',
|
|
126
|
+
);
|
|
127
|
+
continue;
|
|
128
|
+
} else if (cmd === 'serve') {
|
|
129
|
+
console.log(` ${cmd.padEnd(18)} ${desc}`);
|
|
130
|
+
console.log(
|
|
131
|
+
' [--port <n>] HTTP server port (default: 8080)',
|
|
132
|
+
);
|
|
133
|
+
continue;
|
|
119
134
|
} else if (cmd === 'clean') {
|
|
120
135
|
console.log(' clean Remove the dist/ directory');
|
|
121
136
|
console.log(
|
|
@@ -137,7 +152,18 @@ function printUsage() {
|
|
|
137
152
|
}
|
|
138
153
|
|
|
139
154
|
function run(cmd: string): void {
|
|
140
|
-
|
|
155
|
+
try {
|
|
156
|
+
execSync(cmd, { cwd: process.cwd(), stdio: 'inherit' });
|
|
157
|
+
} catch (err: unknown) {
|
|
158
|
+
const code = (err as { status?: number }).status;
|
|
159
|
+
if (code != null) {
|
|
160
|
+
console.error(`tada ${command} failed with exit code ${code}`);
|
|
161
|
+
process.exit(code);
|
|
162
|
+
} else {
|
|
163
|
+
console.error(`tada ${command} failed`);
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
141
167
|
}
|
|
142
168
|
|
|
143
169
|
function copyDirRecursive(src: string, dest: string): void {
|
|
@@ -370,6 +396,7 @@ async function initCommand(args: string[]): Promise<void> {
|
|
|
370
396
|
title: 'Topics',
|
|
371
397
|
links: [
|
|
372
398
|
{ text: 'Lectures', internal: '/lectures/index.html' },
|
|
399
|
+
{ text: 'Labs', internal: '/labs/index.html' },
|
|
373
400
|
{
|
|
374
401
|
text: 'Problem Sets',
|
|
375
402
|
internal: '/problem_sets/index.html',
|
package/build/globals.test.ts
CHANGED
|
@@ -30,10 +30,11 @@ describe('createGlobals', () => {
|
|
|
30
30
|
expect(g.cx).toBe(g.classNames);
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
-
test('renderTimeZoneChooser includes
|
|
33
|
+
test('renderTimeZoneChooser includes hidden disabled select, inline script, and noscript', () => {
|
|
34
34
|
const g = makeGlobals('index', { defaultTimeZone: 'America/New_York' });
|
|
35
|
-
|
|
36
|
-
expect(
|
|
35
|
+
const html = g.renderTimeZoneChooser();
|
|
36
|
+
expect(html).toContain('<select class="time-zone" hidden disabled>');
|
|
37
|
+
expect(html).toContain('<noscript>Times shown in ET.</noscript>');
|
|
37
38
|
});
|
|
38
39
|
});
|
|
39
40
|
|
package/build/globals.ts
CHANGED
|
@@ -18,8 +18,16 @@ export default function createGlobals(
|
|
|
18
18
|
const defaultTz = timezones.find(
|
|
19
19
|
t => t.value === siteVariables.defaultTimeZone,
|
|
20
20
|
)!;
|
|
21
|
+
const options = timezones
|
|
22
|
+
.map(tz => {
|
|
23
|
+
const label =
|
|
24
|
+
tz.value === 'UTC' ? tz.label : `${tz.label} (${tz.abbreviation})`;
|
|
25
|
+
const selected = tz.value === defaultTz.value ? ' selected' : '';
|
|
26
|
+
return `<option value="${tz.value}"${selected}>${label}</option>`;
|
|
27
|
+
})
|
|
28
|
+
.join('');
|
|
21
29
|
const renderTimeZoneChooser = () =>
|
|
22
|
-
`<select class="time-zone" hidden
|
|
30
|
+
`<span class="time-zone-label" hidden>Times shown in </span><select class="time-zone" hidden disabled>${options}</select><noscript>Times shown in ${defaultTz.abbreviation}.</noscript>`;
|
|
23
31
|
|
|
24
32
|
return {
|
|
25
33
|
isHomePage: subPath === 'index',
|
|
@@ -382,9 +382,7 @@ describe('injectKatexStylesheet', () => {
|
|
|
382
382
|
const result = injectKatexStylesheet(html, p => p);
|
|
383
383
|
|
|
384
384
|
expect(result).toContain('href="/katex/katex.min.css"');
|
|
385
|
-
expect(result).toContain('
|
|
386
|
-
expect(result).toContain('onload="this.media=\'all\'"');
|
|
387
|
-
expect(result).toContain('<noscript>');
|
|
385
|
+
expect(result).toContain('rel="stylesheet"');
|
|
388
386
|
});
|
|
389
387
|
|
|
390
388
|
test('applies basePath to the stylesheet URL', () => {
|
package/build/serve.ts
CHANGED
|
@@ -20,7 +20,9 @@ try {
|
|
|
20
20
|
process.exit(1);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
function resolvePathname(
|
|
23
|
+
function resolvePathname(
|
|
24
|
+
pathname: string,
|
|
25
|
+
): { filePath: string; mtime: Date } | null {
|
|
24
26
|
let decodedPath: string;
|
|
25
27
|
try {
|
|
26
28
|
decodedPath = decodeURIComponent(pathname);
|
|
@@ -49,17 +51,27 @@ function resolvePathname(pathname: string): string | null {
|
|
|
49
51
|
return null;
|
|
50
52
|
}
|
|
51
53
|
|
|
52
|
-
return resolvedPath;
|
|
54
|
+
return { filePath: resolvedPath, mtime: stat.mtime };
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
function createResponse(req: Request): Response {
|
|
56
58
|
const url = new URL(req.url);
|
|
57
|
-
const
|
|
58
|
-
if (!
|
|
59
|
+
const result = resolvePathname(url.pathname);
|
|
60
|
+
if (!result) {
|
|
59
61
|
return new Response('Not Found', { status: 404 });
|
|
60
62
|
}
|
|
61
63
|
|
|
62
|
-
|
|
64
|
+
const headers = {
|
|
65
|
+
'Cache-Control': 'no-cache',
|
|
66
|
+
'Last-Modified': result.mtime.toUTCString(),
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const ims = req.headers.get('If-Modified-Since');
|
|
70
|
+
if (ims && new Date(ims) >= new Date(headers['Last-Modified'])) {
|
|
71
|
+
return new Response(null, { status: 304, headers });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return new Response(Bun.file(result.filePath), { headers });
|
|
63
75
|
}
|
|
64
76
|
|
|
65
77
|
function getPortArg(): number {
|
package/build/site-variables.ts
CHANGED
|
@@ -10,7 +10,7 @@ const configDir = getProjectDir();
|
|
|
10
10
|
|
|
11
11
|
const DEFAULT: Partial<SiteVariables> = {
|
|
12
12
|
basePath: '/',
|
|
13
|
-
features: { search: true, code: true, favicon:
|
|
13
|
+
features: { search: true, code: true, favicon: true },
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
const isValid = compileJsonSchema(siteSchema);
|
package/build/templates.ts
CHANGED
|
@@ -23,7 +23,12 @@ const renderStack: string[] = [];
|
|
|
23
23
|
let errorStack: string[] | null = null;
|
|
24
24
|
|
|
25
25
|
// JSON data files that live in the user's config directory
|
|
26
|
-
const
|
|
26
|
+
const REQUIRED_JSON_DATA_FILES: string[] = ['nav.json'];
|
|
27
|
+
const OPTIONAL_JSON_DATA_FILES: string[] = ['authors.json'];
|
|
28
|
+
const JSON_DATA_FILES: string[] = [
|
|
29
|
+
...REQUIRED_JSON_DATA_FILES,
|
|
30
|
+
...OPTIONAL_JSON_DATA_FILES,
|
|
31
|
+
];
|
|
27
32
|
|
|
28
33
|
function getHtmlTemplatesDir(): string {
|
|
29
34
|
return path.resolve(getPackageDir(), 'templates');
|
|
@@ -97,6 +102,9 @@ export function compileTemplates(
|
|
|
97
102
|
for (const fileName of JSON_DATA_FILES) {
|
|
98
103
|
const filePath = path.join(jsonDir, fileName);
|
|
99
104
|
if (!fs.existsSync(filePath)) {
|
|
105
|
+
if (REQUIRED_JSON_DATA_FILES.includes(fileName)) {
|
|
106
|
+
throw new Error(`Missing required config file: ${filePath}`);
|
|
107
|
+
}
|
|
100
108
|
continue;
|
|
101
109
|
}
|
|
102
110
|
|
|
@@ -95,6 +95,18 @@ public class LiterateRunner {
|
|
|
95
95
|
stderrThread.setDaemon(true);
|
|
96
96
|
stderrThread.start();
|
|
97
97
|
|
|
98
|
+
// Forward stdin to target VM in a background thread
|
|
99
|
+
OutputStream targetStdin = process.getOutputStream();
|
|
100
|
+
Thread stdinThread = new Thread(() -> {
|
|
101
|
+
try {
|
|
102
|
+
System.in.transferTo(targetStdin);
|
|
103
|
+
targetStdin.close();
|
|
104
|
+
} catch (IOException ignored) {
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
stdinThread.setDaemon(true);
|
|
108
|
+
stdinThread.start();
|
|
109
|
+
|
|
98
110
|
int activeBlock = -1;
|
|
99
111
|
|
|
100
112
|
try {
|
|
@@ -199,6 +211,7 @@ public class LiterateRunner {
|
|
|
199
211
|
} catch (VMDisconnectedException ignored) {
|
|
200
212
|
}
|
|
201
213
|
stderrThread.join(1000);
|
|
214
|
+
stdinThread.join(1000);
|
|
202
215
|
}
|
|
203
216
|
|
|
204
217
|
// Output JSON to our own stdout
|
|
@@ -39,6 +39,12 @@ export function parseLiterateJava(
|
|
|
39
39
|
continue;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
// Skip fences with a non-Java language tag (e.g., ```text)
|
|
43
|
+
const lang = token.info.trim();
|
|
44
|
+
if (token.type === 'fence' && lang !== '' && lang !== 'java') {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
|
|
42
48
|
const code = token.content;
|
|
43
49
|
const codeLines = code.endsWith('\n')
|
|
44
50
|
? code.slice(0, -1).split('\n')
|
|
@@ -138,6 +144,7 @@ export function executeLiterateJava(
|
|
|
138
144
|
className: string,
|
|
139
145
|
classPath: string,
|
|
140
146
|
codeBlocks: LiterateCodeBlock[],
|
|
147
|
+
stdin?: string,
|
|
141
148
|
): LiterateRunnerEntry[] {
|
|
142
149
|
const runnerDir = path.join(__dirname, 'jdi-runner');
|
|
143
150
|
ensureRunnerCompiled(runnerDir);
|
|
@@ -153,9 +160,13 @@ export function executeLiterateJava(
|
|
|
153
160
|
const result = execFileSync(
|
|
154
161
|
'java',
|
|
155
162
|
['-cp', runnerDir, 'LiterateRunner', className, classPath, rangesJson],
|
|
156
|
-
{ timeout: 30000, encoding: 'utf-8' },
|
|
163
|
+
{ timeout: 30000, encoding: 'utf-8', input: stdin },
|
|
157
164
|
);
|
|
158
|
-
const
|
|
165
|
+
const raw: [number, string][] = JSON.parse(result);
|
|
166
|
+
const entries: LiterateRunnerEntry[] = raw.map(([blockIndex, output]) => ({
|
|
167
|
+
blockIndex,
|
|
168
|
+
output,
|
|
169
|
+
}));
|
|
159
170
|
log.debug`LiterateRunner returned ${entries.length} output entries`;
|
|
160
171
|
return entries;
|
|
161
172
|
} catch (err: unknown) {
|
package/build/utils/render.ts
CHANGED
|
@@ -127,9 +127,7 @@ export function injectAssetTags(
|
|
|
127
127
|
.join('');
|
|
128
128
|
const asyncLinkTags = asyncAssets
|
|
129
129
|
.map(
|
|
130
|
-
asset =>
|
|
131
|
-
`<link href="${applyBasePath('/' + asset)}" rel="stylesheet" media="print" onload="this.media='all'">` +
|
|
132
|
-
`<noscript><link href="${applyBasePath('/' + asset)}" rel="stylesheet"></noscript>`,
|
|
130
|
+
asset => `<link href="${applyBasePath('/' + asset)}" rel="stylesheet">`,
|
|
133
131
|
)
|
|
134
132
|
.join('');
|
|
135
133
|
|
|
@@ -143,9 +141,7 @@ export function injectKatexStylesheet(
|
|
|
143
141
|
applyBasePath: (subPath: string) => string,
|
|
144
142
|
): string {
|
|
145
143
|
const href = applyBasePath('/katex/katex.min.css');
|
|
146
|
-
const tag =
|
|
147
|
-
`<link href="${href}" rel="stylesheet" media="print" onload="this.media='all'">` +
|
|
148
|
-
`<noscript><link href="${href}" rel="stylesheet"></noscript>`;
|
|
144
|
+
const tag = `<link href="${href}" rel="stylesheet">`;
|
|
149
145
|
return html.replace('<head>', `<head>${tag}`);
|
|
150
146
|
}
|
|
151
147
|
|
|
@@ -416,6 +412,9 @@ export function renderLiterateJavaPageAsset({
|
|
|
416
412
|
|
|
417
413
|
validateFrontMatter(pageVariables, filePath);
|
|
418
414
|
|
|
415
|
+
const stdin =
|
|
416
|
+
typeof pageVariables.stdin === 'string' ? pageVariables.stdin : undefined;
|
|
417
|
+
|
|
419
418
|
// Compile and execute the concatenated Java source
|
|
420
419
|
let tempDir: string | undefined;
|
|
421
420
|
let blockOutputMap: Map<number, string> | null = null;
|
|
@@ -429,6 +428,7 @@ export function renderLiterateJavaPageAsset({
|
|
|
429
428
|
className,
|
|
430
429
|
tempDir,
|
|
431
430
|
codeBlocks,
|
|
431
|
+
stdin,
|
|
432
432
|
);
|
|
433
433
|
blockOutputMap = new Map(
|
|
434
434
|
outputEntries.map(e => [e.blockIndex, e.output]),
|
|
@@ -447,9 +447,17 @@ export function renderLiterateJavaPageAsset({
|
|
|
447
447
|
validatorOptions: { enabled: false },
|
|
448
448
|
});
|
|
449
449
|
let fenceIndex = 0;
|
|
450
|
+
const defaultFence = md.renderer.rules.fence!;
|
|
450
451
|
|
|
451
|
-
md.renderer.rules.fence = (tokens, idx) => {
|
|
452
|
+
md.renderer.rules.fence = (tokens, idx, options, env, self) => {
|
|
452
453
|
const token = tokens[idx];
|
|
454
|
+
const lang = token.info.trim();
|
|
455
|
+
|
|
456
|
+
// Non-Java fences render with the default renderer (no line numbers)
|
|
457
|
+
if (lang !== '' && lang !== 'java') {
|
|
458
|
+
return defaultFence(tokens, idx, options, env, self);
|
|
459
|
+
}
|
|
460
|
+
|
|
453
461
|
const code = token.content;
|
|
454
462
|
const lines = code.endsWith('\n')
|
|
455
463
|
? code.slice(0, -1).split('\n')
|
package/build/watch.ts
CHANGED
|
@@ -75,7 +75,7 @@ type ChangeCategory = 'content' | 'public' | 'config';
|
|
|
75
75
|
const log = makeLogger(__filename);
|
|
76
76
|
const wslog = makeLogger('WebSocket');
|
|
77
77
|
|
|
78
|
-
// --- WebSocket server
|
|
78
|
+
// --- WebSocket server ---
|
|
79
79
|
|
|
80
80
|
let webSocketsReady = false;
|
|
81
81
|
let watcherReady = false;
|
|
@@ -511,23 +511,28 @@ initialBuild()
|
|
|
511
511
|
initialBuildFailed = true;
|
|
512
512
|
})
|
|
513
513
|
.finally(() => {
|
|
514
|
-
// Watch content, public, data files, site config
|
|
515
514
|
const watchPaths: string[] = [contentDir, publicDir];
|
|
516
515
|
|
|
517
|
-
// Watch
|
|
516
|
+
// Watch config files (nav.json, authors.json, site config) via the
|
|
517
|
+
// project root directory (depth 0). Chokidar v4 doesn't emit unlink
|
|
518
|
+
// for individually watched files, so we must watch the directory.
|
|
518
519
|
const jsonDataDir = getJsonDataDir();
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
520
|
+
const configFilePaths = new Set([
|
|
521
|
+
...JSON_DATA_FILES.map(f => path.resolve(jsonDataDir, f)),
|
|
522
|
+
path.resolve('site.dev.json'),
|
|
523
|
+
]);
|
|
524
|
+
const configWatcher = chokidar.watch(jsonDataDir, {
|
|
525
|
+
ignoreInitial: true,
|
|
526
|
+
depth: 0,
|
|
527
|
+
});
|
|
528
|
+
const onConfigFileChange = (filePath: string) => {
|
|
529
|
+
if (configFilePaths.has(path.resolve(filePath))) {
|
|
530
|
+
onFileChange(filePath);
|
|
523
531
|
}
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
if (fs.existsSync(siteConfigPath)) {
|
|
529
|
-
watchPaths.push(siteConfigPath);
|
|
530
|
-
}
|
|
532
|
+
};
|
|
533
|
+
configWatcher.on('add', onConfigFileChange);
|
|
534
|
+
configWatcher.on('change', onConfigFileChange);
|
|
535
|
+
configWatcher.on('unlink', onConfigFileChange);
|
|
531
536
|
|
|
532
537
|
const watcher = chokidar.watch(watchPaths, {
|
|
533
538
|
ignoreInitial: true,
|
|
@@ -538,12 +543,28 @@ initialBuild()
|
|
|
538
543
|
watcher.on('add', onFileChange);
|
|
539
544
|
watcher.on('unlink', onFileChange);
|
|
540
545
|
|
|
541
|
-
|
|
546
|
+
let configWatcherReady = false;
|
|
547
|
+
let contentWatcherReady = false;
|
|
548
|
+
|
|
549
|
+
function onWatcherReady(): void {
|
|
550
|
+
if (!configWatcherReady || !contentWatcherReady) {
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
542
553
|
watcherReady = true;
|
|
543
554
|
if (initialBuildFailed) {
|
|
544
555
|
broadcast('error');
|
|
545
556
|
}
|
|
546
557
|
broadcast('ready');
|
|
547
558
|
log.info`Watching for changes...`;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
configWatcher.on('ready', () => {
|
|
562
|
+
configWatcherReady = true;
|
|
563
|
+
onWatcherReady();
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
watcher.on('ready', () => {
|
|
567
|
+
contentWatcherReady = true;
|
|
568
|
+
onWatcherReady();
|
|
548
569
|
});
|
|
549
570
|
});
|
package/init/content/index.md
CHANGED
|
@@ -11,8 +11,9 @@ This is an example site built with <%= page.toolName %>.
|
|
|
11
11
|
|
|
12
12
|
## Getting started
|
|
13
13
|
|
|
14
|
-
Edit the files in `content/` to add your own pages.
|
|
15
|
-
|
|
14
|
+
Edit the files in `content/` to add your own pages. To configure the nav,
|
|
15
|
+
update `nav.json`. Update `authors.json` with your information, or delete it
|
|
16
|
+
if you won't use the `author` front matter field.
|
|
16
17
|
|
|
17
18
|
See the [Markdown examples page](/markdown.html) for syntax examples.
|
|
18
19
|
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
parent: /labs/00/index.html
|
|
2
|
+
parentLabel: Lab 0
|
|
3
|
+
title: Counting vowels
|
|
4
|
+
author: alex
|
|
5
|
+
description: An example of a literate Java program that takes predefined input.
|
|
6
|
+
stdin: "hello, world!\n"
|
|
7
|
+
|
|
8
|
+
A Java program can process input in different ways: it could open a file and
|
|
9
|
+
read it; it could open a network connection and wait for data from another
|
|
10
|
+
computer on the Internet, or it can read from the standard in.
|
|
11
|
+
|
|
12
|
+
The <dfn>standard in</dfn> (or `stdin`) is one of three streams available to a
|
|
13
|
+
running program. The other two streams are the standard out and standard error.
|
|
14
|
+
|
|
15
|
+
<figure>
|
|
16
|
+
<svg viewBox="0 50 410 90" xmlns="http://www.w3.org/2000/svg" font-size="14" fill="none" style="margin:1rem auto;display:block;max-width:410px">
|
|
17
|
+
<defs>
|
|
18
|
+
<marker id="arrow" markerWidth="8" markerHeight="8" refX="8" refY="4" orient="auto">
|
|
19
|
+
<path d="M0,0 L8,4 L0,8 Z" stroke="currentColor" fill="currentColor"></path>
|
|
20
|
+
</marker>
|
|
21
|
+
</defs>
|
|
22
|
+
<!-- Java program box -->
|
|
23
|
+
<rect x="155" y="60" width="100" height="60" stroke-width="2" stroke="currentColor"></rect>
|
|
24
|
+
<text x="205" y="95" text-anchor="middle" fill="currentColor">Program</text>
|
|
25
|
+
<!-- stdin arrow (incoming from left) -->
|
|
26
|
+
<path d="M20 90H143" marker-end="url(#arrow)" stroke="currentColor"></path>
|
|
27
|
+
<text x="80" y="82" text-anchor="middle" fill="currentColor" font-family="var(--mono-font)">stdin</text>
|
|
28
|
+
<!-- stdout arrow (outgoing, upper right) -->
|
|
29
|
+
<path d="M267 75H390" marker-end="url(#arrow)" stroke="currentColor"></path>
|
|
30
|
+
<text x="328" y="67" text-anchor="middle" fill="currentColor" font-family="var(--mono-font)">stdout</text>
|
|
31
|
+
<!-- stderr arrow (outgoing, lower right) -->
|
|
32
|
+
<path d="M267 105H390" marker-end="url(#arrow)" stroke="currentColor"></path>
|
|
33
|
+
<text x="328" y="125" text-anchor="middle" fill="currentColor" font-family="var(--mono-font)">stderr</text>
|
|
34
|
+
</svg>
|
|
35
|
+
<figcaption>
|
|
36
|
+
A running Java program (depicted as a box) with the standard in stream as an
|
|
37
|
+
incoming arrow and the standard out & error streams as outgoing arrows.
|
|
38
|
+
</figcaption>
|
|
39
|
+
</figure>
|
|
40
|
+
|
|
41
|
+
When a Java program calls `System.out.print()` (or `IO.print()`), the program
|
|
42
|
+
sends data to the standard out. When a program uses `System.err.print()`, it
|
|
43
|
+
sends data to the standard error, an output stream similar to `System.out`
|
|
44
|
+
but reserved for error messages.
|
|
45
|
+
|
|
46
|
+
`System.in` is an `InputStream` object that allows a program to read characters
|
|
47
|
+
from the standard input, one character at a time. Its `read()` method returns an
|
|
48
|
+
integer value.
|
|
49
|
+
|
|
50
|
+
- If the integer is positive, it represents a Unicode character coming through
|
|
51
|
+
the stream.
|
|
52
|
+
- If the integer is -1, there is no more input in the stream.
|
|
53
|
+
|
|
54
|
+
The -1 is known as a <dfn>sentinel</dfn>, or a reserved value known ahead of
|
|
55
|
+
time to represent a specific condition. Because all Unicode characters are
|
|
56
|
+
positive numbers, -1 cannot be converted to a character, and is instead used
|
|
57
|
+
to represent the specific condition of "*no more input in the stream*."
|
|
58
|
+
|
|
59
|
+
Let's implement a program that uses `System.in.read()` to get all the characters
|
|
60
|
+
from the standard in, counting how many characters are vowels. When there are
|
|
61
|
+
no more characters in the stream, the program prints the total number of vowels.
|
|
62
|
+
|
|
63
|
+
<!---
|
|
64
|
+
Hiding the method signature for main() keeps us focused on the program's logic,
|
|
65
|
+
and avoids discussion of IOException.
|
|
66
|
+
```
|
|
67
|
+
void main() throws Exception {
|
|
68
|
+
```
|
|
69
|
+
-->
|
|
70
|
+
|
|
71
|
+
We'll start the program with a `numVowels` counter initialized to zero,
|
|
72
|
+
the additive identity, and a second integer variable `ch` used to store the
|
|
73
|
+
return value of the `read()` method:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
int numVowels = 0;
|
|
77
|
+
int ch;
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Using an indefinite loop (since we don't know in advance how many characters
|
|
81
|
+
we'll get), we call the `read()` method and check if it returned the sentinel:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
while (true) {
|
|
85
|
+
ch = System.in.read();
|
|
86
|
+
if (ch == -1) {
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
If `read()` returns the sentinel, we stop the loop with `break`. Otherwise,
|
|
92
|
+
we test whether `ch` is a vowel:
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
if ("aeiouAEIOU".indexOf(ch) >= 0) {
|
|
96
|
+
numVowels++;
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
<!---
|
|
101
|
+
Hide the closing brace of the `while` loop to avoid indenting the conditional.
|
|
102
|
+
```
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
-->
|
|
106
|
+
|
|
107
|
+
Putting lower case and upper case vowels into a `String` allows us to use the
|
|
108
|
+
`indexOf()` method to check if `ch` is one of those characters. If `ch` is
|
|
109
|
+
nowhere in the string, `indexOf()` returns -1.
|
|
110
|
+
|
|
111
|
+
When reading input from a terminal, the loop ends when the user presses
|
|
112
|
+
<kbd>Ctrl</kbd>+<kbd>D</kbd> on the keyboard. If we type the input
|
|
113
|
+
|
|
114
|
+
```text
|
|
115
|
+
hello, world!
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Then the last line of the program, which prints the total count, will output 3:
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
IO.println("vowels: " + numVowels);
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
<!---
|
|
125
|
+
Hiding the closing brace for main().
|
|
126
|
+
```
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
-->
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
parent: /labs/index.html
|
|
2
|
+
parentLabel: Labs
|
|
3
|
+
title: Lab 0
|
|
4
|
+
author: alex
|
|
5
|
+
description: An example lab page for <%= site.title %>.
|
|
6
|
+
|
|
7
|
+
In this lab, we'll explore the [`VowelCounter` program](./VowelCounter.html).
|
|
8
|
+
|
|
9
|
+
## Review questions
|
|
10
|
+
|
|
11
|
+
??? question What is an input stream?
|
|
12
|
+
|
|
13
|
+
An input stream is an abstraction that represents arbitrary data going into a
|
|
14
|
+
program. The data in a stream doesn't have a predefined size or length.
|
|
15
|
+
When you write a program that uses an input stream, the program may wait for
|
|
16
|
+
more data.
|
|
17
|
+
|
|
18
|
+
???
|
|
19
|
+
|
|
20
|
+
??? question What is a sentinel? Provide an example.
|
|
21
|
+
|
|
22
|
+
A sentinel is a special value a program uses to change its behavior.
|
|
23
|
+
For example, `System.in.read()` returns -1 when the standard in stream doesn't
|
|
24
|
+
have any more characters.
|
|
25
|
+
|
|
26
|
+
Another example: the character `'q'` might be reserved to indicate a command
|
|
27
|
+
like "quit." When a user presses <kbd>Q</kbd>, the program exits.
|
|
28
|
+
|
|
29
|
+
???
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
title: Labs
|
|
2
|
+
author: alex
|
|
3
|
+
description: An example page for <%= site.title %>.
|
|
4
|
+
|
|
5
|
+
In each one-hour lab, we'll dive deeper and write code together, exploring how
|
|
6
|
+
to apply the concepts we learned in lecture to solve real-world problems and
|
|
7
|
+
write full-length programs.
|
|
8
|
+
|
|
9
|
+
0. [`VowelCounter` and user input](/labs/00/index.html)
|
package/init/content/markdown.md
CHANGED
package/package.json
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abreen/tada",
|
|
3
|
-
"version": "1.2
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A static site generator",
|
|
6
6
|
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/abreen/Tada.git"
|
|
10
|
+
},
|
|
11
|
+
"author": "Alex Breen <npm@abreen.email>",
|
|
7
12
|
"bin": {
|
|
8
13
|
"tada": "bin/tada.ts"
|
|
9
14
|
},
|
|
@@ -20,6 +25,7 @@
|
|
|
20
25
|
],
|
|
21
26
|
"scripts": {
|
|
22
27
|
"prepare": "husky && bun run scripts/check-deps.ts",
|
|
28
|
+
"prepack": "bun run scripts/prepack.ts",
|
|
23
29
|
"init-example": "bun run bin/tada.ts init example --no-interactive",
|
|
24
30
|
"lint": "eslint src/ build/ bin/",
|
|
25
31
|
"typecheck": "tsc --noEmit",
|
|
@@ -29,6 +35,10 @@
|
|
|
29
35
|
"engines": {
|
|
30
36
|
"bun": ">=1.0.0"
|
|
31
37
|
},
|
|
38
|
+
"os": [
|
|
39
|
+
"darwin",
|
|
40
|
+
"linux"
|
|
41
|
+
],
|
|
32
42
|
"dependencies": {
|
|
33
43
|
"@tsconfig/node23": "^23.0.2",
|
|
34
44
|
"@vscode/markdown-it-katex": "^1.1.2",
|
package/schema/site.schema.json
CHANGED
|
@@ -4,13 +4,12 @@
|
|
|
4
4
|
"properties": {
|
|
5
5
|
"features": {
|
|
6
6
|
"type": "object",
|
|
7
|
-
"default": { "search": true, "code": true },
|
|
7
|
+
"default": { "search": true, "code": true, "favicon": true },
|
|
8
8
|
"properties": {
|
|
9
9
|
"search": { "type": "boolean", "default": true },
|
|
10
10
|
"code": { "type": "boolean", "default": true },
|
|
11
|
-
"favicon": { "type": "boolean", "default":
|
|
11
|
+
"favicon": { "type": "boolean", "default": true }
|
|
12
12
|
},
|
|
13
|
-
"required": ["search", "code", "favicon"],
|
|
14
13
|
"additionalProperties": false
|
|
15
14
|
},
|
|
16
15
|
"base": { "type": "string", "pattern": "^https?:\\/\\/[-.:a-zA-Z0-9]+$" },
|
package/src/_base.scss
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
@use 'mixins' as *;
|
|
2
|
+
|
|
1
3
|
:root {
|
|
2
4
|
--box-border-width: 5px;
|
|
3
5
|
|
|
@@ -13,10 +15,6 @@
|
|
|
13
15
|
--main-width: 900px;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
p {
|
|
17
|
-
margin: 0 0 1rem 0;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
18
|
h1,
|
|
21
19
|
h2,
|
|
22
20
|
h3,
|
|
@@ -27,14 +25,6 @@ h6 {
|
|
|
27
25
|
-webkit-break-after: avoid;
|
|
28
26
|
break-after: avoid;
|
|
29
27
|
line-height: 1.3;
|
|
30
|
-
|
|
31
|
-
/*
|
|
32
|
-
* Enable Inter's features:
|
|
33
|
-
* ss03 - round quotes & commas
|
|
34
|
-
* ss04 - disambiguation (make uppercase i and lowercase L distinct)
|
|
35
|
-
* dlig - discretionary ligatures (for style)
|
|
36
|
-
*/
|
|
37
|
-
font-feature-settings: 'ss03', 'ss04', 'dlig';
|
|
38
28
|
}
|
|
39
29
|
|
|
40
30
|
h1 {
|
|
@@ -52,11 +42,15 @@ body {
|
|
|
52
42
|
line-height: var(--line-height);
|
|
53
43
|
|
|
54
44
|
/*
|
|
55
|
-
* Enable Inter's features
|
|
56
|
-
*
|
|
57
|
-
*
|
|
45
|
+
* Enable Inter's features:
|
|
46
|
+
* liga - standard ligatures
|
|
47
|
+
* calt - contextual alternates
|
|
48
|
+
* ss03 - round quotes & commas (legibility)
|
|
49
|
+
* cv01 - alternate 1 (style)
|
|
50
|
+
* cv05 - lower case L with tail (legibility)
|
|
51
|
+
* cv10 - capital G with spur (style)
|
|
58
52
|
*/
|
|
59
|
-
font-feature-settings: 'ss03', '
|
|
53
|
+
font-feature-settings: 'liga', 'calt', 'ss03', 'cv01', 'cv05', 'cv10';
|
|
60
54
|
|
|
61
55
|
@media (prefers-color-scheme: dark) {
|
|
62
56
|
& {
|
|
@@ -118,8 +112,46 @@ pre {
|
|
|
118
112
|
}
|
|
119
113
|
}
|
|
120
114
|
|
|
115
|
+
details .content > *:first-child {
|
|
116
|
+
margin-top: 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
details .content > *:last-child {
|
|
120
|
+
margin-bottom: 0;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
details summary {
|
|
124
|
+
cursor: pointer;
|
|
125
|
+
@include user-select(none);
|
|
126
|
+
padding: var(--details-padding);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
details .content {
|
|
130
|
+
padding: 0 var(--details-padding) var(--details-padding)
|
|
131
|
+
var(--details-padding);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@media (hover: hover) {
|
|
135
|
+
details summary {
|
|
136
|
+
&:hover {
|
|
137
|
+
color: var(--fg2-color);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
&:active {
|
|
141
|
+
color: var(--link-active-color);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
main details {
|
|
147
|
+
margin-bottom: 1rem;
|
|
148
|
+
border-radius: var(--border-radius);
|
|
149
|
+
border: 2px solid var(--fg-color);
|
|
150
|
+
}
|
|
151
|
+
|
|
121
152
|
input[type='text'],
|
|
122
153
|
input[type='search'] {
|
|
154
|
+
font-family: var(--font);
|
|
123
155
|
background: var(--bg-color);
|
|
124
156
|
color: var(--fg-color);
|
|
125
157
|
border: var(--control-border-width) solid var(--fg2-color);
|
|
@@ -143,3 +175,13 @@ input[type='search'] {
|
|
|
143
175
|
cursor: not-allowed;
|
|
144
176
|
}
|
|
145
177
|
}
|
|
178
|
+
|
|
179
|
+
figure {
|
|
180
|
+
margin: 1rem 0;
|
|
181
|
+
|
|
182
|
+
figcaption {
|
|
183
|
+
margin: 0 3rem;
|
|
184
|
+
font-size: var(--font-size-smaller);
|
|
185
|
+
color: var(--fg2-color);
|
|
186
|
+
}
|
|
187
|
+
}
|
package/src/_layout.scss
CHANGED
|
@@ -26,18 +26,11 @@ body > .container {
|
|
|
26
26
|
padding-bottom: 2rem;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
body.default .body,
|
|
30
|
-
body.literate .body {
|
|
31
|
-
> *:first-child {
|
|
32
|
-
margin-top: 0;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
29
|
body.default .title-and-info,
|
|
37
30
|
body.default nav.toc,
|
|
38
31
|
body.literate .title-and-info,
|
|
39
32
|
body.literate nav.toc {
|
|
40
|
-
margin-bottom:
|
|
33
|
+
margin-bottom: var(--gap);
|
|
41
34
|
}
|
|
42
35
|
|
|
43
36
|
body.default .title-and-info,
|
|
@@ -47,12 +40,17 @@ body.literate .title-and-info {
|
|
|
47
40
|
}
|
|
48
41
|
}
|
|
49
42
|
|
|
43
|
+
body.default .title-and-info {
|
|
44
|
+
padding-bottom: var(--gap);
|
|
45
|
+
border-bottom: 1px solid var(--fg2-color);
|
|
46
|
+
}
|
|
47
|
+
|
|
50
48
|
body.default main table,
|
|
51
49
|
body.literate main table {
|
|
52
50
|
width: 100%;
|
|
53
51
|
}
|
|
54
52
|
|
|
55
|
-
@media (
|
|
53
|
+
@media (width < 900px) {
|
|
56
54
|
.toc-is-active .main-content {
|
|
57
55
|
display: flex;
|
|
58
56
|
flex-direction: column;
|
|
@@ -81,7 +79,7 @@ body.literate main table {
|
|
|
81
79
|
}
|
|
82
80
|
}
|
|
83
81
|
|
|
84
|
-
@media (
|
|
82
|
+
@media (width >= 900px) {
|
|
85
83
|
body.default main table,
|
|
86
84
|
body.literate main table {
|
|
87
85
|
width: auto;
|
package/src/question/style.scss
CHANGED
|
@@ -35,8 +35,7 @@
|
|
|
35
35
|
@include user-select(none);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
.question-a-body >
|
|
39
|
-
.question-a-body > ul:first-child {
|
|
38
|
+
.question-a-body > *:first-child {
|
|
40
39
|
margin-top: 0;
|
|
41
40
|
}
|
|
42
41
|
|
|
@@ -49,19 +48,25 @@
|
|
|
49
48
|
@media (prefers-reduced-motion: no-preference) {
|
|
50
49
|
transition:
|
|
51
50
|
color 0.25s,
|
|
52
|
-
background-color 0.25s
|
|
51
|
+
background-color 0.25s,
|
|
52
|
+
border-color 0.25s,
|
|
53
|
+
box-shadow 0.25s;
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
.question-a-body:not([data-revealed]) > * {
|
|
57
58
|
color: transparent;
|
|
58
59
|
background-color: var(--bg2-color);
|
|
60
|
+
border-color: transparent;
|
|
61
|
+
box-shadow: none;
|
|
59
62
|
pointer-events: none;
|
|
60
63
|
}
|
|
61
64
|
|
|
62
65
|
.question-a-body:not([data-revealed]) > * * {
|
|
63
66
|
color: transparent;
|
|
64
67
|
background-color: var(--bg2-color);
|
|
68
|
+
border-color: transparent;
|
|
69
|
+
box-shadow: none;
|
|
65
70
|
}
|
|
66
71
|
|
|
67
72
|
.question-a-body::after {
|
package/src/search/style.scss
CHANGED
|
@@ -61,12 +61,8 @@ header {
|
|
|
61
61
|
color: var(--fg2-color);
|
|
62
62
|
|
|
63
63
|
kbd {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
border: 1px solid var(--fg2-color);
|
|
67
|
-
border-radius: 4px;
|
|
68
|
-
font-family: var(--font);
|
|
69
|
-
font-size: var(--font-size-small);
|
|
64
|
+
border-color: var(--fg2-color);
|
|
65
|
+
box-shadow: 0 2px 0 var(--fg2-color);
|
|
70
66
|
line-height: 1.4;
|
|
71
67
|
}
|
|
72
68
|
}
|
package/src/style.scss
CHANGED
|
@@ -133,34 +133,6 @@ a.footnote-ref,
|
|
|
133
133
|
@include user-select(none);
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
details .content > p:last-child,
|
|
137
|
-
details .content > div:last-child {
|
|
138
|
-
margin-bottom: 0;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
details summary {
|
|
142
|
-
cursor: pointer;
|
|
143
|
-
@include user-select(none);
|
|
144
|
-
padding: var(--details-padding);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
details .content {
|
|
148
|
-
padding: 0 var(--details-padding) var(--details-padding)
|
|
149
|
-
var(--details-padding);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
@media (hover: hover) {
|
|
153
|
-
details summary {
|
|
154
|
-
&:hover {
|
|
155
|
-
color: var(--fg2-color);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
&:active {
|
|
159
|
-
color: var(--link-active-color);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
136
|
hr {
|
|
165
137
|
border: none;
|
|
166
138
|
height: 32px;
|
|
@@ -181,16 +153,6 @@ hr::after {
|
|
|
181
153
|
mask-repeat: no-repeat;
|
|
182
154
|
}
|
|
183
155
|
|
|
184
|
-
main p,
|
|
185
|
-
main details {
|
|
186
|
-
margin-bottom: 1rem;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
main details {
|
|
190
|
-
border-radius: var(--border-radius);
|
|
191
|
-
border: 2px solid var(--fg-color);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
156
|
button,
|
|
195
157
|
a.button,
|
|
196
158
|
input[type='submit'] {
|
|
@@ -244,10 +206,6 @@ button.icon-button {
|
|
|
244
206
|
vertical-align: top;
|
|
245
207
|
}
|
|
246
208
|
|
|
247
|
-
li {
|
|
248
|
-
margin-bottom: 0.5rem;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
209
|
ol.styled-list,
|
|
252
210
|
ul.styled-list {
|
|
253
211
|
color: var(--theme-color-text);
|
|
@@ -472,14 +430,23 @@ figure.bio {
|
|
|
472
430
|
}
|
|
473
431
|
|
|
474
432
|
figcaption {
|
|
475
|
-
font-size: var(--font-size-small);
|
|
476
|
-
color: var(--fg2-color);
|
|
477
433
|
text-align: center;
|
|
478
434
|
margin-top: var(--gap);
|
|
479
435
|
font-style: italic;
|
|
480
436
|
}
|
|
481
437
|
}
|
|
482
438
|
|
|
439
|
+
kbd {
|
|
440
|
+
display: inline-block;
|
|
441
|
+
padding: 1px 5px;
|
|
442
|
+
border: 1px solid var(--fg-color);
|
|
443
|
+
border-radius: 4px;
|
|
444
|
+
box-shadow: 0 2px 0 var(--fg-color);
|
|
445
|
+
font-family: var(--font);
|
|
446
|
+
font-size: var(--font-size-small);
|
|
447
|
+
font-weight: 500;
|
|
448
|
+
}
|
|
449
|
+
|
|
483
450
|
select {
|
|
484
451
|
font-family: var(--font);
|
|
485
452
|
color: var(--fg-color);
|
package/src/timezone/index.ts
CHANGED
|
@@ -100,25 +100,10 @@ function computeOffsets(baseDate: Date) {
|
|
|
100
100
|
});
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
function init(element:
|
|
104
|
-
|
|
105
|
-
element.
|
|
106
|
-
|
|
107
|
-
TIMEZONES.forEach(tz => {
|
|
108
|
-
const opt = document.createElement('option');
|
|
109
|
-
opt.value = tz.value;
|
|
110
|
-
if (tz.value === 'UTC') {
|
|
111
|
-
opt.textContent = tz.label;
|
|
112
|
-
} else {
|
|
113
|
-
opt.textContent = `${tz.label} (${tz.abbreviation})`;
|
|
114
|
-
}
|
|
115
|
-
if (tz.value === selectedTz) {
|
|
116
|
-
opt.selected = true;
|
|
117
|
-
}
|
|
118
|
-
element.appendChild(opt);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
element.removeAttribute('hidden');
|
|
103
|
+
function init(element: HTMLSelectElement, selectedTz: string) {
|
|
104
|
+
element.value = selectedTz;
|
|
105
|
+
element.hidden = false;
|
|
106
|
+
element.disabled = false;
|
|
122
107
|
}
|
|
123
108
|
|
|
124
109
|
function dominantPeriodStyle(
|
package/templates/_top.html
CHANGED
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
}]
|
|
43
43
|
}
|
|
44
44
|
</script>
|
|
45
|
+
<script>document.head.appendChild(document.createElement('style')).textContent='.time-zone-label[hidden],select.time-zone[hidden]{display:revert}'</script>
|
|
45
46
|
<noscript><style>
|
|
46
47
|
.question-a-body:not([data-revealed]) > * { color: unset; background-color: unset; }
|
|
47
48
|
.question-a-body:not([data-revealed]) > * * { color: unset; background-color: unset; }
|
|
Binary file
|