@wxxb789/anc 0.0.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.
- package/LICENSE +21 -0
- package/README.md +295 -0
- package/astro.config.mjs +255 -0
- package/bin/anc.mjs +798 -0
- package/package.json +93 -0
- package/public/_headers +45 -0
- package/public/favicon.ico +0 -0
- package/public/favicon.svg +9 -0
- package/public/fonts/Temml.woff2 +0 -0
- package/scripts/build-snapshot.js +69 -0
- package/scripts/build-wasm.js +67 -0
- package/scripts/build-workspace.js +50 -0
- package/scripts/copy-snapshot.js +48 -0
- package/scripts/copy-wasm.js +57 -0
- package/scripts/emit-redirects.js +64 -0
- package/scripts/generate-corpus.js +421 -0
- package/scripts/init-repository.js +249 -0
- package/scripts/load-config.js +975 -0
- package/scripts/markdown-to-artifact.js +1286 -0
- package/scripts/preview-site.js +364 -0
- package/scripts/publish-set-review.js +157 -0
- package/scripts/resolve-links.js +640 -0
- package/scripts/run-pagefind.js +125 -0
- package/scripts/scan-residue.js +1016 -0
- package/scripts/scan-secrets.js +268 -0
- package/scripts/seed-gitignore.js +410 -0
- package/scripts/snapshot-rows.js +205 -0
- package/scripts/validate-content.js +95 -0
- package/scripts/vendor-provenance.js +307 -0
- package/scripts/verify-output-inventory.js +307 -0
- package/scripts/write-report.js +351 -0
- package/scripts/write-snapshot.js +136 -0
- package/src/components/CollectionExplorer.astro +149 -0
- package/src/components/FacetIndex.astro +45 -0
- package/src/components/LinkedNotes.astro +94 -0
- package/src/components/NoteGraph.astro +434 -0
- package/src/components/NoteList.astro +54 -0
- package/src/components/SiteMetadata.astro +143 -0
- package/src/components/TableOfContents.astro +69 -0
- package/src/components/TagBrowser.astro +70 -0
- package/src/layouts/Layout.astro +305 -0
- package/src/lib/artifact-source.js +72 -0
- package/src/lib/build-bindings.js +11 -0
- package/src/lib/collection-navigation.js +145 -0
- package/src/lib/content.js +92 -0
- package/src/lib/diagram-mode.js +18 -0
- package/src/lib/graph-layout.js +219 -0
- package/src/lib/graph-selection.js +84 -0
- package/src/lib/graph.js +42 -0
- package/src/lib/link-resolution.js +389 -0
- package/src/lib/markdown.js +1469 -0
- package/src/lib/math-mode.js +31 -0
- package/src/lib/math-output.js +131 -0
- package/src/lib/math.js +165 -0
- package/src/lib/mermaid-environment.js +475 -0
- package/src/lib/mermaid-render.js +769 -0
- package/src/lib/preview-model.js +96 -0
- package/src/lib/relations.js +242 -0
- package/src/lib/rendered-marker.js +55 -0
- package/src/lib/route-path.js +192 -0
- package/src/lib/routes.js +342 -0
- package/src/lib/schema.js +578 -0
- package/src/lib/site.js +578 -0
- package/src/lib/snapshot-contract.js +216 -0
- package/src/lib/snapshot-operations.js +147 -0
- package/src/lib/snapshot-queries.js +216 -0
- package/src/lib/snapshot-reader.js +210 -0
- package/src/lib/snapshot.js +233 -0
- package/src/lib/sqlite-warning.js +22 -0
- package/src/lib/sqlite.js +14 -0
- package/src/lib/tag-browser-model.js +18 -0
- package/src/lib/translations.js +533 -0
- package/src/lib/wasm-asset.js +47 -0
- package/src/lib/worker-protocol.js +93 -0
- package/src/pages/404.astro +35 -0
- package/src/pages/about.astro +100 -0
- package/src/pages/collections/[slug].astro +36 -0
- package/src/pages/collections/index.astro +33 -0
- package/src/pages/graph.astro +64 -0
- package/src/pages/index.astro +33 -0
- package/src/pages/notes/[slug].astro +509 -0
- package/src/pages/privacy.astro +121 -0
- package/src/pages/private.astro +59 -0
- package/src/pages/recent.astro +33 -0
- package/src/pages/robots.txt.js +15 -0
- package/src/pages/rss.xml.js +21 -0
- package/src/pages/sitemap.xml.js +20 -0
- package/src/pages/tags/[tag].astro +56 -0
- package/src/pages/tags/index.astro +37 -0
- package/src/scripts/code-copy.js +45 -0
- package/src/scripts/diagram-disabled.js +16 -0
- package/src/scripts/diagram.js +322 -0
- package/src/scripts/graph-client.js +222 -0
- package/src/scripts/graph-draw.js +166 -0
- package/src/scripts/link-preview.js +334 -0
- package/src/scripts/math-disabled.js +18 -0
- package/src/scripts/math.js +133 -0
- package/src/scripts/preferences.js +123 -0
- package/src/scripts/search-dialog.js +651 -0
- package/src/scripts/snapshot-client.js +157 -0
- package/src/scripts/snapshot-worker.js +341 -0
- package/src/scripts/tag-browser.js +153 -0
- package/src/scripts/theme-init.js +35 -0
- package/src/scripts/toc-active.js +38 -0
- package/src/styles/code.css +152 -0
- package/src/styles/diagram.css +149 -0
- package/src/styles/global.css +1739 -0
- package/src/styles/graph.css +317 -0
- package/src/styles/math.css +376 -0
- package/src/styles/tokens.css +171 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The lazy snapshot Worker's message contract.
|
|
3
|
+
*
|
|
4
|
+
* Pure and dependency-free so the Worker and the main-thread client import the
|
|
5
|
+
* same definition: a message the client can send is one the Worker can validate,
|
|
6
|
+
* and a result the Worker can return is one the client can type.
|
|
7
|
+
*
|
|
8
|
+
* The design constrains this shape (`docs/core-design/build-and-runtime.md`):
|
|
9
|
+
* messages carry a discriminated `type`, a request id, and operation-specific
|
|
10
|
+
* validated arguments. There is deliberately **no field that can carry SQL, a
|
|
11
|
+
* URL, a filesystem path, a table name, or a column name** — "named operations,
|
|
12
|
+
* not an SQL console" is structural here, not a convention enforced elsewhere.
|
|
13
|
+
*/
|
|
14
|
+
import { isLookupSlug, isLookupTagKey, normalizePageSize, } from "./snapshot-queries.js";
|
|
15
|
+
/**
|
|
16
|
+
* Finite provisional bounds. Goal 0003 establishes these on the correctness
|
|
17
|
+
* fixtures; goal 0008 must validate or replace them on the benchmark workloads.
|
|
18
|
+
* They are not a capacity claim.
|
|
19
|
+
*/
|
|
20
|
+
export const WORKER_LIMITS = {
|
|
21
|
+
/** Fetch + hash + WASM init + schema validation. */
|
|
22
|
+
startupDeadlineMs: 20_000,
|
|
23
|
+
/** One dispatched request to a validated result. */
|
|
24
|
+
requestDeadlineMs: 8_000,
|
|
25
|
+
/** Concurrent requests before a new one is rejected rather than queued. */
|
|
26
|
+
maxPendingRequests: 16,
|
|
27
|
+
/** Decoded snapshot bytes read before WASM allocation. */
|
|
28
|
+
maxSnapshotBytes: 64 * 1024 * 1024,
|
|
29
|
+
/** Decoded WASM bytes read before instantiation. */
|
|
30
|
+
maxWasmBytes: 8 * 1024 * 1024,
|
|
31
|
+
};
|
|
32
|
+
export const SNAPSHOT_OPERATIONS = [
|
|
33
|
+
'preview',
|
|
34
|
+
'backlinks',
|
|
35
|
+
'outgoing',
|
|
36
|
+
'byTag',
|
|
37
|
+
'localGraph',
|
|
38
|
+
'globalGraph',
|
|
39
|
+
];
|
|
40
|
+
/**
|
|
41
|
+
* Whether an untrusted value is a request the Worker may run.
|
|
42
|
+
*
|
|
43
|
+
* Checks the discriminator, the id, and the lookup-shape of every argument.
|
|
44
|
+
* Page size and cursor are validated here too; `normalizePageSize` clamps rather
|
|
45
|
+
* than rejects, which is the accepted bound ("cap and validate the positive
|
|
46
|
+
* integer page size").
|
|
47
|
+
*/
|
|
48
|
+
export function isSnapshotMessage(value) {
|
|
49
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
50
|
+
return false;
|
|
51
|
+
const message = value;
|
|
52
|
+
const id = message['id'];
|
|
53
|
+
if (typeof id !== 'number' || !Number.isInteger(id) || id <= 0)
|
|
54
|
+
return false;
|
|
55
|
+
const type = message['type'];
|
|
56
|
+
if (typeof type !== 'string' || !SNAPSHOT_OPERATIONS.includes(type))
|
|
57
|
+
return false;
|
|
58
|
+
// The instrumentation flag is optional, and only a boolean is a request the
|
|
59
|
+
// Worker may act on; any other value is refused rather than coerced. Checked
|
|
60
|
+
// before the per-operation branches because `preview`, `localGraph`, and
|
|
61
|
+
// `globalGraph` return from those branches, and an invalid flag must not slip
|
|
62
|
+
// past one of them. Unrelated extra keys stay ignored, as before.
|
|
63
|
+
const measure = message['measure'];
|
|
64
|
+
if (measure !== undefined && typeof measure !== 'boolean')
|
|
65
|
+
return false;
|
|
66
|
+
if (type === 'preview' || type === 'localGraph')
|
|
67
|
+
return isLookupSlug(message['slug']);
|
|
68
|
+
if (type === 'globalGraph') {
|
|
69
|
+
return message['tagKey'] === undefined || message['tagKey'] === null || isLookupTagKey(message['tagKey']);
|
|
70
|
+
}
|
|
71
|
+
if (type === 'byTag') {
|
|
72
|
+
if (!isLookupTagKey(message['tagKey']))
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
else if (!isLookupSlug(message['slug'])) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
const cursor = message['cursor'];
|
|
79
|
+
if (cursor !== undefined && cursor !== null && !isLookupSlug(cursor))
|
|
80
|
+
return false;
|
|
81
|
+
const pageSize = message['pageSize'];
|
|
82
|
+
if (pageSize !== undefined && typeof pageSize !== 'number')
|
|
83
|
+
return false;
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
/** The page size a validated request resolves to, clamped to the accepted range. */
|
|
87
|
+
export function requestPageSize(message) {
|
|
88
|
+
return normalizePageSize(message['pageSize']);
|
|
89
|
+
}
|
|
90
|
+
/** Narrow a reply to the result type a caller asked for. */
|
|
91
|
+
export function isResultOf(reply, type) {
|
|
92
|
+
return reply.ok && reply.result.type === type;
|
|
93
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Static 404.
|
|
4
|
+
*
|
|
5
|
+
* Astro emits `src/pages/404.astro` as `dist/404.html`, which Cloudflare Pages
|
|
6
|
+
* serves for any unmatched request. It is a real page in the layout shell, not
|
|
7
|
+
* a bare message: a reader who lands here still has the full site navigation.
|
|
8
|
+
*
|
|
9
|
+
* The routes offered are the site's own fixed routes. Nothing here reports what
|
|
10
|
+
* was requested — echoing the URL back into the page is how a 404 becomes a
|
|
11
|
+
* reflected-content sink, and it would tell a visitor nothing they did not type.
|
|
12
|
+
*/
|
|
13
|
+
import Layout from '../layouts/Layout.astro';
|
|
14
|
+
import { SITE_MAP } from '../lib/routes.js';
|
|
15
|
+
import { TITLE_SUFFIX } from '../lib/site.js';
|
|
16
|
+
import { NAV_LANGUAGE, translate } from '../lib/translations.js';
|
|
17
|
+
|
|
18
|
+
// The one route with no document behind it at all: the host serves this body
|
|
19
|
+
// for any unmatched address, so there is nothing whose language could be read.
|
|
20
|
+
const t = translate(NAV_LANGUAGE);
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
<Layout title={`${t.notFoundTitle}${TITLE_SUFFIX}`} description={t.notFoundDescription}>
|
|
24
|
+
<article class="prose">
|
|
25
|
+
<h1>{t.notFoundTitle}</h1>
|
|
26
|
+
|
|
27
|
+
<p>{t.notFoundBody}</p>
|
|
28
|
+
|
|
29
|
+
<h2>{t.notFoundWhereTo}</h2>
|
|
30
|
+
|
|
31
|
+
<ul>
|
|
32
|
+
{SITE_MAP.map((item) => <li><a href={item.href}>{t[item.label]}</a></li>)}
|
|
33
|
+
</ul>
|
|
34
|
+
</article>
|
|
35
|
+
</Layout>
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* What this site is, for a reader who did not build it.
|
|
4
|
+
*
|
|
5
|
+
* **Rewritten by TK-31, because every substantive claim it made was false.**
|
|
6
|
+
* The previous version told a reader that "every page here is a note that was
|
|
7
|
+
* reviewed and explicitly approved for publication, one item at a time" and
|
|
8
|
+
* that a note "appears here only because it was named on an explicit approval
|
|
9
|
+
* list". That described the retired owner-specific bridge. This tool publishes a directory
|
|
10
|
+
* of Markdown by default and withholds what the user's `publish.config.yaml`
|
|
11
|
+
* excludes — the opposite arrangement — so the sentence a reader consults *to
|
|
12
|
+
* find out whether the boundary is trustworthy* was the sentence that lied
|
|
13
|
+
* about which direction it runs.
|
|
14
|
+
*
|
|
15
|
+
* **A shipped page rather than a Markdown note the user seeds.** The plan
|
|
16
|
+
* (§4.4) proposes deleting both prose pages and having `init` write them into
|
|
17
|
+
* the notes repository. That is the better end state and it is not reachable
|
|
18
|
+
* from here: `init` does not exist yet — it is TK-32's — and deleting the pages
|
|
19
|
+
* now would remove `/about/` and `/privacy/` from `SITE_MAP` and from three
|
|
20
|
+
* gates, leaving a build with no privacy statement at all in the interval. A
|
|
21
|
+
* page that is true is worth more than a page that is absent, and the sentences
|
|
22
|
+
* below are true of *every* build this tool produces rather than of one corpus.
|
|
23
|
+
*
|
|
24
|
+
* **So it says only what the tool guarantees.** Every claim here is a property
|
|
25
|
+
* of the generator — static output, no analytics, no third-party requests,
|
|
26
|
+
* exclusion by configuration — and none of it describes the user's own
|
|
27
|
+
* editorial process, which this repository cannot know and must not narrate on
|
|
28
|
+
* their behalf. Where the honest answer depends on the operator, it says so and
|
|
29
|
+
* points at them rather than inventing a policy.
|
|
30
|
+
*
|
|
31
|
+
* The prose is in the navigation language and is not translated, which is the
|
|
32
|
+
* same treatment `tests/translations.test.ts` records for this file: it is a
|
|
33
|
+
* document rather than chrome, and translating a document is a content decision
|
|
34
|
+
* this ticket does not own.
|
|
35
|
+
*
|
|
36
|
+
* **The withheld-link paragraph was rewritten on 2026-08-17** and it is the one
|
|
37
|
+
* claim here a reader could previously have been misled by. It said a link to an
|
|
38
|
+
* unpublished note "is not rendered as a link" and that "their content, their
|
|
39
|
+
* number, or that any exist" is not represented. Both became false when the
|
|
40
|
+
* owner made those links live: they are anchors now, they land on `/private/`,
|
|
41
|
+
* and a link's text is routinely the note's own path — so a reader can count
|
|
42
|
+
* them and read where they lived. The paragraph says that plainly, because a
|
|
43
|
+
* reader consulting this page to calibrate trust is exactly the person the
|
|
44
|
+
* omission would cost.
|
|
45
|
+
*/
|
|
46
|
+
import Layout from '../layouts/Layout.astro';
|
|
47
|
+
import { DEFAULT_NAV_LANGUAGE } from '../lib/translations.js';
|
|
48
|
+
import { SITE_NAME, TITLE_SUFFIX } from '../lib/site.js';
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
{/* The prose below is English whatever the site language, so the page says so. */}
|
|
52
|
+
<Layout
|
|
53
|
+
title={`About${TITLE_SUFFIX}`}
|
|
54
|
+
lang={DEFAULT_NAV_LANGUAGE}
|
|
55
|
+
description="What this site is, and how its pages came to be here."
|
|
56
|
+
>
|
|
57
|
+
<article class="prose">
|
|
58
|
+
<h1>About</h1>
|
|
59
|
+
|
|
60
|
+
<p>
|
|
61
|
+
{SITE_NAME} is a reading site. Each page is a note written in Markdown and published as a
|
|
62
|
+
static page, generated ahead of time rather than assembled when you ask for it.
|
|
63
|
+
</p>
|
|
64
|
+
|
|
65
|
+
<h2>How pages get here</h2>
|
|
66
|
+
|
|
67
|
+
<p>
|
|
68
|
+
The site is built from a set of notes written in Markdown. Publication is the default: a note
|
|
69
|
+
becomes a page unless it was explicitly excluded, and the person who runs the build chooses
|
|
70
|
+
what to exclude by listing patterns in a configuration file, or by marking an individual note.
|
|
71
|
+
An excluded note produces no page, no search entry, and none of its content appears here.
|
|
72
|
+
</p>
|
|
73
|
+
|
|
74
|
+
<p>
|
|
75
|
+
Links between notes are resolved when the site is built. A link whose target was not
|
|
76
|
+
published points at a page saying so, rather than at the note — so following one cannot
|
|
77
|
+
lead to something that is not here.
|
|
78
|
+
</p>
|
|
79
|
+
|
|
80
|
+
<p>
|
|
81
|
+
One thing this site does still show you about an unpublished note is the words a published
|
|
82
|
+
note used to refer to it. That link keeps its text exactly as the author wrote it, which
|
|
83
|
+
often means the note's own name and the path it had among the author's notes. It is the
|
|
84
|
+
author's writing rather than anything read from the note it names, and nothing else about
|
|
85
|
+
unpublished notes — their content, their titles, their number — is represented here.
|
|
86
|
+
</p>
|
|
87
|
+
|
|
88
|
+
<h2>How it is built</h2>
|
|
89
|
+
|
|
90
|
+
<p>
|
|
91
|
+
Every page is generated ahead of time and served as a static file. There is no server-side
|
|
92
|
+
application, no database behind a page view, and no account system. Reading, navigating,
|
|
93
|
+
following links, and browsing backlinks all work with JavaScript turned off.
|
|
94
|
+
</p>
|
|
95
|
+
|
|
96
|
+
<p>
|
|
97
|
+
For what this site does and does not collect, see <a href="/privacy/">Privacy</a>.
|
|
98
|
+
</p>
|
|
99
|
+
</article>
|
|
100
|
+
</Layout>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* `/collections/<key>/` — the notes in one flat first-folder collection.
|
|
4
|
+
*
|
|
5
|
+
* A facet aggregates many documents, so its chrome is the navigation language
|
|
6
|
+
* rather than any member's; see `src/lib/translations.ts`. The collection name
|
|
7
|
+
* is artifact text, interpolated unchanged.
|
|
8
|
+
*/
|
|
9
|
+
import Layout from '../../layouts/Layout.astro';
|
|
10
|
+
import NoteList from '../../components/NoteList.astro';
|
|
11
|
+
import { entries } from '../../lib/content.js';
|
|
12
|
+
import { collectionFacets } from '../../lib/routes.js';
|
|
13
|
+
import { TITLE_SUFFIX } from '../../lib/site.js';
|
|
14
|
+
import { NAV_LANGUAGE, translate } from '../../lib/translations.js';
|
|
15
|
+
|
|
16
|
+
export function getStaticPaths() {
|
|
17
|
+
return collectionFacets(entries).map((facet) => ({ params: { slug: facet.key }, props: { facet } }));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { facet } = Astro.props;
|
|
21
|
+
const t = translate(NAV_LANGUAGE);
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
<Layout
|
|
25
|
+
title={`${t.collectionPageTitle(facet.label)}${TITLE_SUFFIX}`}
|
|
26
|
+
description={t.collectionPageDescription(facet.label)}
|
|
27
|
+
>
|
|
28
|
+
<section aria-labelledby="collection-title">
|
|
29
|
+
<div class="section-heading">
|
|
30
|
+
<h1 id="collection-title">{t.collectionPageTitle(facet.label)}</h1>
|
|
31
|
+
<span>{t.noteCount(facet.entries.length)}</span>
|
|
32
|
+
</div>
|
|
33
|
+
<p class="facet-note"><a href="/collections/">{t.allCollections}</a></p>
|
|
34
|
+
<NoteList entries={facet.entries} level={2} lang={NAV_LANGUAGE} />
|
|
35
|
+
</section>
|
|
36
|
+
</Layout>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* `/collections/` — the curated public collections.
|
|
4
|
+
*
|
|
5
|
+
* A corpus route, so the chrome is the navigation language. The collection
|
|
6
|
+
* labels are artifact text and are never translated.
|
|
7
|
+
*/
|
|
8
|
+
import Layout from '../../layouts/Layout.astro';
|
|
9
|
+
import FacetIndex from '../../components/FacetIndex.astro';
|
|
10
|
+
import { entries } from '../../lib/content.js';
|
|
11
|
+
import { collectionFacets, collectionRoute } from '../../lib/routes.js';
|
|
12
|
+
import { TITLE_SUFFIX } from '../../lib/site.js';
|
|
13
|
+
import { NAV_LANGUAGE, translate } from '../../lib/translations.js';
|
|
14
|
+
|
|
15
|
+
const t = translate(NAV_LANGUAGE);
|
|
16
|
+
const facets = collectionFacets(entries);
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
<Layout title={`${t.collectionsTitle}${TITLE_SUFFIX}`} description={t.collectionsDescription}>
|
|
20
|
+
<section aria-labelledby="collections-title">
|
|
21
|
+
<div class="section-heading">
|
|
22
|
+
<h1 id="collections-title">{t.collectionsTitle}</h1>
|
|
23
|
+
<span>{t.collectionCount(facets.length)}</span>
|
|
24
|
+
</div>
|
|
25
|
+
<p class="facet-note">{t.collectionsNote}</p>
|
|
26
|
+
<FacetIndex
|
|
27
|
+
facets={facets}
|
|
28
|
+
route={collectionRoute}
|
|
29
|
+
empty={t.collectionsEmpty}
|
|
30
|
+
lang={NAV_LANGUAGE}
|
|
31
|
+
/>
|
|
32
|
+
</section>
|
|
33
|
+
</Layout>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* `/graph/` — the site-wide graph, requirements section 9.1's reserved route.
|
|
4
|
+
*
|
|
5
|
+
* A corpus route rather than a document route, so its chrome is the navigation
|
|
6
|
+
* language. The note titles it draws are their authors' own words and carry
|
|
7
|
+
* their own `lang`; `NoteGraph` handles that.
|
|
8
|
+
*
|
|
9
|
+
* Requirements section 13.3 admits a global graph only on four conditions, and
|
|
10
|
+
* this page meets them by construction rather than by intent:
|
|
11
|
+
*
|
|
12
|
+
* - *Legible at current node count* — `GLOBAL_NODE_LIMIT` bounds the figure, the
|
|
13
|
+
* bound is stated on the page rather than applied silently, and the figure
|
|
14
|
+
* scrolls below `min-width` rather than shrinking its labels past reading size.
|
|
15
|
+
* - *Keyboard-accessible, or an equivalent searchable list* — keyboard: every
|
|
16
|
+
* node is a real `<a>` in the tab order, and the table under the figure lists
|
|
17
|
+
* the same notes with the notes each is linked to. It is **not** in the site
|
|
18
|
+
* search index: only `/notes/<slug>/` carries `data-pagefind-body`, so
|
|
19
|
+
* Pagefind indexes note bodies alone. The requirement is satisfied by the
|
|
20
|
+
* keyboard half, which is why that is the half this says.
|
|
21
|
+
* - *JS and database budgets* — there is no JavaScript and no database. TK-21
|
|
22
|
+
* owns the layer that would have either, and it enhances this rather than
|
|
23
|
+
* replacing it.
|
|
24
|
+
* - *Decision value beyond visual novelty* — the ranked packing puts the most
|
|
25
|
+
* linked notes at the centre, so the figure answers "what is this site mostly
|
|
26
|
+
* about" at a glance, and the table answers it exactly.
|
|
27
|
+
*/
|
|
28
|
+
import Layout from '../layouts/Layout.astro';
|
|
29
|
+
import NoteGraph from '../components/NoteGraph.astro';
|
|
30
|
+
import { entries, tagFacets } from '../lib/content.js';
|
|
31
|
+
import { globalGraph, hasDrawableGraph } from '../lib/graph.js';
|
|
32
|
+
import { TITLE_SUFFIX } from '../lib/site.js';
|
|
33
|
+
import { NAV_LANGUAGE, translate } from '../lib/translations.js';
|
|
34
|
+
// Loaded as a URL rather than imported, so the stylesheet becomes its own
|
|
35
|
+
// content-hashed asset and the `<link>` below can be emitted conditionally —
|
|
36
|
+
// owner decision 7. A page with nothing to draw requests nothing.
|
|
37
|
+
import graphStylesheet from '../styles/graph.css?url';
|
|
38
|
+
|
|
39
|
+
const t = translate(NAV_LANGUAGE);
|
|
40
|
+
const graph = globalGraph(entries);
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
<Layout title={`${t.graphTitle}${TITLE_SUFFIX}`} description={t.graphDescription}>
|
|
44
|
+
{/* The same predicate the component branches on, so the stylesheet and the
|
|
45
|
+
figure can never disagree about whether there is a graph. */}
|
|
46
|
+
{hasDrawableGraph(graph) && <link slot="head" rel="stylesheet" href={graphStylesheet} />}
|
|
47
|
+
|
|
48
|
+
<NoteGraph
|
|
49
|
+
graph={graph}
|
|
50
|
+
region="site-graph"
|
|
51
|
+
level={1}
|
|
52
|
+
heading={t.graphTitle}
|
|
53
|
+
derivation={t.graphGlobalDerivation}
|
|
54
|
+
empty={t.graphSiteEmpty}
|
|
55
|
+
bounded={t.graphBounded}
|
|
56
|
+
expand={{ href: '/#notes', text: t.graphExpand, label: t.graphExpandLabel }}
|
|
57
|
+
lang={NAV_LANGUAGE}
|
|
58
|
+
explore={{
|
|
59
|
+
scope: 'global',
|
|
60
|
+
centerSlug: null,
|
|
61
|
+
tags: tagFacets().map((facet) => ({ key: facet.key, label: facet.label })),
|
|
62
|
+
}}
|
|
63
|
+
/>
|
|
64
|
+
</Layout>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* The home page.
|
|
4
|
+
*
|
|
5
|
+
* Not one document but the whole corpus, so its chrome is the navigation
|
|
6
|
+
* language — see `src/lib/translations.ts` for the rule, and for why the
|
|
7
|
+
* alternative of borrowing some member's language would make a public page
|
|
8
|
+
* change language because an unrelated note was published.
|
|
9
|
+
*/
|
|
10
|
+
import Layout from '../layouts/Layout.astro';
|
|
11
|
+
import NoteList from '../components/NoteList.astro';
|
|
12
|
+
import { entries } from '../lib/content.js';
|
|
13
|
+
import { SITE_NAME } from '../lib/site.js';
|
|
14
|
+
import { NAV_LANGUAGE, translate } from '../lib/translations.js';
|
|
15
|
+
|
|
16
|
+
const t = translate(NAV_LANGUAGE);
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
<Layout title={SITE_NAME} description={t.homeDescription}>
|
|
20
|
+
<section class="hero">
|
|
21
|
+
<p class="eyebrow">{t.homeEyebrow}</p>
|
|
22
|
+
<h1>{t.homeHeading}</h1>
|
|
23
|
+
<p>{t.homeIntro}</p>
|
|
24
|
+
</section>
|
|
25
|
+
|
|
26
|
+
<section id="notes" aria-labelledby="notes-title">
|
|
27
|
+
<div class="section-heading">
|
|
28
|
+
<h2 id="notes-title">{t.homeNotesHeading}</h2>
|
|
29
|
+
<span>{t.publishedCount(entries.length)}</span>
|
|
30
|
+
</div>
|
|
31
|
+
<NoteList entries={entries} lang={NAV_LANGUAGE} />
|
|
32
|
+
</section>
|
|
33
|
+
</Layout>
|