@geekity/cms 0.5.0 → 0.6.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.
@@ -5,17 +5,64 @@ import type { SiteData } from './context.ts';
5
5
  */
6
6
  export declare const DEFAULT_MENU_NAME = "primary";
7
7
  /**
8
- * The flags one menu item may carry, as `site.json` spells them and as the
9
- * settings screen writes them at the end of a line.
8
+ * What one `rel` value may be spelled with: letters and dashes, which is what
9
+ * every HTML link type is spelled with.
10
10
  *
11
- * A flag is a word rather than a value, and the list is here rather than
12
- * spread through the parsers so that a second one — `nofollow`, say, or a flag
13
- * that opens a link in its own tab — is one entry and its handling in the
14
- * theme, not a new shape for an item.
11
+ * Deliberately not a list of the values this CMS knows about (TASK-114). The
12
+ * trailing part of a link line is whatever `rel` values the person typed —
13
+ * `me`, `nofollow`, `author`, `noopener`, one somebody's own reader looks for
14
+ * — and all of them reach the rendered attribute. A closed list would mean
15
+ * every value anybody wanted had to be added here first, and `me` was never a
16
+ * flag in the first place: it is a `rel` value that this CMS happened to be
17
+ * the only one to know.
15
18
  */
16
- export declare const MENU_ITEM_FLAGS: readonly ['me'];
17
- /** One of {@link MENU_ITEM_FLAGS}. */
18
- export type MenuItemFlag = (typeof MENU_ITEM_FLAGS)[number];
19
+ export declare const REL_VALUE_PATTERN: RegExp;
20
+ /**
21
+ * What a link line's trailing part may be, in the words both boxes explain it
22
+ * in, beside {@link LINK_URL_RULE}.
23
+ */
24
+ export declare const LINK_REL_RULE = "the rel values the link carries, a word each \u2014 \"| me\", \"| nofollow noopener\"";
25
+ /**
26
+ * One bar-separated part as the `rel` values it names, or `undefined` when it
27
+ * is not a list of them.
28
+ *
29
+ * Every word has to be one: `nofollow noopener` is a list, `a=1|2`'s `2` is
30
+ * not, and that is what keeps a bar inside a URL a bar. An empty part names no
31
+ * values rather than an empty list, because `About | /about/ |` is a line with
32
+ * a trailing bar, not a line with a rel.
33
+ */
34
+ export declare function relValuesOf(text: string): string[] | undefined;
35
+ /**
36
+ * Some `rel` values as one attribute holds them: lower case, each said once,
37
+ * in the order they were first typed.
38
+ *
39
+ * A theme prints this string and nothing else, so it never joins a list itself
40
+ * and never emits two `rel` attributes on one link. Lower case is what makes
41
+ * saying a value twice — `me` typed on a profile link that already carries it
42
+ * — a no-op rather than `rel="me ME"`.
43
+ */
44
+ export declare function relText(values: Iterable<string>): string;
45
+ /** A link line split at the point where its `rel` values begin. */
46
+ export interface LinkLine {
47
+ /** The line with its trailing `rel` values taken off: the label and the URL. */
48
+ text: string;
49
+ /** The values that were typed, as one attribute holds them. Empty for none. */
50
+ rel: string;
51
+ }
52
+ /**
53
+ * One typed link line, with the `rel` values taken off the end of it.
54
+ *
55
+ * The values come off the right, one bar-separated part at a time, and only
56
+ * while the last part is a list of rel values — so `Mastodon |
57
+ * https://example.social/@me | me | nofollow` and `... | me nofollow` are the
58
+ * same line, and `Odd | /odd/?a=1|2` keeps its bar because `2` is not one.
59
+ *
60
+ * The one place a link line is split, for the reason the URL rule is worded
61
+ * once (TASK-114): the menu box on the Navigation screen and the Links box on
62
+ * a profile take the same line, so somebody who learns one box has learned the
63
+ * other.
64
+ */
65
+ export declare function splitLinkRel(line: string): LinkLine;
19
66
  /**
20
67
  * What a menu name may be spelled with.
21
68
  *
@@ -51,16 +98,17 @@ export interface NavigationItem {
51
98
  /** Where it goes: a site-root path, or an absolute URL for somewhere else. */
52
99
  url: string;
53
100
  /**
54
- * Whether the link carries `rel="me"`, which is how Mastodon and the rest of
55
- * the IndieWeb verify that the site and the profile it links are the same
56
- * person.
101
+ * What the link's `rel` attribute says, as one string holding every value:
102
+ * `me` for a profile that is you, `nofollow noopener` for somewhere you are
103
+ * only pointing at, whatever else a reader of this site looks for.
57
104
  *
58
- * Present only when it is set. An unmarked item is spelled exactly as every
59
- * item was before flags existed, so a `site.json` holding a plain
60
- * `{ label, url }` is a complete item rather than one missing a key, and a
61
- * flag added later never has to be written across a file to mean nothing.
105
+ * Present only when there is something to say, so a `site.json` holding a
106
+ * plain `{ label, url }` is a complete item rather than one missing a key.
107
+ * Already lower case and deduplicated ({@link relText}), because a theme
108
+ * prints it as it stands and no theme should be joining a list or emitting
109
+ * two `rel` attributes on one link.
62
110
  */
63
- me?: true;
111
+ rel?: string;
64
112
  }
65
113
  /** Every menu one site stores, by name. */
66
114
  export type NavigationMenus = Record<string, NavigationItem[]>;
@@ -148,10 +196,15 @@ export declare function menusOf(value: unknown): NavigationMenus;
148
196
  * One menu's stored value as the items it names, ignoring anything that is not
149
197
  * one.
150
198
  *
151
- * A flag is read only when it is spelled exactly `true`: `"yes"`, `1` and
152
- * `false` all leave the item unmarked, so a flag is either set or it is not
153
- * and a theme writes `{% if item.me %}` without wondering what else the file
154
- * might have put there.
199
+ * `rel` is read through {@link relText}, so a file that wrote it in capitals
200
+ * or said a value twice renders one attribute either way, and a `rel` that is
201
+ * not a string is an item with none rather than an item dropped.
202
+ *
203
+ * `me: true` is still read, as `rel: "me"`. It is how every marked item was
204
+ * spelled before the trailing part of a line was a list of rel values
205
+ * (TASK-107, TASK-114), and a site that had set the only flag there was must
206
+ * not lose it on upgrade; the Navigation screen writes `rel` the next time
207
+ * that menu is saved. Only the literal `true` counts, the way it always did.
155
208
  */
156
209
  export declare function navigationItemsOf(value: unknown): NavigationItem[];
157
210
  /**
@@ -161,13 +214,9 @@ export declare function navigationItemsOf(value: unknown): NavigationItem[];
161
214
  * `about/` would be resolved against whatever page it was printed on, which is
162
215
  * never what a menu means.
163
216
  *
164
- * A line may end in the flags the item carries, one per bar — `Mastodon |
165
- * https://example.social/@me | me` — and those are taken off the end first,
166
- * from the right, and only while the last part is the name of a flag this CMS
167
- * actually has ({@link MENU_ITEM_FLAGS}). That is what lets a URL still hold a
168
- * bar, a query string say: a trailing `| elsewhere` is not a flag, so it stays
169
- * part of the URL and the line is refused for not naming one, rather than
170
- * being quietly read as a flag nobody asked for.
217
+ * A line may end in the `rel` values the link carries — `Mastodon |
218
+ * https://example.social/@me | me`, `A source | https://example.com/thing |
219
+ * nofollow noopener` — which {@link splitLinkRel} takes off the end first.
171
220
  *
172
221
  * What is left splits at its first bar, so a label still cannot hold one.
173
222
  */
@@ -192,4 +241,33 @@ export declare function menuItemsText(items: readonly NavigationItem[]): string;
192
241
  * half of the sentence.
193
242
  */
194
243
  export declare function menuItemLineProblem(value: string): string | undefined;
244
+ /**
245
+ * What a URL typed into a link box may be, in the words both boxes explain it
246
+ * in: the menu on the Navigation screen and the Links on a user's profile.
247
+ *
248
+ * One sentence fragment rather than two, so the rule {@link isLinkUrl} applies
249
+ * is worded the same wherever somebody is told about it (TASK-112). Somebody
250
+ * who learns one box should not be taught the wrong thing about the other.
251
+ */
252
+ export declare const LINK_URL_RULE = "a path like /about/ or an absolute http:// or https:// URL, with no spaces in it";
253
+ /**
254
+ * Whether a typed URL is one a link box accepts: a site-root path, or an
255
+ * absolute http(s) URL, and no whitespace either way.
256
+ *
257
+ * A bare `about/` would be resolved against whatever page it was printed on,
258
+ * which is never what a link box means, and a `javascript:` or `data:` URL is
259
+ * not somewhere a reader goes.
260
+ *
261
+ * The whitespace half is not decoration. `new URL` does not throw on
262
+ * `https://shll.me/@a | me`: it reads the space and the bar as path
263
+ * characters and percent-encodes them, so a check that only asked the parser
264
+ * took a label, a URL and a trailing word as one URL and stored it — which is
265
+ * how a reader of shll.me ended up at `/author/a%20%7C%20me/` (TASK-112). A
266
+ * URL somebody types has no spaces in it; a space means they typed two things.
267
+ *
268
+ * Deliberately a test rather than a normaliser: a link keeps the URL as it was
269
+ * typed, so `https://example.social/@me` is what the link says and what the
270
+ * box shows it back as.
271
+ */
272
+ export declare function isLinkUrl(url: string): boolean;
195
273
  //# sourceMappingURL=navigation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../src/web/navigation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE7C;;;GAGG;AACH,eAAO,MAAM,iBAAiB,YAAY,CAAC;AAE3C;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,YAAI,IAAI,CAAU,CAAC;AAE/C,sCAAsC;AACtC,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5D;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,iBAAiB,QAAsB,CAAC;AAErD,sFAAsF;AACtF,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAEvC;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAmBhE;AAED,4EAA4E;AAC5E,MAAM,WAAW,cAAc;IAC7B,6BAA6B;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;;;;OASG;IACH,EAAE,CAAC,EAAE,IAAI,CAAC;CACX;AAED,2CAA2C;AAC3C,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,EAAE,CAAC,CAAC;AAE/D,6CAA6C;AAC7C,MAAM,WAAW,QAAS,SAAQ,cAAc;IAC9C,oEAAoE;IACpE,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,+EAA+E;AAC/E,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;AAElD,wDAAwD;AACxD,MAAM,WAAW,qBAAqB;IACpC,2DAA2D;IAC3D,IAAI,EAAE,QAAQ,CAAC;IACf,oEAAoE;IACpE,GAAG,EAAE,MAAM,CAAC;IACZ,+DAA+D;IAC/D,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,QAAQ,EAAE,CAEzE;AAED,6DAA6D;AAC7D,MAAM,WAAW,sBAAsB;IACrC,2DAA2D;IAC3D,IAAI,EAAE,QAAQ,CAAC;IACf,oEAAoE;IACpE,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,QAAQ,CAMzE;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,QAAQ,EACd,IAAI,GAAE,MAA0B,GAC/B,cAAc,EAAE,CAElB;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,QAAQ,GAAG,eAAe,CAEzD;AAED;;;;;;;;;GASG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,CASvD;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,EAAE,CAYlE;AAeD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAyBnE;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,EAAE,CAOjE;AAED,8DAA8D;AAC9D,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,cAAc,EAAE,GAAG,MAAM,CAItE;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAOrE"}
1
+ {"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../src/web/navigation.ts"],"names":[],"mappings":"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"}
@@ -4,15 +4,88 @@
4
4
  */
5
5
  export const DEFAULT_MENU_NAME = 'primary';
6
6
  /**
7
- * The flags one menu item may carry, as `site.json` spells them and as the
8
- * settings screen writes them at the end of a line.
7
+ * What one `rel` value may be spelled with: letters and dashes, which is what
8
+ * every HTML link type is spelled with.
9
9
  *
10
- * A flag is a word rather than a value, and the list is here rather than
11
- * spread through the parsers so that a second one — `nofollow`, say, or a flag
12
- * that opens a link in its own tab — is one entry and its handling in the
13
- * theme, not a new shape for an item.
10
+ * Deliberately not a list of the values this CMS knows about (TASK-114). The
11
+ * trailing part of a link line is whatever `rel` values the person typed —
12
+ * `me`, `nofollow`, `author`, `noopener`, one somebody's own reader looks for
13
+ * — and all of them reach the rendered attribute. A closed list would mean
14
+ * every value anybody wanted had to be added here first, and `me` was never a
15
+ * flag in the first place: it is a `rel` value that this CMS happened to be
16
+ * the only one to know.
14
17
  */
15
- export const MENU_ITEM_FLAGS = ['me'];
18
+ export const REL_VALUE_PATTERN = /^[a-z-]+$/i;
19
+ /**
20
+ * What a link line's trailing part may be, in the words both boxes explain it
21
+ * in, beside {@link LINK_URL_RULE}.
22
+ */
23
+ export const LINK_REL_RULE = 'the rel values the link carries, a word each — "| me", "| nofollow noopener"';
24
+ /**
25
+ * One bar-separated part as the `rel` values it names, or `undefined` when it
26
+ * is not a list of them.
27
+ *
28
+ * Every word has to be one: `nofollow noopener` is a list, `a=1|2`'s `2` is
29
+ * not, and that is what keeps a bar inside a URL a bar. An empty part names no
30
+ * values rather than an empty list, because `About | /about/ |` is a line with
31
+ * a trailing bar, not a line with a rel.
32
+ */
33
+ export function relValuesOf(text) {
34
+ const words = text
35
+ .trim()
36
+ .split(/\s+/)
37
+ .filter((word) => word !== '');
38
+ if (words.length === 0)
39
+ return undefined;
40
+ return words.every((word) => REL_VALUE_PATTERN.test(word)) ? words : undefined;
41
+ }
42
+ /**
43
+ * Some `rel` values as one attribute holds them: lower case, each said once,
44
+ * in the order they were first typed.
45
+ *
46
+ * A theme prints this string and nothing else, so it never joins a list itself
47
+ * and never emits two `rel` attributes on one link. Lower case is what makes
48
+ * saying a value twice — `me` typed on a profile link that already carries it
49
+ * — a no-op rather than `rel="me ME"`.
50
+ */
51
+ export function relText(values) {
52
+ const said = new Set();
53
+ for (const value of values) {
54
+ for (const word of value.trim().toLowerCase().split(/\s+/)) {
55
+ if (word !== '')
56
+ said.add(word);
57
+ }
58
+ }
59
+ return [...said].join(' ');
60
+ }
61
+ /**
62
+ * One typed link line, with the `rel` values taken off the end of it.
63
+ *
64
+ * The values come off the right, one bar-separated part at a time, and only
65
+ * while the last part is a list of rel values — so `Mastodon |
66
+ * https://example.social/@me | me | nofollow` and `... | me nofollow` are the
67
+ * same line, and `Odd | /odd/?a=1|2` keeps its bar because `2` is not one.
68
+ *
69
+ * The one place a link line is split, for the reason the URL rule is worded
70
+ * once (TASK-114): the menu box on the Navigation screen and the Links box on
71
+ * a profile take the same line, so somebody who learns one box has learned the
72
+ * other.
73
+ */
74
+ export function splitLinkRel(line) {
75
+ const values = [];
76
+ let text = line;
77
+ for (;;) {
78
+ const bar = text.lastIndexOf('|');
79
+ if (bar === -1)
80
+ break;
81
+ const rel = relValuesOf(text.slice(bar + 1));
82
+ if (rel === undefined)
83
+ break;
84
+ values.unshift(...rel);
85
+ text = text.slice(0, bar);
86
+ }
87
+ return { text: text.trim(), rel: relText(values) };
88
+ }
16
89
  /**
17
90
  * What a menu name may be spelled with.
18
91
  *
@@ -140,10 +213,15 @@ export function menusOf(value) {
140
213
  * One menu's stored value as the items it names, ignoring anything that is not
141
214
  * one.
142
215
  *
143
- * A flag is read only when it is spelled exactly `true`: `"yes"`, `1` and
144
- * `false` all leave the item unmarked, so a flag is either set or it is not
145
- * and a theme writes `{% if item.me %}` without wondering what else the file
146
- * might have put there.
216
+ * `rel` is read through {@link relText}, so a file that wrote it in capitals
217
+ * or said a value twice renders one attribute either way, and a `rel` that is
218
+ * not a string is an item with none rather than an item dropped.
219
+ *
220
+ * `me: true` is still read, as `rel: "me"`. It is how every marked item was
221
+ * spelled before the trailing part of a line was a list of rel values
222
+ * (TASK-107, TASK-114), and a site that had set the only flag there was must
223
+ * not lose it on upgrade; the Navigation screen writes `rel` the next time
224
+ * that menu is saved. Only the literal `true` counts, the way it always did.
147
225
  */
148
226
  export function navigationItemsOf(value) {
149
227
  if (!Array.isArray(value))
@@ -152,12 +230,16 @@ export function navigationItemsOf(value) {
152
230
  for (const entry of value) {
153
231
  if (typeof entry !== 'object' || entry === null)
154
232
  continue;
155
- const { label, url, me } = entry;
233
+ const { label, url, rel, me } = entry;
156
234
  if (typeof label !== 'string' || label === '')
157
235
  continue;
158
236
  if (typeof url !== 'string' || url === '')
159
237
  continue;
160
- items.push({ label, url, ...(me === true ? { me: true } : {}) });
238
+ const values = relText([
239
+ ...(me === true ? ['me'] : []),
240
+ ...(typeof rel === 'string' ? [rel] : []),
241
+ ]);
242
+ items.push({ label, url, ...(values === '' ? {} : { rel: values }) });
161
243
  }
162
244
  return items;
163
245
  }
@@ -180,42 +262,22 @@ function menuLines(value) {
180
262
  * `about/` would be resolved against whatever page it was printed on, which is
181
263
  * never what a menu means.
182
264
  *
183
- * A line may end in the flags the item carries, one per bar — `Mastodon |
184
- * https://example.social/@me | me` — and those are taken off the end first,
185
- * from the right, and only while the last part is the name of a flag this CMS
186
- * actually has ({@link MENU_ITEM_FLAGS}). That is what lets a URL still hold a
187
- * bar, a query string say: a trailing `| elsewhere` is not a flag, so it stays
188
- * part of the URL and the line is refused for not naming one, rather than
189
- * being quietly read as a flag nobody asked for.
265
+ * A line may end in the `rel` values the link carries — `Mastodon |
266
+ * https://example.social/@me | me`, `A source | https://example.com/thing |
267
+ * nofollow noopener` — which {@link splitLinkRel} takes off the end first.
190
268
  *
191
269
  * What is left splits at its first bar, so a label still cannot hold one.
192
270
  */
193
271
  export function menuItemOf(line) {
194
- const flags = new Set();
195
- let rest = line;
196
- for (;;) {
197
- const bar = rest.lastIndexOf('|');
198
- if (bar === -1)
199
- break;
200
- const flag = rest
201
- .slice(bar + 1)
202
- .trim()
203
- .toLowerCase();
204
- if (!MENU_ITEM_FLAGS.includes(flag))
205
- break;
206
- flags.add(flag);
207
- rest = rest.slice(0, bar);
208
- }
209
- const bar = rest.indexOf('|');
272
+ const { text, rel } = splitLinkRel(line);
273
+ const bar = text.indexOf('|');
210
274
  if (bar === -1)
211
275
  return undefined;
212
- const label = rest.slice(0, bar).trim();
213
- const url = rest.slice(bar + 1).trim();
214
- if (label === '' || url === '')
215
- return undefined;
216
- if (!url.startsWith('/') && !isAbsoluteHttpUrl(url))
276
+ const label = text.slice(0, bar).trim();
277
+ const url = text.slice(bar + 1).trim();
278
+ if (label === '' || !isLinkUrl(url))
217
279
  return undefined;
218
- return { label, url, ...(flags.has('me') ? { me: true } : {}) };
280
+ return { label, url, ...(rel === '' ? {} : { rel }) };
219
281
  }
220
282
  /**
221
283
  * A menu box as the ordered items it names, dropping the lines that are not
@@ -238,7 +300,7 @@ export function menuItemsFromText(value) {
238
300
  /** The items as a box shows them: one line each, in order. */
239
301
  export function menuItemsText(items) {
240
302
  return items
241
- .map((item) => `${item.label} | ${item.url}${item.me === true ? ' | me' : ''}`)
303
+ .map((item) => `${item.label} | ${item.url}${item.rel === undefined ? '' : ` | ${item.rel}`}`)
242
304
  .join('\n');
243
305
  }
244
306
  /**
@@ -252,18 +314,42 @@ export function menuItemLineProblem(value) {
252
314
  const bad = menuLines(value).find((line) => menuItemOf(line) === undefined);
253
315
  return bad === undefined
254
316
  ? undefined
255
- : `A menu item is "Label | URL", one per line, where the URL is a path ` +
256
- `like /about/ or an absolute http:// or https:// URL, and ends ` +
257
- `"| me" for a link that should carry rel="me". "${bad}" is not one.`;
317
+ : `A menu item is "Label | URL", one per line, where the URL is ` +
318
+ `${LINK_URL_RULE}, and may end in ${LINK_REL_RULE}. "${bad}" is not one.`;
258
319
  }
259
320
  /**
260
- * Whether a menu URL is an absolute http(s) one.
321
+ * What a URL typed into a link box may be, in the words both boxes explain it
322
+ * in: the menu on the Navigation screen and the Links on a user's profile.
323
+ *
324
+ * One sentence fragment rather than two, so the rule {@link isLinkUrl} applies
325
+ * is worded the same wherever somebody is told about it (TASK-112). Somebody
326
+ * who learns one box should not be taught the wrong thing about the other.
327
+ */
328
+ export const LINK_URL_RULE = 'a path like /about/ or an absolute http:// or https:// URL, with no spaces in it';
329
+ /**
330
+ * Whether a typed URL is one a link box accepts: a site-root path, or an
331
+ * absolute http(s) URL, and no whitespace either way.
332
+ *
333
+ * A bare `about/` would be resolved against whatever page it was printed on,
334
+ * which is never what a link box means, and a `javascript:` or `data:` URL is
335
+ * not somewhere a reader goes.
261
336
  *
262
- * Deliberately a test rather than a normaliser: a menu keeps the URL as it was
337
+ * The whitespace half is not decoration. `new URL` does not throw on
338
+ * `https://shll.me/@a | me`: it reads the space and the bar as path
339
+ * characters and percent-encodes them, so a check that only asked the parser
340
+ * took a label, a URL and a trailing word as one URL and stored it — which is
341
+ * how a reader of shll.me ended up at `/author/a%20%7C%20me/` (TASK-112). A
342
+ * URL somebody types has no spaces in it; a space means they typed two things.
343
+ *
344
+ * Deliberately a test rather than a normaliser: a link keeps the URL as it was
263
345
  * typed, so `https://example.social/@me` is what the link says and what the
264
346
  * box shows it back as.
265
347
  */
266
- function isAbsoluteHttpUrl(url) {
348
+ export function isLinkUrl(url) {
349
+ if (url === '' || /\s/.test(url))
350
+ return false;
351
+ if (url.startsWith('/'))
352
+ return true;
267
353
  let parsed;
268
354
  try {
269
355
  parsed = new URL(url);
@@ -1 +1 @@
1
- {"version":3,"file":"navigation.js","sourceRoot":"","sources":["../../src/web/navigation.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"navigation.js","sourceRoot":"","sources":["../../src/web/navigation.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.5.0",
3
+ "version": "0.6.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",
@@ -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`. Each of
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", "me": true }
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 `me: true` on a link that should
886
- carry `rel="me"`. `url` is a site-root path or an absolute URL for somewhere
887
- else, so put it through the `url` filter and a site served from a subdirectory
888
- still links correctly. `current` is true for the item whose path is the one
889
- being rendered, comparing without the trailing slash; an item pointing off the
890
- site is never current. `me` is for the IndieWeb's identity check: Mastodon
891
- verifies a link on a profile by looking for a `rel="me"` link back, so a footer
892
- link to a profile marked `me` is what makes the tick appear.
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
@@ -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="me" href="{{ link.href }}">{{ link.label }}</a></li>
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 marked `me` carries `rel="me"`, which is how Mastodon and the rest of
21
- the IndieWeb verify that the site and the profile it links belong to the same
22
- person: the profile links back, and the pair is the proof.
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.me %} rel="me"{% endif %}{% if item.current %} class="is-current" aria-current="page"{% endif %}>{{ item.label }}</a></li>
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>