const slugify = require("@sindresorhus/slugify"); const markdownIt = require("markdown-it"); const fs = require("fs"); const matter = require("gray-matter"); const faviconsPlugin = require("eleventy-plugin-gen-favicons"); const tocPlugin = require("eleventy-plugin-nesting-toc"); const { parse } = require("node-html-parser"); const htmlMinifier = require("html-minifier"); const pluginRss = require("@11ty/eleventy-plugin-rss"); const { headerToId, namedHeadingsFilter } = require("./src/helpers/utils"); const { userMarkdownSetup, userEleventySetup, } = require("./src/helpers/userSetup"); const Image = require("@11ty/eleventy-img"); function transformImage(src, cls, alt, sizes, widths = ["500", "700", "auto"]) { let options = { widths: widths, formats: ["webp", "jpeg"], outputDir: "./dist/img/optimized", urlPath: "/img/optimized", }; // generate images, while this is async we don’t wait Image(src, options); let metadata = Image.statsSync(src, options); return metadata; } const tagRegex = /(^|\s|\>)(#[^\s!@#$%^&*()=+\.,\[{\]};:'"?><]+)(?!([^<]*>))/g; module.exports = function (eleventyConfig) { eleventyConfig.setLiquidOptions({ dynamicPartials: true, }); let markdownLib = markdownIt({ breaks: true, html: true, linkify: true, }) .use(require("markdown-it-anchor"), { slugify: headerToId, }) .use(require("markdown-it-mark")) .use(require("markdown-it-footnote")) .use(function (md) { md.renderer.rules.hashtag_open = function (tokens, idx) { return ''; }; }) .use(require("markdown-it-mathjax3"), { tex: { inlineMath: [["$", "$"]], }, options: { skipHtmlTags: { "[-]": ["pre"] }, }, }) .use(require("markdown-it-attrs")) .use(require("markdown-it-task-checkbox"), { disabled: true, divWrap: false, divClass: "checkbox", idPrefix: "cbx_", ulClass: "task-list", liClass: "task-list-item", }) .use(require("markdown-it-plantuml"), { openMarker: "```plantuml", closeMarker: "```", }) .use(namedHeadingsFilter) .use(function (md) { //https://github.com/DCsunset/markdown-it-mermaid-plugin const origFenceRule = md.renderer.rules.fence || function (tokens, idx, options, env, self) { return self.renderToken(tokens, idx, options, env, self); }; md.renderer.rules.fence = (tokens, idx, options, env, slf) => { const token = tokens[idx]; if (token.info === "mermaid") { const code = token.content.trim(); return `
${code}
`; } if (token.info === "transclusion") { const code = token.content.trim(); return `
${md.render(code)}
`; } if (token.info.startsWith("ad-")) { const code = token.content.trim(); const parts = code.split("\n") let titleLine; let collapse; let collapsible = false let collapsed = true let icon; let color; let nbLinesToSkip = 0 for (let i = 0; i<4; i++) { if (parts[i] && parts[i].trim()) { let line = parts[i] && parts[i].trim().toLowerCase() if (line.startsWith("title:")) { titleLine = line.substring(6); nbLinesToSkip ++; } else if (line.startsWith("icon:")) { icon = line.substring(5); nbLinesToSkip ++; } else if (line.startsWith("collapse:")) { collapsible = true collapse = line.substring(9); if (collapse && collapse.trim().toLowerCase() == 'open') { collapsed = false } nbLinesToSkip ++; } else if (line.startsWith("color:")) { color = line.substring(6); nbLinesToSkip ++; } } } const foldDiv = collapsible ? `
` : ""; const titleDiv = titleLine ? `
${titleLine}
${foldDiv}
` : ""; let collapseClasses = titleLine && collapsible ? 'is-collapsible' : '' if (collapsible && collapsed) { collapseClasses += " is-collapsed" } let res = `
${titleDiv}\n
${md.render( parts.slice(nbLinesToSkip).join("\n") )}
`; return res } // Other languages return origFenceRule(tokens, idx, options, env, slf); }; const defaultImageRule = md.renderer.rules.image || function (tokens, idx, options, env, self) { return self.renderToken(tokens, idx, options, env, self); }; md.renderer.rules.image = (tokens, idx, options, env, self) => { const imageName = tokens[idx].content; const [fileName, width] = imageName.split("|"); if (width) { const widthIndex = tokens[idx].attrIndex("width"); const widthAttr = `${width}px`; if (widthIndex < 0) { tokens[idx].attrPush(["width", widthAttr]); } else { tokens[idx].attrs[widthIndex][1] = widthAttr; } } return defaultImageRule(tokens, idx, options, env, self); }; const defaultLinkRule = md.renderer.rules.link_open || function (tokens, idx, options, env, self) { return self.renderToken(tokens, idx, options, env, self); }; md.renderer.rules.link_open = function (tokens, idx, options, env, self) { const aIndex = tokens[idx].attrIndex("target"); const classIndex = tokens[idx].attrIndex("class"); if (aIndex < 0) { tokens[idx].attrPush(["target", "_blank"]); } else { tokens[idx].attrs[aIndex][1] = "_blank"; } if (classIndex < 0) { tokens[idx].attrPush(["class", "external-link"]); } else { tokens[idx].attrs[classIndex][1] = "external-link"; } return defaultLinkRule(tokens, idx, options, env, self); }; }) .use(userMarkdownSetup); eleventyConfig.setLibrary("md", markdownLib); eleventyConfig.addFilter("isoDate", function (date) { return date && date.toISOString(); }); eleventyConfig.addFilter("link", function (str) { return ( str && str.replace(/\[\[(.*?\|.*?)\]\]/g, function (match, p1) { //Check if it is an embedded excalidraw drawing or mathjax javascript if (p1.indexOf("],[") > -1 || p1.indexOf('"$"') > -1) { return match; } const [fileLink, linkTitle] = p1.split("|"); let fileName = fileLink.replaceAll("&", "&"); let header = ""; let headerLinkPath = ""; if (fileLink.includes("#")) { [fileName, header] = fileLink.split("#"); headerLinkPath = `#${headerToId(header)}`; } let permalink = `/notes/${slugify(fileName)}`; let noteIcon = process.env.NOTE_ICON_DEFAULT; const title = linkTitle ? linkTitle : fileName; let deadLink = false; try { const startPath = "./src/site/notes/"; const fullPath = fileName.endsWith(".md") ? `${startPath}${fileName}` : `${startPath}${fileName}.md`; const file = fs.readFileSync(fullPath, "utf8"); const frontMatter = matter(file); if (frontMatter.data.permalink) { permalink = frontMatter.data.permalink; } if ( frontMatter.data.tags && frontMatter.data.tags.indexOf("gardenEntry") != -1 ) { permalink = "/"; } if (frontMatter.data.noteIcon) { noteIcon = frontMatter.data.noteIcon; } } catch { deadLink = true; } if(deadLink){ return `
${title}`; } return `${title}`; }) ); }); eleventyConfig.addFilter("taggify", function (str) { return ( str && str.replace(tagRegex, function (match, precede, tag) { return `${precede}${tag}`; }) ); }); eleventyConfig.addFilter("searchableTags", function (str) { let tags; let match = str && str.match(tagRegex); if (match) { tags = match .map((m) => { return `"${m.split("#")[1]}"`; }) .join(", "); } if (tags) { return `${tags},`; } else { return ""; } }); eleventyConfig.addFilter("hideDataview", function (str) { return ( str && str.replace(/\(\S+\:\:(.*)\)/g, function (_, value) { return value.trim(); }) ); }); eleventyConfig.addTransform("callout-block", function (str) { const parsed = parse(str); const transformCalloutBlocks = ( blockquotes = parsed.querySelectorAll("blockquote") ) => { for (const blockquote of blockquotes) { transformCalloutBlocks(blockquote.querySelectorAll("blockquote")); let content = blockquote.innerHTML; let titleDiv = ""; let calloutType = ""; let isCollapsable; let isCollapsed; const calloutMeta = /\[!([\w-]*)\](\+|\-){0,1}(\s?.*)/; if (!content.match(calloutMeta)) { continue; } content = content.replace( calloutMeta, function (metaInfoMatch, callout, collapse, title) { isCollapsable = Boolean(collapse); isCollapsed = collapse === "-"; const titleText = title.replace(/(<\/{0,1}\w+>)/, "") ? title : `${callout.charAt(0).toUpperCase()}${callout .substring(1) .toLowerCase()}`; const fold = isCollapsable ? `
` : ``; calloutType = callout; titleDiv = `
${titleText}
${fold}
`; return ""; } ); blockquote.tagName = "div"; blockquote.classList.add("callout"); blockquote.classList.add(isCollapsable ? "is-collapsible" : ""); blockquote.classList.add(isCollapsed ? "is-collapsed" : ""); blockquote.setAttribute("data-callout", calloutType.toLowerCase()); blockquote.innerHTML = `${titleDiv}\n
${content}
`; } }; transformCalloutBlocks(); return str && parsed.innerHTML; }); function fillPictureSourceSets(src, cls, alt, meta, width, imageTag) { imageTag.tagName = "picture"; let html = ` ` if (meta.webp && meta.webp[1] && meta.webp[1].url) { html += `` } if (meta.jpeg && meta.jpeg[1] && meta.jpeg[1].url) { html += `` } html += `${alt}`; imageTag.innerHTML = html; } eleventyConfig.addTransform("picture", function (str) { const parsed = parse(str); for (const imageTag of parsed.querySelectorAll(".cm-s-obsidian img")) { const src = imageTag.getAttribute("src"); if (src && src.startsWith("/") && !src.endsWith(".svg")) { const cls = imageTag.classList.value; const alt = imageTag.getAttribute("alt"); const width = imageTag.getAttribute("width") || ''; try { const meta = transformImage( "./src/site" + decodeURI(imageTag.getAttribute("src")), cls.toString(), alt, ["(max-width: 480px)", "(max-width: 1024px)"] ); if (meta) { fillPictureSourceSets(src, cls, alt, meta, width, imageTag); } } catch { // Make it fault tolarent. } } } return str && parsed.innerHTML; }); eleventyConfig.addTransform("table", function (str) { const parsed = parse(str); for (const t of parsed.querySelectorAll(".cm-s-obsidian > table")) { let inner = t.innerHTML; t.tagName = "div"; t.classList.add("table-wrapper"); t.innerHTML = `${inner}
`; } for (const t of parsed.querySelectorAll( ".cm-s-obsidian > .block-language-dataview > table" )) { t.classList.add("dataview"); t.classList.add("table-view-table"); t.querySelector("thead")?.classList.add("table-view-thead"); t.querySelector("tbody")?.classList.add("table-view-tbody"); t.querySelectorAll("thead > tr")?.forEach((tr) => { tr.classList.add("table-view-tr-header"); }); t.querySelectorAll("thead > tr > th")?.forEach((th) => { th.classList.add("table-view-th"); }); } return str && parsed.innerHTML; }); eleventyConfig.addTransform("htmlMinifier", (content, outputPath) => { if ( process.env.NODE_ENV === "production" && outputPath && outputPath.endsWith(".html") ) { return htmlMinifier.minify(content, { useShortDoctype: true, removeComments: true, collapseWhitespace: true, minifyCSS: true, minifyJS: true, keepClosingSlash: true, }); } return content; }); eleventyConfig.addPassthroughCopy("src/site/img"); eleventyConfig.addPassthroughCopy("src/site/scripts"); eleventyConfig.addPassthroughCopy("src/site/styles/_theme.*.css"); eleventyConfig.addPlugin(faviconsPlugin, { outputDir: "dist" }); eleventyConfig.addPlugin(tocPlugin, { ul: true, tags: ["h1", "h2", "h3", "h4", "h5", "h6"], }); eleventyConfig.addFilter("dateToZulu", function (date) { if (!date) return ""; return new Date(date).toISOString("dd-MM-yyyyTHH:mm:ssZ"); }); eleventyConfig.addFilter("jsonify", function (variable) { return JSON.stringify(variable) || '""'; }); eleventyConfig.addFilter("validJson", function (variable) { if (Array.isArray(variable)) { return variable.map((x) => x.replaceAll("\\", "\\\\")).join(","); } else if (typeof variable === "string") { return variable.replaceAll("\\", "\\\\"); } return variable; }); eleventyConfig.addPlugin(pluginRss, { posthtmlRenderOptions: { closingSingleTag: "slash", singleTags: ["link"], }, }); userEleventySetup(eleventyConfig); return { dir: { input: "src/site", output: "dist", data: `_data`, }, templateFormats: ["njk", "md", "11ty.js"], htmlTemplateEngine: "njk", markdownTemplateEngine: "njk", passthroughFileCopy: true, }; };