Skip to content

Instantly share code, notes, and snippets.

@lackac
Created December 13, 2009 12:20
Show Gist options
  • Save lackac/255396 to your computer and use it in GitHub Desktop.
Save lackac/255396 to your computer and use it in GitHub Desktop.
Bookmarklet or Greasemonkey script to keep record of your favorite Ruby5 stories

/*

Ruby5 Favorites

You can use this script to keep record of your favorite stories from Ruby5. The script will show a star icon next to eacy story title on Ruby5 episode pages through which you can mark those stories as favorites. It also shows a nice list of your marked stories in the top right section of the webpage. The script works as a userscript and it can work through a bookmarklet too. I have tested it with Firefox 3.5, Safari 4.0.4 and Google Chrome 4.0.249.30.

Install as a User Script

If you have Greasemonkey (or GreaseKit in case of Safari) installed, open the raw version of the ruby5_favorites.user.js file in this gist in your browser and follow the instructions. Each time you visit Ruby5 after you have installed the script your favorites will appear and you can mark stories as favorites on each episode page.

Install as a Bookmarklet

Altough the userscript way is more convenient you are still able to use this script as a bookmarklet. For this to work you have to create a bookmark in your bookmarks bar and set its address to the text below. Of course this will not load automatically when you visit Ruby5. You have to click the bookmarklet each time you visit a Ruby5 page.

javascript:Ruby5Favs();function%20Ruby5Favs(){if(window.location.host!="ruby5.envylabs.com"){if(confirm("You're%20not%20on%20Ruby5.%20Do%20you%20want%20to%20go%20to%20Ruby5?\n(You'll%20need%20to%20press%20the%20%20bookmarklet%20again%20when%20you're%20there.)")){window.location="http://ruby5.envylabs.com";};}else{var%20js=document.createElement('script');js.src='http://gist.github.com/255396.txt';js.type='text/javascript';document.getElementsByTagName('head')[0].appendChild(js);};};

Credits

Created by LacKac.

Copyright (c) 2009 László Bácsi, released under the MIT license.

(js style comments to let http://gist.github.com/gists/255396.txt work as a js file) */

// ==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('&nbsp;<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);
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment