Skip to content

Commit 92039de

Browse files
Anushreebasicsparloughconooi
authored
fix: make heading anchors copy full URL to clipboard fixes #6756 (#7099)
### Summary This PR fixes issue #6756 where clicking the '#' anchor next to headings did not copy the section link to the clipboard. ### Changes - Updated `HeaderWrapperExtension` to add an inline `onclick` handler for the heading anchor `<a>` elements. - Clicking '#' now: - Copies the full URL including fragment to the clipboard - Updates the URL in the address bar - Prevents default page jump behavior - Temporarily replaces '#' with a smaller 'Copied!' text for 1.2 seconds to provide visual feedback ### Testing - Verified that clicking '#' on h2–h5 headings copies the correct URL - Confirmed that the page URL updates without scrolling - Works across multiple headings on the same page ### Notes - This approach adds behavior directly in the anchor element. - Alternative approach would be a global client-side handler, but this is safe and consistent with current Jaspr HTML generation. Fixes #6756 reference video: https://github.com/user-attachments/assets/06411e8d-07c2-4057-a20e-ace125897a16 --------- Co-authored-by: Parker Lougheed <parlough@gmail.com> Co-authored-by: Connie Ooi <connieooi@google.com>
1 parent ef861b8 commit 92039de

File tree

1 file changed

+52
-0
lines changed

1 file changed

+52
-0
lines changed

site/lib/src/client/global_scripts.dart

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import 'dart:convert';
66

77
import 'package:http/http.dart' as http;
8+
import 'package:meta/meta.dart';
89
import 'package:universal_web/js_interop.dart';
910
import 'package:universal_web/web.dart' as web;
1011

@@ -13,6 +14,7 @@ import 'package:universal_web/web.dart' as web;
1314
/// These are temporary until they can be integrated with their
1415
/// relevant Jaspr components.
1516
void setUpSite() {
17+
_setUpHeadingLinkCopy();
1618
_setUpSidenav();
1719
_setUpSearchKeybindings();
1820
_setUpTabs();
@@ -24,6 +26,56 @@ void setUpSite() {
2426
_setUpSteppers();
2527
}
2628

29+
void _setUpHeadingLinkCopy() {
30+
final anchors = web.document.querySelectorAll(
31+
'a.heading-link[href]',
32+
);
33+
34+
for (var i = 0; i < anchors.length; i++) {
35+
final anchor = anchors.item(i) as web.HTMLAnchorElement;
36+
37+
// Store the original text before any click handlers modify it.
38+
final originalText = anchor.textContent;
39+
40+
@awaitNotRequired
41+
Future<void> copyAnchorToClipboard(String url) async {
42+
try {
43+
await web.window.navigator.clipboard.writeText(url).toDart;
44+
// TODO: Replace this with a tooltip of some sort.
45+
anchor.textContent = 'Link copied!';
46+
47+
Future<void>.delayed(const Duration(milliseconds: 1200), () {
48+
anchor.textContent = originalText;
49+
});
50+
} catch (_) {
51+
anchor.textContent = 'Failed to copy';
52+
}
53+
}
54+
55+
anchor.addEventListener(
56+
'click',
57+
((web.Event event) {
58+
event.preventDefault();
59+
60+
final fragment = anchor.hash;
61+
if (!fragment.startsWith('#')) return;
62+
63+
final headingId = fragment.substring(1);
64+
if (headingId.isEmpty) return;
65+
66+
final url = Uri.parse(
67+
web.window.location.href,
68+
).replace(fragment: headingId).toString();
69+
70+
// Update the URL hash without triggering a scroll jump.
71+
web.window.history.replaceState(null, '', '#$headingId');
72+
73+
copyAnchorToClipboard(url);
74+
}).toJS,
75+
);
76+
}
77+
}
78+
2779
void _setUpSidenav() {
2880
final sidenav = web.document.getElementById('sidenav');
2981
if (sidenav == null) return;

0 commit comments

Comments
 (0)