Skip to content

Commit b3a5dc0

Browse files
add link icon to section headers
1 parent 2867e2c commit b3a5dc0

File tree

3 files changed

+103
-0
lines changed

3 files changed

+103
-0
lines changed

assets/css/index.css

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,37 @@ section.prose {
3535
@apply text-lg font-medium pb-3 border-b border-b-redis-pen-700 border-opacity-50;
3636
}
3737

38+
/* Header link styles */
39+
.header-link {
40+
@apply text-slate-400 hover:text-slate-600 transition-all duration-200 no-underline cursor-pointer;
41+
text-decoration: none !important;
42+
}
43+
44+
.header-link:hover {
45+
@apply text-slate-600;
46+
text-decoration: none !important;
47+
}
48+
49+
.header-link svg {
50+
@apply w-4 h-4 inline-block;
51+
}
52+
53+
/* Ensure header links don't interfere with prose styling */
54+
.prose h1 .header-link,
55+
.prose h2 .header-link,
56+
.prose h3 .header-link,
57+
.prose h4 .header-link,
58+
.prose h5 .header-link,
59+
.prose h6 .header-link {
60+
@apply text-slate-400 hover:text-slate-600;
61+
text-decoration: none !important;
62+
}
63+
64+
/* Feedback state for copied links */
65+
.header-link.copied {
66+
@apply text-green-500;
67+
}
68+
3869
.prose p, .prose ol, .prose ul {
3970
@apply text-base;
4071
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{{- $anchor := .Anchor | safeURL -}}
2+
{{- $level := .Level -}}
3+
{{- $text := .Text | safeHTML -}}
4+
<h{{ $level }} id="{{ $anchor }}" class="group relative">
5+
{{ $text }}
6+
<a href="#{{ $anchor }}" class="header-link opacity-0 group-hover:opacity-100 transition-opacity duration-200 ml-2 align-middle" aria-label="Link to this section" title="Copy link to clipboard">
7+
<svg class="inline-block w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
8+
<path fill-rule="evenodd" d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z" clip-rule="evenodd"></path>
9+
</svg>
10+
</a>
11+
</h{{ $level }}>

static/js/index.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,70 @@ const mobileMenu = (() => {
9898
toggleMenu('products-mobile-menu', 'productsMobileMenuState')
9999
} else if (event.target.closest('[data-resources-mobile-menu-toggle]')) {
100100
toggleMenu('resources-mobile-menu', 'resourcesMobileMenuState')
101+
} else if (event.target.closest('.header-link')) {
102+
// Handle header link clicks
103+
event.preventDefault()
104+
copyHeaderLinkToClipboard(event.target.closest('.header-link'))
101105
}
102106
}
103107

108+
// Copy header link URL to clipboard
109+
function copyHeaderLinkToClipboard(linkElement) {
110+
const href = linkElement.getAttribute('href')
111+
const fullUrl = window.location.origin + window.location.pathname + href
112+
113+
if (navigator.clipboard && navigator.clipboard.writeText) {
114+
navigator.clipboard.writeText(fullUrl).then(() => {
115+
showCopyFeedback(linkElement)
116+
}).catch(err => {
117+
console.error('Failed to copy link: ', err)
118+
fallbackCopyToClipboard(fullUrl, linkElement)
119+
})
120+
} else {
121+
// Fallback for older browsers
122+
fallbackCopyToClipboard(fullUrl, linkElement)
123+
}
124+
}
125+
126+
// Show visual feedback when link is copied
127+
function showCopyFeedback(linkElement) {
128+
const originalTitle = linkElement.getAttribute('title')
129+
130+
linkElement.setAttribute('title', 'Copied!')
131+
linkElement.classList.add('copied')
132+
133+
setTimeout(() => {
134+
linkElement.setAttribute('title', originalTitle)
135+
linkElement.classList.remove('copied')
136+
}, 1500)
137+
}
138+
139+
// Fallback copy method for older browsers
140+
function fallbackCopyToClipboard(text, linkElement) {
141+
const textArea = document.createElement('textarea')
142+
textArea.value = text
143+
textArea.style.position = 'fixed'
144+
textArea.style.left = '-999999px'
145+
textArea.style.top = '-999999px'
146+
document.body.appendChild(textArea)
147+
textArea.focus()
148+
textArea.select()
149+
150+
try {
151+
const successful = document.execCommand('copy')
152+
if (successful) {
153+
showCopyFeedback(linkElement)
154+
console.log('Link copied to clipboard (fallback)')
155+
} else {
156+
console.error('Fallback copy failed')
157+
}
158+
} catch (err) {
159+
console.error('Fallback copy failed: ', err)
160+
}
161+
162+
document.body.removeChild(textArea)
163+
}
164+
104165
function allowFocus(selector, state) {
105166
const container = document.querySelector(selector)
106167
const focusable = container.querySelectorAll('button, [href], input, select, textarea')

0 commit comments

Comments
 (0)