@abreen/tada 1.13.4 → 1.13.9

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.
@@ -8,6 +8,7 @@ import {
8
8
  updateProjectScan,
9
9
  } from '../source-model';
10
10
  import type { Asset, HtmlOutputAnalysis, SiteVariables } from '../types';
11
+ import type { TadaBuildMeta } from './compiler-types';
11
12
 
12
13
  export interface TadaSourceRecord {
13
14
  sourcePath: string;
@@ -21,28 +22,24 @@ export interface TadaSourceRecord {
21
22
  authorKey?: string;
22
23
  }
23
24
 
25
+ export interface TadaOutputOwner {
26
+ kind: 'content' | 'public';
27
+ sourcePath: string;
28
+ }
29
+
24
30
  export interface TadaSnapshot {
25
31
  siteVariables: SiteVariables;
26
32
  assetFiles: string[];
27
33
  navData: unknown;
28
34
  authorsData: unknown;
29
- processedExts: Set<string>;
30
35
  contentRecords: Map<string, TadaSourceRecord>;
31
36
  publicRecords: Map<string, TadaSourceRecord>;
32
- outputOwners: Map<string, { kind: 'content' | 'public'; sourcePath: string }>;
37
+ outputOwners: Map<string, TadaOutputOwner>;
33
38
  reversePartialDeps: Map<string, Set<string>>;
34
39
  reverseTraceDeps: Map<string, Set<string>>;
35
40
  reverseInternalTargetDeps: Map<string, Set<string>>;
36
41
  reverseAuthorDeps: Map<string, Set<string>>;
37
- contentFiles: Set<string>;
38
- buildContentFiles: Set<string>;
39
- publicFiles: Set<string>;
40
- literateJavaOutputPaths: Set<string>;
41
- contentOwners: Map<string, string>;
42
- publicOwners: Map<string, string>;
43
- sourceOutputPaths: Map<string, Set<string>>;
44
- sourceTargetPaths: Map<string, Set<string>>;
45
- validTargets: Set<string>;
42
+ scan: TadaProjectScan;
46
43
  }
47
44
 
48
45
  function buildReverseMap(
@@ -63,22 +60,11 @@ function buildReverseMap(
63
60
  return reverse;
64
61
  }
65
62
 
66
- function cloneSetMap(
67
- source: Map<string, Set<string>>,
68
- ): Map<string, Set<string>> {
69
- return new Map(
70
- [...source.entries()].map(([key, values]) => [key, new Set(values)]),
71
- );
72
- }
73
-
74
63
  export function collectOutputOwners(snapshot: {
75
64
  contentRecords: Map<string, TadaSourceRecord>;
76
65
  publicRecords: Map<string, TadaSourceRecord>;
77
- }): Map<string, { kind: 'content' | 'public'; sourcePath: string }> {
78
- const owners = new Map<
79
- string,
80
- { kind: 'content' | 'public'; sourcePath: string }
81
- >();
66
+ }): Map<string, TadaOutputOwner> {
67
+ const owners = new Map<string, TadaOutputOwner>();
82
68
 
83
69
  for (const record of snapshot.contentRecords.values()) {
84
70
  for (const outputPath of record.outputs.keys()) {
@@ -116,23 +102,12 @@ export function createSnapshot({
116
102
  publicRecords: Map<string, TadaSourceRecord>;
117
103
  }): TadaSnapshot {
118
104
  const allRecords = [...contentRecords.values()];
119
- const reverseAuthorDeps = new Map<string, Set<string>>();
120
- for (const record of allRecords) {
121
- if (!record.authorKey) {
122
- continue;
123
- }
124
- if (!reverseAuthorDeps.has(record.authorKey)) {
125
- reverseAuthorDeps.set(record.authorKey, new Set());
126
- }
127
- reverseAuthorDeps.get(record.authorKey)!.add(record.sourcePath);
128
- }
129
105
 
130
106
  return {
131
107
  siteVariables,
132
108
  assetFiles,
133
109
  navData,
134
110
  authorsData,
135
- processedExts: scan.processedExts,
136
111
  contentRecords,
137
112
  publicRecords,
138
113
  outputOwners: collectOutputOwners({ contentRecords, publicRecords }),
@@ -145,16 +120,10 @@ export function createSnapshot({
145
120
  allRecords,
146
121
  record => record.internalTargets,
147
122
  ),
148
- reverseAuthorDeps,
149
- contentFiles: new Set(scan.contentFiles),
150
- buildContentFiles: new Set(scan.buildContentFiles),
151
- publicFiles: new Set(scan.publicFiles),
152
- literateJavaOutputPaths: new Set(scan.literateJavaOutputPaths),
153
- contentOwners: new Map(scan.contentOwners),
154
- publicOwners: new Map(scan.publicOwners),
155
- sourceOutputPaths: cloneSetMap(scan.sourceOutputPaths),
156
- sourceTargetPaths: cloneSetMap(scan.sourceTargetPaths),
157
- validTargets: new Set(scan.validTargets),
123
+ reverseAuthorDeps: buildReverseMap(allRecords, record =>
124
+ record.authorKey ? [record.authorKey] : [],
125
+ ),
126
+ scan,
158
127
  };
159
128
  }
160
129
 
@@ -214,14 +183,24 @@ export function collectHtmlAnalysisByPath(
214
183
  ): Map<string, HtmlOutputAnalysis> {
215
184
  const htmlAnalysisByPath = new Map<string, HtmlOutputAnalysis>();
216
185
  for (const record of contentRecords.values()) {
217
- for (const [outputPath, analysis] of record.htmlAnalysisByOutputPath ||
218
- new Map<string, HtmlOutputAnalysis>()) {
186
+ if (!record.htmlAnalysisByOutputPath) {
187
+ continue;
188
+ }
189
+ for (const [outputPath, analysis] of record.htmlAnalysisByOutputPath) {
219
190
  htmlAnalysisByPath.set(outputPath, cloneHtmlOutputAnalysis(analysis));
220
191
  }
221
192
  }
222
193
  return htmlAnalysisByPath;
223
194
  }
224
195
 
196
+ export function createBuildMeta(snapshot: TadaSnapshot): TadaBuildMeta {
197
+ return {
198
+ htmlAssetsByPath: collectHtmlAssetsByPath(snapshot.contentRecords),
199
+ htmlAnalysisByPath: collectHtmlAnalysisByPath(snapshot.contentRecords),
200
+ siteVariables: snapshot.siteVariables,
201
+ };
202
+ }
203
+
225
204
  export function normalizeInternalTarget(target: string): string {
226
205
  return normalizeOutputPath(target);
227
206
  }
@@ -0,0 +1,84 @@
1
+ import type { ChokidarOptions } from 'chokidar';
2
+ import type { TadaBuildMeta } from './compiler-types';
3
+ import type { TadaSnapshot } from './snapshot';
4
+
5
+ export type WatchEventKind = 'add' | 'change' | 'unlink';
6
+
7
+ export interface FileChange {
8
+ path: string;
9
+ kind: WatchEventKind;
10
+ }
11
+
12
+ export interface ChangeBatch {
13
+ changes: FileChange[];
14
+ }
15
+
16
+ export interface WatchTarget {
17
+ path: string;
18
+ chokidar?: ChokidarOptions;
19
+ filter?: (filePath: string) => boolean;
20
+ }
21
+
22
+ export interface WatchDiagnostic {
23
+ message: string;
24
+ }
25
+
26
+ export interface WriteFileMutation {
27
+ kind: 'write';
28
+ path: string;
29
+ content: string | Buffer;
30
+ }
31
+
32
+ export interface DeleteFileMutation {
33
+ kind: 'delete';
34
+ path: string;
35
+ }
36
+
37
+ export type FileMutation = WriteFileMutation | DeleteFileMutation;
38
+
39
+ export interface ReplaceRootCommitPlan {
40
+ kind: 'replace-root';
41
+ stagedPath: string;
42
+ targetPath: string;
43
+ }
44
+
45
+ export interface ApplyMutationsCommitPlan {
46
+ kind: 'apply-mutations';
47
+ rootDir: string;
48
+ mutations: FileMutation[];
49
+ }
50
+
51
+ export type CommitPlan = ReplaceRootCommitPlan | ApplyMutationsCommitPlan;
52
+
53
+ export type CompilerBuildResult =
54
+ | {
55
+ ok: true;
56
+ snapshot: TadaSnapshot;
57
+ commit: CommitPlan;
58
+ meta: TadaBuildMeta;
59
+ }
60
+ | { ok: false; diagnostics: WatchDiagnostic[] };
61
+
62
+ export interface WatchCompiler {
63
+ getWatchTargets(): WatchTarget[];
64
+ build(
65
+ snapshot: TadaSnapshot | undefined,
66
+ batch?: ChangeBatch,
67
+ ): Promise<CompilerBuildResult>;
68
+ }
69
+
70
+ export type WatchLifecycleEvent =
71
+ | { kind: 'watching' }
72
+ | { kind: 'build-started'; batch?: ChangeBatch }
73
+ | { kind: 'build-succeeded'; batch?: ChangeBatch; meta: TadaBuildMeta }
74
+ | {
75
+ kind: 'build-failed';
76
+ batch?: ChangeBatch;
77
+ diagnostics: WatchDiagnostic[];
78
+ };
79
+
80
+ export interface WatchEngineOptions {
81
+ compiler: WatchCompiler;
82
+ debounceMs?: number;
83
+ onEvent?: (event: WatchLifecycleEvent) => void | Promise<void>;
84
+ }
@@ -2,7 +2,7 @@ import { B } from '../colors';
2
2
  import { makeLogger } from '../log';
3
3
  import { config, getConfigFileName } from '../templates';
4
4
  import { validateConfigLinks } from '../validate-config-links';
5
- import type { WatchDiagnostic } from '../../watch/types';
5
+ import type { WatchDiagnostic } from './types';
6
6
  import { assertNoOutputPathConflicts, type TadaProjectScan } from './snapshot';
7
7
 
8
8
  const log = makeLogger(import.meta.url);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abreen/tada",
3
- "version": "1.13.4",
3
+ "version": "1.13.9",
4
4
  "type": "module",
5
5
  "description": "A static site generator",
6
6
  "license": "MIT",
@@ -15,7 +15,6 @@
15
15
  "files": [
16
16
  "bin/",
17
17
  "build/",
18
- "watch/",
19
18
  "src/",
20
19
  "templates/",
21
20
  "fonts/",
package/src/_base.scss CHANGED
@@ -38,12 +38,13 @@ body {
38
38
  * Enable Inter's features:
39
39
  * liga - standard ligatures
40
40
  * calt - contextual alternates
41
- * ss03 - round quotes & commas (legibility)
42
- * cv01 - alternate 1 (style)
43
- * cv05 - lower case L with tail (legibility)
44
- * cv10 - capital G with spur (style)
41
+ * ss07 - square punctuation
42
+ * ss08 - square quotes
43
+ * cv01 - alternate 1
44
+ * cv08 - uppercase I with serif
45
+ * cv10 - capital G with spur
45
46
  */
46
- font-feature-settings: 'liga', 'calt', 'ss03', 'cv01', 'cv05', 'cv10';
47
+ font-feature-settings: 'liga', 'calt', 'ss07', 'ss08', 'cv01', 'cv08', 'cv10';
47
48
  line-height: var(--line-height);
48
49
  color: var(--fg-color);
49
50
  background-color: var(--bg-color);
@@ -9,6 +9,8 @@ interface SlidesPresentDetail {
9
9
  slideIndex?: number;
10
10
  }
11
11
 
12
+ const FULLSCREEN_STORAGE_KEY = 'slidesFullscreen';
13
+
12
14
  function isInteractive(
13
15
  view: Window & typeof globalThis,
14
16
  target: EventTarget | null,
@@ -83,6 +85,16 @@ export default function mountSlides(window: Window): void | (() => void) {
83
85
  presentButton.disabled = false;
84
86
  }
85
87
  if (fullscreenCheckbox) {
88
+ try {
89
+ const stored = window.localStorage.getItem(FULLSCREEN_STORAGE_KEY);
90
+ if (stored === 'false') {
91
+ fullscreenCheckbox.checked = false;
92
+ } else if (stored === 'true') {
93
+ fullscreenCheckbox.checked = true;
94
+ }
95
+ } catch {
96
+ // ignored
97
+ }
86
98
  fullscreenCheckbox.disabled = false;
87
99
  }
88
100
 
@@ -365,6 +377,20 @@ export default function mountSlides(window: Window): void | (() => void) {
365
377
  exitPresentation();
366
378
  }
367
379
 
380
+ function handleFullscreenPreferenceChange(): void {
381
+ if (!fullscreenCheckbox) {
382
+ return;
383
+ }
384
+ try {
385
+ window.localStorage.setItem(
386
+ FULLSCREEN_STORAGE_KEY,
387
+ String(fullscreenCheckbox.checked),
388
+ );
389
+ } catch {
390
+ // Ignore storage errors so the checkbox remains usable.
391
+ }
392
+ }
393
+
368
394
  function handleFullscreenChange(): void {
369
395
  if (
370
396
  isPresenting &&
@@ -460,6 +486,10 @@ export default function mountSlides(window: Window): void | (() => void) {
460
486
  }
461
487
 
462
488
  presentButton?.addEventListener('click', handlePresentClick);
489
+ fullscreenCheckbox?.addEventListener(
490
+ 'change',
491
+ handleFullscreenPreferenceChange,
492
+ );
463
493
  closeButton.addEventListener('click', handleCloseClick);
464
494
  document.addEventListener('fullscreenchange', handleFullscreenChange);
465
495
  window.addEventListener('keydown', handleKeydown);
@@ -470,6 +500,10 @@ export default function mountSlides(window: Window): void | (() => void) {
470
500
  return () => {
471
501
  exitPresentation();
472
502
  presentButton?.removeEventListener('click', handlePresentClick);
503
+ fullscreenCheckbox?.removeEventListener(
504
+ 'change',
505
+ handleFullscreenPreferenceChange,
506
+ );
473
507
  closeButton.removeEventListener('click', handleCloseClick);
474
508
  document.removeEventListener('fullscreenchange', handleFullscreenChange);
475
509
  window.removeEventListener('keydown', handleKeydown);
package/src/style.scss CHANGED
@@ -121,7 +121,7 @@ a.footnote-ref {
121
121
 
122
122
  a.footnote-ref,
123
123
  .footnote-marker {
124
- font-feature-settings: 'ss06';
124
+ font-feature-settings: 'ss06'; // squared numbers
125
125
  }
126
126
 
127
127
  .footnote-item {
@@ -58,13 +58,20 @@ async function goToStep(
58
58
  }
59
59
 
60
60
  function updateSourceHighlight(panel: HTMLElement, currentLine: number): void {
61
- panel.querySelectorAll('.line-number').forEach(ln => {
62
- ln.classList.remove('trace-line-active');
63
- });
64
- const el = panel.querySelector(`.line-number[data-line="${currentLine}"]`);
65
- if (el) {
66
- el.classList.add('trace-line-active');
67
- const elRect = el.getBoundingClientRect();
61
+ panel
62
+ .querySelectorAll(
63
+ '.code-row.trace-line-active, .line-number.trace-line-active',
64
+ )
65
+ .forEach(el => {
66
+ el.classList.remove('trace-line-active');
67
+ });
68
+ const lineNumber = panel.querySelector(
69
+ `.line-number[data-line="${currentLine}"]`,
70
+ );
71
+ const row = lineNumber?.closest('.code-row') as HTMLElement | null;
72
+ if (row) {
73
+ row.classList.add('trace-line-active');
74
+ const elRect = row.getBoundingClientRect();
68
75
  const panelRect = panel.getBoundingClientRect();
69
76
  panel.scrollTop += elRect.top - panelRect.top - panel.clientHeight / 2;
70
77
  }
@@ -112,16 +112,17 @@
112
112
  }
113
113
  }
114
114
 
115
- .line-number.trace-line-active {
116
- font-weight: bold;
117
- color: var(--fg-color);
118
-
119
- &::before {
120
- position: absolute;
121
- left: 0;
122
- margin-right: 0.25em;
123
- line-height: 1.3;
124
- content: '\25B6';
115
+ .code-row.trace-line-active {
116
+ background: var(--bg-trace-line-active);
117
+
118
+ .line-number {
119
+ font-weight: bold;
120
+ color: var(--fg-color);
121
+ background: var(--bg-trace-line-active);
122
+ }
123
+
124
+ code {
125
+ background: none;
125
126
  }
126
127
  }
127
128
  }
@@ -32,6 +32,7 @@
32
32
  --bg2-color-translucent: hsl(
33
33
  var(--tint-hue) calc(12% * var(--tint-amount)) 92% / 66%
34
34
  );
35
+ --bg-trace-line-active: <%= bgTraceLineActive %>;
35
36
  --box-shadow: 0 2px 9px
36
37
  hsl(var(--tint-hue) calc(85% * var(--tint-amount)) 10% / 25%);
37
38
  --backdrop-filter: blur(10px);
@@ -87,6 +88,7 @@
87
88
  --bg2-color-translucent: hsl(
88
89
  var(--tint-hue) calc(12% * var(--tint-amount)) 12% / 66%
89
90
  );
91
+ --bg-trace-line-active: <%= bgTraceLineActiveDark %>;
90
92
  --box-shadow: 0 3px 8px
91
93
  hsl(var(--tint-hue) calc(33% * var(--tint-amount)) 8% / 70%);
92
94
  --link-active-color: #fff;
@@ -7,7 +7,7 @@
7
7
  <div class="slides-header" data-pagefind-ignore>
8
8
  <button type="button" data-slides-present disabled>Present</button>
9
9
  <label>
10
- <input type="checkbox" data-slides-fullscreen checked disabled>
10
+ <input id="slides-fullscreen" type="checkbox" data-slides-fullscreen checked disabled>
11
11
  Full screen
12
12
  </label>
13
13
  </div>