Greasy Fork is available in English.

TwiShell

Enhance Twitter Web with lots of features.

질문, 리뷰하거나, 이 스크립트를 신고하세요.
  1. // ==UserScript==
  2. // @name TwiShell
  3. // @namespace TwiShell
  4. // @description Enhance Twitter Web with lots of features.
  5. // @match http://twitter.com/*
  6. // @match https://twitter.com/*
  7. // @version 3.17
  8. // @run-at document-start
  9. // ==/UserScript==
  10.  
  11. //noinspection ThisExpressionReferencesGlobalObjectJS
  12. (function (window) {
  13. 'use strict';
  14. var document = window.document;
  15.  
  16. var ELEMENT_NODE = 1,
  17. TEXT_NODE = 3;
  18.  
  19. var click = function (node) {
  20. var event = document.createEvent("Event");
  21. event.initEvent("click", true, true);
  22. node.dispatchEvent(event);
  23. };
  24.  
  25. var hasClass = function (node, cls) {
  26. return node.classList && node.classList.contains(cls);
  27. };
  28.  
  29. var addClass = function (node, cls) {
  30. node.classList.add(cls);
  31. };
  32.  
  33. var removeClass = function (node, cls) {
  34. node.classList.remove(cls);
  35. };
  36.  
  37. var toggleClass = function (node, cls) {
  38. node.classList.toggle(cls);
  39. };
  40.  
  41. var wantsMoreTimelineItems = function () {
  42. var event = document.createEvent("Event");
  43. event.initEvent("uiNearTheBottom", true, true);
  44. document.dispatchEvent(event);
  45. };
  46.  
  47. // throttle function call in specified interval
  48. var throttle = function (fn, interval) {
  49. var fnTimer;
  50. var repeatCalling = false;
  51. return function wrapper() {
  52. if (!fnTimer) {
  53. fn();
  54. fnTimer = setTimeout(function () {
  55. fnTimer = null;
  56. if (repeatCalling) {
  57. repeatCalling = false;
  58. wrapper();
  59. }
  60. }, interval);
  61. }
  62. else {
  63. repeatCalling = true;
  64. }
  65. };
  66. };
  67.  
  68. var globalDialog,
  69. retweetDialog;
  70.  
  71. var getOriginalTweetText = function (tweetNode) {
  72. var originalTweetText = Array.prototype.reduce.call(
  73. tweetNode.querySelector(".js-tweet-text").childNodes,
  74. function (acc, childNode) {
  75. var text;
  76. if (childNode.nodeType === TEXT_NODE) {
  77. text = childNode.textContent;
  78. }
  79. else if (childNode.nodeType === ELEMENT_NODE) {
  80. text = childNode.getAttribute("data-expanded-url") || childNode.textContent;
  81. }
  82. if (text) {
  83. acc.push(text);
  84. }
  85. return acc;
  86. },
  87. []
  88. ).join("");
  89. var screenName = tweetNode.getAttribute("data-screen-name");
  90. return "RT @" + screenName + ": " + originalTweetText.split("\n").map(function (s) {
  91. return "<div>" + s + "</div>";
  92. }).join("");
  93. };
  94.  
  95. var hideRetweetDialog = function () {
  96. click(document.querySelector("#retweet-tweet-dialog .modal-close"));
  97. };
  98.  
  99. var showGlobalTweetDialog = function () {
  100. globalDialog.querySelector(".draggable").setAttribute(
  101. "style",
  102. retweetDialog.querySelector(".draggable").getAttribute("style")
  103. );
  104. click(document.getElementById("global-new-tweet-button"));
  105. globalDialog.querySelector(".modal-title").innerHTML =
  106. retweetDialog.querySelector(".modal-title").innerHTML;
  107. };
  108.  
  109. var prepareRT = function (tweetNode) {
  110. hideRetweetDialog();
  111. showGlobalTweetDialog();
  112. var text = getOriginalTweetText(tweetNode);
  113. fillInTweetBox(text);
  114. };
  115.  
  116. var fillInTweetBox = function (text) {
  117. var tweetBox = globalDialog.querySelector(".tweet-box"),
  118. range = document.createRange(),
  119. selection = window.getSelection();
  120.  
  121. tweetBox.innerHTML = text;
  122. tweetBox.focus();
  123. // Place cursor in the front.
  124. range.selectNodeContents(tweetBox);
  125. range.collapse(true);
  126. selection.removeAllRanges();
  127. selection.addRange(range);
  128. };
  129.  
  130. var replaceCancelButton = function () {
  131. if (!retweetDialog) { // sometimes it will become null
  132. return;
  133. }
  134. var btnCancel = retweetDialog.querySelector(".cancel-action");
  135. addClass(btnCancel, "rt-action");
  136. removeClass(btnCancel, "cancel-action");
  137. btnCancel.innerHTML = "RT";
  138. btnCancel.addEventListener("click", function () {
  139. prepareRT(retweetDialog.querySelector(".tweet"));
  140. }, false);
  141. };
  142.  
  143. var isAttachedMap = Object.create(null);
  144. var attachProtectedTweet = function () {
  145. Array.prototype.forEach.call(document.querySelectorAll(".tweet[data-protected=true]"), function (protectedTweet) {
  146. var itemId = protectedTweet.getAttribute("data-item-id");
  147. if (!(isAttachedMap[itemId])) {
  148. isAttachedMap[itemId] = true;
  149. var rtBtn = protectedTweet.querySelector(".js-toggleRt button");
  150. rtBtn.removeAttribute("disabled");
  151. removeClass(rtBtn, "is-disabled");
  152. removeClass(rtBtn, "js-disableTweetAction");
  153. rtBtn.addEventListener("click", function (evt) {
  154. if (evt.button == 2) { // Ignore right-clicks
  155. return;
  156. }
  157. evt.stopPropagation();
  158. evt.preventDefault();
  159. for (var tweet = evt.target.parentNode; !(hasClass(tweet, "tweet")); tweet = tweet.parentNode) {}
  160. prepareRT(tweet);
  161. }, false);
  162. }
  163. });
  164. };
  165.  
  166. var tcoMatcher = /^http(?:s)?:\/\/t\.co\/[0-9A-Za-z]+$/i;
  167. var expandAllUrl = function () {
  168. Array.prototype.forEach.call(
  169. document.querySelectorAll("a.twitter-timeline-link:not(.url-expanded)"),
  170. function (tlLink) {
  171. var expandedUrl = tlLink.getAttribute("data-expanded-url");
  172. if (expandedUrl && tcoMatcher.test(tlLink.getAttribute("href"))) {
  173. tlLink.setAttribute("href", expandedUrl);
  174. }
  175. addClass(tlLink, "url-expanded");
  176. }
  177. );
  178. };
  179.  
  180. var removeLangAttr = function () {
  181. Array.prototype.forEach.call(document.querySelectorAll("p.js-tweet-text[lang]"), function (el) {
  182. el.removeAttribute("lang");
  183. });
  184. };
  185.  
  186. document.addEventListener("DOMContentLoaded", function () {
  187. globalDialog = document.getElementById("global-tweet-dialog");
  188. retweetDialog = document.getElementById("retweet-tweet-dialog");
  189. replaceCancelButton();
  190. }, false);
  191.  
  192. var styles = [
  193. "@media screen {",
  194. ".content-main, .profile-page-header {float: left !important;}",
  195. "body.three-col .wrapper {width: 900px !important;}",
  196. "body.three-col .content-main {min-height: 700px;}",
  197. "body.three-col .permalink-container {width: 900px !important;}",
  198. ".dashboard {float: right !important;}",
  199. "#suggested-users {clear: none !important;}",
  200. "}"
  201. ].join("");
  202.  
  203. var addStyle = function (css) {
  204. var node = document.createElement("style");
  205. node.type = "text/css";
  206. node.appendChild(document.createTextNode(css));
  207. document.documentElement.appendChild(node);
  208. node = null;
  209. };
  210.  
  211. addStyle(styles);
  212.  
  213. var throttledExpandUrl = throttle(expandAllUrl, 100);
  214. var throttledAttachProtectedTweet = throttle(attachProtectedTweet, 100);
  215. var throttledRemoveLangAttr = throttle(removeLangAttr, 100);
  216. new MutationObserver(function (mutations) {
  217. mutations.forEach(function (mutation) {
  218. throttledExpandUrl();
  219. throttledAttachProtectedTweet();
  220. throttledRemoveLangAttr();
  221. });
  222. }).observe(document, {
  223. childList: true,
  224. subtree: true
  225. });
  226. })(this);