Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge branch 'preview'

  • Loading branch information...
commit cc418f9d58ad69dbcee4e942676cb6612b140a54 2 parents 2f58c9a + c356d1a
@dhcole dhcole authored
View
29 _includes/model.js
@@ -214,6 +214,22 @@ function getFiles(tree, path, searchstr) {
}
}
+// Load Config
+// -------
+//
+// Load _config.yml
+
+function loadConfig(user, reponame, branch, cb) {
+ var repo = getRepo(user, reponame);
+ repo.read(branch, "_config.yml", function(err, data) {
+ if (err) return cb(err);
+ app.state.jekyll = !err;
+ app.state.config = jsyaml.load(data);
+ cb();
+ });
+}
+
+
// Load Posts
// -------
//
@@ -222,15 +238,8 @@ function getFiles(tree, path, searchstr) {
function loadPosts(user, reponame, branch, path, cb) {
var repo = getRepo(user, reponame);
- function loadConfig(cb) {
- repo.read(branch, "_config.yml", function(err, data) {
- if (err) return cb(err);
- cb(null, jsyaml.load(data));
- });
- }
-
function load(repodata) {
- loadConfig(function(err, config) {
+ loadConfig(user, reponame, branch, function(err, config) {
app.state.jekyll = !err;
app.state.config = config;
@@ -243,6 +252,9 @@ function loadPosts(user, reponame, branch, path, cb) {
if (err) return cb("Branches couldn't be fetched");
app.state.path = path ? path : "";
app.state.branches = _.filter(branches, function(b) { return b !== branch });
+ repo.getSha(branch, app.state.path, function(err, sha) {
+ app.state.sha = sha;
+ });
cb(null, getFiles(tree, path, ""));
});
});
@@ -446,6 +458,7 @@ function loadPost(user, repo, branch, path, file, cb) {
var res = { raw_metadata: "", published: false, writeable: writeable() };
res.content = content.replace(/^(---\n)((.|\n)*?)\n---\n?/, function(match, dashes, frontmatter) {
res.raw_metadata = frontmatter;
+ res.metadata = jsyaml.load(frontmatter);
res.published = published(frontmatter);
return "";
}).trim();
View
4 _includes/routers/application.js
@@ -43,6 +43,10 @@ routers.Application = Backbone.Router.extend({
app.instance.posts(url.user, url.repo, url.branch, url.path);
} else if (url.mode === "new") {
app.instance.newPost(url.user, url.repo, url.branch, url.path);
+ } else if (url.mode === "preview") {
+ var parts = _.extractFilename(url.path);
+ app.state.file = parts[1];
+ app.instance.preview(url.user, url.repo, url.branch, parts[0], parts[1], url.mode);
} else {
var parts = _.extractFilename(url.path);
app.state.file = parts[1];
View
70 _includes/util.js
@@ -238,3 +238,73 @@ _.chunkedPath = function(path) {
});
}
+/* Full layout preview */
+
+_.preview = function(view) {
+ var model = view.model,
+ q = queue(1),
+ p = {
+ site: app.state.config,
+ post: model.metadata,
+ page: model.metadata,
+ content: Liquid.parse(marked(model.content)).render({
+ site: app.state.config,
+ post: model.metadata,
+ page: model.metadata
+ }) || ''
+ };
+
+ if (p.site.prose && p.site.prose.site) {
+ _(p.site.prose.site).each(function(file, key) {
+ q.defer(function(cb){
+ $.ajax({
+ cache: true,
+ dataType: 'jsonp',
+ jsonp: false,
+ jsonpCallback: 'callback',
+ url: file,
+ success: function(d) {
+ p.site[key] = d;
+ cb();
+ }
+ });
+ });
+ });
+ }
+
+ q.defer(getLayout);
+ q.await(function() {
+ var content = p.content;
+
+ // Set base URL to public site
+ content = content.replace(/(<head(?:.*)>)/, function() {
+ return arguments[1] + '<base href="' + app.state.config.prose.siteurl + '">';
+ });
+
+ document.write(content);
+ document.close();
+ });
+
+ function getLayout(cb) {
+ var file = p.page.layout;
+
+ model.repo.read(app.state.branch, '_layouts/' + file + '.html', function(err, d) {
+ if (err) return cb(err);
+ var meta = (d.split('---')[1]) ? jsyaml.load(d.split('---')[1]) : {},
+ content = (d.split('---')[2]) ? d.split('---')[2] : d,
+ template = Liquid.parse(content);
+ p.page = _(p.page).extend(meta);
+ p.content = template.render({
+ site: p.site,
+ post: p.post,
+ page: p.page,
+ content: p.content
+ });
+ if (meta.layout) q.defer(getLayout);
+ cb();
+ });
+
+ }
+
+}
+
View
75 _includes/vendor/github.js
@@ -15,24 +15,26 @@
//
// I'm not proud of this and neither should you be if you were responsible for the XMLHttpRequest spec.
- function _request(method, path, data, cb, raw) {
+ function _request(method, path, data, cb, raw, sync) {
function getURL() {
var url = API_URL + path;
return url + ((/\?/).test(url) ? "&" : "?") + (new Date()).getTime();
}
var xhr = new XMLHttpRequest();
- if (!raw) {xhr.dataType = "json"}
-
- xhr.open(method, getURL());
- xhr.onreadystatechange = function () {
- if (this.readyState == 4) {
- if (this.status >= 200 && this.status < 300 || this.status === 304) {
- cb(null, raw ? this.responseText : this.responseText ? JSON.parse(this.responseText) : true);
- } else {
- cb({request: this, error: this.status});
+ if (!raw) {xhr.dataType = "json";}
+
+ xhr.open(method, getURL(), !sync);
+ if (!sync) {
+ xhr.onreadystatechange = function () {
+ if (this.readyState == 4) {
+ if (this.status >= 200 && this.status < 300 || this.status === 304) {
+ cb(null, raw ? this.responseText : this.responseText ? JSON.parse(this.responseText) : true);
+ } else {
+ cb({request: this, error: this.status});
+ }
}
- }
+ };
}
xhr.setRequestHeader('Accept','application/vnd.github.raw');
xhr.setRequestHeader('Content-Type','application/json');
@@ -46,6 +48,7 @@
);
}
data ? xhr.send(JSON.stringify(data)) : xhr.send();
+ if (sync) return xhr.response;
}
// User API
@@ -80,10 +83,12 @@
// -------
this.show = function(username, cb) {
- _request("GET", "/users/"+username, null, function(err, res) {
+ var command = username ? "/users/"+username : "/user";
+
+ _request("GET", command, null, function(err, res) {
cb(err, res);
});
- }
+ };
// List user repositories
// -------
@@ -111,6 +116,24 @@
cb(err, res);
});
};
+
+ // Follow user
+ // -------
+
+ this.follow = function(username, cb) {
+ _request("PUT", "/user/following/"+username, null, function(err, res) {
+ cb(err, res);
+ });
+ };
+
+ // Unfollow user
+ // -------
+
+ this.unfollow = function(username, cb) {
+ _request("DELETE", "/user/following/"+username, null, function(err, res) {
+ cb(err, res);
+ });
+ };
};
@@ -305,8 +328,12 @@
// Get contents
// --------
- this.contents = function(path, cb) {
- _request("GET", repoPath + "/contents", { path: path }, cb);
+ this.contents = function(branch, path, cb) {
+ _request("GET", repoPath + "/contents?ref=" + branch, { path: path }, cb);
+ };
+
+ this.contentsSync = function(branch, path) {
+ return _request("GET", repoPath + "/contents/" + path + "?ref=" + branch, null, null, 'raw', true);
};
// Fork repository
@@ -342,7 +369,7 @@
updateTree(branch, function(err, latestCommit) {
that.getTree(latestCommit+"?recursive=true", function(err, tree) {
// Update Tree
- var newTree = _.reject(tree, function(ref) { return ref.path === path });
+ var newTree = _.reject(tree, function(ref) { return ref.path === path; });
_.each(newTree, function(ref) {
if (ref.type === "tree") delete ref.sha;
});
@@ -406,7 +433,6 @@
Github.Gist = function(options) {
var id = options.id;
- var that = this;
var gistPath = "/gists/"+id;
// Read the gist
@@ -418,6 +444,21 @@
});
};
+ // Create the gist
+ // --------
+ // {
+ // "description": "the description for this gist",
+ // "public": true,
+ // "files": {
+ // "file1.txt": {
+ // "content": "String file contents"
+ // }
+ // }
+ // }
+
+ this.create = function(options, cb){
+ _request("POST","/gists", options, cb);
+ };
// Delete the gist
// --------
View
1,426 _includes/vendor/liquid.min.js
@@ -0,0 +1,1426 @@
+{% raw %}
+
+var Liquid = {
+
+ author: 'M@ McCray <darthapo@gmail.com>',
+ version: '1.2.1',
+
+ readTemplateFile: function(path) {
+ throw ("This liquid context does not allow includes.");
+ },
+
+ registerFilters: function(filters) {
+ Liquid.Template.registerFilter(filters);
+ },
+
+ parse: function(src) {
+ return Liquid.Template.parse(src);
+ }
+
+};
+
+if (!Array.prototype.indexOf) {
+ Array.prototype.indexOf = function(obj) {
+ for (var i=0; i<this.length; i++) {
+ if (this[i] == obj) return i;
+ }
+
+ return -1;
+ };
+}
+
+if (!Array.prototype.clear) {
+ Array.prototype.clear = function() {
+ this.length = 0;
+ };
+}
+
+if (!Array.prototype.map) {
+ Array.prototype.map = function(fun /*, thisp*/) {
+ var len = this.length;
+ if (typeof fun != "function")
+ throw 'Array.map requires first argument to be a function';
+
+ var res = new Array(len);
+ var thisp = arguments[1];
+ for (var i = 0; i < len; i++) {
+ if (i in this)
+ res[i] = fun.call(thisp, this[i], i, this);
+ }
+
+ return res;
+ };
+}
+
+if (!Array.prototype.first) {
+ Array.prototype.first = function() {
+ return this[0];
+ };
+}
+
+if (!Array.prototype.last) {
+ Array.prototype.last = function() {
+ return this[this.length - 1];
+ };
+}
+
+if (!Array.prototype.flatten) {
+ Array.prototype.flatten = function() {
+ var len = this.length;
+ var arr = [];
+ for (var i = 0; i < len; i++) {
+ if (this[i] instanceof Array) {
+ arr = arr.concat(this[i]);
+ } else {
+ arr.push(this[i]);
+ }
+ }
+
+ return arr;
+ };
+}
+
+if (!Array.prototype.each) {
+ Array.prototype.each = function(fun /*, thisp*/) {
+ var len = this.length;
+ if (typeof fun != "function")
+ throw 'Array.each requires first argument to be a function';
+
+ var thisp = arguments[1];
+ for (var i = 0; i < len; i++) {
+ if (i in this)
+ fun.call(thisp, this[i], i, this);
+ }
+
+ return null;
+ };
+}
+
+if (!Array.prototype.include) {
+ Array.prototype.include = function(arg) {
+ var len = this.length;
+
+ return this.indexOf(arg) >= 0;
+ for (var i = 0; i < len; i++) {
+ if (arg == this[i]) return true;
+ }
+
+ return false;
+ };
+}
+
+
+if (!String.prototype.capitalize) {
+ String.prototype.capitalize = function() {
+ return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
+ };
+}
+
+if (!String.prototype.strip) {
+ String.prototype.strip = function() {
+ return this.replace(/^\s+/, '').replace(/\s+$/, '');
+ };
+}
+
+
+Liquid.extensions = {};
+Liquid.extensions.object = {};
+
+Liquid.extensions.object.update = function(newObj) {
+ for (var p in newObj) {
+ this[p] = newObj[p];
+ }
+
+ return this;
+};
+
+Liquid.extensions.object.hasKey = function(arg) {
+ return !!this[arg];
+};
+
+Liquid.extensions.object.hasValue = function(arg) {
+ for (var p in this) {
+ if (this[p] == arg) return true;
+ }
+
+ return false;
+};
+
+/* Simple JavaScript Inheritance
+ * By John Resig http://ejohn.org/
+ * MIT Licensed.
+ */
+(function(){
+ var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
+
+ this.Class = function(){};
+
+ Class.extend = function(prop) {
+ var _super = this.prototype;
+
+ initializing = true;
+ var prototype = new this();
+ initializing = false;
+
+ for (var name in prop) {
+ prototype[name] = typeof prop[name] == "function" &&
+ typeof _super[name] == "function" && fnTest.test(prop[name]) ?
+ (function(name, fn){
+ return function() {
+ var tmp = this._super;
+
+ this._super = _super[name];
+
+ var ret = fn.apply(this, arguments);
+ this._super = tmp;
+
+ return ret;
+ };
+ })(name, prop[name]) :
+ prop[name];
+ }
+
+ function Class() {
+ if ( !initializing && this.init )
+ this.init.apply(this, arguments);
+ }
+
+ Class.prototype = prototype;
+
+ Class.prototype.constructor = Class;
+
+ Class.extend = arguments.callee;
+
+ return Class;
+ };
+})();
+
+Liquid.Tag = Class.extend({
+
+ init: function(tagName, markup, tokens) {
+ this.tagName = tagName;
+ this.markup = markup;
+ this.nodelist = this.nodelist || [];
+ this.parse(tokens);
+ },
+
+ parse: function(tokens) {
+ },
+
+ render: function(context) {
+ return '';
+ }
+
+});
+Liquid.Block = Liquid.Tag.extend({
+
+ init: function(tagName, markup, tokens){
+ this.blockName = tagName;
+ this.blockDelimiter = "end"+ this.blockName;
+ this._super(tagName, markup, tokens);
+ },
+
+ parse: function(tokens) {
+ if (!this.nodelist) this.nodelist = [];
+ this.nodelist.clear();
+
+ var token = tokens.shift();
+ tokens.push(''); // To ensure we don't lose the last token passed in...
+ while(tokens.length) {
+
+ if( /^\{\%/.test(token) ) { // It's a tag...
+ var tagParts = token.match(/^\{\%\s*(\w+)\s*(.*)?\%\}$/);
+
+ if(tagParts) {
+ if( this.blockDelimiter == tagParts[1] ) {
+ this.endTag();
+ return;
+ }
+ if( tagParts[1] in Liquid.Template.tags ) {
+ this.nodelist.push( new Liquid.Template.tags[tagParts[1]]( tagParts[1], tagParts[2], tokens ) );
+ } else {
+ this.unknownTag( tagParts[1], tagParts[2], tokens );
+ }
+ } else {
+ throw ( "Tag '"+ token +"' was not properly terminated with: %}");
+ }
+ } else if(/^\{\{/.test(token)) { // It's a variable...
+ this.nodelist.push( this.createVariable(token) );
+ } else { //if(token != '') {
+ this.nodelist.push( token );
+ } // Ignores tokens that are empty
+ token = tokens.shift(); // Assign the next token to loop again...
+ }
+
+ this.assertMissingDelimitation();
+ },
+
+ endTag: function() {},
+
+ unknownTag: function(tag, params, tokens) {
+ switch(tag) {
+ case 'else': throw (this.blockName +" tag does not expect else tag"); break;
+ case 'end': throw ("'end' is not a valid delimiter for "+ this.blockName +" tags. use "+ this.blockDelimiter); break;
+ default: throw ("Unknown tag: "+ tag);
+ }
+ },
+
+ createVariable: function(token) {
+ var match = token.match(/^\{\{(.*)\}\}$/);
+ if(match) { return new Liquid.Variable(match[1]); }
+ else { throw ("Variable '"+ token +"' was not properly terminated with: }}"); }
+ },
+
+ render: function(context) {
+ return this.renderAll(this.nodelist, context);
+ },
+
+ renderAll: function(list, context) {
+ return (list || []).map(function(token, i){
+ var output = '';
+ try { // hmmm... feels a little heavy
+ output = ( token['render'] ) ? token.render(context) : token;
+ } catch(e) {
+ output = context.handleError(e);
+ }
+ return output;
+ });
+ },
+
+ assertMissingDelimitation: function(){
+ throw (this.blockName +" tag was never closed");
+ }
+});
+Liquid.Document = Liquid.Block.extend({
+
+ init: function(tokens){
+ this.blockDelimiter = []; // [], really?
+ this.parse(tokens);
+ },
+
+ assertMissingDelimitation: function() {
+ }
+});
+Liquid.Strainer = Class.extend({
+
+ init: function(context) {
+ this.context = context;
+ },
+
+ respondTo: function(methodName) {
+ methodName = methodName.toString();
+ if (methodName.match(/^__/)) return false;
+ if (Liquid.Strainer.requiredMethods.include(methodName)) return false;
+ return (methodName in this);
+ }
+});
+
+Liquid.Strainer.filters = {};
+
+Liquid.Strainer.globalFilter = function(filters) {
+ for (var f in filters) {
+ Liquid.Strainer.filters[f] = filters[f];
+ }
+}
+
+Liquid.Strainer.requiredMethods = ['respondTo', 'context'];
+
+Liquid.Strainer.create = function(context) {
+ var strainer = new Liquid.Strainer(context);
+ for (var f in Liquid.Strainer.filters) {
+ strainer[f] = Liquid.Strainer.filters[f];
+ }
+ return strainer;
+}
+Liquid.Context = Class.extend({
+
+ init: function(assigns, registers, rethrowErrors) {
+ this.scopes = [ assigns ? assigns : {} ];
+ this.registers = registers ? registers : {};
+ this.errors = [];
+ this.rethrowErrors = rethrowErrors;
+ this.strainer = Liquid.Strainer.create(this);
+ },
+
+ get: function(varname) {
+ return this.resolve(varname);
+ },
+
+ set: function(varname, value) {
+ this.scopes[0][varname] = value;
+ },
+
+ hasKey: function(key) {
+ return (this.resolve(key)) ? true : false;
+ },
+
+ push: function() {
+ var scpObj = {};
+ this.scopes.unshift(scpObj);
+ return scpObj // Is this right?
+ },
+
+ merge: function(newScope) {
+ return Liquid.extensions.object.update.call(this.scopes[0], newScope);
+ },
+
+ pop: function() {
+ if(this.scopes.length == 1){ throw "Context stack error"; }
+ return this.scopes.shift();
+ },
+
+ stack: function(lambda, bind) {
+ var result = null;
+ this.push();
+ try {
+ result = lambda.apply(bind ? bind : this.strainer);
+ } finally {
+ this.pop();
+ }
+ return result;
+ },
+
+ invoke: function(method, args) {
+ if( this.strainer.respondTo(method) ) {
+ var result = this.strainer[method].apply(this.strainer, args);
+ return result;
+ } else {
+ return (args.length == 0) ? null : args[0]; // was: $pick
+ }
+ },
+
+ resolve: function(key) {
+ switch(key) {
+ case null:
+ case 'nil':
+ case 'null':
+ case '':
+ return null;
+
+ case 'true':
+ return true;
+
+ case 'false':
+ return false;
+
+ case 'blank':
+ case 'empty':
+ return '';
+
+ default:
+ if((/^'(.*)'$/).test(key)) // Single quoted strings
+ { return key.replace(/^'(.*)'$/, '$1'); }
+
+ else if((/^"(.*)"$/).test(key)) // Double quoted strings
+ { return key.replace(/^"(.*)"$/, '$1'); }
+
+ else if((/^(\d+)$/).test(key)) // Integer...
+ { return parseInt( key.replace(/^(\d+)$/ , '$1') ); }
+
+ else if((/^(\d[\d\.]+)$/).test(key)) // Float...
+ { return parseFloat( key.replace(/^(\d[\d\.]+)$/, '$1') ); }
+
+ else if((/^\((\S+)\.\.(\S+)\)$/).test(key)) {// Ranges
+ var range = key.match(/^\((\S+)\.\.(\S+)\)$/),
+ left = parseInt(range[1]),
+ right = parseInt(range[2]),
+ arr = [];
+ if (isNaN(left) || isNaN(right)) {
+ left = range[1].charCodeAt(0);
+ right = range[2].charCodeAt(0);
+
+ var limit = right-left+1;
+ for (var i=0; i<limit; i++) arr.push(String.fromCharCode(i+left));
+ } else { // okay to make array
+ var limit = right-left+1;
+ for (var i=0; i<limit; i++) arr.push(i+left);
+ }
+ return arr;
+ } else {
+ var result = this.variable(key);
+ return result;
+ }
+ }
+ },
+
+ findVariable: function(key) {
+ for (var i=0; i < this.scopes.length; i++) {
+ var scope = this.scopes[i];
+ if( scope && typeof(scope[key]) !== 'undefined' ) {
+ var variable = scope[key];
+ if(typeof(variable) == 'function'){
+ variable = variable.apply(this);
+ scope[key] = variable;
+ }
+ if(variable && typeof(variable) == 'object' && ('toLiquid' in variable)) {
+ variable = variable.toLiquid();
+ }
+ if(variable && typeof(variable) == 'object' && ('setContext' in variable)){
+ variable.setContext(self);
+ }
+ return variable;
+ }
+ };
+ return null;
+ },
+
+ variable: function(markup) {
+ if(typeof markup != 'string') {
+ return null;
+ }
+
+ var parts = markup.match( /\[[^\]]+\]|(?:[\w\-]\??)+/g ),
+ firstPart = parts.shift(),
+ squareMatch = firstPart.match(/^\[(.*)\]$/);
+
+ if(squareMatch)
+ { firstPart = this.resolve( squareMatch[1] ); }
+
+ var object = this.findVariable(firstPart),
+ self = this;
+
+ if(object) {
+ parts.each(function(part){
+ var squareMatch = part.match(/^\[(.*)\]$/);
+ if(squareMatch) {
+ var part = self.resolve( squareMatch[1] );
+ if( typeof(object[part]) == 'function'){ object[part] = object[part].apply(this); }// Array?
+ object = object[part];
+ if(typeof(object) == 'object' && ('toLiquid' in object)){ object = object.toLiquid(); }
+ } else {
+ if( (typeof(object) == 'object' || typeof(object) == 'hash') && (part in object)) {
+ var res = object[part];
+ if( typeof(res) == 'function'){ res = object[part] = res.apply(self) ; }
+ if( typeof(res) == 'object' && ('toLiquid' in res)){ object = res.toLiquid(); }
+ else { object = res; }
+ }
+ else if( (/^\d+$/).test(part) ) {
+ var pos = parseInt(part);
+ if( typeof(object[pos]) == 'function') { object[pos] = object[pos].apply(self); }
+ if(typeof(object[pos]) == 'object' && typeof(object[pos]) == 'object' && ('toLiquid' in object[pos])) { object = object[pos].toLiquid(); }
+ else { object = object[pos]; }
+ }
+ else if( object && typeof(object[part]) == 'function' && ['length', 'size', 'first', 'last'].include(part) ) {
+ object = object[part].apply(part);
+ if('toLiquid' in object){ object = object.toLiquid(); }
+ }
+ else {
+ return object = null;
+ }
+ if(typeof(object) == 'object' && ('setContext' in object)){ object.setContext(self); }
+ }
+ });
+ }
+ return object;
+ },
+
+ addFilters: function(filters) {
+ filters = filters.flatten();
+ filters.each(function(f){
+ if(typeof(f) != 'object'){ throw ("Expected object but got: "+ typeof(f)) }
+ this.strainer.addMethods(f);
+ });
+ },
+
+ handleError: function(err) {
+ this.errors.push(err);
+ if(this.rethrowErrors){ throw err; }
+ return "Liquid error: " + (err.message ? err.message : (err.description ? err.description : err));
+ }
+
+});
+Liquid.Template = Class.extend({
+
+ init: function() {
+ this.root = null;
+ this.registers = {};
+ this.assigns = {};
+ this.errors = [];
+ this.rethrowErrors = false;
+ },
+
+ parse: function(src) {
+ this.root = new Liquid.Document( Liquid.Template.tokenize(src) );
+ return this;
+ },
+
+ render: function() {
+ if(!this.root){ return ''; }
+ var args = {
+ ctx: arguments[0],
+ filters: arguments[1],
+ registers: arguments[2]
+ }
+ var context = null;
+
+ if(args.ctx instanceof Liquid.Context ) {
+ context = args.ctx;
+ this.assigns = context.assigns;
+ this.registers = context.registers;
+ } else {
+ if(args.ctx){
+ Liquid.extensions.object.update.call(this.assigns, args.ctx);
+ }
+ if(args.registers){
+ Liquid.extensions.object.update.call(this.registers, args.registers);
+ }
+ context = new Liquid.Context(this.assigns, this.registers, this.rethrowErrors)
+ }
+
+ if(args.filters){ context.addFilters(arg.filters); }
+
+ try {
+ return this.root.render(context).join('');
+ } finally {
+ this.errors = context.errors;
+ }
+ },
+
+ renderWithErrors: function() {
+ var savedRethrowErrors = this.rethrowErrors;
+ this.rethrowErrors = true;
+ var res = this.render.apply(this, arguments);
+ this.rethrowErrors = savedRethrowErrors;
+ return res;
+ }
+});
+
+
+Liquid.Template.tags = {};
+
+Liquid.Template.registerTag = function(name, klass) {
+ Liquid.Template.tags[ name ] = klass;
+}
+
+Liquid.Template.registerFilter = function(filters) {
+ Liquid.Strainer.globalFilter(filters)
+}
+
+Liquid.Template.tokenize = function(src) {
+ var tokens = src.split( /(\{\%.*?\%\}|\{\{.*?\}\}?)/ );
+ if(tokens[0] == ''){ tokens.shift(); }
+ return tokens;
+}
+
+
+Liquid.Template.parse = function(src) {
+ return (new Liquid.Template()).parse(src);
+}
+Liquid.Variable = Class.extend({
+
+ init: function(markup) {
+ this.markup = markup;
+ this.name = null;
+ this.filters = [];
+ var self = this;
+ var match = markup.match(/\s*("[^"]+"|'[^']+'|[^\s,|]+)/);
+ if( match ) {
+ this.name = match[1];
+ var filterMatches = markup.match(/\|\s*(.*)/);
+ if(filterMatches) {
+ var filters = filterMatches[1].split(/\|/);
+ filters.each(function(f){
+ var matches = f.match(/\s*(\w+)/);
+ if(matches) {
+ var filterName = matches[1];
+ var filterArgs = [];
+ (f.match(/(?:[:|,]\s*)("[^"]+"|'[^']+'|[^\s,|]+)/g) || []).flatten().each(function(arg){
+ var cleanupMatch = arg.match(/^[\s|:|,]*(.*?)[\s]*$/);
+ if(cleanupMatch)
+ { filterArgs.push( cleanupMatch[1] );}
+ });
+ self.filters.push( [filterName, filterArgs] );
+ }
+ });
+ }
+ }
+ },
+
+ render: function(context) {
+ if(this.name == null){ return ''; }
+ var output = context.get(this.name);
+ this.filters.each(function(filter) {
+ var filterName = filter[0],
+ filterArgs = (filter[1] || []).map(function(arg){
+ return context.get(arg);
+ });
+ filterArgs.unshift(output); // Push in input value into the first argument spot...
+ output = context.invoke(filterName, filterArgs);
+ });
+
+ return output;
+ }
+});
+Liquid.Condition = Class.extend({
+
+ init: function(left, operator, right) {
+ this.left = left;
+ this.operator = operator;
+ this.right = right;
+ this.childRelation = null;
+ this.childCondition = null;
+ this.attachment = null;
+ },
+
+ evaluate: function(context) {
+ context = context || new Liquid.Context();
+ var result = this.interpretCondition(this.left, this.right, this.operator, context);
+ switch(this.childRelation) {
+ case 'or':
+ return (result || this.childCondition.evaluate(context));
+ case 'and':
+ return (result && this.childCondition.evaluate(context));
+ default:
+ return result;
+ }
+ },
+
+ or: function(condition) {
+ this.childRelation = 'or';
+ this.childCondition = condition;
+ },
+
+ and: function(condition) {
+ this.childRelation = 'and';
+ this.childCondition = condition;
+ },
+
+ attach: function(attachment) {
+ this.attachment = attachment;
+ return this.attachment;
+ },
+
+ isElse: false,
+
+ interpretCondition: function(left, right, op, context) {
+ if(!op)
+ { return context.get(left); }
+
+ left = context.get(left);
+ right = context.get(right);
+ op = Liquid.Condition.operators[op];
+ if(!op)
+ { throw ("Unknown operator "+ op); }
+
+ var results = op(left, right);
+ return results;
+ },
+
+ toString: function() {
+ return "<Condition "+ this.left +" "+ this.operator +" "+ this.right +">";
+ }
+
+});
+
+Liquid.Condition.operators = {
+ '==': function(l,r) { return (l == r); },
+ '=': function(l,r) { return (l == r); },
+ '!=': function(l,r) { return (l != r); },
+ '<>': function(l,r) { return (l != r); },
+ '<': function(l,r) { return (l < r); },
+ '>': function(l,r) { return (l > r); },
+ '<=': function(l,r) { return (l <= r); },
+ '>=': function(l,r) { return (l >= r); },
+
+ 'contains': function(l,r) { return l.include(r); },
+ 'hasKey': function(l,r) { return Liquid.extensions.object.hasKey.call(l, r); },
+ 'hasValue': function(l,r) { return Liquid.extensions.object.hasValue.call(l, r); }
+}
+
+Liquid.ElseCondition = Liquid.Condition.extend({
+
+ isElse: true,
+
+ evaluate: function(context) {
+ return true;
+ },
+
+ toString: function() {
+ return "<ElseCondition>";
+ }
+
+});
+Liquid.Drop = Class.extend({
+ setContext: function(context) {
+ this.context = context;
+ },
+ beforeMethod: function(method) {
+
+ },
+ invokeDrop: function(method) {
+ var results = this.beforeMethod();
+ if( !results && (method in this) )
+ { results = this[method].apply(this); }
+ return results;
+ },
+ hasKey: function(name) {
+ return true;
+ }
+});
+var hackObjectEach = function(fun /*, thisp*/) {
+ if (typeof fun != "function")
+ throw 'Object.each requires first argument to be a function';
+
+ var i = 0;
+ var thisp = arguments[1];
+ for (var p in this) {
+ var value = this[p], pair = [p, value];
+ pair.key = p;
+ pair.value = value;
+ fun.call(thisp, pair, i, this);
+ i++;
+ }
+
+ return null;
+};
+
+Liquid.Template.registerTag( 'assign', Liquid.Tag.extend({
+
+ tagSyntax: /((?:\(?[\w\-\.\[\]]\)?)+)\s*=\s*((?:"[^"]+"|'[^']+'|[^\s,|]+)+)/,
+
+ init: function(tagName, markup, tokens) {
+ var parts = markup.match(this.tagSyntax)
+ if( parts ) {
+ this.to = parts[1];
+ this.from = parts[2];
+ } else {
+ throw ("Syntax error in 'assign' - Valid syntax: assign [var] = [source]");
+ }
+ this._super(tagName, markup, tokens)
+ },
+ render: function(context) {
+ context.scopes.last()[this.to.toString()] = context.get(this.from);
+ return '';
+ }
+}));
+
+Liquid.Template.registerTag( 'cache', Liquid.Block.extend({
+ tagSyntax: /(\w+)/,
+
+ init: function(tagName, markup, tokens) {
+ var parts = markup.match(this.tagSyntax)
+ if( parts ) {
+ this.to = parts[1];
+ } else {
+ throw ("Syntax error in 'cache' - Valid syntax: cache [var]");
+ }
+ this._super(tagName, markup, tokens);
+ },
+ render: function(context) {
+ var output = this._super(context);
+ context.scopes.last()[this.to] = [output].flatten().join('');
+ return '';
+ }
+}));
+
+
+Liquid.Template.registerTag( 'capture', Liquid.Block.extend({
+ tagSyntax: /(\w+)/,
+
+ init: function(tagName, markup, tokens) {
+ var parts = markup.match(this.tagSyntax)
+ if( parts ) {
+ this.to = parts[1];
+ } else {
+ throw ("Syntax error in 'capture' - Valid syntax: capture [var]");
+ }
+ this._super(tagName, markup, tokens);
+ },
+ render: function(context) {
+ var output = this._super(context);
+ context.set( this.to, [output].flatten().join('') );
+ return '';
+ }
+}));
+
+Liquid.Template.registerTag( 'case', Liquid.Block.extend({
+
+ tagSyntax : /("[^"]+"|'[^']+'|[^\s,|]+)/,
+ tagWhenSyntax : /("[^"]+"|'[^']+'|[^\s,|]+)(?:(?:\s+or\s+|\s*\,\s*)("[^"]+"|'[^']+'|[^\s,|]+.*))?/,
+
+ init: function(tagName, markup, tokens) {
+ this.blocks = [];
+ this.nodelist = [];
+
+ var parts = markup.match(this.tagSyntax)
+ if( parts ) {
+ this.left = parts[1];
+ } else {
+ throw ("Syntax error in 'case' - Valid syntax: case [condition]");
+ }
+
+ this._super(tagName, markup, tokens);
+ },
+ unknownTag: function(tag, markup, tokens) {
+ switch(tag) {
+ case 'when':
+ this.recordWhenCondition(markup);
+ break;
+ case 'else':
+ this.recordElseCondition(markup);
+ break;
+ default:
+ this._super(tag, markup, tokens);
+ }
+
+ },
+ render: function(context) {
+ var self = this,
+ output = [],
+ execElseBlock = true;
+
+ context.stack(function(){
+ for (var i=0; i < self.blocks.length; i++) {
+ var block = self.blocks[i];
+ if( block.isElse ) {
+ if(execElseBlock == true){ output = [output, self.renderAll(block.attachment, context)].flatten(); }
+ return output;
+ } else if( block.evaluate(context) ) {
+ execElseBlock = false;
+ output = [output, self.renderAll(block.attachment, context)].flatten();
+ }
+ };
+ });
+
+ return output;
+ },
+ recordWhenCondition: function(markup) {
+ while(markup) {
+ var parts = markup.match(this.tagWhenSyntax);
+ if(!parts) {
+ throw ("Syntax error in tag 'case' - Valid when condition: {% when [condition] [or condition2...] %} ");
+ }
+
+ markup = parts[2];
+
+ var block = new Liquid.Condition(this.left, '==', parts[1]);
+ this.blocks.push( block );
+ this.nodelist = block.attach([]);
+ }
+ },
+ recordElseCondition: function(markup) {
+ if( (markup || '').strip() != '') {
+ throw ("Syntax error in tag 'case' - Valid else condition: {% else %} (no parameters) ")
+ }
+ var block = new Liquid.ElseCondition();
+ this.blocks.push(block);
+ this.nodelist = block.attach([]);
+ }
+}));
+
+Liquid.Template.registerTag( 'comment', Liquid.Block.extend({
+ render: function(context) {
+ return '';
+ }
+}));
+
+Liquid.Template.registerTag( 'cycle', Liquid.Tag.extend({
+
+ tagSimpleSyntax: /"[^"]+"|'[^']+'|[^\s,|]+/,
+ tagNamedSyntax: /("[^"]+"|'[^']+'|[^\s,|]+)\s*\:\s*(.*)/,
+
+ init: function(tag, markup, tokens) {
+ var matches, variables;
+ matches = markup.match(this.tagNamedSyntax);
+ if(matches) {
+ this.variables = this.variablesFromString(matches[2]);
+ this.name = matches[1];
+ } else {
+ matches = markup.match(this.tagSimpleSyntax);
+ if(matches) {
+ this.variables = this.variablesFromString(markup);
+ this.name = "'"+ this.variables.toString() +"'";
+ } else {
+ throw ("Syntax error in 'cycle' - Valid syntax: cycle [name :] var [, var2, var3 ...]");
+ }
+ }
+ this._super(tag, markup, tokens);
+ },
+
+ render: function(context) {
+ var self = this,
+ key = context.get(self.name),
+ output = '';
+
+ if(!context.registers['cycle']) {
+ context.registers['cycle'] = {};
+ }
+
+ if(!context.registers['cycle'][key]) {
+ context.registers['cycle'][key] = 0;
+ }
+
+ context.stack(function(){
+ var iter = context.registers['cycle'][key],
+ results = context.get( self.variables[iter] );
+ iter += 1;
+ if(iter == self.variables.length){ iter = 0; }
+ context.registers['cycle'][key] = iter;
+ output = results;
+ });
+
+ return output;
+ },
+
+ variablesFromString: function(markup) {
+ return markup.split(',').map(function(varname){
+ var match = varname.match(/\s*("[^"]+"|'[^']+'|[^\s,|]+)\s*/);
+ return (match[1]) ? match[1] : null
+ });
+ }
+}));
+
+Liquid.Template.registerTag( 'for', Liquid.Block.extend({
+ tagSyntax: /(\w+)\s+in\s+((?:\(?[\w\-\.\[\]]\)?)+)/,
+
+ init: function(tag, markup, tokens) {
+ var matches = markup.match(this.tagSyntax);
+ if(matches) {
+ this.variableName = matches[1];
+ this.collectionName = matches[2];
+ this.name = this.variableName +"-"+ this.collectionName;
+ this.attributes = {};
+ var attrmarkup = markup.replace(this.tagSyntax, '');
+ var attMatchs = markup.match(/(\w*?)\s*\:\s*("[^"]+"|'[^']+'|[^\s,|]+)/g);
+ if(attMatchs) {
+ attMatchs.each(function(pair){
+ pair = pair.split(":");
+ this.attributes[pair[0].strip()] = pair[1].strip();
+ }, this);
+ }
+ } else {
+ throw ("Syntax error in 'for loop' - Valid syntax: for [item] in [collection]");
+ }
+ this._super(tag, markup, tokens);
+ },
+
+ render: function(context) {
+ var self = this,
+ output = [],
+ collection = (context.get(this.collectionName) || []),
+ range = [0, collection.length];
+
+ if(!context.registers['for']){ context.registers['for'] = {}; }
+
+ if(this.attributes['limit'] || this.attributes['offset']) {
+ var offset = 0,
+ limit = 0,
+ rangeEnd = 0,
+ segment = null;
+
+ if(this.attributes['offset'] == 'continue')
+ { offset = context.registers['for'][this.name]; }
+ else
+ { offset = context.get( this.attributes['offset'] ) || 0; }
+
+ limit = context.get( this.attributes['limit'] );
+
+ rangeEnd = (limit) ? offset + limit + 1 : collection.length;
+ range = [ offset, rangeEnd - 1 ];
+
+ context.registers['for'][this.name] = rangeEnd;
+ }
+
+ segment = collection.slice(range[0], range[1]);
+ if(!segment || segment.length == 0){ return ''; }
+
+ context.stack(function(){
+ var length = segment.length;
+
+ segment.each(function(item, index){
+ context.set( self.variableName, item );
+ context.set( 'forloop', {
+ name: self.name,
+ length: length,
+ index: (index + 1),
+ index0: index,
+ rindex: (length - index),
+ rindex0:(length - index - 1),
+ first: (index == 0),
+ last: (index == (length - 1))
+ });
+ output.push( (self.renderAll(self.nodelist, context) || []).join('') );
+ });
+ });
+
+ return [output].flatten().join('');
+ }
+}));
+
+Liquid.Template.registerTag( 'if', Liquid.Block.extend({
+
+ tagSyntax: /("[^"]+"|'[^']+'|[^\s,|]+)\s*([=!<>a-z_]+)?\s*("[^"]+"|'[^']+'|[^\s,|]+)?/,
+
+ init: function(tag, markup, tokens) {
+ this.nodelist = [];
+ this.blocks = [];
+ this.pushBlock('if', markup);
+ this._super(tag, markup, tokens);
+ },
+
+ unknownTag: function(tag, markup, tokens) {
+ if( ['elsif', 'else'].include(tag) ) {
+ this.pushBlock(tag, markup);
+ } else {
+ this._super(tag, markup, tokens);
+ }
+ },
+
+ render: function(context) {
+ var self = this,
+ output = '';
+ context.stack(function(){
+ for (var i=0; i < self.blocks.length; i++) {
+ var block = self.blocks[i];
+ if( block.evaluate(context) ) {
+ output = self.renderAll(block.attachment, context);
+ return;
+ }
+ };
+ })
+ return [output].flatten().join('');
+ },
+
+ pushBlock: function(tag, markup) {
+ var block;
+ if(tag == 'else') {
+ block = new Liquid.ElseCondition();
+ } else {
+ var expressions = markup.split(/\b(and|or)\b/).reverse(),
+ expMatches = expressions.shift().match( this.tagSyntax );
+
+ if(!expMatches){ throw ("Syntax Error in tag '"+ tag +"' - Valid syntax: "+ tag +" [expression]"); }
+
+ var condition = new Liquid.Condition(expMatches[1], expMatches[2], expMatches[3]);
+
+ while(expressions.length > 0) {
+ var operator = expressions.shift(),
+ expMatches = expressions.shift().match( this.tagSyntax );
+ if(!expMatches){ throw ("Syntax Error in tag '"+ tag +"' - Valid syntax: "+ tag +" [expression]"); }
+
+ var newCondition = new Liquid.Condition(expMatches[1], expMatches[2], expMatches[3]);
+ newCondition[operator](condition);
+ condition = newCondition;
+ }
+
+ block = condition;
+ }
+ block.attach([]);
+ this.blocks.push(block);
+ this.nodelist = block.attachment;
+ }
+}));
+
+Liquid.Template.registerTag( 'ifchanged', Liquid.Block.extend({
+
+ render: function(context) {
+ var self = this,
+ output = '';
+ context.stack(function(){
+ var results = self.renderAll(self.nodelist, context).join('');
+ if(results != context.registers['ifchanged']) {
+ output = results;
+ context.registers['ifchanged'] = output;
+ }
+ });
+ return output;
+ }
+}));
+
+Liquid.Template.registerTag( 'include', Liquid.Tag.extend({
+
+ tagSyntax: /((?:"[^"]+"|'[^']+'|[^\s,|]+)+)(\s+(?:with|for)\s+((?:"[^"]+"|'[^']+'|[^\s,|]+)+))?/,
+
+ init: function(tag, markup, tokens) {
+ var matches = (markup || '').match(this.tagSyntax);
+ if(matches) {
+ this.templateName = matches[1];
+ this.templateNameVar = this.templateName.substring(1, this.templateName.length - 1);
+ this.variableName = matches[3];
+ this.attributes = {};
+
+ var attMatchs = markup.match(/(\w*?)\s*\:\s*("[^"]+"|'[^']+'|[^\s,|]+)/g);
+ if(attMatchs) {
+ attMatchs.each(function(pair){
+ pair = pair.split(":");
+ this.attributes[pair[0].strip()] = pair[1].strip();
+ }, this);
+ }
+ } else {
+ throw ("Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]");
+ }
+ this._super(tag, markup, tokens);
+ },
+
+ render: function(context) {
+ var self = this,
+ source = Liquid.readTemplateFile( context.get(this.templateName) ),
+ partial = Liquid.parse(source),
+ variable = context.get((this.variableName || this.templateNameVar)),
+ output = '';
+ context.stack(function(){
+ self.attributes.each = hackObjectEach;
+ self.attributes.each(function(pair){
+ context.set(pair.key, context.get(pair.value));
+ })
+
+ if(variable instanceof Array) {
+ output = variable.map(function(variable){
+ context.set( self.templateNameVar, variable );
+ return partial.render(context);
+ });
+ } else {
+ context.set(self.templateNameVar, variable);
+ output = partial.render(context);
+ }
+ });
+ output = [output].flatten().join('');
+ return output
+ }
+}));
+
+Liquid.Template.registerTag( 'unless', Liquid.Template.tags['if'].extend({
+
+ render: function(context) {
+ var self = this,
+ output = '';
+ context.stack(function(){
+ var block = self.blocks[0];
+ if( !block.evaluate(context) ) {
+ output = self.renderAll(block.attachment, context);
+ return;
+ }
+ for (var i=1; i < self.blocks.length; i++) {
+ var block = self.blocks[i];
+ if( block.evaluate(context) ) {
+ output = self.renderAll(block.attachment, context);
+ return;
+ }
+ };
+ })
+ return output;
+ }
+}));
+Liquid.Template.registerFilter({
+
+ size: function(iterable) {
+ return (iterable['length']) ? iterable.length : 0;
+ },
+
+ downcase: function(input) {
+ return input.toString().toLowerCase();
+ },
+
+ upcase: function(input) {
+ return input.toString().toUpperCase();
+ },
+
+ capitalize: function(input) {
+ return input.toString().capitalize();
+ },
+
+ escape: function(input) {
+ input = input.toString();
+ input = input.replace(/&/g, '&amp;');
+ input = input.replace(/</g, '&lt;');
+ input = input.replace(/>/g, '&gt;');
+ input = input.replace(/"/g, '&quot;');
+ return input;
+ },
+
+ h: function(input) {
+ input = input.toString();
+ input = input.replace(/&/g, '&amp;');
+ input = input.replace(/</g, '&lt;');
+ input = input.replace(/>/g, '&gt;');
+ input = input.replace(/"/g, '&quot;');
+ return input;
+ },
+
+ truncate: function(input, length, string) {
+ if(!input || input == ''){ return ''; }
+ length = length || 50;
+ string = string || "...";
+
+ var seg = input.slice(0, length);
+ return (input.length > length ?
+ input.slice(0, length) + string :
+ input);
+ },
+
+ truncatewords: function(input, words, string) {
+ if(!input || input == ''){ return ''; }
+ words = parseInt(words || 15);
+ string = string || '...';
+ var wordlist = input.toString().split(" "),
+ l = Math.max((words), 0);
+ return (wordlist.length > l) ? wordlist.slice(0,l).join(' ') + string : input;
+ },
+
+ truncate_words: function(input, words, string) {
+ if(!input || input == ''){ return ''; }
+ words = parseInt(words || 15);
+ string = string || '...';
+ var wordlist = input.toString().split(" "),
+ l = Math.max((words), 0);
+ return (wordlist.length > l) ? wordlist.slice(0,l).join(' ') + string : input;
+ },
+
+ strip_html: function(input) {
+ return input.toString().replace(/<.*?>/g, '');
+ },
+
+ strip_newlines: function(input) {
+ return input.toString().replace(/\n/g, '')
+ },
+
+ join: function(input, separator) {
+ separator = separator || ' ';
+ return input.join(separator);
+ },
+
+ sort: function(input) {
+ return input.sort();
+ },
+
+ reverse: function(input) {
+ return input.reverse();
+ },
+
+ replace: function(input, string, replacement) {
+ replacement = replacement || '';
+ return input.toString().replace(new RegExp(string, 'g'), replacement);
+ },
+
+ replace_first: function(input, string, replacement) {
+ replacement = replacement || '';
+ return input.toString().replace(new RegExp(string, ""), replacement);
+ },
+
+ newline_to_br: function(input) {
+ return input.toString().replace(/\n/g, "<br/>\n");
+ },
+
+ date: function(input, format) {
+ var date;
+ if( input instanceof Date ){ date = input; }
+ if(!(date instanceof Date) && input == 'now'){ date = new Date(); }
+ if(!(date instanceof Date)){ date = new Date(input); }
+ if(!(date instanceof Date)){ date = new Date(Date.parse(input));}
+ if(!(date instanceof Date)){ return input; } // Punt
+ return date.strftime(format);
+ },
+
+ first: function(input) {
+ return input[0];
+ },
+
+ last: function(input) {
+ input = input;
+ return input[input.length -1];
+ }
+});
+
+
+if(!(new Date()).strftime) {(function(){
+Date.ext={};Date.ext.util={};Date.ext.util.xPad=function(x,pad,r){if(typeof (r)=="undefined"){r=10}for(;parseInt(x,10)<r&&r>1;r/=10){x=pad.toString()+x}return x.toString()};Date.prototype.locale="en-GB";if(document.getElementsByTagName("html")&&document.getElementsByTagName("html")[0].lang){Date.prototype.locale=document.getElementsByTagName("html")[0].lang}Date.ext.locales={};Date.ext.locales.en={a:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],A:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],b:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],B:["January","February","March","April","May","June","July","August","September","October","November","December"],c:"%a %d %b %Y %T %Z",p:["AM","PM"],P:["am","pm"],x:"%d/%m/%y",X:"%T"};Date.ext.locales["en-US"]=Date.ext.locales.en;Date.ext.locales["en-US"].c="%a %d %b %Y %r %Z";Date.ext.locales["en-US"].x="%D";Date.ext.locales["en-US"].X="%r";Date.ext.locales["en-GB"]=Date.ext.locales.en;Date.ext.locales["en-AU"]=Date.ext.locales["en-GB"];Date.ext.formats={a:function(d){return Date.ext.locales[d.locale].a[d.getDay()]},A:function(d){return Date.ext.locales[d.locale].A[d.getDay()]},b:function(d){return Date.ext.locales[d.locale].b[d.getMonth()]},B:function(d){return Date.ext.locales[d.locale].B[d.getMonth()]},c:"toLocaleString",C:function(d){return Date.ext.util.xPad(parseInt(d.getFullYear()/100,10),0)},d:["getDate","0"],e:["getDate"," "],g:function(d){return Date.ext.util.xPad(parseInt(Date.ext.util.G(d)/100,10),0)},G:function(d){var y=d.getFullYear();var V=parseInt(Date.ext.formats.V(d),10);var W=parseInt(Date.ext.formats.W(d),10);if(W>V){y++}else{if(W===0&&V>=52){y--}}return y},H:["getHours","0"],I:function(d){var I=d.getHours()%12;return Date.ext.util.xPad(I===0?12:I,0)},j:function(d){var ms=d-new Date(""+d.getFullYear()+"/1/1 GMT");ms+=d.getTimezoneOffset()*60000;var doy=parseInt(ms/60000/60/24,10)+1;return Date.ext.util.xPad(doy,0,100)},m:function(d){return Date.ext.util.xPad(d.getMonth()+1,0)},M:["getMinutes","0"],p:function(d){return Date.ext.locales[d.locale].p[d.getHours()>=12?1:0]},P:function(d){return Date.ext.locales[d.locale].P[d.getHours()>=12?1:0]},S:["getSeconds","0"],u:function(d){var dow=d.getDay();return dow===0?7:dow},U:function(d){var doy=parseInt(Date.ext.formats.j(d),10);var rdow=6-d.getDay();var woy=parseInt((doy+rdow)/7,10);return Date.ext.util.xPad(woy,0)},V:function(d){var woy=parseInt(Date.ext.formats.W(d),10);var dow1_1=(new Date(""+d.getFullYear()+"/1/1")).getDay();var idow=woy+(dow1_1>4||dow1_1<=1?0:1);if(idow==53&&(new Date(""+d.getFullYear()+"/12/31")).getDay()<4){idow=1}else{if(idow===0){idow=Date.ext.formats.V(new Date(""+(d.getFullYear()-1)+"/12/31"))}}return Date.ext.util.xPad(idow,0)},w:"getDay",W:function(d){var doy=parseInt(Date.ext.formats.j(d),10);var rdow=7-Date.ext.formats.u(d);var woy=parseInt((doy+rdow)/7,10);return Date.ext.util.xPad(woy,0,10)},y:function(d){return Date.ext.util.xPad(d.getFullYear()%100,0)},Y:"getFullYear",z:function(d){var o=d.getTimezoneOffset();var H=Date.ext.util.xPad(parseInt(Math.abs(o/60),10),0);var M=Date.ext.util.xPad(o%60,0);return(o>0?"-":"+")+H+M},Z:function(d){return d.toString().replace(/^.*\(([^)]+)\)$/,"$1")},"%":function(d){return"%"}};Date.ext.aggregates={c:"locale",D:"%m/%d/%y",h:"%b",n:"\n",r:"%I:%M:%S %p",R:"%H:%M",t:"\t",T:"%H:%M:%S",x:"locale",X:"locale"};Date.ext.aggregates.z=Date.ext.formats.z(new Date());Date.ext.aggregates.Z=Date.ext.formats.Z(new Date());Date.ext.unsupported={};Date.prototype.strftime=function(fmt){if(!(this.locale in Date.ext.locales)){if(this.locale.replace(/-[a-zA-Z]+$/,"") in Date.ext.locales){this.locale=this.locale.replace(/-[a-zA-Z]+$/,"")}else{this.locale="en-GB"}}var d=this;while(fmt.match(/%[cDhnrRtTxXzZ]/)){fmt=fmt.replace(/%([cDhnrRtTxXzZ])/g,function(m0,m1){var f=Date.ext.aggregates[m1];return(f=="locale"?Date.ext.locales[d.locale][m1]:f)})}var str=fmt.replace(/%([aAbBCdegGHIjmMpPSuUVwWyY%])/g,function(m0,m1){var f=Date.ext.formats[m1];if(typeof (f)=="string"){return d[f]()}else{if(typeof (f)=="function"){return f.call(d,d)}else{if(typeof (f)=="object"&&typeof (f[0])=="string"){return Date.ext.util.xPad(d[f[0]](),f[1])}else{return m1}}}});d=null;return str};
+})();}
+/* Cross-Browser Split 1.0.1
+(c) Steven Levithan <stevenlevithan.com>; MIT License
+An ECMA-compliant, uniform cross-browser split method
+
+Fixes problems with IE's broken String#split method.
+See http://blog.stevenlevithan.com/archives/cross-browser-split
+*/
+
+var cbSplit;
+
+if (!cbSplit) {
+
+cbSplit = function (str, separator, limit) {
+ if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
+ return cbSplit._nativeSplit.call(str, separator, limit);
+ }
+
+ var output = [],
+ lastLastIndex = 0,
+ flags = (separator.ignoreCase ? "i" : "") +
+ (separator.multiline ? "m" : "") +
+ (separator.sticky ? "y" : ""),
+ separator = RegExp(separator.source, flags + "g"), // make `global` and avoid `lastIndex` issues by working with a copy
+ separator2, match, lastIndex, lastLength;
+
+ str = str + ""; // type conversion
+ if (!cbSplit._compliantExecNpcg) {
+ separator2 = RegExp("^" + separator.source + "$(?!\\s)", flags); // doesn't need /g or /y, but they don't hurt
+ }
+
+ /* behavior for `limit`: if it's...
+ - `undefined`: no limit.
+ - `NaN` or zero: return an empty array.
+ - a positive number: use `Math.floor(limit)`.
+ - a negative number: no limit.
+ - other: type-convert, then use the above rules. */
+ if (limit === undefined || +limit < 0) {
+ limit = Infinity;
+ } else {
+ limit = Math.floor(+limit);
+ if (!limit) {
+ return [];
+ }
+ }
+
+ while (match = separator.exec(str)) {
+ lastIndex = match.index + match[0].length; // `separator.lastIndex` is not reliable cross-browser
+
+ if (lastIndex > lastLastIndex) {
+ output.push(str.slice(lastLastIndex, match.index));
+
+ if (!cbSplit._compliantExecNpcg && match.length > 1) {
+ match[0].replace(separator2, function () {
+ for (var i = 1; i < arguments.length - 2; i++) {
+ if (arguments[i] === undefined) {
+ match[i] = undefined;
+ }
+ }
+ });
+ }
+
+ if (match.length > 1 && match.index < str.length) {
+ Array.prototype.push.apply(output, match.slice(1));
+ }
+
+ lastLength = match[0].length;
+ lastLastIndex = lastIndex;
+
+ if (output.length >= limit) {
+ break;
+ }
+ }
+
+ if (separator.lastIndex === match.index) {
+ separator.lastIndex++; // avoid an infinite loop
+ }
+ }
+
+ if (lastLastIndex === str.length) {
+ if (lastLength || !separator.test("")) {
+ output.push("");
+ }
+ } else {
+ output.push(str.slice(lastLastIndex));
+ }
+
+ return output.length > limit ? output.slice(0, limit) : output;
+};
+
+cbSplit._compliantExecNpcg = /()??/.exec("")[1] === undefined; // NPCG: nonparticipating capturing group
+cbSplit._nativeSplit = String.prototype.split;
+
+} // end `if (!cbSplit)`
+
+String.prototype.split = function (separator, limit) {
+ return cbSplit(this, separator, limit);
+};
+
+{% endraw %}
View
71 _includes/vendor/liquid.patch.js
@@ -0,0 +1,71 @@
+Liquid.readTemplateFile = function(path) {
+ var repo = getRepo(app.state.user, app.state.repo);
+ return repo.contentsSync(app.state.branch, '_includes/' + path);
+}
+
+
+Liquid.Template.registerTag( 'include', Liquid.Tag.extend({
+
+ tagSyntax: /((?:"[^"]+"|'[^']+'|[^\s,|]+)+)(\s+(?:with|for)\s+((?:"[^"]+"|'[^']+'|[^\s,|]+)+))?/,
+
+ init: function(tag, markup, tokens) {
+ var matches = (markup || '').match(this.tagSyntax);
+ if(matches) {
+ this.templateName = matches[1];
+ this.templateNameVar = this.templateName.substring(1, this.templateName.length - 1);
+ this.variableName = matches[3];
+ this.attributes = {};
+
+ var attMatchs = markup.match(/(\w*?)\s*\:\s*("[^"]+"|'[^']+'|[^\s,|]+)/g);
+ if(attMatchs) {
+ attMatchs.each(function(pair){
+ pair = pair.split(":");
+ this.attributes[pair[0].strip()] = pair[1].strip();
+ }, this);
+ }
+ } else {
+ throw ("Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]");
+ }
+ this._super(tag, markup, tokens);
+ },
+
+ render: function(context) {
+ var self = this,
+ source = Liquid.readTemplateFile( this.templateName ),
+ partial = Liquid.parse(source),
+ variable = context.get((this.variableName || this.templateNameVar)),
+ output = '';
+ context.stack(function(){
+ self.attributes.each = hackObjectEach;
+ self.attributes.each(function(pair){
+ context.set(pair.key, context.get(pair.value));
+ })
+
+ if(variable instanceof Array) {
+ output = variable.map(function(variable){
+ context.set( self.templateNameVar, variable );
+ return partial.render(context);
+ });
+ } else {
+ context.set(self.templateNameVar, variable);
+ output = partial.render(context);
+ }
+ });
+ output = [output].flatten().join('');
+ return output
+ }
+}));
+
+
+Liquid.Block.prototype.renderAll = function(list, context) {
+ return (list || []).map(function(token, i){
+ var output = '';
+ try { // hmmm... feels a little heavy
+ output = ( token['render'] ) ? token.render(context) : token;
+ } catch(e) {
+
+ console.log(context.handleError(e));
+ }
+ return output;
+ });
+};
View
1  _includes/vendor/queue.min.js
@@ -0,0 +1 @@
+(function(){if(typeof module==="undefined")self.queue=queue;else module.exports=queue;queue.version="1.0.1";function queue(parallelism){var queue={},active=0,remaining=0,head,tail,error=null,results=[],await=noop,awaitAll;if(arguments.length<1)parallelism=Infinity;queue.defer=function(){if(!error){var node=arguments;node.index=results.push(undefined)-1;if(tail)tail.next=node,tail=tail.next;else head=tail=node;++remaining;pop()}return queue};queue.await=function(f){await=f;awaitAll=false;if(!remaining)notify();return queue};queue.awaitAll=function(f){await=f;awaitAll=true;if(!remaining)notify();return queue};function pop(){if(head&&active<parallelism){var node=head,f=node[0],a=Array.prototype.slice.call(node,1),i=node.index;if(head===tail)head=tail=null;else head=head.next;++active;a.push(function(e,r){--active;if(error!=null)return;if(e!=null){error=e;remaining=results=head=tail=null;notify()}else{results[i]=r;if(--remaining)pop();else notify()}});f.apply(null,a)}}function notify(){if(error!=null)await(error);else if(awaitAll)await(null,results);else await.apply(null,[null].concat(results))}return queue}function noop(){}})();
View
12 _includes/views/application.js
@@ -110,6 +110,18 @@ views.Application = Backbone.View.extend({
}, this));
},
+ preview: function (user, repo, branch, path, file, mode) {
+ this.loading('Preview post ...');
+
+ loadConfig(user, repo, branch, _.bind(function() {
+ loadPost(user, repo, branch, path, file, _.bind(function (err, data) {
+ if (err) return this.notify('error', 'The requested resource could not be found.');
+ new views.Preview({ model: data }).render();
+ }, this));
+ }, this));
+ },
+
+
newPost: function (user, repo, branch, path) {
this.loading('Creating file ...');
loadPosts(user, repo, branch, path, _.bind(function (err, data) {
View
56 _includes/views/post.js
@@ -83,9 +83,19 @@ views.Post = Backbone.View.extend({
_toggleView: function(e) {
var that = this;
- this.toggleView($(e.currentTarget).attr('data-view'));
- _.delay(function() { that.refreshCodeMirror(); }, 1);
- return false;
+ if ($(e.currentTarget).attr('data-view') === 'preview' &&
+ this.model.metadata &&
+ this.model.metadata.layout
+ ) {
+ var hash = window.location.hash.split('/');
+ hash[2] = 'preview';
+ this.stashFile();
+ $(e.currentTarget).attr({ target: '_blank', href: hash.join('/') });
+ } else {
+ this.toggleView($(e.currentTarget).attr('data-view'));
+ _.delay(function() { that.refreshCodeMirror(); }, 1);
+ return false;
+ }
},
_toggleMeta: function(e) {
@@ -149,11 +159,15 @@ views.Post = Backbone.View.extend({
this.prevContent = this.serialize();
if (!window.shortcutsRegistered) {
key('⌘+s, ctrl+s', _.bind(function() { this.updateFile(); return false; }, this));
+ key('ctrl+r', _.bind(function() { this.stashApply(); return false; }, this));
key('ctrl+shift+right', _.bind(function() { this.right(); return false; }, this));
key('ctrl+shift+left', _.bind(function() { this.left(); return false; }, this));
key('esc', _.bind(function() { this.toggleView('compose'); return false; }, this));
window.shortcutsRegistered = true;
}
+
+ // Stash editor and metadataEditor content to localStorage on pagehide event
+ window.addEventListener('pagehide', this.stashFile, false);
},
// TODO: We might not wanna use this
@@ -184,6 +198,7 @@ views.Post = Backbone.View.extend({
var published = this.$('#post_published').prop('checked');
this.model.raw_metadata = updatePublished(this.model.raw_metadata, published);
+ console.log(this.model.raw_metadata);
this.metadataEditor.setValue(this.model.raw_metadata);
published ? $('#post').addClass('published') : $('#post').removeClass('published');
@@ -238,6 +253,7 @@ views.Post = Backbone.View.extend({
function patch() {
if (that.updateMetaData()) {
that.model.content = that.prevContent;
+ console.log(that.prevContent);
that.editor.setValue(that.prevContent);
patchFile(app.state.user, app.state.repo, app.state.branch, filepath, filecontent, message, function(err) {
@@ -302,6 +318,40 @@ views.Post = Backbone.View.extend({
});
},
+ stashFile: function(event) {
+ if (event) event.preventDefault();
+
+ console.log(event, window.localStorage);
+
+ if (!window.localStorage) return false;
+
+ var storage = window.localStorage,
+ filepath = $('input.filepath').val(),
+ filecontent = this.serialize();
+
+ storage.setItem(filepath, JSON.stringify({
+ sha: app.state.sha,
+ content: this.editor ? this.editor.getValue() : null,
+ raw_metadata: this.model.jekyll && this.metadataEditor ? this.metadataEditor.getValue() : null
+ }));
+ },
+
+ stashApply: function() {
+ if (!window.localStorage) return false;
+
+ var storage = window.localStorage,
+ filepath = $('input.filepath').val();
+
+ var stash = JSON.parse(storage.getItem(filepath));
+
+ if (stash && stash.sha === app.state.sha) {
+ if (this.editor) this.editor.setValue(stash.content);
+ if (this.metadataEditor) this.metadataEditor.setValue(stash.raw_metadata);
+ } else {
+ storage.delItem(filepath);
+ }
+ },
+
updateFile: function() {
var that = this,
filepath = $('input.filepath').val(),
View
25 _includes/views/preview.js
@@ -0,0 +1,25 @@
+(function(config, models, views, routers, utils, templates) {
+
+views.Preview = Backbone.View.extend({
+ render: function() {
+ this.stashApply();
+ _.preview(this);
+ return this;
+ },
+ stashApply: function() {
+ if (!window.localStorage) return false;
+
+ var storage = window.localStorage,
+ filepath = window.location.hash.split('/').slice(4).join('/');
+
+ var stash = JSON.parse(storage.getItem(filepath));
+
+ if (stash) {
+ this.model.content = stash.content;
+ this.model.raw_metadata = stash.raw_metadata;
+ this.model.metadata = jsyaml.load(stash.raw_metadata);
+ }
+ }
+});
+
+}).apply(this, window.args);
View
4 boot.js
@@ -26,6 +26,9 @@
{% include vendor/codemirror/ruby.js %}
{% include vendor/codemirror/yaml.js %}
{% include vendor/jquery.cookie.js %}
+{% include vendor/liquid.min.js %}
+{% include vendor/liquid.patch.js %}
+{% include vendor/queue.min.js %}
window.app = {
@@ -54,6 +57,7 @@ window.args = _(window.app).toArray();
{% include views/header.js %}
{% include views/posts.js %}
{% include views/post.js %}
+{% include views/preview.js %}
// Prevent exit when there are unsaved changes
window.onbeforeunload = function() {
Please sign in to comment.
Something went wrong with that request. Please try again.