2 // @name Social commentaries for the Wok
3 // @description Add likes, comments and references from social sites to the Wok pages
4 // @namespace http://oblomov.myopenid.com
5 // @include http://wok.oblomov.eu/*
6 // @include http://localhost/~oblomov/wok/*
7 // @author Giuseppe "Oblomov" Bilotta
12 if (typeof(unsafeWindow) == 'undefined') unsafeWindow = window;
14 var social = function() {
15 var img_preload = new Image();
17 var doc = unsafeWindow.document;
19 /* list of the permalinks we want to check social networks for.
20 the first one will always be the document location */
21 this.permalink = [ unsafeWindow.location.toString() ];
23 /* parents to the permalink */
24 this.permalink_parent = [ doc.getElementById('pageinfo') ];
26 var load_script = function(url) {
27 var e = doc.createElement('script');
28 e.type = 'text/javascript';
31 doc.getElementsByTagName('head')[0].appendChild(e);
34 var weekday = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
35 var month = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
37 var date2link = function(d, l) {
41 if (typeof(d.getDate) == 'function') {
46 cal.setUTCFullYear(parseInt(d.substring(0,4)));
47 cal.setUTCMonth(parseInt(d.substring(5,7)) - 1);
48 cal.setUTCDate(parseInt(d.substring(8,10)));
49 cal.setUTCHours(parseInt(d.substring(11,13)));
50 cal.setUTCMinutes(parseInt(d.substring(14,16)));
51 cal.setUTCSeconds(parseInt(d.substring(17,19)));
54 var delta = (now - cal)/1000;
56 if (delta < 48*3600) {
57 if (cal.getDate() == now.getDate()) {
62 } else if (cal.getYear() == now.getYear()) {
63 data = 'on ' + weekday[cal.getDay()] + ', ' + month[cal.getMonth()] + ' ' + cal.getDate();
65 data = 'on ' + [cal.getYear(), cal.getMonth() + 1, cal.getDate()].join('-');
68 data += ' at ' + [cal.getHours(), cal.getMinutes()].join(':');
71 return '<a href="' + l + '">' + data + '</a>';
77 var create_style = function(string) {
78 var ssheet = doc.createElement('style');
79 ssheet.type = 'text/css';
80 ssheet.innerHTML = string;
81 doc.getElementsByTagName('head')[0].appendChild(ssheet);
84 this.gen_style = function() {
85 var cssrules = '.social ';
86 cssrules += '{display:block;border:2px inset black;height:auto;padding:0;font-size:90%;line-height:1;margin:0}';
87 cssrules += '.invisible {display:none}';
88 cssrules += '.social.logo {vertical-align:top}';
89 cssrules += '.social.logo {padding:0;margin:0 auto;display:inline-block;border:2px lightblue}';
90 cssrules += '.social.logo.inactive {border-style:outset}';
91 cssrules += '.social.logo.active {border-style:inset}';
92 create_style(cssrules);
95 this.friendfeed = new function() {
96 img_preload.src = 'http://friendfeed.com/favicon.ico';
97 img_preload.src = 'http://friendfeed.com/static/images/logo-small.png';
99 var from2link = function(from) {
100 return '<a href="http://friendfeed.com/' + from.id + '">' + from.name + '</a>';
103 var from2linkimg = function(from) {
104 return '<a class="floatlink" href="http://friendfeed.com/' + from.id + '"><img src="http://friendfeed-api.com/v2/picture/' + from.id + '?size=small" /></a>';
107 this.gen_style = function() {
108 var cssrules = '.friendfeed {border-color:lightblue}';
109 cssrules += '.friendfeed .floatlink {margin:0 1ex .5ex 0;float:left}';
110 cssrules += '.friendfeed .entrybody {margin:0;clear:both}';
111 cssrules += '.friendfeed .entrydiv {padding:.5ex;border-top:.25ex dashed lightblue;text-align:justify}';
112 cssrules += '.friendfeed .entrydiv:first-child {border-top:none}';
113 cssrules += '.friendfeed .entryfrom {margin:.5ex 1ex .5ex 0;clear:both}';
114 cssrules += '.friendfeed .entrycomments {margin-left:2.2em}';
115 cssrules += '.friendfeed .entryliked {margin:0 2ex;text-indent:-2ex}';
116 cssrules += '.friendfeed .entrycomment {border-top: .1ex dotted blue; padding:1ex .5ex}';
117 cssrules += '.friendfeed .entrycomment p {margin:0}';
118 cssrules += '.friendfeed .entrycommentfrom {font-size:smaller;line-height:150%}';
119 create_style(cssrules);
122 this.process = function(linkidx, data) {
123 var url = unsafeWindow.social.permalink[linkidx];
126 if (!(data.entries && data.entries.length > 0))
131 var ecount = data.entries.length;
132 /* check if the only entry is from me and it has no comment --skip it then */
133 var entry = data.entries[0];
134 if (entry.from.id == 'oblomov' && !(entry.comments) && !(entry.likes))
137 var divid = 'friendfeed_'+linkidx;
138 var pdiv = doc.getElementById(divid);
141 pdiv = doc.createElement('div');
142 pdiv.setAttribute('class', 'social friendfeed invisible');
143 pdiv.setAttribute('id', divid);
144 pdiv.setAttribute('title', 'FriendFeed comments on '+url);
146 unsafeWindow.social.permalink_parent[linkidx].appendChild(pdiv);
148 var logo = doc.createElement('img');
149 logo.className = 'social logo inactive';
150 logo.src = linkidx ? 'http://friendfeed.com/favicon.ico' : 'http://friendfeed.com/static/images/logo-small.png';
151 logo.setAttribute('alt', 'FriendFeed logo');
152 logo.setAttribute('title', 'FriendFeed logo');
153 pdiv.parentNode.insertBefore(logo,pdiv);
155 logo.addEventListener('click', function() {
156 if (pdiv.className.match(' invisible')) {
157 logo.className = logo.className.replace(' inactive',' active');
158 pdiv.className = pdiv.className.replace(' invisible','');
160 logo.className = logo.className.replace(' active',' inactive');
161 pdiv.className = pdiv.className + ' invisible';
166 for (var i=0; i < ecount; ++i) {
167 var entry = data.entries[i];
169 var p_entry_id = divid + '_entry_' + entry.id;
171 // skip this entry if we already added it from a different source
172 if (doc.getElementById(p_entry_id))
178 if (entry.body != last_body) {
179 last_body = entry.body;
180 fed += '<p class="entrybody">' + entry.body;
182 ediv = doc.createElement('div');
183 ediv.className = 'entrydiv';
184 pdiv.appendChild(ediv);
186 ediv = pdiv.lastChild;
187 fed = ediv.innerHTML;
189 fed += '<p id="' + p_entry_id + '" class="entryfrom">' + from2linkimg(entry.from) + 'By ' + from2link(entry.from) + '<br/>' + date2link(entry.date, entry.url);
190 fed += '<div class="entrycomments"></div>';
191 ediv.innerHTML = fed;
192 ediv = ediv.lastChild;
194 var likes = entry.likes || [];
195 var jcount = likes.length;
197 var liked = '<p class="entryliked">Liked by ';
198 for (var j=0; j < jcount; ++j) {
201 liked += (j > 0 ? ', ' : '') + from2link(like.from);
203 liked += ' and ' + like.body;
206 var ldiv = doc.createElement('div');
207 ldiv.innerHTML = liked;
208 ediv.appendChild(ldiv);
211 var comments = entry.comments || [];
212 jcount = comments.length;
214 for (var j=0 ; j < jcount; ++j) {
215 var comment = comments[j];
216 var cdiv = doc.createElement('div');
217 cdiv.className = 'entrycomment';
218 ediv.appendChild(cdiv);
221 cdiv.innerHTML = from2linkimg(comment.from) +
222 '<p class="entrycommentfrom">' + date2link(comment.date) + ' by ' + from2link(comment.from) +
223 '<p>' + comment.body;
225 cdiv.innerHTML += comment.body;
232 this.load = function(linkidx) {
233 var url = unsafeWindow.social.permalink[linkidx];
234 var callback = '(function(data){social.friendfeed.process('+linkidx+',data)})';
235 var cb_enc = escape(callback);
237 var wokloc = url.replace(/localhost\/~oblomov\/wok/g,'wok.oblomov.eu');
238 /* the same page has at least two possible URLs: if it ends with /, it could be present without /,
239 and if it ends with /?index.html it could be present without it */
241 if (wokloc.charAt(wokloc.length-1) == '/') {
242 locs.push(wokloc.substring(0,wokloc.length-1));
244 if (wokloc.match(/\/index.html$/)) {
245 locs.push(wokloc.substring(0,wokloc.length-10));
246 locs.push(wokloc.substring(0,wokloc.length-11));
250 for (var i=0; i < locs.length; ++i) {
251 locs_enc.push(encodeURI(locs[i]));
253 var ff_url = 'http://friendfeed-api.com/v2/url?url=' + locs_enc.join(',') +
254 '&callback=' + cb_enc + '&cache=' + Math.floor(Math.random()*1024*1024);
258 ff_url = 'http://friendfeed-api.com/v2/search?q=' + locs_enc[0] +
259 '&callback=' + cb_enc + '&cache=' + Math.floor(Math.random()*1024*1024);
265 this.twitter = new function() {
266 img_preload.src = 'http://twitter.com/favicon.ico';
268 this.gen_style = function() {
269 var cssrules = '.twitter {border-color:lightblue;min-height:56px}';
270 cssrules += '.twitter .floatlink {margin:0 1ex .5ex 0;float:left}';
271 cssrules += '.twitter .entry {margin:0;clear:both;padding:.5ex;border-top:.25ex dashed lightblue;text-align:justify}';
272 cssrules += '.twitter .entry:first-child {border-top:none}';
273 cssrules += '.twitter .entryfrom {margin:.5ex 1ex .5ex 0}';
274 create_style(cssrules);
277 this.process = function(linkidx, data) {
278 var url = unsafeWindow.social.permalink[linkidx];
281 if (!(data.results && data.results.length > 0))
284 var divid = 'twitter_'+linkidx;
285 var pdiv = doc.getElementById(divid);
288 pdiv = doc.createElement('div');
289 pdiv.setAttribute('class', 'social twitter invisible');
290 pdiv.setAttribute('id', divid);
291 pdiv.setAttribute('title', 'Twits on '+url);
293 unsafeWindow.social.permalink_parent[linkidx].appendChild(pdiv);
295 var logo = doc.createElement('img');
296 logo.className = 'social logo inactive';
297 logo.src = 'http://twitter.com/favicon.ico';
298 logo.setAttribute('alt', 'Twitter logo');
299 logo.setAttribute('title', 'Twitter logo');
300 pdiv.parentNode.insertBefore(logo,pdiv);
302 logo.addEventListener('click', function() {
303 if (pdiv.className.match(' invisible')) {
304 logo.className = logo.className.replace(' inactive',' active');
305 pdiv.className = pdiv.className.replace(' invisible','');
307 logo.className = logo.className.replace(' active',' inactive');
308 pdiv.className = pdiv.className + ' invisible';
313 var ecount = data.results.length;
314 for (var i=0; i < ecount; ++i) {
315 var entry = data.results[i];
317 var p_entry_id = divid + '_entry_' + entry.id;
319 // skip this entry if we already added it from a different source
320 if (doc.getElementById(p_entry_id))
323 var ediv = doc.createElement('div');
324 ediv.className = 'entrydiv';
325 pdiv.appendChild(ediv);
327 var profilink = 'http://twitter.com/' + entry.from_user;
328 var content = '<p class="entry"><a class="floatlink" href="' + profilink + '">';
329 content += '<img src="' + entry.profile_image_url + '"></a>';
330 content += entry.text + '</p>';
331 content += '<p class="entryfrom">By <a href="' + profilink + '">'+ entry.from_user + '</a> ';
332 var entrydate = new Date(entry.created_at);
333 content += date2link(entrydate, profilink + '/status/' + entry.id_str);
335 ediv.innerHTML = content;
339 this.load = function(linkidx) {
340 var url = unsafeWindow.social.permalink[linkidx];
341 unsafeWindow.social.twitter['process_' + linkidx] = function(data) {
342 unsafeWindow.social.twitter.process(linkidx, data);
344 var callback = 'window.social.twitter.process_' + linkidx;
345 var cb_enc = escape(callback);
347 var wokloc = url.replace(/localhost\/~oblomov\/wok/g,'wok.oblomov.eu');
348 /* the same page has at least two possible URLs: if it ends with /, it could be present without /,
349 and if it ends with /?index.html it could be present without it */
351 if (wokloc.charAt(wokloc.length-1) == '/') {
352 locs.push(wokloc.substring(0,wokloc.length-1));
354 if (wokloc.match(/\/index.html$/)) {
355 locs.push(wokloc.substring(0,wokloc.length-10));
356 locs.push(wokloc.substring(0,wokloc.length-11));
360 for (var i=0; i < locs.length; ++i) {
361 locs_enc.push(encodeURI(locs[i]));
363 var twurl = 'http://search.twitter.com/search.json?q=' + locs_enc.join('+OR+') +
364 '&callback=' + cb_enc + '&cache=' + Math.floor(Math.random()*1024*1024);
369 this.buzz = new function() {
371 this.gen_style = function() {
372 var cssrules = '.social.logo.buzz {border:2px solid transparent;display:inline-block}';
373 cssrules += '.buzz .counter {background-image: url("http://www.gstatic.com/buzz/api/images/buzz-counter-long.png")}';
374 cssrules += '.buzz .counter {background-origin: padding-box;background-position:0 0;background-size: auto}';
375 cssrules += '.buzz .counter {background-clip: border-box;background-attachment:scroll}';
376 cssrules += '.buzz .counter {color:black;font-family:sans-serif;font-weight:700;font-size:11px}';
377 cssrules += '.buzz .counter {display:inline-block;margin:0;padding:0;text-align:center;text-decoration:none}';
378 cssrules += '.buzz .counter {padding-left:58px;width:94px;height:18px;line-height:18px}';
379 cssrules += '.buzz .counter .value {width:32px;text-align:center;line-height:18px}';
380 create_style(cssrules);
383 this.process = function(linkidx, data) {
384 var url = unsafeWindow.social.permalink[linkidx];
386 for (var link in data) {
387 if (opera && opera.postError) {
388 opera.postError("link: " + link + ", datum: " + data[link]);
395 var buzz = doc.createElement('a');
396 buzz.setAttribute('class', 'social logo buzz');
397 buzz.setAttribute('title', 'Buzz on '+url);
398 buzz.setAttribute('href', 'http://www.google.com/buzz/post?url=' + encodeURIComponent(url.replace(/localhost\/~oblomov\/wok/g,'wok.oblomov.eu')));
400 unsafeWindow.social.permalink_parent[linkidx].appendChild(buzz);
401 buzz.innerHTML = '<span class="counter"><span class="value">' + count + '</span></span>';
404 this.load = function(linkidx) {
405 var url = unsafeWindow.social.permalink[linkidx];
406 unsafeWindow.social.buzz['process_' + linkidx] = function(data) {
407 unsafeWindow.social.buzz.process(linkidx, data);
409 var wokloc = url.replace(/localhost\/~oblomov\/wok/g,'wok.oblomov.eu');
410 var callback = 'window.social.buzz.process_' + linkidx;
411 var cb_enc = escape(callback);
413 var burl = 'http://www.google.com/buzz/api/buzzThis/buzzCounter?url='+
414 encodeURI(wokloc) + '&callback=' + cb_enc + '&cache=' + Math.floor(Math.random()*1024*1024);
420 this.friendfeed.gen_style();
421 this.twitter.gen_style();
422 this.buzz.gen_style();
424 this.collect = function() {
425 /* we don't need stuff like //div[contains(concat(' ',normalize-space(@class),' '),' foo ')]
426 because we know they are single-classed */
427 var footers = doc.evaluate('//div[@class="inlinefooter"]',doc,null,XPathResult.ORDERED_NODE_ITERATOR_TYPE,null);
430 var footer = footers.iterateNext();
432 var perma = doc.evaluate('*//span[@class="permalink"]/a',footer,null,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue;
434 unsafeWindow.social.permalink.push(perma.href.toString());
435 unsafeWindow.social.permalink_parent.push(footer);
437 footer = footers.iterateNext();
440 if (opera && opera.postError) {
441 opera.postError("failed to iterate over permalinks: " + e);
446 this.load = function() {
447 for (var i=0; i < this.permalink.length; ++i) {
448 unsafeWindow.social.friendfeed.load(i);
449 unsafeWindow.social.twitter.load(i);
450 unsafeWindow.social.buzz.load(i);
455 unsafeWindow.social = new social();
456 unsafeWindow.social.collect();
457 unsafeWindow.social.load();