Compare commits
10
Commits
dev
..
d40f8b3d0f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d40f8b3d0f | ||
|
|
e79d54ba34 | ||
|
|
81901a862e | ||
|
|
987d7b2d84 | ||
|
|
4e22db5261 | ||
|
|
8373853971 | ||
|
|
c1f4565bfe | ||
|
|
f138066ee6 | ||
|
|
59f9af119a | ||
|
|
87901c56d2 |
@@ -302,10 +302,6 @@ export async function hydrateMintGrid(options: HydrateOptions): Promise<void> {
|
|||||||
|
|
||||||
const all = await fetchListing(type);
|
const all = await fetchListing(type);
|
||||||
if (!all) return;
|
if (!all) return;
|
||||||
// The reader may have navigated away while the fetch was in flight. The router has
|
|
||||||
// already swapped this grid out of the document, so rendering into it (and telling the
|
|
||||||
// caller to re-apply filters against cards nobody can see) is wasted work.
|
|
||||||
if (!grid.isConnected) return;
|
|
||||||
const items = limit === undefined ? all : all.slice(0, limit);
|
const items = limit === undefined ? all : all.slice(0, limit);
|
||||||
|
|
||||||
const cards = renderMintGrid(grid, items, {
|
const cards = renderMintGrid(grid, items, {
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ const schema = [
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="mint-grid" data-mint-grid="fedimint">
|
<div class="mint-grid" data-mint-grid>
|
||||||
{federations.map((federation, i) => <MintCard mint={federation} rank={i + 1} />)}
|
{federations.map((federation, i) => <MintCard mint={federation} rank={i + 1} />)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -259,7 +259,7 @@ const schema = [
|
|||||||
|
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
const grid = document.querySelector<HTMLElement>('[data-mint-grid="fedimint"]');
|
const grid = document.querySelector<HTMLElement>('[data-mint-grid]');
|
||||||
const searchInput = document.querySelector<HTMLInputElement>('[data-search-input]');
|
const searchInput = document.querySelector<HTMLInputElement>('[data-search-input]');
|
||||||
const sortSelect = document.querySelector<HTMLSelectElement>('[data-sort]');
|
const sortSelect = document.querySelector<HTMLSelectElement>('[data-sort]');
|
||||||
const hideOffline = document.querySelector<HTMLButtonElement>('[data-hide-offline]');
|
const hideOffline = document.querySelector<HTMLButtonElement>('[data-hide-offline]');
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ const schema = [
|
|||||||
{t('lnurlMints.showing', { total: mints.length, online, offline })}
|
{t('lnurlMints.showing', { total: mints.length, online, offline })}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="mint-grid" data-mint-grid="lnurl">
|
<div class="mint-grid" data-mint-grid>
|
||||||
{mints.map((mint, i) => (
|
{mints.map((mint, i) => (
|
||||||
<MintCard
|
<MintCard
|
||||||
mint={mint}
|
mint={mint}
|
||||||
@@ -272,7 +272,7 @@ const schema = [
|
|||||||
// `t` formats its own numbers, so the grouping separator follows the locale too.
|
// `t` formats its own numbers, so the grouping separator follows the locale too.
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
const grid = document.querySelector<HTMLElement>('[data-mint-grid="lnurl"]');
|
const grid = document.querySelector<HTMLElement>('[data-mint-grid]');
|
||||||
const searchInput = document.querySelector<HTMLInputElement>('[data-search-input]');
|
const searchInput = document.querySelector<HTMLInputElement>('[data-search-input]');
|
||||||
const sortSelect = document.querySelector<HTMLSelectElement>('[data-sort]');
|
const sortSelect = document.querySelector<HTMLSelectElement>('[data-sort]');
|
||||||
const hideOffline = document.querySelector<HTMLButtonElement>('[data-hide-offline]');
|
const hideOffline = document.querySelector<HTMLButtonElement>('[data-hide-offline]');
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ const schema = [
|
|||||||
{t('mints.showing', { total: mints.length, online, offline })}
|
{t('mints.showing', { total: mints.length, online, offline })}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="mint-grid" data-mint-grid="cashu">
|
<div class="mint-grid" data-mint-grid>
|
||||||
{mints.map((mint, i) => <MintCard mint={mint} rank={i + 1} capabilities={mint.capabilities} />)}
|
{mints.map((mint, i) => <MintCard mint={mint} rank={i + 1} capabilities={mint.capabilities} />)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -244,7 +244,7 @@ const schema = [
|
|||||||
// `t` formats its own numbers, so the grouping separator follows the locale too.
|
// `t` formats its own numbers, so the grouping separator follows the locale too.
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
const grid = document.querySelector<HTMLElement>('[data-mint-grid="cashu"]');
|
const grid = document.querySelector<HTMLElement>('[data-mint-grid]');
|
||||||
const searchInput = document.querySelector<HTMLInputElement>('[data-search-input]');
|
const searchInput = document.querySelector<HTMLInputElement>('[data-search-input]');
|
||||||
const sortSelect = document.querySelector<HTMLSelectElement>('[data-sort]');
|
const sortSelect = document.querySelector<HTMLSelectElement>('[data-sort]');
|
||||||
const hideOffline = document.querySelector<HTMLButtonElement>('[data-hide-offline]');
|
const hideOffline = document.querySelector<HTMLButtonElement>('[data-hide-offline]');
|
||||||
|
|||||||
@@ -208,15 +208,6 @@ export function armSharedTransitions(): void {
|
|||||||
* only does its work at module scope is dead after the first navigation. It does fire
|
* only does its work at module scope is dead after the first navigation. It does fire
|
||||||
* `astro:page-load` on every arrival, including the first, but on the first it waits
|
* `astro:page-load` on every arrival, including the first, but on the first it waits
|
||||||
* for `window.load`, which is far too late to be the only trigger. Hence both.
|
* for `window.load`, which is far too late to be the only trigger. Hence both.
|
||||||
*
|
|
||||||
* The listener is never removed, on purpose: a layout island (the language switcher,
|
|
||||||
* the login dialog) has DOM on every page, and this is what keeps it alive. The flip
|
|
||||||
* side is that `setup` runs on *every* page the router swaps in, not only pages that
|
|
||||||
* include the script. A page-level script must therefore look its DOM up by a marker
|
|
||||||
* unique to that page — `data-mint-grid="cashu"`, `data-home-grid="fedimint"` — and do
|
|
||||||
* nothing when the marker is absent. A marker shared between pages means a stale page's
|
|
||||||
* setup finds the current page's DOM and rewrites it: the listing pages once flashed
|
|
||||||
* back to the previous ecosystem's mints for exactly that reason.
|
|
||||||
*/
|
*/
|
||||||
export function onReady(setup: () => void): void {
|
export function onReady(setup: () => void): void {
|
||||||
let done = false;
|
let done = false;
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
/**
|
|
||||||
* The three listing pages must each own their grid.
|
|
||||||
*
|
|
||||||
* `<ClientRouter />` swaps pages without reloading, and `onReady` in `scripts/reveal.ts`
|
|
||||||
* re-runs every page's setup on every arrival — deliberately, since that is what keeps
|
|
||||||
* the layout islands alive. So once `/mints` has been visited, its setup also runs on
|
|
||||||
* `/fedimints`. If both pages mark their grid the same way, the stale cashu setup finds
|
|
||||||
* the fedimint grid, fetches `?type=cashu` and rewrites it: the page shows fedimints for
|
|
||||||
* a moment, then flashes back to cashu mints. That happened.
|
|
||||||
*
|
|
||||||
* The guard is that each page's marker carries its ecosystem, and its script only looks
|
|
||||||
* for its own. This checks the template, the query and the hydrate call agree, and that
|
|
||||||
* no bare `data-mint-grid` has crept back in. Plain node, source read as text, like
|
|
||||||
* `i18n-wiring.test.mjs`.
|
|
||||||
*/
|
|
||||||
import test from 'node:test';
|
|
||||||
import assert from 'node:assert/strict';
|
|
||||||
import { readFileSync } from 'node:fs';
|
|
||||||
import path from 'node:path';
|
|
||||||
import { fileURLToPath } from 'node:url';
|
|
||||||
|
|
||||||
const pagesDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../src/pages/[...locale]');
|
|
||||||
const read = (file) => readFileSync(path.join(pagesDir, file), 'utf8');
|
|
||||||
|
|
||||||
const LISTINGS = [
|
|
||||||
['mints.astro', 'cashu'],
|
|
||||||
['fedimints.astro', 'fedimint'],
|
|
||||||
['lnurl-mints.astro', 'lnurl'],
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const [file, type] of LISTINGS) {
|
|
||||||
test(`${file} marks, queries and hydrates its grid as ${type}`, () => {
|
|
||||||
const source = read(file);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
source,
|
|
||||||
new RegExp(`<div class="mint-grid" data-mint-grid="${type}">`),
|
|
||||||
`${file}: template grid is not marked data-mint-grid="${type}"`,
|
|
||||||
);
|
|
||||||
assert.match(
|
|
||||||
source,
|
|
||||||
new RegExp(`querySelector<HTMLElement>\\('\\[data-mint-grid="${type}"\\]'\\)`),
|
|
||||||
`${file}: script does not query [data-mint-grid="${type}"]`,
|
|
||||||
);
|
|
||||||
assert.match(
|
|
||||||
source,
|
|
||||||
new RegExp(`type: '${type}',`),
|
|
||||||
`${file}: hydrateMintGrid is not called with type '${type}'`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// A bare marker, in either the template or the query, is the bug coming back.
|
|
||||||
assert.doesNotMatch(source, /data-mint-grid[>\s]/, `${file}: bare data-mint-grid in template`);
|
|
||||||
assert.doesNotMatch(source, /\[data-mint-grid\]/, `${file}: bare [data-mint-grid] selector`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
test('the three listings use three different markers', () => {
|
|
||||||
const types = new Set(LISTINGS.map(([, type]) => type));
|
|
||||||
assert.equal(types.size, LISTINGS.length);
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user