+114
eleventy/data.ts
+114
eleventy/data.ts
···
1
+
import { type Dict } from './type-utils';
2
+
3
+
interface ItemData {
4
+
title?: string;
5
+
subtitle?: string;
6
+
summary?: string;
7
+
tags?: string[];
8
+
date?: string | Date;
9
+
started?: string | Date;
10
+
updated?: string | Date;
11
+
updates?: Array<Update>;
12
+
qualifiers?: Qualifiers;
13
+
image?: Image;
14
+
link?: string;
15
+
splash?: string;
16
+
book?: Book;
17
+
standalonePage?: boolean;
18
+
featured?: boolean;
19
+
draft?: boolean;
20
+
/**
21
+
* Allow overriding the normal feed ID to enable keeping feed entries stable even if
22
+
* the slug changes.
23
+
*/
24
+
feedId?: string;
25
+
/** Markdown-enabled thanks to people who contributed to the thing. */
26
+
thanks?: string;
27
+
discuss?: {
28
+
hn: string;
29
+
lobsters: string;
30
+
};
31
+
sendEmail?: boolean;
32
+
feedOnly?: boolean;
33
+
}
34
+
35
+
/** Extending the base Eleventy item with my own data */
36
+
declare module '../types/eleventy' {
37
+
interface Data extends ItemData {}
38
+
}
39
+
40
+
type Image = string | { cdn: string } | { url: string };
41
+
42
+
export function imageValue(
43
+
data: Pick<ItemData, 'image' | 'book'> | undefined,
44
+
): string | undefined {
45
+
if (!data) return undefined;
46
+
47
+
if (typeof data.image == 'string') return data.image;
48
+
49
+
if (typeof data.image == 'object' && data.image != null) {
50
+
console.log(JSON.stringify(data.image));
51
+
let x =
52
+
'cdn' in data.image
53
+
? `https://cdn.chriskrycho.com/images/${data.image.cdn}`
54
+
: data.image.url;
55
+
console.log(x);
56
+
return x;
57
+
}
58
+
59
+
if (typeof data.book == 'object' && data.book != null) return data.book.cover;
60
+
61
+
return undefined;
62
+
}
63
+
64
+
export type Author = { author: string } | { authors: string[] };
65
+
66
+
export interface Review {
67
+
review?: {
68
+
rating:
69
+
| 'Required'
70
+
| 'Recommended'
71
+
| 'Recommended With Qualifications'
72
+
| 'Not Recommended';
73
+
summary: string;
74
+
};
75
+
}
76
+
77
+
export interface BookMeta {
78
+
title: string;
79
+
year?: number | string;
80
+
cover?: string;
81
+
link?: string;
82
+
}
83
+
84
+
// Must be a `type` alias because interfaces cannot extend unions.
85
+
export type Book = BookMeta & Author & Review;
86
+
87
+
export function isBook(maybeBook: unknown): maybeBook is Book {
88
+
if (typeof maybeBook !== 'object' || !maybeBook) {
89
+
return false;
90
+
}
91
+
92
+
const maybe = maybeBook as Dict<unknown>;
93
+
94
+
return (
95
+
typeof maybe.title === 'string' &&
96
+
(typeof maybe.author === 'string' || Array.isArray(maybe.authors)) &&
97
+
(typeof maybe.year == 'number' || typeof maybe.year === 'string') &&
98
+
typeof maybe.review === 'object' &&
99
+
typeof maybe.cover === 'string' &&
100
+
typeof maybe.link === 'string'
101
+
);
102
+
}
103
+
104
+
export interface Update {
105
+
at: string | Date;
106
+
changes: string;
107
+
}
108
+
109
+
export interface Qualifiers {
110
+
audience?: string;
111
+
context?: string;
112
+
epistemic?: string;
113
+
discusses?: string[];
114
+
}
+3
-89
eleventy/feed.ts
+3
-89
eleventy/feed.ts
···
2
2
import { DateTime } from 'luxon';
3
3
import { Maybe, Result } from 'true-myth';
4
4
5
-
import { Dict, EleventyClass, Item } from '../types/eleventy';
5
+
import { EleventyClass, Item } from '../types/eleventy';
6
6
import JsonFeed, { FeedItem } from '../types/json-feed';
7
7
import absoluteUrl from './absolute-url';
8
8
import { canParseDate } from './date-time';
···
12
12
import markdown from './markdown';
13
13
import localeDate from './locale-date';
14
14
import niceList from './nice-list';
15
+
import { type Book, imageValue, isBook, type Qualifiers } from './data';
15
16
16
17
type BuildInfo = typeof import('../site/_data/build');
17
18
type SiteConfig = typeof import('../site/_data/config');
···
19
20
/** Defensive function in case handed bad data */
20
21
const optionalString = (value: unknown): string | undefined =>
21
22
typeof value === 'string' ? value : undefined;
22
-
23
-
type Author = { author: string } | { authors: string[] };
24
-
25
-
interface Review {
26
-
review?: {
27
-
rating:
28
-
| 'Required'
29
-
| 'Recommended'
30
-
| 'Recommended With Qualifications'
31
-
| 'Not Recommended';
32
-
summary: string;
33
-
};
34
-
}
35
-
36
-
interface BookMeta {
37
-
title: string;
38
-
year?: number | string;
39
-
cover?: string;
40
-
link?: string;
41
-
}
42
-
43
-
// Must be a `type` alias because interfaces cannot extend unions.
44
-
type Book = BookMeta & Author & Review;
45
-
46
-
interface Update {
47
-
at: string | Date;
48
-
changes: string;
49
-
}
50
-
51
-
/** Extending the base Eleventy item with my own data */
52
-
declare module '../types/eleventy' {
53
-
interface Data {
54
-
title?: string;
55
-
subtitle?: string;
56
-
summary?: string;
57
-
tags?: string[];
58
-
date?: string | Date;
59
-
started?: string | Date;
60
-
updated?: string | Date;
61
-
updates?: Array<Update>;
62
-
qualifiers?: Qualifiers;
63
-
image?: string;
64
-
link?: string;
65
-
splash?: string;
66
-
book?: Book;
67
-
standalonePage?: boolean;
68
-
featured?: boolean;
69
-
draft?: boolean;
70
-
/**
71
-
* Allow overriding the normal feed ID to enable keeping feed entries stable even if
72
-
* the slug changes.
73
-
*/
74
-
feedId?: string;
75
-
/** Markdown-enabled thanks to people who contributed to the thing. */
76
-
thanks?: string;
77
-
discuss?: {
78
-
hn: string;
79
-
lobsters: string;
80
-
};
81
-
sendEmail?: boolean;
82
-
feedOnly?: boolean;
83
-
}
84
-
}
85
-
86
-
interface Qualifiers {
87
-
audience?: string;
88
-
context?: string;
89
-
epistemic?: string;
90
-
discusses?: string[];
91
-
}
92
-
93
-
function isBook(maybeBook: unknown): maybeBook is Book {
94
-
if (typeof maybeBook !== 'object' || !maybeBook) {
95
-
return false;
96
-
}
97
-
98
-
const maybe = maybeBook as Dict<unknown>;
99
-
100
-
return (
101
-
typeof maybe.title === 'string' &&
102
-
(typeof maybe.author === 'string' || Array.isArray(maybe.authors)) &&
103
-
(typeof maybe.year == 'number' || typeof maybe.year === 'string') &&
104
-
typeof maybe.review === 'object' &&
105
-
typeof maybe.cover === 'string' &&
106
-
typeof maybe.link === 'string'
107
-
);
108
-
}
109
23
110
24
const joinAuthors = (authors: string[]): Result<string, string> => {
111
25
switch (authors.length) {
···
305
219
item.data?.updated instanceof Date
306
220
? isoDate(item.data.updated)
307
221
: undefined,
308
-
image: optionalString(item.data?.image ?? item.data?.book?.cover),
222
+
image: imageValue(item.data),
309
223
external_url: optionalString(item.data?.link ?? item.data?.book?.link),
310
224
tags: Array.isArray(item.data?.tags) ? item.data?.tags : [],
311
225
banner_image:
+7
eleventy/type-utils.ts
+7
eleventy/type-utils.ts
+2
-9
types/eleventy.d.ts
+2
-9
types/eleventy.d.ts
···
1
1
type ServeStaticOptions = import('serve-static').ServeStaticOptions;
2
2
3
-
// ---- Utility types
4
-
interface Dict<T = unknown> {
5
-
[key: string]: T | undefined;
6
-
}
7
-
8
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
9
-
type AnyFunction<T = any> = (...args: any[]) => T;
10
-
11
3
// ---- Eleventy types
12
4
interface BrowserSyncConfig {
13
5
/** Browsersync includes a user-interface that is accessed via a separate port. The UI allows to controls all devices, push sync updates and much more. */
···
67
59
68
60
type Empty = { isEmpty: true; empty: string } | { isEmpty: false };
69
61
62
+
import { AnyFunction, Dict } from '../eleventy/type-utils';
70
63
import type { GrayMatterFile, GrayMatterOption } from 'gray-matter';
71
64
72
65
export type Engine = (input: string) => GrayMatterFile<string>;
···
162
155
In addition to Global Data Files global data can be added to the Eleventy
163
156
config object using the `addGlobalData` method. This is especially useful
164
157
for plugins.
165
-
158
+
166
159
The first value of `addGlobalData` is the key that will be available to
167
160
your templates and the second value is the value of the value returned to
168
161
the template.