Skip to content

Commit 3ab1beb

Browse files
committed
feat(postprocess): add copy button into code blocks
Adds a "Copy" button to all `<pre><code>` blocks in the generated HTML. Ensures improved UX for users reading code examples. Fixes: #6600 Private-ref: #6600 --- type: pre_commit_static_analysis_report description: Results of running static analysis checks when committing changes. report: - task: lint_filenames status: passed - task: lint_editorconfig status: passed - task: lint_markdown status: na - task: lint_package_json status: na - task: lint_repl_help status: na - task: lint_javascript_src status: passed - task: lint_javascript_cli status: na - task: lint_javascript_examples status: na - task: lint_javascript_tests status: na - task: lint_javascript_benchmarks status: na - task: lint_python status: na - task: lint_r status: na - task: lint_c_src status: na - task: lint_c_examples status: na - task: lint_c_benchmarks status: na - task: lint_c_tests_fixtures status: na - task: lint_shell status: na - task: lint_typescript_declarations status: na - task: lint_typescript_tests status: na - task: lint_license_headers status: passed ---
1 parent 630ddb7 commit 3ab1beb

File tree

1 file changed

+30
-0
lines changed

1 file changed

+30
-0
lines changed

lib/node_modules/@stdlib/_tools/markdown/to-html/lib/post_process.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ var replace = require( '@stdlib/string/replace' );
2828
var RE_VIEW_BOX = /(?:(view-box)(=".*?"))/g;
2929
var RE_CLIP_PATH_OPEN = /(?:<(clip-path)(.*?>))/g;
3030
var RE_CLIP_PATH_CLOSE = /<\/clip-path>/g;
31+
var RE_CODE_BLOCKS = /<pre>\s*<code class="([^"]*)">\s*([\s\S]*?)\s*<\/code>\s*<\/pre>/g;
3132

3233

3334
// MAIN //
@@ -53,9 +54,38 @@ function postProcess( html ) {
5354
html = replace( html, RE_VIEW_BOX, 'viewBox$2' );
5455
html = replace( html, RE_CLIP_PATH_OPEN, '<clipPath$2' );
5556
html = replace( html, RE_CLIP_PATH_CLOSE, '</clipPath>' );
57+
html = replace( html, RE_CODE_BLOCKS, injectCopyButton );
58+
html += getCopyButtonStyles();
5659
return html;
5760
}
5861

62+
/**
63+
* Injects a "Copy" button into a code block.
64+
*
65+
* This function wraps a `<pre><code>` block in a container div and inserts a
66+
* "Copy" button above it. The button uses the Clipboard API to copy the visible
67+
* text content (`innerText`) of the `<code>` element when clicked.
68+
*
69+
* @private
70+
* @param {string} match The entire matched HTML
71+
* @param {string} lang The language class
72+
* @param {string} code The raw HTML contents of the code block
73+
* @returns {string} copy button HTML
74+
*/
75+
function injectCopyButton( match, lang, code ) {
76+
return '<div class="code-container"><button class="copy-button" onclick="const codeContainer = this.closest(\'.code-container\');if ( codeContainer ) { const codeElement = codeContainer.querySelector( \'code\' ); if ( codeElement ) { try { navigator.clipboard.writeText( codeElement.innerText ); this.innerText = \'Copied\'; setTimeout(() => { this.innerText = \'Copy\' }, 2000); } catch (err) { console.error(err); } } }">Copy</button><pre><code class="' + lang + '">' + code + '</code></pre></div>';
77+
}
78+
79+
/**
80+
* Returns copy button styles.
81+
*
82+
* @private
83+
* @returns {string} copy button styles
84+
*/
85+
function getCopyButtonStyles() {
86+
return '<style>.code-container { position: relative; } .copy-button { position: absolute; top: 8px; right: 8px; background: #eee; border: none; padding: 5px 10px; cursor: pointer; font-size: 0.8em; border-radius: 4px; } .copy-button:hover { background: #ddd; }</style>';
87+
}
88+
5989

6090
// EXPORTS //
6191

0 commit comments

Comments
 (0)