Skip to content

Instantly share code, notes, and snippets.

@un4ckn0wl3z
Created December 19, 2025 03:10
Show Gist options
  • Select an option

  • Save un4ckn0wl3z/0c2b5b4fe359d8c92f41d038e7c1e030 to your computer and use it in GitHub Desktop.

Select an option

Save un4ckn0wl3z/0c2b5b4fe359d8c92f41d038e7c1e030 to your computer and use it in GitHub Desktop.

Revisions

  1. un4ckn0wl3z created this gist Dec 19, 2025.
    148 changes: 148 additions & 0 deletions CGL.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,148 @@
    // ==UserScript==
    // @name Ctrl Drag Crop + Auto Scroll (Anchored)
    // @namespace https://un4ckn0wl3z.dev/tm/crop-links-scroll-fixed
    // @version 1.3
    // @description Ctrl + drag to crop, auto-scroll with anchored start point
    // @match *://*/*
    // @grant GM_setClipboard
    // ==/UserScript==

    (function () {
    'use strict';

    let overlay, box;
    let startX, startY_doc;
    let selecting = false;
    let scrollTimer = null;

    const EDGE = 40;
    const SPEED = 20;
    const INTERVAL = 16;

    function createOverlay() {
    overlay = document.createElement('div');
    overlay.style.cssText = `
    position: fixed;
    inset: 0;
    z-index: 999999;
    cursor: crosshair;
    background: rgba(0,0,0,0.05);
    `;

    box = document.createElement('div');
    box.style.cssText = `
    position: absolute;
    border: 2px dashed red;
    background: rgba(255,0,0,0.15);
    pointer-events: none;
    `;

    overlay.appendChild(box);
    document.body.appendChild(overlay);
    }

    function destroyOverlay() {
    stopScroll();
    overlay?.remove();
    overlay = null;
    box = null;
    }

    function intersect(a, b) {
    return !(
    b.right < a.left ||
    b.left > a.right ||
    b.bottom < a.top ||
    b.top > a.bottom
    );
    }

    function extractLinks(rect) {
    return [...document.querySelectorAll('a')]
    .filter(a => intersect(rect, a.getBoundingClientRect()))
    .map(a => ({
    href: a.href,
    text: a.innerText.trim()
    }));
    }

    function startScroll(dir) {
    if (scrollTimer) return;
    scrollTimer = setInterval(() => {
    window.scrollBy(0, dir * SPEED);
    }, INTERVAL);
    }

    function stopScroll() {
    clearInterval(scrollTimer);
    scrollTimer = null;
    }

    document.addEventListener('mousedown', e => {
    if (!e.ctrlKey || e.button !== 0) return;

    e.preventDefault();
    selecting = true;

    startX = e.clientX;
    startY_doc = e.clientY + window.scrollY;

    createOverlay();
    });

    document.addEventListener('mousemove', e => {
    if (!selecting) return;

    const curX = e.clientX;
    const curY_doc = e.clientY + window.scrollY;

    const left = Math.min(startX, curX);
    const right = Math.max(startX, curX);
    const top_doc = Math.min(startY_doc, curY_doc);
    const bottom_doc = Math.max(startY_doc, curY_doc);

    // Convert document Y back to viewport Y
    box.style.left = `${left}px`;
    box.style.top = `${top_doc - window.scrollY}px`;
    box.style.width = `${right - left}px`;
    box.style.height = `${bottom_doc - top_doc}px`;

    // Auto scroll
    if (e.clientY > window.innerHeight - EDGE) {
    startScroll(1);
    } else if (e.clientY < EDGE) {
    startScroll(-1);
    } else {
    stopScroll();
    }
    });

    document.addEventListener('mouseup', e => {
    if (!selecting) return;

    selecting = false;
    stopScroll();

    const endY_doc = e.clientY + window.scrollY;

    const rect = {
    left: Math.min(startX, e.clientX),
    right: Math.max(startX, e.clientX),
    top: Math.min(startY_doc, endY_doc) - window.scrollY,
    bottom: Math.max(startY_doc, endY_doc) - window.scrollY
    };

    const links = extractLinks(rect);
    const output = links
    //.map(l => `${l.href}${l.text ? ' | ' + l.text : ''}`)
    .map(l => `${l.href}`)
    .join('\n');

    GM_setClipboard(output);
    console.log('Extracted links:', links);
    alert(`Extracted ${links.length} links (copied)`);

    destroyOverlay();
    });

    })();