Asta copier

Adds copy button and double-click functionality to extract formatted text from asta.allen.ai

// ==UserScript==
// @name         Asta copier
// @namespace    https://violentmonkey.github.io/
// @version      1.7
// @description  Adds copy button and double-click functionality to extract formatted text from asta.allen.ai
// @author       Bui Quoc Dung
// @match        *://asta.allen.ai/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const ACCORDION_SUMMARY_SELECTOR    = '.MuiAccordionSummary-root';
    const ACCORDION_ROOT_SELECTOR       = '.MuiAccordion-root';
    const ACCORDION_HEADING_SELECTOR    = '.MuiAccordion-heading';
    const ACCORDION_BODY_SELECTOR       = '.MuiTypography-body1';
    const CITATION_SELECTOR             = '.MuiChip-root';
    const PARAGRAPH_SELECTOR            = '.MuiAccordionDetails-root';
    const COPY_BUTTON_CLASS             = 'MuiButtonBase-root MuiIconButton-root MuiIconButton-sizeSmall';

    function findDisclaimerButton() {
        const buttons = document.querySelectorAll('button');
        for (let button of buttons) {
            if (button.textContent.includes('Disclaimer')) {
                return button;
            }
        }
        return null;
    }

    function expandSections() {
        console.log("Expanding all sections...");
        document.querySelectorAll(ACCORDION_SUMMARY_SELECTOR).forEach(btn => btn.click());
    }

    function copyToClipboard(text) {
        let tempContainer = document.createElement('div');
        tempContainer.innerHTML = text;
        document.body.appendChild(tempContainer);
        let range = document.createRange();
        range.selectNode(tempContainer);
        let selection = window.getSelection();
        selection.removeAllRanges();
        selection.addRange(range);

        try {
            document.execCommand('copy');
            console.log("Copied formatted text.");
        } catch (err) {
            console.error("Copy failed", err);
        }

        selection.removeAllRanges();
        document.body.removeChild(tempContainer);
    }

    function processText(paragraphElement, includeHeading = true) {
        let tempDiv = paragraphElement.cloneNode(true);

        let paragraphText = tempDiv.innerHTML;

        tempDiv.querySelectorAll(CITATION_SELECTOR).forEach(citation => {
            let linkElement = citation.closest('a');
            if (linkElement) {
                let hyperlink = linkElement.href;
                let citationText = citation.innerText;
                let formattedCitation = `<a href="${hyperlink}" target="_blank">${citationText}</a>`;
                paragraphText = paragraphText.replace(citation.outerHTML, formattedCitation);
            }
        });

        paragraphText = paragraphText.replace(/<div[^>]*>/g, ' ')
                                     .replace(/<\/div>/g, '')
                                     .replace(/\s+/g, ' ');

        paragraphText = paragraphText
                        .replace(/Is this section helpful\? /g, "")
                        .replace(/Is this table helpful\? /g, "");

        if (includeHeading) {
            let headingText = "";
            let intermediateText = "";

            let accordion = paragraphElement.closest(ACCORDION_ROOT_SELECTOR);
            if (accordion) {
                let headingElement = accordion.querySelector(ACCORDION_HEADING_SELECTOR);
                if (headingElement) {
                    headingText = `<strong><u>${headingElement.innerText.trim()}</u></strong>`.trim();
                }

                let intermediateElement = accordion.querySelector(ACCORDION_BODY_SELECTOR);
                if (intermediateElement) {
                    intermediateText = intermediateElement.innerText.trim();
                }

                if (headingText) {
                    let prefix = headingText + ':\n';
                    if (intermediateText) {
                        prefix += intermediateText + '\n';
                    }
                    paragraphText = prefix + paragraphText;
                }
            }
        }

        return paragraphText;
    }

    function closeMenuIfOpen() {
        const collapseBtn = document.querySelector('button[data-track-name="thread_bar__collapse_btn"]');
        if (!collapseBtn) return;

        const closeIcon = collapseBtn.querySelector('svg[data-testid="CloseIcon"]');
        if (closeIcon) {
            collapseBtn.click();
            console.log("Closed menu panel before copying.");
        } else {
            console.log("Menu panel already closed.");
        }
    }

    function addCopyButton() {
        let targetButton = findDisclaimerButton();
        if (!targetButton) {
            return;
        }

        if (document.querySelector('#custom-copy-button')) {
            return;
        }
        let copyButton = document.createElement('button');
        copyButton.id = 'custom-copy-button';
        copyButton.className = COPY_BUTTON_CLASS;
        copyButton.style.marginLeft = '10px';
        copyButton.style.color = 'black';
        copyButton.title = "Copy Text";
        copyButton.textContent = "Copy";

        targetButton.parentNode.insertBefore(copyButton, targetButton.nextSibling);

        copyButton.addEventListener('click', () => {
            console.log("Copy button clicked!");
            copyButton.textContent = "Coping";

            // đóng panel nếu nó đang mở
            closeMenuIfOpen();

            expandSections();

            setTimeout(() => {
                let copiedText = extractAllText();
                copyToClipboard(copiedText);
                copyButton.textContent = "Copy";
            }, 1500);
        });
    }

    function extractAllText() {
        let paragraphs = document.querySelectorAll(PARAGRAPH_SELECTOR);
        let copiedText = "";

        paragraphs = Array.from(paragraphs).slice(0, -1);

        paragraphs.forEach(paragraph => {
            let processedText = processText(paragraph, true);
            copiedText += processedText + '\n\n';
        });

        return copiedText;
    }

    function handleDoubleClick(event) {
        const targetDiv = event.target.closest(PARAGRAPH_SELECTOR);
        if (!targetDiv) return;

        closeMenuIfOpen();

        let accordion = targetDiv.closest(ACCORDION_ROOT_SELECTOR);
        if (accordion) {
            let button = accordion.querySelector(ACCORDION_SUMMARY_SELECTOR);
            if (button) {
                button.click();
            }
        }

        setTimeout(() => {
            let textContent = processText(targetDiv, true);
            copyToClipboard(textContent);
        }, 500);
    }

    function initialize() {
        const observer = new MutationObserver(() => {
            addCopyButton();
        });

        observer.observe(document.body, { childList: true, subtree: true });

        document.body.addEventListener('dblclick', handleDoubleClick);

        setTimeout(addCopyButton, 2000);
    }

    initialize();
})();