Mercurial > wikked
changeset 15:238299b93f4c
Made all Javascript code use RequireJS.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Sun, 30 Dec 2012 23:15:32 -0800 |
parents | 0a0a98b97e9c |
children | 8d6c2a5ed08d |
files | wikked/static/js/require.js wikked/static/js/wikked.js wikked/static/js/wikked/app.js wikked/static/js/wikked/client.js wikked/static/js/wikked/handlebars.js wikked/static/js/wikked/models.js wikked/static/js/wikked/util.js wikked/static/js/wikked/views.js wikked/templates/index.html |
diffstat | 9 files changed, 894 insertions(+), 704 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wikked/static/js/require.js Sun Dec 30 23:15:32 2012 -0800 @@ -0,0 +1,35 @@ +/* + RequireJS 2.1.2 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. + Available via the MIT or new BSD license. + see: http://github.com/jrburke/requirejs for details +*/ +var requirejs,require,define; +(function(Y){function H(b){return"[object Function]"===L.call(b)}function I(b){return"[object Array]"===L.call(b)}function x(b,c){if(b){var d;for(d=0;d<b.length&&(!b[d]||!c(b[d],d,b));d+=1);}}function M(b,c){if(b){var d;for(d=b.length-1;-1<d&&(!b[d]||!c(b[d],d,b));d-=1);}}function r(b,c){return da.call(b,c)}function i(b,c){return r(b,c)&&b[c]}function E(b,c){for(var d in b)if(r(b,d)&&c(b[d],d))break}function Q(b,c,d,i){c&&E(c,function(c,h){if(d||!r(b,h))i&&"string"!==typeof c?(b[h]||(b[h]={}),Q(b[h], +c,d,i)):b[h]=c});return b}function t(b,c){return function(){return c.apply(b,arguments)}}function Z(b){if(!b)return b;var c=Y;x(b.split("."),function(b){c=c[b]});return c}function J(b,c,d,i){c=Error(c+"\nhttp://requirejs.org/docs/errors.html#"+b);c.requireType=b;c.requireModules=i;d&&(c.originalError=d);return c}function ea(b){function c(a,g,v){var e,n,b,c,d,j,f,h=g&&g.split("/");e=h;var l=m.map,k=l&&l["*"];if(a&&"."===a.charAt(0))if(g){e=i(m.pkgs,g)?h=[g]:h.slice(0,h.length-1);g=a=e.concat(a.split("/")); +for(e=0;g[e];e+=1)if(n=g[e],"."===n)g.splice(e,1),e-=1;else if(".."===n)if(1===e&&(".."===g[2]||".."===g[0]))break;else 0<e&&(g.splice(e-1,2),e-=2);e=i(m.pkgs,g=a[0]);a=a.join("/");e&&a===g+"/"+e.main&&(a=g)}else 0===a.indexOf("./")&&(a=a.substring(2));if(v&&(h||k)&&l){g=a.split("/");for(e=g.length;0<e;e-=1){b=g.slice(0,e).join("/");if(h)for(n=h.length;0<n;n-=1)if(v=i(l,h.slice(0,n).join("/")))if(v=i(v,b)){c=v;d=e;break}if(c)break;!j&&(k&&i(k,b))&&(j=i(k,b),f=e)}!c&&j&&(c=j,d=f);c&&(g.splice(0,d, +c),a=g.join("/"))}return a}function d(a){z&&x(document.getElementsByTagName("script"),function(g){if(g.getAttribute("data-requiremodule")===a&&g.getAttribute("data-requirecontext")===j.contextName)return g.parentNode.removeChild(g),!0})}function y(a){var g=i(m.paths,a);if(g&&I(g)&&1<g.length)return d(a),g.shift(),j.require.undef(a),j.require([a]),!0}function f(a){var g,b=a?a.indexOf("!"):-1;-1<b&&(g=a.substring(0,b),a=a.substring(b+1,a.length));return[g,a]}function h(a,g,b,e){var n,u,d=null,h=g?g.name: +null,l=a,m=!0,k="";a||(m=!1,a="_@r"+(L+=1));a=f(a);d=a[0];a=a[1];d&&(d=c(d,h,e),u=i(p,d));a&&(d?k=u&&u.normalize?u.normalize(a,function(a){return c(a,h,e)}):c(a,h,e):(k=c(a,h,e),a=f(k),d=a[0],k=a[1],b=!0,n=j.nameToUrl(k)));b=d&&!u&&!b?"_unnormalized"+(M+=1):"";return{prefix:d,name:k,parentMap:g,unnormalized:!!b,url:n,originalName:l,isDefine:m,id:(d?d+"!"+k:k)+b}}function q(a){var g=a.id,b=i(k,g);b||(b=k[g]=new j.Module(a));return b}function s(a,g,b){var e=a.id,n=i(k,e);if(r(p,e)&&(!n||n.defineEmitComplete))"defined"=== +g&&b(p[e]);else q(a).on(g,b)}function C(a,g){var b=a.requireModules,e=!1;if(g)g(a);else if(x(b,function(g){if(g=i(k,g))g.error=a,g.events.error&&(e=!0,g.emit("error",a))}),!e)l.onError(a)}function w(){R.length&&(fa.apply(F,[F.length-1,0].concat(R)),R=[])}function A(a,g,b){var e=a.map.id;a.error?a.emit("error",a.error):(g[e]=!0,x(a.depMaps,function(e,c){var d=e.id,h=i(k,d);h&&(!a.depMatched[c]&&!b[d])&&(i(g,d)?(a.defineDep(c,p[d]),a.check()):A(h,g,b))}),b[e]=!0)}function B(){var a,g,b,e,n=(b=1E3*m.waitSeconds)&& +j.startTime+b<(new Date).getTime(),c=[],h=[],f=!1,l=!0;if(!T){T=!0;E(k,function(b){a=b.map;g=a.id;if(b.enabled&&(a.isDefine||h.push(b),!b.error))if(!b.inited&&n)y(g)?f=e=!0:(c.push(g),d(g));else if(!b.inited&&(b.fetched&&a.isDefine)&&(f=!0,!a.prefix))return l=!1});if(n&&c.length)return b=J("timeout","Load timeout for modules: "+c,null,c),b.contextName=j.contextName,C(b);l&&x(h,function(a){A(a,{},{})});if((!n||e)&&f)if((z||$)&&!U)U=setTimeout(function(){U=0;B()},50);T=!1}}function D(a){r(p,a[0])|| +q(h(a[0],null,!0)).init(a[1],a[2])}function G(a){var a=a.currentTarget||a.srcElement,b=j.onScriptLoad;a.detachEvent&&!V?a.detachEvent("onreadystatechange",b):a.removeEventListener("load",b,!1);b=j.onScriptError;(!a.detachEvent||V)&&a.removeEventListener("error",b,!1);return{node:a,id:a&&a.getAttribute("data-requiremodule")}}function K(){var a;for(w();F.length;){a=F.shift();if(null===a[0])return C(J("mismatch","Mismatched anonymous define() module: "+a[a.length-1]));D(a)}}var T,W,j,N,U,m={waitSeconds:7, +baseUrl:"./",paths:{},pkgs:{},shim:{},map:{},config:{}},k={},X={},F=[],p={},S={},L=1,M=1;N={require:function(a){return a.require?a.require:a.require=j.makeRequire(a.map)},exports:function(a){a.usingExports=!0;if(a.map.isDefine)return a.exports?a.exports:a.exports=p[a.map.id]={}},module:function(a){return a.module?a.module:a.module={id:a.map.id,uri:a.map.url,config:function(){return m.config&&i(m.config,a.map.id)||{}},exports:p[a.map.id]}}};W=function(a){this.events=i(X,a.id)||{};this.map=a;this.shim= +i(m.shim,a.id);this.depExports=[];this.depMaps=[];this.depMatched=[];this.pluginMaps={};this.depCount=0};W.prototype={init:function(a,b,c,e){e=e||{};if(!this.inited){this.factory=b;if(c)this.on("error",c);else this.events.error&&(c=t(this,function(a){this.emit("error",a)}));this.depMaps=a&&a.slice(0);this.errback=c;this.inited=!0;this.ignore=e.ignore;e.enabled||this.enabled?this.enable():this.check()}},defineDep:function(a,b){this.depMatched[a]||(this.depMatched[a]=!0,this.depCount-=1,this.depExports[a]= +b)},fetch:function(){if(!this.fetched){this.fetched=!0;j.startTime=(new Date).getTime();var a=this.map;if(this.shim)j.makeRequire(this.map,{enableBuildCallback:!0})(this.shim.deps||[],t(this,function(){return a.prefix?this.callPlugin():this.load()}));else return a.prefix?this.callPlugin():this.load()}},load:function(){var a=this.map.url;S[a]||(S[a]=!0,j.load(this.map.id,a))},check:function(){if(this.enabled&&!this.enabling){var a,b,c=this.map.id;b=this.depExports;var e=this.exports,n=this.factory; +if(this.inited)if(this.error)this.emit("error",this.error);else{if(!this.defining){this.defining=!0;if(1>this.depCount&&!this.defined){if(H(n)){if(this.events.error)try{e=j.execCb(c,n,b,e)}catch(d){a=d}else e=j.execCb(c,n,b,e);this.map.isDefine&&((b=this.module)&&void 0!==b.exports&&b.exports!==this.exports?e=b.exports:void 0===e&&this.usingExports&&(e=this.exports));if(a)return a.requireMap=this.map,a.requireModules=[this.map.id],a.requireType="define",C(this.error=a)}else e=n;this.exports=e;if(this.map.isDefine&& +!this.ignore&&(p[c]=e,l.onResourceLoad))l.onResourceLoad(j,this.map,this.depMaps);delete k[c];this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}else this.fetch()}},callPlugin:function(){var a=this.map,b=a.id,d=h(a.prefix);this.depMaps.push(d);s(d,"defined",t(this,function(e){var n,d;d=this.map.name;var v=this.map.parentMap?this.map.parentMap.name:null,f=j.makeRequire(a.parentMap,{enableBuildCallback:!0, +skipMap:!0});if(this.map.unnormalized){if(e.normalize&&(d=e.normalize(d,function(a){return c(a,v,!0)})||""),e=h(a.prefix+"!"+d,this.map.parentMap),s(e,"defined",t(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),d=i(k,e.id)){this.depMaps.push(e);if(this.events.error)d.on("error",t(this,function(a){this.emit("error",a)}));d.enable()}}else n=t(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),n.error=t(this,function(a){this.inited=!0;this.error= +a;a.requireModules=[b];E(k,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&delete k[a.map.id]});C(a)}),n.fromText=t(this,function(e,c){var d=a.name,u=h(d),v=O;c&&(e=c);v&&(O=!1);q(u);r(m.config,b)&&(m.config[d]=m.config[b]);try{l.exec(e)}catch(k){throw Error("fromText eval for "+d+" failed: "+k);}v&&(O=!0);this.depMaps.push(u);j.completeLoad(d);f([d],n)}),e.load(a.name,f,n,m)}));j.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){this.enabling=this.enabled=!0;x(this.depMaps,t(this,function(a, +b){var c,e;if("string"===typeof a){a=h(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=i(N,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;s(a,"defined",t(this,function(a){this.defineDep(b,a);this.check()}));this.errback&&s(a,"error",this.errback)}c=a.id;e=k[c];!r(N,c)&&(e&&!e.enabled)&&j.enable(a,this)}));E(this.pluginMaps,t(this,function(a){var b=i(k,a.id);b&&!b.enabled&&j.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c= +this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){x(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};j={config:m,contextName:b,registry:k,defined:p,urlFetched:S,defQueue:F,Module:W,makeModuleMap:h,nextTick:l.nextTick,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=m.pkgs,c=m.shim,e={paths:!0,config:!0,map:!0};E(a,function(a,b){e[b]?"map"===b?Q(m[b],a,!0,!0):Q(m[b],a,!0):m[b]=a});a.shim&&(E(a.shim,function(a, +b){I(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=j.makeShimExports(a);c[b]=a}),m.shim=c);a.packages&&(x(a.packages,function(a){a="string"===typeof a?{name:a}:a;b[a.name]={name:a.name,location:a.location||a.name,main:(a.main||"main").replace(ga,"").replace(aa,"")}}),m.pkgs=b);E(k,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=h(b))});if(a.deps||a.callback)j.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(Y,arguments)); +return b||a.exports&&Z(a.exports)}},makeRequire:function(a,d){function f(e,c,u){var i,m;d.enableBuildCallback&&(c&&H(c))&&(c.__requireJsBuild=!0);if("string"===typeof e){if(H(c))return C(J("requireargs","Invalid require call"),u);if(a&&r(N,e))return N[e](k[a.id]);if(l.get)return l.get(j,e,a);i=h(e,a,!1,!0);i=i.id;return!r(p,i)?C(J("notloaded",'Module name "'+i+'" has not been loaded yet for context: '+b+(a?"":". Use require([])"))):p[i]}K();j.nextTick(function(){K();m=q(h(null,a));m.skipMap=d.skipMap; +m.init(e,c,u,{enabled:!0});B()});return f}d=d||{};Q(f,{isBrowser:z,toUrl:function(b){var d=b.lastIndexOf("."),g=null;-1!==d&&(g=b.substring(d,b.length),b=b.substring(0,d));return j.nameToUrl(c(b,a&&a.id,!0),g)},defined:function(b){return r(p,h(b,a,!1,!0).id)},specified:function(b){b=h(b,a,!1,!0).id;return r(p,b)||r(k,b)}});a||(f.undef=function(b){w();var c=h(b,a,!0),d=i(k,b);delete p[b];delete S[c.url];delete X[b];d&&(d.events.defined&&(X[b]=d.events),delete k[b])});return f},enable:function(a){i(k, +a.id)&&q(a).enable()},completeLoad:function(a){var b,c,d=i(m.shim,a)||{},h=d.exports;for(w();F.length;){c=F.shift();if(null===c[0]){c[0]=a;if(b)break;b=!0}else c[0]===a&&(b=!0);D(c)}c=i(k,a);if(!b&&!r(p,a)&&c&&!c.inited){if(m.enforceDefine&&(!h||!Z(h)))return y(a)?void 0:C(J("nodefine","No define call for "+a,null,[a]));D([a,d.deps||[],d.exportsFn])}B()},nameToUrl:function(a,b){var c,d,h,f,j,k;if(l.jsExtRegExp.test(a))f=a+(b||"");else{c=m.paths;d=m.pkgs;f=a.split("/");for(j=f.length;0<j;j-=1)if(k= +f.slice(0,j).join("/"),h=i(d,k),k=i(c,k)){I(k)&&(k=k[0]);f.splice(0,j,k);break}else if(h){c=a===h.name?h.location+"/"+h.main:h.location;f.splice(0,j,c);break}f=f.join("/");f+=b||(/\?/.test(f)?"":".js");f=("/"===f.charAt(0)||f.match(/^[\w\+\.\-]+:/)?"":m.baseUrl)+f}return m.urlArgs?f+((-1===f.indexOf("?")?"?":"&")+m.urlArgs):f},load:function(a,b){l.load(j,a,b)},execCb:function(a,b,c,d){return b.apply(d,c)},onScriptLoad:function(a){if("load"===a.type||ha.test((a.currentTarget||a.srcElement).readyState))P= +null,a=G(a),j.completeLoad(a.id)},onScriptError:function(a){var b=G(a);if(!y(b.id))return C(J("scripterror","Script error",a,[b.id]))}};j.require=j.makeRequire();return j}var l,w,A,D,s,G,P,K,ba,ca,ia=/(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,ja=/[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,aa=/\.js$/,ga=/^\.\//;w=Object.prototype;var L=w.toString,da=w.hasOwnProperty,fa=Array.prototype.splice,z=!!("undefined"!==typeof window&&navigator&&document),$=!z&&"undefined"!==typeof importScripts,ha=z&& +"PLAYSTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/,V="undefined"!==typeof opera&&"[object Opera]"===opera.toString(),B={},q={},R=[],O=!1;if("undefined"===typeof define){if("undefined"!==typeof requirejs){if(H(requirejs))return;q=requirejs;requirejs=void 0}"undefined"!==typeof require&&!H(require)&&(q=require,require=void 0);l=requirejs=function(b,c,d,y){var f,h="_";!I(b)&&"string"!==typeof b&&(f=b,I(c)?(b=c,c=d,d=y):b=[]);f&&f.context&&(h=f.context);(y=i(B,h))||(y=B[h]=l.s.newContext(h)); +f&&y.configure(f);return y.require(b,c,d)};l.config=function(b){return l(b)};l.nextTick="undefined"!==typeof setTimeout?function(b){setTimeout(b,4)}:function(b){b()};require||(require=l);l.version="2.1.2";l.jsExtRegExp=/^\/|:|\?|\.js$/;l.isBrowser=z;w=l.s={contexts:B,newContext:ea};l({});x(["toUrl","undef","defined","specified"],function(b){l[b]=function(){var c=B._;return c.require[b].apply(c,arguments)}});if(z&&(A=w.head=document.getElementsByTagName("head")[0],D=document.getElementsByTagName("base")[0]))A= +w.head=D.parentNode;l.onError=function(b){throw b;};l.load=function(b,c,d){var i=b&&b.config||{},f;if(z)return f=i.xhtml?document.createElementNS("http://www.w3.org/1999/xhtml","html:script"):document.createElement("script"),f.type=i.scriptType||"text/javascript",f.charset="utf-8",f.async=!0,f.setAttribute("data-requirecontext",b.contextName),f.setAttribute("data-requiremodule",c),f.attachEvent&&!(f.attachEvent.toString&&0>f.attachEvent.toString().indexOf("[native code"))&&!V?(O=!0,f.attachEvent("onreadystatechange", +b.onScriptLoad)):(f.addEventListener("load",b.onScriptLoad,!1),f.addEventListener("error",b.onScriptError,!1)),f.src=d,K=f,D?A.insertBefore(f,D):A.appendChild(f),K=null,f;$&&(importScripts(d),b.completeLoad(c))};z&&M(document.getElementsByTagName("script"),function(b){A||(A=b.parentNode);if(s=b.getAttribute("data-main"))return q.baseUrl||(G=s.split("/"),ba=G.pop(),ca=G.length?G.join("/")+"/":"./",q.baseUrl=ca,s=ba),s=s.replace(aa,""),q.deps=q.deps?q.deps.concat(s):[s],!0});define=function(b,c,d){var i, +f;"string"!==typeof b&&(d=c,c=b,b=null);I(c)||(d=c,c=[]);!c.length&&H(d)&&d.length&&(d.toString().replace(ia,"").replace(ja,function(b,d){c.push(d)}),c=(1===d.length?["require"]:["require","exports","module"]).concat(c));if(O){if(!(i=K))P&&"interactive"===P.readyState||M(document.getElementsByTagName("script"),function(b){if("interactive"===b.readyState)return P=b}),i=P;i&&(b||(b=i.getAttribute("data-requiremodule")),f=B[i.getAttribute("data-requirecontext")])}(f?f.defQueue:R).push([b,c,d])};define.amd= +{jQuery:!0};l.exec=function(b){return eval(b)};l(q)}})(this);
--- a/wikked/static/js/wikked.js Sun Dec 30 20:08:11 2012 -0800 +++ b/wikked/static/js/wikked.js Sun Dec 30 23:15:32 2012 -0800 @@ -1,713 +1,51 @@ /** - * Make Javascript suck less. + * RequireJS configuration. + * + * We need to alias/shim some of the libraries. */ -String.prototype.format = function() { - var args = arguments; - return this.replace(/\{(\d+)\}/g, function(match, number) { - return typeof args[number] != 'undefined' ? args[number] : match; - }); -}; - -this.Wikked = {}; - -/** - * Helper class to load template files - * by name from the `tpl` directory. - */ -var TemplateLoader = Wikked.TemplateLoader = { - loadedTemplates: {}, - get: function(name, callback) { - if (name in this.loadedTemplates) { - callback(this.loadedTemplates[name]); - } else { - var $loader = this; - url = '/tpl/' + name + '.html' + '?' + (new Date()).getTime(); - $.get(url, function(data) { - $loader.loadedTemplates[name] = data; - callback(data); - }); +require.config({ + urlArgs: "bust=" + (new Date()).getTime(), + paths: { + jquery: 'jquery-1.8.3.min', + underscore: 'underscore-min', + backbone: 'backbone-min', + handlebars: 'handlebars-1.0.rc.1' + }, + shim: { + 'jquery': { + exports: '$' + }, + 'underscore': { + exports: '_' + }, + 'backbone': { + deps: ['underscore', 'jquery'], + exports: 'Backbone' + }, + 'handlebars': { + exports: 'Handlebars' } } -}; - -//-------------------------------------------------------------// - -/** - * Handlebars helper: reverse iterator. - */ -Handlebars.registerHelper('eachr', function(context, options) { - if (context === undefined) { - return ''; - } - data = undefined; - if (options.data) { - data = Handlebars.createFrame(options.data); - } - var out = ''; - for (var i=context.length - 1; i >= 0; i--) { - if (data !== undefined) { - data.index = (context.length - 1 - i); - data.rindex = i; - } - out += options.fn(context[i], { data: data }); - } - return out; -}); - -/** - * Would you believe Handlebars doesn't have an equality - * operator? - */ -Handlebars.registerHelper('ifeq', function(context, options) { - if (context == options.hash.to) { - return options.fn(this); - } - return options.inverse(this); -}); -Handlebars.registerHelper('ifneq', function(context, options) { - if (context != options.hash.to) { - return options.fn(this); - } - return options.inverse(this); }); //-------------------------------------------------------------// /** - * Client-side Wikked. - */ -var PageFormatter = Wikked.PageFormatter = { - formatLink: function(link) { - return link.toLowerCase().replace(/[^a-z0-9_\.\-\(\)\/]+/g, '-'); - }, - formatText: function(text) { - var $f = this; - text = text.replace(/^\[\[([a-z]+)\:\s*(.+)\]\]\s*$/m, function(m, a, b) { - var p = "<p><span class=\"preview-wiki-meta\">\n"; - p += "<span class=\"meta-name\">" + a + "</span>"; - p += "<span class=\"meta-value\">" + b + "</span>\n"; - p += "</span></p>\n\n"; - return p; - }); - text = text.replace(/\[\[([^\|\]]+)\|([^\]]+)\]\]/g, function(m, a, b) { - var url = $f.formatLink(b); - return '[' + a + '](/#/read/' + url + ')'; - }); - text = text.replace(/\[\[([^\]]+\/)?([^\]]+)\]\]/g, function(m, a, b) { - var url = $f.formatLink(a + b); - return '[' + b + '](/#/read/' + url + ')'; - }); - return text; - } -}; - -//-------------------------------------------------------------// - -/** - * Start the main app once the page is loaded. + * Entry point: run Backbone! + * + * We also import scripts like `handlebars` and `client` that + * are not used directly by anybody, but need to be evaluated. */ -$(function() { - - /** - * Wiki page models. - */ - var NavigationModel = Backbone.Model.extend({ - idAttribute: 'path', - defaults: function() { - return { - path: "main-page", - action: "read", - user: false - }; - }, - initialize: function() { - this.on('change:path', function(model, path) { - model._onChangePath(path); - }); - this._onChangePath(this.get('path')); - this.on('change:auth', function(model, auth) { - model._onChangeAuth(auth); - }); - this._onChangeAuth(this.get('auth')); - return this; - }, - _onChangePath: function(path) { - this.set('url_home', '/#/read/main-page'); - this.set('url_read', '/#/read/' + path); - this.set('url_edit', '/#/edit/' + path); - this.set('url_hist', '/#/changes/' + path); - this.set('url_search', '/search'); - }, - _onChangeAuth: function(auth) { - if (auth) { - this.set('url_login', false); - this.set('url_logout', '/#/logout'); - this.set('username', auth.username); - } else { - this.set('url_login', '/#/login'); - this.set('url_logout', false); - this.set('username', false); - } - } - }); - - var FooterModel = Backbone.Model.extend({ - defaults: function() { - return { - url_extras: [ { name: 'Home', url: '/' } ] - }; - }, - addExtraUrl: function(name, url, index) { - if (index === undefined) { - this.get('url_extras').push({ name: name, url: url }); - } else { - this.get('url_extras').splice(index, 0, { name: name, url: url }); - } - } - }); - - var LoginModel = Backbone.Model.extend({ - doLogin: function(form) { - $.post('/api/user/login', $(form).serialize()) - .success(function() { - app.navigate('/', { trigger: true }); - }) - .error(function() { - alert("Error while logging in..."); - }); - } - }); - - var PageModel = Backbone.Model.extend({ - idAttribute: 'path', - defaults: function() { - return { - path: "main-page" - }; - }, - initialize: function() { - this.on('change:path', function(model, path) { - model._onChangePath(path); - }); - this.on('change:text', function(model, text) { - model._onChangeText(text); - }); - this._onChangePath(this.get('path')); - this._onChangeText(''); - return this; - }, - url: function() { - var base = _.result(this, 'urlRoot') || _.result(this.collection, 'url') || urlError(); - if (this.isNew()) return base; - return base + (base.charAt(base.length - 1) === '/' ? '' : '/') + this.id; - }, - title: function() { - return this.getMeta('title'); - }, - getMeta: function(key) { - var meta = this.get('meta'); - if (meta === undefined) { - return undefined; - } - return meta[key]; - }, - _onChangePath: function(path) { - }, - _onChangeText: function(text) { - this.set('content', new Handlebars.SafeString(text)); - } - }); - - var PageStateModel = PageModel.extend({ - urlRoot: '/api/state/' - }); - - var MasterPageModel = PageModel.extend({ - initialize: function() { - this.nav = new NavigationModel({ id: this.id }); - this.footer = new FooterModel(); - MasterPageModel.__super__.initialize.apply(this, arguments); - this.on('change:auth', function(model, auth) { - model._onChangeAuth(auth); - }); - if (this.action !== undefined) { - this.nav.set('action', this.action); - this.footer.set('action', this.action); - } - return this; - }, - _onChangePath: function(path) { - MasterPageModel.__super__._onChangePath.apply(this, arguments); - this.nav.set('path', path); - }, - _onChangeAuth: function(auth) { - this.nav.set('auth', auth); - } - }); - - var PageReadModel = MasterPageModel.extend({ - urlRoot: '/api/read/', - action: 'read', - _onChangePath: function(path) { - PageReadModel.__super__._onChangePath.apply(this, arguments); - this.footer.addExtraUrl('Pages Linking Here', '/#/inlinks/' + path, 1); - this.footer.addExtraUrl('JSON', '/api/read/' + path); - } - }); - - var PageSourceModel = MasterPageModel.extend({ - urlRoot: '/api/raw/', - action: 'source' - }); - - var PageEditModel = MasterPageModel.extend({ - urlRoot: '/api/edit/', - action: 'edit', - doEdit: function(form) { - var path = this.get('path'); - $.post('/api/edit/' + path, $(form).serialize()) - .success(function(data) { - app.navigate('/read/' + path, { trigger: true }); - }) - .error(function() { - alert('Error saving page...'); - }); - } - }); - - var PageHistoryModel = MasterPageModel.extend({ - urlRoot: '/api/history/', - action: 'history', - _onChangePath: function(path) { - PageHistoryModel.__super__._onChangePath.apply(this, arguments); - this.set('url_rev', '/#/revision/' + path); - this.set('url_diffc', '/#/diff/c/' + path); - this.set('url_diffr', '/#/diff/r/' + path); - } - }); - - var PageRevisionModel = MasterPageModel.extend({ - urlRoot: '/api/revision/', - idAttribute: 'path_and_rev', - action: 'revision', - defaults: function() { - return { - path: "main-page", - rev: "tip" - }; - }, - initialize: function() { - PageRevisionModel.__super__.initialize.apply(this, arguments); - this.on('change:path', function(model, path) { - model._onChangePathOrRev(path, model.get('rev')); - }); - this.on('change:rev', function(model, rev) { - model._onChangePathOrRev(model.get('path'), rev); - }); - this._onChangePathOrRev(this.get('path'), this.get('rev')); - return this; - }, - _onChangePathOrRev: function(path, rev) { - this.set('path_and_rev', path + '/' + rev); - this.set('disp_rev', rev); - if (rev.match(/[a-f0-9]{40}/)) { - this.set('disp_rev', rev.substring(0, 8)); - } - } - }); - - var PageDiffModel = MasterPageModel.extend({ - urlRoot: '/api/diff/', - idAttribute: 'path_and_revs', - action: 'diff', - defaults: function() { - return { - path: "main-page", - rev1: "tip", - rev2: "" - }; - }, - initialize: function() { - PageDiffModel.__super__.initialize.apply(this, arguments); - this.on('change:path', function(model, path) { - model._onChangePathOrRevs(path, model.get('rev')); - }); - this.on('change:rev1', function(model, rev1) { - model._onChangePathOrRevs(model.get('path'), rev1, model.get('rev2')); - }); - this.on('change:rev2', function(model, rev2) { - model._onChangePathOrRevs(model.get('path'), model.get('rev1'), rev2); - }); - this._onChangePathOrRevs(this.get('path'), this.get('rev1'), this.get('rev2')); - return this; - }, - _onChangePathOrRevs: function(path, rev1, rev2) { - this.set('path_and_revs', path + '/' + rev1 + '/' + rev2); - if (!rev2) { - this.set('path_and_revs', path + '/' + rev1); - } - this.set('disp_rev1', rev1); - if (rev1 !== undefined && rev1.match(/[a-f0-9]{40}/)) { - this.set('disp_rev1', rev1.substring(0, 8)); - } - this.set('disp_rev2', rev2); - if (rev2 !== undefined && rev2.match(/[a-f0-9]{40}/)) { - this.set('disp_rev2', rev2.substring(0, 8)); - } - } - }); - - var IncomingLinksModel = MasterPageModel.extend({ - urlRoot: '/api/inlinks/', - action: 'inlinks' - }); - - var WikiSearchModel = MasterPageModel.extend({ - urlRoot: '/api/search/', - action: 'search', - title: function() { - return 'Search'; - }, - execute: function(query) { - var $model = this; - $.getJSON('/api/search', { q: query }) - .success(function (data) { - $model.set('hits', data.hits); - }) - .error(function() { - alert("Error searching..."); - }); - } - }); +require([ + 'wikked/app', + 'wikked/handlebars', + 'wikked/client', + 'backbone' + ], + function(app, hb, client, Backbone) { - /** - * Wiki page views. - */ - var PageView = Backbone.View.extend({ - tagName: 'div', - className: 'wrapper', - initialize: function() { - PageView.__super__.initialize.apply(this, arguments); - var $view = this; - this.model.on("change", function() { $view.render(); }); - return this; - }, - render: function(view) { - if (this.templateName !== undefined) { - this.renderTemplate(this.templateName, this.renderCallback); - } - this.renderTitle(this.titleFormat); - return this; - }, - renderTemplate: function(tpl_name, callback) { - var $view = this; - TemplateLoader.get(tpl_name, function(src) { - var template = Handlebars.compile(src); - $view.$el.html(template($view.model.toJSON())); - if (callback !== undefined) { - callback.call($view, $view, $view.model); - } - }); - }, - renderTitle: function(formatter) { - var title = this.model.title(); - if (formatter !== undefined) { - title = formatter.call(this, title); - } - document.title = title; - } - }); - _.extend(PageView, Backbone.Events); - - var NavigationView = PageView.extend({ - templateName: 'nav', - initialize: function() { - NavigationView.__super__.initialize.apply(this, arguments); - this.render(); - return this; - }, - render: function() { - this.renderTemplate('nav'); - }, - postRender: function() { - var $view = this; - this.$('#search').submit(function() { - app.navigate('/search/' + $(this.q).val(), { trigger: true }); - return false; - }); - } - }); - - var FooterView = PageView.extend({ - templateName: 'footer', - initialize: function() { - FooterView.__super__.initialize.apply(this, arguments); - this.render(); - return this; - }, - render: function() { - this.renderTemplate('footer'); - }, - postRender: function() { - } - }); - - var LoginView = PageView.extend({ - templateName: 'login', - initialize: function() { - LoginView.__super__.initialize.apply(this, arguments); - this.render(); - return this; - }, - render: function() { - this.renderTemplate('login', function(view, model) { - this.$('form#login').submit(function() { - model.doLogin(this); - return false; - }); - }); - document.title = 'Login'; - } - }); - - var MasterPageView = PageView.extend({ - initialize: function() { - MasterPageView.__super__.initialize.apply(this, arguments); - this.nav = new NavigationView({ model: this.model.nav }); - this.footer = new FooterView({ model: this.model.footer }); - this.render(); - return this; - }, - renderCallback: function(view, model) { - this.nav.$el.prependTo(this.$el); - this.nav.postRender(); - this.footer.$el.appendTo(this.$el); - this.footer.postRender(); - } - }); - - var PageReadView = MasterPageView.extend({ - templateName: 'read-page', - initialize: function() { - PageReadView.__super__.initialize.apply(this, arguments); - // Also get the current state, and show a warning - // if the page is new or modified. - var stateModel = new PageStateModel({ path: this.model.get('path') }); - stateModel.fetch({ - success: function(model, response, options) { - if (model.get('state') == 'new' || model.get('state') == 'modified') { - TemplateLoader.get('state-warning', function(src) { - var template = Handlebars.compile(src); - var warning = $(template(model.toJSON())); - warning.css('display', 'none'); - warning.prependTo($('#app')); - warning.slideDown(); - $('.dismiss', warning).click(function() { - warning.slideUp(); - return false; - }); - }); - } - } - }); - return this; - }, - renderCallback: function(view, model) { - PageReadView.__super__.renderCallback.apply(this, arguments); - // Replace all wiki links with proper hyperlinks using the JS app's - // URL scheme. - this.$('a.wiki-link[data-wiki-url]').each(function(i) { - var jel = $(this); - if (jel.hasClass('missing')) - jel.attr('href', '/#/edit/' + jel.attr('data-wiki-url')); - else - jel.attr('href', '/#/read/' + jel.attr('data-wiki-url')); - }); - } - }); - - var PageEditView = MasterPageView.extend({ - templateName: 'edit-page', - renderCallback: function(view, model) { - PageEditView.__super__.renderCallback.apply(this, arguments); - this.$('#page-edit').submit(function() { - model.doEdit(this); - return false; - }); - }, - titleFormat: function(title) { - return 'Editing: ' + title; - } - }); + var router = new app.Router(); + Backbone.history.start();//{ pushState: true }); - var PageHistoryView = MasterPageView.extend({ - templateName: 'history-page', - renderCallback: function(view, model) { - PageHistoryView.__super__.renderCallback.apply(this, arguments); - this.$('#diff-page').submit(function() { - view._triggerDiff(this, model.get('path')); - return false; - }); - }, - titleFormat: function(title) { - return 'History: ' + title; - }, - _triggerDiff: function(form, path) { - var rev1 = $('input[name=rev1]:checked', form).val(); - var rev2 = $('input[name=rev2]:checked', form).val(); - app.navigate('/diff/r/' + path + '/' + rev1 + '/' + rev2, { trigger: true }); - } - }); - - var PageRevisionView = MasterPageView.extend({ - templateName: 'revision-page', - titleFormat: function(title) { - return title + ' [' + this.model.get('rev') + ']'; - } - }); - - var PageDiffView = MasterPageView.extend({ - templateName: 'diff-page', - titleFormat: function(title) { - return title + ' [' + this.model.get('rev1') + '-' + this.model.get('rev2') + ']'; - } - }); - - var IncomingLinksView = MasterPageView.extend({ - templateName: 'inlinks-page', - titleFormat: function(title) { - return 'Incoming Links: ' + title; - } - }); - - var WikiSearchView = MasterPageView.extend({ - templateName: 'search-results' - }); - - /** - * Main URL router. - */ - var AppRouter = Backbone.Router.extend({ - routes: { - 'read/*path': "readPage", - '': "readMainPage", - 'edit/*path': "editPage", - 'changes/*path': "showPageHistory", - 'inlinks/*path': "showIncomingLinks", - 'revision/*path/:rev': "readPageRevision", - 'diff/c/*path/:rev': "showDiffWithPrevious", - 'diff/r/*path/:rev1/:rev2':"showDiff", - 'search/:query': "showSearchResults", - 'login': "showLogin", - 'logout': "doLogout" - }, - readPage: function(path) { - var view = new PageReadView({ - el: $('#app'), - model: new PageReadModel({ path: path }) - }); - view.model.fetch(); - this.navigate('/read/' + path); - }, - readMainPage: function() { - this.readPage('main-page'); - }, - editPage: function(path) { - var view = new PageEditView({ - el: $('#app'), - model: new PageEditModel({ path: path }) - }); - view.model.fetch(); - this.navigate('/edit/' + path); - }, - showPageHistory: function(path) { - var view = new PageHistoryView({ - el: $('#app'), - model: new PageHistoryModel({ path: path }) - }); - view.model.fetch(); - this.navigate('/changes/' + path); - }, - showIncomingLinks: function(path) { - var view = new IncomingLinksView({ - el: $('#app'), - model: new IncomingLinksModel({ path: path }) - }); - view.model.fetch(); - this.navigate('/inlinks/' + path); - }, - readPageRevision: function(path, rev) { - var view = new PageRevisionView({ - el: $('#app'), - rev: rev, - model: new PageRevisionModel({ path: path, rev: rev }) - }); - view.model.fetch(); - this.navigate('/revision/' + path + '/' + rev); - }, - showDiffWithPrevious: function(path, rev) { - var view = new PageDiffView({ - el: $('#app'), - rev1: rev, - model: new PageDiffModel({ path: path, rev1: rev }) - }); - view.model.fetch(); - this.navigate('/diff/c/' + path + '/' + rev); - }, - showDiff: function(path, rev1, rev2) { - var view = new PageDiffView({ - el: $('#app'), - rev1: rev1, - rev2: rev2, - model: new PageDiffModel({ path: path, rev1: rev1, rev2: rev2 }) - }); - view.model.fetch(); - this.navigate('/diff/r/' + path + '/' + rev1 + '/' + rev2); - }, - showSearchResults: function(query) { - if (query === '') { - query = this.getQueryVariable('q'); - } - var view = new WikiSearchView({ - el: $('#app'), - model: new WikiSearchModel() - }); - view.model.execute(query); - this.navigate('/search/' + query); - }, - showLogin: function() { - var view = new LoginView({ - el: $('#app'), - model: new LoginModel() - }); - this.navigate('/login'); - }, - doLogout: function() { - $.post('/api/user/logout') - .success(function(data) { - app.navigate('/', { trigger: true }); - }) - .error(function() { - alert("Error logging out!"); - }); - }, - getQueryVariable: function(variable) { - var query = window.location.search.substring(1); - var vars = query.split("&"); - for (var i = 0; i < vars.length; i++) { - var pair = vars[i].split("="); - if (pair[0] == variable) { - return unescape(pair[1]); - } - } - return false; - } - }); - - /** - * Launch! - */ - var app = new AppRouter(); - Backbone.history.start();//{ pushState: true }); });
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wikked/static/js/wikked/app.js Sun Dec 30 23:15:32 2012 -0800 @@ -0,0 +1,147 @@ +/** + * The main Wikked app/router. + */ +define([ + 'jquery', + 'underscore', + 'backbone', + './views', + './models' + ], + function($, _, Backbone, Views, Models) { + + /** + * Main router. + */ + var AppRouter = Backbone.Router.extend({ + routes: { + 'read/*path': "readPage", + '': "readMainPage", + 'edit/*path': "editPage", + 'changes/*path': "showPageHistory", + 'inlinks/*path': "showIncomingLinks", + 'revision/*path/:rev': "readPageRevision", + 'diff/c/*path/:rev': "showDiffWithPrevious", + 'diff/r/*path/:rev1/:rev2':"showDiff", + 'search/:query': "showSearchResults", + 'login': "showLogin", + 'logout': "doLogout" + }, + readPage: function(path) { + var view = new Views.PageReadView({ + el: $('#app'), + model: new Models.PageReadModel({ path: path }) + }); + view.model.setApp(this); + view.model.fetch(); + this.navigate('/read/' + path); + }, + readMainPage: function() { + this.readPage('main-page'); + }, + editPage: function(path) { + var view = new Views.PageEditView({ + el: $('#app'), + model: new Models.PageEditModel({ path: path }) + }); + view.model.setApp(this); + view.model.fetch(); + this.navigate('/edit/' + path); + }, + showPageHistory: function(path) { + var view = new Views.PageHistoryView({ + el: $('#app'), + model: new Models.PageHistoryModel({ path: path }) + }); + view.model.setApp(this); + view.model.fetch(); + this.navigate('/changes/' + path); + }, + showIncomingLinks: function(path) { + var view = new Views.IncomingLinksView({ + el: $('#app'), + model: new Models.IncomingLinksModel({ path: path }) + }); + view.model.setApp(this); + view.model.fetch(); + this.navigate('/inlinks/' + path); + }, + readPageRevision: function(path, rev) { + var view = new Views.PageRevisionView({ + el: $('#app'), + rev: rev, + model: new Models.PageRevisionModel({ path: path, rev: rev }) + }); + view.model.setApp(this); + view.model.fetch(); + this.navigate('/revision/' + path + '/' + rev); + }, + showDiffWithPrevious: function(path, rev) { + var view = new Views.PageDiffView({ + el: $('#app'), + rev1: rev, + model: new Models.PageDiffModel({ path: path, rev1: rev }) + }); + view.model.setApp(this); + view.model.fetch(); + this.navigate('/diff/c/' + path + '/' + rev); + }, + showDiff: function(path, rev1, rev2) { + var view = new Views.PageDiffView({ + el: $('#app'), + rev1: rev1, + rev2: rev2, + model: new Models.PageDiffModel({ path: path, rev1: rev1, rev2: rev2 }) + }); + view.model.setApp(this); + view.model.fetch(); + this.navigate('/diff/r/' + path + '/' + rev1 + '/' + rev2); + }, + showSearchResults: function(query) { + if (query === '') { + query = this.getQueryVariable('q'); + } + var view = new Views.WikiSearchView({ + el: $('#app'), + model: new Models.WikiSearchModel() + }); + view.model.setApp(this); + view.model.execute(query); + this.navigate('/search/' + query); + }, + showLogin: function() { + var view = new Views.LoginView({ + el: $('#app'), + model: new Models.LoginModel() + }); + view.model.setApp(this); + this.navigate('/login'); + }, + doLogout: function() { + var $app = this; + $.post('/api/user/logout') + .success(function(data) { + $app.navigate('/', { trigger: true }); + }) + .error(function() { + alert("Error logging out!"); + }); + }, + getQueryVariable: function(variable) { + var query = window.location.search.substring(1); + var vars = query.split("&"); + for (var i = 0; i < vars.length; i++) { + var pair = vars[i].split("="); + if (pair[0] == variable) { + return unescape(pair[1]); + } + } + return false; + } + }); + + return { + Router: AppRouter + }; +}); +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wikked/static/js/wikked/client.js Sun Dec 30 23:15:32 2012 -0800 @@ -0,0 +1,39 @@ +/** + * Client-side Wikked. + */ +define(function() { + + var PageFormatter = { + formatLink: function(link) { + return link.toLowerCase().replace(/[^a-z0-9_\.\-\(\)\/]+/g, '-'); + }, + formatText: function(text) { + var $f = this; + text = text.replace(/^\[\[([a-z]+)\:\s*(.+)\]\]\s*$/gm, function(m, a, b) { + var p = "<p><span class=\"preview-wiki-meta\">\n"; + p += "<span class=\"meta-name\">" + a + "</span>"; + p += "<span class=\"meta-value\">" + b + "</span>\n"; + p += "</span></p>\n\n"; + return p; + }); + text = text.replace(/\[\[([^\|\]]+)\|([^\]]+)\]\]/g, function(m, a, b) { + var url = $f.formatLink(b); + return '[' + a + '](/#/read/' + url + ')'; + }); + text = text.replace(/\[\[([^\]]+\/)?([^\]]+)\]\]/g, function(m, a, b) { + var url = $f.formatLink(a + b); + return '[' + b + '](/#/read/' + url + ')'; + }); + return text; + } + }; + //TODO: remove this and move the JS code from the template to the view. + if (!window.Wikked) + window.Wikked = {}; + window.Wikked.PageFormatter = PageFormatter; + + return { + PageFormatter: PageFormatter + }; +}); +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wikked/static/js/wikked/handlebars.js Sun Dec 30 23:15:32 2012 -0800 @@ -0,0 +1,48 @@ +/** + * Handlebars helpers and extensions. + */ +define([ + 'handlebars' + ], + function(Handlebars) { + + /** + * Handlebars helper: reverse iterator. + */ + Handlebars.registerHelper('eachr', function(context, options) { + if (context === undefined) { + return ''; + } + data = undefined; + if (options.data) { + data = Handlebars.createFrame(options.data); + } + var out = ''; + for (var i=context.length - 1; i >= 0; i--) { + if (data !== undefined) { + data.index = (context.length - 1 - i); + data.rindex = i; + } + out += options.fn(context[i], { data: data }); + } + return out; + }); + + /** + * Would you believe Handlebars doesn't have an equality + * operator? + */ + Handlebars.registerHelper('ifeq', function(context, options) { + if (context == options.hash.to) { + return options.fn(this); + } + return options.inverse(this); + }); + Handlebars.registerHelper('ifneq', function(context, options) { + if (context != options.hash.to) { + return options.fn(this); + } + return options.inverse(this); + }); +}); +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wikked/static/js/wikked/models.js Sun Dec 30 23:15:32 2012 -0800 @@ -0,0 +1,319 @@ +/** + * Wikked models. + */ +define([ + 'require', + 'jquery', + 'underscore', + 'backbone', + 'handlebars' + ], + function(require, $, _, Backbone, Handlebars) { + + var NavigationModel = Backbone.Model.extend({ + idAttribute: 'path', + defaults: function() { + return { + path: "main-page", + action: "read", + user: false + }; + }, + initialize: function() { + this.on('change:path', function(model, path) { + model._onChangePath(path); + }); + this._onChangePath(this.get('path')); + this.on('change:auth', function(model, auth) { + model._onChangeAuth(auth); + }); + this._onChangeAuth(this.get('auth')); + return this; + }, + doSearch: function(form) { + this.app.navigate('/search/' + $(form.q).val(), { trigger: true }); + }, + _onChangePath: function(path) { + this.set('url_home', '/#/read/main-page'); + this.set('url_read', '/#/read/' + path); + this.set('url_edit', '/#/edit/' + path); + this.set('url_hist', '/#/changes/' + path); + this.set('url_search', '/search'); + }, + _onChangeAuth: function(auth) { + if (auth) { + this.set('url_login', false); + this.set('url_logout', '/#/logout'); + this.set('username', auth.username); + } else { + this.set('url_login', '/#/login'); + this.set('url_logout', false); + this.set('username', false); + } + } + }); + + var FooterModel = Backbone.Model.extend({ + defaults: function() { + return { + url_extras: [ { name: 'Home', url: '/' } ] + }; + }, + addExtraUrl: function(name, url, index) { + if (index === undefined) { + this.get('url_extras').push({ name: name, url: url }); + } else { + this.get('url_extras').splice(index, 0, { name: name, url: url }); + } + } + }); + + var LoginModel = Backbone.Model.extend({ + setApp: function(app) { + this.app = app; + }, + doLogin: function(form) { + var $model = this; + $.post('/api/user/login', $(form).serialize()) + .success(function() { + $model.app.navigate('/', { trigger: true }); + }) + .error(function() { + alert("Error while logging in..."); + }); + } + }); + + var PageModel = Backbone.Model.extend({ + idAttribute: 'path', + defaults: function() { + return { + path: "main-page" + }; + }, + initialize: function() { + this.on('change:path', function(model, path) { + model._onChangePath(path); + }); + this.on('change:text', function(model, text) { + model._onChangeText(text); + }); + this._onChangePath(this.get('path')); + this._onChangeText(''); + return this; + }, + url: function() { + var base = _.result(this, 'urlRoot') || _.result(this.collection, 'url') || urlError(); + if (this.isNew()) return base; + return base + (base.charAt(base.length - 1) === '/' ? '' : '/') + this.id; + }, + title: function() { + return this.getMeta('title'); + }, + getMeta: function(key) { + var meta = this.get('meta'); + if (meta === undefined) { + return undefined; + } + return meta[key]; + }, + setApp: function(app) { + this.app = app; + if (this._onAppSet !== undefined) { + this._onAppSet(app); + } + }, + _onChangePath: function(path) { + }, + _onChangeText: function(text) { + this.set('content', new Handlebars.SafeString(text)); + } + }); + + var PageStateModel = PageModel.extend({ + urlRoot: '/api/state/' + }); + + var MasterPageModel = PageModel.extend({ + initialize: function() { + this.nav = new NavigationModel({ id: this.id }); + this.footer = new FooterModel(); + MasterPageModel.__super__.initialize.apply(this, arguments); + this.on('change:auth', function(model, auth) { + model._onChangeAuth(auth); + }); + if (this.action !== undefined) { + this.nav.set('action', this.action); + this.footer.set('action', this.action); + } + return this; + }, + _onAppSet: function(app) { + this.nav.app = app; + this.footer.app = app; + }, + _onChangePath: function(path) { + MasterPageModel.__super__._onChangePath.apply(this, arguments); + this.nav.set('path', path); + }, + _onChangeAuth: function(auth) { + this.nav.set('auth', auth); + } + }); + + var PageReadModel = MasterPageModel.extend({ + urlRoot: '/api/read/', + action: 'read', + _onChangePath: function(path) { + PageReadModel.__super__._onChangePath.apply(this, arguments); + this.footer.addExtraUrl('Pages Linking Here', '/#/inlinks/' + path, 1); + this.footer.addExtraUrl('JSON', '/api/read/' + path); + } + }); + + var PageSourceModel = MasterPageModel.extend({ + urlRoot: '/api/raw/', + action: 'source' + }); + + var PageEditModel = MasterPageModel.extend({ + urlRoot: '/api/edit/', + action: 'edit', + doEdit: function(form) { + var $model = this; + var path = this.get('path'); + $.post('/api/edit/' + path, $(form).serialize()) + .success(function(data) { + $model.app.navigate('/read/' + path, { trigger: true }); + }) + .error(function() { + alert('Error saving page...'); + }); + } + }); + + var PageHistoryModel = MasterPageModel.extend({ + urlRoot: '/api/history/', + action: 'history', + doDiff: function(form) { + var rev1 = $('input[name=rev1]:checked', form).val(); + var rev2 = $('input[name=rev2]:checked', form).val(); + this.app.navigate('/diff/r/' + this.get('path') + '/' + rev1 + '/' + rev2, { trigger: true }); + }, + _onChangePath: function(path) { + PageHistoryModel.__super__._onChangePath.apply(this, arguments); + this.set('url_rev', '/#/revision/' + path); + this.set('url_diffc', '/#/diff/c/' + path); + this.set('url_diffr', '/#/diff/r/' + path); + } + }); + + var PageRevisionModel = MasterPageModel.extend({ + urlRoot: '/api/revision/', + idAttribute: 'path_and_rev', + action: 'revision', + defaults: function() { + return { + path: "main-page", + rev: "tip" + }; + }, + initialize: function() { + PageRevisionModel.__super__.initialize.apply(this, arguments); + this.on('change:path', function(model, path) { + model._onChangePathOrRev(path, model.get('rev')); + }); + this.on('change:rev', function(model, rev) { + model._onChangePathOrRev(model.get('path'), rev); + }); + this._onChangePathOrRev(this.get('path'), this.get('rev')); + return this; + }, + _onChangePathOrRev: function(path, rev) { + this.set('path_and_rev', path + '/' + rev); + this.set('disp_rev', rev); + if (rev.match(/[a-f0-9]{40}/)) { + this.set('disp_rev', rev.substring(0, 8)); + } + } + }); + + var PageDiffModel = MasterPageModel.extend({ + urlRoot: '/api/diff/', + idAttribute: 'path_and_revs', + action: 'diff', + defaults: function() { + return { + path: "main-page", + rev1: "tip", + rev2: "" + }; + }, + initialize: function() { + PageDiffModel.__super__.initialize.apply(this, arguments); + this.on('change:path', function(model, path) { + model._onChangePathOrRevs(path, model.get('rev')); + }); + this.on('change:rev1', function(model, rev1) { + model._onChangePathOrRevs(model.get('path'), rev1, model.get('rev2')); + }); + this.on('change:rev2', function(model, rev2) { + model._onChangePathOrRevs(model.get('path'), model.get('rev1'), rev2); + }); + this._onChangePathOrRevs(this.get('path'), this.get('rev1'), this.get('rev2')); + return this; + }, + _onChangePathOrRevs: function(path, rev1, rev2) { + this.set('path_and_revs', path + '/' + rev1 + '/' + rev2); + if (!rev2) { + this.set('path_and_revs', path + '/' + rev1); + } + this.set('disp_rev1', rev1); + if (rev1 !== undefined && rev1.match(/[a-f0-9]{40}/)) { + this.set('disp_rev1', rev1.substring(0, 8)); + } + this.set('disp_rev2', rev2); + if (rev2 !== undefined && rev2.match(/[a-f0-9]{40}/)) { + this.set('disp_rev2', rev2.substring(0, 8)); + } + } + }); + + var IncomingLinksModel = MasterPageModel.extend({ + urlRoot: '/api/inlinks/', + action: 'inlinks' + }); + + var WikiSearchModel = MasterPageModel.extend({ + urlRoot: '/api/search/', + action: 'search', + title: function() { + return 'Search'; + }, + execute: function(query) { + var $model = this; + $.getJSON('/api/search', { q: query }) + .success(function (data) { + $model.set('hits', data.hits); + }) + .error(function() { + alert("Error searching..."); + }); + } + }); + + return { + NavigationModel: NavigationModel, + FooterModel: FooterModel, + PageReadModel: PageReadModel, + PageEditModel: PageEditModel, + PageHistoryModel: PageHistoryModel, + IncomingLinksModel: IncomingLinksModel, + PageRevisionModel: PageRevisionModel, + PageDiffModel: PageDiffModel, + WikiSearchModel: WikiSearchModel, + LoginModel: LoginModel, + PageStateModel: PageStateModel + }; +}); +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wikked/static/js/wikked/util.js Sun Dec 30 23:15:32 2012 -0800 @@ -0,0 +1,43 @@ +/** + * Various utility classes/functions. + */ +define([ + 'jquery' + ], + function($) { + + /** + * Make Javascript suck less. + */ + String.prototype.format = function() { + var args = arguments; + return this.replace(/\{(\d+)\}/g, function(match, number) { + return typeof args[number] != 'undefined' ? args[number] : match; + }); + }; + + /** + * Helper class to load template files + * by name from the `tpl` directory. + */ + var TemplateLoader = { + loadedTemplates: {}, + get: function(name, callback) { + if (name in this.loadedTemplates) { + callback(this.loadedTemplates[name]); + } else { + var $loader = this; + url = '/tpl/' + name + '.html' + '?' + (new Date()).getTime(); + $.get(url, function(data) { + $loader.loadedTemplates[name] = data; + callback(data); + }); + } + } + }; + + return { + TemplateLoader: TemplateLoader + }; +}); +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wikked/static/js/wikked/views.js Sun Dec 30 23:15:32 2012 -0800 @@ -0,0 +1,225 @@ +/** + * Wikked views. + */ +define([ + 'jquery', + 'underscore', + 'backbone', + 'handlebars', + './models', + './util' + ], + function($, _, Backbone, Handlebars, Models, Util) { + + var PageView = Backbone.View.extend({ + tagName: 'div', + className: 'wrapper', + initialize: function() { + PageView.__super__.initialize.apply(this, arguments); + var $view = this; + this.model.on("change", function() { $view.render(); }); + return this; + }, + render: function(view) { + if (this.templateName !== undefined) { + this.renderTemplate(this.templateName, this.renderCallback); + } + this.renderTitle(this.titleFormat); + return this; + }, + renderTemplate: function(tpl_name, callback) { + var $view = this; + Util.TemplateLoader.get(tpl_name, function(src) { + var template = Handlebars.compile(src); + $view.$el.html(template($view.model.toJSON())); + if (callback !== undefined) { + callback.call($view, $view, $view.model); + } + }); + }, + renderTitle: function(formatter) { + var title = this.model.title(); + if (formatter !== undefined) { + title = formatter.call(this, title); + } + document.title = title; + } + }); + _.extend(PageView, Backbone.Events); + + var NavigationView = PageView.extend({ + templateName: 'nav', + initialize: function() { + NavigationView.__super__.initialize.apply(this, arguments); + this.render(); + return this; + }, + render: function() { + this.renderTemplate('nav'); + }, + postRender: function() { + var model = this.model; + this.$('#search').submit(function(e) { + e.preventDefault(); + model.doSearch(this); + return false; + }); + } + }); + + var FooterView = PageView.extend({ + templateName: 'footer', + initialize: function() { + FooterView.__super__.initialize.apply(this, arguments); + this.render(); + return this; + }, + render: function() { + this.renderTemplate('footer'); + }, + postRender: function() { + } + }); + + var LoginView = PageView.extend({ + templateName: 'login', + initialize: function() { + LoginView.__super__.initialize.apply(this, arguments); + this.render(); + return this; + }, + render: function() { + this.renderTemplate('login', function(view, model) { + this.$('#login').submit(function(e) { + e.preventDefault(); + model.doLogin(this); + return false; + }); + }); + document.title = 'Login'; + } + }); + + var MasterPageView = PageView.extend({ + initialize: function() { + MasterPageView.__super__.initialize.apply(this, arguments); + this.nav = new NavigationView({ model: this.model.nav }); + this.footer = new FooterView({ model: this.model.footer }); + this.render(); + return this; + }, + renderCallback: function(view, model) { + this.nav.$el.prependTo(this.$el); + this.nav.postRender(); + this.footer.$el.appendTo(this.$el); + this.footer.postRender(); + } + }); + + var PageReadView = MasterPageView.extend({ + templateName: 'read-page', + initialize: function() { + PageReadView.__super__.initialize.apply(this, arguments); + // Also get the current state, and show a warning + // if the page is new or modified. + var stateModel = new Models.PageStateModel({ path: this.model.get('path') }); + stateModel.fetch({ + success: function(model, response, options) { + if (model.get('state') == 'new' || model.get('state') == 'modified') { + Util.TemplateLoader.get('state-warning', function(src) { + var template = Handlebars.compile(src); + var warning = $(template(model.toJSON())); + warning.css('display', 'none'); + warning.prependTo($('#app')); + warning.slideDown(); + $('.dismiss', warning).click(function() { + warning.slideUp(); + return false; + }); + }); + } + } + }); + return this; + }, + renderCallback: function(view, model) { + PageReadView.__super__.renderCallback.apply(this, arguments); + // Replace all wiki links with proper hyperlinks using the JS app's + // URL scheme. + this.$('a.wiki-link[data-wiki-url]').each(function(i) { + var jel = $(this); + if (jel.hasClass('missing')) + jel.attr('href', '/#/edit/' + jel.attr('data-wiki-url')); + else + jel.attr('href', '/#/read/' + jel.attr('data-wiki-url')); + }); + } + }); + + var PageEditView = MasterPageView.extend({ + templateName: 'edit-page', + renderCallback: function(view, model) { + PageEditView.__super__.renderCallback.apply(this, arguments); + this.$('#page-edit').submit(function(e) { + e.preventDefault(); + model.doEdit(this); + return false; + }); + }, + titleFormat: function(title) { + return 'Editing: ' + title; + } + }); + + var PageHistoryView = MasterPageView.extend({ + templateName: 'history-page', + renderCallback: function(view, model) { + PageHistoryView.__super__.renderCallback.apply(this, arguments); + this.$('#diff-page').submit(function(e) { + e.preventDefault(); + model.doDiff(this); + return false; + }); + }, + titleFormat: function(title) { + return 'History: ' + title; + } + }); + + var PageRevisionView = MasterPageView.extend({ + templateName: 'revision-page', + titleFormat: function(title) { + return title + ' [' + this.model.get('rev') + ']'; + } + }); + + var PageDiffView = MasterPageView.extend({ + templateName: 'diff-page', + titleFormat: function(title) { + return title + ' [' + this.model.get('rev1') + '-' + this.model.get('rev2') + ']'; + } + }); + + var IncomingLinksView = MasterPageView.extend({ + templateName: 'inlinks-page', + titleFormat: function(title) { + return 'Incoming Links: ' + title; + } + }); + + var WikiSearchView = MasterPageView.extend({ + templateName: 'search-results' + }); + + return { + PageReadView: PageReadView, + PageEditView: PageEditView, + PageHistoryView: PageHistoryView, + IncomingLinksView: IncomingLinksView, + PageRevisionView: PageRevisionView, + PageDiffView: PageDiffView, + WikiSearchView: WikiSearchView, + LoginView: LoginView + }; +}); +
--- a/wikked/templates/index.html Sun Dec 30 20:08:11 2012 -0800 +++ b/wikked/templates/index.html Sun Dec 30 23:15:32 2012 -0800 @@ -9,10 +9,6 @@ <body> <div id="app" class="container"> </div> - <script src="/js/jquery-1.8.3.min.js"></script> - <script src="/js/underscore-min.js"></script> - <script src="/js/backbone-min.js"></script> - <script src="/js/handlebars-1.0.rc.1.js"></script> - <script src="/js/wikked.js{{cache_bust}}"></script> + <script data-main="/js/wikked.js" src="/js/require.js{{cache_bust}}"></script> </body> </html>