+2
-2
.eleventy.js
+2
-2
.eleventy.js
+5
-5
eleventy/absolute-url.ts
+5
-5
eleventy/absolute-url.ts
···
1
-
import { Result } from 'true-myth'
2
-
import { URL } from 'url'
3
-
import { logErr, toString } from './utils'
1
+
import { Result } from 'true-myth';
2
+
import { URL } from 'url';
3
+
import { logErr, toString } from './utils';
4
4
5
5
export const absoluteUrl = (path: string, baseUrl: string): string =>
6
6
Result.tryOrElse(logErr, () => new URL(path, baseUrl))
7
7
.map(toString)
8
-
.unwrapOr(path)
8
+
.unwrapOr(path);
9
9
10
-
export default absoluteUrl
10
+
export default absoluteUrl;
+48
-48
eleventy/archive-by-year.ts
+48
-48
eleventy/archive-by-year.ts
···
1
-
import { DateTime } from 'luxon'
2
-
import { Item } from '../types/eleventy'
3
-
import { fromDateOrString, canParseDate } from './date-time'
1
+
import { DateTime } from 'luxon';
2
+
import { Item } from '../types/eleventy';
3
+
import { fromDateOrString, canParseDate } from './date-time';
4
4
5
5
export interface Year {
6
-
name: string
7
-
months: Month[]
6
+
name: string;
7
+
months: Month[];
8
8
}
9
9
10
10
export interface Month {
11
-
name: string
12
-
days: Day[]
11
+
name: string;
12
+
days: Day[];
13
13
}
14
14
15
15
export interface Day {
16
-
name: string
17
-
items: Item[]
16
+
name: string;
17
+
items: Item[];
18
18
}
19
19
20
-
type Archive = Year[]
20
+
type Archive = Year[];
21
21
22
22
export const enum Order {
23
23
OldFirst = 'OLD_FIRST',
24
24
NewFirst = 'NEW_FIRST',
25
25
}
26
26
27
-
const YEAR_FORMAT = 'yyyy'
28
-
const MONTH_FORMAT = 'MMM'
29
-
const DAY_FORMAT = 'dd'
27
+
const YEAR_FORMAT = 'yyyy';
28
+
const MONTH_FORMAT = 'MMM';
29
+
const DAY_FORMAT = 'dd';
30
30
31
-
export const TZ_OPTIONS: Intl.DateTimeFormatOptions = { timeZoneName: 'America/Denver' }
31
+
export const TZ_OPTIONS: Intl.DateTimeFormatOptions = { timeZoneName: 'America/Denver' };
32
32
33
-
type DayMap = Map<number, Item[]>
34
-
type MonthMap = Map<number, [string, DayMap]>
35
-
type YearMap = Map<number, [string, MonthMap]>
33
+
type DayMap = Map<number, Item[]>;
34
+
type MonthMap = Map<number, [string, DayMap]>;
35
+
type YearMap = Map<number, [string, MonthMap]>;
36
36
37
37
export const byDate = (order: Order) => (a: Item, b: Item): number => {
38
38
// Sort order is meaningless if either doesn't have the relevant comparison key.
39
39
if (!canParseDate(a.date) || !canParseDate(b.date)) {
40
-
return 0
40
+
return 0;
41
41
}
42
42
43
-
const aDate = fromDateOrString(a.date).toSeconds()
44
-
const bDate = fromDateOrString(b.date).toSeconds()
45
-
return order === Order.OldFirst ? aDate - bDate : bDate - aDate
46
-
}
43
+
const aDate = fromDateOrString(a.date).toSeconds();
44
+
const bDate = fromDateOrString(b.date).toSeconds();
45
+
return order === Order.OldFirst ? aDate - bDate : bDate - aDate;
46
+
};
47
47
48
48
export const byUpdated = (order: Order) => (a: Item, b: Item): number => {
49
49
// Sort order is meaningless if either doesn't have the relevant comparison key.
50
50
if (!a.data || !b.data) {
51
-
return 0
51
+
return 0;
52
52
}
53
53
54
54
// Likewise if the value isn't parseable for either.
55
55
if (!canParseDate(a.data.updated) || !canParseDate(b.data.updated)) {
56
-
return 0
56
+
return 0;
57
57
}
58
58
59
-
const aUpdated = fromDateOrString(a.data.updated).toSeconds()
60
-
const bUpdated = fromDateOrString(b.data.updated).toSeconds()
61
-
return order === Order.OldFirst ? aUpdated - bUpdated : bUpdated - aUpdated
62
-
}
59
+
const aUpdated = fromDateOrString(a.data.updated).toSeconds();
60
+
const bUpdated = fromDateOrString(b.data.updated).toSeconds();
61
+
return order === Order.OldFirst ? aUpdated - bUpdated : bUpdated - aUpdated;
62
+
};
63
63
64
-
const dateTimeFromItem = ({ date }: Item): DateTime => fromDateOrString(date)
64
+
const dateTimeFromItem = ({ date }: Item): DateTime => fromDateOrString(date);
65
65
66
66
const daysFromDayMap = (dayMap: DayMap, byEntries: SortByEntries, order: Order): Day[] =>
67
67
[...dayMap.entries()].sort(byEntries).map(([, items]) => ({
68
68
name: dateTimeFromItem(items[0]).toFormat(DAY_FORMAT, TZ_OPTIONS),
69
69
items: items.slice().sort(byDate(order)),
70
-
}))
70
+
}));
71
71
72
72
const monthsFromMonthMap = (
73
73
monthMap: MonthMap,
···
77
77
[...monthMap.entries()].sort(byEntries).map(([, [name, dayMap]]) => ({
78
78
name,
79
79
days: daysFromDayMap(dayMap, byEntries, order),
80
-
}))
80
+
}));
81
81
82
82
const dayMapFromItem = (item: Item, dateTime: DateTime): DayMap =>
83
-
new Map([[dateTime.day, [item]]])
83
+
new Map([[dateTime.day, [item]]]);
84
84
85
85
const monthMapFromItem = (item: Item, dateTime: DateTime): MonthMap =>
86
86
new Map([
···
88
88
dateTime.month,
89
89
[dateTime.toFormat(MONTH_FORMAT, TZ_OPTIONS), dayMapFromItem(item, dateTime)],
90
90
],
91
-
])
91
+
]);
92
92
93
93
function toYearMap(yearMap: YearMap, item: Item): YearMap {
94
-
const itemDateTime = dateTimeFromItem(item)
95
-
const { year, month, day } = itemDateTime
94
+
const itemDateTime = dateTimeFromItem(item);
95
+
const { year, month, day } = itemDateTime;
96
96
97
-
const existingMonthMap = yearMap.get(year)
97
+
const existingMonthMap = yearMap.get(year);
98
98
if (existingMonthMap) {
99
-
const existingDayMap = existingMonthMap[1].get(month)
99
+
const existingDayMap = existingMonthMap[1].get(month);
100
100
if (existingDayMap) {
101
-
const existingDay = existingDayMap[1].get(day)
101
+
const existingDay = existingDayMap[1].get(day);
102
102
if (existingDay) {
103
-
existingDay.push(item)
103
+
existingDay.push(item);
104
104
} else {
105
-
existingDayMap[1].set(day, [item])
105
+
existingDayMap[1].set(day, [item]);
106
106
}
107
107
} else {
108
108
existingMonthMap[1].set(month, [
109
109
itemDateTime.toFormat(MONTH_FORMAT, TZ_OPTIONS),
110
110
dayMapFromItem(item, itemDateTime),
111
-
])
111
+
]);
112
112
}
113
113
} else {
114
114
yearMap.set(year, [
115
115
itemDateTime.toFormat(YEAR_FORMAT, TZ_OPTIONS),
116
116
monthMapFromItem(item, itemDateTime),
117
-
])
117
+
]);
118
118
}
119
-
return yearMap
119
+
return yearMap;
120
120
}
121
121
122
-
type SortByEntries = ([a]: [number, unknown], [b]: [number, unknown]) => number
122
+
type SortByEntries = ([a]: [number, unknown], [b]: [number, unknown]) => number;
123
123
124
124
const sortBy = (order: Order): SortByEntries => ([a], [b]): number =>
125
-
order === Order.OldFirst ? a - b : b - a
125
+
order === Order.OldFirst ? a - b : b - a;
126
126
127
127
const intoYear = (byEntries: SortByEntries, order: Order) => ([, [name, monthMap]]: [
128
128
number,
···
130
130
]): Year => ({
131
131
name,
132
132
months: monthsFromMonthMap(monthMap, byEntries, order),
133
-
})
133
+
});
134
134
135
135
/**
136
136
Given a collection of items, generate a yearly-and-monthly-and-daily grouping.
···
138
138
@param items The collection to produce an archive for
139
139
*/
140
140
export default function archiveByYear(items: Item[], order = Order.NewFirst): Archive {
141
-
const byOrder = sortBy(order)
141
+
const byOrder = sortBy(order);
142
142
143
143
return [...items.reduce(toYearMap, new Map()).entries()]
144
144
.sort(byOrder)
145
-
.map(intoYear(byOrder, order))
145
+
.map(intoYear(byOrder, order));
146
146
}
+74
-74
eleventy/config.ts
+74
-74
eleventy/config.ts
···
1
-
import { env } from 'process'
1
+
import { env } from 'process';
2
2
3
-
import { DateTime } from 'luxon'
3
+
import { DateTime } from 'luxon';
4
4
5
-
import { Config, Item, UserConfig, Collection } from '../types/eleventy'
6
-
import absoluteUrl from './absolute-url'
7
-
import archiveByYear, { byDate, byUpdated, Order } from './archive-by-year'
8
-
import copyright from './copyright'
9
-
import currentPage from './current-page'
10
-
import toDateTime, { canParseDate, fromDateOrString, TZ } from './date-time'
11
-
import isoDate from './iso-date'
12
-
import localeDate from './locale-date'
13
-
import markdown from './markdown'
14
-
import * as PageLinks from './page-links'
15
-
import spacewell from './plugin-spacewell'
16
-
import typeset from './plugin-typeset'
17
-
import siteTitle from './site-title'
18
-
import excludingCollection from './excluding-collection'
19
-
import toCollection from './to-collection'
5
+
import { Config, Item, UserConfig, Collection } from '../types/eleventy';
6
+
import absoluteUrl from './absolute-url';
7
+
import archiveByYear, { byDate, byUpdated, Order } from './archive-by-year';
8
+
import copyright from './copyright';
9
+
import currentPage from './current-page';
10
+
import toDateTime, { canParseDate, fromDateOrString, TZ } from './date-time';
11
+
import isoDate from './iso-date';
12
+
import localeDate from './locale-date';
13
+
import markdown from './markdown';
14
+
import * as PageLinks from './page-links';
15
+
import spacewell from './plugin-spacewell';
16
+
import typeset from './plugin-typeset';
17
+
import siteTitle from './site-title';
18
+
import excludingCollection from './excluding-collection';
19
+
import toCollection from './to-collection';
20
20
21
-
import './feed' // for extension of types -- TODO: move those types elsewhere!
21
+
import './feed'; // for extension of types -- TODO: move those types elsewhere!
22
22
23
-
const BUILD_TIME = DateTime.fromJSDate(new Date(), TZ).toSeconds()
23
+
const BUILD_TIME = DateTime.fromJSDate(new Date(), TZ).toSeconds();
24
24
25
25
// Hack around the fact that in dev I want this to work on *every run*, but in prod builds
26
26
// I just want one time for the whole run.
27
27
const buildTime = () =>
28
-
env.DEV ? DateTime.fromJSDate(new Date(), TZ).toSeconds() : BUILD_TIME
28
+
env.DEV ? DateTime.fromJSDate(new Date(), TZ).toSeconds() : BUILD_TIME;
29
29
30
30
const isLive = (item: Item) =>
31
31
canParseDate(item.date) &&
32
32
fromDateOrString(item.date).toSeconds() <= buildTime() &&
33
-
!item.data?.draft
33
+
!item.data?.draft;
34
34
35
-
const isNotVoid = <A>(a: A | null | undefined): a is A => a != null
35
+
const isNotVoid = <A>(a: A | null | undefined): a is A => a != null;
36
36
37
37
const excludingStandalonePages = (item: Item): boolean =>
38
-
!(item.data?.standalonePage ?? false)
38
+
!(item.data?.standalonePage ?? false);
39
39
40
-
const filterStandalonePages = (items: Item[]) => items.filter(excludingStandalonePages)
40
+
const filterStandalonePages = (items: Item[]) => items.filter(excludingStandalonePages);
41
41
42
42
/**
43
43
Use a path to create a collection from all items contained within it.
···
55
55
.filter(isLive)
56
56
.filter(excludingStandalonePages)
57
57
.sort(byDate(Order.NewFirst)),
58
-
)
58
+
);
59
59
}
60
60
61
61
const inCollectionNamed = (name: string) => (item: Item): boolean =>
62
-
item.data?.collections[name]?.includes(item) ?? false
62
+
item.data?.collections[name]?.includes(item) ?? false;
63
63
64
64
function latest(collection: Collection): Item[] {
65
65
const all = collection
66
66
.getAll()
67
67
.filter(isLive)
68
68
.filter(excludingStandalonePages)
69
-
.sort(byDate(Order.NewFirst))
69
+
.sort(byDate(Order.NewFirst));
70
70
71
71
return [
72
72
all.find(inCollectionNamed('essays')),
···
76
76
all.find(inCollectionNamed('elsewhere')),
77
77
]
78
78
.filter(isNotVoid)
79
-
.sort(byDate(Order.NewFirst))
79
+
.sort(byDate(Order.NewFirst));
80
80
}
81
81
82
-
const hasUpdated = (item: Item) => canParseDate(item.data?.updated)
82
+
const hasUpdated = (item: Item) => canParseDate(item.data?.updated);
83
83
84
84
function mostRecentlyUpdated(collection: Collection): Item[] {
85
85
const all = collection
···
87
87
.filter(isLive)
88
88
.filter(excludingStandalonePages)
89
89
.filter(hasUpdated)
90
-
.sort(byUpdated(Order.NewFirst))
90
+
.sort(byUpdated(Order.NewFirst));
91
91
92
92
return [
93
93
all.find(inCollectionNamed('essays')),
···
95
95
all.find(inCollectionNamed('library')),
96
96
]
97
97
.filter(isNotVoid)
98
-
.sort(byUpdated(Order.NewFirst))
98
+
.sort(byUpdated(Order.NewFirst));
99
99
}
100
100
101
-
const isFeatured = (item: Item): boolean => item.data?.featured ?? false
101
+
const isFeatured = (item: Item): boolean => item.data?.featured ?? false;
102
102
103
103
const featured = (collection: Collection): Item[] =>
104
104
collection
···
107
107
.filter(excludingStandalonePages)
108
108
.sort(byDate(Order.NewFirst))
109
109
.filter(isFeatured)
110
-
.sort(byDate(Order.NewFirst))
110
+
.sort(byDate(Order.NewFirst));
111
111
112
112
function config(config: Config): UserConfig {
113
113
config.addPlugin(
···
115
115
only: '.content-block',
116
116
disable: ['smallCaps', 'hyphenate', 'ligatures', 'smallCaps'],
117
117
}),
118
-
)
118
+
);
119
119
120
-
config.addPlugin(spacewell({ emDashes: true, enDashes: true }))
120
+
config.addPlugin(spacewell({ emDashes: true, enDashes: true }));
121
121
122
-
config.addFilter('md', markdown.render.bind(markdown))
123
-
config.addFilter('inlineMd', markdown.renderInline.bind(markdown))
122
+
config.addFilter('md', markdown.render.bind(markdown));
123
+
config.addFilter('inlineMd', markdown.renderInline.bind(markdown));
124
124
125
-
config.addFilter('toCollection', toCollection)
126
-
config.addFilter('stringify', (obj) => JSON.stringify(obj))
127
-
config.addFilter('archiveByYears', archiveByYear)
128
-
config.addFilter('absoluteUrl', absoluteUrl)
129
-
config.addFilter('isoDate', isoDate)
130
-
config.addFilter('toDateTime', toDateTime)
131
-
config.addFilter('siteTitle', siteTitle)
125
+
config.addFilter('toCollection', toCollection);
126
+
config.addFilter('stringify', (obj) => JSON.stringify(obj));
127
+
config.addFilter('archiveByYears', archiveByYear);
128
+
config.addFilter('absoluteUrl', absoluteUrl);
129
+
config.addFilter('isoDate', isoDate);
130
+
config.addFilter('toDateTime', toDateTime);
131
+
config.addFilter('siteTitle', siteTitle);
132
132
config.addFilter('withValidDate', (items: Item[]) =>
133
133
items.filter((item) => canParseDate(item.date)),
134
-
)
135
-
config.addFilter('current', currentPage)
136
-
config.addFilter('editLink', PageLinks.edit)
137
-
config.addFilter('historyLink', PageLinks.history)
138
-
config.addFilter('sourceLink', PageLinks.source)
139
-
config.addFilter('excludingCollection', excludingCollection)
140
-
config.addFilter('excludingStandalonePages', filterStandalonePages)
134
+
);
135
+
config.addFilter('current', currentPage);
136
+
config.addFilter('editLink', PageLinks.edit);
137
+
config.addFilter('historyLink', PageLinks.history);
138
+
config.addFilter('sourceLink', PageLinks.source);
139
+
config.addFilter('excludingCollection', excludingCollection);
140
+
config.addFilter('excludingStandalonePages', filterStandalonePages);
141
141
config.addFilter('concat', (a: Item[] | undefined, b: Item[] | undefined) => {
142
-
return (a ?? []).concat(b ?? [])
143
-
})
144
-
config.addFilter('localeDate', localeDate)
145
-
config.addFilter('isLive', (items: Item[]) => items.filter(isLive))
142
+
return (a ?? []).concat(b ?? []);
143
+
});
144
+
config.addFilter('localeDate', localeDate);
145
+
config.addFilter('isLive', (items: Item[]) => items.filter(isLive));
146
146
147
-
config.addShortcode('localeDate', localeDate)
148
-
config.addShortcode('copyright', copyright)
147
+
config.addShortcode('localeDate', localeDate);
148
+
config.addShortcode('copyright', copyright);
149
149
150
-
config.addPassthroughCopy('site/_redirects')
151
-
config.addPassthroughCopy('site/robots.txt')
150
+
config.addPassthroughCopy('site/_redirects');
151
+
config.addPassthroughCopy('site/robots.txt');
152
152
config.addPassthroughCopy({
153
153
'site/_assets': 'assets',
154
154
'site/_styles': 'styles',
155
-
})
155
+
});
156
156
157
-
config.addCollection('live', (collection) => collection.getAllSorted().filter(isLive))
157
+
config.addCollection('live', (collection) => collection.getAllSorted().filter(isLive));
158
158
config.addCollection('pages', (collection) =>
159
159
collection.getAll().filter((item) => item.data?.standalonePage),
160
-
)
161
-
addCollectionFromDir(config, 'journal')
162
-
addCollectionFromDir(config, 'essays')
163
-
addCollectionFromDir(config, 'library')
164
-
addCollectionFromDir(config, 'notes')
165
-
addCollectionFromDir(config, 'elsewhere')
160
+
);
161
+
addCollectionFromDir(config, 'journal');
162
+
addCollectionFromDir(config, 'essays');
163
+
addCollectionFromDir(config, 'library');
164
+
addCollectionFromDir(config, 'notes');
165
+
addCollectionFromDir(config, 'elsewhere');
166
166
167
-
config.addCollection('latest', latest)
168
-
config.addCollection('updated', mostRecentlyUpdated)
169
-
config.addCollection('featured', featured)
167
+
config.addCollection('latest', latest);
168
+
config.addCollection('updated', mostRecentlyUpdated);
169
+
config.addCollection('featured', featured);
170
170
171
-
config.setLibrary('md', markdown)
171
+
config.setLibrary('md', markdown);
172
172
173
-
config.setDataDeepMerge(true)
173
+
config.setDataDeepMerge(true);
174
174
175
175
return {
176
176
dir: {
···
183
183
dataTemplateEngine: 'njk',
184
184
htmlTemplateEngine: 'njk',
185
185
markdownTemplateEngine: 'njk',
186
-
}
186
+
};
187
187
}
188
188
189
189
// Needs to be this way so that the import resolves as expected in `.eleventy.js`.
190
-
module.exports = config
190
+
module.exports = config;
+6
-6
eleventy/copyright.ts
+6
-6
eleventy/copyright.ts
···
1
-
const ROLLOVER_DATE = new Date('January 1, 2020')
2
-
const BASE = 'copyright Chris Krycho, 2019'
1
+
const ROLLOVER_DATE = new Date('January 1, 2020');
2
+
const BASE = 'copyright Chris Krycho, 2019';
3
3
const LICENSE = {
4
4
content: {
5
5
name: 'Creative Commons Attribution 4.0',
···
11
11
url:
12
12
'https://github.com/chriskrycho/v5.chriskrycho.com/blob/master/LICENSE.md#software',
13
13
},
14
-
}
14
+
};
15
15
16
16
const copyrightDate = (date: Date): string =>
17
-
date >= ROLLOVER_DATE ? `${BASE}–${date.getFullYear()}` : BASE
17
+
date >= ROLLOVER_DATE ? `${BASE}–${date.getFullYear()}` : BASE;
18
18
19
19
const copyright = (date: Date, license: keyof typeof LICENSE = 'content'): string =>
20
20
`${copyrightDate(date)} under a <a href='${LICENSE[license].url}'>${
21
21
LICENSE[license].name
22
-
}</a> license`
22
+
}</a> license`;
23
23
24
-
export default copyright
24
+
export default copyright;
+3
-3
eleventy/current-page.ts
+3
-3
eleventy/current-page.ts
···
1
-
import { Page, Item } from '../types/eleventy'
1
+
import { Page, Item } from '../types/eleventy';
2
2
3
3
export const currentPage = (allItems: Item[], page: Page): Item | undefined =>
4
-
allItems.find((item) => item.url === page.url)
4
+
allItems.find((item) => item.url === page.url);
5
5
6
-
export default currentPage
6
+
export default currentPage;
+13
-13
eleventy/date-time.ts
+13
-13
eleventy/date-time.ts
···
1
-
import { DateTime, DateTimeOptions } from 'luxon'
1
+
import { DateTime, DateTimeOptions } from 'luxon';
2
2
3
-
type Parse = (text: string, options?: DateTimeOptions | undefined) => DateTime
3
+
type Parse = (text: string, options?: DateTimeOptions | undefined) => DateTime;
4
4
5
5
const maybeDateTime = (parse: Parse, input: string): DateTime | null => {
6
-
const parsed = parse(input)
7
-
return parsed.isValid ? parsed : null
8
-
}
6
+
const parsed = parse(input);
7
+
return parsed.isValid ? parsed : null;
8
+
};
9
9
10
-
export const TZ = { zone: 'America/Denver' }
10
+
export const TZ = { zone: 'America/Denver' };
11
11
12
12
// Same parsing rules as 11ty itself uses: ISO or SQL, nothing else.
13
13
export const toDateTime = (input: string): DateTime => {
14
14
const dateTime =
15
15
maybeDateTime((s) => DateTime.fromISO(s, TZ), input) ??
16
-
maybeDateTime((s) => DateTime.fromSQL(s, TZ), input)
17
-
if (!dateTime) throw new Error(`Could not parse date: ${input}`)
18
-
return dateTime
19
-
}
16
+
maybeDateTime((s) => DateTime.fromSQL(s, TZ), input);
17
+
if (!dateTime) throw new Error(`Could not parse date: ${input}`);
18
+
return dateTime;
19
+
};
20
20
21
21
export const canParseDate = (date: unknown): date is string | Date =>
22
-
typeof date === 'string' || date instanceof Date
22
+
typeof date === 'string' || date instanceof Date;
23
23
24
24
export const fromDateOrString = (date: Date | string): DateTime =>
25
-
typeof date === 'string' ? toDateTime(date) : DateTime.fromJSDate(date, TZ)
25
+
typeof date === 'string' ? toDateTime(date) : DateTime.fromJSDate(date, TZ);
26
26
27
-
export default toDateTime
27
+
export default toDateTime;
+2
-2
eleventy/excluding-collection.ts
+2
-2
eleventy/excluding-collection.ts
···
1
-
import { Item } from '../types/eleventy'
1
+
import { Item } from '../types/eleventy';
2
2
3
3
export default function excludingCollection(items: Item[], collection: Item[]): Item[] {
4
-
return items.filter((item) => !collection.includes(item))
4
+
return items.filter((item) => !collection.includes(item));
5
5
}
+91
-91
eleventy/feed.ts
+91
-91
eleventy/feed.ts
···
1
-
import stripTags from 'striptags'
1
+
import stripTags from 'striptags';
2
2
3
-
import { Dict, EleventyClass, Item } from '../types/eleventy'
4
-
import JsonFeed, { FeedItem } from '../types/json-feed'
5
-
import absoluteUrl from './absolute-url'
6
-
import { canParseDate } from './date-time'
7
-
import isoDate from './iso-date'
8
-
import siteTitle from './site-title'
9
-
import toCollection from './to-collection'
10
-
import markdown from './markdown'
11
-
import localeDate from './locale-date'
12
-
import { DateTime } from 'luxon'
3
+
import { Dict, EleventyClass, Item } from '../types/eleventy';
4
+
import JsonFeed, { FeedItem } from '../types/json-feed';
5
+
import absoluteUrl from './absolute-url';
6
+
import { canParseDate } from './date-time';
7
+
import isoDate from './iso-date';
8
+
import siteTitle from './site-title';
9
+
import toCollection from './to-collection';
10
+
import markdown from './markdown';
11
+
import localeDate from './locale-date';
12
+
import { DateTime } from 'luxon';
13
13
14
-
type BuildInfo = typeof import('../site/_data/build')
15
-
type SiteConfig = typeof import('../site/_data/config')
14
+
type BuildInfo = typeof import('../site/_data/build');
15
+
type SiteConfig = typeof import('../site/_data/config');
16
16
17
17
/** Defensive function in case handed bad data */
18
18
const optionalString = (value: unknown): string | undefined =>
19
-
typeof value === 'string' ? value : undefined
19
+
typeof value === 'string' ? value : undefined;
20
20
21
21
interface Book {
22
-
title: string
23
-
author: string
24
-
year?: number | string
22
+
title: string;
23
+
author: string;
24
+
year?: number | string;
25
25
review?: {
26
26
rating:
27
27
| 'Required'
28
28
| 'Recommended'
29
29
| 'Recommended With Qualifications'
30
-
| 'Not Recommended'
31
-
summary: string
32
-
}
33
-
cover?: string
34
-
link?: string
30
+
| 'Not Recommended';
31
+
summary: string;
32
+
};
33
+
cover?: string;
34
+
link?: string;
35
35
}
36
36
37
37
/** Extending the base Eleventy item with my own data */
38
38
declare module '../types/eleventy' {
39
39
interface Data {
40
-
title?: string
41
-
subtitle?: string
42
-
summary?: string
43
-
tags?: string[]
44
-
date?: string | Date
45
-
updated?: string | Date
40
+
title?: string;
41
+
subtitle?: string;
42
+
summary?: string;
43
+
tags?: string[];
44
+
date?: string | Date;
45
+
updated?: string | Date;
46
46
qualifiers?: {
47
-
audience?: string
48
-
epistemic?: string
49
-
}
50
-
image?: string
51
-
link?: string
52
-
splash?: string
53
-
book?: Book
54
-
standalonePage?: boolean
55
-
featured?: boolean
56
-
draft?: boolean
47
+
audience?: string;
48
+
epistemic?: string;
49
+
};
50
+
image?: string;
51
+
link?: string;
52
+
splash?: string;
53
+
book?: Book;
54
+
standalonePage?: boolean;
55
+
featured?: boolean;
56
+
draft?: boolean;
57
57
/**
58
58
* Allow overriding the normal feed ID to enable keeping feed entries stable even if
59
59
* the slug changes.
60
60
*/
61
-
feedId?: string
61
+
feedId?: string;
62
62
}
63
63
}
64
64
···
70
70
| 'bigint'
71
71
| 'string'
72
72
| 'symbol'
73
-
| 'function'
73
+
| 'function';
74
74
75
75
function hasType<T extends TypeOf>(type: T, item: unknown): item is T {
76
-
return typeof item === type
76
+
return typeof item === type;
77
77
}
78
78
79
79
function isBook(maybeBook: unknown): maybeBook is Book {
80
80
if (typeof maybeBook !== 'object' || !maybeBook) {
81
-
return false
81
+
return false;
82
82
}
83
83
84
-
const maybe = maybeBook as Dict<unknown>
84
+
const maybe = maybeBook as Dict<unknown>;
85
85
86
86
return (
87
87
typeof maybe.title === 'string' &&
···
90
90
hasType('object', maybe.review) &&
91
91
hasType('string', maybe.cover) &&
92
92
hasType('string', maybe.link)
93
-
)
93
+
);
94
94
}
95
95
96
96
function describe(book: Book): string {
97
97
const linked = (content: string): string =>
98
-
book.link ? `<a href='${book.link}' rel='nofollow'>${content}</a>` : content
98
+
book.link ? `<a href='${book.link}' rel='nofollow'>${content}</a>` : content;
99
99
100
-
const year = book.year ? ` (${book.year})` : ''
100
+
const year = book.year ? ` (${book.year})` : '';
101
101
102
-
const title = linked(`<cite>${book.title}</cite>`)
103
-
const bookInfo = `<p>${title}, ${book.author}${year}</p>`
102
+
const title = linked(`<cite>${book.title}</cite>`);
103
+
const bookInfo = `<p>${title}, ${book.author}${year}</p>`;
104
104
const review = book.review
105
105
? `<p><b>${book.review.rating}:</b> ${book.review.summary}</p>`
106
-
: ''
106
+
: '';
107
107
108
-
return `${bookInfo}\n${review}`
108
+
return `${bookInfo}\n${review}`;
109
109
}
110
110
111
111
function entryTitleFor(item: Item): string {
112
-
return item.data?.title ?? localeDate(item.date, 'yyyy.MM.dd.HHmm')
112
+
return item.data?.title ?? localeDate(item.date, 'yyyy.MM.dd.HHmm');
113
113
}
114
114
115
115
function contentHtmlFor(
···
120
120
const subtitle =
121
121
typeof item.data?.subtitle === 'string'
122
122
? `<p><i>${markdown.renderInline(item.data.subtitle)}</i></p>`
123
-
: ''
123
+
: '';
124
124
125
125
const audience =
126
126
typeof item.data?.qualifiers?.audience === 'string'
127
127
? `<p><b>Assumed audience:</b> ${markdown.renderInline(
128
128
item.data.qualifiers.audience,
129
129
)}</p>`
130
-
: ''
130
+
: '';
131
131
132
132
const epistemicStatus =
133
133
typeof item.data?.qualifiers?.epistemic === 'string'
134
134
? `<p><b>Epistemic status:</b> ${markdown.renderInline(
135
135
item.data.qualifiers.epistemic,
136
136
)}</p>`
137
-
: ''
137
+
: '';
138
138
139
-
const book = item.data?.book
140
-
const bookInfo = isBook(book) ? describe(book) : ''
139
+
const book = item.data?.book;
140
+
const bookInfo = isBook(book) ? describe(book) : '';
141
141
142
142
const reply = includeReplyViaEmail
143
143
? ((): string => {
144
-
const replySubject = encodeURIComponent(entryTitleFor(item))
145
-
const replyUrl = `mailto:${config.author.email}?subject=${replySubject}`
146
-
return `<hr/><p><a href="${replyUrl}">Reply via email!</a></p>`
144
+
const replySubject = encodeURIComponent(entryTitleFor(item));
145
+
const replyUrl = `mailto:${config.author.email}?subject=${replySubject}`;
146
+
return `<hr/><p><a href="${replyUrl}">Reply via email!</a></p>`;
147
147
})()
148
-
: ''
148
+
: '';
149
149
150
-
return subtitle + audience + epistemicStatus + bookInfo + item.templateContent + reply
150
+
return subtitle + audience + epistemicStatus + bookInfo + item.templateContent + reply;
151
151
}
152
152
153
153
function titleFor(item: Item): string | undefined {
154
-
const sectionMarker = toCollection(item.inputPath)
155
-
const { title } = item.data ?? {}
156
-
return sectionMarker && title ? `[${sectionMarker}] ${title}` : undefined
154
+
const sectionMarker = toCollection(item.inputPath);
155
+
const { title } = item.data ?? {};
156
+
return sectionMarker && title ? `[${sectionMarker}] ${title}` : undefined;
157
157
}
158
158
159
159
function summaryFor(item: Item): string {
160
-
return item.data?.summary ?? item.data?.subtitle ?? stripTags(item.templateContent)
160
+
return item.data?.summary ?? item.data?.subtitle ?? stripTags(item.templateContent);
161
161
}
162
162
163
163
/**
···
192
192
optionalString(item.data?.book?.cover) ??
193
193
optionalString(item.data?.image),
194
194
}
195
-
: null
195
+
: null;
196
196
197
197
type JSONFeedConfig = {
198
-
items: Item[]
199
-
config: SiteConfig
200
-
permalink: string
201
-
title: string
202
-
includeReplyViaEmail: boolean
203
-
}
198
+
items: Item[];
199
+
config: SiteConfig;
200
+
permalink: string;
201
+
title: string;
202
+
includeReplyViaEmail: boolean;
203
+
};
204
204
205
205
/**
206
206
Generate a JSON Feed compliant object for a given set of items.
···
228
228
DateTime.fromISO(b as string).toMillis() -
229
229
DateTime.fromISO(a as string).toMillis(),
230
230
),
231
-
})
231
+
});
232
232
233
233
interface EleventyData {
234
234
collections: {
235
-
all: Item[]
236
-
[key: string]: Item[] | undefined
237
-
}
238
-
config: SiteConfig
239
-
page: Item
240
-
pages: BuildInfo[]
241
-
permalink?: string
235
+
all: Item[];
236
+
[key: string]: Item[] | undefined;
237
+
};
238
+
config: SiteConfig;
239
+
page: Item;
240
+
pages: BuildInfo[];
241
+
permalink?: string;
242
242
}
243
243
244
-
type ClassData = ReturnType<NonNullable<EleventyClass['data']>>
244
+
type ClassData = ReturnType<NonNullable<EleventyClass['data']>>;
245
245
246
246
export class JSONFeed implements EleventyClass {
247
-
declare collection?: string
248
-
declare title?: string
249
-
declare permalink?: string
247
+
declare collection?: string;
248
+
declare title?: string;
249
+
declare permalink?: string;
250
250
251
-
includeReplyViaEmail = true
251
+
includeReplyViaEmail = true;
252
252
253
253
data(): ClassData {
254
254
return {
···
258
258
return (
259
259
this.permalink ??
260
260
(this.collection ? `/${this.collection}/feed.json` : '/feed.json')
261
-
)
261
+
);
262
262
},
263
-
}
263
+
};
264
264
}
265
265
266
266
render({ collections, config, page }: EleventyData): string {
267
-
const collection = this.collection ?? 'live'
268
-
const title = this.title ?? config.title.normal
267
+
const collection = this.collection ?? 'live';
268
+
const title = this.title ?? config.title.normal;
269
269
return JSON.stringify(
270
270
jsonFeed({
271
271
items: collections[collection] ?? [],
···
274
274
title,
275
275
includeReplyViaEmail: this.includeReplyViaEmail,
276
276
}),
277
-
)
277
+
);
278
278
}
279
279
}
280
280
281
-
export default JSONFeed
281
+
export default JSONFeed;
+3
-3
eleventy/iso-date.ts
+3
-3
eleventy/iso-date.ts
···
1
-
import { fromDateOrString } from './date-time'
1
+
import { fromDateOrString } from './date-time';
2
2
3
3
const isoDate = (date: Date | string): string =>
4
-
fromDateOrString(date).toISO({ includeOffset: true })
4
+
fromDateOrString(date).toISO({ includeOffset: true });
5
5
6
-
export default isoDate
6
+
export default isoDate;
+4
-4
eleventy/locale-date.ts
+4
-4
eleventy/locale-date.ts
···
1
-
import { fromDateOrString } from './date-time'
2
-
import { TZ_OPTIONS } from './archive-by-year'
1
+
import { fromDateOrString } from './date-time';
2
+
import { TZ_OPTIONS } from './archive-by-year';
3
3
4
4
export const localeDate = (date: Date | string, format = 'DDD'): string =>
5
-
fromDateOrString(date).toFormat(format, TZ_OPTIONS)
5
+
fromDateOrString(date).toFormat(format, TZ_OPTIONS);
6
6
7
-
export default localeDate
7
+
export default localeDate;
+31
-31
eleventy/markdown.ts
+31
-31
eleventy/markdown.ts
···
1
-
import { getLanguage, highlight as _highlight } from 'highlight.js'
2
-
import markdownIt from 'markdown-it'
3
-
import abbr from 'markdown-it-abbr'
4
-
import anchor, { AnchorOptions } from 'markdown-it-anchor'
5
-
import defList from 'markdown-it-deflist'
6
-
import footnotes from 'markdown-it-footnote'
7
-
import implicitFigures from 'markdown-it-implicit-figures'
8
-
import sup from 'markdown-it-sup'
9
-
import Core from 'markdown-it/lib/parser_core'
10
-
import Token from 'markdown-it/lib/token'
11
-
import { env } from 'process'
12
-
import { Result } from 'true-myth'
13
-
import slugify from 'uslug'
1
+
import { getLanguage, highlight as _highlight } from 'highlight.js';
2
+
import markdownIt from 'markdown-it';
3
+
import abbr from 'markdown-it-abbr';
4
+
import anchor, { AnchorOptions } from 'markdown-it-anchor';
5
+
import defList from 'markdown-it-deflist';
6
+
import footnotes from 'markdown-it-footnote';
7
+
import implicitFigures from 'markdown-it-implicit-figures';
8
+
import sup from 'markdown-it-sup';
9
+
import Core from 'markdown-it/lib/parser_core';
10
+
import Token from 'markdown-it/lib/token';
11
+
import { env } from 'process';
12
+
import { Result } from 'true-myth';
13
+
import slugify from 'uslug';
14
14
15
15
type HighlightError = {
16
-
short: string
17
-
long: string
18
-
}
16
+
short: string;
17
+
long: string;
18
+
};
19
19
20
20
function highlight(
21
21
languageName: string,
···
27
27
long: `error highlighting '${languageName}' with highlight.js\ncontent:\n${content}\n`,
28
28
},
29
29
() => _highlight(languageName, content).value,
30
-
)
30
+
);
31
31
}
32
32
33
33
function logErr(err: HighlightError): void {
34
-
console.error(env['DEBUG'] ? err.long : err.short)
34
+
console.error(env['DEBUG'] ? err.long : err.short);
35
35
}
36
36
37
37
/**
···
51
51
content: opts.permalinkSymbol,
52
52
}),
53
53
new Token('span_close', 'span', -1),
54
-
]
54
+
];
55
55
56
56
const openTokens = [
57
57
Object.assign(new Token('link_open', 'a', 1), {
···
60
60
['href', opts.permalinkHref?.(slug)],
61
61
],
62
62
}),
63
-
]
63
+
];
64
64
65
-
const closeTokens = [...marker, new Token('link_close', 'a', -1)]
65
+
const closeTokens = [...marker, new Token('link_close', 'a', -1)];
66
66
67
-
state.tokens[idx + 1].children?.unshift(...openTokens)
68
-
state.tokens[idx + 1].children?.push(...closeTokens)
67
+
state.tokens[idx + 1].children?.unshift(...openTokens);
68
+
state.tokens[idx + 1].children?.push(...closeTokens);
69
69
}
70
70
71
71
const md = markdownIt({
···
73
73
highlight: (str, lang) =>
74
74
lang && getLanguage(lang)
75
75
? highlight(lang, str).unwrapOrElse((e) => {
76
-
logErr(e)
77
-
return str
76
+
logErr(e);
77
+
return str;
78
78
})
79
79
: str,
80
80
})
···
92
92
renderPermalink,
93
93
slugify,
94
94
})
95
-
.use(abbr)
95
+
.use(abbr);
96
96
97
97
md.renderer.rules.footnote_caption = (tokens, idx): string => {
98
-
let n = Number(tokens[idx].meta.id + 1).toString()
98
+
let n = Number(tokens[idx].meta.id + 1).toString();
99
99
100
100
if (tokens[idx].meta.subId > 0) {
101
-
n += ':' + tokens[idx].meta.subId
101
+
n += ':' + tokens[idx].meta.subId;
102
102
}
103
103
104
-
return n
105
-
}
104
+
return n;
105
+
};
106
106
107
-
export default md
107
+
export default md;
+5
-5
eleventy/page-links.ts
+5
-5
eleventy/page-links.ts
···
1
-
import SiteConfig from '../site/_data/config'
1
+
import SiteConfig from '../site/_data/config';
2
2
3
-
const corrected = (path: string): string => path.replace(/^\.\//, '')
3
+
const corrected = (path: string): string => path.replace(/^\.\//, '');
4
4
5
5
/** Link to the history of the file on GitHub */
6
6
export const history = (path: string): string =>
7
-
`${SiteConfig.repo}/commits/main/${corrected(path)}`
7
+
`${SiteConfig.repo}/commits/main/${corrected(path)}`;
8
8
9
9
/** Link to edit the file on GitHub */
10
10
export const edit = (inputPath: string): string =>
11
-
`${SiteConfig.repo}/edit/main/${corrected(inputPath)}`
11
+
`${SiteConfig.repo}/edit/main/${corrected(inputPath)}`;
12
12
13
13
/** Link to view the file on GitHub */
14
14
export const source = (inputPath: string): string =>
15
-
`${SiteConfig.repo}/blob/main/${corrected(inputPath)}`
15
+
`${SiteConfig.repo}/blob/main/${corrected(inputPath)}`;
+18
-18
eleventy/plugin-spacewell.ts
+18
-18
eleventy/plugin-spacewell.ts
···
1
-
import spacewell, { Options } from '../lib/spacewell'
2
-
import { Config } from 'eleventy'
3
-
import cheerio from 'cheerio'
1
+
import spacewell, { Options } from '../lib/spacewell';
2
+
import { Config } from 'eleventy';
3
+
import cheerio from 'cheerio';
4
4
5
-
type Plugin = (eleventyConfig: Config, pluginNamespace?: string) => string | void
6
-
type Content = Parameters<Config['addTransform']>[0]
5
+
type Plugin = (eleventyConfig: Config, pluginNamespace?: string) => string | void;
6
+
type Content = Parameters<Config['addTransform']>[0];
7
7
8
-
const PAGE_CONTENT_SELECTOR = '.content'
8
+
const PAGE_CONTENT_SELECTOR = '.content';
9
9
10
10
export default function plugin(options: Options): Plugin {
11
-
const wellSpaced = spacewell(options)
11
+
const wellSpaced = spacewell(options);
12
12
13
13
const transform: Plugin = (eleventyConfig, pluginNamespace) => {
14
14
const t = (content: Content, outputPath: string): string => {
15
15
if (!outputPath.endsWith('.html')) {
16
-
return content
16
+
return content;
17
17
}
18
18
19
-
const dom = cheerio.load(content)
20
-
const pageContent = dom.html(PAGE_CONTENT_SELECTOR)
19
+
const dom = cheerio.load(content);
20
+
const pageContent = dom.html(PAGE_CONTENT_SELECTOR);
21
21
22
22
if (pageContent) {
23
-
dom(PAGE_CONTENT_SELECTOR).replaceWith(wellSpaced(pageContent))
24
-
return dom.html()
23
+
dom(PAGE_CONTENT_SELECTOR).replaceWith(wellSpaced(pageContent));
24
+
return dom.html();
25
25
}
26
26
27
-
return content
28
-
}
27
+
return content;
28
+
};
29
29
30
30
return pluginNamespace
31
31
? eleventyConfig.namespace(pluginNamespace, () => {
32
-
eleventyConfig.addTransform('spacewell', t)
32
+
eleventyConfig.addTransform('spacewell', t);
33
33
})
34
-
: eleventyConfig.addTransform('spacewell', t)
35
-
}
34
+
: eleventyConfig.addTransform('spacewell', t);
35
+
};
36
36
37
-
return transform
37
+
return transform;
38
38
}
+9
-9
eleventy/plugin-typeset.ts
+9
-9
eleventy/plugin-typeset.ts
···
1
-
import typeset, { Options } from 'typeset'
2
-
import { Config } from 'eleventy'
1
+
import typeset, { Options } from 'typeset';
2
+
import { Config } from 'eleventy';
3
3
4
-
type Plugin = (eleventyConfig: Config, pluginNamespace?: string) => string | void
5
-
type Content = Parameters<Config['addTransform']>[0]
4
+
type Plugin = (eleventyConfig: Config, pluginNamespace?: string) => string | void;
5
+
type Content = Parameters<Config['addTransform']>[0];
6
6
7
7
export default function plugin(options: Options): Plugin {
8
8
const transform: Plugin = (eleventyConfig, pluginNamespace) => {
9
9
const t = (content: Content, outputPath: string): string =>
10
-
outputPath.endsWith('.html') ? typeset(content, options) : content
10
+
outputPath.endsWith('.html') ? typeset(content, options) : content;
11
11
12
12
return pluginNamespace
13
13
? eleventyConfig.namespace(pluginNamespace, () => {
14
-
eleventyConfig.addTransform('typeset', t)
14
+
eleventyConfig.addTransform('typeset', t);
15
15
})
16
-
: eleventyConfig.addTransform('typeset', t)
17
-
}
16
+
: eleventyConfig.addTransform('typeset', t);
17
+
};
18
18
19
-
return transform
19
+
return transform;
20
20
}
+9
-9
eleventy/site-title.ts
+9
-9
eleventy/site-title.ts
···
1
-
import SiteConfig from '../site/_data/config'
2
-
type SiteConfig = typeof SiteConfig
1
+
import SiteConfig from '../site/_data/config';
2
+
type SiteConfig = typeof SiteConfig;
3
3
4
-
const SEP = ' — '
4
+
const SEP = ' — ';
5
5
6
6
const baseTitle = (siteName: string, authorName: string): string =>
7
-
`${siteName}, by ${authorName}`
7
+
`${siteName}, by ${authorName}`;
8
8
9
-
const extended = (base: string, itemTitle: string): string => `${itemTitle}${SEP}${base}`
9
+
const extended = (base: string, itemTitle: string): string => `${itemTitle}${SEP}${base}`;
10
10
11
11
export const siteTitle = (pageTitle: string | undefined, config: SiteConfig): string => {
12
-
const base = baseTitle(config.title.normal, config.author.name)
12
+
const base = baseTitle(config.title.normal, config.author.name);
13
13
return pageTitle && pageTitle !== config.title.normal
14
14
? extended(base, pageTitle)
15
-
: base
16
-
}
15
+
: base;
16
+
};
17
17
18
-
export default siteTitle
18
+
export default siteTitle;
+5
-5
eleventy/to-collection.ts
+5
-5
eleventy/to-collection.ts
···
1
-
import path from 'path'
1
+
import path from 'path';
2
2
3
-
const EXCLUDES = ['.', 'site']
3
+
const EXCLUDES = ['.', 'site'];
4
4
5
-
const excluded = (entry: string): boolean => !EXCLUDES.includes(entry)
5
+
const excluded = (entry: string): boolean => !EXCLUDES.includes(entry);
6
6
7
7
/** Get the collection corresponding to a given path slug */
8
8
export function toCollection(slug: string): string | undefined {
9
-
return path.dirname(slug.trim()).split(path.sep).filter(excluded)[0]
9
+
return path.dirname(slug.trim()).split(path.sep).filter(excluded)[0];
10
10
}
11
11
12
-
export default toCollection
12
+
export default toCollection;
+5
-5
eleventy/utils.ts
+5
-5
eleventy/utils.ts
···
1
-
import { env } from 'process'
1
+
import { env } from 'process';
2
2
3
3
interface ToString {
4
-
toString(): string
4
+
toString(): string;
5
5
}
6
6
7
-
export const toString = (a: ToString): string => a.toString()
7
+
export const toString = (a: ToString): string => a.toString();
8
8
9
9
export const logErr = (err: unknown): void => {
10
10
if (env['DEBUG']) {
11
-
console.error(err)
11
+
console.error(err);
12
12
}
13
-
}
13
+
};
+18
-18
gulpfile.js
+18
-18
gulpfile.js
···
1
-
const { src, dest, parallel, series, watch } = require('gulp')
2
-
const sass = require('gulp-sass')
3
-
const del = require('del')
1
+
const { src, dest, parallel, series, watch } = require('gulp');
2
+
const sass = require('gulp-sass');
3
+
const del = require('del');
4
4
5
-
sass.compiler = require('sass')
5
+
sass.compiler = require('sass');
6
6
7
7
const build = (file) => () =>
8
8
src(file)
···
14
14
})
15
15
.on('error', sass.logError),
16
16
)
17
-
.pipe(dest('./site/_styles'))
17
+
.pipe(dest('./site/_styles'));
18
18
19
19
function style() {
20
-
return build('./site/_includes/styles/style.scss')()
20
+
return build('./site/_includes/styles/style.scss')();
21
21
}
22
22
23
23
function print() {
24
-
return build('./site/_includes/styles/print.scss')()
24
+
return build('./site/_includes/styles/print.scss')();
25
25
}
26
26
27
27
function fonts() {
28
-
return build('./site/_includes/styles/fonts.scss')()
28
+
return build('./site/_includes/styles/fonts.scss')();
29
29
}
30
30
31
31
function clean() {
···
33
33
'./site/_includes/styles/style.css',
34
34
'./site/_includes/styles/fonts.css',
35
35
'./site/_includes/styles/print.css',
36
-
])
36
+
]);
37
37
}
38
38
39
-
const all = parallel(style, fonts, print)
39
+
const all = parallel(style, fonts, print);
40
40
41
41
function watchStyles() {
42
-
watch('./site/_includes/styles/**/*.scss', all)
42
+
watch('./site/_includes/styles/**/*.scss', all);
43
43
}
44
44
45
-
exports.clean = clean
46
-
exports.style = style
47
-
exports.fonts = fonts
48
-
exports.print = print
49
-
exports.watch = watchStyles
50
-
exports.all = all
51
-
exports.default = series(clean, all)
45
+
exports.clean = clean;
46
+
exports.style = style;
47
+
exports.fonts = fonts;
48
+
exports.print = print;
49
+
exports.watch = watchStyles;
50
+
exports.all = all;
51
+
exports.default = series(clean, all);
+21
-21
lib/spacewell.ts
+21
-21
lib/spacewell.ts
···
1
-
import { logErr } from '../eleventy/utils'
1
+
import { logErr } from '../eleventy/utils';
2
2
3
-
const THIN_SP = ' '
3
+
const THIN_SP = ' ';
4
4
// const HAIR_SP = ' '
5
-
const EM_DASH = '—'
6
-
const EN_DASH = '–'
5
+
const EM_DASH = '—';
6
+
const EN_DASH = '–';
7
7
8
8
/**
9
9
Wrap em dashes and their immediate neighbors in non-breaking span and hair spaces.
···
13
13
return content.replace(
14
14
/(—|—|—|—)/g,
15
15
`${THIN_SP}${EM_DASH}${THIN_SP}`,
16
-
)
16
+
);
17
17
}
18
18
19
19
/**
···
22
22
variant is used.
23
23
*/
24
24
export function enDashes(content: string): string {
25
-
const OPEN = '<dash-wrap>'
26
-
const CLOSE = '</dash-wrap>'
25
+
const OPEN = '<dash-wrap>';
26
+
const CLOSE = '</dash-wrap>';
27
27
28
28
// Do numbers first. Include a variety of ways digits might be constructed,
29
29
// including e.g. Bible verses, other punctuation, etc.
30
-
const numPatt = /([\d:.⅒⅑⅛⅜⅝⅞⅐⅙⅚⅕⅖⅗⅘¼¾⅓⅔½]+) ?(–|–|&8211;|–) ?([\d:.⅒⅑⅛⅜⅝⅞⅐⅙⅚⅕⅖⅗⅘¼¾⅓⅔½]+)/g
31
-
const wordPatt = /(\w+) ?(–|–|&8211;|&x2013;) ?(\w+)/g
32
-
const replacement = `${OPEN}$1${THIN_SP}${EN_DASH}${THIN_SP}$3${CLOSE}`
30
+
const numPatt = /([\d:.⅒⅑⅛⅜⅝⅞⅐⅙⅚⅕⅖⅗⅘¼¾⅓⅔½]+) ?(–|–|&8211;|–) ?([\d:.⅒⅑⅛⅜⅝⅞⅐⅙⅚⅕⅖⅗⅘¼¾⅓⅔½]+)/g;
31
+
const wordPatt = /(\w+) ?(–|–|&8211;|&x2013;) ?(\w+)/g;
32
+
const replacement = `${OPEN}$1${THIN_SP}${EN_DASH}${THIN_SP}$3${CLOSE}`;
33
33
34
-
return content.replace(numPatt, replacement).replace(wordPatt, replacement)
34
+
return content.replace(numPatt, replacement).replace(wordPatt, replacement);
35
35
}
36
36
37
37
/**
···
43
43
// sentences. Basically, I *think* it should just be anytime
44
44
// that the period follows a capital letter, but there may be
45
45
// the occasional exception.
46
-
logErr('`spacewell#initials()` not yet implemented.')
47
-
return content
46
+
logErr('`spacewell#initials()` not yet implemented.');
47
+
return content;
48
48
}
49
49
50
50
// NOTE: keys are mapped to names of functions in the module.
···
52
52
emDashes,
53
53
enDashes,
54
54
initials,
55
-
} as const
55
+
} as const;
56
56
57
57
export interface Options {
58
-
emDashes?: boolean
59
-
enDashes?: boolean
60
-
initials?: boolean
58
+
emDashes?: boolean;
59
+
enDashes?: boolean;
60
+
initials?: boolean;
61
61
}
62
62
63
63
/**
···
66
66
@param options Options for which spacing rules to use.
67
67
@param content A document element to apply rules to.
68
68
*/
69
-
export default function spacewell(options: Options): (content: string) => string
70
-
export default function spacewell(options: Options, content: string): string
69
+
export default function spacewell(options: Options): (content: string) => string;
70
+
export default function spacewell(options: Options, content: string): string;
71
71
export default function spacewell(
72
72
options: Options,
73
73
content?: string,
···
75
75
function op(c: string): string {
76
76
return (Object.keys(options) as Array<keyof Options>)
77
77
.filter((key) => Boolean(options[key]))
78
-
.reduce((transformed, cfgKey) => FUNCTIONS[cfgKey](transformed), c)
78
+
.reduce((transformed, cfgKey) => FUNCTIONS[cfgKey](transformed), c);
79
79
}
80
80
81
-
return content ? op(content) : op
81
+
return content ? op(content) : op;
82
82
}
-1
package-lock.json
-1
package-lock.json
-1
package.json
-1
package.json
+1
-1
site/_data/config.js
+1
-1
site/_data/config.js
+1
-1
site/_data/pages.js
+1
-1
site/_data/pages.js
+4
-4
site/_feeds/elsewhere.11ty.js
+4
-4
site/_feeds/elsewhere.11ty.js
+4
-4
site/_feeds/essays.11ty.js
+4
-4
site/_feeds/essays.11ty.js
+4
-4
site/_feeds/feed-without-reply.11ty.js
+4
-4
site/_feeds/feed-without-reply.11ty.js
···
1
-
import JSONFeed from '../../eleventy/feed'
1
+
import JSONFeed from '../../eleventy/feed';
2
2
3
3
module.exports = class FeedWithoutReply extends JSONFeed {
4
-
includeReplyViaEmail = false
5
-
permalink = '/feed-without-reply.json'
6
-
}
4
+
includeReplyViaEmail = false;
5
+
permalink = '/feed-without-reply.json';
6
+
};
+4
-4
site/_feeds/journal.11ty.js
+4
-4
site/_feeds/journal.11ty.js
+4
-4
site/_feeds/library.11ty.js
+4
-4
site/_feeds/library.11ty.js
+4
-4
site/_feeds/notes.11ty.js
+4
-4
site/_feeds/notes.11ty.js
+112
-109
types/eleventy.d.ts
+112
-109
types/eleventy.d.ts
···
1
-
type ServeStaticOptions = import('serve-static').ServeStaticOptions
1
+
type ServeStaticOptions = import('serve-static').ServeStaticOptions;
2
2
3
3
// ---- Utility types
4
4
interface Dict<T = unknown> {
5
-
[key: string]: T | undefined
5
+
[key: string]: T | undefined;
6
6
}
7
7
8
8
// eslint-disable-next-line @typescript-eslint/no-explicit-any
9
-
type AnyFunction<T = any> = (...args: any[]) => T
9
+
type AnyFunction<T = any> = (...args: any[]) => T;
10
10
11
11
// ---- Eleventy types
12
12
interface BrowserSyncConfig {
···
14
14
ui?:
15
15
| false
16
16
| {
17
-
port: number
17
+
port: number;
18
18
weinre?: {
19
-
port: number
20
-
}
21
-
}
19
+
port: number;
20
+
};
21
+
};
22
22
23
23
files?:
24
24
| string
25
25
| Array<
26
26
| string
27
27
| {
28
-
match?: string[]
29
-
fn?: (event: unknown, file: string) => unknown
28
+
match?: string[];
29
+
fn?: (event: unknown, file: string) => unknown;
30
30
}
31
31
>
32
-
| false
32
+
| false;
33
33
34
-
watchEvents?: string[]
35
-
watch?: boolean
36
-
ignore?: string[]
37
-
single?: boolean
34
+
watchEvents?: string[];
35
+
watch?: boolean;
36
+
ignore?: string[];
37
+
single?: boolean;
38
38
watchOptions?: {
39
-
ignoreInitial?: boolean
40
-
ignored?: boolean
41
-
}
39
+
ignoreInitial?: boolean;
40
+
ignored?: boolean;
41
+
};
42
42
server?:
43
43
| boolean
44
44
| string
45
45
| string[]
46
46
| {
47
-
baseDir?: string
48
-
directory?: boolean
49
-
serveStaticOptions?: ServeStaticOptions
50
-
routes?: Dict<string>
51
-
}
47
+
baseDir?: string;
48
+
directory?: boolean;
49
+
serveStaticOptions?: ServeStaticOptions;
50
+
routes?: Dict<string>;
51
+
};
52
52
53
53
/* eslint-disable @typescript-eslint/no-explicit-any */
54
54
proxy?:
55
55
| string
56
56
| boolean
57
57
| {
58
-
target?: string
59
-
ws?: boolean
60
-
middleware?: any
61
-
reqHeaders?: string[]
62
-
proxyReq?: any
63
-
proxyRes?: any
64
-
}
58
+
target?: string;
59
+
ws?: boolean;
60
+
middleware?: any;
61
+
reqHeaders?: string[];
62
+
proxyReq?: any;
63
+
proxyRes?: any;
64
+
};
65
65
/* eslint-enable @typescript-eslint/no-explicit-any */
66
66
}
67
67
68
-
type Empty = { isEmpty: true; empty: string } | { isEmpty: false }
68
+
type Empty = { isEmpty: true; empty: string } | { isEmpty: false };
69
69
70
70
type GrayMatter = {
71
-
content: string
72
-
excerpt?: string
73
-
orig: Buffer
74
-
language: string
75
-
matter: string
76
-
stringify(): string
77
-
} & Empty
71
+
content: string;
72
+
excerpt?: string;
73
+
orig: Buffer;
74
+
language: string;
75
+
matter: string;
76
+
stringify(): string;
77
+
} & Empty;
78
78
79
-
export type Engine = (input: string) => GrayMatter
79
+
export type Engine = (input: string) => GrayMatter;
80
80
81
81
export type EngineName =
82
82
| 'html'
···
90
90
| 'ejs'
91
91
| 'haml'
92
92
| 'pug'
93
-
| 'jstl'
93
+
| 'jstl';
94
94
95
95
export interface Page {
96
96
/** the full path to the source input file (including the path to the input directory) */
97
-
inputPath: string
97
+
inputPath: string;
98
98
/**
99
99
Mapped from the input file name, useful for permalinks. Read more about
100
100
[`fileSlug`].
101
101
102
102
[`fileSlug`]: https://www.11ty.io/docs/data/#fileslug
103
103
*/
104
-
fileSlug: string
104
+
fileSlug: string;
105
105
/** the full path to the output file to be written for this content */
106
-
outputPath: string
106
+
outputPath: string;
107
107
/** url used to link to this piece of content. */
108
-
url: string
108
+
url: string;
109
109
/**
110
110
the resolved date used for sorting. Read more about [Content Dates].
111
111
112
112
[Content Dates]: https://www.11ty.io/docs/dates/
113
113
*/
114
-
date: string | Date
114
+
date: string | Date;
115
115
}
116
116
117
117
interface Data {
118
118
collections: {
119
-
[key: string]: Item[] | undefined
120
-
}
119
+
[key: string]: Item[] | undefined;
120
+
};
121
121
}
122
122
123
123
/** An `Item` is just like a `Page`, but with the actual data from render available. */
124
124
interface Item extends Page {
125
125
/** all data for this piece of content (includes any data inherited from layouts) */
126
-
data?: Data
126
+
data?: Data;
127
127
128
128
/** the rendered content of this template. This does *not• include layout wrappers */
129
-
templateContent: string
129
+
templateContent: string;
130
130
}
131
131
132
132
export interface Collection {
133
-
getAll(): Item[]
133
+
getAll(): Item[];
134
134
135
135
/**
136
136
Note that while Array `.reverse()` mutates the array in-place, all Eleventy
···
140
140
141
141
[warning]: https://www.11ty.io/docs/collections/#array-reverse
142
142
*/
143
-
getAllSorted(): Item[]
143
+
getAllSorted(): Item[];
144
144
145
-
getFilteredByTag(tagName: string): Item[]
145
+
getFilteredByTag(tagName: string): Item[];
146
146
147
-
getFilteredByGlob(glob: string | string[]): Item[]
147
+
getFilteredByGlob(glob: string | string[]): Item[];
148
148
}
149
149
150
150
interface Renderer {
151
-
render(input: string): string
151
+
render(input: string): string;
152
152
}
153
153
154
-
type Raw<T = unknown> = string | Buffer | Promise<T>
154
+
type Raw<T = unknown> = string | Buffer | Promise<T>;
155
155
156
156
export interface EleventyClass {
157
157
data?: () => {
158
-
excludeFromEleventyCollections?: boolean
159
-
standalonePage?: boolean
160
-
permalink?: (...args: unknown[]) => Raw
161
-
[key: string]: unknown
162
-
}
158
+
excludeFromEleventyCollections?: boolean;
159
+
standalonePage?: boolean;
160
+
permalink?: (...args: unknown[]) => Raw;
161
+
[key: string]: unknown;
162
+
};
163
163
164
-
render(...args: unknown[]): Raw
164
+
render(...args: unknown[]): Raw;
165
165
}
166
166
167
167
export interface Config {
168
168
dir?: {
169
169
/** Controls the top level directory/file/glob that we’ll use to look for templates. */
170
-
input?: string
170
+
input?: string;
171
171
172
172
/** Controls the directory inside which the finished templates will be written to. */
173
-
output?: string
173
+
output?: string;
174
174
175
175
/**
176
176
The includes directory is meant for [Eleventy layouts], include files, extends
···
181
181
182
182
**Note:** This value is relative to your input directory.
183
183
*/
184
-
includes?: string
184
+
includes?: string;
185
185
186
186
/**
187
187
This configuration option is optional but useful if you want your [Eleventy
···
202
202
203
203
**Note:** This value is relative to your input directory.
204
204
*/
205
-
layouts?: string
205
+
layouts?: string;
206
206
207
-
data?: string
208
-
}
207
+
data?: string;
208
+
};
209
209
210
210
/**
211
211
The `data.dir` global data files run through this template engine before transforming
···
213
213
214
214
[Global Data Files]: https://www.11ty.io/docs/data-global/
215
215
*/
216
-
dataTemplateEngine?: EngineName | false
217
-
markdownTemplateEngine?: EngineName | false
218
-
htmlTemplateEngine?: EngineName | false
219
-
templateFormats?: EngineName[]
216
+
dataTemplateEngine?: EngineName | false;
217
+
markdownTemplateEngine?: EngineName | false;
218
+
htmlTemplateEngine?: EngineName | false;
219
+
templateFormats?: EngineName[];
220
220
221
221
/**
222
222
If your site lives in a different subdirectory (particularly useful with GitHub
···
225
225
structure. Leading or trailing slashes are all normalized away, so don’t worry about
226
226
it.
227
227
*/
228
-
pathPrefix?: string
229
-
passthroughFileCopy?: boolean
230
-
htmlOutputSuffx?: string
231
-
jsDataFileSuffix?: string
228
+
pathPrefix?: string;
229
+
passthroughFileCopy?: boolean;
230
+
htmlOutputSuffx?: string;
231
+
jsDataFileSuffix?: string;
232
232
233
233
addCollection(
234
234
name: string,
235
235
builder: (
236
236
collection: Collection,
237
237
) => Page[] | Record<string, unknown> | Promise<Record<string, unknown>>,
238
-
): void
238
+
): void;
239
239
240
-
addFilter(name: string, filter: AnyFunction): string | void
240
+
addFilter(name: string, filter: AnyFunction): string | void;
241
241
242
242
addTransform(
243
243
name: string,
244
244
transform: (content: string, outputPath: string) => string | Promise<string>,
245
-
): string
245
+
): string;
246
246
247
247
addLinter(
248
248
name: string,
···
251
251
inputPath: string,
252
252
outputPath: string,
253
253
) => void | Promise<void>,
254
-
): void
254
+
): void;
255
255
256
-
addShortcode(name: string, shortcode: AnyFunction<string>): string
257
-
addLiquidShortcode(name: string, shortcode: AnyFunction<string>): void
258
-
addNunjucksShortcode(name: string, shortcode: AnyFunction<string>): void
259
-
addHandlebarsShortcode(name: string, shortcode: AnyFunction<string>): void
260
-
addJavascriptShortcode(name: string, shortcode: AnyFunction<string>): void
256
+
addShortcode(name: string, shortcode: AnyFunction<string>): string;
257
+
addLiquidShortcode(name: string, shortcode: AnyFunction<string>): void;
258
+
addNunjucksShortcode(name: string, shortcode: AnyFunction<string>): void;
259
+
addHandlebarsShortcode(name: string, shortcode: AnyFunction<string>): void;
260
+
addJavascriptShortcode(name: string, shortcode: AnyFunction<string>): void;
261
261
addPairedShortcode(
262
262
name: string,
263
263
shortcode: <A>(content: string, ...args: A[]) => string,
264
-
): void
264
+
): void;
265
265
266
-
addJavaScriptFunction(name: string, fn: AnyFunction<string>): void
266
+
addJavaScriptFunction(name: string, fn: AnyFunction<string>): void;
267
267
268
268
addLiquidFilter(
269
269
name: string,
270
270
filter: <A>(...args: A[]) => unknown,
271
-
): Record<string, unknown>
271
+
): Record<string, unknown>;
272
272
273
-
addNunjucksFilter(name: string, filter: <A>(...args: A[]) => unknown): void
273
+
addNunjucksFilter(name: string, filter: <A>(...args: A[]) => unknown): void;
274
274
275
275
addNunjucksAsyncFilter(
276
276
name: string,
277
277
filter: <T>(value: T, callback: <E, R>(err: E | null, res: R) => unknown) => void,
278
-
): void
278
+
): void;
279
279
addNunjucksAsyncFilter(
280
280
name: string,
281
281
filter: <T, U>(
···
283
283
value2: U,
284
284
callback: <E, R>(err: E | null, res: R) => unknown,
285
285
) => void,
286
-
): void
286
+
): void;
287
287
addNunjucksAsyncFilter(
288
288
name: string,
289
289
filter: <T, U, V>(
···
292
292
value3: V,
293
293
callback: <E, R>(err: E | null, res: R) => unknown,
294
294
) => void,
295
-
): void
295
+
): void;
296
296
297
-
addHandlebarsHelper(name: string, helper: AnyFunction<string>): Record<string, unknown>
297
+
addHandlebarsHelper(
298
+
name: string,
299
+
helper: AnyFunction<string>,
300
+
): Record<string, unknown>;
298
301
299
302
/**
300
303
Plugins are custom code that Eleventy can import into a project from an external
···
306
309
plugin’s documentation (e.g. the [eleventy-plugin-syntaxhighlight README](https://github.com/11ty/eleventy-plugin-syntaxhighlight/blob/master/README.md))
307
310
to learn what options are available to you.
308
311
*/
309
-
addPlugin<F extends AnyFunction>(fn: F, config?: Parameters<F>[0]): void
312
+
addPlugin<F extends AnyFunction>(fn: F, config?: Parameters<F>[0]): void;
310
313
311
314
/**
312
315
Searching the entire directory structure for files to copy based on file extensions
···
318
321
319
322
@param path The file path to copy (may be an individual file or directory.)
320
323
*/
321
-
addPassthroughCopy(path: string): void
322
-
addPassthroughCopy(mapping: { [inputPath: string]: string }): void
324
+
addPassthroughCopy(path: string): void;
325
+
addPassthroughCopy(mapping: { [inputPath: string]: string }): void;
323
326
324
327
/**
325
328
You can namespace parts of your configuration using `eleventyConfig.namespace`. This
···
329
332
@param withName The string prefix to apply to the items.
330
333
@param context A callback in which to add your namespaced items.
331
334
*/
332
-
namespace(withName: string, context: () => void): void
335
+
namespace(withName: string, context: () => void): void;
333
336
334
-
setTemplateFormats(to: EngineName[]): void
337
+
setTemplateFormats(to: EngineName[]): void;
335
338
336
339
/**
337
340
* Opts in to a full deep merge when combining the Data Cascade. This will use
···
346
349
*
347
350
* @param to `true` to enable deep merge, `false` (the current default) to opt out.
348
351
*/
349
-
setDataDeepMerge(to: boolean): void
350
-
setWatchJavaScriptDependencies(to: boolean): void
351
-
setBrowserSyncConfig(to: BrowserSyncConfig): void
352
+
setDataDeepMerge(to: boolean): void;
353
+
setWatchJavaScriptDependencies(to: boolean): void;
354
+
setBrowserSyncConfig(to: BrowserSyncConfig): void;
352
355
setFrontMatterParsingOptions(to: {
353
-
excerpt?: boolean
354
-
excerpt_separator?: string
355
-
excerpt_alias?: string
356
-
engines?: Dict<Engine>
357
-
}): void
358
-
setLibrary(to: EngineName, using: Renderer): void
356
+
excerpt?: boolean;
357
+
excerpt_separator?: string;
358
+
excerpt_alias?: string;
359
+
engines?: Dict<Engine>;
360
+
}): void;
361
+
setLibrary(to: EngineName, using: Renderer): void;
359
362
}
360
363
361
364
type NonMethodNames<T> = {
362
-
[K in keyof T]: T[K] extends AnyFunction ? never : K
363
-
}[keyof T]
365
+
[K in keyof T]: T[K] extends AnyFunction ? never : K;
366
+
}[keyof T];
364
367
365
-
type F = NonMethodNames<Config>
368
+
type F = NonMethodNames<Config>;
366
369
367
-
export type UserConfig = Pick<Config, NonNullable<NonMethodNames<Config>>>
370
+
export type UserConfig = Pick<Config, NonNullable<NonMethodNames<Config>>>;
+39
-39
types/json-feed.d.ts
+39
-39
types/json-feed.d.ts
···
3
3
The URL of the version of the format the feed uses. This should appear at the very
4
4
top, though we recognize that not all JSON generators allow for ordering.
5
5
*/
6
-
version: 'https://jsonfeed.org/version/1'
6
+
version: 'https://jsonfeed.org/version/1';
7
7
8
8
/**
9
9
The name of the feed, which will often correspond to the name of the website (blog,
10
10
for instance), though not necessarily
11
11
*/
12
-
title: string
12
+
title: string;
13
13
14
14
/**
15
15
(optional but strongly recommended) The URL of the resource that the feed describes.
···
18
18
required. But it may not make sense in the case of a file created on a desktop
19
19
computer, when that file is not shared or is shared only privately.
20
20
*/
21
-
home_page_url?: string
21
+
home_page_url?: string;
22
22
23
23
/**
24
24
(optional but strongly recommended) The URL of the feed, and serves as the unique
25
25
identifier for the feed. As with `home_page_url`, this should be considered required
26
26
for feeds on the public web.
27
27
*/
28
-
feed_url?: string
28
+
feed_url?: string;
29
29
30
30
/**
31
31
Provides more detail, beyond the `title`, on what the feed is about. A feed reader
32
32
may display this text.
33
33
*/
34
-
description?: string
34
+
description?: string;
35
35
36
36
/**
37
37
Description of the purpose of the feed. This is for the use of people looking at
38
38
the raw JSON, and should be ignored by feed readers.
39
39
*/
40
-
user_comment?: string
40
+
user_comment?: string;
41
41
42
42
/**
43
43
The URL of a feed that provides the next n items, where n is determined by the
···
46
46
be the same as `feed_url`, and it must not be the same as a previous `next_url`
47
47
(to avoid infinite loops).
48
48
*/
49
-
next_url?: string
49
+
next_url?: string;
50
50
51
51
/**
52
52
The URL of an image for the feed suitable to be used in a timeline, much the way an
···
54
54
so that it can be scaled-down and so that it can look good on retina displays. It
55
55
should use transparency where appropriate, since it may be rendered on a non-white background.
56
56
*/
57
-
icon?: string
57
+
icon?: string;
58
58
59
59
/**
60
60
The URL of an image for the feed suitable to be used in a source list. It should be
···
62
62
on retina displays). As with `icon`, this image should use transparency where
63
63
appropriate, since it may be rendered on a non-white background.
64
64
*/
65
-
favicon?: string
65
+
favicon?: string;
66
66
67
67
/**
68
68
Specifies the feed author. The author object has several members. These are all
69
69
optional ― but if you provide an author object, then at least one is required.
70
70
*/
71
-
author?: Author
71
+
author?: Author;
72
72
73
73
/**
74
74
Says whether or not the feed is finished ― that is, whether or not it will ever
···
76
76
could expire. If the value is true, then it’s expired. Any other value, or the
77
77
absence of expired, means the feed may continue to update.
78
78
*/
79
-
expired?: boolean
79
+
expired?: boolean;
80
80
81
81
/**
82
82
Describes endpoints that can be used to subscribe to real-time notifications from
···
85
85
86
86
[“Subscribing to Real-time Notifications”]: https://jsonfeed.org/version/1#subscribing-to-real-time-notifications
87
87
*/
88
-
hubs?: Hub[]
88
+
hubs?: Hub[];
89
89
90
90
/** The items in the feed. */
91
-
items: FeedItem[]
91
+
items: FeedItem[];
92
92
}
93
93
94
94
export interface FeedItem {
···
99
99
coerce it to a string. Ideally, the `id` is the full URL of the resource described by
100
100
the item, since URLs make great unique identifiers.
101
101
*/
102
-
id: string
102
+
id: string;
103
103
104
104
/**
105
105
The URL of the resource described by the item. It’s the permalink. This may be the
106
106
same as the id ― but should be present regardless.
107
107
*/
108
-
url?: string
108
+
url?: string;
109
109
110
110
/**
111
111
The URL of a page elsewhere. This is especially useful for linkblogs. If `url` links
112
112
to where you’re talking about a thing, then `external_url` links to the thing you’re
113
113
talking about.
114
114
*/
115
-
external_url?: string
115
+
external_url?: string;
116
116
117
117
/**
118
118
Plain text. Microblog items in particular may omit titles.
119
119
*/
120
-
title?: string
120
+
title?: string;
121
121
122
122
/**
123
123
The plain text of the item.
···
127
127
whichever makes sense for your resource. (It doesn’t even have to be the same for
128
128
each item in a feed.)
129
129
*/
130
-
content_text?: string
130
+
content_text?: string;
131
131
132
132
/**
133
133
The HTML of the item. Important: the only place HTML is allowed in this format is in
···
138
138
whichever makes sense for your resource. (It doesn’t even have to be the same for
139
139
each item in a feed.)
140
140
*/
141
-
content_html?: string
141
+
content_html?: string;
142
142
143
143
/**
144
144
A plain text sentence or two describing the item. This might be presented in a
145
145
timeline, for instance, where a detail view would display all of `content_html` or
146
146
`content_text`.
147
147
*/
148
-
summary?: string
148
+
summary?: string;
149
149
150
150
/**
151
151
The URL of the main image for the item. This image may also appear in the
···
153
153
featured image. Feed readers may use the image as a preview (probably resized as a
154
154
thumbnail and placed in a timeline).
155
155
*/
156
-
image?: string
156
+
image?: string;
157
157
158
158
/**
159
159
The URL of an image to use as a banner. Some blogging systems (such as [Medium])
···
164
164
165
165
[Medium]: https://medium.com/
166
166
*/
167
-
banner_image?: string
167
+
banner_image?: string;
168
168
169
169
/**
170
170
Specifies the date in [RFC 3339] format. (Example: `2010-02-07T14:04:00-05:00`.)
171
171
172
172
[RFC 3339]: https://www.ietf.org/rfc/rfc3339.txt
173
173
*/
174
-
date_published?: string
174
+
date_published?: string;
175
175
176
176
/**
177
177
Specifies the modification date in [RFC 3339] format.
178
178
179
179
[RFC 3339]: https://www.ietf.org/rfc/rfc3339.txt
180
180
*/
181
-
date_modified?: string
181
+
date_modified?: string;
182
182
183
183
/**
184
184
The same structure as the top-level `author`. If not specified in an item, then the
185
185
top-level `author`, if present, is the author of the item.
186
186
*/
187
-
author?: Author
187
+
author?: Author;
188
188
189
189
/**
190
190
Any plain text values you want. Tags tend to be just one word, but they may be
191
191
anything. Note: they are not the equivalent of Twitter hashtags. Some blogging
192
192
systems and other feed formats call these categories.
193
193
*/
194
-
tags?: string[]
194
+
tags?: string[];
195
195
196
196
/**
197
197
An individual item may have one or more attachments. List related resources.
198
198
Podcasts, for instance, would include an attachment that’s an audio or video file.
199
199
*/
200
-
attachments?: Attachment[]
200
+
attachments?: Attachment[];
201
201
}
202
202
203
203
export interface Attachment {
204
204
/** Specifies the location of the attachment. */
205
-
url: string
205
+
url: string;
206
206
207
207
/** Specifies the type of the attachment, such as “audio/mpeg.” */
208
-
mime_type: string
208
+
mime_type: string;
209
209
210
210
/**
211
211
A name for the attachment. Important: if there are multiple attachments, and two or
···
213
213
as alternate representations of the same thing. In this way a podcaster, for
214
214
instance, might provide an audio recording in different formats.
215
215
*/
216
-
title?: string
216
+
title?: string;
217
217
218
218
/**
219
219
Specifies how large the file is.
220
220
*/
221
-
size_in_bytes?: number
221
+
size_in_bytes?: number;
222
222
223
223
/**
224
224
Specifies how long it takes to listen to or watch, when played at normal speed
225
225
*/
226
-
duration_in_seconds?: number
226
+
duration_in_seconds?: number;
227
227
}
228
228
229
229
/**
···
237
237
*/
238
238
export interface Author {
239
239
/** The author’s name */
240
-
name?: string
240
+
name?: string;
241
241
242
242
/**
243
243
The URL of a site owned by the author. It could be a blog, micro-blog, Twitter
···
245
245
but that’s not required. The URL could be a mailto: link, though we suspect that
246
246
will be rare.
247
247
*/
248
-
url?: string
248
+
url?: string;
249
249
250
250
/**
251
251
The URL for an image for the author. As with icon, it should be square and
252
252
relatively large ― such as 512 x 512 ― and should use transparency where
253
253
appropriate, since it may be rendered on a non-white background.
254
254
*/
255
-
avatar?: string
255
+
avatar?: string;
256
256
}
257
257
258
258
/**
···
272
272
the JSON Feed website
273
273
*/
274
274
export interface Hub {
275
-
type: string
276
-
topic: string
277
-
[key: string]: unknown
275
+
type: string;
276
+
topic: string;
277
+
[key: string]: unknown;
278
278
}
279
279
280
-
export default JsonFeed
280
+
export default JsonFeed;
+2
-2
types/markdown-it-abbr.d.ts
+2
-2
types/markdown-it-abbr.d.ts
···
4
4
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
5
5
// TypeScript Version: 2.3
6
6
7
-
import MarkdownIt = require('markdown-it')
7
+
import MarkdownIt = require('markdown-it');
8
8
9
9
declare module 'markdown-it-abbr' {
10
-
export default function abbr(md: MarkdownIt): void
10
+
export default function abbr(md: MarkdownIt): void;
11
11
}
+2
-2
types/markdown-it-deflist.d.ts
+2
-2
types/markdown-it-deflist.d.ts
···
4
4
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
5
5
// TypeScript Version: 2.3
6
6
7
-
import MarkdownIt = require('markdown-it')
7
+
import MarkdownIt = require('markdown-it');
8
8
9
9
declare module 'markdown-it-deflist' {
10
-
export default function defList(md: MarkdownIt): void
10
+
export default function defList(md: MarkdownIt): void;
11
11
}
+2
-2
types/markdown-it-footnote.d.ts
+2
-2
types/markdown-it-footnote.d.ts
···
4
4
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
5
5
// TypeScript Version: 2.3
6
6
7
-
import MarkdownIt = require('markdown-it')
7
+
import MarkdownIt = require('markdown-it');
8
8
9
9
declare module 'markdown-it-footnote' {
10
-
export default function footnote(md: MarkdownIt): void
10
+
export default function footnote(md: MarkdownIt): void;
11
11
}
+6
-6
types/markdown-it-implicit-figures.d.ts
+6
-6
types/markdown-it-implicit-figures.d.ts
···
4
4
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
5
5
// TypeScript Version: 2.3
6
6
7
-
import MarkdownIt = require('markdown-it')
7
+
import MarkdownIt = require('markdown-it');
8
8
9
9
declare module 'markdown-it-implicit-figures' {
10
10
export interface Options {
···
13
13
`<figure data-type="image">`. This can be useful for applying special styling for
14
14
different kind of figures.
15
15
*/
16
-
dataType?: boolean
16
+
dataType?: boolean;
17
17
18
18
/**
19
19
Set `figcaption` to `true` to put the alternative text in a
···
26
26
</figure>
27
27
```
28
28
*/
29
-
figcaption?: boolean
29
+
figcaption?: boolean;
30
30
31
31
/**
32
32
Set `tabindex` to `true` to add a `tabindex` property to each figure, beginning
···
34
34
with [this css-trick](https://css-tricks.com/expanding-images-html5/), which
35
35
expands figures upon mouse-over.
36
36
*/
37
-
tabindex?: boolean
37
+
tabindex?: boolean;
38
38
39
39
/**
40
40
Put a link around the image if there is none yet. For example:
···
43
43
<a href="img.png"><img src="img.png"></a>
44
44
```
45
45
*/
46
-
link?: boolean
46
+
link?: boolean;
47
47
}
48
48
49
49
/**
···
70
70
<figure><a href="page.html"><img src="fig.png" alt=""></a></figure>
71
71
```
72
72
*/
73
-
export default function implicitFigures(md: MarkdownIt, options?: Options): void
73
+
export default function implicitFigures(md: MarkdownIt, options?: Options): void;
74
74
}
+3
-3
types/markdown-it-sup.d.ts
+3
-3
types/markdown-it-sup.d.ts
···
4
4
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
5
5
// TypeScript Version: 2.3
6
6
7
-
import MarkdownIt = require('markdown-it')
7
+
import MarkdownIt = require('markdown-it');
8
8
9
-
declare function sup(md: MarkdownIt): void
10
-
export = sup
9
+
declare function sup(md: MarkdownIt): void;
10
+
export = sup;
+6
-6
types/typeset.d.ts
+6
-6
types/typeset.d.ts
···
5
5
| 'smallCaps'
6
6
| 'punctuation'
7
7
| 'hangingPunctuation'
8
-
| 'spaces'
8
+
| 'spaces';
9
9
10
10
export type Options = {
11
11
/** string of a CSS selector to skip */
12
-
ignore?: string
12
+
ignore?: string;
13
13
/** string of a CSS selector to only apply typeset */
14
-
only?: string
14
+
only?: string;
15
15
/** array of features to disable */
16
-
disable?: OptionName[]
17
-
}
16
+
disable?: OptionName[];
17
+
};
18
18
19
-
export default function typeset(html: string, options?: Options): string
19
+
export default function typeset(html: string, options?: Options): string;