501 lines
16 KiB
JavaScript
501 lines
16 KiB
JavaScript
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 '<a class="tag" onclick="toggleTagSearch(this)">';
|
||
};
|
||
})
|
||
.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 `<pre class="mermaid">${code}</pre>`;
|
||
}
|
||
if (token.info === "transclusion") {
|
||
const code = token.content.trim();
|
||
return `<div class="transclusion">${md.render(code)}</div>`;
|
||
}
|
||
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 ? `<div class="callout-fold">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-chevron-down">
|
||
<polyline points="6 9 12 15 18 9"></polyline>
|
||
</svg>
|
||
</div>` : "";
|
||
const titleDiv = titleLine
|
||
? `<div class="callout-title"><div class="callout-title-inner">${titleLine}</div>${foldDiv}</div>`
|
||
: "";
|
||
let collapseClasses = titleLine && collapsible ? 'is-collapsible' : ''
|
||
if (collapsible && collapsed) {
|
||
collapseClasses += " is-collapsed"
|
||
}
|
||
|
||
let res = `<div data-callout-metadata class="callout ${collapseClasses}" data-callout="${
|
||
token.info.substring(3)
|
||
}">${titleDiv}\n<div class="callout-content">${md.render(
|
||
parts.slice(nbLinesToSkip).join("\n")
|
||
)}</div></div>`;
|
||
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 `<a class="internal-link is-unresolved" href="/404">${title}</a>`;
|
||
}
|
||
return `<a class="internal-link" data-note-icon="${noteIcon}" href="${permalink}${headerLinkPath}">${title}</a>`;
|
||
})
|
||
);
|
||
});
|
||
|
||
eleventyConfig.addFilter("taggify", function (str) {
|
||
return (
|
||
str &&
|
||
str.replace(tagRegex, function (match, precede, tag) {
|
||
return `${precede}<a class="tag" onclick="toggleTagSearch(this)" data-content="${tag}">${tag}</a>`;
|
||
})
|
||
);
|
||
});
|
||
|
||
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
|
||
? `<div class="callout-fold"><i icon-name="chevron-down"></i></div>`
|
||
: ``;
|
||
|
||
calloutType = callout;
|
||
titleDiv = `<div class="callout-title"><div class="callout-title-inner">${titleText}</div>${fold}</div>`;
|
||
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<div class="callout-content">${content}</div>`;
|
||
}
|
||
};
|
||
|
||
transformCalloutBlocks();
|
||
|
||
return str && parsed.innerHTML;
|
||
});
|
||
|
||
function fillPictureSourceSets(src, cls, alt, meta, width, imageTag) {
|
||
imageTag.tagName = "picture";
|
||
let html = `<source
|
||
media="(max-width:480px)"
|
||
srcset="${meta.webp[0].url}"
|
||
type="image/webp"
|
||
/>
|
||
<source
|
||
media="(max-width:480px)"
|
||
srcset="${meta.jpeg[0].url}"
|
||
/>
|
||
`
|
||
if (meta.webp && meta.webp[1] && meta.webp[1].url) {
|
||
html += `<source
|
||
media="(max-width:1920px)"
|
||
srcset="${meta.webp[1].url}"
|
||
type="image/webp"
|
||
/>`
|
||
}
|
||
if (meta.jpeg && meta.jpeg[1] && meta.jpeg[1].url) {
|
||
html += `<source
|
||
media="(max-width:1920px)"
|
||
srcset="${meta.jpeg[1].url}"
|
||
/>`
|
||
}
|
||
html += `<img
|
||
class="${cls.toString()}"
|
||
src="${src}"
|
||
alt="${alt}"
|
||
width="${width}"
|
||
/>`;
|
||
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 = `<table>${inner}</table>`;
|
||
}
|
||
|
||
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,
|
||
};
|
||
};
|