feat: add Copy for LLM sticky header button on blog posts#108
feat: add Copy for LLM sticky header button on blog posts#108
Conversation
Adds a sticky header bar that slides in from the top when users scroll past the main header on blog posts. The bar displays the post title and a "Copy for LLM" button that copies the full blog content as clean markdown to the clipboard, optimized for pasting into AI assistants. - New partial: blog-sticky-header.html with embedded raw markdown via Hugo's .RawContent in a hidden script tag - New JS: copy-for-llm.js with scroll detection, clipboard copy, shortcode cleaning (lightboximg → markdown images, signup → removed), and "Copied!" feedback animation - SCSS: sticky bar styled with $pine background, CSS transform slide transition, responsive (title hidden on mobile) - Blog-only: does not appear on homepage, services, or case study pages Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
✅ Deploy Preview for masterpoint ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
WalkthroughAdds a sticky header feature to blog posts that displays the post title and a "Copy for LLM" button. When the page header scrolls out of view, the sticky header becomes visible at the top. The copy function cleans and transforms the post content before sending it to the clipboard. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Browser
participant DOM
participant Clipboard
User->>Browser: Scroll page
Browser->>DOM: Fire scroll event (throttled 50ms)
DOM->>DOM: Get header bounding rect
alt Header scrolled out of view
DOM->>DOM: Add 'visible' class to sticky header
DOM->>User: Display sticky header
else Header still visible
DOM->>DOM: Remove 'visible' class
DOM->>User: Hide sticky header
end
User->>DOM: Click "Copy for LLM" button
DOM->>DOM: Extract rawMarkdownContent
DOM->>DOM: Clean content<br/>(normalize URLs,<br/>convert shortcodes,<br/>normalize whitespace)
DOM->>Clipboard: Write cleaned text
Clipboard->>User: Show "Copied!" feedback
Note over User,DOM: Feedback removed after 2 seconds
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
assets/css/custom.scss (1)
3569-3571: Improve copied-state contrast for button readability.
#2ecc71with inherited white text is low-contrast for the small button label. Set an explicit darker text color (or darker green background) for accessible contrast.🎨 Suggested tweak
&.copied { background: `#2ecc71`; + color: `#0b2b2c`; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@assets/css/custom.scss` around lines 3569 - 3571, The .copied state (&.copied in assets/css/custom.scss) uses background `#2ecc71` which yields low contrast with white text; update the rule to ensure accessible contrast by either setting an explicit darker text color (e.g., color: a dark green/near-black) or using a darker background (e.g., darken(`#2ecc71`, 15-25%)) so the resulting contrast ratio meets WCAG (>=4.5:1) for small button labels; apply the change to the &.copied selector and prefer using your existing color variables or a darken()/mix() helper so it stays consistent with the theme.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@assets/js/copy-for-llm.js`:
- Around line 56-58: The copyToClipboard(content).then(...) chain currently only
handles success and can produce unhandled promise rejections; add a .catch(...)
on the promise returned by copyToClipboard to handle failures, log the error
(e.g., console.error or existing logger) and call a user-facing failure handler
(create or reuse a function like showCopyFailedFeedback or
showCopyErrorFeedback) so users get visible feedback instead of silent failures;
update the call site where copyToClipboard and showCopiedFeedback are used to
include this .catch and ensure the error handler is implemented.
In `@layouts/partials/blog-sticky-header.html`:
- Around line 9-17: The template is using the safeHTML transformer inside a
<script> payload which can allow content containing </script> to break out and
cause script-breakout injection; remove the use of safeHTML for the fields
rendered inside the script block (e.g. .RawContent, .Title, .Permalink,
.Params.description, .Params.author) and instead render them with the default
auto-escaping or a JSON-encoding helper (e.g. jsonify) or an HTML-escaping
helper appropriate for Hugo templates so the content is safely escaped and
cannot prematurely close the <script> tag.
---
Nitpick comments:
In `@assets/css/custom.scss`:
- Around line 3569-3571: The .copied state (&.copied in assets/css/custom.scss)
uses background `#2ecc71` which yields low contrast with white text; update the
rule to ensure accessible contrast by either setting an explicit darker text
color (e.g., color: a dark green/near-black) or using a darker background (e.g.,
darken(`#2ecc71`, 15-25%)) so the resulting contrast ratio meets WCAG (>=4.5:1)
for small button labels; apply the change to the &.copied selector and prefer
using your existing color variables or a darken()/mix() helper so it stays
consistent with the theme.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 77971a7b-0170-4d99-9d00-7a2aca9555fd
📒 Files selected for processing (5)
assets/css/custom.scssassets/js/copy-for-llm.jslayouts/_default/single.htmllayouts/partials/blog-sticky-header.htmllayouts/partials/scripts.html
| copyToClipboard(content).then(function () { | ||
| showCopiedFeedback(); | ||
| }); |
There was a problem hiding this comment.
Handle clipboard copy failures to avoid silent/unhandled errors.
Lines 56-58 only handle success. Add a .catch(...) to prevent unhandled promise rejection and give the user failure feedback.
🛠️ Suggested fix
- copyToClipboard(content).then(function () {
- showCopiedFeedback();
- });
+ copyToClipboard(content)
+ .then(function () {
+ showCopiedFeedback();
+ })
+ .catch(function () {
+ var originalHTML = copyBtn.innerHTML;
+ copyBtn.innerHTML = '<i class="fa fa-times"></i> Copy failed';
+ setTimeout(function () {
+ copyBtn.innerHTML = originalHTML;
+ }, 2000);
+ });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| copyToClipboard(content).then(function () { | |
| showCopiedFeedback(); | |
| }); | |
| copyToClipboard(content) | |
| .then(function () { | |
| showCopiedFeedback(); | |
| }) | |
| .catch(function () { | |
| var originalHTML = copyBtn.innerHTML; | |
| copyBtn.innerHTML = '<i class="fa fa-times"></i> Copy failed'; | |
| setTimeout(function () { | |
| copyBtn.innerHTML = originalHTML; | |
| }, 2000); | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@assets/js/copy-for-llm.js` around lines 56 - 58, The
copyToClipboard(content).then(...) chain currently only handles success and can
produce unhandled promise rejections; add a .catch(...) on the promise returned
by copyToClipboard to handle failures, log the error (e.g., console.error or
existing logger) and call a user-facing failure handler (create or reuse a
function like showCopyFailedFeedback or showCopyErrorFeedback) so users get
visible feedback instead of silent failures; update the call site where
copyToClipboard and showCopiedFeedback are used to include this .catch and
ensure the error handler is implemented.
| # {{ .Title | safeHTML }} | ||
|
|
||
| URL: {{ .Permalink }} | ||
| {{ with .Params.author }}Author: {{ . }} | ||
| {{ end }}Published: {{ .Date.Format "January 2, 2006" }} | ||
| {{ with .Params.description }} | ||
| > {{ . | safeHTML }} | ||
| {{ end }} | ||
| {{ .RawContent | safeHTML }}</script> |
There was a problem hiding this comment.
Remove safeHTML inside the embedded script payload to avoid script-breakout injection.
At Line 17 (and similarly Lines 9 and 15), rendering raw content with safeHTML inside a <script> block can prematurely close the script if content contains </script>, breaking the page and creating an injection vector.
🔧 Suggested fix
<script id="rawMarkdownContent" type="text/plain">
-# {{ .Title | safeHTML }}
+# {{ .Title }}
URL: {{ .Permalink }}
{{ with .Params.author }}Author: {{ . }}
{{ end }}Published: {{ .Date.Format "January 2, 2006" }}
{{ with .Params.description }}
-> {{ . | safeHTML }}
+> {{ . }}
{{ end }}
-{{ .RawContent | safeHTML }}</script>
+{{ .RawContent }}</script>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # {{ .Title | safeHTML }} | |
| URL: {{ .Permalink }} | |
| {{ with .Params.author }}Author: {{ . }} | |
| {{ end }}Published: {{ .Date.Format "January 2, 2006" }} | |
| {{ with .Params.description }} | |
| > {{ . | safeHTML }} | |
| {{ end }} | |
| {{ .RawContent | safeHTML }}</script> | |
| # {{ .Title }} | |
| URL: {{ .Permalink }} | |
| {{ with .Params.author }}Author: {{ . }} | |
| {{ end }}Published: {{ .Date.Format "January 2, 2006" }} | |
| {{ with .Params.description }} | |
| > {{ . }} | |
| {{ end }} | |
| {{ .RawContent }}</script> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@layouts/partials/blog-sticky-header.html` around lines 9 - 17, The template
is using the safeHTML transformer inside a <script> payload which can allow
content containing </script> to break out and cause script-breakout injection;
remove the use of safeHTML for the fields rendered inside the script block (e.g.
.RawContent, .Title, .Permalink, .Params.description, .Params.author) and
instead render them with the default auto-escaping or a JSON-encoding helper
(e.g. jsonify) or an HTML-escaping helper appropriate for Hugo templates so the
content is safely escaped and cannot prematurely close the <script> tag.


what
why
how it works
blog-sticky-header.html): Renders the sticky bar HTML and embeds the raw markdown content at build time using Hugo's.RawContentin a hidden<script type="text/plain">tagcopy-for-llm.js): Handles scroll-based visibility (shows bar when header scrolls out of view), clipboard copy vianavigator.clipboard.writeText()withexecCommandfallback, and cleans Hugo shortcodes (lightboximg→ markdown images,signup→ removed,button/buttonout→ markdown links)$pinebackground matching the blog header, CSS transform slide-down animation, responsive (title hidden on mobile, button goes full-width)references
Summary by CodeRabbit
Release Notes