TwiShell

Enhance Twitter Web with lots of features.

Tính đến 21-01-2015. Xem phiên bản mới nhất.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

Bạn sẽ cần cài đặt một tiện ích mở rộng như Tampermonkey hoặc Violentmonkey để cài đặt kịch bản này.

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

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

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

You will need to install a user script manager extension to install this script.

(Tôi đã có Trình quản lý tập lệnh người dùng, hãy cài đặt nó!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name TwiShell
// @namespace TwiShell
// @description Enhance Twitter Web with lots of features.
// @match http://twitter.com/*
// @match https://twitter.com/*
// @version 3.16
// @run-at document-start
// ==/UserScript==

//noinspection ThisExpressionReferencesGlobalObjectJS
(function (window) {
    'use strict';
    var document = window.document;

    var ELEMENT_NODE = 1,
        TEXT_NODE = 3;

    var click = function (node) {
        var event = document.createEvent("Event");
        event.initEvent("click", true, true);
        node.dispatchEvent(event);
    };

    var hasClass = function (node, cls) {
        return node.classList && node.classList.contains(cls);
    };

    var addClass = function (node, cls) {
        node.classList.add(cls);
    };

    var removeClass = function (node, cls) {
        node.classList.remove(cls);
    };

    var toggleClass = function (node, cls) {
        node.classList.toggle(cls);
    };

    var wantsMoreTimelineItems = function () {
        var event = document.createEvent("Event");
        event.initEvent("uiNearTheBottom", true, true);
        document.dispatchEvent(event);
    };

    // throttle function call in specified interval
    var throttle = function (fn, interval) {
        var fnTimer;
        var repeatCalling = false;
        return function wrapper() {
            if (!fnTimer) {
                fn();
                fnTimer = setTimeout(function () {
                    fnTimer = null;
                    if (repeatCalling) {
                        repeatCalling = false;
                        wrapper();
                    }
                }, interval);
            }
            else {
                repeatCalling = true;
            }
        };
    };

    var globalDialog,
        retweetDialog;

    var getOriginalTweetText = function (tweetNode) {
        var originalTweetText = Array.prototype.reduce.call(
            tweetNode.querySelector(".js-tweet-text").childNodes,
            function (acc, childNode) {
                var text;
                if (childNode.nodeType === TEXT_NODE) {
                    text = childNode.textContent;
                }
                else if (childNode.nodeType === ELEMENT_NODE) {
                    text = childNode.getAttribute("data-expanded-url") || childNode.textContent;
                }
                if (text) {
                    acc.push(text);
                }
                return acc;
            },
            []
        ).join("");
        var screenName = tweetNode.getAttribute("data-screen-name");
        return "RT @" + screenName + ": " + originalTweetText.split("\n").map(function (s) {
                return "<div>" + s + "</div>";
            }).join("");
    };

    var hideRetweetDialog = function () {
        click(document.querySelector("#retweet-tweet-dialog .modal-close"));
    };

    var showGlobalTweetDialog = function () {
        globalDialog.querySelector(".draggable").setAttribute(
            "style",
            retweetDialog.querySelector(".draggable").getAttribute("style")
        );
        click(document.getElementById("global-new-tweet-button"));
        globalDialog.querySelector(".modal-title").innerHTML =
            retweetDialog.querySelector(".modal-title").innerHTML;
    };

    var prepareRT = function (tweetNode) {
        hideRetweetDialog();
        showGlobalTweetDialog();
        var text = getOriginalTweetText(tweetNode);
        fillInTweetBox(text);
    };

    var fillInTweetBox = function (text) {
        var tweetBox = globalDialog.querySelector(".tweet-box"),
            range = document.createRange(),
            selection = window.getSelection();

        tweetBox.innerHTML = text;
        tweetBox.focus();
        // Place cursor in the front.
        range.selectNodeContents(tweetBox);
        range.collapse(true);
        selection.removeAllRanges();
        selection.addRange(range);
    };

    var replaceCancelButton = function () {
        if (!retweetDialog) { // sometimes it will become null
            return;
        }
        var btnCancel = retweetDialog.querySelector(".cancel-action");
        addClass(btnCancel, "rt-action");
        removeClass(btnCancel, "cancel-action");
        btnCancel.innerHTML = "RT";
        btnCancel.addEventListener("click", function () {
            prepareRT(retweetDialog.querySelector(".tweet"));
        }, false);
    };

    var isAttachedMap = Object.create(null);
    var attachProtectedTweet = function () {
        Array.prototype.forEach.call(document.querySelectorAll(".tweet[data-protected=true]"), function (protectedTweet) {
            var itemId = protectedTweet.getAttribute("data-item-id");
            if (!(isAttachedMap[itemId])) {
                isAttachedMap[itemId] = true;
                var rtBtn = protectedTweet.querySelector(".js-toggleRt button");
                rtBtn.removeAttribute("disabled");
                removeClass(rtBtn, "is-disabled");
                removeClass(rtBtn, "js-disableTweetAction");
                rtBtn.addEventListener("click", function (evt) {
                    if (evt.button == 2) { // Ignore right-clicks
                        return;
                    }
                    evt.stopPropagation();
                    evt.preventDefault();
                    for (var tweet = evt.target.parentNode; !(hasClass(tweet, "tweet")); tweet = tweet.parentNode) {}
                    prepareRT(tweet);
                }, false);
            }
        });
    };

    var tcoMatcher = /^http(?:s)?:\/\/t\.co\/[0-9A-Za-z]+$/i;
    var expandAllUrl = function () {
        Array.prototype.forEach.call(
            document.querySelectorAll("a.twitter-timeline-link:not(.url-expanded)"),
            function (tlLink) {
                var expandedUrl = tlLink.getAttribute("data-expanded-url");
                if (expandedUrl && tcoMatcher.test(tlLink.getAttribute("href"))) {
                    tlLink.setAttribute("href", expandedUrl);
                }
                addClass(tlLink, "url-expanded");
            }
        );
    };

    var removeLangAttr = function () {
        Array.prototype.forEach.call(document.querySelectorAll("p.js-tweet-text[lang]"), function (el) {
            el.removeAttribute("lang");
        });
    };

    document.addEventListener("DOMContentLoaded", function () {
        globalDialog = document.getElementById("global-tweet-dialog");
        retweetDialog = document.getElementById("retweet-tweet-dialog");
        replaceCancelButton();
    }, false);

    var styles = [
        "@media screen {",
        ".content-main, .profile-page-header {float: left !important;}",
        "body.three-col .wrapper {width: 900px !important;}",
        "body.three-col .content-main {min-height: 700px;}",
        ".dashboard {float: right !important;}",
        "#suggested-users {clear: none !important;}",
        "}"
    ].join("");

    var addStyle = function (css) {
        var node = document.createElement("style");
        node.type = "text/css";
        node.appendChild(document.createTextNode(css));
        document.documentElement.appendChild(node);
        node = null;
    };

    addStyle(styles);

    var throttledExpandUrl = throttle(expandAllUrl, 100);
    var throttledAttachProtectedTweet = throttle(attachProtectedTweet, 100);
    var throttledRemoveLangAttr = throttle(removeLangAttr, 100);
    new MutationObserver(function (mutations) {
        mutations.forEach(function (mutation) {
            throttledExpandUrl();
            throttledAttachProtectedTweet();
            throttledRemoveLangAttr();
        });
    }).observe(document, {
            childList: true,
            subtree: true
        });
})(this);