Stack Pop

Adds the first StackOverflow and/or Reddit answer to your search result

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Stack Pop
// @namespace    https://codeberg.org/happybits/stack-pop
// @license      MIT
// @version      1.3
// @description  Adds the first StackOverflow and/or Reddit answer to your search result
// @author       happybits
// @match        https://www.google.com/search*
// @icon
// @grant        GM_xmlhttpRequest
// ==/UserScript==
 
// This is a userscript StackPop that is supposted to run by Tampermonkey when the user enters a google search
 
try {

    log("StackPop 1.3")

    let rso
    let stackPopDiv

    go()
 
    async function go() {
 
        // Wait for the google search to appear in the DOM
 
        rso = await waitFor("#rso")
 
        // Inject StackPop before the google search result
 
        stackPopDiv = document.createElement("div")
        stackPopDiv.id = "stack-pop"
        rso.before(stackPopDiv)
 
        // Find the first search result that leads to Stack Overflow
 
        insertIfFound(
            {
                urlStartWith: "https://stackoverflow.com",
                questionSelector: page => page.querySelector("h1").textContent,
                answerSelector: page => page.querySelector(".answer .s-prose").innerHTML,
            }
        )
 
        // Find the first search result that leads to Reddit
 
        insertIfFound(
            {
                urlStartWith: "https://www.reddit.com",
                questionSelector: page => page.querySelector("h1").textContent,
                answerSelector: page => page.querySelector(".Comment").querySelector(".RichTextJSON-root").innerHTML,
            }
        )
    }
 
    // Insert StackPop if part of the search result (ex StackOverflow or Reddit)
 
    function insertIfFound({ urlStartWith, questionSelector, answerSelector}) {
 
        const firstHit = firstLinkThatStartsWith(urlStartWith)
 
        // If any, get the answer inject it to the search page
 
        if (firstHit) {
 
            // HTTP call using Tampermonkey's built in function
 
            GM_xmlhttpRequest({
                method: "GET",
                url: firstHit,
                onload: function (response) {
 
                    // Build a DOM from the text-response and parse the question and the first answer
 
                    const page = new DOMParser().parseFromString(response.responseText, 'text/html');
                    const question = questionSelector(page) //  page.querySelector(questionSelector).textContent
                    const firstAnswerContent = answerSelector(page) // page.querySelector(answerSelector).innerHTML
 
                    // Create the StackPop widget
 
                    const widget = document.createElement("div")
 
                    const backgroundColor = "#f4ad25"
                    widget.innerHTML = `
 
                    <style>${stackPopStyling}</style>
 
                    <div style="border:solid 2px ${backgroundColor}; margin: 1em 0; overflow: auto;">
 
                        <a href="${firstHit}" style="text-decoration:none">
                            <div style="cursor:pointer; background-color:${backgroundColor}; color:white; padding:0.5em; font-size: 1.5em">
                                ${encodeHTMLEntities(question)}
                            </div>
                        </a>
 
                        <div style="padding:0 1.5em 0.5em 1.5em;">
                            ${firstAnswerContent}
                        </div>
 
                    </div>
                    `
 
                    // Add the possiblitiy to copy code to the clipboard
 
                    addCopyCodeButtons(widget)
 
                    // Add widget below
 
                    stackPopDiv.append(widget)
 
                }
            });
 
 
        }
 
 
    }
 
    // Add the possiblitiy to copy code to the clipboard
 
    function addCopyCodeButtons(element) {
        element.querySelectorAll("pre").forEach(preElement => {
 
            const elementHasCode = preElement.firstChild.nodeName.toLowerCase() === "code"
 
            if (elementHasCode) {
                const codeToCopy = preElement.querySelector("code").innerText
                preElement.append(copyIcon(codeToCopy))
            }
        });
    }
 
 
    // Get the first link in the Google search result area that starts with a specific string
 
    function firstLinkThatStartsWith(urlPart) {
 
        const googleResults = Array.from(rso.children)
 
        return googleResults
 
            .filter(result => result.querySelector("a")?.href)
            .map(result => result.querySelector("a").href)
            .find(link => link.startsWith(urlPart))
    }
 
 
    // CSS Styling for StackPop box
 
    const stackPopStyling = `
        #stack-pop {
            width: 700px;
        }
 
        #stack-pop li {
            margin-left: 1.5em;
            list-style: normal;
        }
 
        #stack-pop pre {
            background-color: #eee;
            padding: 1em;
            position:relative;
        }
 
        #stack-pop .copy-icon{
            position:absolute;
            right:4px;
            top:4px;
            height:33px;
            width:33px;
            opacity:0.5;
            cursor:pointer;
        }
 
        #stack-pop .copy-icon:hover{
            opacity:1;
        }
 
        #stack-pop code {
            background-color: #eee;
        }
 
        #stack-pop img {
            max-width: 100%;
        }
        `
 
    // This is a generic method that can be used to select element that may take some time to appear in the DOM
    // The second parameter "scope" is optional, of you want to limit the query
 
    function waitFor(selector, scope) {
 
        const pause = 10
        let maxTime = 10000
 
        return new Promise(resolve => {
 
            function inner() {
                if (maxTime <= 0) {
                    throw "Timeout for select " + selector
                }
                const element = (scope ?? document).querySelector(selector)
 
                if (element) {
                    resolve(element)
                    return
                }
                maxTime -= pause
                setTimeout(inner)
            }
 
            inner()
        })
    }
 
    // A simple log function which shows with a TAMPER-prefix
 
    function log(...message) {
        console.log('%c TAMPER ', 'color: white; background-color: #61dbfb', ...message);
    }
 
    // Create a copy icon (used for copying code to the clipboard)
 
    function copyIcon(textToCopyIfClicked) {
 
        const img = document.createElement("img")
        img.className = "copy-icon"
        img.title = "Copy to clipboard"
        img.src = "data:image/png;base64, UklGRvACAABXRUJQVlA4WAoAAAAYAAAANgAAOAAAQUxQSHcAAAABcFvbbtv8PdShEksNghW0QhpITnNIlfZxQAuQzgHofSJiAvCUhsXrQHiz2mvxq/vqBR+L7yM/aa14txZAkuJfErCWiCuaHCI3Y4k5zkHm/yXGICNpCCUcQhyAJAEkAWBzZ4xHFmfCeF7v1ZHua7xJ3bT4nDrCUwBWUDggcgEAAPAKAJ0BKjcAOQA+bTSURyQjIiEkGA2wgA2JaQDREEGjmANij+OeoAXWR7EsWPRzuwcNBgq5Du9vVd2+k2kGrGmAE1sdF3OpU0UbjJe/ni0mmBjpwOA3uZJ6569JAWP1AAD+9jeN+v4qM9kSG8dSMALfMGwGD3FF9bbU+gpg4WjBy3tfK842lgA0VyUMfZz3ElSaGGuqLqd12/tyU3fr9anAddYtAn7Xq0b+enT1NycxT2/vZZhOO115TWb5or5bBv4paaHUOH9/Dfp+YtanA+tAAX/tuyu604rmWOEPcP8R9t3tjaIIR7lPJZ3PPJRH/nkg4qbqeRmNratNGmpid+8s5svRhbwBZLf6tfLdlpNcUi0mx5pqaLkFtP6i72HM4z95hucWvXu4cLVz713xnMz6Bjph+AbuLAYAVq4aY/4n5Jz4sEaioamwCrwdqcgG7je6Yy7bftlFoh7itBFC/4434b5/dISkOBAdk5rlzMNJQABFWElG2AAAAElJKgAIAAAABgASAQMAAQAAAAEAAAAaAQUAAQAAAFYAAAAbAQUAAQAAAF4AAAAoAQMAAQAAAAMAAAAxAQIAEQAAAGYAAABphwQAAQAAAHgAAAAAAAAAo5MAAOgDAACjkwAA6AMAAHBhaW50Lm5ldCA0LjMuMTIAAAUAAJAHAAQAAAAwMjMwAaADAAEAAAABAAAAAqAEAAEAAAA3AAAAA6AEAAEAAAA5AAAABaAEAAEAAAC6AAAAAAAAAAIAAQACAAQAAABSOTgAAgAHAAQAAAAwMTAwAAAAAA=="
        img.onclick = () => copyTextToClipboard(textToCopyIfClicked)
        return img
    }
 
    // Copy text to clipboard (surprise)
 
    function copyTextToClipboard(text) {
 
        navigator.clipboard.writeText(text).then(function () {
            log('Copied to clipboard');
        }, function (err) {
            throw err
        });
    }
 
    // Encode and decode string to and from HTML
 
    function encodeHTMLEntities(rawStr) {
        return rawStr.replace(/[\u00A0-\u9999<>\&]/g, ((i) => `&#${i.charCodeAt(0)};`));
    }
 
    function decodeHTMLEntities(rawStr) {
        return rawStr.replace(/&#(\d+);/g, ((_, dec) => `${String.fromCharCode(dec)}`));
    }
 
 
    // Unexpected errors is shown in red
 
} catch (exception) {
    console.log('%c TAMPER ', 'color: white; background-color: red', exception);
}

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址