Tweetdeck gallery

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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);