Tweetdeck gallery

Allows navigating through tweets in tweetdeck by keyboard shorcuts. Also makes ctrl c copy the link of the currently loaded tweet.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Tweetdeck gallery
// @namespace    https://github.com/argit2/tweetdeck-gallery
// @version      1.0
// @description  Allows navigating through tweets in tweetdeck by keyboard shorcuts. Also makes ctrl c copy the link of the currently loaded tweet.
// @author       You
// @match        https://tweetdeck.twitter.com/
// @grant        GM_setClipboard
// ==/UserScript==

/*
Usage

Click somewhere in the page, type the number of the column you wanna see, and use letters j and k to navigate between media tweets

Ctrl c when a tweet is open copies the link

*/

var firstTweet = true;
var currentColumn = 1;
var currentTweet = null;
var nextTweet = null; // used as a safeguard in some scenarios where the currentTweet is lost
const preemptiveLoad = 3; // if only this amount of tweets remaining to see, will attempt to load more

function removeAllChildren (elem) {
  while (elem.lastElementChild) {
    elem.removeChild(elem.lastElementChild);
  }
}

function doOnceLoaded(selector, func) {
    let interv = setInterval(function() {
        if (document.querySelector(selector)) {
            func();
            clearInterval(interv);
        }
    }, 500); // check every 100ms
}

function playVideo()
{
    let vid = document.querySelector("video")
    if (vid) {
        vid.play();
    }
}

function setAutoplay() {
    let node = document.querySelector("div#open-modal");
    new MutationObserver(playVideo).observe(node, { childList: true});
}

function gatherTweets() {
    return Array.filter(Array.from(document.querySelectorAll(`.js-column:nth-child(${currentColumn}) .js-column-holder article`)), elemExists);
}

function gatherMediaTweets() {
    let elements = gatherTweets();
    let mediaTweets = Array.filter(elements, elem => {
        let linkElem = mediaLinkElem(elem);
        if (linkElem) {
            let link = linkElem.href;
            if (link.includes("t.co")) {
                return true;
            }
        }
        return false;
    });
    return mediaTweets;
}

function loadNextTweets() {
    let elements = gatherTweets();
    if (elements) {
        // goes up then down
        elements[elements.length - 2].scrollIntoView(false);
        elements[elements.length - 1].scrollIntoView(false);
    }
}

// doesn't really serve for anything lmfao
function loadPrevTweets() {
    let elements = gatherTweets();
    if (elements) {
        // goes down then up
        elements[1].scrollIntoView(false);
        elements[0].scrollIntoView(false);
    }
}

function elemExists(elem)
{
    return elem && elem.style.visibility != "hidden" && elem.style.display !== "none";
}

function getCurrentTweet () {
    if (! currentTweet) {
        resetCurrentTweet();
    }
    return currentTweet;
}

function getRealCurrentTweet() {
    // gathers the one in the page instead of the clone
}

function setCurrentTweet (mediaTweets, tweet) {
    if (!tweet) {
        currentTweet = null;
        nextTweet = null;
        return;
    }
    currentTweet = tweet;
    let index = findIndexMediaTweet(mediaTweets, tweet);
    let next = getMediaTweet(mediaTweets, index + 1)
    if (next) {
        nextTweet = next;
    }
    else {
        nextTweet = null;
    }
}

function getMediaTweet(mediaTweets, index) {
    if (index < 0 || index >= mediaTweets.length)
    {
        return null;
    }
    return mediaTweets[index];
}

function resetCurrentTweet () {
    let mediaTweets = gatherMediaTweets();
    if (mediaTweets) {
        setCurrentTweet(mediaTweets, mediaTweets[0]);
        firstTweet = true;
    }
    else {
        print("Error: no mediaTweets on current column");
    }
}

function mediaLinkElem (tweet) {
    let elem = tweet.querySelector("a.media-item");
    if (!elem) {
            elem = tweet.querySelector("a.media-image");
    }
    return elem;
}

function showCurrentTweet () {
    let current = getCurrentTweet();
    let linkElem = mediaLinkElem(current);
    if (linkElem) {
        //linkElem.scrollIntoView(false);
        linkElem.click();
    }
}

function findIndexMediaTweet(arr, tweet) {
    return arr.findIndex(x => {
        let elem1 = mediaLinkElem(x);
        let elem2 = mediaLinkElem(tweet);
        return elem1 && elem2 && elem1.href == elem2.href;
    });
}

function currentTweetLost(mediaTweets, current){
    console.log("Error: currentTweet lost. This is probably due to it being unloaded as the script scrolls down without being able to find media posts. Resetting value to first visible media post.");
    console.log("Lost tweet:", current);
    console.log("Visible media tweets:", mediaTweets);
    setCurrentTweet(mediaTweets, mediaTweets[0]);
    showCurrentTweet();
}

function showNextTweet () {
    let current = getCurrentTweet();
    let mediaTweets = gatherMediaTweets();
    let index = 0;
    let indexToShow = 0;
    if (! firstTweet) {
        index = findIndexMediaTweet(mediaTweets, current);
        indexToShow = index + 1;
    }

    if (index == -1) {
        if (! nextTweet) {
            currentTweetLost(mediaTweets, current);
            return;
        }
        console.log(currentTweet, nextTweet);
        setCurrentTweet(mediaTweets, nextTweet);
        showCurrentTweet();
    }
    else if (indexToShow < mediaTweets.length) {
       setCurrentTweet(mediaTweets, mediaTweets[indexToShow]);
       showCurrentTweet();
    }
    // atempts to load more even if it's not the last
    if (indexToShow >= mediaTweets.length - preemptiveLoad) {
        loadNextTweets();
    }
    firstTweet = false;
}

function showPreviousTweet () {
    let current = getCurrentTweet();
    let mediaTweets = gatherMediaTweets();
    let index = findIndexMediaTweet(mediaTweets, current);
    if (index == -1) {
        currentTweetLost(mediaTweets, current);
        return;
    }
    if (index >= 1) {
       setCurrentTweet(mediaTweets, mediaTweets[index - 1]);
       showCurrentTweet();
    }
    if (index - 1 <= preemptiveLoad) {
        loadPrevTweets();
    }
    firstTweet = false;
}

function setCurrentColumn (colNumber) {
    currentColumn = colNumber;
    resetCurrentTweet();
}


function doc_keyUp(e) {
    let x = e.keyCode;
    if (x >= 49 && x <= 57){
            // numbers
            setCurrentColumn(x - 48);
    }

    if (x == ctrlKey) {
        ctrlDown = false;
    }

    switch (x) {
        case cKey:
            if (ctrlDown) {
                copyOpenTweetLink();
            }
            break;
        case jKey:
            showNextTweet();
            break;
        case kKey:
            showPreviousTweet();
            break;
        default:
            break;
    }
}

function doc_keyDown (e) {
    if (e.keyCode == ctrlKey) {
        ctrlDown = true;
    }
}

function copyOpenTweetLink() {
    let elem = document.querySelector("div#open-modal .tweet-timestamp a");
    if (elem && elem.href) {
        GM_setClipboard(elem.href);
    }
}

const ctrlKey = 17,
      cKey = 67,
      jKey = 74,
      kKey = 75;
var ctrlDown = false;
doOnceLoaded("div#open-modal", setAutoplay);
document.addEventListener('keyup', doc_keyUp, false);
document.addEventListener('keydown', doc_keyDown, false);