Add a Copy button to code blocks on FreeFlarum, and automatically merge multi-line “code pills” into one proper code box, without breaking the editor.
Important behaviour note:
This setup only activates for multi-line code.
Single-line code (for example a one-line CSS rule) will not trigger unless it spans more than one line.
Note: When editing posts on FreeFlarum, the editor can sometimes alter code formatting or symbols. Always double-check your code before posting to make sure it hasn’t been changed.
Step 1: Open your FreeFlarum Admin Panel
Go to your forum
Tap your avatar (top right)
Go to Admin
Step 2: Add the JavaScript (Footer)
In Admin, go to Appearance → Custom HTML
Scroll to the Footer box
Paste all of the following code into the Footer box
Click Save Changes
<script>(function(){function inEditor(n){return!!(n&&n.closest&&(n.closest(".Composer,.ComposerPage,.TextEditor,.TextEditor-editor,.TextEditor-controls,.ComposerBody,.Composer-content,.Modal,.Form-group")||n.closest("[contenteditable='true']")))}function inContent(n){return!!(n&&n.closest&&n.closest(".Post-body,.CommentPost-body,.Post-content,.Comment-content,.item-body,.BlogPost,.BlogPost-body,.DiscussionPage,.UserPage,.Page-content"))}function addCopy(pre,code){if(!pre||pre.dataset.ffCopy)return;pre.dataset.ffCopy="1";var b=document.createElement("button");b.type="button";b.className="ff-copy-btn";b.textContent="Copy";b.addEventListener("click",function(e){e.preventDefault();e.stopPropagation();var t=code.innerText||code.textContent||"";function ok(){b.textContent="Copied!";setTimeout(function(){b.textContent="Copy"},1200)}function fail(){b.textContent="Copy failed";setTimeout(function(){b.textContent="Copy"},1200)}function fb(){var ta=document.createElement("textarea");ta.value=t;ta.style.position="fixed";ta.style.left="-9999px";document.body.appendChild(ta);ta.focus();ta.select();try{document.execCommand("copy")?ok():fail()}catch(x){fail()}document.body.removeChild(ta)}if(navigator.clipboard&&window.isSecureContext){navigator.clipboard.writeText(t).then(ok).catch(fb)}else fb()});pre.appendChild(b)}function prepPreBlocks(root){(root||document).querySelectorAll("pre>code").forEach(function(code){var pre=code.parentElement;if(!pre||inEditor(pre)||!inContent(pre))return;pre.classList.add("ff-code-pre");addCopy(pre,code)})}function isPill(c){return c&&c.tagName==="CODE"&&!c.closest("pre")&&!c.closest(".ff-code-pre")}function isCodeOnlyBlock(el){if(!el||el.nodeType!==1||inEditor(el)||!inContent(el))return false;var t=(el.textContent||"").replace(/\u00a0/g," ").trim();if(!t)return false;if(!el.querySelector("code"))return false;for(var n=el.firstChild;n;n=n.nextSibling){if(n.nodeType===3&&n.textContent.trim()==="")continue;if(n.nodeType===1&&(n.tagName==="CODE"||n.tagName==="BR"||(n.querySelector&&n.querySelector("code"))))continue;var txt=(n.textContent||"").trim();if(txt&&!(n.querySelector&&n.querySelector("code")))return false}return true}function blockRoot(node){return(node.closest&&node.closest("p,li,blockquote,div,section,article"))||node.parentElement}function collectBlocksFrom(code){var start=blockRoot(code);if(!start||start.tagName==="PRE")return null;var blocks=[],el=start,guard=0;while(el&&guard++<160){if(isCodeOnlyBlock(el)){blocks.push(el);el=el.nextElementSibling;continue}break}return blocks.length?blocks:null}function blocksText(blocks){var out=[];blocks.forEach(function(b,bi){b.querySelectorAll("code").forEach(function(c){if(isPill(c))out.push(c.textContent)});if(bi<blocks.length-1)out.push("\n")});return out.join("\n").replace(/\n{3,}/g,"\n\n").trim()}function mergePills(root){(root||document).querySelectorAll("code").forEach(function(code){if(!isPill(code)||inEditor(code)||!inContent(code)||code.dataset.ffDone)return;var blocks=collectBlocksFrom(code);if(!blocks||!blocks.length)return;var txt=blocksText(blocks);if(!txt||txt.indexOf("\n")===-1)return;var pre=document.createElement("pre");pre.className="ff-code-pre";var merged=document.createElement("code");merged.textContent=txt;pre.appendChild(merged);blocks[0].parentNode.insertBefore(pre,blocks[0]);blocks.forEach(function(b){b.remove()});addCopy(pre,merged);code.dataset.ffDone="1"})}function scan(root){prepPreBlocks(root);mergePills(root)}var t=null;function schedule(){if(t)clearTimeout(t);t=setTimeout(function(){scan(document)},80)}function hookHistory(k){var o=history[k];if(!o)return;history[k]=function(){var r=o.apply(this,arguments);schedule();setTimeout(schedule,250);return r}}hookHistory("pushState");hookHistory("replaceState");addEventListener("popstate",function(){schedule();setTimeout(schedule,250)});addEventListener("pageshow",function(){schedule();setTimeout(schedule,250)});new MutationObserver(function(m){for(var i=0;i<m.length;i++){for(var j=0;j<m[i].addedNodes.length;j++){var n=m[i].addedNodes[j];if(n&&n.nodeType===1){schedule();return}}}}).observe(document.body,{childList:true,subtree:true});if(document.readyState==="loading"){document.addEventListener("DOMContentLoaded",function(){schedule();setTimeout(schedule,400)})}else{schedule();setTimeout(schedule,400)}})();
</script>
Step 3: Add the CSS styling
Go to Appearance → Custom CSS
Paste the following CSS
Click Save Changes
.ff-code-pre{
position:relative!important;
margin:12px 0!important;
padding:44px 14px 14px!important;
border-radius:18px!important;
background:rgba(0,0,0,.04)!important;
overflow:auto!important;
-webkit-overflow-scrolling:touch!important
}
.ff-code-pre code{
display:block!important;
white-space:pre!important;
font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace!important;
font-size:13px!important;
line-height:1.45!important
}
.ff-copy-btn{
position:absolute!important;
top:10px!important;
right:10px!important;
z-index:9!important;
font-size:12px!important;
line-height:1!important;
padding:7px 12px!important;
border:0!important;
border-radius:999px!important;
background:#e7e7e7!important;
color:#000!important;
cursor:pointer!important;
box-shadow:0 2px 8px rgba(0,0,0,.18)!important
}.ff-code-pre code{
white-space:pre-wrap!important;
word-break:break-word!important;
}
Step 4: Test it
Create a post
Use the code block button (or fenced code)
Publish
A Copy button should appear in the code block