on 8cef528 help sign in
urbit.org/lib/lib.js
231 lines 6.0 kB history raw
1import remark from "remark";
2import html from "remark-html";
3import fs from "fs";
4import { join, parse } from "path";
5import matter from "gray-matter";
6import toml from "@iarna/toml";
8// export async function markdownToHtml(markdown) {
9// const result = await remark().use(html).process(markdown)
10// return result.toString()
11// }
13const directories = {
14 blog: join(process.cwd(), "content/blog"),
15 events: join(process.cwd(), "content/events"),
16 docs: join(process.cwd(), "content/docs"),
17};
19export function getPostSlugs(key) {
20 return fs.readdirSync(directories[key]);
23// NB Gavin: This could be much simpler if we converted everything to a more common standard, like YAML
24const options = {
25 engines: {
26 toml: toml.parse.bind(toml),
27 },
28 language: "toml",
29 delimiters: "+++",
30};
32export function getPostBySlug(slug, fields = [], key) {
33 const realSlug = slug.replace(/\.md$/, "");
34 const fullPath = join(directories[key], `${realSlug}.md`);
35 const fileContents = fs.readFileSync(fullPath, "utf8");
36 const { data, content } = matter(fileContents, options);
37 const items = {};
39 // Ensure only the minimal needed data is exposed
40 fields.forEach((field) => {
41 if (field === "slug") {
42 items[field] = realSlug;
43 }
45 if (field === "content") {
46 items[field] = content;
47 }
49 if (data[field]) {
50 items[field] = data[field];
51 }
52 });
54 // NB Gavin: This could be removed if the TOML engine returned a JSON date (string) instead of a JS Date object. The YAML engine did this parsing by default.
55 items.date = JSON.stringify(items.date).replace(/\"/g, "");
57 return items;
60export function getAllPosts(fields = [], key) {
61 const slugs = getPostSlugs(key);
62 const posts = slugs
63 .map((slug) => getPostBySlug(slug, fields, key))
64 // sort posts by date in descending order
65 .sort((post1, post2) => (post1.date > post2.date ? -1 : 1));
67 return posts;
70export function getNextPost(slug, fields = [], key) {
71 let resultPost = null;
73 getAllPosts(fields, key).forEach((post, index, array) => {
74 if (post.slug === slug) {
75 resultPost = array[index - 1];
76 }
77 });
78 return resultPost;
81export function getPreviousPost(slug, fields = [], key) {
82 let resultPost = null;
84 getAllPosts(fields, key).forEach((post, index, array) => {
85 if (post.slug === slug) {
86 resultPost = array[index + 1];
87 }
88 });
89 return resultPost;
92export function formatDate(d) {
93 // const wk = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
94 const mn = [
95 "January",
96 "February",
97 "March",
98 "April",
99 "May",
100 "June",
101 "July",
102 "August",
103 "September",
104 "October",
105 "November",
106 "December",
107 ];
108 return mn[d.getMonth()] + " " + d.getDate() + ", " + d.getFullYear();
111// export function capitalize(str) {
112// return str.charAt(0).toUpperCase() + str.slice(1);
113// }
115// export function generateCrumbs(path) {
116// const crumbs = path.split('/')
117// return crumbs
118// .filter(v => v !== '')
119// .map((crumb, index) => ({
120// name: capitalize(crumb),
121// key: crumb,
122// path: crumbs.slice(0, index+2).join('/'),
123// }))
124// }
126export const getAllFiles = function (dirPath, arrayOfFiles) {
127 let files = fs.readdirSync(dirPath);
129 arrayOfFiles = arrayOfFiles || [];
131 files.forEach(function (file) {
132 if (fs.statSync(dirPath + "/" + file).isDirectory()) {
133 arrayOfFiles = getAllFiles(dirPath + "/" + file, arrayOfFiles);
134 } else {
135 arrayOfFiles.push(join(__dirname, dirPath, "/", file));
136 }
137 });
139 return arrayOfFiles;
140};
142function get(obj, path, defaultValue) {
143 for (const index of path) {
144 if (obj[index] === undefined) {
145 return defaultValue;
146 }
147 obj = obj[index];
148 }
149 return obj;
152function set(obj, path, value) {
153 let piece = obj;
154 for (let i = 0; i < path.length - 1; i++) {
155 if (piece[path[i]] === undefined) {
156 piece[path[i]] = typeof path[i + 1] === "number" ? [] : {};
157 }
158 piece = piece[path[i]];
159 }
160 piece[path[path.length - 1]] = value;
161 return obj;
164/**
165 * buildPageTree is written to provide a recursive index of posts
166 * for nested directories for creating sidebars
167 * @param {string} path Absolute path to the subdirectory.
168 * @returns {Object.<string,string|Object<string,string[]|number>>} Title of the directory, child pages, and child directories (recursive structure).
169 *
170 */
172// type PageTree =
173// { title: string,
174// pages: [{
175// title: string,
176// base: string,
177// slug: string,
178// weight: number,
179// }],
180// children: { [string]: PageTree },
181// }
183export function buildPageTree(path) {
184 const metadata = matter(fs.readFileSync(join(path, "_index.md")), options);
186 const children = fs.readdirSync(path, { withFileTypes: true });
187 const pages = children.filter((f) => f.isFile() && f.name !== "_index.md");
188 const subdirs = children.filter((f) => f.isDirectory());
190 return {
191 title: metadata.data.title,
192 pages: pages.map((page) => {
193 const { data: pageData } = matter(
194 fs.readFileSync(join(path, page.name)),
195 options
196 );
197 return {
198 title: pageData.title,
199 base: page.name,
200 slug: page.name.replace(/.md$/, ""),
201 weight: pageData?.weight ?? 0,
202 };
203 }),
204 children: Object.fromEntries(
205 subdirs.map((subdir) => [
206 subdir.name,
207 buildPageTree(join(path, subdir.name)),
208 ])
209 ),
210 };
213export const getPage = (path) => {
214 try {
215 let fileContents = fs.readFileSync(`${path}.md`, "utf8");
216 if (fileContents) {
217 const { data, content } = matter(fileContents, options);
218 return { data, content };
219 }
220 } catch {
221 try {
222 let fileContents = fs.readFileSync(`${path}/_index.md`, "utf8");
223 if (fileContents) {
224 const { data, content } = matter(fileContents, options);
225 return { data, content };
226 }
227 } catch {
228 console.error("no md file for slug");
229 }
230 }
231};