@abreen/tada 1.0.2 → 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 (146) hide show
  1. package/README.md +96 -48
  2. package/bin/tada.ts +621 -0
  3. package/bin/validators.test.ts +205 -0
  4. package/bin/validators.ts +85 -0
  5. package/{webpack/apply-base-path-plugin.js → build/apply-base-path-plugin.ts} +37 -17
  6. package/build/build-manifest.test.ts +349 -0
  7. package/build/build-manifest.ts +165 -0
  8. package/build/bundle.ts +116 -0
  9. package/{webpack/code.test.js → build/code.test.ts} +10 -7
  10. package/build/colors.ts +25 -0
  11. package/build/content-watch.ts +107 -0
  12. package/build/copy.ts +118 -0
  13. package/{webpack/deflist-id-plugin.js → build/deflist-id-plugin.ts} +7 -6
  14. package/{webpack/external-links-plugin.js → build/external-links-plugin.ts} +14 -5
  15. package/build/features.ts +11 -0
  16. package/build/generate-content-assets.ts +320 -0
  17. package/build/generate-favicon.ts +172 -0
  18. package/build/generate-fonts.ts +31 -0
  19. package/build/generate-katex-assets.ts +35 -0
  20. package/{webpack/generate-manifest-plugin.js → build/generate-manifest.ts} +29 -36
  21. package/build/globals.test.ts +99 -0
  22. package/build/globals.ts +88 -0
  23. package/{webpack/heading-subtitle-plugin.js → build/heading-subtitle-plugin.ts} +4 -2
  24. package/build/json-schema.test.ts +57 -0
  25. package/build/json-schema.ts +33 -0
  26. package/build/log.test.ts +111 -0
  27. package/build/log.ts +167 -0
  28. package/build/markdown-plugins.test.ts +396 -0
  29. package/{webpack/pagefind-plugin.test.js → build/pagefind.test.ts} +74 -13
  30. package/build/pagefind.ts +339 -0
  31. package/build/pdf-text.test.ts +52 -0
  32. package/{webpack/pdf-text.js → build/pdf-text.ts} +47 -27
  33. package/build/pipeline.ts +120 -0
  34. package/{webpack/reachability.test.js → build/reachability.test.ts} +3 -3
  35. package/{webpack/reachability.js → build/reachability.ts} +77 -34
  36. package/build/serve.ts +112 -0
  37. package/build/site-variables.ts +68 -0
  38. package/{webpack/templates.js → build/templates.ts} +38 -35
  39. package/{webpack/text-to-id.js → build/text-to-id.ts} +2 -2
  40. package/build/toc-plugin.test.ts +105 -0
  41. package/{webpack/toc-plugin.js → build/toc-plugin.ts} +32 -13
  42. package/build/types.ts +181 -0
  43. package/build/util.ts +26 -0
  44. package/{webpack/utils/code.js → build/utils/code.ts} +119 -60
  45. package/{webpack/utils/content-files.js → build/utils/content-files.ts} +40 -35
  46. package/build/utils/derive-theme.test.ts +111 -0
  47. package/build/utils/derive-theme.ts +85 -0
  48. package/build/utils/file-types.test.ts +61 -0
  49. package/build/utils/file-types.ts +13 -0
  50. package/build/utils/front-matter.test.ts +77 -0
  51. package/{webpack/utils/front-matter.js → build/utils/front-matter.ts} +22 -9
  52. package/{webpack → build}/utils/jdi-runner/LiterateRunner.java +1 -1
  53. package/{webpack/utils/literate-java.js → build/utils/literate-java.ts} +68 -38
  54. package/{webpack/utils/markdown.js → build/utils/markdown.ts} +126 -49
  55. package/build/utils/paths.test.ts +91 -0
  56. package/{webpack/utils/paths.js → build/utils/paths.ts} +18 -22
  57. package/{webpack/utils/render.js → build/utils/render.ts} +208 -130
  58. package/build/utils/shiki-highlighter.ts +29 -0
  59. package/build/validate-internal-links-plugin.test.ts +106 -0
  60. package/{webpack/validate-internal-links-plugin.js → build/validate-internal-links-plugin.ts} +47 -20
  61. package/{webpack/watch-reachability-state.test.js → build/watch-reachability-state.test.ts} +8 -8
  62. package/{webpack/watch-reachability-state.js → build/watch-reachability-state.ts} +63 -24
  63. package/{webpack/watch-reload-client.js → build/watch-reload-client.ts} +3 -1
  64. package/build/watch.ts +549 -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/{content → init/content}/index.md +9 -3
  70. package/{content → init/content}/lectures/index.md +1 -1
  71. package/{content → init/content}/markdown.md +28 -8
  72. package/init/content/problem_sets/index.html +14 -0
  73. package/package.json +33 -22
  74. package/{templates → schema}/nav.schema.json +1 -2
  75. package/{webpack → schema}/site.schema.json +4 -12
  76. package/src/_alerts.scss +96 -0
  77. package/src/_base.scss +145 -0
  78. package/src/{layout.scss → _layout.scss} +36 -1
  79. package/src/anchor/index.ts +0 -1
  80. package/src/anchor/style.scss +1 -9
  81. package/src/code/index.ts +3 -3
  82. package/src/code.scss +1 -1
  83. package/src/critical.scss +5 -0
  84. package/src/header/_base.scss +129 -0
  85. package/src/header/style.scss +3 -131
  86. package/src/index.ts +4 -5
  87. package/src/question/style.scss +1 -1
  88. package/src/search/index.test.ts +203 -0
  89. package/src/search/index.ts +38 -93
  90. package/src/search/pdf-utils.ts +79 -0
  91. package/src/search/style.scss +8 -28
  92. package/src/style.scss +15 -326
  93. package/src/timezone/index.ts +5 -6
  94. package/src/toc/style.scss +5 -39
  95. package/src/util.ts +9 -6
  96. package/templates/_theme.scss +38 -14
  97. package/templates/_top.html +1 -1
  98. package/tsconfig.json +10 -6
  99. package/types/dev.d.ts +1 -0
  100. package/types/file-system-access.d.ts +5 -0
  101. package/types/markdown-it-plugins.d.ts +11 -0
  102. package/types/site-variables.d.ts +6 -10
  103. package/types/untyped-modules.d.ts +47 -0
  104. package/bin/tada.js +0 -361
  105. package/config/authors.json +0 -1
  106. package/config/nav.json +0 -28
  107. package/content/problem_sets/index.md +0 -6
  108. package/types/dev.ts +0 -3
  109. package/webpack/build-state.js +0 -97
  110. package/webpack/colors.js +0 -15
  111. package/webpack/config.base.js +0 -151
  112. package/webpack/config.dev.js +0 -23
  113. package/webpack/config.prod.js +0 -32
  114. package/webpack/content-watch-plugin.js +0 -153
  115. package/webpack/features.js +0 -5
  116. package/webpack/generate-content-assets-plugin.js +0 -308
  117. package/webpack/generate-favicon-plugin.js +0 -198
  118. package/webpack/generate-fonts-plugin.js +0 -69
  119. package/webpack/globals.js +0 -74
  120. package/webpack/json-schema.js +0 -19
  121. package/webpack/log.js +0 -143
  122. package/webpack/markdown-plugins.test.js +0 -203
  123. package/webpack/pagefind-plugin.js +0 -379
  124. package/webpack/print-flair-plugin.js +0 -22
  125. package/webpack/serve.js +0 -104
  126. package/webpack/site-variables.js +0 -53
  127. package/webpack/util.js +0 -49
  128. package/webpack/utils/define-plugin.js +0 -20
  129. package/webpack/utils/file-types.js +0 -26
  130. package/webpack/utils/parse-hsl.js +0 -8
  131. package/webpack/utils/shiki-highlighter.js +0 -26
  132. package/webpack/watch.js +0 -166
  133. /package/{webpack → build}/flair.json +0 -0
  134. /package/{webpack → build}/utils/jdi-runner/LiterateRunner.class +0 -0
  135. /package/fonts/google-sans-code/{GoogleSansCodeVariable-Italic.ttf → ttf/GoogleSansCodeVariable-Italic.ttf} +0 -0
  136. /package/fonts/google-sans-code/{GoogleSansCodeVariable.ttf → ttf/GoogleSansCodeVariable.ttf} +0 -0
  137. /package/fonts/inter/{InterVariable-Italic.ttf → ttf/InterVariable-Italic.ttf} +0 -0
  138. /package/fonts/inter/{InterVariable.ttf → ttf/InterVariable.ttf} +0 -0
  139. /package/{content → init/content}/lectures/01/Pair.java.md +0 -0
  140. /package/{content → init/content}/lectures/01/Rectangle.java +0 -0
  141. /package/{content → init/content}/lectures/01/demo.py +0 -0
  142. /package/{content → init/content}/lectures/01/index.md +0 -0
  143. /package/{content → init/content}/lectures/01/lecture1.pdf +0 -0
  144. /package/{public → init/public}/avatars/alex.jpg +0 -0
  145. /package/{public → init/public}/test.txt +0 -0
  146. /package/{templates → schema}/authors.schema.json +0 -0
@@ -1,14 +1,7 @@
1
1
  :root {
2
- --theme-hue: <%= themeHue %>deg;
3
- --theme-saturation: <%= themeSaturation %>%;
4
- --theme-lightness: <%= themeLightness %>%;
5
- --theme-color: hsl(
6
- var(--theme-hue) var(--theme-saturation) var(--theme-lightness)
7
- );
8
- --theme-color-text: hsl(
9
- var(--theme-hue) clamp(30%, var(--theme-saturation), 70%)
10
- clamp(25%, var(--theme-lightness), 42%)
11
- );
2
+ --theme-color: <%= themeColorLight %>;
3
+ --theme-color-text: <%= themeColorTextLight %>;
4
+ --text-on-theme: <%= textOnThemeLight %>;
12
5
 
13
6
  // Background & foreground color tint
14
7
  --tint-hue: <%= tintHue %>deg;
@@ -62,15 +55,30 @@
62
55
  --mono-line-height: 1.5;
63
56
  --mono-border-radius: 0.375em;
64
57
 
58
+ // SVG icons
59
+ --icon-external-link: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24'><rect x='3' y='3' width='18' height='18' stroke='<%= iconColor %>' stroke-width='2' fill='none' rx='2'/><path d='M9 15 L15 9' stroke='<%= iconColor %>' stroke-width='2' stroke-linecap='round' fill='none'/><path d='M9 9 H15 V15' stroke='<%= iconColor %>' stroke-width='2' stroke-linecap='round' fill='none'/></svg>");
60
+ --icon-external-link-hover: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24'><rect x='3' y='3' width='18' height='18' stroke='<%= iconColorHover %>' stroke-width='2' fill='none' rx='2'/><path d='M9 15 L15 9' stroke='<%= iconColorHover %>' stroke-width='2' stroke-linecap='round' fill='none'/><path d='M9 9 H15 V15' stroke='<%= iconColorHover %>' stroke-width='2' stroke-linecap='round' fill='none'/></svg>");
61
+ --icon-external-link-active: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24'><rect x='3' y='3' width='18' height='18' stroke='black' stroke-width='2' fill='none' rx='2'/><path d='M9 15 L15 9' stroke='black' stroke-width='2' stroke-linecap='round' fill='none'/><path d='M9 9 H15 V15' stroke='black' stroke-width='2' stroke-linecap='round' fill='none'/></svg>");
62
+ --icon-external-translucent: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24'><g opacity='0.3' stroke='<%= iconColor %>' stroke-width='2' stroke-linecap='round' fill='none'><rect x='3' y='3' width='18' height='18' rx='2'/><path d='M9 15 L15 9' stroke-linecap='round'/><path d='M9 9 H15 V15' stroke-linecap='round'/></g></svg>");
63
+ --icon-warning: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 24 24'><polygon points='12,2 22,20 2,20' fill='none' stroke='<%= iconColor %>' stroke-width='2' stroke-linejoin='round'/><line x1='12' y1='9' x2='12' y2='13' stroke='<%= iconColor %>' stroke-width='2' stroke-linecap='round'/><line x1='12' y1='16' x2='12' y2='16' stroke='<%= iconColor %>' stroke-width='2' stroke-linecap='round'/></svg>");
64
+ --icon-warning-sm: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24'><polygon points='12,2 22,20 2,20' fill='none' stroke='<%= iconColor %>' stroke-width='2' stroke-linejoin='round'/><line x1='12' y1='9' x2='12' y2='13' stroke='<%= iconColor %>' stroke-width='2' stroke-linecap='round'/><line x1='12' y1='16' x2='12' y2='16' stroke='<%= iconColor %>' stroke-width='2' stroke-linecap='round'/></svg>");
65
+ --icon-warning-sm-hover: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24'><polygon points='12,2 22,20 2,20' fill='none' stroke='<%= iconColorHover %>' stroke-width='2' stroke-linejoin='round'/><line x1='12' y1='9' x2='12' y2='13' stroke='<%= iconColorHover %>' stroke-width='2' stroke-linecap='round'/><line x1='12' y1='16' x2='12' y2='16' stroke='<%= iconColorHover %>' stroke-width='2' stroke-linecap='round'/></svg>");
66
+ --icon-note: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 24 24'><circle cx='12' cy='12' r='10' fill='none' stroke='<%= iconColor %>' stroke-width='2'/><line x1='12' y1='10' x2='12' y2='16' stroke='<%= iconColor %>' stroke-width='2' stroke-linecap='round'/><line x1='12' y1='7' x2='12' y2='7' stroke='<%= iconColor %>' stroke-width='2' stroke-linecap='round'/></svg>");
67
+ --icon-note-sm: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24'><circle cx='12' cy='12' r='10' fill='none' stroke='<%= iconColor %>' stroke-width='2'/><line x1='12' y1='10' x2='12' y2='16' stroke='<%= iconColor %>' stroke-width='2' stroke-linecap='round'/><line x1='12' y1='7' x2='12' y2='7' stroke='<%= iconColor %>' stroke-width='2' stroke-linecap='round'/></svg>");
68
+ --icon-note-sm-hover: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24'><circle cx='12' cy='12' r='10' fill='none' stroke='<%= iconColorHover %>' stroke-width='2'/><line x1='12' y1='10' x2='12' y2='16' stroke='<%= iconColorHover %>' stroke-width='2' stroke-linecap='round'/><line x1='12' y1='7' x2='12' y2='7' stroke='<%= iconColorHover %>' stroke-width='2' stroke-linecap='round'/></svg>");
69
+ --icon-search: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24'><circle cx='11' cy='11' r='7' stroke='<%= iconColor %>' stroke-width='2' fill='none'/><line x1='16.5' y1='16.5' x2='22' y2='22' stroke='<%= iconColor %>' stroke-width='2' stroke-linecap='round'/></svg>");
70
+ --icon-anchor: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='<%= iconColor %>' fill-rule='evenodd' clip-rule='evenodd' d='M10.975 14.51a1.05 1.05 0 0 0 0-1.485 2.95 2.95 0 0 1 0-4.172l3.536-3.535a2.95 2.95 0 1 1 4.172 4.172l-1.093 1.092a1.05 1.05 0 0 0 1.485 1.485l1.093-1.092a5.05 5.05 0 0 0-7.142-7.142L9.49 7.368a5.05 5.05 0 0 0 0 7.142c.41.41 1.075.41 1.485 0zm2.05-5.02a1.05 1.05 0 0 0 0 1.485 2.95 2.95 0 0 1 0 4.172l-3.5 3.5a2.95 2.95 0 1 1-4.171-4.172l1.025-1.025a1.05 1.05 0 0 0-1.485-1.485L3.87 12.99a5.05 5.05 0 0 0 7.142 7.142l3.5-3.5a5.05 5.05 0 0 0 0-7.142 1.05 1.05 0 0 0-1.485 0z'/></svg>");
71
+ --icon-chevron: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'><polyline points='9 18 15 12 9 6' stroke='<%= iconColor %>' stroke-width='2' fill='none' stroke-linecap='round' stroke-linejoin='round'/></svg>");
72
+ --icon-breadcrumb: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24'><path d='M3 7 H17 V22' stroke='<%= iconColor %>' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' fill='none'/><polyline points='11 16 17 22 23 16' stroke='<%= iconColor %>' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' fill='none'/></svg>");
73
+
65
74
  color-scheme: light dark;
66
75
  }
67
76
 
68
77
  @media (prefers-color-scheme: dark) {
69
78
  :root {
70
- --theme-color-text: hsl(
71
- var(--theme-hue) clamp(30%, var(--theme-saturation), 70%)
72
- clamp(50%, calc(var(--theme-lightness) + 15%), 80%)
73
- );
79
+ --theme-color: <%= themeColorDark %>;
80
+ --theme-color-text: <%= themeColorTextDark %>;
81
+ --text-on-theme: <%= textOnThemeDark %>;
74
82
 
75
83
  --fg-color: hsl(var(--tint-hue) calc(10% * var(--tint-amount)) 92%);
76
84
  --fg-color-translucent: hsl(
@@ -93,5 +101,21 @@
93
101
 
94
102
  --warning-bg-color: hsl(36 77% 27%);
95
103
  --note-bg-color: hsl(209 90% 25%);
104
+
105
+ // SVG icons (dark overrides)
106
+ --icon-external-link: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24'><rect x='3' y='3' width='18' height='18' stroke='<%= iconColorDark %>' stroke-width='2' fill='none' rx='2'/><path d='M9 15 L15 9' stroke='<%= iconColorDark %>' stroke-width='2' stroke-linecap='round' fill='none'/><path d='M9 9 H15 V15' stroke='<%= iconColorDark %>' stroke-width='2' stroke-linecap='round' fill='none'/></svg>");
107
+ --icon-external-link-hover: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24'><rect x='3' y='3' width='18' height='18' stroke='<%= iconColorHoverDark %>' stroke-width='2' fill='none' rx='2'/><path d='M9 15 L15 9' stroke='<%= iconColorHoverDark %>' stroke-width='2' stroke-linecap='round' fill='none'/><path d='M9 9 H15 V15' stroke='<%= iconColorHoverDark %>' stroke-width='2' stroke-linecap='round' fill='none'/></svg>");
108
+ --icon-external-link-active: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24'><rect x='3' y='3' width='18' height='18' stroke='white' stroke-width='2' fill='none' rx='2'/><path d='M9 15 L15 9' stroke='white' stroke-width='2' stroke-linecap='round' fill='none'/><path d='M9 9 H15 V15' stroke='white' stroke-width='2' stroke-linecap='round' fill='none'/></svg>");
109
+ --icon-external-translucent: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24'><g opacity='0.3' stroke='<%= iconColorTranslucentDark %>' stroke-width='2' stroke-linecap='round' fill='none'><rect x='3' y='3' width='18' height='18' rx='2'/><path d='M9 15 L15 9' stroke-linecap='round'/><path d='M9 9 H15 V15' stroke-linecap='round'/></g></svg>");
110
+ --icon-warning: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 24 24'><polygon points='12,2 22,20 2,20' fill='none' stroke='<%= iconColorDark %>' stroke-width='2' stroke-linejoin='round'/><line x1='12' y1='9' x2='12' y2='13' stroke='<%= iconColorDark %>' stroke-width='2' stroke-linecap='round'/><line x1='12' y1='16' x2='12' y2='16' stroke='<%= iconColorDark %>' stroke-width='2' stroke-linecap='round'/></svg>");
111
+ --icon-warning-sm: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24'><polygon points='12,2 22,20 2,20' fill='none' stroke='<%= iconColorDark %>' stroke-width='2' stroke-linejoin='round'/><line x1='12' y1='9' x2='12' y2='13' stroke='<%= iconColorDark %>' stroke-width='2' stroke-linecap='round'/><line x1='12' y1='16' x2='12' y2='16' stroke='<%= iconColorDark %>' stroke-width='2' stroke-linecap='round'/></svg>");
112
+ --icon-warning-sm-hover: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24'><polygon points='12,2 22,20 2,20' fill='none' stroke='<%= iconColorHoverDark %>' stroke-width='2' stroke-linejoin='round'/><line x1='12' y1='9' x2='12' y2='13' stroke='<%= iconColorHoverDark %>' stroke-width='2' stroke-linecap='round'/><line x1='12' y1='16' x2='12' y2='16' stroke='<%= iconColorHoverDark %>' stroke-width='2' stroke-linecap='round'/></svg>");
113
+ --icon-note: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 24 24'><circle cx='12' cy='12' r='10' fill='none' stroke='<%= iconColorDark %>' stroke-width='2'/><line x1='12' y1='10' x2='12' y2='16' stroke='<%= iconColorDark %>' stroke-width='2' stroke-linecap='round'/><line x1='12' y1='7' x2='12' y2='7' stroke='<%= iconColorDark %>' stroke-width='2' stroke-linecap='round'/></svg>");
114
+ --icon-note-sm: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24'><circle cx='12' cy='12' r='10' fill='none' stroke='<%= iconColorDark %>' stroke-width='2'/><line x1='12' y1='10' x2='12' y2='16' stroke='<%= iconColorDark %>' stroke-width='2' stroke-linecap='round'/><line x1='12' y1='7' x2='12' y2='7' stroke='<%= iconColorDark %>' stroke-width='2' stroke-linecap='round'/></svg>");
115
+ --icon-note-sm-hover: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24'><circle cx='12' cy='12' r='10' fill='none' stroke='<%= iconColorHoverDark %>' stroke-width='2'/><line x1='12' y1='10' x2='12' y2='16' stroke='<%= iconColorHoverDark %>' stroke-width='2' stroke-linecap='round'/><line x1='12' y1='7' x2='12' y2='7' stroke='<%= iconColorHoverDark %>' stroke-width='2' stroke-linecap='round'/></svg>");
116
+ --icon-search: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24'><circle cx='11' cy='11' r='7' stroke='<%= iconColorDark %>' stroke-width='2' fill='none'/><line x1='16.5' y1='16.5' x2='22' y2='22' stroke='<%= iconColorDark %>' stroke-width='2' stroke-linecap='round'/></svg>");
117
+ --icon-anchor: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='<%= iconColorDark %>' fill-rule='evenodd' clip-rule='evenodd' d='M10.975 14.51a1.05 1.05 0 0 0 0-1.485 2.95 2.95 0 0 1 0-4.172l3.536-3.535a2.95 2.95 0 1 1 4.172 4.172l-1.093 1.092a1.05 1.05 0 0 0 1.485 1.485l1.093-1.092a5.05 5.05 0 0 0-7.142-7.142L9.49 7.368a5.05 5.05 0 0 0 0 7.142c.41.41 1.075.41 1.485 0zm2.05-5.02a1.05 1.05 0 0 0 0 1.485 2.95 2.95 0 0 1 0 4.172l-3.5 3.5a2.95 2.95 0 1 1-4.171-4.172l1.025-1.025a1.05 1.05 0 0 0-1.485-1.485L3.87 12.99a5.05 5.05 0 0 0 7.142 7.142l3.5-3.5a5.05 5.05 0 0 0 0-7.142 1.05 1.05 0 0 0-1.485 0z'/></svg>");
118
+ --icon-chevron: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'><polyline points='9 18 15 12 9 6' stroke='<%= iconColorDark %>' stroke-width='2' fill='none' stroke-linecap='round' stroke-linejoin='round'/></svg>");
119
+ --icon-breadcrumb: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24'><path d='M3 7 H17 V22' stroke='<%= iconColorDark %>' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' fill='none'/><polyline points='11 16 17 22 23 16' stroke='<%= iconColorDark %>' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' fill='none'/></svg>");
96
120
  }
97
121
  }
@@ -54,7 +54,7 @@
54
54
  <header data-pagefind-ignore>
55
55
  <details>
56
56
  <summary aria-label="Toggle site navigation">
57
- <span class="logo" aria-hidden="true"><%= site.symbol %></span>
57
+ <% if (site.symbol) { %><span class="logo" aria-hidden="true"><%= site.symbol %></span><% } %>
58
58
  <span class="site-title" aria-hidden="true"><%= site.title %></span>
59
59
  <div id="to-top-container" class="to-top-container"></div>
60
60
  <% if (site.features.search) { %>
package/tsconfig.json CHANGED
@@ -1,15 +1,19 @@
1
1
  {
2
- "extends": "@tsconfig/node23/tsconfig.json",
3
2
  "compilerOptions": {
4
- "target": "ES6",
5
- "lib": ["es2024", "dom"],
6
- "module": "esnext",
3
+ "target": "ESNext",
4
+ "lib": ["ESNext", "dom", "dom.iterable"],
5
+ "module": "Preserve",
6
+ "moduleDetection": "force",
7
7
  "moduleResolution": "bundler",
8
+ "allowImportingTsExtensions": true,
9
+ "verbatimModuleSyntax": true,
10
+ "noEmit": true,
8
11
  "outDir": "./dist/",
9
12
  "resolveJsonModule": true,
10
13
  "strict": true,
14
+ "skipLibCheck": true,
11
15
  "noUnusedLocals": true
12
16
  },
13
- "include": ["src", "types"],
14
- "exclude": ["node_modules", "**/*.spec.ts", "**/*.test.ts"]
17
+ "include": ["src", "types", "build", "bin"],
18
+ "exclude": ["node_modules"]
15
19
  }
package/types/dev.d.ts ADDED
@@ -0,0 +1 @@
1
+ declare const __IS_DEV__: boolean;
@@ -0,0 +1,5 @@
1
+ interface Window {
2
+ showSaveFilePicker?: (options?: {
3
+ suggestedName?: string;
4
+ }) => Promise<FileSystemFileHandle>;
5
+ }
@@ -0,0 +1,11 @@
1
+ declare module 'markdown-it-footnote' {
2
+ import type MarkdownIt from 'markdown-it';
3
+ const plugin: MarkdownIt.PluginSimple;
4
+ export default plugin;
5
+ }
6
+
7
+ declare module 'markdown-it-deflist' {
8
+ import type MarkdownIt from 'markdown-it';
9
+ const plugin: MarkdownIt.PluginSimple;
10
+ export default plugin;
11
+ }
@@ -1,16 +1,12 @@
1
- interface TimezoneDef {
1
+ interface TimeZone {
2
2
  value: string;
3
3
  label: string;
4
4
  abbreviation: string;
5
5
  offsetMinutes?: number;
6
6
  }
7
7
 
8
- interface Window {
9
- siteVariables: {
10
- base: string;
11
- basePath: string;
12
- titlePostfix?: string;
13
- defaultTimeZone: string;
14
- timezones: TimezoneDef[];
15
- };
16
- }
8
+ declare const __SITE_BASE__: string;
9
+ declare const __SITE_BASE_PATH__: string;
10
+ declare const __SITE_TITLE_POSTFIX__: string;
11
+ declare const __SITE_DEFAULT_TIMEZONE__: string;
12
+ declare const __SITE_TIMEZONES__: TimeZone[];
@@ -0,0 +1,47 @@
1
+ declare module 'pagefind' {
2
+ interface PagefindIndex {
3
+ addHTMLFile(file: {
4
+ sourcePath: string;
5
+ content: string;
6
+ }): Promise<{ errors: string[] }>;
7
+ addCustomRecord(record: {
8
+ url: string;
9
+ content: string;
10
+ language: string;
11
+ meta: Record<string, string>;
12
+ }): Promise<{ errors: string[] }>;
13
+ writeFiles(options: { outputPath: string }): Promise<{ errors: string[] }>;
14
+ deleteIndex(): Promise<void>;
15
+ }
16
+
17
+ export function createIndex(options?: {
18
+ keepIndexUrl?: boolean;
19
+ verbose?: boolean;
20
+ }): Promise<{ index: PagefindIndex | null; errors: string[] }>;
21
+
22
+ export function close(): Promise<void>;
23
+ }
24
+
25
+ declare module 'katex/contrib/render-a11y-string' {
26
+ export default function renderA11yString(
27
+ text: string,
28
+ settings?: Record<string, unknown>,
29
+ ): string;
30
+ }
31
+
32
+ declare module 'jsdom' {
33
+ export class JSDOM {
34
+ constructor(html?: string | Buffer, options?: Record<string, unknown>);
35
+ static fragment(html: string): DocumentFragment;
36
+ readonly window: DOMWindow;
37
+ }
38
+
39
+ interface DOMWindow extends Window {
40
+ close(): void;
41
+ }
42
+ }
43
+
44
+ declare module 'java-parser' {
45
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
46
+ export function parse(source: string): any;
47
+ }
package/bin/tada.js DELETED
@@ -1,361 +0,0 @@
1
- #!/usr/bin/env bun
2
- const fs = require('fs');
3
- const path = require('path');
4
- const readline = require('readline');
5
- const { execSync } = require('child_process');
6
-
7
- const SYSTEM_TIME_ZONE = Intl.DateTimeFormat().resolvedOptions().timeZone;
8
-
9
- const packageDir = path.resolve(__dirname, '..');
10
-
11
- const COMMANDS = {
12
- init: 'Create a new Tada site',
13
- dev: 'Build the site for development',
14
- prod: 'Build the site for production',
15
- watch: 'Watch for changes and rebuild',
16
- serve: 'Start a local development server',
17
- clean: 'Remove the dist/ directory',
18
- };
19
-
20
- const INIT_QUESTIONS = {
21
- title: {
22
- prompt: 'Site title',
23
- defaultValue: 'Introduction to Computer Science',
24
- validate: v => (v ? null : 'Title is required'),
25
- },
26
- symbol: {
27
- prompt: 'Logo symbol (1-5 uppercase chars)',
28
- defaultValue: 'CS 0',
29
- validate: validateSymbol,
30
- },
31
- themeColor: {
32
- prompt: 'Theme color',
33
- defaultValue: 'hsl(195 70% 40%)',
34
- validate: validateHslColor,
35
- },
36
- tintHue: {
37
- prompt: 'Background tint hue (0-360)',
38
- defaultValue: '20',
39
- validate: v => {
40
- const n = Number(v);
41
- if (!Number.isInteger(n) || n < 0 || n > 360) {
42
- return 'Must be an integer from 0 to 360';
43
- }
44
- return null;
45
- },
46
- },
47
- tintAmount: {
48
- prompt: 'Background tint amount (0-100)',
49
- defaultValue: '100',
50
- validate: v => {
51
- const n = Number(v);
52
- if (!Number.isInteger(n) || n < 0 || n > 100) {
53
- return 'Must be an integer from 0 to 100';
54
- }
55
- return null;
56
- },
57
- },
58
- defaultTimeZone: {
59
- prompt: 'Default time zone',
60
- defaultValue: SYSTEM_TIME_ZONE,
61
- validate: v => (v ? null : 'Time zone is required'),
62
- },
63
- prodBase: {
64
- prompt: 'Production base URL',
65
- defaultValue: 'https://example.edu',
66
- validate: validateUrl,
67
- },
68
- prodBasePath: {
69
- prompt: 'Production base path',
70
- defaultValue: '/',
71
- validate: validateBasePath,
72
- },
73
- };
74
-
75
- function printUsage() {
76
- console.log('Usage: tada <command>\n');
77
- console.log('Commands:');
78
- console.log(' init <dirname> Initialize a new site');
79
- for (const [cmd, desc] of Object.entries(COMMANDS)) {
80
- if (cmd === 'init') {
81
- console.log(' [--default] Use defaults for all config options');
82
- continue;
83
- }
84
- console.log(` ${cmd.padEnd(18)} ${desc}`);
85
- }
86
- }
87
-
88
- const webpackCli = require.resolve('webpack-cli/bin/cli.js');
89
-
90
- function run(cmd) {
91
- execSync(cmd, { cwd: process.cwd(), stdio: 'inherit' });
92
- }
93
-
94
- function copyDirRecursive(src, dest) {
95
- fs.mkdirSync(dest, { recursive: true });
96
- for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
97
- const srcPath = path.join(src, entry.name);
98
- const destPath = path.join(dest, entry.name);
99
- if (entry.isDirectory()) {
100
- copyDirRecursive(srcPath, destPath);
101
- } else {
102
- fs.copyFileSync(srcPath, destPath);
103
- }
104
- }
105
- }
106
-
107
- // --- init command ---
108
-
109
- function ask(rl, question, { defaultValue, validate } = {}) {
110
- const suffix = defaultValue != null ? ` (default: ${defaultValue})` : '';
111
- return new Promise(resolve => {
112
- function prompt() {
113
- rl.question(`${question}${suffix}? `, answer => {
114
- const value = answer.trim() || defaultValue || '';
115
- if (validate) {
116
- const error = validate(value);
117
- if (error) {
118
- console.error(`Error: ${error}`);
119
- prompt();
120
- return;
121
- }
122
- }
123
- resolve(value);
124
- });
125
- }
126
- prompt();
127
- });
128
- }
129
-
130
- function validateSymbol(value) {
131
- if (!value) {
132
- return 'Symbol is required';
133
- }
134
- if (value.length > 5) {
135
- return 'Symbol must be 5 characters or fewer';
136
- }
137
- if (!/^[A-Z0-9\- ]{1,5}$/.test(value)) {
138
- return 'Symbol must contain only uppercase letters, digits, hyphens, and spaces';
139
- }
140
- return null;
141
- }
142
-
143
- function validateHslColor(value) {
144
- if (!value) {
145
- return 'Color is required';
146
- }
147
- if (!/^hsl\(\d+(deg)? \d+% \d+%\)$/.test(value)) {
148
- return 'Color must be in HSL format, e.g. hsl(195 70% 40%)';
149
- }
150
- return null;
151
- }
152
-
153
- function validateUrl(value) {
154
- if (!value) {
155
- return 'URL is required';
156
- }
157
- if (!/^https?:\/\/[-.:a-zA-Z0-9]+$/.test(value)) {
158
- return 'Must be a valid URL like https://example.edu (no trailing slash or path)';
159
- }
160
- return null;
161
- }
162
-
163
- function validateBasePath(value) {
164
- if (!/^\/[-a-zA-Z0-9]*$/.test(value)) {
165
- return 'Must start with / and contain only letters, digits, and hyphens';
166
- }
167
- return null;
168
- }
169
-
170
- function createSiteConfig({
171
- title,
172
- symbol,
173
- themeColor,
174
- tintHue,
175
- tintAmount,
176
- defaultTimeZone,
177
- base,
178
- basePath,
179
- internalDomains,
180
- }) {
181
- return {
182
- title,
183
- symbol,
184
- features: { search: true, code: true, favicon: true },
185
- base,
186
- basePath,
187
- internalDomains,
188
- defaultTimeZone,
189
- codeLanguages: { java: 'java', py: 'python' },
190
- themeColor,
191
- tintHue: Number(tintHue),
192
- tintAmount: Number(tintAmount),
193
- vars: {},
194
- };
195
- }
196
-
197
- async function initCommand(args) {
198
- const dirname = args[0];
199
- const useDefaults = args[1] === '--default';
200
-
201
- if (!dirname) {
202
- console.error('Error: Provide a name for the new directory');
203
- console.log('Usage: tada init <dirname> [--default]');
204
- process.exit(1);
205
- }
206
-
207
- const projectDir = path.resolve(process.cwd(), dirname);
208
- if (fs.existsSync(projectDir)) {
209
- console.error(`Error: "${dirname}" already exists`);
210
- process.exit(1);
211
- }
212
-
213
- const rl = readline.createInterface({
214
- input: process.stdin,
215
- output: process.stdout,
216
- });
217
-
218
- let message = `Creating a new Tada site in ${projectDir}`;
219
- if (useDefaults) {
220
- console.log(message + ' using default config');
221
- } else {
222
- console.log(message);
223
- }
224
-
225
- const config = {};
226
-
227
- for (const [key, { prompt, defaultValue, validate }] of Object.entries(
228
- INIT_QUESTIONS,
229
- )) {
230
- if (useDefaults) {
231
- config[key] = defaultValue;
232
- } else {
233
- config[key] = await ask(rl, prompt, { defaultValue, validate });
234
- }
235
- }
236
-
237
- const {
238
- title,
239
- symbol,
240
- themeColor,
241
- tintHue,
242
- tintAmount,
243
- defaultTimeZone,
244
- prodBase,
245
- prodBasePath,
246
- } = config;
247
-
248
- rl.close();
249
-
250
- // Derive internal domain from production base URL
251
- const prodDomain = new URL(prodBase).hostname;
252
-
253
- // Create project directory
254
- fs.mkdirSync(path.join(projectDir, 'config'), { recursive: true });
255
-
256
- // Generate site configs
257
- const devConfig = createSiteConfig({
258
- title,
259
- symbol,
260
- themeColor,
261
- tintHue,
262
- tintAmount,
263
- defaultTimeZone,
264
- base: 'http://localhost:8080',
265
- basePath: '/',
266
- internalDomains: ['localhost'],
267
- });
268
-
269
- const prodConfig = createSiteConfig({
270
- title,
271
- symbol,
272
- themeColor,
273
- tintHue,
274
- tintAmount,
275
- defaultTimeZone,
276
- base: prodBase,
277
- basePath: prodBasePath,
278
- internalDomains: [prodDomain],
279
- });
280
-
281
- fs.writeFileSync(
282
- path.join(projectDir, 'config/site.dev.json'),
283
- JSON.stringify(devConfig, null, 2) + '\n',
284
- );
285
-
286
- fs.writeFileSync(
287
- path.join(projectDir, 'config/site.prod.json'),
288
- JSON.stringify(prodConfig, null, 2) + '\n',
289
- );
290
-
291
- // Copy nav and authors data files to the project's config directory
292
- fs.copyFileSync(
293
- path.join(packageDir, 'config/nav.json'),
294
- path.join(projectDir, 'config/nav.json'),
295
- );
296
- fs.copyFileSync(
297
- path.join(packageDir, 'config/authors.json'),
298
- path.join(projectDir, 'config/authors.json'),
299
- );
300
-
301
- // Copy content/ and public/ from the package
302
- copyDirRecursive(
303
- path.join(packageDir, 'content'),
304
- path.join(projectDir, 'content'),
305
- );
306
- copyDirRecursive(
307
- path.join(packageDir, 'public'),
308
- path.join(projectDir, 'public'),
309
- );
310
-
311
- console.log(`\nGenerated a new site in ${projectDir}`);
312
- console.log(`\nNext steps:`);
313
- console.log(` cd ${dirname}`);
314
- console.log(` tada dev`);
315
- console.log(` tada serve`);
316
- }
317
-
318
- // --- main ---
319
-
320
- const command = process.argv[2];
321
-
322
- switch (command) {
323
- case 'init':
324
- initCommand(process.argv.slice(3));
325
- break;
326
-
327
- case 'dev':
328
- run(
329
- `bun ${webpackCli} --config ${path.join(packageDir, 'webpack/config.dev.js')}`,
330
- );
331
- break;
332
-
333
- case 'prod':
334
- run(
335
- `bun ${webpackCli} --config ${path.join(packageDir, 'webpack/config.prod.js')}`,
336
- );
337
- break;
338
-
339
- case 'watch':
340
- run(`bun ${path.join(packageDir, 'webpack/watch.js')}`);
341
- break;
342
-
343
- case 'serve':
344
- run(`bun ${path.join(packageDir, 'webpack/serve.js')}`);
345
- break;
346
-
347
- case 'clean':
348
- fs.rmSync(path.resolve(process.cwd(), 'dist'), {
349
- recursive: true,
350
- force: true,
351
- });
352
- console.log('Cleaned dist/');
353
- break;
354
-
355
- default:
356
- printUsage();
357
- if (command && command !== '--help' && command !== '-h') {
358
- process.exit(1);
359
- }
360
- break;
361
- }
@@ -1 +0,0 @@
1
- { "alex": { "name": "Alex Breen", "avatar": "/avatars/alex.jpg" } }
package/config/nav.json DELETED
@@ -1,28 +0,0 @@
1
- [
2
- {
3
- "title": "Navigation",
4
- "links": [{ "text": "Home", "internal": "/index.html" }]
5
- },
6
- {
7
- "title": "Topics",
8
- "links": [
9
- { "text": "Lectures", "internal": "/lectures/index.html" },
10
- {
11
- "text": "Problem Sets",
12
- "internal": "/problem_sets/index.html",
13
- "disabled": true
14
- }
15
- ]
16
- },
17
- {
18
- "title": "Links",
19
- "links": [
20
- { "text": "Zoom", "external": "https://zoom.com" },
21
- {
22
- "text": "Canvas",
23
- "external": "https://www.instructure.com/",
24
- "disabled": true
25
- }
26
- ]
27
- }
28
- ]
@@ -1,6 +0,0 @@
1
- title: Problem Sets
2
- author: alex
3
- description: An example page for <%= site.title %> which is not generated.
4
- skip: true
5
-
6
- This page is not generated during the build because `skip` is set to `true`.
package/types/dev.ts DELETED
@@ -1,3 +0,0 @@
1
- interface Window {
2
- IS_DEV: boolean | undefined;
3
- }