refactor: moved plugins to own folders
This commit is contained in:
parent
a212df96dd
commit
63bd438e45
6 changed files with 235 additions and 223 deletions
68
lib/cache-busting/plugin.js
Normal file
68
lib/cache-busting/plugin.js
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
const fs = require("fs");
|
||||
const crypto = require("crypto");
|
||||
const path = require("path");
|
||||
|
||||
const fileHashCache = {};
|
||||
const getFileHash = (file, dir = "css") => {
|
||||
const cacheKey = `${dir}/${file}`;
|
||||
if (fileHashCache[cacheKey]) return fileHashCache[cacheKey];
|
||||
|
||||
const filePath = path.join(__dirname, '..', '..', dir, file);
|
||||
try {
|
||||
const content = fs.readFileSync(filePath, "utf-8");
|
||||
const hash = crypto.createHash("md5").update(content).digest("hex").slice(0, 8);
|
||||
fileHashCache[cacheKey] = hash;
|
||||
return hash;
|
||||
} catch (e) {
|
||||
console.warn(`Could not hash file: ${file} in ${dir}`);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const copyHashedFiles = (sourceDir, outputDir) => {
|
||||
if (!fs.existsSync(sourceDir)) return;
|
||||
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
const dirName = path.basename(sourceDir);
|
||||
const files = fs.readdirSync(sourceDir);
|
||||
for (const file of files) {
|
||||
const hash = getFileHash(file, dirName);
|
||||
const ext = path.extname(file);
|
||||
const base = path.basename(file, ext);
|
||||
const hashedName = `${base}${hash == null ? '' : `.${hash}`}${ext}`;
|
||||
|
||||
fs.copyFileSync(
|
||||
path.join(sourceDir, file),
|
||||
path.join(outputDir, hashedName)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const cacheBustingPlugin = (eleventyConfig, { rootDir }) => {
|
||||
eleventyConfig.addFilter("fileHash", (file, dir = "css") => {
|
||||
const hash = getFileHash(file, dir);
|
||||
const ext = path.extname(file);
|
||||
const base = path.basename(file, ext);
|
||||
return `/${dir}/${base}.${hash}${ext}`;
|
||||
});
|
||||
|
||||
eleventyConfig.on("eleventy.before", async () => {
|
||||
// Copy CSS files with hashes
|
||||
const cssDir = path.join(rootDir, "css");
|
||||
const outputCssDir = path.join(rootDir, "_site", "css");
|
||||
copyHashedFiles(cssDir, outputCssDir);
|
||||
|
||||
// Copy assets files with hashes
|
||||
const assetsDir = path.join(rootDir, "assets");
|
||||
const outputAssetsDir = path.join(rootDir, "_site", "assets");
|
||||
copyHashedFiles(assetsDir, outputAssetsDir);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
cacheBustingPlugin,
|
||||
getFileHash,
|
||||
};
|
||||
17
lib/details/plugin.js
Normal file
17
lib/details/plugin.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
const markdownItContainer = require("markdown-it-container");
|
||||
|
||||
const markdownItDetails = (md) => {
|
||||
md.use(markdownItContainer, 'details', {
|
||||
validate: (params) => params.trim().match(/^(.*)$/),
|
||||
render: (tokens, idx) => {
|
||||
const m = tokens[idx].info.trim().match(/^(.*)$/);
|
||||
if (tokens[idx].nesting === 1) {
|
||||
const title = md.utils.escapeHtml(m[1]);
|
||||
return `<details class="expandable">\n<summary>${title}</summary>\n`;
|
||||
}
|
||||
return '</details>\n';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = markdownItDetails;
|
||||
43
lib/hashtags/plugin.js
Normal file
43
lib/hashtags/plugin.js
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
const hashtagRegex = /^#([a-zA-Z][a-zA-Z0-9_\\\/\-]*)(?![a-zA-Z0-9_-])/;
|
||||
|
||||
const HASH_CODE = '#'.charCodeAt(0);
|
||||
const SPACE_CODE = ' '.charCodeAt(0);
|
||||
const TAB_CODE = '\t'.charCodeAt(0);
|
||||
const NEWLINE_CODE = '\n'.charCodeAt(0);
|
||||
const CARRIAGE_RETURN_CODE = '\r'.charCodeAt(0);
|
||||
|
||||
const markdownItHashtag = (md) => {
|
||||
md.inline.ruler.push('hashtag', function(state, silent) {
|
||||
const pos = state.pos;
|
||||
const ch = state.src.charCodeAt(pos);
|
||||
|
||||
if (ch !== HASH_CODE) return false;
|
||||
|
||||
if (pos > 0) {
|
||||
const prevCh = state.src.charCodeAt(pos - 1);
|
||||
if (prevCh !== SPACE_CODE && prevCh !== TAB_CODE && prevCh !== NEWLINE_CODE && prevCh !== CARRIAGE_RETURN_CODE) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const match = state.src.slice(pos).match(hashtagRegex);
|
||||
if (!match) return false;
|
||||
|
||||
if (!silent) {
|
||||
const token = state.push('hashtag', 'a', 0);
|
||||
token.content = match[1];
|
||||
token.markup = '#';
|
||||
}
|
||||
|
||||
state.pos += match[0].length;
|
||||
return true;
|
||||
});
|
||||
|
||||
md.renderer.rules.hashtag = function(tokens, idx) {
|
||||
const tagName = tokens[idx].content;
|
||||
const slug = tagName.toLowerCase();
|
||||
return `<a href="/tags/${slug}/" class="inline-tag">#${md.utils.escapeHtml(tagName)}</a>`;
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = markdownItHashtag;
|
||||
41
lib/strip-trailing-hashtags/plugin.js
Normal file
41
lib/strip-trailing-hashtags/plugin.js
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
// Plugin: Strip trailing hashtag-only paragraphs from rendered output
|
||||
// Must be applied AFTER footnote plugin since footnotes are moved to the end
|
||||
const markdownItStripTrailingHashtags = (md) => {
|
||||
md.core.ruler.push('strip_trailing_hashtags', function(state) {
|
||||
const tokens = state.tokens;
|
||||
|
||||
const isHashtagOnlyParagraph = (inlineToken) =>
|
||||
inlineToken?.type === 'inline' &&
|
||||
inlineToken.children?.every(child =>
|
||||
child.type === 'hashtag' ||
|
||||
(child.type === 'text' && child.content.trim() === '') ||
|
||||
child.type === 'softbreak'
|
||||
) &&
|
||||
inlineToken.children?.some(child => child.type === 'hashtag');
|
||||
|
||||
const isHashtagParagraphAt = (idx) =>
|
||||
tokens[idx]?.type === 'paragraph_open' &&
|
||||
tokens[idx + 1]?.type === 'inline' &&
|
||||
tokens[idx + 2]?.type === 'paragraph_close' &&
|
||||
isHashtagOnlyParagraph(tokens[idx + 1]);
|
||||
|
||||
const footnoteIdx = tokens.findIndex(t => t.type === 'footnote_block_open');
|
||||
const footnoteSectionStart = footnoteIdx === -1 ? tokens.length : footnoteIdx;
|
||||
|
||||
const hashtagSectionStart = Array.from(
|
||||
{ length: Math.floor(footnoteSectionStart / 3) },
|
||||
(_, i) => footnoteSectionStart - 3 * (i + 1)
|
||||
).reduce(
|
||||
(start, idx) => isHashtagParagraphAt(idx) ? idx : start,
|
||||
footnoteSectionStart
|
||||
);
|
||||
|
||||
state.tokens = tokens.filter((_, idx) =>
|
||||
idx < hashtagSectionStart || idx >= footnoteSectionStart
|
||||
);
|
||||
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = markdownItStripTrailingHashtags;
|
||||
58
lib/tags/plugin.js
Normal file
58
lib/tags/plugin.js
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
const fs = require("fs");
|
||||
|
||||
const extractTags = (content, mdInstance) => {
|
||||
if (!content) return [];
|
||||
|
||||
const collectHashtags = (tokens) =>
|
||||
tokens.flatMap(token => [
|
||||
...(token.type === 'hashtag' ? [token.content] : []),
|
||||
...(token.children ? collectHashtags(token.children) : [])
|
||||
]);
|
||||
|
||||
const tokens = mdInstance.parse(content, {});
|
||||
const tags = collectHashtags(tokens);
|
||||
return [...new Set(tags)];
|
||||
}
|
||||
|
||||
const expandHierarchicalTags = (tags) => {
|
||||
const expanded = new Set();
|
||||
tags.forEach(tag => {
|
||||
expanded.add(tag);
|
||||
const parts = tag.split('/');
|
||||
for (let i = 1; i < parts.length; i++) {
|
||||
const parentTag = parts.slice(0, i).join('/');
|
||||
expanded.add(parentTag);
|
||||
}
|
||||
});
|
||||
return [...expanded];
|
||||
};
|
||||
|
||||
const getPostTags = (post, tagMdInstance) => {
|
||||
const filePath = post.inputPath;
|
||||
try {
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
const tags = extractTags(content, tagMdInstance);
|
||||
return tags.map(tag => tag.toLowerCase());
|
||||
} catch (e) {
|
||||
// Skip if file can't be read
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
const getRecipeTags = (recipe, tagMdInstance) => {
|
||||
const filePath = recipe.inputPath;
|
||||
try {
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
const tags = extractTags(content, tagMdInstance);
|
||||
return tags.map(tag => tag.toLowerCase());
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
extractTags,
|
||||
expandHierarchicalTags,
|
||||
getPostTags,
|
||||
getRecipeTags,
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue