Skip to content

Commit 7e148f7

Browse files
Merge pull request #157 from phel-lang/add-copy-on-code-snippets
Add copy button on code snippets
2 parents 8024882 + da000e7 commit 7e148f7

File tree

7 files changed

+164
-91
lines changed

7 files changed

+164
-91
lines changed

css/components/code-block.css

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/* Code block with copy button */
2+
.code-block-wrapper {
3+
position: relative;
4+
}
5+
6+
.code-block-wrapper pre {
7+
margin: 0;
8+
}
9+
10+
/* Preserve default margins for wrapped pre elements not in homepage */
11+
.two-column-layout .code-block-wrapper,
12+
.blog-entry .code-block-wrapper,
13+
.one-column-layout .code-block-wrapper:not(.homepage-content .code-block-wrapper) {
14+
margin: var(--space-lg) 0;
15+
}
16+
17+
.copy-code-button {
18+
position: absolute;
19+
top: 0;
20+
right: 0.75rem;
21+
width: 36px;
22+
height: 36px;
23+
opacity: 0;
24+
transition: opacity 200ms ease-in-out, background-color 200ms ease-in-out, transform 200ms ease-in-out;
25+
cursor: pointer;
26+
background-color: rgba(255, 255, 255, 0.95);
27+
border: 1px solid #d1d5db;
28+
border-radius: var(--radius-md);
29+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
30+
z-index: 100;
31+
display: flex;
32+
align-items: center;
33+
justify-content: center;
34+
padding: 0;
35+
}
36+
37+
.copy-code-button svg {
38+
width: 20px;
39+
height: 20px;
40+
stroke: #475569;
41+
stroke-width: 2;
42+
}
43+
44+
.code-block-wrapper:hover .copy-code-button {
45+
opacity: 1;
46+
}
47+
48+
.copy-code-button:hover {
49+
background-color: #f3f4f6;
50+
transform: scale(1.05);
51+
}
52+
53+
.copy-code-button.copied {
54+
background-color: var(--color-phel-primary);
55+
border-color: var(--color-phel-primary);
56+
}
57+
58+
.copy-code-button.copied svg {
59+
stroke: white;
60+
}
61+

css/components/dark-mode.css

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -598,11 +598,6 @@
598598
color: var(--color-dark-text-accent);
599599
}
600600

601-
.dark .badge-success {
602-
background: var(--color-dark-current-badge-bg);
603-
color: var(--color-dark-current-badge-text);
604-
}
605-
606601
.dark .badge-warning {
607602
background: rgba(245, 158, 11, 0.2);
608603
color: #fbbf24;
@@ -781,6 +776,29 @@
781776
background: rgba(191, 164, 255, 0.3);
782777
}
783778

779+
/* Dark mode code block copy button */
780+
.dark .copy-code-button {
781+
background-color: rgba(42, 49, 60, 0.95);
782+
border-color: #475569;
783+
}
784+
785+
.dark .copy-code-button svg {
786+
stroke: #cbd5e1;
787+
}
788+
789+
.dark .copy-code-button:hover {
790+
background-color: #2f3741;
791+
}
792+
793+
.dark .copy-code-button.copied {
794+
background-color: var(--color-dark-text-accent);
795+
border-color: var(--color-dark-text-accent);
796+
}
797+
798+
.dark .copy-code-button.copied svg {
799+
stroke: white;
800+
}
801+
784802
/* Homepage Title Accent - Dark Mode */
785803
.dark .homepage-title-accent {
786804
color: var(--color-dark-text-accent);

css/components/documentation.css

Lines changed: 2 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -536,65 +536,6 @@ h6:hover .zola-anchor {
536536
opacity: 1;
537537
}
538538

539-
/* Info boxes / Callouts */
540-
@utility callout {
541-
padding: var(--space-lg) var(--space-xl);
542-
margin: var(--space-xl) 0;
543-
border-left: 4px solid;
544-
border-radius: var(--radius-lg);
545-
box-shadow: var(--shadow-sm);
546-
position: relative;
547-
}
548-
549-
.callout::before {
550-
content: '';
551-
position: absolute;
552-
top: var(--space-lg);
553-
left: var(--space-lg);
554-
font-size: var(--text-2xl);
555-
line-height: 1;
556-
}
557-
558-
.callout-info {
559-
background: rgba(59, 130, 246, 0.05);
560-
border-left-color: #3b82f6;
561-
}
562-
563-
.callout-info::before {
564-
content: 'ℹ️';
565-
}
566-
567-
.callout-warning {
568-
background: rgba(245, 158, 11, 0.05);
569-
border-left-color: #f59e0b;
570-
}
571-
572-
.callout-warning::before {
573-
content: '⚠️';
574-
}
575-
576-
.callout-success {
577-
background: rgba(16, 185, 129, 0.05);
578-
border-left-color: #10b981;
579-
}
580-
581-
.callout-success::before {
582-
content: '✅';
583-
}
584-
585-
.callout-error {
586-
background: rgba(239, 68, 68, 0.05);
587-
border-left-color: #ef4444;
588-
}
589-
590-
.callout-error::before {
591-
content: '❌';
592-
}
593-
594-
.callout p:first-of-type {
595-
margin-left: 2em;
596-
}
597-
598539
/* API documentation specific */
599540
.api-doc {
600541
margin: var(--space-2xl) 0;
@@ -635,12 +576,7 @@ h6:hover .zola-anchor {
635576
line-height: var(--leading-relaxed);
636577
}
637578

638-
/* Code examples with copy button */
639-
.code-block-wrapper {
640-
position: relative;
641-
margin: var(--space-xl) 0;
642-
}
643-
579+
/* Code examples with header */
644580
.code-block-header {
645581
display: flex;
646582
justify-content: space-between;
@@ -655,28 +591,10 @@ h6:hover .zola-anchor {
655591
color: var(--color-light-text-secondary);
656592
}
657593

658-
.code-block-wrapper pre {
659-
margin: 0;
594+
.code-block-header + pre {
660595
border-radius: 0 0 var(--radius-lg) var(--radius-lg);
661596
}
662597

663-
.copy-code-button {
664-
padding: var(--space-xs) var(--space-md);
665-
font-size: var(--text-xs);
666-
font-weight: 600;
667-
color: var(--color-light-link);
668-
background: transparent;
669-
border: 1px solid var(--color-light-link);
670-
border-radius: var(--radius-sm);
671-
cursor: pointer;
672-
transition: all var(--duration-fast) var(--ease-out);
673-
}
674-
675-
.copy-code-button:hover {
676-
background: var(--color-light-surface-hover);
677-
transform: translateY(-1px);
678-
}
679-
680598
.copy-code-button:active {
681599
transform: translateY(0);
682600
}

css/components/features.css

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,12 @@
7676
margin-right: -50vw;
7777
}
7878

79+
.homepage-code-section .code-block-wrapper {
80+
margin-top: var(--space-2xl);
81+
margin-bottom: var(--space-2xl);
82+
}
83+
84+
.homepage-code-section .code-block-wrapper pre,
7985
.homepage-code-section pre {
8086
margin-left: 0;
8187
margin-right: 0;
@@ -157,18 +163,25 @@
157163
padding: var(--space-xl) 0;
158164
}
159165

160-
.homepage-code-section > * {
166+
.homepage-code-section > *:not(.code-block-wrapper) {
161167
max-width: var(--container-content);
162168
margin-left: auto;
163169
margin-right: auto;
164170
padding-left: var(--space-xl);
165171
padding-right: var(--space-xl);
166172
}
167173

168-
.homepage-code-section pre {
174+
.homepage-code-section .code-block-wrapper {
169175
max-width: var(--container-content);
170176
margin-left: auto;
171177
margin-right: auto;
178+
padding-left: 0;
179+
padding-right: 0;
180+
}
181+
182+
.homepage-code-section .code-block-wrapper pre {
183+
margin-left: auto;
184+
margin-right: auto;
172185
}
173186
}
174187

scripts/concat-tailwind.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const parts = [
1616
'css/components/documentation.css',
1717
'css/components/blog.css',
1818
'css/components/features.css',
19+
'css/components/code-block.css',
1920
'css/components/dark-mode.css',
2021
];
2122

static/copy-code.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
document.addEventListener('DOMContentLoaded', function() {
2+
// Add copy functionality to all pre blocks
3+
const preBlocks = document.querySelectorAll('pre');
4+
5+
preBlocks.forEach(pre => {
6+
// Wrap pre in a container if not already wrapped
7+
if (!pre.parentElement.classList.contains('code-block-wrapper')) {
8+
const wrapper = document.createElement('div');
9+
wrapper.className = 'code-block-wrapper';
10+
pre.parentNode.insertBefore(wrapper, pre);
11+
wrapper.appendChild(pre);
12+
}
13+
14+
// Create copy button element
15+
const copyButton = document.createElement('button');
16+
copyButton.className = 'copy-code-button';
17+
copyButton.setAttribute('aria-label', 'Copy code to clipboard');
18+
copyButton.innerHTML = `
19+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
20+
<rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect>
21+
<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path>
22+
</svg>
23+
`;
24+
25+
// Add button to wrapper
26+
pre.parentElement.appendChild(copyButton);
27+
28+
// Handle copy
29+
copyButton.addEventListener('click', async () => {
30+
const code = pre.querySelector('code');
31+
const text = code ? code.textContent : pre.textContent;
32+
33+
try {
34+
await navigator.clipboard.writeText(text);
35+
36+
// Add copied state
37+
copyButton.classList.add('copied');
38+
39+
// Change icon to checkmark
40+
copyButton.innerHTML = `
41+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
42+
<polyline points="20 6 9 17 4 12"></polyline>
43+
</svg>
44+
`;
45+
46+
// Reset after 2 seconds
47+
setTimeout(() => {
48+
copyButton.classList.remove('copied');
49+
copyButton.innerHTML = `
50+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
51+
<rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect>
52+
<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path>
53+
</svg>
54+
`;
55+
}, 2000);
56+
} catch (err) {
57+
console.error('Failed to copy code:', err);
58+
}
59+
});
60+
});
61+
});

templates/base.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
<script type="text/javascript" src="{{ get_url(path='mobile-menu.js') }}"></script>
4646
<script type="text/javascript" src="{{ get_url(path='dark-mode.js') }}"></script>
4747
<script type="text/javascript" src="{{ get_url(path='sidebar-toggle.js') }}"></script>
48+
<script type="text/javascript" src="{{ get_url(path='copy-code.js') }}" defer></script>
4849
<script type="text/javascript" src="{{ get_url(path='elasticlunr.min.js') }}" defer></script>
4950
<script type="text/javascript" src="{{ get_url(path='api_search.js') }}" defer></script>
5051
<script type="text/javascript" src="{{ get_url(path='search.js') }}" defer></script>

0 commit comments

Comments
 (0)