X-Git-Url: http://git.bytex64.net/?a=blobdiff_plain;f=www%2Fjs%2Fblerg.js;h=6d555b8868058767eaa942c5af84b623a825c443;hb=21db04ef3e4f66d7b5c4c9be2be36703b18cbcaa;hp=2ee3467dcef8e2dc933364688cc9b4fc7b8a6436;hpb=0a73588293307fc51c995bda6f98e082a61df89c;p=blerg.git diff --git a/www/js/blerg.js b/www/js/blerg.js index 2ee3467..6d555b8 100644 --- a/www/js/blerg.js +++ b/www/js/blerg.js @@ -5,10 +5,13 @@ // Config var baseURL = ''; var recordTemplate = new Template( - '
#{data}
Posted #{date}
' + '
#{data}
Posted #{date}. [permalink] [reply]
' ); var tagRecordTemplate = new Template( - '
#{data}
Posted by @#{author} on #{date}
' + '
#{data}
Posted by @#{author} on #{date}. [permalink] [reply]
' +); +var latestRecordsTemplate = new Template( + '
@#{author} #{data}
' ); // Page elements @@ -28,6 +31,8 @@ function LoginStatus() { if (cookies.auth && cookies.username) { this.loggedIn = true; this.username = cookies.username; + this.requestFeedStatus(); + this.feedStatusUpdateInterval = setInterval(this.requestFeedStatus.bind(this), 900000); } else { this.loggedIn = false; this.username = null; @@ -48,6 +53,8 @@ LoginStatus.prototype.login = function(username, password) { this.username = username; document.cookie = "username=" + username; $('login.password').value = ''; + this.requestFeedStatus(); + this.feedStatusUpdateInterval = setInterval(this.requestFeedStatus.bind(this), 900000); this.update(); } else { alert("Could not log in"); @@ -66,10 +73,11 @@ LoginStatus.prototype.logout = function() { parameters: { username: this.username }, - onSuccess: function(r) { + onComplete: function(r) { this.loggedIn = false; document.cookie = "auth=; expires=1-Jan-1970 00:00:00 GMT"; this.update(); + clearInterval(this.feedStatusUpdateInterval); }.bind(this) }); document.cookie = "username=; expires=1-Jan-1970 00:00:00 GMT"; @@ -77,8 +85,9 @@ LoginStatus.prototype.logout = function() { LoginStatus.prototype.update = function() { if (this.loggedIn) { - $('userlink').href = '/#' + this.username; + $('userlink').href = baseURL + '/#' + this.username; $('userlink').update('@' + this.username); + $('reflink').href = baseURL + '/#/ref/' + this.username; $('login').hide(); $('logout').show(); } else { @@ -91,7 +100,7 @@ LoginStatus.prototype.update = function() { LoginStatus.prototype.post = function(msg) { if (!this.loggedIn) { alert("You are not logged in!"); - exit(0); + return; } new Ajax.Request(baseURL + '/put', { @@ -104,12 +113,10 @@ LoginStatus.prototype.post = function(msg) { if (j && j.status == 'success') { $('post.content').value = ''; if (location.hash != '#' + this.username) { - location.hash = this.username; - hashSwitch(); + qlink(this.username); } else { currentPager.itemCount++; - currentPager.pageStart = null; - currentPager.loadItems(); + currentPager.reload(); } } else { alert('Post failed!'); @@ -121,28 +128,51 @@ LoginStatus.prototype.post = function(msg) { }); } +LoginStatus.prototype.requestFeedStatus = function() { + new Ajax.Request('/feedinfo', { + parameters: { username: this.username }, + onSuccess: function(r) { + var j = r.responseText.evalJSON(); + if (j['new'] > 0) { + $('newFeedMessages').update('(' + j['new'] + ' new)'); + } else { + $('newFeedMessages').update(''); + } + } + }); +} // Base object for paged data function Pager() { this.itemsPerPage = 10; -} - -Pager.prototype.initPager = function() { this.itemCache = new Hash(); this.pageStart = null; } +Pager.prototype.updateState = function(m) { + return false; +} + +Pager.prototype.show = function() { + items.show(); +} + +Pager.prototype.hide = function() { + items.hide(); + items.update(); + $('newer_link').hide(); + $('older_link').hide(); +} + Pager.prototype.olderPage = function() { if (this.pageStart >= this.itemsPerPage) { - this.pageStart -= this.itemsPerPage; - this.displayItems(); + qlink(this.baseFrag + '/p' + (this.pageStart - this.itemsPerPage)); } } Pager.prototype.newerPage = function() { if (this.pageStart + this.itemsPerPage < this.itemCount) { - this.pageStart += this.itemsPerPage; - this.displayItems(); + qlink(this.baseFrag + '/p' + (this.pageStart + this.itemsPerPage)); } } @@ -167,47 +197,141 @@ Pager.prototype.displayItems = function() { items.insert("There doesn't seem to be anything here!"); } - if (this.pageStart < this.itemCount - 1) + if (this.pageStart < this.itemCount - 1) { + $('newer_link').href = baseURL + '/#' + this.baseFrag + '/p' + (this.pageStart + this.itemsPerPage); $('newer_link').show(); - else + } else { $('newer_link').hide(); + } - if (this.pageStart >= 10) + if (this.pageStart >= 10) { + $('older_link').href = baseURL + '/#' + this.baseFrag + '/p' + (this.pageStart - this.itemsPerPage); $('older_link').show(); - else + } else { $('older_link').hide(); + } + + document.body.scrollTo(); } +Pager.prototype.reload = function() { + this.pageStart = null; + this.loadItems(null, null, Pager.prototype.showPageAt.bind(this, this.itemCount - 1)); +} -// Object to render user pages -function User(username) { - this.initPager(); - this.username = username; +Pager.prototype.showPageAt = function(r) { + var end = (r - 9 > 0 ? r - 9 : 0); + if (this.itemCache[r] && this.itemCache[end]) { + this.pageStart = r; + this.displayItems(); + } else { + this.loadItems((r >= 49 ? r - 49 : 0), r, Pager.prototype.showPageAt.bind(this, r)); + } +} - new Ajax.Request(baseURL + '/info/' + username, { - method: 'get', - onSuccess: function(r) { - var j = r.responseText.evalJSON(); - if (j) { - this.itemCount = parseInt(j.record_count); - this.displayItems(); - } - }.bind(this) - }); +Pager.prototype.showRecord = function(r) { + if (this.itemCache[r]) { + $('older_link').hide(); + $('newer_link').hide(); + items.update(this.itemCache[r].html); + } else { + this.loadItems(r, r, Pager.prototype.showRecord.bind(this, r)); + } +} + +Pager.prototype.loadItems = function(from, to, continuation) { } + + +// Object to render user pages +function User(m) { + Pager.call(this); + this.username = m[1]; + this.baseFrag = m[1]; + this.permalink = (m[2] != 'p'); + this.pageStart = parseInt(m[3]); } User.prototype = new Pager(); User.prototype.constructor = User; +User.prototype.updateState = function(m) { + if (m[1] != this.username) + return false; + + this.permalink = (m[2] != 'p'); + this.pageStart = parseInt(m[3]); + this.show(); + + return true; +} + User.prototype.show = function() { + Pager.prototype.show.call(this); + $$('[name=section]').each(function(v) { v.update(' @' + this.username) }.bind(this)); - $('welcome').hide(); - items.show(); - $('reflink').href = '/#ref/' + this.username; $('rss').show(); $('rsslink').href = '/rss/' + this.username; + $$('[name=user.reflink]').each(function(e) { + e.href = baseURL + '/#/ref/' + this.username; + }.bind(this)); + $('usercontrols').show(); + + if (this.permalink && this.pageStart >= 0) { + this.showRecord(this.pageStart); + } else if (this.pageStart >= 0) { + this.showPageAt(this.pageStart); + } else { + this.reload(); + } +} + +User.prototype.hide = function() { + Pager.prototype.hide.call(this); + $('signup').hide(); + $('rss').hide(); + $('usercontrols').hide(); } -User.prototype.loadItems = function(from, to) { +User.prototype.reload = function() { + this.pageStart = null; + + $$('[name=user.subscribelink]').each(Element.hide); + $$('[name=user.unsubscribelink]').each(Element.hide); + + if (loginStatus.loggedIn) { + new Ajax.Request(baseURL + '/feedinfo/' + this.username, { + method: 'post', + parameters: { + username: loginStatus.username + }, + onSuccess: function(r) { + var json = r.responseText.evalJSON(); + if (json.subscribed) { + $$('[name=user.subscribelink]').each(Element.hide); + $$('[name=user.unsubscribelink]').each(Element.show); + } else { + $$('[name=user.subscribelink]').each(Element.show); + $$('[name=user.unsubscribelink]').each(Element.hide); + } + } + }); + } + + new Ajax.Request(baseURL + '/info/' + this.username, { + method: 'get', + onSuccess: function(r) { + var response = r.responseText.evalJSON(); + if (response) { + this.itemCount = parseInt(response.record_count); + this.showPageAt(this.itemCount - 1); + } + }.bind(this) + }); +} + +User.prototype.loadItems = function(from, to, continuation) { + if (to < 0) + return; + var url; if (from != undefined && to != undefined) { url = baseURL + '/get/' + this.username + '/' + from + '-' + to; @@ -223,13 +347,14 @@ User.prototype.loadItems = function(from, to) { if (records && records.length > 0) { records.each(function(v) { v.id = v.record; + v.author = this.username; mangleRecord(v, recordTemplate); - }); + }.bind(this)); this.addItems(records); if (!this.pageStart) this.pageStart = records[0].recInt; } - this.displayItems(); + continuation(); }.bind(this), onFailure: function(r) { this.displayItems(); @@ -243,26 +368,111 @@ User.prototype.loadItems = function(from, to) { function mangleRecord(record, template) { record.recInt = parseInt(record.record); - // Sanitize HTML input - record.data = record.data.replace('&', '&').replace('<', '<').replace('>', '>'); + var lines = record.data.split(/\r?\n/); + if (lines[lines.length - 1] == '') + lines.pop(); + + var out = ['

']; + var endpush = null; + var listMode = false; + lines.each(function(l) { + if (l == '') { + if (out[out.length - 1] == '
') { + out[out.length - 1] = '

'; + } + if (out[out.length - 1] == '') { + out.push(''); + out.push('

'); + listMode = false; + } + return; + } + + // Put quoted material into a special paragraph + if (l[0] == '>') { + var pi = out.lastIndexOf('

'); + if (pi != -1) { + out[pi] = '

'; + l = l.replace(/^>\s*/, ''); + } + } - // Turn HTTP URLs into links - record.data = record.data.replace(/(\s|^)(https?:\/\/[a-zA-Z0-9.-]*[a-zA-Z0-9](\/(\S*[^.!,;?()\s])?)?)/g, '$1$2'); + // Sanitize HTML input + l = l.replace(/&/g, '&').replace(//g, '>'); - // Turn markdown links into links - record.data = record.data.replace(/(\s|^)\[([^\]]+)\]\((https?:\/\/[a-zA-Z0-9.-]*[a-zA-Z0-9](\/[^)]*?)?)\)/, '$1$2'); + // Turn HTTP URLs into links + l = l.replace(/(\s|^)(https?:\/\/[a-zA-Z0-9.-]*[a-zA-Z0-9](\/([^\s"]*[^.!,;?()\s])?)?)/g, '$1$2'); - // Turn *foo* into italics and **foo** into bold - record.data = record.data.replace(/(\s)\*\*([^*]+)\*\*(\s)/, '$1$2$3'); - record.data = record.data.replace(/(\s)\*([^*]+)\*(\s)/, '$1$2$3'); + // Turn markdown links into links + var re; - // Turn refs and tags into links - record.data = record.data.replace(/(\s|^)#(\w+)/g, '$1#$2'); - record.data = record.data.replace(/(\s|^)@(\w+)/g, '$1@$2'); + // Craft a regex that finds URLs that end in the extensions specified by BlergMedia.audioExtensions. + re = new RegExp('(\\s|^)\\[([^\\]]+)\\]\\((https?:\\/\\/[a-zA-Z0-9.-]*[a-zA-Z0-9]\\/[^)"]*?\\.(' + BlergMedia.audioExtensions.join('|') + '))\\)', 'g'); + l = l.replace(re, '$1$2 '); + + // Ditto, but use the extended markdown link syntax to specify the format + re = new RegExp('(\\s|^)\\[([^\\]]+)\\]\\((https?:\\/\\/[a-zA-Z0-9.-]*[a-zA-Z0-9](\\/[^)"]*?)?)\\s+audio:(' + BlergMedia.audioExtensions.join('|') + ')\\)', 'g'); + l = l.replace(re, '$1$2 '); + + // Craft a regex that finds URLs that end in the extensions specified by BlergMedia.videoExtensions. + re = new RegExp('(\\s|^)\\[([^\\]]+)\\]\\((https?:\\/\\/[a-zA-Z0-9.-]*[a-zA-Z0-9]\\/[^)"]*?\\.(' + BlergMedia.videoExtensions.join('|') + '))\\)', 'g'); + l = l.replace(re, '$1$2 '); + + // Ditto, but use the extended markdown link syntax to specify the format + re = new RegExp('(\\s|^)\\[([^\\]]+)\\]\\((https?:\\/\\/[a-zA-Z0-9.-]*[a-zA-Z0-9](\\/[^)"]*?)?)\\s+video:(' + BlergMedia.videoExtensions.join('|') + ')\\)', 'g'); + l = l.replace(re, '$1$2 '); + + // Regular markdown links + l = l.replace(/(\s|^)\[([^\]]+)\]\((https?:\/\/[a-zA-Z0-9.-]*[a-zA-Z0-9](\/[^)"]*?)?)\)/g, '$1$2'); + + // Turn *foo* into italics and **foo** into bold + l = l.replace(/([^\w\\]|^)\*\*(\w[^*]*)\*\*(\W|$)/g, '$1$2$3'); + l = l.replace(/([^\w\\]|^)\*(\w[^*]*)\*(\W|$)/g, '$1$2$3'); + + // Turn refs and tags into links + l = l.replace(/(\s|^)#([A-Za-z0-9_-]+)/g, '$1#$2'); + l = l.replace(/(\s|^)@([A-Za-z0-9_-]+)(\/\d+)?/g, '$1@$2'); + + // Create lists when lines begin with * + if (l[0] == '*') { + if (!listMode) { + var pi = out.lastIndexOf('

'); + out[pi] = '

'); + + record.data = out.join(''); record.date = (new Date(record.timestamp * 1000)).toString(); record.html = template.evaluate(record); } @@ -273,25 +483,28 @@ function displayError(msg) { // Object for browsing tags -function Tag(type, tag) { - this.initPager(); - this.type = type; - this.tag = tag; +function Tag(m) { + Pager.call(this); + this.type = m[1]; + this.tag = m[2]; + this.pageStart = parseInt(m[3]); var url = baseURL + "/tag/"; - switch(type) { + switch(this.type) { case 'tag': //url += '%23'; url += 'H'; // apache is eating the hash, even encoded. Probably a security feature. + this.baseFrag = '/tag/' + this.tag; break; case 'ref': url += '%40'; + this.baseFrag = '/ref/' + this.tag; break; default: - alert('Invalid tag type: ' + type); + alert('Invalid tag type: ' + this.type); return; } - url += tag; + url += this.tag; new Ajax.Request(url, { method: 'get', @@ -304,7 +517,9 @@ function Tag(type, tag) { mangleRecord(v, tagRecordTemplate) }); this.addItems(j); - this.pageStart = j.length - 1; + if (!this.pageStart) + this.pageStart = j.length - 1; + this.itemCount = j.length; } this.displayItems(); }.bind(this), @@ -317,28 +532,88 @@ function Tag(type, tag) { Tag.prototype = new Pager(); Tag.prototype.constructor = Tag; +Tag.prototype.updateState = function(m) { + if (this.type != m[1] || this.tag != m[2]) + return false; + + this.pageStart = parseInt(m[3]) || this.itemCount - 1; + this.displayItems(); + + return true; +} + Tag.prototype.show = function() { + Pager.prototype.show.call(this); + var ctype = {ref: '@', tag: '#'}[this.type]; $$('[name=section]').each(function(v) { v.update(' about ' + ctype + this.tag); }.bind(this)); - $('welcome').hide(); - $('post').hide(); - $('older_link').hide(); - $('newer_link').hide(); - $('rss').hide(); - items.show(); } -function postPopup() { - if (loginStatus.loggedIn) { + +// Pager for browsing subscription feeds +function Feed(m) { + Pager.call(this); + this.username = loginStatus.username; + this.baseFrag = '/feed'; + this.pageStart = parseInt(m[1]); + + new Ajax.Request(baseURL + '/feed', { + method: 'post', + parameters: { + username: loginStatus.username + }, + onSuccess: function(r) { + var response = r.responseText.evalJSON(); + if (response) { + var maxid = response.length - 1; + response.each(function(v, i) { + v.id = maxid - i; + mangleRecord(v, tagRecordTemplate) + }); + this.addItems(response); + if (!this.pageStart) + this.pageStart = response.length - 1; + this.itemCount = response.length; + loginStatus.requestFeedStatus(); + } + this.displayItems(); + }.bind(this), + onFailure: function(r) { + this.displayItems(); + }.bind(this) + }); +} +Feed.prototype = new Pager(); +Feed.prototype.constructor = Feed; + +Feed.prototype.updateState = function(m) { + this.pageStart = parseInt(m[1]) || this.itemCount - 1; + this.displayItems(); + + return true; +} + +Feed.prototype.show = function() { + Pager.prototype.show.call(this); + $$('[name=section]').each(function(v) { + v.update(' ' + loginStatus.username + "'s spycam"); + }.bind(this)); +} + + +function postPopup(initial) { + if (loginStatus.loggedIn || initial) { var post = $('post'); if (post.visible()) { post.hide(); } else { post.show(); - if (currentPager.username && currentPager.username != loginStatus.username && !$('post.content').value) { + if (initial) { + $('post.content').value = initial; + } else if (!$('post.content').value && currentPager.username && currentPager.username != loginStatus.username) { $('post.content').value = '@' + currentPager.username + ': '; } $('post.content').focus(); @@ -357,8 +632,7 @@ function signup() { }, onSuccess: function(r) { $('signup').hide(); - location.hash = username; - hashSwitch(); + qlink(username); loginStatus.login(username, password); }, @@ -370,17 +644,81 @@ function signup() { function signup_cancel() { $('signup').hide(); - hashSwitch(); + urlSwitch(); +} + +function passwd() { + var old_password = $('passwd.old_password').value; + var new_password = $('passwd.new_password').value; + + new Ajax.Request(baseURL + '/passwd', { + parameters: { + username: loginStatus.username, + password: old_password, + new_password: new_password + }, + onSuccess: function(r) { + if (r.responseJSON.status == 'success') { + alert('Password changed'); + passwd_cancel(); + } else { + alert('Password change failed. Your password has NOT been changed.'); + } + }, + onFailure: function(r) { + alert('Password change error'); + } + }); +} + +function passwd_cancel() { + $('passwd').hide(); + $('navigation').show(); + urlSwitch(); } -function newer_page() { - if (currentPager) - currentPager.newerPage(); +function subscribe() { + new Ajax.Request(baseURL + '/subscribe/' + currentPager.username, { + method: 'post', + parameters: { + username: loginStatus.username + }, + onSuccess: function(r) { + var response = r.responseText.evalJSON(); + if (response.status == 'success') { + alert("You call " + currentPager.username + " and begin breathing heavily into the handset."); + $$('[name=user.subscribelink]').each(Element.hide); + $$('[name=user.unsubscribelink]').each(Element.show); + } else { + alert('Failed to subscribe. This is probably for the best'); + } + }, + onFailure: function(r) { + alert('Failed to subscribe. This is probably for the best'); + } + }); } -function older_page() { - if (currentPager) - currentPager.olderPage(); +function unsubscribe() { + new Ajax.Request(baseURL + '/unsubscribe/' + currentPager.username, { + method: 'post', + parameters: { + username: loginStatus.username + }, + onSuccess: function(r) { + var response = r.responseText.evalJSON(); + if (response.status == 'success') { + alert("You come to your senses."); + $$('[name=user.subscribelink]').each(Element.show); + $$('[name=user.unsubscribelink]').each(Element.hide); + } else { + alert('You are unable to tear yourself away (because something failed on the server)'); + } + }, + onFailure: function(r) { + alert('You are unable to tear yourself away (because something failed on the server)'); + } + }); } var resizePostContentTimeout = null; @@ -402,68 +740,102 @@ function resizePostContent() { }, 150); } -function hashSwitch() { - var m; - if (m = location.search.match(/^\?post\/([^/]+)\/(.+)/)) { - $('post').show(); - $('post.content').value = '[' + decodeURIComponent(m[1]).replace(']','').replace('[','') + '](' + decodeURIComponent(m[2]) + ')'; - } else if (m = location.hash.match(/^#(ref|tag)\/(\w+)$/)) { - currentPager = new Tag(m[1], m[2]); - currentPager.show(); - } else if (m = location.hash.match(/^#(\w+)(:(\d+))?$/)) { - if (!currentPager || currentPager.username != m[1]) - currentPager = new User(m[1]); - currentPager.show(); - loginStatus.update(); - - if (m[3]) { - var r = parseInt(m[3]); - currentPager.showRecord = r; - if (currentPager.recordCache[r]) { - currentPager.displayItems(); - } else { - currentPager.loadItems((r >= 49 ? r - 49 : 0), r); - } - } else { - currentPager.pageStart = currentPager.itemCount - 1; - currentPager.loadItems(); - } - } else { - $$('[name=section]').each(function(v) { v.update('Welcome') }); - $('signup').hide(); - items.update(); - items.hide(); - $('newer_link').hide(); - $('older_link').hide(); - $('welcome').show(); - $('rss').hide(); - } +var tickerTimer = null; +var tickerHead, tickerTail; + +function tickerFader(a, b, p) { + var p2 = 1 - p; + + a.style.opacity = p; + a.style.lineHeight = (100 * p) + '%'; + + b.style.opacity = p2; + b.style.lineHeight = (100 * p2) + '%'; + if (p == 1.0) + b.hide(); } -var lastHash; -function hashCheck() { - if (location.hash != lastHash) { - lastHash = location.hash; - hashSwitch(); +function ticker() { + tickerHead.show(); + Bytex64.FX.run(tickerFader.curry(tickerHead, tickerTail), 0.5); + tickerHead = tickerHead.nextSibling; + tickerTail = tickerTail.nextSibling; + if (tickerHead == null) { + stopTicker(); + loadLatest.delay(10); } } -function init() { - items = $('items'); - loginStatus = new LoginStatus(); +function startTicker() { + stopTicker(); + for (var elem = $('latest-posts').firstChild; elem != null; elem = elem.nextSibling) { + elem.hide(); + } - lastHash = location.hash; - hashSwitch(); + // Show the first five + tickerHead = $('latest-posts').firstChild; + for (var i = 0; i < 10 && tickerHead; i++) { + tickerHead.show(); + tickerHead = tickerHead.nextSibling; + } + tickerTail = $('latest-posts').firstChild; + tickerTimer = setInterval(ticker, 5000); +} - setInterval(hashCheck, 250); +function stopTicker() { + if (tickerTimer) + clearInterval(tickerTimer); + tickerTimer = null; +} - document.body.observe('keyup', function(event) { - if (event.shiftKey && event.keyCode == '32') { - postPopup(); - event.stop(); +function loadLatest() { + new Ajax.Request(baseURL + '/latest.json', { + method: 'GET', + onSuccess: function(r) { + var j = r.responseText.evalJSON(); + + $('latest-tags').update(); + j.tags.each(function(v) { + var a = new Element('a', {href: baseURL + '/#/tag/' + v}); + a.insert('#' + v); + a.onclick = "return qlink()"; + a.className = 'ref'; + $('latest-tags').insert(a); + $('latest-tags').appendChild(document.createTextNode(' ')); + }); + + $('latest-posts').update(); + j.records.each(function(v) { + v.data = v.data.replace(/&/g, '&').replace(//g, '>'); + v.date = (new Date(v.timestamp * 1000)).toString(); + var html = latestRecordsTemplate.evaluate(v); + $('latest-posts').insert(html); + }); + startTicker(); } }); - $('post.content').addEventListener('keyup', function(event) { - event.stopPropagation(); - }, true); +} + +function qlink(loc) { + if (loc) { + location.hash = loc; + } else if (event && event.target) { + location.href = event.target.href; + } else { + // Bogus qlink + return; + } + lastHash = location.hash; + urlSwitch(); + return false; +} + +function ExternalURLPost(m) { + this.title = decodeURIComponent(m[1]).replace(']','').replace('[',''); + this.url = decodeURIComponent(m[2]); +} + +ExternalURLPost.prototype.show = function() { + $('post.content').value = '[' + this.title + '](' + this.url + ')'; + $('post').show(); }