CustomScrollbar

Adds a custom scrollbar to chat with a snap-to-bottom button that glows when a new messages is received. The userlist scrollbar is hidden until you hover over the list and hides after the mouse leaves.

  1. // ==UserScript==
  2. // @name CustomScrollbar
  3. // @namespace skyboy@kongregate
  4. // @author skyboy
  5. // @version 1.1.1
  6. // @description Adds a custom scrollbar to chat with a snap-to-bottom button that glows when a new messages is received. The userlist scrollbar is hidden until you hover over the list and hides after the mouse leaves.
  7. // @include *://www.kongregate.com/games/*/*
  8. // @homepage http://userscripts.org/scripts/show/121867
  9. // ==/UserScript==
  10. if (/^\/?games\/[^\/]+\/[^\/?]+(\?.*)?$/.test(window.location.pathname)) {
  11. setTimeout(function() {
  12. window.location.assign("javascript:void(("+((function() {
  13. function addNow(a) {
  14. $$('head').invoke('insert', a);
  15. }
  16. (function(){
  17. function update(array, args) {
  18. var arrayLength = array.length, length = args.length;
  19. array.length += length;
  20. while (length--) array[arrayLength + length] = args[length];
  21. return array;
  22. }
  23. Element.addMethods({setTop:function(e,y){
  24. return Element.setStyle(e, {top:(y|0)+'px'});
  25. },setPosSvH:function(e,y,h){
  26. return Element.setStyle(e, {top:(y|0)+'px',height:(h|0)+'px'});
  27. },setHeight:function(e,y){
  28. return Element.setStyle(e, {height:(y|0)+'px'});
  29. },getTop:function(e){
  30. return parseInt(Element.getStyle(e, 'top'),10)|0;
  31. },removeClassNames:function(e,n){
  32. $A(arguments.length==2?$w(n):arguments.shift()).each(Element.removeClassName.curry(e));
  33. return $(e)
  34. } });
  35. Object.extend(Function.prototype,{bindArgs:function(thisObj, args){
  36. var __method = this;
  37. args = $A(args).slice(0);
  38. return function() {
  39. var a = update(args.slice(0), arguments);
  40. return __method.apply(thisObj, a);
  41. }
  42. },passiveWrap:function(wrapper){
  43. var __method = this;
  44. return function() {
  45. var a = $A(arguments);
  46. a.unshift(__method.bindArgs(this, arguments));
  47. return wrapper.apply(this, a);
  48. }
  49. }});
  50. })();
  51. addNow(("<style>.scrollbar,.scrollbar *{background-image:url(\""+($.jStorage.get("skyscrollbg")||'http://i.imgur.com/Fb8Hz.png')+"\");vertical-align:top;overflow:hidden;background-repeat:repeat-y;width:15px}.noyscroll {overflow-y:hidden!important;}" +
  52. ".scrollbar *{position:absolute}.scroller *{height:3px;}.scroller .top{background-position:-48px 0;}.scroller .bot{bottom:0;background-position:-48px -13px;}.scroller:hover .top{background-position:-64px 0}" +
  53. ".scroller .mid{height:10px;background-position:-48px -3px;top:50%;margin-top:-5px;}.scroller:hover .mid{background-position:-64px -3px;}.scrolldown{height:16px;background-position:0 -17px;bottom:16px}" +
  54. "#kong_game_ui .message_window_table .chat_message_window{margin-top:0;}.message_window_table{table-layout:fixed;width:100%}.message_window_table,.message_window_table td{border-spacing:0;border:0;}" +
  55. ".scrolldown:hover{background-position:-16px -17px}.scrolldown.down{background-position:-32px -17px}.scroller:hover{background-position:-144px 0;}.scroller.down{background-position:-160px 0;}" +
  56. ".bottomsnap.glow{background-position:-96px -17px}.bottomsnap.glow:not(.down):hover{background-position:-96px 0}.scrollup{height:16px;}.scroller:hover .bot{background-position:-64px -13px;}" +
  57. ".scroller{background-position:-128px 0;min-height:16px;}.scrollbar{background-position:-112px 0;}.scrollup:hover{background-position:-16px 0;}.scrollup.down{background-position:-32px 0;}" +
  58. ".bottomsnap{height:16px;background-position:-48px -17px;bottom:0}.bottomsnap:hover{background-position:-64px -17px}.bottomsnap.down{background-position:-80px -17px}" +
  59. ".scroller.down .mid{background-position:-80px -3px}.scroller.down .top{background-position:-80px 0;}.scroller.down .bot{background-position:-80px -13px;}" +
  60. ".bartop{background-position:-176px 0;height:33px;top:16px}.barbot{background-position:-192px 0;height:33px;bottom:32px}</style>"));
  61. document.observe('holodeck:ready', function(){
  62. var CD=ChatDialogue,p=CD.prototype;
  63. CD.SCROLL_INCREMENT = 15;
  64. CD.SCROLL_DELAY = .02;
  65. CD.SCROLL_HALT = 0.20;
  66. CD.WINDOW_SCROLL_DELAY = .5;
  67. p.initialize=p.initialize.passiveWrap(function(p,n){
  68. var t=this,c='noyscroll',r=p();
  69. function sa(e,t,h,p){e.scrollTop=(t+((p|0)*(t/h)))*(e.scrollHeight / h);};
  70. function f(t){t.scrolltimer2=0;var a=t.scrollTop,b=t.scrollHeight||1;t.removeClassName(c);setTimeout(sa,350,t,a,b,18);};
  71. function f2(t){t.scrolltimer=0;var a=t.scrollTop,b=t.scrollHeight||1;t.addClassName(c);sa(t,a,b);};
  72. function f3(){var t=this;if(t.scrolltimer2){clearTimeout(t.scrolltimer2);t.scrolltimer2=0;return;}if(!t.scrolltimer)t.scrolltimer=setTimeout(f2,1000,t)};
  73. function f4(){var t=this;if(t.scrolltimer){clearTimeout(t.scrolltimer);t.scrolltimer=0;return;}if(!t.scrolltimer2)t.scrolltimer2=setTimeout(f,500,t)};
  74. try{n.down('.users_in_room').addClassName(c).observe('mouseover',f4).observe('mouseout',f3);}catch(_){}
  75. function dv(c,h){return "<div class='"+c+"'>"+(h||'')+"</div>"}
  76. var mwt = new Element('table',{'class':'message_window_table'}),mwn=t._message_window_node;
  77. var d=new Element('td'),s=new Element('td',{'style':'width:15px;height:100%;','class':'scrollbar'});
  78. s.update(dv("scrollup")+dv("bartop nm")+dv("barbot nm")+dv("scroller",dv("top")+dv("mid")+dv("bot"))+dv("scrolldown")+dv("bottomsnap"));
  79. d.insert(mwn.addClassName(c).replace(new Element('div', {'style':'margin-top:3px;position:relative;'}).insert(mwt)));
  80. mwt.insert(new Element('tr').insert(d).insert(s));
  81. t._scrollbar_node = s;
  82. var bs = s.down('.bottomsnap'), sd = s.down('.scrolldown'), su = s.down('.scrollup'), sc = s.down('.scroller');
  83. function rcng(){if(mwn.scrollTop+mwn.getHeight()+(ChatDialogue.SCROLL_FUDGE/5)>=mwn.scrollHeight)bs.removeClassName('glow');}
  84. function usp(){
  85. var mwn=t._message_window_node,h=mwn.getHeight();
  86. if (h) {
  87. var sn = t._scrollbar_node, s=sn.down('.scroller');
  88. s.setTop(((mwn.scrollTop/(mwn.scrollHeight-h))*(h-48-s.getHeight())|0)+16);
  89. rcng();
  90. }
  91. }
  92. function udp2(d,p){(p = t._scroll_pe) && p.stop();t._scroll_pe = new PeriodicalExecuter(function(){t._message_window_node.scrollTop += d;usp()},CD.SCROLL_DELAY)}
  93. function upd(d,c){t._message_window_node.scrollTop+=d*=CD.SCROLL_INCREMENT;usp();if(!c)wrp(),t._scroll_pe=new PeriodicalExecuter(function(){t._message_window_node.scrollTop += d;usp();udp2(d)},CD.SCROLL_HALT)}
  94. function stp(p,e){Object.isFunction(p)&&p(e);e&&e.stop();return false}
  95. function wrp(p,e){Object.isFunction(p)&&p();(p=t._scroll_pe)&&p.stop();e&&this.stopObserving(e.eventName,arguments.callee);}
  96. bs.observe('mousedown',(function(e){if(e.isLeftClick())bs.addClassName('down'),document.observe('mouseup',(function(){t.scrollToBottom();bs.removeClassName('down');}).wrap(wrp))}).wrap(stp));
  97. sd.observe('mousedown', (function(e){if (e.isLeftClick()) { sd.addClassName('down');upd(+1);document.observe('mouseup', sd.removeClassName.bind(sd, 'down').wrap(wrp).wrap(stp))}}).wrap(stp));
  98. su.observe('mousedown', (function(e){if (e.isLeftClick()) { su.addClassName('down');upd(-1);document.observe('mouseup', su.removeClassName.bind(su, 'down').wrap(wrp).wrap(stp))}}).wrap(stp));
  99. function msmv(e){
  100. var p=sc.getHeight(),my=e.pageY-msmv.y,h=s.getHeight()-32-p;
  101. if(h>0){
  102. p=Math.min(Math.max(msmv.t+my,16),h);
  103. sc.setTop(p);
  104. (my=t._message_window_node).scrollTop = Math.max((p-16)/(h-16)*(my.scrollHeight-my.getHeight()),0);
  105. }
  106. }
  107. function somvrcn(){document.stopObserving('mousemove',msmv);sc.removeClassName('down');rcng();document.stopObserving('mouseup',somvrcn)}
  108. sc.observe('mousedown', (function(e){somvrcn();if(e.isLeftClick()){sc.addClassName('down');msmv.y=e.pageY,msmv.t=sc.getTop();document.observe('mousemove',msmv).observe('mouseup', somvrcn);}}).wrap(stp)).setTop(16);
  109. var rmsw=3;
  110. function msw(e,s,n){
  111. if (e.detail)
  112. s=e.detail;
  113. else if (e.wheelDelta)
  114. s=e.wheelDelta/-120;
  115. else throw new Error('Scrolling improperly supported.');
  116. if (((n=t._message_window_node),s>0)?rmsw&1||n.scrollTop<(n.scrollHeight-n.getHeight()):rmsw&2||n.scrollTop>0) {
  117. rmsw=3;
  118. stp(wrp,e);
  119. t._scroll_pe=new PeriodicalExecuter(me.wrap(wrp),CD.WINDOW_SCROLL_DELAY);
  120. upd(s,1);
  121. return false;
  122. }
  123. }
  124. mwt.observe('mousewheel', msw).observe('DOMMouseScroll', msw);
  125. function me(n){rmsw=(Number((n=t._message_window_node).scrollTop>0)<<1)|Number(n.scrollTop<(n.scrollHeight-n.getHeight()));}
  126. mwt.observe('mouseenter',me).observe('mouseleave',(function(){rmsw=0;}).wrap(wrp.bindAsEventListener(0,0,0)));
  127. s.observe('click',function(e,l){if((l=e.target)==s||l.hasClassName('nm'))msmv.y=sc.cumulativeOffset()[1],msmv.t=sc.getTop()+(e.pageY>msmv.y?-sc.getHeight():-16)+8,msmv(e),rcng()});
  128. return r;
  129. });
  130. p.insert=p.insert.passiveWrap(function(p){
  131. var t=this,mwn=t._message_window_node,h=mwn.getHeight();
  132. if (h) {
  133. var mwsh=mwn.scrollTop+h, sh=Math.max(Math.min((h/mwn.scrollHeight)*(h-48)|0, h-48), 16);
  134. var sn = t._scrollbar_node;
  135. if (mwsh+ChatDialogue.SCROLL_FUDGE<mwn.scrollHeight) {
  136. sn.down('.bottomsnap').addClassName('glow');
  137. sn.down('.scroller').setHeight(sh);
  138. } else {
  139. sn.down('.scroller').setPosSvH(h-32-sh, sh);
  140. sn.down('.bottomsnap').removeClassName('glow');
  141. }
  142. }
  143. return p();
  144. });
  145. p.scrollToBottom=p.scrollToBottom.passiveWrap(function(p){
  146. var r=p(),t=this,mwn=t._message_window_node,h=mwn.getHeight();
  147. if (h) {
  148. var mwsh=mwn.scrollTop+h, sh=Math.max(Math.min((h/mwn.scrollHeight)*(h-48)|0, h-48), 16);
  149. var sn = t._scrollbar_node;
  150. sn.down('.scroller').setPosSvH(h-32-sh, sh);
  151. sn.down('.bottomsnap').removeClassName('glow');
  152. }
  153. return r;
  154. });
  155. p.setMessageWindowHeight=p.setMessageWindowHeight.passiveWrap(function(p,h){
  156. var r,t=this,mwn=t._message_window_node;
  157. if (h) {
  158. var mwsh=mwn.scrollTop+h, sh=Math.max(Math.min((h/mwn.scrollHeight)*(h-48)|0, h-48), 16);
  159. var sn = t._scrollbar_node;
  160. if (mwsh+ChatDialogue.SCROLL_FUDGE<mwn.scrollHeight) {
  161. sn.down('.bottomsnap').addClassName('glow');
  162. sn.down('.scroller').setHeight(sh);
  163. } else {
  164. r=true;
  165. }
  166. }
  167. return r?(r=p(),t.scrollToBottom(),r):p();
  168. });
  169. p.clear=p.clear.passiveWrap(function(){
  170. var r,t=this,mwn=t._message_window_node,h=mwn.getHeight();
  171. if (h) {
  172. var sn = t._scrollbar_node;
  173. sn.down('.scroller').setPosSvH(16, h-48);
  174. sn.down('.bottomsnap').removeClassName('glow');
  175. }
  176. return p();
  177. });
  178. p = ChatWindow.prototype;
  179. p.showChatWindow = p.showChatWindow.passiveWrap(function(p) {p=p(); if (this._active_room) { this._active_room.show(); } return p});
  180. holodeck.addChatCommand("scrollskin", function(h,i){i=(i.replace(/\S+\s+(\S+)/,'$1')||'').strip();if(i){$.jStorage.set("skyscrollbg",i);addNow('<style>.scrollbar,.scrollbar *{background-image:url("'+i+'");</style>')}return false})
  181. });
  182. }).toString().replace(/[\r\n\f]/g,''))+")())");
  183. }, 1250);
  184. }