-
Notifications
You must be signed in to change notification settings - Fork 1
feat: add Copy for LLM sticky header button on blog posts #108
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,141 @@ | ||
| /** | ||
| * Copy for LLM - Sticky Header with Copy-to-Clipboard | ||
| * Shows a sticky bar when scrolling past the main header on blog posts. | ||
| * Copies the blog content as clean markdown to the clipboard. | ||
| */ | ||
| (function () { | ||
| "use strict"; | ||
|
|
||
| if (document.readyState === "loading") { | ||
| document.addEventListener("DOMContentLoaded", init); | ||
| } else { | ||
| init(); | ||
| } | ||
|
|
||
| function init() { | ||
| var stickyHeader = document.getElementById("blogStickyHeader"); | ||
| var copyBtn = document.getElementById("copyForLlm"); | ||
| var rawContent = document.getElementById("rawMarkdownContent"); | ||
|
|
||
| if (!stickyHeader || !copyBtn || !rawContent) return; | ||
|
|
||
| // --- Scroll-based visibility --- | ||
| var pageHeader = document.querySelector("header[aria-label='header']"); | ||
| if (!pageHeader) return; | ||
|
|
||
| function updateVisibility() { | ||
| var headerBottom = pageHeader.getBoundingClientRect().bottom; | ||
| if (headerBottom <= 0) { | ||
| stickyHeader.classList.add("visible"); | ||
| } else { | ||
| stickyHeader.classList.remove("visible"); | ||
| } | ||
| } | ||
|
|
||
| updateVisibility(); | ||
|
|
||
| var scrollTimeout; | ||
| window.addEventListener( | ||
| "scroll", | ||
| function () { | ||
| if (!scrollTimeout) { | ||
| scrollTimeout = setTimeout(function () { | ||
| updateVisibility(); | ||
| scrollTimeout = null; | ||
| }, 50); | ||
| } | ||
| }, | ||
| { passive: true }, | ||
| ); | ||
|
|
||
| // --- Copy logic --- | ||
| copyBtn.addEventListener("click", function () { | ||
| var content = rawContent.textContent; | ||
| content = cleanContent(content); | ||
|
|
||
| copyToClipboard(content).then(function () { | ||
| showCopiedFeedback(); | ||
| }); | ||
| }); | ||
|
|
||
| function cleanContent(text) { | ||
| // Fix relative or protocol-relative URL line — make it absolute | ||
| text = text.replace( | ||
| /^(URL: )(\/\/[^\s]*|\/[^\s]*)/m, | ||
| function (match, prefix, path) { | ||
| if (path.startsWith("//")) { | ||
| return prefix + window.location.protocol + path; | ||
| } | ||
| return prefix + window.location.origin + path; | ||
| }, | ||
| ); | ||
|
|
||
| // Convert lightboximg shortcodes to markdown images | ||
| // {{< lightboximg "/path/to/img.png" "Alt text" >}} | ||
| text = text.replace( | ||
| /\{\{<\s*lightboximg\s+"([^"]+)"\s+"([^"]+)"\s*>\}\}/g, | ||
| "", | ||
| ); | ||
|
|
||
| // Remove signup shortcodes | ||
| text = text.replace(/\{\{<\s*signup\s*>\}\}/g, ""); | ||
|
|
||
| // Remove other common shortcodes that don't translate to markdown | ||
| text = text.replace( | ||
| /\{\{<\s*(button|buttonout)\s+link="([^"]+)"\s+text="([^"]+)"\s*>\}\}/g, | ||
| "[$3]($2)", | ||
| ); | ||
|
|
||
| // Convert HTML line breaks to newlines | ||
| text = text.replace(/<br\s*\/?>/gi, "\n"); | ||
|
|
||
| // Clean up excessive blank lines (3+ newlines → 2) | ||
| text = text.replace(/\n{3,}/g, "\n\n"); | ||
|
|
||
| // Trim leading/trailing whitespace | ||
| text = text.trim(); | ||
|
|
||
| return text; | ||
| } | ||
|
|
||
| function copyToClipboard(text) { | ||
| if (navigator.clipboard && navigator.clipboard.writeText) { | ||
| return navigator.clipboard.writeText(text).catch(function () { | ||
| return fallbackCopy(text); | ||
| }); | ||
| } | ||
| return fallbackCopy(text); | ||
| } | ||
|
|
||
| function fallbackCopy(text) { | ||
| return new Promise(function (resolve, reject) { | ||
| var textarea = document.createElement("textarea"); | ||
| textarea.value = text; | ||
| textarea.style.position = "fixed"; | ||
| textarea.style.left = "-9999px"; | ||
| textarea.style.top = "-9999px"; | ||
| document.body.appendChild(textarea); | ||
| textarea.select(); | ||
| try { | ||
| document.execCommand("copy"); | ||
| resolve(); | ||
| } catch (err) { | ||
| reject(err); | ||
| } finally { | ||
| document.body.removeChild(textarea); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| function showCopiedFeedback() { | ||
| var originalHTML = copyBtn.innerHTML; | ||
| copyBtn.innerHTML = '<i class="fa fa-check"></i> Copied!'; | ||
| copyBtn.classList.add("copied"); | ||
|
|
||
| setTimeout(function () { | ||
| copyBtn.innerHTML = originalHTML; | ||
| copyBtn.classList.remove("copied"); | ||
| }, 2000); | ||
| } | ||
| } | ||
| })(); | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,18 @@ | ||||||||||||||||||||||||||||||||||||||
| {{ if .RawContent }} | ||||||||||||||||||||||||||||||||||||||
| <div id="blogStickyHeader"> | ||||||||||||||||||||||||||||||||||||||
| <span class="sticky-title">{{ .Title }}</span> | ||||||||||||||||||||||||||||||||||||||
| <button id="copyForLlm" class="button sml" aria-label="Copy page content as markdown for LLM"> | ||||||||||||||||||||||||||||||||||||||
| <i class="fa fa-copy"></i> Copy for LLM | ||||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||
| <script id="rawMarkdownContent" type="text/plain"> | ||||||||||||||||||||||||||||||||||||||
| # {{ .Title | safeHTML }} | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| URL: {{ .Permalink }} | ||||||||||||||||||||||||||||||||||||||
| {{ with .Params.author }}Author: {{ . }} | ||||||||||||||||||||||||||||||||||||||
| {{ end }}Published: {{ .Date.Format "January 2, 2006" }} | ||||||||||||||||||||||||||||||||||||||
| {{ with .Params.description }} | ||||||||||||||||||||||||||||||||||||||
| > {{ . | safeHTML }} | ||||||||||||||||||||||||||||||||||||||
| {{ end }} | ||||||||||||||||||||||||||||||||||||||
| {{ .RawContent | safeHTML }}</script> | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+9
to
+17
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove At Line 17 (and similarly Lines 9 and 15), rendering raw content with 🔧 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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
| {{ end }} | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
📝 Committable suggestion
🤖 Prompt for AI Agents