@geekity/cms 0.10.0 → 0.11.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/README.md +15 -3
- package/admin/components/fields.njk +55 -12
- package/admin/layouts/base.njk +5 -1
- package/admin/layouts/settings-page.njk +7 -2
- package/admin/layouts/shell.njk +1 -1
- package/admin/pages/account/forgot.njk +1 -1
- package/admin/pages/account/login.njk +1 -1
- package/admin/pages/account/reset.njk +4 -2
- package/admin/pages/account/setup.njk +1 -1
- package/admin/pages/dashboard/home.njk +1 -0
- package/admin/pages/documents/list.njk +1 -0
- package/admin/pages/documents/taxonomy.njk +1 -0
- package/admin/pages/federation/followers.njk +3 -0
- package/admin/pages/federation/settings.njk +5 -0
- package/admin/pages/media/library.njk +39 -0
- package/admin/pages/navigation/menus.njk +6 -1
- package/admin/pages/settings/discussion.njk +7 -0
- package/admin/pages/settings/email.njk +10 -0
- package/admin/pages/settings/general.njk +7 -0
- package/admin/pages/settings/permalinks.njk +6 -0
- package/admin/pages/settings/reading.njk +7 -0
- package/admin/pages/tools/content-index.njk +1 -0
- package/admin/pages/tools/personal-data.njk +5 -0
- package/admin/pages/users/edit.njk +7 -5
- package/admin/pages/users/list.njk +1 -0
- package/admin/pages/users/new.njk +5 -1
- package/admin/static/admin-bar.css +16 -0
- package/admin/static/admin.css +103 -3
- package/dist/admin/documents.d.ts.map +1 -1
- package/dist/admin/documents.js +18 -0
- package/dist/admin/documents.js.map +1 -1
- package/dist/admin/media.d.ts +13 -2
- package/dist/admin/media.d.ts.map +1 -1
- package/dist/admin/media.js +58 -3
- package/dist/admin/media.js.map +1 -1
- package/dist/admin/store.d.ts +1 -1
- package/dist/admin/store.d.ts.map +1 -1
- package/dist/admin/store.js +1 -1
- package/dist/admin/store.js.map +1 -1
- package/dist/admin/uploads.d.ts +5 -2
- package/dist/admin/uploads.d.ts.map +1 -1
- package/dist/admin/uploads.js +6 -2
- package/dist/admin/uploads.js.map +1 -1
- package/dist/config.d.ts +10 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/federation/article.d.ts.map +1 -1
- package/dist/federation/article.js +37 -1
- package/dist/federation/article.js.map +1 -1
- package/dist/images/alt-text.d.ts +51 -0
- package/dist/images/alt-text.d.ts.map +1 -0
- package/dist/images/alt-text.js +113 -0
- package/dist/images/alt-text.js.map +1 -0
- package/dist/images/markup.d.ts +11 -0
- package/dist/images/markup.d.ts.map +1 -1
- package/dist/images/markup.js +12 -0
- package/dist/images/markup.js.map +1 -1
- package/package.json +8 -8
- package/themes/default/README.md +33 -0
- package/themes/default/layouts/post.njk +10 -1
- package/themes/default/partials/comment-form.njk +33 -14
- package/themes/default/partials/contact-form.njk +32 -14
- package/themes/default/static/style.css +109 -24
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Alt text for the media library.
|
|
3
|
+
*
|
|
4
|
+
* It is kept in one JSON object keyed by the upload's path under
|
|
5
|
+
* `content/uploads/`, beside `site.json` in `content/_data/`: it is public,
|
|
6
|
+
* it cannot be derived from anything, and so it is a file (decision-9). An
|
|
7
|
+
* entry is `{"alt": "A dog asleep on a rug"}` or `{"decorative": true}`; an
|
|
8
|
+
* upload with no entry has no alt text yet, which is the case to fix.
|
|
9
|
+
*
|
|
10
|
+
* The library's text is what an image is offered with when it is inserted.
|
|
11
|
+
* Once inserted, the post's own `` is the alt text for that use, since
|
|
12
|
+
* the same picture can need describing differently in two posts.
|
|
13
|
+
*/
|
|
14
|
+
/** Where the library's alt text is kept, relative to the content directory. */
|
|
15
|
+
export declare const MEDIA_FILE = "_data/media.json";
|
|
16
|
+
/** What the library says about how an image is read to somebody who cannot see it. */
|
|
17
|
+
export type AltText = {
|
|
18
|
+
kind: 'described';
|
|
19
|
+
text: string;
|
|
20
|
+
} | {
|
|
21
|
+
kind: 'decorative';
|
|
22
|
+
};
|
|
23
|
+
/** Every upload's alt text, by its path under `content/uploads/`. */
|
|
24
|
+
export type AltTextLibrary = ReadonlyMap<string, AltText>;
|
|
25
|
+
/** The library, parsed only when the file's bytes changed since the last read. */
|
|
26
|
+
export declare function readAltTexts(contentDir: string): AltTextLibrary;
|
|
27
|
+
/**
|
|
28
|
+
* Set one upload's alt text, or forget it when `alt` is `undefined`.
|
|
29
|
+
*
|
|
30
|
+
* Keys the form does not manage are kept, so a hand-edited entry for another
|
|
31
|
+
* file survives an edit to this one.
|
|
32
|
+
*/
|
|
33
|
+
export declare function writeAltText(contentDir: string, source: string, alt: AltText | undefined): Promise<void>;
|
|
34
|
+
/** An image in a document that tells a reader who cannot see it nothing. */
|
|
35
|
+
export interface UndescribedImage {
|
|
36
|
+
/** The `src` as the document wrote it. */
|
|
37
|
+
src: string;
|
|
38
|
+
/** The last segment of its path, which is how a warning names it. */
|
|
39
|
+
name: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Every image in a document's HTML whose alt text is missing.
|
|
43
|
+
*
|
|
44
|
+
* An image is described when its own `alt` says something. An empty `alt` is
|
|
45
|
+
* a deliberate silence only when the library marks that upload decorative;
|
|
46
|
+
* anywhere else it is the silence of somebody who never got round to it. A tag
|
|
47
|
+
* with no `alt` at all is always missing, because a screen reader then reads
|
|
48
|
+
* out the file name.
|
|
49
|
+
*/
|
|
50
|
+
export declare function undescribedImages(html: string, library: AltTextLibrary): UndescribedImage[];
|
|
51
|
+
//# sourceMappingURL=alt-text.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alt-text.d.ts","sourceRoot":"","sources":["../../src/images/alt-text.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;GAYG;AAEH,+EAA+E;AAC/E,eAAO,MAAM,UAAU,qBAAqB,CAAC;AAE7C,sFAAsF;AACtF,MAAM,MAAM,OAAO,GAAG;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,CAAC;AAEnF,qEAAqE;AACrE,MAAM,MAAM,cAAc,GAAG,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAI1D,kFAAkF;AAClF,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,cAAc,CAW/D;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAChC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,OAAO,GAAG,SAAS,GACvB,OAAO,CAAC,IAAI,CAAC,CAOf;AAED,4EAA4E;AAC5E,MAAM,WAAW,gBAAgB;IAC/B,0CAA0C;IAC1C,GAAG,EAAE,MAAM,CAAC;IACZ,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,gBAAgB,EAAE,CAI3F"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { readFileIfPresentSync, updateFileAtomically } from '../files/atomic.js';
|
|
3
|
+
import { imagesIn } from './markup.js';
|
|
4
|
+
/**
|
|
5
|
+
* Alt text for the media library.
|
|
6
|
+
*
|
|
7
|
+
* It is kept in one JSON object keyed by the upload's path under
|
|
8
|
+
* `content/uploads/`, beside `site.json` in `content/_data/`: it is public,
|
|
9
|
+
* it cannot be derived from anything, and so it is a file (decision-9). An
|
|
10
|
+
* entry is `{"alt": "A dog asleep on a rug"}` or `{"decorative": true}`; an
|
|
11
|
+
* upload with no entry has no alt text yet, which is the case to fix.
|
|
12
|
+
*
|
|
13
|
+
* The library's text is what an image is offered with when it is inserted.
|
|
14
|
+
* Once inserted, the post's own `` is the alt text for that use, since
|
|
15
|
+
* the same picture can need describing differently in two posts.
|
|
16
|
+
*/
|
|
17
|
+
/** Where the library's alt text is kept, relative to the content directory. */
|
|
18
|
+
export const MEDIA_FILE = '_data/media.json';
|
|
19
|
+
const cache = new Map();
|
|
20
|
+
/** The library, parsed only when the file's bytes changed since the last read. */
|
|
21
|
+
export function readAltTexts(contentDir) {
|
|
22
|
+
const file = mediaFile(contentDir);
|
|
23
|
+
const text = readFileIfPresentSync(file);
|
|
24
|
+
if (text === undefined)
|
|
25
|
+
return new Map();
|
|
26
|
+
const cached = cache.get(file);
|
|
27
|
+
if (cached?.text === text)
|
|
28
|
+
return cached.library;
|
|
29
|
+
const library = parseLibrary(text);
|
|
30
|
+
cache.set(file, { text, library });
|
|
31
|
+
return library;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Set one upload's alt text, or forget it when `alt` is `undefined`.
|
|
35
|
+
*
|
|
36
|
+
* Keys the form does not manage are kept, so a hand-edited entry for another
|
|
37
|
+
* file survives an edit to this one.
|
|
38
|
+
*/
|
|
39
|
+
export async function writeAltText(contentDir, source, alt) {
|
|
40
|
+
await updateFileAtomically(mediaFile(contentDir), (current) => {
|
|
41
|
+
const all = current === undefined ? {} : parseObject(current);
|
|
42
|
+
if (alt === undefined)
|
|
43
|
+
delete all[source];
|
|
44
|
+
else
|
|
45
|
+
all[source] = alt.kind === 'decorative' ? { decorative: true } : { alt: alt.text };
|
|
46
|
+
return `${JSON.stringify(all, null, 2)}\n`;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Every image in a document's HTML whose alt text is missing.
|
|
51
|
+
*
|
|
52
|
+
* An image is described when its own `alt` says something. An empty `alt` is
|
|
53
|
+
* a deliberate silence only when the library marks that upload decorative;
|
|
54
|
+
* anywhere else it is the silence of somebody who never got round to it. A tag
|
|
55
|
+
* with no `alt` at all is always missing, because a screen reader then reads
|
|
56
|
+
* out the file name.
|
|
57
|
+
*/
|
|
58
|
+
export function undescribedImages(html, library) {
|
|
59
|
+
return imagesIn(html)
|
|
60
|
+
.filter((image) => !described(image, library))
|
|
61
|
+
.map((image) => ({ src: image.src, name: imageName(image.src) }));
|
|
62
|
+
}
|
|
63
|
+
function described(image, library) {
|
|
64
|
+
if (image.alt === undefined)
|
|
65
|
+
return false;
|
|
66
|
+
if (image.alt.trim() !== '')
|
|
67
|
+
return true;
|
|
68
|
+
return image.source !== undefined && library.get(image.source)?.kind === 'decorative';
|
|
69
|
+
}
|
|
70
|
+
function imageName(src) {
|
|
71
|
+
const pathname = src.split(/[?#]/)[0] ?? src;
|
|
72
|
+
const name = pathname.slice(pathname.lastIndexOf('/') + 1);
|
|
73
|
+
try {
|
|
74
|
+
return decodeURIComponent(name) || src;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return name || src;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function mediaFile(contentDir) {
|
|
81
|
+
return path.join(contentDir, ...MEDIA_FILE.split('/'));
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The file's entries, each checked, since a person may edit it by hand. An
|
|
85
|
+
* entry that is neither shape is ignored rather than failing a page.
|
|
86
|
+
*/
|
|
87
|
+
function parseLibrary(text) {
|
|
88
|
+
const library = new Map();
|
|
89
|
+
for (const [source, value] of Object.entries(parseObject(text))) {
|
|
90
|
+
if (!isRecord(value))
|
|
91
|
+
continue;
|
|
92
|
+
if (value['decorative'] === true) {
|
|
93
|
+
library.set(source, { kind: 'decorative' });
|
|
94
|
+
}
|
|
95
|
+
else if (typeof value['alt'] === 'string' && value['alt'].trim() !== '') {
|
|
96
|
+
library.set(source, { kind: 'described', text: value['alt'].trim() });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return library;
|
|
100
|
+
}
|
|
101
|
+
function parseObject(text) {
|
|
102
|
+
try {
|
|
103
|
+
const parsed = JSON.parse(text);
|
|
104
|
+
return isRecord(parsed) ? parsed : {};
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return {};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function isRecord(value) {
|
|
111
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=alt-text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alt-text.js","sourceRoot":"","sources":["../../src/images/alt-text.ts"],"names":[],"mappings":"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"}
|
package/dist/images/markup.d.ts
CHANGED
|
@@ -46,6 +46,17 @@ export interface ImageLoading {
|
|
|
46
46
|
* would have to reproduce the exact HTML around them to hand it back.
|
|
47
47
|
*/
|
|
48
48
|
export declare function responsiveImages(html: string, describe: DescribeImage, loading?: ImageLoading): string;
|
|
49
|
+
/** One `<img>` in a fragment of HTML, as far as alt text is concerned. */
|
|
50
|
+
export interface ShownImage {
|
|
51
|
+
/** The `src` as written. */
|
|
52
|
+
src: string;
|
|
53
|
+
/** The upload it points at, relative to `content/uploads/`, when it points at one. */
|
|
54
|
+
source: string | undefined;
|
|
55
|
+
/** The `alt` attribute, or `undefined` when the tag has none at all. */
|
|
56
|
+
alt: string | undefined;
|
|
57
|
+
}
|
|
58
|
+
/** Every `<img>` in a fragment that has a `src`, in the order they appear. */
|
|
59
|
+
export declare function imagesIn(html: string): ShownImage[];
|
|
49
60
|
/**
|
|
50
61
|
* The same rewrite, over one site's own derived images.
|
|
51
62
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markup.d.ts","sourceRoot":"","sources":["../../src/images/markup.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAgB,MAAM,eAAe,CAAC;AAE/E;;;;;;;;;GASG;AAEH,uEAAuE;AACvE,MAAM,MAAM,aAAa,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,cAAc,GAAG,SAAS,CAAC;AAE3E;;;;;;;;GAQG;AACH,eAAO,MAAM,WAAW,UAAU,CAAC;AAEnC;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAQD;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,aAAa,EACvB,OAAO,GAAE,YAAiB,GACzB,MAAM,CAqBR;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,YAAiB,GACzB,MAAM,CAiBR"}
|
|
1
|
+
{"version":3,"file":"markup.d.ts","sourceRoot":"","sources":["../../src/images/markup.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAgB,MAAM,eAAe,CAAC;AAE/E;;;;;;;;;GASG;AAEH,uEAAuE;AACvE,MAAM,MAAM,aAAa,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,cAAc,GAAG,SAAS,CAAC;AAE3E;;;;;;;;GAQG;AACH,eAAO,MAAM,WAAW,UAAU,CAAC;AAEnC;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAQD;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,aAAa,EACvB,OAAO,GAAE,YAAiB,GACzB,MAAM,CAqBR;AAED,0EAA0E;AAC1E,MAAM,WAAW,UAAU;IACzB,4BAA4B;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,sFAAsF;IACtF,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,wEAAwE;IACxE,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;CACzB;AAED,8EAA8E;AAC9E,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,EAAE,CASnD;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,YAAiB,GACzB,MAAM,CAiBR"}
|
package/dist/images/markup.js
CHANGED
|
@@ -49,6 +49,18 @@ export function responsiveImages(html, describe, loading = {}) {
|
|
|
49
49
|
return picture({ tag, attributes: parsed, record, lead });
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
|
+
/** Every `<img>` in a fragment that has a `src`, in the order they appear. */
|
|
53
|
+
export function imagesIn(html) {
|
|
54
|
+
const found = [];
|
|
55
|
+
for (const match of html.matchAll(IMG_TAG)) {
|
|
56
|
+
const attributes = parseAttributes(match[1] ?? '');
|
|
57
|
+
const src = attributes.get('src');
|
|
58
|
+
if (src === undefined || src === '')
|
|
59
|
+
continue;
|
|
60
|
+
found.push({ src, source: uploadPath(src), alt: attributes.get('alt') });
|
|
61
|
+
}
|
|
62
|
+
return found;
|
|
63
|
+
}
|
|
52
64
|
/**
|
|
53
65
|
* The same rewrite, over one site's own derived images.
|
|
54
66
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markup.js","sourceRoot":"","sources":["../../src/images/markup.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"markup.js","sourceRoot":"","sources":["../../src/images/markup.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geekity/cms",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A file-first CMS: Markdown content, a SQLite index, a Hono server that is both the editor and the public site.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,27 +28,27 @@
|
|
|
28
28
|
"LICENSE"
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@fedify/fedify": "^2.3.
|
|
32
|
-
"@fedify/hono": "^2.3.
|
|
33
|
-
"@fedify/vocab": "^2.3.
|
|
31
|
+
"@fedify/fedify": "^2.3.8",
|
|
32
|
+
"@fedify/hono": "^2.3.8",
|
|
33
|
+
"@fedify/vocab": "^2.3.8",
|
|
34
34
|
"@hono/node-server": "^2.1.1",
|
|
35
35
|
"@js-temporal/polyfill": "^0.5.1",
|
|
36
36
|
"chokidar": "^5.0.0",
|
|
37
37
|
"gray-matter": "^4.0.3",
|
|
38
|
-
"hono": "^4.13.
|
|
38
|
+
"hono": "^4.13.9",
|
|
39
39
|
"js-yaml": "^5.4.2",
|
|
40
40
|
"markdown-it": "^15.0.2",
|
|
41
41
|
"markdown-it-footnote": "^4.0.0",
|
|
42
|
-
"nodemailer": "^10.0.
|
|
42
|
+
"nodemailer": "^10.0.11",
|
|
43
43
|
"nunjucks": "^3.2.4",
|
|
44
44
|
"sharp": "^0.35.4"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@11ty/eleventy": "^3.1.6",
|
|
48
48
|
"@codemirror/lang-markdown": "^6.5.2",
|
|
49
|
-
"@fedify/cli": "2.3.
|
|
49
|
+
"@fedify/cli": "2.3.8",
|
|
50
50
|
"@types/markdown-it-footnote": "^3.0.4",
|
|
51
|
-
"@types/node": "^26.6.
|
|
51
|
+
"@types/node": "^26.6.3",
|
|
52
52
|
"@types/nodemailer": "^8.0.2",
|
|
53
53
|
"@types/nunjucks": "^3.2.6",
|
|
54
54
|
"codemirror": "^6.0.2",
|
package/themes/default/README.md
CHANGED
|
@@ -303,6 +303,22 @@ later is not news. A post tagged `indienews` opens the line with a
|
|
|
303
303
|
told the post is for it; a page never prints one, and its line is a
|
|
304
304
|
`p.page-meta` rather than a `p.entry-meta`.
|
|
305
305
|
|
|
306
|
+
**Every post's page has one `h1`.** A post with a name of its own is headed by
|
|
307
|
+
it, as above. A note, or a reply without a title, opens on its words instead,
|
|
308
|
+
so it has no `header`; in its place is an `h1` a screen reader and a crawler
|
|
309
|
+
navigate by and a sighted reader never sees, saying what the post is, who wrote
|
|
310
|
+
it and when:
|
|
311
|
+
|
|
312
|
+
```html
|
|
313
|
+
<h1 class="screen-reader-text">Note by Ada Lovelace, 11 September 2026</h1>
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
It says `Reply` for a reply and leaves out the name or the date the post does
|
|
317
|
+
not have. It is not a `p-name`: a parser that found one would take the post
|
|
318
|
+
for an article. The headings under it step down one level at a time: the
|
|
319
|
+
conversation and the comment form are `h2`, and a refused form's error summary
|
|
320
|
+
is an `h3` inside the form's section.
|
|
321
|
+
|
|
306
322
|
What it is filed under is printed under the words and inside the article, so
|
|
307
323
|
that each link is a `p-category` of this entry: the categories first, then the
|
|
308
324
|
tags, each as one `p.post-categories` from `partials/tags.njk`.
|
|
@@ -1355,6 +1371,23 @@ technology, and give it `tabindex="-1"` and `autocomplete="off"`. A submission
|
|
|
1355
1371
|
that filled it is dropped. **Do not** remove it from a replacement partial —
|
|
1356
1372
|
it is one of three things standing between the site and a spam queue.
|
|
1357
1373
|
|
|
1374
|
+
### When a submission is refused
|
|
1375
|
+
|
|
1376
|
+
The CMS answers a refused comment with the post again, `problems` and `error`
|
|
1377
|
+
filled in. The packaged partial tells assistive technology about it in two
|
|
1378
|
+
places, and a replacement partial should do the same:
|
|
1379
|
+
|
|
1380
|
+
- **A summary before the form.** One element with `role="alert"`,
|
|
1381
|
+
`tabindex="-1"` and `autofocus`, labelled by a heading that is `error`, or a
|
|
1382
|
+
sentence of your own when there is none, and holding a link to each field in
|
|
1383
|
+
`problems` (`<a href="#comment-name">…</a>`). `autofocus` moves focus to it on
|
|
1384
|
+
load with no script, so it has to be the first `autofocus` on the page.
|
|
1385
|
+
- **On each field in `problems`.** `aria-invalid="true"` and
|
|
1386
|
+
`aria-describedby` naming the element that holds its message, so a screen
|
|
1387
|
+
reader reads the message with the field.
|
|
1388
|
+
|
|
1389
|
+
The contact form below keeps the same contract.
|
|
1390
|
+
|
|
1358
1391
|
### When somebody is signed in
|
|
1359
1392
|
|
|
1360
1393
|
Two more keys are on `commentForm` when — and only when — the request carries a
|
|
@@ -24,11 +24,20 @@
|
|
|
24
24
|
{% set entryUrl = page.url or url %}
|
|
25
25
|
<article class="blog-post h-entry">
|
|
26
26
|
{#- A post with no name of its own (a note, or a reply without a title) is
|
|
27
|
-
its words, so it opens on them
|
|
27
|
+
its words, so it opens on them in sight. Its page still needs one h1 to
|
|
28
|
+
navigate by, so it gets one that says what it is, who wrote it and when,
|
|
29
|
+
hidden from sight. It is no `p-name`: a parser would take that as a
|
|
30
|
+
title and read the note as an article. -#}
|
|
28
31
|
{% if named %}
|
|
29
32
|
<header>
|
|
30
33
|
<h1 class="p-name">{{ title }}</h1>
|
|
31
34
|
</header>
|
|
35
|
+
{% else %}
|
|
36
|
+
<h1 class="screen-reader-text">
|
|
37
|
+
{{- "Reply" if postType == "reply" else "Note" }}
|
|
38
|
+
{%- if bioAuthor.name %} by {{ bioAuthor.name }}{% endif %}
|
|
39
|
+
{%- if date %}, {{ date | date }}{% endif -%}
|
|
40
|
+
</h1>
|
|
32
41
|
{% endif %}
|
|
33
42
|
|
|
34
43
|
{#- A reply cites the post it answers, whose URL is also the link its
|
|
@@ -29,8 +29,27 @@
|
|
|
29
29
|
<p class="comment-notice" role="status">{{ commentNotice }}</p>
|
|
30
30
|
{% endif %}
|
|
31
31
|
|
|
32
|
-
{
|
|
33
|
-
|
|
32
|
+
{#
|
|
33
|
+
A refused submission leads with this: what went wrong, and a link to each
|
|
34
|
+
field to put right. It is an alert and takes focus on load, so a screen
|
|
35
|
+
reader hears it at once and a keyboard starts from it, with no script. A
|
|
36
|
+
field it links to is marked `aria-invalid` and names its message with
|
|
37
|
+
`aria-describedby`, so the message is read with the field.
|
|
38
|
+
#}
|
|
39
|
+
{% set problems = commentForm.problems %}
|
|
40
|
+
{% if commentForm.error or problems.name or problems.email or problems.url or problems.body %}
|
|
41
|
+
<div class="comment-error" role="alert" tabindex="-1" autofocus aria-labelledby="comment-error-heading">
|
|
42
|
+
<h3 id="comment-error-heading">{{ commentForm.error or 'Your comment was not posted. Fix the fields marked below.' }}</h3>
|
|
43
|
+
{% if problems.name or problems.email or problems.url or problems.body %}
|
|
44
|
+
<ul>
|
|
45
|
+
{% for key in ['name', 'email', 'url', 'body'] %}
|
|
46
|
+
{% if problems[key] %}
|
|
47
|
+
<li><a href="#comment-{{ key }}">{{ problems[key] }}</a></li>
|
|
48
|
+
{% endif %}
|
|
49
|
+
{% endfor %}
|
|
50
|
+
</ul>
|
|
51
|
+
{% endif %}
|
|
52
|
+
</div>
|
|
34
53
|
{% endif %}
|
|
35
54
|
|
|
36
55
|
<form method="post" action="{{ commentForm.action }}">
|
|
@@ -83,34 +102,34 @@
|
|
|
83
102
|
|
|
84
103
|
<p class="comment-field">
|
|
85
104
|
<label for="comment-name">Name</label>
|
|
86
|
-
<input id="comment-name" name="{{ commentForm.fields.name }}" type="text" value="{{ commentForm.values.name }}" maxlength="{{ commentForm.nameLength }}" autocomplete="name" required />
|
|
87
|
-
{% if
|
|
88
|
-
<span class="comment-field-error">{{
|
|
105
|
+
<input id="comment-name" name="{{ commentForm.fields.name }}" type="text" value="{{ commentForm.values.name }}" maxlength="{{ commentForm.nameLength }}" autocomplete="name" required{% if problems.name %} aria-invalid="true" aria-describedby="comment-name-error"{% endif %} />
|
|
106
|
+
{% if problems.name %}
|
|
107
|
+
<span class="comment-field-error" id="comment-name-error">{{ problems.name }}</span>
|
|
89
108
|
{% endif %}
|
|
90
109
|
</p>
|
|
91
110
|
|
|
92
111
|
<p class="comment-field">
|
|
93
112
|
<label for="comment-email">Email <span class="comment-optional">(optional, never shown)</span></label>
|
|
94
|
-
<input id="comment-email" name="{{ commentForm.fields.email }}" type="email" value="{{ commentForm.values.email }}" autocomplete="email" />
|
|
95
|
-
{% if
|
|
96
|
-
<span class="comment-field-error">{{
|
|
113
|
+
<input id="comment-email" name="{{ commentForm.fields.email }}" type="email" value="{{ commentForm.values.email }}" autocomplete="email"{% if problems.email %} aria-invalid="true" aria-describedby="comment-email-error"{% endif %} />
|
|
114
|
+
{% if problems.email %}
|
|
115
|
+
<span class="comment-field-error" id="comment-email-error">{{ problems.email }}</span>
|
|
97
116
|
{% endif %}
|
|
98
117
|
</p>
|
|
99
118
|
|
|
100
119
|
<p class="comment-field">
|
|
101
120
|
<label for="comment-url">Your site <span class="comment-optional">(optional)</span></label>
|
|
102
|
-
<input id="comment-url" name="{{ commentForm.fields.url }}" type="text" inputmode="url" value="{{ commentForm.values.url }}" autocomplete="url" />
|
|
103
|
-
{% if
|
|
104
|
-
<span class="comment-field-error">{{
|
|
121
|
+
<input id="comment-url" name="{{ commentForm.fields.url }}" type="text" inputmode="url" value="{{ commentForm.values.url }}" autocomplete="url"{% if problems.url %} aria-invalid="true" aria-describedby="comment-url-error"{% endif %} />
|
|
122
|
+
{% if problems.url %}
|
|
123
|
+
<span class="comment-field-error" id="comment-url-error">{{ problems.url }}</span>
|
|
105
124
|
{% endif %}
|
|
106
125
|
</p>
|
|
107
126
|
{% endif %}
|
|
108
127
|
|
|
109
128
|
<p class="comment-field">
|
|
110
129
|
<label for="comment-body">Comment</label>
|
|
111
|
-
<textarea id="comment-body" name="{{ commentForm.fields.body }}" rows="6" maxlength="{{ commentForm.bodyLength }}" required>{{ commentForm.values.body }}</textarea>
|
|
112
|
-
{% if
|
|
113
|
-
<span class="comment-field-error">{{
|
|
130
|
+
<textarea id="comment-body" name="{{ commentForm.fields.body }}" rows="6" maxlength="{{ commentForm.bodyLength }}" required{% if problems.body %} aria-invalid="true" aria-describedby="comment-body-error"{% endif %}>{{ commentForm.values.body }}</textarea>
|
|
131
|
+
{% if problems.body %}
|
|
132
|
+
<span class="comment-field-error" id="comment-body-error">{{ problems.body }}</span>
|
|
114
133
|
{% endif %}
|
|
115
134
|
<span class="comment-hint">Markdown works. Links are marked <code>nofollow ugc</code>; HTML is not.</span>
|
|
116
135
|
</p>
|
|
@@ -29,8 +29,26 @@
|
|
|
29
29
|
{% else %}
|
|
30
30
|
<h2 class="contact-form-title">Send a message</h2>
|
|
31
31
|
|
|
32
|
-
{
|
|
33
|
-
|
|
32
|
+
{#
|
|
33
|
+
A refused submission leads with this, as the comment form does: what went
|
|
34
|
+
wrong, and a link to each field to put right. It is an alert and takes
|
|
35
|
+
focus on load; a field it links to is marked `aria-invalid` and names its
|
|
36
|
+
message with `aria-describedby`.
|
|
37
|
+
#}
|
|
38
|
+
{% set problems = contactForm.problems %}
|
|
39
|
+
{% if contactForm.error or problems.name or problems.email or problems.subject or problems.message %}
|
|
40
|
+
<div class="contact-error" role="alert" tabindex="-1" autofocus aria-labelledby="contact-error-heading">
|
|
41
|
+
<h3 id="contact-error-heading">{{ contactForm.error or 'Your message was not sent. Fix the fields marked below.' }}</h3>
|
|
42
|
+
{% if problems.name or problems.email or problems.subject or problems.message %}
|
|
43
|
+
<ul>
|
|
44
|
+
{% for key in ['name', 'email', 'subject', 'message'] %}
|
|
45
|
+
{% if problems[key] %}
|
|
46
|
+
<li><a href="#contact-{{ key }}">{{ problems[key] }}</a></li>
|
|
47
|
+
{% endif %}
|
|
48
|
+
{% endfor %}
|
|
49
|
+
</ul>
|
|
50
|
+
{% endif %}
|
|
51
|
+
</div>
|
|
34
52
|
{% endif %}
|
|
35
53
|
|
|
36
54
|
<form method="post" action="{{ contactForm.action }}">
|
|
@@ -49,34 +67,34 @@
|
|
|
49
67
|
|
|
50
68
|
<p class="contact-field">
|
|
51
69
|
<label for="contact-name">Name</label>
|
|
52
|
-
<input id="contact-name" name="{{ contactForm.fields.name }}" type="text" value="{{ contactForm.values.name }}" maxlength="{{ contactForm.nameLength }}" autocomplete="name" required />
|
|
53
|
-
{% if
|
|
54
|
-
<span class="contact-field-error">{{
|
|
70
|
+
<input id="contact-name" name="{{ contactForm.fields.name }}" type="text" value="{{ contactForm.values.name }}" maxlength="{{ contactForm.nameLength }}" autocomplete="name" required{% if problems.name %} aria-invalid="true" aria-describedby="contact-name-error"{% endif %} />
|
|
71
|
+
{% if problems.name %}
|
|
72
|
+
<span class="contact-field-error" id="contact-name-error">{{ problems.name }}</span>
|
|
55
73
|
{% endif %}
|
|
56
74
|
</p>
|
|
57
75
|
|
|
58
76
|
<p class="contact-field">
|
|
59
77
|
<label for="contact-email">Email</label>
|
|
60
|
-
<input id="contact-email" name="{{ contactForm.fields.email }}" type="email" value="{{ contactForm.values.email }}" autocomplete="email" required />
|
|
61
|
-
{% if
|
|
62
|
-
<span class="contact-field-error">{{
|
|
78
|
+
<input id="contact-email" name="{{ contactForm.fields.email }}" type="email" value="{{ contactForm.values.email }}" autocomplete="email" required{% if problems.email %} aria-invalid="true" aria-describedby="contact-email-error"{% endif %} />
|
|
79
|
+
{% if problems.email %}
|
|
80
|
+
<span class="contact-field-error" id="contact-email-error">{{ problems.email }}</span>
|
|
63
81
|
{% endif %}
|
|
64
82
|
<span class="contact-hint">So there is somewhere to reply to. It is never shown on the site.</span>
|
|
65
83
|
</p>
|
|
66
84
|
|
|
67
85
|
<p class="contact-field">
|
|
68
86
|
<label for="contact-subject">Subject <span class="contact-optional">(optional)</span></label>
|
|
69
|
-
<input id="contact-subject" name="{{ contactForm.fields.subject }}" type="text" value="{{ contactForm.values.subject }}" maxlength="{{ contactForm.subjectLength }}" />
|
|
70
|
-
{% if
|
|
71
|
-
<span class="contact-field-error">{{
|
|
87
|
+
<input id="contact-subject" name="{{ contactForm.fields.subject }}" type="text" value="{{ contactForm.values.subject }}" maxlength="{{ contactForm.subjectLength }}"{% if problems.subject %} aria-invalid="true" aria-describedby="contact-subject-error"{% endif %} />
|
|
88
|
+
{% if problems.subject %}
|
|
89
|
+
<span class="contact-field-error" id="contact-subject-error">{{ problems.subject }}</span>
|
|
72
90
|
{% endif %}
|
|
73
91
|
</p>
|
|
74
92
|
|
|
75
93
|
<p class="contact-field">
|
|
76
94
|
<label for="contact-message">Message</label>
|
|
77
|
-
<textarea id="contact-message" name="{{ contactForm.fields.message }}" rows="8" maxlength="{{ contactForm.messageLength }}" required>{{ contactForm.values.message }}</textarea>
|
|
78
|
-
{% if
|
|
79
|
-
<span class="contact-field-error">{{
|
|
95
|
+
<textarea id="contact-message" name="{{ contactForm.fields.message }}" rows="8" maxlength="{{ contactForm.messageLength }}" required{% if problems.message %} aria-invalid="true" aria-describedby="contact-message-error"{% endif %}>{{ contactForm.values.message }}</textarea>
|
|
96
|
+
{% if problems.message %}
|
|
97
|
+
<span class="contact-field-error" id="contact-message-error">{{ problems.message }}</span>
|
|
80
98
|
{% endif %}
|
|
81
99
|
</p>
|
|
82
100
|
|