the home site for me: also iteration 3 or 4 of my site

feat: add inline emoji fetching from cachet

dunkirk.sh 20a4c7dd 51535cdf

verified
Changed files
+107
content
sass
static
templates
+2
content/blog/2024-10-11_example_post.md
···
---
But these should be used sparingly, if at all.
+
+
You can also use emojis inline from the hackclub slack like this :yay:! This is just done by writing `:emoji:` and it gets progressively enhanced with a bit of js as long as the emoji is in cachet!
+32
sass/css/_emoji-inline.scss
···
+
.emoji-inline--wrapper {
+
vertical-align: baseline;
+
height: auto;
+
position: relative;
+
overflow: visible;
+
vertical-align: top;
+
object-fit: contain;
+
align-items: center;
+
display: inline-flex;
+
overflow: hidden;
+
width: 22px;
+
height: 22px;
+
}
+
+
.emoji-inline--wrapper img {
+
object-fit: contain;
+
position: absolute;
+
top: 50%;
+
overflow: hidden;
+
width: 22px !important;
+
height: 22px !important;
+
margin-top: -11px;
+
margin-left: 0 !important;
+
margin-right: 0 !important;
+
margin-bottom: 0 !important;
+
border: none !important;
+
border-radius: 0 !important;
+
padding: 0 !important;
+
opacity: 1 !important;
+
max-width: 22px !important;
+
display: inline !important;
+
}
+1
sass/css/main.scss
···
@use "copy-button";
@use "view-transitions";
+
@use "emoji-inline";
+65
static/js/emoji-replace.js
···
+
document.addEventListener("DOMContentLoaded", () => {
+
const content = document.querySelector("main");
+
if (!content) return;
+
+
const walker = document.createTreeWalker(content, NodeFilter.SHOW_TEXT, {
+
acceptNode: (node) => {
+
// Skip code blocks, pre tags, and script/style tags
+
let parent = node.parentElement;
+
while (parent) {
+
const tag = parent.tagName.toLowerCase();
+
if (
+
tag === "code" ||
+
tag === "pre" ||
+
tag === "script" ||
+
tag === "style"
+
) {
+
return NodeFilter.FILTER_REJECT;
+
}
+
parent = parent.parentElement;
+
}
+
return NodeFilter.FILTER_ACCEPT;
+
},
+
});
+
+
const nodesToReplace = [];
+
while (walker.nextNode()) {
+
const node = walker.currentNode;
+
if (/:[\w-]+:/.test(node.textContent)) {
+
nodesToReplace.push(node);
+
}
+
}
+
+
nodesToReplace.forEach((node) => {
+
const frag = document.createDocumentFragment();
+
const parts = node.textContent.split(/(:[\w-]+:)/);
+
+
parts.forEach((part) => {
+
if (/^:[\w-]+:$/.test(part)) {
+
const name = part.slice(1, -1);
+
+
const span = document.createElement("span");
+
span.className = "emoji-inline--wrapper";
+
+
const img = document.createElement("img");
+
img.src = `https://cachet.dunkirk.sh/emojis/${name}/r`;
+
img.alt = part;
+
img.className = "emoji-inline";
+
img.loading = "lazy";
+
img.setAttribute("aria-label", `${name} emoji`);
+
+
// Fallback: if image fails to load, show original text
+
img.onerror = () => {
+
span.replaceWith(document.createTextNode(part));
+
};
+
+
span.appendChild(img);
+
frag.appendChild(span);
+
} else if (part) {
+
frag.appendChild(document.createTextNode(part));
+
}
+
});
+
+
node.replaceWith(frag);
+
});
+
});
+7
templates/head.html
···
defer
></script>
+
{% set emojiJsHash = get_hash(path="js/emoji-replace.js", sha_type=256,
+
base64=true) %}
+
<script
+
src="{{ get_url(path='js/emoji-replace.js?' ~ emojiJsHash, trailing_slash=false) | safe }}"
+
defer
+
></script>
+
<script type="speculationrules">
{
"prerender": [