2import { join, parse } from "path";
3import matter from "gray-matter";
4import toml from "@iarna/toml";
5import { DateTime } from "luxon";
7const directories = (dir) => {
10 return join(process.cwd(), "content/blog");
12 return join(process.cwd(), "content/events");
14 return join(process.cwd(), "content/docs");
16 return join(process.cwd(), "content/grants");
18 return join(process.cwd(), "content/media");
20 return join(process.cwd(), "content/updates");
22 return join(process.cwd(), "content");
24 return join(process.cwd(), `content/${dir}`);
28export function getPostSlugs(key) {
29 const dir = fs.existsSync(directories(key))
30 ? fs.readdirSync(directories(key), { withFileTypes: true })
33 .filter((f) => f.isFile() && f.name !== "_index.md")
37// NB Gavin: This could be much simpler if we converted everything to a more common standard, like YAML
40 toml: toml.parse.bind(toml),
46export function getPostBySlug(slug, fields = [], key) {
47 const realSlug = slug.replace(/\.md$/, "");
48 const fullPath = join(directories(key), `${realSlug}.md`);
49 const fileContents = fs.readFileSync(fullPath, "utf8");
50 const { data, content } = matter(fileContents, options);
53 // Ensure only the minimal needed data is exposed
54 fields.forEach((field) => {
55 if (field === "slug") {
56 items[field] = realSlug;
59 if (field === "content") {
60 items[field] = content;
64 items[field] = data[field];
71export function getAllPosts(fields = [], key, sort = "") {
72 const slugs = getPostSlugs(key);
74 .map((slug) => getPostBySlug(slug, fields, key))
75 // sort posts by date in descending order
76 .sort((post1, post2) => {
77 if (sort === "date") {
78 return DateTime.fromISO(post1.date) > DateTime.fromISO(post2.date)
81 } else if (sort === "weight") {
82 return post1.weight > post2.weight ? -1 : 1;
88export function getAllEvents(fields = [], key) {
89 const slugs = getPostSlugs(key);
91 .map((slug) => getPostBySlug(slug, fields, key))
92 // sort posts by date in descending order
93 .sort((post1, post2) =>
94 DateTime.fromISO(post1.starts) > DateTime.fromISO(post2.starts) ? -1 : 1
99function getAllPaths(dirPath, arrayOfFiles) {
100 let files = fs.readdirSync(dirPath);
102 arrayOfFiles = arrayOfFiles || [];
104 files.forEach(function (file) {
105 if (fs.statSync(dirPath + "/" + file).isDirectory()) {
106 arrayOfFiles = getAllPaths(dirPath + "/" + file, arrayOfFiles);
108 arrayOfFiles.push(join(dirPath, "/", file));
115export function getDocsPaths() {
116 return getAllPaths(directories("docs"), []);
119export function getNextPost(slug, fields = [], key, sort = "date") {
120 let resultPost = null;
122 getAllPosts(fields, key, sort).forEach((post, index, array) => {
123 if (post.slug === slug) {
124 if (typeof array[index - 1] !== "undefined") {
125 resultPost = array[index - 1];
132export function getPreviousPost(slug, fields = [], key, sort = "date") {
133 let resultPost = null;
135 getAllPosts(fields, key, sort).forEach((post, index, array) => {
136 if (post.slug === slug) {
137 if (typeof array[index + 1] !== "undefined") {
138 resultPost = array[index + 1];
145export function formatDateWithTime(d) {
146 const date = DateTime.fromISO(d, {
148 }).toLocaleString(DateTime.DATETIME_FULL);
152export function capitalize(str) {
153 return str.charAt(0).toUpperCase() + str.slice(1);
156// Takes an ISO 8601 date string
157// Returns a time relative to the IANA timezone and iso8601 date
158export function generateDisplayDate(iso8601, zone = "America/Los_Angeles") {
159 return DateTime.fromISO(iso8601, { zone });
162// Takes an ISO 8601 date string
163// Returns a time relative to user's current timezone
164export function generateRealtimeDate(iso8601) {
165 return DateTime.fromISO(iso8601).setZone("system");
168// Formats a DateTime object to something like "July 15, 2021, 5:00 PM"
169export function formatDateAndTime(dateTimeObject) {
170 return dateTimeObject.toLocaleString(DateTime.DATETIME_HUGE);
173// Formats a DateTime object to something like "5:00 PM"
174export function formatTime(dateTimeObject) {
175 const dt = dateTimeObject.toLocaleString(DateTime.TIME_SIMPLE);
176 return dt.replace(/:00/g, "");
179export function formatTimeZone(dateTimeObject) {
180 return dateTimeObject.offsetNameShort;
183// Formats a DateTime object to something like "Jul 15, 2021"
184export function formatDate(dateTimeObject) {
185 return dateTimeObject.toLocaleString(DateTime.DATE_FULL);
188// export function generateCrumbs(path) {
189// const crumbs = path.split('/')
191// .filter(v => v !== '')
192// .map((crumb, index) => ({
193// name: capitalize(crumb),
195// path: crumbs.slice(0, index+2).join('/'),
199export const getPage = (path) => {
201 let fileContents = fs.readFileSync(`${path}.md`, "utf8");
203 const { data, content } = matter(fileContents, options);
204 return { data, content };
208 let fileContents = fs.readFileSync(`${path}/_index.md`, "utf8");
210 const { data, content } = matter(fileContents, options);
211 return { data, content };
214 console.error("no md file for slug");
219// Returns an array of unique grants categories
220export function getGrantsCategories() {
221 const paths = fs.readdirSync(directories("grants"));
222 const uniqueCategories = paths
223 // Get only markdown files
224 .filter((path) => parse(path).ext === ".md")
227 matter(fs.readFileSync(join(directories("grants"), path)), options)
229 // Each markdown file has an array of categories. Collapse these arrays into one array.
230 .reduce((acc, metadata) => {
231 return [...acc, ...metadata.data.taxonomies.grant_category];
233 // Deduplicate single array of categories such that the output is an array where each category is represented only once.
234 .reduce((acc, uniqueItem) => {
235 if (acc.includes(uniqueItem)) {
238 return [...acc, uniqueItem];
240 return uniqueCategories;
243// Returns an array of unique grants types
244export function getGrantsTypes() {
245 const paths = fs.readdirSync(directories("grants"));
246 const uniqueTypes = paths
247 // Get only markdown files
248 .filter((path) => parse(path).ext === ".md")
249 // Parse frontmatter to JSON
251 matter(fs.readFileSync(join(directories("grants"), path)), options)
253 // Each markdown file has an array of types. Collapse these arrays into one array.
254 .reduce((acc, metadata) => {
255 return [...acc, ...metadata.data.taxonomies.grant_type];
257 // Deduplicate single array of types such that the output is an array where each type is represented only once.
258 .reduce((acc, uniqueItem) => {
259 if (acc.includes(uniqueItem)) {
262 return [...acc, uniqueItem];
267// Returns a number which represents how many grants are open, or uncompleted
268export function getOpenGrantsCount() {
269 const paths = fs.readdirSync(directories("grants"));
271 // Get only markdown files
272 .filter((path) => parse(path).ext === ".md")
273 // Parse frontmatter to JSON
275 matter(fs.readFileSync(join(directories("grants"), path)), options)
277 // Count grants which are both uncompleted and not assigned
278 .reduce((acc, metadata) => {
280 !metadata.data.extra.completed &&
281 !metadata.data.extra.canceled &&
282 metadata.data.extra.assignee === ""
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list