-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathfeed.js
118 lines (102 loc) · 2.99 KB
/
feed.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import config from "../config.js";
import { escapeCharacters } from "./lib/character-reference.js";
import fs from "node:fs/promises";
import { guessPath } from "./lib/guess-path.js";
import mustache from "mustache";
import path from "node:path";
const toFilesFormat = (template) => ({
dest: guessPath(template, config.dir.dest, "feed"),
metadata: guessPath(template, config.dir.metadata, "index.json"),
template,
});
const gatherFiles = async () => {
const filesIterator = await fs.glob(
`${config.dir.template}**/_feed.mustache`,
);
const templates = await Array.fromAsync(filesIterator);
return Promise.all(templates.map(toFilesFormat));
};
const toAbsoluteURL = (prefix, url) => {
if (!url.startsWith("/")) {
return url;
}
return `${prefix}${url}`;
};
const toAbsoluteURLAll = (prefix, match, attr, url) => {
const absoluteURL = toAbsoluteURL(prefix, url);
return `${attr}="${absoluteURL}"`;
};
const extendItem = (prefix, item) => {
const link = toAbsoluteURL(prefix, item.link);
if (item.body) {
return {
...item,
body: item.body.replace(
/(href|src)="(\/.*?)"/gu,
toAbsoluteURLAll.bind(null, prefix),
),
link,
};
}
return {
...item,
link,
};
};
const readLatestData = async (prefix, dataFile) => {
const basename = path.basename(dataFile, ".json");
const data = await fs.readFile(dataFile).then(JSON.parse);
if (!data[0].published) {
return { [basename]: [] };
}
const latest = data.slice(0, 10);
const extended = await Promise.all(latest.map(extendItem.bind(null, prefix)));
return { [basename]: extended };
};
const readAllData = async (prefix) => {
const filesIterator = await fs.glob(`${config.dir.data}**/*.json`);
const dataFiles = await Array.fromAsync(filesIterator);
const data = await Promise.all(
dataFiles.map(readLatestData.bind(null, prefix)),
);
return Object.assign(...data);
};
const mergeData = async (file, metadata, data) => {
const overrides = await fs.readFile(file.metadata).then(JSON.parse);
return {
...metadata,
...data,
...overrides,
};
};
const build = async (metadata, data, file) => {
const [merged, template] = await Promise.all([
mergeData(file, metadata, data),
fs.readFile(file.template, "utf8"),
]);
const rendered = mustache.render(template, merged, null, {
escape: escapeCharacters,
});
await fs.mkdir(path.dirname(file.dest), { recursive: true });
await fs.writeFile(file.dest, rendered);
};
const comparePublished = (a, b) =>
Number.parseInt(b.published, 10) - Number.parseInt(a.published, 10);
const prefix = `${config.scheme}://${config.domain}`;
const [files, { articles, books, links, statuses }] = await Promise.all([
gatherFiles(),
readAllData(prefix),
]);
await Promise.all(
files.map(
build.bind(null, config, {
articles,
books,
items: [...articles, ...books, ...links, ...statuses]
.toSorted(comparePublished)
.slice(0, 10),
links,
statuses,
}),
),
);