separating deardesi/desirae

This commit is contained in:
AJ ONeal 2015-01-13 03:18:13 -07:00
parent 332b9e2125
commit ef8d7b5796
24 changed files with 1622 additions and 0 deletions

17
app.js Normal file
View File

@ -0,0 +1,17 @@
'use strict';
// Declare app level module which depends on views, and components
angular.module('myApp', [
'ngRoute',
'myApp.about',
'myApp.authors',
'myApp.site',
'myApp.build',
'myApp.configure',
'myApp.post',
'myApp.version',
'myApp.services'
]).
config(['$routeProvider', function ($routeProvider) {
$routeProvider.otherwise({redirectTo: '/about'});
}]);

50
bower.json Normal file
View File

@ -0,0 +1,50 @@
{
"name": "deardesi",
"version": "1.0.0",
"authors": [
"AJ ONeal <awesome@coolaj86.com>"
],
"description": "A blogging platform in the browser. Wow!",
"main": "deardesi.js",
"moduleType": [
"globals",
"node"
],
"keywords": [
"dear",
"desi",
"deardesi",
"blog",
"blogging",
"platform",
"browser"
],
"license": "Apache2",
"homepage": "http://github.com/coolaj86/deardesi",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {
"mustache": "~0.8.2",
"bluebird": "~2.6.2",
"rsvp": "~3.0.16",
"escape-string-regexp": "~1.0.2",
"js-yaml": "~3.2.5",
"path": "~3.46.1",
"forEachAsync": "~5.0.5",
"node-uuid": "~1.4.2",
"markdown-it": "~3.0.2",
"angular": "~1.3.8",
"angular-route": "~1.3.8",
"html5-boilerplate": "~4.3.0",
"bootstrap": "~3.3.1",
"md5": "~0.1.3"
},
"resolutions": {
"bluebird": "~2.6.2"
}
}

View File

@ -0,0 +1,63 @@
angular.module('myApp.services', []).
factory('Desirae', ['$q', function($q) {
var Desi = window.Desi || require('./deardesi').Desi
, desi = {}
, fsapi = window.fsapi
;
return {
reset: function () {
desi = {};
}
, toDesiDate: Desi.toLocaleDate
, meta: function () {
var d = $q.defer()
;
if (desi.meta) {
d.resolve(desi);
return d.promise;
}
Desi.init(desi).then(function () {
d.resolve(desi);
});
return d.promise;
}
, build: function (env) {
var d = $q.defer()
;
if (desi.built) {
d.resolve(desi);
return d.promise;
}
Desi.buildAll(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));
}
};
}]
);

View File

@ -0,0 +1,9 @@
'use strict';
angular.module('myApp.version.interpolate-filter', [])
.filter('interpolate', ['version', function(version) {
return function(text) {
return String(text).replace(/\%VERSION\%/mg, version);
};
}]);

View File

@ -0,0 +1,15 @@
'use strict';
describe('myApp.version module', function() {
beforeEach(module('myApp.version'));
describe('interpolate filter', function() {
beforeEach(module(function($provide) {
$provide.value('version', 'TEST_VER');
}));
it('should replace VERSION', inject(function(interpolateFilter) {
expect(interpolateFilter('before %VERSION% after')).toEqual('before TEST_VER after');
}));
});
});

View File

@ -0,0 +1,9 @@
'use strict';
angular.module('myApp.version.version-directive', [])
.directive('appVersion', ['version', function(version) {
return function(scope, elm, attrs) {
elm.text(version);
};
}]);

View File

@ -0,0 +1,17 @@
'use strict';
describe('myApp.version module', function() {
beforeEach(module('myApp.version'));
describe('app-version directive', function() {
it('should print current version', function() {
module(function($provide) {
$provide.value('version', 'TEST_VER');
});
inject(function($compile, $rootScope) {
var element = $compile('<span app-version></span>')($rootScope);
expect(element.text()).toEqual('TEST_VER');
});
});
});
});

View File

@ -0,0 +1,8 @@
'use strict';
angular.module('myApp.version', [
'myApp.version.interpolate-filter',
'myApp.version.version-directive'
])
.value('version', '0.8.0');

View File

@ -0,0 +1,11 @@
'use strict';
describe('myApp.version module', function() {
beforeEach(module('myApp.version'));
describe('version service', function() {
it('should return current version', inject(function(version) {
expect(version).toEqual('0.1');
}));
});
});

47
views/about/about.html Normal file
View File

@ -0,0 +1,47 @@
<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-lg-7">
<p>Setup your new blog in just 5 minutes.</p>
</div>
<div class="col-lg-5">
</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>

14
views/about/about.js Normal file
View File

@ -0,0 +1,14 @@
'use strict';
angular.module('myApp.about', ['ngRoute'])
.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/about', {
templateUrl: 'views/about/about.html',
controller: 'AboutCtrl'
});
}])
.controller('AboutCtrl', [function() {
}]);

16
views/about/view2_test.js Normal file
View File

@ -0,0 +1,16 @@
'use strict';
describe('myApp.view2 module', function() {
beforeEach(module('myApp.view2'));
describe('view2 controller', function(){
it('should ....', inject(function($controller) {
//spec body
var view2Ctrl = $controller('View2Ctrl');
expect(view2Ctrl).toBeDefined();
}));
});
});

233
views/authors/authors.html Normal file
View File

@ -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 &amp; 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 &amp; 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>
-->

119
views/authors/authors.js Normal file
View File

@ -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);
*/
}]);

119
views/build/build.html Normal file
View File

@ -0,0 +1,119 @@
<div class="container">
<form ng-submit="Post.upsert()" class="form-horizontal">
<div class="row">
<div class="page-header">
<h1>Build Static Site</h1>
<h3><span ng-bind="Build.blogdir"></span></h3>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<h2>Are you ready?</h2>
<p>
<small><a href="https://www.youtube.com/watch?v=-E0oiKjLzTc" target="_blank">Bonesaw</a> is READY!</small>
</p>
<p>
<button
ng-click="Build.build(['production', 'development'])"
class="btn btn-danger"
type="button"
>Build Sites</button>
</p>
<span class="help-block">Push the RED button... you know you want to!</span>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="well bs-component">
<fieldset>
<legend>Production</legend>
<p><a ng-href="{{Build.production_url}}" target="_blank"><span ng-bind="Build.production_url"></span></a></p>
<div class="form-group">
<label for="inputProdCanonicalUrl" class="col-lg-2 control-label">Canonical URL</label>
<div class="col-lg-10">
<input ng-model="Build.production_url"
required="required"
placeholder="i.e. https://example.com/myblog"
type="url"
class="form-control"
id="inputProdCanonicalUrl">
<br/>
</div>
</div>
<div class="form-group">
<label for="inputProdOutput" class="col-lg-2 control-label">Output Path</label>
<div class="col-lg-10">
<input
disabled
ng-value="Build.blogdir + '/compiled'"
type="text" class="form-control" id="inputProdOutput" disabled>
</div>
</div>
<div class="form-group">
<div class="col-lg-10">
<!-- TODO progress bar -->
</div>
<div class="col-lg-2">
<button
ng-click="Build.build(['production'])"
class="btn btn-primary pull-right"
type="button"
>Build Production Site</button>
</div>
</div>
</fieldset>
</div>
<div class="well bs-component">
<fieldset>
<legend>Development</legend>
<p><a ng-href="{{Build.development_url}}" target="_blank"><span ng-bind="Build.development_url"></span></a></p>
<div class="form-group">
<label for="inputDevCanonicalUrl" class="col-lg-2 control-label">Canonical URL</label>
<div class="col-lg-10">
<input
ng-model="Build.development_url"
required="required"
disabled
placeholder="i.e. https://example.com/myblog"
type="url"
class="form-control"
id="inputDevCanonicalUrl">
<br/>
</div>
</div>
<div class="form-group">
<label for="inputDevOutput" class="col-lg-2 control-label">Output Path</label>
<div class="col-lg-10">
<input
disabled
ng-value="Build.blogdir + '/compiled_dev'"
type="text" class="form-control" id="inputDevOutput" disabled>
</div>
</div>
<div class="form-group">
<div class="col-lg-10">
<!-- TODO progress bar -->
</div>
<div class="col-lg-2">
<button
ng-click="Build.build(['development'])"
class="btn btn-primary pull-right"
type="button"
>Build Development Site</button>
</div>
</div>
</fieldset>
</div>
</div>
</div>
</form>
</div>

91
views/build/build.js Normal file
View File

@ -0,0 +1,91 @@
'use strict';
angular.module('myApp.build', ['ngRoute'])
.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/build', {
templateUrl: 'views/build/build.html',
controller: 'BuildCtrl as Build'
});
}])
.controller('BuildCtrl'
, ['$scope', '$location', '$timeout', 'Desirae'
, function ($scope, $location, $timeout, DesiraeService) {
var scope = this
, path = window.path
;
function init() {
console.log('desi loading');
DesiraeService.meta().then(function (desi) {
scope.blogdir = desi.blogdir.path.replace(/^\/(Users|home)\/[^\/]+\//, '~/');
scope.site = desi.site;
console.log(desi.site.base_url);
console.log(desi.site.base_path);
scope.production_url = desi.site.base_url + path.join('/', desi.site.base_path);
console.log(scope.production_url);
// this is the responsibility of the build system (Dear Desi), not the library (Desirae)
scope.development_url = location.href.replace(/\/(#.*)?$/, '') + path.join('/', 'compiled_dev');
console.log(scope.development_url);
}).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;
});
scope.extensions = ['md', 'html'];
}
scope.onError = function (e) {
console.error(e);
if (window.confirm("Encountered an error. Please inspect the console.\n\nWould you like to ignore the error and continue?")) {
return window.Promise.resolve();
} else {
return window.Promise.reject();
}
};
scope.buildOne = function (envstr) {
var env
;
// TODO is there a legitimate case where in addition to base_path (root of the blog)
// a user would need owner_base? i.e. school.edu/~/rogers/blog school.edu/~/rogers/assets
if ('production' === envstr) {
env = {
url: scope.production_url
, base_url: scope.development_url.replace(/(https?:\/\/[^\/#?]+)/, '$1')
, compiled_path: 'compiled'
, since: 0
, onError: scope.onError
};
} else {
env = {
url: scope.development_url
, base_url: scope.development_url.replace(/(https?:\/\/[^\/#?]+)/, '$1')
, base_path: scope.development_url.replace(/https?:\/\/[^\/#?]+/, '')
, compiled_path: 'compiled_dev'
, since: 0
, onError: scope.onError
};
}
return DesiraeService.build(env).then(function () {
DesiraeService.write(env);
});
};
scope.build = function (envs) {
window.forEachAsync(envs, function (env) {
return scope.buildOne(env);
}).then(function () {
window.alert('Build(s) Complete');
});
};
init();
}]);

View File

@ -0,0 +1,72 @@
<div class="container">
<div class="row">
<div class="page-header">
<h1>Site Configuration</h1>
<h3><span ng-bind="Site.blogdir"></span></h3>
</div>
</div>
<form class="form-horizontal">
<div class="row">
<div class="col-lg-12">
<div class="well bs-component">
<fieldset>
<legend>Advanced</legend>
<div class="form-group">
<label for="inputRootConf" class="col-lg-2 control-label">Root Pages</label>
<div class="col-lg-8">
<input type="text" class="form-control" id="inputRootConf" placeholder="i.e. images">
</div>
<div class="col-lg-2">
<button class="btn" type="button">Add</button>
</div>
</div>
<div class="form-group">
<label for="inputThemesConf" class="col-lg-2 control-label">Themes</label>
<div class="col-lg-8">
<input type="text" class="form-control" id="inputThemesConf" placeholder="i.e. images">
</div>
<div class="col-lg-2">
<button class="btn" type="button">Add</button>
</div>
</div>
<div class="form-group">
<label for="inputCollectionsConf" class="col-lg-2 control-label">Collections</label>
<div class="col-lg-8">
<input type="text" class="form-control" id="inputCollectionsConf" placeholder="i.e. images">
</div>
<div class="col-lg-2">
<button class="btn" type="button">Add</button>
</div>
</div>
<div class="form-group">
<label for="inputAssetsConf" class="col-lg-2 control-label">Assets</label>
<div class="col-lg-8">
<input type="text" class="form-control" id="inputAssetsConf" placeholder="i.e. images">
</div>
<div class="col-lg-2">
<button class="btn" type="button">Add</button>
</div>
</div>
<div class="form-group">
<label for="inputWidgetsConf" class="col-lg-2 control-label">Widgets</label>
<div class="col-lg-8">
<input type="text" class="form-control" id="inputWidgetsConf" placeholder="i.e. images">
</div>
<div class="col-lg-2">
<button class="btn" type="button">Add</button>
</div>
</div>
</fieldset>
</div>
</div>
</div>
</form>
</div>

View File

@ -0,0 +1,13 @@
'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() {
}]);

83
views/create/create.html Normal file
View File

@ -0,0 +1,83 @@
<div class="container">
<div class="row">
<div class="page-header">
<h1>Blog Configuration</h1>
</div>
</div>
<form class="form-horizontal">
<div class="row">
<div class="col-lg-12">
<div class="well bs-component">
<fieldset>
<div class="form-group">
<label for="inputEmail" class="col-lg-2 control-label">Email</label>
<div class="col-lg-10">
<input type="text" class="form-control" id="inputEmail" placeholder="Email">
</div>
</div>
<div class="form-group">
<label for="inputPassword" class="col-lg-2 control-label">Password</label>
<div class="col-lg-10">
<input type="password" class="form-control" id="inputPassword" placeholder="Password">
<div class="checkbox">
<label>
<input type="checkbox"> Checkbox
</label>
</div>
</div>
</div>
<div class="form-group">
<label for="textArea" class="col-lg-2 control-label">Textarea</label>
<div class="col-lg-10">
<textarea class="form-control" rows="3" id="textArea"></textarea>
<span class="help-block">A longer block of help text that breaks onto a new line and may extend beyond one line.</span>
</div>
</div>
<div class="form-group">
<label class="col-lg-2 control-label">Radios</label>
<div class="col-lg-10">
<div class="radio">
<label>
<input type="radio" name="optionsRadios" id="optionsRadios1" value="option1" checked="">
Option one is this
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="optionsRadios" id="optionsRadios2" value="option2">
Option two can be something else
</label>
</div>
</div>
</div>
<div class="form-group">
<label for="select" class="col-lg-2 control-label">Selects</label>
<div class="col-lg-10">
<select class="form-control" id="select">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
</select>
<br>
<select multiple="" class="form-control">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
</select>
</div>
</div>
<div class="form-group">
<div class="col-lg-10 col-lg-offset-2">
<button class="btn btn-default">Cancel</button>
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</fieldset>
<div id="source-button" class="btn btn-primary btn-xs" style="display: none;">&lt; &gt;</div></div>
</div>

186
views/post/post.html Normal file
View File

@ -0,0 +1,186 @@
<div class="container">
<form ng-submit="Post.upsert()" class="form-horizontal">
<div class="row">
<div class="page-header">
<h1>Write a Post</h1>
<h3><span ng-bind="Post.selected.abspath" ></span></h3>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="well bs-component">
<fieldset>
<div class="form-group">
<label for="inputPostTitle" class="col-lg-2 control-label">Title*</label>
<div class="col-lg-10">
<input
required="required"
ng-model="Post.selected.post.yml.title"
ng-change="Post.onChange()"
type="text"
class="form-control"
id="inputPostTitle"
placeholder="i.e. My First Post"
>
</div>
</div>
<!--
<div class="form-group">
<label for="inputPostUuid" class="col-lg-2 control-label">UUID</label>
<div class="col-lg-10">
<input
disabled
type="text"
class="form-control"
id="inputPostUuid"
placeholder="ERROR"
>
</div>
</div>
-->
<div class="form-group">
<label for="textAreaPost" class="col-lg-2 control-label">Post*</label>
<div class="col-lg-10">
<textarea
required="required"
ng-model="Post.selected.post.body"
ng-change="Post.onChange()"
class="form-control"
rows="20"
id="textAreaPost"
placeholder="i.e. I Don't Know Anything About the Gold Standard... But I Do Love Little Kittens!"
></textarea>
<span class="help-block">Put your lovely post here, in github-flavored markdown!</span>
</div>
</div>
<div class="form-group">
<label for="select" class="col-lg-2 control-label">Format*</label>
<div class="col-lg-10">
<select
required="required"
ng-model="Post.selected.format"
ng-change="Post.onChange()"
class="form-control"
id="select">
<option value="html">HTML</option>
<option selected="selected" value="md">Markdown</option>
<option disabled="disabled" value="jade">Jade (Not Implemented)</option>
</select>
<!--div class="checkbox">
<label>
<input type="checkbox"> Draft
</label>
</div-->
</div>
</div>
<div class="form-group">
<label for="textAreaDesc" class="col-lg-2 control-label">Description*
<small>(<span ng-bind="Post.selected.post.yml.description.length || 0"></span>/140)</small></label>
<div class="col-lg-10">
<textarea
required="required"
ng-change="Post.onChange()"
ng-model="Post.selected.post.yml.description"
placeholder="i.e. An alternate recipe for Peeta Mellarks's famous apple goat cheese tarts using only ingredients available in district 10"
class="form-control"
rows="2"
id="textAreaDesc"></textarea>
<span class="help-block">The description is often used by search engines as the snippit shown in search results.</span>
</div>
</div>
<div class="form-group">
<label for="textAreaYaml" class="col-lg-2 control-label">Frontmatter</label>
<div class="col-lg-10">
<textarea
required="required"
ng-change="Post.onFrontmatterChange()"
ng-model="Post.selected.post.frontmatter"
class="form-control"
rows="5"
id="textAreaYaml"></textarea>
</div>
</div>
<div class="form-group">
<label for="inputPostAbsPath" class="col-lg-2 control-label">Filepath</label>
<div class="col-lg-10">
<input
required="required"
disabled
ng-model="Post.selected.abspath"
ng-change="Post.onChange()"
type="text"
class="form-control"
id="inputPostAbsPath"
placeholder="i.e. ~/blog.me.co/posts/my-first-post.md"
>
</div>
</div>
<!--
<div class="form-group">
<label for="select" class="col-lg-2 control-label">Selects</label>
<div class="col-lg-10">
<select class="form-control" id="select">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
</select>
</div>
TODO theme -->
<!-- TODO layout -->
<!-- TODO swatch -->
<!--
<div class="form-group">
<label for="inputPostDate" class="col-lg-2 control-label">Date</label>
<div class="col-lg-10">
<input
disabled
type="text"
class="form-control"
id="inputPostDate"
ng-placeholder="i.e. {{Post.year}}-{{Post.month}}-{{Post.day}} {{Post.hour}}:{{Post.minute}}"
>
</div>
</div>
-->
<!--
<div class="form-group">
<label for="inputPostPermalink" class="col-lg-2 control-label">Permalink</label>
<div class="col-lg-10">
<input
required="required"
type="text"
class="form-control"
id="inputPostPermalink"
placeholder="i.e. /articles/my-first-post.html"
>
</div>
</div>
-->
<div class="form-group">
<div class="col-lg-10 col-lg-offset-2">
<!--button class="btn btn-default">Save Draft</button-->
<button
type="submit"
class="btn btn-primary pull-right"
>Publish</button>
</div>
</div>
</fieldset>
<div id="source-button" class="btn btn-primary btn-xs" style="display: none;">&lt; &gt;</div>
</div>
</div>
</div>
</form>
</div>

165
views/post/post.js Normal file
View File

@ -0,0 +1,165 @@
'use strict';
angular.module('myApp.post', ['ngRoute'])
.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/post', {
templateUrl: 'views/post/post.html',
controller: 'PostCtrl as Post'
});
}])
.controller('PostCtrl'
, ['$scope', '$location', '$timeout', 'Desirae'
, function ($scope, $location, $timeout, Desirae) {
var scope = this
;
function init() {
console.log('desi loading');
Desirae.meta().then(function (desi) {
scope.blogdir = desi.blogdir.path.replace(/^\/(Users|home)\/[^\/]+\//, '~/');
scope.site = desi.site;
newPost();
updateDate();
}).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;
});
scope.extensions = ['md', 'html'];
}
function newPost() {
scope.selected = {
format: 'md'
, permalink: "/article/new.md"
, uuid: window.uuid.v4()
, abspath: scope.blogdir
, post: {
yml: {
title: ""
, permalink: "/article/new.md"
, date: Desirae.toDesiDate(new Date())// "YYYY-MM-DD HH:MM pm" // TODO desirae
, updated: null
, description: ""
, categories: []
, tags: []
, theme: null
, layout: null
, swatch: null
}
}
};
scope.selected.date = scope.selected.post.yml.date;
scope.selected.post.frontmatter = window.jsyaml.dump(scope.selected.post.yml).trim();
}
scope.onChange = function () {
var post = scope.selected.post
, selected = scope.selected
;
post.yml.title = post.yml.title || '';
post.yml.description = post.yml.description || '';
if (selected.permalink === post.yml.permalink) {
selected.permalink = '/articles/' + post.yml.title.toLowerCase()
.replace(/["']/g, '')
.replace(/\W/g, '-')
.replace(/^-+/g, '')
.replace(/-+$/g, '')
.replace(/--/g, '-')
+ '.' + selected.format
;
post.yml.permalink = selected.permalink;
}
if (window.path.extname(post.yml.permalink) !== '.' + selected.format) {
post.yml.permalink = post.yml.permalink.replace(/\.\w+$/, '.' + selected.format);
}
post.frontmatter = window.jsyaml.dump(post.yml).trim();
// TODO use some sort of filepath pattern in config.yml
selected.path = window.path.join((selected.collection || 'posts'), window.path.basename(post.yml.permalink));
selected.abspath = window.path.join(scope.blogdir, selected.path);
};
scope.onFrontmatterChange = function () {
var data
;
try {
if (!scope.selected.post.frontmatter || !scope.selected.post.frontmatter.trim()) {
throw new Error('deleted frontmatter');
}
data = window.jsyaml.load(scope.selected.post.frontmatter);
scope.selected.format = data.permalink.replace(/.*\.(\w+$)/, '$1');
if (!data.permalink) {
data = scope.selected.permalink;
}
scope.selected.post.yml = data;
} catch(e) {
console.error(e);
console.error('ignoring update that created parse error');
scope.selected.post.frontmatter = window.jsyaml.dump(scope.selected.post.yml).trim();
}
};
function updateDate() {
$timeout.cancel(scope.dtlock);
scope.dtlock = $timeout(function () {
if (scope.selected && scope.selected.date === scope.selected.post.yml.date) {
scope.selected.date = scope.selected.post.yml.date = Desirae.toDesiDate(new Date());
}
scope.onChange();
updateDate();
}, 60 * 1000);
}
scope.upsert = function () {
console.log('upserted');
if (-1 === scope.extensions.indexOf(scope.selected.format)) {
window.alert('.' + scope.selected.format + ' is not a supported extension.\n\nPlease choose from: .' + scope.extensions.join(' .'));
return;
}
scope.selected.post.yml.uuid = scope.selected.uuid;
['updated', 'theme', 'layout', 'swatch'].forEach(function (key) {
if (!scope.selected.post.yml[key]) {
delete scope.selected.post.yml[key];
}
});
scope.onChange();
var files = []
;
files.push({
path: scope.selected.path
, contents:
'---\n'
+ scope.selected.post.frontmatter.trim()
+ '\n'
+ '---\n'
+ '\n'
+ scope.selected.post.body.trim()
});
console.log(files);
Desirae.putFiles(files).then(function (results) {
console.log('TODO check for error');
console.log(results);
$location.path('/build');
}).catch(function (e) {
$timeout.cancel(scope.dtlock);
console.error(scope.site);
console.error(e);
window.alert("Error Nation! :/");
throw e;
});
};
init();
}]);

200
views/site/site.html Normal file
View File

@ -0,0 +1,200 @@
<div class="container">
<div class="row">
<div class="page-header">
<h1>Site Configuration</h1>
<h3><span ng-bind="Site.blogdir"></span><button class="btn btn-primary pull-right" type="submit" form="formSiteConf">Save &amp; Continue</button></h3>
</div>
</div>
<form id="formSiteConf" class="form-horizontal" ng-submit="Site.upsert()">
<div class="row">
<div class="col-lg-12">
<div class="well bs-component">
<fieldset>
<legend>General</legend>
<div class="form-group">
<label for="inputBlogTitle" class="col-lg-3 control-label">Title</label>
<div class="col-lg-9">
<input
ng-model="Site.site.title"
required="required"
type="text" class="form-control" id="inputBlogTitle" placeholder="My Awesome Blog">
</div>
</div>
<div class="form-group">
<label for="inputBlogTagline" class="col-lg-3 control-label">Tagline</label>
<div class="col-lg-9">
<input
ng-model="Site.site.tagline"
type="text" class="form-control" id="inputBlogTagline"
placeholder="i.e. I have not failed, I've just found 10,000 ways that do not work.">
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="form-group">
<label for="inputSiteDesc" class="col-lg-3 control-label">Description
<small>(<span ng-bind="Site.site.description.length || 0"></span>/140)</small></label>
<div class="col-lg-9">
<textarea
ng-model="Site.site.description"
required="required"
class="form-control" id="inputSiteDesc" placeholder="i.e. For tips and tricks for try-hard ethical master cleanses, 3 wolf moons on Tumblr, disruptive lo-fi, preserving narwhals and eating kale chips, etc. YOLO."></textarea>
</div>
</div>
</div>
</div>
<!-- TODO -->
<!--
<div class="form-group">
<label for="inputProdUrl" class="col-lg-3 control-label">URL</label>
<div class="col-lg-9">
<input
ng-model="Site.site.url"
ng-change="Site.updateUrls()"
placeholder="i.e. https://example.com/myblog"
type="url"
class="form-control"
id="inputProdUrl">
<br/>
</div>
</div>
-->
<div class="form-group">
<label for="inputProdHost" class="col-lg-3 control-label">Base URL</label>
<div class="col-lg-9">
<input ng-model="Site.site.base_url"
required="required"
placeholder="i.e. https://example.com in https://example.com/myblog"
type="url"
class="form-control"
id="inputProdHost">
<br/>
</div>
</div>
<div class="form-group">
<label for="inputProdBase" class="col-lg-3 control-label">Base Path</label>
<div class="col-lg-9">
<input ng-model="Site.site.base_path"
required="required"
placeholder="i.e. / for blog.test.com or /blog for test.com/blog"
type="text"
class="form-control"
id="inputProdBase">
</div>
</div>
<div class="form-group">
<label for="inputProdOutput" class="col-lg-3 control-label">Output Path</label>
<div class="col-lg-9">
<input
ng-value="Site.blogdir + '/compiled'"
type="text" class="form-control" id="inputProdOutput" disabled>
</div>
</div>
</fieldset>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="well bs-component">
<fieldset>
<legend>Development</legend>
<div class="form-group">
<label for="inputDevHost" class="col-lg-3 control-label">Base URL</label>
<div class="col-lg-9">
<input
value="https://local.dear.desi:65080"
type="url"
class="form-control"
id="inputDevHost"
disabled>
<br/>
</div>
</div>
<div class="form-group">
<label for="inputDevBase" class="col-lg-3 control-label">Base Path</label>
<div class="col-lg-9">
<input
value="/compiled_dev"
placeholder="i.e. /blog in https://example.com/blog"
type="text" class="form-control" id="inputDevBase"
disabled>
</div>
</div>
<div class="form-group">
<label for="inputDevOutput" class="col-lg-3 control-label">Output Path</label>
<div class="col-lg-9">
<input
ng-value="Site.blogdir + '/compiled_dev'"
type="text"
class="form-control"
id="inputDevOutput"
disabled>
</div>
</div>
</fieldset>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="well bs-component">
<fieldset>
<legend>Plugins</legend>
<div class="form-group">
<label for="inputGoogleAnalyticsId" class="col-lg-3 control-label">Google Analytics ID</label>
<div class="col-lg-9">
<input
ng-model="Site.site.google_analytics_tracking_id"
placeholder="i.e. UA-XXXYYYZZ-1"
type="text"
class="form-control"
id="inputGoogleAnalyticsId"
>
<br/>
<small>Found in the Admin section of Google Analytics</small>
</div>
</div>
<div class="form-group">
<label for="inputDisqusShortname" class="col-lg-3 control-label">Disqus Shortname</label>
<div class="col-lg-9">
<input
ng-model="Site.site.disqus_shortname"
placeholder="i.e. johndoe-blog"
type="text" class="form-control" id="inputDisqusShortname"
>
<br/>
<small>Found under Admin &gt; Settings in Disqus</small>
</div>
</div>
</fieldset>
</div>
</div>
</div>
<button class="btn btn-primary pull-right" type="submit">Save &amp; Continue</button>
</form>
</div>

49
views/site/site.js Normal file
View File

@ -0,0 +1,49 @@
'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', '$location', 'Desirae', function ($scope, $location, Desirae) {
var scope = this
;
function init() {
console.log('desi loading');
Desirae.meta().then(function (desi) {
scope.blogdir = desi.blogdir.path.replace(/^\/(Users|home)\/[^\/]+\//, '~/');
scope.site = desi.site;
}).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;
});
}
scope.upsert = function () {
var files = []
;
files.push({ path: 'site.yml', contents: scope.site });
console.log(files);
Desirae.putFiles(files).then(function (results) {
console.log('TODO check for error');
console.log(results);
$location.path('/post');
}).catch(function (e) {
console.error(scope.site);
console.error(e);
window.alert("Error Nation! :/");
throw e;
});
};
init();
}]);

16
views/site/view1_test.js Normal file
View File

@ -0,0 +1,16 @@
'use strict';
describe('myApp.view1 module', function() {
beforeEach(module('myApp.view1'));
describe('view1 controller', function(){
it('should ....', inject(function($controller) {
//spec body
var view1Ctrl = $controller('View1Ctrl');
expect(view1Ctrl).toBeDefined();
}));
});
});