mirror of
https://github.com/svemagie/blog-eleventy-indiekit.git
synced 2026-04-02 16:44:56 +02:00
feat: convert #hashtags in post content to category links
Adds a markdown-it inline rule that transforms #tag text into links to /categories/tag/ on-site. Syndication targets (Bluesky, Mastodon, Bridgy) continue to receive raw #tag text, which their native facet/hashtag detection handles automatically. Edge cases handled: headings, hex colors, URL fragments, code blocks, pure numbers are all excluded from conversion. Confab-Link: http://localhost:8080/sessions/0ec83454-d346-4329-8aaf-6b12139bf596
This commit is contained in:
@@ -330,11 +330,19 @@
|
||||
@apply text-sm text-surface-600 dark:text-surface-400 flex flex-wrap gap-2 items-center;
|
||||
}
|
||||
|
||||
/* Category tags */
|
||||
/* Category tags (post metadata pills) */
|
||||
.p-category {
|
||||
@apply inline-block px-2 py-0.5 text-xs bg-surface-100 dark:bg-surface-800 text-surface-600 dark:text-surface-300 rounded border border-surface-200 dark:border-surface-700 hover:border-surface-400 dark:hover:border-surface-500 transition-colors;
|
||||
}
|
||||
|
||||
/* Inline hashtags in post content — styled as subtle links, not pills */
|
||||
.e-content a.hashtag,
|
||||
.prose a.hashtag {
|
||||
@apply text-accent-600 dark:text-accent-400 no-underline hover:underline font-medium;
|
||||
/* Override prose default link styling (no border-bottom, no color shift) */
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Webmention facepile - overlapping avatar display */
|
||||
.facepile {
|
||||
@apply flex flex-wrap items-center;
|
||||
|
||||
@@ -77,6 +77,45 @@ export default function (eleventyConfig) {
|
||||
slugify: (s) => s.toLowerCase().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, ""),
|
||||
level: [2, 3, 4],
|
||||
});
|
||||
|
||||
// Hashtag plugin: converts #tag to category links on-site
|
||||
// Syndication targets (Bluesky, Mastodon) handle raw #tag natively via facet detection
|
||||
md.inline.ruler.push("hashtag", (state, silent) => {
|
||||
const pos = state.pos;
|
||||
if (state.src.charCodeAt(pos) !== 0x23 /* # */) return false;
|
||||
|
||||
// Must be at start of string or preceded by whitespace/punctuation (not part of a URL fragment or hex color)
|
||||
if (pos > 0) {
|
||||
const prevChar = state.src.charAt(pos - 1);
|
||||
if (!/[\s()\[\]{},;:!?"'«»""'']/.test(prevChar)) return false;
|
||||
}
|
||||
|
||||
// Match hashtag: # followed by letter/underscore, then word chars (letters, digits, underscores)
|
||||
const tail = state.src.slice(pos + 1);
|
||||
const match = tail.match(/^([a-zA-Z_]\w*)/);
|
||||
if (!match) return false;
|
||||
|
||||
const tag = match[1];
|
||||
|
||||
// Skip pure hex color codes (3, 4, 6, or 8 hex digits with nothing else)
|
||||
if (/^[0-9a-fA-F]{3,8}$/.test(tag)) return false;
|
||||
|
||||
if (!silent) {
|
||||
const slug = tag.toLowerCase().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
|
||||
const tokenOpen = state.push("link_open", "a", 1);
|
||||
tokenOpen.attrSet("href", `/categories/${slug}/`);
|
||||
tokenOpen.attrSet("class", "p-category hashtag");
|
||||
|
||||
const tokenText = state.push("text", "", 0);
|
||||
tokenText.content = `#${tag}`;
|
||||
|
||||
state.push("link_close", "a", -1);
|
||||
}
|
||||
|
||||
state.pos = pos + 1 + tag.length;
|
||||
return true;
|
||||
});
|
||||
|
||||
eleventyConfig.setLibrary("md", md);
|
||||
|
||||
// Syntax highlighting for fenced code blocks (```lang)
|
||||
|
||||
Reference in New Issue
Block a user