From d8eb85ca86d26985faa5fae1c5aabf589b69e0fc Mon Sep 17 00:00:00 2001 From: Valeriy Date: Tue, 28 Oct 2025 10:56:02 +0200 Subject: [PATCH 1/3] [CN-59083] update anchered links --- astro.config.mjs | 18 ++--- src/assets/images/link.svg | 7 ++ src/scripts/rehypeButtonHeadings.mjs | 69 ++++++++++++++++++ src/style/global.css | 104 +++++++++++++++++++++++++++ 4 files changed, 186 insertions(+), 12 deletions(-) create mode 100644 src/assets/images/link.svg create mode 100644 src/scripts/rehypeButtonHeadings.mjs diff --git a/astro.config.mjs b/astro.config.mjs index 2b65a57d..07adbca9 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -20,6 +20,7 @@ import developerSidebar from './src/content/sidebars/developer.ts'; import customConsentScript from './src/scripts/custom-consent-mode.js?raw'; import postHogScript from './src/scripts/posthog.js?raw'; import gtmScript from './src/scripts/gtm.js?raw'; +import { rehypeButtonHeadings } from "./src/scripts/rehypeButtonHeadings.mjs"; let site; @@ -38,6 +39,9 @@ const config = defineConfig({ integrations: [ starlight({ title: 'Crowdin Docs', + markdown: { + headingLinks: false // Add this line + }, logo: { replacesTitle: true, light: './src/assets/logo/dark.svg', @@ -229,20 +233,10 @@ const config = defineConfig({ remarkPlugins: [ remarkHeadingId, // Support custom heading IDs. ], + rehypePlugins: [ rehypeHeadingIds, - [ - rehypeAutolinkHeadings, - { - behavior: 'wrap', // Wrap the heading text in a link. - }, - ], - [ - rehypeExternalLinks, - { - target: '_blank', // Open external links in a new tab. - } - ] + rehypeButtonHeadings, ], }, vite: { diff --git a/src/assets/images/link.svg b/src/assets/images/link.svg new file mode 100644 index 00000000..d3ad5d15 --- /dev/null +++ b/src/assets/images/link.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/scripts/rehypeButtonHeadings.mjs b/src/scripts/rehypeButtonHeadings.mjs new file mode 100644 index 00000000..9aa5c6ad --- /dev/null +++ b/src/scripts/rehypeButtonHeadings.mjs @@ -0,0 +1,69 @@ +/** + * Custom rehype plugin to add button elements with heading icons to headings + * The button is used onClick handlers for copy-link functionality + */ +export function rehypeButtonHeadings() { + return (tree) => { + function traverse(node) { + // Check if it's a heading element (h1-h6) with an id + if ( + node.type === 'element' && + /^h[1-6]$/.test(node.tagName) && + node.properties && + node.properties.id + ) { + const headingId = node.properties.id; + + const onClickHandler = `(function(btn) { + const url = window.location.origin + window.location.pathname + '#${headingId}'; + if (navigator.clipboard) { + navigator.clipboard.writeText(url) + .then(() => { + const originalTitle = btn.title; + btn.setAttribute('data-copied', 'true'); + + // Reset after 2 seconds + setTimeout(() => { + btn.removeAttribute('data-copied'); + }, 2000); + }) + .catch(err => console.error('Failed to copy:', err)); + } else { + console.error('Clipboard API not available. Requires HTTPS or localhost.'); + } + })(this)`; + + const button = { + type: 'element', + tagName: 'button', + properties: { + type: 'button', + title: 'Click to copy link', + onClick: onClickHandler, + 'data-tooltip': 'Copy link', + class: 'tooltip-container' + }, + children: [ + { + type: 'element', + tagName: 'span', + properties: { + className: ['heading-icon'] + }, + children: [] + } + ] + }; + + node.children.push(button); + } + + if (node.children && Array.isArray(node.children)) { + node.children.forEach(child => traverse(child)); + } + } + + traverse(tree); + }; +} + diff --git a/src/style/global.css b/src/style/global.css index e2080bab..423e3a5c 100644 --- a/src/style/global.css +++ b/src/style/global.css @@ -101,6 +101,110 @@ h1, h2, h3, h4, h5, h6 { } } +/* Heading button with tooltip */ + +.sl-markdown-content :is(h1, h2, h3, h4, h5, h6) { + button.tooltip-container { + position: relative; + background-color: transparent !important; + border: none; + cursor: pointer; + padding: 0; + + /* Tooltip text */ + &::before { + content: attr(data-tooltip); + position: absolute; + bottom: calc(100% + 0.5rem); + left: 50%; + transform: translateX(-50%); + padding: 0.375rem 0.75rem; + background-color: rgba(38, 50, 56, 0.95); + color: white; + font-size: 0.875rem; + white-space: nowrap; + border-radius: 0.375rem; + opacity: 0; + pointer-events: none; + transition: opacity 0.2s ease-in-out; + z-index: 1000; + } + + /* Tooltip arrow */ + &::after { + content: ''; + position: absolute; + bottom: calc(100% + 0.125rem); + left: 50%; + transform: translateX(-50%); + width: 0; + height: 0; + border-left: 0.375rem solid transparent; + border-right: 0.375rem solid transparent; + border-top: 0.375rem solid rgba(38, 50, 56, 0.95); + opacity: 0; + pointer-events: none; + transition: opacity 0.2s ease-in-out; + z-index: 1000; + } + + /* Only show tooltip when link is copied */ + &[data-copied="true"]::before { + content: "Link copied!"; + background-color: rgba(67, 160, 71, 0.95); + opacity: 1; + } + + &[data-copied="true"]::after { + border-top-color: rgba(67, 160, 71, 0.95); + opacity: 1; + } + } + + .heading-icon { + margin-left: 0.25rem; + background-image: url("/src/assets/images/link.svg"); + background-repeat: no-repeat; + background-position: center; + background-size: contain; + width: 1.25rem; + height: 1.25rem; + display: inline-block; + visibility: hidden; + } + + &:hover { + .heading-icon { + visibility: visible; + } + } +} + +/* Light mode adjustments */ +:root[data-theme='light'] .sl-markdown-content :is(h1, h2, h3, h4, h5, h6) { + button.tooltip-container::before { + background-color: rgba(38, 50, 56, 0.95); + color: white; + } + + button.tooltip-container::after { + border-top-color: rgba(38, 50, 56, 0.95); + } + + button.tooltip-container[data-copied="true"]::before { + background-color: rgba(67, 160, 71, 0.95); + } + + button.tooltip-container[data-copied="true"]::after { + border-top-color: rgba(67, 160, 71, 0.95); + } +} + +/* Dark mode: invert the link icon */ +:root:not([data-theme='light']) .sl-markdown-content :is(h1, h2, h3, h4, h5, h6) .heading-icon { + filter: brightness(0) saturate(100%) invert(100%); +} + /* Markdown content */ .sl-markdown-content { From 131892086143f690048db9dcb419d01bf77257af Mon Sep 17 00:00:00 2001 From: Valeriy Date: Tue, 28 Oct 2025 11:19:50 +0200 Subject: [PATCH 2/3] [CN-59083] update anchered links --- src/style/global.css | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/style/global.css b/src/style/global.css index 423e3a5c..f6ef0fe1 100644 --- a/src/style/global.css +++ b/src/style/global.css @@ -119,7 +119,7 @@ h1, h2, h3, h4, h5, h6 { left: 50%; transform: translateX(-50%); padding: 0.375rem 0.75rem; - background-color: rgba(38, 50, 56, 0.95); + background-color: var(--color-gray-800); color: white; font-size: 0.875rem; white-space: nowrap; @@ -141,7 +141,7 @@ h1, h2, h3, h4, h5, h6 { height: 0; border-left: 0.375rem solid transparent; border-right: 0.375rem solid transparent; - border-top: 0.375rem solid rgba(38, 50, 56, 0.95); + border-top: 0.375rem solid var(--color-gray-800); opacity: 0; pointer-events: none; transition: opacity 0.2s ease-in-out; @@ -151,12 +151,12 @@ h1, h2, h3, h4, h5, h6 { /* Only show tooltip when link is copied */ &[data-copied="true"]::before { content: "Link copied!"; - background-color: rgba(67, 160, 71, 0.95); + background-color: var(--color-accent-600); opacity: 1; } &[data-copied="true"]::after { - border-top-color: rgba(67, 160, 71, 0.95); + border-top-color: var(--color-accent-600); opacity: 1; } } @@ -183,20 +183,20 @@ h1, h2, h3, h4, h5, h6 { /* Light mode adjustments */ :root[data-theme='light'] .sl-markdown-content :is(h1, h2, h3, h4, h5, h6) { button.tooltip-container::before { - background-color: rgba(38, 50, 56, 0.95); + background-color: var(--color-gray-800); color: white; } button.tooltip-container::after { - border-top-color: rgba(38, 50, 56, 0.95); + border-top-color: var(--color-gray-800); } button.tooltip-container[data-copied="true"]::before { - background-color: rgba(67, 160, 71, 0.95); + background-color: var(--color-accent-600); } button.tooltip-container[data-copied="true"]::after { - border-top-color: rgba(67, 160, 71, 0.95); + border-top-color: var(--color-accent-600); } } From ba5dc1feb63d17eb52301b643e0b7bee68f7ff15 Mon Sep 17 00:00:00 2001 From: ValTarasenko Date: Tue, 28 Oct 2025 13:04:56 +0200 Subject: [PATCH 3/3] Update astro.config.mjs Update astro.config.mjs clear usless lines --- astro.config.mjs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/astro.config.mjs b/astro.config.mjs index 07adbca9..ecaf2177 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -40,7 +40,7 @@ const config = defineConfig({ starlight({ title: 'Crowdin Docs', markdown: { - headingLinks: false // Add this line + headingLinks: false }, logo: { replacesTitle: true, @@ -233,7 +233,6 @@ const config = defineConfig({ remarkPlugins: [ remarkHeadingId, // Support custom heading IDs. ], - rehypePlugins: [ rehypeHeadingIds, rehypeButtonHeadings,