Google Docs Preserve Indent

Preserve indent on alt+enter

// ==UserScript==
// @name         Google Docs Preserve Indent
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Preserve indent on alt+enter
// @match        https://docs.google.com/*
// @license      MIT
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // --- utility to simulate mouse events ---
    function simulateMouseEvent(type, x, y) {
        const evt = new MouseEvent(type, {
            bubbles: true,
            cancelable: true,
            view: window,
            clientX: x,
            clientY: y,
            button: 0
        });
        document.elementFromPoint(x, y)?.dispatchEvent(evt);
    }

    // --- find the indent-end handle ---
    function getIndentHandle() {
        return document.querySelector(
            '#kix-horizontal-ruler .docs-ruler-inner > div:nth-child(6) .docs-ruler-indent-end'
        );
    }

    // --- send a plain Enter keypress into the editor iframe ---
    function sendPlainEnter() {
        const iframe = document.querySelector('iframe.docs-texteventtarget-iframe');
        const win = iframe?.contentWindow || window;
        const target = iframe?.contentDocument?.body || document.body;
        const down = new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true });
        const up   = new KeyboardEvent('keyup',   { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true });
        target.dispatchEvent(down);
        target.dispatchEvent(up);
    }

    // --- main preserve-indent routine ---
    function preserveIndent() {
        const handle = getIndentHandle();
        if (!handle) return;
        // capture the old "left" in px
        const initialLeft = parseFloat(getComputedStyle(handle).left);

        // insert new line
        sendPlainEnter();

        // after the new line is created, the indent handle moves;
        // wait a moment then drag it back
        setTimeout(() => {
            const h2 = getIndentHandle();
            if (!h2) return;
// compute how much the handle has shifted
            const newLeft = parseFloat(getComputedStyle(h2).left);
            const delta   = initialLeft - newLeft;

            // center of the little handle glyph
            const rect2   = h2.getBoundingClientRect();
            const originX = rect2.left + rect2.width  / 2;
            const originY = rect2.top  + rect2.height / 2;
            const targetX = originX + delta;

            simulateMouseEvent('mousedown', originX, originY);
            simulateMouseEvent('mousemove', targetX, originY);
            simulateMouseEvent('mouseup',   targetX, originY);
        }, 100);
    }

    // --- intercept Alt+Enter ---
    function handleKeydown(e) {
        if (e.altKey && !e.ctrlKey && !e.shiftKey && e.key === 'Enter') {
            e.preventDefault();
            e.stopImmediatePropagation();
            preserveIndent();
        }
    }

    // --- attach to both top window and the editor iframe ---
    function attachListeners() {
        // top‐level (for menus, etc.)
        window.addEventListener('keydown', handleKeydown, true);

        // inside the document editing iframe
        const iframe = document.querySelector('iframe.docs-texteventtarget-iframe');
        if (iframe) {
            const doc = iframe.contentDocument || iframe.contentWindow.document;
            doc.addEventListener('keydown', handleKeydown, true);
        } else {
            // retry if iframe not yet present
            setTimeout(attachListeners, 500);
        }
    }

    // wait for overall load
    window.addEventListener('load', attachListeners);
})();