create-eziwiki 0.2.0 → 0.3.0
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/package.json +1 -1
- package/template/app/[...slug]/page.tsx +56 -2
- package/template/components/layout/MovedPage.tsx +45 -0
- package/template/components/layout/PageNavigation.tsx +73 -0
- package/template/lib/content/aliases.test.ts +76 -0
- package/template/lib/content/aliases.ts +114 -0
- package/template/lib/content/registry.ts +31 -0
- package/template/lib/graph/health.test.ts +60 -0
- package/template/lib/graph/health.ts +58 -0
- package/template/lib/markdown/callout.test.ts +87 -0
- package/template/lib/markdown/mermaid.test.ts +72 -0
- package/template/lib/markdown/rehype-mermaid.ts +133 -0
- package/template/lib/markdown/rehype-plugins.ts +45 -0
- package/template/lib/markdown/remark-callout.ts +173 -0
- package/template/lib/markdown/render.ts +15 -1
- package/template/lib/navigation/sequence.test.ts +73 -0
- package/template/lib/navigation/sequence.ts +100 -0
- package/template/package-lock.json +29 -0
- package/template/package.json +1 -0
- package/template/scripts/check-links.ts +60 -18
- package/template/styles/markdown.css +157 -0
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"@shikijs/rehype": "^4.3.1",
|
|
13
13
|
"ajv": "^8.12.0",
|
|
14
14
|
"ajv-formats": "^3.0.1",
|
|
15
|
+
"beautiful-mermaid": "^1.1.3",
|
|
15
16
|
"github-slugger": "^2.0.0",
|
|
16
17
|
"gray-matter": "^4.0.3",
|
|
17
18
|
"hast-util-to-string": "^3.0.1",
|
|
@@ -2655,6 +2656,28 @@
|
|
|
2655
2656
|
"baseline-browser-mapping": "dist/cli.js"
|
|
2656
2657
|
}
|
|
2657
2658
|
},
|
|
2659
|
+
"node_modules/beautiful-mermaid": {
|
|
2660
|
+
"version": "1.1.3",
|
|
2661
|
+
"resolved": "https://registry.npmjs.org/beautiful-mermaid/-/beautiful-mermaid-1.1.3.tgz",
|
|
2662
|
+
"integrity": "sha512-TItrtrAyHp1vwFfFVYauWGrquouk/6SS21Aq3RsxindSYZODcN4xYrPZD6BiZRU+o5mKJzDPz9MUSMvELdylyg==",
|
|
2663
|
+
"license": "MIT",
|
|
2664
|
+
"dependencies": {
|
|
2665
|
+
"elkjs": "^0.11.0",
|
|
2666
|
+
"entities": "^7.0.1"
|
|
2667
|
+
}
|
|
2668
|
+
},
|
|
2669
|
+
"node_modules/beautiful-mermaid/node_modules/entities": {
|
|
2670
|
+
"version": "7.0.1",
|
|
2671
|
+
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
|
|
2672
|
+
"integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
|
|
2673
|
+
"license": "BSD-2-Clause",
|
|
2674
|
+
"engines": {
|
|
2675
|
+
"node": ">=0.12"
|
|
2676
|
+
},
|
|
2677
|
+
"funding": {
|
|
2678
|
+
"url": "https://github.com/fb55/entities?sponsor=1"
|
|
2679
|
+
}
|
|
2680
|
+
},
|
|
2658
2681
|
"node_modules/binary-extensions": {
|
|
2659
2682
|
"version": "2.3.0",
|
|
2660
2683
|
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
|
@@ -3244,6 +3267,12 @@
|
|
|
3244
3267
|
"dev": true,
|
|
3245
3268
|
"license": "ISC"
|
|
3246
3269
|
},
|
|
3270
|
+
"node_modules/elkjs": {
|
|
3271
|
+
"version": "0.11.1",
|
|
3272
|
+
"resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.11.1.tgz",
|
|
3273
|
+
"integrity": "sha512-zxxR9k+rx5ktMwT/FwyLdPCrq7xN6e4VGGHH8hA01vVYKjTFik7nHOxBnAYtrgYUB1RpAiLvA1/U2YraWxyKKg==",
|
|
3274
|
+
"license": "EPL-2.0"
|
|
3275
|
+
},
|
|
3247
3276
|
"node_modules/emoji-regex": {
|
|
3248
3277
|
"version": "9.2.2",
|
|
3249
3278
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
package/template/package.json
CHANGED
|
@@ -1,40 +1,82 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Reports
|
|
4
|
+
* Reports on the shape of the link graph.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* Two kinds of finding, treated differently. An unresolved link is an error in
|
|
7
|
+
* the content and `--strict` makes it fail the build, which is what CI passes.
|
|
8
|
+
* A page nothing links to, or nothing links on from, is not an error — a
|
|
9
|
+
* correct wiki can have either — so those are reported and never fail. They are
|
|
10
|
+
* the shapes a set of documents falls into as it stops being a wiki, and
|
|
11
|
+
* neither is visible from inside a single page.
|
|
12
|
+
*
|
|
13
|
+
* Reporting rather than failing on unresolved links by default is deliberate
|
|
14
|
+
* too: a dangling link in one page is not a reason to block a deploy of the
|
|
15
|
+
* other twenty, and content is often written before the page it references.
|
|
10
16
|
*/
|
|
11
17
|
|
|
12
18
|
import { getLinkGraph } from '../lib/graph/build';
|
|
19
|
+
import { getWikiHealth } from '../lib/graph/health';
|
|
20
|
+
import type { GraphNode } from '../lib/graph/build';
|
|
13
21
|
|
|
14
22
|
const strict = process.argv.includes('--strict');
|
|
15
23
|
const { broken, nodes, edges } = getLinkGraph();
|
|
16
24
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Prints a list of pages under a heading, or nothing when there are none.
|
|
27
|
+
*
|
|
28
|
+
* @param label - What the pages have in common, singular and plural
|
|
29
|
+
* @param explanation - Why it is worth knowing
|
|
30
|
+
* @param pages - The pages found
|
|
31
|
+
*/
|
|
32
|
+
function report(
|
|
33
|
+
label: { one: string; many: string },
|
|
34
|
+
explanation: string,
|
|
35
|
+
pages: GraphNode[],
|
|
36
|
+
): void {
|
|
37
|
+
if (pages.length === 0) return;
|
|
38
|
+
|
|
39
|
+
const noun = pages.length === 1 ? label.one : label.many;
|
|
40
|
+
|
|
41
|
+
console.log(`⚠️ ${pages.length} ${noun} — ${explanation}`);
|
|
42
|
+
for (const page of pages) console.log(` content/${page.path}.md`);
|
|
43
|
+
console.log();
|
|
20
44
|
}
|
|
21
45
|
|
|
22
|
-
|
|
46
|
+
if (broken.length > 0) {
|
|
47
|
+
console.log(`\n🔗 ${broken.length} unresolved link${broken.length === 1 ? '' : 's'}:\n`);
|
|
23
48
|
|
|
24
|
-
for (const link of broken) {
|
|
25
|
-
|
|
49
|
+
for (const link of broken) {
|
|
50
|
+
console.log(` content/${link.from}.md`);
|
|
26
51
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
52
|
+
if (link.reason === 'ambiguous') {
|
|
53
|
+
console.log(` [[${link.target}]] is ambiguous — matches ${link.candidates?.join(', ')}`);
|
|
54
|
+
console.log(' Use the full path to disambiguate.');
|
|
55
|
+
} else {
|
|
56
|
+
console.log(` [[${link.target}]] matches no page`);
|
|
57
|
+
}
|
|
32
58
|
}
|
|
59
|
+
|
|
60
|
+
console.log();
|
|
61
|
+
} else {
|
|
62
|
+
console.log(`\n🔗 Links OK — ${edges.length} links across ${nodes.length} pages\n`);
|
|
33
63
|
}
|
|
34
64
|
|
|
35
|
-
|
|
65
|
+
const { orphans, deadEnds } = getWikiHealth();
|
|
66
|
+
|
|
67
|
+
report(
|
|
68
|
+
{ one: 'orphaned page', many: 'orphaned pages' },
|
|
69
|
+
'nothing links here, so a reader can only arrive from the sidebar',
|
|
70
|
+
orphans,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
report(
|
|
74
|
+
{ one: 'dead end', many: 'dead ends' },
|
|
75
|
+
'no links out, so a reader arrives with nowhere to go',
|
|
76
|
+
deadEnds,
|
|
77
|
+
);
|
|
36
78
|
|
|
37
|
-
if (strict) {
|
|
79
|
+
if (broken.length > 0 && strict) {
|
|
38
80
|
console.error('❌ Failing because --strict was passed.\n');
|
|
39
81
|
process.exit(1);
|
|
40
82
|
}
|
|
@@ -239,3 +239,160 @@ html.dark .ezw-broken-link {
|
|
|
239
239
|
}
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
|
+
|
|
243
|
+
/* Callouts from `> [!NOTE]`, GitHub's and Obsidian's shared syntax.
|
|
244
|
+
Distinguished by a coloured edge and a heading rather than an icon: no glyph
|
|
245
|
+
to load, and the meaning survives when colour alone is not perceived. */
|
|
246
|
+
.ezw-callout {
|
|
247
|
+
--ezw-callout-accent: var(--color-text-muted);
|
|
248
|
+
margin: 1.5rem 0;
|
|
249
|
+
padding: 0.875rem 1.125rem;
|
|
250
|
+
border: 1px solid var(--color-border);
|
|
251
|
+
border-left: 3px solid var(--ezw-callout-accent);
|
|
252
|
+
border-radius: 0.375rem;
|
|
253
|
+
background: var(--color-sidebar-bg);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.ezw-callout__title {
|
|
257
|
+
margin: 0 0 0.5rem;
|
|
258
|
+
font-size: 0.8125rem;
|
|
259
|
+
font-weight: 600;
|
|
260
|
+
letter-spacing: 0.01em;
|
|
261
|
+
color: var(--ezw-callout-accent);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/* The body's first block sits against the title, and the last against the
|
|
265
|
+
floor, so a one-paragraph callout is not padded twice over. */
|
|
266
|
+
.ezw-callout > :nth-child(2) {
|
|
267
|
+
margin-top: 0;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.ezw-callout > :last-child {
|
|
271
|
+
margin-bottom: 0;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/* A foldable callout is a <details>; the marker is dropped in favour of the
|
|
275
|
+
title carrying the affordance, and the cursor says it is interactive. */
|
|
276
|
+
summary.ezw-callout__title {
|
|
277
|
+
cursor: pointer;
|
|
278
|
+
list-style: none;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
summary.ezw-callout__title::-webkit-details-marker {
|
|
282
|
+
display: none;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
summary.ezw-callout__title::before {
|
|
286
|
+
content: '▸';
|
|
287
|
+
display: inline-block;
|
|
288
|
+
/* A trailing space in `content` collapses, leaving the marker against the
|
|
289
|
+
title, so the gap is a margin. */
|
|
290
|
+
margin-right: 0.4em;
|
|
291
|
+
transition: transform 120ms ease;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
details[open] > summary.ezw-callout__title::before {
|
|
295
|
+
transform: rotate(90deg);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
details.ezw-callout:not([open]) > summary.ezw-callout__title {
|
|
299
|
+
margin-bottom: 0;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.ezw-callout--note {
|
|
303
|
+
--ezw-callout-accent: #1d4ed8;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.ezw-callout--tip {
|
|
307
|
+
--ezw-callout-accent: #047857;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.ezw-callout--important {
|
|
311
|
+
--ezw-callout-accent: #6d28d9;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.ezw-callout--warning {
|
|
315
|
+
--ezw-callout-accent: #b45309;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.ezw-callout--caution {
|
|
319
|
+
--ezw-callout-accent: #b91c1c;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.dark .ezw-callout--note {
|
|
323
|
+
--ezw-callout-accent: #93c5fd;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.dark .ezw-callout--tip {
|
|
327
|
+
--ezw-callout-accent: #6ee7b7;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.dark .ezw-callout--important {
|
|
331
|
+
--ezw-callout-accent: #c4b5fd;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.dark .ezw-callout--warning {
|
|
335
|
+
--ezw-callout-accent: #fcd34d;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.dark .ezw-callout--caution {
|
|
339
|
+
--ezw-callout-accent: #fca5a5;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/* A link on each heading pointing at itself, so a section can be shared
|
|
343
|
+
without reading the id out of the address bar. */
|
|
344
|
+
.ezw-heading {
|
|
345
|
+
position: relative;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.ezw-heading__anchor {
|
|
349
|
+
margin-left: 0.4em;
|
|
350
|
+
font-weight: 400;
|
|
351
|
+
color: var(--color-text-muted);
|
|
352
|
+
text-decoration: none;
|
|
353
|
+
/* Hidden until wanted, but only visually: it stays in the accessible tree
|
|
354
|
+
and in the tab order, so a keyboard or screen reader user can reach it. */
|
|
355
|
+
opacity: 0;
|
|
356
|
+
transition: opacity 120ms ease;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.ezw-heading:hover .ezw-heading__anchor,
|
|
360
|
+
.ezw-heading__anchor:focus-visible {
|
|
361
|
+
opacity: 1;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.ezw-heading__anchor:hover {
|
|
365
|
+
color: var(--color-text);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/* A pointer is not always available. On touch the anchor is simply shown,
|
|
369
|
+
since there is no hover to reveal it with. */
|
|
370
|
+
@media (hover: none) {
|
|
371
|
+
.ezw-heading__anchor {
|
|
372
|
+
opacity: 0.5;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/* Diagrams from ```mermaid fences, drawn during the build.
|
|
377
|
+
The SVG refers to --bg and --fg but no longer fixes them inline, so the
|
|
378
|
+
colours come from here and follow the theme — the same arrangement the
|
|
379
|
+
syntax highlighter uses for code. */
|
|
380
|
+
.ezw-mermaid {
|
|
381
|
+
--bg: transparent;
|
|
382
|
+
--fg: var(--color-text);
|
|
383
|
+
--line: var(--color-text-muted);
|
|
384
|
+
--accent: var(--color-primary, #2563eb);
|
|
385
|
+
--muted: var(--color-text-muted);
|
|
386
|
+
--surface: var(--color-code-bg);
|
|
387
|
+
--border: var(--color-border);
|
|
388
|
+
margin: 1.5rem 0;
|
|
389
|
+
/* A wide diagram scrolls inside its own box rather than widening the page. */
|
|
390
|
+
overflow-x: auto;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.ezw-mermaid svg {
|
|
394
|
+
display: block;
|
|
395
|
+
max-width: 100%;
|
|
396
|
+
height: auto;
|
|
397
|
+
margin-inline: auto;
|
|
398
|
+
}
|