From 1e6f7a276688d00f222dbe2fa0f189ed3deff3aa Mon Sep 17 00:00:00 2001 From: ache Date: Mon, 2 May 2022 02:08:35 +0200 Subject: New version of ache.one --- src/build/index.mjs | 83 ++++++++++++++++++++++++++++++++++++ src/build/list-articles.mjs | 7 +++ src/build/load-svg.mjs | 17 ++++++++ src/build/remove-footnote-header.mjs | 17 ++++++++ src/build/rss.mjs | 42 ++++++++++++++++++ src/build/special_box.mjs | 24 +++++++++++ src/build/to-html.mjs | 47 ++++++++++++++++++++ 7 files changed, 237 insertions(+) create mode 100644 src/build/index.mjs create mode 100644 src/build/list-articles.mjs create mode 100644 src/build/load-svg.mjs create mode 100644 src/build/remove-footnote-header.mjs create mode 100644 src/build/rss.mjs create mode 100644 src/build/special_box.mjs create mode 100644 src/build/to-html.mjs (limited to 'src/build') diff --git a/src/build/index.mjs b/src/build/index.mjs new file mode 100644 index 0000000..84d4d9a --- /dev/null +++ b/src/build/index.mjs @@ -0,0 +1,83 @@ +import fs from 'node:fs'; +import mustache from 'mustache'; +import {u} from 'unist-builder'; +import {select} from 'hast-util-select'; +import {toString as hastToString} from 'mdast-util-to-string'; + +import {toHtmlRaw, toString} from './to-html.mjs'; +import loadSVG from './load-svg.mjs'; +import listArticles from './list-articles.mjs'; +import getRSS from './rss.mjs'; + +const loadMD = (listFile, suffix) => { + const listContent = []; + for (const file of listFile) { + const content = fs.readFileSync(`${suffix}/${file}`, 'utf8'); + const htmlContent = toHtmlRaw(content); + const htmlRender = toString(htmlContent); + + const titleHtml = select('h1', htmlContent); + const intro = select('p', htmlContent); + const logo = select('img', htmlContent); + logo.properties.src = `${suffix}/${logo.properties.src}`; + titleHtml.children[0].properties.href = `${suffix}/${file.slice(0, -3)}.html`; + + const title = hastToString(titleHtml); + const domTitle = title.replace(/\s+/g, '-').toLowerCase(); // Maybe encodeURI + console.log(`Create : ${title}`); + + listContent.push({ + name: file.slice(0, -3), + content: htmlRender, + intro: toString(u('root', [titleHtml, intro])), + introDesc: hastToString(intro), + imageUrl: logo.properties.src, + title, + domTitle, + }); + } + + return listContent; +}; + +const leftPanelTmpl = fs.readFileSync('src/templates/left.tmpl', 'utf8'); +const headerTmpl = fs.readFileSync('src/templates/header.tmpl', 'utf8'); +const articleTmpl = fs.readFileSync('src/templates/article.tmpl', 'utf8'); +const indexTmpl = fs.readFileSync('src/templates/index.tmpl', 'utf8'); + +const partials = { + header: headerTmpl, + leftPanel: leftPanelTmpl, +}; + +const svg = loadSVG(); + +const articles = loadMD(listArticles, 'articles'); + +for (const article of articles) { + const context = { + svg, + title: `${article.title} - ache`, + content: article.content, + domTitle: article.domTitle, + }; + const output = mustache.render(articleTmpl, context, partials); + + fs.writeFileSync(`articles/${article.name}.html`, output); +} + +console.log('Create : rss.xml'); +const xmlFeed = getRSS(articles); +fs.writeFileSync('rss.xml', xmlFeed); + +{ + const context = { + title: 'ache: Blog personnel', + svg, + articles, + }; + + console.log('Create : Home page'); + const output = mustache.render(indexTmpl, context, partials); + fs.writeFileSync('index.html', output); +} diff --git a/src/build/list-articles.mjs b/src/build/list-articles.mjs new file mode 100644 index 0000000..d327573 --- /dev/null +++ b/src/build/list-articles.mjs @@ -0,0 +1,7 @@ +const listArticles = [ + 'bizarreries-du-langage-c.md', + 'retour-sur-laoc-2021-semaine-1.md', + '2FA-discord-sur-pc.md', + 'duckduckgo-google-en-mieux.md', +]; +export default listArticles; diff --git a/src/build/load-svg.mjs b/src/build/load-svg.mjs new file mode 100644 index 0000000..6bf8db4 --- /dev/null +++ b/src/build/load-svg.mjs @@ -0,0 +1,17 @@ +import fs from 'node:fs'; + +const loadSVG = () => { + const svg = {}; + + for (const file of fs.readdirSync('s/imgM')) { + if (file.endsWith('.svg')) { + const contentFile = fs.readFileSync(`s/imgM/${file}`, 'utf8'); + const name = file.slice(0, -'.svg'.length); + svg[name] = contentFile; + } + } + + return svg; +}; + +export default loadSVG; diff --git a/src/build/remove-footnote-header.mjs b/src/build/remove-footnote-header.mjs new file mode 100644 index 0000000..a238692 --- /dev/null +++ b/src/build/remove-footnote-header.mjs @@ -0,0 +1,17 @@ +import {visit} from 'unist-util-visit'; + +// This plugin is an example to let users write HTML with directives. +// It’s informative but rather useless. +// See below for others examples. +/** @type {import('unified').Plugin<[], import('mdast').Root>} */ +export default function specialBox() { + return tree => { + visit(tree, node => { + if (node?.tagName === 'h2' && node?.properties?.id === 'footnote-label') { + node.tagName = 'hr'; + node.children = []; // Exposure of children, Roman's way + } + }); + }; +} + diff --git a/src/build/rss.mjs b/src/build/rss.mjs new file mode 100644 index 0000000..606cdef --- /dev/null +++ b/src/build/rss.mjs @@ -0,0 +1,42 @@ +import RSS from 'rss'; + +const siteUrl = 'https://ache.one'; + +const getRSS = articles => { + const rssFeed = new RSS({ + title: 'ache: Blog personnel', + description: 'Programmation, Algorithmique, Système, *pick you poison*', + // eslint-disable-next-line camelcase + feed_url: `${siteUrl}/feed.xml`, + // eslint-disable-next-line camelcase + site_url: siteUrl, + // eslint-disable-next-line camelcase + image_url: `${siteUrl}/ache.svg`, + language: 'fr', + pubDate: (new Date().toLocaleString()), + ttl: '1440', + // eslint-disable-next-line camelcase + custom_elements: [''], + }); + + for (const article of articles.slice(0, 10)) { + rssFeed.item({ + title: article.title, + description: '
' + article.content + '
', + // eslint-disable-next-line camelcase + image_url: article.imageUrl, + url: `${siteUrl}/articles/${article.domTitle}`, + guid: article.domTitle, + author: 'ache', + // eslint-disable-next-line camelcase + custom_elements: [ + {logo: article.imageUrl}, + {intro: article.introDesc}, + ], + }); + } + + return rssFeed.xml({indent: false}).replace(/<\?xml version="1.0" encoding="UTF-8"\?>/g, ''); +}; + +export default getRSS; diff --git a/src/build/special_box.mjs b/src/build/special_box.mjs new file mode 100644 index 0000000..0b284ec --- /dev/null +++ b/src/build/special_box.mjs @@ -0,0 +1,24 @@ +import {visit} from 'unist-util-visit'; + +// This plugin is an example to let users write HTML with directives. +// It’s informative but rather useless. +// See below for others examples. +/** @type {import('unified').Plugin<[], import('mdast').Root>} */ +export default function specialBox() { + return tree => { + visit(tree, node => { + if (node.type === 'containerDirective' && ( + node.name === 'attention' + || node.name === 'question' + || node.name === 'information')) { + const data = node.data || (node.data = {}); + + data.hName = 'div'; + data.hProperties = { + className: 'special-box ' + node.name, + }; + } + }); + }; +} + diff --git a/src/build/to-html.mjs b/src/build/to-html.mjs new file mode 100644 index 0000000..d8c3a84 --- /dev/null +++ b/src/build/to-html.mjs @@ -0,0 +1,47 @@ +import {unified} from 'unified'; +import remarkParse from 'remark-parse'; +import remarkGfm from 'remark-gfm'; +import remarkToc from 'remark-toc'; +import remarkDirective from 'remark-directive'; +import remarkMath from 'remark-math'; +import remarkRehype from 'remark-rehype'; +import rehypeSlug from 'rehype-slug'; +import rehypeKaTeX from 'rehype-katex'; +import rehypeRaw from 'rehype-raw'; +import rehypeAutolinkHeadings from 'rehype-autolink-headings'; +import rehypeStringify from 'rehype-stringify'; +import rehypeHighlight from 'rehype-highlight'; +import {h} from 'hastscript'; +import remarkSpecialBox from './special_box.mjs'; +import remarkRemoveFootnoteHeader from './remove-footnote-header.mjs'; + +const autoLinkOption = { + behavior: 'wrap', + properties: { + ariaHidden: true, + tabIndex: -1, + className: 'anchor', + }, +}; + +const generator = unified() + .use(remarkParse) + .use(remarkGfm) + .use(remarkToc, {heading: 'Sommaire', tight: true, ordered: true}) + .use(remarkMath) + .use(remarkDirective) + .use(remarkSpecialBox) + .use(remarkRehype, {allowDangerousHtml: true}) + .use(rehypeRaw) + .use(remarkRemoveFootnoteHeader) + .use(rehypeKaTeX) + .use(rehypeSlug) + .use(rehypeHighlight) + .use(rehypeAutolinkHeadings, autoLinkOption) + .use(rehypeStringify); + +const toHtml = content => generator.processSync(content); + +export const toHtmlRaw = content => generator.runSync(generator.parse(content)); +export const toString = content => generator.stringify(content); +export default toHtml; -- cgit v1.2.3-54-g00ecf