From dd1317aec57d006a5e520ebdb3b9eacf18fa2057 Mon Sep 17 00:00:00 2001 From: Tomas Roun Date: Tue, 1 Apr 2025 20:08:22 +0200 Subject: [PATCH 1/3] Add a copy button to all code samples --- python_docs_theme/static/copybutton.js | 98 ++++++++------------ python_docs_theme/static/pydoctheme.css | 22 +++-- python_docs_theme/static/pydoctheme_dark.css | 13 +++ 3 files changed, 66 insertions(+), 67 deletions(-) diff --git a/python_docs_theme/static/copybutton.js b/python_docs_theme/static/copybutton.js index f176ff6..629d67f 100644 --- a/python_docs_theme/static/copybutton.js +++ b/python_docs_theme/static/copybutton.js @@ -1,65 +1,47 @@ -// ``function*`` denotes a generator in JavaScript, see -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function* -function* getHideableCopyButtonElements(rootElement) { - // yield all elements with the "go" (Generic.Output), - // "gp" (Generic.Prompt), or "gt" (Generic.Traceback) CSS class - for (const el of rootElement.querySelectorAll('.go, .gp, .gt')) { - yield el - } - // tracebacks (.gt) contain bare text elements that need to be - // wrapped in a span to hide or show the element - for (let el of rootElement.querySelectorAll('.gt')) { - while ((el = el.nextSibling) && el.nodeType !== Node.DOCUMENT_NODE) { - // stop wrapping text nodes when we hit the next output or - // prompt element - if (el.nodeType === Node.ELEMENT_NODE && el.matches(".gp, .go")) { - break - } - // if the node is a text node with content, wrap it in a - // span element so that we can control visibility - if (el.nodeType === Node.TEXT_NODE && el.textContent.trim()) { - const wrapper = document.createElement("span") - el.after(wrapper) - wrapper.appendChild(el) - el = wrapper - } - yield el +// Extract copyable text from the code block ignoring the +// prompts and output. +function getCopyableText(rootElement) { + rootElement = rootElement.cloneNode(true) + // tracebacks (.gt) contain bare text elements that + // need to be removed + const tracebacks = rootElement.querySelectorAll(".gt") + for (const el of tracebacks) { + while ( + el.nextSibling && + (el.nextSibling.nodeType !== Node.DOCUMENT_NODE || + !el.nextSibling.matches(".gp, .go")) + ) { + el.nextSibling.remove() } } + // Remove all elements with the "go" (Generic.Output), + // "gp" (Generic.Prompt), or "gt" (Generic.Traceback) CSS class + const elements = rootElement.querySelectorAll(".gp, .go, .gt") + for (const el of elements) { + el.remove() + } + return rootElement.innerText.trim() } - const loadCopyButton = () => { - /* Add a [>>>] button in the top-right corner of code samples to hide - * the >>> and ... prompts and the output and thus make the code - * copyable. */ - const hide_text = _("Hide the prompts and output") - const show_text = _("Show the prompts and output") - - const button = document.createElement("span") + const button = document.createElement("button") button.classList.add("copybutton") - button.innerText = ">>>" - button.title = hide_text - button.dataset.hidden = "false" - const buttonClick = event => { + button.type = "button" + button.innerText = _("Copy") + button.title = _("Copy to clipboard") + + const makeOnButtonClick = () => { + let timeout = null // define the behavior of the button when it's clicked - event.preventDefault() - const buttonEl = event.currentTarget - const codeEl = buttonEl.nextElementSibling - if (buttonEl.dataset.hidden === "false") { - // hide the code output - for (const el of getHideableCopyButtonElements(codeEl)) { - el.hidden = true - } - buttonEl.title = show_text - buttonEl.dataset.hidden = "true" - } else { - // show the code output - for (const el of getHideableCopyButtonElements(codeEl)) { - el.hidden = false - } - buttonEl.title = hide_text - buttonEl.dataset.hidden = "false" + return event => { + clearTimeout(timeout) + const buttonEl = event.currentTarget + const codeEl = buttonEl.nextElementSibling + navigator.clipboard.writeText(getCopyableText(codeEl)) + buttonEl.innerText = _("Copied!") + timeout = setTimeout(() => { + buttonEl.innerText = _("Copy") + }, 1500) } } @@ -78,10 +60,8 @@ const loadCopyButton = () => { // if we find a console prompt (.gp), prepend the (deeply cloned) button const clonedButton = button.cloneNode(true) // the onclick attribute is not cloned, set it on the new element - clonedButton.onclick = buttonClick - if (el.querySelector(".gp") !== null) { - el.prepend(clonedButton) - } + clonedButton.onclick = makeOnButtonClick() + el.prepend(clonedButton) }) } diff --git a/python_docs_theme/static/pydoctheme.css b/python_docs_theme/static/pydoctheme.css index 40d9cb0..6d50092 100644 --- a/python_docs_theme/static/pydoctheme.css +++ b/python_docs_theme/static/pydoctheme.css @@ -442,17 +442,23 @@ div.genindex-jumpbox a { top: 0; right: 0; font-family: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace; - padding-left: 0.2em; - padding-right: 0.2em; + font-size: 80%; + padding-left: .5em; + padding-right: .5em; + height: 100%; + max-height: min(100%, 2.4em); border-radius: 0 3px 0 0; - color: #ac9; /* follows div.body pre */ - border-color: #ac9; /* follows div.body pre */ - border-style: solid; /* follows div.body pre */ - border-width: 1px; /* follows div.body pre */ + color: #000; + background-color: #fff; + border: 1px solid #ac9; /* follows div.body pre */ +} + +.copybutton:hover { + background-color: #eee; } -.copybutton[data-hidden='true'] { - text-decoration: line-through; +.copybutton:active { + background-color: #ddd; } @media (max-width: 1023px) { diff --git a/python_docs_theme/static/pydoctheme_dark.css b/python_docs_theme/static/pydoctheme_dark.css index 4509960..582e4dd 100644 --- a/python_docs_theme/static/pydoctheme_dark.css +++ b/python_docs_theme/static/pydoctheme_dark.css @@ -176,3 +176,16 @@ img.invert-in-dark-mode { --versionchanged: var(--middle-color); --deprecated: var(--bad-color); } + +.copybutton { + color: #ac9; /* follows div.body pre */ + background-color: #222222; /* follows body */ +} + +.copybutton:hover { + background-color: #434343; +} + +.copybutton:active { + background-color: #656565; +} From f067c154509b0f6156e0076a566021eaff0bb26d Mon Sep 17 00:00:00 2001 From: Tomas Roun Date: Mon, 14 Apr 2025 18:58:16 +0200 Subject: [PATCH 2/3] Fix indentation --- python_docs_theme/static/copybutton.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/python_docs_theme/static/copybutton.js b/python_docs_theme/static/copybutton.js index 629d67f..102285f 100644 --- a/python_docs_theme/static/copybutton.js +++ b/python_docs_theme/static/copybutton.js @@ -34,14 +34,14 @@ const loadCopyButton = () => { let timeout = null // define the behavior of the button when it's clicked return event => { - clearTimeout(timeout) - const buttonEl = event.currentTarget - const codeEl = buttonEl.nextElementSibling - navigator.clipboard.writeText(getCopyableText(codeEl)) - buttonEl.innerText = _("Copied!") - timeout = setTimeout(() => { - buttonEl.innerText = _("Copy") - }, 1500) + clearTimeout(timeout) + const buttonEl = event.currentTarget + const codeEl = buttonEl.nextElementSibling + navigator.clipboard.writeText(getCopyableText(codeEl)) + buttonEl.innerText = _("Copied!") + timeout = setTimeout(() => { + buttonEl.innerText = _("Copy") + }, 1500) } } From c0c40f604830a2e6e94fbcb9a0aee7fdb5209b9c Mon Sep 17 00:00:00 2001 From: Tomas Roun Date: Mon, 14 Apr 2025 19:25:42 +0200 Subject: [PATCH 3/3] Add feature detection & check for errors --- python_docs_theme/static/copybutton.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/python_docs_theme/static/copybutton.js b/python_docs_theme/static/copybutton.js index 102285f..9df468e 100644 --- a/python_docs_theme/static/copybutton.js +++ b/python_docs_theme/static/copybutton.js @@ -33,11 +33,23 @@ const loadCopyButton = () => { const makeOnButtonClick = () => { let timeout = null // define the behavior of the button when it's clicked - return event => { + return async event => { + // check if the clipboard is available + if (!navigator.clipboard || !navigator.clipboard.writeText) { + return; + } + clearTimeout(timeout) const buttonEl = event.currentTarget const codeEl = buttonEl.nextElementSibling - navigator.clipboard.writeText(getCopyableText(codeEl)) + + try { + await navigator.clipboard.writeText(getCopyableText(codeEl)) + } catch (e) { + console.error(e.message) + return + } + buttonEl.innerText = _("Copied!") timeout = setTimeout(() => { buttonEl.innerText = _("Copy") pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy