From f6b63644906f26f056fd0cee6d91df8f26775fca Mon Sep 17 00:00:00 2001 From: Kevin Putnam Date: Tue, 2 Jun 2020 11:21:34 -0700 Subject: [PATCH] Implements copy button (#1180) * Adds support for sphinx_copybutton. Closes #1134 Signed-off-by: Michael Vincerra * Additional config. Signed-off-by: Michael Vincerra * Remove cruft.;) Signed-off-by: Michael Vincerra * **DO NOT MERGE** Please verify changes with tutorials/proxy.rst. Once verified remove changes to proxy.rst. Signed-off-by: Kevin Putnam * Provides example usage of new codeblock copy behavior: 1. ShellSession (line 228 in kernel-modules-dkms.rst) - will copy everything except prompt. 2. Console (line 74 in kernel-modules-dkms.rst) - will copy only the input line without the prompt. Signed-off-by: Kevin Putnam Co-authored-by: Michael Vincerra --- make.bat | 1 + requirements.txt | 1 + source/Makefile | 1 + source/_scripts/js/copybutton.js | 147 ++++++++++++++++++ .../otc_tcs_sphinx_theme/static/tcs_theme.css | 9 ++ source/conf.py | 20 ++- source/guides/kernel/kernel-modules-dkms.rst | 6 +- 7 files changed, 180 insertions(+), 5 deletions(-) create mode 100644 source/_scripts/js/copybutton.js diff --git a/make.bat b/make.bat index d67dad35..070470e4 100644 --- a/make.bat +++ b/make.bat @@ -80,6 +80,7 @@ if "%1" == "html" ( if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. + copy source\_scripts\js\copybutton.js %BUILDDIR%\html\_static goto end ) diff --git a/requirements.txt b/requirements.txt index df0eec31..8270722f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,4 @@ sphinx-sitemap==1.0.2 Jinja2==2.10.1 GitPython==3.0.8 sphinx-tabs +sphinx-copybutton diff --git a/source/Makefile b/source/Makefile index 480400e2..753ee703 100644 --- a/source/Makefile +++ b/source/Makefile @@ -73,6 +73,7 @@ htmlzh: html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + cp _scripts/js/copybutton.js $(BUILDDIR)/html/_static @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." diff --git a/source/_scripts/js/copybutton.js b/source/_scripts/js/copybutton.js new file mode 100644 index 00000000..3200366d --- /dev/null +++ b/source/_scripts/js/copybutton.js @@ -0,0 +1,147 @@ +// introduces special behavior for ShellSession + +// Localization support +const messages = { + 'en': { + 'copy': 'Copy', + 'copy_to_clipboard': 'Copy to clipboard', + 'copy_success': 'Copied!', + 'copy_failure': 'Failed to copy', + }, + 'es' : { + 'copy': 'Copiar', + 'copy_to_clipboard': 'Copiar al portapapeles', + 'copy_success': '¡Copiado!', + 'copy_failure': 'Error al copiar', + }, + 'de' : { + 'copy': 'Kopieren', + 'copy_to_clipboard': 'In die Zwischenablage kopieren', + 'copy_success': 'Kopiert!', + 'copy_failure': 'Fehler beim Kopieren', + } +} + +let locale = 'en' +if( document.documentElement.lang !== undefined + && messages[document.documentElement.lang] !== undefined ) { + locale = document.documentElement.lang +} + +/** + * Set up copy/paste for code blocks + */ + +const runWhenDOMLoaded = cb => { + if (document.readyState != 'loading') { + cb() + } else if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', cb) + } else { + document.attachEvent('onreadystatechange', function() { + if (document.readyState == 'complete') cb() + }) + } +} + +const codeCellId = index => `codecell${index}` + +// Clears selected text since ClipboardJS will select the text when copying +const clearSelection = () => { + if (window.getSelection) { + window.getSelection().removeAllRanges() + } else if (document.selection) { + document.selection.empty() + } +} + +// Changes tooltip text for two seconds, then changes it back +const temporarilyChangeTooltip = (el, newText) => { + const oldText = el.getAttribute('data-tooltip') + el.setAttribute('data-tooltip', newText) + setTimeout(() => el.setAttribute('data-tooltip', oldText), 2000) +} + +// Callback when a copy button is clicked. Will be passed the node that was clicked +// should then grab the text and replace pieces of text that shouldn't be used in output +var copyTargetText = (trigger) => { + var target = document.querySelector(trigger.attributes['data-clipboard-target'].value); + var textContent = target.innerText.split('\n'); + var copybuttonPromptText = '$ '; // Inserted from config + var onlyCopyPromptLines = true; // Inserted from config + var removePrompts = true; // Inserted from config + + grandParent = target.parentElement.parentElement; + blockType = grandParent.classList; + if (blockType[0].includes("ShellSession")) { + onlyCopyPromptLines = false; + } + + // Text content line filtering based on prompts (if a prompt text is given) + if (copybuttonPromptText.length > 0) { + // If only copying prompt lines, remove all lines that don't start w/ prompt + if (onlyCopyPromptLines) { + linesWithPrompt = textContent.filter((line) => { + return line.startsWith(copybuttonPromptText) || (line.length == 0); // Keep newlines + }); + // Check to make sure we have at least one non-empty line + var nonEmptyLines = linesWithPrompt.filter((line) => {return line.length > 0}); + // If we detected lines w/ prompt, then overwrite textContent w/ those lines + if ((linesWithPrompt.length > 0) && (nonEmptyLines.length > 0)) { + textContent = linesWithPrompt; + } + } + // Remove the starting prompt from any remaining lines + if (removePrompts) { + textContent.forEach((line, index) => { + if (line.startsWith(copybuttonPromptText)) { + textContent[index] = line.slice(copybuttonPromptText.length); + } + }); + } + } + textContent = textContent.join('\n'); + // Remove a trailing newline to avoid auto-running when pasting + if (textContent.endsWith("\n")) { + textContent = textContent.slice(0, -1) + } + return textContent +} + +const addCopyButtonToCodeCells = () => { + // If ClipboardJS hasn't loaded, wait a bit and try again. This + // happens because we load ClipboardJS asynchronously. + if (window.ClipboardJS === undefined) { + setTimeout(addCopyButtonToCodeCells, 250) + return + } + + // Add copybuttons to all of our code cells + const codeCells = document.querySelectorAll('div.highlight pre') + codeCells.forEach((codeCell, index) => { + const id = codeCellId(index) + codeCell.setAttribute('id', id) + const pre_bg = getComputedStyle(codeCell).backgroundColor; + + const clipboardButton = id => + ` + ${messages[locale]['copy_to_clipboard']} + ` + codeCell.insertAdjacentHTML('afterend', clipboardButton(id)) + }) + + // Initialize with a callback so we can modify the text before copy + const clipboard = new ClipboardJS('.copybtn', {text: copyTargetText}) + + // Update UI with error/success messages + clipboard.on('success', event => { + clearSelection() + temporarilyChangeTooltip(event.trigger, messages[locale]['copy_success']) + }) + + clipboard.on('error', event => { + temporarilyChangeTooltip(event.trigger, messages[locale]['copy_failure']) + }) +} + +runWhenDOMLoaded(addCopyButtonToCodeCells) \ No newline at end of file diff --git a/source/_themes/otc_tcs_sphinx_theme/static/tcs_theme.css b/source/_themes/otc_tcs_sphinx_theme/static/tcs_theme.css index f6161a3d..28e4bc02 100644 --- a/source/_themes/otc_tcs_sphinx_theme/static/tcs_theme.css +++ b/source/_themes/otc_tcs_sphinx_theme/static/tcs_theme.css @@ -368,6 +368,15 @@ div.highlight-python .highlight:before{ white-space: pre; } +div.highlight-ShellSession .highlight:before{ + background: #909090; + color: white; + content: " Shell "; + font-family: SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",Courier,monospace; + font-size: 14px; + white-space: pre; +} + div.highlight-console .highlight:before{ background: #909090; color: white; diff --git a/source/conf.py b/source/conf.py index a8dff6e5..44a1e31c 100644 --- a/source/conf.py +++ b/source/conf.py @@ -15,6 +15,9 @@ import sys import os import shlex +#support for modified code block +from pygments.lexers.shell import BashSessionLexer +from sphinx.highlighting import lexers # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -26,6 +29,17 @@ import shlex # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' +############# +# +# Add a special lexer to add a class to console lexer +# +############# + +class copyAllConsole (BashSessionLexer): + name = 'ShellSession' + +lexers['ShellSession'] = copyAllConsole(startinLine=True) + # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. @@ -34,7 +48,8 @@ import shlex #] extensions = [ - 'sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx_sitemap', 'sphinx_tabs.tabs' + 'sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx_sitemap', + 'sphinx_tabs.tabs', 'sphinx_copybutton' ] # Add any paths that contain templates here, relative to this directory. @@ -174,7 +189,8 @@ html_favicon = '_images/favicon.ico' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -#html_static_path = ['_static'] +# html_static_path = ['_scripts'] +copybutton_prompt_text = "$ " # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied diff --git a/source/guides/kernel/kernel-modules-dkms.rst b/source/guides/kernel/kernel-modules-dkms.rst index cf767011..20b686ee 100644 --- a/source/guides/kernel/kernel-modules-dkms.rst +++ b/source/guides/kernel/kernel-modules-dkms.rst @@ -71,7 +71,7 @@ bundle: and *lts* kernels are enabled to build and load out-of-tree kernel modules with DKMS. - .. code-block:: bash + .. code-block:: console $ uname -r 5.XX.YY-ZZZZ.native @@ -225,9 +225,9 @@ The instructions below show a generic example: #. Create or modify the :file:`dkms.conf` file inside of the extracted source code directory. - .. code-block:: bash + .. code-block:: ShellSession - $EDITOR dkms.conf + $ EDITOR dkms.conf MAKE="make -C src/ KERNELDIR=/lib/modules/${kernelver}/build" CLEAN="make -C src/ clean"