UNPKG

apage

Version:

A single-page website from lots of markdown

495 lines (434 loc) 17.6 kB
// Generated by CoffeeScript 1.9.2 /*! Apage 0.0.22 //// MIT Licence //// http://apage.richplastow.com/ */ (function() { var Article, Main, dirname, filename, filterLine, marked, ordername, page, parseFilename, renderer, script, tidypath, ª, ªA, ªB, ªE, ªF, ªI, ªN, ªO, ªR, ªS, ªU, ªV, ªX, ªclone, ªex, ªhas, ªpopulate, ªretrieve, ªtype, slice = [].slice; ªI = 'Apage'; ªV = '0.0.22'; ªA = 'array'; ªB = 'boolean'; ªE = 'error'; ªF = 'function'; ªN = 'number'; ªO = 'object'; ªR = 'regexp'; ªS = 'string'; ªU = 'undefined'; ªX = 'null'; ª = console.log.bind(console); ªex = function(x, a, b) { var pos; if (-1 === (pos = a.indexOf(x))) { return x; } else { return b.charAt(pos); } }; ªhas = function(h, n, t, f) { if (t == null) { t = true; } if (f == null) { f = false; } if (-1 !== h.indexOf(n)) { return t; } else { return f; } }; ªtype = function(x) { return {}.toString.call(x).match(/\s([a-z|A-Z]+)/)[1].toLowerCase(); }; ªretrieve = function(instances, identifier) { var instance; instance = instances[identifier]; if (!instance) { switch (typeof identifier) { case ªS: throw new Error("'" + identifier + "' does not exist"); break; case ªN: throw new Error("`" + identifier + "` does not exist"); break; case ªU: throw new Error("`identifier` is `undefined`"); break; default: throw new Error("`identifier` is type '" + (ªtype(identifier)) + "'"); } } return instance; }; ªpopulate = function(candidate, subject, rules, updating) { var errors, j, k, key, len, len1, rule, test, type, use, value; if (ªO !== ªtype(candidate)) { return ["`candidate` is type '" + (ªtype(candidate)) + "' not 'object'"]; } errors = []; for (j = 0, len = rules.length; j < len; j++) { rule = rules[j]; key = rule[0], use = rule[1], type = rule[2], test = rule[3]; value = candidate[key]; if (void 0 === value) { if (updating || void 0 !== use) { continue; } else { errors.push("Missing field '" + key + "' is mandatory"); } } else if (type !== ªtype(value)) { errors.push("Field '" + key + "' is type '" + (ªtype(value)) + "' not '" + type + "'"); } else if (!test.test(value)) { errors.push("Field '" + key + "' fails " + ('' + test)); } } if (errors.length) { return errors; } for (k = 0, len1 = rules.length; k < len1; k++) { rule = rules[k]; key = rule[0], use = rule[1], type = rule[2], test = rule[3]; value = candidate[key]; if (void 0 === value) { if (void 0 === subject[key]) { subject[key] = use; } } else { subject[key] = value; } } }; ªclone = function(subject, rules) { var j, key, len, out, rule; out = {}; for (j = 0, len = rules.length; j < len; j++) { rule = rules[j]; key = ªS === typeof rule ? rule : rule[0]; out[key] = subject[key]; } return out; }; parseFilename = function(nm, part) { var dash, dot, parts; if (ªS !== ªtype(nm)) { throw new Error("`nm` is " + (ªtype(nm)) + ", not string"); } parts = {}; dash = nm.indexOf('-'); dot = nm.indexOf('.'); if (-1 === dot) { nm += '.'; dot = nm.length - 1; } if (-1 !== dash && dash < dot) { parts.order = nm.substr(0, dash) * 1; if (isNaN(parts.order)) { dash = -1; } } else { dash = -1; } parts.title = nm.substr(dash + 1, dot - dash - 1); parts.ext = nm.substr(dot + 1); parts.slug = parts.title.toLowerCase().replace(/[“”‘’,]/g, '').replace(/[\s–—…·:;]/g, '-').replace(/^-+|-+$/g, '').replace(/-+/g, '-').replace(/[àáäâèéëêìíïîòóöôùúüûñç]/g, function(c) { return ªex(c, 'àáäâèéëêìíïîòóöôùúüûñç', 'aaaaeeeeiiiioooouuuunc'); }); if (isNaN(parts.order)) { parts.order = parts.slug * 1; } if (isNaN(parts.order)) { parts.order = parts.slug; } if (!part) { return parts; } if (ªU !== ªtype(parts[part])) { return parts[part]; } throw new Error("`part` not recognised, use 'order|title|slug|ext'"); }; Article = (function() { Article.prototype.I = 'Article'; Article.prototype.toString = function() { return "[object " + this.I + "]"; }; Article.prototype._rules = { config: [['path', void 0, 'string', /^[a-z0-9][-\/a-z0-9]{0,63}\.[.a-z0-9]+$/i], ['raw', '', 'string', /^[^\x00-\x08\x0E-\x1F]{0,10000}$/]], properties: [['id', void 0, 'string', /^apage_[-_0-9a-z]{1,10}$/], ['order', void 0, 'string', /^[-_0-9a-z]{1,10}$/]] }; function Article(config) { var errors, fname, i, j, key, len, line, ref, ref1, value; if (config == null) { config = {}; } this._config = {}; if (errors = ªpopulate(config, this._config, this._rules.config)) { throw new Error('Invalid `config`:\n ' + errors.join('\n ')); } this.path = this._config.path.replace(/^[.\/]+|[.\/]+$/g, ''); this.id = (function() { var j, len, ref, results; ref = this.path.split('/'); results = []; for (i = j = 0, len = ref.length; j < len; i = ++j) { fname = ref[i]; results.push(parseFilename(fname, 'slug')); } return results; }).call(this); this.id = 'apage_' + this.id.join('_'); this.order = parseFilename(fname, 'order'); this.front = []; if ('---\n' === this._config.raw.substr(0, 4)) { this._config.raw = this._config.raw.split('---\n'); ref = this._config.raw[1].split('\n'); for (i = j = 0, len = ref.length; j < len; i = ++j) { line = ref[i]; ref1 = line.split(':'), key = ref1[0], value = 2 <= ref1.length ? slice.call(ref1, 1) : []; key = key != null ? key.replace(/^\s+|\s+$/g, '') : void 0; value = value != null ? value.join(':').replace(/^\s+|\s+$/g, '') : void 0; if (!key || !value) { continue; } switch (key) { case 'id': this.id = "apage_" + (value.replace(/^apage_/, '')); break; case 'order': this.order = isNaN(value * 1) ? value : parseInt(value, 10); break; case 'title': this.title = value; break; default: this.front.push([key, value]); } } this._config.raw = (this._config.raw.slice(2)).join('---\n'); } this._config.raw = this._config.raw.replace(/^\s+|\s+$/g, ''); this.title = this.title || (this._config.raw.split('\n'))[0]; this.html = (marked(this._config.raw)).replace(/\\/g, '\\\\').split('\n'); if (errors = ªpopulate(config, this, this._rules.properties, true)) { throw new Error('Invalid `config`:\n ' + errors.join('\n ')); } } Article.prototype.clone = function() { return ªclone(this, ['id', 'path', 'order']); }; Article.prototype.destructor = function() {}; Article.prototype.edit = function(amend) { return ªpopulate(amend, this, this._rules.properties, true); }; Article.prototype.config = function(key, value) { var obj; switch (arguments.length) { case 0: return ªclone(this._config, this._rules.config); case 1: switch (ªtype(key)) { case ªS: return this._config[key]; case ªO: return ªpopulate(key, this._config, this._rules.config, true); } break; case 2: obj = {}; obj[key] = value; return this.config(obj); } }; return Article; })(); Main = (function() { Main.prototype.I = ªI; Main.prototype.V = ªV; Main.prototype.toString = function() { return "[object " + this.I + "]"; }; Main.prototype._rules = { config: [['title', 'Untitled', 'string', /^[^\x00-\x1F]{1,24}$/], ['url', false, 'string', /^[-:.\/a-z0-9]{1,64}$/], ['plugin', '', 'string', /^[^\x00-\x08\x0E-\x1F]{0,100000}$/]] }; function Main(config) { var errors; if (config == null) { config = {}; } this._config = {}; this._articles = []; if (errors = ªpopulate(config, this._config, this._rules.config)) { throw new Error('Invalid `config`:\n ' + errors.join('\n ')); } } Main.prototype.config = function(key, value) { var obj; switch (arguments.length) { case 0: return ªclone(this._config, this._rules.config); case 1: switch (ªtype(key)) { case ªS: return this._config[key]; case ªO: return ªpopulate(key, this._config, this._rules.config, true); } break; case 2: obj = {}; obj[key] = value; return this.config(obj); } }; Main.prototype.browse = function() { var a, j, len, ref, results; ref = this._articles; results = []; for (j = 0, len = ref.length; j < len; j++) { a = ref[j]; results.push((function(a) { return { id: a.id, order: a.order }; })(a)); } return results; }; Main.prototype.read = function(identifier) { var art; art = ªretrieve(this._articles, identifier); return art.clone(); }; Main.prototype.destroy = function(identifier) { var art; art = ªretrieve(this._articles, identifier); delete this._articles[art.id]; this._articles.splice(art.index, 1); art.destructor(); return this; }; Main.prototype.edit = function(identifier, amend) { var art, errors; art = ªretrieve(this._articles, identifier); if (errors = art.edit(amend)) { throw new Error('Invalid `amend`:\n ' + errors.join('\n ')); } return this; }; Main.prototype.add = function(article) { var instance; if (!article) { return this; } instance = new Article(article); if (this._articles[instance.id]) { throw new Error("'" + instance.id + "' already exists"); } instance.index = this._articles.length; this._articles.push(instance); this._articles.sort(function(a, b) { if (a.order > b.order) { return 1; } if (a.order < b.order) { return -1; } if (a.id > b.id) { return 1; } if (a.id < b.id) { return -1; } return 0; }); this._articles[instance.id] = instance; return this; }; Main.prototype.render = function() { return "" + (page(this._config, this._articles)); }; return Main; })(); if (ªF === typeof define && define.amd) { define(function() { return Main; }); } else if (ªO === typeof module && module && module.exports) { module.exports = Main; } else { this[ªI] = Main; } if (ªF === typeof define && define.amd) { } else if (ªO === typeof module && module && module.exports) { marked = require('marked'); } else { marked = window.marked; } renderer = new marked.Renderer; renderer.heading = function(text, level) { return "<h" + level + ">" + text + "</h" + level + ">\n"; }; marked.setOptions({ renderer: renderer }); page = function(config, articles) { var article, comment, generator, i, j, k, len, len1, line, out, ref; generator = ªI + " " + ªV + " http://apage.richplastow.com/"; comment = config.plugin ? '‘Inspect Element’ here, for Apage’s injected CSS' : 'Apage was configured with no plugins, so no CSS is injected here'; out = ["<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <title>" + config.title + "</title>\n <meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\">\n <meta name=\"generator\" content=\"" + generator + "\">\n <style>\n /* " + comment + " */\n </style>\n" + (script(config, articles)) + "\n</head>\n<body>\n"]; if (!articles.length) { out.push('<!-- Apage was rendered with no articles -->\n'); } for (i = j = 0, len = articles.length; j < len; i = ++j) { article = articles[i]; out.push("<article id=\"" + article.id + "\"\n class=\"apage\"\n data-apage-opath=\"/" + article.path + "\"\n data-apage-dname=\"_" + (dirname(article.path)) + "\"\n data-apage-order=\"" + article.order + "\"\n data-apage-front='" + ((JSON.stringify(article.front)).replace(/'/g, "&#39;")) + "'\n data-apage-title=\"" + article.title + "\">"); ref = article.html; for (k = 0, len1 = ref.length; k < len1; k++) { line = ref[k]; out.push(filterLine(config, line)); } out.push("</article><!-- / #" + article.id + " -->\n\n"); } comment = config.plugin ? '‘Inspect Element’ here, for Apage’s injected elements' : 'Apage was configured with no plugins, so nothing is injected here'; out.push("<!-- " + comment + " -->"); return out.join('\n ') + '\n\n</body>\n</html>'; }; filterLine = function(config, line) { var rx; if (config.url) { rx = new RegExp('href="' + config.url, 'g'); return line.replace(rx, 'href="'); } else { return line; } }; tidypath = function(p) { var name, order; name = filename(p).split('-'); order = name[0]; name = isNaN(order * 1) ? name.join('-') : name.slice(1).join('-'); return dirname(p) + name.split('.').slice(0, -1).join('.'); }; dirname = function(p) { return ªhas(p, '/', p.split('/').slice(0, -1).join('_') + '_', ''); }; filename = function(p) { return p.split('/').slice(-1)[0]; }; ordername = function(p) { var order; order = filename(p).split('-')[0]; if (isNaN(order * 1)) { return ''; } else { return order * 1; } }; script = function(config, articles) { if (!config.plugin) { return ''; } return "\n <script>\n\n//// When the DOM is ready, set up Apage and inject the plugins. \nwindow.addEventListener('load', function () { (function (d) { 'use strict'; \n\n\n//// Declare iterator, length and HTML-reference variables. \nvar i, l, $ref\n\n\n//// Initialize three arrays which are available to all Apage plugins. \n ,arts = []\n ,resolvers = []\n ,updaters = []\n\n\n//// Like jQuery, but native. \n ,$ = d.querySelector.bind(d)\n ,$$ = d.querySelectorAll.bind(d)\n\n\n//// Get a reference to all `<article class=\"apage\">` elements. \n ,$arts = $$('article.apage')\n\n\n//// Convert JavaScript’s native `arguments` object to an array. \n ,getArgs = function (args, offset) {\n return Array.prototype.slice.call(args, offset || 0);\n }\n\n\n//// `unattribute($ref,'data-apage-','opath'...)` removes data attributes. \n ,unattribute = function ($ref, prefix) {\n for ( var i=0, suffs=getArgs(arguments,2), l=suffs.length; i<l; i++ ) {\n $ref.removeAttribute(prefix + suffs[i]);\n }\n }\n\n\n//// Runs each resolver in order. These are added by the plugins, below. \n//// Resolvers are used to map a query to an article. \n ,resolve = function (query) {\n for (var i=0, l=resolvers.length, backstop, result={}; i<l; i++) {\n result = resolvers[i](query);\n if (result.art) { break; } // `query` does resolve to an article\n backstop = result.backstop || backstop; // may return a backstop\n }\n return result.art ? result.art : backstop; //@todo test logic of 'last valid backstop return' with several plugins at once\n }\n\n\n//// Runs each updater in order. These are added by the plugins, below. \n//// Updaters change the current DOM state, eg to show a single article. \n ,update = function (query) {\n for (var i=0, l=updaters.length, current=resolve(query); i<l; i++) {\n updaters[i](current);\n }\n }\n\n\n//// Tidies the URL hash and runs `update()` when the URL hash changes. \n ,onHashchange = function (event) {\n update( window.location.hash.substr(1).replace(/\\//g,'_') );\n if (event) { event.preventDefault(); }\n }\n\n;\n\n\n//// Populate the `arts` array using data from Apage `<ARTICLE>` elements. \n//// Then, remove all 'data-apage-*' attributes except 'data-apage-dname'. \nfor (i=0, l=$arts.length; i<l; i++) {\n $ref = $arts[i];\n arts.push({\n id: $ref.getAttribute('id')\n ,opath: $ref.getAttribute('data-apage-opath')\n ,dname: $ref.getAttribute('data-apage-dname')\n ,order: $ref.getAttribute('data-apage-order')\n ,front: JSON.parse( $ref.getAttribute('data-apage-front') )\n ,title: $ref.getAttribute('data-apage-title')\n ,$ref: $ref\n });\n unattribute($ref,'data-apage-','opath','order','front','title');\n}\n\n\n//// Begin injecting plugins. \n\n" + config.plugin + "\n\n//// End injecting plugins. \n\n\n//// Run each updater when the page loads, and when the URL hash changes. \nonHashchange();\nwindow.addEventListener('hashchange', onHashchange);\n\n\n}).call(this, document) });\n\n </script>\n"; }; }).call(this);