/* 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-markdown','lib-json','lib-atom','lib-tags')
var url = request.path

// /*
if(appjet.appName != 'v-test.js' && appjet.appName != 'v-test' && !appjet.isPreview && request.headers.Host != 'vortices.vezquex.com')
{
        response.redirect('http://vortices.vezquex.com' + url + paramGen(null, true))
}
// */

// storage.laOps.use.css = false
        
if(!storage.laOps.newUser) storage.laOps.newUser = {
        values: {
                upvotes: 0,
                downvotes: 0,
                postCount: 0,
                liked: 0
        },
        collections: 'votes,contacts'
}
//storage.laOps = 0

collectionsAre(['nodes','files'])
var initialComments = 25
var mp3id = 0
var currentTime = new Date()
var algorithm = (rp.sort || request.cookies.sort || 'Hot')

var out = {
        container: DIV({'class':'container'}),
        logo: A({href: '/', id: 'logo'}, IMG({alt:'Vortices', src: 'http://files.appjet.net/download?id=obj-RwRAff4Rw'})),//http://files.appjet.net/download?id=obj-O1ayfS0jr
        footer: P('©2009'),
        analytics: ''
}

var container = out.container

/*        Feeds
-----------------------------------------------------*/

function get_feed(filt)
{
        var base
        if(filt)
                rp = filt
        else
                delete rp.feed

        if(rp)
        {
                base = storage.nodes.filter(rp)
        }
        else base = storage.nodes.filter({parent: 'top', 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 score(node)
}

function printPage()
{
        page.setTitle('Vortices :: type for justice')
        page.setFavicon('http://files.appjet.net/download?id=obj-OWYK1dKFh')

        var sorts = ['Hot', 'New', 'Top', 'Replies'] //'A-Z'
        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)))
        })

        var tags = UL()
        storage.tags.sortBy('-count').limit(5).forEach(function(tag){
                tags.push(LI(A({href: '/tag/'+tag.name}, tag.name)))//tag.count //tag.name.substring(0, 1).toUpperCase() + tag.name.substring(1, tag.name.length)
        })
        
        out.menu = UL({'class': 'sidebar'},//id: 'menu', 
                LI({id: 'sort'},
                        'Sort'
                        ,resort
                ),
                LI({id: 'tags'},
                        link('/tags','Tags'),
                        tags
                )
        )

        if(user)
        {
                if(!rp.edit) out.form = postForm()
                out.menu.unshift(
                        LI(userLink(user),
                                UL(
                                        LI(link('/posts/'+user.alias,'Posts')),
                                        LI(link('/profile/'+user.alias,'Profile')),
                                        LI(link('/subscriptions/'+user.id, 'Subscriptions')),
                                        LI(link('/liked/'+user.alias, 'Liked')),
                                        LI(la.signOut())
                                )
                        )
                )
        }
        else
        {
                out.form = la.signIn(null, true)
//                container.push(statusBanner("Welcome to Vortices, the all-purpose social network for friends, news, and life."))
        }

        print(
                DIV({id:'page'},
                        DIV(
                                out.logo,
                                out.form
                        ),
                        out.menu,
                        out.container,
                        DIV({id: 'footer'}, out.footer)
//                          ,DIV({id: 'up'}, A({href: '#', title: 'Jump to Top'}, '^'))
                )
        )
}

//Editing
function get_edit()
{
        var node = getStorable(rp.edit)
        if(request.method == 'GET' && (node.wiki_tag == 'wiki' || node.author == user.alias) && !node.deleted)
        {
                var tags = html(
                        accessTags(node).sort().map(function(tag){
                                        return SPAN(tag, SUP(A({rel: 'nofollow', href: '/untag'+paramGen({id: node.id, tag: tag, redirect: '/edit/'+node.id})}, 'x')))
                        })
                        .join(' ')
                )
                print(
                        H2('Edit'),
                        FORM({action:'/edit', 'class': 'submit', method:'post'},
                                INPUT({type:'hidden',name:'node',value:node.id}),
                                                LABEL(SPAN('Title'), INPUT({name:'title', autocomplete:'off', type: 'text', value:node.title||'', maxlength:255})),
                                                INPUT({type:'submit', value: 'Save'}),
                                        LABEL(SPAN('URL'), INPUT({name:'link', autocomplete:'off', type: 'text', value:node.link||''})),
                                LABEL(SPAN('Text'), TEXTAREA({name:'text'}, raw(node.text))),
                                        LABEL(SPAN('Tags'), INPUT({name:'tags', type: 'text', maxlength:255})),
                                        P(tags)
                        )
                )
        }
        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 postForm(ops)
{
        ops = extend({button: 'Post'}, ops)
        var id = (ops.parent||'main')+'-form'
   
        var form = FORM({method:'post', action:'/submit', id:id, 'class':'submit', enctype:'multipart/form-data'},
                INPUT({name:'title', type: 'text'}),//, value: 'what\'s on your mind?'
                INPUT({type:'submit', value: ops.button}),
                P({'class':'ops'},
                        A({'class':'toggler', href:'#'},'Link'),
                        A({'class':'toggler', href:'#'},'Text'),
//                         A({'class':'toggler', href:'#'},'Upload'),
                        A({'class':'toggler', href:'#'},'Tags')
                ),
                LABEL({'class':'toggle t-Link'}, SPAN('URL'), INPUT({value: 'http://', name:'link', type: 'text'})),
                LABEL({'class':'toggle t-Text'}, SPAN('Text'), TEXTAREA({name:'text'})),
//                 LABEL({'class':'toggle t-Upload'}, SPAN('Upload'), INPUT({type:'file', name:'upload'})),
                LABEL({'class':'toggle t-Tags'}, SPAN('Tags'), INPUT({value: '', name:'tags', type: 'text', maxlength:255}))
        )
                
        return 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', enctype:'multipart/form-data'})
        if(fields.title != undefined)
                form.push(LABEL(SPAN('Title'),INPUT({value: fields.title||'', id: 'title', name:'title',maxlength:255, type: 'text'})), BR())
        if(fields.link != undefined)
                form.push(LABEL(SPAN('URL'),INPUT({value: fields.link||'http://', 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())
        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')
        if(user) get_reply(undefined, {action: 'submit', title: '',link: '',text: '',tags: ''})
}

function post_submit(ajax)
{
        var node = {
                author: user.alias,
                downvotes: 0,
                replies: 0,
                time: currentTime,
                upvotes: 1,
                parent: rp.parent || 'top'
        }

        if(rp.link && rp.link != 'http://')
        {
                node.link = trim(rp.link)
        }
        else
        {
                if(rp.text == '')// && rp.parent
                {
                        rp.text = rp.title
                        delete rp.title
                }
        }

        if(rp.title) node.title = trim(rp.title)
        node.text = sanitize(rp.text||'')

        node = storage.nodes.add(node)
        
/*
        try
        {
                var file = appjet._native.request_uploadedFile('upload')
                file = storage.files.add({
                        file: file,
                        author: user.alias,
                        filename: file.filesystemName,
                })
                node.file = file
                addTags(node, 'file')
        }
        catch(e){}
*/
        
        user.votes.add({node: node.id, type: 'up'})
        user.postCount += 1
        if(rp.tags) addTags(node, rp.tags)
        if(node.parent)
        {
//                 node.ancestor = parent.ancestor
                var parent = node.parent
                while(parent != 'top')
                {
                        parent = getStorable(parent)
                        parent.replies += 1
                        parent = parent.parent
                }
        }
        
        if(ajax)
        {
                return html(renderNode(node, 0, false))
        }
        else
        {
                if(!rp.at) rp.at = node.id
                response.redirect('node/'+rp.at+'#'+node.id)
//                 print(request.headers, request.params)
        }
}

function post_ajaxsubmit()
{
        if(!user) r = {error: 'Sign in or register before voting.'}
        else
        {
                var r = {error: false, html: post_submit(true)._text}
        }
        page.setMode('plain')
        print(raw(JSON.stringify(r)))
        response.stop()
}

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(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()
{
        get_filter({parent: 'top', deleted: undefined}, container, initialComments)

        out.footer = P(
                pluralize(storage.users.size(), 'user', 'users'), ' | ',//link('/users', )
                pluralize(storage.nodes.size(), 'post', 'posts'), ' | ',
                link('node/obj-LGrYTPdrt', 'help')
        )
        printPage()
}

function get_tags()
{
        page.setTitle('Tags - Vortices')
        tags = UL({style: '-moz-column-count: 3;'})
        var pageLinks = paginate(storage.tags.sortBy('-count'), function(tag){
                fsize = 8*Math.log(tag.count+1) + 10
                tags.push(LI(A({href: '/tag/'+tag.name, 'style': 'font-size: '+fsize+'px'}, tag.name)), ' ')//, ' ', SPAN(tag.count)
        }, 50)
        container.push(H1('Tags'), tags, pageLinks())
        printPage()
}

function get_tag(tag)
{
        page.setTitle('Tagged '+tag+' - Vortices')
        tag = tag || rp.tag
        container.push(H1('Tag: ', tag))
        get_filter(tagged(tag), container)
        printPage()
}

//Custom page
function get_filter(params, useContainer, children)
{
    printWhenDone = false
        if(!params)
        {
                delete rp.filter
                var params = rp
                useContainer = container
                printWhenDone = true
        }

        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
        fn = function(n){ useContainer.push(renderNode(n, children, true)) }
        var pageLinks = paginate(list, function(n){fn(n, true)})
        x = pageLinks()
        useContainer.push(x)
        if(printWhenDone) printPage()
}

//Individual Node
function get_node(node, descendants)
{
        if(!node)
        {
                var node = getStorable(rp.node)
                page.setTitle((node.title||'[Untitled]')+' - Vortices')
        }
        
        if(typeof node == 'string') var node = getStorable(node)
        
        if(rp.raw) print(node)
        else
        {
                container.push(renderNode(node, descendants || 100, true))
                printPage()
        }
}

function subscriptions(subs)
{
        who = getStorable(subs)
        var contactsList = []
        myFriends = who.contacts.filter({subscribe: true})
        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.head.write('<link rel="alternate" href="/subFeed?id='+rp.subscriptions+'" type="application/atom+xml"/>')
        if(!user || user.id != s.who.id) person(s.who.alias, 'Subscriptions')
        else page.setTitle(s.who.alias, ' - Subscriptions - Vortices')
        if(s.contactsList.length)
        {
                get_filter({author: s.contactsList}, container)
        }
        else container.push(BR(), P(s.who.alias, ' has not subscribed to anyone yet.'))
        printPage()
}

/*
//User page
function get_users()
{
        var list = TABLE({'class': 'users'},
                        COL(),
                        TR(
                                TH(link('?as=login','User')),
                                TH(link('?as=postCount','Posts')),
//                                 TH(link('?as=upvotes','+')),
//                                 TH(link('?as=downvotes','-')),
                                TH(link('?as=registered','Registered'))
                        )
                )

        var us = storage.users.sortBy(rp.as || 'registered')
        var pageLinks = paginate(us, function(u){
                list.push(TR(
                        TD(userLink(u)),
                        TD(u.postCount),
//                         TD(u.upvotes),
//                         TD(u.downvotes),
                        TD(timeHover(u.registered))
                ))
        }, 64)

        print(H1('Users'), list, pageLinks())
}
*/

function person(alias, pageTitle)
{
        var who = storage.users.filter({alias: alias}).first()
        if(!who)
        {
                container.push(statusBanner('Error: No user with the name of "'+alias+'" exists.'))
                return
        }
        var edit = subscribeLink = ''
        if(user)
        {
        //Contacts
                var myFriend = user.contacts.filter({name: alias}).first()
                if(rp.action)
                {
                        var yourFriend = who.contacts.filter({name: user.alias}).first()
                }
                if(rp.action == 'add')
                {
                        if(yourFriend)
                        {
                                if(yourFriend.fan)
                                {
                                        myFriend.subscribe = true
                                        yourFriend.fan = true
                                }
                        }
                        else if(who.id != user.id)
                        {
                                myFriend = user.contacts.add({name: alias, subscribe: true})
                                who.contacts.add({name: user.alias, fan: true})
                        }
                        container.push(statusBanner('You are now subscribed to '+alias+'.'))
                }
                else if(rp.action == 'remove')
                {
                        if(yourFriend.subscribe)
                        {
                                myFriend.subscribe = false
                                yourFriend.fan = false
                        }
                        else
                        {
                                user.contacts.remove(myFriend)
                                who.contacts.remove(yourFriend)
                                myFriend = null
                        }
                        container.push(statusBanner('You are no longer subscribed to '+alias+'.'))
                }
                if(who.id != user.id) subscribeLink = A({href: paramGen({action: 'add'})},'+ Subscribe')
                if(myFriend && myFriend.subscribe) subscribeLink = A({href: paramGen({action: 'remove'})},'- Unsubscribe')
        }

        function listFriends()
        {
                var friends = who.contacts.filter({subscribe: true})
                var size = friends.size()
                var flist = ''
                if(size > 0)
                {
                        flist = UL()
                        friends.limit(10).forEach(function(person){
                                flist.push(LI(userLink(person.name)))
                        })
                }
                if(size > 10) flist.push(LI(A({onclick: "viewAllSubs('"+who.id+"')"},'view all '+size+' friends')))
                var r = SPAN(
                        link('/subscriptions/'+who.id, pluralize(friends.size(), 'Subscription', 'Subscriptions')),
                        flist
                )
                return r
        }

        pageTitle = pageTitle ? ' - '+pageTitle : ''
        page.setTitle(alias, pageTitle,' - Vortices')
        if(who.css) page.head.write('<style type="text/css">'+who.css+'</style>')
        var pic = image('http://gravatar.com/avatar/'+md5(who.email||'')+'?s=64&d=identicon')
        container.push(
//                 DIV(voteButtons(who)),
                H1(
                        pic,' ',
                        (who.alias || '[Anonymous]'),' ',
                        SPAN({'class':'ops'},
                                link('/profile/'+alias,'Profile'),
                                link('/posts/'+alias, 'Posts'),//pluralize(who.postCount,'Post','Posts')
                                link('/user/'+alias, 'Posts + Replies'),
//                                 link('/filter?'+alias, 'Replies'),//pluralize(who.postCount,'Reply','Replies')
//                                 link('/wall/'+who.id,'Wall'),
                                link('/liked/'+alias, who.liked+' Liked'),
                                listFriends(),
                                subscribeLink
                        )
                )
//                 ,P({id:who.id}, score(who))
        )
        return who
}

function get_viewAllSubs()
{
                var friends = getStorable(rp.id).contacts.filter({subscribe: true})
                                var flist = []
                friends.skip(10).forEach(function(person){
                        flist.push(person.name)
                })
                print(html(flist))
}

function get_user()
{
        person(rp.user)
        get_filter({author: rp.user}, container)
        printPage()
}

function get_posts()
{
        person(rp.posts)
        get_filter({author: rp.posts, parent: 'top'}, container)
        printPage()
}

function get_liked()
{
        who = person(rp.liked)
        var liked = []
        who.votes.filter({type: 'up'}).forEach(function(vote){
                try
                {
                        var n = getStorable(vote.node)
                        if(n.author != who.alias) liked.push(n)
                }
                catch(e)
                {
                        container.push(DIV(P('Invalid ID: ',vote)))
                }
        })

        liked//.sort(sorting[algorithm])
                 .forEach(function(n){
                         container.push(renderNode(n, 0))
                 })
         printPage()
}

/*
function get_replies()
{
        person(rp.posts)
        printPage()
}

function get_wall()
{
        person(rp.user)
        get_filter({parent: rp.wall}, container)
        printPage()
}
*/

function get_profile()
{
        var who = person(rp.profile, 'Profile')
        container.push(
                P({id: 'registered'}, 'Registered ', timeHover(who.registered)),
                DIV({'class': 'text'}, markdown(who.bio))
        )
        if(user.id == who.id)
        {
                container.push(
                        HR(),
                        H2('Edit Your Profile'),
                        DIV(//{'class':'clearfix'},
                                FORM({action:'/profile/'+user.alias, 'class': 'submit', method:'post', style: 'margin-left: 7.5em;'},
                                        LABEL(SPAN('Email'),INPUT({name:'email', type: 'text', value:user.email||''})),BR(),
                                        LABEL(SPAN('Biography'),TEXTAREA({name:'bio'},raw(user.bio||''))),BR(),
                                        LABEL(SPAN('CSS'),TEXTAREA({name:'css'},raw(user.css||''))),BR(),
                                        INPUT({type:'submit', value:'Save'})
                                )
                        ), BR(),
                        P('You may use HTML, CSS, and Markdown to spice up your profile.')
                )
        }

        printPage()
}

function post_profile()
{
        status('Profile updated.')
        var fields = ['email'] 
        fields.forEach(function(param)
        {
                user[param] = rp[param]
        })
        user.bio = sanitize(trim(rp.bio))
        user.css = sanitize(trim(rp.css))
        response.redirect('/profile/'+user.alias)
}

function get_download()
{
        var file = getStorable(rp.download)
        if (!file)
        {
                response.notFound()
                return
        }
        page.setMode('plain')
        response.setCacheable(true)
        response.setHeader('Content-Type', file.contentType)
        appjet._native.writeBytes(file.fileContents)
        response.stop()
}

cleanDispatch()

function voteButtons(node)
{
        var r = DIV({'class': 'vote'})
        if(user && !node.deleted)
        {
                var upClass = '', downClass = ''
                thisvote = user.votes.filter({node: node.id}).first()
                if(thisvote != undefined)
                {
                        if(thisvote.type == 'down')
                                downClass = ' voted'
                        else
                                upClass = ' voted'
                }
                r.push(
                        A({'class': 'up' + upClass, onclick: "return vote('up','"+node.id+"',this)", href: 'javascript:void(0)'}, ''),
                        A({'class': 'down' + downClass, onclick: "return vote('down','"+node.id+"',this)", href: 'javascript:void(0)'}, '')
                )
        }
        else
        {
                r.push(
                        A({onclick: "return false", title: 'Sign in to vote.'}, ''),
                        A({onclick: "return false", title: 'Sign in to vote.'}, '')
                )
        }
        return r
}

function renderNode(node, descendants, headline, time)
{
        var taglinks = repl = repls = contracted = ''

        tags = accessTags(node).sort()
        if(tags.length > 0)
                taglinks = SPAN({'class':'tags'}, ' ', html(tags.map(function(t){ return link('/tag/'+t,t) }).join('')), ' ')

        if(!descendants && node.replies) repls = pluralize(node.replies, 'reply', 'replies')+' '
        
        if(node.deleted || (node.upvotes - node.downvotes <= -4))
                                contracted = {'class':' contracted',caption:'',icon:'Show'}//▷
                else contracted = {'class':'',caption:'',icon:'Hide'}//◢

        if(user) repl = A({
         href: '/reply/'+node.id+'/at/'+(rp.node || node.id)+'#reply',
        'class':'replyButton', onclick: "replyto('"+node.id+"');return false;"}, 'Reply')

        var text = node.text||''
        var media = DIV()
        if(!node.html_tag)
        {
                text = markdown(
                        text.replace(/(http\:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,4}((?:\/\S*)?(?:[a-zA-Z0-9_\(\)])+)\.mp3)/g,  
                                function(str, p1, p2, p3, offset, s)
                                {
                                        return supplant({file: str, title: p2, id: ++mp3id}, """
<object type="application/x-shockwave-flash" data="http://friendfeed.com/static/flash/audioplayer.swf" id="audioplayer1" height="24" width="333">
<param name="movie" value="http://friendfeed.com/static/flash/audioplayer.swf">
<param name="FlashVars" value="playerID={id}&amp;soundFile={file}&amp;titles={title}">
<param name="quality" value="high">
<param name="menu" value="false">
<param name="wmode" value="transparent">
</object> 
                                                                """
                                        )
                                }
                        )
                                
                        .replace(/(http\:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(?:\/\S*)?(?:[a-zA-Z0-9_])+\.(?:jpg|jpeg|gif|png|JPG|JPEG|GIF|PNG))/g, 
                                function(str)
                                {
                                        media.push(IMG({src: str, alt: ''}))
                                        return ''
                                }
                        )
                                
                        .replace(/http\:\/\/[a-z]{2,3}\.youtube\.com\/watch\?v=([A-Za-z0-9\-_]+)/g, 
                                function(str, p1)
                                {
                                        return '<img class="youtube" alt="" onclick="playVideo(\''+p1+'\', this)" style="width: 32px; height: 32px; padding: 0 98px 65px 0; background: url(http://img.youtube.com/vi/'+p1+'/2.jpg)" src="http://files.appjet.net/download?id=obj-Nu4xwqGzh"/>'
                                }
                        )
                )
        }
        
// /*
        if(node.file)
        {
                var file = node.file
                var path = '/download/' + file.id + '/plain/1'
                 if(file.file.contentType.split('/')[0] == 'image')
                {
                        media.push(image(path))
                }
                media.push(link(path, 'View '+file.filename))
        }
// */
        levelAuthorType = ''
        if(user.alias == node.author) levelAuthorType = ' self'
        var level = DIV({id:node.id, id:node.id, 'class':'node' + levelAuthorType + contracted['class']},
                voteButtons(node),
                taglinks,
                nodeHeader(node),
                DIV({'class': 'text'}, media, text),
                P({'class': 'info'},
                        userLink(node.author),' ',
                        SPAN({'class': 'score'}, score(node)),' ',
                        A({href:'/node/'+node.id}, repls, timeHover(node.time,'')),
//                         'revN ',
                        parentJump(node, headline),
                        SPAN({'class': 'ops'},
                                nodeOptions(node),
                                repl
                        )
//                         ,A({title: contracted.caption, 'class':'control', href: 'javascript:void(0)', onclick: "toggleNode(this,'"+node.id+"')"}, contracted.icon)
                )
        )

        if(node.replies)
        {
                var children = storage.nodes.filter({parent: node.id})
                var num = descendants
                if(descendants)
                {
                        var nextLevel = DIV({id: 'children-of-'+node.id, 'class':'child'})
                        children.sort(sorting[algorithm]).forEach(function(c){
                                if(descendants)
                                {
                                        nextLevel.push(renderNode(c, descendants, false, node.time))
                                        descendants -= 1
                                }
                                else return false
                        })
                        level.push(nextLevel)
                }
                else
                {
                        level.push(
                                P({'class': 'info'},
                                        A({href:'/node/'+node.id}, pluralize(node.replies - (descendants||0), 'more reply', 'more replies'))
                                )
                        )
                }
        }

        return level
}

function nodeHeader(node)
{
        if(node.title)
                return H2(A({href: node.link||'/node/'+node.id}, (node.title||'[Link]'), domain(node)))
        else
                return ''
}

function sanitize(text)
{
        return text.replace(/<(\/)?(script|style)/gi,'&lt;$1$2').replace(/on(load|click|mouseover|mouseout|focus|blur)=("|')/gi,'on$1=&quot;')
/*
Tests
1. <script type="text/javascript">alert('JS injection.');</scrip>t
2. <span attribute='something' onmouseover="alert('haxed')">event JS injection</span>
3. Style Injection <style>body {background: #024;}</style>
*/
}

function nodeOptions(node)
{
        var r = ''
        if(node.wiki_tag == 'wiki' || (user && node.author == user.alias && !node.deleted))
                r = link('/edit/' + node.id,'Edit')
//        if(user && node.author == user.alias && !node.deleted) r.push(A({href: '/delete/' + node.id + '/at/'+(rp.node || node.id), onclick: "return window.confirm('Deleting is irreversable. Continue?')"},'Delete'))
        return r
}

function parentJump(node, externalParent)
{
        if(node.parent == 'top') return ''
        else if(externalParent)
        {
                return A({href: '/node/'+node.parent}, 'Context')//◤title: 'Go to Parent', 
        }
//         else return A({href: '#'+node.parent}, 'Parent')//◤title: 'Jump to Parent',
        return '' 
}

function paginate(list, x, per)
{
        start = parseInt(rp.start) || 0
        items = per || 20
        size = list.size()
        list = list.skip(start).limit(items)
        list.forEach(function(n){ x(n) })

        //Links
        return function(){
                var blank = SPAN(raw('&nbsp;'))
                var pageLinks = P({'class':'nav'})

                if(start != 0)
                {
                        newStart = Math.max(0, (start - items))
                        pageLinks.push(link('?start='+ newStart, '◄ Previous'))
                }
                else
                {
                        pageLinks.push(blank)
                }

                var last = Math.floor(size / items)
                pageLinks.push(
                        SPAN({'class': ''},
                                ((start/items)+1),
                           ' / ',
                                A({href: '?start='+(last*items)}, last+1)
                        )
                )

                if(size > start + items)
                {
                        pageLinks.push(A({href: '?start='+ (start + items)}, 'Next ►'))
                }
                else
                {
                        pageLinks.push(blank)
                }
                return pageLinks
        }
}

/*        Utilities
-----------------------------------------------------*/
function domain(node)
{
    var r = ''
    if(node.link)
    {
                r = SPAN()
                var d = (/https?:\/\/(www\.)?((\w|\.|-)*)/).exec(node.link)
                if(d) r.push(' ', IMG({src: 'http://'+d[2]+'/favicon.ico', 'class':'favicon'}), ' ', d[2])
                else r.push('(bad link)')
    }
   return r
}

function score(node)
{
        return pluralize(node.upvotes - node.downvotes,'point','points')
}

function userLink(who)
{
        if(typeof who == 'string') who = storage.users.filter({alias: who}).first()||{}
                return A({href: '/user/'+who.alias, 'class':'user'},
//                         image('http://gravatar.com/avatar/'+md5(who.email)+'?s=12&d=identicon'),
                        who.alias 
                )
}

/*-----------------------------------------------------*/
/* appjet:client */
/*-----------------------------------------------------*/
$(document).ready(function(){
                $('#main-form [name=title]').focus()
                replyOps('main-form', null)
                
//                 $('.menu ul li').css('background-color', $('body').css('background-color'))
                
                imageResize('.node')
})

function imageResize(el)
{
        $(el+' img').toggle(
                function () { $(this).css({'max-width':'none', 'max-height':'none'}) },
                function () { $(this).css({'max-width':'100%', 'max-height':'8em'}) }
        )
}

function replyto(parent)
{
        var id = 'replyto-'+parent
        var theForm
        if($('#'+id).length)
        {
                theForm = $('#'+id).toggle()
        }
        else
        {
                theForm = $('#main-form').clone()
                .attr({'id':id})
                                .append('<input type="hidden" name="parent" value="'+parent+'"/>')
                                .insertAfter('#'+parent+' .info:first')
                        theForm.get(0).reset()
                        
                        replyOps(id, parent)
        }
        theForm.find('[name=title]').focus()
}

function replyOps(form, parent)
{
        var theForm = $('#'+form)
                .submit(function(){
                        var theform = $(this)
                        var params = {plain: true}
                        var fields = ['title', 'link', 'text', 'tags', 'parent']//'upload', 
                        fields.forEach(function(field){
                                var fieldValue = theform.find('[name='+field+']')
                                if(fieldValue.length) params[field] = fieldValue.val() 
                        })

                        if(params.title == '' && params.text == '') return false
//                         if(!params.upload)
                        {
                                theform.attr({'disabled':'disabled'})
                                $.post('/ajaxsubmit', params,
                                        function(data)
                                        {
                                                if(parent)
                                                {
                                                        theform.remove()
                                                        if($('#'+parent+' .child').length > 0){ $(data.html).prependTo('#'+parent+' .child:first') }
                                                        else{ $('#'+parent).append('<div id="children-of-'+parent+'" class="child">'+data.html+'</div>') }
//                                                         window.location = '#'+parent
                                                         imageResize('#'+parent+' .node:first')
                                                }
                                                else
                                                {
                                                        theform.get(0).reset()
                                                        $(data.html).prependTo('.container')
                                                        imageResize('.node:first')
                                                        setTimeout(function(){ theform.removeAttr('disabled') }, 5000)
                                                }
                                        }
                                        ,'json')
                                        
                                return false
                        }
                        return true
                })
                
        theForm.find('.toggler').click(function(){
                        $(this.parentNode.parentNode).find('.t-'+this.innerHTML).toggle().focus()
                        return false
                })
}

var viewAllSubs = function(id)
{
        $.get('/viewAllSubs', {id: id},
                function(data)
                {
                        data.forEach(function(alias){
                                $('#subs').append('<li><a href="/user/'+alias+'/">'+alias+'</a></li>')
                        })
                }
                ,'json')
}

var vote = function(dir, nodeId, el)
{
        $(el).toggleClass('voted')
        $('#'+nodeId+' > .vote .'+(dir == 'up' ? 'down' : 'up')).removeClass('voted')

        $.post('/ajaxvote', {plain: true, vote: dir, on: nodeId, json: true},
                function(data)
                {
                        $('#'+nodeId+' .score:first').text(data.score)
                }
                ,'json')

        return false
}

var toggleNode = function(link, node)
{
        $('#'+node).toggleClass('contracted')
        if(link.innerHTML == 'Hide')//◢
        {
                link.innerHTML = 'Show'//▷
//                 link.setAttribute('title','Show')
        }
        else
        {
                link.innerHTML = 'Hide'//◢
//                 link.setAttribute('title','Hide')
        }
}

function setCookie(name,value,days) {
        if (days) {
                var date = new Date();
                date.setTime(date.getTime()+(days*24*60*60*1000));
                var expires = "; expires="+date.toGMTString();
        }
        else var expires = "";
        document.cookie = name+"="+value+expires+"; path=/";
}

function playVideo(id, thumb)
{
    $(thumb).after('<br /><object id="'+id+'-movie" width="425" height="344"><param name="movie" value="http://www.youtube.com/v/'+id+'&fs=1&autoplay=1"></param><embed src="http://www.youtube.com/v/'+id+'&fs=1&autoplay=1" type="application/x-shockwave-flash" width="425" height="344"></embed></object><br />')
}


/* appjet:css */
/*-----------------------------------------------------*/

body, input, textarea {font-family: 'Corbel', sans-serif;}

body {
        background: #222 url(http://files.appjet.net/download?id=obj-SHHJfPmzi) no-repeat;
        color: #fff;
        font-size: 15px;
        margin: 0 auto;
        min-width: 20em;
        max-width: 70em;
        padding: 0 1em 1em 0;
}

.container {
        -moz-border-radius: 1em;
        background: #fff;
        clear: left;
        color: #000;
        margin: 0 1em 0 9em;
        padding: 1em 1em 2em;
        z-index: 1;
}

.sidebar {
    position: fixed;
    font-weight: bold;
    margin: 0;
    padding: 0;
    top: 70px;
    width: 9em;
}

img {border: 0; max-height: 8em;}

a {text-decoration: none;}
a:link {color: #00c;}
a:visited {color: #89a;}
a:hover {color: #090;}

.sidebar a:hover {
        background: #456;
}

.tags { float: right; width: 5em; overflow: hidden; }
.tags a { display: block; padding: .125em 0; white-space: nowrap; }

#appjetfooter {display: none;}


/* unverified */

p, h1 {margin: 0;}
h1 img {float: left; padding-right: .25em;}
h1 > span {font-size: .5em;}
h2 {margin: 0;}
h2 a span, .info {color: #999;}
h2 a span {font-size: .67em;}
.child h2 {font-size: 1.2em;}
.text h1 {line-height: 1em;}

input, textarea {
        -moz-border-radius: 2px;
        font-size: 1.2em;
}
textarea {
        height: 6em;
}

.submit {
        background: rgba(0, 0, 0, 0.1);
        margin: 0 0 0 3em;
        -moz-border-radius: .5em .5em;
        padding: .33em;
}
.submit input[type="submit"] {
        padding: .125em 1em;
}
.submit input[type="text"], .submit input[type="password"], .submit textarea {
        border: 1px #555 solid;
        border: 0;
        padding: .25em;
        width: 25em;
}
.submit label span {
        display: block;
        font-size: 1.2em;
        height: 0;
        left: -5.5em;
        position: relative;
        text-align: right;
        top: .33em;
        width: 5em;
}
#main-form { background: rgba(255, 255, 255, 0.1); float:left;}
label.toggle { display: none; }

.node {
        background: #fff;
        border-top: 1px #ddd dotted;
        clear: left;
        overflow: auto;
        padding: .25em 0 0;
}
.node:first-child {
        border: 0;
}
.node ol, .node ul {
        margin-left: 1em;
}
.child {
        margin: 0 0 0 0;
}
.child .node {
        font-size: .98em;
        margin: 0 0 0 1.5em;
        border-width: 0 0 0 0;
}
.self {
        background: #eef;
}
.ops a, a.control {
        color: #6b6;
        font-weight: bold;
        padding: 0 .5em;
}

/* Menu
---------------------------------*/
.sidebar ul {
        padding: 0 0 0 1em;
}
.sidebar li {
        list-style: none;
}

.menu li ul {
        display: none;
}
.menu li:hover > ul {
        display: block;
}
.menu ul li {
        border-top: 1px #333 solid;
}
.sidebar a {
        color: #fff;
        padding: 0 .67em;
        display: block;
        line-height: 2em;
}
.menu ul ul {
        position: absolute;
        left: 100%;
        top: 0;
        margin-left: -1em;
}



abbr[title] {
        border: 0;
}


.text {
        margin: 0 1em 0 1.5em;
}
.info {
        margin: 0 0 0 1.5em;
}
.text p {
        overflow: auto;
        margin: 0 0 .75em 0;
        font-size: 1.2em;
}
.text p:last-child {
        margin: 0;
}
.info a, .ops a, a.control {
        color: #89a;
}
.ops a:hover, a.control:hover {background: #dfffc6;}
.submit .ops a:hover {background: #456;}


.vote {
        float: left;
        margin: 0 .25em 0 0;
        text-align: center;
        width: 1.5em;
}
.vote a {
        outline: 0;
        background-repeat: repeat-x;
        -moz-border-radius: 2px;
        background-position: 0 -2px;
        color: #ddd;
        display: block;
        font-size: 1.25em;
        line-height: 1.1em;
}
.vote a:hover {
/*        background-image: url(http://files.appjet.net/download?id=obj-LuE3EVCfx);*/
/*        color: #fff;*/
}
.voted {font-weight: bold;}
.up.voted {color: #0c0;}
.down.voted {color: #c00;}
.up:hover {background-color: #080;}
.down:hover {background-color: #800;}
.voted:hover {color: #888;background-color: #333;}


.nav {
        clear: left;
        margin-top: 2em;
}
.nav > a, .nav span {
        display: block;
        width: 33.3%;
        float: left;
}
.nav a {
        background: none;
}
.nav a:last-child {
        text-align: right;
}
.nav span {
        text-align: center;
}


.message {
        background: rgba(255, 255, 255, .33);
        border: 1px rgba(255, 255, 255, .33) solid;
        padding: .5em;
}

blockquote {
        border-left: 2px #888 solid;
        margin: 1em;
        padding: 0 .5em;
}
#spacer {
        height: 5em;
}


.contracted .text, .contracted .node, .contracted .ops, .contracted h2, .contracted .tags { display: none; }
.contracted .vote { visibility: hidden; height: 1px; }


table th { text-align: left; }
.users { border-collapse: collapse; }
.users a { display: block; }
.users col { width: 10em; overflow: hidden; }
.users td { border-bottom: 1px #444 solid; padding-right: .5em; }

#up { position: fixed; bottom: 0;right: 0; margin: 0; }
#up a { font-size: 200%; }

#footer {
        text-align: center;
        margin: 1em 1em 0 9em;
}

#la-sign-in {
/*
        background: #666 url(http://files.appjet.net/download?id=obj-LuE3EVCfx) repeat-x;
        position: absolute;
        top: 2.5em!important;
        right: 0!important;
        width: 12em;
        padding: .5em;
        -moz-border-radius: .5em 0 .5em .5em;
*/
}

.ops ul {
        -moz-border-radius: .5em;
        display: none;
        left: 0;
        padding: .5em 0 0;
        position: absolute;
        top: 0;
}
.ops li {
        background: #aaa;
        list-style: none;
        width: 10em;
}
.ops li a {
        display: block;
}
.ops span {
        position: relative;
}
.ops span:hover ul {
        display: block;
}
.favicon {
        width: 16px;
        height: 16px;
        opacity: .33;
}
.clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
#logo {float:left;}
hr {margin: 1em 0;}
.container a.user {font-weight: bold; color: #024; font-size:1.1em;}


© Copyright 2007-2009 AppJet Inc.