-
Notifications
You must be signed in to change notification settings - Fork 1
/
posts.mjs
138 lines (123 loc) · 3.63 KB
/
posts.mjs
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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import matter from "gray-matter";
import recursiveReadDir from "recursive-readdir";
import toc from "markdown-toc";
import formatDate from "date-fns/format/index.js";
import fetch from "node-fetch";
const POSTS_DIR = "_posts";
const PATH_TO_CONTENT = new Map();
const POSTS_CACHE = [];
const MEDIUM_POSTS_CACHE = [];
/**
* Returns a pretty printed version of a date.
* @param {string} published
* @return {string}
*/
function prettyDate(published) {
const d = published.toISOString().split("T")[0];
return formatDate(d, "MMM Do, YYYY");
}
/**
* Sorts posts by their 'published' date property.
* @param {string} postA
* @param {string} postB
* @return {Array}
*/
function comparePostDate(postA, postB) {
const a = new Date(postA.data.published);
const b = new Date(postB.data.published);
if (a > b) {
return -1;
}
if (a < b) {
return 1;
}
return 0;
}
/**
* Fetches my posts from Medium which are originals and not re-posts.
* @param {string=} username
*/
async function fetchMediumPosts(username = "@ebidel") {
const resp = await fetch(`https://medium.com/${username}/latest?format=json`);
const text = await resp.text();
const posts = Object.values(
JSON.parse(text.split("</x>")[1]).payload.references.Post
);
return posts
.filter((post) => !post.importedUrl)
.map((post) => {
const href = `https://medium.com/${username}/${post.uniqueSlug}`;
const published = new Date(post.firstPublishedAt);
return {
path: href,
href,
excerpt: post.previewContent.subtitle,
data: {
title: post.title,
published,
publishedStr: prettyDate(published),
},
};
});
}
/**
* Reads the .md file, runs it through matter, and updates the content cache.
* @param {string} path file to read.
* @return {!Object}
*/
function refreshMarkdownFileContent(path) {
const firstTwoLines = function (file, opts) {
file.excerpt = file.content.split("\n").slice(0, 4).join(" ");
};
const result = matter.read(path, {
excerpt: firstTwoLines,
// Everything between end of front matter and this delimiter is used as
// post summary.
excerpt_separator: "<!-- end -->",
});
// Use front matter summary if one was found.
if (result.data.excerpt) {
result.excerpt = result.data.excerpt;
}
result.href = result.path.replace(POSTS_DIR, "/posts").replace(".md", "");
result.toc = toc(result.content).content;
result.data.publishedStr = prettyDate(result.data.published);
PATH_TO_CONTENT.set(result.path, result); // add/update to cache.
return result;
}
/**
* Lists files in a directory, recursively.
* @param {string} path Base folder to start.
* @return {Array<Object>}
*/
async function list(path) {
const posts = [];
if (PATH_TO_CONTENT.size) {
return POSTS_CACHE;
}
// TODO: cache and return this instead.
const files = (await recursiveReadDir(path)).filter((f) => f.endsWith(".md"));
const results = files.map((f) => refreshMarkdownFileContent(f));
// // Add in medium posts.
// if (!MEDIUM_POSTS_CACHE.length) {
// const posts = await fetchMediumPosts('@ebidel');
// MEDIUM_POSTS_CACHE.push(...posts);
// }
posts.push(...results, ...MEDIUM_POSTS_CACHE);
posts.sort(comparePostDate); // Sort final list.
POSTS_CACHE.push(...posts); // Cache results for later.
return posts;
}
/**
* Gets a post.
* @param {string} path
* @param {boolean=} useCache Whether to consult the cache. Default to true.
* @return {Object}
*/
function get(path, useCache = true) {
if (useCache) {
return PATH_TO_CONTENT.get(path);
}
return refreshMarkdownFileContent(path);
}
export { list, get };