adding to gitea repo

This commit is contained in:
Lucca Pirovano
2026-06-15 14:20:38 -04:00
commit cd7a2bd773
21 changed files with 1994 additions and 0 deletions
@@ -0,0 +1,213 @@
// ==UserScript==
// @name Submit Click (F12) + Copy CURRENT Visible Question (F10)
// @namespace vm-submit-click-and-copy-visible-text
// @match https://*netacad.com/*
// @require https://cdn.jsdelivr.net/npm/@violentmonkey/shortcut@1
// @grant none
// @version 2.0
// @run-at document-end
// ==/UserScript==
(function () {
'use strict';
// ────────────────────────────────────────────────
// Submit button click (F12) unchanged
// ────────────────────────────────────────────────
function findSubmitButton() {
let btns = document.querySelectorAll('button[aria-label="submit"].submit-button');
for (const btn of btns) {
const innerDiv = btn.querySelector('div.submit');
if (innerDiv && innerDiv.textContent.trim() === 'Submit') return btn;
}
function searchShadows(root) {
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
let node;
while ((node = walker.nextNode())) {
if (node.shadowRoot) {
const shadowBtns = node.shadowRoot.querySelectorAll('button[aria-label="submit"].submit-button');
for (const btn of shadowBtns) {
const innerDiv = btn.querySelector('div.submit');
if (innerDiv && innerDiv.textContent.trim() === 'Submit') return btn;
}
const found = searchShadows(node.shadowRoot);
if (found) return found;
}
}
return null;
}
return searchShadows(document.body);
}
function triggerSubmitClick() {
const btn = findSubmitButton();
if (!btn) return false;
try { btn.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window })); } catch {}
if (btn.click) try { btn.click(); } catch {}
return true;
}
// ────────────────────────────────────────────────
// Copy CURRENT visible question text (F10) improved
// ────────────────────────────────────────────────
function isVisibleAndInView(el) {
if (!el) return false;
const style = window.getComputedStyle(el);
if (style.display === 'none' || style.visibility === 'hidden' || parseFloat(style.opacity) < 0.1) return false;
let current = el;
let depth = 0;
while (current && depth < 15) {
const s = window.getComputedStyle(current);
if (s.display === 'none' || s.visibility === 'hidden' || parseFloat(s.opacity) < 0.1) return false;
current = current.parentElement;
depth++;
}
const rect = el.getBoundingClientRect();
if (rect.width < 50 || rect.height < 20) return false; // too tiny for question text
// Roughly in viewport
return rect.top > -50 && rect.bottom < (window.innerHeight + 50) &&
rect.left > -50 && rect.right < (window.innerWidth + 50);
}
function findCurrentQuestionSpan() {
console.log('[TextCopy] Searching for CURRENT visible question (strict mode)...');
const containerSelectors = [
'[class*="body-inner"]',
'.mcq__body-inner',
'.component__body-inner',
'[class*="mcq__body"]',
'[class*="component__body"]'
];
const spanSelectors = ['p span', 'p > span', 'span'];
let candidates = [];
// Light DOM
containerSelectors.forEach(sel => {
document.querySelectorAll(sel).forEach(container => {
if (!isVisibleAndInView(container)) return;
spanSelectors.forEach(sSel => {
container.querySelectorAll(sSel).forEach(span => {
const txt = span.textContent?.trim();
if (!txt || txt.length < 20) return;
if (!isVisibleAndInView(span)) return;
const rect = span.getBoundingClientRect();
const area = rect.width * rect.height;
const z = parseFloat(window.getComputedStyle(span).zIndex) || 0;
const hasQuestionMark = txt.includes('?');
candidates.push({
span,
txt,
area,
zIndex: isNaN(z) ? 0 : z,
hasQuestionMark,
depth: span.compareDocumentPosition(document.body) // rough DOM order
});
});
});
});
});
// Shadow DOM
function walkShadows(root) {
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
let node;
while ((node = walker.nextNode())) {
if (node.shadowRoot) {
containerSelectors.forEach(sel => {
node.shadowRoot.querySelectorAll(sel).forEach(container => {
if (!isVisibleAndInView(container)) return;
spanSelectors.forEach(sSel => {
container.querySelectorAll(sSel).forEach(span => {
const txt = span.textContent?.trim();
if (!txt || txt.length < 20) return;
if (!isVisibleAndInView(span)) return;
const rect = span.getBoundingClientRect();
const area = rect.width * rect.height;
const z = parseFloat(window.getComputedStyle(span).zIndex) || 0;
const hasQuestionMark = txt.includes('?');
candidates.push({
span,
txt,
area,
zIndex: isNaN(z) ? 0 : z,
hasQuestionMark,
depth: span.compareDocumentPosition(document.body)
});
});
});
});
});
walkShadows(node.shadowRoot);
}
}
}
walkShadows(document.body);
if (candidates.length === 0) {
console.log('[TextCopy] No visible candidates found');
return null;
}
// Sort: prefer question mark → higher z-index → larger area → earlier in DOM
candidates.sort((a, b) => {
if (a.hasQuestionMark !== b.hasQuestionMark) return b.hasQuestionMark - a.hasQuestionMark;
if (a.zIndex !== b.zIndex) return b.zIndex - a.zIndex;
if (a.area !== b.area) return b.area - a.area;
return a.depth - b.depth;
});
const best = candidates[0];
console.log('[TextCopy] Selected:', best.txt.substring(0, 120) + (best.txt.length > 120 ? '...' : ''));
console.log('[TextCopy] Stats → area:', best.area, '| z-index:', best.zIndex, '| has ?:', best.hasQuestionMark, '| candidates total:', candidates.length);
return best.span;
}
async function copyCurrentQuestion() {
const el = findCurrentQuestionSpan();
if (!el) return;
const text = el.textContent?.trim() || '';
if (!text) return;
console.log('[TextCopy] Copying:', text.substring(0, 120) + (text.length > 120 ? '...' : ''));
try {
await navigator.clipboard.writeText(text);
console.log('[TextCopy] SUCCESS current question copied');
} catch (err) {
console.error('[TextCopy] Clipboard failed:', err);
const ta = document.createElement('textarea');
ta.value = text;
ta.style.position = 'fixed'; ta.style.opacity = '0';
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
document.body.removeChild(ta);
console.log('[TextCopy] Fallback copy done');
}
}
// ────────────────────────────────────────────────
// Hotkeys
// ────────────────────────────────────────────────
VM.shortcut.register('f12', () => {
setTimeout(() => { if (!triggerSubmitClick()) setTimeout(triggerSubmitClick, 400); }, 60);
});
VM.shortcut.register('f10', copyCurrentQuestion);
console.log('[VM-Tools] Loaded v2.0 → F12 = Submit | F10 = Copy CURRENT visible question (strict visibility)');
})();