From 49689c8225dbb0ecfc25ce787561c1024b8f5aa5 Mon Sep 17 00:00:00 2001 From: byte <byteturtle@byteturtle.eu> Date: Sun, 9 Mar 2025 11:57:49 +0100 Subject: [PATCH] initial commit, added caret-read-progress.js --- README.md | 1 + caret-read-progress.js | 123 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 README.md create mode 100644 caret-read-progress.js diff --git a/README.md b/README.md new file mode 100644 index 0000000..b281815 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +* `caret-read-progress.js` - highlights the line/position while reading a text - move back/forth with left/right arrows. diff --git a/caret-read-progress.js b/caret-read-progress.js new file mode 100644 index 0000000..fe6a556 --- /dev/null +++ b/caret-read-progress.js @@ -0,0 +1,123 @@ +// ==UserScript== +// @name Caret Read Progress +// @namespace http://tampermonkey.net/ +// @version 1.5 +// @description Moves caret with arrow keys, highlights read text, works across paragraphs and divs +// @author You +// @match *://*/* +// @grant none +// ==/UserScript== + +(function () { + 'use strict'; + + let highlightDiv = document.createElement("div"); + highlightDiv.style.position = "absolute"; + highlightDiv.style.background = "rgba(255, 255, 0, 0.3)"; // Yellow highlight + highlightDiv.style.height = "1.5em"; + highlightDiv.style.pointerEvents = "none"; + highlightDiv.style.zIndex = "9999"; + highlightDiv.style.width = "0px"; // Start with no highlight + document.body.appendChild(highlightDiv); + + document.addEventListener("keydown", function (event) { + if (event.key !== "ArrowRight" && event.key !== "ArrowLeft") return; + + let sel = window.getSelection(); + if (!sel.rangeCount) return; + + let range = sel.getRangeAt(0); + let moved = moveCaret(event.key === "ArrowRight" ? 1 : -1); + + if (moved) updateHighlight(); + }); + + function moveCaret(direction) { + let sel = window.getSelection(); + if (!sel.rangeCount) return false; + + let range = sel.getRangeAt(0); + let node = range.startContainer; + let offset = range.startOffset; + + // Move within the same text node + if (direction === 1 && offset < node.length) { + offset++; + } else if (direction === -1 && offset > 0) { + offset--; + } + // If at the end of a node, move to the next text node + else if (direction === 1) { + let nextNode = findNextTextNode(node); + if (nextNode) { + node = nextNode; + offset = 0; + } else { + return false; + } + } + // If at the start of a node, move to the previous text node + else if (direction === -1) { + let prevNode = findPreviousTextNode(node); + if (prevNode) { + node = prevNode; + offset = prevNode.length; + } else { + return false; + } + } + + // Update selection range + let newRange = document.createRange(); + newRange.setStart(node, offset); + newRange.collapse(true); + sel.removeAllRanges(); + sel.addRange(newRange); + + return true; + } + + function findNextTextNode(node) { + while (node) { + if (node.nextSibling) { + node = node.nextSibling; + while (node && node.nodeType !== Node.TEXT_NODE) { + node = node.firstChild || node.nextSibling; + } + return node; + } + node = node.parentNode; + } + return null; + } + + function findPreviousTextNode(node) { + while (node) { + if (node.previousSibling) { + node = node.previousSibling; + while (node && node.nodeType !== Node.TEXT_NODE) { + node = node.lastChild || node.previousSibling; + } + return node; + } + node = node.parentNode; + } + return null; + } + + function updateHighlight() { + let sel = window.getSelection(); + if (!sel.rangeCount) return; + + let range = sel.getRangeAt(0); + let rect = range.getBoundingClientRect(); + + if (rect.width === 0 && rect.height === 0) { + return; // Ignore empty selections + } + + highlightDiv.style.top = `${rect.top + window.scrollY}px`; + highlightDiv.style.left = `0px`; // Always start from the left + highlightDiv.style.width = `${rect.right}px`; // Expand highlight only to where you've read + } +})();