@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
@@ -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
  }
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
  }
@@ -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
+ }
@@ -0,0 +1,40 @@
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 'jsdom' {
26
+ export class JSDOM {
27
+ constructor(html?: string | Buffer, options?: Record<string, unknown>);
28
+ static fragment(html: string): DocumentFragment;
29
+ readonly window: DOMWindow;
30
+ }
31
+
32
+ interface DOMWindow extends Window {
33
+ close(): void;
34
+ }
35
+ }
36
+
37
+ declare module 'java-parser' {
38
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
+ export function parse(source: string): any;
40
+ }
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,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`.
@@ -1,97 +0,0 @@
1
- const watchStateByCompiler = new WeakMap();
2
- const buildDeltaByCompiler = new WeakMap();
3
-
4
- function cloneSet(values) {
5
- return new Set(values || []);
6
- }
7
-
8
- function getEmptyBuildDelta() {
9
- return {
10
- changedSourceFiles: new Set(),
11
- changedHtmlAssetPaths: new Set(),
12
- removedHtmlAssetPaths: new Set(),
13
- templatesChanged: false,
14
- };
15
- }
16
-
17
- function setWatchState(compiler, watchState) {
18
- watchStateByCompiler.set(compiler, {
19
- contentFiles: cloneSet(watchState?.contentFiles),
20
- buildContentFiles: cloneSet(watchState?.buildContentFiles),
21
- changedContentFiles: cloneSet(watchState?.changedContentFiles),
22
- templatesChanged: Boolean(watchState?.templatesChanged),
23
- structureChanged: Boolean(watchState?.structureChanged),
24
- });
25
- }
26
-
27
- function getWatchState(compiler) {
28
- const state = watchStateByCompiler.get(compiler);
29
- if (!state) {
30
- return null;
31
- }
32
-
33
- return {
34
- contentFiles: cloneSet(state.contentFiles),
35
- buildContentFiles: cloneSet(state.buildContentFiles),
36
- changedContentFiles: cloneSet(state.changedContentFiles),
37
- templatesChanged: state.templatesChanged,
38
- structureChanged: state.structureChanged,
39
- };
40
- }
41
-
42
- function setBuildDelta(compiler, buildDelta) {
43
- buildDeltaByCompiler.set(compiler, {
44
- changedSourceFiles: cloneSet(buildDelta?.changedSourceFiles),
45
- changedHtmlAssetPaths: cloneSet(buildDelta?.changedHtmlAssetPaths),
46
- removedHtmlAssetPaths: cloneSet(buildDelta?.removedHtmlAssetPaths),
47
- templatesChanged: Boolean(buildDelta?.templatesChanged),
48
- });
49
- }
50
-
51
- function mergeIntoSet(target, values) {
52
- if (!values) {
53
- return;
54
- }
55
-
56
- for (const value of values) {
57
- target.add(value);
58
- }
59
- }
60
-
61
- function updateBuildDelta(compiler, buildDelta) {
62
- const current = getBuildDelta(compiler);
63
- mergeIntoSet(current.changedSourceFiles, buildDelta?.changedSourceFiles);
64
- mergeIntoSet(
65
- current.changedHtmlAssetPaths,
66
- buildDelta?.changedHtmlAssetPaths,
67
- );
68
- mergeIntoSet(
69
- current.removedHtmlAssetPaths,
70
- buildDelta?.removedHtmlAssetPaths,
71
- );
72
- current.templatesChanged =
73
- current.templatesChanged || Boolean(buildDelta?.templatesChanged);
74
- buildDeltaByCompiler.set(compiler, current);
75
- }
76
-
77
- function getBuildDelta(compiler) {
78
- const delta = buildDeltaByCompiler.get(compiler);
79
- if (!delta) {
80
- return getEmptyBuildDelta();
81
- }
82
-
83
- return {
84
- changedSourceFiles: cloneSet(delta.changedSourceFiles),
85
- changedHtmlAssetPaths: cloneSet(delta.changedHtmlAssetPaths),
86
- removedHtmlAssetPaths: cloneSet(delta.removedHtmlAssetPaths),
87
- templatesChanged: delta.templatesChanged,
88
- };
89
- }
90
-
91
- module.exports = {
92
- getBuildDelta,
93
- getWatchState,
94
- setBuildDelta,
95
- setWatchState,
96
- updateBuildDelta,
97
- };
package/webpack/colors.js DELETED
@@ -1,15 +0,0 @@
1
- const util = require('util');
2
-
3
- const R = (str, ...args) => util.styleText(['red'], String.raw(str, ...args));
4
- const G = (str, ...args) => util.styleText(['green'], String.raw(str, ...args));
5
- const B = (str, ...args) => util.styleText(['blue'], String.raw(str, ...args));
6
- const Y = (str, ...args) =>
7
- util.styleText(['yellow'], String.raw(str, ...args));
8
- const L = (str, ...args) =>
9
- util.styleText(['blackBright'], String.raw(str, ...args));
10
- const P = (str, ...args) =>
11
- util.styleText(['magenta'], String.raw(str, ...args));
12
- const I = (str, ...args) =>
13
- util.styleText(['italic', 'bold'], String.raw(str, ...args));
14
-
15
- module.exports = { R, G, B, Y, L, P, I };