From 05f1ffed83950997744af5e3c1d34ad2bec2be42 Mon Sep 17 00:00:00 2001 From: Rares Tritean Date: Sun, 22 Feb 2026 10:26:02 +0200 Subject: [PATCH] Add copy path badge to endpoints --- RWSTemplate/styles/custom-scripts.js | 133 ++++++++++++++++++--------- docfx.json | 21 ++++- 2 files changed, 110 insertions(+), 44 deletions(-) diff --git a/RWSTemplate/styles/custom-scripts.js b/RWSTemplate/styles/custom-scripts.js index f7857b5..d6c9167 100644 --- a/RWSTemplate/styles/custom-scripts.js +++ b/RWSTemplate/styles/custom-scripts.js @@ -130,59 +130,108 @@ document.addEventListener('DOMContentLoaded', function () { const operationId = operationMap[key]; if (operationId) { + // path (operationPath) used by copy button + const operationPath = path; // 1. Create a wrapper const wrapper = document.createElement('span'); wrapper.style.display = 'inline-flex'; wrapper.style.alignItems = 'center'; - // 2. Operation ID Badge - const idSpan = document.createElement('span'); - idSpan.textContent = ` [${operationId}]`; - idSpan.style.fontSize = '12px'; - idSpan.style.color = '#555'; - idSpan.style.marginLeft = '12px'; - idSpan.style.fontFamily = 'monospace'; - idSpan.style.backgroundColor = 'rgba(0,0,0,0.05)'; - idSpan.style.padding = '2px 6px'; - idSpan.style.borderRadius = '4px'; - idSpan.style.border = '1px solid #ddd'; - idSpan.className = 'inserted-operation-id'; - idSpan.title = 'Operation ID'; - - // 3. Anchor Link - const anchorId = `/operations/${operationId}`; - const anchorLink = document.createElement('a'); - anchorLink.href = `#${anchorId}`; - anchorLink.textContent = '🔗'; - anchorLink.style.marginLeft = '8px'; - anchorLink.style.textDecoration = 'none'; - anchorLink.style.fontSize = '14px'; - anchorLink.style.cursor = 'pointer'; - anchorLink.title = `Permalink to ${operationId}`; - - // Add click handler to always trigger navigation - anchorLink.addEventListener('click', (e) => { + // 2. Operation ID Badge (badge contains copy button and inline copy button) + const badge = document.createElement('span'); + badge.style.display = 'inline-flex'; + badge.style.alignItems = 'center'; + badge.style.fontSize = '12px'; + badge.style.color = '#555'; + badge.style.marginLeft = '0'; + badge.style.fontFamily = 'monospace'; + badge.style.backgroundColor = '#ffffff'; + badge.style.boxShadow = '0 1px 4px rgba(0,0,0,0.08)'; + badge.style.padding = '2px'; + badge.style.borderRadius = '4px'; + badge.style.border = '1px solid #ddd'; + badge.className = 'inserted-operation-id-badge'; + + // Copy button (click copies the operation path) + const copyBtn = document.createElement('button'); + copyBtn.type = 'button'; + copyBtn.innerHTML = ' '; + copyBtn.title = `Copy path: ${operationPath}`; + copyBtn.setAttribute('aria-label', `Copy path ${operationPath}`); + copyBtn.style.font = 'inherit'; + copyBtn.style.color = 'inherit'; + copyBtn.style.background = 'transparent'; + copyBtn.style.border = 'none'; + copyBtn.style.padding = '0'; + copyBtn.style.margin = '0'; + copyBtn.style.cursor = 'pointer'; + copyBtn.style.display = 'inline-flex'; + copyBtn.style.alignItems = 'center'; + copyBtn.style.justifyContent = 'center'; + copyBtn.style.boxSizing = 'border-box'; + copyBtn.style.backgroundClip = 'padding-box'; + copyBtn.style.borderRadius = '4px'; + copyBtn.className = 'inserted-operation-id'; + + // Copy-to-clipboard behavior (copies the operation path, e.g. "/resource/{id}") + copyBtn.addEventListener('click', async (e) => { e.preventDefault(); - console.log(`Direct click on operation link: ${operationId}`); - navigateToOperation(operationId); + // Prevent the click from bubbling to the collapsible header + e.stopPropagation(); + const textToCopy = operationPath; + try { + if (navigator.clipboard && navigator.clipboard.writeText) { + await navigator.clipboard.writeText(textToCopy); + } else { + // Fallback for older browsers + const ta = document.createElement('textarea'); + ta.value = textToCopy; + ta.style.position = 'fixed'; + ta.style.opacity = '0'; + document.body.appendChild(ta); + ta.select(); + document.execCommand('copy'); + document.body.removeChild(ta); + } + + // Temporary feedback (show SVG checkmark briefly) + const original = copyBtn.innerHTML; + copyBtn.innerHTML = ' '; + setTimeout(() => { copyBtn.innerHTML = original; }, 1000); + } catch (err) { + console.error('Copy failed', err); + } }); - wrapper.appendChild(idSpan); - wrapper.appendChild(anchorLink); - container.appendChild(wrapper); + badge.appendChild(copyBtn); + + // 3. Anchor ID + const anchorId = `/operations/${operationId}`; + + wrapper.appendChild(badge); - // 4. Set the ID on the container for scrolling + const parent = container.parentElement || container; + if (parent) { + if (getComputedStyle(parent).position === 'static') { + parent.style.position = 'relative'; + } + wrapper.style.position = 'absolute'; + wrapper.style.top = '50%'; + wrapper.style.transform = 'translateY(-50%)'; + wrapper.style.right = '12px'; + wrapper.style.zIndex = '9999'; + wrapper.style.pointerEvents = 'auto'; + try { parent.style.overflow = 'visible'; } catch (e) { /* ignore */ } + copyBtn.style.width = '20px'; + copyBtn.style.height = '20px'; + + parent.appendChild(wrapper); + } else { + // fallback to previous behavior + container.appendChild(wrapper); + } if (!container.id) { container.id = anchorId; - } else { - // Append invisible anchor target - const anchorTarget = document.createElement('a'); - anchorTarget.id = anchorId; - anchorTarget.style.position = 'absolute'; - anchorTarget.style.top = '-100px'; // Offset for bad headers - anchorTarget.style.visibility = 'hidden'; - container.style.position = 'relative'; // Ensure absolute positioning is relative to container - container.appendChild(anchorTarget); } container.dataset.opIdInjected = 'true'; diff --git a/docfx.json b/docfx.json index f6a94a3..ccef19e 100644 --- a/docfx.json +++ b/docfx.json @@ -19,6 +19,9 @@ { "files": [ "api/**.yml" + ], + "exclude": [ + "_site/**" ] }, { @@ -27,6 +30,9 @@ "articles/**/toc.yml", "toc.yml", "*.md" + ], + "exclude": [ + "_site/**" ] } ], @@ -37,6 +43,9 @@ "**/*.json", "**/*.html", "**/*.js" + ], + "exclude": [ + "_site/**" ] } ], @@ -56,13 +65,21 @@ "fileMetadataFiles": [], "filemetadata": { "langs": { - "api/**.yml": [ "csharp", "vb", "fsharp", "cpp" ] + "api/**.yml": [ + "csharp", + "vb", + "fsharp", + "cpp" + ] } }, "template": [ "RWSTemplate" ], - "postProcessors": [ "ExtractSearchIndex", "EnvironmentVariableProcessor" ], + "postProcessors": [ + "ExtractSearchIndex", + "EnvironmentVariableProcessor" + ], "globalMetadata": { "_enableSearch": true },