Created
November 15, 2025 23:23
-
-
Save bquast/b93654f6b37aa0a4b8904cc041416500 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Grokipedia Search Result Copy URL | |
| // @namespace http://tampermonkey.net/ | |
| // @version 1.0 | |
| // @description Adds a copy URL button to each search result on the Grokipedia search results page. | |
| // @author Bastiaan Quast | |
| // @match https://grokipedia.com/search* | |
| // @grant GM_setClipboard | |
| // @grant GM_addStyle | |
| // @run-at document-idle | |
| // ==/UserScript== | |
| (function() { | |
| 'use strict'; | |
| // SVG icon for the copy button | |
| const COPY_ICON_SVG = ` | |
| <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path> | |
| <rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect> | |
| </svg> | |
| `; | |
| // Add CSS styles for the new button | |
| GM_addStyle(` | |
| .grok-copy-url-btn { | |
| background: none; | |
| border: none; | |
| cursor: pointer; | |
| padding: 4px; | |
| margin-left: 8px; /* Add some space next to the title */ | |
| border-radius: 4px; | |
| color: var(--fg-secondary); /* Use site's CSS variable */ | |
| display: inline-flex; | |
| align-items: center; | |
| /* Ensure it's not hidden by parent styles */ | |
| z-index: 10; | |
| position: relative; | |
| } | |
| .grok-copy-url-btn:hover { | |
| background-color: var(--overlay-hover); /* Use site's CSS variable */ | |
| color: var(--fg-primary); /* Use site's CSS variable */ | |
| } | |
| .grok-copy-url-btn svg { | |
| width: 16px; | |
| height: 16px; | |
| } | |
| `); | |
| // Function to add the copy button to a single search result element | |
| function addCopyButton(resultElement) { | |
| // Find the element containing the title text | |
| const titleSpan = resultElement.querySelector('span.line-clamp-1 > span'); | |
| if (!titleSpan) return; | |
| // Find the container to inject the button into | |
| const titleContainer = titleSpan.closest('.flex.items-center.gap-2'); | |
| // Check if button already exists | |
| if (!titleContainer || titleContainer.querySelector('.grok-copy-url-btn')) { | |
| return; | |
| } | |
| // 1. Get the title and format it for the URL | |
| const title = titleSpan.textContent.trim(); | |
| if (!title) return; // Skip if title is empty | |
| const articleUrl = `https://grokipedia.com/page/${title.replace(/ /g, '_')}`; | |
| // 2. Create the button | |
| const button = document.createElement('button'); | |
| button.innerHTML = COPY_ICON_SVG; | |
| button.className = 'grok-copy-url-btn'; | |
| button.title = 'Copy article URL'; | |
| // 3. Add click event listener | |
| button.addEventListener('click', (e) => { | |
| e.preventDefault(); // Stop the click from navigating to the article | |
| e.stopPropagation(); // Stop the click from propagating to the parent link | |
| // Use GM_setClipboard to copy the URL | |
| GM_setClipboard(articleUrl, 'text'); | |
| // Provide visual feedback to the user | |
| button.innerHTML = 'Copied!'; | |
| setTimeout(() => { | |
| button.innerHTML = COPY_ICON_SVG; | |
| }, 1500); | |
| }); | |
| // 4. Inject the button into the DOM | |
| titleContainer.appendChild(button); | |
| } | |
| // Function to find and process all search results | |
| function processResults() { | |
| // Select all result items that haven't been processed yet | |
| const results = document.querySelectorAll('div.rounded-md.p-2.cursor-pointer:not([data-copy-btn-added])'); | |
| results.forEach(result => { | |
| // Mark as processed to avoid re-adding buttons | |
| result.dataset.copyBtnAdded = 'true'; | |
| addCopyButton(result); | |
| }); | |
| } | |
| // Poll the page every 500ms to find new results | |
| // This is more robust for dynamic pages (SPAs) than a MutationObserver | |
| setInterval(processResults, 500); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment