@abreen/tada 1.1.0 → 1.2.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.
Files changed (88) hide show
  1. package/README.md +68 -16
  2. package/bin/tada.ts +289 -24
  3. package/bin/validators.test.ts +4 -3
  4. package/bin/validators.ts +4 -2
  5. package/build/apply-base-path-plugin.ts +24 -13
  6. package/build/build-manifest.test.ts +349 -0
  7. package/build/build-manifest.ts +165 -0
  8. package/build/bundle.ts +19 -20
  9. package/build/code.test.ts +7 -3
  10. package/build/content-watch.ts +6 -6
  11. package/build/copy.ts +2 -2
  12. package/build/deflist-id-plugin.ts +1 -1
  13. package/build/external-links-plugin.ts +2 -2
  14. package/build/features.ts +1 -1
  15. package/build/generate-content-assets.ts +12 -7
  16. package/build/generate-favicon.ts +13 -6
  17. package/build/generate-fonts.ts +2 -2
  18. package/build/generate-katex-assets.ts +35 -0
  19. package/build/generate-manifest.ts +2 -2
  20. package/build/globals.test.ts +9 -11
  21. package/build/globals.ts +6 -7
  22. package/build/json-schema.test.ts +1 -1
  23. package/build/log.test.ts +1 -1
  24. package/build/log.ts +2 -2
  25. package/build/markdown-plugins.test.ts +118 -10
  26. package/build/pagefind.test.ts +2 -2
  27. package/build/pagefind.ts +5 -5
  28. package/build/pdf-text.test.ts +52 -0
  29. package/build/pipeline.ts +42 -15
  30. package/build/reachability.test.ts +1 -1
  31. package/build/serve.ts +3 -3
  32. package/build/site-variables.ts +13 -5
  33. package/build/templates.ts +7 -6
  34. package/build/toc-plugin.test.ts +2 -2
  35. package/build/toc-plugin.ts +1 -1
  36. package/build/types.ts +15 -6
  37. package/build/util.ts +6 -6
  38. package/build/utils/code.ts +5 -5
  39. package/build/utils/content-files.ts +3 -3
  40. package/build/utils/derive-theme.test.ts +1 -1
  41. package/build/utils/derive-theme.ts +1 -1
  42. package/build/utils/file-types.test.ts +1 -1
  43. package/build/utils/front-matter.test.ts +1 -4
  44. package/build/utils/front-matter.ts +2 -2
  45. package/build/utils/literate-java.ts +11 -10
  46. package/build/utils/markdown.ts +41 -9
  47. package/build/utils/paths.test.ts +2 -2
  48. package/build/utils/paths.ts +5 -1
  49. package/build/utils/render.ts +45 -32
  50. package/build/utils/shiki-highlighter.ts +1 -1
  51. package/build/validate-internal-links-plugin.test.ts +1 -1
  52. package/build/validate-internal-links-plugin.ts +1 -1
  53. package/build/watch-reachability-state.test.ts +2 -2
  54. package/build/watch-reachability-state.ts +1 -1
  55. package/build/watch.ts +74 -98
  56. package/{content → init/content}/lectures/index.md +1 -1
  57. package/{content → init/content}/markdown.md +26 -7
  58. package/package.json +9 -7
  59. package/{templates → schema}/nav.schema.json +1 -2
  60. package/{build → schema}/site.schema.json +1 -2
  61. package/src/_alerts.scss +4 -0
  62. package/src/_base.scss +41 -2
  63. package/src/_layout.scss +37 -0
  64. package/src/anchor/index.ts +0 -1
  65. package/src/index.ts +3 -3
  66. package/src/search/index.test.ts +203 -0
  67. package/src/search/index.ts +3 -79
  68. package/src/search/pdf-utils.ts +79 -0
  69. package/src/search/style.scss +0 -14
  70. package/src/style.scss +9 -57
  71. package/src/timezone/index.ts +5 -6
  72. package/src/util.ts +1 -1
  73. package/templates/_top.html +1 -1
  74. package/types/dev.d.ts +1 -3
  75. package/types/site-variables.d.ts +6 -10
  76. package/types/untyped-modules.d.ts +7 -0
  77. package/config/authors.json +0 -1
  78. package/config/nav.json +0 -28
  79. /package/{content → init/content}/index.md +0 -0
  80. /package/{content → init/content}/lectures/01/Pair.java.md +0 -0
  81. /package/{content → init/content}/lectures/01/Rectangle.java +0 -0
  82. /package/{content → init/content}/lectures/01/demo.py +0 -0
  83. /package/{content → init/content}/lectures/01/index.md +0 -0
  84. /package/{content → init/content}/lectures/01/lecture1.pdf +0 -0
  85. /package/{content → init/content}/problem_sets/index.html +0 -0
  86. /package/{public → init/public}/avatars/alex.jpg +0 -0
  87. /package/{public → init/public}/test.txt +0 -0
  88. /package/{templates → schema}/authors.schema.json +0 -0
@@ -1,11 +1,11 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import _ from 'lodash';
4
- import { compile as compileJsonSchema, doValidation } from './json-schema.js';
5
- import { makeLogger } from './log.js';
6
- import { getPackageDir, getConfigDir } from './utils/paths.js';
4
+ import { compile as compileJsonSchema, doValidation } from './json-schema';
5
+ import { makeLogger } from './log';
6
+ import { getPackageDir, getConfigDir } from './utils/paths';
7
7
  import type { ValidateFunction } from 'ajv';
8
- import type { Logger, SiteVariables } from './types.js';
8
+ import type { Logger, SiteVariables } from './types';
9
9
 
10
10
  const log: Logger = makeLogger(__filename);
11
11
 
@@ -100,9 +100,10 @@ export function compileTemplates(
100
100
  continue;
101
101
  }
102
102
 
103
- // Schema validation (schemas live in the package templates dir)
103
+ // Schema validation
104
+ const schemaDir = path.resolve(getPackageDir(), 'schema');
104
105
  const schemaFile = `${path.parse(fileName).name}.schema.json`;
105
- const schemaPath = path.join(htmlDir, schemaFile);
106
+ const schemaPath = path.join(schemaDir, schemaFile);
106
107
  if (!fs.existsSync(schemaPath)) {
107
108
  throw new Error(`Missing JSON Schema for ${fileName}: ${schemaPath}`);
108
109
  }
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, test } from 'bun:test';
2
- import { generateTocHtml, generateCodeTocHtml } from './toc-plugin.js';
3
- import type { JavaTocEntry } from './types.js';
2
+ import { generateTocHtml, generateCodeTocHtml } from './toc-plugin';
3
+ import type { JavaTocEntry } from './types';
4
4
 
5
5
  describe('generateTocHtml', () => {
6
6
  test('returns empty string for empty array', () => {
@@ -1,6 +1,6 @@
1
1
  import type MarkdownIt from 'markdown-it';
2
2
  import { convertMarkdown as curlyQuote } from 'quote-quote';
3
- import type { JavaTocEntry } from './types.js';
3
+ import type { JavaTocEntry } from './types';
4
4
 
5
5
  interface HeadingItem {
6
6
  kind: 'heading';
package/build/types.ts CHANGED
@@ -2,23 +2,28 @@
2
2
  export interface SiteVariables {
3
3
  base: string;
4
4
  basePath: string;
5
- title?: string;
6
- titlePostfix?: string;
5
+ title: string;
6
+ titlePostfix: string;
7
7
  symbol?: string;
8
8
  faviconSymbol?: string;
9
- courseId?: string;
10
- themeColor?: string;
9
+ themeColor: string;
11
10
  faviconColor?: string;
12
11
  faviconFontWeight?: number;
13
12
  internalDomains?: string[];
14
- defaultTimeZone?: string;
15
- features?: { search?: boolean; code?: boolean; favicon?: boolean };
13
+ defaultTimeZone: string;
14
+ features?: FeatureConfig;
16
15
  codeLanguages?: Record<string, string>;
17
16
  tintHue?: number;
18
17
  tintAmount?: number;
19
18
  vars?: Record<string, unknown>;
20
19
  }
21
20
 
21
+ export type FeatureConfig = {
22
+ search: boolean;
23
+ code: boolean;
24
+ favicon: boolean;
25
+ };
26
+
22
27
  /** A rendered content asset ready to write to dist/ */
23
28
  export interface Asset {
24
29
  assetPath: string;
@@ -69,6 +74,7 @@ export interface Logger {
69
74
  export interface RenderPlainTextOptions {
70
75
  filePath: string;
71
76
  contentDir: string;
77
+ distDir: string;
72
78
  siteVariables: SiteVariables;
73
79
  validInternalTargets: Set<string>;
74
80
  assetFiles: string[];
@@ -78,6 +84,7 @@ export interface RenderPlainTextOptions {
78
84
  export interface RenderCodePageOptions {
79
85
  filePath: string;
80
86
  contentDir: string;
87
+ distDir: string;
81
88
  siteVariables: SiteVariables;
82
89
  assetFiles: string[];
83
90
  }
@@ -86,6 +93,7 @@ export interface RenderCodePageOptions {
86
93
  export interface RenderLiterateJavaOptions {
87
94
  filePath: string;
88
95
  contentDir: string;
96
+ distDir: string;
89
97
  siteVariables: SiteVariables;
90
98
  assetFiles: string[];
91
99
  skipExecution?: boolean;
@@ -169,4 +177,5 @@ export interface SiteConfigInput {
169
177
  base: string;
170
178
  basePath: string;
171
179
  internalDomains: string[];
180
+ features: FeatureConfig;
172
181
  }
package/build/util.ts CHANGED
@@ -4,9 +4,9 @@ export {
4
4
  getFilesByExtensions,
5
5
  getValidInternalTargets,
6
6
  shouldSkipContentFile,
7
- } from './utils/content-files.js';
8
- export { extensionIsMarkdown } from './utils/file-types.js';
9
- export { createMarkdown } from './utils/markdown.js';
7
+ } from './utils/content-files';
8
+ export { extensionIsMarkdown } from './utils/file-types';
9
+ export { createMarkdown } from './utils/markdown';
10
10
  export {
11
11
  createApplyBasePath,
12
12
  getContentDir,
@@ -15,12 +15,12 @@ export {
15
15
  getProjectDir,
16
16
  getPublicDir,
17
17
  normalizeOutputPath,
18
- } from './utils/paths.js';
18
+ } from './utils/paths';
19
19
  export {
20
20
  injectAssetTags,
21
21
  renderCodePageAsset,
22
22
  renderCopiedContentAsset,
23
23
  renderLiterateJavaPageAsset,
24
24
  renderPlainTextPageAsset,
25
- } from './utils/render.js';
26
- export { parseFrontMatter } from './utils/front-matter.js';
25
+ } from './utils/render';
26
+ export { parseFrontMatter } from './utils/front-matter';
@@ -1,11 +1,11 @@
1
1
  import MarkdownIt from 'markdown-it';
2
2
  import { parse as parseJava } from 'java-parser';
3
3
  import { JSDOM } from 'jsdom';
4
- import { makeLogger } from '../log.js';
5
- import { getHighlighter } from './shiki-highlighter.js';
6
- import externalLinksPlugin from '../external-links-plugin.js';
7
- import applyBasePathPlugin from '../apply-base-path-plugin.js';
8
- import type { JavaTocEntry, SiteVariables } from '../types.js';
4
+ import { makeLogger } from '../log';
5
+ import { getHighlighter } from './shiki-highlighter';
6
+ import externalLinksPlugin from '../external-links-plugin';
7
+ import applyBasePathPlugin from '../apply-base-path-plugin';
8
+ import type { JavaTocEntry, SiteVariables } from '../types';
9
9
 
10
10
  interface CstNode {
11
11
  name?: string;
@@ -1,12 +1,12 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import { parseFrontMatterAndContent } from './front-matter.js';
3
+ import { parseFrontMatterAndContent } from './front-matter';
4
4
  import {
5
5
  getProcessedExtensions,
6
6
  extensionIsMarkdown,
7
7
  isLiterateJava,
8
- } from './file-types.js';
9
- import { getPublicDir, normalizeOutputPath } from './paths.js';
8
+ } from './file-types';
9
+ import { getPublicDir, normalizeOutputPath } from './paths';
10
10
 
11
11
  function walkFiles(dir: string): string[] {
12
12
  return fs.readdirSync(dir).flatMap(file => {
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, test } from 'bun:test';
2
2
  import { parse, oklch } from 'culori';
3
- import { deriveTheme, getTextOnColor } from './derive-theme.js';
3
+ import { deriveTheme, getTextOnColor } from './derive-theme';
4
4
 
5
5
  function getL(hex: string): number {
6
6
  return oklch(parse(hex))!.l;
@@ -1,6 +1,6 @@
1
1
  import { parse, oklch, rgb, toGamut, formatHex } from 'culori';
2
2
  import type { Oklch, Rgb } from 'culori';
3
- import type { DerivedTheme } from '../types.js';
3
+ import type { DerivedTheme } from '../types';
4
4
 
5
5
  // OKLCH lightness range for the theme color used as backgrounds/outlines
6
6
  const LIGHT_THEME_L_MIN = 0.35;
@@ -3,7 +3,7 @@ import {
3
3
  getProcessedExtensions,
4
4
  extensionIsMarkdown,
5
5
  isLiterateJava,
6
- } from './file-types.js';
6
+ } from './file-types';
7
7
 
8
8
  describe('getProcessedExtensions', () => {
9
9
  test('includes md, markdown, html, and custom extensions', () => {
@@ -1,8 +1,5 @@
1
1
  import { describe, expect, test } from 'bun:test';
2
- import {
3
- parseFrontMatterAndContent,
4
- parseFrontMatter,
5
- } from './front-matter.js';
2
+ import { parseFrontMatterAndContent, parseFrontMatter } from './front-matter';
6
3
 
7
4
  describe('parseFrontMatter', () => {
8
5
  test('parses markdown front matter separated by blank line', () => {
@@ -1,6 +1,6 @@
1
1
  import fm from 'front-matter';
2
- import { extensionIsMarkdown } from './file-types.js';
3
- import type { ParsedContent } from '../types.js';
2
+ import { extensionIsMarkdown } from './file-types';
3
+ import type { ParsedContent } from '../types';
4
4
 
5
5
  interface RawParsedFrontMatter {
6
6
  frontMatter: string | null;
@@ -1,16 +1,16 @@
1
1
  import fs from 'fs';
2
2
  import os from 'os';
3
3
  import path from 'path';
4
- import { execSync } from 'child_process';
5
- import { createMarkdown } from './markdown.js';
6
- import { makeLogger } from '../log.js';
7
- import { parseFrontMatterAndContent } from './front-matter.js';
4
+ import { execFileSync } from 'child_process';
5
+ import { createMarkdown } from './markdown';
6
+ import { makeLogger } from '../log';
7
+ import { parseFrontMatterAndContent } from './front-matter';
8
8
  import type {
9
9
  SiteVariables,
10
10
  LiterateJavaParseResult,
11
11
  LiterateCodeBlock,
12
12
  LiterateRunnerEntry,
13
- } from '../types.js';
13
+ } from '../types';
14
14
 
15
15
  const log = makeLogger(__filename);
16
16
 
@@ -88,7 +88,7 @@ export function compileJavaSource(
88
88
  log.debug`Compiling ${className}.java (${javaSource.split('\n').length} lines) in ${tempDir}`;
89
89
 
90
90
  try {
91
- execSync(`javac "${className}.java"`, {
91
+ execFileSync('javac', [`${className}.java`], {
92
92
  cwd: tempDir,
93
93
  timeout: 30000,
94
94
  stdio: ['pipe', 'pipe', 'pipe'],
@@ -120,7 +120,7 @@ function ensureRunnerCompiled(runnerDir: string): void {
120
120
  log.debug`Compiling LiterateRunner.java`;
121
121
 
122
122
  try {
123
- execSync('javac LiterateRunner.java', {
123
+ execFileSync('javac', ['LiterateRunner.java'], {
124
124
  cwd: runnerDir,
125
125
  timeout: 30000,
126
126
  stdio: ['pipe', 'pipe', 'pipe'],
@@ -150,8 +150,9 @@ export function executeLiterateJava(
150
150
  log.debug`Running LiterateRunner with ${blockRanges.length} block range(s)`;
151
151
 
152
152
  try {
153
- const result = execSync(
154
- `java -cp "${runnerDir}" LiterateRunner "${className}" "${classPath}" '${rangesJson}'`,
153
+ const result = execFileSync(
154
+ 'java',
155
+ ['-cp', runnerDir, 'LiterateRunner', className, classPath, rangesJson],
155
156
  { timeout: 30000, encoding: 'utf-8' },
156
157
  );
157
158
  const entries: LiterateRunnerEntry[] = JSON.parse(result);
@@ -174,7 +175,7 @@ export function executeLiterateJava(
174
175
 
175
176
  export function checkJavac(): boolean {
176
177
  try {
177
- execSync('javac -version', { stdio: ['pipe', 'pipe', 'pipe'] });
178
+ execFileSync('javac', ['-version'], { stdio: ['pipe', 'pipe', 'pipe'] });
178
179
  return true;
179
180
  } catch {
180
181
  return false;
@@ -7,15 +7,18 @@ import markdownItAnchor from 'markdown-it-anchor';
7
7
  import markdownItFootnote from 'markdown-it-footnote';
8
8
  import markdownItDeflist from 'markdown-it-deflist';
9
9
  import markdownItContainer from 'markdown-it-container';
10
- import textToId from '../text-to-id.js';
11
- import { getHighlighter } from './shiki-highlighter.js';
12
- import headingSubtitlePlugin from '../heading-subtitle-plugin.js';
13
- import deflistIdPlugin from '../deflist-id-plugin.js';
14
- import externalLinksPlugin from '../external-links-plugin.js';
15
- import validateInternalLinksPlugin from '../validate-internal-links-plugin.js';
16
- import applyBasePathPlugin from '../apply-base-path-plugin.js';
17
- import { tocPlugin } from '../toc-plugin.js';
18
- import type { SiteVariables } from '../types.js';
10
+ import textToId from '../text-to-id';
11
+ import { getHighlighter } from './shiki-highlighter';
12
+ import headingSubtitlePlugin from '../heading-subtitle-plugin';
13
+ import deflistIdPlugin from '../deflist-id-plugin';
14
+ import externalLinksPlugin from '../external-links-plugin';
15
+ import validateInternalLinksPlugin from '../validate-internal-links-plugin';
16
+ import applyBasePathPlugin from '../apply-base-path-plugin';
17
+ import { tocPlugin } from '../toc-plugin';
18
+ import markdownItKatex from '@vscode/markdown-it-katex';
19
+ import katex from 'katex';
20
+ import renderA11yString from 'katex/contrib/render-a11y-string';
21
+ import type { SiteVariables } from '../types';
19
22
 
20
23
  interface CreateMarkdownOptions {
21
24
  validatorOptions?: Record<string, unknown>;
@@ -44,6 +47,7 @@ export function createMarkdown(
44
47
  .use(validateInternalLinksPlugin, validatorOptions)
45
48
  .use(applyBasePathPlugin, siteVariables)
46
49
  .use(tocPlugin)
50
+ .use(markdownItKatex)
47
51
  .use(markdownItContainer, 'details', {
48
52
  marker: '<',
49
53
  validate: function (params: string) {
@@ -78,6 +82,34 @@ export function createMarkdown(
78
82
  },
79
83
  });
80
84
 
85
+ // The @vscode/markdown-it-katex plugin's renderers swallow errors thrown
86
+ // by katex.renderToString() and return error HTML instead of propagating
87
+ // the exception. Override the renderers so parse errors fail the build.
88
+ // The plugin's tokenizer ($ and $$ delimiter parsing) is kept.
89
+ // Each renderer also adds an aria-label with a screen-reader-friendly
90
+ // description generated by katex/contrib/render-a11y-string.
91
+ markdown.renderer.rules.math_inline = (tokens: Token[], idx: number) => {
92
+ const latex = tokens[idx].content;
93
+ const html = katex.renderToString(latex, { throwOnError: true });
94
+ const a11y = markdown.utils.escapeHtml(renderA11yString(latex));
95
+ return html.replace(
96
+ '<span class="katex">',
97
+ `<span class="katex" aria-label="${a11y}">`,
98
+ );
99
+ };
100
+ const katexBlockRenderer = (tokens: Token[], idx: number) => {
101
+ const latex = tokens[idx].content;
102
+ const html = katex.renderToString(latex, {
103
+ throwOnError: true,
104
+ displayMode: true,
105
+ });
106
+ const a11y = markdown.utils.escapeHtml(renderA11yString(latex));
107
+ return `<p class="katex-block" aria-label="${a11y}">` + html + `</p>\n`;
108
+ };
109
+ markdown.renderer.rules.math_block = katexBlockRenderer;
110
+ markdown.renderer.rules.math_inline_block = katexBlockRenderer;
111
+ markdown.renderer.rules.math_inline_bare_block = katexBlockRenderer;
112
+
81
113
  const usedIds = new Map<string, number>();
82
114
  markdown.use(markdownItContainer, 'alert', {
83
115
  marker: '!',
@@ -9,8 +9,8 @@ import {
9
9
  getConfigDir,
10
10
  createApplyBasePath,
11
11
  normalizeOutputPath,
12
- } from './paths.js';
13
- import type { SiteVariables } from '../types.js';
12
+ } from './paths';
13
+ import type { SiteVariables } from '../types';
14
14
 
15
15
  describe('getPackageDir', () => {
16
16
  test('returns an absolute path', () => {
@@ -1,5 +1,5 @@
1
1
  import path from 'path';
2
- import type { SiteVariables } from '../types.js';
2
+ import type { SiteVariables } from '../types';
3
3
 
4
4
  export function getPackageDir(): string {
5
5
  return path.resolve(__dirname, '..', '..');
@@ -17,6 +17,10 @@ export function getDistDir(): string {
17
17
  return path.resolve(getProjectDir(), 'dist');
18
18
  }
19
19
 
20
+ export function getProdDistDir(): string {
21
+ return path.resolve(getProjectDir(), 'dist-prod');
22
+ }
23
+
20
24
  export function getPublicDir(): string {
21
25
  return path.resolve(getProjectDir(), 'public');
22
26
  }
@@ -2,31 +2,28 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import _ from 'lodash';
4
4
  import { stripHtml } from 'string-strip-html';
5
- import { makeLogger } from '../log.js';
6
- import { B } from '../colors.js';
7
- import createGlobals from '../globals.js';
8
- import { render, json } from '../templates.js';
5
+ import { isFeatureEnabled } from '../features';
6
+ import { makeLogger } from '../log';
7
+ import { B } from '../colors';
8
+ import createGlobals from '../globals';
9
+ import { render, json } from '../templates';
9
10
  import {
10
11
  extractJavaMethodToc,
11
12
  renderCodeSegment,
12
13
  renderCodeWithComments,
13
- } from './code.js';
14
- import { extensionIsMarkdown } from './file-types.js';
15
- import {
16
- createApplyBasePath,
17
- normalizeOutputPath,
18
- getDistDir,
19
- } from './paths.js';
20
- import { parseFrontMatterAndContent } from './front-matter.js';
21
- import { createMarkdown } from './markdown.js';
22
- import { generateTocHtml, generateCodeTocHtml } from '../toc-plugin.js';
14
+ } from './code';
15
+ import { extensionIsMarkdown } from './file-types';
16
+ import { createApplyBasePath, normalizeOutputPath } from './paths';
17
+ import { parseFrontMatterAndContent } from './front-matter';
18
+ import { createMarkdown } from './markdown';
19
+ import { generateTocHtml, generateCodeTocHtml } from '../toc-plugin';
23
20
  import {
24
21
  parseLiterateJava,
25
22
  hasMainMethod,
26
23
  deriveClassName,
27
24
  compileJavaSource,
28
25
  executeLiterateJava,
29
- } from './literate-java.js';
26
+ } from './literate-java';
30
27
  import type {
31
28
  Asset,
32
29
  SiteVariables,
@@ -34,7 +31,7 @@ import type {
34
31
  RenderCodePageOptions,
35
32
  RenderLiterateJavaOptions,
36
33
  RenderCopiedContentOptions,
37
- } from '../types.js';
34
+ } from '../types';
38
35
 
39
36
  const log = makeLogger(__filename);
40
37
 
@@ -112,6 +109,7 @@ export function injectAssetTags(
112
109
  html: string,
113
110
  assetFiles: string[],
114
111
  applyBasePath: (subPath: string) => string,
112
+ distDir: string,
115
113
  ): string {
116
114
  const jsAssets = assetFiles.filter(f => f.endsWith('.js'));
117
115
  const cssAssets = assetFiles.filter(f => f.endsWith('.css'));
@@ -121,8 +119,6 @@ export function injectAssetTags(
121
119
  .join('');
122
120
  const criticalAssets = cssAssets.filter(f => f.includes('critical.bundle.'));
123
121
  const asyncAssets = cssAssets.filter(f => !f.includes('critical.bundle.'));
124
-
125
- const distDir = getDistDir();
126
122
  const criticalTags = criticalAssets
127
123
  .map(asset => {
128
124
  const css = fs.readFileSync(path.join(distDir, asset), 'utf-8');
@@ -142,6 +138,17 @@ export function injectAssetTags(
142
138
  .replace('</head>', `${scriptTags}</head>`);
143
139
  }
144
140
 
141
+ export function injectKatexStylesheet(
142
+ html: string,
143
+ applyBasePath: (subPath: string) => string,
144
+ ): string {
145
+ 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>`;
149
+ return html.replace('<head>', `<head>${tag}`);
150
+ }
151
+
145
152
  function toContentAssetPath(contentDir: string, filePath: string): string {
146
153
  return path
147
154
  .relative(contentDir, filePath)
@@ -152,6 +159,7 @@ function toContentAssetPath(contentDir: string, filePath: string): string {
152
159
  export function renderPlainTextPageAsset({
153
160
  filePath,
154
161
  contentDir,
162
+ distDir,
155
163
  siteVariables,
156
164
  validInternalTargets,
157
165
  assetFiles,
@@ -190,11 +198,14 @@ export function renderPlainTextPageAsset({
190
198
  subPath,
191
199
  });
192
200
 
193
- const html = injectAssetTags(
194
- render(`${pageVariables.template}.html`, templateParameters) as string,
195
- assetFiles,
196
- applyBasePath,
197
- );
201
+ const templateHtml = render(
202
+ `${pageVariables.template}.html`,
203
+ templateParameters,
204
+ ) as string;
205
+ let html = injectAssetTags(templateHtml, assetFiles, applyBasePath, distDir);
206
+ if (templateHtml.includes('class="katex"')) {
207
+ html = injectKatexStylesheet(html, applyBasePath);
208
+ }
198
209
 
199
210
  return [
200
211
  {
@@ -207,6 +218,7 @@ export function renderPlainTextPageAsset({
207
218
  export function renderCodePageAsset({
208
219
  filePath,
209
220
  contentDir,
221
+ distDir,
210
222
  siteVariables,
211
223
  assetFiles,
212
224
  }: RenderCodePageOptions): Asset[] {
@@ -247,6 +259,7 @@ export function renderCodePageAsset({
247
259
  render('code.html', templateParameters) as string,
248
260
  assetFiles,
249
261
  applyBasePath,
262
+ distDir,
250
263
  );
251
264
 
252
265
  return [
@@ -288,10 +301,9 @@ function renderPlainTextContent(
288
301
  filePath,
289
302
  sourceUrlPath,
290
303
  validTargets: validInternalTargets,
291
- codeExtensions:
292
- siteVariables.features?.code === false
293
- ? []
294
- : Object.keys(siteVariables.codeLanguages!),
304
+ codeExtensions: isFeatureEnabled(siteVariables, 'code')
305
+ ? Object.keys(siteVariables.codeLanguages!)
306
+ : [],
295
307
  },
296
308
  });
297
309
 
@@ -381,6 +393,7 @@ export function stripHtmlComments(str: string): string {
381
393
  export function renderLiterateJavaPageAsset({
382
394
  filePath,
383
395
  contentDir,
396
+ distDir,
384
397
  siteVariables,
385
398
  assetFiles,
386
399
  skipExecution,
@@ -509,11 +522,11 @@ export function renderLiterateJavaPageAsset({
509
522
  subPath,
510
523
  });
511
524
 
512
- const html = injectAssetTags(
513
- render('literate.html', templateParameters) as string,
514
- assetFiles,
515
- applyBasePath,
516
- );
525
+ const templateHtml = render('literate.html', templateParameters) as string;
526
+ let html = injectAssetTags(templateHtml, assetFiles, applyBasePath, distDir);
527
+ if (templateHtml.includes('class="katex"')) {
528
+ html = injectKatexStylesheet(html, applyBasePath);
529
+ }
517
530
 
518
531
  return [
519
532
  {
@@ -1,5 +1,5 @@
1
1
  import type { HighlighterGeneric, BundledLanguage, BundledTheme } from 'shiki';
2
- import { makeLogger } from '../log.js';
2
+ import { makeLogger } from '../log';
3
3
 
4
4
  const log = makeLogger(__filename);
5
5
 
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, test } from 'bun:test';
2
2
  import MarkdownIt from 'markdown-it';
3
- import validateInternalLinks from './validate-internal-links-plugin.js';
3
+ import validateInternalLinks from './validate-internal-links-plugin';
4
4
 
5
5
  function createMd(
6
6
  validTargets: string[],
@@ -1,7 +1,7 @@
1
1
  import type MarkdownIt from 'markdown-it';
2
2
  import type Token from 'markdown-it/lib/token.mjs';
3
3
  import path from 'path';
4
- import { makeLogger } from './log.js';
4
+ import { makeLogger } from './log';
5
5
 
6
6
  const log = makeLogger(__filename);
7
7
 
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, test } from 'bun:test';
2
- import { collectReachableSiteAssets } from './reachability.js';
3
- import WatchReachabilityState from './watch-reachability-state.js';
2
+ import { collectReachableSiteAssets } from './reachability';
3
+ import WatchReachabilityState from './watch-reachability-state';
4
4
 
5
5
  function createReader(htmlByPath: Map<string, string>) {
6
6
  const calls: string[] = [];
@@ -1,4 +1,4 @@
1
- import { collectDirectSiteAssetLinks } from './reachability.js';
1
+ import { collectDirectSiteAssetLinks } from './reachability';
2
2
 
3
3
  type ReadHtmlForAsset = (assetPath: string) => string;
4
4