Created
November 26, 2010 00:11
-
-
Save badp/716098 to your computer and use it in GitHub Desktop.
This causes Chrome dev to crash.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name TwitterUnread | |
| // @namespace http://henrik.heimbuerger.de/greasemonkey/ | |
| // @description [alpha01] ... | |
| // @include http://twitter.com/ | |
| // @include http://twitter.com/# | |
| // @include http://www.twitter.com/ | |
| // @include http://www.twitter.com/# | |
| // @include https://twitter.com/ | |
| // @include https://twitter.com/# | |
| // @include https://www.twitter.com/ | |
| // @include https://www.twitter.com/# | |
| // ==/UserScript== | |
| // TODO: | |
| // � better visual style for read tweets | |
| // � fallback for enhancing if the events all occur before the script can be loaded | |
| // � trigger initialization from an actual twttr pointcut instead of polling | |
| // � give the 'mark read' button its own icon | |
| // � only process the Home timeline, support switching between the streams, etc. | |
| // � store read state in the cloud | |
| // � support for Opera and Chrome | |
| // wait for jQuery and twttr.app | |
| function waiter() { | |
| if(unsafeWindow.jQuery && unsafeWindow.twttr | |
| && unsafeWindow.twttr.app | |
| && unsafeWindow.twttr.app.currentPage() | |
| && unsafeWindow.twttr.app.currentPage().streamManager | |
| && unsafeWindow.twttr.app.currentPage().streamManager.streams | |
| && unsafeWindow.twttr.app.currentPage().streamManager.streams.current) { | |
| console.debug('ready'); | |
| script(unsafeWindow.jQuery, unsafeWindow.twttr); | |
| } else { | |
| console.debug('waiting') | |
| setTimeout(waiter, 100); | |
| } | |
| } | |
| waiter(); | |
| function script($, twttr) { | |
| console.debug('start'); | |
| /* | |
| * Helper function for adding CSS to the current page | |
| */ | |
| function addGlobalStyle(css) { | |
| var head, style; | |
| head = document.getElementsByTagName('head')[0]; | |
| if (!head) { return; } | |
| style = document.createElement('style'); | |
| style.type = 'text/css'; | |
| style.innerHTML = css; | |
| head.appendChild(style); | |
| } | |
| function addGlobalScript(code) { | |
| var head, script; | |
| head = document.getElementsByTagName('head')[0]; | |
| if (!head) { return; } | |
| script = document.createElement('script'); | |
| script.type = 'text/javascript'; | |
| script.innerHTML = code; | |
| head.appendChild(script); | |
| } | |
| addGlobalStyle('.tweet-actions .mark-read-action span i{background-position:0 0;margin-right:0;margin-left:3px;}.tweet-actions .mark-read-action:hover span i{background-position:-16px 0;}'); | |
| /* | |
| * jQuery.ScrollTo - Easy element scrolling using jQuery. | |
| * Copyright (c) 2007-2009 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com | |
| * Dual licensed under MIT and GPL. | |
| * Date: 5/25/2009 | |
| * @author Ariel Flesler | |
| * @version 1.4.2 | |
| * | |
| * http://flesler.blogspot.com/2007/10/jqueryscrollto.html | |
| */ | |
| console.debug('initializing $.scrollTo'); | |
| addGlobalScript("(function(d){var k=d.scrollTo=function(a,i,e){d(window).scrollTo(a,i,e)};k.defaults={axis:'xy',duration:parseFloat(d.fn.jquery)>=1.3?0:1};k.window=function(a){return d(window)._scrollable()};d.fn._scrollable=function(){return this.map(function(){var a=this,i=!a.nodeName||d.inArray(a.nodeName.toLowerCase(),['iframe','#document','html','body'])!=-1;if(!i)return a;var e=(a.contentWindow||a).document||a.ownerDocument||a;return d.browser.safari||e.compatMode=='BackCompat'?e.body:e.documentElement})};d.fn.scrollTo=function(n,j,b){if(typeof j=='object'){b=j;j=0}if(typeof b=='function')b={onAfter:b};if(n=='max')n=9e9;b=d.extend({},k.defaults,b);j=j||b.speed||b.duration;b.queue=b.queue&&b.axis.length>1;if(b.queue)j/=2;b.offset=p(b.offset);b.over=p(b.over);return this._scrollable().each(function(){var q=this,r=d(q),f=n,s,g={},u=r.is('html,body');switch(typeof f){case'number':case'string':if(/^([+-]=)?\d+(\.\d+)?(px|%)?$/.test(f)){f=p(f);break}f=d(f,this);case'object':if(f.is||f.style)s=(f=d(f)).offset()}d.each(b.axis.split(''),function(a,i){var e=i=='x'?'Left':'Top',h=e.toLowerCase(),c='scroll'+e,l=q[c],m=k.max(q,i);if(s){g[c]=s[h]+(u?0:l-r.offset()[h]);if(b.margin){g[c]-=parseInt(f.css('margin'+e))||0;g[c]-=parseInt(f.css('border'+e+'Width'))||0}g[c]+=b.offset[h]||0;if(b.over[h])g[c]+=f[i=='x'?'width':'height']()*b.over[h]}else{var o=f[h];g[c]=o.slice&&o.slice(-1)=='%'?parseFloat(o)/100*m:o}if(/^\d+$/.test(g[c]))g[c]=g[c]<=0?0:Math.min(g[c],m);if(!a&&b.queue){if(l!=g[c])t(b.onAfterFirst);delete g[c]}});t(b.onAfter);function t(a){r.animate(g,j,b.easing,a&&function(){a.call(this,n,b)})}}).end()};k.max=function(a,i){var e=i=='x'?'Width':'Height',h='scroll'+e;if(!d(a).is('html,body'))return a[h]-d(a)[e.toLowerCase()]();var c='client'+e,l=a.ownerDocument.documentElement,m=a.ownerDocument.body;return Math.max(l[h],m[h])-Math.min(l[c],m[c])};function p(a){return typeof a=='object'?a:{top:a,left:a}}})(jQuery)"); | |
| /* | |
| * jQuery AOP - jQuery plugin to add features of aspect-oriented programming (AOP) to jQuery. | |
| * http://jquery-aop.googlecode.com/ | |
| * | |
| * Licensed under the MIT license: | |
| * http://www.opensource.org/licenses/mit-license.php | |
| * | |
| * Version: 1.3 | |
| * | |
| * Cross-frame type detection based on Daniel Steigerwald's code (http://daniel.steigerwald.cz) | |
| * http://gist.github.com/204554 | |
| */ | |
| console.debug('initializing $.aop'); | |
| addGlobalScript("(function(){var j=1;var k=2;var l=3;var m=4;var n=5;var o=6;var p=true;var q='arguments';var r='undefined';var s=(function(){var toString=Object.prototype.toString,toStrings={},nodeTypes={1:'element',3:'textnode',9:'document',11:'fragment'},types='Arguments Array Boolean Date Document Element Error Fragment Function NodeList Null Number Object RegExp String TextNode Undefined Window'.split(' ');for(var i=types.length;i--;){var b=types[i],constructor=window[b];if(constructor){try{toStrings[toString.call(new constructor)]=b.toLowerCase()}catch(e){}}}return function(a){return a==null&&(a===undefined?r:'null')||a.nodeType&&nodeTypes[a.nodeType]||typeof a.length=='number'&&(a.callee&&q||a.alert&&'window'||a.item&&'nodelist')||toStrings[toString.call(a)]}})();var t=function(a){return s(a)=='function'};var u=function(b,c,d){var f=b[c];if(d.type!=o&&!t(f)){var oldObject=f;f=function(){var a=arguments.length>0?q+'[0]':'';for(var i=1;i<arguments.length;i++){a+=','+q+'['+i+']'}return eval('oldObject('+a+');')}}var h;if(d.type==j||d.type==k||d.type==l)h=function(){var a,exceptionThrown=null;try{a=f.apply(this,arguments)}catch(e){exceptionThrown=e}if(d.type==j)if(exceptionThrown==null)a=d.value.apply(this,[a,c]);else throw exceptionThrown;else if(d.type==k&&exceptionThrown!=null)a=d.value.apply(this,[exceptionThrown,c]);else if(d.type==l)a=d.value.apply(this,[a,exceptionThrown,c]);return a};else if(d.type==m)h=function(){d.value.apply(this,[arguments,c]);return f.apply(this,arguments)};else if(d.type==o)h=function(){return d.value.apply(this,arguments)};else if(d.type==n){h=function(){var a={object:this,args:Array.prototype.slice.call(arguments)};return d.value.apply(a.object,[{arguments:a.args,method:c,proceed:function(){return f.apply(a.object,a.args)}}])}}h.unweave=function(){b[c]=f;pointcut=b=h=f=null};b[c]=h;return h};var v=function(a,b,c){var d=[];for(var f in a){var g=null;try{g=a[f]}catch(e){}if(g!=null&&f.match(b.method)&&t(g))d[d.length]={source:a,method:f,advice:c}}return d};var w=function(a,b){var c=typeof(a.target.prototype)!=r?a.target.prototype:a.target;var d=[];if(b.type!=o&&typeof(c[a.method])==r){var e=v(a.target,a,b);if(e.length==0)e=v(c,a,b);for(var i in e)d[d.length]=u(e[i].source,e[i].method,e[i].advice)}else{d[0]=u(c,a.method,b)}return p?d:d[0]};$.aop={after:function(a,b){return w(a,{type:j,value:b})},afterThrow:function(a,b){return w(a,{type:k,value:b})},afterFinally:function(a,b){return w(a,{type:l,value:b})},before:function(a,b){return w(a,{type:m,value:b})},around:function(a,b){return w(a,{type:n,value:b})},introduction:function(a,b){return w(a,{type:o,value:b})},setup:function(a){p=a.regexMatch}}})()"); | |
| console.debug('plugin initialization complete'); | |
| var LOCAL_STORAGE_KEY_LAST_READ_TWEET_ID = 'lastReadTweetID'; | |
| var TWITTER_ATTRIBUTE_TWEET_ID = 'data-item-id'; | |
| var MAX_FETCH_MORE = 3; | |
| var hasScrolled = false; | |
| var isCurrentlyEnhancing = false; | |
| function getLastReadTweetID() { | |
| console.debug('retrieving last read tweet ID: ' + localStorage.getItem(LOCAL_STORAGE_KEY_LAST_READ_TWEET_ID)); | |
| return localStorage.getItem(LOCAL_STORAGE_KEY_LAST_READ_TWEET_ID); | |
| } | |
| function setLastReadTweet(tweetID) { | |
| console.debug('setting last read tweet ID to: ' + tweetID); | |
| localStorage.setItem(LOCAL_STORAGE_KEY_LAST_READ_TWEET_ID, tweetID); | |
| } | |
| function executeWithLastReadTweet(callback, fetchCycleCounter) { | |
| if(!fetchCycleCounter) fetchCycleCounter = 0; | |
| console.debug('Searching for last read tweet, currently on iteration ' + fetchCycleCounter + '/' + MAX_FETCH_MORE); | |
| foundTweet = $('div.stream-item[data-item-id="' + getLastReadTweetID() + '"]').get(0); | |
| if(foundTweet) { | |
| console.debug('found: ' + foundTweet); | |
| callback($(foundTweet)); | |
| } else if(!foundTweet && fetchCycleCounter < MAX_FETCH_MORE) { | |
| console.debug('fetching more...'); | |
| twttr.app.currentPage().streamManager.getMoreOldItems(function() {executeWithLastReadTweet(callback, fetchCycleCounter+1);}); | |
| } else { | |
| console.debug('giving up search'); | |
| } | |
| } | |
| function onMarkReadActionClick() { | |
| var tweet = $(this).parents('div.stream-item'); | |
| var tweetID = tweet.attr(TWITTER_ATTRIBUTE_TWEET_ID); | |
| console.log('marking as read until ' + tweetID); | |
| setLastReadTweet(tweetID); | |
| blurOutReadTweets(tweet); | |
| } | |
| function onJumpHomeClick() { | |
| executeWithLastReadTweet(function(lastReadTweet) { | |
| scrollToTweet(lastReadTweet.next()); | |
| }); | |
| } | |
| function blurOutReadTweets(lastReadTweet) { | |
| if(lastReadTweet) { | |
| console.log('blurring from ' + lastReadTweet.attr(TWITTER_ATTRIBUTE_TWEET_ID)); | |
| lastReadTweet.nextAll().andSelf().css('opacity', '0.3'); | |
| // in case the user is trying to 'mark unread' | |
| lastReadTweet.prevAll().css('opacity', '1.0'); | |
| } else { | |
| alert('last read tweet not found'); | |
| } | |
| } | |
| function scrollToTweet(tweet) { | |
| if(tweet) { | |
| console.log('scrolling to ' + tweet.attr(TWITTER_ATTRIBUTE_TWEET_ID)); | |
| $.scrollTo(tweet, { | |
| axis: 'y', // no horizontal scrolling please | |
| duration: 1500, // scrolling should be visible to give the user feedback on the current situation | |
| easing: 'swing', // just makes it look a bit nicer | |
| offset: -window.innerHeight // we want that tweet to be at the *bottom* of the screen | |
| }); | |
| } | |
| } | |
| function enhanceWithAction(result) { | |
| if(!isCurrentlyEnhancing) { | |
| isCurrentlyEnhancing = true; | |
| console.group('enhancing'); | |
| // add the new actions | |
| $('span.tweet-actions:not(.enhanced)') | |
| // make sure they are only enhanced once | |
| .addClass('enhanced') | |
| // add the 'mark read until here' action | |
| .append('<a class="mark-read-action" href="#"><span><i></i><b>Mark read until here</b></span></a>') | |
| // add the click handlers | |
| .children('a.mark-read-action').click(onMarkReadActionClick); | |
| console.groupEnd(); | |
| executeWithLastReadTweet(function(lastReadTweet) { | |
| // blur out read tweets | |
| blurOutReadTweets(lastReadTweet); | |
| // on the first load, also scroll to the last unread tweet (plus one, so we see where the list of unread tweets ends) | |
| if(!hasScrolled) { | |
| scrollToTweet(lastReadTweet.next()); | |
| hasScrolled = true; | |
| } | |
| }); | |
| isCurrentlyEnhancing = false; | |
| } | |
| return result; | |
| } | |
| function initHooks() { | |
| // hook into the tweet loading | |
| $.aop.after({target: twttr.app.currentPage().streamManager.streams.current, method: 'onShowOldItems'}, enhanceWithAction); | |
| $.aop.after({target: twttr.app.currentPage().streamManager.streams.current, method: 'onShowNewItems'}, enhanceWithAction); | |
| // the logo is used to just jump to your current reading state | |
| $('#logo').unbind('click').click(onJumpHomeClick); | |
| } | |
| initHooks(); | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This userscript developed by Henrik Heimbuerger. Latest version.