/* appjet:version 0.1 */
/*
This code is GPL, thanks. Please clone it, but keep that in mind.
For license text and more information:
http://www.gnu.org/copyleft/gpl.html
*/
import("storage");
import("quickforms");
import("lib-user");
if (!storage.showdownlib)
{
storage.showdownlib = wget('http://www.gibberish.com/hacks/appjet/showdown.js');
}
eval(storage.showdownlib);
if (!storage.dateformatlib)
{
storage.dateformatlib = wget('http://stevenlevithan.com/assets/misc/date.format.js');
}
eval(storage.dateformatlib);
if (!storage.pages)
{
storage.pages = new StorableCollection();
}
var wikiname = 'WikiJet';
// change that to something more appropriate if you clone pls
// also, lib-user won't use it, so don't come crying to me
// ========================
// Basic model manip
// ========================
var get_page = function(pagename, revnumber)
{
if (pagename == 'pagelist') return true;
var thispage = storage.pages.filter({name: pagename});
var onepage = thispage.filter({rev_number: (revnumber==null ? thispage.size() : revnumber)});
return onepage.next();
};
var set_page = function(name, value)
{
var howmany = storage.pages.size();
var lastone = get_page(name);
var revnum = lastone ? lastone.rev_number + 1 : 1;
var newpage = new StorableObject();
newpage.name = name;
newpage.text = value;
newpage.date = new Date();
newpage.user = user ? user.id : null;
newpage.rev_number = revnum;
storage.pages.add(newpage);
return newpage;
};
// ========================
// Helpers
// ========================
var strip_tags = function(html)
{
return html.replace(/<\/?[^>]+>/gi, '');
};
var do_template = function(vars_obj,tmp_str,printit)
{
var sub_each = function(replacee, propname)
{
return vars_obj[propname];
}
if (printit != null)
print(raw(tmp_str.replace(/#\{([^\}]*)\}/g, sub_each)));
else
return tmp_str.replace(/#\{([^\}]*)\}/g, sub_each);
};
var normalize_name = function(name)
{
var norm = name.replace(/^[\s]+/, '')
.replace(/[\s]+$/, '')
.replace(/[\s]+/g, '_')
.replace(/\W/g, '')
.split('_')
;
for (var i=0; i < norm.length; i++)
{
norm[i] = norm[i].substr(0,1).toUpperCase() + norm[i].substr(1);
}
return norm.join('_');
};
var wikify = function(pagesrc)
{
pagesrc = strip_tags(pagesrc);
converter = new Showdown.converter();
pagesrc = converter.makeHtml(pagesrc);
var write_link = function(replacee, linktxt)
{
var linkee = normalize_name(linktxt);
if (get_page(linkee))
{
return '<a href="/' + linkee + '">' + linktxt + '</a>';
}
else
{
return '<a href="/_edit/' + linkee + '">' + linktxt + '</a>';
}
};
return pagesrc.replace(/\[\[(.*?)\]\]/g, write_link);
};
var render = function(title, filling, ctrls, header)
{
if (header != null)
page.head.write(header);
print(H1(wikiname + ": " + title));
if (user != null) print(H4('Logged in as ' + user.uname));
print(DIV({id: 'body'}, raw(filling)));
print(raw('<p>'));
ctrls.unshift(link('/', 'Home'));
if (!user)
{
ctrls.push(link('/lib-user/login', 'Login'));
ctrls.push(link('/lib-user/register', 'Register'));
}
else
{
ctrls.push(link('/lib-user/logout', 'Logout'));
}
print(DIV({id: 'ctrls'}, raw(ctrls.join(' - '))));
// assumes that entries in ctrls are already htmlstrings
/*
print(link("/_edit/" + name, "Edit page"));
print(link("/_history/" + name, "Page history"));
*/
};
var page_url = function(pagename)
{
return "http://" +
(appjet.mainDomain == 'appjet.net' ? (appjet.appName + ".") : '') +
appjet.mainDomain + "/" +
normalize_name(pagename);
};
// ========================
// URL actions
// ========================
var pagelist = function()
{
var pagecode = '<ul>';
var obj = null;
var pagestubs = storage.pages.filter({rev_number: 1});
for (var ct = 0; ct < pagestubs.size(); ct++)
{
obj = pagestubs.next();
pagecode += '<li>' +
'<a href="/' + normalize_name(obj.name) + '">' +
obj.name.replace(/_/g, ' ') +
'</a></li>'
;
}
//print(raw());
pagecode += "</ul>\n";
render('Page List', pagecode, [link('/', 'Home page')]);
};
var recent = function()
{
var pagecode = '<table><thead><tr><th>Page</th><th>Revised at</th><th>Revision</th></tr></thead><tbody>';
var obj = null;
var revs = storage.pages.sort(function(a,b){return a.date < b.date;});
for (var ct = 0; ct < 25; ct++)
{
obj = revs.next();
if (typeof obj == 'undefined') break;
pagecode +=
'<tr><td><a href="/' + normalize_name(obj.name) + '">' +
obj.name.replace(/_/g, ' ') +
'</a></td><td><small>' + obj.date + "</small></td>" +
'<td><a href="/' + normalize_name(obj.name) + '/rev/' + obj.rev_number + '">' +
obj.rev_number + '</a></td></tr>'
;
}
pagecode += "</tbody></table>\n";
render('Recent Changes', pagecode, [link('/', 'Home page')],
'<link rel="alternate" type="application/rss+xml" title="' + wikiname + ' RSS Feed" href="http://' +
(appjet.mainDomain == 'appjet.net' ? (appjet.appName + ".") : '') +
appjet.mainDomain + '/recent.xml" />'
);
};
var recent_rss = function()
{
page.setMode("plain");
response.setContentType('application/rss+xml');
var thetime = new Date();
print(raw("""<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>"""));
print(raw("<title>"+ wikiname +": Recent Changes</title>"));
print(raw("<link>http://"+ (appjet.mainDomain=='appjet.net' ? appjet.appName + '.' : '') + appjet.mainDomain +"</link>"));
print(raw("""<description>Track the most recent changes to the wiki in this feed.</description>
<language>en</language>
<generator>WikiJet-0.3</generator>"""));
print(raw("<lastBuildDate>"+
thetime.toGMTString() +
"</lastBuildDate>"
));
var chgs = storage.pages.sort(function(a,b){return a.date < b.date;});
for (var ct = 0; ct < 25; ct++)
{
var obj = chgs.next();
var objuser = getStorable(obj.user);
print(raw("<item>"));
print(raw("<title>" + obj.name.replace(/_/g, ' ') + "</title>"));
print(raw("<link>" + page_url(obj.name) + "</link>"));
print(raw("<description>" + wikify(obj.text) + "</description>"));
print(raw("<pubDate>" + obj.date.toGMTString() + "</pubDate>"));
if (typeof objuser != 'undefined')
print(raw("<dc:creator>" + objuser.uname + "</dc:creator>"));
print(raw("</item>"));
}
print(raw("""
</channel>
</rss>
"""));
};
var history = function(name)
{
nname = normalize_name(name);
var pagecode = "<UL>\n";
var rev = null;
var pagehistory = storage.pages.filter({name: nname})
.sort(function(a,b){return a.rev_number < b.rev_number;});
pagecode += "<table><thead><tr>" +
"<th>Revision</th><th>Date</th><th>User</th>" +
"</tr></thead><tbody>";
for (var ct = 0; ct < pagehistory.size(); ct++)
{
rev = pagehistory.next();
historyuser = getStorable(rev.user);
pagecode += "<tr>";
pagecode += "<td>" + link('/' + nname +
'/rev/' + rev.rev_number,
rev.rev_number) + "</td>";
pagecode += "<td><small>" + rev.date + "</small></td>";
pagecode += "<td>" + (historyuser ? historyuser.uname : '') + "</td>";
pagecode += '</tr>\n';
}
pagecode += "</tbody></table>\n";
render('Revision history of \''+name+'\'',
pagecode,
[link('/'+nname, 'Back to \''+name+'\'')]
);
};
var revision = function(name, revnum)
{
nname = normalize_name(name);
wpage = get_page(nname, revnum);
if (wpage)
{
render(name + ', revision ' + revnum + ' (' + wpage.date + ')',
wikify(wpage.text),
[
link("/_edit/" + nname + '/rev/' + revnum, "Edit page with this revision"),
link("/_history/" + name, "Page history"),
link("/pagelist", "All pages")
]
);
}
else
{
render(name + " not found. ",
link("/_edit/" + nname, "Create it"),
[]
);
}
};
var template_test = function()
{
page.setMode("plain");
response.setContentType('application/rss+xml');
var thetime = new Date();
var headvars = {
wikiname: wikiname,
topurl: (appjet.mainDomain=='appjet.net' ? appjet.appName + '.' : '') + appjet.mainDomain,
gmtnow: thetime.toGMTString()
};
var thehead = """<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
<title>#{wikiname}: Recent Changes</title>
<link>http://#{topurl}</link>
<description>Track the most recent changes to the wiki in this feed.</description>
<language>en</language>
<generator>WikiJet-0.3</generator>
<lastBuildDate>#{gmtnow}</lastBuildDate>
""";
do_template(headvars, thehead, 'printit');
var chgs = storage.pages.sort(function(a,b){return a.date < b.date;});
var chgtmp = """<item>
<title>#{objtitle}</title>
<link>#{objurl}</link>
<description>#{objdesc}</description>
<pubDate>#{objgmt}</pubdate>
#{objcreatortag}
</item>""";
var chgcollect = ""; var chgvars = {};
for (var ct = 0; ct < 25; ct++)
{
var obj = chgs.next();
var objuser = getStorable(obj.user);
chgvars = {
objtitle: obj.name.replace(/_/g, ' '),
objurl: page_url(obj.name),
objdesc: wikify(obj.text),
objgmt: obj.date.toGMTString(),
objcreatortag: typeof objuser != 'undefined' ? "<dc:creator>" + objuser.uname + "</dc:creator>" : ''
};
chgcollect += do_template(chgvars, chgtmp) + "\n";
}
print(raw(chgcollect + "</channel>\n</rss>"));
};
var view = function(name)
{
if (name == '') name = 'Home';
nname = normalize_name(name);
wpage = get_page(nname);
if (wpage)
{
var pagetitle = nname.replace(/_/g, ' ');
var pagecode = wikify(wpage.text);
}
else
{
var pagecode = name + " not found. ";
pagecode += link("/_edit/" + nname, "Create it");
var pagetitle = nname + ' doesn\'t exist yet';
}
if (wpage)
{
var themenu = [
link("/recent", "Recent changes"),
link("/pagelist", "All pages")
];
if (user)
{
themenu.unshift(link("/_edit/" + nname, "Edit"));
themenu.unshift(link("/_history/" + nname, "Page history"));
}
}
render(pagetitle, pagecode, themenu);
};
var edit = function(name, revnumber)
{
nname = normalize_name(name);
var thisurl = "/_edit/" + nname;
if (revnumber) thisurl += "/rev/" + revnumber;
if (request.isPost)
{
if (user)
{
set_page(nname, request.params.body);
}
response.redirect("/" + nname);
}
else
{
if (user)
{
var pagebody = "";
var thepage = get_page(nname, revnumber);
if (thepage)
{
var body = thepage.text;
if (revnumber)
{
pagebody = "<p>This revision is dated " + thepage.date +
". Saving this edit will revert the page to this older version! " +
"(<a href='/_edit/" + nname + "'>Edit current version instead</a>)</p>" +
pagebody;
}
}
else
{
body = "";
}
var form = new QuickForm({action: "/_edit/" + nname, method: "post"});
form.addInputTextArea("body", {
label: "Body",
value: body
});
form.addSubmit("submit","Save");
pagebody += form.toHTML();
render("Edit '" + (thepage ? thepage.name.replace(/_/g, ' ') : nname) + "'",
pagebody,
[]
);
}
else
{
response.redirect("/lib-user/login?and=" + thisurl);
}
}
};
// =======================================
// I say it's duck season, and I say FIRE
// =======================================
var pdispatch = function(urls)
{
for (var i = 0; i < urls.length; i += 2)
{
var p = urls[i];
var f = urls[i+1];
var result = new RegExp(p).exec(request.path);
if (result)
{
var args = result.slice(1, result.length);
//printp(args);
f.apply(null, args);
break;
}
//printp(result);
}
};
urls = [
"^/pagelist$", pagelist,
"^/recent$", recent,
"^/recent.xml$", recent_rss,
"^/([A-Za-z0-9_]*)$", view,
"/_history/([A-Za-z0-9_]*)$", history,
"/_edit/([A-Za-z0-9_]*)$", edit,
"/_edit/([A-Za-z0-9_]*?)/rev/([0-9]*)", edit,
"/([A-Za-z0-9_]*?)/rev/([0-9]*)$", revision,
"/template/", template_test
];
pdispatch(urls);
/* appjet:css */
body {
margin: 10px;
}
h1 {
margin: 0px 0px 10px;
border-bottom: 1px solid #88a;
font-family: Sylfaen,Georgia,serif;
font-weight: normal;
padding-bottom: 5px;
}
div#body {
margin: 0px 0px 20px;
}
div#ctrls {
border-top: 1px solid #88a;
padding-top: 5px;
}