began setup wizard
This commit is contained in:
parent
bac3b49985
commit
31cd11ad60
|
@ -0,0 +1,19 @@
|
|||
BUGS
|
||||
====
|
||||
|
||||
* index page /index/index.html
|
||||
* rss feed missing
|
||||
|
||||
Usability
|
||||
=========
|
||||
|
||||
* compile dev vs prod
|
||||
* new posts
|
||||
|
||||
Feautres
|
||||
========
|
||||
|
||||
* permalink url maker
|
||||
* tags
|
||||
* categories
|
||||
* output to os.tmpdir (i.e. /tmp)
|
51
README.md
51
README.md
|
@ -1,9 +1,11 @@
|
|||
Desirae
|
||||
=====
|
||||
|
||||
In development.
|
||||
A blog platform built for Developers, but with normal people in mind.
|
||||
|
||||
Blog Platform. A Ruhoh knock-off written in JavaScript for the Browser
|
||||
Desirae runs entirely in the browser, but needs a little help from Node.js for saving and retrieving files.
|
||||
|
||||
She can also be run from entirely headless from node.js.
|
||||
|
||||
Key Features
|
||||
------------
|
||||
|
@ -18,6 +20,51 @@ Node (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).
|
||||
|
||||
Install and Usage
|
||||
=================
|
||||
|
||||
If you're on OS X or Linux, it's as easy as pie to install and use Desirae.
|
||||
|
||||
```bash
|
||||
git clone git@github.com:DearDesi/desirae.git
|
||||
pushd desirae
|
||||
|
||||
# Downloads and installs node.js and a few other tools Desirae needs
|
||||
bash setup.sh ./blog
|
||||
```
|
||||
|
||||
After the initial installation you can launch Dear Desi, the Web-based configuration and build tool like so:
|
||||
|
||||
```
|
||||
deardesi ./blog 65080
|
||||
```
|
||||
|
||||
Or, if you prefer, you can build with `desirae` from the command line:
|
||||
|
||||
```
|
||||
desirae build ./blog
|
||||
|
||||
desirae build-dev ./blog
|
||||
```
|
||||
|
||||
Create a new Post
|
||||
-----------------
|
||||
|
||||
```
|
||||
desirae post "My First Post"
|
||||
```
|
||||
|
||||
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)
|
||||
* `desirae.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.
|
||||
|
||||
Widgets
|
||||
=======
|
||||
|
||||
|
|
6
app.js
6
app.js
|
@ -4,8 +4,12 @@
|
|||
angular.module('myApp', [
|
||||
'ngRoute',
|
||||
'myApp.about',
|
||||
'myApp.authors',
|
||||
'myApp.site',
|
||||
'myApp.build',
|
||||
'myApp.version'
|
||||
'myApp.create',
|
||||
'myApp.version',
|
||||
'myApp.services'
|
||||
]).
|
||||
config(['$routeProvider', function ($routeProvider) {
|
||||
$routeProvider.otherwise({redirectTo: '/about'});
|
||||
|
|
|
@ -41,7 +41,8 @@
|
|||
"angular": "~1.3.8",
|
||||
"angular-route": "~1.3.8",
|
||||
"html5-boilerplate": "~4.3.0",
|
||||
"bootstrap": "~3.3.1"
|
||||
"bootstrap": "~3.3.1",
|
||||
"md5": "~0.1.3"
|
||||
},
|
||||
"resolutions": {
|
||||
"bluebird": "~2.6.2"
|
||||
|
|
|
@ -1,9 +1,62 @@
|
|||
angular.module('myApp.services', []).
|
||||
factory('MyService', function($http) {
|
||||
var MyService = {};
|
||||
$http.get('resources/data.json').success(function(response) {
|
||||
MyService.data = response;
|
||||
factory('Desirae', ['$q', function($q) {
|
||||
var Desi = window.Desi || require('./deardesi').Desi
|
||||
, desi = {}
|
||||
, fsapi = window.fsapi
|
||||
;
|
||||
|
||||
return {
|
||||
reset: function () {
|
||||
desi = {};
|
||||
}
|
||||
, meta: function () {
|
||||
var d = $q.defer()
|
||||
;
|
||||
|
||||
if (desi.meta) {
|
||||
d.resolve(desi);
|
||||
return d.promise;
|
||||
}
|
||||
|
||||
Desi.init(desi).then(function () {
|
||||
d.resolve(desi);
|
||||
});
|
||||
return MyService;
|
||||
}
|
||||
|
||||
return d.promise;
|
||||
}
|
||||
, build: function (env) {
|
||||
var d = $q.defer()
|
||||
;
|
||||
|
||||
if (desi.built) {
|
||||
d.resolve(desi);
|
||||
return d.promise;
|
||||
}
|
||||
|
||||
Desi.build(desi, env).then(function () {
|
||||
d.resolve(desi);
|
||||
});
|
||||
|
||||
return d.promise;
|
||||
}
|
||||
, write: function (env) {
|
||||
var d = $q.defer()
|
||||
;
|
||||
|
||||
if (desi.written) {
|
||||
d.resolve(desi);
|
||||
return d.promise;
|
||||
}
|
||||
|
||||
Desi.write(desi, env).then(function () {
|
||||
d.resolve(desi);
|
||||
});
|
||||
|
||||
return d.promise;
|
||||
}
|
||||
, putFiles: function (files) {
|
||||
return $q.when(fsapi.putFiles(files));
|
||||
}
|
||||
};
|
||||
}]
|
||||
);
|
||||
|
|
1063
deardesi.js
1063
deardesi.js
File diff suppressed because it is too large
Load Diff
13
index.html
13
index.html
|
@ -38,8 +38,10 @@
|
|||
|
||||
<div id="navbar-main" ng-class="!navCollapsed && 'in'" class="navbar-collapse collapse">
|
||||
<ul style="padding-top: 9px;" class="nav navbar-nav">
|
||||
<li><a href="#/build">Configure</a></li>
|
||||
<li><a href="#/post">Create Post</a></li>
|
||||
<li><a href="#/authors">Authors</a></li>
|
||||
<li><a href="#/site">Site</a></li>
|
||||
<li><a href="#/build">Build</a></li>
|
||||
<li><a href="#/create">Create Post</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -102,9 +104,14 @@
|
|||
<!-- UX Using Angular, but not getting fancy -->
|
||||
<script src="./bower_components/angular/angular.js"></script>
|
||||
<script src="./bower_components/angular-route/angular-route.js"></script>
|
||||
<script src="./bower_components/md5/build/md5.min.js"></script>
|
||||
<script src="./app.js"></script>
|
||||
<script src="./views/build/build.js"></script>
|
||||
<script src="./views/about/about.js"></script>
|
||||
<script src="./views/authors/authors.js"></script>
|
||||
<script src="./views/site/site.js"></script>
|
||||
<script src="./views/build/build.js"></script>
|
||||
<script src="./views/create/create.js"></script>
|
||||
<script src="components/desirae/desirae.js"></script>
|
||||
<script src="components/version/version.js"></script>
|
||||
<script src="components/version/version-directive.js"></script>
|
||||
<script src="components/version/interpolate-filter.js"></script>
|
||||
|
|
|
@ -193,6 +193,8 @@
|
|||
|
||||
var extensions = ''
|
||||
, dotfiles = ''
|
||||
, contents = ''
|
||||
, sha1sum = ''
|
||||
;
|
||||
|
||||
if (Array.isArray(opts.extensions)) {
|
||||
|
@ -201,10 +203,17 @@
|
|||
if (opts.dotfiles) {
|
||||
dotfiles = '&dotfiles=true';
|
||||
}
|
||||
if (opts.contents) {
|
||||
contents = '&contents=true';
|
||||
}
|
||||
if (false === opts.sha1sum) {
|
||||
sha1sum = '&sha1sum=false';
|
||||
}
|
||||
|
||||
return request.post('/api/fs/walk?_method=GET' + dotfiles + extensions, {
|
||||
return request.post('/api/fs/walk?_method=GET' + dotfiles + extensions + contents + sha1sum, {
|
||||
dirs: collections
|
||||
}).then(function (resp) {
|
||||
console.log(collections);
|
||||
return JSON.parse(resp);
|
||||
});
|
||||
};
|
||||
|
@ -223,6 +232,102 @@
|
|||
});
|
||||
};
|
||||
|
||||
fsapi.getConfigs = function (confs) {
|
||||
var opts = { extensions: ['yml', 'yaml', 'json'], dotfiles: false, contents: true, sha1sum: true }
|
||||
;
|
||||
|
||||
return fsapi.getMeta(confs, opts).then(function (collections) {
|
||||
var obj = {}
|
||||
;
|
||||
|
||||
Object.keys(collections).forEach(function (key) {
|
||||
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 = {}
|
||||
;
|
||||
|
||||
if (/\.(ya?ml)$/i.test(file.name)) {
|
||||
console.log();
|
||||
try {
|
||||
data = exports.YAML.parse(file.contents) || {};
|
||||
if ("undefined" === obj[keyname][filename]) {
|
||||
data = {};
|
||||
}
|
||||
} catch(e) {
|
||||
console.error("Could not parse yaml for " + filename);
|
||||
console.error(file);
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
else if (/\.(json)$/i.test(file.name)) {
|
||||
try {
|
||||
data = JSON.parse(file.contents) || {};
|
||||
} catch(e) {
|
||||
console.error("Could not parse json for " + filename);
|
||||
console.error(file);
|
||||
console.error(e);
|
||||
}
|
||||
} else {
|
||||
console.error("Not sure what to do with this one...");
|
||||
console.error(file);
|
||||
}
|
||||
|
||||
obj[keyname][filename] = data;
|
||||
/*
|
||||
if (!obj[keyname][filename]) {
|
||||
obj[keyname][filename] = {};
|
||||
}
|
||||
|
||||
Object.keys(data).forEach(function (key) {
|
||||
obj[keyname][filename][key] = data[key];
|
||||
});
|
||||
*/
|
||||
});
|
||||
});
|
||||
|
||||
return obj;
|
||||
});
|
||||
};
|
||||
fsapi.getAllPartials = function () {
|
||||
return fsapi.getConfigs(['partials', 'partials.yml']).then(function (results) {
|
||||
var partials = {}
|
||||
;
|
||||
|
||||
Object.keys(results.partials).forEach(function (key) {
|
||||
var partial = partials[key];
|
||||
Object.keys(partial).forEach(function (prop) {
|
||||
if (partials[prop]) {
|
||||
console.warn('partial \'' + prop + '\' overwritten by ' + key);
|
||||
}
|
||||
partials[prop] = partial[prop];
|
||||
});
|
||||
});
|
||||
|
||||
return partials;
|
||||
});
|
||||
};
|
||||
fsapi.getBlogdir = function () {
|
||||
return request.get('/api/fs').then(function (resp) {
|
||||
return JSON.parse(resp);
|
||||
});
|
||||
};
|
||||
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 };
|
||||
});
|
||||
};
|
||||
|
||||
fsapi.getData = function () {
|
||||
return request.get('/data.yml').then(function (resp) {
|
||||
return exports.YAML.parse(resp);
|
||||
|
@ -232,8 +337,10 @@
|
|||
fsapi.getCache = function () {
|
||||
return request.get('/cache.json').then(function (resp) {
|
||||
return JSON.parse(resp);
|
||||
}).catch(function () {
|
||||
}).catch(function (/*e*/) {
|
||||
return {};
|
||||
}).then(function (obj) {
|
||||
return obj;
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ var PromiseA = require('bluebird').Promise
|
|||
, 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()
|
||||
;
|
||||
|
||||
function strip(prefix, pathname) {
|
||||
|
@ -32,7 +33,7 @@ function walkDir(parent, sub, opts) {
|
|||
return false;
|
||||
}
|
||||
|
||||
if ('.' === name[0] && !opts.dotfiles) {
|
||||
if (!opts.dotfiles && ('.' === name[0])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
44
server.js
44
server.js
|
@ -15,9 +15,11 @@ var connect = require('connect')
|
|||
, getfs = require('./lib/fsapi').getfs
|
||||
, putfs = require('./lib/fsapi').putfs
|
||||
|
||||
, config = require('./config.yml')
|
||||
, blogdir = process.argv[2] || 'blog'
|
||||
, port = process.argv[3] || '65080'
|
||||
|
||||
, path = require('path')
|
||||
, blogdir = path.resolve(config.blogdir || __dirname)
|
||||
//, config = require(path.join('./', blogdir, 'config.yml'))
|
||||
;
|
||||
|
||||
|
||||
|
@ -41,6 +43,13 @@ app
|
|||
return;
|
||||
}
|
||||
|
||||
if (!dirnames.every(function (dirname) {
|
||||
return 'string' === typeof dirname;
|
||||
})) {
|
||||
res.json({ error: "malformed request: " + JSON.stringify(dirnames) });
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
if (req.query.excludes) {
|
||||
opts.excludes = req.query.excludes.split(',');
|
||||
|
@ -54,6 +63,12 @@ app
|
|||
if ('true' === req.query.dotfiles) {
|
||||
opts.dotfiles = true;
|
||||
}
|
||||
if ('false' === req.query.sha1sum) {
|
||||
opts.sha1sum = false;
|
||||
}
|
||||
if ('true' === req.query.contents) {
|
||||
opts.contents = true;
|
||||
}
|
||||
|
||||
// TODO opts.contents?
|
||||
walk.walkDirs(blogdir, dirnames, opts).then(function (stats) {
|
||||
|
@ -125,18 +140,29 @@ app
|
|||
});
|
||||
})
|
||||
|
||||
.use('/api/fs', function (req, res, next) {
|
||||
next();
|
||||
.use('/api/fs', function (req, res) {
|
||||
var pathname = path.resolve(blogdir)
|
||||
;
|
||||
|
||||
res.json({
|
||||
path: pathname
|
||||
, name: path.basename(pathname)
|
||||
, relativePath: path.dirname(pathname)
|
||||
//, cwd: path.resolve()
|
||||
//, patharg: blogdir
|
||||
});
|
||||
return;
|
||||
})
|
||||
.use('/api/fs/static', serveStatic('.'))
|
||||
.use('/api/fs/static', serveStatic(blogdir))
|
||||
|
||||
.use(serveStatic('.'))
|
||||
.use(serveStatic(blogdir))
|
||||
.use(serveStatic('./'))
|
||||
.use('/compiled_dev', serveStatic(path.join(blogdir, '/compiled_dev')))
|
||||
// TODO
|
||||
//.use(serveStatic(tmpdir))
|
||||
;
|
||||
|
||||
module.exports = app;
|
||||
|
||||
require('http').createServer().on('request', app).listen(process.argv[2] || 65080, function () {
|
||||
console.log('listening ' + (process.argv[2] || 65080));
|
||||
require('http').createServer().on('request', app).listen(port, function () {
|
||||
console.log('listening ' + port);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
bootstrap_cdn: //maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css
|
||||
bootswatches:
|
||||
- Cerulean
|
||||
- Cosmo
|
||||
- Cyborg
|
||||
- Darkly
|
||||
- Flatly
|
||||
- Journal
|
||||
- Lumen
|
||||
- Paper
|
||||
- Readable
|
||||
- Sandstone
|
||||
- Simplex
|
||||
- Slate
|
||||
- Spacelab
|
||||
- Superhero
|
||||
- United
|
||||
- Yeti
|
||||
bootswatches_2:
|
||||
- Amelia
|
||||
- Cerulean
|
||||
- Cosmo
|
||||
- Cyborg
|
||||
- Flatly
|
||||
- Journal
|
||||
- Readable
|
||||
- Simplex
|
||||
- Slate
|
||||
- Spacelab
|
||||
- Superhero
|
||||
- United
|
||||
bootswatch_cdn: //maxcdn.bootstrapcdn.com/bootswatch/3.3.1/{{name}}/bootstrap.min.css
|
||||
fontawesome_cdn: //maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css
|
||||
|
||||
bootswatch_2_download: http://bootswatch.com/2/{{bootswatch}}/bootstrap.min.css
|
||||
bootstrap_2_cdn://maxcdn.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.min.css
|
|
@ -1,45 +1,47 @@
|
|||
<div ui-view="content" autocroll="false">
|
||||
<div style="margin-bottom: 0;" class="jumbotron">
|
||||
<div class="row">
|
||||
<div class="container">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
|
||||
<div class="page-header">
|
||||
<h1>Welcome to Dear Desi! <small ng-bind="'(v%VERSION%)' | interpolate"></small></h1>
|
||||
</div>
|
||||
|
||||
<div class="jumbotron">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<p>Dear Desi, ...</p><br/>
|
||||
<div class="col-lg-7">
|
||||
<p>Setup your new blog in just 5 minutes.</p>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="text-align: center;" class="col-md-6"><img ng-src="http://dropsha.re/files/fly6+.8/desi-parker-2.jpg" style="border: 5px solid white; width: 260px; height: 260px;" class="img-circle"/>
|
||||
<h1>Desirae</h1>
|
||||
<h3>The in-browser static blog generator
|
||||
<small ng-bind="'(v%VERSION%)' | interpolate"></small>
|
||||
</h3>
|
||||
</div>
|
||||
<div style="text-align: left;" class="col-md-6">
|
||||
<div>
|
||||
<legend>
|
||||
<h2><span>Features</span></h2>
|
||||
</legend>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<ul>
|
||||
<li>Builds in the Browser
|
||||
<ul>
|
||||
<li>Write content in Markdown, Jade, or HTML</li>
|
||||
<li>Mustache Templates</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Git, Dropbox, or regular Folders for management</li>
|
||||
<li>Go headless with Node.js support</li>
|
||||
<li>Dual Licensed Apache2 and MIT</li>
|
||||
<li>No Ruby version Hell - it'll still work in 6 months! :-D</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-7">
|
||||
<br/>
|
||||
<iframe width="560" height="315" src="//www.youtube.com/embed/YZzhIIJmlE0" frameborder="0" allowfullscreen></iframe>
|
||||
<br/>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<h2>Dear Desi is...</h2>
|
||||
<br/>
|
||||
<ul>
|
||||
<li>Builds in the Browser
|
||||
<ul>
|
||||
<li>The in-browser static blog generator</li>
|
||||
<li>Write content in Markdown, Jade, or HTML</li>
|
||||
<li>Mustache Templates</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Git, Dropbox, or regular Folders for management</li>
|
||||
<li>Go headless with Node.js support</li>
|
||||
<li>Dual Licensed Apache2 and MIT</li>
|
||||
<li>No Ruby version Hell - it'll still work in 6 months! :-D</li>
|
||||
</ul>
|
||||
<h3>What are you waiting for?</h3>
|
||||
<br/>
|
||||
<a class="btn btn-primary btn-lg pull-right" href="/#authors">Get Started</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,233 @@
|
|||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="page-header">
|
||||
<div class="col-md-5 col-sm-6 col-xs-8">
|
||||
<h1>Primary Author</h1>
|
||||
|
||||
<div class="row" ng-if="Authors.authors">
|
||||
<div class="col-md-offset-1 col-md-8">
|
||||
<div class="form-group">
|
||||
<select
|
||||
ng-options="author as author.filename for (handle, author) in Authors.authors"
|
||||
class="form-control"
|
||||
ng-model="Authors.selectedAuthor"
|
||||
ng-change="Authors.selectAuthor()"
|
||||
></select>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col-md-5 col-sm-6 col-xs-4">
|
||||
<br/>
|
||||
<img style="height:75px;" ng-src="{{Authors.headshot}}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form class="form-horizontal" name="newAuthors" ng-submit="Authors.upsert(Authors.selectedAuthor)">
|
||||
<div class="row">
|
||||
<div class="col-sm-8">
|
||||
<small><span ng-bind="Authors.blogdir"
|
||||
></span>/authors/<span ng-bind="Authors.selectedAuthor.handle"
|
||||
></span><span ng-if="Authors.selectedAuthor.handle"
|
||||
>.yml</span></small>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<button class="btn btn-success pull-right" type="submit" ng-disabled="Authors.dirty || !Authors.selectedAuthor.handle">Save & Continue</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<br/>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="well bs-component">
|
||||
<fieldset>
|
||||
<legend>Profile Basics</legend>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="inputAuthorName" class="col-lg-4 control-label">Name*</label>
|
||||
<div class="col-lg-8">
|
||||
<input ng-model="Authors.selectedAuthor.name"
|
||||
required="required"
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="inputAuthorName"
|
||||
name="inputAuthorName"
|
||||
placeholder="i.e. John Doe">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="inputAuthorNickname" class="col-lg-4 control-label">Handle*</label>
|
||||
<div class="col-lg-8">
|
||||
<input ng-model="Authors.selectedAuthor.handle"
|
||||
required="required"
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="inputAuthorNickname"
|
||||
name="inputAuthorNickname"
|
||||
placeholder="i.e. johndoe">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="inputAuthorEmail" class="col-lg-4 control-label">Email*</label>
|
||||
<div class="col-lg-8">
|
||||
<input ng-model="Authors.selectedAuthor.email" ng-change="Authors.updateHeadshotUrl()"
|
||||
required="required"
|
||||
type="email"
|
||||
class="form-control"
|
||||
id="inputAuthorEmail"
|
||||
name="inputAuthorEmail"
|
||||
placeholder="i.e. john.doe@gmail.com">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="inputAuthorWebsite" class="col-lg-4 control-label">Website</label>
|
||||
<div class="col-lg-8">
|
||||
<input ng-model="Authors.selectedAuthor.website"
|
||||
type="url"
|
||||
class="form-control"
|
||||
id="inputAuthorWebsite"
|
||||
name="inputAuthorWebsite"
|
||||
placeholder="i.e. http://johndoe.name"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="form-group">
|
||||
<label for="inputAuthorBio" class="col-lg-2 control-label">Bio
|
||||
<small>(<span ng-bind="Authors.selectedAuthor.bio.length || 0"></span>/140)</small></label>
|
||||
<div class="col-lg-10">
|
||||
<textarea ng-model="Authors.selectedAuthor.bio"
|
||||
class="form-control" id="inputAuthorBio" placeholder="i.e. Brogrammatic Ninja-throwing Rockstar Badassian Wizard JavaScript Superstar. 3+ years experience as a jalapeno poppers brony. YOLO."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="form-group">
|
||||
<label for="inputAuthorHeadshot" class="col-lg-2 control-label">Headshot</label>
|
||||
<div class="col-lg-10">
|
||||
<input ng-model="Authors.selectedAuthor.headshot" ng-change="Authors.updateHeadshotUrl()"
|
||||
type="text" class="form-control" id="inputAuthorHeadshot" placeholder="i.e. https://i.imgur.com/qqpxDmJ.jpg">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-12">
|
||||
<div class="well bs-component">
|
||||
<fieldset>
|
||||
<legend>Social</legend>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="inputAuthorTwitter" class="col-lg-2 control-label">Twitter</label>
|
||||
<div class="col-lg-10">
|
||||
<input ng-model="Authors.selectedAuthor.twitter"
|
||||
type="text" class="form-control" id="inputAuthorTwitter" placeholder="i.e. @johndoe">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="inputAuthorFacebook" class="col-lg-2 control-label">Facebook URL</label>
|
||||
<div class="col-lg-10">
|
||||
<input ng-model="Authors.selectedAuthor.facebook"
|
||||
type="text" class="form-control" id="inputAuthorFacebook" placeholder="i.e. facebook.com/johndoe">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="inputAuthorGooglePlus" class="col-lg-2 control-label">Google+ URL</label>
|
||||
<div class="col-lg-10">
|
||||
<input ng-model="Authors.selectedAuthor.googleplus"
|
||||
type="text" class="form-control" id="inputAuthorGooglePlus" placeholder="i.e. plus.google.com/+johndoe">
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-12">
|
||||
<div class="well bs-component">
|
||||
<fieldset>
|
||||
<legend>Developers</legend>
|
||||
<div class="form-group">
|
||||
<label for="inputAuthorGithub" class="col-lg-2 control-label">Github</label>
|
||||
<div class="col-lg-10">
|
||||
<input ng-model="Authors.selectedAuthor.github"
|
||||
type="text" class="form-control" id="inputAuthorGithub" placeholder="i.e. johndoe">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="inputAuthorStackOverflow" class="col-lg-2 control-label">StackOverflow</label>
|
||||
<div class="col-lg-10">
|
||||
<input ng-model="Authors.selectedAuthor.stackoverflow"
|
||||
type="text" class="form-control" id="inputAuthorStackOverflow" placeholder="i.e. http://stackoverflow.com/users/151312/johndoe">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-12">
|
||||
<div class="well bs-component">
|
||||
<fieldset>
|
||||
<legend>Feeds</legend>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="inputAuthorFeedburner" class="col-lg-2 control-label">Feedburner</label>
|
||||
<div class="col-lg-10">
|
||||
<input ng-model="Authors.selectedAuthor.feedburner"
|
||||
type="text" class="form-control" id="inputAuthorFeedburner" placeholder="i.e. johndoe">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary pull-right" type="submit">Save & Continue</button>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
Instagram
|
||||
Etsy
|
||||
<div class="form-group">
|
||||
<label for="inputAuthorPinterest" class="col-lg-2 control-label">Pinterest</label>
|
||||
<div class="col-lg-10">
|
||||
<input ng-model="Authors.selectedAuthor.pinterest"
|
||||
type="text" class="form-control" id="inputAuthorPinterest" placeholder="i.e. @johndoe">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
-->
|
|
@ -0,0 +1,119 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('myApp.authors', ['ngRoute'])
|
||||
|
||||
.config(['$routeProvider', function($routeProvider) {
|
||||
$routeProvider.when('/authors', {
|
||||
templateUrl: 'views/authors/authors.html',
|
||||
controller: 'AuthorsCtrl as Authors'
|
||||
});
|
||||
}])
|
||||
|
||||
.controller('AuthorsCtrl'
|
||||
, ['$scope', '$timeout', '$location', 'Desirae'
|
||||
, function($scope, $timeout, $location, Desirae) {
|
||||
var scope = this
|
||||
;
|
||||
|
||||
scope.newAuthor = function () {
|
||||
console.log('new author');
|
||||
scope.new = { filename: 'new' };
|
||||
scope.selectAuthor(scope.new);
|
||||
};
|
||||
|
||||
scope.selectAuthor = function (author) {
|
||||
// TODO watch any change
|
||||
scope.selectedAuthor = author || scope.selectedAuthor;
|
||||
scope.updateHeadshotUrlNow();
|
||||
};
|
||||
|
||||
scope.upsert = function () {
|
||||
var author = scope.selectedAuthor
|
||||
, files = []
|
||||
, filename = author.filename
|
||||
;
|
||||
|
||||
delete author.filename;
|
||||
if ('new' !== filename && filename !== author.handle) {
|
||||
files.push({ path: 'authors/' + filename + '.yml', contents: '', delete: true });
|
||||
}
|
||||
files.push({ path: 'authors/' + author.handle + '.yml', contents: window.jsyaml.dump(author) });
|
||||
|
||||
console.log(files);
|
||||
|
||||
Desirae.putFiles(files).then(function (results) {
|
||||
console.log('updated author', results);
|
||||
$location.path('/site');
|
||||
}).catch(function (e) {
|
||||
author.filename = filename;
|
||||
console.error(e);
|
||||
window.alert("Error Nation! :/");
|
||||
throw e;
|
||||
});
|
||||
};
|
||||
|
||||
scope.updateHeadshotUrlNow = function () {
|
||||
var gravatar = 'http://www.gravatar.com/avatar/' + window.md5((scope.selectedAuthor.email||'foo').toLowerCase()) + '?d=identicon'
|
||||
;
|
||||
|
||||
if (scope.selectedAuthor.headshot) {
|
||||
scope.headshot = scope.selectedAuthor.headshot;
|
||||
}
|
||||
else if (scope.selectedAuthor.email) {
|
||||
scope.headshot = gravatar;
|
||||
}
|
||||
else {
|
||||
scope.headshot = 'http://www.gravatar.com/avatar/' + window.md5((scope.selectedAuthor.email||'foo').toLowerCase()) + '?d=mm';
|
||||
}
|
||||
};
|
||||
|
||||
scope.updateHeadshotUrl = function () {
|
||||
$timeout.cancel(scope.hslock);
|
||||
scope.hslock = $timeout(function () {
|
||||
scope.updateHeadshotUrlNow();
|
||||
}, 300);
|
||||
};
|
||||
|
||||
function init() {
|
||||
scope.newAuthor();
|
||||
|
||||
console.log('desi loading');
|
||||
Desirae.meta().then(function (desi) {
|
||||
var filename
|
||||
;
|
||||
|
||||
scope.blogdir = desi.blogdir.path.replace(/^\/(Users|home)\/[^\/]+\//, '~/');
|
||||
desi.authors = desi.authors || {};
|
||||
desi.authors.new = scope.new;
|
||||
scope.authors = desi.authors;
|
||||
|
||||
Object.keys(desi.authors).forEach(function (filename) {
|
||||
if ('new' === filename) {
|
||||
return;
|
||||
}
|
||||
desi.authors[filename].filename = filename;
|
||||
desi.authors[filename].handle = desi.authors[filename].handle || filename;
|
||||
});
|
||||
|
||||
filename = Object.keys(desi.authors)[0];
|
||||
scope.selectedAuthor = desi.authors[filename];
|
||||
|
||||
scope.updateHeadshotUrlNow();
|
||||
}).catch(function (e) {
|
||||
window.alert("An Error Occured. Most errors that occur in the init phase are parse errors in the config files or permissions errors on files or directories, but check the error console for details.");
|
||||
console.error(e);
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
|
||||
init();
|
||||
/*
|
||||
$scope.$watch(angular.bind(this, function () { return this.selectedAuthor; }), function (newValue, oldValue) {
|
||||
//$scope.$watch('Authors.selecteAuthor', function (newValue, oldValue)
|
||||
console.log(newValue, oldValue);
|
||||
if(newValue !== oldValue) {
|
||||
scope.dirty = true;
|
||||
}
|
||||
}, true);
|
||||
*/
|
||||
}]);
|
|
@ -10,20 +10,4 @@ angular.module('myApp.build', ['ngRoute'])
|
|||
}])
|
||||
|
||||
.controller('BuildCtrl', [function() {
|
||||
var Desi = window.Desi || require('./deardesi').Desi
|
||||
, scope = this
|
||||
, desi = {}
|
||||
;
|
||||
|
||||
Desi.init(desi).then(function () {
|
||||
scope.run = function () {
|
||||
return Desi.runDesi(desi).then(function () { Desi.otherStuff(); })
|
||||
.catch(function (e) {
|
||||
console.error('A great and uncatchable error has befallen the land. Read ye here for das detalles..');
|
||||
console.error(e.message);
|
||||
console.error(e);
|
||||
throw e;
|
||||
});
|
||||
};
|
||||
});
|
||||
}]);
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('myApp.configure', ['ngRoute'])
|
||||
|
||||
.config(['$routeProvider', function($routeProvider) {
|
||||
$routeProvider.when('/configure', {
|
||||
templateUrl: 'views/configure/configure.html',
|
||||
controller: 'ConfigureCtrl as Configure'
|
||||
});
|
||||
}])
|
||||
|
||||
.controller('ConfigureCtrl', [function() {
|
||||
var Desi = window.Desi || require('./deardesi').Desi
|
||||
, scope = this
|
||||
, desi = {}
|
||||
;
|
||||
|
||||
Desi.init(desi).then(function () {
|
||||
scope.run = function () {
|
||||
return Desi.runDesi(desi).then(function () { Desi.otherStuff(); })
|
||||
.catch(function (e) {
|
||||
console.error('A great and uncatchable error has befallen the land. Read ye here for das detalles..');
|
||||
console.error(e.message);
|
||||
console.error(e);
|
||||
throw e;
|
||||
});
|
||||
};
|
||||
});
|
||||
}]);
|
|
@ -2,6 +2,7 @@
|
|||
<div class="row">
|
||||
<div class="page-header">
|
||||
<h1>Blog Configuration</h1>
|
||||
<h3><span ng-bind="Site.blogdir"></span></h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -9,71 +10,47 @@
|
|||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="well bs-component">
|
||||
<fieldset>
|
||||
<legend>General</legend>
|
||||
<fieldset>
|
||||
<legend>General</legend>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="inputBlogTitle" class="col-lg-2 control-label">Title</label>
|
||||
<div class="col-lg-10">
|
||||
<input type="text" class="form-control" id="inputBlogTitle" placeholder="My Awesome Blog">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="inputBlogTitle" class="col-lg-2 control-label">Title</label>
|
||||
<div class="col-lg-10">
|
||||
<input type="text" class="form-control" id="inputBlogTitle" placeholder="My Awesome Blog" ng-model="Site.title">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="inputBlogTagline" class="col-lg-2 control-label">Tagline</label>
|
||||
<div class="col-lg-10">
|
||||
<input type="text" class="form-control" id="inputBlogTagline" placeholder="For try-hard ethical master cleanse, 3 wolf moon Tumblr, disrupt lo-fi, narwhals and kale chips. YOLO.">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="inputBlogTagline" class="col-lg-2 control-label">Tagline</label>
|
||||
<div class="col-lg-10">
|
||||
<input type="text" class="form-control" id="inputBlogTagline" placeholder="For try-hard ethical master cleanse, 3 wolf moon Tumblr, disrupt lo-fi, narwhals and kale chips. YOLO." ng-model="Site.tagline">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="inputBlogRoot" class="col-lg-2 control-label">Blog Root</label>
|
||||
<div class="col-lg-10">
|
||||
<input type="text" class="form-control" id="inputBlogRoot" disabled placeholder="./blog">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="inputBlogTheme" class="col-lg-2 control-label">Default Theme</label>
|
||||
<div class="col-lg-10">
|
||||
<select class="form-control" id="inputBlogTheme"
|
||||
ng-options="item as item for item in ['twitter', 'sunburst']"
|
||||
ng-model="themes.default"></select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="inputBlogNav" class="col-lg-2 control-label">Navigation</label>
|
||||
<div class="col-lg-10">
|
||||
<div ng-repeat="nav in ['index', 'portfolio', 'archive']" class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="nav.selected"> <span ng-bind="nav"></span>
|
||||
</label>
|
||||
</div>
|
||||
<!--input type="text" class="form-control" id="inputBlogNav" disabled placeholder=""-->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="col-lg-12">
|
||||
<div class="well bs-component">
|
||||
<fieldset>
|
||||
<legend>Production</legend>
|
||||
<div class="form-group">
|
||||
<label for="inputProdHost" class="col-lg-3 control-label">Host</label>
|
||||
<label for="inputProdHost" class="col-lg-3 control-label">Base URL</label>
|
||||
<div class="col-lg-9">
|
||||
<input type="text" class="form-control" id="inputProdHost" placeholder="http://dear.desi">
|
||||
<input ng-model="Site.base_url"
|
||||
placeholder="i.e. https://example.com in https://example.com/myblog"
|
||||
type="text" class="form-control" id="inputProdHost">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="inputProdBase" class="col-lg-3 control-label">Base Path</label>
|
||||
<div class="col-lg-9">
|
||||
<input type="text" class="form-control" id="inputProdBase" placeholder="/blog">
|
||||
<input ng-model="Site.base_path"
|
||||
placeholder="i.e. /blog in https://example.com/blog"
|
||||
type="text" class="form-control" id="inputProdBase">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
@ -86,31 +63,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div class="well bs-component">
|
||||
<fieldset>
|
||||
<legend>Development</legend>
|
||||
<div class="form-group">
|
||||
<label for="inputDevHost" class="col-lg-3 control-label">Host</label>
|
||||
<div class="col-lg-9">
|
||||
<input type="text" class="form-control" id="inputDevHost" disabled placeholder="http://local.dear.desi:8080">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="inputDevBase" class="col-lg-3 control-label">Base Path</label>
|
||||
<div class="col-lg-9">
|
||||
<input type="text" class="form-control" id="inputDevBase" disabled placeholder="/compiled_dev">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="inputDevOutput" class="col-lg-3 control-label">Output Path</label>
|
||||
<div class="col-lg-9">
|
||||
<input type="text" class="form-control" id="inputDevOutput" disabled placeholder="./compiled_dev">
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('myApp.site', ['ngRoute'])
|
||||
|
||||
.config(['$routeProvider', function($routeProvider) {
|
||||
$routeProvider.when('/site', {
|
||||
templateUrl: 'views/site/site.html',
|
||||
controller: 'SiteCtrl as Site'
|
||||
});
|
||||
}])
|
||||
|
||||
.controller('SiteCtrl', ['$scope', 'Desirae', function($scope, Desirae) {
|
||||
var scope = this
|
||||
;
|
||||
|
||||
console.log('desi loading');
|
||||
Desirae.meta().then(function (desi) {
|
||||
console.log('desi loaded');
|
||||
console.log(desi);
|
||||
scope.blogdir = desi.blogdir.path.replace(/^\/(Users|home)\/[^\/]+\//, '~/');
|
||||
}).catch(function (e) {
|
||||
window.alert("An Error Occured. Most errors that occur in the init phase are parse errors in the config files or permissions errors on files or directories, but check the error console for details.");
|
||||
console.error(e);
|
||||
throw e;
|
||||
});
|
||||
}]);
|
Loading…
Reference in New Issue