@escape-game-over/atlas 0.1.29 → 0.1.31
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/docs/NOT-BUILT.md +13 -18
- package/docs/toolchain.md +6 -4
- package/package.json +1 -1
- package/src/astro/carousel.ts +39 -5
- package/src/site/api.ts +5 -8
- package/src/site/create.ts +30 -5
package/docs/NOT-BUILT.md
CHANGED
|
@@ -500,27 +500,22 @@ looking at" better than any link can.
|
|
|
500
500
|
|
|
501
501
|
### Should `/en-US/about-us` redirect when the default locale is unprefixed?
|
|
502
502
|
|
|
503
|
-
**
|
|
503
|
+
**Yes, by one wildcard — not by a rule per page.**
|
|
504
504
|
|
|
505
505
|
With `prefixDefaultLocale: false` the default locale is served at `/about-us`
|
|
506
|
-
and nothing is built under `/en-US/`. Claiming the
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
and slugs are translated per locale, so the URL a reader lands on by editing
|
|
510
|
-
`/el-GR/sxetika-me-emas` down to English keeps the *Greek* slug —
|
|
506
|
+
and nothing is built under `/en-US/`. Claiming the pages under it one rule at a
|
|
507
|
+
time does not work: slugs are translated per locale, so the URL a reader lands
|
|
508
|
+
on by editing `/el-GR/sxetika-me-emas` down to English keeps the *Greek* slug —
|
|
511
509
|
`/en-US/sxetika-me-emas`. Covering that means the cross product of the prefix
|
|
512
|
-
with every locale's spelling of every slug,
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
not the translated slug, and it is Cloudflare's syntax rather than something
|
|
522
|
-
`ResolvedRedirect` can carry to another host. The pages get the 404 page, which
|
|
523
|
-
is what it is for.
|
|
510
|
+
with every locale's spelling of every slug, a `_redirects` of hundreds against
|
|
511
|
+
the 2000 a host reads.
|
|
512
|
+
|
|
513
|
+
`/en-US/* /:splat 301` is one line instead. It still does not cover the
|
|
514
|
+
translated slug — `/en-US/sxetika-me-emas` goes to `/sxetika-me-emas`, which
|
|
515
|
+
404s — but that URL 404ed without the rule too, so it is never worse and it
|
|
516
|
+
answers every English slug. It is Cloudflare's syntax, which is what every site
|
|
517
|
+
deploys to. It is written last, so a stated `/en-US/old` rule still fires, and
|
|
518
|
+
left out if anything is built under the prefix.
|
|
524
519
|
|
|
525
520
|
---
|
|
526
521
|
|
package/docs/toolchain.md
CHANGED
|
@@ -93,14 +93,16 @@ slug or a switched-off page moves the rule with the page:
|
|
|
93
93
|
| -------------- | ------------- | ---------------------------------------------- |
|
|
94
94
|
| `/` | `/en-US` | `prefixDefaultLocale: true` leaves `/` unowned |
|
|
95
95
|
| `/en-US` | `/` | `prefixDefaultLocale: false` leaves it unbuilt |
|
|
96
|
+
| `/en-US/*` | `/:splat` | the same, for every page under the prefix |
|
|
96
97
|
| `/news/page/1` | `/news` | page one is the bare slug, never `page/1` |
|
|
97
98
|
|
|
98
99
|
The first two are the same rule in its two directions: whichever root the
|
|
99
100
|
routing mode does not serve points at the one it does. Both together cost one
|
|
100
|
-
rule
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
rule. The wildcard costs one more, and it is written last in the file, because
|
|
102
|
+
Cloudflare fires the first rule that matches and a stated `/en-US/old` rule has
|
|
103
|
+
to be reached before it. The page-one rule costs one per paginated list per
|
|
104
|
+
language. Nothing here scales with the number of pages a site has. A project
|
|
105
|
+
that states its own rule for one of these paths keeps it: the inferred one steps
|
|
104
106
|
aside.
|
|
105
107
|
|
|
106
108
|
The root points at whichever route has an empty slug, so nothing has to name a
|
package/package.json
CHANGED
package/src/astro/carousel.ts
CHANGED
|
@@ -62,6 +62,17 @@ export interface CarouselOptions {
|
|
|
62
62
|
* renders, and calling would make a project write the first position twice.
|
|
63
63
|
*/
|
|
64
64
|
readonly onChange: (index: number) => void;
|
|
65
|
+
/**
|
|
66
|
+
* Called with `autoplayMs` whenever a countdown to the next automatic
|
|
67
|
+
* advance begins, and with `null` when autoplay stops.
|
|
68
|
+
*
|
|
69
|
+
* For a progress bar. A countdown begins on attach, after every tick and
|
|
70
|
+
* after every manual move, and each one starts from zero — so the bar
|
|
71
|
+
* restarts on every number and stops on `null`, and never has to know why.
|
|
72
|
+
* A hold (hover, focus, a hidden tab, `pause`) and a detach report `null`
|
|
73
|
+
* once, not once per reason.
|
|
74
|
+
*/
|
|
75
|
+
readonly onCountdown?: (ms: number | null) => void;
|
|
65
76
|
}
|
|
66
77
|
|
|
67
78
|
export interface Carousel {
|
|
@@ -107,6 +118,7 @@ export function carousel(options: CarouselOptions): Carousel {
|
|
|
107
118
|
swipeThreshold = 10,
|
|
108
119
|
lockMs = 500,
|
|
109
120
|
onChange,
|
|
121
|
+
onCountdown,
|
|
110
122
|
} = options;
|
|
111
123
|
|
|
112
124
|
/**
|
|
@@ -143,9 +155,19 @@ export function carousel(options: CarouselOptions): Carousel {
|
|
|
143
155
|
*/
|
|
144
156
|
const holds = new Set<string>();
|
|
145
157
|
|
|
158
|
+
/** Whether a countdown is running, so `null` is reported once per stop. */
|
|
159
|
+
let counting = false;
|
|
160
|
+
|
|
161
|
+
const stopAutoplay = (): void => {
|
|
162
|
+
clearInterval(autoplayTimer);
|
|
163
|
+
if (!counting) return;
|
|
164
|
+
counting = false;
|
|
165
|
+
onCountdown?.(null);
|
|
166
|
+
};
|
|
167
|
+
|
|
146
168
|
const hold = (reason: string): void => {
|
|
147
169
|
holds.add(reason);
|
|
148
|
-
|
|
170
|
+
stopAutoplay();
|
|
149
171
|
};
|
|
150
172
|
|
|
151
173
|
const release = (reason: string): void => {
|
|
@@ -171,16 +193,28 @@ export function carousel(options: CarouselOptions): Carousel {
|
|
|
171
193
|
};
|
|
172
194
|
|
|
173
195
|
const restartAutoplay = (): void => {
|
|
174
|
-
clearInterval(autoplayTimer);
|
|
175
196
|
// Nothing to rotate through, nobody watching, or no autoplay asked
|
|
176
197
|
// for — in each case, nothing to schedule.
|
|
177
|
-
if (
|
|
178
|
-
|
|
198
|
+
if (
|
|
199
|
+
autoplayMs === undefined ||
|
|
200
|
+
!canMove ||
|
|
201
|
+
!attached ||
|
|
202
|
+
holds.size > 0
|
|
203
|
+
) {
|
|
204
|
+
stopAutoplay();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
clearInterval(autoplayTimer);
|
|
179
208
|
autoplayTimer = setInterval(() => {
|
|
180
209
|
// No `restartAutoplay` here: the interval already paces itself, and
|
|
181
210
|
// resetting it from inside its own tick would only churn timers.
|
|
182
211
|
if (claim()) move(index + 1);
|
|
212
|
+
// Reported even when the lock refused the move: the next tick is
|
|
213
|
+
// still `autoplayMs` away either way.
|
|
214
|
+
onCountdown?.(autoplayMs);
|
|
183
215
|
}, autoplayMs);
|
|
216
|
+
counting = true;
|
|
217
|
+
onCountdown?.(autoplayMs);
|
|
184
218
|
};
|
|
185
219
|
|
|
186
220
|
/** The only place `index` changes. `at` may be out of range or negative. */
|
|
@@ -326,7 +360,7 @@ export function carousel(options: CarouselOptions): Carousel {
|
|
|
326
360
|
return () => {
|
|
327
361
|
listeners.abort();
|
|
328
362
|
attached = false;
|
|
329
|
-
|
|
363
|
+
stopAutoplay();
|
|
330
364
|
|
|
331
365
|
// The automatic holds belong to this attachment: an element
|
|
332
366
|
// detached while hovered would otherwise keep "hover" forever,
|
package/src/site/api.ts
CHANGED
|
@@ -309,14 +309,11 @@ export interface Site<
|
|
|
309
309
|
* **What comes back is more than what went in.** lib adds the rules the
|
|
310
310
|
* route table implies, for the URLs its own design leaves unpublished but
|
|
311
311
|
* reachable: whichever site root the routing mode does not serve — `/` when
|
|
312
|
-
* every locale is prefixed, `/en-US` when the default locale is not —
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
* you
|
|
316
|
-
*
|
|
317
|
-
* Pages under an unused locale prefix are *not* claimed: that is a rule per
|
|
318
|
-
* page per language, and it still misses the reader who edits a translated
|
|
319
|
-
* slug's prefix. See `NOT-BUILT.md`.
|
|
312
|
+
* every locale is prefixed, `/en-US` when the default locale is not — the
|
|
313
|
+
* pages under that unused `/en-US/`, as one `/en-US/* /:splat` wildcard
|
|
314
|
+
* written last, and `/news/page/1` for a list whose page one is the bare
|
|
315
|
+
* path. A rule you state for one of those paths replaces the inferred one,
|
|
316
|
+
* so pass `[]` and you still get a file worth writing.
|
|
320
317
|
*
|
|
321
318
|
* Returns the rules as data. Rendering is a separate step —
|
|
322
319
|
* `buildCloudflareRedirects` writes the `_redirects` that Cloudflare and
|
package/src/site/create.ts
CHANGED
|
@@ -513,10 +513,8 @@ export function createSite<
|
|
|
513
513
|
* switched-off page moves the rule with it instead of leaving one pointing
|
|
514
514
|
* at a 404.
|
|
515
515
|
*
|
|
516
|
-
*
|
|
517
|
-
*
|
|
518
|
-
* it is a rule per page per language — a file of hundreds against the 2000
|
|
519
|
-
* a host reads, for URLs nothing ever linked. See `NOT-BUILT.md`.
|
|
516
|
+
* The pages under an unused prefix are `prefixSplat`'s, as one wildcard
|
|
517
|
+
* rather than a rule per page per language. See `NOT-BUILT.md`.
|
|
520
518
|
*
|
|
521
519
|
* Both permanent. Neither is a state that changes while the config stays as
|
|
522
520
|
* it is: page one will never live at `/page/1`, and the root will not move
|
|
@@ -570,6 +568,32 @@ export function createSite<
|
|
|
570
568
|
return [...root, ...pageOne];
|
|
571
569
|
}
|
|
572
570
|
|
|
571
|
+
/**
|
|
572
|
+
* `/en-US/about` → `/about` when the default locale is unprefixed: the
|
|
573
|
+
* pages under the prefix `inferredRedirects` points at the root, in one
|
|
574
|
+
* Cloudflare wildcard. A translated slug under the wrong prefix still ends
|
|
575
|
+
* on the 404, one hop later — no worse than without it.
|
|
576
|
+
*
|
|
577
|
+
* Kept apart because it has to be written last: Cloudflare fires the first
|
|
578
|
+
* rule that matches, and a wildcard ahead of a stated `/en-US/old` rule
|
|
579
|
+
* would swallow it. Skipped when anything is built under the prefix, which
|
|
580
|
+
* the wildcard would make unreachable.
|
|
581
|
+
*/
|
|
582
|
+
function prefixSplat(): readonly ResolvedRedirect[] {
|
|
583
|
+
if (prefixDefaultLocale) return [];
|
|
584
|
+
const prefix = `/${defaultLocale}/`;
|
|
585
|
+
for (const path of builtPaths) {
|
|
586
|
+
if (path.startsWith(prefix)) return [];
|
|
587
|
+
}
|
|
588
|
+
return [
|
|
589
|
+
{
|
|
590
|
+
from: `${prefix}*`,
|
|
591
|
+
to: "/:splat",
|
|
592
|
+
status: statusFor("permanent"),
|
|
593
|
+
},
|
|
594
|
+
];
|
|
595
|
+
}
|
|
596
|
+
|
|
573
597
|
function redirects(
|
|
574
598
|
rules: readonly RedirectRule<RouteId, L>[]
|
|
575
599
|
): readonly ResolvedRedirect[] {
|
|
@@ -590,6 +614,7 @@ export function createSite<
|
|
|
590
614
|
}));
|
|
591
615
|
|
|
592
616
|
const claimed = new Set(stated.map((rule) => rule.from));
|
|
617
|
+
const splat = prefixSplat().filter((rule) => !claimed.has(rule.from));
|
|
593
618
|
const inferred = inferredRedirects().filter(
|
|
594
619
|
// Dropped rather than reported, both times, because neither is a
|
|
595
620
|
// mistake anyone made: a rule the project wrote for one of these
|
|
@@ -604,7 +629,7 @@ export function createSite<
|
|
|
604
629
|
// So a rule that shadows a real page is rejected rather than
|
|
605
630
|
// sitting dead in the file.
|
|
606
631
|
builtPaths,
|
|
607
|
-
rules: [...inferred, ...stated],
|
|
632
|
+
rules: [...inferred, ...stated, ...splat],
|
|
608
633
|
});
|
|
609
634
|
}
|
|
610
635
|
|