@escape-game-over/atlas 0.1.25 → 0.1.26
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/client-scripts.md +55 -84
- package/docs/rich-text.md +9 -4
- package/package.json +5 -4
- package/src/astro/AtlasElement.astro +26 -0
- package/src/astro/ConsentElement.astro +18 -12
- package/src/astro/Image.astro +1 -2
- package/src/astro/MetaTags.astro +27 -14
- package/src/astro/RichText.astro +60 -43
- package/src/astro/client.ts +7 -8
- package/src/astro/consent.ts +10 -10
- package/src/astro/element.ts +57 -92
- package/src/astro/ref.ts +46 -0
- package/src/astro/markup.ts +0 -656
package/docs/client-scripts.md
CHANGED
|
@@ -73,9 +73,9 @@ runs when the element enters the page, `signal` ends what it registered when the
|
|
|
73
73
|
element leaves, and what it returns is the undo for everything else.
|
|
74
74
|
|
|
75
75
|
```ts
|
|
76
|
-
export const thing = element("go-thing", {
|
|
77
|
-
button.
|
|
78
|
-
return loop.attach(host);
|
|
76
|
+
export const thing = element("go-thing", ({ host, signal }) => {
|
|
77
|
+
ref(host, "[data-thing-button]").addEventListener("click", open, { signal }); // ends with the visit
|
|
78
|
+
return loop.attach(host); // and so does this
|
|
79
79
|
});
|
|
80
80
|
```
|
|
81
81
|
|
|
@@ -363,96 +363,70 @@ skips the helper and writes it in `onChange`. Nothing degrades.
|
|
|
363
363
|
|
|
364
364
|
## `element`
|
|
365
365
|
|
|
366
|
-
A
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
the facet simply never matches. `element` names every attribute a script and its
|
|
370
|
-
template share, types the values in them, and registers the behaviour.
|
|
366
|
+
A custom element that enhances server-rendered markup: `element` registers the
|
|
367
|
+
behaviour and runs it for as long as the element is on the page, and `ref` and
|
|
368
|
+
`refs` find what it works on by plain attribute selectors.
|
|
371
369
|
|
|
372
370
|
```ts
|
|
373
|
-
// faq.ts — imported by the template, and loaded by the page's script
|
|
374
|
-
export const faq = element("go-faq", {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
for (const { element, values } of row.all()) {
|
|
379
|
-
values.status; // "open" | "soon"
|
|
371
|
+
// faq.ts — imported by the template for the tag, and loaded by the page's script
|
|
372
|
+
export const faq = element("go-faq", ({ host, signal }) => {
|
|
373
|
+
const input = ref<HTMLInputElement>(host, "[data-faq-search]");
|
|
374
|
+
for (const row of refs<HTMLDetailsElement>(host, "[data-faq-row]")) {
|
|
375
|
+
row.dataset.key;
|
|
380
376
|
}
|
|
381
|
-
const input = search.one<HTMLInputElement>(); // a marker role is the element itself
|
|
382
377
|
});
|
|
383
378
|
```
|
|
384
379
|
|
|
385
380
|
```astro
|
|
381
|
+
---
|
|
382
|
+
import AtlasElement from "@escape-game-over/atlas/astro/element";
|
|
383
|
+
---
|
|
384
|
+
|
|
386
385
|
<script src="./faq.ts"></script>
|
|
387
386
|
|
|
388
|
-
<
|
|
389
|
-
<input
|
|
390
|
-
<details
|
|
391
|
-
</
|
|
387
|
+
<AtlasElement of={faq} class="…">
|
|
388
|
+
<input data-faq-search type="search">
|
|
389
|
+
<details data-faq-row data-key={key}>…</details>
|
|
390
|
+
</AtlasElement>
|
|
392
391
|
```
|
|
393
392
|
|
|
394
393
|
It registers itself when loaded in a browser and does nothing in Node, so the
|
|
395
|
-
template imports the same file for the
|
|
396
|
-
|
|
397
|
-
- **
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
- **
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
394
|
+
template imports the same file for the tag.
|
|
395
|
+
|
|
396
|
+
- **What the script asks for is what devtools shows.** A selector is a string
|
|
397
|
+
the template wrote as an attribute; there is no naming scheme to decode.
|
|
398
|
+
Prefix the names per element (`data-faq-…`) so two elements sharing a
|
|
399
|
+
subtree cannot pick up each other's.
|
|
400
|
+
- **Missing is loud in dev.** `ref` throws naming the root and the selector;
|
|
401
|
+
`refs` returns what it found, none included. A throw inside `connect` leaves
|
|
402
|
+
that element inert and lands in the dev panel, as does any uncaught error on
|
|
403
|
+
the page — a failing listener, a rejected `await`.
|
|
404
|
+
- **Values are the DOM's.** `data(row, "key")` reads `data-key` and throws, as
|
|
405
|
+
`ref` does, when it is missing; an optional one is `row.dataset.state`. Parse
|
|
406
|
+
where the script needs a number or a list. State a stylesheet reads is a plain attribute the
|
|
407
|
+
script sets — `data-blocked` — which Tailwind selects as `group-data-blocked:`.
|
|
408
|
+
- **The root says it is one.** `AtlasElement` renders the tag with
|
|
409
|
+
`data-atlas-root`, so a project gives every root a display in one stylesheet
|
|
410
|
+
rule. A root rendered by hand says so on the dev server.
|
|
411
|
+
|
|
412
|
+
A sentence the script updates keeps its words in the page. The copy wraps the
|
|
413
|
+
changing part in a variant run, `RichText`'s `attrs` gives that run an
|
|
414
|
+
attribute, and the script writes only that run:
|
|
415
|
+
|
|
416
|
+
```ts
|
|
417
|
+
"faq.matched": { "en-US": "[v:count]{count}[/v] answers found" },
|
|
418
|
+
```
|
|
418
419
|
|
|
419
420
|
```astro
|
|
420
|
-
<
|
|
421
|
+
<RichText spans={rich("faq.matched", { count: "" })} attrs={{ count: { "data-faq-count": "" } }} />
|
|
421
422
|
```
|
|
422
423
|
|
|
423
424
|
```ts
|
|
424
|
-
|
|
425
|
+
for (const each of refs(host, "[data-faq-count]")) each.textContent = String(hits.size);
|
|
425
426
|
```
|
|
426
427
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
`write` sets fields back onto an element, for state a stylesheet reads — a
|
|
431
|
-
dropdown that is blocked — so the script uses the names the template did.
|
|
432
|
-
|
|
433
|
-
**What it still cannot reach is the stylesheet.** Tailwind generates CSS from
|
|
434
|
-
class names it finds as literals in the source, so a variant keyed on one of
|
|
435
|
-
these attributes has to spell it: `group-data-network-dropdown-blocked/dd:`.
|
|
436
|
-
That is the one place a rename is not a compile error. It is also the one place
|
|
437
|
-
a drift is visible, since the style stops applying, where a script reading the
|
|
438
|
-
wrong attribute fails silently. `attribute(field)` returns the name for the
|
|
439
|
-
consumers that can take a computed one, such as an `attributeFilter`.
|
|
440
|
-
|
|
441
|
-
It keeps the rule at the top of this page in the only reading that admits a
|
|
442
|
-
lookup: it finds only elements carrying its own marker, writes only its own
|
|
443
|
-
attributes, and sets no class, `aria` or `hidden`. The names are the project's,
|
|
444
|
-
so nothing in this package has to be matched.
|
|
445
|
-
|
|
446
|
-
- **Names come from the tag.** Every attribute is `data-<tag>-<role>` plus the
|
|
447
|
-
field, and the browser refuses to define one tag twice, so two elements cannot
|
|
448
|
-
share an attribute.
|
|
449
|
-
- **Lookups stay in their instance.** A nested copy of the element keeps its
|
|
450
|
-
own roles.
|
|
451
|
-
- **The root says it is one.** `{...faq.root}` writes `data-atlas-root`, so a
|
|
452
|
-
project gives every root a display in one stylesheet rule. Forget it and the
|
|
453
|
-
element says so on the dev server.
|
|
454
|
-
- **Names are checked in the editor.** A tag without a hyphen, or a role that is
|
|
455
|
-
not camelCase, is a compile error where it is written.
|
|
428
|
+
Marks are parsed before placeholders are filled, so a run filled with `""`
|
|
429
|
+
still renders as an empty element for the script to write into.
|
|
456
430
|
|
|
457
431
|
## `youtube`
|
|
458
432
|
|
|
@@ -587,16 +561,13 @@ right properties is a faithful stand-in.
|
|
|
587
561
|
`matches` read live as a browser's is, and fakes the `<video>` down to
|
|
588
562
|
`paused`, `currentSrc`, `networkState`, `autoplay`, `play`, `pause` and `load`,
|
|
589
563
|
including what resource selection does after a reload.
|
|
590
|
-
- `tests/
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
`type-tests/component.ts` pins the role types and the reserved keys.
|
|
598
|
-
|
|
599
|
-
`carousel`, `consent` and `element` have none. They need a real DOM and
|
|
564
|
+
- `tests/ref.test.ts` fakes a root down to its two query methods, and pins
|
|
565
|
+
that `ref` throws naming the root and the selector.
|
|
566
|
+
- `tests/element.test.ts` stubs `customElements` to upgrade on `define`, as a
|
|
567
|
+
browser does, and pins what `connect` is handed.
|
|
568
|
+
|
|
569
|
+
`carousel` and `consent` have none, and `element` has no lifecycle test — moves
|
|
570
|
+
and teardown. They need a real DOM and
|
|
600
571
|
this package carries no environment for one; adding `happy-dom` as a dev
|
|
601
572
|
dependency and setting `environment` in `vitest.config.ts` is what that would
|
|
602
573
|
take.
|
package/docs/rich-text.md
CHANGED
|
@@ -234,15 +234,20 @@ faqPage([{ question, answer: html(rich("faq.city.a", { network: "network" })) }]
|
|
|
234
234
|
|
|
235
235
|
## The renderer half
|
|
236
236
|
|
|
237
|
-
`@escape-game-over/atlas/astro/rich-text` renders the runs
|
|
238
|
-
|
|
237
|
+
`@escape-game-over/atlas/astro/rich-text` renders the runs, and only the runs:
|
|
238
|
+
the caller writes the element around them. The site supplies the look — a class
|
|
239
|
+
per `[v:name]` and one for links — usually in a small wrapper
|
|
239
240
|
([`examples/b2c/src/components/RichText.astro`](../examples/b2c/src/components/RichText.astro)):
|
|
240
241
|
|
|
241
242
|
```astro
|
|
242
|
-
<
|
|
243
|
+
<p class="…">
|
|
244
|
+
<RichText spans={rich("about.intro", { venue: "contact" })} variants={{ accent: "…" }} link="…" />
|
|
245
|
+
</p>
|
|
243
246
|
```
|
|
244
247
|
|
|
245
|
-
|
|
248
|
+
`attrs` puts attributes on a variant's run, so a script can find it and rewrite
|
|
249
|
+
its words — see [client-scripts.md](client-scripts.md). A variant with neither a
|
|
250
|
+
class nor attrs still renders its words, and warns at build.
|
|
246
251
|
|
|
247
252
|
## What fails, and where
|
|
248
253
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@escape-game-over/atlas",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.26",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Typed, data-driven machinery for static multi-locale, multi-deployment Astro sites.",
|
|
6
6
|
"private": false,
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"./client": "./src/astro/client.ts",
|
|
19
19
|
"./astro/consent-banner": "./src/astro/ConsentBanner.astro",
|
|
20
20
|
"./astro/document": "./src/astro/Document.astro",
|
|
21
|
+
"./astro/element": "./src/astro/AtlasElement.astro",
|
|
21
22
|
"./astro/image": "./src/astro/Image.astro",
|
|
22
23
|
"./astro/rich-text": "./src/astro/RichText.astro"
|
|
23
24
|
},
|
|
@@ -50,10 +51,10 @@
|
|
|
50
51
|
"typescript": ">=5"
|
|
51
52
|
},
|
|
52
53
|
"devDependencies": {
|
|
53
|
-
"@biomejs/biome": "2.5.
|
|
54
|
-
"@types/node": "26.
|
|
54
|
+
"@biomejs/biome": "2.5.14",
|
|
55
|
+
"@types/node": "26.6.2",
|
|
55
56
|
"@vitest/coverage-istanbul": "5.0.1",
|
|
56
|
-
"astro": "7.3.
|
|
57
|
+
"astro": "7.3.3",
|
|
57
58
|
"typescript": "6.0.3",
|
|
58
59
|
"vitest": "5.0.1"
|
|
59
60
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Renders a custom element with the root attribute: what a stylesheet gives
|
|
4
|
+
* every root its display by, and what `element` checks for in dev.
|
|
5
|
+
*
|
|
6
|
+
* ```astro
|
|
7
|
+
* <AtlasElement of={trailer} data-video={video} class="…">…</AtlasElement>
|
|
8
|
+
* ```
|
|
9
|
+
*/
|
|
10
|
+
import type { HTMLAttributes } from "astro/types";
|
|
11
|
+
import { type ElementTag, ROOT_ATTRIBUTE } from "./element.ts";
|
|
12
|
+
|
|
13
|
+
interface Props extends HTMLAttributes<"div"> {
|
|
14
|
+
readonly of: ElementTag;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const { of, ...rest } = Astro.props;
|
|
18
|
+
const Tag = of.tag;
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
<Tag
|
|
22
|
+
{...rest}
|
|
23
|
+
{...{ [ROOT_ATTRIBUTE]: "" }}
|
|
24
|
+
>
|
|
25
|
+
<slot />
|
|
26
|
+
</Tag>
|
|
@@ -1,32 +1,34 @@
|
|
|
1
1
|
---
|
|
2
2
|
/** Internal to `ConsentBanner.astro`, so its script ships only where it renders. */
|
|
3
3
|
import type { HTMLAttributes } from "astro/types";
|
|
4
|
+
import AtlasElement from "./AtlasElement.astro";
|
|
4
5
|
import { consentBanner } from "./consent.ts";
|
|
5
6
|
|
|
6
7
|
type Props = HTMLAttributes<"div">;
|
|
7
8
|
---
|
|
8
9
|
|
|
9
|
-
<
|
|
10
|
+
<AtlasElement
|
|
11
|
+
of={consentBanner}
|
|
10
12
|
{...Astro.props}
|
|
11
|
-
{...consentBanner.root}
|
|
12
13
|
hidden
|
|
13
14
|
role="dialog"
|
|
14
15
|
aria-modal="false"
|
|
15
16
|
>
|
|
16
17
|
<slot />
|
|
17
|
-
</
|
|
18
|
+
</AtlasElement>
|
|
18
19
|
|
|
19
20
|
<script>
|
|
20
21
|
import {
|
|
21
22
|
applyConsent,
|
|
22
|
-
|
|
23
|
+
CONSENT_ANSWER,
|
|
24
|
+
CONSENT_REOPEN,
|
|
23
25
|
consentApplies,
|
|
24
|
-
consentReopen,
|
|
25
26
|
consentStore,
|
|
26
27
|
} from "./consent.ts";
|
|
27
28
|
import { element } from "./element.ts";
|
|
29
|
+
import { refs } from "./ref.ts";
|
|
28
30
|
|
|
29
|
-
element("atlas-consent",
|
|
31
|
+
element("atlas-consent", ({ host, signal }) => {
|
|
30
32
|
// The head script left no consent hook: nothing here sets a cookie.
|
|
31
33
|
if (!consentApplies()) return;
|
|
32
34
|
|
|
@@ -35,11 +37,15 @@ element("atlas-consent", CONSENT_ROLES, (host, { answer }, signal) => {
|
|
|
35
37
|
host.hidden = false;
|
|
36
38
|
};
|
|
37
39
|
|
|
38
|
-
for (const
|
|
39
|
-
|
|
40
|
+
for (const button of refs(host, `[${CONSENT_ANSWER}]`)) {
|
|
41
|
+
const choice = button.getAttribute(CONSENT_ANSWER);
|
|
42
|
+
if (choice !== "granted" && choice !== "denied") {
|
|
43
|
+
throw new Error(`${CONSENT_ANSWER} is "${choice}"`);
|
|
44
|
+
}
|
|
45
|
+
button.addEventListener(
|
|
40
46
|
"click",
|
|
41
47
|
() => {
|
|
42
|
-
store.record(
|
|
48
|
+
store.record(choice);
|
|
43
49
|
host.hidden = true;
|
|
44
50
|
},
|
|
45
51
|
{ signal }
|
|
@@ -47,9 +53,9 @@ element("atlas-consent", CONSENT_ROLES, (host, { answer }, signal) => {
|
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
// Outside the banner — withdrawing must be as easy as granting.
|
|
50
|
-
for (const
|
|
51
|
-
|
|
52
|
-
|
|
56
|
+
for (const button of refs(document, `[${CONSENT_REOPEN}]`)) {
|
|
57
|
+
button.hidden = false;
|
|
58
|
+
button.addEventListener("click", show, { signal });
|
|
53
59
|
}
|
|
54
60
|
|
|
55
61
|
// Applied, not re-recorded: recording stamps today's date, and an answer
|
package/src/astro/Image.astro
CHANGED
package/src/astro/MetaTags.astro
CHANGED
|
@@ -25,27 +25,40 @@ const { tags } = Astro.props;
|
|
|
25
25
|
// again as HTML would turn each quote into an entity and leave a block no
|
|
26
26
|
// parser reads.
|
|
27
27
|
if (tag.kind === "script") {
|
|
28
|
-
|
|
28
|
+
return (
|
|
29
|
+
<script
|
|
30
|
+
type={tag.type}
|
|
31
|
+
is:inline
|
|
32
|
+
set:html={tag.content}
|
|
33
|
+
/>
|
|
34
|
+
);
|
|
29
35
|
}
|
|
30
36
|
// `is:inline` so the bundler leaves a third-party URL alone, and `defer`
|
|
31
37
|
// because a script that blocks the parser costs the thing it measures.
|
|
32
38
|
if (tag.kind === "externalScript") {
|
|
33
|
-
|
|
39
|
+
return (
|
|
40
|
+
<script
|
|
41
|
+
is:inline
|
|
42
|
+
defer
|
|
43
|
+
src={tag.src}
|
|
44
|
+
{...tag.attrs}
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
34
47
|
}
|
|
35
48
|
// Tag Manager's fallback, and only ever in the body list. Hidden the way
|
|
36
49
|
// Google's own snippet hides it — a zero-sized iframe nothing can see.
|
|
37
50
|
if (tag.kind === "noscriptFrame") {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
return (
|
|
52
|
+
<noscript>
|
|
53
|
+
<iframe
|
|
54
|
+
src={tag.src}
|
|
55
|
+
title='Google Tag Manager'
|
|
56
|
+
height='0'
|
|
57
|
+
width='0'
|
|
58
|
+
style='display:none;visibility:hidden'
|
|
59
|
+
/>
|
|
60
|
+
</noscript>
|
|
61
|
+
);
|
|
49
62
|
}
|
|
50
63
|
if (tag.kind === "meta") return <meta {...tag.attrs} />;
|
|
51
64
|
// Named rather than left to a trailing `return`, which used to catch
|
|
@@ -59,4 +72,4 @@ const { tags } = Astro.props;
|
|
|
59
72
|
// copied into every project.
|
|
60
73
|
const unhandled: never = tag;
|
|
61
74
|
throw new Error(`Unhandled meta tag: ${JSON.stringify(unhandled)}`);
|
|
62
|
-
|
|
75
|
+
})}
|
package/src/astro/RichText.astro
CHANGED
|
@@ -1,71 +1,88 @@
|
|
|
1
1
|
---
|
|
2
2
|
/**
|
|
3
3
|
* Renders what `site.rich()` returns. The site supplies the look: a class per
|
|
4
|
-
* `[v:name]` variant and one for links; everything else is decided here.
|
|
4
|
+
* `[v:name]` variant and one for links; everything else is decided here. Only
|
|
5
|
+
* the runs — the caller writes the element they sit in.
|
|
5
6
|
*
|
|
6
7
|
* ```astro
|
|
7
|
-
* <
|
|
8
|
+
* <p class="…">
|
|
9
|
+
* <RichText spans={rich("about.intro", { venue: "contact" })} variants={{ accent: "…" }} />
|
|
10
|
+
* </p>
|
|
8
11
|
* ```
|
|
9
12
|
*/
|
|
10
|
-
import type { HTMLTag } from "astro/types";
|
|
11
13
|
import type { RichText } from "../content/index.ts";
|
|
12
14
|
import { warn } from "../warn.ts";
|
|
13
15
|
|
|
14
16
|
interface Props {
|
|
15
17
|
readonly spans: RichText;
|
|
16
|
-
/** The element wrapping the runs. Defaults to `p`. */
|
|
17
|
-
readonly as?: HTMLTag;
|
|
18
|
-
readonly class?: string;
|
|
19
18
|
/** `[v:name]` → classes. */
|
|
20
19
|
readonly variants?: Readonly<Record<string, string>>;
|
|
20
|
+
/** `[v:name]` → attributes, so a script can find that run. */
|
|
21
|
+
readonly attrs?: Readonly<Record<string, Readonly<Record<string, string>>>>;
|
|
21
22
|
/** Classes on every link: `[a:]`, `[mail]` and `[tel]`. */
|
|
22
23
|
readonly link?: string;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
const {
|
|
26
|
-
spans,
|
|
27
|
-
as: Tag = "p",
|
|
28
|
-
class: className,
|
|
29
|
-
variants = {},
|
|
30
|
-
link,
|
|
31
|
-
}: Props = Astro.props;
|
|
26
|
+
const { spans, variants = {}, attrs = {}, link }: Props = Astro.props;
|
|
32
27
|
|
|
33
28
|
// The words still render: losing the sentence is worse than losing its style.
|
|
29
|
+
// A run with attributes and no class is a script's hook, not a missing style.
|
|
34
30
|
const variantClass = (variant: string): string | undefined => {
|
|
35
|
-
if (!Object.hasOwn(variants, variant)) {
|
|
31
|
+
if (!Object.hasOwn(variants, variant) && !Object.hasOwn(attrs, variant)) {
|
|
36
32
|
warn(
|
|
37
33
|
Astro.url.pathname,
|
|
38
|
-
`[v:${variant}] has no class in RichText's variants.`
|
|
34
|
+
`[v:${variant}] has no class in RichText's variants and no attrs.`
|
|
39
35
|
);
|
|
40
36
|
}
|
|
41
|
-
return variants[variant];
|
|
37
|
+
return Object.hasOwn(variants, variant) ? variants[variant] : undefined;
|
|
42
38
|
};
|
|
39
|
+
|
|
40
|
+
const variantAttrs = (variant: string) =>
|
|
41
|
+
Object.hasOwn(attrs, variant) ? attrs[variant] : undefined;
|
|
43
42
|
---
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
44
|
+
{spans.map((span) => {
|
|
45
|
+
switch (span.kind) {
|
|
46
|
+
case "text":
|
|
47
|
+
return span.text;
|
|
48
|
+
case "bold":
|
|
49
|
+
return <strong>{span.text}</strong>;
|
|
50
|
+
case "variant":
|
|
51
|
+
return (
|
|
52
|
+
<span
|
|
53
|
+
{...variantAttrs(span.variant)}
|
|
54
|
+
class={variantClass(span.variant)}>
|
|
55
|
+
{span.text}
|
|
56
|
+
</span>
|
|
57
|
+
);
|
|
58
|
+
case "break":
|
|
59
|
+
return <br />;
|
|
60
|
+
case "link":
|
|
61
|
+
return span.to === "external" ? (
|
|
62
|
+
<a
|
|
63
|
+
href={span.href}
|
|
64
|
+
class={link}
|
|
65
|
+
target='_blank'
|
|
66
|
+
rel='noopener noreferrer'>
|
|
67
|
+
{span.text}
|
|
68
|
+
</a>
|
|
69
|
+
) : (
|
|
70
|
+
<a
|
|
71
|
+
href={span.href}
|
|
72
|
+
class={link}>
|
|
73
|
+
{span.text}
|
|
74
|
+
</a>
|
|
75
|
+
);
|
|
76
|
+
case "mail":
|
|
77
|
+
case "tel":
|
|
78
|
+
return (
|
|
79
|
+
<a
|
|
80
|
+
href={span.href}
|
|
81
|
+
class={link}>
|
|
82
|
+
{span.text}
|
|
83
|
+
</a>
|
|
84
|
+
);
|
|
85
|
+
default:
|
|
86
|
+
return span satisfies never;
|
|
87
|
+
}
|
|
88
|
+
})}
|
package/src/astro/client.ts
CHANGED
|
@@ -20,14 +20,13 @@ export {
|
|
|
20
20
|
consentApplies,
|
|
21
21
|
consentStore,
|
|
22
22
|
} from "./consent.ts";
|
|
23
|
-
export {
|
|
23
|
+
export {
|
|
24
|
+
type Connect,
|
|
25
|
+
type ElementContext,
|
|
26
|
+
type ElementTag,
|
|
27
|
+
element,
|
|
28
|
+
} from "./element.ts";
|
|
24
29
|
export * from "./filters.ts";
|
|
25
30
|
export * from "./filters-view.ts";
|
|
26
|
-
export {
|
|
27
|
-
type Component,
|
|
28
|
-
type Marked,
|
|
29
|
-
type Markup,
|
|
30
|
-
marker,
|
|
31
|
-
markup,
|
|
32
|
-
} from "./markup.ts";
|
|
31
|
+
export { data, ref, refs } from "./ref.ts";
|
|
33
32
|
export * from "./youtube.ts";
|
package/src/astro/consent.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { CONSENT_UPDATE_GLOBAL } from "../analytics/google.ts";
|
|
2
|
-
import {
|
|
2
|
+
import type { ElementTag } from "./element.ts";
|
|
3
3
|
|
|
4
|
-
/** Internal to `ConsentBanner.astro`: the element and
|
|
5
|
-
export const
|
|
6
|
-
|
|
7
|
-
}
|
|
8
|
-
export const
|
|
9
|
-
export const
|
|
4
|
+
/** Internal to `ConsentBanner.astro`: the element, and what its script finds. */
|
|
5
|
+
export const consentBanner: ElementTag<"atlas-consent"> = {
|
|
6
|
+
tag: "atlas-consent",
|
|
7
|
+
};
|
|
8
|
+
export const CONSENT_ANSWER = "data-atlas-consent-answer";
|
|
9
|
+
export const CONSENT_REOPEN = "data-atlas-consent-reopen";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* What a site's consent markup spreads: the two answer buttons inside
|
|
@@ -15,9 +15,9 @@ export const consentReopen = markup("atlas-consent-reopen");
|
|
|
15
15
|
* withdraw.
|
|
16
16
|
*/
|
|
17
17
|
export const consent = {
|
|
18
|
-
accept:
|
|
19
|
-
decline:
|
|
20
|
-
reopen: {
|
|
18
|
+
accept: { [CONSENT_ANSWER]: "granted" },
|
|
19
|
+
decline: { [CONSENT_ANSWER]: "denied" },
|
|
20
|
+
reopen: { [CONSENT_REOPEN]: "", hidden: "" },
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
/**
|