copybutton.js 3.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. // ``function*`` denotes a generator in JavaScript, see
  2. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*
  3. function* getHideableCopyButtonElements(rootElement) {
  4. // yield all elements with the "go" (Generic.Output),
  5. // "gp" (Generic.Prompt), or "gt" (Generic.Traceback) CSS class
  6. for (const el of rootElement.querySelectorAll('.go, .gp, .gt')) {
  7. yield el
  8. }
  9. // tracebacks (.gt) contain bare text elements that need to be
  10. // wrapped in a span to hide or show the element
  11. for (let el of rootElement.querySelectorAll('.gt')) {
  12. while ((el = el.nextSibling) && el.nodeType !== Node.DOCUMENT_NODE) {
  13. // stop wrapping text nodes when we hit the next output or
  14. // prompt element
  15. if (el.nodeType === Node.ELEMENT_NODE && el.matches(".gp, .go")) {
  16. break
  17. }
  18. // if the node is a text node with content, wrap it in a
  19. // span element so that we can control visibility
  20. if (el.nodeType === Node.TEXT_NODE && el.textContent.trim()) {
  21. const wrapper = document.createElement("span")
  22. el.after(wrapper)
  23. wrapper.appendChild(el)
  24. el = wrapper
  25. }
  26. yield el
  27. }
  28. }
  29. }
  30. const loadCopyButton = () => {
  31. /* Add a [>>>] button in the top-right corner of code samples to hide
  32. * the >>> and ... prompts and the output and thus make the code
  33. * copyable. */
  34. const hide_text = "Hide the prompts and output"
  35. const show_text = "Show the prompts and output"
  36. const button = document.createElement("span")
  37. button.classList.add("copybutton")
  38. button.innerText = ">>>"
  39. button.title = hide_text
  40. button.dataset.hidden = "false"
  41. const buttonClick = event => {
  42. // define the behavior of the button when it's clicked
  43. event.preventDefault()
  44. const buttonEl = event.currentTarget
  45. const codeEl = buttonEl.nextElementSibling
  46. if (buttonEl.dataset.hidden === "false") {
  47. // hide the code output
  48. for (const el of getHideableCopyButtonElements(codeEl)) {
  49. el.hidden = true
  50. }
  51. buttonEl.title = show_text
  52. buttonEl.dataset.hidden = "true"
  53. } else {
  54. // show the code output
  55. for (const el of getHideableCopyButtonElements(codeEl)) {
  56. el.hidden = false
  57. }
  58. buttonEl.title = hide_text
  59. buttonEl.dataset.hidden = "false"
  60. }
  61. }
  62. const highlightedElements = document.querySelectorAll(
  63. ".highlight-python .highlight,"
  64. + ".highlight-python3 .highlight,"
  65. + ".highlight-pycon .highlight,"
  66. + ".highlight-pycon3 .highlight,"
  67. + ".highlight-default .highlight"
  68. )
  69. // create and add the button to all the code blocks that contain >>>
  70. highlightedElements.forEach(el => {
  71. el.style.position = "relative"
  72. // if we find a console prompt (.gp), prepend the (deeply cloned) button
  73. const clonedButton = button.cloneNode(true)
  74. // the onclick attribute is not cloned, set it on the new element
  75. clonedButton.onclick = buttonClick
  76. if (el.querySelector(".gp") !== null) {
  77. el.prepend(clonedButton)
  78. }
  79. })
  80. }
  81. if (document.readyState !== "loading") {
  82. loadCopyButton()
  83. } else {
  84. document.addEventListener("DOMContentLoaded", loadCopyButton)
  85. }