merging with master

This commit is contained in:
Tim Griesser 2013-05-29 18:26:10 -04:00
commit 091790a525
26 changed files with 1005 additions and 260 deletions

View File

@ -1,6 +1,6 @@
# [Ghost v0.1.1](https://github.com/TryGhost/Ghost) [![Build Status](https://magnum.travis-ci.com/TryGhost/Ghost.png?token=hMRLUurj2P3wzBdscyQs&branch=master)](https://magnum.travis-ci.com/TryGhost/Ghost)
Ghost is a free, open, simple blogging platform that's available to anyone who wants to use it. Created and maintained by [John O'Nolan](http://twitter.com/JohnONolan) + [Hannah Wolfe](http://twitter.com/ErisDS) + an amazing group of [contributors](https://github.com/TryGhost/Ghost/pulse).
Ghost is a free, open, simple blogging platform that's available to anyone who wants to use it. Lovingly created and maintained by [John O'Nolan](http://twitter.com/JohnONolan) + [Hannah Wolfe](http://twitter.com/ErisDS) + an amazing group of [contributors](https://github.com/TryGhost/Ghost/contributors).
Visit the project's home page at [http://tryghost.org](http://tryghost.org)!

114
app.js
View File

@ -6,17 +6,23 @@
// Module dependencies.
var express = require('express'),
when = require('when'),
_ = require('underscore'),
errors = require('./core/shared/errorHandling'),
admin = require('./core/admin/controllers'),
frontend = require('./core/frontend/controllers'),
api = require('./core/shared/api'),
flash = require('connect-flash'),
Ghost = require('./core/ghost'),
I18n = require('./core/lang/i18n'),
filters = require('./core/frontend/filters'),
helpers = require('./core/frontend/helpers'),
// ## Variables
auth,
authAPI,
ghostLocals,
loading = when.defer(),
/**
* Create new Ghost object
@ -24,6 +30,7 @@
*/
ghost = new Ghost();
ghost.app().configure('development', function () {
ghost.app().use(express.favicon(__dirname + '/content/images/favicon.ico'));
ghost.app().use(express.errorHandler({ dumpExceptions: true, showStack: true }));
@ -66,53 +73,76 @@
next();
};
helpers.loadCoreHelpers(ghost);
/**
* API routes..
* @todo auth should be public auth not user auth
* Expose the standard locals that every external page should have available;
* path, navItems and ghostGlobals
*/
ghost.app().get('/api/v0.1/posts', authAPI, api.requestHandler(api.posts.browse));
ghost.app().post('/api/v0.1/posts', authAPI, api.requestHandler(api.posts.add));
ghost.app().get('/api/v0.1/posts/:id', authAPI, api.requestHandler(api.posts.read));
ghost.app().put('/api/v0.1/posts/:id', authAPI, api.requestHandler(api.posts.edit));
ghost.app().del('/api/v0.1/posts/:id', authAPI, api.requestHandler(api.posts.destroy));
ghost.app().get('/api/v0.1/settings', authAPI, api.requestHandler(api.settings.browse));
ghost.app().get('/api/v0.1/settings/:key', authAPI, api.requestHandler(api.settings.read));
ghost.app().put('/api/v0.1/settings', authAPI, api.requestHandler(api.settings.edit));
ghostLocals = function(req, res, next) {
ghost.doFilter('ghostNavItems', {path: req.path, navItems: []}, function(navData) {
// Make sure we have a locals value.
res.locals = res.locals || {};
/**
* Admin routes..
* @todo put these somewhere in admin
*/
// Extend it with nav data and ghostGlobals
_.extend(res.locals, navData, {
ghostGlobals: ghost.globals()
});
ghost.app().get(/^\/logout\/?$/, admin.logout);
ghost.app().get('/ghost/login/', admin.login);
ghost.app().get('/ghost/register/', admin.register);
ghost.app().post('/ghost/login/', admin.auth);
ghost.app().post('/ghost/register/', admin.doRegister);
ghost.app().get('/ghost/editor/:id', auth, admin.editor);
ghost.app().get('/ghost/editor', auth, admin.editor);
ghost.app().get('/ghost/blog', auth, admin.blog);
ghost.app().get('/ghost/settings', auth, admin.settings);
ghost.app().get('/ghost/debug', auth, admin.debug.index);
ghost.app().get('/ghost/debug/db/delete/', auth, admin.debug.dbdelete);
ghost.app().get('/ghost/debug/db/populate/', auth, admin.debug.dbpopulate);
ghost.app().get(/^\/(ghost$|(ghost-admin|admin|wp-admin|dashboard|login)\/?)/, auth, function (req, res) {
res.redirect('/ghost/');
});
ghost.app().get('/ghost/', auth, admin.index);
next();
});
};
/**
* Frontend routes..
* @todo dynamic routing, homepage generator, filters ETC ETC
*/
ghost.app().get('/:slug', frontend.single);
ghost.app().get('/', frontend.homepage);
// Expose the promise we will resolve after our pre-loading
ghost.loaded = loading.promise;
when.all([filters.loadCoreFilters(ghost), helpers.loadCoreHelpers(ghost)]).then(function () {
ghost.app().listen(3333, function () {
console.log("Express server listening on port " + 3333);
});
/**
* API routes..
* @todo auth should be public auth not user auth
*/
ghost.app().get('/api/v0.1/posts', authAPI, api.requestHandler(api.posts.browse));
ghost.app().post('/api/v0.1/posts', authAPI, api.requestHandler(api.posts.add));
ghost.app().get('/api/v0.1/posts/:id', authAPI, api.requestHandler(api.posts.read));
ghost.app().put('/api/v0.1/posts/:id', authAPI, api.requestHandler(api.posts.edit));
ghost.app().del('/api/v0.1/posts/:id', authAPI, api.requestHandler(api.posts.destroy));
ghost.app().get('/api/v0.1/settings', authAPI, api.requestHandler(api.settings.browse));
ghost.app().get('/api/v0.1/settings/:key', authAPI, api.requestHandler(api.settings.read));
ghost.app().put('/api/v0.1/settings', authAPI, api.requestHandler(api.settings.edit));
/**
* Admin routes..
* @todo put these somewhere in admin
*/
ghost.app().get(/^\/logout\/?$/, admin.logout);
ghost.app().get('/ghost/login/', admin.login);
ghost.app().get('/ghost/register/', admin.register);
ghost.app().post('/ghost/login/', admin.auth);
ghost.app().post('/ghost/register/', admin.doRegister);
ghost.app().get('/ghost/editor/:id', auth, admin.editor);
ghost.app().get('/ghost/editor', auth, admin.editor);
ghost.app().get('/ghost/content', auth, admin.content);
ghost.app().get('/ghost/settings', auth, admin.settings);
ghost.app().get('/ghost/debug', auth, admin.debug.index);
ghost.app().get('/ghost/debug/db/delete/', auth, admin.debug.dbdelete);
ghost.app().get('/ghost/debug/db/populate/', auth, admin.debug.dbpopulate);
ghost.app().get(/^\/(ghost$|(ghost-admin|admin|wp-admin|dashboard|login)\/?)/, auth, function (req, res) {
res.redirect('/ghost/');
});
ghost.app().get('/ghost/', auth, admin.index);
/**
* Frontend routes..
* @todo dynamic routing, homepage generator, filters ETC ETC
*/
ghost.app().get('/:slug', ghostLocals, frontend.single);
ghost.app().get('/', ghostLocals, frontend.homepage);
ghost.app().listen(3333, function () {
console.log("Express server listening on port " + 3333);
// Let everyone know we have finished loading
loading.resolve();
});
}, errors.logAndThrowError);
}());

View File

@ -68,7 +68,8 @@
connection: {
filename: './core/shared/data/testdb.db'
},
debug: true
debug: false
// debug: true
},
staging: {},
@ -86,6 +87,17 @@
};
/**
* @property {Array} nav
*/
config.nav = [{
title: 'Home',
url: '/'
}, {
title: 'Admin',
url: '/ghost'
}];
/**
* @property {Object} exports
*/

View File

@ -0,0 +1,27 @@
(function(){
"use strict";
/**
* Because I didn't want to write over FancyFirstChar
*/
var ExampleFilter;
var ExampleFilter = function(ghost){
this.ghost = function() {
return ghost;
}
}
ExampleFilter.prototype.init = function() {
this.ghost().registerFilter('messWithAdmin', function(adminNavbar){
console.log('adminnavbar settings run');
delete adminNavbar.add;
return adminNavbar;
});
};
module.exports = ExampleFilter;
}());

View File

@ -9,6 +9,7 @@
return ghost;
};
};
FancyFirstChar.prototype.init = function () {
this.ghost().registerFilter('prePostsRender', function (posts) {
var post,
@ -41,5 +42,7 @@
FancyFirstChar.prototype.activate = function () {};
FancyFirstChar.prototype.deactivate = function () {};
module.exports = FancyFirstChar;
}());

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

View File

@ -149,10 +149,83 @@
}
});
// ## Shortcuts
// Zen writing mode
shortcut.add("Alt+Shift+Z", function () {
$('body').toggleClass('zen');
});
var MarkdownShortcuts = [
{
'key': 'Ctrl+B',
'style': 'bold'
},
{
'key': 'Meta+B',
'style': 'bold'
},
{
'key': 'Ctrl+I',
'style': 'italic'
},
{
'key': 'Meta+I',
'style': 'italic'
},
{
'key': 'Ctrl+Alt+U',
'style': 'strike'
},
{
'key': 'Ctrl+Shift+K',
'style': 'code'
},
{
'key': 'Alt+1',
'style': 'h1'
},
{
'key': 'Alt+2',
'style': 'h2'
},
{
'key': 'Alt+3',
'style': 'h3'
},
{
'key': 'Alt+4',
'style': 'h4'
},
{
'key': 'Alt+5',
'style': 'h5'
},
{
'key': 'Alt+6',
'style': 'h6'
},
{
'key': 'Ctrl+Shift+L',
'style': 'link'
},
{
'key': 'Ctrl+Shift+I',
'style': 'image'
},
{
'key': 'Ctrl+Q',
'style': 'blockquote'
},
{
'key': 'Ctrl+Shift+1',
'style': 'currentdate'
}
];
$.each(MarkdownShortcuts, function (index, short) {
shortcut.add(short.key, function () {
return editor.addMarkdown({style: short.style});
});
});
});
}(jQuery, Showdown, CodeMirror, shortcut));

View File

@ -0,0 +1,53 @@
/*global console, jQuery, CodeMirror*/
// # Surrounds given text with Markdown syntax
(function ($) {
"use strict";
var Markdown = {
init : function (options, elem) {
var self = this;
self.elem = elem;
self.style = (typeof options === 'string') ? options : options.style;
self.options = $.extend({}, CodeMirror.prototype.addMarkdown.options, options);
self.replace();
},
replace: function () {
var text = this.elem.getSelection(), md;
if (this.options.syntax[this.style]) {
md = this.options.syntax[this.style].replace('$1', text);
this.elem.replaceSelection(md);
} else {
console.log("Invalid style.");
}
}
};
CodeMirror.prototype.addMarkdown = function (options) {
var markdown = Object.create(Markdown);
markdown.init(options, this);
};
CodeMirror.prototype.addMarkdown.options = {
style: null,
syntax: {
bold: "**$1**",
italic: "_$1_",
strike: "~~$1~~",
code: "`$1`",
h1: "\n# $1\n",
h2: "\n## $1\n",
h3: "\n### $1\n",
h4: "\n#### $1\n",
h5: "\n##### $1\n",
h6: "\n###### $1\n",
link: "[$1](http://)",
image: "!image[$1](http://)",
blockquote: "> $1",
currentDate: new Date().toLocaleString()
}
};
}(jQuery));

View File

@ -4,141 +4,381 @@
*
*/
.widget {
width:341px;
height:300px;
background:#fff;
box-shadow: $shadow;
float:left;
margin:0 15px 15px 0;
display:none;
/* ==========================================================================
Widget Base
========================================================================== */
%widget, .widget {
width: $widget-base-width;
height: $widget-base-height;
float:left;
position:relative;
margin:0 15px 15px 0;
display: none;
background-color:#fff;
box-shadow: $shadow;
.widget-content {
@include box-sizing(border-box);
margin-bottom: 40px;
padding: 20px;
}
.widget-footer, .widget-header {
@include box-sizing(border-box);
width: 100%;
height: 40px;
font-size: 1.2em;
color: #cecbc7;
border-top: 1px solid #EDECE4;
.widget-title {
display: inline-block;
padding-top: 7px;
padding-left: 15px;
vertical-align: middle;
text-transform: uppercase;
}
}
.widget-settings-toggle {
@include box-sizing(border-box);
display: block;
height: 39px;
width: 46px;
float: right;
padding: 7px 14px;
border-left: 1px solid #EDECE4;
cursor: pointer;
@include icon($i-settings, 1em);
}
.widget-footer {
position: absolute;
bottom: 0;
}
}
.none {
margin-right:0;
/* ==========================================================================
Widget Sizes
========================================================================== */
.widget-1x2 {
height: $widget-base-height * 2;
}
.time {
background-image: url(../img/dash/Time@2x.png);
background-size: 341px 300px;
.widget-2x2 {
width: $widget-base-width * 2;
height: $widget-base-height * 2;
}
.image {
max-width: 100%;
width: 682px + 15px;
background-image: url(../img/dash/Image@2x.png);
background-size: 697px 300px;
.widget-2x1 {
width: $widget-base-width * 2;
}
.stats {
max-width: 100%;
width: 682px + 15px;
height: 615px;
background-image: url(../img/dash/Stats@2x.png);
background-size: 697px 615px;
/* ==========================================================================
Widget Variations
========================================================================== */
%widget-number, .widget-number {
@extend %widget;
.widget-content {
.info {
margin-top: 30px;
.count {
display: block;
font-size: 5em;
line-height: 1em;
font-weight: 400;
color: #4a4a4a;
}
.sub {
font-size: 2em;
color: #9b9b9b;
mark {
background-color: transparent;
&.up {
color: $green;
}
&.down {
color: $red;
}
} // mark
} // .sub
} // .info
} // .widget-content
&.widget-2x2 {
.widget-content {
.info {
margin-top: 100px;
.count {
font-size: 9em;
}
.sub {
font-size: 2.5em;
}
} // .info
} // .widget-content
} // .widget-2x2
} // %widget-number, .widget-number
//For the settings panel
.widget-settings {
@extend %widget;
background-color: #2d3032;
.widget-header {
height: 40px;
border-top: none;
border-bottom: 1px solid #4a4a4a;
color: #7E878B;
}
.widget-content {
padding: 0;
}
label {
width:100%;
height: 40px;
display: block;
border-bottom: 1px solid #4a4a4a;
font-size: 1.2em;
}
.title {
@include box-sizing(border-box);
display: inline-block;
width: 100px;
height: 100%;
padding: 8px;
color: #E3EDF2;
text-transform: uppercase;
text-align: right;
}
input[type="text"] {
@include box-sizing(border-box);
height: 100%;
padding: 8px;
color: #E3EDF2;
text-transform: none;
background: none;
border: none;
border-left: 1px solid #4a4a4a;
}
.widget-footer, .widget-header {
border-color: #4a4a4a;
}
.widget-settings-toggle {
border-color: #4a4a4a;
&.close {
@include icon($i-x, 1em);
}
&.done {
background-color: #A0B95D;
color: #ffffff;
@include icon($i-check, 1em);
}
}
} // .widget-settings
.none {
margin-right:0;
}
.facebook {
background-image: url(../img/dash/Facebook@2x.png);
background-size: 341px 300px;
/* ==========================================================================
Individual Widgets
========================================================================== */
.widget-time {
@extend %widget;
.summary {
margin-bottom: 30px;
font-size: 1.4em;
color: #cecbc7;
.day {
float: left;
}
.weather {
float: right;
// TODO: icon for weather
}
}
time {
margin-top: 30px;
.clock {
display: block;
font-size: 5em;
line-height: 1em;
font-weight: 400;
color: #4a4a4a;
}
.date {
font-size: 2em;
color: #9b9b9b;
}
}
} // .widget-time
.widget-image {
@extend %widget;
.widget-content {
height: 100%;
background-image: url(../img/dash/Image@2x.png);
background-size: 100% 100%;
}
.widget-footer {
margin-top: -40px;
opacity: 0;
background: #ffffff;
@include transition(opacity 200ms linear);
}
&:hover {
.widget-footer {
opacity: 1;
}
}
} // .widget-image
.widget-stats {
@extend %widget-number;
}
.gplus {
background-image: url(../img/dash/GooglePlus@2x.png);
background-size: 341px 300px;
.widget-facebook {
@extend %widget-number;
.info {
.faces {
display: block;
width: 100%;
height: 30px;
margin-top: 25px;
background-image: url("../img/dash/Facebook_Images@2x.png");
background-size: 100% 100%;
}
}
}
.twitter {
background-image: url(../img/dash/Twitter@2x.png);
background-size: 341px 300px;
.widget-gplus {
@extend %widget-number;
}
.campaignmonitor {
background-image: url(../img/dash/CampaignMonitor@2x.png);
background-size: 341px 300px;
.widget-twitter {
@extend %widget;
}
.posts {
background-image: url(../img/dash/PostsStats@2x.png);
background-size: 341px 300px;
position: relative;
.widget-campaignmonitor {
@extend %widget-number;
}
.chart {
box-sizing: border-box;
width: 250px;
height: 250px;
margin: 25px auto 0 auto;
background: #242628;
border: #efefef 54px solid;
border-radius: 500px;
}
#poststats {
.widget-posts {
@extend %widget;
position: relative;
top:-54px;
left: -54px;
.chart {
@include box-sizing(border-box);
width: 250px;
height: 250px;
position: relative;
z-index: 1;
margin: 0 auto;
background: #242628;
border: #efefef 54px solid;
border-radius: 250px;
#poststats {
position: relative;
top:-54px;
left: -54px;
}
.data {
position: absolute;
top: 5px;
color: $midgrey;
font-size: 13px;
list-style: none;
.ready {
font-size: 18px;
vertical-align: -5%;
margin-right: 5px;
color: $green;
}
.pending {
font-size: 18px;
vertical-align: -5%;
margin-right: 5px;
color: #f9e15d;
}
.draft {
font-size: 18px;
vertical-align: -5%;
margin-right: 5px;
color: $red;
}
}
}
}
.data {
position: absolute;
top: 87px;
color: $midgrey;
font-size: 13px;
list-style: none;
}
.ready {
font-size: 18px;
vertical-align: -5%;
margin-right: 5px;
color: $green;
}
.pending {
font-size: 18px;
vertical-align: -5%;
margin-right: 5px;
color: #f9e15d;
}
.draft {
font-size: 18px;
vertical-align: -5%;
margin-right: 5px;
color: $red;
}
/*
.dashboard-controls {
.dashboard-controls {
@extend %box;
padding:0 15px;
.text {
.text {
padding:12px 0;
}
}
.controls-nav {
.controls-nav {
float:left;
margin-left:20px;
ul {
ul {
border-left: $lightgrey 1px solid;
li {
li {
margin: 0;
border-right: 1px solid $lightgrey;
a {
a {
padding: 12px 15px;
color: $grey;
span {
span {
color: $darkgrey;
}
&:hover {
&:hover {
color: $darkgrey;
text-decoration: none;
}
@ -147,18 +387,18 @@
}
}
.widget-stats {
span {
.widget-stats {
span {
display: block;
font-size: 1.6em;
line-height: 1.2em;
color: $grey;
margin-bottom: 15px;
strong {
strong {
font-size: 1.2em;
}
}
span:first-child {
span:first-child {
font-size: 5.4em;
line-height: 1.4em;
color: #000;
@ -166,16 +406,16 @@
}
}
@media only screen and (min-width: 1200px) {
.span4 .vert2 {
.widget-stats {
span {
@media only screen and (min-width: 1200px) {
.span4 .vert2 {
.widget-stats {
span {
font-size: 2.6em;
strong {
strong {
font-size: 1.2em;
}
}
span:first-child {
span:first-child {
font-size: 12.4em;
}
}
@ -183,30 +423,30 @@
}
// Time & Date Box
.time-date {
.time {
.time-date {
.time {
font-size: 7.4em;
line-height: 0.7em;
border-bottom: 1px solid $lightgrey;
span {
span {
font-size: 0.2em;
color: $grey;
text-transform: uppercase;
font-style: normal;
}
@media only screen and (min-width: 1400px) {
span {
@media only screen and (min-width: 1400px) {
span {
font-size: 0.3em;
}
}
}
.date {
.date {
font-size: 2.2em;
line-height: 1em;
font-weight: bold;
color: #000;
padding: 15px 0;
span {
span {
font-size: 0.7em;
font-weight: normal;
display: block;
@ -217,23 +457,23 @@
}
// Post Statuses Box
.post-statuses {
.status-levels {
.post-statuses {
.status-levels {
width: 30%;
div {
div {
text-indent: -9999px;
}
}
.status-text {
.status-text {
width: 70%;
text-transform: uppercase;
font-size: 1.2em;
color: $grey;
div {
div {
background: none;
padding: 15px 0;
}
strong {
strong {
font-size: 1.6em;
width: 60px;
padding-right: 5px;
@ -241,38 +481,38 @@
display: inline-block;
}
}
.scheduled {
.scheduled {
background: $green;
strong {
strong {
color: $green;
}
}
.pending {
.pending {
background: #fcd039;
strong {
strong {
color: #fcd039;
}
}
.draft {
.draft {
background: $red;
strong {
strong {
color: $red;
}
}
}
.todays-traffic {
ul {
li {
.todays-traffic {
ul {
li {
position: relative;
padding: 10px;
margin-bottom: 1px;
div {
div {
position: relative;
z-index: 99;
}
}
li:before {
li:before {
content: '';
position: absolute;
height: 34px;
@ -280,84 +520,84 @@
left: 0;
z-index: 20;
}
li:nth-child(1):before {
li:nth-child(1):before {
background: $blue;
width: 80%;
}
li:nth-child(2):before {
li:nth-child(2):before {
background: lighten($blue, 3%);
width: 60%;
}
li:nth-child(3):before {
li:nth-child(3):before {
background: lighten($blue, 6%);
width: 40%;
}
li:nth-child(4):before {
li:nth-child(4):before {
background: lighten($blue, 10%);
width: 20%;
}
}
}
.table {
.table {
width: 100%;
display: block;
margin-bottom: 10px;
thead, tbody, tr {
thead, tbody, tr {
display: block;
}
@media only screen and (min-width: 400px) {
thead {
@media only screen and (min-width: 400px) {
thead {
display: none;
}
}
tbody {
tr {
tbody {
tr {
background: $lightbrown;
margin-top: 5px;
display: block;
position: relative;
&:first-child {
&:first-child {
margin-top: 0;
}
}
@media only screen and (min-width: 1200px) {
tr {
@media only screen and (min-width: 1200px) {
tr {
padding: 0 10px 0 40px;
margin-top: 15px;
}
}
td {
td {
padding: 10px;
text-align: right;
color: $grey;
strong {
strong {
color: #000;
}
span {
span {
display: none;
}
@media only screen and (min-width: 500px) {
span {
@media only screen and (min-width: 500px) {
span {
display: inline;
}
}
.callout {
.callout {
color: $green;
}
&:first-child {
&:first-child {
text-align: left;
}
}
}
.user-img {
.user-img {
position: absolute;
top: 0;
left: 0;
display: none;
}
@media only screen and (min-width: 1200px) {
.user-img {
@media only screen and (min-width: 1200px) {
.user-img {
display: block;
}
}

View File

@ -182,4 +182,11 @@ $green: #9FBB58;
text-decoration: none;
}
}
}
}
/* =============================================================================
Widgets
============================================================================= */
$widget-base-height: 300px;
$widget-base-width: 340px;

View File

@ -17,32 +17,36 @@
name: 'Dashboard',
navClass: 'dashboard',
key: 'admin.navbar.dashboard',
defaultString: 'dashboard',
path: ''
// defaultString: 'dashboard',
path: '/'
},
blog: {
content: {
name: 'Content',
navClass: 'content',
key: 'admin.navbar.blog',
defaultString: 'blog',
path: '/blog'
key: 'admin.navbar.content',
// defaultString: 'content',
path: '/content/'
},
add: {
name: 'New Post',
navClass: 'editor',
key: 'admin.navbar.editor',
defaultString: 'editor',
path: '/editor'
// defaultString: 'editor',
path: '/editor/'
},
settings: {
name: 'Settings',
navClass: 'settings',
key: 'admin.navbar.settings',
defaultString: 'settings',
path: '/settings'
// defaultString: 'settings',
path: '/settings/'
}
};
ghost.doFilter('messWithAdmin', adminNavbar, function() {
console.log('the dofilter hook called in /core/admin/controllers/index.js');
});
// TODO - make this a util or helper
function setSelected(list, name) {
_.each(list, function (item, key) {
@ -64,9 +68,9 @@
console.log('user found: ', user);
req.session.user = "ghostadmin";
res.redirect(req.query.redirect || '/ghost/');
}, function (err) {
}, function (error) {
// Do something here to signal the reason for an error
console.log(err.stack);
req.flash('error', error.message);
res.redirect('/ghost/login/');
});
},
@ -78,16 +82,19 @@
});
},
'doRegister': function (req, res) {
// console.log(req.body);
if (req.body.email_address !== '' && req.body.password.length > 5) {
var email = req.body.email_address,
password = req.body.password;
if (email !== '' && password.length > 5) {
api.users.add({
email_address: req.body.email_address,
password: req.body.password
email_address: email,
password: password
}).then(function (user) {
console.log('user added', user);
res.redirect('/ghost/login/');
}, function (error) {
console.log('there was an error', error);
req.flash('error', error.message);
res.redirect('/ghost/register/');
});
} else {
req.flash('error', "The password is too short. Have at least 6 characters in there");
@ -111,7 +118,7 @@
.then(function (post) {
res.render('editor', {
bodyClass: 'editor',
adminNav: setSelected(adminNavbar, 'blog'),
adminNav: setSelected(adminNavbar, 'content'),
title: post.get('title'),
content: post.get('content')
});
@ -123,12 +130,12 @@
});
}
},
'blog': function (req, res) {
'content': function (req, res) {
api.posts.browse()
.then(function (posts) {
res.render('blog', {
res.render('content', {
bodyClass: 'manage',
adminNav: setSelected(adminNavbar, 'blog'),
adminNav: setSelected(adminNavbar, 'content'),
posts: posts.toJSON()
});
});

View File

@ -4,9 +4,9 @@
$(document).ready(function(){
//$('body').click(function(){
$('.time').fadeIn(1000);
$('.image').delay(300).fadeIn(1000);
$('.posts').delay(600).fadeIn(900, function(){
$('.widget-time').fadeIn(1000);
$('.widget-image').delay(300).fadeIn(1000);
$('.widget-posts').delay(600).fadeIn(900, function(){
var ctx = $("#poststats").get(0).getContext("2d");
var data = [
@ -32,32 +32,133 @@
});
$('.stats').delay(800).fadeIn(1000);
$('.facebook').delay(1000).fadeIn(1000);
$('.gplus').delay(1200).fadeIn(1000);
$('.twitter').delay(1300).fadeIn(1000);
$('.campaignmonitor').delay(1400).fadeIn(1000);
$('.widget-stats').delay(800).fadeIn(1000);
$('.widget-facebook').delay(1000).fadeIn(1000);
$('.widget-gplus').delay(1200).fadeIn(1000);
$('.widget-twitter').delay(1300).fadeIn(1000);
$('.widget-campaignmonitor').delay(1400).fadeIn(1000);
//});
});
</script>
{{/contentFor}}
{{!< default}}
<div class="time widget"></div>
<div class="image widget"></div>
<div class="posts widget none">
<div class="chart">
<canvas id="poststats" width="250" height="250"></canvas>
<ul class="data">
<li><span class="ready">9</span> Ready</li>
<li><span class="pending">4</span> Pending</li>
<li><span class="draft">1</span> Draft</li>
</ul>
</div>
<article class="widget-time">
<section class="widget-content">
<header class="summary clearfix">
<span class="day">Today</span>
<span class="weather">12°</span>
</header>
<time>
<span class="clock">12:42pm</span>
<span class="date">Monday / March 5 / 2013</span>
</time>
</section>
<footer class="widget-footer">
<span class="widget-title">Linz, Austria</span>
<div class="widget-settings-toggle cog"></div>
</footer>
</article>
<div class="widget-image widget-2x1">
<section class="widget-content">
</section>
<footer class="widget-footer">
<span class="widget-title">Ghost</span>
<div class="widget-settings-toggle cog"></div>
</footer>
</div>
<div class="stats widget"></div>
<div class="facebook widget"></div>
<div class="gplus widget none"></div>
<div class="twitter widget"></div>
<div class="campaignmonitor widget none"></div>
<div class="widget-posts none">
<section class="widget-content">
<div class="chart">
<canvas id="poststats" width="250" height="250"></canvas>
<ul class="data">
<li><span class="ready">9</span> Ready</li>
<li><span class="pending">4</span> Pending</li>
<li><span class="draft">1</span> Draft</li>
</ul>
</div>
</section>
<footer class="widget-footer">
<span class="widget-title">Upcoming Posts</span>
<div class="widget-settings-toggle"></div>
</footer>
</div>
<div class="widget-stats widget-2x2">
<section class="widget-content">
<div class="info">
<span class="count">293,042</span>
<span class="sub"><mark class="up">+35%</mark> in the last 30 days</span>
</div>
</section>
<footer class="widget-footer">
<span class="widget-title">Google Analytics Unique Visitors</span>
<div class="widget-settings-toggle"></div>
</footer>
</div>
<div class="widget-facebook">
<section class="widget-content">
<div class="info">
<span class="count">9,392</span>
<span class="sub"><mark class="down">-39</mark> likes today</span>
<span class="faces"></span>
</div>
</section>
<footer class="widget-footer">
<span class="widget-title">Facebook</span>
<div class="widget-settings-toggle"></div>
</footer>
</div>
<div class="widget-gplus none">
<section class="widget-content">
<div class="info">
<span class="count">4,103</span>
<span class="sub">have you in circles</span>
</div>
</section>
<footer class="widget-footer">
<span class="widget-title">Google Plus</span>
<div class="widget-settings-toggle"></div>
</footer>
</div>
<div class="widget-twitter widget-settings">
<header class="widget-header">
<span class="widget-title">Twitter Settings</span>
<div class="widget-settings-toggle close"></div>
</header>
<section class="widget-content">
<label>
<span class="title">Account</span> <input type="text" value="@JohnONolan"/>
</label>
<label>
<span class="title">Display</span> <input type="text" value="Latest Tweets"/>
</label>
<label>
<span class="title">Quantity</span> <input type="text" value="6"/>
</label>
<label>
<span class="title">Account</span> <input type="text" value="Account"/>
</label>
</section>
<footer class="widget-footer">
<div class="widget-settings-toggle done"></div>
</footer>
</div>
<div class="widget-campaignmonitor none">
<section class="widget-content">
<div class="info">
<span class="count">3,502</span>
<span class="sub"><mark class="up">+35</mark> subscribers this week</span>
</div>
</section>
<footer class="widget-footer">
<span class="widget-title">Campaign Monitor</span>
<div class="widget-settings-toggle"></div>
</footer>
</div>

View File

@ -4,6 +4,7 @@
<script src="/core/admin/assets/lib/showdown/showdown.js"></script>
<script src="/core/admin/assets/lib/showdown/extensions/ghostdown.js"></script>
<script src="/core/admin/assets/lib/shortcuts.js"></script>
<script src="/core/admin/assets/js/markdown-actions.js"></script>
<script src="/core/admin/assets/js/editor.js"></script>
<script src="/core/admin/assets/js/tagui.js"></script>
{{/contentFor}}

View File

@ -1,3 +1,4 @@
<header id="global-header" class="navbar">
<a id="ghost" href="#" data-off-canvas="left"><span class="hidden">Ghost</span></a>
<nav id="global-nav" role="navigation">
@ -22,4 +23,4 @@
</li>
</ul>
</nav>
</header>
</header>

View File

@ -16,14 +16,14 @@
'homepage': function (req, res) {
api.posts.browse().then(function (posts) {
ghost.doFilter('prePostsRender', posts.toJSON(), function (posts) {
res.render('index', {posts: posts, ghostGlobals: ghost.globalConfig});
res.render('index', {posts: posts, ghostGlobals: ghost.globalConfig, navItems: res.locals.navItems});
});
});
},
'single': function (req, res) {
api.posts.read({'slug': req.params.slug}).then(function (post) {
ghost.doFilter('prePostsRender', post.toJSON(), function (post) {
res.render('single', {post: post, ghostGlobals: ghost.globalConfig});
res.render('single', {post: post, ghostGlobals: ghost.globalConfig, navItems: res.locals.navItems});
});
});
}

View File

@ -0,0 +1,30 @@
(function () {
"use strict";
var _ = require('underscore'),
coreFilters;
coreFilters = function (ghost) {
ghost.registerFilter('ghostNavItems', function (args) {
var selectedItem;
// Set the nav items based on the config
args.navItems = ghost.config().nav;
// Mark the current selected Item
selectedItem = _.find(args.navItems, function (item) {
// TODO: Better selection determination?
return item.url === args.path;
});
if (selectedItem) {
selectedItem.active = true;
}
return args;
});
};
module.exports.loadCoreFilters = coreFilters;
}());

View File

@ -0,0 +1,53 @@
(function () {
"use strict";
var fs = require('fs'),
path = require('path'),
_ = require('underscore'),
handlebars = require('express-hbs').handlebars,
nodefn = require('when/node/function'),
GhostNavHelper;
GhostNavHelper = function (navTemplate) {
// Bind the context for our methods.
_.bindAll(this, 'compileTemplate', 'renderNavItems');
if (_.isFunction(navTemplate)) {
this.navTemplateFunc = navTemplate;
} else {
this.navTemplatePath = navTemplate;
}
};
GhostNavHelper.prototype.compileTemplate = function (templatePath) {
var self = this;
// Allow people to overwrite the navTemplatePath
templatePath = templatePath || this.navTemplatePath;
return nodefn.call(fs.readFile, templatePath).then(function(navTemplateContents) {
// TODO: Can handlebars compile async?
self.navTemplateFunc = handlebars.compile(navTemplateContents.toString());
});
};
GhostNavHelper.prototype.renderNavItems = function (navItems) {
var output;
output = this.navTemplateFunc({links: navItems});
return output;
};
// A static helper method for registering with ghost
GhostNavHelper.registerWithGhost = function(ghost) {
var templatePath = path.join(ghost.paths().frontendViews, 'nav.hbs'),
ghostNavHelper = new GhostNavHelper(templatePath);
return ghostNavHelper.compileTemplate().then(function() {
ghost.registerThemeHelper("ghostNav", ghostNavHelper.renderNavItems);
});
};
module.exports = GhostNavHelper;
}());

View File

@ -3,10 +3,11 @@
var _ = require('underscore'),
moment = require('moment'),
when = require('when'),
navHelper = require('./ghostNav'),
coreHelpers;
coreHelpers = function (ghost) {
/**
* [ description]
* @todo ghost core helpers + a way for themes to register them
@ -39,6 +40,10 @@
return output;
});
return when.all([
// Just one async helper for now, but could be more in the future
navHelper.registerWithGhost(ghost)
]);
};

View File

@ -0,0 +1,7 @@
<nav id="site-navigation" role="navigation">
<ul>
{{#links}}
<li class="{{#active}}active{{/active}}"><a title="{{title}}" href="{{url}}">{{title}}</a></li>
{{/links}}
</ul>
</nav>

View File

@ -13,10 +13,10 @@
_ = require('underscore'),
Polyglot = require('node-polyglot'),
models = require('./shared/models'),
ExampleFilter = require('../content/plugins/exampleFilters'),
Ghost,
instance,
filterCallbacks = {},
instance,
statuses;
// ## Article Statuses
@ -40,10 +40,12 @@
*/
Ghost = function () {
var app,
plugin,
polyglot;
if (!instance) {
instance = this;
plugin = new ExampleFilter(instance).init();
app = express();
@ -60,19 +62,32 @@
dataProvider: models,
statuses: function () { return statuses; },
polyglot: function () { return polyglot; },
plugin: function() { return plugin; },
paths: function () {
return {
'activeTheme': __dirname + '/../content/' + config.themeDir + '/' + config.activeTheme + '/',
'adminViews': __dirname + '/admin/views/',
'lang': __dirname + '/lang/'
'activeTheme': __dirname + '/../content/' + config.themeDir + '/' + config.activeTheme + '/',
'adminViews': __dirname + '/admin/views/',
'frontendViews': __dirname + '/frontend/views/',
'lang': __dirname + '/lang/'
};
}
});
}
return instance;
};
/**
* Holds the filters
* @type {Array}
*/
Ghost.prototype.filterCallbacks = [];
/**
* Holds the filter hooks (that are built in to Ghost Core)
* @type {Array}
*/
Ghost.prototype.filters = [];
/**
* @param {string} name
* @param {Function} fn
@ -105,11 +120,11 @@
* @param {Function} fn
*/
Ghost.prototype.registerFilter = function (name, fn) {
if (!filterCallbacks.hasOwnProperty(name)) {
filterCallbacks[name] = [];
if (!this.filterCallbacks.hasOwnProperty(name)) {
this.filterCallbacks[name] = [];
}
console.log('registering filter for ', name);
filterCallbacks[name].push(fn);
this.filterCallbacks[name].push(fn);
};
/**
@ -121,14 +136,15 @@
Ghost.prototype.doFilter = function (name, args, callback) {
var fn;
if (filterCallbacks.hasOwnProperty(name)) {
for (fn in filterCallbacks[name]) {
if (filterCallbacks[name].hasOwnProperty(fn)) {
if (this.filterCallbacks.hasOwnProperty(name)) {
for (fn in this.filterCallbacks[name]) {
if (this.filterCallbacks[name].hasOwnProperty(fn)) {
console.log('doing filter for ', name);
args = filterCallbacks[name][fn](args);
args = this.filterCallbacks[name][fn](args);
}
}
}
callback(args);
};

View File

@ -50,15 +50,16 @@ module.exports = {
users: [
{
"id": "1",
"username": "johnonolan",
"id": "1",
"username": "johnonolan",
"first_name": "John",
"last_name": "O'Nolan",
"password": "$2a$10$.pb3wOEhbEPvArvOBB.iyuKslBjC7lSXCUzp29civDTvCg3M1j0XO",
"email_address": "john@onolan.org",
"profile_picture": "logo.png",
"cover_picture": "",
"bio": "Interactive designer, public speaker, startup advisor and writer. Living in Austria, attempting world domination via keyboard.",
"url": "john.onolan.org",
"bio": "Interactive designer, public speaker, startup advisor and writer. Living in Austria, attempting world domination via keyboard.",
"url": "john.onolan.org",
"created_by": 1,
"updated_by": 1
}

View File

@ -10,7 +10,7 @@
errors = {
throwError: function (err) {
if (!err) {
return;
err = new Error("An error occurred");
}
if (_.isString(err)) {

View File

@ -31,6 +31,14 @@
runThrowError.should['throw']("test2");
});
it("throws error even if nothing passed", function () {
var runThrowError = function () {
errors.throwError();
};
runThrowError.should['throw']("An error occurred");
});
it("logs errors", function () {
var err = new Error("test1"),
logStub = sinon.stub(console, "log");

View File

@ -0,0 +1,69 @@
/*globals describe, beforeEach, it*/
(function () {
"use strict";
var should = require('should'),
sinon = require('sinon'),
_ = require('underscore'),
path = require('path'),
GhostNavHelper = require('../../frontend/helpers/ghostNav');
describe('ghostNav Helper', function () {
var navTemplatePath = path.join(process.cwd(), 'core/frontend/views/nav.hbs');
should.exist(GhostNavHelper, "GhostNavHelper exists");
it('can compile the nav template', function (done) {
var helper = new GhostNavHelper(navTemplatePath);
helper.compileTemplate().then(function () {
should.exist(helper.navTemplateFunc);
_.isFunction(helper.navTemplateFunc).should.equal(true);
done();
}, done);
});
it('can render nav items', function () {
var helper = new GhostNavHelper(function (data) { return "rendered " + data.links.length; }),
templateSpy = sinon.spy(helper, 'navTemplateFunc'),
fakeNavItems = [{
title: 'test1',
url: '/test1'
}, {
title: 'test2',
url: '/test2'
}],
rendered;
rendered = helper.renderNavItems(fakeNavItems);
// Returns a string returned from navTemplateFunc
should.exist(rendered);
rendered.should.equal("rendered 2");
templateSpy.calledWith({ links: fakeNavItems }).should.equal(true);
});
it('can register with ghost', function (done) {
var fakeGhost = {
paths: function () {
return {
frontendViews: path.join(process.cwd(), 'core/frontend/views/')
};
},
registerThemeHelper: function () {
return;
}
},
registerStub = sinon.stub(fakeGhost, 'registerThemeHelper');
GhostNavHelper.registerWithGhost(fakeGhost).then(function () {
registerStub.called.should.equal(true);
done();
}, done);
});
});
}());

View File

@ -4,7 +4,7 @@
"private": true,
"scripts": {
"start": "node app",
"test": "grunt validate"
"test": "grunt validate --verbose"
},
"dependencies": {
"express": "3.1.x",
@ -23,6 +23,7 @@
},
"devDependencies": {
"grunt": "~0.4.1",
"grunt-cli": "0.1.9",
"grunt-jslint": "0.2.x",
"should": "~1.2.2",
"grunt-mocha-test": "~0.4.0",