Skip to content

Commit 2c93b01

Browse files
okuryuCopilot
andauthored
fix: use absolute positioning for copy button in code blocks (#684)
* fix: use absolute positioning for copy button in code blocks The copy button was using `float: right` which caused it to take up space in the document flow, resulting in an extra empty line appearing at the bottom of code blocks. Switch to `position: absolute` with `right: 1rem` and `bottom: 1rem` to overlay the button without affecting the code block layout. This also adds `position: relative` to `pre` elements to establish the positioning context. Refs: #635 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: add code block toolbar with language label and copy button Replace the floating copy button with a dedicated toolbar at the bottom of code blocks that includes a language label and copy button. This resolves the visual issue where the copy button caused an empty-line appearance at the bottom of code blocks. Changes: - Add createToolbarElement() to generate toolbar with language label - Update copy button click handler to use closest('pre') for robust DOM traversal - Add .code-toolbar flex layout and .code-language styling - Update print media query to hide toolbar instead of copy button Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 49b7173 commit 2c93b01

File tree

3 files changed

+36
-16
lines changed

3 files changed

+36
-16
lines changed

src/generators/legacy-html/assets/api.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ const setupFlavorToggles = () => {
180180
const setupCopyButton = () => {
181181
document.querySelectorAll('.copy-button').forEach(button => {
182182
button.addEventListener('click', e => {
183-
const parent = e.target.parentNode;
183+
const parent = e.target.closest('pre');
184184
const flavorToggle = parent.querySelector('.js-flavor-toggle');
185185

186186
let code;

src/generators/legacy-html/assets/style.css

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -540,7 +540,7 @@ tt,
540540
}
541541

542542
pre {
543-
padding: 1rem;
543+
padding: 1rem 1rem 0.5rem;
544544
vertical-align: top;
545545
border-radius: 4px;
546546
margin: 1rem;
@@ -1014,8 +1014,24 @@ kbd {
10141014
filter: invert(1);
10151015
}
10161016

1017+
.code-toolbar {
1018+
display: flex;
1019+
justify-content: space-between;
1020+
align-items: center;
1021+
margin-top: 0.5rem;
1022+
padding-top: 0.5rem;
1023+
border-top: 1px solid rgb(128 128 128 / 20%);
1024+
}
1025+
1026+
.code-language {
1027+
font-size: 0.75rem;
1028+
text-transform: uppercase;
1029+
letter-spacing: 1px;
1030+
font-weight: 700;
1031+
opacity: 0.6;
1032+
}
1033+
10171034
.copy-button {
1018-
float: right;
10191035
outline: none;
10201036
font-size: 10px;
10211037
color: #fff;
@@ -1028,7 +1044,6 @@ kbd {
10281044
text-transform: uppercase;
10291045
font-weight: 700;
10301046
padding: 0 0.5rem;
1031-
margin-right: 0.2rem;
10321047
height: 1.5rem;
10331048
transition-property:
10341049
background-color, border-color, color, box-shadow, filter;
@@ -1132,7 +1147,7 @@ kbd {
11321147
border-bottom: 1px solid var(--color-text-primary);
11331148
}
11341149

1135-
.js-flavor-toggle ~ :not(.copy-button) {
1150+
.js-flavor-toggle ~ :not(.code-toolbar) {
11361151
display: block !important;
11371152
background-position: top right;
11381153
background-size: 142px 20px;
@@ -1147,7 +1162,7 @@ kbd {
11471162
background-image: url('./js-flavor-esm.svg');
11481163
}
11491164

1150-
.copy-button {
1165+
.code-toolbar {
11511166
display: none;
11521167
}
11531168
}

src/utils/highlighter.mjs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,18 @@ import shikiConfig from '../../shiki.config.mjs';
1010
// to attribute the current language of the <pre> element
1111
const languagePrefix = 'language-';
1212

13-
// Creates a static button element which is used for the "copy" button
14-
// within codeboxes for copying the code to the clipboard
15-
const copyButtonElement = createElement(
16-
'button',
17-
{ class: 'copy-button' },
18-
'copy'
19-
);
13+
/**
14+
* Creates a toolbar element with a language label and copy button.
15+
*
16+
* @param {string} languageId - The language identifier for the code block.
17+
* @returns {import('hast').Element} The toolbar element.
18+
*/
19+
function createToolbarElement(languageId) {
20+
return createElement('div', { class: 'code-toolbar' }, [
21+
createElement('span', { class: 'code-language' }, languageId),
22+
createElement('button', { class: 'copy-button' }, 'copy'),
23+
]);
24+
}
2025

2126
/**
2227
* Checks if the given node is a valid code element.
@@ -103,8 +108,8 @@ export default function rehypeShikiji() {
103108
// Adds the original language back to the <pre> element
104109
children[0].properties.class = `${children[0].properties.class} ${codeLanguage}`;
105110

106-
// Adds the "copy" button to the <pre> element
107-
children[0].children.push(copyButtonElement);
111+
// Adds the toolbar (language label + copy button) to the <pre> element
112+
children[0].children.push(createToolbarElement(languageId));
108113

109114
// Replaces the <pre> element with the updated one
110115
parent.children.splice(index, 1, ...children);
@@ -165,7 +170,7 @@ export default function rehypeShikiji() {
165170
checked: codeElements[0].properties.language === 'cjs',
166171
}),
167172
...codeElements,
168-
copyButtonElement,
173+
createToolbarElement('javascript'),
169174
]
170175
);
171176

0 commit comments

Comments
 (0)