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 {padding:0;margin:0 auto;display:inline-block;border:2px lightblue}';
89 cssrules += '.social.logo.inactive {border-style:outset}';
90 cssrules += '.social.logo.active {border-style:inset}';
91 create_style(cssrules);
94 this.friendfeed = new function() {
95 img_preload.src = 'http://friendfeed.com/favicon.ico';
96 img_preload.src = 'http://friendfeed.com/static/images/logo-small.png';
98 var from2link = function(from) {
99 return '<a href="http://friendfeed.com/' + from.id + '">' + from.name + '</a>';
102 var from2linkimg = function(from) {
103 return '<a class="floatlink" href="http://friendfeed.com/' + from.id + '"><img src="http://friendfeed-api.com/v2/picture/' + from.id + '?size=small" /></a>';
106 this.gen_style = function() {
107 var cssrules = '.friendfeed {border-color:lightblue}';
108 cssrules += '.friendfeed .floatlink {margin:0 1ex .5ex 0;float:left}';
109 cssrules += '.friendfeed .entrybody {margin:0;clear:both}';
110 cssrules += '.friendfeed .entrydiv {padding:.5ex;border-top:.25ex dashed lightblue;text-align:justify}';
111 cssrules += '.friendfeed .entrydiv:first-child {border-top:none}';
112 cssrules += '.friendfeed .entryfrom {margin:.5ex 1ex .5ex 0;clear:both}';
113 cssrules += '.friendfeed .entrycomments {margin-left:2.2em}';
114 cssrules += '.friendfeed .entryliked {margin:0 2ex;text-indent:-2ex}';
115 cssrules += '.friendfeed .entrycomment {border-top: .1ex dotted blue; padding:1ex .5ex}';
116 cssrules += '.friendfeed .entrycomment p {margin:0}';
117 cssrules += '.friendfeed .entrycommentfrom {font-size:smaller;line-height:150%}';
118 create_style(cssrules);
121 this.process = function(linkidx, data) {
122 var url = unsafeWindow.social.permalink[linkidx];
125 if (!(data.entries && data.entries.length > 0))
130 var ecount = data.entries.length;
131 /* check if the only entry is from me and it has no comment --skip it then */
132 var entry = data.entries[0];
133 if (entry.from.id == 'oblomov' && !(entry.comments) && !(entry.likes))
136 var divid = 'friendfeed_'+linkidx;
137 var pdiv = doc.getElementById(divid);
140 pdiv = doc.createElement('div');
141 pdiv.setAttribute('class', 'social friendfeed invisible');
142 pdiv.setAttribute('id', divid);
143 pdiv.setAttribute('title', 'FriendFeed comments on '+url);
145 unsafeWindow.social.permalink_parent[linkidx].appendChild(pdiv);
147 var logo = doc.createElement('img');
148 logo.className = 'social logo inactive';
149 logo.src = linkidx ? 'http://friendfeed.com/favicon.ico' : 'http://friendfeed.com/static/images/logo-small.png';
150 logo.setAttribute('alt', 'FriendFeed logo');
151 logo.setAttribute('title', 'FriendFeed logo');
152 pdiv.parentNode.insertBefore(logo,pdiv);
154 logo.addEventListener('click', function() {
155 if (pdiv.className.match(' invisible')) {
156 logo.className = logo.className.replace(' inactive',' active');
157 pdiv.className = pdiv.className.replace(' invisible','');
159 logo.className = logo.className.replace(' active',' inactive');
160 pdiv.className = pdiv.className + ' invisible';
165 for (var i=0; i < ecount; ++i) {
166 var entry = data.entries[i];
168 var p_entry_id = divid + '_entry_' + entry.id;
170 // skip this entry if we already added it from a different source
171 if (doc.getElementById(p_entry_id))
177 if (entry.body != last_body) {
178 last_body = entry.body;
179 fed += '<p class="entrybody">' + entry.body;
181 ediv = doc.createElement('div');
182 ediv.className = 'entrydiv';
183 pdiv.appendChild(ediv);
185 ediv = pdiv.lastChild;
186 fed = ediv.innerHTML;
188 fed += '<p id="' + p_entry_id + '" class="entryfrom">' + from2linkimg(entry.from) + 'By ' + from2link(entry.from) + '<br/>' + date2link(entry.date, entry.url);
189 fed += '<div class="entrycomments"></div>';
190 ediv.innerHTML = fed;
191 ediv = ediv.lastChild;
193 var likes = entry.likes || [];
194 var jcount = likes.length;
196 var liked = '<p class="entryliked">Liked by ';
197 for (var j=0; j < jcount; ++j) {
200 liked += (j > 0 ? ', ' : '') + from2link(like.from);
202 liked += ' and ' + like.body;
205 var ldiv = doc.createElement('div');
206 ldiv.innerHTML = liked;
207 ediv.appendChild(ldiv);
210 var comments = entry.comments || [];
211 jcount = comments.length;
213 for (var j=0 ; j < jcount; ++j) {
214 var comment = comments[j];
215 var cdiv = doc.createElement('div');
216 cdiv.className = 'entrycomment';
217 ediv.appendChild(cdiv);
220 cdiv.innerHTML = from2linkimg(comment.from) +
221 '<p class="entrycommentfrom">' + date2link(comment.date) + ' by ' + from2link(comment.from) +
222 '<p>' + comment.body;
224 cdiv.innerHTML += comment.body;
231 this.load = function(linkidx) {
232 var url = unsafeWindow.social.permalink[linkidx];
233 var callback = '(function(data){social.friendfeed.process('+linkidx+',data)})';
234 var cb_enc = escape(callback);
236 var wokloc = url.replace(/localhost\/~oblomov\/wok/g,'wok.oblomov.eu');
237 /* the same page has at least two possible URLs: if it ends with /, it could be present without /,
238 and if it ends with /?index.html it could be present without it */
240 if (wokloc.charAt(wokloc.length-1) == '/') {
241 locs.push(wokloc.substring(0,wokloc.length-1));
243 if (wokloc.match(/\/index.html$/)) {
244 locs.push(wokloc.substring(0,wokloc.length-10));
245 locs.push(wokloc.substring(0,wokloc.length-11));
249 for (var i=0; i < locs.length; ++i) {
250 locs_enc.push(encodeURI(locs[i]));
252 var ff_url = 'http://friendfeed-api.com/v2/url?url=' + locs_enc.join(',') +
253 '&callback=' + cb_enc + '&cache=' + Math.floor(Math.random()*1024*1024);
256 ff_url = 'http://friendfeed-api.com/v2/search?q=' + locs_enc[0] +
257 '&callback=' + cb_enc + '&cache=' + Math.floor(Math.random()*1024*1024);
262 this.twitter = new function() {
263 img_preload.src = 'http://twitter.com/favicon.ico';
265 this.gen_style = function() {
266 var cssrules = '.twitter {border-color:lightblue;min-height:56px}';
267 cssrules += '.twitter .floatlink {margin:0 1ex .5ex 0;float:left}';
268 cssrules += '.twitter .entry {margin:0;clear:both;padding:.5ex;border-top:.25ex dashed lightblue;text-align:justify}';
269 cssrules += '.twitter .entry:first-child {border-top:none}';
270 cssrules += '.twitter .entryfrom {margin:.5ex 1ex .5ex 0}';
271 create_style(cssrules);
274 this.process = function(linkidx, data) {
275 var url = unsafeWindow.social.permalink[linkidx];
278 if (!(data.results && data.results.length > 0))
281 var divid = 'twitter_'+linkidx;
282 var pdiv = doc.getElementById(divid);
285 pdiv = doc.createElement('div');
286 pdiv.setAttribute('class', 'social twitter invisible');
287 pdiv.setAttribute('id', divid);
288 pdiv.setAttribute('title', 'Twits on '+url);
290 unsafeWindow.social.permalink_parent[linkidx].appendChild(pdiv);
292 var logo = doc.createElement('img');
293 logo.className = 'social logo inactive';
294 logo.src = 'http://twitter.com/favicon.ico';
295 logo.setAttribute('alt', 'Twitter logo');
296 logo.setAttribute('title', 'Twitter logo');
297 pdiv.parentNode.insertBefore(logo,pdiv);
299 logo.addEventListener('click', function() {
300 if (pdiv.className.match(' invisible')) {
301 logo.className = logo.className.replace(' inactive',' active');
302 pdiv.className = pdiv.className.replace(' invisible','');
304 logo.className = logo.className.replace(' active',' inactive');
305 pdiv.className = pdiv.className + ' invisible';
310 var ecount = data.results.length;
311 for (var i=0; i < ecount; ++i) {
312 var entry = data.results[i];
314 var p_entry_id = divid + '_entry_' + entry.id;
316 // skip this entry if we already added it from a different source
317 if (doc.getElementById(p_entry_id))
320 var ediv = doc.createElement('div');
321 ediv.className = 'entrydiv';
322 pdiv.appendChild(ediv);
324 var profilink = 'http://twitter.com/' + entry.from_user;
325 var content = '<p class="entry"><a class="floatlink" href="' + profilink + '">';
326 content += '<img src="' + entry.profile_image_url + '"></a>';
327 content += entry.text + '</p>';
328 content += '<p class="entryfrom">By <a href="' + profilink + '">'+ entry.from_user + '</a> ';
329 var entrydate = new Date(entry.created_at);
330 content += date2link(entrydate, profilink + '/status/' + entry.id_str);
332 ediv.innerHTML = content;
336 this.load = function(linkidx) {
337 var url = unsafeWindow.social.permalink[linkidx];
338 unsafeWindow.social.twitter['process_' + linkidx] = function(data) {
339 unsafeWindow.social.twitter.process(linkidx, data);
341 var callback = 'window.social.twitter.process_' + linkidx;
342 var cb_enc = escape(callback);
344 var wokloc = url.replace(/localhost\/~oblomov\/wok/g,'wok.oblomov.eu');
345 /* the same page has at least two possible URLs: if it ends with /, it could be present without /,
346 and if it ends with /?index.html it could be present without it */
350 for (var i=0; i < locs.length; ++i) {
351 locs_enc.push(encodeURI(locs[i]));
353 var twurl = 'http://search.twitter.com/search.json?q=' + locs_enc.join('+OR+') +
354 '&callback=' + cb_enc + '&cache=' + Math.floor(Math.random()*1024*1024);
360 this.friendfeed.gen_style();
361 this.twitter.gen_style();
363 this.collect = function() {
364 /* we don't need stuff like //div[contains(concat(' ',normalize-space(@class),' '),' foo ')]
365 because we know they are single-classed */
366 var footers = doc.evaluate('//div[@class="inlinefooter"]',doc,null,XPathResult.ORDERED_NODE_ITERATOR_TYPE,null);
369 var footer = footers.iterateNext();
371 var perma = doc.evaluate('*//span[@class="permalink"]/a',footer,null,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue;
373 unsafeWindow.social.permalink.push(perma.href.toString());
374 unsafeWindow.social.permalink_parent.push(footer);
376 footer = footers.iterateNext();
379 if (opera && opera.postError) {
380 opera.postError("failed to iterate over permalinks: " + e);
385 this.load = function() {
386 for (var i=0; i < this.permalink.length; ++i) {
387 unsafeWindow.social.friendfeed.load(i);
388 unsafeWindow.social.twitter.load(i);
393 unsafeWindow.social = new social();
394 unsafeWindow.social.collect();
395 unsafeWindow.social.load();