@abreen/tada 1.13.7 → 1.14.1

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 (49) hide show
  1. package/README.md +46 -0
  2. package/build/generate-content-assets.ts +39 -185
  3. package/build/pipeline.ts +1 -1
  4. package/build/source-model.ts +2 -14
  5. package/build/types.d.ts +18 -18
  6. package/build/utils/html-comments.ts +3 -0
  7. package/build/utils/jdi-runner/TraceRunner.java +227 -38
  8. package/build/utils/markdown-partials.ts +235 -0
  9. package/build/utils/markdown.ts +12 -1
  10. package/build/utils/python-runner/trace_runner.py +58 -13
  11. package/build/utils/render.ts +14 -12
  12. package/build/utils/trace-core.ts +136 -14
  13. package/build/utils/trace-heap.ts +34 -0
  14. package/build/utils/trace-java.ts +60 -7
  15. package/build/utils/trace-layout.ts +58 -13
  16. package/build/utils/trace-python.ts +6 -2
  17. package/build/utils/trace-svg.ts +54 -60
  18. package/build/utils/trace.ts +193 -78
  19. package/build/watch/build-full.ts +26 -52
  20. package/build/watch/build-helpers.ts +66 -0
  21. package/build/watch/build-incremental.ts +32 -60
  22. package/build/watch/build-result.ts +19 -0
  23. package/build/watch/compiler-types.d.ts +2 -2
  24. package/build/watch/compiler.ts +30 -62
  25. package/build/watch/config-paths.ts +56 -0
  26. package/build/watch/engine.ts +199 -0
  27. package/{watch → build/watch}/fs-commit.ts +1 -3
  28. package/build/watch/index.ts +1 -1
  29. package/build/watch/mutations.ts +23 -25
  30. package/build/watch/planner.ts +14 -78
  31. package/build/watch/runtime.ts +26 -38
  32. package/build/watch/snapshot.ts +26 -47
  33. package/build/watch/types.d.ts +84 -0
  34. package/build/watch/validation.ts +1 -1
  35. package/init/content/lectures/02/_pr1.md +1 -1
  36. package/init/content/lectures/02/index.md +2 -2
  37. package/init/content/lectures/02/subdir/_foobar.md +3 -0
  38. package/init/content/lectures/02/subdir/_pr2.md +3 -3
  39. package/package.json +1 -2
  40. package/src/slides/index.ts +6 -2
  41. package/src/trace/index.ts +49 -12
  42. package/src/trace/style.scss +10 -0
  43. package/src/trace/types.d.ts +16 -4
  44. package/build/utils/include.ts +0 -57
  45. package/build/watch-reachability-state.ts +0 -305
  46. package/init/content/lectures/02/subdir/_foobar.html +0 -3
  47. package/watch/engine.ts +0 -296
  48. package/watch/events.ts +0 -10
  49. package/watch/types.d.ts +0 -103
@@ -1,16 +1,12 @@
1
1
  import path from 'path';
2
- import {
3
- getProjectConfigBaseName,
4
- getSiteConfigBaseName,
5
- getSupportedConfigFilePaths,
6
- } from '../config-files';
7
2
  import { loadProjectConfig } from '../config-loader';
8
3
  import { getProjectConfigDir } from '../templates';
9
- import type { ChangeBatch } from '../../watch/types';
4
+ import type { ChangeBatch } from './types';
10
5
  import type { TadaProjectScan, TadaSnapshot } from './snapshot';
6
+ import { classifyWatchConfigPath } from './config-paths';
11
7
 
12
- export interface TadaWatchPlan {
13
- kind: 'full' | 'incremental';
8
+ export interface TadaIncrementalWatchPlan {
9
+ kind: 'incremental';
14
10
  scan: TadaProjectScan;
15
11
  contentToRender: Set<string>;
16
12
  publicToRender: Set<string>;
@@ -18,6 +14,8 @@ export interface TadaWatchPlan {
18
14
  publicToRemove: Set<string>;
19
15
  }
20
16
 
17
+ export type TadaWatchPlan = { kind: 'full' } | TadaIncrementalWatchPlan;
18
+
21
19
  export function diffAuthorKeys(previous: unknown, next: unknown): Set<string> {
22
20
  const previousMap =
23
21
  previous && typeof previous === 'object'
@@ -60,7 +58,7 @@ export function createTadaWatchPlan({
60
58
  batch,
61
59
  scan,
62
60
  }: {
63
- snapshot: TadaSnapshot | undefined;
61
+ snapshot: TadaSnapshot;
64
62
  batch: ChangeBatch;
65
63
  scan: TadaProjectScan;
66
64
  }): TadaWatchPlan {
@@ -69,66 +67,24 @@ export function createTadaWatchPlan({
69
67
  const contentToRemove = new Set<string>();
70
68
  const publicToRemove = new Set<string>();
71
69
  const projectConfigDir = getProjectConfigDir();
72
- const siteConfigPaths = new Set(
73
- getSupportedConfigFilePaths('.', getSiteConfigBaseName('dev')).map(
74
- filePath => path.resolve(filePath),
75
- ),
76
- );
77
- const navPaths = new Set(
78
- getSupportedConfigFilePaths(
79
- projectConfigDir,
80
- getProjectConfigBaseName('nav'),
81
- ).map(filePath => path.resolve(filePath)),
82
- );
83
- const authorsPaths = new Set(
84
- getSupportedConfigFilePaths(
85
- projectConfigDir,
86
- getProjectConfigBaseName('authors'),
87
- ).map(filePath => path.resolve(filePath)),
88
- );
89
-
90
- if (!snapshot) {
91
- return {
92
- kind: 'full',
93
- scan,
94
- contentToRender,
95
- publicToRender,
96
- contentToRemove,
97
- publicToRemove,
98
- };
99
- }
100
70
 
101
71
  for (const change of batch.changes) {
102
- const resolvedPath = path.resolve(change.path);
103
- if (siteConfigPaths.has(resolvedPath) || navPaths.has(resolvedPath)) {
104
- return {
105
- kind: 'full',
106
- scan,
107
- contentToRender,
108
- publicToRender,
109
- contentToRemove,
110
- publicToRemove,
111
- };
72
+ const configKind = classifyWatchConfigPath(change.path);
73
+ if (configKind === 'site' || configKind === 'nav') {
74
+ return { kind: 'full' };
112
75
  }
113
76
  }
114
77
 
115
78
  for (const change of batch.changes) {
116
79
  const resolvedPath = path.resolve(change.path);
117
- if (authorsPaths.has(resolvedPath)) {
80
+ if (classifyWatchConfigPath(resolvedPath) === 'authors') {
118
81
  const nextAuthorsData = loadProjectConfig(
119
82
  projectConfigDir,
120
83
  'authors',
121
84
  snapshot.siteVariables,
122
85
  )?.value;
123
86
  if (snapshot.authorsData === undefined || nextAuthorsData === undefined) {
124
- return {
125
- kind: 'full',
126
- scan,
127
- contentToRender,
128
- publicToRender,
129
- contentToRemove,
130
- publicToRemove,
131
- };
87
+ return { kind: 'full' };
132
88
  }
133
89
  const changedAuthorKeys = diffAuthorKeys(
134
90
  snapshot.authorsData,
@@ -144,17 +100,6 @@ export function createTadaWatchPlan({
144
100
  contentToRender.add(resolvedPath);
145
101
  } else if (snapshot.contentRecords.has(resolvedPath)) {
146
102
  contentToRemove.add(resolvedPath);
147
- const previousRecord = snapshot.contentRecords.get(resolvedPath)!;
148
- for (const partialPath of previousRecord.partialDeps) {
149
- addDependents(
150
- contentToRender,
151
- snapshot.reversePartialDeps,
152
- partialPath,
153
- );
154
- }
155
- for (const tracePath of previousRecord.traceDeps) {
156
- addDependents(contentToRender, snapshot.reverseTraceDeps, tracePath);
157
- }
158
103
  }
159
104
 
160
105
  if (scan.publicFiles.has(resolvedPath)) {
@@ -168,15 +113,13 @@ export function createTadaWatchPlan({
168
113
  }
169
114
 
170
115
  const changedTargets = new Set<string>();
171
- const removedTargets = new Set<string>();
172
- for (const target of snapshot.validTargets) {
116
+ for (const target of snapshot.scan.validTargets) {
173
117
  if (!scan.validTargets.has(target)) {
174
118
  changedTargets.add(target);
175
- removedTargets.add(target);
176
119
  }
177
120
  }
178
121
  for (const target of scan.validTargets) {
179
- if (!snapshot.validTargets.has(target)) {
122
+ if (!snapshot.scan.validTargets.has(target)) {
180
123
  changedTargets.add(target);
181
124
  }
182
125
  }
@@ -184,13 +127,6 @@ export function createTadaWatchPlan({
184
127
  addDependents(contentToRender, snapshot.reverseInternalTargetDeps, target);
185
128
  }
186
129
 
187
- if (removedTargets.size > 0 && contentToRender.size === 0) {
188
- const rootOwner = snapshot.outputOwners.get('index.html');
189
- if (rootOwner?.kind === 'content') {
190
- contentToRender.add(rootOwner.sourcePath);
191
- }
192
- }
193
-
194
130
  for (const [outputPath, owner] of snapshot.outputOwners) {
195
131
  const nextContentOwner = scan.contentOwners.get(outputPath);
196
132
  const nextPublicOwner = scan.publicOwners.get(outputPath);
@@ -3,9 +3,7 @@ import { B } from '../colors';
3
3
  import { makeLogger, printFlair } from '../log';
4
4
  import { startServer } from '../serve';
5
5
  import { WatchPagefindRunner } from '../pagefind';
6
- import type { TadaBuildMeta } from './compiler-types';
7
- import type { TadaSnapshot } from './snapshot';
8
- import type { WatchLifecycleEvent } from '../../watch/types';
6
+ import type { WatchLifecycleEvent } from './types';
9
7
  import {
10
8
  WATCH_RELOAD_MESSAGE_REBUILDING,
11
9
  WATCH_RELOAD_MESSAGE_RELOAD,
@@ -30,7 +28,6 @@ export class TadaWatchRuntime {
30
28
  private httpPort: number | undefined;
31
29
  private distDir: string;
32
30
  private server: Bun.Server<undefined> | null;
33
- private lastBuildFailed: boolean;
34
31
  private pagefindRunner: WatchPagefindRunner | undefined;
35
32
  private pagefindSiteVariablesKey: string | undefined;
36
33
 
@@ -38,7 +35,6 @@ export class TadaWatchRuntime {
38
35
  this.httpPort = httpPort;
39
36
  this.distDir = distDir;
40
37
  this.server = null;
41
- this.lastBuildFailed = false;
42
38
  this.pagefindSiteVariablesKey = undefined;
43
39
  }
44
40
 
@@ -68,51 +64,45 @@ export class TadaWatchRuntime {
68
64
  wslog.debug`WebSocket server listening at ws://localhost:${this.server.port}${WATCH_RELOAD_PATH}`;
69
65
  }
70
66
 
71
- async onEvent(
72
- event: WatchLifecycleEvent<TadaSnapshot, TadaBuildMeta>,
73
- ): Promise<void> {
67
+ async onEvent(event: WatchLifecycleEvent): Promise<void> {
74
68
  switch (event.kind) {
75
69
  case 'build-started':
76
- if (!event.initial) {
77
- for (const change of event.batch?.changes || []) {
78
- log.event`${B`${path.basename(change.path)}`} ${describeChange(change.kind)}`;
79
- }
80
- this.broadcast(WATCH_RELOAD_MESSAGE_REBUILDING);
70
+ if (!event.batch) {
71
+ return;
72
+ }
73
+ for (const change of event.batch.changes) {
74
+ log.event`${B`${path.basename(change.path)}`} ${describeChange(change.kind)}`;
81
75
  }
76
+ this.broadcast(WATCH_RELOAD_MESSAGE_REBUILDING);
82
77
  return;
83
78
  case 'build-succeeded': {
84
- const recoveredFromFailure = this.lastBuildFailed;
85
- this.lastBuildFailed = false;
86
79
  printFlair();
87
80
  this.ensureServerStarted();
88
- if (!event.initial && (event.changed || recoveredFromFailure)) {
81
+ if (event.batch) {
89
82
  this.broadcast(WATCH_RELOAD_MESSAGE_RELOAD);
90
83
  }
91
- if (event.meta && event.changed) {
92
- if (event.meta.siteVariables.features.search !== false) {
93
- const siteVariablesKey = JSON.stringify(event.meta.siteVariables);
94
- if (
95
- !this.pagefindRunner ||
96
- this.pagefindSiteVariablesKey !== siteVariablesKey
97
- ) {
98
- this.pagefindRunner = new WatchPagefindRunner();
99
- this.pagefindSiteVariablesKey = siteVariablesKey;
100
- }
101
- this.pagefindRunner.update(
102
- this.distDir,
103
- event.meta.htmlAssetsByPath,
104
- event.meta.htmlAnalysisByPath,
105
- );
106
- setImmediate(() => this.pagefindRunner!.run());
107
- } else {
108
- this.pagefindRunner = undefined;
109
- this.pagefindSiteVariablesKey = undefined;
84
+ if (event.meta.siteVariables.features.search !== false) {
85
+ const siteVariablesKey = JSON.stringify(event.meta.siteVariables);
86
+ if (
87
+ !this.pagefindRunner ||
88
+ this.pagefindSiteVariablesKey !== siteVariablesKey
89
+ ) {
90
+ this.pagefindRunner = new WatchPagefindRunner();
91
+ this.pagefindSiteVariablesKey = siteVariablesKey;
110
92
  }
93
+ this.pagefindRunner.update(
94
+ this.distDir,
95
+ event.meta.htmlAssetsByPath,
96
+ event.meta.htmlAnalysisByPath,
97
+ );
98
+ setImmediate(() => this.pagefindRunner!.run());
99
+ } else {
100
+ this.pagefindRunner = undefined;
101
+ this.pagefindSiteVariablesKey = undefined;
111
102
  }
112
103
  return;
113
104
  }
114
105
  case 'build-failed':
115
- this.lastBuildFailed = true;
116
106
  for (const diagnostic of event.diagnostics) {
117
107
  log.error`${diagnostic.message}`;
118
108
  }
@@ -120,8 +110,6 @@ export class TadaWatchRuntime {
120
110
  case 'watching':
121
111
  log.info`Watching for changes...`;
122
112
  return;
123
- case 'build-skipped':
124
- return;
125
113
  }
126
114
  }
127
115
  }
@@ -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);
@@ -2,4 +2,4 @@
2
2
 
3
3
  Here's the `_pr1.md` partial. From this partial we can use the page-level
4
4
  front matter fields from the page that includes us. The page that called
5
- `include()` has a title of: <%= page.title %>
5
+ this partial has a title of: <%= page.title %>
@@ -8,6 +8,6 @@ author: alex
8
8
 
9
9
  Here is a page constructed from multiple partials.
10
10
 
11
- <%= include('_pr1.md') %>
11
+ {{{ _pr1.md }}}
12
12
 
13
- <%= include('subdir/_pr2.md') %>
13
+ {{{ subdir/_pr2.md }}}
@@ -0,0 +1,3 @@
1
+ Here's a nested Markdown partial in a subdirectory.
2
+
3
+ Here's the title of the page including this partial: <%= page.title %>
@@ -1,6 +1,6 @@
1
1
  ## Problem 2 # The second example problem
2
2
 
3
- Here's the `_pr2.md` partial. If we use `include()` here, the path is resolved
4
- relative to this file, **not** the page or partial that included us.
3
+ Here's the `_pr2.md` partial. If we include another partial here, the path is
4
+ resolved relative to this file, **not** the page or partial that included us.
5
5
 
6
- <%= include('_foobar.html') %>
6
+ {{{ _foobar.md }}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abreen/tada",
3
- "version": "1.13.7",
3
+ "version": "1.14.1",
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/",
@@ -249,8 +249,8 @@ export default function mountSlides(window: Window): void | (() => void) {
249
249
  }
250
250
  }
251
251
 
252
- function resetReadyTraces(): void {
253
- const widgets = slidesRoot.querySelectorAll(
252
+ function resetReadyTraces(scope: HTMLElement = slidesRoot): void {
253
+ const widgets = scope.querySelectorAll(
254
254
  '.trace-widget:not(.trace-disabled)',
255
255
  );
256
256
 
@@ -341,7 +341,11 @@ export default function mountSlides(window: Window): void | (() => void) {
341
341
  hidePresentationControls();
342
342
  }
343
343
 
344
+ const previousIndex = activeIndex;
344
345
  setActiveSlide(activeIndex + delta);
346
+ if (delta === -1 && activeIndex !== previousIndex) {
347
+ resetReadyTraces(slides[activeIndex]);
348
+ }
345
349
  focusSlide(slides[activeIndex]);
346
350
  }
347
351