# Did you mean DearDesi? If you're looking for [DearDesi](http://dear.desi), the DIY blog platform for normal people you should go to . Desirae (this repo) is the code that programmers to use to make DearDesi better for everyone and to make Desirae-compatible blog platforms. (it's blog-ception!) # Desirae (v0.9) Desirae is a static webpage compiler written in JavaScript. It can run entirely in the browser (with a little help from a minimal just to serve to read and save files). It can also run entirely from the commandline (with io.js / node.js). **Features:** - `JavaScript` - it's so stable it takes 10 years to get new any features. - Won't break every time you upgrade OS X and reinstall `brew` (_cough_ ruby) - Decent use of `try { ... } catch(e) ...` and `promise.catch()` - the idea is that it shouldn't blow up at the slightest parse error without telling you which page is to blame (_cough_ ruhoh _cough_ jekyll)... bless me - Browser (optional) - using your front-end templates to build in your front-end? Imagine that! - io.js (node.js) (optional) - if you'd prefer to go headless, you can. - The server is _very_ minimal and could easily be implemented in any language (such as ruby or python). # Installation ```bash bower install --save desirae npm install --save desirae ``` # Why Because I hate ruby. Well, I don't hate it, but it hates me. Or at least it moves too fast and has too many breaking changes between updates. Anyway, I had to drop [ruhoh](http://ruhoh.com) because I made a new year's resolution to blog more (last year I made dozens of gists and 0 blog posts) and I just couldn't get the right ruby versions and gems and whatnot... so I gave up and wrote my own (because I needed something compatible with ruhoh). # Usage Overview ### Before we get started (disclaimers) **Browser**: The default fs adapter will request the config files from `/api/fs/`. **Node**: Remember that desirae is built browser-first, and optimized to reduce the number of round-trips (in case someone actually desides to host a Desi service, y'know?), so... the node adapter is built with the server in mind. If you can't respect that, don't look at the code. :-) **NOTE**: The mixing of `snake_case` with `camelCase` is somewhat intentional. It's an artifact of this project being born out of the ashes of my [ruhoh](http://ruhoh.com/) blog, which is built in ruby and uses YAML. ### Getting Started First off you need to declare a state object that will be used in every _desirae_ action. ```javascript var desi = {}; ``` After that you'll load any plugins you need. Here's how you would load all of the common plugins: ```javascript // load the module whether in browser or node function dload(filename, exportname) { return dload('undefined' !== typeof window && window[exportname] || require(filename)[exportname]; } // // 1. Transform (yml, slug, etc) // Desi.registerTransform( 'lint' , dload('desirae/lib/transform-core', 'DesiraeTransformCore').lint , { collections: true } ); Desi.registerTransform( 'root' , dload('desirae/lib/transform-core', 'DesiraeTransformCore').root , { root: true } ); Desi.registerTransform( 'normalize' , dload('desirae/lib/transform-core', 'DesiraeTransformCore').normalize , { root: true, collections: true } ); Desi.registerTransform( 'disqus' , dload('desirae/lib/transform-core', 'DesiraeTransformCore').disqus , { collections: true } ); // // 2. Register Aggregators (rss, categories, tags, etc) // Desi.registerAggregator(dload('desirae/lib/aggregate-core', 'DesiraeAggregateCore').collate); // // 3. Register Datamappers (ruhoh, desirae, jade, mustache, liquid) // Desi.registerDataMapper('desirae', dload('desirae/lib/datamap-core', 'DesiraeDatamapCore')); Desi.registerDataMapper('desirae@1.0', dload('desirae/lib/datamap-core', 'DesiraeDatamapCore')); Desi.registerDataMapper('ruhoh', dload('desirae-datamap-ruhoh', 'DesiraeDatamapRuhoh')); Desi.registerDataMapper('ruhoh@1.0', dload('desirae-datamap-ruhoh', 'DesiraeDatamapRuhoh')); Desi.registerDataMapper('ruhoh@2.6', dload('desirae-datamap-ruhoh', 'DesiraeDatamapRuhoh')); // // 4. Register Renderers (md -> html, less -> css, etc) // Desi.registerRenderer( 'js' , dload('desirae/lib/render-core', 'DesiraeRenderJs') , { themes: true, assets: true } ); Desi.registerRenderer( 'css' , dload('desirae/lib/render-core', 'DesiraeRenderCss') , { themes: true, assets: true } ); Desi.registerRenderer( 'html' , dload('desirae/lib/render-core', 'DesiraeRenderHtml') , { root: true, collections: true, themes: true, assets: true } ); ['md', 'markdown'].forEach(function (ext) { Desi.registerRenderer( ext , dload('desirae/lib/render-core', 'DesiraeRenderMarkdown') , { root: true, collections: true } ); }); Desi.registerRenderer( 'jade' , dload('desirae/lib/render-core', 'DesiraeRenderJade') , { root: true, collections: true, themes: true } ); ``` And then you'll initialize Desirae with an _environment_. ```javascript Desirae.init(desi, { url: "https://johndoe.exmaple.com/blog", base_url: "https://johndoe.exmaple.com", base_path: "/blog", compiled_path: "compiled_dev", // default: continue when possible onError: function (e) { return Promise.reject(e); }, // io.js / node.js only working_path: "./path/to/blog", }).then(function () { console.log("Desirae is initialized"); }); ``` Using the paths specified in the environment it will read the appropriate `config.yml`, `site.yml`, and `authors/*.yml` files to initialize itself. Then you can specify to build the static blog. You'll need to pass the environment again. ``` Desirae.buildAll(desi, env).then(function () { console.log('Desirae built your blog!'); }); ``` Finally, you save the built out to disk. ``` Desirae.write(desi, env).then(function () { console.log('Desirae pushd all files to the appropriate fs adapter!'); }); ``` ### Plugins You need to start every file with a wrapper that is browser and io.js/node.js compatible ```javascript /*jshint -W054 */ (function (exports) { "use strict"; var DesiraeMyModule = {}; // ... a bunch of code ... DesiraeMyModule.doStuff = doStuff; exports.DesiraeMyModule = DesiraeMyModule.DesiraeMyModule = DesiraeMyModule; })(("undefined" !== typeof exports && exports) || window); ``` Other than that, just be mindful that your code needs to run in both iojs/node and browser environments so steer away from things that are super iojs/node-ish or super window-ish. # Configuration There are a few configuration files: - `site.yml` is stuff that might be unique to your site, such as (title, url, adwords id, etc) - `authors/<>` contains information about you (name, handle, facebook, etc) - `config.yml` contains directives that describe _how_ the blog should be compiled - more technical stuff. If any of these files change, the entire site needs to be retemplated. # API I'd like to make the plugin system connect-style API for adding plugins or whatever so that, for example, so you could have a custom markdown preprocessor (that handles includes, perhaps) and still pass the string along to the 'real' markdown parser afterwards. But here's what I've got so far: ## Rendering Engines ### (html, markdown, jade) - `Desirae.registerRenderer(ext, fn)` For example, if you want to add the ability to render from `slim` or `haml` instead of just `markdown` you could find the appropriate JavaScript module (or make requests to an API service that renders them for you) and do this ```javascript var slim = exports.slimjs || require("slimjs"); function render(contentstr /*, desi*/) { return Promise.resolve(slim(contentstr)); } Desirae.registerRenderer(".slim", render); ``` ## Data Mapping ### (desirae, ruhoh, etc) - `Desirae.registerDataMapper(ext, fn)` If you want to use a non-desirae theme that uses attributes in a different than how Desirae creates the view object internally (i.e. it wants `{{ page.name }}` instead of `{{ entity.title }}`), you can use a data mapper to accomplish this. Please try not to modify the original object if you can avoid it. _TODO: maybe pass in a two-level deep shallow copy ?_ ```javascript Desirae.registerDataMapper("ruhoh@3.0", function (view) { return { page: { name: view.entity.title, }, author: { nickname: view.author.twitter, }, // ... }; }); ``` The default datamapper is `ruhoh@2.6` (note that that might be misnamed, I'm a little confused as to how Ruhoh's template versions correspond to Ruhoh proper versions). ## Adapters ### (fs, http, dropbox) I'd love to work with anyone who is familiar with the Dropbox or similar APIs. I think it would be awesome to support various means of storage. Perhaps github gists too. # Server Obviously there has to be a server with some sort of storage and retrieval mechanism. I've implemented a very simple node server using the filesystem. ## GET /api/fs/walk `GET http://local.dear.desi:8080/api/fs/walk?dir=posts&dotfiles=true&extensions=md,markdown,jade,htm,html` - `dir` **must** be supplied. returns a flat list of all files, recursively - `dotfiles` default to `false`. includes dotfiles when `true`. - `extensions` defaults to `null`. inclode **only** the supplied extensions when `true`. ```json [ { "name": "happy-new-year.md", "createdDate": "2015-01-05T18:19:30.000Z", "lastModifiedDate": "2015-01-05T18:19:30.000Z", "size": 2121, "relativePath": "posts/2015" }, { "name": "tips-for-the-ages.jade", "createdDate": "2014-06-16T18:19:30.000Z", "lastModifiedDate": "2014-06-16T18:19:30.000Z", "size": 389, "relativePath": "posts" }, { "name": "my-first-post.html", "createdDate": "2013-08-01T22:47:37.000Z", "lastModifiedDate": "2013-08-01T22:47:37.000Z", "size": 4118, "relativePath": "posts/2013" } ] ``` To retrieve multiple dir listings at once: - for a few simple dirs without special chars just change `dir` to `dirs` and separate with commas `GET http://local.dear.desi:8080/api/fs/walk?dirs=posts/2015,posts/2013&dotfiles=true&extensions=md,markdown,jade,htm,html` - for many dirs, or dirs with special chars, `POST` an object containing an array of `dirs` with `&_method=GET` appended to the url. ``` POST http://local.dear.desi:8080/api/fs/walk?dotfiles=true&extensions=md,markdown,jade,htm,html&_method=GET { "dirs": [ "old", "2013,12", "2013,11" ] } ``` ```javascript { "posts/2015": [ { "name": ... }, { ... } ] , "posts/2013": [ ... ] } ``` ## GET /api/fs/files `GET http://local.dear.desi:8080/api/fs/files?path=posts/happy-new-year.md` ```json { "path": "posts/intro-to-http-with-netcat-node-connect.md", "createdDate": "2013-08-01T22:47:37.000Z", "lastModifiedDate": "2013-08-01T22:47:37.000Z", "contents": "...", "sha1": "6eae3a5b062c6d0d79f070c26e6d62486b40cb46" } ``` To retrieve multiple files at once: - for a few simple files without special chars just change `path` to `paths` and separate with commas `GET http://local.dear.desi:8080/api/fs/files?paths=posts/foo.md,posts/bar.md` - for many files, or files with special chars, `POST` an object containing an array of `pathss` with `&_method=GET` appended to the url. ``` POST http://local.dear.desi:8080/api/fs/files?dotfiles=true&extensions=md,markdown,jade,htm,html&_method=GET { "paths": [ "posts/foo.md", "posts/2013,11,30.md" ] } ``` ```json [ { "path": "posts/foo.md" , "lastModifiedDate": "2013-08-01T22:47:37.000Z" , "contents": "..." , "sha1": "6eae3a5b062c6d0d79f070c26e6d62486b40cb46" } , ... ] ``` ## POST /api/fs/files By default this should assume that you intended to write to the compiled directory and return an error if you try to write to any other directory, unless `compiled=false` (not yet implemented). `_method=PUT` is just for funzies. Including `sha1` is optional, but recommended. `lastModifiedDate` is optional and may or may not make any difference. `strict` (not yet implemented) fail immediately and completely on any error ```json POST http://local.dear.desi:8080/api/fs/files?compiled=true&_method=PUT { "files": [ { "path": "posts/foo.md" , "name": "foo.md" , "relativePath": "posts" , "createdDate": "2013-08-01T22:47:37.000Z" , "lastModifiedDate": "2013-08-01T22:47:37.000Z" , "contents": "..." , "sha1": "6eae3a5b062c6d0d79f070c26e6d62486b40cb46" , "delete": false } , ... ] } ``` The response may include errors of all shapes and sizes. ```json { "error": { message: "any top-level error", ... } , "errors": [ { "type": "file|directory" , "message": "maybe couldn't create the directory, but maybe still wrote the file. Maybe not" } , ... ] } ``` ## POST /api/fs/copy ```json { "files": { "assets/logo.png": "compiled/assets/logo.png" } } ``` # License This Source Code Form is subject to the terms of the Mozilla \ Public License, v. 2.0. If a copy of the MPL was not distributed \ with this file, You can obtain one at \ https://mozilla.org/MPL/2.0/.