7bb5fe24788262f65a82eacdec390165f0de6fa8
[blerg.git] / www / js / blerg.js
1 // Config
2 var baseURL = '';
3 var recordTemplate = new Template(
4     '<div class="record">#{data}<div class="info">Posted #{date}</div></div>'
5 );
6 var tagRecordTemplate = new Template(
7     '<div class="record">#{data}<div class="info">Posted by <a href="\##{author}">@#{author}</a> on #{date}</div></div>'
8 );
9
10 // Page elements
11 var items;
12
13 // Other globals
14 var currentPager;
15 var loginStatus;
16
17 // Object to keep track of login status
18 function LoginStatus() {
19     var cookies = {};
20     document.cookie.split(/;\s+/).each(function(v) {
21         kv = v.split('=');
22         cookies[kv[0]] = kv[1];
23     });
24     if (cookies.auth && cookies.username) {
25         this.loggedIn = true;
26         this.username = cookies.username;
27     } else {
28         this.loggedIn = false;
29         this.username = null;
30     }
31     this.update();
32 }
33
34 LoginStatus.prototype.login = function(username, password) {
35     new Ajax.Request(baseURL + '/login', {
36         parameters: {
37             username: username,
38             password: password,
39         },
40         onSuccess: function(r) {
41             var j = r.responseText.evalJSON();
42             if (j && j.status == 'success') {
43                 this.loggedIn = true;
44                 this.username = username;
45                 document.cookie = "username=" + username;
46                 $('login.password').value = '';
47                 this.update();
48             } else {
49                 alert("Could not log in");
50                 $('login.username').focus();
51             }
52         }.bind(this),
53         onFailure: function(r) {
54             alert("Could not log in");
55             $('login.username').focus();
56         }
57     });
58 }
59
60 LoginStatus.prototype.logout = function() {
61     new Ajax.Request(baseURL + '/logout', {
62         parameters: {
63             username: this.username,
64         },
65         onSuccess: function(r) {
66             this.loggedIn = false;
67             document.cookie = "auth=; expires=1-Jan-1970 00:00:00 GMT";
68             this.update();
69         }.bind(this),
70     });
71     document.cookie = "username=; expires=1-Jan-1970 00:00:00 GMT";
72 }
73
74 LoginStatus.prototype.update = function() {
75     if (this.loggedIn) {
76         $('userlink').href = '#' + this.username;
77         $('userlink').update('@' + this.username);
78         $('login').hide();
79         $('logout').show();
80     } else {
81         $('post').hide();
82         $('login').show();
83         $('logout').hide();
84     }
85 }
86
87 LoginStatus.prototype.post = function(msg) {
88     if (!this.loggedIn) {
89         alert("You are not logged in!");
90         exit(0);
91     }
92
93     new Ajax.Request(baseURL + '/put', {
94         parameters: {
95             username: this.username,
96             data: msg
97         },
98         onSuccess: function(r) {
99             var j = r.responseText.evalJSON();
100             if (j && j.status == 'success') {
101                 $('post.content').value = '';
102                 if (location.hash != '#' + this.username) {
103                     location.hash = this.username;
104                     hashSwitch();
105                 } else {
106                     currentPager.itemCount++;
107                     currentPager.pageStart = null;
108                     currentPager.loadItems();
109                 }
110             } else {
111                 alert('Post failed!');
112             }
113         }.bind(this),
114         onFailure: function(r) {
115             alert('Post failed!');
116         }
117     });
118 }
119
120
121 // Base object for paged data
122 function Pager() {
123     this.itemsPerPage = 10;
124 }
125
126 Pager.prototype.initPager = function() {
127     this.itemCache = new Hash();
128     this.pageStart = null;
129 }
130
131 Pager.prototype.olderPage = function() {
132     if (this.pageStart >= this.itemsPerPage) {
133         this.pageStart -= this.itemsPerPage;
134         this.displayItems();
135     }
136 }
137
138 Pager.prototype.newerPage = function() {
139     if (this.pageStart + this.itemsPerPage < this.itemCount) {
140         this.pageStart += this.itemsPerPage;
141         this.displayItems();
142     }
143 }
144
145 Pager.prototype.addItems = function(items) {
146     items.each(function(v) {
147         if (!this.itemCache[v.id])
148             this.itemCache[v.id] = v;
149     }.bind(this));
150 }
151
152 Pager.prototype.displayItems = function() {
153     if (this.pageStart == undefined)
154         this.pageStart == this.itemCount - 1;
155     items.update();
156
157     if (this.pageStart != undefined && this.itemCache[this.pageStart]) {
158         var end = (this.pageStart >= this.itemsPerPage ? this.pageStart - this.itemsPerPage + 1 : 0);
159         for (var i = this.pageStart; i >= end; i--) {
160             items.insert(this.itemCache[i].html);
161         }
162     } else {
163         items.insert("There doesn't seem to be anything here!");
164     }
165
166     if (this.pageStart < this.itemCount - 1)
167         $('newer_link').show();
168     else
169         $('newer_link').hide();
170
171     if (this.pageStart >= 10)
172         $('older_link').show();
173     else
174         $('older_link').hide();
175 }
176
177
178 // Object to render user pages
179 function User(username) {
180     this.initPager();
181     this.username = username;
182
183     new Ajax.Request(baseURL + '/info/' + username, {
184         method: 'get',
185         onSuccess: function(r) {
186             var j = r.responseText.evalJSON();
187             if (j) {
188                 this.itemCount = parseInt(j.record_count);
189                 this.displayItems();
190             }
191         }.bind(this),
192     });
193 }
194 User.prototype = new Pager();
195 User.prototype.constructor = User;
196
197 User.prototype.show = function() {
198     $$('[name=section]').each(function(v) { v.update(' @' + this.username) }.bind(this));
199     $('welcome').hide();
200     items.show();
201     $('reflink').href = '#ref/' + this.username;
202     $('rss').show();
203     $('rsslink').href = '/rss/' + this.username;
204 }
205
206 User.prototype.loadItems = function(from, to) {
207     var url;
208     if (from != undefined && to != undefined) {
209         url = baseURL + '/get/' + this.username + '/' + from + '-' + to;
210         this.pageStart = to;
211     } else {
212         url = baseURL + '/get/' + this.username;
213     }
214
215     new Ajax.Request(url, {
216         method: 'get',
217         onSuccess: function(r) {
218             var records = r.responseText.evalJSON();
219             if (records && records.length > 0) {
220                 records.each(function(v) {
221                     v.id = v.record;
222                     mangleRecord(v, recordTemplate);
223                 });
224                 this.addItems(records);
225                 if (!this.pageStart)
226                     this.pageStart = records[0].recInt;
227             }
228             this.displayItems();
229         }.bind(this),
230         onFailure: function(r) {
231             this.displayItems();
232         }.bind(this),
233         on404: function(r) {
234             displayError('User not found');
235         }
236     });
237 }
238
239 function mangleRecord(record, template) {
240     record.recInt = parseInt(record.record);
241
242     // Sanitize HTML input
243     record.data = record.data.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt');
244
245     // Turn HTTP URLs into links
246     record.data = record.data.replace(/(\s|^)(https?:\/\/[a-zA-Z0-9.-]*[a-zA-Z0-9](\/(\S*[^.!,;?()\s])?)?)/g, '$1<a href="$2">$2</a>');
247
248     // Turn markdown links into links
249     record.data = record.data.replace(/(\s|^)\[([^\]]+)\]\((https?:\/\/[a-zA-Z0-9.-]*[a-zA-Z0-9](\/[^)]*?)?)\)/, '$1<a href="$3">$2</a>');
250
251     // Turn refs and tags into links
252     record.data = record.data.replace(/(\s|^)#(\w+)/g, '$1<a href="#tag/$2">#$2</a>');
253     record.data = record.data.replace(/(\s|^)@(\w+)/g, '$1<a href="#$2">@$2</a>');
254
255     // Turn newlines into linebreaks and paragraphs
256     record.data = record.data.replace(/\r?\n\r?\n/g, "<p>").replace(/\r?\n/g, "<br>");
257
258     record.date = (new Date(record.timestamp * 1000)).toString();
259     record.html = template.evaluate(record);
260 }
261
262 function displayError(msg) {
263     items.innerText = msg;
264 }
265
266
267 // Object for browsing tags
268 function Tag(type, tag) {
269     this.initPager();
270     this.type = type;
271     this.tag = tag;
272
273     var url = baseURL + "/tag/";
274     switch(type) {
275     case 'tag':
276         //url += '%23';
277         url += 'H';  // apache is eating the hash, even encoded.  Probably a security feature.
278         break;
279     case 'ref':
280         url += '%40';
281         break;
282     default:
283         alert('Invalid tag type: ' + type);
284         return;
285     }
286     url += tag;
287
288     new Ajax.Request(url, {
289         method: 'get',
290         onSuccess: function(r) {
291             var j = r.responseText.evalJSON();
292             if (j) {
293                 var maxid = j.length - 1;
294                 j.each(function(v, i) {
295                     v.id = maxid - i;
296                     mangleRecord(v, tagRecordTemplate)
297                 });
298                 this.addItems(j);
299                 this.pageStart = j.length - 1;
300             }
301             this.displayItems();
302         }.bind(this),
303         onFailure: function(r) {
304             this.displayItems();
305         }.bind(this)
306     });
307
308 }
309 Tag.prototype = new Pager();
310 Tag.prototype.constructor = Tag;
311
312 Tag.prototype.show = function() {
313     var ctype = {ref: '@', tag: '#'}[this.type];
314
315     $$('[name=section]').each(function(v) {
316         v.update(' about ' + ctype + this.tag);
317     }.bind(this));
318     $('welcome').hide();
319     $('post').hide();
320     $('older_link').hide();
321     $('newer_link').hide();
322     $('rss').hide();
323     items.show();
324 }
325
326 function postPopup() {
327     if (loginStatus.loggedIn) {
328         var post = $('post');
329         if (post.visible()) {
330             post.hide();
331         } else {
332             post.show();
333             if (currentPager.username && currentPager.username != loginStatus.username && !$('post.content').value) {
334                 $('post.content').value = '@' + currentPager.username + ': ';
335             }
336             $('post.content').focus();
337         }
338     }
339 }
340
341 function signup() {
342     var username = $('signup.username').value;
343     var password = $('signup.password').value;
344
345     new Ajax.Request(baseURL + '/create', {
346         parameters: {
347             username: username,
348             password: password,
349         },
350         onSuccess: function(r) {
351             $('signup').hide();
352             location.hash = username;
353             hashSwitch();
354
355             loginStatus.login(username, password);
356         },
357         onFailure: function(r) {
358             alert("Failed to create user");
359         }
360     });
361 }
362
363 function signup_cancel() {
364     $('signup').hide();
365     hashSwitch();
366 }
367
368 function newer_page() {
369     if (currentPager)
370         currentPager.newerPage();
371 }
372
373 function older_page() {
374     if (currentPager)
375         currentPager.olderPage();
376 }
377
378 var resizePostContentTimeout = null;
379 function resizePostContent() {
380     if (resizePostContentTimeout)
381         clearTimeout(resizePostContentTimeout);
382     resizePostContentTimeout = setTimeout(function() {
383         var c = $('post.content');
384         var lines = Math.floor(c.value.length / (100 * (c.clientWidth / 1000))) + 1;
385         var m = c.value.match(/\r?\n/g);
386         if (m)
387             lines += m.length;
388         if (lines <= 3) {
389             c.style.height = "";
390         } else {
391             c.style.height = (lines * 17) + "pt";
392         }
393         resizePostContentTimeout = null;
394     }, 150);
395 }
396
397 function hashSwitch() {
398     var m = location.hash.match(/^#((ref|tag)\/)?(\w+)(:(\d+))?/);
399     if (m) {
400         if (m[1]) {
401             currentPager = new Tag(m[2], m[3]);
402             currentPager.show();
403         } else {
404             if (!currentPager || currentPager.username != m[3])
405                 currentPager = new User(m[3]);
406             currentPager.show();
407             loginStatus.update();
408
409             if (m[5]) {
410                 var r = parseInt(m[5]);
411                 currentPager.showRecord = r;
412                 if (currentPager.recordCache[r]) {
413                     currentPager.displayItems();
414                 } else {
415                     currentPager.loadItems((r >= 49 ? r - 49 : 0), r);
416                 }
417             } else {
418                 currentPager.pageStart = currentPager.itemCount - 1;
419                 currentPager.loadItems();
420             }
421         }
422     } else {
423         $$('[name=section]').each(function(v) { v.update('Welcome') });
424         $('signup').hide();
425         items.update();
426         items.hide();
427         $('newer_link').hide();
428         $('older_link').hide();
429         $('welcome').show();
430         $('rss').hide();
431     }
432 }
433
434 var lastHash;
435 function hashCheck() {
436     if (location.hash != lastHash) {
437         lastHash = location.hash;
438         hashSwitch();
439     }
440 }
441
442 function init() {
443     items = $('items');
444     loginStatus = new LoginStatus();
445
446     lastHash = location.hash;
447     hashSwitch();
448
449     setInterval(hashCheck, 250);
450
451     document.body.observe('keyup', function(event) {
452         if (event.shiftKey && event.keyCode == '32') {
453             postPopup();
454             event.stop();
455         }
456     });
457 }