From c8fceff537b66240825cef3c23b647d9c34011c0 Mon Sep 17 00:00:00 2001 From: Evan Wondrasek Date: Mon, 28 Nov 2022 21:53:44 -0800 Subject: [PATCH 1/6] [Solo] Add anchor link to content headings, visible on hover --- assets/css/screen.css | 16 ++++++++++++++++ assets/js/main.js | 15 +++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/assets/css/screen.css b/assets/css/screen.css index e9400eb..03f82b6 100644 --- a/assets/css/screen.css +++ b/assets/css/screen.css @@ -767,6 +767,22 @@ hr { } } +.anchor-link { + opacity: 0; + text-decoration: none; + margin-left: 0em; +} + +h1:hover .anchor-link, +h2:hover .anchor-link, +h3:hover .anchor-link, +h4:hover .anchor-link, +h5:hover .anchor-link, +h6:hover .anchor-link { + opacity: 1; + text-decoration: none; +} + /* Custom CTA /* ---------------------------------------------------------- */ diff --git a/assets/js/main.js b/assets/js/main.js index 7a5235d..7daeba0 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -52,3 +52,18 @@ function initParallax() { (function () { pagination(true, initParallax); })(); + +(function (window, document) { + var addAnchors = () => { + var headings = document.querySelectorAll('.gh-content h1, .gh-content h2, .gh-content h3, gh-.content h4, .gh-content h5, .gh-content h6') + headings.forEach((heading) => { + heading.insertAdjacentHTML('beforeend', ` + + + + `) + }) + } + + document.addEventListener('DOMContentLoaded', addAnchors) +})(window, document); From d5f7c2071d687dde8e3db482c06b012c33aacfe7 Mon Sep 17 00:00:00 2001 From: Evan Wondrasek Date: Sun, 9 Feb 2025 19:05:59 -0800 Subject: [PATCH 2/6] Add .gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba6e2d1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +robots.txt +node_modules/ +yarn-error.log +yarn.lock From bb0e6cd2ca81d7869ded2534eb87b5a090b7a342 Mon Sep 17 00:00:00 2001 From: Evan Wondrasek Date: Wed, 26 Feb 2025 10:43:33 -0800 Subject: [PATCH 3/6] Clicking heading link icon now copies URL to clipboard --- assets/css/screen.css | 52 +++++++++++++++++++++++++------- assets/js/main.js | 70 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 109 insertions(+), 13 deletions(-) diff --git a/assets/css/screen.css b/assets/css/screen.css index bfb0abb..18abd9e 100644 --- a/assets/css/screen.css +++ b/assets/css/screen.css @@ -794,18 +794,50 @@ hr { .anchor-link { opacity: 0; - text-decoration: none; - margin-left: 0em; + padding: 8px; + margin-left: 4px; + color: var(--ghost-accent-color); + border: none; + background: none; + cursor: pointer; + transition: opacity 0.3s ease; + min-width: 44px; + min-height: 44px; + display: inline-flex; + align-items: center; + justify-content: center; } -h1:hover .anchor-link, -h2:hover .anchor-link, -h3:hover .anchor-link, -h4:hover .anchor-link, -h5:hover .anchor-link, -h6:hover .anchor-link { - opacity: 1; - text-decoration: none; +@media (hover: hover) { + .anchor-link { + opacity: 0; + } + + h1:hover .anchor-link, + h2:hover .anchor-link, + h3:hover .anchor-link, + h4:hover .anchor-link, + h5:hover .anchor-link, + h6:hover .anchor-link, + .anchor-link:focus { + opacity: 1; + } +} + +@media (hover: none) { + .anchor-link { + opacity: 0.8; + } +} + +.anchor-link.copied { + animation: pulse 0.4s ease; +} + +@keyframes pulse { + 0% { transform: scale(1); } + 50% { transform: scale(1.2); } + 100% { transform: scale(1); } } /* Custom CTA diff --git a/assets/js/main.js b/assets/js/main.js index 7daeba0..d98df05 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -58,11 +58,75 @@ function initParallax() { var headings = document.querySelectorAll('.gh-content h1, .gh-content h2, .gh-content h3, gh-.content h4, .gh-content h5, .gh-content h6') headings.forEach((heading) => { heading.insertAdjacentHTML('beforeend', ` - + `) - }) + + const anchor = heading.querySelector('.anchor-link'); + anchor.addEventListener('click', (e) => { + e.preventDefault(); + const url = new URL(window.location.href); + url.hash = heading.id; + + // Try modern clipboard API first + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(url.toString()) + .then(() => { + showCopiedFeedback(anchor); + }) + .catch(() => { + // Fallback for clipboard API failure + fallbackCopy(url.toString(), anchor); + }); + } else { + // Fallback for browsers without clipboard API + fallbackCopy(url.toString(), anchor); + } + }); + }); + } + + function showCopiedFeedback(element) { + element.classList.add('copied'); + // For screen readers + element.setAttribute('aria-label', 'Link copied to clipboard'); + + setTimeout(() => { + element.classList.remove('copied'); + element.setAttribute('aria-label', 'Copy link to this section'); + }, 2000); + } + + function fallbackCopy(text, element) { + // Create temporary input + const textArea = document.createElement('textarea'); + textArea.value = text; + textArea.style.position = 'fixed'; // Avoid scrolling to bottom + document.body.appendChild(textArea); + + // Handle iOS devices + if (navigator.userAgent.match(/ipad|iphone/i)) { + textArea.contentEditable = true; + textArea.readOnly = true; + const range = document.createRange(); + range.selectNodeContents(textArea); + const selection = window.getSelection(); + selection.removeAllRanges(); + selection.addRange(range); + textArea.setSelectionRange(0, 999999); + } else { + textArea.select(); + } + + try { + document.execCommand('copy'); + showCopiedFeedback(element); + } catch (err) { + console.error('Fallback copy failed:', err); + } + + document.body.removeChild(textArea); } document.addEventListener('DOMContentLoaded', addAnchors) From 12b273c01093d1865ce5732d901b7742288f3439 Mon Sep 17 00:00:00 2001 From: Evan Wondrasek Date: Fri, 28 Feb 2025 11:25:52 -0800 Subject: [PATCH 4/6] Fix deep link not working for H4 headings --- assets/js/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/js/main.js b/assets/js/main.js index d98df05..66f8c6b 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -55,7 +55,7 @@ function initParallax() { (function (window, document) { var addAnchors = () => { - var headings = document.querySelectorAll('.gh-content h1, .gh-content h2, .gh-content h3, gh-.content h4, .gh-content h5, .gh-content h6') + var headings = document.querySelectorAll('.gh-content h1, .gh-content h2, .gh-content h3, .gh-content h4, .gh-content h5, .gh-content h6') headings.forEach((heading) => { heading.insertAdjacentHTML('beforeend', ` `) @@ -82,11 +90,13 @@ function initParallax() { function showCopiedFeedback(element) { element.classList.add('copied'); - element.setAttribute('aria-label', 'Link copied to clipboard'); + element.setAttribute('aria-label', TOOLTIP_TEXTS.copied); + element.setAttribute('data-tooltip', TOOLTIP_TEXTS.copied); setTimeout(() => { element.classList.remove('copied'); - element.setAttribute('aria-label', 'Copy link to this section'); + element.setAttribute('aria-label', TOOLTIP_TEXTS.default); + element.setAttribute('data-tooltip', TOOLTIP_TEXTS.default); }, 2000); }