Google Docs Preserve Indent

Preserve indent on alt+enter

  1. // ==UserScript==
  2. // @name Google Docs Preserve Indent
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0
  5. // @description Preserve indent on alt+enter
  6. // @match https://docs.google.com/*
  7. // @license MIT
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. (function() {
  12. 'use strict';
  13.  
  14. // --- utility to simulate mouse events ---
  15. function simulateMouseEvent(type, x, y) {
  16. const evt = new MouseEvent(type, {
  17. bubbles: true,
  18. cancelable: true,
  19. view: window,
  20. clientX: x,
  21. clientY: y,
  22. button: 0
  23. });
  24. document.elementFromPoint(x, y)?.dispatchEvent(evt);
  25. }
  26.  
  27. // --- find the indent-end handle ---
  28. function getIndentHandle() {
  29. return document.querySelector(
  30. '#kix-horizontal-ruler .docs-ruler-inner > div:nth-child(6) .docs-ruler-indent-end'
  31. );
  32. }
  33.  
  34. // --- send a plain Enter keypress into the editor iframe ---
  35. function sendPlainEnter() {
  36. const iframe = document.querySelector('iframe.docs-texteventtarget-iframe');
  37. const win = iframe?.contentWindow || window;
  38. const target = iframe?.contentDocument?.body || document.body;
  39. const down = new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true });
  40. const up = new KeyboardEvent('keyup', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true });
  41. target.dispatchEvent(down);
  42. target.dispatchEvent(up);
  43. }
  44.  
  45. // --- main preserve-indent routine ---
  46. function preserveIndent() {
  47. const handle = getIndentHandle();
  48. if (!handle) return;
  49. // capture the old "left" in px
  50. const initialLeft = parseFloat(getComputedStyle(handle).left);
  51.  
  52. // insert new line
  53. sendPlainEnter();
  54.  
  55. // after the new line is created, the indent handle moves;
  56. // wait a moment then drag it back
  57. setTimeout(() => {
  58. const h2 = getIndentHandle();
  59. if (!h2) return;
  60. // compute how much the handle has shifted
  61. const newLeft = parseFloat(getComputedStyle(h2).left);
  62. const delta = initialLeft - newLeft;
  63.  
  64. // center of the little handle glyph
  65. const rect2 = h2.getBoundingClientRect();
  66. const originX = rect2.left + rect2.width / 2;
  67. const originY = rect2.top + rect2.height / 2;
  68. const targetX = originX + delta;
  69.  
  70. simulateMouseEvent('mousedown', originX, originY);
  71. simulateMouseEvent('mousemove', targetX, originY);
  72. simulateMouseEvent('mouseup', targetX, originY);
  73. }, 100);
  74. }
  75.  
  76. // --- intercept Alt+Enter ---
  77. function handleKeydown(e) {
  78. if (e.altKey && !e.ctrlKey && !e.shiftKey && e.key === 'Enter') {
  79. e.preventDefault();
  80. e.stopImmediatePropagation();
  81. preserveIndent();
  82. }
  83. }
  84.  
  85. // --- attach to both top window and the editor iframe ---
  86. function attachListeners() {
  87. // top‐level (for menus, etc.)
  88. window.addEventListener('keydown', handleKeydown, true);
  89.  
  90. // inside the document editing iframe
  91. const iframe = document.querySelector('iframe.docs-texteventtarget-iframe');
  92. if (iframe) {
  93. const doc = iframe.contentDocument || iframe.contentWindow.document;
  94. doc.addEventListener('keydown', handleKeydown, true);
  95. } else {
  96. // retry if iframe not yet present
  97. setTimeout(attachListeners, 500);
  98. }
  99. }
  100.  
  101. // wait for overall load
  102. window.addEventListener('load', attachListeners);
  103. })();