|
// ==UserScript== |
|
// @name Ruby5 favorites |
|
// @namespace http://icanscale.com |
|
// @description Keep record of your favorite Ruby5 stories |
|
// @include http://ruby5.envylabs.com/* |
|
// ==/UserScript== |
|
|
|
// jQuery 1.3.2 should already be loaded in the site so we don't need to do that |
|
|
|
if (typeof unsafeWindow == 'undefined') { |
|
unsafeWindow = window; // for being able to use this as a bookmarklet |
|
} |
|
|
|
// Loading Underscore.js |
|
(function loadUnderscore(loading) { |
|
if (typeof unsafeWindow._ == 'undefined') { |
|
if (!loading) { |
|
var Underscore_JS = document.createElement('script'); |
|
Underscore_JS.src = 'http://documentcloud.github.com/underscore/underscore-min.js'; |
|
Underscore_JS.type = 'text/javascript'; |
|
document.getElementsByTagName('head')[0].appendChild(Underscore_JS); |
|
} |
|
window.setTimeout(function() {loadUnderscore(true)}, 100); |
|
} else { |
|
$ = unsafeWindow.jQuery; |
|
_ = unsafeWindow._; |
|
$(Ruby5Favorites); |
|
} |
|
})(); |
|
|
|
// Add styles |
|
(function(styles) { |
|
var cssElem = document.createElement('style'); |
|
cssElem.type = 'text/css'; |
|
cssElem.media = 'screen'; |
|
if (cssElem.styleSheet) { |
|
cssElem.styleSheet.cssText = styles; // IE method |
|
} else { |
|
cssElem.appendChild(document.createTextNode(styles)); // others |
|
} |
|
document.getElementsByTagName('head')[0].appendChild(cssElem); |
|
})(' \ |
|
#favorites { \ |
|
position: fixed; \ |
|
top: 0; \ |
|
right: 0; \ |
|
border: solid #ccc; \ |
|
border-width: 0 0 2px 2px; \ |
|
-moz-border-radius-bottomleft: 10px; \ |
|
-webkit-border-bottom-left-radius: 10px; \ |
|
background-color: white; \ |
|
} \ |
|
#favorites h6 { \ |
|
cursor: pointer; \ |
|
width: 1px; \ |
|
height: 32px; \ |
|
padding-right: 36px; \ |
|
border: solid #ccc; \ |
|
border-width: 0 0 1px 1px; \ |
|
-moz-border-radius-bottomleft: 10px; \ |
|
-webkit-border-bottom-left-radius: 10px; \ |
|
line-height: 32px; \ |
|
font-weight: bold; \ |
|
background: transparent url("http://assets.icanscale.com/i/star.png") top right no-repeat; \ |
|
background-color: #white; \ |
|
text-indent: 40px; \ |
|
overflow: hidden; \ |
|
} \ |
|
#favorites-list { \ |
|
margin: 0; \ |
|
padding: 0; \ |
|
max-width: 200px; \ |
|
max-height: 300px; \ |
|
overflow: auto; \ |
|
text-align: left; \ |
|
-moz-border-radius-bottomleft: 10px; \ |
|
-webkit-border-bottom-left-radius: 10px; \ |
|
} \ |
|
#favorites-list li { \ |
|
margin: 0.5em; \ |
|
} \ |
|
#favorites-list ul { \ |
|
margin: 0; \ |
|
padding: 0; \ |
|
} \ |
|
#favorites-list .episode-link { \ |
|
font-weight: bold; \ |
|
} \ |
|
#favorites-list .fav-del-link { \ |
|
text-decoration: none; \ |
|
} \ |
|
.stories-listing li a.toggle-fav { \ |
|
position: absolute; \ |
|
border-width: 0; \ |
|
margin: -8px 0 0 0.25em; \ |
|
width: 32px; \ |
|
height: 32px; \ |
|
background: transparent url("http://assets.icanscale.com/i/star-off.png") top left no-repeat; \ |
|
} \ |
|
.stories-listing li a.toggle-fav.favorite { \ |
|
background: transparent url("http://assets.icanscale.com/i/star.png") top left no-repeat; \ |
|
} \ |
|
'); |
|
|
|
function Ruby5Favorites() { |
|
function getFavorites() { |
|
function extractMeta(uri, title) { |
|
var match; |
|
if (uri && (match = uri.match(/episode-(\d+)-.*#story-(\d+)/))) { |
|
return { |
|
'uri': uri, 'episode': match[1]-0, 'story': match[2]-0, |
|
'title': title |
|
}; |
|
} |
|
}; |
|
|
|
var favs = localStorage.getItem('favorites'); |
|
favs = favs ? favs.split("\n") : []; |
|
favs = _(favs).chain() |
|
.map(function(fav) { |
|
var uri = fav.split(" ")[0], p = fav.indexOf(" "); |
|
var title = p != -1 ? fav.substr(p+1) : null; |
|
return extractMeta(uri, title); |
|
}) |
|
.sortBy(function(fav) { |
|
if (fav) { |
|
return - fav.episode*10 + fav.story; |
|
} |
|
}) |
|
.value(); |
|
|
|
function save() { |
|
var serialized = _.map(favs, function(fav) { |
|
return fav.uri + ' ' + fav.title; |
|
}).join("\n"); |
|
localStorage.setItem('favorites', serialized); |
|
} |
|
|
|
return { |
|
isFavorite: function(uri) { |
|
return _(favs).detect(function(fav) { |
|
return fav.uri == uri; |
|
}); |
|
}, |
|
addFavorite: function(uri, title) { |
|
// is it already saved to the list? |
|
if (_(favs).detect(function(fav) { return fav.uri == uri; })) { |
|
return; |
|
} |
|
favs.push(extractMeta(uri, title)); |
|
save(); |
|
this.render(); |
|
}, |
|
removeFavorite: function(uri) { |
|
// is it in the list? |
|
if (!_(favs).detect(function(fav) { return fav.uri == uri; })) { |
|
return; |
|
} |
|
favs = _.reject(favs, function(fav) { |
|
return fav.uri == uri; |
|
}); |
|
save(); |
|
this.render(); |
|
}, |
|
all: function() { |
|
return favs; |
|
}, |
|
groupedByEpisode: function() { |
|
return _.inject(favs, {}, function(memo, fav) { |
|
var key = 'Episode '+fav.episode; |
|
if (memo[key] == undefined) { memo[key] = []; } |
|
memo[key].push(fav); |
|
return memo; |
|
}); |
|
}, |
|
render: function() { |
|
if ($('#favorites').length == 0) { |
|
var self = this; |
|
var list = $('<ul id="favorites-list"></ul>').click(function(e) { |
|
var target = $(e.target); |
|
if (target.hasClass('fav-link')) { |
|
var href = target.attr('href'), story = href.match(/#.*$/)[0]; |
|
if (href == window.location.href.replace(/#.*$/, '') + story) { |
|
// same page, emulating click event on story link |
|
e.preventDefault(); |
|
$('.stories-listing a[href='+story+']').click(); |
|
} |
|
} else if (target.hasClass('fav-del-link')) { |
|
e.preventDefault(); |
|
var uri = target.prev().attr('href'), story = uri.match(/#.*$/)[0]; |
|
self.removeFavorite(uri); |
|
if (uri == window.location.href.replace(/#.*$/, '') + story) { |
|
$('.stories-listing a[href='+story+'] + a').removeClass('favorite'); |
|
} |
|
} |
|
}).hide(); |
|
var title = $('<h6>Ruby5 Favorites</h6>').toggle(function() { |
|
$('#favorites h6') |
|
.animate({width: '163px', textIndent: '0px'}, function() { |
|
list.animate({height: "toggle"}); |
|
}); |
|
}, function() { |
|
list.animate({height: "toggle"}, function() { |
|
$('#favorites h6').animate({width: '1px', textIndent: '40px'}); |
|
}); |
|
}); |
|
$('<div id="favorites"></div>').appendTo('body') |
|
.append(title).append(list); |
|
} |
|
// jQuery .html('') is really slow for this if there are many links |
|
var list = $('#favorites-list'); |
|
list.get(0).innerHTML = ""; |
|
if (favs.length == 0) { |
|
list.append('<li>No favorites yet. Mark some favorites with the star icons.</li>'); |
|
return; |
|
} |
|
var favsInGroups = this.groupedByEpisode(); |
|
for (var episode in favsInGroups) { |
|
var epfavs = favsInGroups[episode]; |
|
var eplink = |
|
$('<a></a>') |
|
.addClass('episode-link') |
|
.attr('href', epfavs[0].uri.replace(/#.*$/, '')) |
|
.text('Episode '+epfavs[0].episode); |
|
var eplist = $('<ul></ul>'); |
|
_.each(epfavs, function(fav) { |
|
eplist.append( |
|
$('<li></li>').append( |
|
$('<a></a>').addClass('fav-link').attr('href', fav.uri) |
|
.text(fav.title || 'Story '+fav.story) |
|
).append(' <a class="fav-del-link" href="#">✖</a>') |
|
); |
|
}); |
|
list.append($('<li></li>').append(eplink).append(eplist)); |
|
} |
|
} |
|
} |
|
} |
|
|
|
var isEpisodePage = $('#current-episode-title').length > 0; |
|
var favorites = getFavorites(); |
|
unsafeWindow.favs = favorites; |
|
|
|
favorites.render(); |
|
|
|
if (isEpisodePage) { |
|
var location = window.location.href; |
|
if (!location.match(/\/episodes\/\d+/)) { |
|
// trying to find out premalink |
|
var slug = $('#current-episode-title').text().toLowerCase().replace(/[ #,-]+/g, '-'); |
|
var prevId = $('h2:eq(1) a').attr('href').match(/\/(\d+)/)[1]-0; |
|
location = 'http://ruby5.envylabs.com/episodes/'+(prevId+1)+'-'+slug; |
|
} |
|
location = location.replace(/(\/stories\/.*)?(#.*)?$/, ''); |
|
|
|
$('.stories-listing li').each(function() { |
|
var li = $(this); |
|
var uri = li.find('a:first').attr('href'); |
|
if (uri.match(/^#/)) { |
|
uri = location + uri; |
|
} |
|
var title = $(this).find('a').text().replace(/^\s*/, '').replace(/\s*$/, ''); |
|
|
|
var star = $('<a href="#" class="toggle-fav"></a>') |
|
.click(function(e) { |
|
e.preventDefault(); |
|
e.stopPropagation(); |
|
if ($(this).hasClass('favorite')) { |
|
favorites.removeFavorite(uri); |
|
$(this).removeClass('favorite'); |
|
} else { |
|
favorites.addFavorite(uri, title); |
|
$(this).addClass('favorite'); |
|
} |
|
}); |
|
if (favorites.isFavorite(uri)) { |
|
star.addClass('favorite'); |
|
} |
|
li.append(star); |
|
}); |
|
} |
|
} |