@abreen/tada 1.0.2 → 1.1.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 (120) hide show
  1. package/README.md +29 -33
  2. package/bin/tada.ts +356 -0
  3. package/bin/validators.test.ts +204 -0
  4. package/bin/validators.ts +83 -0
  5. package/{webpack/apply-base-path-plugin.js → build/apply-base-path-plugin.ts} +16 -7
  6. package/build/bundle.ts +117 -0
  7. package/{webpack/code.test.js → build/code.test.ts} +6 -7
  8. package/build/colors.ts +25 -0
  9. package/build/content-watch.ts +107 -0
  10. package/build/copy.ts +118 -0
  11. package/{webpack/deflist-id-plugin.js → build/deflist-id-plugin.ts} +7 -6
  12. package/{webpack/external-links-plugin.js → build/external-links-plugin.ts} +14 -5
  13. package/build/features.ts +11 -0
  14. package/build/generate-content-assets.ts +315 -0
  15. package/build/generate-favicon.ts +165 -0
  16. package/build/generate-fonts.ts +31 -0
  17. package/{webpack/generate-manifest-plugin.js → build/generate-manifest.ts} +29 -36
  18. package/build/globals.test.ts +101 -0
  19. package/{webpack/globals.js → build/globals.ts} +28 -13
  20. package/{webpack/heading-subtitle-plugin.js → build/heading-subtitle-plugin.ts} +4 -2
  21. package/build/json-schema.test.ts +57 -0
  22. package/build/json-schema.ts +33 -0
  23. package/build/log.test.ts +111 -0
  24. package/build/log.ts +167 -0
  25. package/{webpack/markdown-plugins.test.js → build/markdown-plugins.test.ts} +94 -9
  26. package/{webpack/pagefind-plugin.test.js → build/pagefind.test.ts} +74 -13
  27. package/build/pagefind.ts +339 -0
  28. package/{webpack/pdf-text.js → build/pdf-text.ts} +47 -27
  29. package/build/pipeline.ts +93 -0
  30. package/{webpack/reachability.test.js → build/reachability.test.ts} +3 -3
  31. package/{webpack/reachability.js → build/reachability.ts} +77 -34
  32. package/build/serve.ts +112 -0
  33. package/{webpack/site-variables.js → build/site-variables.ts} +22 -15
  34. package/{webpack → build}/site.schema.json +3 -10
  35. package/{webpack/templates.js → build/templates.ts} +35 -33
  36. package/{webpack/text-to-id.js → build/text-to-id.ts} +2 -2
  37. package/build/toc-plugin.test.ts +105 -0
  38. package/{webpack/toc-plugin.js → build/toc-plugin.ts} +32 -13
  39. package/build/types.ts +172 -0
  40. package/build/util.ts +26 -0
  41. package/{webpack/utils/code.js → build/utils/code.ts} +119 -60
  42. package/{webpack/utils/content-files.js → build/utils/content-files.ts} +40 -35
  43. package/build/utils/derive-theme.test.ts +111 -0
  44. package/build/utils/derive-theme.ts +85 -0
  45. package/build/utils/file-types.test.ts +61 -0
  46. package/build/utils/file-types.ts +13 -0
  47. package/build/utils/front-matter.test.ts +80 -0
  48. package/{webpack/utils/front-matter.js → build/utils/front-matter.ts} +22 -9
  49. package/{webpack → build}/utils/jdi-runner/LiterateRunner.java +1 -1
  50. package/{webpack/utils/literate-java.js → build/utils/literate-java.ts} +63 -34
  51. package/{webpack/utils/markdown.js → build/utils/markdown.ts} +94 -49
  52. package/build/utils/paths.test.ts +91 -0
  53. package/{webpack/utils/paths.js → build/utils/paths.ts} +14 -22
  54. package/{webpack/utils/render.js → build/utils/render.ts} +188 -123
  55. package/build/utils/shiki-highlighter.ts +29 -0
  56. package/build/validate-internal-links-plugin.test.ts +106 -0
  57. package/{webpack/validate-internal-links-plugin.js → build/validate-internal-links-plugin.ts} +47 -20
  58. package/{webpack/watch-reachability-state.test.js → build/watch-reachability-state.test.ts} +8 -8
  59. package/{webpack/watch-reachability-state.js → build/watch-reachability-state.ts} +63 -24
  60. package/{webpack/watch-reload-client.js → build/watch-reload-client.ts} +3 -1
  61. package/build/watch.ts +573 -0
  62. package/content/index.md +9 -3
  63. package/content/markdown.md +2 -1
  64. package/content/problem_sets/index.html +14 -0
  65. package/fonts/google-sans-code/woff2/GoogleSansCodeVariable-Italic.woff2 +0 -0
  66. package/fonts/google-sans-code/woff2/GoogleSansCodeVariable.woff2 +0 -0
  67. package/fonts/inter/woff2/InterVariable-Italic.woff2 +0 -0
  68. package/fonts/inter/woff2/InterVariable.woff2 +0 -0
  69. package/package.json +28 -19
  70. package/src/_alerts.scss +92 -0
  71. package/src/_base.scss +106 -0
  72. package/src/{layout.scss → _layout.scss} +0 -2
  73. package/src/anchor/style.scss +1 -9
  74. package/src/code/index.ts +3 -3
  75. package/src/code.scss +1 -1
  76. package/src/critical.scss +5 -0
  77. package/src/header/_base.scss +129 -0
  78. package/src/header/style.scss +3 -131
  79. package/src/index.ts +1 -2
  80. package/src/question/style.scss +1 -1
  81. package/src/search/index.ts +36 -15
  82. package/src/search/style.scss +9 -15
  83. package/src/style.scss +6 -269
  84. package/src/toc/style.scss +5 -39
  85. package/src/util.ts +8 -5
  86. package/templates/_theme.scss +38 -14
  87. package/tsconfig.json +10 -6
  88. package/types/file-system-access.d.ts +5 -0
  89. package/types/markdown-it-plugins.d.ts +11 -0
  90. package/types/untyped-modules.d.ts +40 -0
  91. package/bin/tada.js +0 -361
  92. package/content/problem_sets/index.md +0 -6
  93. package/webpack/build-state.js +0 -97
  94. package/webpack/colors.js +0 -15
  95. package/webpack/config.base.js +0 -151
  96. package/webpack/config.dev.js +0 -23
  97. package/webpack/config.prod.js +0 -32
  98. package/webpack/content-watch-plugin.js +0 -153
  99. package/webpack/features.js +0 -5
  100. package/webpack/generate-content-assets-plugin.js +0 -308
  101. package/webpack/generate-favicon-plugin.js +0 -198
  102. package/webpack/generate-fonts-plugin.js +0 -69
  103. package/webpack/json-schema.js +0 -19
  104. package/webpack/log.js +0 -143
  105. package/webpack/pagefind-plugin.js +0 -379
  106. package/webpack/print-flair-plugin.js +0 -22
  107. package/webpack/serve.js +0 -104
  108. package/webpack/util.js +0 -49
  109. package/webpack/utils/define-plugin.js +0 -20
  110. package/webpack/utils/file-types.js +0 -26
  111. package/webpack/utils/parse-hsl.js +0 -8
  112. package/webpack/utils/shiki-highlighter.js +0 -26
  113. package/webpack/watch.js +0 -166
  114. /package/{webpack → build}/flair.json +0 -0
  115. /package/{webpack → build}/utils/jdi-runner/LiterateRunner.class +0 -0
  116. /package/fonts/google-sans-code/{GoogleSansCodeVariable-Italic.ttf → ttf/GoogleSansCodeVariable-Italic.ttf} +0 -0
  117. /package/fonts/google-sans-code/{GoogleSansCodeVariable.ttf → ttf/GoogleSansCodeVariable.ttf} +0 -0
  118. /package/fonts/inter/{InterVariable-Italic.ttf → ttf/InterVariable-Italic.ttf} +0 -0
  119. /package/fonts/inter/{InterVariable.ttf → ttf/InterVariable.ttf} +0 -0
  120. /package/types/{dev.ts → dev.d.ts} +0 -0
@@ -0,0 +1,61 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import {
3
+ getProcessedExtensions,
4
+ extensionIsMarkdown,
5
+ isLiterateJava,
6
+ } from './file-types.js';
7
+
8
+ describe('getProcessedExtensions', () => {
9
+ test('includes md, markdown, html, and custom extensions', () => {
10
+ expect(getProcessedExtensions(['java'])).toEqual([
11
+ 'md',
12
+ 'markdown',
13
+ 'html',
14
+ 'java',
15
+ ]);
16
+ });
17
+
18
+ test('works with empty code extensions', () => {
19
+ expect(getProcessedExtensions([])).toEqual(['md', 'markdown', 'html']);
20
+ });
21
+ });
22
+
23
+ describe('extensionIsMarkdown', () => {
24
+ test('returns true for .md', () => {
25
+ expect(extensionIsMarkdown('.md')).toBe(true);
26
+ });
27
+
28
+ test('returns true for .markdown', () => {
29
+ expect(extensionIsMarkdown('.markdown')).toBe(true);
30
+ });
31
+
32
+ test('returns false for .html', () => {
33
+ expect(extensionIsMarkdown('.html')).toBe(false);
34
+ });
35
+
36
+ test('returns false for .txt', () => {
37
+ expect(extensionIsMarkdown('.txt')).toBe(false);
38
+ });
39
+ });
40
+
41
+ describe('isLiterateJava', () => {
42
+ test('returns true for .java.md files', () => {
43
+ expect(isLiterateJava('Example.java.md')).toBe(true);
44
+ });
45
+
46
+ test('case insensitive', () => {
47
+ expect(isLiterateJava('Example.JAVA.MD')).toBe(true);
48
+ });
49
+
50
+ test('returns false for plain .md files', () => {
51
+ expect(isLiterateJava('readme.md')).toBe(false);
52
+ });
53
+
54
+ test('returns false for .java files', () => {
55
+ expect(isLiterateJava('Example.java')).toBe(false);
56
+ });
57
+
58
+ test('handles paths with directories', () => {
59
+ expect(isLiterateJava('/content/code/Example.java.md')).toBe(true);
60
+ });
61
+ });
@@ -0,0 +1,13 @@
1
+ import path from 'path';
2
+
3
+ export function getProcessedExtensions(codeExtensions: string[]): string[] {
4
+ return ['md', 'markdown', 'html', ...codeExtensions];
5
+ }
6
+
7
+ export function extensionIsMarkdown(ext: string): boolean {
8
+ return ['.md', '.markdown'].includes(ext);
9
+ }
10
+
11
+ export function isLiterateJava(filePath: string): boolean {
12
+ return path.basename(filePath).toLowerCase().endsWith('.java.md');
13
+ }
@@ -0,0 +1,80 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import {
3
+ parseFrontMatterAndContent,
4
+ parseFrontMatter,
5
+ } from './front-matter.js';
6
+
7
+ describe('parseFrontMatter', () => {
8
+ test('parses markdown front matter separated by blank line', () => {
9
+ const raw = 'title: Hello\nauthor: Alice\n\nBody content here.';
10
+ const result = parseFrontMatter(raw, '.md');
11
+ expect(result.frontMatter).toBe('title: Hello\nauthor: Alice');
12
+ expect(result.content).toBe('\nBody content here.');
13
+ });
14
+
15
+ test('handles .markdown extension', () => {
16
+ const raw = 'title: Test\n\nContent.';
17
+ const result = parseFrontMatter(raw, '.markdown');
18
+ expect(result.frontMatter).toBe('title: Test');
19
+ });
20
+
21
+ test('handles .html extension', () => {
22
+ const raw = 'title: Page\nlayout: full\n\n<h1>Hi</h1>';
23
+ const result = parseFrontMatter(raw, '.html');
24
+ expect(result.frontMatter).toBe('title: Page\nlayout: full');
25
+ expect(result.content).toBe('\n<h1>Hi</h1>');
26
+ });
27
+
28
+ test('returns null frontMatter for unknown extensions', () => {
29
+ const raw = 'title: Nope\n\nContent';
30
+ const result = parseFrontMatter(raw, '.txt');
31
+ expect(result.frontMatter).toBe(null);
32
+ expect(result.content).toBe(raw);
33
+ });
34
+
35
+ test('returns null frontMatter when content starts with blank line', () => {
36
+ const raw = '\nNo front matter here.';
37
+ const result = parseFrontMatter(raw, '.md');
38
+ expect(result.frontMatter).toBe(null);
39
+ expect(result.content).toBe(raw);
40
+ });
41
+
42
+ test('handles YAML multiline pipe syntax', () => {
43
+ const raw =
44
+ 'title: Hello\ndescription: |\n This is a\n multiline value\n\nBody.';
45
+ const result = parseFrontMatter(raw, '.md');
46
+ expect(result.frontMatter).toContain('description: |');
47
+ expect(result.frontMatter).toContain(' This is a');
48
+ expect(result.frontMatter).toContain(' multiline value');
49
+ });
50
+ });
51
+
52
+ describe('parseFrontMatterAndContent', () => {
53
+ test('returns parsed pageVariables and content', () => {
54
+ const raw = 'title: Hello World\nauthor: Bob\n\n# Main Content';
55
+ const result = parseFrontMatterAndContent(raw, '.md');
56
+ expect(result.pageVariables.title).toBe('Hello World');
57
+ expect(result.pageVariables.author).toBe('Bob');
58
+ expect(result.content).toContain('# Main Content');
59
+ });
60
+
61
+ test('handles content with no front matter fields', () => {
62
+ const raw = '\nJust content, no front matter.';
63
+ const result = parseFrontMatterAndContent(raw, '.md');
64
+ expect(result.pageVariables).toEqual({});
65
+ expect(result.content).toBe(raw);
66
+ });
67
+
68
+ test('handles boolean and numeric front matter values', () => {
69
+ const raw = 'toc: true\norder: 5\n\nContent.';
70
+ const result = parseFrontMatterAndContent(raw, '.md');
71
+ expect(result.pageVariables.toc).toBe(true);
72
+ expect(result.pageVariables.order).toBe(5);
73
+ });
74
+
75
+ test('handles Windows-style line endings', () => {
76
+ const raw = 'title: Hello\r\n\r\nBody content.';
77
+ const result = parseFrontMatterAndContent(raw, '.md');
78
+ expect(result.pageVariables.title).toBe('Hello');
79
+ });
80
+ });
@@ -1,18 +1,30 @@
1
- const fm = require('front-matter');
2
- const { extensionIsMarkdown } = require('./file-types');
1
+ import fm from 'front-matter';
2
+ import { extensionIsMarkdown } from './file-types.js';
3
+ import type { ParsedContent } from '../types.js';
3
4
 
4
- function parseFrontMatterAndContent(raw, ext) {
5
+ interface RawParsedFrontMatter {
6
+ frontMatter: string | null;
7
+ content: string;
8
+ }
9
+
10
+ export function parseFrontMatterAndContent(
11
+ raw: string,
12
+ ext: string,
13
+ ): ParsedContent {
5
14
  const { frontMatter, content } = parseFrontMatter(raw, ext);
6
15
 
7
16
  // Add delimiters to satisfy the front-matter library
8
17
  const result = fm(`---\n${frontMatter}\n---\n`);
9
18
 
10
- return { pageVariables: result.attributes, content };
19
+ return {
20
+ pageVariables: result.attributes as Record<string, unknown>,
21
+ content,
22
+ };
11
23
  }
12
24
 
13
- function parseFrontMatterPlainText(rawContent) {
25
+ function parseFrontMatterPlainText(rawContent: string): RawParsedFrontMatter {
14
26
  const lines = rawContent.split(/\r?\n/);
15
- const fmLines = [];
27
+ const fmLines: string[] = [];
16
28
  let i = 0;
17
29
 
18
30
  while (i < lines.length) {
@@ -45,7 +57,10 @@ function parseFrontMatterPlainText(rawContent) {
45
57
  };
46
58
  }
47
59
 
48
- function parseFrontMatter(rawContent, ext) {
60
+ export function parseFrontMatter(
61
+ rawContent: string,
62
+ ext: string,
63
+ ): RawParsedFrontMatter {
49
64
  if (extensionIsMarkdown(ext) || ext === '.html') {
50
65
  return parseFrontMatterPlainText(rawContent);
51
66
  } else {
@@ -53,5 +68,3 @@ function parseFrontMatter(rawContent, ext) {
53
68
  return { frontMatter: null, content: rawContent };
54
69
  }
55
70
  }
56
-
57
- module.exports = { parseFrontMatter, parseFrontMatterAndContent };
@@ -186,7 +186,7 @@ public class LiterateRunner {
186
186
  }
187
187
  }
188
188
  } catch (VMDisconnectedException e) {
189
- // VM died unexpectedly drain any remaining output
189
+ // VM died unexpectedly, drain any remaining output
190
190
  Thread.sleep(10);
191
191
  String output = drainStream(targetStdout);
192
192
  if (!output.isEmpty() && activeBlock >= 0) {
@@ -1,16 +1,25 @@
1
- const fs = require('fs');
2
- const os = require('os');
3
- const path = require('path');
4
- const { execSync } = require('child_process');
5
- const { createMarkdown } = require('./markdown');
6
- const { makeLogger } = require('../log');
7
- const { parseFrontMatterAndContent } = require('./front-matter');
1
+ import fs from 'fs';
2
+ import os from 'os';
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';
8
+ import type {
9
+ SiteVariables,
10
+ LiterateJavaParseResult,
11
+ LiterateCodeBlock,
12
+ LiterateRunnerEntry,
13
+ } from '../types.js';
8
14
 
9
15
  const log = makeLogger(__filename);
10
16
 
11
17
  const MAIN_PATTERN = /\bvoid\s+main\s*\(/m;
12
18
 
13
- function parseLiterateJava(rawContent, siteVariables) {
19
+ export function parseLiterateJava(
20
+ rawContent: string,
21
+ siteVariables: SiteVariables,
22
+ ): LiterateJavaParseResult {
14
23
  const { pageVariables, content } = parseFrontMatterAndContent(
15
24
  rawContent,
16
25
  '.md',
@@ -21,7 +30,7 @@ function parseLiterateJava(rawContent, siteVariables) {
21
30
  });
22
31
  const tokens = md.parse(content, {});
23
32
 
24
- const codeBlocks = [];
33
+ const codeBlocks: LiterateCodeBlock[] = [];
25
34
  let javaLine = 1;
26
35
 
27
36
  for (let i = 0; i < tokens.length; i++) {
@@ -45,7 +54,7 @@ function parseLiterateJava(rawContent, siteVariables) {
45
54
  const javaSource = codeBlocks.map(b => b.content).join('');
46
55
  const visibleBlockIndices = codeBlocks
47
56
  .map((b, i) => (b.hidden ? null : i))
48
- .filter(i => i !== null);
57
+ .filter((i): i is number => i !== null);
49
58
 
50
59
  const hiddenCount = codeBlocks.length - visibleBlockIndices.length;
51
60
  log.debug`Parsed ${codeBlocks.length} code block(s) (${hiddenCount} hidden), ${javaSource.split('\n').length} Java line(s)`;
@@ -59,16 +68,19 @@ function parseLiterateJava(rawContent, siteVariables) {
59
68
  };
60
69
  }
61
70
 
62
- function hasMainMethod(javaSource) {
71
+ export function hasMainMethod(javaSource: string): boolean {
63
72
  return MAIN_PATTERN.test(javaSource);
64
73
  }
65
74
 
66
- function deriveClassName(filePath) {
75
+ export function deriveClassName(filePath: string): string {
67
76
  const name = path.parse(filePath).name;
68
77
  return path.parse(name).name;
69
78
  }
70
79
 
71
- function compileJavaSource(javaSource, className) {
80
+ export function compileJavaSource(
81
+ javaSource: string,
82
+ className: string,
83
+ ): string {
72
84
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tada-literate-'));
73
85
  const javaFile = path.join(tempDir, `${className}.java`);
74
86
  fs.writeFileSync(javaFile, javaSource);
@@ -81,16 +93,19 @@ function compileJavaSource(javaSource, className) {
81
93
  timeout: 30000,
82
94
  stdio: ['pipe', 'pipe', 'pipe'],
83
95
  });
84
- } catch (err) {
85
- const stderr = err.stderr ? err.stderr.toString() : err.message;
96
+ } catch (err: unknown) {
97
+ const execErr = err as { stderr?: Buffer; message: string };
98
+ const stderr = execErr.stderr ? execErr.stderr.toString() : execErr.message;
86
99
  fs.rmSync(tempDir, { recursive: true, force: true });
87
- throw new Error(`Compilation failed for ${className}.java:\n${stderr}`);
100
+ throw new Error(`Compilation failed for ${className}.java:\n${stderr}`, {
101
+ cause: err,
102
+ });
88
103
  }
89
104
 
90
105
  return tempDir;
91
106
  }
92
107
 
93
- function ensureRunnerCompiled(runnerDir) {
108
+ function ensureRunnerCompiled(runnerDir: string): void {
94
109
  const sourceFile = path.join(runnerDir, 'LiterateRunner.java');
95
110
  const classFile = path.join(runnerDir, 'LiterateRunner.class');
96
111
 
@@ -110,20 +125,27 @@ function ensureRunnerCompiled(runnerDir) {
110
125
  timeout: 30000,
111
126
  stdio: ['pipe', 'pipe', 'pipe'],
112
127
  });
113
- } catch (err) {
114
- const stderr = err.stderr ? err.stderr.toString() : err.message;
115
- throw new Error(`Failed to compile LiterateRunner.java:\n${stderr}`);
128
+ } catch (err: unknown) {
129
+ const execErr = err as { stderr?: Buffer; message: string };
130
+ const stderr = execErr.stderr ? execErr.stderr.toString() : execErr.message;
131
+ throw new Error(`Failed to compile LiterateRunner.java:\n${stderr}`, {
132
+ cause: err,
133
+ });
116
134
  }
117
135
  }
118
136
 
119
- function executeLiterateJava(className, classPath, codeBlocks) {
137
+ export function executeLiterateJava(
138
+ className: string,
139
+ classPath: string,
140
+ codeBlocks: LiterateCodeBlock[],
141
+ ): LiterateRunnerEntry[] {
120
142
  const runnerDir = path.join(__dirname, 'jdi-runner');
121
143
  ensureRunnerCompiled(runnerDir);
122
144
 
123
145
  const blockRanges = codeBlocks.map(b => [b.javaStartLine, b.javaEndLine]);
124
146
  const rangesJson = JSON.stringify(blockRanges);
125
147
 
126
- log.info`Executing literate Java: ${className}`;
148
+ log.debug`Executing literate Java: ${className}`;
127
149
 
128
150
  log.debug`Running LiterateRunner with ${blockRanges.length} block range(s)`;
129
151
 
@@ -132,22 +154,29 @@ function executeLiterateJava(className, classPath, codeBlocks) {
132
154
  `java -cp "${runnerDir}" LiterateRunner "${className}" "${classPath}" '${rangesJson}'`,
133
155
  { timeout: 30000, encoding: 'utf-8' },
134
156
  );
135
- const entries = JSON.parse(result);
157
+ const entries: LiterateRunnerEntry[] = JSON.parse(result);
136
158
  log.debug`LiterateRunner returned ${entries.length} output entries`;
137
159
  return entries;
138
- } catch (err) {
139
- const stderr = err.stderr ? err.stderr.toString() : '';
140
- const stdout = err.stdout ? err.stdout.toString() : '';
160
+ } catch (err: unknown) {
161
+ const execErr = err as {
162
+ stderr?: Buffer | string;
163
+ stdout?: Buffer | string;
164
+ message: string;
165
+ };
166
+ const stderr = execErr.stderr ? execErr.stderr.toString() : '';
167
+ const stdout = execErr.stdout ? execErr.stdout.toString() : '';
141
168
  throw new Error(
142
- `Execution failed for ${className}:\n${stderr || stdout || err.message}`,
169
+ `Execution failed for ${className}:\n${stderr || stdout || execErr.message}`,
170
+ { cause: err },
143
171
  );
144
172
  }
145
173
  }
146
174
 
147
- module.exports = {
148
- parseLiterateJava,
149
- hasMainMethod,
150
- deriveClassName,
151
- compileJavaSource,
152
- executeLiterateJava,
153
- };
175
+ export function checkJavac(): boolean {
176
+ try {
177
+ execSync('javac -version', { stdio: ['pipe', 'pipe', 'pipe'] });
178
+ return true;
179
+ } catch {
180
+ return false;
181
+ }
182
+ }
@@ -1,9 +1,27 @@
1
- const MarkdownIt = require('markdown-it');
2
- const { convertMarkdown: curlyQuote } = require('quote-quote');
3
- const textToId = require('../text-to-id');
4
- const { getHighlighter } = require('./shiki-highlighter');
1
+ import MarkdownIt from 'markdown-it';
2
+ import type { Options } from 'markdown-it/lib/index.mjs';
3
+ import type Token from 'markdown-it/lib/token.mjs';
4
+ import type Renderer from 'markdown-it/lib/renderer.mjs';
5
+ import { convertMarkdown as curlyQuote } from 'quote-quote';
6
+ import markdownItAnchor from 'markdown-it-anchor';
7
+ import markdownItFootnote from 'markdown-it-footnote';
8
+ import markdownItDeflist from 'markdown-it-deflist';
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';
5
19
 
6
- function capitalize(str) {
20
+ interface CreateMarkdownOptions {
21
+ validatorOptions?: Record<string, unknown>;
22
+ }
23
+
24
+ function capitalize(str: string): string {
7
25
  if (str.length < 2) {
8
26
  return str;
9
27
  }
@@ -11,31 +29,34 @@ function capitalize(str) {
11
29
  return str[0].toUpperCase() + str.slice(1);
12
30
  }
13
31
 
14
- function createMarkdown(siteVariables, options = {}) {
32
+ export function createMarkdown(
33
+ siteVariables: SiteVariables,
34
+ options: CreateMarkdownOptions = {},
35
+ ): MarkdownIt {
15
36
  const { validatorOptions = {} } = options;
16
37
  const markdown = new MarkdownIt({ html: true, typographer: true })
17
- .use(require('../heading-subtitle-plugin'))
18
- .use(require('markdown-it-anchor'), { tabIndex: false })
19
- .use(require('markdown-it-footnote'))
20
- .use(require('markdown-it-deflist'))
21
- .use(require('../deflist-id-plugin'))
22
- .use(require('../external-links-plugin'), siteVariables)
23
- .use(require('../validate-internal-links-plugin'), validatorOptions)
24
- .use(require('../apply-base-path-plugin'), siteVariables)
25
- .use(require('../toc-plugin').tocPlugin)
26
- .use(require('markdown-it-container'), 'details', {
38
+ .use(headingSubtitlePlugin)
39
+ .use(markdownItAnchor, { tabIndex: false })
40
+ .use(markdownItFootnote)
41
+ .use(markdownItDeflist)
42
+ .use(deflistIdPlugin)
43
+ .use(externalLinksPlugin, siteVariables)
44
+ .use(validateInternalLinksPlugin, validatorOptions)
45
+ .use(applyBasePathPlugin, siteVariables)
46
+ .use(tocPlugin)
47
+ .use(markdownItContainer, 'details', {
27
48
  marker: '<',
28
- validate: function (params) {
29
- return params.trim().match(/^details\s+(.*)$/);
49
+ validate: function (params: string) {
50
+ return !!params.trim().match(/^details\s+(.*)$/);
30
51
  },
31
52
 
32
- render: function (tokens, idx) {
33
- var m = tokens[idx].info.trim().match(/^details\s+(.*)$/);
53
+ render: function (tokens: Token[], idx: number) {
54
+ const m = tokens[idx].info.trim().match(/^details\s+(.*)$/);
34
55
 
35
56
  if (tokens[idx].nesting === 1) {
36
57
  return (
37
58
  '<details><summary>' +
38
- markdown.renderInline(m[1]) +
59
+ markdown.renderInline(m![1]) +
39
60
  '</summary><div class="content">\n'
40
61
  );
41
62
  } else {
@@ -43,12 +64,12 @@ function createMarkdown(siteVariables, options = {}) {
43
64
  }
44
65
  },
45
66
  })
46
- .use(require('markdown-it-container'), 'section', {
67
+ .use(markdownItContainer, 'section', {
47
68
  marker: ':',
48
- validate: function (params) {
49
- return params.trim().match(/^section$/);
69
+ validate: function (params: string) {
70
+ return !!params.trim().match(/^section$/);
50
71
  },
51
- render: function (tokens, idx) {
72
+ render: function (tokens: Token[], idx: number) {
52
73
  if (tokens[idx].nesting === 1) {
53
74
  return '<section>\n';
54
75
  } else {
@@ -57,13 +78,13 @@ function createMarkdown(siteVariables, options = {}) {
57
78
  },
58
79
  });
59
80
 
60
- const usedIds = new Map();
61
- markdown.use(require('markdown-it-container'), 'alert', {
81
+ const usedIds = new Map<string, number>();
82
+ markdown.use(markdownItContainer, 'alert', {
62
83
  marker: '!',
63
- validate: function (params) {
64
- return params.trim().match(/^(note|warning)\s*"?(.+)?"?$/);
84
+ validate: function (params: string) {
85
+ return !!params.trim().match(/^(note|warning)\s*"?(.+)?"?$/);
65
86
  },
66
- render: function (tokens, idx) {
87
+ render: function (tokens: Token[], idx: number) {
67
88
  const matches = tokens[idx].info
68
89
  .trim()
69
90
  .match(/^(note|warning)\s*"?(.+)?"?$/);
@@ -86,7 +107,7 @@ function createMarkdown(siteVariables, options = {}) {
86
107
  const renderedTitle = markdown.utils.escapeHtml(curlyQuote(title));
87
108
  html += `<p class="title" id="${titleId}">${renderedTitle}</p>\n`;
88
109
  } else {
89
- const defaultTitle = capitalize(type);
110
+ const defaultTitle = capitalize(type || '');
90
111
  html += `<p class="title">${defaultTitle}</p>\n`;
91
112
  }
92
113
  html += '<div class="content">\n';
@@ -97,15 +118,15 @@ function createMarkdown(siteVariables, options = {}) {
97
118
  },
98
119
  });
99
120
 
100
- markdown.use(require('markdown-it-container'), 'question', {
121
+ markdown.use(markdownItContainer, 'question', {
101
122
  marker: '?',
102
- validate: function (params) {
103
- return params.trim().match(/^question\s+(.+)$/);
123
+ validate: function (params: string) {
124
+ return !!params.trim().match(/^question\s+(.+)$/);
104
125
  },
105
- render: function (tokens, idx) {
126
+ render: function (tokens: Token[], idx: number) {
106
127
  const m = tokens[idx].info.trim().match(/^question\s+(.+)$/);
107
128
  if (tokens[idx].nesting === 1) {
108
- const question = markdown.renderInline(m[1]);
129
+ const question = markdown.renderInline(m![1]);
109
130
  return (
110
131
  '<div class="question">' +
111
132
  '<p class="question-q"><span class="question-label">Q.</span><span>' +
@@ -130,28 +151,54 @@ function createMarkdown(siteVariables, options = {}) {
130
151
  markdown.renderer.rules.footnote_block_close = () => '</ol></div>';
131
152
 
132
153
  // Change appearance of reference
133
- const caption = markdown.renderer.rules.footnote_caption;
134
- markdown.renderer.rules.footnote_caption = (...args) => {
135
- const str = caption(...args);
154
+ const caption = markdown.renderer.rules.footnote_caption!;
155
+ markdown.renderer.rules.footnote_caption = (
156
+ tokens: Token[],
157
+ idx: number,
158
+ options: Options,
159
+ env: unknown,
160
+ self: Renderer,
161
+ ) => {
162
+ const str = caption(tokens, idx, options, env, self);
136
163
  return str.slice(1, str.length - 1);
137
164
  };
138
165
 
139
- const footnoteRef = markdown.renderer.rules.footnote_ref;
140
- markdown.renderer.rules.footnote_ref = (...args) =>
141
- footnoteRef(...args)
166
+ const footnoteRef = markdown.renderer.rules.footnote_ref!;
167
+ markdown.renderer.rules.footnote_ref = (
168
+ tokens: Token[],
169
+ idx: number,
170
+ options: Options,
171
+ env: unknown,
172
+ self: Renderer,
173
+ ) =>
174
+ footnoteRef(tokens, idx, options, env, self)
142
175
  .replace('<sup class="footnote-ref">', '')
143
176
  .replace('</sup>', '')
144
177
  .replace('<a href="', '<a class="footnote-ref" href="');
145
178
 
146
- const footnoteAnchor = markdown.renderer.rules.footnote_anchor;
147
- markdown.renderer.rules.footnote_anchor = (...args) =>
148
- footnoteAnchor(...args).replace('\u21a9\uFE0E', '\u2191');
179
+ const footnoteAnchor = markdown.renderer.rules.footnote_anchor!;
180
+ markdown.renderer.rules.footnote_anchor = (
181
+ tokens: Token[],
182
+ idx: number,
183
+ options: Options,
184
+ env: unknown,
185
+ self: Renderer,
186
+ ) =>
187
+ footnoteAnchor(tokens, idx, options, env, self).replace(
188
+ '\u21a9\uFE0E',
189
+ '\u2191',
190
+ );
149
191
 
150
192
  /*
151
193
  * Customize lists (add wrapper element)
152
194
  */
153
- const proxy = (tokens, idx, options, env, self) =>
154
- self.renderToken(tokens, idx, options);
195
+ const proxy = (
196
+ tokens: Token[],
197
+ idx: number,
198
+ options: Options,
199
+ _env: unknown,
200
+ self: Renderer,
201
+ ) => self.renderToken(tokens, idx, options);
155
202
 
156
203
  const itemOpen = markdown.renderer.rules.list_item_open || proxy;
157
204
  markdown.renderer.rules.list_item_open = (
@@ -240,5 +287,3 @@ function createMarkdown(siteVariables, options = {}) {
240
287
 
241
288
  return markdown;
242
289
  }
243
-
244
- module.exports = { createMarkdown };