Compare commits

...

13 Commits

Author SHA1 Message Date
AJ ONeal 706111f8ba fix directory redirects 2020-11-11 17:47:50 -07:00
AJ ONeal b0a56bec64 0.12.4 2020-11-08 21:51:34 -07:00
AJ ONeal 5ee8ea9b60 bugfix Promise and fs 2020-11-08 21:51:35 -07:00
AJ ONeal 215cef976f update author info 2020-11-08 20:29:39 -07:00
AJ ONeal ceb90c2bfa 0.12.0 2020-11-08 20:16:59 -07:00
AJ ONeal b5223b1053 remove deps, update to valid SPDX license 2020-11-08 20:16:55 -07:00
AJ ONeal a9cb7a58a3 0.11.6 2020-11-08 20:03:42 -07:00
AJ ONeal c81ff7a441 bugfix missing navigation links 2020-11-08 20:03:30 -07:00
AJ ONeal 9131fb9a42 make Prettier 2020-11-08 20:01:26 -07:00
AJ ONeal 0909c8e9cc v0.11.5: merge some leftovers 2018-11-07 10:38:50 -07:00
AJ ONeal 60e2764a1b Merge branch 'v1.0' 2018-11-07 10:38:12 -07:00
AJ ONeal d77b91e27a v0.11.4: update mustache dep 2018-11-07 10:27:39 -07:00
AJ ONeal eaf0c9bfba v0.11.3: update URL 2018-11-07 10:16:39 -07:00
25 changed files with 1689 additions and 1720 deletions

1
.prettierrc.json Normal file
View File

@ -0,0 +1 @@
{}

94
DATA.md
View File

@ -1,12 +1,10 @@
Data
====
# Data
Every template gets an object with the exact same structure - whether it's a template or a widget or a page or a post.
Here we only document Desirae's default behavior, but there are many objects added for compatibility with Ruhoh that are not documented.
config.yml vs site.yml
------
## config.yml vs site.yml
site.yml is for anything that changes the content of the site (navigation, title, analytic and ad ids, default author, etc)
@ -16,66 +14,60 @@ config.yml is for anything that doesn't change the site (from where to read dire
desi = {}
```
desi
====
# desi
* `config` - literally `config.yml`, parsed
* `site` - literally `site.yml`, parsed
* `authors` - literally the authors from `authors/*.yml`, parsed
* `author` - the primary author of the site
* `env` - urls and paths for this build (be it production, development, staging, etc)
* `content` - pre-rendered content (i.e. content rendered into the post layout rendered into the default layout)
* `collection` - config related to this collection
* `entity` - the page, post, article, etc that is the focus of the present template process
* `themes` - all themes
* `theme` - the default theme
* `layout` - the selected layout for this theme
* `satch` - the selected swatch for this theme
* `categories` - all categories
* `tags` - all tags
* `styles` - ??? goes into the final template in the head
* `scripts` - ?? that goes into the final template just before the body close
- `config` - literally `config.yml`, parsed
- `site` - literally `site.yml`, parsed
- `authors` - literally the authors from `authors/*.yml`, parsed
- `author` - the primary author of the site
- `env` - urls and paths for this build (be it production, development, staging, etc)
- `content` - pre-rendered content (i.e. content rendered into the post layout rendered into the default layout)
- `collection` - config related to this collection
- `entity` - the page, post, article, etc that is the focus of the present template process
- `themes` - all themes
- `theme` - the default theme
- `layout` - the selected layout for this theme
- `satch` - the selected swatch for this theme
- `categories` - all categories
- `tags` - all tags
- `styles` - ??? goes into the final template in the head
- `scripts` - ?? that goes into the final template just before the body close
desi.entity
===========
# desi.entity
stuff
* `uuid`
* `title`
* `disqus_url`
* `disqus_identifier`
- `uuid`
- `title`
- `disqus_url`
- `disqus_identifier`
more stuff
* `type` - `post`, `page`, etc
* `authors` - literally the relevant authors from `authors/*.yml`, parsed
* `author` - the primary author of this entity
* `theme` - null or a non-default theme
* `layout` - null or a non-default layout for this theme
* `swatch` - null or a non-default swatch for this theme
* `categories`: [] // *all* categories in all collections
* `tags`: [] // *all* categories in all collections
* `production_canonical_url` the PRODUCTION canonical_url for this entity
* `production_url` the PRODUCTION url for this entity
* `production_path` the PRODUCTION path for this entity
* `url` the full url in the current environment (might be production, development, etc)
* `path` the non-host part (i.e. `/compiled_dev/articles/my-first-post.html`)
* `previous` the previous entity in this collection
* `next` the next entitiy in this collection
- `type` - `post`, `page`, etc
- `authors` - literally the relevant authors from `authors/*.yml`, parsed
- `author` - the primary author of this entity
- `theme` - null or a non-default theme
- `layout` - null or a non-default layout for this theme
- `swatch` - null or a non-default swatch for this theme
- `categories`: [] // _all_ categories in all collections
- `tags`: [] // _all_ categories in all collections
- `production_canonical_url` the PRODUCTION canonical_url for this entity
- `production_url` the PRODUCTION url for this entity
- `production_path` the PRODUCTION path for this entity
- `url` the full url in the current environment (might be production, development, etc)
- `path` the non-host part (i.e. `/compiled_dev/articles/my-first-post.html`)
- `previous` the previous entity in this collection
- `next` the next entitiy in this collection
NOTE: Plugins, widgets, etc SHOULD NOT modify config, site, authors, author, or env.
desi.posts
==========
# desi.posts
, posts: { collated: desi.collated }
desi.config
===========
# desi.config
desi.site
===========
# desi.site
desi.env
===========
# desi.env

View File

@ -2,61 +2,65 @@ This is what an entity looks like:
```yml
# inherited from File Entity
path : My Posts/My-Old-Name.html
lastModifiedDate : 2015-07-04T13:56:01Z
createdDate : 2015-07-04T13:56:01Z
contents : '...' # whatever the file is
path: My Posts/My-Old-Name.html
lastModifiedDate: 2015-07-04T13:56:01Z
createdDate: 2015-07-04T13:56:01Z
contents: "..." # whatever the file is
# inherited from Collection Entity
name : My-Old-Name.html
relativePath : My Posts
ext : .html
collection : posts
name: My-Old-Name.html
relativePath: My Posts
ext: .html
collection: posts
# inherited from Content Entity
frontmatter : '---\n...\n---' # frontmatter as a string
yml : {} # frontmatter, parsed
body : 'I think ...' # body, after frontmatter
frontmatter: '---\n...\n---' # frontmatter as a string
yml: {} # frontmatter, parsed
body: "I think ..." # body, after frontmatter
# inherited from Normalized Entity
title : My Title # yml.title | titlize(entity.name)
slug : my-title # slugify(title)
slug_path : my-posts # slugifyPath(relativePath)
title: My Title # yml.title | titlize(entity.name)
slug: my-title # slugify(title)
slug_path: my-posts # slugifyPath(relativePath)
year : 2014
month : 07
day : 04
hour : 13
twelve_hour : 1
meridian : pm
minute : 22
year: 2014
month: 07
day: 04
hour: 13
twelve_hour: 1
meridian: pm
minute: 22
categories : ['tech']
tags : ['http','url','website']
categories: ["tech"]
tags:
["http", "url", "website"]
# includes index.html
relative_file : /posts/foo/index.html
# includes index.html
relative_file:
/posts/foo/index.html
# excludes index.html
relative_href : /posts/foo/
# excludes index.html
relative_href:
/posts/foo/
# actual url of this file, even if redirect
# excludes index.html
url : http://dev.example.com/posts/foo/
# actual url of this file, even if redirect
# excludes index.html
url:
http://dev.example.com/posts/foo/
# the appropriate url, even in a redirect or duplicate
# excludes index.html
canonical_url : http://dev.example.com/posts/foo/
# the appropriate url, even in a redirect or duplicate
# excludes index.html
canonical_url:
http://dev.example.com/posts/foo/
# production url, even in development (for disqus, etc)
# excludes index.html
production_url : http://example.com/posts/foo/
# production url, even in development (for disqus, etc)
# excludes index.html
production_url: http://example.com/posts/foo/
```
Note: The option `env.explicitIndexes` turns on `/index.html`. This option is automatically turned on when Dropbox is the host.
TODO
----
## TODO
* path relative from / in the browser
* path relative from base_path on the file system
- path relative from / in the browser
- path relative from base_path on the file system

View File

@ -1,14 +1,12 @@
Glossary
========
# Glossary
Canonical URL
--------
base\_url + base\_path + permalink
## Canonical URL
Base URL
----
base_url + base_path + permalink
base\_url is the point of ownership
## Base URL
base_url is the point of ownership
In most cases that would be https://johndoe.com
@ -16,27 +14,25 @@ In some cases that might be https://school.edu/~/johndoe
It does NOT include a trailing /
Base Path
-----
## Base Path
base\_path is the blog directory
base_path is the blog directory
In most cases that would be / or /blog/
It DOES include BOTH a LEADING and TRAILING slash.
In the case of https://school.edu/~/johndoe/weblog, the base\_path would be /weblog/.
In the case of https://school.edu/~/johndoe/weblog, the base_path would be /weblog/.
Permalink
------
## Permalink
The permalink is the permanent part of the URL, after the path to the blog.
For example:
* http://blog.johndoe.com/articles/first-post.html the permalink is articles/first-post.html
* http://johndoe.com/blog/articles/first-post.html the permalink is still articles/first-post.html
* http://school.edu/~/johndoe/blog/articles/first-post.html the permalink is yet still articles/first-post.html
- http://blog.johndoe.com/articles/first-post.html the permalink is articles/first-post.html
- http://johndoe.com/blog/articles/first-post.html the permalink is still articles/first-post.html
- http://school.edu/~/johndoe/blog/articles/first-post.html the permalink is yet still articles/first-post.html
The permalink is ALWAYS RELATIVE (no leading slash)
@ -44,5 +40,5 @@ It is designed so that if you ever move your blog from one domain, point of owne
a very simple one-line redirect can be made to your webserver and all of the posts will end up in the right place
once again.
base\_url the
permalink refers to
base_url the
permalink refers to

206
LICENSE
View File

@ -1,202 +1,4 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the 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/.

210
README.md
View File

@ -1,5 +1,4 @@
Did you mean DearDesi?
======================
# Did you mean DearDesi?
If you're looking for [DearDesi](http://dear.desi), the DIY blog platform for normal people
you should go to <http://dear.desi>.
@ -7,8 +6,7 @@ you should go to <http://dear.desi>.
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 (v0.9)
Desirae is a static webpage compiler written in JavaScript.
@ -19,18 +17,17 @@ 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).
- `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
============
# Installation
```bash
bower install --save desirae
@ -38,8 +35,7 @@ bower install --save desirae
npm install --save desirae
```
Why
===
# Why
Because I hate ruby.
@ -51,14 +47,13 @@ more (last year I made dozens of gists and 0 blog posts) and I just couldn't get
versions and gems and whatnot... so I gave up and wrote my own (because I needed something
compatible with ruhoh).
Usage Overview
==============
# Usage Overview
### Before we get started
(disclaimers)
**Browser**: The default fs adapter will request the config files from `/api/fs/`.
**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
@ -70,11 +65,10 @@ It's an artifact of this project being born out of the ashes of my
### Getting Started
First off you need to declare a state object that will be used in every *desirae* action.
First off you need to declare a state object that will be used in every _desirae_ action.
```javascript
var desi = {}
;
var desi = {};
```
After that you'll load any plugins you need.
@ -156,26 +150,24 @@ Desi.registerRenderer(
);
```
And then you'll initialize Desirae with an *environment*.
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'
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);
}
// 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');
// io.js / node.js only
working_path: "./path/to/blog",
}).then(function () {
console.log("Desirae is initialized");
});
```
@ -198,43 +190,38 @@ Desirae.write(desi, env).then(function () {
});
```
### 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 = {}
;
(function (exports) {
"use strict";
var DesiraeMyModule = {};
// ... a bunch of code ...
DesiraeMyModule.doStuff = doStuff;
exports.DesiraeMyModule = DesiraeMyModule.DesiraeMyModule = DesiraeMyModule;
}('undefined' !== typeof exports && exports || window));
})(("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
=============
# 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/<<your-handle.yml>>` contains information about you (name, handle, facebook, etc)
* `config.yml` contains directives that describe *how* the blog should be compiled - more technical stuff.
- `site.yml` is stuff that might be unique to your site, such as (title, url, adwords id, etc)
- `authors/<<your-handle.yml>>` 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
===
# 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)
@ -246,28 +233,26 @@ But here's what I've got so far:
### (html, markdown, jade)
* `Desirae.registerRenderer(ext, fn)`
- `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 PromiseA.resolve(slim(contentstr));
var slim = exports.slimjs || require("slimjs");
function render(contentstr /*, desi*/) {
return Promise.resolve(slim(contentstr));
}
Desirae.registerRenderer('.slim', render);
Desirae.registerRenderer(".slim", render);
```
## Data Mapping
### (desirae, ruhoh, etc)
* `Desirae.registerDataMapper(ext, fn)`
- `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
@ -276,18 +261,18 @@ 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 ?*
_TODO: maybe pass in a two-level deep shallow copy ?_
```javascript
Desirae.registerDataMapper('ruhoh@3.0', function (view) {
Desirae.registerDataMapper("ruhoh@3.0", function (view) {
return {
page: {
name: view.entity.title
}
, author: {
nickname: view.author.twitter
}
// ...
name: view.entity.title,
},
author: {
nickname: view.author.twitter,
},
// ...
};
});
```
@ -305,54 +290,54 @@ 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
======
# 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 /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`.
- `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": "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"
{
"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
- 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.
- 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
@ -367,27 +352,27 @@ POST http://local.dear.desi:8080/api/fs/walk?dotfiles=true&extensions=md,markdow
}
```
GET /api/fs/files
-------------
## 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"
{
"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
- 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.
- 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
@ -406,8 +391,7 @@ POST http://local.dear.desi:8080/api/fs/files?dotfiles=true&extensions=md,markdo
]
```
POST /api/fs/files
------------------
## 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).
@ -452,9 +436,15 @@ The response may include errors of all shapes and sizes.
}
```
POST /api/fs/copy
------------------
## POST /api/fs/copy
```json
{ files: { "assets/logo.png": "compiled/assets/logo.png" } }
{ "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/.

46
TODO.md
View File

@ -4,7 +4,6 @@ show file path
show prod url
show dev url
POST tests
create a title and delete it (no error)
change the format. does the permalink change? (yes)
@ -16,22 +15,17 @@ change the format in the frontmatter permalink. does the format change? (yes)
create a description and delete it (no error)
create a description. does the frontmatter change? (yes)
protection
Don't allow changing the uuid, original_url, or original_date
TODO
---
## TODO
check that no other post uses the same permalink
default data-model 'ruhoh@2.2'
other data-model 'desirae@1.0'
Widgets
=======
# Widgets
All widgets should export an object with a `create(widgetConf, desiState)` function that returns a promise.
@ -42,7 +36,7 @@ widgets:
# stuff like google ad and disqus ids should go in config.yml or data.yml
config:
foobeep: boop
handle:
- html
- markdown
@ -52,11 +46,10 @@ widgets:
```
```javascript
'use strict';
"use strict";
module.exports.Foogizmo.create = function (foogizmoConf, desiState) {
return new Promise(function (resolve) {
function pager(desiPageState) {
// Do processing
@ -68,29 +61,28 @@ module.exports.Foogizmo.create = function (foogizmoConf, desiState) {
desiPostState.fooembedinator = function (fooval) {
// figure out what type of link fooval is and return iframe html
return '<iframe src="http://embedinator.com/"' + foovalProcessed + '></iframe>'
}
return (
'<iframe src="http://embedinator.com/"' +
foovalProcessed +
"></iframe>"
);
};
}
resolve({ foopager: pager, fooposter: poster });
});
}
};
```
Overlays
--------
## Overlays
For any config a widget uses, it should also check on post.fooconfig and theme.fooconfig to make sure that they don't override the foogizmo.config.fooconfig
Migrating from Ruhoh
====================
# Migrating from Ruhoh
There are only a few things in Ruhoh that could only be done in ruby or were otherwise difficult to work around.
config.yml
----------
## config.yml
Instead of having special names for some properties (`_root`)
and `use` sub attributes for others (`twitter` theme, posts directory),
@ -117,19 +109,15 @@ widgets [NO CHANGE]
All directories are ignored by default. If you want a directory to be interpreted as a collection of pages you need to specify it in the `collections` hash.
data.yml
--------
## data.yml
No changes
config.ru
---------
## config.ru
REMOVED (ruby only)
themes layout
-------------
## themes layout
TODO

View File

@ -1,15 +1,10 @@
{
"name": "desirae",
"version": "0.11.2",
"authors": [
"AJ ONeal <awesome@coolaj86.com>"
],
"authors": ["AJ ONeal <awesome@coolaj86.com>"],
"description": "A blogging platform in the browser. Wow!",
"main": "desirae.js",
"moduleType": [
"globals",
"node"
],
"moduleType": ["globals", "node"],
"keywords": [
"desirae",
"dear",
@ -26,15 +21,8 @@
],
"license": "Apache2",
"homepage": "http://github.com/DearDesi/desirae",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"ignore": ["**/.*", "node_modules", "bower_components", "test", "tests"],
"dependencies": {
"bluebird": "~2.6.2",
"escape-string-regexp": "~1.0.2",
"forEachAsync": "~5.0.5",
"js-yaml": "~3.2.5",

1159
desirae.js

File diff suppressed because it is too large Load Diff

View File

@ -1,25 +1,23 @@
/*jshint -W054 */
;(function (exports) {
'use strict';
var path = exports.path || require('path')
, months
, cores = {}
;
(function (exports) {
"use strict";
var path = exports.path || require("path"),
months,
cores = {};
months = {
1: 'January'
, 2: 'February'
, 3: 'March'
, 4: 'April'
, 5: 'May'
, 6: 'June'
, 7: 'July'
, 8: 'August'
, 9: 'September'
, 10: 'October'
, 11: 'November'
, 12: 'December'
1: "January",
2: "February",
3: "March",
4: "April",
5: "May",
6: "June",
7: "July",
8: "August",
9: "September",
10: "October",
11: "November",
12: "December",
};
function byDate(a, b) {
@ -67,9 +65,7 @@
}
function collate(entities, env) {
var yearsArr = []
;
var yearsArr = [];
entities.forEach(function (f) {
var set;
var yindex = 3000 - f.year;
@ -85,10 +81,10 @@
if (!set.months[mindex]) {
set.months[mindex] = {
month_name: monthName
, month_number: mindex
, month: monthName
, pages: []
month_name: monthName,
month_number: mindex,
month: monthName,
pages: [],
};
}
set = set.months[mindex];
@ -125,4 +121,4 @@
};
exports.DesiraeAggregateCore = cores.DesiraeAggregateCore = cores;
}('undefined' !== typeof exports && exports || window));
})(("undefined" !== typeof exports && exports) || window);

View File

@ -1,67 +1,66 @@
/*jshint -W054 */
;(function (exports) {
'use strict';
(function (exports) {
"use strict";
function create(Desi) {
// Chrome, Firefox, and even MSIE11+ all support crypto
var crypto = window.crypto || window.msCrypto
, PromiseA = window.Promise
, algos
;
var crypto = window.crypto || window.msCrypto,
Promise = window.Promise,
algos;
// convenience mappings for common digest algorithms
algos = {
'sha1': 'SHA-1'
, 'sha256': 'SHA-256'
, 'sha512': 'SHA-512'
sha1: "SHA-1",
sha256: "SHA-256",
sha512: "SHA-512",
};
// The function to generate a sha1sum is the same as generating any digest
// but here's a shortcut function anyway
function sha1sum(str) {
return hashsum('sha1', str);
return hashsum("sha1", str);
}
// a more general convenience function
function hashsum(hash, str) {
// you have to convert from string to array buffer
var ab
// you have to represent the algorithm as an object
, algo = { name: algos[hash] }
;
if ('string' === typeof str) {
// you have to convert from string to array buffer
var ab,
// you have to represent the algorithm as an object
algo = { name: algos[hash] };
if ("string" === typeof str) {
ab = str2ab(str);
} else {
ab = str;
}
// All crypto digest methods return a promise
return crypto.subtle.digest(algo, ab).then(function (digest) {
// you have to convert the ArrayBuffer to a DataView and then to a hex String
return ab2hex(digest);
}).catch(function (e) {
// if you specify an unsupported digest algorithm or non-ArrayBuffer, you'll get an error
console.error('sha1sum ERROR');
console.error(e);
throw e;
});
return crypto.subtle
.digest(algo, ab)
.then(function (digest) {
// you have to convert the ArrayBuffer to a DataView and then to a hex String
return ab2hex(digest);
})
.catch(function (e) {
// if you specify an unsupported digest algorithm or non-ArrayBuffer, you'll get an error
console.error("sha1sum ERROR");
console.error(e);
throw e;
});
}
// convert from arraybuffer to hex
function ab2hex(ab) {
var dv = new DataView(ab)
, i
, len
, hex = ''
, c
;
var dv = new DataView(ab),
i,
len,
hex = "",
c;
for (i = 0, len = dv.byteLength; i < len; i += 1) {
c = dv.getUint8(i).toString(16);
if (c.length < 2) {
c = '0' + c;
c = "0" + c;
}
hex += c;
@ -72,36 +71,31 @@
// convert from string to arraybuffer
function str2ab(stringToEncode, insertBom) {
stringToEncode = stringToEncode.replace(/\r\n/g,"\n");
stringToEncode = stringToEncode.replace(/\r\n/g, "\n");
var utftext = []
, n
, c
;
var utftext = [],
n,
c;
if (true === insertBom) {
utftext[0] = 0xef;
utftext[1] = 0xbb;
utftext[2] = 0xbf;
if (true === insertBom) {
utftext[0] = 0xef;
utftext[1] = 0xbb;
utftext[2] = 0xbf;
}
for (n = 0; n < stringToEncode.length; n += 1) {
c = stringToEncode.charCodeAt(n);
if (c < 128) {
utftext[utftext.length]= c;
utftext[utftext.length] = c;
} else if (c > 127 && c < 2048) {
utftext[utftext.length] = (c >> 6) | 192;
utftext[utftext.length] = (c & 63) | 128;
} else {
utftext[utftext.length] = (c >> 12) | 224;
utftext[utftext.length] = ((c >> 6) & 63) | 128;
utftext[utftext.length] = (c & 63) | 128;
}
else if((c > 127) && (c < 2048)) {
utftext[utftext.length] = (c >> 6) | 192;
utftext[utftext.length] = (c & 63) | 128;
}
else {
utftext[utftext.length] = (c >> 12) | 224;
utftext[utftext.length] = ((c >> 6) & 63) | 128;
utftext[utftext.length] = (c & 63) | 128;
}
}
return new Uint8Array(utftext).buffer;
}
@ -112,26 +106,21 @@
//
// FSAPI
//
var fsapi
;
var fsapi;
function request() {
}
request.get = function (url/*, query*/) {
function request() {}
request.get = function (url /*, query*/) {
// Return a new promise.
return new PromiseA(function(resolve, reject) {
return new Promise(function (resolve, reject) {
// Do the usual XHR stuff
var req = new XMLHttpRequest()
;
req.onload = function() {
var req = new XMLHttpRequest();
req.onload = function () {
// This is called even on 404 etc
// so check the status
if (200 === req.status) {
// Resolve the promise with the response text
resolve(req.response);
}
else {
} else {
// Otherwise reject with the status text
// which will hopefully be a meaningful error
reject(Error(req.statusText));
@ -139,30 +128,27 @@
};
// Handle network errors
req.onerror = function() {
req.onerror = function () {
reject(Error("Network Error"));
};
// Make the request
req.open('GET', url);
req.open("GET", url);
req.send();
});
};
request.post = function (url/*, query*/, body) {
request.post = function (url /*, query*/, body) {
// Return a new promise.
return new PromiseA(function(resolve, reject) {
return new Promise(function (resolve, reject) {
// Do the usual XHR stuff
var req = new XMLHttpRequest()
;
req.onload = function() {
var req = new XMLHttpRequest();
req.onload = function () {
// This is called even on 404 etc
// so check the status
if (200 === req.status) {
// Resolve the promise with the response text
resolve(req.response);
}
else {
} else {
// Otherwise reject with the status text
// which will hopefully be a meaningful error
reject(Error(req.statusText));
@ -170,14 +156,14 @@
};
// Handle network errors
req.onerror = function() {
req.onerror = function () {
reject(Error("Network Error"));
};
req.open('POST', url);
req.open("POST", url);
req.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
// Make the request
if ('string' !== typeof body) {
if ("string" !== typeof body) {
body = JSON.stringify(body);
}
req.send(body);
@ -188,87 +174,93 @@
fsapi.getMeta = function (collections, opts) {
opts = opts || {};
var extensions = ''
, dotfiles = ''
, contents = ''
, sha1sum = ''
;
var extensions = "",
dotfiles = "",
contents = "",
sha1sum = "";
if (Array.isArray(opts.extensions)) {
extensions = '&extensions=' + opts.extensions.join(','); // md,markdown,jade,htm,html
extensions = "&extensions=" + opts.extensions.join(","); // md,markdown,jade,htm,html
}
if (opts.dotfiles) {
dotfiles = '&dotfiles=true';
dotfiles = "&dotfiles=true";
}
if (opts.contents) {
contents = '&contents=true';
contents = "&contents=true";
}
if (false === opts.sha1sum) {
sha1sum = '&sha1sum=false';
sha1sum = "&sha1sum=false";
}
return request.post('/api/fs/walk?_method=GET' + dotfiles + extensions + contents + sha1sum, {
dirs: collections
}).then(function (resp) {
return JSON.parse(resp);
}).catch(function (e) {
throw e;
});
return request
.post(
"/api/fs/walk?_method=GET" +
dotfiles +
extensions +
contents +
sha1sum,
{
dirs: collections,
}
)
.then(function (resp) {
return JSON.parse(resp);
})
.catch(function (e) {
throw e;
});
};
fsapi.getContents = function (filepaths) {
return request.post('/api/fs/files?_method=GET', {
paths: filepaths
}).then(function (resp) {
return JSON.parse(resp);
});
return request
.post("/api/fs/files?_method=GET", {
paths: filepaths,
})
.then(function (resp) {
return JSON.parse(resp);
});
};
fsapi.getCache = function () {
return request.get('/api/fs/static/cache.json').then(function (resp) {
return JSON.parse(resp);
}).catch(function (/*e*/) {
return {};
}).then(function (obj) {
return obj;
});
return request
.get("/api/fs/static/cache.json")
.then(function (resp) {
return JSON.parse(resp);
})
.catch(function (/*e*/) {
return {};
})
.then(function (obj) {
return obj;
});
};
fsapi.copy = function (files) {
var body = { files: files };
body = JSON.stringify(body); // this is more or less instant for a few MiB of posts
return request.post('/api/fs/copy', body).then(function (resp) {
var response = JSON.parse(resp)
;
return request.post("/api/fs/copy", body).then(function (resp) {
var response = JSON.parse(resp);
// not accurate for utf8/unicode, but close enough
response.size = body.length;
return response;
});
};
fsapi.putFiles = function (files) {
var body = { files: files }
;
var body = { files: files };
files.forEach(function (file) {
if (!file.contents || 'string' === typeof file.contents) {
if (!file.contents || "string" === typeof file.contents) {
return;
}
if (/\.json$/i.test(file.path)) {
file.contents = JSON.stringify(file.contents);
}
else if (/\.ya?ml$/i.test(file.path)) {
file.contents = exports.jsyaml.dump(file.contents);
} else if (/\.ya?ml$/i.test(file.path)) {
file.contents = exports.jsyaml.dump(file.contents);
}
});
body = JSON.stringify(body); // this is more or less instant for a few MiB of posts
return request.post('/api/fs/files', body).then(function (resp) {
var response = JSON.parse(resp)
;
return request.post("/api/fs/files", body).then(function (resp) {
var response = JSON.parse(resp);
// not accurate for utf8/unicode, but close enough
response.size = body.length;
return response;
@ -281,4 +273,4 @@
} else {
exports.create = create;
}
}('undefined' !== typeof exports && exports || window));
})(("undefined" !== typeof exports && exports) || window);

View File

@ -1,7 +1,9 @@
'use strict';
"use strict";
module.exports.convert = function () {
console.error("I haven't implemented a ruhoh -> nuhoh converter yet, but it's not very hard to do.");
console.error(
"I haven't implemented a ruhoh -> nuhoh converter yet, but it's not very hard to do."
);
console.error("see https://github.com/coolaj86/nuhoh/tree/master/MIGRATE.md");
throw new Error('Not Implemented.');
throw new Error("Not Implemented.");
};

View File

@ -1,6 +1,6 @@
/*jshint -W054 */
;(function (exports) {
'use strict';
(function (exports) {
"use strict";
function desiMap(obj) {
obj.desi = obj;
@ -8,4 +8,4 @@
}
exports.DesiraeDatamapCore = desiMap.DesiraeDatamapCore = desiMap;
}('undefined' !== typeof exports && exports || window));
})(("undefined" !== typeof exports && exports) || window);

View File

@ -1,19 +1,17 @@
/*jshint -W054 */
;(function (exports) {
'use strict';
(function (exports) {
"use strict";
function create(Desi) {
Desi.YAML = {};
Desi.YAML.parse = (exports.jsyaml || require('js-yaml')).load;
Desi.YAML.stringify = (exports.jsyaml || require('js-yaml')).dump;
Desi.YAML = {};
Desi.YAML.parse = (exports.jsyaml || require("js-yaml")).load;
Desi.YAML.stringify = (exports.jsyaml || require("js-yaml")).dump;
function readFrontMatter(text) {
var lines
, line
, padIndent = ''
, ymllines = []
;
var lines,
line,
padIndent = "",
ymllines = [];
lines = text.split(/\n/);
line = lines.shift();
@ -25,14 +23,14 @@
// that start without indentation, so
// we can add it if this is the case
if (lines[0] && lines[0].match(/^\S/)) {
padIndent = '';
padIndent = "";
}
while (true) {
line = lines.shift();
// premature end-of-file (unsupported yaml)
if (!line && '' !== line) {
if (!line && "" !== line) {
ymllines = [];
break;
}
@ -44,24 +42,21 @@
if (line) {
// supported yaml
ymllines.push(padIndent + line);
ymllines.push(padIndent + line);
}
}
// XXX can't be sorted because arrays get messed up
//ymllines.sort();
if (ymllines) {
return '---\n' + ymllines.join('\n');
return "---\n" + ymllines.join("\n");
}
return;
}
function separateText(text, fm) {
var len
, yml
;
var len, yml;
yml = readFrontMatter(fm);
// strip frontmatter from text, if any
@ -72,27 +67,26 @@
len = 0;
}
return text.split(/\n/).slice(len).join('\n');
return text.split(/\n/).slice(len).join("\n");
}
function parseText(text) {
var fm = readFrontMatter(text)
, body = fm && separateText(text, fm)
, yml
;
var fm = readFrontMatter(text),
body = fm && separateText(text, fm),
yml;
if (fm) {
try {
yml = Desi.YAML.parse(fm);
} catch(e) {
} catch (e) {
//
}
}
return {
yml: yml
, frontmatter: fm
, body: body
yml: yml,
frontmatter: fm,
body: body,
};
}
@ -112,4 +106,4 @@
} else {
exports.create = create;
}
}('undefined' !== typeof exports && exports || window));
})(("undefined" !== typeof exports && exports) || window);

View File

@ -1,51 +1,47 @@
'use strict';
"use strict";
var PromiseA = require('bluebird').Promise
, fs = PromiseA.promisifyAll(require('fs'))
;
var fs = require('fs').promises;
function create(Desi, options) {
var fsapi = Desi.fsapi
;
var fsapi = Desi.fsapi;
options.blogdir = options.blogdir || options.working_path;
fsapi.getMeta = function (dirnames, opts) {
opts = opts || {};
var extensions = ''
, dotfiles = ''
, contents = ''
, sha1sum = ''
;
var extensions = "",
dotfiles = "",
contents = "",
sha1sum = "";
if (Array.isArray(opts.extensions)) {
extensions = '&extensions=' + opts.extensions.join(','); // md,markdown,jade,htm,html
extensions = "&extensions=" + opts.extensions.join(","); // md,markdown,jade,htm,html
}
if (opts.dotfiles) {
dotfiles = '&dotfiles=true';
dotfiles = "&dotfiles=true";
}
if (opts.contents) {
contents = '&contents=true';
contents = "&contents=true";
}
if (false === opts.sha1sum) {
sha1sum = '&sha1sum=false';
sha1sum = "&sha1sum=false";
}
return fsapi.walk.walkDirs(options.blogdir, dirnames, opts);
};
fsapi.getContents = function (filepaths) {
return fsapi.getfs(options.blogdir, filepaths);
};
fsapi.getCache = function () {
return fs.readFileAsync(options.blogdir, '/cache.json').catch(function (/*e*/) {
return {};
}).then(function (obj) {
return obj;
});
return fs
.readFile(options.blogdir, "/cache.json")
.catch(function (/*e*/) {
return {};
})
.then(function (obj) {
return obj;
});
};
fsapi.copy = function (files) {
@ -55,14 +51,13 @@ function create(Desi, options) {
fsapi.putFiles = function (files, opts) {
files.forEach(function (file) {
if (!file.contents || 'string' === typeof file.contents) {
if (!file.contents || "string" === typeof file.contents) {
return;
}
if (/\.json$/i.test(file.path)) {
file.contents = JSON.stringify(file.contents);
}
else if (/\.ya?ml$/i.test(file.path)) {
file.contents = Desi.YAML.stringify(file.contents);
} else if (/\.ya?ml$/i.test(file.path)) {
file.contents = Desi.YAML.stringify(file.contents);
}
});

View File

@ -1,18 +1,18 @@
'use strict';
"use strict";
var PromiseA = require('bluebird').Promise
, fs = PromiseA.promisifyAll(require('fs'))
, forEachAsync = require('foreachasync').forEachAsync
, path = require('path')
, walk = require('walk')
, escapeRegExp = require('escape-string-regexp')
, safeResolve = require('../utils').safeResolve
, sha1sum = function (str) { return require('secret-utils').hashsum('sha1', str); }
, mkdirp = PromiseA.promisify(require('mkdirp'))
, fsExtra = PromiseA.promisifyAll(require('fs.extra'))
//, tmpdir = require('os').tmpdir()
;
var fs = require("fs").promises;
var path = require("path");
var forEachAsync = require("foreachasync").forEachAsync;
var walk = require("walk");
var escapeRegExp = require("escape-string-regexp");
var safeResolve = require("../utils").safeResolve;
var sha1sum = function (str) {
return require("secret-utils").hashsum("sha1", str);
};
var copyAll = require("util").promisify(require("fs.extra").copy);
//, tmpdir = require('os').tmpdir()
function strip(prefix, pathname) {
return pathname.substr(prefix.length + 1);
}
@ -23,24 +23,24 @@ function walkDir(parent, sub, opts) {
opts.sha1sum = true;
}
var prefix = path.resolve(parent)
, trueRoot = path.resolve(prefix, sub)
, files = []
;
var prefix = path.resolve(parent);
var trueRoot = path.resolve(prefix, sub);
var files = [];
function filter(name) {
if (!name) {
return false;
}
if (!opts.dotfiles && ('.' === name[0])) {
if (!opts.dotfiles && "." === name[0]) {
return false;
}
if (opts.extensions && opts.extensions.length) {
if (!opts.extensions.some(function (ext) {
return new RegExp('\\.' + escapeRegExp(ext) + '$').test(name);
})) {
if (
!opts.extensions.some(function (ext) {
return new RegExp("\\." + escapeRegExp(ext) + "$").test(name);
})
) {
return false;
}
}
@ -48,11 +48,9 @@ function walkDir(parent, sub, opts) {
return true;
}
return new PromiseA(function (resolve) {
var walker = walk.walk(trueRoot)
;
walker.on('nodeError', function (filepath, stat, next) {
return new Promise(function (resolve) {
var walker = walk.walk(trueRoot);
walker.on("nodeError", function (filepath, stat, next) {
//stats.forEach(function (stat) {
if (!filter(stat.name)) {
return;
@ -60,59 +58,56 @@ function walkDir(parent, sub, opts) {
stat.error.path = path.join(strip(prefix, filepath), stat.name);
files.push({
name: stat.name
, relativePath: strip(prefix, filepath)
, path: path.join(strip(prefix, filepath), stat.name)
name: stat.name,
relativePath: strip(prefix, filepath),
path: path.join(strip(prefix, filepath), stat.name),
, type: undefined
, error: stat.error
type: undefined,
error: stat.error,
});
//});
next();
});
walker.on('files', function (root, stats, next) {
var dirname = strip(prefix, root)
;
walker.on("files", function (root, stats, next) {
var dirname = strip(prefix, root);
function eachFile(stat) {
var file
;
var file;
if (!filter(stat.name)) {
return PromiseA.resolve();
return Promise.resolve();
}
file = {
name: stat.name
, relativePath: dirname
, path: path.join(dirname, stat.name)
name: stat.name,
relativePath: dirname,
path: path.join(dirname, stat.name),
, createdDate: (stat.birthtime||stat.ctime).toISOString()
, lastModifiedDate: stat.mtime.toISOString()
createdDate: (stat.birthtime || stat.ctime).toISOString(),
lastModifiedDate: stat.mtime.toISOString(),
, size: stat.size
, type: undefined // TODO include mimetype
size: stat.size,
type: undefined, // TODO include mimetype
};
files.push(file);
if (!(opts.sha1sum || opts.content)) {
return PromiseA.resolve();
return Promise.resolve();
}
// TODO stream sha1 (for assets)
return fs.readFileAsync(path.join(root, stat.name), null).then(function (buffer) {
var contents = buffer.toString('utf8')
;
return fs
.readFile(path.join(root, stat.name), null)
.then(function (buffer) {
var contents = buffer.toString("utf8");
file.sha1 = sha1sum(contents);
file.type = undefined;
file.sha1 = sha1sum(contents);
file.type = undefined;
if (opts.contents) {
file.contents = contents;
}
});
if (opts.contents) {
file.contents = contents;
}
});
}
if (!opts.contents) {
@ -125,7 +120,7 @@ function walkDir(parent, sub, opts) {
}
});
walker.on('end', function () {
walker.on("end", function () {
resolve(files);
});
});
@ -134,9 +129,7 @@ function walkDir(parent, sub, opts) {
function walkDirs(parent, subs, opts) {
opts = opts || {};
var collections = {}
;
var collections = {};
return forEachAsync(subs, function (sub) {
return walkDir(parent, sub, opts).then(function (results) {
collections[sub] = results;
@ -146,59 +139,53 @@ function walkDirs(parent, subs, opts) {
});
}
function getfs(blogdir, filepaths) {
var files = []
;
var files = [];
return forEachAsync(filepaths, function (filepath) {
var pathname = safeResolve(blogdir, filepath)
;
var pathname = safeResolve(blogdir, filepath);
return fs
.lstat(pathname)
.then(function (stat) {
return fs.readFile(pathname, null).then(function (buffer) {
files.push({
name: path.basename(pathname),
relativePath: path.dirname(filepath),
path: filepath,
return fs.lstatAsync(pathname).then(function (stat) {
return fs.readFileAsync(pathname, null).then(function (buffer) {
createdDate: (stat.birthtime || stat.ctime).toISOString(),
lastModifiedDate: stat.mtime.toISOString(),
files.push({
name: path.basename(pathname)
, relativePath: path.dirname(filepath)
, path: filepath
, createdDate: (stat.birthtime||stat.ctime).toISOString()
, lastModifiedDate: stat.mtime.toISOString()
, contents: buffer.toString('utf8')
, size: buffer.length
, sha1: sha1sum(buffer)
, type: undefined
contents: buffer.toString("utf8"),
size: buffer.length,
sha1: sha1sum(buffer),
type: undefined,
});
});
})
.catch(function (e) {
files.push({ path: filepath, error: e.message });
});
}).catch(function (e) {
files.push({ path: filepath, error: e.message });
});
}).then(function () {
return files;
});
}
function makeAllDirs(dirpaths) {
var errors = []
;
var errors = [];
return forEachAsync(dirpaths, function (pathname) {
return mkdirp(pathname).catch(function (e) {
return fs.mkdir(pathname, { recursive: true }).catch(function (e) {
// TODO exclude attempting to write files to this dir?
errors.push({
type: 'directory'
type: "directory",
, directory: pathname
directory: pathname,
, message: e.message
, code: e.code
, errno: e.errno
, status: e.status
, syscall: e.syscall
message: e.message,
code: e.code,
errno: e.errno,
status: e.status,
syscall: e.syscall,
});
});
}).then(function () {
return errors;
@ -207,156 +194,162 @@ function makeAllDirs(dirpaths) {
function copyfs(blogdir, files) {
// TODO switch format to { source: ..., dest: ..., opts: ... } ?
var results = { errors: [] }
, dirpaths = {}
, sources = Object.keys(files)
;
var results = { errors: [] },
dirpaths = {},
sources = Object.keys(files);
return forEachAsync(sources, function (source) {
/*
var nsource = safeResolve(blogdir, source)
;
*/
var dest = safeResolve(blogdir, files[source])
, pathname = path.dirname(dest)
//, filename = path.basename(dest)
;
var dest = safeResolve(blogdir, files[source]),
pathname = path.dirname(dest);
//, filename = path.basename(dest)
dirpaths[pathname] = true;
return PromiseA.resolve();
}).then(function () {
// TODO is it better to do this lazy-like or as a batch?
// I figure as batch when there may be hundreds of files,
// likely within 2 or 3 directories
return makeAllDirs(Object.keys(dirpaths)).then(function (errors) {
errors.forEach(function (e) {
results.errors.push(e);
});
});
}).then(function () {
// TODO allow delete?
return forEachAsync(sources, function (source) {
return fsExtra.copyAsync(
safeResolve(blogdir, source)
, safeResolve(blogdir, files[source])
, { replace: true }
).catch(function (e) {
results.errors.push({
type: 'file'
, source: source
, destination: files[source]
, message: e.message
, code: e.code
, errno: e.errno
, status: e.status
, syscall: e.syscall
return Promise.resolve();
})
.then(function () {
// TODO is it better to do this lazy-like or as a batch?
// I figure as batch when there may be hundreds of files,
// likely within 2 or 3 directories
return makeAllDirs(Object.keys(dirpaths)).then(function (errors) {
errors.forEach(function (e) {
results.errors.push(e);
});
});
})
.then(function () {
// TODO allow delete?
return forEachAsync(sources, function (source) {
return copyAll(
safeResolve(blogdir, source),
safeResolve(blogdir, files[source]),
{ replace: true }
)
.catch(function (e) {
results.errors.push({
type: "file",
source: source,
destination: files[source],
message: e.message,
code: e.code,
errno: e.errno,
status: e.status,
syscall: e.syscall,
});
});
});
})
.catch(function (e) {
results.error = {
message: e.message,
code: e.code,
errno: e.errno,
status: e.status,
syscall: e.syscall,
};
})
.then(function () {
return results;
});
}).catch(function (e) {
results.error = {
message: e.message
, code: e.code
, errno: e.errno
, status: e.status
, syscall: e.syscall
};
}).then(function () {
return results;
});
}
function putfs(blogdir, files, options) {
options = options || {};
var putfsResults = { errors: [] }
, dirpaths = {}
;
var putfsResults = { errors: [] },
dirpaths = {};
return forEachAsync(files, function (file) {
var filepath = safeResolve(blogdir, file.path || path.join(file.relativePath, file.name))
, pathname = path.dirname(filepath)
, filename = file.name || path.basename(filepath)
;
var filepath = safeResolve(
blogdir,
file.path || path.join(file.relativePath, file.name)
),
pathname = path.dirname(filepath),
filename = file.name || path.basename(filepath);
file.realPath = filepath;
file.name = filename;
dirpaths[pathname] = true;
return PromiseA.resolve();
}).then(function () {
// TODO is it better to do this lazy-like or as a batch?
// I figure as batch when there may be hundreds of files,
// likely within 2 or 3 directories
return forEachAsync(Object.keys(dirpaths), function (pathname) {
return mkdirp(pathname).catch(function (e) {
// TODO exclude attempting to write files to this dir?
putfsResults.errors.push({
type: 'directory'
, directory: pathname
return Promise.resolve();
})
.then(function () {
// TODO is it better to do this lazy-like or as a batch?
// I figure as batch when there may be hundreds of files,
// likely within 2 or 3 directories
return forEachAsync(Object.keys(dirpaths), function (pathname) {
return fs.mkdir(pathname, { recursive: true }).catch(function (e) {
// TODO exclude attempting to write files to this dir?
putfsResults.errors.push({
type: "directory",
, message: e.message
, code: e.code
, errno: e.errno
, status: e.status
, syscall: e.syscall
});
directory: pathname,
});
});
}).then(function () {
// TODO sort deletes last
return forEachAsync(files, function (file) {
// TODO use lastModifiedDate as per client request?
// TODO compare sha1 sums for integrity
// NOTE existsAsync is backwards
return fs.existsAsync(file.realPath).then(function () {
return fs.writeFileAsync(file.realPath, file.contents, 'utf8');
}).catch(function (/*exists*/) {
if (file.delete || !file.contents) {
return fs.unlinkAsync(file.realPath);
}
if (false === options.replace || false === options.overwrite) {
throw new Error('EEXIST: the file already exists');
}
return fs.writeFileAsync(file.realPath, file.contents, 'utf8');
}).catch(function (e) {
putfsResults.errors.push({
type: 'file'
, file: file.realPath
, delete: !file.contents
, path: file.path
, relativePath: file.relativePath
, name: file.name
, message: e.message
, code: e.code
, errno: e.errno
, status: e.status
, syscall: e.syscall
message: e.message,
code: e.code,
errno: e.errno,
status: e.status,
syscall: e.syscall,
});
});
});
})
.then(function () {
// TODO sort deletes last
return forEachAsync(files, function (file) {
// TODO use lastModifiedDate as per client request?
// TODO compare sha1 sums for integrity
return fs
.access(file.realPath)
.then(function () {
if (file.delete || !file.contents) {
return fs.unlink(file.realPath);
}
if (false === options.replace || false === options.overwrite) {
throw new Error("EEXIST: the file already exists");
}
return fs.writeFile(file.realPath, file.contents, "utf8");
})
.catch(function () {
return fs.writeFile(file.realPath, file.contents, "utf8");
})
.catch(function (e) {
putfsResults.errors.push({
type: "file",
file: file.realPath,
delete: !file.contents,
path: file.path,
relativePath: file.relativePath,
name: file.name,
message: e.message,
code: e.code,
errno: e.errno,
status: e.status,
syscall: e.syscall,
});
});
});
})
.catch(function (e) {
putfsResults.error = {
message: e.message,
code: e.code,
errno: e.errno,
status: e.status,
syscall: e.syscall,
};
})
.then(function () {
return putfsResults;
});
}).catch(function (e) {
putfsResults.error = {
message: e.message
, code: e.code
, errno: e.errno
, status: e.status
, syscall: e.syscall
};
}).then(function () {
return putfsResults;
});
}
/*
walkDirs('blog', ['posts'], { contents: false }).then(function (stats) {

View File

@ -1,5 +1,5 @@
'use strict';
"use strict";
exports.fsapi = require('./fsapi');
exports.sha1sum = require('./sha1sum').sha1sum;
exports.realFsapi = require('./fsapi-real');
exports.fsapi = require("./fsapi");
exports.sha1sum = require("./sha1sum").sha1sum;
exports.realFsapi = require("./fsapi-real");

View File

@ -1,9 +1,7 @@
'use strict';
"use strict";
var PromiseA = require('bluebird').Promise
, secretutils = require('secret-utils')
;
var secretutils = require("secret-utils");
module.exports.sha1sum = function (str) {
return PromiseA.resolve( secretutils.hashsum('sha1', str) );
return Promise.resolve(secretutils.hashsum("sha1", str));
};

View File

@ -1,39 +1,35 @@
/*jshint -W054 */
;(function (exports) {
'use strict';
(function (exports) {
"use strict";
var PromiseA = exports.Promise || require('bluebird').Promise
;
function renderMd(contentstr/*, desi*/) {
var markitdown = (exports.markdownit || require('markdown-it'))({ html: true, linkify: true })
;
return PromiseA.resolve(
function renderMd(contentstr /*, desi*/) {
var markitdown = (exports.markdownit || require("markdown-it"))({
html: true,
linkify: true,
});
return Promise.resolve(
markitdown.render(contentstr)
//.replace('&quot;', '"')
//.replace('&#39;', "'")
//.replace('&#x2F;', '/')
//.replace('&quot;', '"')
//.replace('&#39;', "'")
//.replace('&#x2F;', '/')
);
}
function renderNoop(contentstr/*, desi*/) {
function renderNoop(contentstr /*, desi*/) {
// hmmm... that was easy
return PromiseA.resolve(contentstr);
return Promise.resolve(contentstr);
}
function renderJade(contentstr, desi, options) {
options = options || {};
if (!('pretty' in options)) {
if (!("pretty" in options)) {
options.pretty = true;
}
var jade = (exports.jade || require('jade'))
, fn = jade.compile(contentstr, options)
, html = fn(desi)
;
return PromiseA.resolve(html);
var jade = exports.jade || require("jade"),
fn = jade.compile(contentstr, options),
html = fn(desi);
return Promise.resolve(html);
}
exports.DesiraeRenderMarkdown = renderMd.DesiraeRenderMarkdown = renderMd;
@ -41,4 +37,4 @@
exports.DesiraeRenderCss = renderNoop.DesiraeRenderCss = renderNoop;
exports.DesiraeRenderJs = renderNoop.DesiraeRenderJs = renderNoop;
exports.DesiraeRenderJade = renderJade.DesiraeRenderJade = renderJade;
}('undefined' !== typeof exports && exports || window));
})(("undefined" !== typeof exports && exports) || window);

View File

@ -1,143 +1,153 @@
/*jshint -W054 */
;(function (exports) {
'use strict';
var cores = {}
, Desi = exports.Desirae || require('desirae').Desirae
, path = exports.path || require('path')
;
(function (exports) {
"use strict";
var cores = {},
Desi = exports.Desirae || require("desirae").Desirae,
path = exports.path || require("path");
cores.lint = function (desi, env, collection, entity) {
// TODO splice
//desi.content.collections = desi.content.collections.filter(function (entity) {
// TODO throw for any files that don't have a registered renderer
if (!entity.yml) {
if (!desi.config.empty_frontmatter) {
throw new Error("no frontmatter for " + (entity.path || entity.name) + "."
+ "Set `config.yml.empty_frontmatter: include|skip` to ignore this error."
);
}
if ('include' === desi.config.empty_frontmatter) {
entity.yml = {};
}
else if ('skip' === desi.config.empty_frontmatter) {
return false;
}
else {
throw new Error('unrecognize option ' + desi.config.empty_frontmatter +
' for `config.yml.empty_frontmatter: include|skip`.');
}
// TODO throw for any files that don't have a registered renderer
if (!entity.yml) {
if (!desi.config.empty_frontmatter) {
throw new Error(
"no frontmatter for " +
(entity.path || entity.name) +
"." +
"Set `config.yml.empty_frontmatter: include|skip` to ignore this error."
);
}
if (!entity.body || !entity.body.trim()) {
if (!desi.config.empty_body) {
throw new Error('empty content file ' + (entity.path || entity.name)
+ '. Set `config.yml.empty_body: include|skip` to ignore this error.'
);
}
if ("include" === desi.config.empty_frontmatter) {
entity.yml = {};
} else if ("skip" === desi.config.empty_frontmatter) {
return false;
} else {
throw new Error(
"unrecognize option " +
desi.config.empty_frontmatter +
" for `config.yml.empty_frontmatter: include|skip`."
);
}
}
if ('include' === desi.config.empty_body) {
entity.body = '';
}
else if ('skip' === desi.config.empty_body) {
return false;
}
else {
throw new Error('unrecognize option ' + desi.config.empty_frontmatter +
' for `config.yml.empty_body: include|skip`.');
}
if (!entity.body || !entity.body.trim()) {
if (!desi.config.empty_body) {
throw new Error(
"empty content file " +
(entity.path || entity.name) +
". Set `config.yml.empty_body: include|skip` to ignore this error."
);
}
return true;
if ("include" === desi.config.empty_body) {
entity.body = "";
} else if ("skip" === desi.config.empty_body) {
return false;
} else {
throw new Error(
"unrecognize option " +
desi.config.empty_frontmatter +
" for `config.yml.empty_body: include|skip`."
);
}
}
return true;
//});
};
cores.root = function (desi, env, collection, entity) {
entity.yml = entity.yml || {};
entity.layout = entity.yml.layout || '__page__';
entity.layout = entity.yml.layout || "__page__";
// _root is not subject to the same permalink rules as collections,
// so we just go ahead and define that here
if (/^index\.\w+$/.test(entity.path)) {
entity.permalink = '/';
entity.permalink = "/";
} else {
entity.permalink = entity.yml.permalink || entity.path.replace(/\.\w+$/, '/');
entity.permalink =
entity.yml.permalink || entity.path.replace(/\.\w+$/, "/");
entity.redirects = entity.redirects || [];
entity.redirects.push(entity.permalink.replace(/\/$/, '/index.html'));
entity.redirects.push(entity.permalink.replace(/\/$/, "/index.html"));
}
};
cores.normalize = function (desi, env, collection, entity) {
entity.title = entity.yml.title || Desi.firstCap(entity.name.replace(/\.\w+$/, ''));
entity.date = entity.yml.date;
entity.title =
entity.yml.title || Desi.firstCap(entity.name.replace(/\.\w+$/, ""));
entity.date = entity.yml.date;
if (!entity.date) {
// TODO tell YAML parser to keep the date a string
entity.date = new Date(entity.yml.created_at
|| entity.yml.time
|| entity.yml.updated_at
|| entity.createdDate
|| entity.lastModifiedDate
entity.date = new Date(
entity.yml.created_at ||
entity.yml.time ||
entity.yml.updated_at ||
entity.createdDate ||
entity.lastModifiedDate
).toISOString();
}
if ('object' === typeof entity.date) {
if ("object" === typeof entity.date) {
entity.date = entity.date.toISOString();
}
entity.updated_at = entity.yml.updated_at || entity.lastModifiedDate;
entity.updated_at = entity.yml.updated_at || entity.lastModifiedDate;
entity.published_at = Desi.fromLocaleDate(entity.date || entity.lastModifiedDate);
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.published_at = Desi.fromLocaleDate(
entity.date || entity.lastModifiedDate
);
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;
// let's just agree that that's too far
//entity.second = entity.published_at.second;
entity.slug = Desi.slugify(entity.title);
entity.slug_path = Desi.slugifyPath(entity.relativePath);
entity.slugPath = Desi.slugifyPath(entity.relativePath);
entity.slug = Desi.slugify(entity.title);
entity.slug_path = Desi.slugifyPath(entity.relativePath);
entity.slugPath = Desi.slugifyPath(entity.relativePath);
// TODO type checking like below
entity.redirects = Array.isArray(entity.yml.redirects) && entity.yml.redirects|| [];
entity.redirects =
(Array.isArray(entity.yml.redirects) && entity.yml.redirects) || [];
// categories
if (Array.isArray(entity.yml.categories)) {
entity.categories = entity.yml.categories;
}
else if ('string' === typeof entity.yml.categories) {
entity.categories = [entity.yml.categories];
}
else if ('string' === typeof entity.yml.category) {
entity.categories = [entity.yml.category];
}
else {
entity.categories = [];
entity.categories = entity.yml.categories;
} else if ("string" === typeof entity.yml.categories) {
entity.categories = [entity.yml.categories];
} else if ("string" === typeof entity.yml.category) {
entity.categories = [entity.yml.category];
} else {
entity.categories = [];
}
// tags
if (Array.isArray(entity.yml.tags)) {
entity.tags = entity.yml.tags;
}
else if ('string' === typeof entity.yml.tags) {
entity.tags = [entity.yml.tags];
}
else {
entity.tags = [];
entity.tags = entity.yml.tags;
} else if ("string" === typeof entity.yml.tags) {
entity.tags = [entity.yml.tags];
} else {
entity.tags = [];
}
entity.permalink = entity.permalink || entity.yml.permalink;
entity.permalink = entity.permalink || entity.yml.permalink;
if (!entity.permalink) {
// try the fallback_permalink first (because we're looking at files that don't have yml)
// then try the normal permalink (because :filename -> :title and whatnot, so it'll work)
entity.permalink = Desi.permalinkify(desi, collection.fallback_permalink || collection.permalink, entity);
entity.permalink = Desi.permalinkify(
desi,
collection.fallback_permalink || collection.permalink,
entity
);
}
/*
if (!/\.x?html?$/.test(entity.permalink)) {
entity.htmllink = path.join(entity.permalink, 'index.html');
@ -145,38 +155,51 @@
*/
// relative to the site
entity.relative_file = path.join(env.base_path, entity.permalink)
.replace(/\/$/, '/index.html');
entity.relative_href = path.join(env.base_path, entity.permalink)
.replace(/\/index\.html$/, '/');
entity.relative_link = entity.relative_href;
entity.relative_file = path
.join(env.base_path, entity.permalink)
.replace(/\/$/, "/index.html");
entity.relative_href = path
.join(env.base_path, entity.permalink)
.replace(/\/index\.html$/, "/");
entity.relative_link = entity.relative_href;
entity.url = env.base_url + path.join(env.base_path, entity.permalink)
.replace(/\/index\.html$/, '/');
entity.canonical_url = env.base_url + path.join(env.base_path, entity.permalink)
.replace(/\/index\.html$/, '/');
entity.production_url = desi.site.base_url + path.join(desi.site.base_path, entity.permalink)
.replace(/\/index\.html$/, '/');
entity.relative_url = path.join(env.base_path, entity.permalink)
.replace(/\/index\.html$/, '/');
entity.url =
env.base_url +
path.join(env.base_path, entity.permalink).replace(/\/index\.html$/, "/");
entity.canonical_url =
env.base_url +
path.join(env.base_path, entity.permalink).replace(/\/index\.html$/, "/");
entity.production_url =
desi.site.base_url +
path
.join(desi.site.base_path, entity.permalink)
.replace(/\/index\.html$/, "/");
entity.relative_url = path
.join(env.base_path, entity.permalink)
.replace(/\/index\.html$/, "/");
if (env.explicitIndexes || env.explicitIndices || env.explicit_indexes || env.explicit_indices) {
if (
env.explicitIndexes ||
env.explicitIndices ||
env.explicit_indexes ||
env.explicit_indices
) {
// NOTE: file_url is NOT replaced
['url', 'canonical_url', 'production_url', 'relative_url'].forEach(function (url) {
entity[url] = entity[url].replace(/\/$/, '/index.html');
});
["url", "canonical_url", "production_url", "relative_url"].forEach(
function (url) {
entity[url] = entity[url].replace(/\/$/, "/index.html");
}
);
}
// i.e. bootstrap, hero page, darkly
entity.theme = entity.theme || entity.yml.theme;
entity.layout = entity.layout || entity.yml.layout;
entity.swatch = entity.swatch || entity.yml.swatch;
entity.theme = entity.theme || entity.yml.theme;
entity.layout = entity.layout || entity.yml.layout;
entity.swatch = entity.swatch || entity.yml.swatch;
};
cores.disqus = function (desi, env, collection, entity) {
var yml = entity.yml
;
var yml = entity.yml;
if (yml.uuid) {
entity.disqus_identifier = yml.uuid;
}
@ -184,4 +207,4 @@
};
exports.DesiraeTransformCore = cores.DesiraeTransformCore = cores;
}('undefined' !== typeof exports && exports || window));
})(("undefined" !== typeof exports && exports) || window);

View File

@ -1,9 +1,7 @@
;(function (exports) {
'use strict';
var path = exports.path || require('path')
;
(function (exports) {
"use strict";
var path = exports.path || require("path");
function escapeRegExp(str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}
@ -11,10 +9,8 @@
function safeResolve(basename, target) {
basename = path.resolve(basename);
var targetname = path.resolve(basename, target)
, re = new RegExp('^' + escapeRegExp(basename) + '(/|$)')
;
var targetname = path.resolve(basename, target),
re = new RegExp("^" + escapeRegExp(basename) + "(/|$)");
return re.test(targetname) && targetname;
}
@ -22,29 +18,24 @@
exports.escapeRegExp = escapeRegExp;
function create(Desi) {
var fsapi = Desi.fsapi || require('./node-adapters').fsapi
;
var fsapi = Desi.fsapi || require("./node-adapters").fsapi;
fsapi.getConfigs = function (confs) {
var opts = { extensions: ['yml', 'yaml', 'json'], dotfiles: false, contents: true, sha1sum: true }
;
var opts = {
extensions: ["yml", "yaml", "json"],
dotfiles: false,
contents: true,
sha1sum: true,
};
return fsapi.getMeta(confs, opts).then(function (collections) {
var obj = {}
;
var obj = {};
Object.keys(collections).forEach(function (key) {
var files = collections[key]
, keyname = key.replace(/\.(json|ya?ml|\/)$/i, '')
;
var files = collections[key],
keyname = key.replace(/\.(json|ya?ml|\/)$/i, "");
obj[keyname] = obj[keyname] || {};
files.forEach(function (file) {
var filename = file.name.replace(/\.(json|ya?ml)$/i, '')
, data = {}
;
var filename = file.name.replace(/\.(json|ya?ml)$/i, ""),
data = {};
if (file.error) {
console.error(file);
console.error(file.error);
@ -57,17 +48,16 @@
if ("undefined" === data) {
data = {};
}
} catch(e) {
} catch (e) {
data = { error: e };
console.error("Could not parse yaml for " + filename);
console.error(file);
console.error(e);
}
}
else if (/\.(json)$/i.test(file.name)) {
} else if (/\.(json)$/i.test(file.name)) {
try {
data = JSON.parse(file.contents) || {};
} catch(e) {
} catch (e) {
data = { error: e };
console.error("Could not parse json for " + filename);
console.error(file);
@ -99,36 +89,34 @@
};
fsapi.getAllPartials = function () {
return fsapi.getConfigs(['partials', 'partials.yml']).then(function (results) {
var partials = {}
;
return fsapi
.getConfigs(["partials", "partials.yml"])
.then(function (results) {
var partials = {};
Object.keys(results.partials).forEach(function (key) {
var partial = results.partials[key];
Object.keys(partial).forEach(function (prop) {
if (partials[prop]) {
console.warn("partial '" + prop + "' overwritten by " + key);
}
Object.keys(results.partials).forEach(function (key) {
var partial = results.partials[key]
;
Object.keys(partial).forEach(function (prop) {
if (partials[prop]) {
console.warn('partial \'' + prop + '\' overwritten by ' + key);
}
partials[prop] = partial[prop];
partials[prop] = partial[prop];
});
});
});
return partials;
});
return partials;
});
};
fsapi.getAllConfigFiles = function () {
return fsapi.getConfigs(['config.yml', 'site.yml', 'authors']).then(function (results) {
var authors = results.authors
, config = results.config.config
, site = results.site.site
;
return { config: config, authors: authors, site: site };
});
return fsapi
.getConfigs(["config.yml", "site.yml", "authors"])
.then(function (results) {
var authors = results.authors,
config = results.config.config,
site = results.site.site;
return { config: config, authors: authors, site: site };
});
};
return exports;
@ -136,8 +124,7 @@
if (exports.Desirae) {
create(exports.Desirae);
}
else {
} else {
exports.create = create;
}
}('undefined' !== typeof exports && exports || window));
})(("undefined" !== typeof exports && exports) || window);

View File

@ -1,5 +1,5 @@
;(function (exports) {
'use strict';
(function (exports) {
"use strict";
exports.verifyConfig = function (conf) {
if (!conf.NuhohSpec) {
@ -28,14 +28,18 @@
if (!Array.isArray(conf.collections)) {
if (conf.posts) {
console.error("Please indent and nest 'posts' under the key 'collection' to continue");
console.error(
"Please indent and nest 'posts' under the key 'collection' to continue"
);
}
throw new Error("missing key 'collections'.");
}
if (!conf.themes) {
if (conf.twitter) {
console.error("Please indent and nest 'twitter' under the key 'themes' to continue");
console.error(
"Please indent and nest 'twitter' under the key 'themes' to continue"
);
}
throw new Error("missing key 'themes'");
}
@ -55,4 +59,4 @@
throw new Error("missing key root");
}
};
}('undefined' !== typeof exports && exports || window));
})(("undefined" !== typeof exports && exports) || window);

175
package-lock.json generated Normal file
View File

@ -0,0 +1,175 @@
{
"name": "desirae",
"version": "0.12.4",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"requires": {
"sprintf-js": "~1.0.2"
}
},
"crypto-rand": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/crypto-rand/-/crypto-rand-0.0.2.tgz",
"integrity": "sha1-Hn3CMQLhiRo+6zQPtwrElYCzLt0="
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
"esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
},
"foreachasync": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/foreachasync/-/foreachasync-5.1.3.tgz",
"integrity": "sha512-q3/l5D2pyZG8aYe9xZQDAnS/KyP7QJakLUNrU/qGkd4bQUTzCUmvVFjagRggoxxjtb1ZrJB+jQeG4ojOLWkztw=="
},
"fs-extra": {
"version": "0.6.4",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.6.4.tgz",
"integrity": "sha1-9G8MdbeEH40gCzNIzU1pHVoJnRU=",
"requires": {
"jsonfile": "~1.0.1",
"mkdirp": "0.3.x",
"ncp": "~0.4.2",
"rimraf": "~2.2.0"
},
"dependencies": {
"mkdirp": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz",
"integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc="
}
}
},
"fs.extra": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/fs.extra/-/fs.extra-1.3.2.tgz",
"integrity": "sha1-3QI/kwE77iRTHxszUUw3sg/ZM0k=",
"requires": {
"fs-extra": "~0.6.1",
"mkdirp": "~0.3.5",
"walk": "^2.3.9"
},
"dependencies": {
"mkdirp": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz",
"integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc="
}
}
},
"js-yaml": {
"version": "3.14.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz",
"integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==",
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
},
"jsonfile": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-1.0.1.tgz",
"integrity": "sha1-6l7+QLg2kLmGZ2FKc5L8YOhCwN0="
},
"linkify-it": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-0.1.5.tgz",
"integrity": "sha1-OMWD0y+pPtcm2gDHrwAQeL+2uUU=",
"requires": {
"uc.micro": "^1.0.0"
},
"dependencies": {
"uc.micro": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
"integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA=="
}
}
},
"markdown-it": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-3.1.0.tgz",
"integrity": "sha1-IKcejmexKXyWrEfQD3tuaQ1uDDY=",
"requires": {
"argparse": "~ 1.0.0",
"linkify-it": "~ 0.1.2",
"mdurl": "~ 1.0.0",
"uc.micro": "~ 0.1.0"
}
},
"mdurl": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
"integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4="
},
"mustache": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/mustache/-/mustache-2.3.2.tgz",
"integrity": "sha512-KpMNwdQsYz3O/SBS1qJ/o3sqUJ5wSb8gb0pul8CO0S56b9Y2ALm8zCfsjPXsqGFfoNBkDwZuZIAjhsZI03gYVQ=="
},
"ncp": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/ncp/-/ncp-0.4.2.tgz",
"integrity": "sha1-q8xsvT7C7Spyn/bnwfqPAXhKhXQ="
},
"node-uuid": {
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz",
"integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc="
},
"rimraf": {
"version": "2.2.8",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz",
"integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI="
},
"secret-utils": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/secret-utils/-/secret-utils-1.0.2.tgz",
"integrity": "sha1-88GhRhCpTH+gMX3RVqj+AJgDb6c=",
"requires": {
"crypto-rand": "0.0.2",
"urlsafe-base64": "0.0.2"
}
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
},
"uc.micro": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-0.1.0.tgz",
"integrity": "sha1-7aESHR/blhVO1v3oJHu724MzCMo="
},
"urlsafe-base64": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/urlsafe-base64/-/urlsafe-base64-0.0.2.tgz",
"integrity": "sha1-+VqmedXqb86RQtY8tXCd4x7r7UI="
},
"walk": {
"version": "2.3.14",
"resolved": "https://registry.npmjs.org/walk/-/walk-2.3.14.tgz",
"integrity": "sha512-5skcWAUmySj6hkBdH6B6+3ddMjVQYH5Qy9QGbPmN8kVmLteXk+yVXg+yfk1nbX30EYakahLrr8iPcCxJQSCBeg==",
"requires": {
"foreachasync": "^3.0.0"
},
"dependencies": {
"foreachasync": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/foreachasync/-/foreachasync-3.0.0.tgz",
"integrity": "sha1-VQKYfchxS+M5IJfzLgBxyd7gfPY="
}
}
}
}
}

View File

@ -1,14 +1,15 @@
{
"name": "desirae",
"version": "0.11.2",
"version": "0.12.4",
"description": "An in-browser static blog library and static site generator. Similar to Jekyll, Octopress, Nanoc, etc",
"main": "desirae.js",
"scripts": {
"prettier": "prettier --write './**/*.{js,css,html,json,md,py,xml}'",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git@git.daplie.com:Daplie/desirae.git"
"type": "git+https",
"url": "https://git.coolaj86.com/coolaj86/desirae.js.git"
},
"keywords": [
"dear",
@ -22,21 +23,19 @@
"octopress",
"nanoc"
],
"author": "AJ ONeal",
"license": "Apache2",
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
"license": "MPL-2.0",
"bugs": {
"url": "https://git.daplie.com/Daplie/desirae/issues"
"url": "https://git.coolaj86.com/coolaj86/desirae.js/issues"
},
"homepage": "https://git.daplie.com/Daplie/desirae",
"homepage": "https://git.coolaj86.com/coolaj86/desirae.js",
"dependencies": {
"bluebird": "^2.5.3",
"escape-string-regexp": "^1.0.2",
"foreachasync": "^5.0.5",
"fs.extra": "^1.3.2",
"js-yaml": "^3.2.5",
"markdown-it": "^3.0.2",
"mkdirp": "^0.5.0",
"mustache": "^1.0.0",
"mustache": "^2.3.2",
"node-uuid": "^1.4.2",
"secret-utils": "^1.0.2",
"walk": "^2.3.9"

View File

@ -1,4 +1,4 @@
'use strict';
"use strict";
// http://ruhoh.com/docs/2/pages/#toc_41
/*
@ -6,32 +6,31 @@
Otherwise it should use the the permalink for that collection.
*/
var tags
, permalinkTransforms
, cases
, path = /*exports.path ||*/ require('path')
;
var tags,
permalinkTransforms,
cases,
path = /*exports.path ||*/ require("path");
tags = {
year: "Year from the pages filename"
, month: "Month from the pages filename"
, day: "Day from the pages filename"
, path: "The page file's path relative to the base of your website."
, relative_path: "The page file's path relative to its name-spaced directory."
, filename: "The page file's filename (path is not included)."
, categories: "The specified categories for this page. If more than one category is set, only the first one is used. If no categories exist, the URL omits this parameter."
, i_month: "Month from the pages filename without leading zeros."
, i_day: "Day from the pages filename without leading zeros."
, title: "The title, as a slug."
, slug: "alias of title"
, name: "alias of title"
, collection: "i.e. posts/ or essays/ or whatever/"
year: "Year from the pages filename",
month: "Month from the pages filename",
day: "Day from the pages filename",
path: "The page file's path relative to the base of your website.",
relative_path: "The page file's path relative to its name-spaced directory.",
filename: "The page file's filename (path is not included).",
categories:
"The specified categories for this page. If more than one category is set, only the first one is used. If no categories exist, the URL omits this parameter.",
i_month: "Month from the pages filename without leading zeros.",
i_day: "Day from the pages filename without leading zeros.",
title: "The title, as a slug.",
slug: "alias of title",
name: "alias of title",
collection: "i.e. posts/ or essays/ or whatever/",
};
function pad(str, n) {
str = str.toString();
if (str.length < n) {
str = '0' + str;
str = "0" + str;
}
return str;
@ -39,49 +38,51 @@ function pad(str, n) {
// https://www.youtube.com/watch?v=1NryFD9_hR0&list=RDOeLUK4a6Ojc&index=2
cases = {
"/:title.html" : "/my-title.html"
, ":title/" : "/my-title/"
, "/:bad/:title/" : "/:bad/my-title/"
, "/:slug/" : "/my-title/"
, "/:path/:name.html" : "/posts/fun/my-title.html"
, "/:relative_path/:name/" : "/fun/my-title/"
, "/:year-:month-:day/:name" : "/2015-07-04/my-title/"
, "/:year/:i_month/:i_day/:name" : "/2015/7/4/my-title/"
, "/:filename.html" : "/my-file-name.html"
, "/:filename" : "/my-file-name/"
, "/:filename/" : "/my-file-name/"
, "/:collection/:title/" : "/posts/my-title/"
, "/:collection/:filename" : "/posts/my-file-name/"
, "/:something/:or/:other" : "/:something/:or/:other/"
, "/:categories/:title/" : "/desi/my-title/"
"/:title.html": "/my-title.html",
":title/": "/my-title/",
"/:bad/:title/": "/:bad/my-title/",
"/:slug/": "/my-title/",
"/:path/:name.html": "/posts/fun/my-title.html",
"/:relative_path/:name/": "/fun/my-title/",
"/:year-:month-:day/:name": "/2015-07-04/my-title/",
"/:year/:i_month/:i_day/:name": "/2015/7/4/my-title/",
"/:filename.html": "/my-file-name.html",
"/:filename": "/my-file-name/",
"/:filename/": "/my-file-name/",
"/:collection/:title/": "/posts/my-title/",
"/:collection/:filename": "/posts/my-file-name/",
"/:something/:or/:other": "/:something/:or/:other/",
"/:categories/:title/": "/desi/my-title/",
};
Object.keys(cases).forEach(function (tpl) {
var entity
, tpld
;
var entity, tpld;
entity = {
year : '2015'
, month : '07'
, day : '04'
, title : "My Title"
, slug : "my-title"
, name : "My-File-Name.html"
, relativePath : "posts/fun"
, path : "posts/fun/My-File-Name.html"
, collection : "posts"
, yml : { categories: ['desi'] }
year: "2015",
month: "07",
day: "04",
title: "My Title",
slug: "my-title",
name: "My-File-Name.html",
relativePath: "posts/fun",
path: "posts/fun/My-File-Name.html",
collection: "posts",
yml: { categories: ["desi"] },
};
tpld = permalinker(tpl, entity);
if (cases[tpl] !== tpld) {
console.error('[ERROR]');
console.error(tpl + ' ' + tpld + ' ' + cases[tpl]);
console.error("[ERROR]");
console.error(tpl + " " + tpld + " " + cases[tpl]);
throw new Error(
"Did not template permalink correctly. "
+ tpl + ' ' + tpld + ' ' + cases[tpl]
"Did not template permalink correctly. " +
tpl +
" " +
tpld +
" " +
cases[tpl]
);
}
});