Adds copy button and double-click functionality to extract formatted text from ScholarQA
当前为
// ==UserScript==
// @name ScholarQA Enhanced Copy Tools
// @namespace https://violentmonkey.github.io/
// @version 1.1
// @description Adds copy button and double-click functionality to extract formatted text from ScholarQA
// @author Bui Quoc Dung
// @match *://scholarqa.allen.ai/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
// Define CSS selector variables for easier maintenance
const ACCORDION_SUMMARY_SELECTOR = '.MuiButtonBase-root.MuiAccordionSummary-root';
const ACCORDION_ROOT_SELECTOR = '.MuiAccordion-root';
const ACCORDION_HEADING_SELECTOR = '.MuiTypography-h5';
const ACCORDION_INTERMEDIATE_SELECTOR = '.MuiTypography-body1';
const HIDDEN_SECTION_SELECTOR = '.sc-jsJBEP';
const CITATION_SELECTOR = '.MuiChip-root';
const PARAGRAPH_SELECTOR = '.sc-hmdomO';
const COPY_BUTTON_CLASS = 'MuiButtonBase-root MuiIconButton-root MuiIconButton-sizeSmall sc-feUZmu eOQWBD css-xz9haa';
// Expands all collapsed sections for complete content extraction
function expandSections() {
console.log("Expanding all sections...");
document.querySelectorAll(ACCORDION_SUMMARY_SELECTOR).forEach(btn => btn.click());
}
// Shows notification when content is copied
function showNotification(x, y) {
let notification = document.createElement('div');
notification.innerText = "Copied!";
Object.assign(notification.style, {
position: 'absolute',
left: `${x}px`,
top: `${y}px`,
background: 'rgba(0, 0, 0, 0.8)',
color: 'white',
padding: '8px 12px',
borderRadius: '5px',
fontSize: '14px',
zIndex: '1000',
transition: 'opacity 0.5s'
});
document.body.appendChild(notification);
setTimeout(() => {
notification.style.opacity = '0';
setTimeout(() => document.body.removeChild(notification), 500);
}, 1500);
}
// Copies formatted HTML content to clipboard
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);
}
// Processes text from a single paragraph element
function processText(paragraphElement, includeHeading = true) {
let tempDiv = paragraphElement.cloneNode(true);
tempDiv.querySelectorAll(HIDDEN_SECTION_SELECTOR).forEach(el => el.remove());
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, ' ');
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>`;
}
let intermediateElement = accordion.querySelector(ACCORDION_INTERMEDIATE_SELECTOR);
if (intermediateElement) {
intermediateText = intermediateElement.innerText.trim();
}
if (headingText) {
let prefix = headingText + ':\n';
if (intermediateText) {
prefix += intermediateText + '\n';
}
paragraphText = prefix + paragraphText;
}
}
}
return paragraphText;
}
// Adds a copy button next to the existing UI buttons
function addCopyButton() {
console.log("Adding copy button...");
let targetButton = document.querySelector('button#\\:rc\\:');
if (!targetButton) {
console.warn("Target button (:rc:) not found!");
return;
}
if (document.querySelector('#custom-copy-button')) {
console.warn("Copy button already exists!");
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', (event) => {
console.log("Copy button clicked!");
copyButton.textContent = "Copied";
expandSections();
setTimeout(() => {
let copiedText = extractAllText();
copyToClipboard(copiedText);
copyButton.textContent = "Copy";
}, 1500);
});
}
// Extracts formatted text from all paragraphs
function extractAllText() {
let paragraphs = document.querySelectorAll(PARAGRAPH_SELECTOR);
let copiedText = "";
paragraphs.forEach(paragraph => {
let processedText = processText(paragraph, true);
copiedText += processedText + '\n\n';
});
return copiedText;
}
// Handles double-click event to copy individual paragraph
function handleDoubleClick(event) {
const targetDiv = event.target.closest(PARAGRAPH_SELECTOR);
if (!targetDiv) return;
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);
showNotification(event.pageX, event.pageY);
}, 500);
}
// Initialize both features
function initialize() {
const observer = new MutationObserver(() => {
addCopyButton();
});
observer.observe(document.body, { childList: true, subtree: true });
document.body.addEventListener('dblclick', handleDoubleClick);
setTimeout(addCopyButton, 2000);
}
initialize();
})();