diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..e0104b4 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,16 @@ +{ "node": true +, "browser": true +, "jquery": true + +, "indent": 2 +, "onevar": true +, "laxcomma": true +, "laxbreak": true + +, "eqeqeq": true +, "immed": true +, "undef": true +, "unused": true +, "latedef": true +, "globals": { "angular": true } +} diff --git a/app.js b/app.js new file mode 100644 index 0000000..a6d70d6 --- /dev/null +++ b/app.js @@ -0,0 +1,12 @@ +'use strict'; + +// Declare app level module which depends on views, and components +angular.module('myApp', [ + 'ngRoute', + 'myApp.about', + 'myApp.build', + 'myApp.version' +]). +config(['$routeProvider', function ($routeProvider) { + $routeProvider.otherwise({redirectTo: '/about'}); +}]); diff --git a/bower.json b/bower.json index 69a836a..adbeea0 100644 --- a/bower.json +++ b/bower.json @@ -30,14 +30,18 @@ ], "dependencies": { "mustache": "~0.8.2", - "bluebird": "~2.5.2", + "bluebird": "~2.6.2", "rsvp": "~3.0.16", "escape-string-regexp": "~1.0.2", "js-yaml": "~3.2.5", "path": "~3.46.1", "forEachAsync": "~5.0.5", "node-uuid": "~1.4.2", - "markdown-it": "~3.0.2" + "markdown-it": "~3.0.2", + "angular": "~1.3.8", + "angular-route": "~1.3.8", + "html5-boilerplate": "~4.3.0", + "bootstrap": "~3.3.1" }, "resolutions": { "bluebird": "~2.6.2" diff --git a/components/desirae/desirae.js b/components/desirae/desirae.js new file mode 100644 index 0000000..377a0b7 --- /dev/null +++ b/components/desirae/desirae.js @@ -0,0 +1,9 @@ +angular.module('myApp.services', []). + factory('MyService', function($http) { + var MyService = {}; + $http.get('resources/data.json').success(function(response) { + MyService.data = response; + }); + return MyService; + } +); diff --git a/components/version/interpolate-filter.js b/components/version/interpolate-filter.js new file mode 100644 index 0000000..03bb198 --- /dev/null +++ b/components/version/interpolate-filter.js @@ -0,0 +1,9 @@ +'use strict'; + +angular.module('myApp.version.interpolate-filter', []) + +.filter('interpolate', ['version', function(version) { + return function(text) { + return String(text).replace(/\%VERSION\%/mg, version); + }; +}]); diff --git a/components/version/interpolate-filter_test.js b/components/version/interpolate-filter_test.js new file mode 100644 index 0000000..ff56c52 --- /dev/null +++ b/components/version/interpolate-filter_test.js @@ -0,0 +1,15 @@ +'use strict'; + +describe('myApp.version module', function() { + beforeEach(module('myApp.version')); + + describe('interpolate filter', function() { + beforeEach(module(function($provide) { + $provide.value('version', 'TEST_VER'); + })); + + it('should replace VERSION', inject(function(interpolateFilter) { + expect(interpolateFilter('before %VERSION% after')).toEqual('before TEST_VER after'); + })); + }); +}); diff --git a/components/version/version-directive.js b/components/version/version-directive.js new file mode 100644 index 0000000..74088f8 --- /dev/null +++ b/components/version/version-directive.js @@ -0,0 +1,9 @@ +'use strict'; + +angular.module('myApp.version.version-directive', []) + +.directive('appVersion', ['version', function(version) { + return function(scope, elm, attrs) { + elm.text(version); + }; +}]); diff --git a/components/version/version-directive_test.js b/components/version/version-directive_test.js new file mode 100644 index 0000000..4a59e11 --- /dev/null +++ b/components/version/version-directive_test.js @@ -0,0 +1,17 @@ +'use strict'; + +describe('myApp.version module', function() { + beforeEach(module('myApp.version')); + + describe('app-version directive', function() { + it('should print current version', function() { + module(function($provide) { + $provide.value('version', 'TEST_VER'); + }); + inject(function($compile, $rootScope) { + var element = $compile('')($rootScope); + expect(element.text()).toEqual('TEST_VER'); + }); + }); + }); +}); diff --git a/components/version/version.js b/components/version/version.js new file mode 100644 index 0000000..227b913 --- /dev/null +++ b/components/version/version.js @@ -0,0 +1,8 @@ +'use strict'; + +angular.module('myApp.version', [ + 'myApp.version.interpolate-filter', + 'myApp.version.version-directive' +]) + +.value('version', '0.8.0'); diff --git a/components/version/version_test.js b/components/version/version_test.js new file mode 100644 index 0000000..4ca6880 --- /dev/null +++ b/components/version/version_test.js @@ -0,0 +1,11 @@ +'use strict'; + +describe('myApp.version module', function() { + beforeEach(module('myApp.version')); + + describe('version service', function() { + it('should return current version', inject(function(version) { + expect(version).toEqual('0.1'); + })); + }); +}); diff --git a/deardesi.js b/deardesi.js index a339ffd..816ef1d 100644 --- a/deardesi.js +++ b/deardesi.js @@ -204,7 +204,7 @@ } } - function runDesi(desi, development) { + function runDesi(desi, env) { var cache = desi.cache //, config = desi.config , cacheByPath = {} @@ -215,16 +215,16 @@ ; desi.urls = desi.config.urls = {}; - if (true || development) { - desi.urls.base_path = desi.config.development.base_path; - desi.urls.url = desi.config.development.url; - desi.urls.development_url = desi.config.development.url; - } else { - desi.config.base_path = desi.urls.base_path = desi.config.production.base_path; - desi.urls.url = desi.config.production.url; - desi.urls.production_url = desi.config.production.url; + desi.env = {}; + if (-1 === ['development', 'staging'].indexOf(env) || !desi.config[env]) { + env = 'production'; } + desi.urls.base_path = desi.config.base_path = desi.config[env].base_path; + desi.urls.url = desi.config[env].url; + desi.config.compiled_path = desi.config[env].compiled_path; + desi.urls[env + '_url'] = desi.config[env].url; + cache.sources = cache.sources || []; cache.sources.forEach(function (source) { cacheByPath[source.path] = source; @@ -279,116 +279,124 @@ console.log(''); console.info('getting config, data, caches...'); - return PromiseA.all([fsapi.getConfig(), fsapi.getData(), fsapi.getCache(), fsapi.getPartials()]).then(function (arr) { - var config = arr[0] - , data = arr[1] - , cache = arr[2] - , partials = arr[3] - , collectionnames = Object.keys(config.collections) - , themenames = Object.keys(config.themes) - .filter(function (k) { return 'default' !== k; }) - //.map(function (n) { return path.join(n, 'layouts'); }) - , assetnames = Object.keys(config.assets) - ; - - console.info('loaded config, data, caches, partials'); - console.log({ - config: arr[0] - , data: arr[1] - , cache: arr[2] - , partials: arr[3] - }); - console.info('last update: ' + (cache.lastUpdate && new Date(cache.lastUpdate) || 'never')); - - // TODO make document configurability - config.rootdir = config.rootdir || '_root'; - return PromiseA.all([ - fsapi.getMeta( - themenames - , { dotfiles: false - , extensions: ['md', 'markdown', 'htm', 'html', 'jade', 'css', 'js', 'yml'] - } - ) - , fsapi.getMeta( - [config.rootdir] - , { dotfiles: false - , extensions: ['md', 'markdown', 'htm', 'html', 'jade'] - } - ) - , fsapi.getMeta( - collectionnames - , { dotfiles: false - , extensions: ['md', 'markdown', 'htm', 'html', 'jade'] - } - ) - , fsapi.getMeta( - assetnames - , { dotfiles: false - //, extensions: ['md', 'markdown', 'htm', 'html', 'jade', 'css', 'js', 'yml'] - } - ) - ]).then(function (things) { - console.info('loaded theme meta, root meta, collection meta'); - console.log({ - theme: things[0] - , root: things[1] - , collection: things[2] - , asset: things[3] - }); - - function noErrors(map) { - Object.keys(map).forEach(function (path) { - map[path] = map[path].filter(function (m) { - if (!m.error && m.size) { - return true; - } - - if (!m.size) { - console.warn("Ignoring 0 byte file " + (m.path || m.name)); - return false; - } - - console.warn("Couldn't get stats for " + (m.path || m.name)); - console.warn(m.error); - }); - }); - - return map; - } - - var themes = noErrors(things[0]) - , root = noErrors(things[1])[config.rootdir] - , collections = noErrors(things[2]) - , assets = noErrors(things[3]) + function Desi() { + } + Desi.init = function (desi) { + return PromiseA.all([fsapi.getConfig(), fsapi.getData(), fsapi.getCache(), fsapi.getPartials()]).then(function (arr) { + var config = arr[0] + , data = arr[1] + , cache = arr[2] + , partials = arr[3] + , collectionnames = Object.keys(config.collections) + , themenames = Object.keys(config.themes) + .filter(function (k) { return 'default' !== k; }) + //.map(function (n) { return path.join(n, 'layouts'); }) + , assetnames = Object.keys(config.assets) ; - if (!themes[Object.keys(themes)[0]].length) { - console.error('Missing THEMES!'); - throw new Error('It seems that your themes directory is missing'); - } + console.info('loaded config, data, caches, partials'); + console.log({ + config: arr[0] + , data: arr[1] + , cache: arr[2] + , partials: arr[3] + }); + console.info('last update: ' + (cache.lastUpdate && new Date(cache.lastUpdate) || 'never')); - if (!root.length) { - console.error('Missing ROOT!'); - } + // TODO make document configurability + config.rootdir = config.rootdir || '_root'; + return PromiseA.all([ + fsapi.getMeta( + themenames + , { dotfiles: false + , extensions: ['md', 'markdown', 'htm', 'html', 'jade', 'css', 'js', 'yml'] + } + ) + , fsapi.getMeta( + [config.rootdir] + , { dotfiles: false + , extensions: ['md', 'markdown', 'htm', 'html', 'jade'] + } + ) + , fsapi.getMeta( + collectionnames + , { dotfiles: false + , extensions: ['md', 'markdown', 'htm', 'html', 'jade'] + } + ) + , fsapi.getMeta( + assetnames + , { dotfiles: false + //, extensions: ['md', 'markdown', 'htm', 'html', 'jade', 'css', 'js', 'yml'] + } + ) + ]).then(function (things) { + console.info('loaded theme meta, root meta, collection meta'); + console.log({ + theme: things[0] + , root: things[1] + , collection: things[2] + , asset: things[3] + }); - if (!collections[Object.keys(collections)[0]].length) { - console.error('Missing Collections!'); - } + function noErrors(map) { + Object.keys(map).forEach(function (path) { + map[path] = map[path].filter(function (m) { + if (!m.error && m.size) { + return true; + } - return { - config: config - , data: data - , cache: cache - , meta: { - themes: themes - , collections: collections - , root: root - , assets: assets + if (!m.size) { + console.warn("Ignoring 0 byte file " + (m.path || m.name)); + return false; + } + + console.warn("Couldn't get stats for " + (m.path || m.name)); + console.warn(m.error); + }); + }); + + return map; } - , partials: partials - }; + + var themes = noErrors(things[0]) + , root = noErrors(things[1])[config.rootdir] + , collections = noErrors(things[2]) + , assets = noErrors(things[3]) + ; + + if (!themes[Object.keys(themes)[0]].length) { + console.error('Missing THEMES!'); + throw new Error('It seems that your themes directory is missing'); + } + + if (!root.length) { + console.error('Missing ROOT!'); + } + + if (!collections[Object.keys(collections)[0]].length) { + console.error('Missing Collections!'); + } + + return { + config: config + , data: data + , cache: cache + , meta: { + themes: themes + , collections: collections + , root: root + , assets: assets + } + , partials: partials + }; + }); }); - }).then(runDesi).then(function (desi) { + }; + + Desi.runDesi = runDesi; + + Desi.otherStuff = function (desi) { var files = {} ; @@ -405,7 +413,7 @@ }); }); - return Object.keys(files).length && fsapi.copy(files).then(function (copied) { + return (Object.keys(files).length && fsapi.copy(files).then(function (copied) { if (copied.error) { console.error(copied.error); throw new Error(copied.error); @@ -419,439 +427,436 @@ } return desi; - }) || PromiseA.resolve(desi); - }).then(runDesi).then(function (desi) { - return readFrontmatter(desi.content.root.concat(desi.content.themes.concat(desi.content.collections))).then(function () { - return desi; - }); - }).then(function (desi) { - // TODO add missing metadata and resave file - desi.navigation = []; + }) || PromiseA.resolve(desi)).then(runDesi).then(function (desi) { + return readFrontmatter(desi.content.root.concat(desi.content.themes.concat(desi.content.collections))).then(function () { + return desi; + }); + }).then(function (desi) { + // TODO add missing metadata and resave file + desi.navigation = []; - desi.content.root.forEach(function (page) { - var name = path.basename(page.path, path.extname(page.path)) - , nindex - ; - - //if (-1 === desi.data.navigation.indexOf(name) && 'index' !== name) - nindex = desi.data.navigation.indexOf(name); - if (-1 === nindex) { - return; - } - - desi.navigation[nindex] = { - title: page.yml && page.yml.title || firstCap(name) - , href: desi.urls.base_path + '/' + name - , path: desi.urls.base_path + '/' + name - , name: name - , active: false // placeholder - }; - }); - - desi.content.root.forEach(function (page) { - page.yml = page.yml || {}; - // TODO make default layout configurable - page.yml.layout = page.yml.layout || '_root'; - - if (!page.relativePath) { - page.relativePath = path.dirname(page.path); - page.name = page.name || path.basename(page.path); - } - - page.relativePath = page.relativePath.replace(desi.config.rootdir, '').replace(/^\//, ''); - page.path = path.join(page.relativePath, page.name); - - // TODO make bare root routes configurable - page.yml.permalink = page.yml.permalink || page.path.replace(/\.\w+$/, ''); - - page.yml.title = page.yml.title || firstCap(page.name.replace(/\.\w+$/, '')); - }); - - desi.content.collections = desi.content.collections.filter(function (article) { - if (!article.yml) { - console.warn("no frontmatter for " + article.name); - console.warn(article.name); - return; - } - - if (!article.body || !article.body.trim()) { - console.warn('Ignoring empty content file ' + (article.path || article.name)); - return; - } - - return true; - }); - - - function normalizeFrontmatter(page) { - var yml = page.yml - ; - - // TODO read the config for this collection for how to create premalink - if (!yml.permalink) { - if (page.name) { - page.htmlname = page.name.replace(/\.\w+$/, '.html'); - } - page.path = page.path || path.join(page.relativePath, page.name); - page.htmlpath = page.path.replace(/\.\w+$/, '.html'); - // TODO strip '_root' or whatever - // strip .html, .md, .jade, etc - if (!/^\/?(index)?\/?index(\.html)?/.test(yml.htmlpath)) { - console.info('found index again'); - yml.permalink = page.htmlpath; - } - console.info('1', yml.permalink); - } - - if (!/\.html?$/.test(yml.permalink)) { - console.info(page.yml.permalink); - yml.permalink = path.join(yml.permalink, 'index.html'); - } - - //yml.permalinkBase = path.join(path.dirname(yml.permalink), path.basename(yml.permalink, path.extname(yml.permalink))); - //yml.permalink = path.join(path.dirname(yml.permalink), path.basename(yml.permalink, path.extname(yml.permalink))); - - if (!page.yml.uuid) { - // TODO only do this if it's going to be saved - // page.yml.uuid = UUID.v4(); - } - - if (!page.yml.date) { - // TODO tell YAML parser to keep the date a string - page.yml.date = new Date(page.yml.created_at || page.yml.time || page.createdDate || page.lastModifiedDate).toISOString(); - } - if ('object' === typeof page.yml.date) { - page.yml.date = page.yml.date.toISOString(); - } - - if (!page.yml.updated_at) { - page.yml.updated_at = page.lastModifiedDate; - } - } - - function normalizeContentEntity(entity) { - entity.ext = path.extname(entity.path); - entity.url = desi.urls.url + path.join(desi.urls.base_path, entity.yml.permalink); - entity.canonical_url = desi.urls.url + path.join(desi.urls.base_path, entity.yml.permalink); - entity.relative_url = path.join(desi.urls.base_path, entity.yml.permalink); - entity.published_at = fromLocaleDate(entity.yml.date); - entity.year = entity.published_at.year; - entity.month = entity.published_at.month; - entity.day = entity.published_at.day; - entity.hour = entity.published_at.hour; - entity.twelve_hour = entity.published_at.twelve_hour; - entity.meridian = entity.published_at.meridian; - entity.minute = entity.published_at.minute; - entity.title = entity.yml.title; - // let's just agree that that's too far - //entity.second = entity.published_at.second; - - // The root index is the one exception - if (/^(index)?\/?index(\.html?)?$/.test(entity.yml.permalink)) { - entity.yml.permalink = ''; - console.info('found index', entity); - } - } - - function byDate(a, b) { - if (a.year > b.year) { - return -1; - } else if (a.year < b.year) { - return 1; - } - - if (a.month > b.month) { - return -1; - } else if (a.month < b.month) { - return 1; - } - - if (a.day > b.day) { - return -1; - } else if (a.day < b.day) { - return 1; - } - - if (a.hour > b.hour) { - return -1; - } else if (a.hour < b.hour) { - return 1; - } - - if (a.minute > b.minute) { - return -1; - } else if (a.minute < b.minute) { - return 1; - } - - if (a.title.toLowerCase() <= b.title.toLowerCase()) { - return -1; - } - - return 1; - } - - function collate(entities) { - var yearsArr = [] - ; - - entities.forEach(function (f) { - var set - , yindex = 3000 - f.year - , mindex = 12 - f.month + desi.content.root.forEach(function (page) { + var name = path.basename(page.path, path.extname(page.path)) + , nindex ; - if (!yearsArr[yindex]) { - yearsArr[yindex] = { year: f.year, months: [] }; + //if (-1 === desi.data.navigation.indexOf(name) && 'index' !== name) + nindex = desi.data.navigation.indexOf(name); + if (-1 === nindex) { + return; } - set = yearsArr[yindex]; - if (!set.months[mindex]) { - set.months[mindex] = { month: months[parseInt(f.month, 10)], pages: [] }; - } - set = set.months[mindex]; - - set.pages.push(f); + desi.navigation[nindex] = { + title: page.yml && page.yml.title || firstCap(name) + , href: desi.urls.base_path + '/' + name + , path: desi.urls.base_path + '/' + name + , name: name + , active: false // placeholder + }; }); - yearsArr = yearsArr.filter(function (y) { - if (!y) { - return false; + desi.content.root.forEach(function (page) { + page.yml = page.yml || {}; + // TODO make default layout configurable + page.yml.layout = page.yml.layout || '_root'; + + if (!page.relativePath) { + page.relativePath = path.dirname(page.path); + page.name = page.name || path.basename(page.path); } - y.months = y.months.filter(function (m) { - return m && m.pages.length; - }); + page.relativePath = page.relativePath.replace(desi.config.rootdir, '').replace(/^\//, ''); + page.path = path.join(page.relativePath, page.name); - if (!y.months.length) { - return false; + // TODO make bare root routes configurable + page.yml.permalink = page.yml.permalink || page.path.replace(/\.\w+$/, ''); + + page.yml.title = page.yml.title || firstCap(page.name.replace(/\.\w+$/, '')); + }); + + desi.content.collections = desi.content.collections.filter(function (article) { + if (!article.yml) { + console.warn("no frontmatter for " + article.name); + console.warn(article.name); + return; + } + + if (!article.body || !article.body.trim()) { + console.warn('Ignoring empty content file ' + (article.path || article.name)); + return; } return true; }); - return { years: yearsArr }; - } - desi.content.root.forEach(normalizeFrontmatter); - // TODO process tags and categories and such - desi.content.collections.forEach(normalizeFrontmatter); - - desi.content.root.forEach(normalizeContentEntity); - desi.content.collections.forEach(normalizeContentEntity); - - desi.content.collections.sort(byDate); - desi.collated = collate(desi.content.collections); - console.info('desi.collated'); - console.info(desi.collated); - - return desi; - }).then(function (desi) { - var compiled = [] - ; - - /* - function compileScriptEntity(entity, i, arr) { - } - */ - desi.assets = []; - function compileThemeEntity(entity, i, arr) { - console.log("compiling " + (i + 1) + "/" + arr.length + " " + (entity.path || entity.name)); - // TODO less / sass / etc - compiled.push({ contents: entity.body || entity.contents, path: path.join(desi.config.compiled_path, 'themes', entity.path) }); - if (/stylesheets.*\.css/.test(entity.path) && (!/google/.test(entity.path) || /obsid/.test(entity.path))) { - // TODO XXX move to a partial - desi.assets.push( - '' - ); - } - } - desi.navigation.filter(function (n) { - return n; - }); - //console.log(desi.navigation); - function compileContentEntity(entity, i, arr) { - console.log("compiling " + (i + 1) + "/" + arr.length + " " + (entity.path || entity.name)); - - var previous = '' - , layers - , view - ; - - layers = getLayout(desi, entity.yml.theme, entity.yml.layout, [entity]); - - view = { - page: entity.yml // data for just *this* page - //, data: desi.data // data.yml - // https://github.com/janl/mustache.js/issues/415 - , data: num2str(desi.data) - , collection: {} // data for just *this* collection - , categories: [] // *all* categories in all collections - , tags: [] // *all* tags in all collections - , site: num2str(desi.site || {}) - , url: entity.canonical_url - , canonical_url: entity.canonical_url - , relative_url: entity.relative_url - , urls: desi.urls - , previous: arr[i - 1] - , next: arr[i + 1] - , posts: { collated: desi.collated } - // TODO concat theme, widget, and site assets - , assets: desi.assets.join('\n') - }; - - //console.log('rel:', view.relative_url); - view.site.author = desi.data.author; - view.site.navigation = JSON.parse(JSON.stringify(desi.navigation)); - view.site.navigation.forEach(function (nav) { - - if (nav.href === view.relative_url) { - nav.active = true; - } - }); - // backwards compat - view.site['navigation?to_pages'] = view.site.navigation; - view.site['navigation?to__root'] = view.site.navigation; - view.data.navigation = view.site.navigation; - view.data['navigation?to_pages'] = view.site.navigation; - view.data['navigation?to__root'] = view.site.navigation; - - layers.forEach(function (current) { - // TODO meta.layout - var body = (current.body || current.contents || '').trim() - , html - , curview = {} + function normalizeFrontmatter(page) { + var yml = page.yml ; - // TODO move to normalization - current.path = current.path || (entity.relativePath + '/' + entity.name); + // TODO read the config for this collection for how to create premalink + if (!yml.permalink) { + if (page.name) { + page.htmlname = page.name.replace(/\.\w+$/, '.html'); + } + page.path = page.path || path.join(page.relativePath, page.name); + page.htmlpath = page.path.replace(/\.\w+$/, '.html'); + // TODO strip '_root' or whatever + // strip .html, .md, .jade, etc + if (!/^\/?(index)?\/?index(\.html)?/.test(yml.htmlpath)) { + console.info('found index again'); + yml.permalink = page.htmlpath; + } + console.info('1', yml.permalink); + } - if (/\.(html|htm)$/.test(current.path)) { - html = body; - } else if (/\.(md|markdown|mdown|mkdn|mkd|mdwn|mdtxt|mdtext)$/.test(current.ext)) { - html = marked.render(body) - //.replace('"', '"') - //.replace(''', "'") - //.replace('/', '/') + if (!/\.html?$/.test(yml.permalink)) { + console.info(page.yml.permalink); + yml.permalink = path.join(yml.permalink, 'index.html'); + } + + //yml.permalinkBase = path.join(path.dirname(yml.permalink), path.basename(yml.permalink, path.extname(yml.permalink))); + //yml.permalink = path.join(path.dirname(yml.permalink), path.basename(yml.permalink, path.extname(yml.permalink))); + + if (!page.yml.uuid) { + // TODO only do this if it's going to be saved + // page.yml.uuid = UUID.v4(); + } + + if (!page.yml.date) { + // TODO tell YAML parser to keep the date a string + page.yml.date = new Date(page.yml.created_at || page.yml.time || page.createdDate || page.lastModifiedDate).toISOString(); + } + if ('object' === typeof page.yml.date) { + page.yml.date = page.yml.date.toISOString(); + } + + if (!page.yml.updated_at) { + page.yml.updated_at = page.lastModifiedDate; + } + } + + function normalizeContentEntity(entity) { + entity.ext = path.extname(entity.path); + entity.url = desi.urls.url + path.join(desi.urls.base_path, entity.yml.permalink); + entity.canonical_url = desi.urls.url + path.join(desi.urls.base_path, entity.yml.permalink); + entity.relative_url = path.join(desi.urls.base_path, entity.yml.permalink); + entity.published_at = fromLocaleDate(entity.yml.date); + entity.year = entity.published_at.year; + entity.month = entity.published_at.month; + entity.day = entity.published_at.day; + entity.hour = entity.published_at.hour; + entity.twelve_hour = entity.published_at.twelve_hour; + entity.meridian = entity.published_at.meridian; + entity.minute = entity.published_at.minute; + entity.title = entity.yml.title; + // let's just agree that that's too far + //entity.second = entity.published_at.second; + + // The root index is the one exception + if (/^(index)?\/?index(\.html?)?$/.test(entity.yml.permalink)) { + entity.yml.permalink = ''; + console.info('found index', entity); + } + } + + function byDate(a, b) { + if (a.year > b.year) { + return -1; + } else if (a.year < b.year) { + return 1; + } + + if (a.month > b.month) { + return -1; + } else if (a.month < b.month) { + return 1; + } + + if (a.day > b.day) { + return -1; + } else if (a.day < b.day) { + return 1; + } + + if (a.hour > b.hour) { + return -1; + } else if (a.hour < b.hour) { + return 1; + } + + if (a.minute > b.minute) { + return -1; + } else if (a.minute < b.minute) { + return 1; + } + + if (a.title.toLowerCase() <= b.title.toLowerCase()) { + return -1; + } + + return 1; + } + + function collate(entities) { + var yearsArr = [] + ; + + entities.forEach(function (f) { + var set + , yindex = 3000 - f.year + , mindex = 12 - f.month ; - } else { - console.error('unknown parser for ' + (entity.path)); - } - view.content = previous; - view.page.content = previous; + if (!yearsArr[yindex]) { + yearsArr[yindex] = { year: f.year, months: [] }; + } + set = yearsArr[yindex]; - // to prevent perfect object equality (and potential template caching) - Object.keys(view).forEach(function (key) { - curview[key] = view[key]; + if (!set.months[mindex]) { + set.months[mindex] = { month: months[parseInt(f.month, 10)], pages: [] }; + } + set = set.months[mindex]; + + set.pages.push(f); }); - previous = Mustache.render(html, curview, desi.partials); - }); - // NOTE: by now, all permalinks should be in the format /path/to/page.html or /path/to/page/index.html - if (/^(index)?(\/?index.html)?$/.test(entity.yml.permalink)) { - console.info('found compiled index'); - compiled.push({ contents: previous, path: path.join(desi.config.compiled_path, 'index.html') }); - } else { - compiled.push({ contents: previous, path: path.join(desi.config.compiled_path, entity.yml.permalink) }); + yearsArr = yearsArr.filter(function (y) { + if (!y) { + return false; + } + + y.months = y.months.filter(function (m) { + return m && m.pages.length; + }); + + if (!y.months.length) { + return false; + } + + return true; + }); + + return { years: yearsArr }; } - entity.yml.redirects = entity.yml.redirects || []; - if (/\/index.html$/.test(entity.yml.permalink)) { - entity.yml.redirects.push(entity.yml.permalink.replace(/\/index.html$/, '.html')); - } else if (/\.html$/.test(entity.yml.permalink)) { - entity.yml.redirects.push(entity.yml.permalink.replace(/\.html?$/, '/index.html')); - } else { - console.info('found index, ignoring redirect'); - } - entity.yml.redirects.forEach(function (redirect) { - var content - ; + desi.content.root.forEach(normalizeFrontmatter); + // TODO process tags and categories and such + desi.content.collections.forEach(normalizeFrontmatter); - // TODO move to partial - content = - '' - + '
' - + 'This page has moved to a ' - + entity.yml.title - + '.
' - + '' - + '' + desi.content.root.forEach(normalizeContentEntity); + desi.content.collections.forEach(normalizeContentEntity); + + desi.content.collections.sort(byDate); + desi.collated = collate(desi.content.collections); + console.info('desi.collated'); + console.info(desi.collated); + + return desi; + }).then(function (desi) { + var compiled = [] ; - compiled.push({ contents: content, path: path.join(desi.config.compiled_path, redirect) }); + /* + function compileScriptEntity(entity, i, arr) { + } + */ + desi.assets = []; + function compileThemeEntity(entity, i, arr) { + console.log("compiling " + (i + 1) + "/" + arr.length + " " + (entity.path || entity.name)); + // TODO less / sass / etc + compiled.push({ contents: entity.body || entity.contents, path: path.join(desi.config.compiled_path, 'themes', entity.path) }); + if (/stylesheets.*\.css/.test(entity.path) && (!/google/.test(entity.path) || /obsid/.test(entity.path))) { + // TODO XXX move to a partial + desi.assets.push( + '' + ); + } + } + desi.navigation.filter(function (n) { + return n; }); - } + //console.log(desi.navigation); + function compileContentEntity(entity, i, arr) { + console.log("compiling " + (i + 1) + "/" + arr.length + " " + (entity.path || entity.name)); - console.info('[first] compiling theme assets'); - desi.content.themes.filter(function (f) { return !/\blayouts\b/.test(f.path); }).forEach(compileThemeEntity); + var previous = '' + , layers + , view + ; - console.info('compiling root pages'); - desi.content.root.forEach(compileContentEntity); - console.info('compiling article pages'); - desi.content.collections.forEach(compileContentEntity); + layers = getLayout(desi, entity.yml.theme, entity.yml.layout, [entity]); - desi.compiled = compiled; - return desi; - }).then(function (desi) { - var compiled = desi.compiled.slice(0) - , batches = [] - , now - , size = 0 - ; + view = { + page: entity.yml // data for just *this* page + //, data: desi.data // data.yml + // https://github.com/janl/mustache.js/issues/415 + , data: num2str(desi.data) + , collection: {} // data for just *this* collection + , categories: [] // *all* categories in all collections + , tags: [] // *all* tags in all collections + , site: num2str(desi.site || {}) + , url: entity.canonical_url + , canonical_url: entity.canonical_url + , relative_url: entity.relative_url + , urls: desi.urls + , previous: arr[i - 1] + , next: arr[i + 1] + , posts: { collated: desi.collated } + // TODO concat theme, widget, and site assets + , assets: desi.assets.join('\n') + }; - if (!compiled.length) { - console.info("No files were deemed worthy to compile. Done"); - return; - } - - // because some servers / proxies are terrible at handling large uploads (>= 100k) - // (vagrant? or express? one of the two is CRAZY slow) - console.info('saving compiled files', desi.compiled); - while (compiled.length) { - batches.push(compiled.splice(0, 500)); - } - - now = Date.now(); - console.info('compiled files'); - return forEachAsync(batches, function (files) { - return fsapi.putFiles(files).then(function (saved) { - size += saved.size; - - if (saved.error) { - console.error(saved.error); - } - - if (!saved.errors || !saved.errors.length) { - return; - } - - saved.errors.forEach(function (e) { - console.error(e); + //console.log('rel:', view.relative_url); + view.site.author = desi.data.author; + view.site.navigation = JSON.parse(JSON.stringify(desi.navigation)); + view.site.navigation.forEach(function (nav) { + + if (nav.href === view.relative_url) { + nav.active = true; + } }); - //console.info('saved ' + files.length + ' files'); - //console.log(saved); + // backwards compat + view.site['navigation?to_pages'] = view.site.navigation; + view.site['navigation?to__root'] = view.site.navigation; + view.data.navigation = view.site.navigation; + view.data['navigation?to_pages'] = view.site.navigation; + view.data['navigation?to__root'] = view.site.navigation; + + layers.forEach(function (current) { + // TODO meta.layout + var body = (current.body || current.contents || '').trim() + , html + , curview = {} + ; + + // TODO move to normalization + current.path = current.path || (entity.relativePath + '/' + entity.name); + + if (/\.(html|htm)$/.test(current.path)) { + html = body; + } else if (/\.(md|markdown|mdown|mkdn|mkd|mdwn|mdtxt|mdtext)$/.test(current.ext)) { + html = marked.render(body) + //.replace('"', '"') + //.replace(''', "'") + //.replace('/', '/') + ; + } else { + console.error('unknown parser for ' + (entity.path)); + } + + view.content = previous; + view.page.content = previous; + + // to prevent perfect object equality (and potential template caching) + Object.keys(view).forEach(function (key) { + curview[key] = view[key]; + }); + previous = Mustache.render(html, curview, desi.partials); + }); + + // NOTE: by now, all permalinks should be in the format /path/to/page.html or /path/to/page/index.html + if (/^(index)?(\/?index.html)?$/.test(entity.yml.permalink)) { + console.info('found compiled index'); + compiled.push({ contents: previous, path: path.join(desi.config.compiled_path, 'index.html') }); + } else { + compiled.push({ contents: previous, path: path.join(desi.config.compiled_path, entity.yml.permalink) }); + } + + entity.yml.redirects = entity.yml.redirects || []; + if (/\/index.html$/.test(entity.yml.permalink)) { + entity.yml.redirects.push(entity.yml.permalink.replace(/\/index.html$/, '.html')); + } else if (/\.html$/.test(entity.yml.permalink)) { + entity.yml.redirects.push(entity.yml.permalink.replace(/\.html?$/, '/index.html')); + } else { + console.info('found index, ignoring redirect'); + } + entity.yml.redirects.forEach(function (redirect) { + var content + ; + + // TODO move to partial + content = + '' + + '' + + 'This page has moved to a ' + + entity.yml.title + + '.
' + + '' + + '' + ; + + compiled.push({ contents: content, path: path.join(desi.config.compiled_path, redirect) }); + }); + } + + console.info('[first] compiling theme assets'); + desi.content.themes.filter(function (f) { return !/\blayouts\b/.test(f.path); }).forEach(compileThemeEntity); + + console.info('compiling root pages'); + desi.content.root.forEach(compileContentEntity); + console.info('compiling article pages'); + desi.content.collections.forEach(compileContentEntity); + + desi.compiled = compiled; + return desi; + }).then(function (desi) { + var compiled = desi.compiled.slice(0) + , batches = [] + , now + , size = 0 + ; + + if (!compiled.length) { + console.info("No files were deemed worthy to compile. Done"); + return; + } + + // because some servers / proxies are terrible at handling large uploads (>= 100k) + // (vagrant? or express? one of the two is CRAZY slow) + console.info('saving compiled files', desi.compiled); + while (compiled.length) { + batches.push(compiled.splice(0, 500)); + } + + now = Date.now(); + console.info('compiled files'); + return forEachAsync(batches, function (files) { + return fsapi.putFiles(files).then(function (saved) { + size += saved.size; + + if (saved.error) { + console.error(saved.error); + } + + if (!saved.errors || !saved.errors.length) { + return; + } + + saved.errors.forEach(function (e) { + console.error(e); + }); + //console.info('saved ' + files.length + ' files'); + //console.log(saved); + }); + }).then(function () { + // TODO update cache + console.info('wrote ' + desi.compiled.length + + ' files (' + (size / (1024 * 1024)).toFixed(2) + + ' MiB) in ' + + ((Date.now() - now) / 1000).toFixed(3) + 's' + ); }); - }).then(function () { - // TODO update cache - console.info('wrote ' + desi.compiled.length - + ' files (' + (size / (1024 * 1024)).toFixed(2) - + ' MiB) in ' - + ((Date.now() - now) / 1000).toFixed(3) + 's' - ); }); - }).catch(function (e) { - console.error('A great and uncatchable error has befallen the land. Read ye here for das detalles..'); - console.error(e.message); - console.error(e); - throw e; - }); + }; + + exports.Desi = Desi.Desi = Desi; }('undefined' !== typeof exports && exports || window)); diff --git a/index.html b/index.html index 4e11fca..7b8c4d5 100644 --- a/index.html +++ b/index.html @@ -1,26 +1,113 @@ + + + + + + + + - - -Open up the console, fool!
- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/views/about/about.html b/views/about/about.html new file mode 100644 index 0000000..cab8ef4 --- /dev/null +++ b/views/about/about.html @@ -0,0 +1,45 @@ +Dear Desi, ...