X.com Timeline Auto-Load with Uninterrupted Reading

Automatically loads new posts on X.com while keeping the reading position intact. Sets a virtual marker at the last visible handler (e.g., @username) before loading new posts and restores the view to this marker.

当前为 2024-11-18 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         X.com Timeline Auto-Load with Uninterrupted Reading
// @name:de      X.com Timeline Auto-Load mit unterbrechungsfreiem Lesen
// @name:fr      X.com Timeline Auto-Load avec lecture ininterrompue
// @name:es      Carga automática de la línea de tiempo de X.com con lectura sin interrupciones
// @name:it      Caricamento automatico della timeline di X.com con lettura ininterrotta
// @name:zh      X.com 时间线自动加载,无缝阅读
// @name:ja      X.com タイムライン自動読み込みと中断のない読書
// @namespace    http://tampermonkey.net/
// @version      4.3
// @description  Automatically loads new posts on X.com while keeping the reading position intact. Sets a virtual marker at the last visible handler (e.g., @username) before loading new posts and restores the view to this marker.
// @description:de Lädt automatisch neue Beiträge auf X.com, ohne die Leseposition zu verlieren. Setzt eine virtuelle Markierung am letzten sichtbaren Handler (z. B. @Benutzername) vor dem Laden neuer Beiträge und stellt die Ansicht zu dieser Markierung wieder her.
// @description:fr Charge automatiquement les nouveaux messages sur X.com tout en conservant la position de lecture. Place un marqueur virtuel au dernier handle visible (par exemple, @nomutilisateur) avant de charger les nouveaux messages et restaure la vue à ce marqueur.
// @description:es Carga automáticamente nuevos posts en X.com mientras mantiene la posición de lectura intacta. Coloca un marcador virtual en el último manejador visible (por ejemplo, @nombredeusuario) antes de cargar nuevos posts y restaura la vista a ese marcador.
// @description:it Carica automaticamente nuovi post su X.com mantenendo intatta la posizione di lettura. Imposta un segnalibro virtuale sull'ultimo handle visibile (es. @nomeutente) prima di caricare nuovi post e ripristina la vista su quel segnalibro.
// @description:zh 在X.com上自动加载新帖子,同时保持阅读位置不变。在加载新帖子之前,在最后一个可见的处理器(例如@用户名)处设置一个虚拟标记,并将视图恢复到该标记。
// @description:ja X.comで新しい投稿を自動的に読み込み、読書位置をそのまま保持します。新しい投稿を読み込む前に、最後に見えるハンドル(例:@ユーザー名)に仮想マーカーを設定し、このマーカーにビューを復元します。
// @icon         https://drive.google.com/file/d/1MaHF2JyG7qv6NDe6T2qoDDKNjiYOXx_o/view?usp=sharing
// @author       Copiis
// @match        https://x.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @license      MIT
// @contributionURL https://paypal.me/Coopiis?country.x=DE&locale.x=de_DE
// @contributionAmount $5
// ==/UserScript==

(function () {
    'use strict';

    let markerId = "last-visible-handler-marker";

    // Funktion: Markierung beim letzten sichtbaren Handler setzen
    function setMarker() {
        const handlers = Array.from(document.querySelectorAll('span')).filter(
            (span) => span.textContent.startsWith("@")
        );

        if (handlers.length > 0) {
            const lastHandler = handlers[handlers.length - 1]; // Letzter sichtbarer Handler

            // Existierende Markierung entfernen
            removeMarker();

            // Markierung erstellen
            const marker = document.createElement('div');
            marker.id = markerId;

            const rect = lastHandler.getBoundingClientRect();
            const markerTop = window.scrollY + rect.bottom; // Unterhalb des letzten Handlers

            marker.style.position = 'absolute';
            marker.style.top = `${markerTop}px`;
            marker.style.width = '1px';
            marker.style.height = '1px';
            marker.style.backgroundColor = 'transparent'; // Unsichtbar
            marker.style.zIndex = '-1';
            document.body.appendChild(marker);

            console.log(`Markierung gesetzt bei Y-Position: ${markerTop}`);
        } else {
            console.warn("Kein sichtbarer Handler gefunden, Markierung konnte nicht gesetzt werden.");
        }
    }

    // Funktion: Markierung entfernen
    function removeMarker() {
        const existingMarker = document.getElementById(markerId);
        if (existingMarker) {
            existingMarker.remove();
            console.log("Markierung entfernt.");
        }
    }

    // Funktion: Zur Markierung scrollen
    function scrollToMarker() {
        const marker = document.getElementById(markerId);
        if (marker) {
            const markerTop = parseInt(marker.style.top, 10); // Y-Position der Markierung
            window.scrollTo({
                top: markerTop,
                behavior: 'smooth' // Sanft scrollen
            });

            // Verifiziere das Scrollen
            setTimeout(() => {
                if (Math.abs(window.scrollY - markerTop) <= 5) {
                    console.log("Erfolgreich zur Markierung gescrollt:", markerTop);
                } else {
                    console.warn(
                        `Scrollen zur Markierung ungenau. Erwartet: ${markerTop}, Tatsächlich: ${window.scrollY}`
                    );
                }
            }, 500); // Warte kurz, um die Scrollposition zu überprüfen

            removeMarker(); // Markierung nach Verwendung löschen
        } else {
            console.warn("Keine Markierung gefunden, konnte nicht scrollen.");
        }
    }

    // Funktion: Klick auf die "Neue Beiträge anzeigen"-Schaltfläche
    function clickNewPostsButton() {
        const button = Array.from(document.querySelectorAll('span')).find(
            (span) => span.textContent.includes("neue Posts anzeigen") || span.textContent.includes("Post anzeigen")
        );

        if (button) {
            const parentButton = button.closest('button');
            if (parentButton) {
                console.log(`Schaltfläche "${button.textContent}" gefunden, wird geklickt...`);
                setMarker(); // Markierung setzen vor dem Laden neuer Beiträge
                parentButton.click();

                // Starte Scrollen nach einer kurzen Verzögerung
                setTimeout(scrollToMarker, 1000);
            } else {
                console.warn("Elternelement der Schaltfläche nicht gefunden.");
            }
        } else {
            console.warn("Keine Schaltfläche für neue Beiträge gefunden.");
        }
    }

    // Funktion: DOM-Änderungen überwachen
    function observeNewPostsButton() {
        const targetNode = document.body;
        const config = { childList: true, subtree: true };

        const callback = function (mutationsList) {
            for (const mutation of mutationsList) {
                if (mutation.type === 'childList') {
                    clickNewPostsButton(); // Versuche die Schaltfläche zu klicken
                }
            }
        };

        const observer = new MutationObserver(callback);
        observer.observe(targetNode, config);

        console.log("MutationObserver gestartet, um die Schaltfläche zu überwachen.");
    }

    // Initialisieren
    function init() {
        console.log("Skript zur Überprüfung neuer Beiträge aktiviert...");
        observeNewPostsButton();
    }

    init();
})();