@geekity/cms 0.5.0 → 0.7.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 +78 -24
- package/admin/pages/documents/editor.njk +13 -1
- package/admin/pages/navigation/menus.njk +8 -5
- package/admin/pages/users/edit.njk +25 -2
- package/admin/static/admin.css +18 -3
- package/dist/admin/accounts.d.ts +10 -0
- package/dist/admin/accounts.d.ts.map +1 -1
- package/dist/admin/accounts.js +15 -3
- package/dist/admin/accounts.js.map +1 -1
- package/dist/admin/comments.d.ts.map +1 -1
- package/dist/admin/comments.js +2 -1
- package/dist/admin/comments.js.map +1 -1
- package/dist/admin/documents.d.ts +3 -0
- package/dist/admin/documents.d.ts.map +1 -1
- package/dist/admin/documents.js +51 -11
- package/dist/admin/documents.js.map +1 -1
- package/dist/admin/federation.d.ts.map +1 -1
- package/dist/admin/federation.js +3 -2
- package/dist/admin/federation.js.map +1 -1
- package/dist/admin/preview.js +3 -0
- package/dist/admin/preview.js.map +1 -1
- package/dist/admin/routes.d.ts.map +1 -1
- package/dist/admin/routes.js +2 -1
- package/dist/admin/routes.js.map +1 -1
- package/dist/admin/users.d.ts +24 -0
- package/dist/admin/users.d.ts.map +1 -1
- package/dist/admin/users.js +102 -30
- package/dist/admin/users.js.map +1 -1
- package/dist/comments/submission.d.ts.map +1 -1
- package/dist/comments/submission.js +2 -1
- package/dist/comments/submission.js.map +1 -1
- package/dist/config.d.ts +10 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/content/document.d.ts +18 -7
- package/dist/content/document.d.ts.map +1 -1
- package/dist/content/document.js +1 -0
- package/dist/content/document.js.map +1 -1
- package/dist/content/index.d.ts +2 -0
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/index.js +1 -0
- package/dist/content/index.js.map +1 -1
- package/dist/content/parser.d.ts.map +1 -1
- package/dist/content/parser.js +19 -1
- package/dist/content/parser.js.map +1 -1
- package/dist/content/post-type.d.ts +48 -0
- package/dist/content/post-type.d.ts.map +1 -0
- package/dist/content/post-type.js +79 -0
- package/dist/content/post-type.js.map +1 -0
- package/dist/content/store.d.ts.map +1 -1
- package/dist/content/store.js +17 -2
- package/dist/content/store.js.map +1 -1
- package/dist/content/sync.d.ts.map +1 -1
- package/dist/content/sync.js +6 -0
- package/dist/content/sync.js.map +1 -1
- package/dist/content/writer.d.ts +1 -1
- package/dist/content/writer.d.ts.map +1 -1
- package/dist/content/writer.js +9 -2
- package/dist/content/writer.js.map +1 -1
- package/dist/federation/article.d.ts +18 -9
- package/dist/federation/article.d.ts.map +1 -1
- package/dist/federation/article.js +87 -17
- package/dist/federation/article.js.map +1 -1
- package/dist/federation/index.d.ts +1 -1
- package/dist/federation/index.d.ts.map +1 -1
- package/dist/federation/index.js +1 -1
- package/dist/federation/index.js.map +1 -1
- package/dist/federation/mount.d.ts.map +1 -1
- package/dist/federation/mount.js +19 -6
- package/dist/federation/mount.js.map +1 -1
- package/dist/index.d.ts +13 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -4
- package/dist/index.js.map +1 -1
- package/dist/notifications/comments.d.ts.map +1 -1
- package/dist/notifications/comments.js +2 -1
- package/dist/notifications/comments.js.map +1 -1
- package/dist/notifications/digest.d.ts.map +1 -1
- package/dist/notifications/digest.js +2 -1
- package/dist/notifications/digest.js.map +1 -1
- package/dist/web/archive.d.ts +1 -1
- package/dist/web/archive.d.ts.map +1 -1
- package/dist/web/archive.js +2 -1
- package/dist/web/archive.js.map +1 -1
- package/dist/web/authors.d.ts +15 -1
- package/dist/web/authors.d.ts.map +1 -1
- package/dist/web/authors.js +9 -1
- package/dist/web/authors.js.map +1 -1
- package/dist/web/context.d.ts +22 -2
- package/dist/web/context.d.ts.map +1 -1
- package/dist/web/context.js +5 -0
- package/dist/web/context.js.map +1 -1
- package/dist/web/conversation.d.ts.map +1 -1
- package/dist/web/conversation.js +2 -1
- package/dist/web/conversation.js.map +1 -1
- package/dist/web/feed-atom.d.ts.map +1 -1
- package/dist/web/feed-atom.js +14 -2
- package/dist/web/feed-atom.js.map +1 -1
- package/dist/web/feed-item.d.ts +21 -7
- package/dist/web/feed-item.d.ts.map +1 -1
- package/dist/web/feed-item.js +13 -6
- package/dist/web/feed-item.js.map +1 -1
- package/dist/web/feed-json.d.ts +13 -2
- package/dist/web/feed-json.d.ts.map +1 -1
- package/dist/web/feed-json.js +3 -1
- package/dist/web/feed-json.js.map +1 -1
- package/dist/web/feed-rss.d.ts.map +1 -1
- package/dist/web/feed-rss.js +2 -2
- package/dist/web/feed-rss.js.map +1 -1
- package/dist/web/feed-xml.d.ts +2 -0
- package/dist/web/feed-xml.d.ts.map +1 -1
- package/dist/web/feed-xml.js +2 -0
- package/dist/web/feed-xml.js.map +1 -1
- package/dist/web/feeds.d.ts +1 -1
- package/dist/web/feeds.d.ts.map +1 -1
- package/dist/web/feeds.js.map +1 -1
- package/dist/web/index.d.ts +4 -4
- package/dist/web/index.d.ts.map +1 -1
- package/dist/web/index.js +1 -1
- package/dist/web/index.js.map +1 -1
- package/dist/web/navigation.d.ts +106 -28
- package/dist/web/navigation.d.ts.map +1 -1
- package/dist/web/navigation.js +135 -49
- package/dist/web/navigation.js.map +1 -1
- package/dist/web/render.d.ts +9 -0
- package/dist/web/render.d.ts.map +1 -1
- package/dist/web/render.js +19 -1
- package/dist/web/render.js.map +1 -1
- package/dist/web/routes.d.ts.map +1 -1
- package/dist/web/routes.js +2 -1
- package/dist/web/routes.js.map +1 -1
- package/dist/webmention/index.d.ts +6 -0
- package/dist/webmention/index.d.ts.map +1 -1
- package/dist/webmention/index.js +3 -0
- package/dist/webmention/index.js.map +1 -1
- package/dist/webmention/links.d.ts +5 -0
- package/dist/webmention/links.d.ts.map +1 -1
- package/dist/webmention/links.js +14 -7
- package/dist/webmention/links.js.map +1 -1
- package/dist/webmention/microformats.d.ts +21 -0
- package/dist/webmention/microformats.d.ts.map +1 -1
- package/dist/webmention/microformats.js +35 -5
- package/dist/webmention/microformats.js.map +1 -1
- package/dist/webmention/public-address.d.ts +30 -0
- package/dist/webmention/public-address.d.ts.map +1 -0
- package/dist/webmention/public-address.js +71 -0
- package/dist/webmention/public-address.js.map +1 -0
- package/dist/webmention/receive.d.ts.map +1 -1
- package/dist/webmention/receive.js +1 -23
- package/dist/webmention/receive.js.map +1 -1
- package/dist/webmention/reply-context.d.ts +67 -0
- package/dist/webmention/reply-context.d.ts.map +1 -0
- package/dist/webmention/reply-context.js +189 -0
- package/dist/webmention/reply-context.js.map +1 -0
- package/dist/webmention/reply-contexts.d.ts +50 -0
- package/dist/webmention/reply-contexts.d.ts.map +1 -0
- package/dist/webmention/reply-contexts.js +163 -0
- package/dist/webmention/reply-contexts.js.map +1 -0
- package/dist/webmention/service.d.ts.map +1 -1
- package/dist/webmention/service.js +9 -2
- package/dist/webmention/service.js.map +1 -1
- package/package.json +10 -10
- package/themes/default/README.md +25 -10
- package/themes/default/layouts/base.njk +5 -3
- package/themes/default/layouts/post.njk +8 -0
- package/themes/default/layouts/search.njk +4 -2
- package/themes/default/partials/bio.njk +5 -1
- package/themes/default/partials/menu.njk +6 -4
- package/themes/default/partials/post-list.njk +17 -5
- package/themes/default/partials/reply-context.njk +29 -0
- package/themes/default/static/style.css +28 -10
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { replyTarget } from '../content/post-type.js';
|
|
4
|
+
import { updateFileAtomically } from '../files/atomic.js';
|
|
5
|
+
import { fetchReplyContext } from './reply-context.js';
|
|
6
|
+
/**
|
|
7
|
+
* Where the fetched reply contexts are kept, relative to the content directory:
|
|
8
|
+
* one JSON object keyed by the URL replied to (decision-19). Eleventy reads it
|
|
9
|
+
* as the `replyContexts` global.
|
|
10
|
+
*/
|
|
11
|
+
export const REPLY_CONTEXTS_FILE = '_data/replyContexts.json';
|
|
12
|
+
/** Build the reply context service for one site. */
|
|
13
|
+
export function createReplyContextService(options) {
|
|
14
|
+
const { store, lookup } = options;
|
|
15
|
+
const logger = options.logger ?? console;
|
|
16
|
+
const file = path.join(options.contentDir, ...REPLY_CONTEXTS_FILE.split('/'));
|
|
17
|
+
let cachedText;
|
|
18
|
+
let cached = {};
|
|
19
|
+
/** The whole file, parsed only when its bytes changed since the last read. */
|
|
20
|
+
function readAll() {
|
|
21
|
+
let text;
|
|
22
|
+
try {
|
|
23
|
+
text = readFileSync(file, 'utf8');
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
cachedText = undefined;
|
|
27
|
+
cached = {};
|
|
28
|
+
return cached;
|
|
29
|
+
}
|
|
30
|
+
if (text !== cachedText) {
|
|
31
|
+
cachedText = text;
|
|
32
|
+
cached = parseContexts(text);
|
|
33
|
+
}
|
|
34
|
+
return cached;
|
|
35
|
+
}
|
|
36
|
+
/** Rewrite the file with one target's entry set, or removed when `undefined`. */
|
|
37
|
+
async function write(target, context) {
|
|
38
|
+
await updateFileAtomically(file, (current) => {
|
|
39
|
+
const all = current === undefined ? {} : parseContexts(current);
|
|
40
|
+
if (context === undefined)
|
|
41
|
+
delete all[target];
|
|
42
|
+
else
|
|
43
|
+
all[target] = context;
|
|
44
|
+
return `${JSON.stringify(all, null, 2)}\n`;
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async function refresh(target) {
|
|
48
|
+
const fetched = await fetchReplyContext(target, { lookup });
|
|
49
|
+
if (!fetched.ok) {
|
|
50
|
+
logger.warn(`Could not read ${target} for a reply's context: ${fetched.reason}`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
await write(target, fetched.context);
|
|
54
|
+
}
|
|
55
|
+
/** Forget a target, unless a post still replies to it. */
|
|
56
|
+
async function forget(target) {
|
|
57
|
+
if (!(target in readAll()))
|
|
58
|
+
return;
|
|
59
|
+
const stillAnswered = store.listAll().some((document) => replyTarget(document) === target);
|
|
60
|
+
if (!stillAnswered)
|
|
61
|
+
await write(target, undefined);
|
|
62
|
+
}
|
|
63
|
+
let chain = Promise.resolve();
|
|
64
|
+
function enqueue(task) {
|
|
65
|
+
chain = chain.then(task).catch((thrown) => {
|
|
66
|
+
logger.warn(`A reply context failed: ${thrown instanceof Error ? thrown.message : String(thrown)}`);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
handle(change) {
|
|
71
|
+
const before = targetOf(change.previous);
|
|
72
|
+
const after = targetOf(change.next);
|
|
73
|
+
// A scan rebuilds the index from files the contexts file sits beside, so
|
|
74
|
+
// it only fetches what that file has never held; an edit fetches a
|
|
75
|
+
// target that changed as well.
|
|
76
|
+
if (after !== undefined) {
|
|
77
|
+
const stored = after in readAll();
|
|
78
|
+
const changed = after !== before && change.origin !== 'scan';
|
|
79
|
+
if (!stored || changed)
|
|
80
|
+
enqueue(() => refresh(after));
|
|
81
|
+
}
|
|
82
|
+
if (before !== undefined && before !== after)
|
|
83
|
+
enqueue(() => forget(before));
|
|
84
|
+
},
|
|
85
|
+
catchUp() {
|
|
86
|
+
const held = readAll();
|
|
87
|
+
const missing = new Set();
|
|
88
|
+
for (const document of store.listAll()) {
|
|
89
|
+
const target = replyTarget(document);
|
|
90
|
+
if (target !== undefined && !(target in held))
|
|
91
|
+
missing.add(target);
|
|
92
|
+
}
|
|
93
|
+
for (const target of missing)
|
|
94
|
+
enqueue(() => refresh(target));
|
|
95
|
+
},
|
|
96
|
+
read(target) {
|
|
97
|
+
return readAll()[target];
|
|
98
|
+
},
|
|
99
|
+
settled() {
|
|
100
|
+
return chain.then(ignore);
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function targetOf(document) {
|
|
105
|
+
return document === undefined ? undefined : replyTarget(document);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The file's entries, each checked, since a person may have edited it by hand.
|
|
109
|
+
* An entry that is not a context is dropped rather than failing a render.
|
|
110
|
+
*/
|
|
111
|
+
function parseContexts(text) {
|
|
112
|
+
let parsed;
|
|
113
|
+
try {
|
|
114
|
+
parsed = JSON.parse(text);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return {};
|
|
118
|
+
}
|
|
119
|
+
if (!isRecord(parsed))
|
|
120
|
+
return {};
|
|
121
|
+
const contexts = {};
|
|
122
|
+
for (const [target, value] of Object.entries(parsed)) {
|
|
123
|
+
if (!isRecord(value))
|
|
124
|
+
continue;
|
|
125
|
+
const author = isRecord(value['author']) ? value['author'] : undefined;
|
|
126
|
+
const authorName = stringOf(author?.['name']);
|
|
127
|
+
const authorUrl = webUrlOf(author?.['url']);
|
|
128
|
+
contexts[target] = {
|
|
129
|
+
url: target,
|
|
130
|
+
...optional('name', stringOf(value['name'])),
|
|
131
|
+
...optional('text', stringOf(value['text'])),
|
|
132
|
+
...(authorName === undefined
|
|
133
|
+
? {}
|
|
134
|
+
: { author: { name: authorName, ...optional('url', authorUrl) } }),
|
|
135
|
+
...optional('published', stringOf(value['published'])),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return contexts;
|
|
139
|
+
}
|
|
140
|
+
function isRecord(value) {
|
|
141
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
142
|
+
}
|
|
143
|
+
function stringOf(value) {
|
|
144
|
+
return typeof value === 'string' && value !== '' ? value : undefined;
|
|
145
|
+
}
|
|
146
|
+
function webUrlOf(value) {
|
|
147
|
+
if (typeof value !== 'string')
|
|
148
|
+
return undefined;
|
|
149
|
+
try {
|
|
150
|
+
const url = new URL(value);
|
|
151
|
+
return url.protocol === 'http:' || url.protocol === 'https:' ? url.href : undefined;
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function optional(key, value) {
|
|
158
|
+
return value === undefined ? {} : { [key]: value };
|
|
159
|
+
}
|
|
160
|
+
function ignore() {
|
|
161
|
+
// Deliberately empty: every failure is already logged against its target.
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=reply-contexts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reply-contexts.js","sourceRoot":"","sources":["../../src/webmention/reply-contexts.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/webmention/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAwB,MAAM,mBAAmB,CAAC;AAC1F,OAAO,KAAK,EAAE,cAAc,EAAkB,MAAM,wBAAwB,CAAC;AAC7E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/webmention/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAwB,MAAM,mBAAmB,CAAC;AAC1F,OAAO,KAAK,EAAE,cAAc,EAAkB,MAAM,wBAAwB,CAAC;AAC7E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAGnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAMzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAE1E;;;;;;;;;;;;;;;;;GAiBG;AAEH,oEAAoE;AACpE,eAAO,MAAM,eAAe,QAAS,CAAC;AAEtC,qCAAqC;AACrC,MAAM,WAAW,gBAAgB;IAC/B,6BAA6B;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,6DAA6D;IAC7D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,sEAAsE;IACtE,QAAQ,CAAC,IAAI,EAAE,SAAS,cAAc,EAAE,CAAC;CAC1C;AAED,uEAAuE;AACvE,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,kDAAkD;AAClD,MAAM,WAAW,8BAA8B;IAC7C,0EAA0E;IAC1E,KAAK,EAAE,UAAU,CAAC;IAClB,+DAA+D;IAC/D,KAAK,EAAE,YAAY,CAAC;IACpB;;;OAGG;IACH,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,GAAG,YAAY,GAAG,SAAS,GAAG,KAAK,GAAG,gBAAgB,CAAC,CAAC;IAC9F,0DAA0D;IAC1D,MAAM,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC;IACtC;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;CAC5C;AAED,iFAAiF;AACjF,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IACrC;;;;;;;OAOG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAAC;IAC1D;;;;;;;;OAQG;IACH,OAAO,CAAC,QAAQ,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAClE,kFAAkF;IAClF,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED,iDAAiD;AACjD,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,8BAA8B,GACtC,iBAAiB,CAsKnB"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { readSiteSettings } from '../admin/settings.js';
|
|
2
|
+
import { replyTarget } from '../content/post-type.js';
|
|
2
3
|
import { isFederatedDocument } from '../federation/article.js';
|
|
3
4
|
import { absoluteUrl } from '../web/negotiate.js';
|
|
4
5
|
import { discoverEndpoint, WEBMENTION_USER_AGENT } from './discovery.js';
|
|
5
|
-
import { externalLinks } from './links.js';
|
|
6
|
+
import { externalLinks, externalTarget } from './links.js';
|
|
6
7
|
import { verifyWebmention } from './receive.js';
|
|
7
8
|
/**
|
|
8
9
|
* Telling the pages a post links to that it links to them.
|
|
@@ -168,12 +169,18 @@ export function createWebmentionService(options) {
|
|
|
168
169
|
},
|
|
169
170
|
};
|
|
170
171
|
}
|
|
171
|
-
/**
|
|
172
|
+
/**
|
|
173
|
+
* Every external page any of these versions of a post links to, in order: the
|
|
174
|
+
* post it replies to first, then the links in its body.
|
|
175
|
+
*/
|
|
172
176
|
function targetsOf(documents, baseUrl) {
|
|
173
177
|
const targets = new Set();
|
|
174
178
|
for (const document of documents) {
|
|
175
179
|
if (document === undefined)
|
|
176
180
|
continue;
|
|
181
|
+
const reply = externalTarget(replyTarget(document), baseUrl);
|
|
182
|
+
if (reply !== undefined)
|
|
183
|
+
targets.add(reply);
|
|
177
184
|
for (const target of externalLinks(document.html, baseUrl))
|
|
178
185
|
targets.add(target);
|
|
179
186
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/webmention/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/webmention/service.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.7.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,33 +28,33 @@
|
|
|
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.7",
|
|
32
|
+
"@fedify/hono": "^2.3.7",
|
|
33
|
+
"@fedify/vocab": "^2.3.7",
|
|
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.8",
|
|
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.10",
|
|
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.7",
|
|
50
50
|
"@types/markdown-it-footnote": "^3.0.4",
|
|
51
|
-
"@types/node": "^26.
|
|
52
|
-
"@types/nodemailer": "^8.0.
|
|
51
|
+
"@types/node": "^26.6.2",
|
|
52
|
+
"@types/nodemailer": "^8.0.2",
|
|
53
53
|
"@types/nunjucks": "^3.2.6",
|
|
54
54
|
"codemirror": "^6.0.2",
|
|
55
55
|
"esbuild": "^0.28.2",
|
|
56
56
|
"highlight.js": "11.12.0",
|
|
57
|
-
"tsx": "^4.23.
|
|
57
|
+
"tsx": "^4.23.15",
|
|
58
58
|
"typescript": "^7.0.2"
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|
package/themes/default/README.md
CHANGED
|
@@ -262,7 +262,9 @@ person on an author archive.
|
|
|
262
262
|
What the bio prints: a round `u-photo` at 50px when they have an avatar,
|
|
263
263
|
"Written by" and their name as a `p-name u-url` linked `rel="author me"` to
|
|
264
264
|
their archive, then `p-job-title` and `p-locality` when the profile says, then
|
|
265
|
-
their `p-note` and their `rel="me"` links as a `ul.hlist.bio-links
|
|
265
|
+
their `p-note` and their `rel="me"` links as a `ul.hlist.bio-links` — each
|
|
266
|
+
link's `rel` is on it ready to print, `me` first and then whatever else the
|
|
267
|
+
Links box typed. Each of
|
|
266
268
|
those is printed only when the profile says it, so a profile holding a name
|
|
267
269
|
alone prints a name alone. A name this site has no account for is printed
|
|
268
270
|
unlinked, because the file still said somebody wrote this.
|
|
@@ -834,7 +836,7 @@ A site stores its menus by name in `content/_data/site.json`:
|
|
|
834
836
|
],
|
|
835
837
|
"footer": [
|
|
836
838
|
{ "label": "Colophon", "url": "/colophon/" },
|
|
837
|
-
{ "label": "Mastodon", "url": "https://example.social/@me", "
|
|
839
|
+
{ "label": "Mastodon", "url": "https://example.social/@me", "rel": "me" }
|
|
838
840
|
]
|
|
839
841
|
}
|
|
840
842
|
}
|
|
@@ -882,14 +884,17 @@ rather than failing to load it.
|
|
|
882
884
|
|
|
883
885
|
### One item
|
|
884
886
|
|
|
885
|
-
Each item is `{ label, url, current }`, plus `
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
887
|
+
Each item is `{ label, url, current }`, plus `rel` on a link that carries `rel`
|
|
888
|
+
values. `url` is a site-root path or an absolute URL for somewhere else, so put
|
|
889
|
+
it through the `url` filter and a site served from a subdirectory still links
|
|
890
|
+
correctly. `current` is true for the item whose path is the one being rendered,
|
|
891
|
+
comparing without the trailing slash; an item pointing off the site is never
|
|
892
|
+
current. `rel` is one string holding every value the line carried, already
|
|
893
|
+
lower case and each said once — print it as the whole attribute,
|
|
894
|
+
`rel="{{ item.rel }}"`, rather than building one of your own. `me` is the value
|
|
895
|
+
most menus have, for the IndieWeb's identity check: Mastodon verifies a link on
|
|
896
|
+
a profile by looking for a `rel="me"` link back, so a footer link to a profile
|
|
897
|
+
marked `me` is what makes the tick appear.
|
|
893
898
|
|
|
894
899
|
### Where this theme prints them
|
|
895
900
|
|
|
@@ -920,6 +925,16 @@ account: it used to print one `rel="me"` link per entry of `siteAuthor.links`,
|
|
|
920
925
|
which meant one nominated user's links stood for the site and nobody else's
|
|
921
926
|
appeared at all.
|
|
922
927
|
|
|
928
|
+
Every menu reads the same: a link in one is the colour, the underline and the
|
|
929
|
+
size of a link in the page's prose, in the sans heading font a `nav` is set in
|
|
930
|
+
(TASK-111). A menu is navigation somebody is meant to use rather than a
|
|
931
|
+
footnote, so none of them is set smaller than the words around it, and the same
|
|
932
|
+
goes for the `rel="me"` links `partials/bio.njk` prints under a post. The two
|
|
933
|
+
links that read as plain text instead are the site title and
|
|
934
|
+
`a.header-link-home`, which are the page saying where it is rather than
|
|
935
|
+
somewhere to go, and the stylesheet names those two rather than reaching for
|
|
936
|
+
every link that happens to sit in a `<header>`.
|
|
937
|
+
|
|
923
938
|
`menus.primary` is the whole of the site menu, in the order the Navigation
|
|
924
939
|
screen names it. Both areas are declared in this theme's `theme.json`, which is
|
|
925
940
|
what puts them on `/admin/navigation` with those labels; a theme that renders a
|
|
@@ -39,7 +39,9 @@
|
|
|
39
39
|
`pagination`, and the posts page is a listing carrying a page's front
|
|
40
40
|
matter, so it is a website like every other listing. -#}
|
|
41
41
|
{% set metaDescription = description or summary or site.tagline %}
|
|
42
|
-
{
|
|
42
|
+
{#- A note has no title, and is named here by its first words. -#}
|
|
43
|
+
{% set pageTitle = title or label %}
|
|
44
|
+
{% set metaTitle = pageTitle or site.title %}
|
|
43
45
|
{% set metaUrl = (page.url or "/") | absoluteUrl %}
|
|
44
46
|
{% set metaImage = image or site.avatar %}
|
|
45
47
|
{% set isEntry = (type == "post" or type == "page") and not pagination %}
|
|
@@ -48,7 +50,7 @@
|
|
|
48
50
|
<head>
|
|
49
51
|
<meta charset="utf-8">
|
|
50
52
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
51
|
-
<title>{% block title %}{% if
|
|
53
|
+
<title>{% block title %}{% if pageTitle %}{{ pageTitle }} · {% endif %}{{ site.title }}{% endblock %}</title>
|
|
52
54
|
{% if metaDescription %}
|
|
53
55
|
<meta name="description" content="{{ metaDescription }}">
|
|
54
56
|
{% endif %}
|
|
@@ -58,7 +60,7 @@
|
|
|
58
60
|
{{ feeds.feedLinks("/", site.title) }}
|
|
59
61
|
{{ feeds.commentsFeedLink("/comments/feed/", site.title + " comments") }}
|
|
60
62
|
{% if commentsFeed %}
|
|
61
|
-
{{ feeds.commentsFeedLink(commentsFeed, "Comments on: " +
|
|
63
|
+
{{ feeds.commentsFeedLink(commentsFeed, "Comments on: " + pageTitle) }}
|
|
62
64
|
{% endif %}
|
|
63
65
|
{% if activityStreams %}
|
|
64
66
|
<link rel="alternate" type="application/activity+json" href="{{ activityStreams }}">
|
|
@@ -23,9 +23,17 @@
|
|
|
23
23
|
{% block content %}
|
|
24
24
|
{% set entryUrl = page.url or url %}
|
|
25
25
|
<article class="blog-post h-entry">
|
|
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 rather than on an empty heading. -#}
|
|
28
|
+
{% if named %}
|
|
26
29
|
<header>
|
|
27
30
|
<h1 class="p-name">{{ title }}</h1>
|
|
28
31
|
</header>
|
|
32
|
+
{% endif %}
|
|
33
|
+
|
|
34
|
+
{#- A reply cites the post it answers, whose URL is also the link its
|
|
35
|
+
webmention points a receiver at. -#}
|
|
36
|
+
{% include "partials/reply-context.njk" %}
|
|
29
37
|
|
|
30
38
|
<section class="e-content">
|
|
31
39
|
{{ content | safe }}
|
|
@@ -37,8 +37,10 @@
|
|
|
37
37
|
<div class="search-results">
|
|
38
38
|
{% for post in posts %}
|
|
39
39
|
<article class="search-result h-entry">
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
{#- A post with no name of its own has none to claim, so its first words
|
|
41
|
+
link it without being marked up as one. -#}
|
|
42
|
+
<h2 class="feed-title{% if post.named %} p-name{% endif %}">
|
|
43
|
+
<a href="{{ post.url | url }}" class="u-url">{{ post.label }}</a>
|
|
42
44
|
</h2>
|
|
43
45
|
{% if post.snippet %}
|
|
44
46
|
<p class="search-snippet">{{ post.snippet | safe }}</p>
|
|
@@ -29,6 +29,10 @@
|
|
|
29
29
|
otherwise. An author archive sets "Posts by", because the card there heads a
|
|
30
30
|
list of somebody's writing rather than crediting one piece of it.
|
|
31
31
|
|
|
32
|
+
A link's `rel` always holds `me` — the CMS puts it there whether or not the
|
|
33
|
+
Links box typed it — followed by whatever else that line carried, so this
|
|
34
|
+
prints one attribute rather than one of its own beside one of theirs.
|
|
35
|
+
|
|
32
36
|
The `p-note` and the `rel="me"` links are printed wherever the card is, which
|
|
33
37
|
is what `bioProfile` used to ask for on an author archive alone. An entry
|
|
34
38
|
left that switch unset because the page footer carried the site's identity
|
|
@@ -62,7 +66,7 @@
|
|
|
62
66
|
{%- if bioAuthor.links.length %}
|
|
63
67
|
<ul class="hlist bio-links">
|
|
64
68
|
{%- for link in bioAuthor.links %}
|
|
65
|
-
<li><a class="u-url" rel="
|
|
69
|
+
<li><a class="u-url" rel="{{ link.rel }}" href="{{ link.href }}">{{ link.label }}</a></li>
|
|
66
70
|
{%- endfor %}
|
|
67
71
|
</ul>
|
|
68
72
|
{%- endif %}
|
|
@@ -17,15 +17,17 @@
|
|
|
17
17
|
which of a page's menus this one is. Two navs with no labels are two navs
|
|
18
18
|
called "navigation".
|
|
19
19
|
|
|
20
|
-
An item
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
An item's `rel` is whatever `rel` values its line carried, already lower case
|
|
21
|
+
and each said once, so it is printed as one attribute and nothing is joined
|
|
22
|
+
here. `me` is the one most menus want: it is how Mastodon and the rest of the
|
|
23
|
+
IndieWeb verify that the site and the profile it links belong to the same
|
|
24
|
+
person — the profile links back, and the pair is the proof.
|
|
23
25
|
#}
|
|
24
26
|
{% macro list(items, label) %}{% if items.length %}
|
|
25
27
|
<nav class="site-nav" aria-label="{{ label }}">
|
|
26
28
|
<ul class="hlist">
|
|
27
29
|
{% for item in items %}
|
|
28
|
-
<li><a href="{{ item.url | url }}"{% if item.
|
|
30
|
+
<li><a href="{{ item.url | url }}"{% if item.rel %} rel="{{ item.rel }}"{% endif %}{% if item.current %} class="is-current" aria-current="page"{% endif %}>{{ item.label }}</a></li>
|
|
29
31
|
{% endfor %}
|
|
30
32
|
</ul>
|
|
31
33
|
</nav>
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
The feed a listing is made of: `h-feed` of `h-entry` items, as the source
|
|
3
3
|
design lists them (decision-16).
|
|
4
4
|
|
|
5
|
-
Expects `posts`.
|
|
6
|
-
`
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
Expects `posts`. A note, which has no title, is printed whole as its
|
|
6
|
+
`e-content`, with its date as the link to it. Every other entry prints its
|
|
7
|
+
title as a linked `p-name`, its `summary` — the very line the feeds publish —
|
|
8
|
+
as a `p-summary` cut to about 280 characters, a Continue reading link, its
|
|
9
|
+
publish date and the categories it is filed under. Tags are deliberately not here: an entry's tags belong
|
|
9
10
|
under the entry, and a feed of twenty posts listing everyone's would be a
|
|
10
11
|
wall of links rather than a list of writing.
|
|
11
12
|
|
|
@@ -24,6 +25,12 @@
|
|
|
24
25
|
{% endif %}
|
|
25
26
|
<article class="feed-item h-entry">
|
|
26
27
|
<div class="feed-content">
|
|
28
|
+
{% if post.inReplyTo %}
|
|
29
|
+
<p class="feed-reply small">In reply to <a class="u-in-reply-to" href="{{ post.inReplyTo }}">{{ post.inReplyTo }}</a></p>
|
|
30
|
+
{% endif %}
|
|
31
|
+
{% if not post.named %}
|
|
32
|
+
<div class="feed-excerpt e-content">{{ post.content | safe }}</div>
|
|
33
|
+
{% else %}
|
|
27
34
|
<{{ feedTitleTag }} class="feed-title p-name">
|
|
28
35
|
<a href="{{ post.url | url }}" class="u-url">{{ post.title }}</a>
|
|
29
36
|
</{{ feedTitleTag }}>
|
|
@@ -38,8 +45,13 @@
|
|
|
38
45
|
<p class="feed-more">
|
|
39
46
|
<a href="{{ post.url | url }}" aria-label="Continue reading: {{ post.title }}">Continue reading<span aria-hidden="true"> →</span></a>
|
|
40
47
|
</p>
|
|
48
|
+
{% endif %}
|
|
41
49
|
<div class="feed-meta">
|
|
42
|
-
{% if post.
|
|
50
|
+
{% if not post.named %}
|
|
51
|
+
<p>
|
|
52
|
+
<a href="{{ post.url | url }}" class="u-url">{% if post.date %}<time class="feed-date dt-published" datetime="{{ post.date | date("iso") }}">{{ post.date | date }}</time>{% else %}{{ post.label }}{% endif %}</a>
|
|
53
|
+
</p>
|
|
54
|
+
{% elif post.date %}
|
|
43
55
|
<p>
|
|
44
56
|
<time class="feed-date dt-published" datetime="{{ post.date | date("iso") }}">
|
|
45
57
|
{{ post.date | date }}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{#
|
|
2
|
+
The post a reply answers, cited in the reply (TASK-123): an embedded
|
|
3
|
+
`u-in-reply-to h-cite` that always carries the target's URL, so a parser
|
|
4
|
+
reads what this post answers even when nothing more is known about it.
|
|
5
|
+
|
|
6
|
+
`replyContext` is what the target said about itself when the reply was saved
|
|
7
|
+
or synced: its name, a short excerpt, its author and its date, each only when
|
|
8
|
+
it said so. It is somebody else's words, so every piece is printed through
|
|
9
|
+
autoescaping and none of it with `safe`.
|
|
10
|
+
#}
|
|
11
|
+
{% if inReplyTo %}
|
|
12
|
+
<div class="reply-context u-in-reply-to h-cite">
|
|
13
|
+
<p class="small">In reply to
|
|
14
|
+
{%- if replyContext.name %} <a class="u-url p-name" href="{{ inReplyTo }}">{{ replyContext.name }}</a>
|
|
15
|
+
{%- else %} <a class="u-url" href="{{ inReplyTo }}">{{ inReplyTo }}</a>
|
|
16
|
+
{%- endif %}
|
|
17
|
+
{%- if replyContext.author %} by <span class="p-author h-card">
|
|
18
|
+
{%- if replyContext.author.url %}<a class="u-url p-name" href="{{ replyContext.author.url }}">{{ replyContext.author.name }}</a>
|
|
19
|
+
{%- else %}<span class="p-name">{{ replyContext.author.name }}</span>{% endif -%}
|
|
20
|
+
</span>
|
|
21
|
+
{%- endif %}
|
|
22
|
+
{%- if replyContext.published %} on <time class="dt-published" datetime="{{ replyContext.published | date("iso") }}">{{ replyContext.published | date }}</time>
|
|
23
|
+
{%- endif -%}
|
|
24
|
+
</p>
|
|
25
|
+
{% if replyContext.text %}
|
|
26
|
+
<blockquote class="p-content">{{ replyContext.text }}</blockquote>
|
|
27
|
+
{% endif %}
|
|
28
|
+
</div>
|
|
29
|
+
{% endif %}
|
|
@@ -299,14 +299,29 @@ a:focus-visible {
|
|
|
299
299
|
color: var(--color-primary);
|
|
300
300
|
}
|
|
301
301
|
|
|
302
|
-
|
|
303
|
-
|
|
302
|
+
/* The two links that are the page saying where it is rather than somewhere to
|
|
303
|
+
go: the site title at the root and the small link home everywhere else. Both
|
|
304
|
+
read as plain text, in the heading font the rules below give them.
|
|
305
|
+
|
|
306
|
+
Named rather than reached for through the element they sit in (TASK-111). A
|
|
307
|
+
bare `header a` also caught the site menu, which is navigation somebody is
|
|
308
|
+
meant to use, and every link inside a post's own `<header>`. The `:visited`
|
|
309
|
+
halves are here because `a:visited` outweighs a bare class, and the hover
|
|
310
|
+
and focus halves because `a:focus-visible` outweighs one too: without them
|
|
311
|
+
these two would take the outline rather than the flourish. */
|
|
312
|
+
|
|
313
|
+
.main-heading a,
|
|
314
|
+
.main-heading a:visited,
|
|
315
|
+
.header-link-home,
|
|
316
|
+
.header-link-home:visited {
|
|
304
317
|
color: var(--color-text);
|
|
305
318
|
text-decoration: none;
|
|
306
319
|
}
|
|
307
320
|
|
|
308
|
-
|
|
309
|
-
|
|
321
|
+
.main-heading a:hover,
|
|
322
|
+
.main-heading a:focus,
|
|
323
|
+
.header-link-home:hover,
|
|
324
|
+
.header-link-home:focus {
|
|
310
325
|
background-color: var(--color-primary);
|
|
311
326
|
color: var(--color-body);
|
|
312
327
|
text-decoration: none;
|
|
@@ -618,10 +633,13 @@ pre code {
|
|
|
618
633
|
|
|
619
634
|
/* The site menu, which the header carries on every page (TASK-105). At the
|
|
620
635
|
root the header is the site title with the tagline under it, and the menu
|
|
621
|
-
is a line of its own under that.
|
|
636
|
+
is a line of its own under that.
|
|
637
|
+
|
|
638
|
+
No size of its own (TASK-111): a menu is navigation somebody is meant to
|
|
639
|
+
use rather than a footnote, so it reads at the page's size, like the footer
|
|
640
|
+
menu, which never had one. */
|
|
622
641
|
.global-header .site-nav {
|
|
623
642
|
margin-top: var(--spacing-2);
|
|
624
|
-
font-size: var(--fontSize-0);
|
|
625
643
|
}
|
|
626
644
|
|
|
627
645
|
/* Everywhere else the header is the small link home, and the menu sits beside
|
|
@@ -690,12 +708,12 @@ pre code {
|
|
|
690
708
|
font-style: italic;
|
|
691
709
|
}
|
|
692
710
|
|
|
693
|
-
/* The links the person calls their own, under the credit: a horizontal list
|
|
694
|
-
|
|
695
|
-
the header now (TASK-105) and
|
|
711
|
+
/* The links the person calls their own, under the credit: a horizontal list in
|
|
712
|
+
the heading font, at the page's size like every other nav (TASK-111). The
|
|
713
|
+
site menu used to share this rule; it is in the header now (TASK-105) and
|
|
714
|
+
the bio carries nobody but the person. */
|
|
696
715
|
.bio-links {
|
|
697
716
|
margin-top: var(--spacing-2);
|
|
698
|
-
font-size: var(--fontSize-0);
|
|
699
717
|
}
|
|
700
718
|
|
|
701
719
|
.bio-avatar {
|