Bauke/tildes-issue-log
Bauke
/
tildes-issue-log
Archived
1
Fork 0
This repository has been archived on 2022-10-04. You can view files and clone it, but cannot push or open issues or pull requests.
tildes-issue-log/source/scripts/html.ts

215 lines
5.5 KiB
TypeScript

import {promises as fsp} from 'fs';
import {basename, join} from 'path';
import fecha from 'fecha';
// @ts-ignore
import htmlclean from 'htmlclean';
import marked from 'marked';
import nunjucks from 'nunjucks';
import wordWrap from 'wordwrap';
import {generateFeeds} from './feeds';
import {OfficialTopic, getTopicsFromMonth} from './official-topics';
import {Statistics} from './statistics';
// Regular expressions taken and slightly adapted from Tildes' Markdown code:
// https://gitlab.com/tildes/tildes/-/blob/89c7c13be2a7fc11d429ee1e340bf2079862eb98/tildes/tildes/lib/markdown.py#L285-296
const groupRegex = /\s(?<!\w)~([\w.]+)\b(?!~)/gm;
const mentionRegex = /\s(?<![\w\\])(?:\/?u\/|@)([\w-]+)\b/gm;
export const months: string[] = [
'zeroary',
'january',
'february',
'march',
'april',
'may',
'june',
'july',
'august',
'september',
'october',
'november',
'december'
];
export interface Post {
markdown: string;
month: number;
rawMarkdown: string;
url: string;
}
export interface PostList {
year: number;
posts: Post[];
}
async function entry(): Promise<void> {
nunjucks.configure(join(__dirname, '../pages/templates/'), {
lstripBlocks: true,
throwOnUndefined: true,
trimBlocks: true
});
const dataDirectory: string = join(__dirname, '../pages/data/');
const statistics: Statistics[] = JSON.parse(
await fsp.readFile(join(dataDirectory, 'statistics.json'), 'utf8')
);
const officialTopics: OfficialTopic[] = JSON.parse(
await fsp.readFile(join(dataDirectory, 'official-topics.json'), 'utf8')
);
const allPosts: PostList[] = [];
// Get a list of all the years we have Markdown files for.
const yearDirectories: string[] = await fsp.readdir(
join(__dirname, '../pages/posts/')
);
for (const year of yearDirectories) {
const postList: PostList = {
year: Number(year),
posts: []
};
// Create the output directory if it doesn't already exist.
await fsp.mkdir(join(__dirname, `../../public/${year}/`), {
recursive: true
});
// Get a list of all the Markdown files.
const markdownFiles: string[] = await fsp.readdir(
join(__dirname, `../pages/posts/${year}/`)
);
for (const file of markdownFiles) {
// Get the filename without the extension.
const fileBasename: string = basename(file, '.md');
// Find the month index in our months array.
const monthNumber: number = months.indexOf(fileBasename);
// Read the actual Markdown.
const markdown: string = await fsp.readFile(
join(__dirname, `../pages/posts/${year}/${file}`),
'utf8'
);
// Add the post to the list.
postList.posts.push({
markdown,
month: monthNumber,
rawMarkdown: markdown,
url: `${year}/${fileBasename}.html`
});
// Get the statistics for that month.
const statistic: Statistics = statistics.find(
val => val.year === Number(year) && val.month === monthNumber
)!;
const topics: OfficialTopic[] = await getTopicsFromMonth(
officialTopics,
Number(year),
monthNumber
);
let renderedHTML: string = marked(markdown);
// Replace ~group with a link to that group.
renderedHTML = renderedHTML.replace(
groupRegex,
' <a class="link-group" href="https://tildes.net/~$1">~$1</a>'
);
// Replace @person with a link to that person.
renderedHTML = renderedHTML.replace(
mentionRegex,
' <a class="link-user" href="https://tildes.net/user/$1">@$1</a>'
);
// Render the post template.
await renderTemplate(
'post.html',
join(__dirname, `../../public/${year}/${fileBasename}.html`),
{
fecha,
months,
officialTopics: topics,
post: {
month: monthNumber,
renderedHTML,
year: Number(year)
},
pluralize,
statistic
}
);
}
// Sort the posts to be ascending by month.
postList.posts.sort((a, b) => a.month - b.month);
allPosts.push(postList);
}
// Sort the post list to be descending by year.
allPosts.sort((a, b) => b.year - a.year);
await renderTemplate(
'posts.html',
join(__dirname, '../../public/posts.html'),
{
months,
posts: allPosts
}
);
// Sort the posts of the most recent year descending by month to select the most recent post.
allPosts[0].posts.sort((a, b) => b.month - a.month);
await renderTemplate(
'index.html',
join(__dirname, '../../public/index.html'),
{
months,
newestPost: {
year: allPosts[0].year,
...allPosts[0].posts[0]
}
}
);
await renderTemplate(
'attributions.html',
join(__dirname, '../../public/attributions.html')
);
await writeEmail(allPosts[0].posts[0].rawMarkdown);
await generateFeeds(allPosts);
}
export async function renderTemplate(
template: string,
location: string,
context: object = {}
): Promise<void> {
const html: string = htmlclean(nunjucks.render(template, context));
await fsp.writeFile(location, html);
}
export async function writeEmail(input: string): Promise<void> {
await fsp.mkdir(join(__dirname, '../../temp/'), {recursive: true});
await fsp.writeFile(
join(__dirname, '../../temp/email.md'),
wordWrap(72)('# Tildes Issue Log\n\n' + input)
);
}
export function pluralize(
singular: string,
plural: string,
count: number
): string {
if (count === 1) {
return singular;
}
return plural;
}
if (require.main === module) {
entry();
}