Kaynağa Gözat

First Commit of TapirWiki As A CouchApp

Jeroen Vet 15 yıl önce
ebeveyn
işleme
413a381886

Dosya farkı çok büyük olduğundan ihmal edildi
+ 11 - 0
_attachments/scripts/jQuery.js


+ 47 - 0
_attachments/scripts/json2.js

@@ -0,0 +1,47 @@
+/*
+    http://www.JSON.org/json2.js
+    2009-09-29
+
+    Public Domain.
+
+    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+    See http://www.JSON.org/js.html
+*/
+
+// Create a JSON object only if one does not already exist. We create the
+// methods in a closure to avoid creating global variables.
+
+if(!this.JSON){this.JSON={};}
+(function(){function f(n){return n<10?'0'+n:n;}
+if(typeof Date.prototype.toJSON!=='function'){Date.prototype.toJSON=function(key){return isFinite(this.valueOf())?this.getUTCFullYear()+'-'+
+f(this.getUTCMonth()+1)+'-'+
+f(this.getUTCDate())+'T'+
+f(this.getUTCHours())+':'+
+f(this.getUTCMinutes())+':'+
+f(this.getUTCSeconds())+'Z':null;};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(key){return this.valueOf();};}
+var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'},rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c==='string'?c:'\\u'+('0000'+a.charCodeAt(0).toString(16)).slice(-4);})+'"':'"'+string+'"';}
+function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==='object'&&typeof value.toJSON==='function'){value=value.toJSON(key);}
+if(typeof rep==='function'){value=rep.call(holder,key,value);}
+switch(typeof value){case'string':return quote(value);case'number':return isFinite(value)?String(value):'null';case'boolean':case'null':return String(value);case'object':if(!value){return'null';}
+gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==='[object Array]'){length=value.length;for(i=0;i<length;i+=1){partial[i]=str(i,value)||'null';}
+v=partial.length===0?'[]':gap?'[\n'+gap+
+partial.join(',\n'+gap)+'\n'+
+mind+']':'['+partial.join(',')+']';gap=mind;return v;}
+if(rep&&typeof rep==='object'){length=rep.length;for(i=0;i<length;i+=1){k=rep[i];if(typeof k==='string'){v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v);}}}}else{for(k in value){if(Object.hasOwnProperty.call(value,k)){v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v);}}}}
+v=partial.length===0?'{}':gap?'{\n'+gap+partial.join(',\n'+gap)+'\n'+
+mind+'}':'{'+partial.join(',')+'}';gap=mind;return v;}}
+if(typeof JSON.stringify!=='function'){JSON.stringify=function(value,replacer,space){var i;gap='';indent='';if(typeof space==='number'){for(i=0;i<space;i+=1){indent+=' ';}}else if(typeof space==='string'){indent=space;}
+rep=replacer;if(replacer&&typeof replacer!=='function'&&(typeof replacer!=='object'||typeof replacer.length!=='number')){throw new Error('JSON.stringify');}
+return str('',{'':value});};}
+if(typeof JSON.parse!=='function'){JSON.parse=function(text,reviver){var j;function walk(holder,key){var k,v,value=holder[key];if(value&&typeof value==='object'){for(k in value){if(Object.hasOwnProperty.call(value,k)){v=walk(value,k);if(v!==undefined){value[k]=v;}else{delete value[k];}}}}
+return reviver.call(holder,key,value);}
+cx.lastIndex=0;if(cx.test(text)){text=text.replace(cx,function(a){return'\\u'+
+('0000'+a.charCodeAt(0).toString(16)).slice(-4);});}
+if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,''))){j=eval('('+text+')');return typeof reviver==='function'?walk({'':j},''):j;}
+throw new SyntaxError('JSON.parse');};}}());
+
+
+
+
+

+ 160 - 0
_attachments/scripts/macros.js

@@ -0,0 +1,160 @@
+function maketapirwikiparseandreplace(){
+    // all the macros should either appear here or be imported here
+    var pageContent = "";
+
+    function includePage(id) {
+	$.ajax({
+		type:	'get',
+		url:	'../../' + id,
+		async:   false,
+		success:	function(data) {
+			var page = JSON.parse(data);
+			pageContent = convert(page.body);
+			},
+
+		error: function (XMLHttpRequest, textStatus, errorThrown) {
+			pageContent = "INCLUDE ERROR: <a href='Javascript: wiki.open(\"" + id + "\")'>" + id + "</a> does not exist yet...";
+			 }
+	});
+	return pageContent;
+    }
+
+   function index() {
+       var pages;
+
+	$.ajax({
+		type:	'get',
+		url:	'_view/titles',
+		async:   false,
+		success:	function(data) {
+			var results = JSON.parse(data);
+			pages = results;
+			},
+		error: function (XMLHttpRequest, textStatus, errorThrown) {
+			pageContent = "Error!";
+			 }
+	});
+
+	var html = "<ul class='page-list'>";
+
+	for(var x = 0; x < pages.total_rows; x++)
+			{
+				var p = pages.rows[x];
+				html += "<li><a href='#" + p.id + "' onClick='Javascript: wiki.open(\"" + p.id + "\")'>" + p.id + "</a> edited on " + p.value.edited_on + " by " + p.value.edited_by + "</li>";
+			}
+	html += "</ul>";
+	return html;
+    }
+
+
+    function recentChanges() {
+	var pages;
+	
+		$.ajax({
+			type:	'get',
+			url:	'_view/titles',
+			async:   false,
+			success:	function(data) {
+				var results = JSON.parse(data);
+				pages = results;
+				},
+			error: function (XMLHttpRequest, textStatus, errorThrown) {
+				pageContent = "Error!";
+				 }
+		});
+
+		pages.rows.sort(function(a,b){	
+			var dateA = Date.parse(a.value.edited_on);
+			var dateB = Date.parse(b.value.edited_on);
+			if (dateA < dateB) {
+				return 1;
+			} else {
+				return -1;
+			}
+		});
+
+
+		var html = "<ul class='page-list'>";
+		var recentPages = 5;
+		if(pages.rows.lenght < recentPages)
+		{
+			recentPages = pages.rows.length;
+		}
+		for(var x = 0; x < recentPages; x++)
+				{
+					var p = pages.rows[x];
+					html += "<li><a href='#" + p.id + "' onClick='Javascript: wiki.open(\"" + p.id + "\")'>" + p.id + "</a> edited on " + p.value.edited_on + " by " + p.value.edited_by + "</li>";
+					}
+		html += "</ul>";
+		return html;
+    }
+
+
+
+    function topicList(title) {
+        var pages;
+
+	$.ajax({
+		type:	'get',
+		url:	'_view/titles',
+		async:   false,
+		success:	function(data) {
+			var results = JSON.parse(data);
+			pages = results;
+			},
+
+		error: function (XMLHttpRequest, textStatus, errorThrown) {
+			pageContent = "INCLUDE ERROR: <a href='Javascript: wiki.open(\"" + id + "\")'>" + id + "</a> does not exist yet...";
+			 }
+	});
+
+	var html = "<ul class='page-list'>";
+	for(var x = 0; x < pages.total_rows; x++)
+			{
+				var p = pages.rows[x];
+				if (p.id.substring(title.length, 0) == title) {
+
+				html += "<li><a href='#" + p.id + "' onClick='Javascript: wiki.open(\"" + p.id + "\")'>" + p.id + "</a> edited on " + p.value.edited_on + " by " + p.value.edited_by + "</li>";
+			}
+			}
+	html += "</ul>";
+	return html;
+    }
+    // regexes
+    var tokens = {
+        goToMacro         : {re : /\{goto:(\S.*?)\}/g,
+                             sub: '<a href="#$1" onClick="javascript: wiki.load()">$1</a>'},
+        indexMacro        : {re : /\{index\}/, 
+                             sub: index},
+        topicMacro        : {re : /\{topic:(\S.*?)\}/g, 
+                             sub: topicList},
+        recentChangesMacro: {re : /\{recentChanges\}/, 
+                             sub: recentChanges},
+        includeMacro      : {re : /\{include:(.*?)\}/g,
+                             sub: includePage }
+    }; // end tapirwikitokens
+
+
+    function parseandreplace(tnode){
+        if (tnode.type =="#wikilink"){
+             tnode.value="<a href='#"+tnode.value+"' onClick='javascript: wiki.load()'>"+tnode.value+"</a>";
+        }
+        else if (tnode.type == "#blocktoken"){              
+            tnode.value=tnode.value.replace(tokens.indexMacro.re, tokens.indexMacro.sub());
+            tnode.value=tnode.value.replace(tokens.topicMacro.re, tokens.topicMacro.sub("$1"));
+            tnode.value=tnode.value.replace(tokens.recentChangesMacro.re, tokens.recentChangesMacro.sub());
+            tnode.value=tnode.value.replace(tokens.includeMacro.re, tokens.includeMacro.sub("$1"));
+            tnode.value=tnode.value.replace(tokens.goToMacro.re, tokens.goToMacro.sub); // can appear both as line and block token!
+         }
+        else if (tnode.type == "#linetoken"){
+            tnode.value=tnode.value.replace(tokens.goToMacro.re, tokens.goToMacro.sub);
+        }
+        else {
+             for (var i=0;i<tnode.siblings.length;i++) {parseandreplace(tnode.siblings[i]);}  // contents 
+        }
+    } // parseandreplace()
+    return function (tree){
+               parseandreplace(tree);
+               return tree;
+           };
+} // end maketapirwikiparserandreplace()           

+ 251 - 0
_attachments/scripts/parser.js

@@ -0,0 +1,251 @@
+/***************************************
+*
+*	Javascript Textile->HTML conversion
+*       jeroen vet jvet.info
+*	
+* Differences from textile 2.0 'standard' as published on :
+* - block quote and block code are ended with double newline, if you want more than one empty line put a space on the line.
+* - all newlines within elements are substituted by <br /> even for notextile or whatnot. If you don't want newlines then 
+*   enter it without newlines 
+* - single newlines are generally allowed (also in 'inline' elements) for some reason not allowed in standard
+* - lists can be mixed (bug in 'standard')
+* - 
+****************************************/
+
+
+
+
+function makeTextileParser(){
+    // This is the "module" which, after initialization returns the parser function
+    // do this in user code: textileParser=makeTextileParser(); then call with: textileParser(text);
+
+    // Define content arrays. Ensure that routine only looks for possible content to spurious matches
+    var phrase =  ["bold", "italics", "emphasis", "strong", "citation", "superscript", "subscript", "span", 
+                   "deleted", "inserted", "code", "notextilein", "image", "acronym", "footref",
+                   "linetoken"]; 
+    var inline = phrase.concat(["texlinkurl", "wikilink", "aliaslink"]); 
+    var headings = ["heading1", "heading2", "heading3", "heading4", "heading5", "heading6"];
+    var blocks = headings.concat(["para", "bulletlist", "numberlist", "blockcode", "blockcodec", "blockquote", "blockquotec",
+                                  "table", "blockhtml","notextilebl", "pre", "footnote", "linkalias", "blocktoken"]); 
+    var flow = blocks.concat(inline);
+    // precontent = inline but no img
+
+
+    // define the object holding the names, regular expressions, and content it can have (to check for)
+    // since JavaScript does not support lookbehind we sometimes need to match more than we need, therefore we adopt 
+    // the following convention: $1 = real match $2 = attributes $3 = payload $4 etc. are optionals
+    // this object must be a closure so that it is initialized only once
+    var markup =  {//block 
+                   // textile does not allow bare text in body -> converted to paragraph so if root only look for blocks (not flow)
+                   root : {re : "", ct : blocks},
+                   heading1 : {re : /(^h1(\S*)\.\s+((?:.|\n)*?)(?:\n\s*\n|\r\n\s*\r\n|$(?!\n)))/m, ct : inline}, //newlines are allowed but no doubles
+                   heading2  : {re : /(^h2(\S*)\.\s+((?:.|\n)*?)(?:\n\s*\n|\r\n\s*\r\n|$(?!\n)))/m, ct : inline}, 
+                   heading3  : {re : /(^h3(\S*)\.\s+((?:.|\n)*?)(?:\n\s*\n|\r\n\s*\r\n|$(?!\n)))/m, ct : inline},
+                   heading4  : {re : /(^h4(\S*)\.\s+((?:.|\n)*?)(?:\n\s*\n|\r\n\s*\r\n|$(?!\n)))/m, ct : inline},
+                   heading5  : {re : /(^h5(\S*)\.\s+((?:.|\n)*?)(?:\n\s*\n|\r\n\s*\r\n|$(?!\n)))/m, ct : inline},
+                   heading6  : {re : /(^h6(\S*)\.\s+((?:.|\n)*?)(?:\n\s*\n|\r\n\s*\r\n|$(?!\n)))/m, ct : inline},
+                   para      : {re : /(^p(\S*)\.\s+((?:.|\n)*?)(?:\n\s*\n|\r\n\s*\r\n|$(?!\n)))/m, ct : inline}, //explicit paragraph
+                   // for html just taking $0 as the match
+                   blockhtml : {re : /(<(p|h[1-6]|div|ul|ol|dl|pre|blockquote|address|fieldset|table|ins|del|script|noscript)(?:"[^"]*"|'[^']*'|[^'">])*?>)((?:.|\n)*)<\/\2>(?:\n|\r\n)?/, ct: []}, 
+
+                   // lists - payload is $3, attributes $2, level $1 - do not have to end with double newline, one is ok
+                   // list is ended by double newline or 
+                   // we allow newlines in list.
+                   // list ends when: 1. double newline encountered 2. end of string ->
+                   // this way you can mix bullet and number lists and allow new lines in list items.
+                   bulletlist: {re : /(^\*((?:\(\S*\))?(?:\[[a-z][a-z]\])?(?:\{\S*\})?)\s*((?:.|\n(?!\n))*)(?:\n\n|\r\n\r\n|$(?!\n)))/m, ct : ["listitemb"]},
+                   numberlist: {re : /(^#((?:\(\S*\))?(?:\[[a-z][a-z]\])?(?:\{\S*\})?)\s*((?:.|\n(?!\n))*)(?:\n\n|\r\n\r\n|$(?!\n)))/m, ct : ["listitemn"]},
+                   
+                   listitemb  : {re : /(^((?:\(\S*\))?(?:\[[a-z][a-z]\])?(?:\{\S*\})?)((?:.|\n(?!\*)|\n\*\*)*)(?:\n|$))/, ct : flow}, // 
+                   listitemn  : {re : /(^((?:\(\S*\))?(?:\[[a-z][a-z]\])?(?:\{\S*\})?)((?:.|\n(?!#)|\n##)*)(?:\n|$))/, ct : flow}, // 
+                   // blockcode  is ended by double newline if you want more newlines in between use a space on line, same goes for blockquote
+                   blockcode  : {re : /(^bc([()]*(?:\(\S*\))?(?:\[[a-z][a-z]\])?(?:\{\S*\})?)\.\s+((?:.|\n)*?)(?:\n\n|\r\n\r\n|$(?!\n)))/m, ct : []},
+                   blockcodec : {re : /(^bc([()]*(?:\(\S*\))?(?:\[[a-z][a-z]\])?(?:\{\S*\})?)\.\.\s+((?:.|\n(?!^(?:h[1-6]|p|pre|bc|fn\d+|bq)\S*\.))*))/m, ct : []},         
+                   blockquote : {re : /(^bq([()]*(?:\(\S*\))?(?:\[[a-z][a-z]\])?(?:\{\S*\})?)\.\s+((?:.|\n)*?)(?:\n\n|\r\n\r\n|$(?!\n)))/m, ct : flow}, 
+                   blockquotec: {re : /(^bq([()]*(?:\(\S*\))?(?:\[[a-z][a-z]\])?(?:\{\S*\})?)\.\.\s+((?:.|\n(?!^(?:h[1-6]|p|pre|bc|fn\d+|bq)\S*\.))*))/m, ct : flow},         
+                   table      : {re : /(^(?:table((?:\(\S*\))?(?:\[[a-z][a-z]\])?(?:\{.*\})?)\.(?:\n|\r\n))?(\|(?:.|\n)*?\|)\s*(?:\n\n|\r\n\r\n|$(?!\n)))/m, ct : ["tablerow"]},
+                   tablerow   : {re : /(^(?:((?:\(\S*\))?(?:\[[a-z][a-z]\])?(?:\{\S*\})?)\.)?(\|(?:.|\n)*?\|)\s*(?:\n|\r\n|$(?!\n)))/m, ct : ["headercell", "datacell"]},
+                   // attributes data cell in this order: colspan, rowspan, alignment, class/id, language, explicit style (all before the dot)
+                   headercell : {re : /(^\|_((?:\\\d+)?(?:\/\d+)?[()]*(?:[<>=^~]|<>)?(?:\/\d+)?(?:\(\S*\))?(?:\[[a-z][a-z]\])?(?:\{.*\})?)?\.([^\|]*)(?:\|$)?)/, ct : flow},
+                   datacell   : {re : /(^\|(?:((?:\\\d+)?(?:\/\d+)?[()]*(?:[<>=^~]|<>)?(?:\/\d+)?(?:\(\S*\))?(?:\[[a-z][a-z]\])?(?:\{.*\})?)\.)?([^\|]*)(?:\|$)?)/, ct : flow},           
+                   bold        : {re : /(?:^|\W)(\*\*((?:\(\S*\))?(?:\[[a-z][a-z]\])?(?:\{\S*\})?)([^\*\s](?:.|\n)+?[^\*\s])\*\*)(?=$|\W)/, ct : inline},   
+                   italics     : {re : /\b(__((?:\(\S*\))?(?:\[[a-z][a-z]\])?(?:\{\S*\})?)([^_\s](?:.|\n)+?[^_\s])__)\b/, ct : inline},        
+                   emphasis    : {re : /\b(_((?:\(\S*\))?(?:\[[a-z][a-z]\])?(?:\{\S*\})?)([^_\s](?:.|\n)+?[^_\s])_)\b/, ct : inline},         
+                   strong      : {re : /(?:^|\W)(\*((?:\(\S*\))?(?:\[[a-z][a-z]\])?(?:\{\S*\})?)([^\*\s](?:.|\n)+?[^\*\s])\*)(?=$|\W)/, ct : inline},         // whereas the textile 2.0 standard does not seem to allow this for some
+                   citation    : {re : /(?:^|\W)(\?\?((?:\(\S*\))?(?:\[[a-z][a-z]\])?(?:\{\S*\})?)([^\?\s](?:.|\n)+?[^\?\s])\?\?)(?=$|\W)/, ct : inline},       // reason.
+                   superscript : {re :  /(\^((?:\(\S*\))?(?:\[[a-z][a-z]\])?(?:\{\S*\})?)((?:.|\n)+?)\^)(?=$|\W)/, ct : inline},    // we allow superscript to start at non word boundery for footnotes etc.
+                   subscript   : {re : /(~((?:\(\S*\))?(?:\[[a-z][a-z]\])?(?:\{\S*\})?)((?:.|\n)+?)~)(?=$|\W)/, ct : inline},       // subscript likewise
+                   span        : {re : /(?:^|\W)(%((?:\(\S*\))?(?:\[[a-z][a-z]\])?(?:\{\S*\})?)([^%\s](?:.|\n)+?[^%\s])%)(?=$|\W)/, ct : inline},
+                   deleted  : {re : /(?:^|\W)(-((?:\(\S*\))?(?:\[[a-z][a-z]\])?(?:\{\S*\})?)([^\-\s](?:.|\n)+?[^\-\s])-)(?=$|\W)/, ct : inline},   // you can only insert or strike whole words
+                   code        : {re : /(?:^|\W)(@((?:\([\w-]*(?:#[-\w]+)?\))?(?:\[[a-z][a-z]\])?(?:\{\S*\})?)((?:.|\n)+?)@)(?=$|\W)/, ct : []},
+                   inserted      : {re : /(?:^|\W)(\+((?:\(\S*\))?(?:\[[a-z][a-z]\])?(?:\{\S*\})?)([^+\s](?:.|\n)+?[^+\s])\+)(?=$|\W)/, ct : inline },
+                   acronym     : {re : /(?:^|\W)(()([A-Z]+)\((.*?)\))/, ct : []},
+                   //footref no payload (no m[3]) 
+                   footref     : {re : /(?:^|\W)\w+(\[()()(\d+)\])/, ct : []},
+                   // footnote no class/id attributes as assigned automatically
+                   footnote    : {re : /(^fn(\d+)((?:<|>|=|<>)?[()]*(?:\[[a-z][a-z]\])?(?:\{\S*\})?)?\.\s+((?:.|\n)*?)(?:\n\s*\n|\r\n\s*\r\n|$(?!\n)))/m, ct : inline},
+		   texlinkurl  : {re : /("((?:\(\S*\))?(?:\[[a-z][a-z]\])?(?:\{\S*\})?)([^"]+)":((?:http|https|ftp|git):\/\/(?:[a-z0-9](?:[\-a-z0-9]*[a-z0-9])?\.)+[a-z]{2,6}(?::\d{1,5})?(?:\/\S*)?))/i, ct : phrase}, 
+                   // image $3 is empty $4 is src file $5 is title $6 is link if any
+                   image       : {re : /(!((?:[<>()]*)?(?:\(\S*\))?(?:\[[a-z][a-z]\])?(?:\{\S*\})?)?()(\S+?)(\(.*\))?!(?::((?:http|https|ftp):\/\/(?:[a-z0-9](?:[\-a-z0-9]*[a-z0-9])?\.)+[a-z]{2,6}(?::\d{1,5})?(?:\/\S*)?))?)/i, ct : []},
+                   pre         : {re : /(^pre\.\s()((?:.|\n)*?)(?:\n\n|\r\n\r\n|$(?!\n)))/m, ct : []}, // pre has a different meaning in textile. It is really the same as bc. but without the code formatting.
+                   // link alias name m[2] value m[4]
+                   linkalias   : {re : /(^\[(\w+?)\]()((?:http|https|ftp|git):\/\/(?:[a-z0-9](?:[\-a-z0-9]*[a-z0-9])?\.)+[a-z]{2,6}(?::\d{1,5})?(?:\/\S*)?))/m, ct : []},
+                   aliaslink   : {re : /("((?:\(\S*\))?(?:\[[a-z][a-z]\])?(?:\{\S*\})?)([^"]+)":(\w+))/i, ct : phrase}, 
+                   // below muchachos get special nodes the payload is put in value (m[2])
+                   notextilebl : {re : /(^notextile\.\s((?:.|\n)*?(?:\n\n|\r\n\r\n|$(?!\n))))/m, ct : []},
+                   notextilein : {re : /(?:^|\s)(==((?:.|\n)+?)==(?=(?:$|\s)))/m, ct : []},
+                   wikilink    : {re : /(?:^|[^A-Za-z0-9!:\>\/])(([A-Z](?:[A-Z0-9]*[a-z][a-z0-9]*[A-Z]|[a-z0-9]*[A-Z][A-Z0-9]*[a-z])[A-Za-z0-9]*))/m, ct : []},
+                   blocktoken  : {re : /(^(\{\S.*?\})\s*$)/m, ct : []},
+                   linetoken   : {re : /(?:^|[^!])((\{\S*?\}(?!\.)|\[\S*\](?!\{)))/m, ct : []}
+                   //no node created for below guys the escape "!" is simply stripped
+                 // texlinkmail : {re : /(?:^|\W)("([^"]+)":((http|https|ftp|git):/{0,2}[^\w+)/i, tag : "a"},
+                 };
+
+    // TODO:
+    // var rfn = /^fn(\d+)\.\s*(.*)/;
+    // var bq = /^bq\.(\.)?\s*/;
+    // var table=/^table\s*\{(.*)\}\..*/;
+    // var trstyle = /^\{(\S+)\}\.\s*\|/;
+
+
+
+    
+    function Node(type, value, parent){
+        // Define a node object that can be of type element type (type = "tag", or text type="#text")
+        this.type = type; // element textile name or "#text" or #htmlblock
+        this.value = value; // in case of element the string with attributes and values, in case text the text string, in case of root contains dictionary aliases
+        this.parent = parent;
+        this.siblings = []; // in case of text this is null
+    }
+    
+    var escwikilink = /!((?:[A-Z](?:[A-Z0-9]*[a-z][a-z0-9]*[A-Z]|[a-z0-9]*[A-Z][A-Z0-9]*[a-z])[A-Za-z0-9]*))/g;
+    var esctoken = /!(\{\S*?\}(?!\.)|\[\S*\](?!\{))/g;
+    function RemoveEscSign(text){               
+        text=text.replace(escwikilink,"$1");
+        text=text.replace(esctoken,"$1");  
+        return text;
+    }
+  
+    // define a parse function. Constructs a tree of nodes
+    // regeexes we need in the loop
+    var rb = new RegExp("^\\*\\s*","mg");
+    var rn = new RegExp("^#\\s*","mg");
+    var parsetext = function(text, parent){
+        // call this function repeatedly until no matches no 
+        // for now logic about which tags not to test (no testing for link if parent is link for example)
+        // we have to find the first outer match. Strip in three parts. Text before the match. 
+        // The match. And text-still to be parsed after the match.
+        while (text.length>0){
+            var first = { markup : "", m: []}; first.m.index=text.length;
+            //find first match
+            for (var count=0; count < markup[parent.type].ct.length && first.m.index > 0; count++ ){   
+                var found=markup[markup[parent.type].ct[count]].re.exec(text);
+                if (found && found.index<first.m.index){
+                    first.markup = markup[parent.type].ct[count]; 
+                    first.m=found;
+                }
+                
+            }
+            if (first.markup !="") {
+                // correct here exceptions in match order 
+                if (first.markup == "footnote"){
+                    var temp =first.m[2];
+                    first.m[2]=first.m[3];
+                    first.m[3]=first.m[4];
+                    first.m[4]=temp;
+                } 
+                var realindex=first.m.index + first.m[0].indexOf(first.m[1]);
+                if (realindex>0) { // deal with text before match
+                    if (parent.type!=="root"){
+                       parent.siblings.push(new Node("#text", RemoveEscSign(text.substring(0,realindex)), parent));
+                    }
+                    else {// no orphaned text under root -> convert to paragraphs unless special code [] or {}
+                        var paragraphs = text.substring(0,realindex).split(/(?:\n\n|\r\n\r\n)/);
+                        for (var i=0;i<paragraphs.length;i++){
+                            var nwsibling=new Node("para", "" , parent);                            
+                            parent.siblings.push(nwsibling);
+                            parsetext(paragraphs[i],nwsibling);
+                        }
+                    } 
+                }
+                // special treatments
+                if (first.markup=="blockhtml"){
+                    // just create a html node
+                    // as regex is greedy first make sure you get only the first outer block (indicated by an unmatched closing tag)
+                    var retago=new RegExp("<"+first.m[2]+'(?:"[^"]*"|'+"'[^']*'|[^'"+'">])*?>',"g"); // opening tag 
+                    var retagc=new RegExp("</"+first.m[2]+">(\\n|\\r\\n)?","g"); // closing tag
+                    var resulto, resultc;
+                    if ((resultc = retagc.exec(first.m[3]))!== null){ // block is nested found a closing tag
+                        do{
+                            if((resulto = retago.exec(first.m[3]))!==null){ // found an opening tag 
+                              if (retagc.lastIndex < retago.lastIndex){ // there are several outer blocks, first one found
+                                 first.m[0]=first.m[0].substring(0,first.m[1].length+retagc.lastIndex); // correct match for first outer block only
+                                 break;
+                              }
+                            }
+                        } while ((resultc = retagc.exec(first.m[3]))!== null);
+                    }
+                    parent.siblings.push(new Node("#html", first.m[0], parent));
+                }
+                else if (first.markup=="notextilebl" || first.markup=="notextilein"){
+                    parent.siblings.push(new Node("#html", first.m[2], parent));   
+                }
+                else if (first.markup=="wikilink"){
+                    parent.siblings.push(new Node("#wikilink", first.m[2], parent));
+                }
+                else if (first.markup=="linetoken"){
+                    parent.siblings.push(new Node("#linetoken", first.m[2], parent));
+                }
+                else if (first.markup=="blocktoken"){
+                    parent.siblings.push(new Node("#blocktoken", first.m[2], parent));
+                }
+                else if (first.markup=="linkalias" && parent.type=="root"){ // only allowed on root level
+                    parent.value[first.m[2]]=first.m[4];
+                } 
+                else{ 
+                    // create a regular node that can have siblings
+                    //var nwsibling=new Node(first.markup, makeattributestring(first.m[2]), parent);
+                    var nwsibling=new Node(first.markup, [first.m[2]], parent);
+                    // do special case lists (normalize nested lists):
+                    if (first.markup=="listitemb"){
+                        first.m[3]=first.m[3].replace(rb,"");
+                    }
+                    if (first.markup=="listitemn"){
+                        first.m[3]=first.m[3].replace(rn,"");
+                    }
+                    // in case more attributes append to value array
+                    for (var i=4; i<first.m.length; i++){
+                        nwsibling.value.push(first.m[i]);
+                    }
+                    parent.siblings.push(nwsibling);
+                    if (first.m[3].length>0){parsetext(first.m[3],nwsibling);}                
+                }
+                // chop of text before and matched text from string
+                text=text.substr(first.m.index+first.m[0].length);
+            }
+            else{ // no match so everything is regular text -> leaf node (#)
+                if (parent.type!="root"){
+                    parent.siblings.push(new Node("#text", RemoveEscSign(text), parent));
+                }
+                else { // orphaned text at the end of document is converted to paragraphs
+                    var paragraphs = text.split(/(?:\n\n|\r\n\r\n)/);
+                    for (var i=0;i<paragraphs.length;i++){
+                        var nwsibling=new Node("para", "" , parent);                            
+                        parent.siblings.push(nwsibling);
+                        parsetext(paragraphs[i],nwsibling);
+                    }
+                }
+                text=""; //same as break
+            }
+        }
+    }; 
+
+
+// this is the function returned
+    return function(text){  // actual parser function to return
+        var p = new Node("root", {}, null);
+        parsetext(text, p);
+        return p;
+    };
+}
+

+ 181 - 0
_attachments/scripts/renderer.js

@@ -0,0 +1,181 @@
+// this is the rendering part
+function makehtmlrenderer(){
+    // initialisation
+
+    var align={'>':'right','<':'left','=':'center','<>':'justify','~':'bottom','^':'top'};
+    // Helper  functions 
+
+    // (borrowed from uncle Ben) function to parse and create an attribute string in this implementation everything can be put (), {} and []
+    function qat(a,v){return ' '+a+'="'+v+'"';}
+    // then the function
+    function makeattributestring(s, tag){
+	if(!s){return "";}
+        // initialize
+        var st="";var at="";
+        // language (l) 
+        var l=/\[(\w\w)\]/.exec(s);
+	if(l !== null) {at += qat('lang',l[1]);}
+        // class and id
+        var ci=/\((\S+)\)/.exec(s);
+        if(ci !== null) {
+	    s = s.replace(/\((\S+)\)/,"");
+	    at += ci[1].replace(/#(.*)$/,' id="$1"').replace(/^(\S+)/,' class="$1"');
+	}
+        // style attribute
+          // alignment    
+        if (tag === "img"){
+            var ta= /(<|>)/.exec(s);if(ta){st +="float:"+align[ta[1]]+";";}
+        }
+        else{
+	    var ta= /(<>|=|<|>)/.exec(s);if(ta){st +="text-align:"+align[ta[1]]+";";}
+            var va= /(~|\^)/.exec(s);if(va){st +="vertical-align:"+align[va[1]]+";";}
+        }
+          // explicit styles
+	var ss=/\{(.+)\}/.exec(s);if(ss){st += ss[1];if(!ss[1].match(/;$/)){st+= ";";}}
+          // padding
+	var pdl = /(\(+)/.exec(s);if(pdl){st+="padding-left:"+pdl[1].length+"em;";}
+	var pdr = /(\)+)/.exec(s);if(pdr){st+="padding-right:"+pdr[1].length+"em;";}            
+	if(st) {at += qat('style',st);}
+        // col span and row span
+        var colspan = /\\(\d+)/.exec(s);if(colspan){at+=qat('colspan',colspan[1]);}
+        var rowspan = /\/(\d+)/.exec(s);if(rowspan){at+=qat('rowspan',rowspan[1]);}
+	return at;
+    }
+  
+    function replacechar(match){
+        if (match=="<"){
+           return "&lt;";
+        }
+        else if (match==">"){
+           return "&gt;";
+        }
+        else if (match=="\""){
+           return "&quot;";
+        }
+        else if (match=="'"){
+           return "&#8217;";
+        }
+        else if (match=="&"){
+          return "&amp;";
+        }
+    }
+
+    function html2entities(sometext){
+       var re=/[<>"'&]/g;
+       return sometext.replace(re, function(m){return replacechar(m);});
+    }
+    
+    // punctuation
+    var ent={"'":"&#8217;"," - ":" &#8211; ","([^!])--([^>])":"$1&#8212;$2"," x ":" &#215; ","\\.\\.\\.":"&#8230;","\\(C\\)":"&#169;","\\(R\\)":"&#174;","\\(TM\\)":"&#8482;"};
+    function punctuate(sometext){
+        for(var i in ent) { 
+            if (ent.hasOwnProperty(i)){
+                sometext=sometext.replace(new RegExp(i,"g"),ent[i]);
+            }
+        }
+        return sometext;
+    }
+
+    var map = {   heading1    : "h1",
+                  heading2    : "h2",
+                  heading3    : "h3",
+                  heading4    : "h4",
+                  heading5    : "h5",
+                  heading6    : "h6",
+                  para        : "p",
+                  bulletlist  : "ul",
+                  numberlist  : "ol",
+                  listitemb   : "li",
+                  listitemn   : "li",
+                  bold        : "b",
+                  italics     : "i",
+                  emphasis    : "em",
+                  strong      : "strong",
+                  citation    : "cite",
+                  superscript : "sup",
+                  subscript   : "sub",
+                  code        : "code",
+                  span        : "span",
+                  deleted     : "del",
+                  inserted    : "ins",
+                  texlinkurl  : "a",
+                  aliaslink   : "a",
+                  blockcode   : "pre",
+                  blockcodec  : "pre",
+                  blockquote  : "blockquote",
+                  blockquotec : "blockquote",
+                  table       : "table",
+                  tablerow    : "tr",
+                  headercell  : "th",
+                  datacell    : "td",
+                  image       : "img",
+                  acronym     : "acronym",
+                  footnote    : "p",
+                  footref     : "sup"
+              };
+    var empty =  { image : true };
+
+
+    function renderhtml(rootnode){
+        // as we need to treat root specially because of dictionary with the linkaliases we need inner function
+        // traverse the tree to build the html we assume that calling program inserts the tags for the 'root'
+        function rhtml(tnode){
+            if (tnode.type.charAt(0) != "#"){
+                if (tnode.type != "root"){ // start tag
+                    var tag ="<"+map[tnode.type];
+                    tag +=tnode.type=="texlinkurl"?" href=\""+tnode.value.pop()+"\" ":"";
+                    tag +=tnode.type=="aliaslink"?" href=\""+aliasdic[tnode.value.pop()]+"\" ":"";                  
+                    tag +=tnode.type=="wikilink"?" href=\"#"+tnode.value.pop()+"\" onClick=\"javascript: wiki.load()\" ":"";
+                    tag +=tnode.type=="acronym"?" title=\""+tnode.value.pop()+"\"":"";
+                    tag +=makeattributestring(tnode.value[0], map[tnode.type]);
+                    tag +=tnode.type=="footnote"?" id=\"fn"+tnode.value[1]+"\" class=\"footnote\"><sup>"+tnode.value[1]+"</sup":"";
+                    tag +=tnode.type=="footref"?" class=\"footnote\"><a href=#fn"+tnode.value[1]+">"+tnode.value[1]+"</a":"";                
+                    if (tnode.type=="image"){  // is the only empty tag in textile
+                        tag +=" src=\""+tnode.value[1]+"\" "; 
+                        tag +=tnode.value[2]!=""?" title=\""+tnode.value[2]+"\" alt=\""+tnode.value[2]+"\" ":"";
+                        if (tnode.value[3]!=""){
+                            tag = "<a href=\""+tnode.value[3]+"\">"+tag+"/></a>"; // not elegant but because of structure textile!
+                        }
+                        else {
+                            tag +=" />";
+                        }
+                    } // image
+                    else { // not an image
+                        tag +=">";
+                    }   
+                    tag+=tnode.type.indexOf("blockcode")?"":"<code>";              
+                    html+=tag;
+                } // tnode.type != root 
+                for (var i=0;i<tnode.siblings.length;i++) {rhtml(tnode.siblings[i]);}  // contents 
+                if ((tnode.type != "root") && !empty[tnode.type]) { //ending tag
+                    html +=tnode.type.indexOf("blockcode")?"":"</code>";
+                    html += "</" + map[tnode.type] +">";}  
+                }
+            else if (tnode.type =="#text"){ 
+                var snippet="";
+                if ((tnode.parent.type !== "code") && (tnode.parent.type !== "blockcode") && (tnode.parent.type !== "pre") &&
+                    (tnode.parent.type !=="blockcodec")){
+                    snippet=punctuate(tnode.value);
+                }
+                else {
+                    snippet=html2entities(tnode.value);
+                } 
+                html += snippet.replace(/\n(?!\n$)/g,"<br />"); // new lines everywhere (except for double newline at the end)! 
+            }
+            else { // all special leaf cases (# html, #wikilink, #linetoken, #blocktoken middleware should have changed last three to html
+                html += tnode.value;
+            }  
+
+        } // end rhtml
+        // initialisations 
+
+        var html="";
+        var aliasdic = rootnode.value;
+        rhtml(rootnode);   
+        return html;
+    } // end renderhtml
+    return function(tree){  // actual rendering function to return
+                   return renderhtml(tree);
+               };
+} // end makehtmlrenderer  
+

+ 614 - 0
_attachments/scripts/tapirwiki.js

@@ -0,0 +1,614 @@
+/* Tapir Wiki JS
+  A wiki system which is hosted by CouchDB...
+  Original work: Joshua Knight
+*/
+
+// JV: make the functions needed for rendering
+textileparser=makeTextileParser();
+tapirparseandreplace=maketapirwikiparseandreplace();
+htmlrenderer=makehtmlrenderer();
+
+function convert(text){ 
+    return htmlrenderer(tapirparseandreplace(textileparser(text)));
+}
+
+String.prototype.toCamelCase = function() {
+	  return this.toString()
+	    .replace(/([A-Z]+)/g, function(m,l){
+	      return l.substr(0,1).toUpperCase() + l.toLowerCase().substr(1,l.length);
+	    })
+	    .replace(/[\-_\s](.)/g, function(m, l){
+	      return l.toUpperCase();
+	    });
+	};
+
+//Set some settings...
+var settings;
+
+wiki = new Object();
+
+//wiki page properties
+wiki._id = "";
+wiki._rev = "";
+wiki.body = "";
+wiki.edited_by = "";
+wiki.edited_on = "";
+wiki.type = "page";
+
+wiki.save = function() {
+
+	if(wiki._rev == null)
+	{
+		wiki._id = $("#title").val();
+	}
+	
+	if(wiki._id == "")
+	{
+		error("Please enter a page title!");
+	}
+	else {
+	wiki.body = $("#body").val();
+	wiki.edited_by = $.cookie("userName");
+	wiki.edited_on = Date();
+
+	$.ajax({
+		type:	'put',
+		url:	'../../' + this._id,
+		data:	JSON.stringify(this),
+		async:	false,
+		success:	function(data) {
+			var response = JSON.parse(data);
+			wiki._rev = response.rev;
+			wiki.open(wiki._id);
+			$.jGrowl("Your page has been saved...", {header: "Cool!"});
+		},
+		error: function (XMLHttpRequest, textStatus, errorThrown) {
+			error("Ooooops!, request failed with status: " + XMLHttpRequest.status + ' ' + XMLHttpRequest.responseText); }
+	});
+	}
+	
+};
+
+wiki.display = function() {
+	var n = $("<div id='page-body'>" + convert(this.body) + "</div>").hide();
+	$("#inner-content").html(n);
+	n.fadeIn("fast");
+	$("#pageTitle").html(this._id);
+	$("#page-menu").html("");
+	$("<li class='pageSpecific'><a href='Javascript: wiki.edit()'>Edit</a></li>").appendTo("#page-menu");
+	$("<li class='pageSpecific'><a href='Javascript: wiki.history()'>History</a></li>").appendTo("#page-menu");
+	$("<li class='pageSpecific'><a href='Javascript: wiki.attachments()'>Attachments</a></li>").appendTo("#page-menu");
+	$("<li class='pageSpecific'><a href='Javascript: wiki.remove()'>Delete</a></li>").appendTo("#page-menu");
+	window.location = "wiki.html#" + this._id;
+};
+
+wiki.init = function() {
+	wiki._id = "";
+	delete wiki._rev;
+	delete wiki._revisions;
+	delete wiki._attachments;
+	wiki.body = "";
+	wiki.edited_on = "";
+	wiki.edited_by = "";
+};
+
+wiki.remove = function() {
+		if (confirm("Really delete this page?")) {
+		$.ajax({
+		type:	'delete',
+		url:	'../../' + wiki._id + '?rev=' + wiki._rev,
+		success:	function(){
+			$.jGrowl("Page has been deleted...", {header: "Cool!"});
+			$("#page-body").fadeOut("slow", wiki.open('FrontPage'));
+			},
+		error: function (XMLHttpRequest, textStatus, errorThrown) {
+			error("Ooooops!, request failed with status: " + XMLHttpRequest.status + ' ' + XMLHttpRequest.responseText); }
+		});
+	}
+};
+
+
+wiki.open = function(id) {
+	wiki.init();
+	$.ajax({
+		type:	'get',
+		url:	'../../' + id + "?revs=true",
+		success:	function(data) {
+			var page = JSON.parse(data);
+			wiki._id = page._id;
+			wiki._rev = page._rev;
+			wiki.body = page.body;
+			wiki._revisions = page._revisions;
+			wiki.edited_on = page.edited_on;
+			wiki.edited_by = page.edited_by;
+			wiki._attachments = page._attachments;
+			wiki.display();
+			},
+
+		error: function (XMLHttpRequest, textStatus, errorThrown) {
+			wiki._id = id;
+			wiki.edit();
+			 }
+	});
+};
+
+wiki.edit = function() {
+
+		$("#inner-content").html("");
+		$("<h2>" + this._id + "</h2>");
+
+		var form = $("<form id='addNote'></form>").hide().appendTo("#inner-content").fadeIn("slow");
+
+		if(wiki._rev)
+		{
+			//if there is a revision, it is an existing page and the title should be displayed read only
+			$("<p id='title'>" + this._id + "</p>").appendTo(form);
+		}
+
+		else
+		{
+			//if no revision, it's a new page and we should let the user enter a page name
+
+			$("<p><input id='title' type='text' value='" + this._id + "'/></p>").appendTo(form);
+			$("#title").focus();
+
+			$("#title").blur(function() {
+				var newTitle = $("#title").val().toCamelCase();
+				$("#title").val(newTitle); 
+			});
+
+			var pages;
+			$.ajax({
+			type:	'get',
+			url:	'_view/titles',
+			async:   false,
+			success:	function(data) {
+				var results = JSON.parse(data);
+				pages = results;
+				},
+
+			error: function (XMLHttpRequest, textStatus, errorThrown) {
+				pageContent = "INCLUDE ERROR!" + id + " does not exist yet...";
+				 }
+			});
+			//create the templates region.
+			var templatesDiv = $("<div id='templates'>Page templates:<a id='shower'>Show</a></div>").appendTo(form);
+			var templatesList = $("<ul id='templatesList'></ul>").hide().appendTo(templatesDiv);
+			$("#shower").click(function() {
+
+				if($("#shower").html() == "Show")
+				{
+					$("#shower").html("Hide");
+					$(templatesList).slideDown("slow");
+				}
+				else
+				{
+					$("#shower").html("Show");
+					$(templatesList).slideUp("slow");
+				}
+				});
+
+			var anyTemplates = false;
+
+			for(var x = 0; x < pages.total_rows; x++)
+					{
+						var template = pages.rows[x];
+						if(template.id.substring(12,0) == "PageTemplate")
+							 {
+								$("<li><a onClick='Javascript: wiki.applyTemplate(\"" + template.id + "\")'>" + template.id.replace("PageTemplate","") + "</a></li>").appendTo(templatesList);
+								anyTemplates = true;
+							 }
+					}
+			if(anyTemplates === false)
+			{
+				//$("<p>No templates found. Create a page with a name starting TEMPLATE_ to get it listed here (e.g TEMPLATE_MyTemplate)</p>").appendTo(templatesList);
+				$(templatesDiv).hide();
+			}
+
+		}
+		$("<p><textarea id='body'>" + this.body + "</textarea></p>").appendTo(form);
+
+		$("#page-menu").html("");
+		$("<li><a href='Javascript: wiki.save();'>Save</a></li>").appendTo("#page-menu").fadeIn("slow");
+		$("#pageTitle").html("New page");
+};
+
+wiki.applyTemplate = function(id) {
+	$.ajax({
+		type:	'get',
+		url:	'../../' + id,
+		success:	function(data) {
+			var template = JSON.parse(data);
+			$("#body").val(template.body);
+			$("#shower").html("Show");
+			$("#templatesList").slideUp("slow");
+			},
+
+		error: function (XMLHttpRequest, textStatus, errorThrown) {
+			error("Error!");
+			 }
+	});
+}
+
+
+wiki.history = function() {
+//This function creates a history list for the current page, showing previous revisions (which occur through editing the page) and conflicts (which occur through replication)
+
+	//create a holder for the history list
+	$('#page-body').html('<h2>Page history</h2><p>This page shows all stored historical versions of this page.</p><ul id="history"></ul>').hide().fadeIn("slow");
+	//get the current rev number
+	var rev = this._revisions.start;
+
+	//create an array to hold the merged list of revisions and conflicts
+	var oldPages = new Array();
+	
+	//iterate through the revisions for the current page
+	for(var x in this._revisions.ids)
+	{
+		$.ajax({
+			type:	'GET',
+			url:	'../../' + this._id + '?rev=' + rev + '-' + this._revisions.ids[x],
+			async:	false,
+			success:	function(data) {
+				var page = JSON.parse(data);
+				oldPages.push(page);
+			},
+			error: function (XMLHttpRequest, textStatus, errorThrown) {
+				error("Ooooops!, request failed with status: " + XMLHttpRequest.status + ' ' + XMLHttpRequest.responseText); }
+		});
+		rev--;
+	}
+
+	//get conflicts for the current page, tag any pages as a conflict so we can identify them as such later
+	$.ajax({ 
+		type:	'GET',
+		url:	'../../' + this._id + '?conflicts=true',
+		async:	false,
+		success: function(data) {
+			var conflicts = JSON.parse(data);
+			for(rev in conflicts._conflicts) {
+				$.ajax({
+					type:	'GET',
+					url:	'../../' + wiki._id + '?rev=' + conflicts._conflicts[rev],
+					async:	false,
+					success: function(data){
+						page = JSON.parse(data);
+						page.conflict = true;
+						oldPages.push(page);
+					},
+					error: function (XMLHttpRequest, textStatus, errorThrown) {
+						error("Ooooops!, request failed with status: " + XMLHttpRequest.status + ' ' + XMLHttpRequest.responseText); 
+					}				
+				});
+			}
+	},
+	error: function (XMLHttpRequest, textStatus, errorThrown) {
+		error("Ooooops!, request failed with status: " + XMLHttpRequest.status + ' ' + XMLHttpRequest.responseText); 
+	}
+	});
+
+	//now we sort by date so that we have a nice chronological list to show the user
+	oldPages.sort(function(a,b){	
+		var dateA = Date.parse(a.edited_on);
+		var dateB = Date.parse(b.edited_on);
+		if (dateA < dateB) {
+			return 1;
+		} else {
+			return -1;
+		}
+	});
+
+	//remember previous versions in an array so we can view a historic version
+	wiki.previousVersions = new Array();
+	
+	for(var page in oldPages)
+	{	
+		var event = "Edited ";
+		if(oldPages[page].conflict) 
+		{
+			event = "Conflict ";
+		}
+
+		wiki.previousVersions[oldPages[page]._rev] = oldPages[page];
+		
+		$('<li>' + event + ' on ' + oldPages[page].edited_on + ' by ' + oldPages[page].edited_by + '<a id="' + oldPages[page]._rev + '"> View</a></li>').appendTo('#history');
+		$('#' + oldPages[page]._rev).click(function(){
+			wiki.body = wiki.previousVersions[this.id].body;
+			wiki.display();
+	});
+	}
+}
+
+wiki.sync = function() {
+	localDBArr = location.toString().split("/",4);
+	localDB = localDBArr[3];
+	var repA = {"source": settings.replicationEndPoint,"target":localDB};
+	var repB = {"source": localDB,"target":settings.replicationEndPoint};
+
+	//replicate remote to local
+	$.ajax({
+		type: 'POST',
+		url:	'/_replicate',
+		data:	JSON.stringify(repA),
+		success:	function(data) {
+			$('#page-body').html("Synchronisation complete!");
+		},
+		error: function (XMLHttpRequest, textStatus, errorThrown) {
+			error("Ooooops!, request failed with status: " + XMLHttpRequest.status + ' ' + XMLHttpRequest.responseText); 
+		}
+	});
+	//and local to remote
+	$.ajax({
+		type: 'POST',
+		url:	'/_replicate',
+		data:	JSON.stringify(repB),
+		success:	function(data) {
+			$('#page-body').html("Synchronisation complete!");
+		},
+		error: function (XMLHttpRequest, textStatus, errorThrown) {
+			error("Ooooops!, request failed with status: " + XMLHttpRequest.status + ' ' + XMLHttpRequest.responseText);
+		}
+	});	
+}
+
+wiki.load = function() {
+	//I'm pretty sure this is horrible...need to wait for the location to be updated before opening the page
+	setTimeout(function() {
+		var requestedPage = location.hash.substring(1);
+		wiki.open(requestedPage);
+	}, 200);
+}
+
+
+wiki.attachments = function() {
+		$('#page-body').html('<h2>Attachments</h2><ul id="attachment-list"></ul><div id="upload-form-holder"></div>').hide().fadeIn("slow");
+	
+		$("<h3>New attachment</h3><form id='attachment-form' method='post' action='' content-type='multipart/form-data'><input id='_attachments' type='file' name='_attachments'/><input type='hidden' name='_rev' value='" + wiki._rev + "'/></form><button id='upload-button'>Upload File</button>").appendTo('#page-body');
+		$("#attachment-form").ajaxForm(function() { 
+            error("Thank you for your comment!"); 
+        });
+        
+		var options = { 
+		        target:    '',    
+		        url:       "../../" + wiki._id, 
+		        type:      "post",
+		        async:	false,
+				success:   function(data) {
+					wiki.open(wiki._id);
+					}
+		        }; 
+        
+		$("#upload-button").click(function() { 
+	        $("#attachment-form").ajaxSubmit(options);
+	    	});
+
+		for(f in wiki._attachments) {
+			$("<li><a href='../../" + wiki._id + "/" +  f + "'>" + f + "</a></li>").appendTo("#attachment-list");
+		}
+}
+
+//Finally, some miscellaneous useful functions
+function identify() {
+	var name=prompt("Please enter your name", $.cookie("userName"));
+	if (name!=null && name!="")
+		{
+			$.cookie("userName", name, { expires: 7 });
+			$("#userName").html(name);
+		}
+}
+
+function error(msg) {
+	$.jGrowl(msg, {header: "Uh Oh!"});
+}
+
+var pageContent = "";
+
+function includePage(id) {
+	$.ajax({
+		type:	'get',
+		url:	'../../' + id,
+		async:   false,
+		success:	function(data) {
+			var page = JSON.parse(data);
+			pageContent = convert(page.body);
+			},
+
+		error: function (XMLHttpRequest, textStatus, errorThrown) {
+			pageContent = "INCLUDE ERROR: <a href='Javascript: wiki.open(\"" + id + "\")'>" + id + "</a> does not exist yet...";
+			 }
+	});
+	return pageContent;
+}
+
+function index() {
+var pages;
+
+	$.ajax({
+		type:	'get',
+		url:	'_view/titles',
+		async:   false,
+		success:	function(data) {
+			var results = JSON.parse(data);
+			pages = results;
+			},
+		error: function (XMLHttpRequest, textStatus, errorThrown) {
+			pageContent = "Error!";
+			 }
+	});
+
+	var html = "<ul class='page-list'>";
+
+	for(var x = 0; x < pages.total_rows; x++)
+			{
+				var p = pages.rows[x];
+				html += "<li><a href='#" + p.id + "' onClick='Javascript: wiki.open(\"" + p.id + "\")'>" + p.id + "</a> edited on " + p.value.edited_on + " by " + p.value.edited_by + "</li>";
+			}
+	html += "</ul>";
+	return html;
+}
+
+
+function recentChanges() {
+	var pages;
+	
+		$.ajax({
+			type:	'get',
+			url:	'_view/titles',
+			async:   false,
+			success:	function(data) {
+				var results = JSON.parse(data);
+				pages = results;
+				},
+			error: function (XMLHttpRequest, textStatus, errorThrown) {
+				pageContent = "Error!";
+				 }
+		});
+
+		pages.rows.sort(function(a,b){	
+			var dateA = Date.parse(a.value.edited_on);
+			var dateB = Date.parse(b.value.edited_on);
+			if (dateA < dateB) {
+				return 1;
+			} else {
+				return -1;
+			}
+		});
+
+
+		var html = "<ul class='page-list'>";
+		var recentPages = 5;
+		if(pages.rows.lenght < recentPages)
+		{
+			recentPages = pages.rows.length;
+		}
+		for(var x = 0; x < recentPages; x++)
+				{
+					var p = pages.rows[x];
+					html += "<li><a href='#" + p.id + "' onClick='Javascript: wiki.open(\"" + p.id + "\")'>" + p.id + "</a> edited on " + p.value.edited_on + " by " + p.value.edited_by + "</li>";
+					}
+		html += "</ul>";
+		return html;
+	}
+
+
+
+function topicList(title) {
+var pages;
+
+	$.ajax({
+		type:	'get',
+		url:	'_view/titles',
+		async:   false,
+		success:	function(data) {
+			var results = JSON.parse(data);
+			pages = results;
+			},
+
+		error: function (XMLHttpRequest, textStatus, errorThrown) {
+			pageContent = "INCLUDE ERROR: <a href='Javascript: wiki.open(\"" + id + "\")'>" + id + "</a> does not exist yet...";
+			 }
+	});
+
+	var html = "<ul class='page-list'>";
+	for(var x = 0; x < pages.total_rows; x++)
+			{
+				var p = pages.rows[x];
+				if (p.id.substring(title.length, 0) == title) {
+
+				html += "<li><a href='#" + p.id + "' onClick='Javascript: wiki.open(\"" + p.id + "\")'>" + p.id + "</a> edited on " + p.value.edited_on + " by " + p.value.edited_by + "</li>";
+			}
+			}
+	html += "</ul>";
+	return html;
+}
+
+
+//And finally...when the document is ready...
+
+$(document).ready(function()
+	{
+
+		//To start, we need the settings for the wiki...
+		$.ajax({
+		    type:	'get',
+		    url:	'../../TAPIRWIKISETTINGS',
+		    async:   false,
+		    success:	function(data) {
+			    var result = JSON.parse(data);
+			    settings = result;
+			    //Set the wiki name
+			    $('#wikiName').html(settings.wikiName);
+
+    			    //And get the menu items
+			    for(item in settings.mainMenu)
+			    {
+				    var m = settings.mainMenu[item];
+				    $("<li><a href='#" + m + "' onClick='Javascript: wiki.open(\"" + m + "\")'>" + m + "</a></li>").appendTo("#main-menu");
+			    }
+
+			    //and if replication is enabled show the sync menu item
+			    if(settings.replicationEnabled && location.toString().match(settings.replicationEndPoint) == null) {
+				    $("<li><a href='#' onClick='Javascript: wiki.sync()'>Synchronise</a></li>").appendTo("#main-menu");				
+			    }
+
+			    var requestedPage = location.hash.substring(1);
+			    if (requestedPage == "")
+			    {
+				    //If it's blank, lets get it from settings
+				    requestedPage  = settings.defaultPage;
+			    }
+
+			    //Now let's open the page
+			    wiki.open(requestedPage);
+
+			    //And now, set the user name. This is stored in a cookie, it's in no way an authentication system, just lets people tag their updates...
+			    var userName = $.cookie("userName");
+			    if (userName == null){
+					//If there is no cookie set, get the default from the settings
+					userName = settings.defaultUserName;
+					$.cookie("userName", userName);
+			    }
+			    $('#userName').html(userName);
+    
+	            },
+
+		    error: function(xhr, statusText) {
+                               if(xhr.status == 404) {
+					//  If we get a 404 from the settings, assume this is a first time install. Lets create some objects in the DB for us
+					//and a place to show progress
+					$("#container").html("<h1>Installing TapirWiki</h1><p>Before you use TapirWiki for the first time, a few default pages need to be loaded:</p><ul id='install-log'></ul><div id='install-result'></div>");
+		                        $.ajax({
+			                         type:	'GET',
+			                         url:	'_show/systempages/_design%2Ftapir',
+			                         async:	false,
+			                         success:	function(data) {
+				                                    var pages = JSON.parse(data);
+                                                                    for(p in pages) {
+						                       pop(pages[p]);
+ 					                            }
+                                                                    $("#install-result").html("<h2>Installation complete</h2><p>Congratulations, TapirWiki has been set up correctly. Please refresh this page to access your new wiki or click <a href='./wiki.html'>here</a>.</p>");
+				                                },
+			                         error: function (XMLHttpRequest, textStatus, errorThrown) {
+				                            error("Ooooops!, request failed with status: " + XMLHttpRequest.status + ' ' + XMLHttpRequest.responseText); }
+                         		});		
+                               }
+		    }
+		});
+	});
+
+function pop(obj) {
+	$.ajax({
+		type:	'put',
+		url:	'../../'+obj._id,
+		data:	JSON.stringify(obj),
+		success: function(data) {
+			$("#install-log").append("<li>" + obj._id + " loaded...</li>"); },
+		error: function(data) {
+			$("#install-log").append("<li style='color:#f00'>" + obj._id + " failed. Please delete this database and try again. If the problem persists, please log an issue <a href='http://code.google.com/p/tapirwiki/'>here</a>.</li>");
+			}
+	});
+	
+}
+
+
+

+ 614 - 0
_attachments/scripts/tapirwiki.js~

@@ -0,0 +1,614 @@
+/* Tapir Wiki JS
+  A wiki system which is hosted by CouchDB...
+  Original work: Joshua Knight
+*/
+
+// JV: make the functions needed for rendering
+textileparser=makeTextileParser();
+tapirparseandreplace=maketapirwikiparseandreplace();
+htmlrenderer=makehtmlrenderer();
+
+function convert(text){ 
+    return htmlrenderer(tapirparseandreplace(textileparser(text)));
+}
+
+String.prototype.toCamelCase = function() {
+	  return this.toString()
+	    .replace(/([A-Z]+)/g, function(m,l){
+	      return l.substr(0,1).toUpperCase() + l.toLowerCase().substr(1,l.length);
+	    })
+	    .replace(/[\-_\s](.)/g, function(m, l){
+	      return l.toUpperCase();
+	    });
+	};
+
+//Set some settings...
+var settings;
+
+wiki = new Object();
+
+//wiki page properties
+wiki._id = "";
+wiki._rev = "";
+wiki.body = "";
+wiki.edited_by = "";
+wiki.edited_on = "";
+wiki.type = "page";
+
+wiki.save = function() {
+
+	if(wiki._rev == null)
+	{
+		wiki._id = $("#title").val();
+	}
+	
+	if(wiki._id == "")
+	{
+		error("Please enter a page title!");
+	}
+	else {
+	wiki.body = $("#body").val();
+	wiki.edited_by = $.cookie("userName");
+	wiki.edited_on = Date();
+
+	$.ajax({
+		type:	'put',
+		url:	'../../' + this._id,
+		data:	JSON.stringify(this),
+		async:	false,
+		success:	function(data) {
+			var response = JSON.parse(data);
+			wiki._rev = response.rev;
+			wiki.open(wiki._id);
+			$.jGrowl("Your page has been saved...", {header: "Cool!"});
+		},
+		error: function (XMLHttpRequest, textStatus, errorThrown) {
+			error("Ooooops!, request failed with status: " + XMLHttpRequest.status + ' ' + XMLHttpRequest.responseText); }
+	});
+	}
+	
+};
+
+wiki.display = function() {
+	var n = $("<div id='page-body'>" + convert(this.body) + "</div>").hide();
+	$("#inner-content").html(n);
+	n.fadeIn("fast");
+	$("#pageTitle").html(this._id);
+	$("#page-menu").html("");
+	$("<li class='pageSpecific'><a href='Javascript: wiki.edit()'>Edit</a></li>").appendTo("#page-menu");
+	$("<li class='pageSpecific'><a href='Javascript: wiki.history()'>History</a></li>").appendTo("#page-menu");
+	$("<li class='pageSpecific'><a href='Javascript: wiki.attachments()'>Attachments</a></li>").appendTo("#page-menu");
+	$("<li class='pageSpecific'><a href='Javascript: wiki.remove()'>Delete</a></li>").appendTo("#page-menu");
+	window.location = "wiki.html#" + this._id;
+};
+
+wiki.init = function() {
+	wiki._id = "";
+	delete wiki._rev;
+	delete wiki._revisions;
+	delete wiki._attachments;
+	wiki.body = "";
+	wiki.edited_on = "";
+	wiki.edited_by = "";
+};
+
+wiki.remove = function() {
+		if (confirm("Really delete this page?")) {
+		$.ajax({
+		type:	'delete',
+		url:	'../../' + wiki._id + '?rev=' + wiki._rev,
+		success:	function(){
+			$.jGrowl("Page has been deleted...", {header: "Cool!"});
+			$("#page-body").fadeOut("slow", wiki.open('FrontPage'));
+			},
+		error: function (XMLHttpRequest, textStatus, errorThrown) {
+			error("Ooooops!, request failed with status: " + XMLHttpRequest.status + ' ' + XMLHttpRequest.responseText); }
+		});
+	}
+};
+
+
+wiki.open = function(id) {
+	wiki.init();
+	$.ajax({
+		type:	'get',
+		url:	'../../' + id + "?revs=true",
+		success:	function(data) {
+			var page = JSON.parse(data);
+			wiki._id = page._id;
+			wiki._rev = page._rev;
+			wiki.body = page.body;
+			wiki._revisions = page._revisions;
+			wiki.edited_on = page.edited_on;
+			wiki.edited_by = page.edited_by;
+			wiki._attachments = page._attachments;
+			wiki.display();
+			},
+
+		error: function (XMLHttpRequest, textStatus, errorThrown) {
+			wiki._id = id;
+			wiki.edit();
+			 }
+	});
+};
+
+wiki.edit = function() {
+
+		$("#inner-content").html("");
+		$("<h2>" + this._id + "</h2>");
+
+		var form = $("<form id='addNote'></form>").hide().appendTo("#inner-content").fadeIn("slow");
+
+		if(wiki._rev)
+		{
+			//if there is a revision, it is an existing page and the title should be displayed read only
+			$("<p id='title'>" + this._id + "</p>").appendTo(form);
+		}
+
+		else
+		{
+			//if no revision, it's a new page and we should let the user enter a page name
+
+			$("<p><input id='title' type='text' value='" + this._id + "'/></p>").appendTo(form);
+			$("#title").focus();
+
+			$("#title").blur(function() {
+				var newTitle = $("#title").val().toCamelCase();
+				$("#title").val(newTitle); 
+			});
+
+			var pages;
+			$.ajax({
+			type:	'get',
+			url:	'../../_design/allPages/_view/titles',
+			async:   false,
+			success:	function(data) {
+				var results = JSON.parse(data);
+				pages = results;
+				},
+
+			error: function (XMLHttpRequest, textStatus, errorThrown) {
+				pageContent = "INCLUDE ERROR!" + id + " does not exist yet...";
+				 }
+			});
+			//create the templates region.
+			var templatesDiv = $("<div id='templates'>Page templates:<a id='shower'>Show</a></div>").appendTo(form);
+			var templatesList = $("<ul id='templatesList'></ul>").hide().appendTo(templatesDiv);
+			$("#shower").click(function() {
+
+				if($("#shower").html() == "Show")
+				{
+					$("#shower").html("Hide");
+					$(templatesList).slideDown("slow");
+				}
+				else
+				{
+					$("#shower").html("Show");
+					$(templatesList).slideUp("slow");
+				}
+				});
+
+			var anyTemplates = false;
+
+			for(var x = 0; x < pages.total_rows; x++)
+					{
+						var template = pages.rows[x];
+						if(template.id.substring(12,0) == "PageTemplate")
+							 {
+								$("<li><a onClick='Javascript: wiki.applyTemplate(\"" + template.id + "\")'>" + template.id.replace("PageTemplate","") + "</a></li>").appendTo(templatesList);
+								anyTemplates = true;
+							 }
+					}
+			if(anyTemplates === false)
+			{
+				//$("<p>No templates found. Create a page with a name starting TEMPLATE_ to get it listed here (e.g TEMPLATE_MyTemplate)</p>").appendTo(templatesList);
+				$(templatesDiv).hide();
+			}
+
+		}
+		$("<p><textarea id='body'>" + this.body + "</textarea></p>").appendTo(form);
+
+		$("#page-menu").html("");
+		$("<li><a href='Javascript: wiki.save();'>Save</a></li>").appendTo("#page-menu").fadeIn("slow");
+		$("#pageTitle").html("New page");
+};
+
+wiki.applyTemplate = function(id) {
+	$.ajax({
+		type:	'get',
+		url:	'../../' + id,
+		success:	function(data) {
+			var template = JSON.parse(data);
+			$("#body").val(template.body);
+			$("#shower").html("Show");
+			$("#templatesList").slideUp("slow");
+			},
+
+		error: function (XMLHttpRequest, textStatus, errorThrown) {
+			error("Error!");
+			 }
+	});
+}
+
+
+wiki.history = function() {
+//This function creates a history list for the current page, showing previous revisions (which occur through editing the page) and conflicts (which occur through replication)
+
+	//create a holder for the history list
+	$('#page-body').html('<h2>Page history</h2><p>This page shows all stored historical versions of this page.</p><ul id="history"></ul>').hide().fadeIn("slow");
+	//get the current rev number
+	var rev = this._revisions.start;
+
+	//create an array to hold the merged list of revisions and conflicts
+	var oldPages = new Array();
+	
+	//iterate through the revisions for the current page
+	for(var x in this._revisions.ids)
+	{
+		$.ajax({
+			type:	'GET',
+			url:	'../../' + this._id + '?rev=' + rev + '-' + this._revisions.ids[x],
+			async:	false,
+			success:	function(data) {
+				var page = JSON.parse(data);
+				oldPages.push(page);
+			},
+			error: function (XMLHttpRequest, textStatus, errorThrown) {
+				error("Ooooops!, request failed with status: " + XMLHttpRequest.status + ' ' + XMLHttpRequest.responseText); }
+		});
+		rev--;
+	}
+
+	//get conflicts for the current page, tag any pages as a conflict so we can identify them as such later
+	$.ajax({ 
+		type:	'GET',
+		url:	'../../' + this._id + '?conflicts=true',
+		async:	false,
+		success: function(data) {
+			var conflicts = JSON.parse(data);
+			for(rev in conflicts._conflicts) {
+				$.ajax({
+					type:	'GET',
+					url:	'../../' + wiki._id + '?rev=' + conflicts._conflicts[rev],
+					async:	false,
+					success: function(data){
+						page = JSON.parse(data);
+						page.conflict = true;
+						oldPages.push(page);
+					},
+					error: function (XMLHttpRequest, textStatus, errorThrown) {
+						error("Ooooops!, request failed with status: " + XMLHttpRequest.status + ' ' + XMLHttpRequest.responseText); 
+					}				
+				});
+			}
+	},
+	error: function (XMLHttpRequest, textStatus, errorThrown) {
+		error("Ooooops!, request failed with status: " + XMLHttpRequest.status + ' ' + XMLHttpRequest.responseText); 
+	}
+	});
+
+	//now we sort by date so that we have a nice chronological list to show the user
+	oldPages.sort(function(a,b){	
+		var dateA = Date.parse(a.edited_on);
+		var dateB = Date.parse(b.edited_on);
+		if (dateA < dateB) {
+			return 1;
+		} else {
+			return -1;
+		}
+	});
+
+	//remember previous versions in an array so we can view a historic version
+	wiki.previousVersions = new Array();
+	
+	for(var page in oldPages)
+	{	
+		var event = "Edited ";
+		if(oldPages[page].conflict) 
+		{
+			event = "Conflict ";
+		}
+
+		wiki.previousVersions[oldPages[page]._rev] = oldPages[page];
+		
+		$('<li>' + event + ' on ' + oldPages[page].edited_on + ' by ' + oldPages[page].edited_by + '<a id="' + oldPages[page]._rev + '"> View</a></li>').appendTo('#history');
+		$('#' + oldPages[page]._rev).click(function(){
+			wiki.body = wiki.previousVersions[this.id].body;
+			wiki.display();
+	});
+	}
+}
+
+wiki.sync = function() {
+	localDBArr = location.toString().split("/",4);
+	localDB = localDBArr[3];
+	var repA = {"source": settings.replicationEndPoint,"target":localDB};
+	var repB = {"source": localDB,"target":settings.replicationEndPoint};
+
+	//replicate remote to local
+	$.ajax({
+		type: 'POST',
+		url:	'/_replicate',
+		data:	JSON.stringify(repA),
+		success:	function(data) {
+			$('#page-body').html("Synchronisation complete!");
+		},
+		error: function (XMLHttpRequest, textStatus, errorThrown) {
+			error("Ooooops!, request failed with status: " + XMLHttpRequest.status + ' ' + XMLHttpRequest.responseText); 
+		}
+	});
+	//and local to remote
+	$.ajax({
+		type: 'POST',
+		url:	'/_replicate',
+		data:	JSON.stringify(repB),
+		success:	function(data) {
+			$('#page-body').html("Synchronisation complete!");
+		},
+		error: function (XMLHttpRequest, textStatus, errorThrown) {
+			error("Ooooops!, request failed with status: " + XMLHttpRequest.status + ' ' + XMLHttpRequest.responseText);
+		}
+	});	
+}
+
+wiki.load = function() {
+	//I'm pretty sure this is horrible...need to wait for the location to be updated before opening the page
+	setTimeout(function() {
+		var requestedPage = location.hash.substring(1);
+		wiki.open(requestedPage);
+	}, 200);
+}
+
+
+wiki.attachments = function() {
+		$('#page-body').html('<h2>Attachments</h2><ul id="attachment-list"></ul><div id="upload-form-holder"></div>').hide().fadeIn("slow");
+	
+		$("<h3>New attachment</h3><form id='attachment-form' method='post' action='' content-type='multipart/form-data'><input id='_attachments' type='file' name='_attachments'/><input type='hidden' name='_rev' value='" + wiki._rev + "'/></form><button id='upload-button'>Upload File</button>").appendTo('#page-body');
+		$("#attachment-form").ajaxForm(function() { 
+            error("Thank you for your comment!"); 
+        });
+        
+		var options = { 
+		        target:    '',    
+		        url:       "../../" + wiki._id, 
+		        type:      "post",
+		        async:	false,
+				success:   function(data) {
+					wiki.open(wiki._id);
+					}
+		        }; 
+        
+		$("#upload-button").click(function() { 
+	        $("#attachment-form").ajaxSubmit(options);
+	    	});
+
+		for(f in wiki._attachments) {
+			$("<li><a href='../../" + wiki._id + "/" +  f + "'>" + f + "</a></li>").appendTo("#attachment-list");
+		}
+}
+
+//Finally, some miscellaneous useful functions
+function identify() {
+	var name=prompt("Please enter your name", $.cookie("userName"));
+	if (name!=null && name!="")
+		{
+			$.cookie("userName", name, { expires: 7 });
+			$("#userName").html(name);
+		}
+}
+
+function error(msg) {
+	$.jGrowl(msg, {header: "Uh Oh!"});
+}
+
+var pageContent = "";
+
+function includePage(id) {
+	$.ajax({
+		type:	'get',
+		url:	'../../' + id,
+		async:   false,
+		success:	function(data) {
+			var page = JSON.parse(data);
+			pageContent = convert(page.body);
+			},
+
+		error: function (XMLHttpRequest, textStatus, errorThrown) {
+			pageContent = "INCLUDE ERROR: <a href='Javascript: wiki.open(\"" + id + "\")'>" + id + "</a> does not exist yet...";
+			 }
+	});
+	return pageContent;
+}
+
+function index() {
+var pages;
+
+	$.ajax({
+		type:	'get',
+		url:	'../_view/titles',
+		async:   false,
+		success:	function(data) {
+			var results = JSON.parse(data);
+			pages = results;
+			},
+		error: function (XMLHttpRequest, textStatus, errorThrown) {
+			pageContent = "Error!";
+			 }
+	});
+
+	var html = "<ul class='page-list'>";
+
+	for(var x = 0; x < pages.total_rows; x++)
+			{
+				var p = pages.rows[x];
+				html += "<li><a href='#" + p.id + "' onClick='Javascript: wiki.open(\"" + p.id + "\")'>" + p.id + "</a> edited on " + p.value.edited_on + " by " + p.value.edited_by + "</li>";
+			}
+	html += "</ul>";
+	return html;
+}
+
+
+function recentChanges() {
+	var pages;
+	
+		$.ajax({
+			type:	'get',
+			url:	'../_view/titles',
+			async:   false,
+			success:	function(data) {
+				var results = JSON.parse(data);
+				pages = results;
+				},
+			error: function (XMLHttpRequest, textStatus, errorThrown) {
+				pageContent = "Error!";
+				 }
+		});
+
+		pages.rows.sort(function(a,b){	
+			var dateA = Date.parse(a.value.edited_on);
+			var dateB = Date.parse(b.value.edited_on);
+			if (dateA < dateB) {
+				return 1;
+			} else {
+				return -1;
+			}
+		});
+
+
+		var html = "<ul class='page-list'>";
+		var recentPages = 5;
+		if(pages.rows.lenght < recentPages)
+		{
+			recentPages = pages.rows.length;
+		}
+		for(var x = 0; x < recentPages; x++)
+				{
+					var p = pages.rows[x];
+					html += "<li><a href='#" + p.id + "' onClick='Javascript: wiki.open(\"" + p.id + "\")'>" + p.id + "</a> edited on " + p.value.edited_on + " by " + p.value.edited_by + "</li>";
+					}
+		html += "</ul>";
+		return html;
+	}
+
+
+
+function topicList(title) {
+var pages;
+
+	$.ajax({
+		type:	'get',
+		url:	'../_view/titles',
+		async:   false,
+		success:	function(data) {
+			var results = JSON.parse(data);
+			pages = results;
+			},
+
+		error: function (XMLHttpRequest, textStatus, errorThrown) {
+			pageContent = "INCLUDE ERROR: <a href='Javascript: wiki.open(\"" + id + "\")'>" + id + "</a> does not exist yet...";
+			 }
+	});
+
+	var html = "<ul class='page-list'>";
+	for(var x = 0; x < pages.total_rows; x++)
+			{
+				var p = pages.rows[x];
+				if (p.id.substring(title.length, 0) == title) {
+
+				html += "<li><a href='#" + p.id + "' onClick='Javascript: wiki.open(\"" + p.id + "\")'>" + p.id + "</a> edited on " + p.value.edited_on + " by " + p.value.edited_by + "</li>";
+			}
+			}
+	html += "</ul>";
+	return html;
+}
+
+
+//And finally...when the document is ready...
+
+$(document).ready(function()
+	{
+
+		//To start, we need the settings for the wiki...
+		$.ajax({
+		    type:	'get',
+		    url:	'../TAPIRWIKISETTINGS',
+		    async:   false,
+		    success:	function(data) {
+			    var result = JSON.parse(data);
+			    settings = result;
+			    //Set the wiki name
+			    $('#wikiName').html(settings.wikiName);
+
+    			    //And get the menu items
+			    for(item in settings.mainMenu)
+			    {
+				    var m = settings.mainMenu[item];
+				    $("<li><a href='#" + m + "' onClick='Javascript: wiki.open(\"" + m + "\")'>" + m + "</a></li>").appendTo("#main-menu");
+			    }
+
+			    //and if replication is enabled show the sync menu item
+			    if(settings.replicationEnabled && location.toString().match(settings.replicationEndPoint) == null) {
+				    $("<li><a href='#' onClick='Javascript: wiki.sync()'>Synchronise</a></li>").appendTo("#main-menu");				
+			    }
+
+			    var requestedPage = location.hash.substring(1);
+			    if (requestedPage == "")
+			    {
+				    //If it's blank, lets get it from settings
+				    requestedPage  = settings.defaultPage;
+			    }
+
+			    //Now let's open the page
+			    wiki.open(requestedPage);
+
+			    //And now, set the user name. This is stored in a cookie, it's in no way an authentication system, just lets people tag their updates...
+			    var userName = $.cookie("userName");
+			    if (userName == null){
+					//If there is no cookie set, get the default from the settings
+					userName = settings.defaultUserName;
+					$.cookie("userName", userName);
+			    }
+			    $('#userName').html(userName);
+    
+	            },
+
+		    error: function(xhr, statusText) {
+                               if(xhr.status == 404) {
+					//  If we get a 404 from the settings, assume this is a first time install. Lets create some objects in the DB for us
+					//and a place to show progress
+					$("#container").html("<h1>Installing TapirWiki</h1><p>Before you use TapirWiki for the first time, a few default pages need to be loaded:</p><ul id='install-log'></ul><div id='install-result'></div>");
+		                        $.ajax({
+			                         type:	'GET',
+			                         url:	'../../' + this._id + '?rev=' + rev + '-' + this._revisions.ids[x],
+			                         async:	false,
+			                         success:	function(data) {
+				                                    var pages = JSON.parse(data);
+                                                                    for(p in pages) {
+						                       pop(pages[p]);
+ 					                            }
+                                                                    $("#install-result").html("<h2>Installation complete</h2><p>Congratulations, TapirWiki has been set up correctly. Please refresh this page to access your new wiki or click <a href='./wiki.html'>here</a>.</p>");
+				                                },
+			                         error: function (XMLHttpRequest, textStatus, errorThrown) {
+				                            error("Ooooops!, request failed with status: " + XMLHttpRequest.status + ' ' + XMLHttpRequest.responseText); }
+                         		});		
+                               }
+		    }
+		});
+	});
+
+function pop(obj) {
+	$.ajax({
+		type:	'put',
+		url:	'../' + obj._id,
+		data:	JSON.stringify(obj),
+		success: function(data) {
+			$("#install-log").append("<li>" + obj._id + " loaded...</li>"); },
+		error: function(data) {
+			$("#install-log").append("<li style='color:#f00'>" + obj._id + " failed. Please delete this database and try again. If the problem persists, please log an issue <a href='http://code.google.com/p/tapirwiki/'>here</a>.</li>");
+			}
+	});
+	
+}
+
+
+

+ 61 - 0
_attachments/style/tapirwiki.css

@@ -0,0 +1,61 @@
+#title{border-bottom:solid 1px #ddd;border-left:none;border-right:none;border-top:none;color:#444;font-size:large;width:100%}
+#content a{color:#00e;cursor:pointer;font-weight:400;text-decoration:none}
+#content a:hover{color:#a00}
+#pageTitle{font-weight:400}
+.page-list a{display:inline-block;min-width:200px}
+.page-list{padding-left: 0}
+.page-list li{color:#777;list-style-type:none;padding-bottom:.25em}
+#navigation{border-top:solid 1px #B3B3B3;clear:both;font-weight:700;margin-left:.5em; background-color: #DBE3EA}
+#navigation ul{margin-bottom:0;margin-top:0;padding-bottom:.5em;padding-left:.5em;padding-top:.5em}
+#navigation a{color:#000;text-decoration:none; opacity: 0.7}
+#navigation a:hover{opacity: 1.0}
+#navigation li{display:inline;list-style:none;padding-left:0;padding-right:1em}
+#page-menu a{-moz-border-radius:5px;-webkit-border-radius:5px;background-color:#eee;padding:.25em;-moz-box-shadow: 0 1px 3px rgba(0,0,0,0.3); -webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.3)}
+#page-menu a:visited{color:#00e}
+#page-menu a:hover{background-color:#ddf}
+#page-menu,.action{float:right}
+#main-menu,#header h1{float:left}
+#main-menu a{text-shadow: white 0px 2px 2px}
+#templates a{margin-left:1em}
+html,body{background:#fff;color:#000;font-family:arial, sans-serif;font-size:small;margin:0;padding:0}
+#header{clear:both;margin-bottom:0;padding-bottom:0;padding-left:.5em; background-color: #f1f1f1}
+#header p{float:right;font-size:smaller;padding-right:1em}
+#wrapper{clear:both}
+div#content{border-top:solid 1px #B3B3B3;clear:both;padding:1em}
+div#content p{line-height:1.4}
+div#footer{border-top:solid 1px #bcb;clear:left;color:#000}
+div#footer p{color:#aaa;font-size:xx-small;margin:0;padding:.5em}
+div#footer a{color:#255;text-decoration:none}
+div#navigation{margin:0;padding:0}
+div#extra{clear:left;width:100%}
+label{float:left;font-weight:700;margin-left:0;margin-right:1em;padding-left:0;text-align:left}
+input{font-family:arial, sans-serif;font-size:small;margin:0;}
+textarea{border:solid 1px #ddd;font-family:arial, sans-serif;font-size:small;height:300px;margin:0;width:100%}
+#page-body{margin-left:.5em;padding-left:.5em}
+#userName{color:#00e;cursor:pointer;text-decoration:underline}
+table, th, td{border: solid 1px #ddd; border-collapse:collapse;}
+th, td{padding: 5px}
+
+#upload-button{margin-top: 0.5em}
+.error {background-color: #00ff00}
+div.jGrowl{padding:10px; z-index:9999; color:#fff; font-size:12px}
+div.ie6{position:absolute}
+div.ie6.top-right{right:auto; bottom:auto; left:expression( ( 0 - jGrowl.offsetWidth+( document.documentElement.clientWidth ? document.documentElement.clientWidth:document.body.clientWidth )+( ignoreMe2 = document.documentElement.scrollLeft ? document.documentElement.scrollLeft:document.body.scrollLeft ) )+'px' );  top:expression( ( 0+( ignoreMe = document.documentElement.scrollTop ? document.documentElement.scrollTop:document.body.scrollTop ) )+'px' )}
+div.ie6.top-left{left:expression( ( 0+( ignoreMe2 = document.documentElement.scrollLeft ? document.documentElement.scrollLeft:document.body.scrollLeft ) )+'px' ); top:expression( ( 0+( ignoreMe = document.documentElement.scrollTop ? document.documentElement.scrollTop:document.body.scrollTop ) )+'px' )}
+div.ie6.bottom-right{left:expression( ( 0 - jGrowl.offsetWidth+( document.documentElement.clientWidth ? document.documentElement.clientWidth:document.body.clientWidth )+( ignoreMe2 = document.documentElement.scrollLeft ? document.documentElement.scrollLeft:document.body.scrollLeft ) )+'px' ); top:expression( ( 0 - jGrowl.offsetHeight+( document.documentElement.clientHeight ? document.documentElement.clientHeight:document.body.clientHeight )+( ignoreMe = document.documentElement.scrollTop ? document.documentElement.scrollTop:document.body.scrollTop ) )+'px' )}
+div.ie6.bottom-left{left:expression( ( 0+( ignoreMe2 = document.documentElement.scrollLeft ? document.documentElement.scrollLeft:document.body.scrollLeft ) )+'px' ); top:expression( ( 0 - jGrowl.offsetHeight+( document.documentElement.clientHeight ? document.documentElement.clientHeight:document.body.clientHeight )+( ignoreMe = document.documentElement.scrollTop ? document.documentElement.scrollTop:document.body.scrollTop ) )+'px' )}
+div.ie6.center{left:expression( ( 0+( ignoreMe2 = document.documentElement.scrollLeft ? document.documentElement.scrollLeft:document.body.scrollLeft ) )+'px' ); top:expression( ( 0+( ignoreMe = document.documentElement.scrollTop ? document.documentElement.scrollTop:document.body.scrollTop ) )+'px' ); width:100%}
+body >div.jGrowl{position:fixed}
+body >div.jGrowl.top-left{left:0px; top:0px}
+body >div.jGrowl.top-right{right:0px; top:0px}
+body >div.jGrowl.bottom-left{left:0px; bottom:0px}
+body >div.jGrowl.bottom-right{right:0px; bottom:0px}
+body >div.jGrowl.center{top:0px; width:50%; left:25%}
+div.center div.jGrowl-notification, div.center div.jGrowl-closer{margin-left:auto; margin-right:auto}
+div.jGrowl div.jGrowl-notification, div.jGrowl div.jGrowl-closer{background-color:#000; opacity:.85;  -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=85)";  filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=85); zoom:1; width:235px; padding:10px; margin-top:5px; margin-bottom:5px; font-family:Tahoma,Arial,Helvetica,sans-serif; font-size:1em; text-align:left; display:none; -moz-border-radius:5px; -webkit-border-radius:5px}
+div.jGrowl div.jGrowl-notification{min-height:40px}
+div.jGrowl div.jGrowl-notification div.header{font-weight:bold; font-size:.85em}
+div.jGrowl div.jGrowl-notification div.close{z-index:99; float:right; font-weight:bold; font-size:1em; cursor:pointer}
+div.jGrowl div.jGrowl-closer{padding-top:4px; padding-bottom:4px; cursor:pointer; font-size:.9em; font-weight:bold; text-align:center}
+@media print{div.jGrowl{display:none}}
+

+ 34 - 0
_attachments/wiki.html

@@ -0,0 +1,34 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+<link rel="stylesheet" href="style/tapirwiki.css" type="text/css">
+<script src="scripts/jQuery.js"></script>
+<script src="scripts/json2.js"></script>
+<script src="scripts/parser.js"></script>
+<script src="scripts/renderer.js"></script>
+<script src="scripts/macros.js"></script>
+<script src="scripts/tapirwiki.js"></script>
+<title>TapirWiki</title>
+</head>
+<body>
+	<div id="container">
+		<div id="wrapper">
+			<div id="header">
+				<h1 id="header-title"><span id='wikiName'></span>::<span
+					id='pageTitle'></span></h1>
+				<p>You are <span id='userName' onClick="javascript: identify();"></span></p>
+				<div style="clear:both"></div>
+			</div>
+			<div id="navigation">
+				<ul id="main-menu"></ul>
+				<ul id="page-menu"></ul>
+				<div style="clear:both"></div>
+			</div>
+			<div id="content">
+				<div id="inner-content"><p>Loading...</p></div>
+			</div>
+		</div>
+		<div id="footer"><p>Powered by <a href="http://code.google.com/p/tapirwiki/">TapirWiki</a> v0.3.2. Find a tapir and love it!</p></div>
+	</div>
+</body>
+</html>

+ 1 - 0
_id

@@ -0,0 +1 @@
+_design/tapir

+ 4 - 0
couchapp.json

@@ -0,0 +1,4 @@
+{
+    "name": "TapirWiki",
+    "description": "A Wiki on the Couch!"
+}

+ 9 - 0
shows/systempages.js

@@ -0,0 +1,9 @@
+function(doc, req){
+    var pages=[];
+    for (page in doc.systempages){
+        pages.push(doc.systempages[page]);
+    }
+    return {
+        json : pages,
+    }
+}

+ 1 - 0
systempages/FrontPage.json

@@ -0,0 +1 @@
+{"body": "h2. Welcome to your new wiki! \n\nWell done, it looks like your new wiki is up and running. This is the first page which users will see when coming to the wiki, unless they follow a link to a specific page. The wiki is a quick and simple way of sharing information between people.\nh2. Features\n\nWith your new wiki you can:\n* Easily add pages to store notes or any information\n* Anyone can edit pages so the information is easier to maintain\n* use the simple formatting to decorate your text whilst focusing on the content\n* Make use of !CouchDB features such as replication to allow users to take the wiki offline\n* Use macros to list and include page content\n* Create page templates to provide a quick starting point for new pages (any page with a name starting with !PageTemplate will become a template!) \n* You can view previous versions of a page using the history button (up at the top, on the right).\n* Upload files / attachments for a page. For example, you can upload a picture for your page.\n\nh2. Recent changes\n\nHere's what's new for this wiki.\n{recentChanges}\n\n{include:HelpOnGettingStarted}\n\nh2. Need more help?\n\nYou can get more help here {goto:Help}", "edited_by": "System", "edited_on": "Mon Dec 14 2009 19:48:41 GMT+0000 (GMT)", "_id": "FrontPage", "type": "page"}

+ 1 - 0
systempages/Help.json

@@ -0,0 +1 @@
+{"body": "h2. Help! Help! Help!\n\nThe following pages provide some useful information for using the wiki. \n\n{topic:HelpOn}\n\nYou can add to these pages by creating a new page called HelpOnBlah!", "edited_by": "Nobody", "edited_on": "Sun Nov 15 2009 15:56:45 GMT+0000 (BST)", "_id": "Help", "type": "page"}

+ 1 - 0
systempages/HelpOnAttachments.json

@@ -0,0 +1 @@
+{"body": "h2. Attachments\n\nYou can upload attachments (files) to any page. This lets you store images and other files alongside your wiki pages. If you are using the replication feature, the attachments will also be replicated with your wiki, so any users will automagically get any new attachments and have files available offline (assuming that's where there wiki is!)\nh3. Uploading an attachment\n\n* Click the \"Attachments\" button (top right) when viewing a page\n* Here you will see a list of existing attachments\n* Click \"Browse File\" and choose a file to attach\n* Click \"Upload File\" to attach.\n\nWhen you next click the attachments button, you will see a list of previously uploaded attachments.\n\nHave a look at HelpOnFormatting to see how to include an image in your page.\n", "edited_by": "System", "edited_on": "Mon Dec 14 2009 19:52:29 GMT+0000 (GMT)", "_id": "HelpOnAttachments", "type": "page"}

Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
systempages/HelpOnFormatting.json


+ 1 - 0
systempages/HelpOnGettingStarted.json

@@ -0,0 +1 @@
+{"body": "h2. Getting started\n\nh3.  Edit this page\n\nYou probably want to edit FrontPage so you have your own content here. You can do that using the button in the top right. You can put whatever you want on this page. As you edit pages, !TapirWiki will record your changes. You can use the page history to view previous versions of each page.\n\nh3. New page\n\nYou can create a new page by following a link to a PageWhichDoesNotExist. For ease of use, a link to a page called NewPage is in the main navigation at the top of each page. \n\nh3. Help\n\nThere are a few other useful bits of help, and you can add your own as you go.\n[topic:HelpOn]\nh3. Advanced settings\n\nAdvanced settings for the wiki are stored in a document in Couch - the doc id is \"TAPIRWIKISETTINGS\". This sets the following:\n* The wiki name (e.g. TapirWiki)\n* The default page (e.g. FrontPage)\n* The default username for new / unknown users (e.g. Anon)\n* The pages which are linked to in the main navigation\n\nEdit this document (using Futon) to change these settings. There is no built in admin functionality in TapirWiki (yet!)\n ", "edited_by": "Nobody", "edited_on": "Mon Dec 14 2009 19:50:16 GMT+0000 (GMT)", "_id": "HelpOnGettingStarted", "type": "page"}

+ 1 - 0
systempages/HelpOnMacros.json

@@ -0,0 +1 @@
+{"body": "h2. Macros\n\nMacros are functions you can call in a wiki page. There are a few simple macros in TapirWiki, this page tells you how to use them. Looking at this page will show each macro in use. If you edit the page, you will see how to call them so that you can use them in your own pages.\n\nh3. Using a macro\nTo use a macro, simply include it in the markup for your page. Try editing this page to see how the macros are invoked. The macros are contained in curly braces {macro} and some take an additional argument {macro:argument}\n\nh4. Index\nThe index macro lists all pages in the wiki. The macro name is \"index\" and it takes no arguments. Use it as follows:\n{index}\n\nh4. Topic List\nThe topic list macro finds all pages whose names begin with a given string. The macro name is \"topic\" and the argument is the first part of the page name which must match to be listed. For example, find all help pages in the Wiki:\n{topic:HelpOn}\n\nh4. Recent Changes\nYou can provide a list of the 5 most recent page edits which have occurred using the recent changes macro. It's a nice one to include on your FrontPage so people know what's going on. The macro name is \"recentChanges\" and it takes no arguments. Use it as follows:\n{recentChanges}\n\nh4. Includes\nYou can include the content from any page within the body of any other page. Use this macro to reduce duplication in your wiki. The macro name is \"include\" and the argument is the page name to include. For example, to include the Help page:\n{include:Help}\n\nIf the page you include cannot be found, you will get an error message and a link to the page. Following the link should enable you to edit the page.\n{include:PageWhichDoesNotExistYet}\n", "edited_by": "Nobody", "edited_on": "Tue Nov 17 2009 21:25:51 GMT+0000 (BST)", "_id": "HelpOnMacros", "type": "page"}

Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
systempages/HelpOnReplication.json


+ 1 - 0
systempages/Index.json

@@ -0,0 +1 @@
+{"body": "h2. Index\n\nBelow is a list of all pages in your wiki:\n\n{index}", "edited_by": "Nobody", "edited_on": "Sat Nov 21 2009 04:47:59 GMT+0000 (GMT)", "_id": "Index", "type": "page"}

+ 1 - 0
systempages/TAPIRWIKISETTINGS.json

@@ -0,0 +1 @@
+{"defaultPage": "FrontPage", "edited_by": "Nobody", "wikiName": "TapirWiki", "replicationEnabled": false, "mainMenu": ["FrontPage", "Index", "NewPage", "Help"], "replicationEndPoint": "http://hostname:5984/dbname", "defaultUserName": "Anon.", "_id": "TAPIRWIKISETTINGS"}

+ 3 - 0
vendor/couchapp/README.md

@@ -0,0 +1,3 @@
+## CouchApp - more than just a filesystem mapper
+
+This is where documentation will go for the client and server JavaScript parts of CouchApp.

+ 199 - 0
vendor/couchapp/_attachments/jquery.couchapp.js

@@ -0,0 +1,199 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License.  You may obtain a copy
+// of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+// Usage: The passed in function is called when the page is ready.
+// CouchApp passes in the app object, which takes care of linking to 
+// the proper database, and provides access to the CouchApp helpers.
+// $.CouchApp(function(app) {
+//    app.db.view(...)
+//    ...
+// });
+
+(function($) {
+
+  function f(n) {    // Format integers to have at least two digits.
+      return n < 10 ? '0' + n : n;
+  }
+
+  Date.prototype.toJSON = function() {
+      return this.getUTCFullYear()   + '/' +
+           f(this.getUTCMonth() + 1) + '/' +
+           f(this.getUTCDate())      + ' ' +
+           f(this.getUTCHours())     + ':' +
+           f(this.getUTCMinutes())   + ':' +
+           f(this.getUTCSeconds())   + ' +0000';
+  };
+  
+  function Design(db, name) {
+    this.view = function(view, opts) {
+      db.view(name+'/'+view, opts);
+    };
+  };
+
+  var login;
+  
+  function init(app) {
+    $(function() {
+      var dbname = document.location.href.split('/')[3];
+      var dname = unescape(document.location.href).split('/')[5];
+      var db = $.couch.db(dbname);
+      var design = new Design(db, dname);
+      
+      // docForm applies CouchDB behavior to HTML forms.
+      function docForm(formSelector, opts) {
+        var localFormDoc = {};
+        opts = opts || {};
+        opts.fields = opts.fields || [];
+        
+        // turn the form into deep json
+        // field names like 'author-email' get turned into json like
+        // {"author":{"email":"quentin@example.com"}}
+        // Note: Fields not found in form are ignored.
+        function formToDeepJSON(form, fields, doc) {
+          var form = $(form);
+          fields.forEach(function(field) {
+            var elem = form.find("[name="+field+"]");
+            if (!elem) return;
+            var parts = field.split('-');
+            var frontObj = doc, frontName = parts.shift();
+            while (parts.length > 0) {
+              frontObj[frontName] = frontObj[frontName] || {}
+              frontObj = frontObj[frontName];
+              frontName = parts.shift();
+            }
+            frontObj[frontName] = elem.val();
+          });
+        };
+        
+        // Apply the behavior
+        $(formSelector).submit(function(e) {
+          e.preventDefault();
+          // formToDeepJSON acts on localFormDoc by reference
+          formToDeepJSON(this, opts.fields, localFormDoc);
+          if (opts.beforeSave) opts.beforeSave(localFormDoc);
+          db.saveDoc(localFormDoc, {
+            success : function(resp) {
+              if (opts.success) opts.success(resp, localFormDoc);
+            }
+          })
+          
+          return false;
+        });
+
+        // populate form from an existing doc
+        function docToForm(doc) {
+          var form = $(formSelector);
+          // fills in forms
+          opts.fields.forEach(function(field) {
+            var parts = field.split('-');
+            var frontObj = doc, frontName = parts.shift();
+            while (frontObj && parts.length > 0) {                
+              frontObj = frontObj[frontName];
+              frontName = parts.shift();
+            }
+            if (frontObj && frontObj[frontName])
+              form.find("[name="+field+"]").val(frontObj[frontName]);
+          });            
+        };
+        
+        if (opts.id) {
+          db.openDoc(opts.id, {
+            success: function(doc) {
+              if (opts.onLoad) opts.onLoad(doc);
+              localFormDoc = doc;
+              docToForm(doc);
+          }});
+        } else if (opts.template) {
+          if (opts.onLoad) opts.onLoad(opts.template);
+          localFormDoc = opts.template;
+          docToForm(localFormDoc);
+        }
+        var instance = {
+          deleteDoc : function(opts) {
+            opts = opts || {};
+            if (confirm("Really delete this document?")) {                
+              db.removeDoc(localFormDoc, opts);
+            }
+          },
+          localDoc : function() {
+            formToDeepJSON(formSelector, opts.fields, localFormDoc);
+            return localFormDoc;
+          }
+        }
+        return instance;
+      }
+      
+      function prettyDate(time){
+      	var date = new Date(time),
+      		diff = (((new Date()).getTime() - date.getTime()) / 1000),
+      		day_diff = Math.floor(diff / 86400);
+
+      	return day_diff < 1 && (
+      			diff < 60 && "just now" ||
+      			diff < 120 && "1 minute ago" ||
+      			diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" ||
+      			diff < 7200 && "1 hour ago" ||
+      			diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") ||
+      		day_diff == 1 && "yesterday" ||
+      		day_diff < 21 && day_diff + " days ago" ||
+      		day_diff < 45 && Math.ceil( day_diff / 7 ) + " weeks ago" ||
+      		day_diff < 730 && Math.ceil( day_diff / 31 ) + " months ago" ||
+      		Math.ceil( day_diff / 365 ) + " years ago";
+      };
+      
+      app({
+        showPath : function(funcname, docid) {
+          // I wish this was shared with path.js...
+          return '/'+[dbname, '_design', dname, '_show', funcname, docid].join('/')
+        },
+        listPath : function(funcname, viewname) {
+          return '/'+[dbname, '_design', dname, '_list', funcname, viewname].join('/')
+        },
+        slugifyString : function(string) {
+          return string.replace(/\W/g,'-').
+            replace(/\-*$/,'').replace(/^\-*/,'').
+            replace(/\-{2,}/,'-');
+        },
+        attemptLogin : function(win, fail) {
+          // depends on nasty hack in blog validation function
+          db.saveDoc({"author":"_self"}, { error: function(s, e, r) {
+            var namep = r.split(':');
+            if (namep[0] == '_self') {
+              login = namep.pop();
+              $.cookies.set("login", login, '/'+dbname)
+              win && win(login);
+            } else {
+              $.cookies.set("login", "", '/'+dbname)
+              fail && fail(s, e, r);
+            }
+          }});        
+        },
+        loggedInNow : function(loggedIn, loggedOut) {
+          login = login || $.cookies.get("login");
+          if (login) {
+            loggedIn && loggedIn(login);
+          } else {
+            loggedOut && loggedOut();
+          }
+        },
+        db : db,
+        design : design,
+        view : design.view,
+        docForm : docForm,
+        prettyDate : prettyDate
+      });
+    });
+  };
+
+  $.CouchApp = $.CouchApp || init;
+
+})(jQuery);

+ 1 - 0
vendor/couchapp/couchapp.js

@@ -0,0 +1 @@
+// this stuff should be properly namespaced etc

+ 23 - 0
vendor/couchapp/date.js

@@ -0,0 +1,23 @@
+function f(n) {    // Format integers to have at least two digits.
+    return n < 10 ? '0' + n : n;
+}
+
+Date.prototype.rfc3339 = function() {
+    return this.getUTCFullYear()   + '-' +
+         f(this.getUTCMonth() + 1) + '-' +
+         f(this.getUTCDate())      + 'T' +
+         f(this.getUTCHours())     + ':' +
+         f(this.getUTCMinutes())   + ':' +
+         f(this.getUTCSeconds())   + 'Z';
+};
+
+// This is a format that collates in order and tends to work with
+// JavaScript's new Date(string) date parsing capabilities, unlike rfc3339.
+Date.prototype.toJSON = function() {
+    return this.getUTCFullYear()   + '/' +
+         f(this.getUTCMonth() + 1) + '/' +
+         f(this.getUTCDate())      + ' ' +
+         f(this.getUTCHours())     + ':' +
+         f(this.getUTCMinutes())   + ':' +
+         f(this.getUTCSeconds())   + ' +0000';
+};

+ 4 - 0
vendor/couchapp/metadata.json

@@ -0,0 +1,4 @@
+{
+  "name": "couchapp",
+  "description": "Official couchapp vendor."
+}

+ 77 - 0
vendor/couchapp/path.js

@@ -0,0 +1,77 @@
+// from couch.js
+function encodeOptions(options, noJson) {
+  var buf = []
+  if (typeof(options) == "object" && options !== null) {
+    for (var name in options) {
+      if (!options.hasOwnProperty(name)) continue;
+      var value = options[name];
+      if (!noJson && (name == "key" || name == "startkey" || name == "endkey")) {
+        value = toJSON(value);
+      }
+      buf.push(encodeURIComponent(name) + "=" + encodeURIComponent(value));
+    }
+  }
+  if (!buf.length) {
+    return "";
+  }
+  return "?" + buf.join("&");
+}
+
+function concatArgs(array, args) {
+  for (var i=0; i < args.length; i++) {
+    array.push(args[i]);
+  };
+  return array;
+};
+
+function makePath(array) {
+  var options, path;
+  
+  if (typeof array[array.length - 1] != "string") {
+    // it's a params hash
+    options = array.pop();
+  }
+  path = array.map(function(item) {return encodeURIComponent(item)}).join('/');
+  if (options) {
+    return path + encodeOptions(options);
+  } else {
+    return path;    
+  }
+};
+
+function assetPath() {
+  var p = req.path, parts = ['', p[0], p[1] , p[2]];
+  return makePath(concatArgs(parts, arguments));
+};
+
+function showPath() {
+  var p = req.path, parts = ['', p[0], p[1] , p[2], '_show'];
+  return makePath(concatArgs(parts, arguments));
+};
+
+function listPath() {
+  var p = req.path, parts = ['', p[0], p[1] , p[2], '_list'];
+  return makePath(concatArgs(parts, arguments));
+};
+
+function olderPath(info) {
+  if (!info) return null;
+  var q = req.query;
+  q.startkey = info.prev_key;
+  q.skip=1;
+  return listPath('index','recent-posts',q);
+}
+
+function makeAbsolute(req, path) {
+  return 'http://' + req.headers.Host + path;
+}
+
+
+function currentPath() {
+  path = req.path.map(function(item) {return encodeURIComponent(item)}).join('/');
+  if (req.query) {
+    return path + encodeOptions(req.query, true);
+  } else {
+    return path;
+  }
+}

+ 33 - 0
vendor/couchapp/template.js

@@ -0,0 +1,33 @@
+// Simple JavaScript Templating
+// John Resig - http://ejohn.org/ - MIT Licensed
+var cache = {};
+
+function template(str, data){
+  // Figure out if we're getting a template, or if we need to
+  // load the template - and be sure to cache the result.
+  var fn = cache[str] ||
+
+  // Generate a reusable function that will serve as a template
+  // generator (and which will be cached).
+    new Function("obj",
+      "var p=[],print=function(){p.push.apply(p,arguments);};" +
+          
+            // Introduce the data as local variables using with(){}
+            "with(obj){p.push('" +
+          
+            // Convert the template into pure JavaScript
+            str
+            .replace(/\n/g, "\\n")
+            .replace(/[\r\t]/g, " ")
+            .replace(/'(?=[^%]*%>)/g,"\t")
+            .split("'").join("\\'")
+            .split("\t").join("'")
+            .replace(/<%=(.+?)%>/g, "',$1,'")
+            .split("<%").join("');")
+            .split("%>").join("p.push('")
+            + "');}return p.join('');");
+  cache[str] = fn;
+  
+  // Provide some basic currying to the user
+  return data ? fn( data ) : fn;
+};

+ 12 - 0
views/titles/map.js

@@ -0,0 +1,12 @@
+function(doc) {
+    if(doc.type == 'page'){
+        emit(null, {
+                     'id': doc._id,
+                    'rev': doc._rev,
+                  'title': doc.title,
+                   'body': doc.body,
+              'edited_on': doc.edited_on,
+              'edited_by': doc.edited_by
+                    });
+     }
+}

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor