fonsp / Pluto.jl

🎈 Simple reactive notebooks for Julia
https://plutojl.org/
MIT License
4.91k stars 284 forks source link

feat: add copy button for markdown code blocks #2799

Closed chayandatta closed 2 months ago

chayandatta commented 5 months ago

fixes: #2668

image image

Thanks @pankgeorg for all the help

github-actions[bot] commented 5 months ago

Try this Pull Request!

Open Julia and type:

  julia> import Pkg
  julia> Pkg.activate(temp=true)
  julia> Pkg.add(url="https://github.com/chayandatta/Pluto.jl", rev="cd/copy-button")
  julia> using Pluto
fonsp commented 5 months ago

Heyyy that's cool! Could you reupload the screenshots? The URLs are from a private repo

fonsp commented 5 months ago

I would like to also use this functionality in more places!

Do you think it is possible to move this functionality to the RawHTMLContainer component? You could take a look at where we call highlight (which uses highlight.js to syntax highlight code blocks). Maybe this can be done in the same place?

Als try to extract this functionality into a new function, just like highlight.

chayandatta commented 5 months ago

Hi, I've moved the code inside RawHTMLContainer and extracted the functionality into a new function.

You could take a look at where we call highlight (which uses highlight.js to syntax highlight code blocks). Maybe this can be done in the same place?

As far I can understand, where hightlight() is being called, It can be accessed only when the notebook is running, not on static mode.

and generateCopyCodeButton generates an element of Type 'ReactElement' and it is unable to prepend(container.prepend()) with the element container because it's type HTMLElement.

fonsp commented 5 months ago

Thanks for taking a look again!

This is not exactly what I meant. 🙈 Instead of using regex to find the code blocks, and then a preact element for the button, consider using container.querySelectorAll to find the code blocks, and document.createElement to create the button.

In CellOutput.js, add this block:

                // EXISTING:
                // Apply syntax highlighting
                try {
                    ... highlight ...
                } catch ... {}

                // NEW:
                // Find code blocks and add a copy button:
                try {
                    if (container.firstElementChild?.matches("div.markdown")) {
                        container.querySelectorAll("pre > code").forEach((code_element) => {
                            const pre = code_element.parentElement
                            add_code_copy_button(pre)
                        })
                    }
                } catch (err) {
                    console.warn("Adding markdown code copy button failed", err)
                }

Then the add_code_copy_button uses document.createElement to create the button, and appendChild to add it to the page.

fonsp commented 5 months ago

As far I can understand, where hightlight() is being called, It can be accessed only when the notebook is running, not on static mode.

It's true that highlight is not called when the notebook is in "safe mode". Let's think about that later!

fonsp commented 4 months ago

Nice! It was currently positioned relative to the cell, not relative to the <pre>. I fixed that, you can see it when you have multiple code blocks in one cell.

Can you make the button look a bit nicer in dark mode?

image

In general, this button should match the styling of all other buttons in the Pluto UI. Carefully take a look at their border, background, light/dark theme, opacity, visibility-on-hover behaviour (simulate a touch screen with dev tools), padding. You can probably get all this behaviour for free if you add selectors to our existing CSS styles.

We usually give buttons an image with the ::before { background-image: url trick. Again, take a look at the other buttons to see how this was done. (Maybe this is a tip in general for future PRs! Try to find existing functionality that achieves some of the same goals that you want, and try to reuse or copy their implementation.)

chayandatta commented 3 months ago

https://github.com/fonsp/Pluto.jl/assets/32599474/eef8c171-5d84-4a3f-9545-d7cf0cd5f1c1

fonsp commented 2 months ago

Thanks!

image

The button was positioned relative to the last line of code (with pre.appendChild(button) it ends up at the end of the child list, and top: -20px moves it up.) I fixed that by prepending the button to the start of the children of <pre>.

Now it's positioned as expected:

image

I also tested this on Firefox and Safari and it works great!

I removed the

.then(() => {
            console.log("Code copied to clipboard:\n" + txt)
        })
        .catch((error) => {
            console.error("Error copying code to clipboard:", error)
        })

Because the then won't be so useful anymore after this is merged, more useful during development, and the catch is actually done automatically! If you have a promise in an event handler that rejects, but there is no catch, Chrome will automatically log it to the console.

Thanks again!