// ==UserScript==
// @name Alphaxiv Assistant Enhancer
// @namespace https://github.com/kagani
// @version 1.0
// @description Copy and download buttons for Alphaxiv Assistant.
// @author Kagan Ilbak
// @match https://www.alphaxiv.org/*
// @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3MTguNDEgNTA0LjQ3IiB3aWR0aD0iNzE4LjQxIiBoZWlnaHQ9IjUwNC40NyIgY2xhc3M9InRleHQtY3VzdG9tLXJlZCBzaXplLThoIGRhcms6dGV4dC13aGl0ZSIgZGF0YS1zZW50cnktZWxlbWVudD0ic3ZnIiBkYXRhLXNlbnRyeS1zb3VyY2UtZmlsZT0iQWxwaFhpdkxvZ28udHN4IiBkYXRhLXNlbnRyeS1jb21wb25lbnQ9IkFscGhhWGl2TG9nbyI+PHBvbHlnb24gZmlsbD0iY3VycmVudENvbG9yIiBwb2ludHM9IjU5MS4xNSAgMjU4LjU0IDcxOC40MSAzODUuNzMgNjYzLjcyIDQ0MC4yOCA1MzYuNTcgMzEzLjYyIDU5MS4xNSAyNTguNTQiIGRhdGEtc2VudHlyLWVsZW1lbnQ9InBvbHlnb24iIGRhdGEtc2VudHktc291cmNlLWZpbGU9IkFscGhhWGl2TG9nby50c3giPjwvcG9seWdvbj48cGF0aCBmaWxsPSJjdXJyZW50Q29sb3IiIGQ9Ik0yNzMuODYuM2MzNC41Ni0yLjQxLDY3LjY2LDkuNzMsOTIuNTEsMzMuNTRsOTQuNjQsOTQuNjMtNTUuMTEsNTQuNTUtOTYuNzYtOTYuNTVjLTE2LjAyLTEyLjctMzcuNjctMTIuMS01My4xOSwxLjExTDU0LjYyLDI4OC44MiwwLDIzNC4yMywyMDQuNzYsMjkuNTdDMjIzLjEyLDEzLjMxLDI0OS4yNywyLjAyLDI3My44Ni4zWiIgZGF0YS1zZW50cnktZWxlbWVudD0icGF0aCIgZGF0YS1zZW50cnktc291cmNlLWZpbGU9IkFscGhhWGl2TG9nby50c3giPjwvcGF0aD48cGF0aCBmaWxsPSJjdXJyZW50Q29sb3IiIGQ9Ik02NjMuNzksMS4yOWw1NC42Miw1NC41OC00MTguMTEsNDE3LjljLTExNC40Myw5NS45NC0yNjMuNTctNTMuNDktMTY3LjA1LTE2Ny41MmwxNjAuNDYtMTYwLjMzLDU0LjYyLDU0LjU4LTE1Ny44OCwxNTcuNzdjLTMzLjE3LDQwLjMyLDE4LjkzLDkxLjQxLDU4LjY2LDU3LjQ4TDY2My43OSwxLjI5WiIgZGF0YS1zZW50cnktZWxlbWVudD0icGF0aCIgZGF0YS1zZW50cnktc291cmNlLWZpbGU9IkFscGhhWGl2TG9nby50c3giPjwvcGF0aD48L3N2Zz4=
// @grant none
// @license MIT
// @downloadURL https://update.greasyfork.icu/scripts/538221/Alphaxiv%20Assistant%20Enhancer.user.js
// @updateURL https://update.greasyfork.icu/scripts/538221/Alphaxiv%20Assistant%20Enhancer.meta.js
// ==/UserScript==
(function() {
const style = document.createElement('style');
style.textContent = `
.__msg-wrapper {
display: flex;
flex-direction: column;
margin-bottom: 0.75em;
}
.__msg-btn-container {
display: flex;
gap: 8px;
align-self: flex-start;
margin-top: 4px;
}
.__msg-copy-btn,
.__msg-download-btn {
background: transparent;
border: none;
padding: 4px;
cursor: pointer;
opacity: 0.6;
transition: opacity 0.2s ease;
}
.__msg-copy-btn:hover,
.__msg-download-btn:hover {
opacity: 1;
}
.__msg-copy-btn svg,
.__msg-download-btn svg {
width: 1rem;
height: 1rem;
fill: currentColor;
}
.__msg-copied-indicator {
align-self: flex-start;
background: rgba(0, 0, 0, 0.75);
color: #fff;
font-size: 0.75rem;
padding: 2px 6px;
border-radius: 3px;
opacity: 0;
transition: opacity 0.2s ease;
margin-top: 4px;
}
.__msg-copied-indicator.show {
opacity: 1;
}
.__code-wrapper {
position: relative;
margin-bottom: 1em;
}
.__copy-container {
position: sticky;
top: 0.25rem;
z-index: 10;
display: flex;
justify-content: flex-end;
background: inherit;
}
.__code-wrapper > pre {
margin-top: -2.5rem;
padding-top: 1rem;
overflow: auto;
}
.__copy-btn {
margin-right: 0.5rem;
margin-bottom: 0.25rem;
background: #444;
color: #fff;
border: none;
border-radius: 3px;
padding: 0.25em 0.6em;
font-size: 0.8em;
cursor: pointer;
opacity: 0.75;
transition: opacity 0.2s ease;
}
.__copy-btn:hover {
opacity: 1;
}
`;
document.head.appendChild(style);
const copyIconSVG = `
`;
const downloadIconSVG = `
`;
const checkmarkSVG = `
`;
function wrapMessage(el) {
if (el.parentElement.classList.contains('__msg-wrapper')) return;
const wrapper = document.createElement('div');
wrapper.classList.add('__msg-wrapper');
el.parentNode.insertBefore(wrapper, el);
wrapper.appendChild(el);
const markdownContainer = el.querySelector('.markdown-content');
const textContent = markdownContainer ? null : el.innerText;
const btnContainer = document.createElement('div');
btnContainer.classList.add('__msg-btn-container');
wrapper.appendChild(btnContainer);
const copyBtn = document.createElement('button');
copyBtn.classList.add('__msg-copy-btn');
copyBtn.innerHTML = copyIconSVG;
btnContainer.appendChild(copyBtn);
const downloadBtn = document.createElement('button');
downloadBtn.classList.add('__msg-download-btn');
downloadBtn.innerHTML = downloadIconSVG;
btnContainer.appendChild(downloadBtn);
const originalCopyHTML = copyIconSVG;
const originalDownloadHTML = downloadIconSVG;
copyBtn.addEventListener('click', async () => {
try {
const toCopy = markdownContainer
? markdownContainer.innerText
: textContent;
await navigator.clipboard.writeText(toCopy);
copyBtn.innerHTML = checkmarkSVG;
setTimeout(() => {
copyBtn.innerHTML = originalCopyHTML;
}, 1200);
} catch {
}
});
downloadBtn.addEventListener('click', () => {
const toDownload = markdownContainer
? markdownContainer.innerText
: textContent;
const blob = new Blob([toDownload], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
const timestamp = Date.now();
a.download = `message_${timestamp}.txt`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
downloadBtn.innerHTML = checkmarkSVG;
setTimeout(() => {
downloadBtn.innerHTML = originalDownloadHTML;
}, 1200);
});
}
function processMessages() {
document.querySelectorAll('[data-sentry-component="ChatMessage"]').forEach(wrapMessage);
}
function addStickyCopyButtons() {
document.querySelectorAll('pre > code').forEach((codeEl) => {
if (codeEl.closest('.__code-wrapper')) return;
const preEl = codeEl.parentElement;
const wrapper = document.createElement('div');
wrapper.classList.add('__code-wrapper');
preEl.parentNode.insertBefore(wrapper, preEl);
const copyContainer = document.createElement('div');
copyContainer.classList.add('__copy-container');
const btn = document.createElement('button');
btn.classList.add('__copy-btn');
btn.innerText = 'Copy';
copyContainer.appendChild(btn);
wrapper.appendChild(copyContainer);
wrapper.appendChild(preEl);
btn.addEventListener('click', async () => {
const text = codeEl.innerText;
try {
await navigator.clipboard.writeText(text);
btn.innerText = 'Copied!';
setTimeout(() => { btn.innerText = 'Copy'; }, 1200);
} catch {
btn.innerText = 'Error';
setTimeout(() => { btn.innerText = 'Copy'; }, 1200);
}
});
});
}
processMessages();
addStickyCopyButtons();
const observer = new MutationObserver(() => {
processMessages();
addStickyCopyButtons();
});
observer.observe(document.body, { childList: true, subtree: true });
})();