Add Tag handler. Also convert all tabs to spaces.
[blerg.git] / www / js / blerg.js
1 /* Blerg is (C) 2011 The Dominion of Awesome, and is distributed under a
2  * BSD-style license.  Please see the COPYING file for details.
3  */
4
5 // Config
6 var baseURL = '';
7 var recordTemplate = new Template(
8     '<div class="record">#{data}<div class="info">Posted #{date}. <a href="' + baseURL + '/\##{author}/#{record}" onclick="return qlink()">[permalink]</a> <a href="#" onclick="postPopup(\'@#{author}/#{record}: \'); return false">[reply]</a></div></div>'
9 );
10 var tagRecordTemplate = new Template(
11     '<div class="record">#{data}<div class="info">Posted by <a class="author ref" href="/\##{author}" onclick="return qlink()">@#{author}</a> on #{date}. <a href="' + baseURL + '/\##{author}/#{record}" onclick="return qlink()">[permalink]</a> <a href="#" onclick="postPopup(\'@#{author}/#{record}: \'); return false">[reply]</a></div></div>'
12 );
13 var latestRecordsTemplate = new Template(
14     '<div class="record"><a class="author ref" href="' + baseURL + '/\##{author}" onclick="return qlink()">@#{author}</a> #{data}</div>'
15 );
16
17 LoginStatus.prototype.post = function(msg) {
18     if (!this.loggedIn) {
19         alert("You are not logged in!");
20         return;
21     }
22
23     new Ajax.Request(baseURL + '/put', {
24         parameters: {
25             username: this.username,
26             data: msg
27         },
28         onSuccess: function(r) {
29             var j = r.responseText.evalJSON();
30             if (j && j.status == 'success') {
31                 $('post.content').value = '';
32                 if (location.hash != '#' + this.username) {
33                     qlink(this.username);
34                 } else {
35                     currentPager.itemCount++;
36                     currentPager.reload();
37                 }
38             } else {
39                 alert('Post failed!');
40             }
41         }.bind(this),
42         onFailure: function(r) {
43             alert('Post failed!');
44         }
45     });
46 }
47
48 // Base object for paged data
49 function Pager() {
50     this.itemsPerPage = 10;
51     this.itemCache = new Hash();
52     this.pageStart = null;
53 }
54
55 Pager.prototype.updateState = function(m) {
56     return false;
57 }
58
59 Pager.prototype.show = function() {
60     items.show();
61 }
62
63 Pager.prototype.hide = function() {
64     items.hide();
65     items.update();
66     $('newer_link').hide();
67     $('older_link').hide();
68 }
69
70 Pager.prototype.olderPage = function() {
71     if (this.pageStart >= this.itemsPerPage) {
72         qlink(this.baseFrag + '/p' + (this.pageStart - this.itemsPerPage));
73     }
74 }
75
76 Pager.prototype.newerPage = function() {
77     if (this.pageStart + this.itemsPerPage < this.itemCount) {
78         qlink(this.baseFrag + '/p' + (this.pageStart + this.itemsPerPage));
79     }
80 }
81
82 Pager.prototype.addItems = function(items) {
83     items.each(function(v) {
84         if (!this.itemCache[v.id])
85             this.itemCache[v.id] = v;
86     }.bind(this));
87 }
88
89 Pager.prototype.displayItems = function() {
90     if (this.pageStart == undefined)
91         this.pageStart == this.itemCount - 1;
92     items.update();
93
94     if (this.pageStart != undefined && this.itemCache[this.pageStart]) {
95         var end = (this.pageStart >= this.itemsPerPage ? this.pageStart - this.itemsPerPage + 1 : 0);
96         for (var i = this.pageStart; i >= end; i--) {
97             items.insert(this.itemCache[i].html);
98         }
99     } else {
100         items.insert("There doesn't seem to be anything here!");
101     }
102
103     if (this.pageStart < this.itemCount - 1) {
104         $('newer_link').href = baseURL + '/#' + this.baseFrag + '/p' + (this.pageStart + this.itemsPerPage);
105         $('newer_link').show();
106     } else {
107         $('newer_link').hide();
108     }
109
110     if (this.pageStart >= 10) {
111         $('older_link').href = baseURL + '/#' + this.baseFrag + '/p' + (this.pageStart - this.itemsPerPage);
112         $('older_link').show();
113     } else {
114         $('older_link').hide();
115     }
116
117     document.body.scrollTo();
118 }
119
120 Pager.prototype.reload = function() {
121     this.pageStart = null;
122     this.loadItems(null, null, Pager.prototype.showPageAt.bind(this, this.itemCount - 1));
123 }
124
125 Pager.prototype.showPageAt = function(r) {
126     var end = (r - 9 > 0 ? r - 9 : 0);
127     if (this.itemCache[r] && this.itemCache[end]) {
128         this.pageStart = r;
129         this.displayItems();
130     } else {
131         this.loadItems((r >= 49 ? r - 49 : 0), r, Pager.prototype.showPageAt.bind(this, r));
132     }
133 }
134
135 Pager.prototype.showRecord = function(r) {
136     if (this.itemCache[r]) {
137         $('older_link').hide();
138         $('newer_link').hide();
139         items.update(this.itemCache[r].html);
140     } else {
141         this.loadItems(r, r, Pager.prototype.showRecord.bind(this, r));
142     }
143 }
144
145 Pager.prototype.loadItems = function(from, to, continuation) { }
146
147 function displayError(msg) {
148     items.innerText = msg;
149 }
150
151 // Pager for browsing subscription feeds
152 function Feed(m) {
153     Pager.call(this);
154     this.username = loginStatus.username;
155     this.baseFrag = '/feed';
156     this.pageStart = parseInt(m[1]);
157
158     new Ajax.Request(baseURL + '/feed', {
159         method: 'post',
160         parameters: {
161             username: loginStatus.username
162         },
163         onSuccess: function(r) {
164             var response = r.responseText.evalJSON();
165             if (response) {
166                 var maxid = response.length - 1;
167                 response.each(function(v, i) {
168                     v.id = maxid - i;
169                     mangleRecord(v, tagRecordTemplate)
170                 });
171                 this.addItems(response);
172                 if (!this.pageStart)
173                     this.pageStart = response.length - 1;
174                 this.itemCount = response.length;
175                 loginStatus.requestFeedStatus();
176             }
177             this.displayItems();
178         }.bind(this),
179         onFailure: function(r) {
180             this.displayItems();
181         }.bind(this)
182     });
183 }
184 Feed.prototype = new Pager();
185 Feed.prototype.constructor = Feed;
186
187 Feed.prototype.updateState = function(m) {
188     this.pageStart = parseInt(m[1]) || this.itemCount - 1;
189     this.displayItems();
190
191     return true;
192 }
193
194 Feed.prototype.show = function() {
195     Pager.prototype.show.call(this);
196     $$('[name=section]').each(function(v) {
197         v.update(' ' + loginStatus.username + "'s spycam");
198     }.bind(this));
199 }
200
201
202 function postPopup(initial) {
203     if (loginStatus.loggedIn || initial) {
204         var post = $('post');
205         if (post.visible()) {
206             post.hide();
207         } else {
208             post.show();
209             if (initial) {
210                 $('post.content').value = initial;
211             } else if (!$('post.content').value && currentPager.username && currentPager.username != loginStatus.username) {
212                 $('post.content').value = '@' + currentPager.username + ': ';
213             }
214             $('post.content').focus();
215         }
216     }
217 }
218
219 function signup() {
220     var username = $('signup.username').value;
221     var password = $('signup.password').value;
222
223     new Ajax.Request(baseURL + '/create', {
224         parameters: {
225             username: username,
226             password: password
227         },
228         onSuccess: function(r) {
229             $('signup').hide();
230             qlink(username);
231
232             loginStatus.login(username, password);
233         },
234         onFailure: function(r) {
235             alert("Failed to create user");
236         }
237     });
238 }
239
240 function signup_cancel() {
241     $('signup').hide();
242     urlSwitch();
243 }
244
245 function passwd() {
246     var old_password = $('passwd.old_password').value;
247     var new_password = $('passwd.new_password').value;
248
249     new Ajax.Request(baseURL + '/passwd', {
250         parameters: {
251             username: loginStatus.username,
252             password: old_password,
253             new_password: new_password
254         },
255         onSuccess: function(r) {
256             if (r.responseJSON.status == 'success') {
257                 alert('Password changed');
258                 passwd_cancel();
259             } else {
260                 alert('Password change failed.  Your password has NOT been changed.');
261             }
262         },
263         onFailure: function(r) {
264             alert('Password change error');
265         }
266     });
267 }
268
269 function passwd_cancel() {
270     $('passwd').hide();
271     $('navigation').show();
272     urlSwitch();
273 }
274
275 function subscribe() {
276     new Ajax.Request(baseURL + '/subscribe/' + currentPager.username, {
277         method: 'post',
278         parameters: {
279             username: loginStatus.username
280         },
281         onSuccess: function(r) {
282             var response = r.responseText.evalJSON();
283             if (response.status == 'success') {
284                 alert("You call " + currentPager.username + " and begin breathing heavily into the handset.");
285                 $$('[name=user.subscribelink]').each(Element.hide);
286                 $$('[name=user.unsubscribelink]').each(Element.show);
287             } else {
288                 alert('Failed to subscribe. This is probably for the best');
289             }
290         },
291         onFailure: function(r) {
292             alert('Failed to subscribe. This is probably for the best');
293         }
294     });
295 }
296
297 function unsubscribe() {
298     new Ajax.Request(baseURL + '/unsubscribe/' + currentPager.username, {
299         method: 'post',
300         parameters: {
301             username: loginStatus.username
302         },
303         onSuccess: function(r) {
304             var response = r.responseText.evalJSON();
305             if (response.status == 'success') {
306                 alert("You come to your senses.");
307                 $$('[name=user.subscribelink]').each(Element.show);
308                 $$('[name=user.unsubscribelink]').each(Element.hide);
309             } else {
310                 alert('You are unable to tear yourself away (because something failed on the server)');
311             }
312         },
313         onFailure: function(r) {
314             alert('You are unable to tear yourself away (because something failed on the server)');
315         }
316     });
317 }
318
319 var tickerTimer = null;
320 var tickerHead, tickerTail;
321
322 function tickerFader(a, b, p) {
323     var p2 = 1 - p;
324
325     a.style.opacity = p;
326     a.style.lineHeight = (100 * p) + '%';
327
328     b.style.opacity = p2;
329     b.style.lineHeight = (100 * p2) + '%';
330     if (p == 1.0)
331         b.hide();
332 }
333
334 function ticker() {
335     tickerHead.show();
336     Bytex64.FX.run(tickerFader.curry(tickerHead, tickerTail), 0.5);
337     tickerHead = tickerHead.nextSibling;
338     tickerTail = tickerTail.nextSibling;
339     if (tickerHead == null) {
340         stopTicker();
341         loadLatest.delay(10);
342     }
343 }
344
345 function startTicker() {
346     stopTicker();
347     for (var elem = $('latest-posts').firstChild; elem != null; elem = elem.nextSibling) {
348         elem.hide();
349     }
350
351     // Show the first five
352     tickerHead = $('latest-posts').firstChild;
353     for (var i = 0; i < 10 && tickerHead; i++) {
354         tickerHead.show();
355         tickerHead = tickerHead.nextSibling;
356     }
357     tickerTail = $('latest-posts').firstChild;
358     tickerTimer = setInterval(ticker, 5000);
359 }
360
361 function stopTicker() {
362     if (tickerTimer)
363         clearInterval(tickerTimer);
364     tickerTimer = null;
365 }
366
367 function loadLatest() {
368     new Ajax.Request(baseURL + '/latest.json', {
369         method: 'GET',
370         onSuccess: function(r) {
371             var j = r.responseText.evalJSON();
372
373             $('latest-tags').update();
374             j.tags.each(function(v) {
375                 var a = new Element('a', {href: baseURL + '/#/tag/' + v});
376                 a.insert('#' + v);
377                 a.onclick = "return qlink()";
378                 a.className = 'ref';
379                 $('latest-tags').insert(a);
380                 $('latest-tags').appendChild(document.createTextNode(' '));
381             });
382
383             $('latest-posts').update();
384             j.records.each(function(v) {
385                 v.data = v.data.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
386                 v.date = (new Date(v.timestamp * 1000)).toString();
387                 var html = latestRecordsTemplate.evaluate(v);
388                 $('latest-posts').insert(html);
389             });
390             startTicker();
391         }
392     });
393 }
394
395 function ExternalURLPost(m) {
396     this.title = decodeURIComponent(m[1]).replace(']','').replace('[','');
397     this.url = decodeURIComponent(m[2]);
398 }
399
400 ExternalURLPost.prototype.show = function() {
401     $('post.content').value = '[' + this.title + '](' + this.url + ')';
402     $('post').show();
403 }