// ==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)'); })();