From 7f3bf8bb553c8d7e6bc341bde29b7aca214c4446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20Biro=C5=A1?= Date: Fri, 10 Oct 2025 14:30:49 +0200 Subject: [PATCH 1/5] feat: Add copy to clipboard Heading --- apify-docs-theme/src/theme/Heading/index.jsx | 70 +++++++++++++++++++ .../src/theme/Heading/styles.module.css | 46 ++++++++++++ .../src/theme/Heading/useCopyToClipboard.js | 20 ++++++ 3 files changed, 136 insertions(+) create mode 100644 apify-docs-theme/src/theme/Heading/index.jsx create mode 100644 apify-docs-theme/src/theme/Heading/styles.module.css create mode 100644 apify-docs-theme/src/theme/Heading/useCopyToClipboard.js diff --git a/apify-docs-theme/src/theme/Heading/index.jsx b/apify-docs-theme/src/theme/Heading/index.jsx new file mode 100644 index 000000000..abaa6b46d --- /dev/null +++ b/apify-docs-theme/src/theme/Heading/index.jsx @@ -0,0 +1,70 @@ +import { useThemeConfig } from '@docusaurus/theme-common'; +import { translate } from '@docusaurus/Translate'; +import useBrokenLinks from '@docusaurus/useBrokenLinks'; +import clsx from 'clsx'; +import React from 'react'; + +import { LinkIcon } from '@apify/ui-icons'; + +import styles from './styles.module.css'; +import { useCopyToClipboard } from './useCopyToClipboard'; + +export default function Heading({ as: As, id, ...props }) { + const brokenLinks = useBrokenLinks(); + const { + navbar: { hideOnScroll }, + } = useThemeConfig(); + + const [isCopied, handleClick] = useCopyToClipboard({ + text: id ?? '', + transform: (text) => { + const url = new URL(window.location.href); + url.hash = `#${text}`; + return url.toString(); + }, + }); + + // H1 headings shouldn't have the copy to clipboard button + if (As === 'h1') { + return ; + } + + // Register the anchor ID so Docusaurus can scroll to it + if (id) { + brokenLinks.collectAnchor(id); + } + + const anchorTitle = translate( + { + id: 'theme.common.headingLinkTitle', + message: 'Direct link to {heading}', + description: 'Title for link to heading', + }, + { + heading: typeof props.children === 'string' ? props.children : id, + }, + ); + + return ( + + {props.children} + + + + + ); +} diff --git a/apify-docs-theme/src/theme/Heading/styles.module.css b/apify-docs-theme/src/theme/Heading/styles.module.css new file mode 100644 index 000000000..d0b73b34b --- /dev/null +++ b/apify-docs-theme/src/theme/Heading/styles.module.css @@ -0,0 +1,46 @@ +.anchorWithStickyNavbar { + scroll-margin-top: calc(var(--ifm-navbar-height) + 0.5rem); +} + +.anchorWithHideOnScrollNavbar { + scroll-margin-top: 0.5rem; +} + +.headingCopyIcon { + display: none; + transform: translateX(0.25rem); + color: var(--ifm-color-emphasis-700); + text-decoration: none; +} + +.headingCopyIcon svg { + stroke: var(--ifm-color-primary); + max-height: 1em !important; +} + +h2:hover .headingCopyIcon, +h3:hover .headingCopyIcon, +h4:hover .headingCopyIcon, +h5:hover .headingCopyIcon, +h6:hover .headingCopyIcon { + display: inline-block; +} + +.headingCopyIcon.copied { + display: inline-block !important; +} + +.headingCopyIcon.copied svg { + display: none; +} + +.headingCopyIcon.copied::after { + content: 'Copied!'; + font-size: 12px; + color: var(--ifm-font-color-secondary); + font-weight: 600; + margin-left: 8px; + vertical-align: middle; + line-height: 1; +} + diff --git a/apify-docs-theme/src/theme/Heading/useCopyToClipboard.js b/apify-docs-theme/src/theme/Heading/useCopyToClipboard.js new file mode 100644 index 000000000..0470f1f09 --- /dev/null +++ b/apify-docs-theme/src/theme/Heading/useCopyToClipboard.js @@ -0,0 +1,20 @@ +import { useState } from 'react'; + +const HIDE_COPIED_AFTER = 2000; + +export const useCopyToClipboard = ({ text, transform }) => { + const [isCopied, setIsCopied] = useState(false); + + const handleClick = async () => { + try { + const textToCopy = transform ? transform(text) : text; + await navigator.clipboard.writeText(textToCopy); + setIsCopied(true); + setTimeout(() => setIsCopied(false), HIDE_COPIED_AFTER); + } catch (err) { + console.error('Failed to copy link:', err); + } + }; + + return [isCopied, handleClick]; +}; From e53e7c0fc35a192186d43f6d8c99ba0ba36e207c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20Biro=C5=A1?= Date: Mon, 13 Oct 2025 09:56:36 +0200 Subject: [PATCH 2/5] fix: PR changes --- apify-docs-theme/src/theme/Heading/index.jsx | 20 ++++++++++--------- .../src/theme/Heading/styles.module.css | 3 ++- .../src/theme/Heading/useCopyToClipboard.js | 20 ------------------- 3 files changed, 13 insertions(+), 30 deletions(-) delete mode 100644 apify-docs-theme/src/theme/Heading/useCopyToClipboard.js diff --git a/apify-docs-theme/src/theme/Heading/index.jsx b/apify-docs-theme/src/theme/Heading/index.jsx index abaa6b46d..be877e227 100644 --- a/apify-docs-theme/src/theme/Heading/index.jsx +++ b/apify-docs-theme/src/theme/Heading/index.jsx @@ -2,12 +2,12 @@ import { useThemeConfig } from '@docusaurus/theme-common'; import { translate } from '@docusaurus/Translate'; import useBrokenLinks from '@docusaurus/useBrokenLinks'; import clsx from 'clsx'; -import React from 'react'; +import React, { useEffect } from 'react'; import { LinkIcon } from '@apify/ui-icons'; +import { useCopyToClipboard } from '@apify/ui-library'; import styles from './styles.module.css'; -import { useCopyToClipboard } from './useCopyToClipboard'; export default function Heading({ as: As, id, ...props }) { const brokenLinks = useBrokenLinks(); @@ -24,14 +24,16 @@ export default function Heading({ as: As, id, ...props }) { }, }); - // H1 headings shouldn't have the copy to clipboard button - if (As === 'h1') { - return ; - } - // Register the anchor ID so Docusaurus can scroll to it - if (id) { - brokenLinks.collectAnchor(id); + useEffect(() => { + if (id) { + brokenLinks.collectAnchor(id); + } + }, [id, brokenLinks]); + + // H1 headings and headings without an id shouldn't have the copy to clipboard button + if (As === 'h1' || !id) { + return ; } const anchorTitle = translate( diff --git a/apify-docs-theme/src/theme/Heading/styles.module.css b/apify-docs-theme/src/theme/Heading/styles.module.css index d0b73b34b..a94765a22 100644 --- a/apify-docs-theme/src/theme/Heading/styles.module.css +++ b/apify-docs-theme/src/theme/Heading/styles.module.css @@ -8,7 +8,8 @@ .headingCopyIcon { display: none; - transform: translateX(0.25rem); + position: relative; + left: .4rem; color: var(--ifm-color-emphasis-700); text-decoration: none; } diff --git a/apify-docs-theme/src/theme/Heading/useCopyToClipboard.js b/apify-docs-theme/src/theme/Heading/useCopyToClipboard.js deleted file mode 100644 index 0470f1f09..000000000 --- a/apify-docs-theme/src/theme/Heading/useCopyToClipboard.js +++ /dev/null @@ -1,20 +0,0 @@ -import { useState } from 'react'; - -const HIDE_COPIED_AFTER = 2000; - -export const useCopyToClipboard = ({ text, transform }) => { - const [isCopied, setIsCopied] = useState(false); - - const handleClick = async () => { - try { - const textToCopy = transform ? transform(text) : text; - await navigator.clipboard.writeText(textToCopy); - setIsCopied(true); - setTimeout(() => setIsCopied(false), HIDE_COPIED_AFTER); - } catch (err) { - console.error('Failed to copy link:', err); - } - }; - - return [isCopied, handleClick]; -}; From baf70a46a2b7de2ef44ec964b0a9f4bfc66a5212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20Biro=C5=A1?= Date: Mon, 13 Oct 2025 11:06:21 +0200 Subject: [PATCH 3/5] fix: Fix build --- apify-docs-theme/src/theme/Heading/index.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apify-docs-theme/src/theme/Heading/index.jsx b/apify-docs-theme/src/theme/Heading/index.jsx index be877e227..2cf6de0b4 100644 --- a/apify-docs-theme/src/theme/Heading/index.jsx +++ b/apify-docs-theme/src/theme/Heading/index.jsx @@ -15,7 +15,7 @@ export default function Heading({ as: As, id, ...props }) { navbar: { hideOnScroll }, } = useThemeConfig(); - const [isCopied, handleClick] = useCopyToClipboard({ + const { isCopied, copyToClipboard } = useCopyToClipboard({ text: id ?? '', transform: (text) => { const url = new URL(window.location.href); @@ -60,7 +60,7 @@ export default function Heading({ as: As, id, ...props }) { id={id}> {props.children} Date: Mon, 13 Oct 2025 14:20:24 +0200 Subject: [PATCH 4/5] fix: smooth scrolling --- apify-docs-theme/src/theme/Heading/index.jsx | 30 ++++++++++++++------ package-lock.json | 2 +- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/apify-docs-theme/src/theme/Heading/index.jsx b/apify-docs-theme/src/theme/Heading/index.jsx index 2cf6de0b4..b26b78b81 100644 --- a/apify-docs-theme/src/theme/Heading/index.jsx +++ b/apify-docs-theme/src/theme/Heading/index.jsx @@ -15,19 +15,24 @@ export default function Heading({ as: As, id, ...props }) { navbar: { hideOnScroll }, } = useThemeConfig(); - const { isCopied, copyToClipboard } = useCopyToClipboard({ - text: id ?? '', - transform: (text) => { - const url = new URL(window.location.href); - url.hash = `#${text}`; - return url.toString(); - }, - }); + const { isCopied, copyToClipboard } = useCopyToClipboard(); // Register the anchor ID so Docusaurus can scroll to it useEffect(() => { if (id) { brokenLinks.collectAnchor(id); + + // Handle scroll on page load if this heading matches the URL hash + const hash = decodeURIComponent(window.location.hash.slice(1)); + if (hash === id) { + // Use setTimeout to ensure the page is fully rendered + setTimeout(() => { + const element = document.getElementById(id); + if (element) { + element.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } + }, 100); + } } }, [id, brokenLinks]); @@ -36,6 +41,13 @@ export default function Heading({ as: As, id, ...props }) { return ; } + const handleCopy = async (e) => { + e.preventDefault(); + const url = new URL(window.location.href); + url.hash = `#${id ?? ''}`; + await copyToClipboard(url.toString()); + }; + const anchorTitle = translate( { id: 'theme.common.headingLinkTitle', @@ -60,7 +72,7 @@ export default function Heading({ as: As, id, ...props }) { id={id}> {props.children} Date: Mon, 13 Oct 2025 14:55:26 +0200 Subject: [PATCH 5/5] fix: update URL when copying url --- apify-docs-theme/src/theme/Heading/index.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apify-docs-theme/src/theme/Heading/index.jsx b/apify-docs-theme/src/theme/Heading/index.jsx index b26b78b81..bed905c13 100644 --- a/apify-docs-theme/src/theme/Heading/index.jsx +++ b/apify-docs-theme/src/theme/Heading/index.jsx @@ -45,6 +45,7 @@ export default function Heading({ as: As, id, ...props }) { e.preventDefault(); const url = new URL(window.location.href); url.hash = `#${id ?? ''}`; + window.location.hash = `#${id ?? ''}`; await copyToClipboard(url.toString()); };