Robin Enhancement Script

Highlight mentions, make links clickable, add tabbed channels & automatically remove spam

  1. // ==UserScript==
  2. // @name Robin Enhancement Script
  3. // @namespace https://www.reddit.com/
  4. // @version 3.3.5
  5. // @description Highlight mentions, make links clickable, add tabbed channels & automatically remove spam
  6. // @author Bag, netnerd01
  7. // @match https://www.reddit.com/robin*
  8. // @grant none
  9. // @grant GM_setValue
  10. // @grant GM_getValue
  11. // ==/UserScript==
  12. (function() {
  13.  
  14. // Grab users username + play nice with RES
  15. var robin_user = $("#header-bottom-right .user a").first().text().toLowerCase();
  16. var ignored_users = {};
  17.  
  18. // for spam counter - very important i know :P
  19. var blocked_spam_el = null;
  20. var blocked_spam = 0;
  21.  
  22. // via RobinEggs
  23. var messageHistory = [];
  24. var messageHistoryIndex = -1;
  25. var _robin_grow_detected = false;
  26.  
  27. var colors = [
  28. 'rgba(255,0,0,0.1)',
  29. 'rgba(0,255,0,0.1)',
  30. 'rgba(0,0,255,0.1)',
  31. 'rgba(0,255,255,0.1)',
  32. 'rgba(255,0,255,0.1)',
  33. 'rgba(255,255,0,0.1)',
  34. 'rgba(211,211,211, .1)',
  35. 'rgba(0,100,0, .1)',
  36. 'rgba(255,20,147, .1)',
  37. 'rgba(184,134,11, .1)',
  38. ];
  39.  
  40.  
  41. // Play nice with Greasemonkey
  42. if(typeof GM_getValue === "undefined") GM_getValue = function(){return false;};
  43. if(typeof GM_setValue === "undefined") GM_setValue = function(){return false;};
  44.  
  45. /**
  46. * Pull tabber out in to semi-stand alone module
  47. * Big thanks to netnerd01 for his pre-work on this
  48. *
  49. * Basic usage - tabbedChannels.init( dom_node_to_add_tabs_to );
  50. * and hook up tabbedChannels.proccessLine(lower_case_text, jquery_of_line_container); to each line detected by the system
  51. */
  52. var tabbedChannels = new function(){
  53. var _self = this;
  54.  
  55. // Default options
  56. this.channels = ["~","*",".","%","$","#",";","^","<3",":gov","#rpg","@"];
  57. this.mode = 'single';
  58.  
  59. // internals
  60. this.unread_counts = {};
  61. this.$el = null;
  62. this.$opt = null;
  63. this.defaultRoomClasses = '';
  64. this.channelMatchingCache = [];
  65.  
  66. //channels user is in currently
  67. this.currentRooms = 0;
  68.  
  69. // When channel is clicked, toggle it on or off
  70. this.toggle_channel = function(e){
  71. var channel = $(e.target).data("filter");
  72. if(channel===null)return; // no a channel
  73.  
  74. if(!$("#robinChatWindow").hasClass("robin-filter-" + channel)){
  75. _self.enable_channel(channel);
  76. $(e.target).addClass("selected");
  77. // clear unread counter
  78. $(e.target).find("span").text(0);
  79. _self.unread_counts[channel] = 0;
  80. }else{
  81. _self.disable_channel(channel);
  82. $(e.target).removeClass("selected");
  83. }
  84.  
  85. // scroll everything correctly
  86. _scroll_to_bottom();
  87. };
  88.  
  89. // Enable a channel
  90. this.enable_channel = function(channel_id){
  91.  
  92. // if using room type "single", deslect other rooms on change
  93. if(this.mode == "single"){
  94. this.disable_all_channels();
  95. }
  96.  
  97. $("#robinChatWindow").addClass("robin-filter robin-filter-" + channel_id);
  98. $("#robinChatWindow").attr("data-channel-key", this.channels[channel_id]);
  99. this.currentRooms++;
  100. // unselect show all
  101. _self.$el.find("span.all").removeClass("selected");
  102. };
  103.  
  104. // disable a channel
  105. this.disable_channel = function(channel_id){
  106. $("#robinChatWindow").removeClass("robin-filter-" + channel_id);
  107. this.currentRooms--;
  108.  
  109. // no rooms selcted, run "show all"
  110. if(this.currentRooms == 0){
  111. this.disable_all_channels();
  112. }else{
  113. // Grab next channel name if u leave a room in multi mode
  114. $("#robinChatWindow").attr("data-channel-key", $(".robin-filters span.selected").first().data("filter-name"));
  115. }
  116. };
  117.  
  118. // turn all channels off
  119. this.disable_all_channels = function(e){
  120. $("#robinChatWindow").attr("class", _self.defaultRoomClasses).attr("data-channel-key","");
  121. _self.$el.find(".robin-filters > span").removeClass("selected");
  122. this.currentRooms = 0;
  123.  
  124. _self.$el.find("span.all").addClass("selected");
  125. _scroll_to_bottom();
  126. };
  127.  
  128. // render tabs
  129. this.drawTabs = function(){
  130. html = '';
  131. for(var i in this.channels){
  132. if(typeof this.channels[i] === 'undefined') continue;
  133. html += '<span data-filter="' + i + '" data-filter-name="'+ this.channels[i] +'">' + this.channels[i] + ' (<span>0</span>)</span> ';
  134. }
  135. this.$el.find(".robin-filters").html(html);
  136. };
  137.  
  138. // After creation of a new channel, go find if any content (not matched by a channel already) is relevant
  139. this.reScanChannels = function(new_channel){
  140. $("#robinChatWindow").find("div.robin-message").each(function(idx,item){
  141. var line = $(item).find(".robin-message--message").text().toLowerCase();
  142. tabbedChannels.proccessLine(line, $(item), true);
  143. });
  144. }
  145.  
  146. // Add new channel
  147. this.addChannel = function(new_channel){
  148. if(this.channels.indexOf(new_channel) === -1){
  149. this.channels.push(new_channel);
  150. this.unread_counts[this.channels.length-1] = 0;
  151. this.updateChannelMatchCache();
  152. this.saveChannelList();
  153. this.drawTabs();
  154.  
  155. // Populate content for channel
  156. this.reScanChannels();
  157.  
  158. // refresh everything after redraw
  159. this.disable_all_channels();
  160. }
  161. };
  162.  
  163. // remove existing channel
  164. this.removeChannel = function(channel){
  165. if(confirm("are you sure you wish to remove the " + channel + " channel?")){
  166. var idx = this.channels.indexOf(channel);
  167. delete this.channels[idx];
  168. this.updateChannelMatchCache();
  169. this.saveChannelList();
  170. this.drawTabs();
  171.  
  172. // sub channels, will fall back to existing channels
  173. this.reScanChannels();
  174.  
  175. // refresh everything after redraw
  176. this.disable_all_channels();
  177. }
  178. };
  179.  
  180.  
  181. // save channel list
  182. this.saveChannelList = function(){
  183. // clean array before save
  184. var channels = this.channels.filter(function (item) { return item != undefined });
  185. GM_setValue("robin-enhance-channels", channels);
  186. };
  187.  
  188. // Change chat mode
  189. this.changeChannelMode = function(e){
  190. _self.mode = $(this).data("type");
  191.  
  192. // swicth bolding
  193. $(this).parent().find("span").css("font-weight","normal");
  194. $(this).css("font-weight","bold");
  195. _self.disable_all_channels();
  196.  
  197. // Update mode setting
  198. GM_setValue("robin-enhance-mode", _self.mode);
  199. };
  200.  
  201. this.updateChannelMatchCache = function(){
  202. var order = this.channels.slice(0);
  203. order.sort(function(a, b){
  204. return b.length - a.length; // ASC -> a - b; DESC -> b - a
  205. });
  206. for(var i in order){
  207. order[i] = this.channels.indexOf(order[i]);
  208. }
  209. // sorted array of channel name indexs
  210.  
  211. this.channelMatchingCache = order;
  212. }
  213.  
  214. // Procces each chat line to create text
  215. this.proccessLine = function(text, $element, rescan){
  216. var i, idx, channel;
  217.  
  218. // If rescanning, clear any existing "channel" classes
  219. if(typeof rescan !== 'undefined' && rescan === true){
  220. $element.removeClass("in-channel");
  221.  
  222. for(i=0; i <= this.channels.length; i++){
  223. $element.removeClass("robin-filter-" + i);
  224. }
  225. }
  226.  
  227. // Scann for channel identifiers
  228. for(i=0; i< this.channelMatchingCache.length; i++){ // sorted so longer get picked out before shorter ones (sub channel matching)
  229. idx = this.channelMatchingCache[i];
  230. channel = this.channels[idx];
  231.  
  232. if(typeof channel === 'undefined') continue;
  233.  
  234. if(text.indexOf(channel) === 0){
  235. $element.addClass("robin-filter-" + idx +" in-channel");
  236. this.unread_counts[idx]++;
  237. return;
  238. }
  239. }
  240. };
  241.  
  242. // If in one channel, auto add channel keys
  243. this.submit_helper = function(){
  244. if($("#robinChatWindow").hasClass("robin-filter")){
  245. // auto add channel key
  246. var channel_key = $("#robinChatWindow").attr("data-channel-key");
  247.  
  248. if($(".text-counter-input").val().indexOf("/me") === 0){
  249. $(".text-counter-input").val("/me " + channel_key + " " + $(".text-counter-input").val().substr(3));
  250. }else if($(".text-counter-input").val().indexOf("/") !== 0){
  251. // if its not a "/" command, add channel
  252. $(".text-counter-input").val(channel_key + " " + $(".text-counter-input").val());
  253. }
  254. }
  255. };
  256.  
  257. // Update everuything
  258. this.tick = function(){
  259. _self.$el.find(".robin-filters span").each(function(){
  260. if($(this).hasClass("selected")) return;
  261. $(this).find("span").text(_self.unread_counts[$(this).data("filter")]);
  262. });
  263. };
  264.  
  265. // Init tab zone
  266. this.init = function($el){
  267. // Load channels
  268. if(GM_getValue("robin-enhance-channels")){
  269. this.channels = GM_getValue("robin-enhance-channels");
  270. }
  271. if(GM_getValue("robin-enhance-mode")){
  272. this.mode = GM_getValue("robin-enhance-mode");
  273. }
  274.  
  275. // init counters
  276. for(var i in this.channels){
  277. this.unread_counts[i] = 0;
  278. }
  279.  
  280. // update channel cache
  281. this.updateChannelMatchCache();
  282.  
  283. // set up el
  284. this.$el = $el;
  285.  
  286. // Create inital markup
  287. this.$el.html("<span class='all selected'>Everything</span><span><div class='robin-filters'></div></span><span class='more'>[Options]</span>");
  288. this.$opt = $("<div class='robin-channel-add' style='display:none'><input name='add-channel'><button>Add channel</button> <span class='channel-mode'>Channel Mode: <span title='View one channel at a time' data-type='single'>Single</span> | <span title='View many channels at once' data-type='multi'>Multi</span></span></div>").insertAfter(this.$el);
  289.  
  290. // Attach events
  291. this.$el.find(".robin-filters").click(this.toggle_channel);
  292. this.$el.find("span.all").click(this.disable_all_channels);
  293. this.$el.find("span.more").click(function(){ $(".robin-channel-add").slideToggle(); });
  294. this.$el.find(".robin-filters").bind("contextmenu", function(e){
  295. e.preventDefault();
  296. e.stopPropagation();
  297. var chan_id = $(e.target).data("filter");
  298. if(chan_id===null)return; // no a channel
  299. _self.removeChannel(_self.channels[chan_id]);
  300. });
  301. // Form events
  302. this.$opt.find(".channel-mode span").click(this.changeChannelMode);
  303. this.$opt.find("button").click(function(){
  304. var new_chan = _self.$opt.find("input[name='add-channel']").val();
  305. if(new_chan != '') _self.addChannel(new_chan);
  306. _self.$opt.find("input[name='add-channel']").val('');
  307. });
  308.  
  309. $("#robinSendMessage").submit(this.submit_helper);
  310. // store default room class
  311. this.defaultRoomClasses = $("#robinChatWindow").attr("class");
  312.  
  313. // redraw tabs
  314. this.drawTabs();
  315.  
  316. // start ticker
  317. setInterval(this.tick, 1000);
  318. }
  319. };
  320.  
  321. /**
  322. * Check if a message is "spam"
  323. */
  324. var is_spam = function(line){
  325. return (
  326. // Hide auto vote messages
  327. (/^voted to (grow|stay|abandon)/.test(line)) ||
  328. // random unicode?
  329. (/[\u0080-\uFFFF]/.test(line)) ||
  330. // hide any auto voter messages
  331. (/\[.*autovoter.*\]/.test(line)) ||
  332. // Common bots
  333. (/^(\[binbot\]|\[robin-grow\])/.test(line)) ||
  334. // repeating chars in line (more than 5). e.g. aaaaaaa !!!!!!!!
  335. (/(.)\1{5,}/.test(line)) ||
  336. // Some common messages
  337. (/(voting will end in approximately|\[i spam the most used phrase\]|\[message from creator\]|\[.*bot.*\])/.test(line)) ||
  338. // no spaces = spam if its longer than 25 chars (dont filter links)
  339. (line.indexOf(" ") === -1 && line.length > 25 && line.indexOf("http") === -1) ||
  340. // repeating same word
  341. /(\b\S+\b)\s+\b\1\b/i.test(line)
  342. );
  343. };
  344.  
  345. /**
  346. * Check if a message is from an ignored user
  347. *
  348. */
  349. var is_ignored = function($usr, $ele){
  350. // no user name, go looking for when said it
  351. if($usr.length === 0){
  352. while($usr.length === 0){
  353. $ele = $ele.prev();
  354. $usr = $ele.find(".robin--username");
  355. }
  356. }
  357. // are they ignored?
  358. return (ignored_users[$usr.text()]);
  359. };
  360.  
  361. /**
  362. * Make links clickable
  363. *
  364. */
  365. var auto_link = function($msg){
  366. var text = $msg.html(); // read as html so stuff stays escaped
  367. // normal links
  368. text = text.replace(/\b(?:https?|ftp):\/\/[a-z0-9-+&@#\/%?=~_|!:,.;]*[a-z0-9-+&@#\/%=~_|]/gim, '<a target="blank" href="$&">$&</a>');
  369.  
  370. // reddit subreddit links
  371. text = text.replace(/\s+\/r\/(\w+)\/?/gi, ' <a target="blank" href="https://reddit.com/r/$1">/r/$1</a>');
  372. text = text.replace(/\s+\/u\/(\w+)\/?/gi, ' <a target="blank" href="https://reddit.com/u/$1">/r/$1</a>');
  373.  
  374. // update text
  375. $msg.html(text);
  376. };
  377.  
  378. /**
  379. * Mute a user
  380. */
  381. var _mute_user = function(usr){
  382. // Add to ignore list
  383. ignored_users[usr] = true;
  384. _render_muted_list();
  385. };
  386.  
  387. /**
  388. * un-mute a user
  389. */
  390. var _unmute_user = function(usr){
  391. // Add to ignore list
  392. delete ignored_users[usr];
  393. _render_muted_list();
  394. };
  395.  
  396. // Render list of ignored users
  397. var _render_muted_list = function(){
  398. var html = "<strong>Ignored users</strong><br>";
  399. for(var u in ignored_users){
  400. html += "<div data-usr='"+ u + "'>" + u + " - [unmute]</div>";
  401. }
  402. $("#muted_users").html(html);
  403. };
  404.  
  405. // Scroll chat back to bottom
  406. var _scroll_to_bottom = function(){
  407. $("#robinChatWindow").scrollTop($("#robinChatMessageList").height());
  408. };
  409.  
  410. // create persistant option
  411. function createOption(name, click_action, default_state){
  412. var checked_markup;
  413. var key = "robin-enhance-" + name.replace(/\W/g, '');
  414. var state = (typeof default_state !== "undefined") ? default_state : false;
  415.  
  416. // try and state if setting is defined
  417. if(GM_getValue(key)){
  418. state = (GM_getValue(key) === 'true') ? true : false;
  419. }
  420. // markup for state
  421. checked_markup = (state === true) ? "checked='checked'" : "";
  422. // render option
  423. var $option = $("<label><input type='checkbox' "+checked_markup+">"+name+"</label>").click(function(){
  424. var checked = $(this).find("input").is(':checked');
  425.  
  426. // persist state
  427. if(checked != state){
  428. GM_setValue(key, checked ? 'true' : 'false'); // true/false stored as strings, to avoid unset matching
  429. state = checked;
  430. }
  431.  
  432. click_action(checked, $(this));
  433. });
  434. // add to dom
  435. $("#robinDesktopNotifier").append($option);
  436. // init
  437. click_action(state, $option)
  438. };
  439.  
  440. // update spam count
  441. var update_spam_count = function(){
  442. blocked_spam++;
  443. blocked_spam_el.innerHTML = blocked_spam;
  444. };
  445.  
  446. // when name is clicked, fill it into the chat box
  447. var fill_name = function(e){
  448. e.preventDefault();
  449. e.stopPropagation();
  450.  
  451. // if text area blank, prefill name. if not, stick it on the end
  452. if($(".text-counter-input").val() === ''){
  453. $(".text-counter-input").val($(this).text() + ' ').focus();
  454. }else{
  455. $(".text-counter-input").val($(".text-counter-input").val() + ' ' + $(this).text()).focus();
  456. }
  457. };
  458.  
  459. // remove channel key from message
  460. var remove_channel_key_from_message = function(message){
  461. if($("#robinChatWindow").attr("data-channel-key")){
  462. var offset = $("#robinChatWindow").attr("data-channel-key").length;
  463. if(offset === 0) return message;
  464.  
  465. if(message.indexOf("/me") === 0){
  466. return "/me "+ message.slice(offset+5);
  467. }else{
  468. return message.slice(offset+1);
  469. }
  470. }
  471. return message;
  472. }
  473.  
  474. /**
  475. * Parse a link and apply changes
  476. */
  477. var parse_line = function($ele){
  478. var $msg = $ele.find(".robin-message--message");
  479. var $usr = $ele.find(".robin--username");
  480. var line = $msg.text().toLowerCase();
  481.  
  482. // dont parse system messages
  483. if($ele.hasClass("robin--user-class--system")){
  484. if(line.indexOf("ratelimit | you are doing that too much") !== -1){
  485. $(".text-counter-input").val(messageHistory[messageHistoryIndex-1]);
  486. }
  487. return;
  488. }
  489.  
  490. // If user is ignored or message looks like "Spam". hide it
  491. if (is_ignored($usr, $ele) || is_spam(line)) {
  492. $ele.addClass("spam-hidden");
  493. update_spam_count();
  494. }
  495.  
  496. // Highlight mentions
  497. if(line.indexOf(robin_user) !== -1){
  498. $ele.addClass("user-mention");
  499. }
  500.  
  501. // Make links clickable
  502. if(!_robin_grow_detected && (line.indexOf("http") !== -1 || line.indexOf("/r/") !== -1 || line.indexOf("/u/") !== -1)){
  503. auto_link($msg);
  504. }
  505.  
  506. // Add mute button to users
  507. if(!$ele.hasClass("robin--user-class--system") && $usr.text().toLowerCase() != robin_user){
  508. $("<span style='font-size:.8em;cursor:pointer'> [mute] </span>").insertBefore($usr).click(function(){
  509. _mute_user($usr.text());
  510. });
  511. }
  512.  
  513. // Track channels
  514. tabbedChannels.proccessLine(line, $ele);
  515.  
  516. // bind click to use (override other click events if we can)
  517. $usr.bindFirst("click", fill_name);
  518. };
  519.  
  520.  
  521. // Detect changes, are parse the new message
  522. $("#robinChatWindow").on('DOMNodeInserted', function(e) {
  523. if ($(e.target).is('div.robin-message')) {
  524. // Apply changes to line
  525. parse_line($(e.target));
  526. }
  527. });
  528.  
  529. // When everything is ready
  530. $(document).ready(function(){
  531.  
  532. // Set default spam filter type
  533. $("#robinChatWindow").addClass("hide-spam");
  534.  
  535. createOption("Hide spam completely (<span id='spamcount'>0</span> removed)", function(checked, ele){
  536. if(checked){
  537. $("#robinChat").removeClass("mute-spam").addClass("hide-spam");
  538. }else{
  539. $("#robinChat").removeClass("hide-spam").addClass("mute-spam");
  540. }
  541. // correct scroll after spam filter change
  542. _scroll_to_bottom();
  543. },true);
  544.  
  545. createOption("Use channel colors", function(checked, ele){
  546. if(checked){
  547. $("#robinChat").addClass("show-colors");
  548. }else{
  549. $("#robinChat").removeClass("show-colors");
  550. }
  551. // correct scroll after spam filter change
  552. _scroll_to_bottom();
  553. },false);
  554.  
  555.  
  556.  
  557. blocked_spam_el = $("#spamcount")[0];
  558.  
  559. // Add Muted list & hook up unmute logic
  560. $('<div id="muted_users" class="robin-chat--sidebar-widget robin-chat--notification-widget"><strong>Ignored users</strong></div>').insertAfter($("#robinDesktopNotifier"));
  561. $('#muted_users').click(function(e){
  562. var user = $(e.target).data("usr");
  563. if(user) _unmute_user(user);
  564. });
  565.  
  566. // Init tabbed channels
  567. tabbedChannels.init($('<div id="filter_tabs"></div>').insertAfter("#robinChatWindow"));
  568.  
  569. // store i copy of last message, in case somthing goes wrong (rate limit)
  570. $("#robinSendMessage").submit(function(){
  571. var user_last_message = $(".text-counter-input").val();
  572.  
  573. // if message history is to long, clear it out
  574. if(messageHistory.length === 25){
  575. messageHistory = messageHistory.shift();
  576. }
  577. messageHistory.push(remove_channel_key_from_message(user_last_message));
  578. messageHistoryIndex = messageHistory.length;
  579. });
  580.  
  581. // up for last message send, down for prev (if moving between em)
  582. $('input.text-counter-input').on('keydown', function(e) {
  583. if(e.keyCode == 38) {
  584. e.preventDefault();
  585. messageHistoryIndex--;
  586. if(messageHistoryIndex > -1){
  587. $(this).val(messageHistory[messageHistoryIndex]);
  588. }
  589. }else if(e.keyCode == 40){
  590. e.preventDefault();
  591. if(messageHistoryIndex <= messageHistory.length){
  592. messageHistoryIndex++;
  593. $(this).val(messageHistory[messageHistoryIndex]);
  594. }else{
  595. $(this).val('');
  596. }
  597. }
  598. });
  599. });
  600.  
  601. // fix by netnerd01
  602. var stylesheet = document.createElement('style');
  603. document.head.appendChild(stylesheet);
  604. stylesheet = stylesheet.sheet;
  605.  
  606. // filter for channel
  607. stylesheet.insertRule("#robinChatWindow.robin-filter div.robin-message { display:none; }", 0);
  608. stylesheet.insertRule("#robinChatWindow.robin-filter div.robin-message.robin--user-class--system { display:block; }", 0);
  609. var color;
  610. for(var c=0;c<35;c++){
  611. color = colors[(c % (colors.length))];
  612.  
  613. stylesheet.insertRule("#robinChat.show-colors #robinChatWindow div.robin-message.robin-filter-"+c+" { background: "+color+";}", 0);
  614. stylesheet.insertRule("#robinChatWindow.robin-filter.robin-filter-"+c+" div.robin-message.robin-filter-"+c+" { display:block;}", 0);
  615. }
  616.  
  617. // Styles for filter tabs
  618. stylesheet.insertRule("#filter_tabs {width:100%; display: table; table-layout: fixed; background:#d7d7d2; border-bottom:1px solid #efefed;}",0);
  619. stylesheet.insertRule("#filter_tabs > span {width:90%; display: table-cell;}",0);
  620. stylesheet.insertRule("#filter_tabs > span.all, #filter_tabs > span.more {width:60px; text-align:center; vertical-align:middle; cursor:pointer;}",0);
  621. stylesheet.insertRule("#filter_tabs > span.all.selected, #filter_tabs > span.all.selected:hover {background: #fff;}", 0);
  622. stylesheet.insertRule("#filter_tabs .robin-filters { display: table; width:100%;table-layout: fixed; '}", 0);
  623. stylesheet.insertRule("#filter_tabs .robin-filters > span { padding: 5px 2px;text-align: center; display: table-cell; cursor: pointer;width:2%; vertical-align: middle; font-size: 1.1em;}", 0);
  624. stylesheet.insertRule("#filter_tabs .robin-filters > span.selected, #filter_tabs .robin-filters > span:hover { background: #fff;}", 0);
  625. stylesheet.insertRule("#filter_tabs .robin-filters > span > span {pointer-events: none;}", 0);
  626.  
  627. stylesheet.insertRule(".robin-channel-add {padding:5px; display:none;}", 0);
  628. stylesheet.insertRule(".robin-channel-add input {padding: 2.5px; }", 0);
  629. stylesheet.insertRule(".robin-channel-add .channel-mode {float:right; font-size:1.2em;padding:5px;}", 0);
  630. stylesheet.insertRule(".robin-channel-add .channel-mode span {cursor:pointer}", 0);
  631. //mentions should show even in filter view
  632. stylesheet.insertRule("#robinChat #robinChatWindow div.robin-message.user-mention { display:block; font-weight:bold; }", 0);
  633.  
  634. // Add initial styles for "spam" messages
  635. stylesheet.insertRule("#robinChat.hide-spam #robinChatWindow div.robin-message.spam-hidden { display:none; }", 0);
  636. stylesheet.insertRule("#robinChat.mute-spam #robinChatWindow div.robin-message.spam-hidden { opacity:0.3; font-size:1.2em; }", 0);
  637. stylesheet.insertRule("#robinChat.show-colors #robinChatWindow div.robin-message.spam-hidden { opacity:0.3; font-size:1.2em; }", 0);
  638. // muted user box
  639. stylesheet.insertRule("#muted_users { font-size:1.2em; }", 0);
  640. stylesheet.insertRule("#muted_users div { padding: 2px 0; }", 0);
  641. stylesheet.insertRule("#muted_users strong { font-weight:bold; }", 0);
  642.  
  643. // FIX RES nightmode (ish) [ by Kei ]
  644. stylesheet.insertRule(".res-nightmode #robinChatWindow div.robin-message { color: #ccc; }", 0);
  645. stylesheet.insertRule(".res-nightmode .robin-chat--sidebar-widget { background: #222; color: #ccc;}", 0);
  646. stylesheet.insertRule(".res-nightmode .robin-room-participant { background: #222; color: #999;}", 0);
  647. stylesheet.insertRule(".res-nightmode #filter_tabs {background: rgb(51, 51, 51);}", 0);
  648. stylesheet.insertRule(".res-nightmode #filter_tabs .robin-filters > span.selected,.res-nightmode #filter_tabs .robin-filters > span:hover,.res-nightmode #filter_tabs > span.all.selected,.res-nightmode #filter_tabs > span.all:hover {background: rgb(34, 34, 34)}", 0);
  649. stylesheet.insertRule(".res-nightmode .robin-chat--input { background: #222 }", 0);
  650. stylesheet.insertRule(".res-nightmode .robin--presence-class--away .robin--username {color: #999;}", 0);
  651. stylesheet.insertRule(".res-nightmode .robin--presence-class--present .robin--username {color: #ccc;}", 0);
  652. stylesheet.insertRule(".res-nightmode #robinChat .robin--user-class--self .robin--username { color: #999; }", 0);
  653. stylesheet.insertRule(".res-nightmode .robin-chat--vote { background: #777; color: #ccc;}", 0);
  654. stylesheet.insertRule(".res-nightmode .robin-chat--buttons button.robin-chat--vote.robin--active { background: #ccc; color:#999; }", 0);
  655.  
  656. $(document).ready(function(){
  657. setTimeout(function(){
  658. // Play nice with robin grow (makes room for tab bar we insert)
  659. if($(".usercount.robin-chat--vote").length !== 0){
  660. _robin_grow_detected = true;
  661. stylesheet.insertRule("#robinChat.robin-chat .robin-chat--body { height: calc(100vh - 150px); }", 0);
  662. }
  663. },500);
  664. });
  665.  
  666. // Allow me to sneek functions in front of other libaries - used when working with robin grow >.< sorry guys
  667. //http://stackoverflow.com/questions/2360655/jquery-event-handlers-always-execute-in-order-they-were-bound-any-way-around-t
  668. $.fn.bindFirst = function(name, fn) {
  669. // bind as you normally would
  670. // don't want to miss out on any jQuery magic
  671. this.on(name, fn);
  672.  
  673. // Thanks to a comment by @Martin, adding support for
  674. // namespaced events too.
  675. this.each(function() {
  676. var handlers = $._data(this, 'events')[name.split('.')[0]];
  677. // take out the handler we just inserted from the end
  678. var handler = handlers.pop();
  679. // move it at the beginning
  680. handlers.splice(0, 0, handler);
  681. });
  682. };
  683.  
  684. })();