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

Configure Feed

Select the types of activity you want to include in your feed.

feat: add inline emoji fetching from cachet

+107
+2
content/blog/2024-10-11_example_post.md
··· 158 158 --- 159 159 160 160 But these should be used sparingly, if at all. 161 + 162 + 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
··· 1 + .emoji-inline--wrapper { 2 + vertical-align: baseline; 3 + height: auto; 4 + position: relative; 5 + overflow: visible; 6 + vertical-align: top; 7 + object-fit: contain; 8 + align-items: center; 9 + display: inline-flex; 10 + overflow: hidden; 11 + width: 22px; 12 + height: 22px; 13 + } 14 + 15 + .emoji-inline--wrapper img { 16 + object-fit: contain; 17 + position: absolute; 18 + top: 50%; 19 + overflow: hidden; 20 + width: 22px !important; 21 + height: 22px !important; 22 + margin-top: -11px; 23 + margin-left: 0 !important; 24 + margin-right: 0 !important; 25 + margin-bottom: 0 !important; 26 + border: none !important; 27 + border-radius: 0 !important; 28 + padding: 0 !important; 29 + opacity: 1 !important; 30 + max-width: 22px !important; 31 + display: inline !important; 32 + }
+1
sass/css/main.scss
··· 6 6 7 7 @use "copy-button"; 8 8 @use "view-transitions"; 9 + @use "emoji-inline";
+65
static/js/emoji-replace.js
··· 1 + document.addEventListener("DOMContentLoaded", () => { 2 + const content = document.querySelector("main"); 3 + if (!content) return; 4 + 5 + const walker = document.createTreeWalker(content, NodeFilter.SHOW_TEXT, { 6 + acceptNode: (node) => { 7 + // Skip code blocks, pre tags, and script/style tags 8 + let parent = node.parentElement; 9 + while (parent) { 10 + const tag = parent.tagName.toLowerCase(); 11 + if ( 12 + tag === "code" || 13 + tag === "pre" || 14 + tag === "script" || 15 + tag === "style" 16 + ) { 17 + return NodeFilter.FILTER_REJECT; 18 + } 19 + parent = parent.parentElement; 20 + } 21 + return NodeFilter.FILTER_ACCEPT; 22 + }, 23 + }); 24 + 25 + const nodesToReplace = []; 26 + while (walker.nextNode()) { 27 + const node = walker.currentNode; 28 + if (/:[\w-]+:/.test(node.textContent)) { 29 + nodesToReplace.push(node); 30 + } 31 + } 32 + 33 + nodesToReplace.forEach((node) => { 34 + const frag = document.createDocumentFragment(); 35 + const parts = node.textContent.split(/(:[\w-]+:)/); 36 + 37 + parts.forEach((part) => { 38 + if (/^:[\w-]+:$/.test(part)) { 39 + const name = part.slice(1, -1); 40 + 41 + const span = document.createElement("span"); 42 + span.className = "emoji-inline--wrapper"; 43 + 44 + const img = document.createElement("img"); 45 + img.src = `https://cachet.dunkirk.sh/emojis/${name}/r`; 46 + img.alt = part; 47 + img.className = "emoji-inline"; 48 + img.loading = "lazy"; 49 + img.setAttribute("aria-label", `${name} emoji`); 50 + 51 + // Fallback: if image fails to load, show original text 52 + img.onerror = () => { 53 + span.replaceWith(document.createTextNode(part)); 54 + }; 55 + 56 + span.appendChild(img); 57 + frag.appendChild(span); 58 + } else if (part) { 59 + frag.appendChild(document.createTextNode(part)); 60 + } 61 + }); 62 + 63 + node.replaceWith(frag); 64 + }); 65 + });
+7
templates/head.html
··· 86 86 defer 87 87 ></script> 88 88 89 + {% set emojiJsHash = get_hash(path="js/emoji-replace.js", sha_type=256, 90 + base64=true) %} 91 + <script 92 + src="{{ get_url(path='js/emoji-replace.js?' ~ emojiJsHash, trailing_slash=false) | safe }}" 93 + defer 94 + ></script> 95 + 89 96 <script type="speculationrules"> 90 97 { 91 98 "prerender": [