How do I add a "copy" button to a code snippet?

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


Download these 2 files and place them as they say in the tutorial.

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

1 Like

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()
1 Like

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.

1 Like

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.
1 Like

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

1 Like

This isn’t quite right. I updated the code snippet to be exactly what you’d paste into the <script> tag.

1 Like

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.

2 Likes

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 Autoimporterchecbox. 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

1 Like

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:
image

1 Like