How do I add a “copy” button for code snippets on my Ghost blog?
The screen capture is from this Ghost post on adding color to code snippets.
I’m running Ghost-CLI 1.23.1 and Ghost 5.25.0.
Any help is much appreciated!
Ted
How do I add a “copy” button for code snippets on my Ghost blog?
The screen capture is from this Ghost post on adding color to code snippets.
I’m running Ghost-CLI 1.23.1 and Ghost 5.25.0.
Any help is much appreciated!
Ted
You have to pick that option when you download the JS and CSS files from prismJS website, see the pic below
Here is a link with some pre-selected options:
https://prismjs.com/download.html#themes=prism-okaidia&languages=markup+css+clike+javascript+css-extras+python&plugins=autolinker+custom-class+show-language+highlight-keywords+inline-color+autoloader+data-uri-highlight+toolbar+copy-to-clipboard+match-braces
As @diliprk said, Prism has a copy to clipboard button plugin. However, the one used on the tutorials site uses custom code to generate the copy button. Here’s the relevant snippet:
function initCodeCopy() {
const codeBlocks = document.querySelectorAll('code[class*="language-"]');
codeBlocks.forEach((block) => {
const lang = parseLanguage(block);
const referenceEl = block.parentElement;
const parent = block.parentElement.parentElement;
const wrapper = document.createElement('div');
wrapper.className = 'code-wrapper';
parent.insertBefore(wrapper, referenceEl);
wrapper.append(block.parentElement);
const copyBtn = document.createElement('button');
copyBtn.setAttribute('class', 'copy-button');
copyBtn.setAttribute('data-lang', lang);
copyBtn.innerHTML = `${lang} <svg viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M7 6V3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1h-3v3c0 .552-.45 1-1.007 1H4.007A1.001 1.001 0 0 1 3 21l.003-14c0-.552.45-1 1.007-1H7zM5.003 8L5 20h10V8H5.003zM9 6h8v10h2V4H9v2z" fill="currentColor"/></svg>`;
wrapper.insertAdjacentElement('beforeend', copyBtn);
});
function parseLanguage(block) {
const className = block.className;
if (className.startsWith('language')) {
const [prefix, lang] = className.split('-');
return lang;
}
}
function copy(e) {
const btn = e.currentTarget;
const lang = btn.dataset.lang;
const text = e.currentTarget.previousSibling.children[0].textContent;
navigator.clipboard.writeText(text).then(
() => {
btn.innerHTML = `copied! <svg viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M7 6V3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1h-3v3c0 .552-.45 1-1.007 1H4.007A1.001 1.001 0 0 1 3 21l.003-14c0-.552.45-1 1.007-1H7zm2 0h8v10h2V4H9v2z" fill="currentColor"/></svg>`;
btn.setAttribute('style', 'opacity: 1');
},
() => alert('failed to copy'),
);
setTimeout(() => {
btn.removeAttribute('style');
btn.innerHTML = `${lang} <svg viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M7 6V3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1h-3v3c0 .552-.45 1-1.007 1H4.007A1.001 1.001 0 0 1 3 21l.003-14c0-.552.45-1 1.007-1H7zM5.003 8L5 20h10V8H5.003zM9 6h8v10h2V4H9v2z" fill="currentColor"/></svg>`;
}, 3000);
}
const copyButtons = document.querySelectorAll('.copy-button');
copyButtons.forEach((btn) => {
btn.addEventListener('click', copy);
});
}
initCodeCopy()
That’s awesome Ryan!
Thanks!
I hope, at some point it will become available through an editing menu or something. Maybe I’ll be able to try and tackle that at some point and request that the code be added to Ghost?
Thanks so much for replying.
Kind regards,
Ted
Thank you to diliprk as well!
@RyanF, does that code have to be copied into a particular file, or can it be put in as a code injection?
If it has to be in a file, would you possibly be able to direct me to the right file?
Kind regards,
Ted
You should be able to add the code to the Site Footer in Code Injection.
Thanks again @RyanF!
Sorry for the noob question, but does the calling of the initCodeCopy()
function have to be done within a file, or can it also be added to the Site Footer through Code Injection?
My complete lack of understanding of node.js is evident. I’m also seeking to find the answer myself, but it could be a while before I know enough to understand.
Kind regards,
Ted
Tried this as an experiment:
<script>
function initCodeCopy() {
const codeBlocks = document.querySelectorAll('code[class*="language-"]');
codeBlocks.forEach((block) => {
const lang = parseLanguage(block);
const referenceEl = block.parentElement;
const parent = block.parentElement.parentElement;
const wrapper = document.createElement('div');
wrapper.className = 'code-wrapper';
parent.insertBefore(wrapper, referenceEl);
wrapper.append(block.parentElement);
const copyBtn = document.createElement('button');
copyBtn.setAttribute('class', 'copy-button');
copyBtn.setAttribute('data-lang', lang);
copyBtn.innerHTML = `${lang} <svg viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M7 6V3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1h-3v3c0 .552-.45 1-1.007 1H4.007A1.001 1.001 0 0 1 3 21l.003-14c0-.552.45-1 1.007-1H7zM5.003 8L5 20h10V8H5.003zM9 6h8v10h2V4H9v2z" fill="currentColor"/></svg>`;
wrapper.insertAdjacentElement('beforeend', copyBtn);
});
function parseLanguage(block) {
const className = block.className;
if (className.startsWith('language')) {
const [prefix, lang] = className.split('-');
return lang;
}
}
function copy(e) {
const btn = e.currentTarget;
const lang = btn.dataset.lang;
const text = e.currentTarget.previousSibling.children[0].textContent;
navigator.clipboard.writeText(text).then(
() => {
btn.innerHTML = `copied! <svg viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M7 6V3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1h-3v3c0 .552-.45 1-1.007 1H4.007A1.001 1.001 0 0 1 3 21l.003-14c0-.552.45-1 1.007-1H7zm2 0h8v10h2V4H9v2z" fill="currentColor"/></svg>`;
btn.setAttribute('style', 'opacity: 1');
},
() => alert('failed to copy'),
);
setTimeout(() => {
btn.removeAttribute('style');
btn.innerHTML = `${lang} <svg viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M7 6V3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1h-3v3c0 .552-.45 1-1.007 1H4.007A1.001 1.001 0 0 1 3 21l.003-14c0-.552.45-1 1.007-1H7zM5.003 8L5 20h10V8H5.003zM9 6h8v10h2V4H9v2z" fill="currentColor"/></svg>`;
}, 3000);
}
const copyButtons = document.querySelectorAll('.copy-button');
copyButtons.forEach((btn) => {
btn.addEventListener('click', copy);
}('initCodeCopy'));
</script>
No luck so far. Seems like the initialization would have to be related to the snippet box.
Hi Ryan, I added the code you sent in a <script>
tag in code injection, and also after the end of the </script>
tag, I also called the initCodeCopy()
function but all it did was add the text in the home page header.
I also did not see any change in my Copy button from the code block that I originally got from the prismJS website CSS/JS download.
It would thus be great if you could send us the prism.css
and prism.JS
files, that are being used in the ghost tutorial post and clear instructions on where/how to call this initCodeCopy()
function ?
Best Regards
This isn’t quite right. I updated the code snippet to be exactly what you’d paste into the <script>
tag.
This code runs independently of Prism—I’m using Prism without any plugins, except the autoimporter.
You may have to write some of your own code to get it to work with your site.
If you want something that will work out of the box, I’d just reach for Prism’s own plugins.
I downloaded the new prism.css and prism.js file with just Autolinker
and Autoimporter
options, checked in the plugins and I only saw the Autoloader
and Autolinker
check box on the prism download page not sure where was Autoimporter
checbox. Your revised code injection script worked (functionally) and I get the copy button alright, but alignment is not coming properly. I guess one has to tinker with the .css to get this alignment working perfectly.
Since I don’t have much time to figure this css out, I will stick with the default layouts I get with the prism JS downloads, for now
Thanks and Regards
This gets you pretty close.
If you have a live version of this up and can share the link, I can give you some guidance on the CSS, as it’ll differ slightly depending on your theme, etc.
For anyone looking for an easier way to implement this, I’ve got it working on my Ghost instance by adding the following to the Header:
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/toolbar/prism-toolbar.min.css" integrity="sha512-Dqf5696xtofgH089BgZJo2lSWTvev4GFo+gA2o4GullFY65rzQVQLQVlzLvYwTo0Bb2Gpb6IqwxYWtoMonfdhQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
And the following to the Footer:
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/toolbar/prism-toolbar.min.js" integrity="sha512-st608h+ZqzliahyzEpETxzU0f7z7a9acN6AFvYmHvpFhmcFuKT8a22TT5TpKpjDa3pt3Wv7Z3SdQBCBdDPhyWA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/copy-to-clipboard/prism-copy-to-clipboard.min.js" integrity="sha512-/kVH1uXuObC0iYgxxCKY41JdWOkKOxorFVmip+YVifKsJ4Au/87EisD1wty7vxN2kAhnWh6Yc8o/dSAXj6Oz7A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
Which looks like this: