@abreen/tada 1.16.5 → 1.16.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -216,6 +216,11 @@ Example site configuration YAML file:
216
216
  ```yaml
217
217
  title: Intro to Computer Science
218
218
  titlePostfix: " - CS 0"
219
+ banner: |
220
+ The semester is now closed. Final grades will become available on
221
+ [Gradescope](https://example.org/course/12345) on December 30.
222
+
223
+ This site is an archive. This is how the site appeared on December 27, 2020.
219
224
  symbol: CS 0
220
225
  themeColor: hsl(351 70% 40%)
221
226
  tintAmount: 0
@@ -238,6 +243,7 @@ fontOverrides:
238
243
  features:
239
244
  search: true
240
245
  favicon: true
246
+ pickers: true
241
247
  base: https://example.edu
242
248
  basePath: /cs0
243
249
  internalDomains:
@@ -257,6 +263,7 @@ vars:
257
263
  |-------|-------------|
258
264
  | `title` | Title for the whole site (also used to derive `titlePostfix`) |
259
265
  | `titlePostfix` | *Optional*, the string to append to each page's `title` |
266
+ | `banner` | *Optional*, Markdown rendered in a bordered box above every page |
260
267
  | `symbol` | Text (1-5 chars) displayed in header (also used as the favicon symbol) |
261
268
  | `themeColor` | Theme color for the site (e.g., `"tomato"`, `"#c04040"`, `"hsl(195 70% 40%)"`) |
262
269
  | `tintHue` | *Optional*, hue (0-360) for background and foreground tinting (default `20`) |
@@ -268,6 +275,7 @@ vars:
268
275
  | `features.search` | Enable search UI and Pagefind index generation |
269
276
  | `features.favicon` | Enable automatically generated favicons |
270
277
  | `features.footer` | Show the Tada footer at the bottom of every page |
278
+ | `features.pickers` | Show the font and contrast pickers at the bottom of every page (default `true`) |
271
279
  | `base` | Full base URL of the deployed site, used for metadata and URL generation |
272
280
  | `basePath` | URL prefix for deployment under a subpath (e.g., `"/cs101"`), use `"/"` at root |
273
281
  | `internalDomains` | Domain names treated as internal by link processing (not marked external) |
package/bin/tada.ts CHANGED
@@ -340,7 +340,7 @@ async function initCommand(args: string[]): Promise<void> {
340
340
  base: 'http://localhost:8080',
341
341
  basePath: '/',
342
342
  internalDomains: ['localhost'],
343
- features: { search: true, favicon: false, footer: true },
343
+ features: { search: true, favicon: false, footer: true, pickers: true },
344
344
  extensionToShikiLanguage: bare ? {} : { java: 'java', py: 'python' },
345
345
  shikiLanguages: bare ? [] : ['java', 'python', 'html'],
346
346
  });
@@ -355,7 +355,7 @@ async function initCommand(args: string[]): Promise<void> {
355
355
  base: prodBase,
356
356
  basePath: prodBasePath,
357
357
  internalDomains: [prodDomain],
358
- features: { search: true, favicon: true, footer: true },
358
+ features: { search: true, favicon: true, footer: true, pickers: true },
359
359
  extensionToShikiLanguage: bare ? {} : { java: 'java', py: 'python' },
360
360
  shikiLanguages: bare ? [] : ['java', 'python', 'html'],
361
361
  });
package/bin/validators.ts CHANGED
@@ -49,8 +49,8 @@ export function validateUrl(value: string): string | null {
49
49
  }
50
50
 
51
51
  export function validateBasePath(value: string): string | null {
52
- if (!/^\/[-a-zA-Z0-9]*$/.test(value)) {
53
- return 'Must start with / and contain only letters, digits, and hyphens';
52
+ if (!/^\/(?:[-a-zA-Z0-9]+(?:\/[-a-zA-Z0-9]+)*)?$/.test(value)) {
53
+ return 'Must start with / and contain path segments made of letters, digits, and hyphens';
54
54
  }
55
55
  return null;
56
56
  }
@@ -14,7 +14,7 @@ const DEFAULT: Partial<SiteVariables> = {
14
14
  basePath: '/',
15
15
  defaultFont: 'sans',
16
16
  defaultContrast: 'standard',
17
- features: { search: true, favicon: true, footer: true },
17
+ features: { search: true, favicon: true, footer: true, pickers: true },
18
18
  };
19
19
 
20
20
  const isValid = compileJsonSchema(siteSchema);
package/build/types.d.ts CHANGED
@@ -37,6 +37,7 @@ export interface SiteVariables {
37
37
  basePath: string;
38
38
  title: string;
39
39
  titlePostfix: string;
40
+ banner?: string;
40
41
  symbol?: string;
41
42
  faviconSymbol?: string;
42
43
  themeColor: string;
@@ -62,6 +63,7 @@ export type FeatureConfig = {
62
63
  search: boolean;
63
64
  favicon: boolean;
64
65
  footer: boolean;
66
+ pickers: boolean;
65
67
  };
66
68
 
67
69
  /** A rendered content asset ready to write to dist/ */
@@ -90,6 +90,7 @@ interface TemplateParametersInput {
90
90
  content: string | null;
91
91
  subPath: string;
92
92
  isWatchMode: boolean;
93
+ bannerHtml?: string;
93
94
  }
94
95
 
95
96
  function resolveAuthor(
@@ -141,6 +142,7 @@ function createTemplateParameters({
141
142
  content,
142
143
  subPath,
143
144
  isWatchMode,
145
+ bannerHtml,
144
146
  }: TemplateParametersInput): Record<string, unknown> {
145
147
  const applyBasePath = createApplyBasePath(siteVariables);
146
148
  return {
@@ -149,12 +151,21 @@ function createTemplateParameters({
149
151
  site: siteVariables,
150
152
  page: pageVariables,
151
153
  content,
154
+ bannerHtml,
152
155
  isWatchMode,
153
156
  speculationRulesHrefMatches: `${applyBasePath('/')}*`,
154
157
  tadaVersion,
155
158
  };
156
159
  }
157
160
 
161
+ function renderSiteBanner(siteVariables: SiteVariables): string {
162
+ if (!siteVariables.banner) {
163
+ return '';
164
+ }
165
+
166
+ return createMarkdown(siteVariables).render(siteVariables.banner);
167
+ }
168
+
158
169
  export function injectAssetTags(
159
170
  html: string,
160
171
  assetFiles: string[],
@@ -290,6 +301,7 @@ export function renderPlainTextPageAsset({
290
301
  content,
291
302
  subPath,
292
303
  isWatchMode: watchMode,
304
+ bannerHtml: renderSiteBanner(siteVariables),
293
305
  });
294
306
 
295
307
  const templateHtml = render(
@@ -371,6 +383,7 @@ export function renderCodePageAsset({
371
383
  content,
372
384
  subPath,
373
385
  isWatchMode: isWatchMode(assetFiles),
386
+ bannerHtml: renderSiteBanner(siteVariables),
374
387
  });
375
388
 
376
389
  const templateHtml = render('code.html', templateParameters) as string;
@@ -714,6 +727,7 @@ export function renderLiterateJavaPageAsset({
714
727
  content: contentHtml,
715
728
  subPath,
716
729
  isWatchMode: isWatchMode(assetFiles),
730
+ bannerHtml: renderSiteBanner(siteVariables),
717
731
  });
718
732
 
719
733
  const templateHtml = render('literate.html', templateParameters) as string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abreen/tada",
3
- "version": "1.16.5",
3
+ "version": "1.16.7",
4
4
  "type": "module",
5
5
  "description": "A static site generator",
6
6
  "license": "MIT",
@@ -4,11 +4,17 @@
4
4
  "properties": {
5
5
  "features": {
6
6
  "type": "object",
7
- "default": { "search": true, "favicon": true, "footer": true },
7
+ "default": {
8
+ "search": true,
9
+ "favicon": true,
10
+ "footer": true,
11
+ "pickers": true
12
+ },
8
13
  "properties": {
9
14
  "search": { "type": "boolean", "default": true },
10
15
  "favicon": { "type": "boolean", "default": true },
11
- "footer": { "type": "boolean", "default": true }
16
+ "footer": { "type": "boolean", "default": true },
17
+ "pickers": { "type": "boolean", "default": true }
12
18
  },
13
19
  "additionalProperties": false
14
20
  },
@@ -16,7 +22,7 @@
16
22
  "basePath": {
17
23
  "type": "string",
18
24
  "default": "/",
19
- "pattern": "^\\/[-a-zA-Z0-9]*?$"
25
+ "pattern": "^\\/(?:[-a-zA-Z0-9]+(?:\\/[-a-zA-Z0-9]+)*)?$"
20
26
  },
21
27
  "internalDomains": {
22
28
  "type": "array",
@@ -24,6 +30,7 @@
24
30
  "items": { "type": "string", "pattern": "^[-.:a-zA-Z0-9]+$" }
25
31
  },
26
32
  "title": { "type": "string" },
33
+ "banner": { "type": "string" },
27
34
  "symbol": { "type": "string", "pattern": "^[A-Z0-9\\- ]{1,5}$" },
28
35
  "titlePostfix": { "type": "string" },
29
36
  "themeColor": { "type": "string" },
package/src/_alerts.scss CHANGED
@@ -102,3 +102,10 @@ i.warning {
102
102
  .alert > p:last-child {
103
103
  margin-bottom: 0;
104
104
  }
105
+
106
+ .site-banner {
107
+ color: var(--fg2-color);
108
+ background: none;
109
+ border: 1px solid var(--fg2-color);
110
+ border-radius: var(--border-radius);
111
+ }
@@ -42,6 +42,23 @@ $duration: 150ms;
42
42
  animation-duration: $duration;
43
43
  }
44
44
 
45
+ // The site-wide banner does not change between pages. Keep it out of the root
46
+ // slide and pin its named transition layer in place without a crossfade.
47
+ .site-banner {
48
+ view-transition-name: site-banner;
49
+ }
50
+
51
+ ::view-transition-group(site-banner),
52
+ ::view-transition-old(site-banner),
53
+ ::view-transition-new(site-banner) {
54
+ animation: none;
55
+ }
56
+
57
+ ::view-transition-old(site-banner),
58
+ ::view-transition-new(site-banner) {
59
+ mix-blend-mode: normal;
60
+ }
61
+
45
62
  // Forward navigation: old content slides left, new content slides in from right
46
63
  .nav-forward::view-transition-old(root) {
47
64
  animation: slide-out-left $duration ease-in;
@@ -598,6 +598,7 @@ export default (window: Window) => {
598
598
  window.addEventListener('pointermove', handleWindowPointerMove);
599
599
  window.addEventListener('keydown', handleWindowKeyDown);
600
600
  window.addEventListener(PAGE_UPDATE_REFRESH_EVENT, handlePageUpdateRefresh);
601
+ input.disabled = false;
601
602
 
602
603
  return () => {
603
604
  window.removeEventListener(
@@ -1,3 +1,4 @@
1
+ <% if (site.features.pickers) { %>
1
2
  <div class="appearance-pickers" data-pagefind-ignore>
2
3
  <div class="preference-picker font-picker" role="group" aria-label="Font style">
3
4
  <button type="button" class="preference-picker-option font-picker-option-sans" data-font-preference-value="sans" aria-label="Use sans-serif fonts" aria-pressed="<%= site.defaultFont === 'sans' %>" disabled>
@@ -16,3 +17,4 @@
16
17
  </button>
17
18
  </div>
18
19
  </div>
20
+ <% } %>
@@ -117,7 +117,6 @@
117
117
  aria-expanded="false"
118
118
  aria-controls="quick-search-results"
119
119
  disabled />
120
- <script>document.currentScript.previousElementSibling.disabled=false;</script>
121
120
  </div>
122
121
  <% } %>
123
122
  </div>
@@ -137,3 +136,8 @@
137
136
  </details>
138
137
  </header>
139
138
  <div class="container">
139
+ <% if (site.banner) { %>
140
+ <aside class="site-banner alert" data-pagefind-ignore>
141
+ <div class="content"><%= bannerHtml %></div>
142
+ </aside>
143
+ <% } %>