/* appjet:version 0.1 */
/*<script>
This work is licensed under a Creative Commons Attribution-Noncommercial 3.0 United States License
http://creativecommons.org/licenses/by-nc/3.0/us/
...Vortices v2.0
yet another social news network...
*/
import('lib-clean-urls','storage','lib-general','lib-account','lib-jquery','lib-json','lib-atom','lib-tags')
if(!appjet.isPreview && request.headers.Host != 'vortices.vezquex.com' && appjet.appName != 'vtest')
response.redirect('http://vortices.vezquex.com'+request.path+paramGen(null, true))
if(!storage.laOps.newUser) storage.laOps.newUser = {
values: {
upvotes: 0,
downvotes: 0,
postCount: 0,
},
collections: 'votes,contacts'
}
//storage.laOps = 0
/*//storage.users.remove({})*/
collectionsAre(['nodes'])
if (!storage.showdownlib) storage.showdownlib = wget('http://vezquex.com/scripts/showdown.js')
eval(storage.showdownlib)
var currentTime = new Date()
var url = request.path
var algorithm = (rp.sort || request.cookies.sort || 'Hot')
/* Feeds
-----------------------------------------------------*/
function get_feed(filt)
{
var base
if(filt)
rp = filt
else
delete rp.feed
if(rp)
{
['parent','deleted'].forEach(function(prop){
if(rp[prop] == 'undefined') rp[prop] = undefined
})
base = storage.nodes.filter(rp)
}
else base = storage.nodes.filter({parent: undefined, deleted: undefined}) //main feed
base = base.sort(sorting['New']).limit(20)
var entries = {
base: base,
i: 0,
forEach: function(f) {
this.base.forEach(function(n) {
return f({
author: n.author,
title: n.title,
link: n.link||'http://vortices.vezquex.com/node/'+n.id,
id: 'tag:vortices.vezquex.com,2008:obj/'+n.id,
updated: n.time,
summary: '',
content: markdown(n.text)
})})}
}
page.setMode('plain')
response.setHeader('Content-Type', 'application/atom+xml')
print(raw(getMainFeed(
'Vortices',
'http://vortices.vezquex.com/feed',
'tag:vortices.vezquex.com,2008:/1',
base.first().time,
entries
)))
response.stop(true)
}
/* Voting
-----------------------------------------------------*/
function post_ajaxvote()
{
page.setMode('plain')
if(!user) r = {status: 'Sign in or register before voting.'}
else
{
var r = {status: false, score: vote()}
}
print(raw(JSON.stringify(r)))
response.stop()
}
function vote()
{
var thisvote
thisvote = user.votes.filter({node: rp.on}).first()
var node = getStorable(rp.on)
if(!thisvote)
{
user.votes.add({node: rp.on, type: rp.vote})
node[rp.vote + 'votes'] += 1
}
else if(thisvote.type != rp.vote)
{
if(rp.vote == 'down')
{
thisvote.type = 'down'
node.downvotes += 1
node.upvotes -= 1
}
else
{
thisvote.type = 'up'
node.upvotes += 1
node.downvotes -= 1
}
}
else //undo vote
{
node[rp.vote+'votes'] -= 1
user.votes.remove({node: rp.on})
}
return {up: node.upvotes, down: node.downvotes}
}
//Header
if(!(rp.plain || request.path == '/feed' || request.path == '/subFeed'))
{
page.setTitle('Vortices // freedom, democracy, and sublimity')
page.head.write("""
<link rel="icon" href="http://vezquex.com/images/icons/prism.ico"/>
"""
)
var sorts = ['Hot', 'New', 'Top', 'Replies']
// var sorts = ['Hot', 'New', 'Top', 'Replies', 'A-Z']
sorts.splice(sorts.indexOf(algorithm), 1)
var resort = UL()
sorts.forEach(function(s){
resort.push(LI({id: s}, A({href: paramGen({'sort': s}, true), onclick: "setCookie('sort','"+s+"')", rel: 'nofollow'}, s)))
})
resort = LI({id: 'sort'},
A({href: paramGen({'sort': algorithm}, true), onclick: "setCookie('sort','"+algorithm+"')", rel: 'nofollow'}, algorithm, '...'),
resort
)
var menu =
UL({id: 'menu'},
LI(A({href: '/'}, IMG({alt:'Vortices', src: 'http://imagethief.appjet.net/img/LRWqqCtHV.png'}))),
resort,
LI(link('/tags','Tags'))
)
if(user)
{
menu.splice(2,0,LI({id: 'post'}, link('/post','Post')))
menu.push(
LI(link('/subscriptions/'+user.id, 'Subscriptions')),
LI(B(link('/user/'+user.alias+'/blog', user.alias + ': '+ (user.upvotes - user.downvotes)))),
LI(la.signOut())
)
//Deleting
function get_delete()
{
var node = getStorable(rp['delete'])
if(node.author == user.alias && !node.deleted)
{
var fields = ['title','link']
fields.forEach(function(field)
{
node[field] = ''
})
node.text = '' //[Deleted]
node.edit = currentTime
node.deleted = true
var nodeTree = [node]
while(nodeTree[nodeTree.length-1].parent)
{
nodeTree[nodeTree.length] = getStorable(nodeTree[nodeTree.length-1].parent)
nodeTree[nodeTree.length-1].replies -= 1
}
response.redirect(rp.at)
}
}
}
else
{
menu.push(LI(la.signIn()))
}
print(
menu,
DIV({id: 'up'}, A({href: '#', title: 'Jump to Top'}, '^'))
)
}
//Editing
function get_edit()
{
var node = getStorable(rp.edit)
if(request.method == 'GET' && (node.wiki == 'true' || node.author == user.alias) && !node.deleted)
{
var catEdit = node.parent ? '' : LABEL(SPAN('Tags'),INPUT({value: accessTags(node).join(', '), name:'tags', type: 'text'}))
print(
H2('Edit'),
FORM({action:'/edit', 'class': 'submit', method:'post'},
LABEL(SPAN('Title'),INPUT({id: 'title', name:'title', value:node.title||'',maxlength:200, type: 'text'})),BR(),
LABEL(SPAN('URL'),INPUT({id: 'link', name:'link', value:node.link||'', type: 'text'})),BR(),
LABEL(SPAN('Text'),TEXTAREA({id:'text', name:'text'}, raw(node.text))),BR(),
catEdit,
INPUT({type:'submit',value:'Edit'}),
INPUT({type:'hidden',name:'node',value:node.id})
)
)
}
else
{
status('Error: You cannot edit this post. Make sure that you are logged in and that you authored this or it is a wiki.')
}
}
function get_vote()
{
if(rp.vote && rp.on)
{
var thisvote
thisvote = user.votes.filter({node: rp.on}).first()
var node = getStorable(rp.on)
if(!thisvote)
{
user.votes.add({node: rp.on, type: rp.vote})
node[rp.vote + 'votes'] += 1
}
else if(thisvote.type != rp.vote)
{
if(rp.vote == 'down')
{
thisvote.type = 'down'
node.downvotes += 1
node.upvotes -= 1
}
else
{
thisvote.type = 'up'
node.upvotes += 1
node.downvotes -= 1
}
}
else //undo vote
{
node[rp.vote+'votes'] -= 1
user.votes.remove({node: rp.on})
}
}
}
/* Main Posting/Reply Form
-----------------------------------------------------*/
function get_reply(parent, fields)
{
at = rp.at
if(!fields) fields = {action: 'submit', title: '',link: '',text: ''}
if(!parent) var parent = rp.reply
if(at == 'undefined') at = rp.reply
var header = 'Reply'
if(parent) get_node(parent,true)
else header = 'Post'
print(H2({id:'reply'},header))
var form = FORM({action:'/'+fields.action, 'class': 'submit', method:'post'})
if(fields.title != undefined)
form.push(LABEL(SPAN('Title'),INPUT({value: fields.title||'', id: 'title', name:'title',maxlength:200, type: 'text'})),BR())
if(fields.link != undefined)
form.push(LABEL(SPAN('URL'),INPUT({value: fields.link||'', name:'link', type: 'text'})),BR())
form.push(LABEL(SPAN('Text'),TEXTAREA({name:'text'},fields.text||'')),BR())
if(fields.tags != undefined)
form.push(LABEL(SPAN('Tags'),INPUT({value: fields.tags||'', name:'tags', type: 'text'})),BR())
if(fields.wiki == '')
form.push(LABEL(INPUT({type:'checkbox', name:'wiki', value: 'true'}),'Wiki'))
else if(fields.wiki == 'true')
form.push(LABEL(INPUT({checked: 'checked', type:'checkbox', name:'wiki', value: 'true'}),'Wiki'))
form.push(
INPUT({type:'submit',value:'Post'}),
INPUT({type:'hidden',name:'parent',value:parent}),
INPUT({type:'hidden',name:'at',value:at})
)
print(form)
}
function get_post()
{
page.setTitle('Post - Vortices')
get_reply(undefined, {action: 'submit', title: '',link: '',text: '',tags: '',wiki: ''})
}
function post_submit()
{
var node = new StorableObject()
var fields = ['title','parent','link','wiki']
fields.forEach(function(param)
{
if(rp[param]) node[param] = trim(rp[param])
})
node.text = sanitize(trim(rp.text))
// if(!node.parent) node.parent = null
node.author = user.alias
node.downvotes = 0
node.upvotes = 1
node.replies = 0
node.time = currentTime
storage.nodes.add(node)
user.votes.add({node: node.id, type: 'up'})
user.postCount += 1
if(rp.tags)
addTags(node, rp.tags)
if(node.parent)
{
var parent = node.parent
while(parent)
{
parent = getStorable(parent)
parent.replies += 1
parent = parent.parent
}
}
if(!rp.at) rp.at = node.id
response.redirect('node/'+rp.at+'#'+node.id)
}
function post_edit()
{
var node = getStorable(rp.node)
var fields = ['title','link']
fields.forEach(function(param)
{
node[param] = rp[param] ? trim(rp[param]) : ''
})
if(rp.tags)
{
addTags(node, rp.tags)
}
node.text = sanitize(trim(rp.text))
node.edit = currentTime
var where = rp.at
if(!where) where = node.id
rp = []
response.redirect('node/'+where)
}
var general =
{
'A-Z': function(a, b, prop)
{
(a[prop]||'').toLowerCase().localeCompare((b[prop]||'').toLowerCase())
},
'greatest': function(a, b, prop) { return b[prop] - a[prop] },
'longest': function(a, b, prop) { return b[prop].size() - a[prop].size() }
}
var sorting = {
user: {
'A-Z': function(a, b) { return general['A-Z'](a, b, 'login')},
Replies: function(a, b) { return general['greatest'](a, b, 'postCount')},
Hot: function(a, b) { return (b.upvotes - b.downvotes) * Math.abs(b.upvotes - b.downvotes) / (currentTime - b.registered.getTime()) - (a.upvotes - a.downvotes) * Math.abs(a.upvotes - a.downvotes) / (currentTime - a.registered.getTime()) }
},
'A-Z': function(a, b) { return general['A-Z'](a, b, 'title')},
Top: function(a, b) { return b.upvotes - b.downvotes - a.upvotes + a.downvotes },
Hot: function(a, b) { return (b.upvotes - b.downvotes) * Math.abs(b.upvotes - b.downvotes) / (currentTime - b.time.getTime()) - (a.upvotes - a.downvotes) * Math.abs(a.upvotes - a.downvotes) / (currentTime - a.time.getTime()) },
RedditHot: function(a, b) { return RedditHot(b) - RedditHot(a) },
New: function(a, b) { return a.id > b.id },
Old: function(a, b) { return a.id < b.id },
Replies: function(a, b) { return general.greatest(a, b, 'replies')},
}
function RedditHot(b)
{
s = b.upvotes - b.downvotes
order = Math.log(Math.max(Math.abs(s), 1), 10)
if (s > 0) sign = 1
else if(s < 0) sign = -1
else sign = 0
seconds = b.time.getTime()/1000 - 1199174400
return order + sign * seconds / 45000
}
//Home page
function get_main()
{
h1 = UL({'class':'headlines'})
pageLinks = get_filter({parent: undefined, deleted: undefined}, h1)
print(
h1,
pageLinks,
P({id: 'footer'},
link('/users', pluralize(storage.users.size(), 'user', 'users')), ' | ',
pluralize(storage.nodes.size(), 'post', 'posts'), ' | ',
link('/node/obj-LGrYTPdrt', 'help')
)
)
}
function get_tags()
{
var tags = UL({'class':'headlines'})
var pageLinks = paginate(storage.tags.sortBy('-count'), function(tag){
tags.push(LI(A({'class': 'headline', href: '/tag/'+tag.name}, tag.name, ' ', SPAN(tag.count))))
})
print(H1('Tags'), tags, pageLinks())
}
function get_tag(tag)
{
tag = tag || rp.tag
h1 = UL({'class':'headlines'})
pL = get_filter(tagged(tag), h1)
print(H1(tag), h1, pL)
}
//Custom page
function get_filter(params, headlinesOnly)
{
if(!params)
{
delete rp.filter
var params = rp
}
var list = storage.nodes.filter(params).sort(sorting[algorithm])
page.head.write('<link rel="alternate" href="/feed'+paramGen(params)+'" type="application/atom+xml"/>')
fn = get_node
if(headlinesOnly) fn = function(n){ headlinesOnly.push(renderHeadline(n)) }
var pageLinks = paginate(list, function(n){fn(n, true)})
x = pageLinks()
if(!headlinesOnly) print(x)
else return x
}
function get_wiki()
{
h1 = UL({'class':'headlines'})
get_filter({wiki: 'true'}, h1)
print(H1('Wikis'), h1)
}
//Individual Node
function get_node(node,noChildrenView)
{
if(!node) var node = getStorable(rp.node)
if(typeof node == 'string') var node = getStorable(node)
if(rp.raw) print(node)
else
{
if(!noChildrenView) page.setTitle((node.title||'[Untitled]')+' - Vortices')
if(!noChildrenView) noChildrenView = false
print(renderNode(node,noChildrenView))
}
}
function subscriptions(subs)
{
who = getStorable(subs)
var contactsList = []
myFriends = who.contacts.filter({relationship: ['friend', 'mutual']})
myFriends.forEach(function(contact){
contactsList.push(contact.name)
})
return {who: who, contactsList: contactsList}
}
function get_subFeed()
{
s = subscriptions(rp.id)
get_feed({author: s.contactsList})
}
function get_subscriptions()
{
s = subscriptions(rp.subscriptions)
page.setTitle((s.who.alias || '[Anonymous]') +'\'s Subscriptions | Vortices')
print(H1((s.who.alias || '[Anonymous]')+'\'s Subscriptions'))
page.head.write('<link rel="alternate" href="/subFeed?id='+s.who.id+'" type="application/atom+xml"/>')
get_filter({author: s.contactsList})
}
//User page
function get_users()
{
var list = TABLE({'class': 'users'},
COL(),
TR(
TH(link('?sort=A-Z','User')),
TH(link('?sort=Top','Points')),
TH(link('?sort=Replies','Posts'))
)
)
userSort = sorting.user[algorithm] ? sorting.user[algorithm] : sorting[algorithm]
var us = storage