mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-04-08 02:52:39 -05:00
Merge branch 'ember'
Conflicts: Gruntfile.js core/client/models/post.js core/client/models/settings.js core/client/models/user.js core/client/router.js package.json
This commit is contained in:
commit
d1f57a2569
165 changed files with 3331 additions and 455 deletions
8
.gitignore
vendored
8
.gitignore
vendored
|
@ -30,9 +30,15 @@ projectFilesBackup
|
|||
|
||||
.build
|
||||
.dist
|
||||
.tmp
|
||||
|
||||
/core/client/tpl/hbs-tpl.js
|
||||
|
||||
/core/clientold/tpl/hbs-tpl.js
|
||||
/core/clientold/assets/css
|
||||
/core/clientold/assets/fonts
|
||||
/core/clientold/assets/vendor
|
||||
/core/client/assets/css
|
||||
!/core/client/assets/css/ember-hacks.css
|
||||
/core/client/assets/fonts
|
||||
/core/server/data/export/exported*
|
||||
/docs
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
!**
|
||||
.build
|
||||
.dist
|
||||
.tmp
|
||||
docs/**
|
||||
_site/**
|
||||
content/images/**
|
||||
|
@ -24,4 +25,4 @@ CONTRIBUTING.md
|
|||
SECURITY.md
|
||||
.travis.yml
|
||||
*.html
|
||||
bower_components/**
|
||||
bower_components/**
|
||||
|
|
207
Gruntfile.js
207
Gruntfile.js
|
@ -28,7 +28,7 @@ var path = require('path'),
|
|||
}()),
|
||||
|
||||
// ## Grunt configuration
|
||||
//
|
||||
|
||||
configureGrunt = function (grunt) {
|
||||
|
||||
// *This is not useful but required for jshint*
|
||||
|
@ -42,7 +42,8 @@ var path = require('path'),
|
|||
var cfg = {
|
||||
// #### Common paths used by tasks
|
||||
paths: {
|
||||
adminAssets: './core/client/assets',
|
||||
// adminAssets: './core/client/', ?? who knows...
|
||||
adminOldAssets: './core/clientold/assets',
|
||||
build: buildDirectory,
|
||||
releaseBuild: path.join(buildDirectory, 'release'),
|
||||
dist: distDirectory,
|
||||
|
@ -58,13 +59,21 @@ var path = require('path'),
|
|||
// See the [grunt dev](#live%20reload) task for how this is used.
|
||||
watch: {
|
||||
handlebars: {
|
||||
files: ['core/client/tpl/**/*.hbs'],
|
||||
files: ['core/clientold/tpl/**/*.hbs'],
|
||||
tasks: ['handlebars']
|
||||
},
|
||||
'handlebars-ember': {
|
||||
files: ['core/client/**/*.hbs'],
|
||||
tasks: ['emberTemplates:dev']
|
||||
},
|
||||
ember: {
|
||||
files: ['core/client/**/*.js'],
|
||||
tasks: ['clean:tmp', 'transpile', 'concat_sourcemap']
|
||||
},
|
||||
concat: {
|
||||
files: [
|
||||
'core/client/*.js',
|
||||
'core/client/**/*.js'
|
||||
'core/clientold/*.js',
|
||||
'core/clientold/**/*.js'
|
||||
],
|
||||
tasks: ['concat']
|
||||
},
|
||||
|
@ -167,7 +176,9 @@ var path = require('path'),
|
|||
Ember: true,
|
||||
Em: true,
|
||||
DS: true,
|
||||
$: true
|
||||
$: true,
|
||||
validator: true,
|
||||
ic: true
|
||||
},
|
||||
node: false,
|
||||
browser: true,
|
||||
|
@ -295,18 +306,64 @@ var path = require('path'),
|
|||
},
|
||||
|
||||
// ### grunt-contrib-handlebars
|
||||
// Compile handlebars templates into a JST file for the admin client
|
||||
// Compile handlebars templates into a JST file for the admin client (old)
|
||||
handlebars: {
|
||||
core: {
|
||||
options: {
|
||||
namespace: 'JST',
|
||||
processName: function (filename) {
|
||||
filename = filename.replace('core/client/tpl/', '');
|
||||
filename = filename.replace('core/clientold/tpl/', '');
|
||||
return filename.replace('.hbs', '');
|
||||
}
|
||||
},
|
||||
files: {
|
||||
'core/client/tpl/hbs-tpl.js': 'core/client/tpl/**/*.hbs'
|
||||
'core/clientold/tpl/hbs-tpl.js': 'core/clientold/tpl/**/*.hbs'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// ### grunt-ember-templates
|
||||
// Compiles handlebar templates for ember
|
||||
emberTemplates: {
|
||||
dev: {
|
||||
options: {
|
||||
templateBasePath: /core\/client\//,
|
||||
templateFileExtensions: /\.hbs/,
|
||||
templateRegistration: function (name, template) {
|
||||
return grunt.config.process("define('ghost/") + name + "', ['exports'], function(__exports__){ __exports__['default'] = " + template + "; });";
|
||||
}
|
||||
},
|
||||
files: {
|
||||
"core/built/scripts/templates-ember.js": "core/client/templates/**/*.hbs"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// ### grunt-es6-module-transpiler
|
||||
// Compiles Ember es6 modules
|
||||
transpile: {
|
||||
client: {
|
||||
type: 'amd',
|
||||
moduleName: function (path) {
|
||||
return 'ghost/' + path;
|
||||
},
|
||||
files: [{
|
||||
expand: true,
|
||||
cwd: 'core/client/',
|
||||
src: ['**/*.js'],
|
||||
dest: '.tmp/ember-transpiled/'
|
||||
}]
|
||||
}
|
||||
},
|
||||
|
||||
// ### grunt-es6-module-transpiler
|
||||
// Compiles Ember es6 modules
|
||||
concat_sourcemap: {
|
||||
client: {
|
||||
src: ['.tmp/ember-transpiled/**/*.js'],
|
||||
dest: 'core/built/scripts/ghost-dev-ember.js',
|
||||
options: {
|
||||
sourcesContent: true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -329,11 +386,17 @@ var path = require('path'),
|
|||
// ### grunt-contrib-clean
|
||||
// Clean up files as part of other tasks
|
||||
clean: {
|
||||
built: {
|
||||
src: ['core/built/**']
|
||||
},
|
||||
release: {
|
||||
src: ['<%= paths.releaseBuild %>/**']
|
||||
},
|
||||
test: {
|
||||
src: ['content/data/ghost-test.db']
|
||||
},
|
||||
tmp: {
|
||||
src: ['.tmp/**']
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -351,6 +414,11 @@ var path = require('path'),
|
|||
src: ['**'],
|
||||
dest: 'core/client/assets/',
|
||||
expand: true
|
||||
}, {
|
||||
cwd: 'bower_components/ghost-ui/dist/',
|
||||
src: ['**'],
|
||||
dest: 'core/clientold/assets/',
|
||||
expand: true
|
||||
}]
|
||||
},
|
||||
prod: {
|
||||
|
@ -364,6 +432,11 @@ var path = require('path'),
|
|||
src: ['**'],
|
||||
dest: 'core/client/assets/',
|
||||
expand: true
|
||||
}, {
|
||||
cwd: 'bower_components/ghost-ui/dist/',
|
||||
src: ['**'],
|
||||
dest: 'core/clientold/assets/',
|
||||
expand: true
|
||||
}]
|
||||
},
|
||||
release: {
|
||||
|
@ -377,6 +450,11 @@ var path = require('path'),
|
|||
src: ['**'],
|
||||
dest: 'core/client/assets/',
|
||||
expand: true
|
||||
}, {
|
||||
cwd: 'bower_components/ghost-ui/dist/',
|
||||
src: ['**'],
|
||||
dest: 'core/clientold/assets/',
|
||||
expand: true
|
||||
}, {
|
||||
expand: true,
|
||||
src: buildGlob,
|
||||
|
@ -406,8 +484,8 @@ var path = require('path'),
|
|||
'core/built/scripts/vendor.js': [
|
||||
'bower_components/jquery/dist/jquery.js',
|
||||
'bower_components/jquery-ui/ui/jquery-ui.js',
|
||||
'core/client/assets/lib/jquery-utils.js',
|
||||
'core/client/assets/lib/uploader.js',
|
||||
'core/clientold/assets/lib/jquery-utils.js',
|
||||
'core/clientold/assets/lib/uploader.js',
|
||||
|
||||
'bower_components/lodash/dist/lodash.underscore.js',
|
||||
'bower_components/backbone/backbone.js',
|
||||
|
@ -425,8 +503,8 @@ var path = require('path'),
|
|||
'core/shared/lib/showdown/extensions/ghostgfm.js',
|
||||
|
||||
// TODO: Remove or replace
|
||||
'core/client/assets/vendor/shortcuts.js',
|
||||
'core/client/assets/vendor/to-title-case.js',
|
||||
'core/clientold/assets/vendor/shortcuts.js',
|
||||
'core/clientold/assets/vendor/to-title-case.js',
|
||||
|
||||
'bower_components/Countable/Countable.js',
|
||||
'bower_components/fastclick/lib/fastclick.js',
|
||||
|
@ -434,32 +512,54 @@ var path = require('path'),
|
|||
],
|
||||
|
||||
'core/built/scripts/helpers.js': [
|
||||
'core/client/init.js',
|
||||
'core/clientold/init.js',
|
||||
|
||||
'core/client/mobile-interactions.js',
|
||||
'core/client/toggle.js',
|
||||
'core/client/markdown-actions.js',
|
||||
'core/client/helpers/index.js',
|
||||
'core/client/assets/lib/editor/index.js',
|
||||
'core/client/assets/lib/editor/markerManager.js',
|
||||
'core/client/assets/lib/editor/uploadManager.js',
|
||||
'core/client/assets/lib/editor/markdownEditor.js',
|
||||
'core/client/assets/lib/editor/htmlPreview.js',
|
||||
'core/client/assets/lib/editor/scrollHandler.js',
|
||||
'core/client/assets/lib/editor/mobileCodeMirror.js'
|
||||
'core/clientold/mobile-interactions.js',
|
||||
'core/clientold/toggle.js',
|
||||
'core/clientold/markdown-actions.js',
|
||||
'core/clientold/helpers/index.js',
|
||||
'core/clientold/assets/lib/editor/index.js',
|
||||
'core/clientold/assets/lib/editor/markerManager.js',
|
||||
'core/clientold/assets/lib/editor/uploadManager.js',
|
||||
'core/clientold/assets/lib/editor/markdownEditor.js',
|
||||
'core/clientold/assets/lib/editor/htmlPreview.js',
|
||||
'core/clientold/assets/lib/editor/scrollHandler.js',
|
||||
'core/clientold/assets/lib/editor/mobileCodeMirror.js'
|
||||
],
|
||||
|
||||
'core/built/scripts/templates.js': [
|
||||
'core/client/tpl/hbs-tpl.js'
|
||||
'core/clientold/tpl/hbs-tpl.js'
|
||||
],
|
||||
|
||||
'core/built/scripts/models.js': [
|
||||
'core/client/models/**/*.js'
|
||||
'core/clientold/models/**/*.js'
|
||||
],
|
||||
|
||||
'core/built/scripts/views.js': [
|
||||
'core/client/views/**/*.js',
|
||||
'core/client/router.js'
|
||||
'core/clientold/views/**/*.js',
|
||||
'core/clientold/router.js'
|
||||
]
|
||||
}
|
||||
},
|
||||
'dev-ember': {
|
||||
files: {
|
||||
'core/built/scripts/vendor-ember.js': [
|
||||
'core/client/assets/vendor/loader.js',
|
||||
'bower_components/jquery/dist/jquery.js',
|
||||
'bower_components/handlebars/handlebars.js',
|
||||
'bower_components/ember/ember.js',
|
||||
'bower_components/ember-resolver/dist/ember-resolver.js',
|
||||
'bower_components/ic-ajax/dist/globals/main.js',
|
||||
'bower_components/validator-js/validator.js',
|
||||
'bower_components/codemirror/lib/codemirror.js',
|
||||
'bower_components/codemirror/addon/mode/overlay.js',
|
||||
'bower_components/codemirror/mode/markdown/markdown.js',
|
||||
'bower_components/codemirror/mode/gfm/gfm.js',
|
||||
'bower_components/showdown/src/showdown.js',
|
||||
'bower_components/moment/moment.js',
|
||||
|
||||
'core/shared/lib/showdown/extensions/ghostimagepreview.js',
|
||||
'core/shared/lib/showdown/extensions/ghostgfm.js',
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -468,8 +568,8 @@ var path = require('path'),
|
|||
'core/built/scripts/ghost.js': [
|
||||
'bower_components/jquery/dist/jquery.js',
|
||||
'bower_components/jquery-ui/ui/jquery-ui.js',
|
||||
'core/client/assets/lib/jquery-utils.js',
|
||||
'core/client/assets/lib/uploader.js',
|
||||
'core/clientold/assets/lib/jquery-utils.js',
|
||||
'core/clientold/assets/lib/uploader.js',
|
||||
|
||||
'bower_components/lodash/dist/lodash.underscore.js',
|
||||
'bower_components/backbone/backbone.js',
|
||||
|
@ -487,35 +587,35 @@ var path = require('path'),
|
|||
'core/shared/lib/showdown/extensions/ghostgfm.js',
|
||||
|
||||
// TODO: Remove or replace
|
||||
'core/client/assets/vendor/shortcuts.js',
|
||||
'core/client/assets/vendor/to-title-case.js',
|
||||
'core/clientold/assets/vendor/shortcuts.js',
|
||||
'core/clientold/assets/vendor/to-title-case.js',
|
||||
|
||||
'bower_components/Countable/Countable.js',
|
||||
'bower_components/fastclick/lib/fastclick.js',
|
||||
'bower_components/nprogress/nprogress.js',
|
||||
|
||||
'core/client/init.js',
|
||||
'core/clientold/init.js',
|
||||
|
||||
'core/client/mobile-interactions.js',
|
||||
'core/client/toggle.js',
|
||||
'core/client/markdown-actions.js',
|
||||
'core/client/helpers/index.js',
|
||||
'core/clientold/mobile-interactions.js',
|
||||
'core/clientold/toggle.js',
|
||||
'core/clientold/markdown-actions.js',
|
||||
'core/clientold/helpers/index.js',
|
||||
|
||||
'core/client/assets/lib/editor/index.js',
|
||||
'core/client/assets/lib/editor/markerManager.js',
|
||||
'core/client/assets/lib/editor/uploadManager.js',
|
||||
'core/client/assets/lib/editor/markdownEditor.js',
|
||||
'core/client/assets/lib/editor/htmlPreview.js',
|
||||
'core/client/assets/lib/editor/scrollHandler.js',
|
||||
'core/client/assets/lib/editor/mobileCodeMirror.js',
|
||||
'core/clientold/assets/lib/editor/index.js',
|
||||
'core/clientold/assets/lib/editor/markerManager.js',
|
||||
'core/clientold/assets/lib/editor/uploadManager.js',
|
||||
'core/clientold/assets/lib/editor/markdownEditor.js',
|
||||
'core/clientold/assets/lib/editor/htmlPreview.js',
|
||||
'core/clientold/assets/lib/editor/scrollHandler.js',
|
||||
'core/clientold/assets/lib/editor/mobileCodeMirror.js',
|
||||
|
||||
'core/client/tpl/hbs-tpl.js',
|
||||
'core/clientold/tpl/hbs-tpl.js',
|
||||
|
||||
'core/client/models/**/*.js',
|
||||
'core/clientold/models/**/*.js',
|
||||
|
||||
'core/client/views/**/*.js',
|
||||
'core/clientold/views/**/*.js',
|
||||
|
||||
'core/client/router.js'
|
||||
'core/clientold/router.js'
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -772,6 +872,11 @@ var path = require('path'),
|
|||
console.log('Use the', 'stable'.bold, 'branch for live blogs.', 'Never'.bold, 'master!');
|
||||
});
|
||||
|
||||
// ### Ember Build *(Utility Task)*
|
||||
// All tasks related to building the Ember client code including transpiling ES6 modules and building templates
|
||||
grunt.registerTask('emberBuild', 'Build Ember JS & templates for development',
|
||||
['clean:tmp', 'emberTemplates:dev', 'transpile', 'concat_sourcemap']);
|
||||
|
||||
|
||||
// ### Init assets
|
||||
// `grunt init` - will run an initial asset build for you
|
||||
|
@ -800,7 +905,7 @@ var path = require('path'),
|
|||
// Compiles handlebars templates, concatenates javascript files for the admin UI into a handful of files instead
|
||||
// of many files, and makes sure the bower dependencies are in the right place.
|
||||
grunt.registerTask('default', 'Build JS & templates for development',
|
||||
['update_submodules', 'handlebars', 'concat', 'copy:dev']);
|
||||
['update_submodules', 'handlebars', 'concat', 'copy:dev', 'emberBuild']);
|
||||
|
||||
// ### Live reload
|
||||
// `grunt dev` - build assets on the fly whilst developing
|
||||
|
@ -814,7 +919,7 @@ var path = require('path'),
|
|||
//
|
||||
// Note that the current implementation of watch only works with casper, not other themes.
|
||||
grunt.registerTask('dev', 'Dev Mode; watch files and restart server on changes',
|
||||
['handlebars', 'concat', 'copy:dev', 'express:dev', 'watch']);
|
||||
['handlebars', 'concat', 'copy:dev', 'emberBuild', 'express:dev', 'watch']);
|
||||
|
||||
// ### Release
|
||||
// Run `grunt release` to create a Ghost release zip file.
|
||||
|
|
|
@ -4,9 +4,12 @@
|
|||
"backbone": "1.0.0",
|
||||
"codemirror": "4.0.1",
|
||||
"Countable": "2.0.2",
|
||||
"ember": "1.5.0",
|
||||
"ember-resolver": "git://github.com/stefanpenner/ember-jj-abrams-resolver.git#181251821cf513bb58d3e192faa13245a816f75e",
|
||||
"fastclick": "1.0.0",
|
||||
"ghost-ui": "0.1.3",
|
||||
"handlebars": "1.3.0",
|
||||
"ic-ajax": "1.0.1",
|
||||
"jquery": "1.11.0",
|
||||
"jquery-file-upload": "9.5.6",
|
||||
"jquery-hammerjs": "1.0.1",
|
||||
|
@ -16,5 +19,8 @@
|
|||
"nprogress": "0.1.2",
|
||||
"showdown": "https://github.com/ErisDS/showdown.git#v0.3.2-ghost",
|
||||
"validator-js": "3.4.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"ember": "~1.4.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
28
core/client/app.js
Executable file
28
core/client/app.js
Executable file
|
@ -0,0 +1,28 @@
|
|||
import Resolver from 'ember/resolver';
|
||||
import initFixtures from 'ghost/fixtures/init';
|
||||
import {currentUser, injectCurrentUser} from 'ghost/initializers/current-user';
|
||||
import {registerNotifications, injectNotifications} from 'ghost/initializers/notifications';
|
||||
import 'ghost/utils/link-view';
|
||||
import 'ghost/utils/text-field';
|
||||
|
||||
var App = Ember.Application.extend({
|
||||
/**
|
||||
* These are debugging flags, they are useful during development
|
||||
*/
|
||||
LOG_ACTIVE_GENERATION: true,
|
||||
LOG_MODULE_RESOLVER: true,
|
||||
LOG_TRANSITIONS: true,
|
||||
LOG_TRANSITIONS_INTERNAL: true,
|
||||
LOG_VIEW_LOOKUPS: true,
|
||||
modulePrefix: 'ghost',
|
||||
Resolver: Resolver['default']
|
||||
});
|
||||
|
||||
initFixtures();
|
||||
|
||||
App.initializer(currentUser);
|
||||
App.initializer(injectCurrentUser);
|
||||
App.initializer(registerNotifications);
|
||||
App.initializer(injectNotifications);
|
||||
|
||||
export default App;
|
74
core/client/assets/css/ember-hacks.css
Normal file
74
core/client/assets/css/ember-hacks.css
Normal file
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
Cosmetic changes to ghost styles, that help during development.
|
||||
The contents should be solved properly or moved into ghost-ui package.
|
||||
*/
|
||||
|
||||
#entry-markdown,
|
||||
.entry-preview,
|
||||
.CodeMirror.cm-s-default {
|
||||
height: 500px !important;
|
||||
}
|
||||
|
||||
.editor input {
|
||||
-webkit-transition: none;
|
||||
-moz-transition: none;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
/*
|
||||
By default nav menu should be displayed as it's visibility is controllerd
|
||||
by GhostPopover
|
||||
*/
|
||||
.navbar .subnav ul {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*
|
||||
Styles for GhostPopoverComponent
|
||||
*/
|
||||
|
||||
.ghost-popover {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ghost-popover.open {
|
||||
display: block;
|
||||
}
|
||||
.fade-in {
|
||||
animation: fadein 0.5s;
|
||||
-moz-animation: fadein 0.5s; /* Firefox */
|
||||
-webkit-animation: fadein 0.5s; /* Safari and Chrome */
|
||||
-o-animation: fadein 0.5s; /* Opera */
|
||||
}
|
||||
@keyframes fadein {
|
||||
from {
|
||||
opacity:0;
|
||||
}
|
||||
to {
|
||||
opacity:1;
|
||||
}
|
||||
}
|
||||
@-moz-keyframes fadein { /* Firefox */
|
||||
from {
|
||||
opacity:0;
|
||||
}
|
||||
to {
|
||||
opacity:1;
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes fadein { /* Safari and Chrome */
|
||||
from {
|
||||
opacity:0;
|
||||
}
|
||||
to {
|
||||
opacity:1;
|
||||
}
|
||||
}
|
||||
@-o-keyframes fadein { /* Opera */
|
||||
from {
|
||||
opacity:0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
75
core/client/assets/vendor/loader.js
vendored
Normal file
75
core/client/assets/vendor/loader.js
vendored
Normal file
|
@ -0,0 +1,75 @@
|
|||
var define, requireModule, require, requirejs;
|
||||
|
||||
(function() {
|
||||
var registry = {}, seen = {}, state = {};
|
||||
var FAILED = false;
|
||||
|
||||
define = function(name, deps, callback) {
|
||||
registry[name] = {
|
||||
deps: deps,
|
||||
callback: callback
|
||||
};
|
||||
};
|
||||
|
||||
requirejs = require = requireModule = function(name) {
|
||||
if (state[name] !== FAILED &&
|
||||
seen.hasOwnProperty(name)) {
|
||||
return seen[name];
|
||||
}
|
||||
|
||||
if (!registry.hasOwnProperty(name)) {
|
||||
throw new Error('Could not find module ' + name);
|
||||
}
|
||||
|
||||
var mod = registry[name];
|
||||
var deps = mod.deps;
|
||||
var callback = mod.callback;
|
||||
var reified = [];
|
||||
var exports;
|
||||
var value;
|
||||
var loaded = false;
|
||||
|
||||
seen[name] = { }; // enable run-time cycles
|
||||
|
||||
try {
|
||||
for (var i=0, l=deps.length; i<l; i++) {
|
||||
if (deps[i] === 'exports') {
|
||||
reified.push(exports = {});
|
||||
} else {
|
||||
reified.push(requireModule(resolve(deps[i], name)));
|
||||
}
|
||||
}
|
||||
|
||||
value = callback.apply(this, reified);
|
||||
loaded = true;
|
||||
} finally {
|
||||
if (!loaded) {
|
||||
state[name] = FAILED;
|
||||
}
|
||||
}
|
||||
return seen[name] = exports || value;
|
||||
};
|
||||
|
||||
function resolve(child, name) {
|
||||
if (child.charAt(0) !== '.') { return child; }
|
||||
|
||||
var parts = child.split('/');
|
||||
var parentBase = name.split('/').slice(0, -1);
|
||||
|
||||
for (var i = 0, l = parts.length; i < l; i++) {
|
||||
var part = parts[i];
|
||||
|
||||
if (part === '..') { parentBase.pop(); }
|
||||
else if (part === '.') { continue; }
|
||||
else { parentBase.push(part); }
|
||||
}
|
||||
|
||||
return parentBase.join('/');
|
||||
}
|
||||
|
||||
requirejs._eak_seen = registry;
|
||||
requirejs.clear = function(){
|
||||
requirejs._eak_seen = registry = {};
|
||||
seen = {};
|
||||
};
|
||||
})();
|
44
core/client/components/-codemirror.js
Normal file
44
core/client/components/-codemirror.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
/* global CodeMirror*/
|
||||
|
||||
var onChangeHandler = function (cm) {
|
||||
cm.component.set('value', cm.getDoc().getValue());
|
||||
};
|
||||
|
||||
var onScrollHandler = function (cm) {
|
||||
var scrollInfo = cm.getScrollInfo(),
|
||||
percentage = scrollInfo.top / scrollInfo.height,
|
||||
component = cm.component;
|
||||
|
||||
// throttle scroll updates
|
||||
component.throttle = Ember.run.throttle(component, function () {
|
||||
this.set('scrollPosition', percentage);
|
||||
}, 50);
|
||||
};
|
||||
|
||||
var Codemirror = Ember.TextArea.extend({
|
||||
initCodemirror: function () {
|
||||
// create codemirror
|
||||
this.codemirror = CodeMirror.fromTextArea(this.get('element'), {
|
||||
lineWrapping: true
|
||||
});
|
||||
this.codemirror.component = this; // save reference to this
|
||||
|
||||
// propagate changes to value property
|
||||
this.codemirror.on('change', onChangeHandler);
|
||||
|
||||
// on scroll update scrollPosition property
|
||||
this.codemirror.on('scroll', onScrollHandler);
|
||||
}.on('didInsertElement'),
|
||||
|
||||
removeThrottle: function () {
|
||||
Ember.run.cancel(this.throttle);
|
||||
}.on('willDestroyElement'),
|
||||
|
||||
removeCodemirrorHandlers: function () {
|
||||
// not sure if this is needed.
|
||||
this.codemirror.off('change', onChangeHandler);
|
||||
this.codemirror.off('scroll', onScrollHandler);
|
||||
}.on('willDestroyElement')
|
||||
});
|
||||
|
||||
export default Codemirror;
|
11
core/client/components/-markdown.js
Normal file
11
core/client/components/-markdown.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
var Markdown = Ember.Component.extend({
|
||||
adjustScrollPosition: function () {
|
||||
var scrollWrapper = this.$('.entry-preview-content').get(0),
|
||||
// calculate absolute scroll position from percentage
|
||||
scrollPixel = scrollWrapper.scrollHeight * this.get('scrollPosition');
|
||||
|
||||
scrollWrapper.scrollTop = scrollPixel; // adjust scroll position
|
||||
}.observes('scrollPosition')
|
||||
});
|
||||
|
||||
export default Markdown;
|
5
core/client/components/activating-list-item.js
Normal file
5
core/client/components/activating-list-item.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
export default Ember.Component.extend({
|
||||
tagName: 'li',
|
||||
classNameBindings: ['active'],
|
||||
active: false
|
||||
});
|
13
core/client/components/blur-text-field.js
Normal file
13
core/client/components/blur-text-field.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
var BlurTextField = Ember.TextField.extend({
|
||||
selectOnClick: false,
|
||||
click: function (event) {
|
||||
if (this.get('selectOnClick')) {
|
||||
event.currentTarget.select();
|
||||
}
|
||||
},
|
||||
focusOut: function () {
|
||||
this.sendAction('action', this.get('value'));
|
||||
}
|
||||
});
|
||||
|
||||
export default BlurTextField;
|
23
core/client/components/file-upload.js
Normal file
23
core/client/components/file-upload.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
var FileUpload = Ember.Component.extend({
|
||||
_file: null,
|
||||
uploadButtonText: 'Text',
|
||||
uploadButtonDisabled: true,
|
||||
change: function (event) {
|
||||
this.set('uploadButtonDisabled', false);
|
||||
this.sendAction('onAdd');
|
||||
this._file = event.target.files[0];
|
||||
},
|
||||
actions: {
|
||||
upload: function () {
|
||||
var self = this;
|
||||
if (!this.uploadButtonDisabled && self._file) {
|
||||
self.sendAction('onUpload', self._file);
|
||||
}
|
||||
|
||||
// Prevent double post by disabling the button.
|
||||
this.set('uploadButtonDisabled', true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default FileUpload;
|
13
core/client/components/gh-form.js
Normal file
13
core/client/components/gh-form.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
export default Ember.View.extend({
|
||||
tagName: 'form',
|
||||
attributeBindings: ['enctype'],
|
||||
reset: function () {
|
||||
this.$().get(0).reset();
|
||||
},
|
||||
didInsertElement: function () {
|
||||
this.get('controller').on('reset', this, this.reset);
|
||||
},
|
||||
willClearRender: function () {
|
||||
this.get('controller').off('reset', this, this.reset);
|
||||
}
|
||||
});
|
21
core/client/components/ghost-notification.js
Normal file
21
core/client/components/ghost-notification.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
var NotificationComponent = Ember.Component.extend({
|
||||
classNames: ['js-bb-notification'],
|
||||
|
||||
didInsertElement: function () {
|
||||
var self = this;
|
||||
|
||||
self.$().on('animationend webkitAnimationEnd oanimationend MSAnimationEnd', function (event) {
|
||||
/* jshint unused: false */
|
||||
self.notifications.removeObject(self.get('message'));
|
||||
});
|
||||
},
|
||||
|
||||
actions: {
|
||||
closeNotification: function () {
|
||||
var self = this;
|
||||
self.notifications.removeObject(self.get('message'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default NotificationComponent;
|
7
core/client/components/ghost-notifications.js
Normal file
7
core/client/components/ghost-notifications.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
var NotificationsComponent = Ember.Component.extend({
|
||||
tagName: 'aside',
|
||||
classNames: 'notifications',
|
||||
messages: Ember.computed.alias('notifications')
|
||||
});
|
||||
|
||||
export default NotificationsComponent;
|
8
core/client/components/ghost-popover.js
Normal file
8
core/client/components/ghost-popover.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
|
||||
var GhostPopover = Ember.Component.extend({
|
||||
classNames: 'ghost-popover',
|
||||
classNameBindings: ['open'],
|
||||
open: false
|
||||
});
|
||||
|
||||
export default GhostPopover;
|
59
core/client/components/modal-dialog.js
Normal file
59
core/client/components/modal-dialog.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
var ModalDialog = Ember.Component.extend({
|
||||
didInsertElement: function () {
|
||||
this.$('#modal-container').fadeIn(50);
|
||||
|
||||
this.$('.modal-background').show().fadeIn(10, function () {
|
||||
$(this).addClass('in');
|
||||
});
|
||||
|
||||
this.$('.js-modal').addClass('in');
|
||||
},
|
||||
|
||||
willDestroyElement: function () {
|
||||
|
||||
this.$('.js-modal').removeClass('in');
|
||||
|
||||
this.$('.modal-background').removeClass('in');
|
||||
|
||||
return this._super();
|
||||
},
|
||||
|
||||
actions: {
|
||||
closeModal: function () {
|
||||
this.sendAction();
|
||||
},
|
||||
confirm: function (type) {
|
||||
var func = this.get('confirm.' + type + '.func');
|
||||
if (typeof func === 'function') {
|
||||
func();
|
||||
}
|
||||
this.sendAction();
|
||||
}
|
||||
},
|
||||
|
||||
klass: function () {
|
||||
var classNames = [];
|
||||
|
||||
classNames.push(this.get('type') ? 'modal-' + this.get('type') : 'modal');
|
||||
|
||||
if (this.get('style')) {
|
||||
this.get('style').split(',').forEach(function (style) {
|
||||
classNames.push('modal-style-' + style);
|
||||
});
|
||||
}
|
||||
|
||||
classNames.push(this.get('animation'));
|
||||
|
||||
return classNames.join(' ');
|
||||
}.property('type', 'style', 'animation'),
|
||||
|
||||
acceptButtonClass: function () {
|
||||
return this.get('confirm.accept.buttonClass') ? this.get('confirm.accept.buttonClass') : 'button-add';
|
||||
}.property('confirm.accept.buttonClass'),
|
||||
|
||||
rejectButtonClass: function () {
|
||||
return this.get('confirm.reject.buttonClass') ? this.get('confirm.reject.buttonClass') : 'button-delete';
|
||||
}.property('confirm.reject.buttonClass')
|
||||
});
|
||||
|
||||
export default ModalDialog;
|
32
core/client/components/upload-modal.js
Normal file
32
core/client/components/upload-modal.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*global console */
|
||||
|
||||
import ModalDialog from 'ghost/components/modal-dialog';
|
||||
|
||||
var UploadModal = ModalDialog.extend({
|
||||
layoutName: 'components/modal-dialog',
|
||||
|
||||
didInsertElement: function () {
|
||||
this._super();
|
||||
|
||||
// @TODO: get this real
|
||||
console.log('UploadController:afterRender');
|
||||
// var filestorage = $('#' + this.options.model.id).data('filestorage');
|
||||
// this.$('.js-drop-zone').upload({fileStorage: filestorage});
|
||||
},
|
||||
|
||||
actions: {
|
||||
closeModal: function () {
|
||||
this.sendAction();
|
||||
},
|
||||
confirm: function (type) {
|
||||
var func = this.get('confirm.' + type + '.func');
|
||||
if (typeof func === 'function') {
|
||||
func();
|
||||
}
|
||||
this.sendAction();
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export default UploadModal;
|
10
core/client/controllers/application.js
Normal file
10
core/client/controllers/application.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
var ApplicationController = Ember.Controller.extend({
|
||||
isLoggedOut: Ember.computed.match('currentPath', /(signin|signup|forgotten|reset)/),
|
||||
actions: {
|
||||
toggleMenu: function () {
|
||||
this.toggleProperty('showMenu');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default ApplicationController;
|
21
core/client/controllers/forgotten.js
Normal file
21
core/client/controllers/forgotten.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*global console, alert */
|
||||
|
||||
var ForgottenController = Ember.Controller.extend({
|
||||
email: '',
|
||||
actions: {
|
||||
submit: function () {
|
||||
var self = this;
|
||||
self.user.fetchForgottenPasswordFor(this.email)
|
||||
.then(function () {
|
||||
alert('@TODO Notification: Success');
|
||||
self.transitionToRoute('signin');
|
||||
})
|
||||
.catch(function (response) {
|
||||
alert('@TODO');
|
||||
console.log(response);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default ForgottenController;
|
55
core/client/controllers/modals/delete-all.js
Normal file
55
core/client/controllers/modals/delete-all.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*global alert */
|
||||
|
||||
var DeleteAllController = Ember.Controller.extend({
|
||||
confirm: {
|
||||
accept: {
|
||||
func: function () {
|
||||
// @TODO make the below real :)
|
||||
alert('Deleting everything!');
|
||||
// $.ajax({
|
||||
// url: Ghost.paths.apiRoot + '/db/',
|
||||
// type: 'DELETE',
|
||||
// headers: {
|
||||
// 'X-CSRF-Token': $("meta[name='csrf-param']").attr('content')
|
||||
// },
|
||||
// success: function onSuccess(response) {
|
||||
// if (!response) {
|
||||
// throw new Error('No response received from server.');
|
||||
// }
|
||||
// if (!response.message) {
|
||||
// throw new Error(response.detail || 'Unknown error');
|
||||
// }
|
||||
|
||||
// Ghost.notifications.addItem({
|
||||
// type: 'success',
|
||||
// message: response.message,
|
||||
// status: 'passive'
|
||||
// });
|
||||
|
||||
// },
|
||||
// error: function onError(response) {
|
||||
// var responseText = JSON.parse(response.responseText),
|
||||
// message = responseText && responseText.error ? responseText.error : 'unknown';
|
||||
// Ghost.notifications.addItem({
|
||||
// type: 'error',
|
||||
// message: ['A problem was encountered while deleting content from your blog. Error: ', message].join(''),
|
||||
// status: 'passive'
|
||||
// });
|
||||
|
||||
// }
|
||||
// });
|
||||
},
|
||||
text: "Delete",
|
||||
buttonClass: "button-delete"
|
||||
},
|
||||
reject: {
|
||||
func: function () {
|
||||
return true;
|
||||
},
|
||||
text: "Cancel",
|
||||
buttonClass: "button"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default DeleteAllController;
|
42
core/client/controllers/modals/delete-post.js
Normal file
42
core/client/controllers/modals/delete-post.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*global alert */
|
||||
|
||||
var DeletePostController = Ember.Controller.extend({
|
||||
confirm: {
|
||||
accept: {
|
||||
func: function () {
|
||||
// @TODO: make this real
|
||||
alert('Deleting post');
|
||||
// self.model.destroy({
|
||||
// wait: true
|
||||
// }).then(function () {
|
||||
// // Redirect to content screen if deleting post from editor.
|
||||
// if (window.location.pathname.indexOf('editor') > -1) {
|
||||
// window.location = Ghost.paths.subdir + '/ghost/content/';
|
||||
// }
|
||||
// Ghost.notifications.addItem({
|
||||
// type: 'success',
|
||||
// message: 'Your post has been deleted.',
|
||||
// status: 'passive'
|
||||
// });
|
||||
// }, function () {
|
||||
// Ghost.notifications.addItem({
|
||||
// type: 'error',
|
||||
// message: 'Your post could not be deleted. Please try again.',
|
||||
// status: 'passive'
|
||||
// });
|
||||
// });
|
||||
},
|
||||
text: "Delete",
|
||||
buttonClass: "button-delete"
|
||||
},
|
||||
reject: {
|
||||
func: function () {
|
||||
return true;
|
||||
},
|
||||
text: "Cancel",
|
||||
buttonClass: "button"
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export default DeletePostController;
|
14
core/client/controllers/modals/upload.js
Normal file
14
core/client/controllers/modals/upload.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
|
||||
var UploadController = Ember.Controller.extend({
|
||||
confirm: {
|
||||
reject: {
|
||||
func: function () { // The function called on rejection
|
||||
return true;
|
||||
},
|
||||
buttonClass: true,
|
||||
text: "Cancel" // The reject button text
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default UploadController;
|
176
core/client/controllers/posts/post.js
Normal file
176
core/client/controllers/posts/post.js
Normal file
|
@ -0,0 +1,176 @@
|
|||
import {parseDateString, formatDate} from 'ghost/utils/date-formatting';
|
||||
|
||||
var equal = Ember.computed.equal;
|
||||
|
||||
var PostController = Ember.ObjectController.extend({
|
||||
|
||||
isPublished: equal('status', 'published'),
|
||||
isDraft: equal('status', 'draft'),
|
||||
isEditingSettings: false,
|
||||
isStaticPage: function (key, val) {
|
||||
if (arguments.length > 1) {
|
||||
this.set('model.page', val ? 1 : 0);
|
||||
this.get('model').save('page').then(function () {
|
||||
this.notifications.showSuccess('Succesfully converted ' + (val ? 'to static page' : 'to post'));
|
||||
}, this.notifications.showErrors);
|
||||
}
|
||||
return !!this.get('model.page');
|
||||
}.property('model.page'),
|
||||
|
||||
isOnServer: function () {
|
||||
return this.get('model.id') !== undefined;
|
||||
}.property('model.id'),
|
||||
|
||||
newSlugBinding: Ember.Binding.oneWay('model.slug'),
|
||||
slugPlaceholder: null,
|
||||
// Requests a new slug when the title was changed
|
||||
updateSlugPlaceholder: function () {
|
||||
var model,
|
||||
self = this,
|
||||
title = this.get('title');
|
||||
|
||||
// If there's a title present we want to
|
||||
// validate it against existing slugs in the db
|
||||
// and then update the placeholder value.
|
||||
if (title) {
|
||||
model = self.get('model');
|
||||
model.generateSlug().then(function (slug) {
|
||||
self.set('slugPlaceholder', slug);
|
||||
}, function () {
|
||||
self.notifications.showWarn('Unable to generate a slug for "' + title + '"');
|
||||
});
|
||||
} else {
|
||||
// If there's no title set placeholder to blank
|
||||
// and don't make an ajax request to server
|
||||
// for a proper slug (as there won't be any).
|
||||
self.set('slugPlaceholder', '');
|
||||
}
|
||||
}.observes('model.title'),
|
||||
|
||||
publishedAt: null,
|
||||
publishedAtChanged: function () {
|
||||
this.set('publishedAt', formatDate(this.get('model.published_at')));
|
||||
}.observes('model.published_at'),
|
||||
|
||||
actions: {
|
||||
editSettings: function () {
|
||||
this.toggleProperty('isEditingSettings');
|
||||
if (this.get('isEditingSettings')) {
|
||||
//Stop editing if the user clicks outside the settings view
|
||||
Ember.run.next(this, function () {
|
||||
var self = this;
|
||||
// @TODO has a race condition with click on the editSettings action
|
||||
$(document).one('click', function () {
|
||||
self.toggleProperty('isEditingSettings');
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
updateSlug: function () {
|
||||
var newSlug = this.get('newSlug'),
|
||||
slug = this.get('model.slug'),
|
||||
placeholder = this.get('slugPlaceholder'),
|
||||
self = this;
|
||||
|
||||
newSlug = (!newSlug && placeholder) ? placeholder : newSlug;
|
||||
|
||||
// Ignore unchanged slugs
|
||||
if (slug === newSlug) {
|
||||
return;
|
||||
}
|
||||
//reset to model's slug on empty string
|
||||
if (!newSlug) {
|
||||
this.set('newSlug', slug);
|
||||
return;
|
||||
}
|
||||
|
||||
//Validation complete
|
||||
this.set('model.slug', newSlug);
|
||||
|
||||
// If the model doesn't currently
|
||||
// exist on the server
|
||||
// then just update the model's value
|
||||
if (!this.get('isOnServer')) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.get('model').save('slug').then(function () {
|
||||
self.notifications.showSuccess('Permalink successfully changed to <strong>' + this.get('model.slug') + '</strong>.');
|
||||
}, this.notifications.showErrors);
|
||||
},
|
||||
|
||||
updatePublishedAt: function (userInput) {
|
||||
var errMessage = '',
|
||||
newPubDate = formatDate(parseDateString(userInput)),
|
||||
pubDate = this.get('publishedAt'),
|
||||
newPubDateMoment,
|
||||
pubDateMoment;
|
||||
|
||||
// if there is no new pub date, mark that until the post is published,
|
||||
// when we'll fill in with the current time.
|
||||
if (!newPubDate) {
|
||||
this.set('publishedAt', '');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for missing time stamp on new data
|
||||
// If no time specified, add a 12:00
|
||||
if (newPubDate && !newPubDate.slice(-5).match(/\d+:\d\d/)) {
|
||||
newPubDate += " 12:00";
|
||||
}
|
||||
|
||||
newPubDateMoment = parseDateString(newPubDate);
|
||||
|
||||
// If there was a published date already set
|
||||
if (pubDate) {
|
||||
// Check for missing time stamp on current model
|
||||
// If no time specified, add a 12:00
|
||||
if (!pubDate.slice(-5).match(/\d+:\d\d/)) {
|
||||
pubDate += " 12:00";
|
||||
}
|
||||
|
||||
pubDateMoment = parseDateString(pubDate);
|
||||
|
||||
// Quit if the new date is the same
|
||||
if (pubDateMoment.isSame(newPubDateMoment)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate new Published date
|
||||
if (!newPubDateMoment.isValid() || newPubDate.substr(0, 12) === "Invalid date") {
|
||||
errMessage = 'Published Date must be a valid date with format: DD MMM YY @ HH:mm (e.g. 6 Dec 14 @ 15:00)';
|
||||
}
|
||||
|
||||
if (newPubDateMoment.diff(new Date(), 'h') > 0) {
|
||||
errMessage = 'Published Date cannot currently be in the future.';
|
||||
}
|
||||
|
||||
if (errMessage) {
|
||||
// Show error message
|
||||
this.notifications.showError(errMessage);
|
||||
//Hack to push a "change" when it's actually staying
|
||||
// the same.
|
||||
//This alerts the listener on post-settings-menu
|
||||
this.notifyPropertyChange('publishedAt');
|
||||
return;
|
||||
}
|
||||
|
||||
//Validation complete
|
||||
this.set('model.published_at', newPubDateMoment.toDate());
|
||||
|
||||
// If the model doesn't currently
|
||||
// exist on the server
|
||||
// then just update the model's value
|
||||
if (!this.get('isOnServer')) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.get('model').save('published_at').then(function () {
|
||||
this.notifications.showSuccess('Publish date successfully changed to <strong>' + this.get('publishedAt') + '</strong>.');
|
||||
}, this.notifications.showErrors);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default PostController;
|
30
core/client/controllers/reset.js
Normal file
30
core/client/controllers/reset.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*global alert, console */
|
||||
var ResetController = Ember.Controller.extend({
|
||||
passwords: {
|
||||
newPassword: '',
|
||||
ne2Password: ''
|
||||
},
|
||||
token: '',
|
||||
submitButtonDisabled: false,
|
||||
actions: {
|
||||
submit: function () {
|
||||
var self = this;
|
||||
this.set('submitButtonDisabled', true);
|
||||
|
||||
this.user.resetPassword(this.passwords, this.token)
|
||||
.then(function () {
|
||||
alert('@TODO Notification : Success');
|
||||
self.transitionToRoute('signin');
|
||||
})
|
||||
.catch(function (response) {
|
||||
alert('@TODO Notification : Failure');
|
||||
console.log(response);
|
||||
})
|
||||
.finally(function () {
|
||||
self.set('submitButtonDisabled', false);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default ResetController;
|
37
core/client/controllers/settings/debug.js
Normal file
37
core/client/controllers/settings/debug.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*global alert, console */
|
||||
|
||||
var Debug = Ember.Controller.extend(Ember.Evented, {
|
||||
uploadButtonText: 'Import',
|
||||
actions: {
|
||||
importData: function (file) {
|
||||
var self = this;
|
||||
this.set('uploadButtonText', 'Importing');
|
||||
this.get('model').importFrom(file)
|
||||
.then(function (response) {
|
||||
console.log(response);
|
||||
alert('@TODO: success');
|
||||
})
|
||||
.catch(function (response) {
|
||||
console.log(response);
|
||||
alert('@TODO: error');
|
||||
})
|
||||
.finally(function () {
|
||||
self.set('uploadButtonText', 'Import');
|
||||
self.trigger('reset');
|
||||
});
|
||||
},
|
||||
sendTestEmail: function () {
|
||||
this.get('model').sendTestEmail()
|
||||
.then(function (response) {
|
||||
console.log(response);
|
||||
alert('@TODO: success');
|
||||
})
|
||||
.catch(function (response) {
|
||||
console.log(response);
|
||||
alert('@TODO: error');
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default Debug;
|
59
core/client/controllers/settings/general.js
Normal file
59
core/client/controllers/settings/general.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
|
||||
var elementLookup = {
|
||||
title: '#blog-title',
|
||||
description: '#blog-description',
|
||||
email: '#email-address',
|
||||
postsPerPage: '#postsPerPage'
|
||||
};
|
||||
|
||||
var SettingsGeneralController = Ember.ObjectController.extend({
|
||||
isDatedPermalinks: function (key, value) {
|
||||
// setter
|
||||
if (arguments.length > 1) {
|
||||
this.set('permalinks', value ? '/:year/:month/:day/:slug/' : '/:slug/');
|
||||
}
|
||||
|
||||
// getter
|
||||
var slugForm = this.get('permalinks');
|
||||
|
||||
return slugForm !== '/:slug/';
|
||||
}.property('permalinks'),
|
||||
|
||||
actions: {
|
||||
'save': function () {
|
||||
// Validate and save settings
|
||||
var model = this.get('model'),
|
||||
// @TODO: Don't know how to scope this to this controllers view because this.view is null
|
||||
errs = model.validate();
|
||||
|
||||
if (errs.length > 0) {
|
||||
// Set the actual element from this view based on the error
|
||||
errs.forEach(function (err) {
|
||||
// @TODO: Probably should still be scoped to this controllers root element.
|
||||
err.el = $(elementLookup[err.el]);
|
||||
});
|
||||
|
||||
// Let the applicationRoute handle validation errors
|
||||
this.send('handleValidationErrors', errs);
|
||||
} else {
|
||||
model.save().then(function () {
|
||||
// @TODO: Notification of success
|
||||
window.alert('Saved data!');
|
||||
}, function () {
|
||||
// @TODO: Notification of error
|
||||
window.alert('Error saving data');
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
'uploadLogo': function () {
|
||||
// @TODO: Integrate with Modal component
|
||||
},
|
||||
|
||||
'uploadCover': function () {
|
||||
// @TODO: Integrate with Modal component
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default SettingsGeneralController;
|
57
core/client/controllers/settings/user.js
Normal file
57
core/client/controllers/settings/user.js
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*global alert */
|
||||
|
||||
var SettingsUserController = Ember.Controller.extend({
|
||||
cover: function () {
|
||||
// @TODO: add {{asset}} subdir path
|
||||
return this.user.getWithDefault('cover', '/shared/img/user-cover.png');
|
||||
}.property('user.cover'),
|
||||
|
||||
coverTitle: function () {
|
||||
return this.get('user.name') + '\'s Cover Image';
|
||||
}.property('user.name'),
|
||||
|
||||
image: function () {
|
||||
// @TODO: add {{asset}} subdir path
|
||||
return 'background-image: url(' + this.user.getWithDefault('image', '/shared/img/user-image.png') + ')';
|
||||
}.property('user.image'),
|
||||
|
||||
actions: {
|
||||
save: function () {
|
||||
alert('@TODO: Saving user...');
|
||||
|
||||
if (this.user.validate().get('isValid')) {
|
||||
this.user.save().then(function (response) {
|
||||
alert('Done saving' + JSON.stringify(response));
|
||||
}, function () {
|
||||
alert('Error saving.');
|
||||
});
|
||||
} else {
|
||||
alert('Errors found! ' + JSON.stringify(this.user.get('errors')));
|
||||
}
|
||||
},
|
||||
|
||||
password: function () {
|
||||
alert('@TODO: Changing password...');
|
||||
var passwordProperties = this.getProperties('password', 'newPassword', 'ne2Password');
|
||||
|
||||
if (this.user.validatePassword(passwordProperties).get('passwordIsValid')) {
|
||||
this.user.saveNewPassword(passwordProperties).then(function () {
|
||||
alert('Success!');
|
||||
// Clear properties from view
|
||||
this.setProperties({
|
||||
'password': '',
|
||||
'newpassword': '',
|
||||
'ne2password': ''
|
||||
});
|
||||
}.bind(this), function (errors) {
|
||||
alert('Errors ' + JSON.stringify(errors));
|
||||
});
|
||||
} else {
|
||||
alert('Errors found! ' + JSON.stringify(this.user.get('passwordErrors')));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default SettingsUserController;
|
60
core/client/fixtures/init.js
Normal file
60
core/client/fixtures/init.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
import postFixtures from 'ghost/fixtures/posts';
|
||||
import userFixtures from 'ghost/fixtures/users';
|
||||
import settingsFixtures from 'ghost/fixtures/settings';
|
||||
|
||||
var response = function (responseBody, status) {
|
||||
status = status || 200;
|
||||
var textStatus = (status === 200) ? 'success' : 'error';
|
||||
|
||||
return {
|
||||
response: responseBody,
|
||||
jqXHR: { status: status },
|
||||
textStatus: textStatus
|
||||
};
|
||||
};
|
||||
|
||||
var user = function (status) {
|
||||
return response(userFixtures.findBy('id', 1), status);
|
||||
};
|
||||
|
||||
var post = function (id, status) {
|
||||
return response(postFixtures.findBy('id', id), status);
|
||||
};
|
||||
|
||||
var posts = function (status) {
|
||||
return response({
|
||||
'posts': postFixtures,
|
||||
'page': 1,
|
||||
'limit': 15,
|
||||
'pages': 1,
|
||||
'total': 2
|
||||
}, status);
|
||||
};
|
||||
|
||||
var settings = function (status) {
|
||||
return response(settingsFixtures, status);
|
||||
};
|
||||
|
||||
var defineFixtures = function (status) {
|
||||
ic.ajax.defineFixture('/ghost/api/v0.1/posts', posts(status));
|
||||
ic.ajax.defineFixture('/ghost/api/v0.1/posts/1', post(1, status));
|
||||
ic.ajax.defineFixture('/ghost/api/v0.1/posts/2', post(2, status));
|
||||
ic.ajax.defineFixture('/ghost/api/v0.1/posts/3', post(3, status));
|
||||
ic.ajax.defineFixture('/ghost/api/v0.1/posts/4', post(4, status));
|
||||
ic.ajax.defineFixture('/ghost/api/v0.1/posts/slug/test%20title/', response('generated-slug', status));
|
||||
|
||||
ic.ajax.defineFixture('/ghost/api/v0.1/signin', user(status));
|
||||
ic.ajax.defineFixture('/ghost/api/v0.1/users/me/', user(status));
|
||||
ic.ajax.defineFixture('/ghost/changepw/', response({
|
||||
msg: 'Password changed successfully'
|
||||
}));
|
||||
ic.ajax.defineFixture('/ghost/api/v0.1/forgotten/', response({
|
||||
redirect: '/ghost/signin/'
|
||||
}));
|
||||
ic.ajax.defineFixture('/ghost/api/v0.1/reset/', response({
|
||||
msg: 'Password changed successfully'
|
||||
}));
|
||||
ic.ajax.defineFixture('/ghost/api/v0.1/settings/?type=blog,theme,app', settings(status));
|
||||
};
|
||||
|
||||
export default defineFixtures;
|
269
core/client/fixtures/posts.js
Normal file
269
core/client/fixtures/posts.js
Normal file
|
@ -0,0 +1,269 @@
|
|||
var posts = [
|
||||
{
|
||||
"id": 4,
|
||||
"uuid": "4dc16b9e-bf90-44c9-97c5-40a0a81e8297",
|
||||
"title": "This post is featured",
|
||||
"slug": "this-post-is-featured",
|
||||
"markdown": "Lorem **ipsum** dolor sit amet, consectetur adipiscing elit. Fusce id felis nec est suscipit scelerisque vitae eu arcu. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aliquam erat volutpat. Sed pellentesque metus vel velit tincidunt aliquet. Nunc condimentum tempus convallis. Sed tincidunt, leo et congue blandit, lorem tortor imperdiet sapien, et porttitor turpis nisl sed tellus. In ultrices urna sit amet mauris suscipit adipiscing.",
|
||||
"html": "<p>Lorem <strong>ipsum<\/strong> dolor sit amet, consectetur adipiscing elit. Fusce id felis nec est suscipit scelerisque vitae eu arcu. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aliquam erat volutpat. Sed pellentesque metus vel velit tincidunt aliquet. Nunc condimentum tempus convallis. Sed tincidunt, leo et congue blandit, lorem tortor imperdiet sapien, et porttitor turpis nisl sed tellus. In ultrices urna sit amet mauris suscipit adipiscing.<\/p>",
|
||||
"image": null,
|
||||
"featured": 1,
|
||||
"page": 0,
|
||||
"status": "published",
|
||||
"language": "en_US",
|
||||
"meta_title": null,
|
||||
"meta_description": null,
|
||||
"author_id": 1,
|
||||
"created_at": "2014-02-15T23:27:08.000Z",
|
||||
"created_by": 1,
|
||||
"updated_at": "2014-02-15T23:27:08.000Z",
|
||||
"updated_by": 1,
|
||||
"published_at": "2014-02-15T23:27:08.000Z",
|
||||
"published_by": 1,
|
||||
"author": {
|
||||
"id": 1,
|
||||
"uuid": "ba9c67e4-8046-4b8c-9349-0eed3cca7529",
|
||||
"name": "Bill Murray",
|
||||
"slug": "manuel_mitasch",
|
||||
"email": "manuel@cms.mine.nu",
|
||||
"image": null,
|
||||
"cover": null,
|
||||
"bio": null,
|
||||
"website": null,
|
||||
"location": null,
|
||||
"accessibility": null,
|
||||
"status": "active",
|
||||
"language": "en_US",
|
||||
"meta_title": null,
|
||||
"meta_description": null,
|
||||
"created_at": "2014-02-15T20:02:25.000Z",
|
||||
"updated_at": "2014-02-15T20:02:25.000Z"
|
||||
},
|
||||
"user": {
|
||||
"id": 1,
|
||||
"uuid": "ba9c67e4-8046-4b8c-9349-0eed3cca7529",
|
||||
"name": "manuel_mitasch",
|
||||
"slug": "manuel_mitasch",
|
||||
"email": "manuel@cms.mine.nu",
|
||||
"image": null,
|
||||
"cover": null,
|
||||
"bio": null,
|
||||
"website": null,
|
||||
"location": null,
|
||||
"accessibility": null,
|
||||
"status": "active",
|
||||
"language": "en_US",
|
||||
"meta_title": null,
|
||||
"meta_description": null,
|
||||
"created_at": "2014-02-15T20:02:25.000Z",
|
||||
"updated_at": "2014-02-15T20:02:25.000Z"
|
||||
},
|
||||
"tags": [
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"uuid": "4dc16b9e-bf90-44c9-97c5-40a0a81e8297",
|
||||
"title": "Example page entry",
|
||||
"slug": "example-page-entry",
|
||||
"markdown": "Lorem **ipsum** dolor sit amet, consectetur adipiscing elit. Fusce id felis nec est suscipit scelerisque vitae eu arcu. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aliquam erat volutpat. Sed pellentesque metus vel velit tincidunt aliquet. Nunc condimentum tempus convallis. Sed tincidunt, leo et congue blandit, lorem tortor imperdiet sapien, et porttitor turpis nisl sed tellus. In ultrices urna sit amet mauris suscipit adipiscing.",
|
||||
"html": "<p>Lorem <strong>ipsum<\/strong> dolor sit amet, consectetur adipiscing elit. Fusce id felis nec est suscipit scelerisque vitae eu arcu. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aliquam erat volutpat. Sed pellentesque metus vel velit tincidunt aliquet. Nunc condimentum tempus convallis. Sed tincidunt, leo et congue blandit, lorem tortor imperdiet sapien, et porttitor turpis nisl sed tellus. In ultrices urna sit amet mauris suscipit adipiscing.<\/p>",
|
||||
"image": null,
|
||||
"featured": 0,
|
||||
"page": 1,
|
||||
"status": "published",
|
||||
"language": "en_US",
|
||||
"meta_title": null,
|
||||
"meta_description": null,
|
||||
"author_id": 1,
|
||||
"created_at": "2014-02-15T23:27:08.000Z",
|
||||
"created_by": 1,
|
||||
"updated_at": "2014-02-15T23:27:08.000Z",
|
||||
"updated_by": 1,
|
||||
"published_at": null,
|
||||
"published_by": null,
|
||||
"author": {
|
||||
"id": 1,
|
||||
"uuid": "ba9c67e4-8046-4b8c-9349-0eed3cca7529",
|
||||
"name": "Slimer",
|
||||
"slug": "manuel_mitasch",
|
||||
"email": "manuel@cms.mine.nu",
|
||||
"image": null,
|
||||
"cover": null,
|
||||
"bio": null,
|
||||
"website": null,
|
||||
"location": null,
|
||||
"accessibility": null,
|
||||
"status": "active",
|
||||
"language": "en_US",
|
||||
"meta_title": null,
|
||||
"meta_description": null,
|
||||
"created_at": "2014-02-15T20:02:25.000Z",
|
||||
"updated_at": "2014-02-15T20:02:25.000Z"
|
||||
},
|
||||
"user": {
|
||||
"id": 1,
|
||||
"uuid": "ba9c67e4-8046-4b8c-9349-0eed3cca7529",
|
||||
"name": "manuel_mitasch",
|
||||
"slug": "manuel_mitasch",
|
||||
"email": "manuel@cms.mine.nu",
|
||||
"image": null,
|
||||
"cover": null,
|
||||
"bio": null,
|
||||
"website": null,
|
||||
"location": null,
|
||||
"accessibility": null,
|
||||
"status": "active",
|
||||
"language": "en_US",
|
||||
"meta_title": null,
|
||||
"meta_description": null,
|
||||
"created_at": "2014-02-15T20:02:25.000Z",
|
||||
"updated_at": "2014-02-15T20:02:25.000Z"
|
||||
},
|
||||
"tags": [
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"uuid": "4dc1cb9e-bf90-44c9-97c5-40a8381e8297",
|
||||
"title": "Dummy draft post",
|
||||
"slug": "dummy-draft-post",
|
||||
"markdown": "Lorem **ipsum** dolor sit amet, consectetur adipiscing elit. Fusce id felis nec est suscipit scelerisque vitae eu arcu. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aliquam erat volutpat. Sed pellentesque metus vel velit tincidunt aliquet. Nunc condimentum tempus convallis. Sed tincidunt, leo et congue blandit, lorem tortor imperdiet sapien, et porttitor turpis nisl sed tellus. In ultrices urna sit amet mauris suscipit adipiscing.",
|
||||
"html": "<p>Lorem <strong>ipsum<\/strong> dolor sit amet, consectetur adipiscing elit. Fusce id felis nec est suscipit scelerisque vitae eu arcu. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aliquam erat volutpat. Sed pellentesque metus vel velit tincidunt aliquet. Nunc condimentum tempus convallis. Sed tincidunt, leo et congue blandit, lorem tortor imperdiet sapien, et porttitor turpis nisl sed tellus. In ultrices urna sit amet mauris suscipit adipiscing.<\/p>",
|
||||
"image": null,
|
||||
"featured": 0,
|
||||
"page": 0,
|
||||
"status": "draft",
|
||||
"language": "en_US",
|
||||
"meta_title": null,
|
||||
"meta_description": null,
|
||||
"author_id": 1,
|
||||
"created_at": "2014-02-15T23:27:08.000Z",
|
||||
"created_by": 1,
|
||||
"updated_at": "2014-02-15T23:27:08.000Z",
|
||||
"updated_by": 1,
|
||||
"published_at": null,
|
||||
"published_by": null,
|
||||
"author": {
|
||||
"id": 1,
|
||||
"uuid": "ba9c67e4-8046-4b8c-9349-0eed3cca7529",
|
||||
"name": "manuel_mitasch",
|
||||
"slug": "manuel_mitasch",
|
||||
"email": "manuel@cms.mine.nu",
|
||||
"image": null,
|
||||
"cover": null,
|
||||
"bio": null,
|
||||
"website": null,
|
||||
"location": null,
|
||||
"accessibility": null,
|
||||
"status": "active",
|
||||
"language": "en_US",
|
||||
"meta_title": null,
|
||||
"meta_description": null,
|
||||
"created_at": "2014-02-15T20:02:25.000Z",
|
||||
"updated_at": "2014-02-15T20:02:25.000Z"
|
||||
},
|
||||
"user": {
|
||||
"id": 1,
|
||||
"uuid": "ba9c67e4-8046-4b8c-9349-0eed3cca7529",
|
||||
"name": "manuel_mitasch",
|
||||
"slug": "manuel_mitasch",
|
||||
"email": "manuel@cms.mine.nu",
|
||||
"image": null,
|
||||
"cover": null,
|
||||
"bio": null,
|
||||
"website": null,
|
||||
"location": null,
|
||||
"accessibility": null,
|
||||
"status": "active",
|
||||
"language": "en_US",
|
||||
"meta_title": null,
|
||||
"meta_description": null,
|
||||
"created_at": "2014-02-15T20:02:25.000Z",
|
||||
"updated_at": "2014-02-15T20:02:25.000Z"
|
||||
},
|
||||
"tags": [
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"uuid": "4b96025d-050c-47ff-8bd4-047e4843b302",
|
||||
"title": "Welcome to Ghost",
|
||||
"slug": "welcome-to-ghost",
|
||||
"markdown": "You're live! Nice. We've put together a little post to introduce you to the Ghost editor and get you started. You can manage your content by signing in to the admin area at `<your blog URL>\/ghost\/`. When you arrive, you can select this post from a list on the left and see a preview of it on the right. Click the little pencil icon at the top of the preview to edit this post and read the next section!\n\n## Getting Started\n\nGhost uses something called Markdown for writing. Essentially, it's a shorthand way to manage your post formatting as you write!\n\nWriting in Markdown is really easy. In the left hand panel of Ghost, you simply write as you normally would. Where appropriate, you can use *shortcuts* to **style** your content. For example, a list:\n\n* Item number one\n* Item number two\n * A nested item\n* A final item\n\nor with numbers!\n\n1. Remember to buy some milk\n2. Drink the milk\n3. Tweet that I remembered to buy the milk, and drank it\n\n### Links\n\nWant to link to a source? No problem. If you paste in url, like http:\/\/ghost.org - it'll automatically be linked up. But if you want to customise your anchor text, you can do that too! Here's a link to [the Ghost website](http:\/\/ghost.org). Neat.\n\n### What about Images?\n\nImages work too! Already know the URL of the image you want to include in your article? Simply paste it in like this to make it show up:\n\n\n\nNot sure which image you want to use yet? That's ok too. Leave yourself a descriptive placeholder and keep writing. Come back later and drag and drop the image in to upload:\n\n![A bowl of bananas]\n\n\n### Quoting\n\nSometimes a link isn't enough, you want to quote someone on what they've said. It was probably very wisdomous. Is wisdomous a word? Find out in a future release when we introduce spellcheck! For now - it's definitely a word.\n\n> Wisdomous - it's definitely a word.\n\n### Working with Code\n\nGot a streak of geek? We've got you covered there, too. You can write inline `<code>` blocks really easily with back ticks. Want to show off something more comprehensive? 4 spaces of indentation gets you there.\n\n .awesome-thing {\n display: block;\n width: 100%;\n }\n\n### Ready for a Break? \n\nThrow 3 or more dashes down on any new line and you've got yourself a fancy new divider. Aw yeah.\n\n---\n\n### Advanced Usage\n\nThere's one fantastic secret about Markdown. If you want, you can write plain old HTML and it'll still work! Very flexible.\n\n<input type=\"text\" placeholder=\"I'm an input field!\" \/>\n\nThat should be enough to get you started. Have fun - and let us know what you think :)",
|
||||
"html": "<p>You're live! Nice. We've put together a little post to introduce you to the Ghost editor and get you started. You can manage your content by signing in to the admin area at <code><your blog URL>\/ghost\/<\/code>. When you arrive, you can select this post from a list on the left and see a preview of it on the right. Click the little pencil icon at the top of the preview to edit this post and read the next section!<\/p>\n\n<h2 id=\"gettingstarted\">Getting Started<\/h2>\n\n<p>Ghost uses something called Markdown for writing. Essentially, it's a shorthand way to manage your post formatting as you write!<\/p>\n\n<p>Writing in Markdown is really easy. In the left hand panel of Ghost, you simply write as you normally would. Where appropriate, you can use <em>shortcuts<\/em> to <strong>style<\/strong> your content. For example, a list:<\/p>\n\n<ul>\n<li>Item number one<\/li>\n<li>Item number two\n<ul><li>A nested item<\/li><\/ul><\/li>\n<li>A final item<\/li>\n<\/ul>\n\n<p>or with numbers!<\/p>\n\n<ol>\n<li>Remember to buy some milk <\/li>\n<li>Drink the milk <\/li>\n<li>Tweet that I remembered to buy the milk, and drank it<\/li>\n<\/ol>\n\n<h3 id=\"links\">Links<\/h3>\n\n<p>Want to link to a source? No problem. If you paste in url, like <a href='http:\/\/ghost.org'>http:\/\/ghost.org<\/a> - it'll automatically be linked up. But if you want to customise your anchor text, you can do that too! Here's a link to <a href=\"http:\/\/ghost.org\">the Ghost website<\/a>. Neat.<\/p>\n\n<h3 id=\"whataboutimages\">What about Images?<\/h3>\n\n<p>Images work too! Already know the URL of the image you want to include in your article? Simply paste it in like this to make it show up:<\/p>\n\n<p><img src=\"https:\/\/ghost.org\/images\/ghost.png\" alt=\"The Ghost Logo\" \/><\/p>\n\n<p>Not sure which image you want to use yet? That's ok too. Leave yourself a descriptive placeholder and keep writing. Come back later and drag and drop the image in to upload:<\/p>\n\n<h3 id=\"quoting\">Quoting<\/h3>\n\n<p>Sometimes a link isn't enough, you want to quote someone on what they've said. It was probably very wisdomous. Is wisdomous a word? Find out in a future release when we introduce spellcheck! For now - it's definitely a word.<\/p>\n\n<blockquote>\n <p>Wisdomous - it's definitely a word.<\/p>\n<\/blockquote>\n\n<h3 id=\"workingwithcode\">Working with Code<\/h3>\n\n<p>Got a streak of geek? We've got you covered there, too. You can write inline <code><code><\/code> blocks really easily with back ticks. Want to show off something more comprehensive? 4 spaces of indentation gets you there.<\/p>\n\n<pre><code>.awesome-thing {\n display: block;\n width: 100%;\n}\n<\/code><\/pre>\n\n<h3 id=\"readyforabreak\">Ready for a Break?<\/h3>\n\n<p>Throw 3 or more dashes down on any new line and you've got yourself a fancy new divider. Aw yeah.<\/p>\n\n<hr \/>\n\n<h3 id=\"advancedusage\">Advanced Usage<\/h3>\n\n<p>There's one fantastic secret about Markdown. If you want, you can write plain old HTML and it'll still work! Very flexible.<\/p>\n\n<p><input type=\"text\" placeholder=\"I'm an input field!\" \/><\/p>\n\n<p>That should be enough to get you started. Have fun - and let us know what you think :)<\/p>",
|
||||
"image": null,
|
||||
"featured": 0,
|
||||
"page": 0,
|
||||
"status": "published",
|
||||
"language": "en_US",
|
||||
"meta_title": null,
|
||||
"meta_description": null,
|
||||
"author_id": 1,
|
||||
"created_at": "2014-02-15T20:02:01.000Z",
|
||||
"created_by": 1,
|
||||
"updated_at": "2014-02-15T20:02:01.000Z",
|
||||
"updated_by": 1,
|
||||
"published_at": "2014-02-15T20:02:01.000Z",
|
||||
"published_by": 1,
|
||||
"author": {
|
||||
"id": 1,
|
||||
"uuid": "ba9c67e4-8046-4b8c-9349-0eed3cca7529",
|
||||
"name": "manuel_mitasch",
|
||||
"slug": "manuel_mitasch",
|
||||
"email": "manuel@cms.mine.nu",
|
||||
"image": null,
|
||||
"cover": null,
|
||||
"bio": null,
|
||||
"website": null,
|
||||
"location": null,
|
||||
"accessibility": null,
|
||||
"status": "active",
|
||||
"language": "en_US",
|
||||
"meta_title": null,
|
||||
"meta_description": null,
|
||||
"created_at": "2014-02-15T20:02:25.000Z",
|
||||
"updated_at": "2014-02-15T20:02:25.000Z"
|
||||
},
|
||||
"user": {
|
||||
"id": 1,
|
||||
"uuid": "ba9c67e4-8046-4b8c-9349-0eed3cca7529",
|
||||
"name": "manuel_mitasch",
|
||||
"slug": "manuel_mitasch",
|
||||
"email": "manuel@cms.mine.nu",
|
||||
"image": null,
|
||||
"cover": null,
|
||||
"bio": null,
|
||||
"website": null,
|
||||
"location": null,
|
||||
"accessibility": null,
|
||||
"status": "active",
|
||||
"language": "en_US",
|
||||
"meta_title": null,
|
||||
"meta_description": null,
|
||||
"created_at": "2014-02-15T20:02:25.000Z",
|
||||
"updated_at": "2014-02-15T20:02:25.000Z"
|
||||
},
|
||||
"tags": [
|
||||
{
|
||||
"id": 1,
|
||||
"uuid": "406edaaf-5b1c-4199-b297-2af90b1de1a7",
|
||||
"name": "Getting Started",
|
||||
"slug": "getting-started",
|
||||
"description": null,
|
||||
"parent_id": null,
|
||||
"meta_title": null,
|
||||
"meta_description": null,
|
||||
"created_at": "2014-02-15T20:02:01.000Z",
|
||||
"created_by": 1,
|
||||
"updated_at": "2014-02-15T20:02:01.000Z",
|
||||
"updated_by": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
export default posts;
|
24
core/client/fixtures/settings.js
Normal file
24
core/client/fixtures/settings.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
var settings = {
|
||||
"title": "Ghost",
|
||||
"description": "Just a blogging platform.",
|
||||
"email": "ghost@tryghost.org",
|
||||
"logo": "",
|
||||
"cover": "",
|
||||
"defaultLang": "en_US",
|
||||
"postsPerPage": "6",
|
||||
"forceI18n": "true",
|
||||
"permalinks": "/:slug/",
|
||||
"activeTheme": "casper",
|
||||
"activeApps": "[]",
|
||||
"installedApps": "[]",
|
||||
"availableThemes": [
|
||||
{
|
||||
"name": "casper",
|
||||
"package": false,
|
||||
"active": true
|
||||
}
|
||||
],
|
||||
"availableApps": []
|
||||
};
|
||||
|
||||
export default settings;
|
23
core/client/fixtures/users.js
Normal file
23
core/client/fixtures/users.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
var users = [
|
||||
{
|
||||
"id": 1,
|
||||
"uuid": "ba9c67e4-8046-4b8c-9349-0eed3cca7529",
|
||||
"name": "some-user",
|
||||
"slug": "some-user",
|
||||
"email": "some@email.com",
|
||||
"image": undefined,
|
||||
"cover": undefined,
|
||||
"bio": "Example bio",
|
||||
"website": "",
|
||||
"location": "Imaginationland",
|
||||
"accessibility": undefined,
|
||||
"status": "active",
|
||||
"language": "en_US",
|
||||
"meta_title": undefined,
|
||||
"meta_description": undefined,
|
||||
"created_at": "2014-02-15T20:02:25.000Z",
|
||||
"updated_at": "2014-03-11T14:06:43.000Z"
|
||||
}
|
||||
];
|
||||
|
||||
export default users;
|
7
core/client/helpers/count-words.js
Normal file
7
core/client/helpers/count-words.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import count from 'ghost/utils/word-count';
|
||||
|
||||
var countWords = Ember.Handlebars.makeBoundHelper(function (markdown) {
|
||||
return count(markdown || '');
|
||||
});
|
||||
|
||||
export default countWords;
|
8
core/client/helpers/format-markdown.js
Normal file
8
core/client/helpers/format-markdown.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
/* global Showdown, Handlebars */
|
||||
var showdown = new Showdown.converter();
|
||||
|
||||
var formatMarkdown = Ember.Handlebars.makeBoundHelper(function (markdown) {
|
||||
return new Handlebars.SafeString(showdown.makeHtml(markdown || ''));
|
||||
});
|
||||
|
||||
export default formatMarkdown;
|
9
core/client/helpers/format-timeago.js
Normal file
9
core/client/helpers/format-timeago.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
/* global moment */
|
||||
var formatTimeago = Ember.Handlebars.makeBoundHelper(function (timeago) {
|
||||
return moment(timeago).fromNow();
|
||||
// stefanpenner says cool for small number of timeagos.
|
||||
// For large numbers moment sucks => single Ember.Object based clock better
|
||||
// https://github.com/manuelmitasch/ghost-admin-ember-demo/commit/fba3ab0a59238290c85d4fa0d7c6ed1be2a8a82e#commitcomment-5396524
|
||||
});
|
||||
|
||||
export default formatTimeago;
|
26
core/client/initializers/current-user.js
Normal file
26
core/client/initializers/current-user.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
import User from 'ghost/models/user';
|
||||
import userFixtures from 'ghost/fixtures/users';
|
||||
|
||||
var currentUser = {
|
||||
name: 'currentUser',
|
||||
|
||||
initialize: function (container) {
|
||||
container.register('user:current', User);
|
||||
}
|
||||
};
|
||||
|
||||
var injectCurrentUser = {
|
||||
name: 'injectCurrentUser',
|
||||
|
||||
initialize: function (container) {
|
||||
if (container.lookup('user:current')) {
|
||||
// @TODO: remove userFixture
|
||||
container.lookup('user:current').setProperties(userFixtures.findBy('id', 1));
|
||||
|
||||
container.injection('route', 'user', 'user:current');
|
||||
container.injection('controller', 'user', 'user:current');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export {currentUser, injectCurrentUser};
|
21
core/client/initializers/notifications.js
Normal file
21
core/client/initializers/notifications.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
import Notifications from 'ghost/utils/notifications';
|
||||
|
||||
var registerNotifications = {
|
||||
name: 'registerNotifications',
|
||||
|
||||
initialize: function (container, application) {
|
||||
application.register('notifications:main', Notifications);
|
||||
}
|
||||
};
|
||||
|
||||
var injectNotifications = {
|
||||
name: 'injectNotifications',
|
||||
|
||||
initialize: function (container, application) {
|
||||
application.inject('controller', 'notifications', 'notifications:main');
|
||||
application.inject('component', 'notifications', 'notifications:main');
|
||||
application.inject('route', 'notifications', 'notifications:main');
|
||||
}
|
||||
};
|
||||
|
||||
export {registerNotifications, injectNotifications};
|
27
core/client/mixins/style-body.js
Normal file
27
core/client/mixins/style-body.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
// mixin used for routes that need to set a css className on the body tag
|
||||
|
||||
var styleBody = Ember.Mixin.create({
|
||||
activate: function () {
|
||||
var cssClasses = this.get('classNames');
|
||||
|
||||
if (cssClasses) {
|
||||
Ember.run.schedule('afterRender', null, function () {
|
||||
cssClasses.forEach(function (curClass) {
|
||||
Ember.$('body').addClass(curClass);
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
deactivate: function () {
|
||||
var cssClasses = this.get('classNames');
|
||||
|
||||
Ember.run.schedule('afterRender', null, function () {
|
||||
cssClasses.forEach(function (curClass) {
|
||||
Ember.$('body').removeClass(curClass);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default styleBody;
|
|
@ -1,35 +1,35 @@
|
|||
/*global Ghost, _, Backbone, NProgress */
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
NProgress.configure({ showSpinner: false });
|
||||
function ghostPaths() {
|
||||
var path = window.location.pathname,
|
||||
subdir = path.substr(0, path.search('/ghost/'));
|
||||
|
||||
// Adds in a call to start a loading bar
|
||||
// This is sets up a success function which completes the loading bar
|
||||
function wrapSync(method, model, options) {
|
||||
if (options !== undefined && _.isObject(options)) {
|
||||
NProgress.start();
|
||||
return {
|
||||
subdir: subdir,
|
||||
adminRoot: subdir + '/ghost',
|
||||
apiRoot: subdir + '/ghost/api/v0.1'
|
||||
};
|
||||
}
|
||||
|
||||
/*jshint validthis:true */
|
||||
var self = this,
|
||||
oldSuccess = options.success;
|
||||
/*jshint validthis:false */
|
||||
var BaseModel = Ember.Object.extend({
|
||||
|
||||
options.success = function () {
|
||||
NProgress.done();
|
||||
return oldSuccess.apply(self, arguments);
|
||||
};
|
||||
}
|
||||
fetch: function () {
|
||||
return ic.ajax.request(this.url, {
|
||||
type: 'GET'
|
||||
});
|
||||
},
|
||||
|
||||
/*jshint validthis:true */
|
||||
return Backbone.sync.call(this, method, model, options);
|
||||
save: function () {
|
||||
return ic.ajax.request(this.url, {
|
||||
type: 'PUT',
|
||||
dataType: 'json',
|
||||
// @TODO: This is passing _oldWillDestory and _willDestroy and should not.
|
||||
data: JSON.stringify(this.getProperties(Ember.keys(this)))
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Ghost.ProgressModel = Backbone.Model.extend({
|
||||
sync: wrapSync
|
||||
});
|
||||
BaseModel.apiRoot = ghostPaths().apiRoot;
|
||||
BaseModel.subdir = ghostPaths().subdir;
|
||||
BaseModel.adminRoot = ghostPaths().adminRoot;
|
||||
|
||||
Ghost.ProgressCollection = Backbone.Collection.extend({
|
||||
sync: wrapSync
|
||||
});
|
||||
}());
|
||||
export default BaseModel;
|
|
@ -1,83 +1,56 @@
|
|||
/*global Ghost, _, Backbone, JSON */
|
||||
(function () {
|
||||
'use strict';
|
||||
import BaseModel from 'ghost/models/base';
|
||||
|
||||
Ghost.Models.Post = Ghost.ProgressModel.extend({
|
||||
var PostModel = BaseModel.extend({
|
||||
url: BaseModel.apiRoot + '/posts/',
|
||||
|
||||
defaults: {
|
||||
status: 'draft'
|
||||
},
|
||||
generateSlug: function () {
|
||||
// @TODO Make this request use this.get('title') once we're an actual user
|
||||
var url = this.get('url') + 'slug/' + encodeURIComponent('test title') + '/';
|
||||
return ic.ajax.request(url, {
|
||||
type: 'GET'
|
||||
});
|
||||
},
|
||||
|
||||
blacklist: ['published', 'draft'],
|
||||
save: function (properties) {
|
||||
var url = this.url,
|
||||
self = this,
|
||||
type,
|
||||
validationErrors = this.validate();
|
||||
|
||||
parse: function (resp) {
|
||||
|
||||
if (resp.posts) {
|
||||
resp = resp.posts[0];
|
||||
}
|
||||
if (resp.status) {
|
||||
resp.published = resp.status === 'published';
|
||||
resp.draft = resp.status === 'draft';
|
||||
}
|
||||
if (resp.tags) {
|
||||
return resp;
|
||||
}
|
||||
return resp;
|
||||
},
|
||||
|
||||
validate: function (attrs) {
|
||||
if (_.isEmpty(attrs.title)) {
|
||||
return 'You must specify a title for the post.';
|
||||
}
|
||||
},
|
||||
|
||||
addTag: function (tagToAdd) {
|
||||
var tags = this.get('tags') || [];
|
||||
tags.push(tagToAdd);
|
||||
this.set('tags', tags);
|
||||
},
|
||||
|
||||
removeTag: function (tagToRemove) {
|
||||
var tags = this.get('tags') || [];
|
||||
tags = _.reject(tags, function (tag) {
|
||||
return tag.id === tagToRemove.id || tag.name === tagToRemove.name;
|
||||
if (validationErrors.length) {
|
||||
return Ember.RSVP.Promise(function (resolve, reject) {
|
||||
return reject(validationErrors);
|
||||
});
|
||||
this.set('tags', tags);
|
||||
},
|
||||
sync: function (method, model, options) {
|
||||
//wrap post in {posts: [{...}]}
|
||||
if (method === 'create' || method === 'update') {
|
||||
options.data = JSON.stringify({posts: [this.attributes]});
|
||||
options.contentType = 'application/json';
|
||||
options.url = model.url() + '?include=tags';
|
||||
}
|
||||
|
||||
return Backbone.Model.prototype.sync.apply(this, arguments);
|
||||
}
|
||||
});
|
||||
|
||||
Ghost.Collections.Posts = Backbone.Collection.extend({
|
||||
currentPage: 1,
|
||||
totalPages: 0,
|
||||
totalPosts: 0,
|
||||
nextPage: 0,
|
||||
prevPage: 0,
|
||||
|
||||
url: Ghost.paths.apiRoot + '/posts/',
|
||||
model: Ghost.Models.Post,
|
||||
|
||||
parse: function (resp) {
|
||||
if (_.isArray(resp.posts)) {
|
||||
this.limit = resp.meta.pagination.limit;
|
||||
this.currentPage = resp.meta.pagination.page;
|
||||
this.totalPages = resp.meta.pagination.pages;
|
||||
this.totalPosts = resp.meta.pagination.total;
|
||||
this.nextPage = resp.meta.pagination.next;
|
||||
this.prevPage = resp.meta.pagination.prev;
|
||||
return resp.posts;
|
||||
}
|
||||
return resp;
|
||||
//If specific properties are being saved,
|
||||
//this is an edit. Otherwise, it's an add.
|
||||
if (properties && properties.length > 0) {
|
||||
type = 'PUT';
|
||||
url += this.get('id');
|
||||
} else {
|
||||
type = 'POST';
|
||||
properties = Ember.keys(this);
|
||||
}
|
||||
});
|
||||
|
||||
}());
|
||||
return ic.ajax.request(url, {
|
||||
type: type,
|
||||
data: this.getProperties(properties)
|
||||
}).then(function (model) {
|
||||
return self.setProperties(model);
|
||||
});
|
||||
},
|
||||
validate: function () {
|
||||
var validationErrors = [];
|
||||
|
||||
if (!(this.get('title') && this.get('title').length)) {
|
||||
validationErrors.push({
|
||||
message: "You must specify a title for the post."
|
||||
});
|
||||
}
|
||||
|
||||
return validationErrors;
|
||||
}
|
||||
});
|
||||
|
||||
export default PostModel;
|
|
@ -1,33 +1,76 @@
|
|||
/*global Backbone, Ghost, _ */
|
||||
(function () {
|
||||
'use strict';
|
||||
//id:0 is used to issue PUT requests
|
||||
Ghost.Models.Settings = Ghost.ProgressModel.extend({
|
||||
url: Ghost.paths.apiRoot + '/settings/?type=blog,theme,app',
|
||||
id: '0',
|
||||
var validator = window.validator;
|
||||
|
||||
parse: function (response) {
|
||||
var result = _.reduce(response.settings, function (settings, setting) {
|
||||
settings[setting.key] = setting.value;
|
||||
import BaseModel from 'ghost/models/base';
|
||||
|
||||
return settings;
|
||||
}, {});
|
||||
var SettingsModel = BaseModel.extend({
|
||||
url: BaseModel.apiRoot + '/settings/?type=blog,theme,app',
|
||||
|
||||
return result;
|
||||
},
|
||||
title: null,
|
||||
description: null,
|
||||
email: null,
|
||||
logo: null,
|
||||
cover: null,
|
||||
defaultLang: null,
|
||||
postsPerPage: null,
|
||||
forceI18n: null,
|
||||
permalinks: null,
|
||||
activeTheme: null,
|
||||
activeApps: null,
|
||||
installedApps: null,
|
||||
availableThemes: null,
|
||||
availableApps: null,
|
||||
|
||||
sync: function (method, model, options) {
|
||||
var settings = _.map(this.attributes, function (value, key) {
|
||||
return { key: key, value: value };
|
||||
});
|
||||
//wrap settings in {settings: [{...}]}
|
||||
if (method === 'update') {
|
||||
options.data = JSON.stringify({settings: settings});
|
||||
options.contentType = 'application/json';
|
||||
}
|
||||
validate: function () {
|
||||
var validationErrors = [],
|
||||
postsPerPage;
|
||||
|
||||
return Backbone.Model.prototype.sync.apply(this, arguments);
|
||||
if (!validator.isLength(this.get('title'), 0, 150)) {
|
||||
validationErrors.push({message: "Title is too long", el: 'title'});
|
||||
}
|
||||
});
|
||||
|
||||
}());
|
||||
if (!validator.isLength(this.get('description'), 0, 200)) {
|
||||
validationErrors.push({message: "Description is too long", el: 'description'});
|
||||
}
|
||||
|
||||
if (!validator.isEmail(this.get('email')) || !validator.isLength(this.get('email'), 0, 254)) {
|
||||
validationErrors.push({message: "Please supply a valid email address", el: 'email'});
|
||||
}
|
||||
|
||||
postsPerPage = this.get('postsPerPage');
|
||||
if (!validator.isInt(postsPerPage) || postsPerPage > 1000) {
|
||||
validationErrors.push({message: "Please use a number less than 1000", el: 'postsPerPage'});
|
||||
}
|
||||
|
||||
if (!validator.isInt(postsPerPage) || postsPerPage < 0) {
|
||||
validationErrors.push({message: "Please use a number greater than 0", el: 'postsPerPage'});
|
||||
}
|
||||
|
||||
return validationErrors;
|
||||
},
|
||||
exportPath: BaseModel.adminRoot + '/export/',
|
||||
importFrom: function (file) {
|
||||
var formData = new FormData();
|
||||
formData.append('importfile', file);
|
||||
return ic.ajax.request(BaseModel.apiRoot + '/db/', {
|
||||
headers: {
|
||||
'X-CSRF-Token': $('meta[name="csrf-param"]').attr('content')
|
||||
},
|
||||
type: 'POST',
|
||||
data: formData,
|
||||
dataType: 'json',
|
||||
cache: false,
|
||||
contentType: false,
|
||||
processData: false
|
||||
});
|
||||
},
|
||||
sendTestEmail: function () {
|
||||
return ic.ajax.request(BaseModel.apiRoot + '/mail/test/', {
|
||||
type: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-Token': $('meta[name="csrf-param"]').attr('content')
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default SettingsModel;
|
||||
|
|
|
@ -1,32 +1,124 @@
|
|||
/*global Ghost,Backbone */
|
||||
(function () {
|
||||
'use strict';
|
||||
import BaseModel from 'ghost/models/base';
|
||||
|
||||
Ghost.Models.User = Ghost.ProgressModel.extend({
|
||||
url: Ghost.paths.apiRoot + '/users/me/',
|
||||
var UserModel = BaseModel.extend({
|
||||
url: BaseModel.apiRoot + '/users/me/',
|
||||
forgottenUrl: BaseModel.apiRoot + '/forgotten/',
|
||||
resetUrl: BaseModel.apiRoot + '/reset/',
|
||||
|
||||
parse: function (resp) {
|
||||
// unwrap user from {users: [{...}]}
|
||||
if (resp.users) {
|
||||
resp = resp.users[0];
|
||||
}
|
||||
save: function () {
|
||||
return ic.ajax.request(this.url, {
|
||||
type: 'POST',
|
||||
data: this.getProperties(Ember.keys(this))
|
||||
});
|
||||
},
|
||||
|
||||
return resp;
|
||||
},
|
||||
validate: function () {
|
||||
var validationErrors = [];
|
||||
|
||||
sync: function (method, model, options) {
|
||||
// wrap user in {users: [{...}]}
|
||||
if (method === 'create' || method === 'update') {
|
||||
options.data = JSON.stringify({users: [this.attributes]});
|
||||
options.contentType = 'application/json';
|
||||
}
|
||||
|
||||
return Backbone.Model.prototype.sync.apply(this, arguments);
|
||||
if (!validator.isLength(this.get('name'), 0, 150)) {
|
||||
validationErrors.push({message: "Name is too long"});
|
||||
}
|
||||
});
|
||||
|
||||
// Ghost.Collections.Users = Backbone.Collection.extend({
|
||||
// url: Ghost.paths.apiRoot + '/users/'
|
||||
// });
|
||||
if (!validator.isLength(this.get('bio'), 0, 200)) {
|
||||
validationErrors.push({message: "Bio is too long"});
|
||||
}
|
||||
|
||||
}());
|
||||
if (!validator.isEmail(this.get('email'))) {
|
||||
validationErrors.push({message: "Please supply a valid email address"});
|
||||
}
|
||||
|
||||
if (!validator.isLength(this.get('location'), 0, 150)) {
|
||||
validationErrors.push({message: "Location is too long"});
|
||||
}
|
||||
|
||||
if (this.get('website').length) {
|
||||
if (!validator.isURL(this.get('website')) ||
|
||||
!validator.isLength(this.get('website'), 0, 2000)) {
|
||||
validationErrors.push({message: "Please use a valid url"});
|
||||
}
|
||||
}
|
||||
|
||||
if (validationErrors.length > 0) {
|
||||
this.set('isValid', false);
|
||||
} else {
|
||||
this.set('isValid', true);
|
||||
}
|
||||
|
||||
this.set('errors', validationErrors);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
saveNewPassword: function (password) {
|
||||
return ic.ajax.request(BaseModel.subdir + '/ghost/changepw/', {
|
||||
type: 'POST',
|
||||
data: password
|
||||
});
|
||||
},
|
||||
|
||||
validatePassword: function (password) {
|
||||
var validationErrors = [];
|
||||
|
||||
if (!validator.equals(password.newPassword, password.ne2Password)) {
|
||||
validationErrors.push("Your new passwords do not match");
|
||||
}
|
||||
|
||||
if (!validator.isLength(password.newPassword, 8)) {
|
||||
validationErrors.push("Your password is not long enough. It must be at least 8 characters long.");
|
||||
}
|
||||
|
||||
if (validationErrors.length > 0) {
|
||||
this.set('passwordIsValid', false);
|
||||
} else {
|
||||
this.set('passwordIsValid', true);
|
||||
}
|
||||
|
||||
this.set('passwordErrors', validationErrors);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
fetchForgottenPasswordFor: function (email) {
|
||||
var self = this;
|
||||
return new Ember.RSVP.Promise(function (resolve, reject) {
|
||||
if (!validator.isEmail(email)) {
|
||||
reject(new Error('Please enter a correct email address.'));
|
||||
} else {
|
||||
resolve(ic.ajax.request(self.forgottenUrl, {
|
||||
type: 'POST',
|
||||
headers: {
|
||||
// @TODO Find a more proper way to do this.
|
||||
'X-CSRF-Token': $('meta[name="csrf-param"]').attr('content')
|
||||
},
|
||||
data: {
|
||||
email: email
|
||||
}
|
||||
}));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
resetPassword: function (passwords, token) {
|
||||
var self = this;
|
||||
return new Ember.RSVP.Promise(function (resolve, reject) {
|
||||
if (!self.validatePassword(passwords).get('passwordIsValid')) {
|
||||
reject(new Error('Errors found! ' + JSON.stringify(self.get('passwordErrors'))));
|
||||
} else {
|
||||
resolve(ic.ajax.request(self.resetUrl, {
|
||||
type: 'POST',
|
||||
headers: {
|
||||
// @TODO: find a more proper way to do this.
|
||||
'X-CSRF-Token': $('meta[name="csrf-param"]').attr('content')
|
||||
},
|
||||
data: {
|
||||
newpassword: passwords.newPassword,
|
||||
ne2password: passwords.ne2Password,
|
||||
token: token
|
||||
}
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default UserModel;
|
|
@ -1,78 +1,30 @@
|
|||
/*global Ghost, Backbone, NProgress */
|
||||
(function () {
|
||||
"use strict";
|
||||
/*global Ember */
|
||||
|
||||
Ghost.Router = Backbone.Router.extend({
|
||||
// ensure we don't share routes between all Router instances
|
||||
var Router = Ember.Router.extend();
|
||||
|
||||
routes: {
|
||||
'' : 'blog',
|
||||
'content/' : 'blog',
|
||||
'settings(/:pane)/' : 'settings',
|
||||
'editor(/:id)/' : 'editor',
|
||||
'debug/' : 'debug',
|
||||
'register/' : 'register',
|
||||
'signup/' : 'signup',
|
||||
'signin/' : 'login',
|
||||
'forgotten/' : 'forgotten',
|
||||
'reset/:token/' : 'reset'
|
||||
},
|
||||
Router.reopen({
|
||||
location: 'history', // use HTML5 History API instead of hash-tag based URLs
|
||||
rootURL: '/ghost/ember/' // admin interface lives under sub-directory /ghost
|
||||
});
|
||||
|
||||
signup: function () {
|
||||
Ghost.currentView = new Ghost.Views.Signup({ el: '.js-signup-box' });
|
||||
},
|
||||
|
||||
login: function () {
|
||||
Ghost.currentView = new Ghost.Views.Login({ el: '.js-login-box' });
|
||||
},
|
||||
|
||||
forgotten: function () {
|
||||
Ghost.currentView = new Ghost.Views.Forgotten({ el: '.js-forgotten-box' });
|
||||
},
|
||||
|
||||
reset: function (token) {
|
||||
Ghost.currentView = new Ghost.Views.ResetPassword({ el: '.js-reset-box', token: token });
|
||||
},
|
||||
|
||||
blog: function () {
|
||||
var posts = new Ghost.Collections.Posts();
|
||||
NProgress.start();
|
||||
posts.fetch({ data: { status: 'all', staticPages: 'all', include: 'author'} }).then(function () {
|
||||
Ghost.currentView = new Ghost.Views.Blog({ el: '#main', collection: posts });
|
||||
NProgress.done();
|
||||
});
|
||||
},
|
||||
|
||||
settings: function (pane) {
|
||||
if (!pane) {
|
||||
// Redirect to settings/general if no pane supplied
|
||||
this.navigate('/settings/general/', {
|
||||
trigger: true,
|
||||
replace: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// only update the currentView if we don't already have a Settings view
|
||||
if (!Ghost.currentView || !(Ghost.currentView instanceof Ghost.Views.Settings)) {
|
||||
Ghost.currentView = new Ghost.Views.Settings({ el: '#main', pane: pane });
|
||||
}
|
||||
},
|
||||
|
||||
editor: function (id) {
|
||||
var post = new Ghost.Models.Post();
|
||||
post.urlRoot = Ghost.paths.apiRoot + '/posts';
|
||||
if (id) {
|
||||
post.id = id;
|
||||
post.fetch({ data: {status: 'all', include: 'tags'}}).then(function () {
|
||||
Ghost.currentView = new Ghost.Views.Editor({ el: '#main', model: post });
|
||||
});
|
||||
} else {
|
||||
Ghost.currentView = new Ghost.Views.Editor({ el: '#main', model: post });
|
||||
}
|
||||
},
|
||||
|
||||
debug: function () {
|
||||
Ghost.currentView = new Ghost.Views.Debug({ el: "#main" });
|
||||
}
|
||||
Router.map(function () {
|
||||
this.route('signin');
|
||||
this.route('signup');
|
||||
this.route('forgotten');
|
||||
this.route('reset', { path: '/reset/:token' });
|
||||
this.resource('posts', { path: '/' }, function () {
|
||||
this.route('post', { path: ':post_id' });
|
||||
});
|
||||
}());
|
||||
this.resource('editor', { path: '/editor/:post_id' });
|
||||
this.route('new', { path: '/editor' });
|
||||
this.resource('settings', function () {
|
||||
this.route('general');
|
||||
this.route('user');
|
||||
this.route('debug');
|
||||
this.route('apps');
|
||||
});
|
||||
this.route('debug');
|
||||
});
|
||||
|
||||
export default Router;
|
36
core/client/routes/application.js
Normal file
36
core/client/routes/application.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
var ApplicationRoute = Ember.Route.extend({
|
||||
actions: {
|
||||
openModal: function (modalName, model) {
|
||||
modalName = 'modals/' + modalName;
|
||||
// We don't always require a modal to have a controller
|
||||
// so we're skipping asserting if one exists
|
||||
if (this.controllerFor(modalName, true)) {
|
||||
this.controllerFor(modalName).set('model', model);
|
||||
}
|
||||
return this.render(modalName, {
|
||||
into: 'application',
|
||||
outlet: 'modal'
|
||||
});
|
||||
},
|
||||
|
||||
closeModal: function () {
|
||||
return this.disconnectOutlet({
|
||||
outlet: 'modal',
|
||||
parentView: 'application'
|
||||
});
|
||||
},
|
||||
|
||||
handleErrors: function (errors) {
|
||||
this.notifications.clear();
|
||||
errors.forEach(function (errorObj) {
|
||||
this.notifications.showError(errorObj.message || errorObj);
|
||||
|
||||
if (errorObj.hasOwnProperty('el')) {
|
||||
errorObj.el.addClass('input-error');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default ApplicationRoute;
|
11
core/client/routes/authenticated.js
Normal file
11
core/client/routes/authenticated.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
var AuthenticatedRoute = Ember.Route.extend({
|
||||
actions: {
|
||||
error: function (error) {
|
||||
if (error.jqXHR.status === 401) {
|
||||
this.transitionTo('signin');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default AuthenticatedRoute;
|
7
core/client/routes/debug.js
Normal file
7
core/client/routes/debug.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
var DebugRoute = Ember.Route.extend({
|
||||
beforeModel: function () {
|
||||
this.transitionTo('settings.debug');
|
||||
}
|
||||
});
|
||||
|
||||
export default DebugRoute;
|
15
core/client/routes/editor.js
Normal file
15
core/client/routes/editor.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
import ajax from 'ghost/utils/ajax';
|
||||
import styleBody from 'ghost/mixins/style-body';
|
||||
import AuthenticatedRoute from 'ghost/routes/authenticated';
|
||||
import Post from 'ghost/models/post';
|
||||
var EditorRoute = AuthenticatedRoute.extend(styleBody, {
|
||||
classNames: ['editor'],
|
||||
controllerName: 'posts.post',
|
||||
model: function (params) {
|
||||
return ajax('/ghost/api/v0.1/posts/' + params.post_id).then(function (post) {
|
||||
return Post.create(post);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default EditorRoute;
|
7
core/client/routes/forgotten.js
Normal file
7
core/client/routes/forgotten.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import styleBody from 'ghost/mixins/style-body';
|
||||
|
||||
var ForgottenRoute = Ember.Route.extend(styleBody, {
|
||||
classNames: ['ghost-forgotten']
|
||||
});
|
||||
|
||||
export default ForgottenRoute;
|
12
core/client/routes/new.js
Normal file
12
core/client/routes/new.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
import styleBody from 'ghost/mixins/style-body';
|
||||
import AuthenticatedRoute from 'ghost/routes/authenticated';
|
||||
|
||||
var NewRoute = AuthenticatedRoute.extend(styleBody, {
|
||||
classNames: ['editor'],
|
||||
|
||||
renderTemplate: function () {
|
||||
this.render('editor');
|
||||
}
|
||||
});
|
||||
|
||||
export default NewRoute;
|
24
core/client/routes/posts.js
Normal file
24
core/client/routes/posts.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
import ajax from 'ghost/utils/ajax';
|
||||
import styleBody from 'ghost/mixins/style-body';
|
||||
import AuthenticatedRoute from 'ghost/routes/authenticated';
|
||||
import Post from 'ghost/models/post';
|
||||
|
||||
var PostsRoute = AuthenticatedRoute.extend(styleBody, {
|
||||
classNames: ['manage'],
|
||||
|
||||
model: function () {
|
||||
return ajax('/ghost/api/v0.1/posts').then(function (response) {
|
||||
return response.posts.map(function (post) {
|
||||
return Post.create(post);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
actions: {
|
||||
openEditor: function (post) {
|
||||
this.transitionTo('editor', post);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default PostsRoute;
|
12
core/client/routes/posts/index.js
Normal file
12
core/client/routes/posts/index.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
var PostsIndexRoute = Ember.Route.extend({
|
||||
// redirect to first post subroute
|
||||
redirect: function () {
|
||||
var firstPost = (this.modelFor('posts') || []).get('firstObject');
|
||||
|
||||
if (firstPost) {
|
||||
this.transitionTo('posts.post', firstPost);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default PostsIndexRoute;
|
11
core/client/routes/posts/post.js
Normal file
11
core/client/routes/posts/post.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
/*global ajax */
|
||||
import Post from 'ghost/models/post';
|
||||
var PostsPostRoute = Ember.Route.extend({
|
||||
model: function (params) {
|
||||
return ajax('/ghost/api/v0.1/posts/' + params.post_id).then(function (post) {
|
||||
return Post.create(post);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default PostsPostRoute;
|
10
core/client/routes/reset.js
Normal file
10
core/client/routes/reset.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import styleBody from 'ghost/mixins/style-body';
|
||||
|
||||
var ResetRoute = Ember.Route.extend(styleBody, {
|
||||
classNames: ['ghost-reset'],
|
||||
setupController: function (controller, params) {
|
||||
controller.token = params.token;
|
||||
}
|
||||
});
|
||||
|
||||
export default ResetRoute;
|
8
core/client/routes/settings.js
Normal file
8
core/client/routes/settings.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
import styleBody from 'ghost/mixins/style-body';
|
||||
import AuthenticatedRoute from 'ghost/routes/authenticated';
|
||||
|
||||
var SettingsRoute = AuthenticatedRoute.extend(styleBody, {
|
||||
classNames: ['settings']
|
||||
});
|
||||
|
||||
export default SettingsRoute;
|
11
core/client/routes/settings/debug.js
Normal file
11
core/client/routes/settings/debug.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import SettingsModel from 'ghost/models/settings';
|
||||
|
||||
var settingsModel = SettingsModel.create();
|
||||
|
||||
var DebugRoute = Ember.Route.extend({
|
||||
model: function () {
|
||||
return settingsModel;
|
||||
}
|
||||
});
|
||||
|
||||
export default DebugRoute;
|
13
core/client/routes/settings/general.js
Normal file
13
core/client/routes/settings/general.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
import ajax from 'ghost/utils/ajax';
|
||||
import AuthenticatedRoute from 'ghost/routes/authenticated';
|
||||
import SettingsModel from 'ghost/models/settings';
|
||||
|
||||
var SettingsGeneralRoute = AuthenticatedRoute.extend({
|
||||
model: function () {
|
||||
return ajax('/ghost/api/v0.1/settings/?type=blog,theme,app').then(function (resp) {
|
||||
return SettingsModel.create(resp);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default SettingsGeneralRoute;
|
10
core/client/routes/settings/index.js
Normal file
10
core/client/routes/settings/index.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import AuthenticatedRoute from 'ghost/routes/authenticated';
|
||||
|
||||
var SettingsIndexRoute = AuthenticatedRoute.extend({
|
||||
// redirect to general tab
|
||||
redirect: function () {
|
||||
this.transitionTo('settings.general');
|
||||
}
|
||||
});
|
||||
|
||||
export default SettingsIndexRoute;
|
34
core/client/routes/signin.js
Normal file
34
core/client/routes/signin.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
import ajax from 'ghost/utils/ajax';
|
||||
import styleBody from 'ghost/mixins/style-body';
|
||||
|
||||
var isEmpty = Ember.isEmpty;
|
||||
|
||||
var SigninRoute = Ember.Route.extend(styleBody, {
|
||||
classNames: ['ghost-login'],
|
||||
|
||||
actions: {
|
||||
login: function () {
|
||||
var self = this,
|
||||
controller = this.get('controller'),
|
||||
data = controller.getProperties('email', 'password');
|
||||
|
||||
if (!isEmpty(data.email) && !isEmpty(data.password)) {
|
||||
|
||||
ajax('/ghost/api/v0.1/signin', data).then(
|
||||
function (response) {
|
||||
self.set('user', response);
|
||||
self.transitionTo('posts');
|
||||
}, function () {
|
||||
window.alert('Error'); // Todo Show notification
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.notifications.clear();
|
||||
|
||||
this.notifications.showError('Must enter email + password');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default SigninRoute;
|
7
core/client/routes/signup.js
Normal file
7
core/client/routes/signup.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import styleBody from 'ghost/mixins/style-body';
|
||||
|
||||
var SignupRoute = Ember.Route.extend(styleBody, {
|
||||
classNames: ['ghost-signup']
|
||||
});
|
||||
|
||||
export default SignupRoute;
|
21
core/client/templates/-floating-header.hbs
Normal file
21
core/client/templates/-floating-header.hbs
Normal file
|
@ -0,0 +1,21 @@
|
|||
<header class="floatingheader">
|
||||
<button class="button-back" href="#">Back</button>
|
||||
{{!-- @TODO: add back title updates depending on featured state --}}
|
||||
<a {{bind-attr class="featured:featured:unfeatured"}} href="#" title="Feature this post">
|
||||
<span class="hidden">Star</span>
|
||||
</a>
|
||||
<small>
|
||||
{{!-- @TODO: the if published doesn't seem to work, needs to be fixed --}}
|
||||
<span class="status">{{#if published}}Published{{else}}Written{{/if}}</span>
|
||||
<span class="normal">by</span>
|
||||
<span class="author">{{#if author.name}}{{author.name}}{{else}}{{author.email}}{{/if}}</span>
|
||||
</small>
|
||||
<section class="post-controls">
|
||||
{{#link-to "editor" this class="post-edit" title="Edit Post"}}
|
||||
<span class="hidden">Edit Post</span>
|
||||
{{/link-to}}
|
||||
<a class="post-settings" title="Post Settings" {{action 'editSettings'}}><span class="hidden">Post Settings</span></a>
|
||||
<!-- @TODO use Ghost Popover (#2565) --->
|
||||
{{view "post-settings-menu-view"}}
|
||||
</section>
|
||||
</header>
|
31
core/client/templates/-navbar.hbs
Normal file
31
core/client/templates/-navbar.hbs
Normal file
|
@ -0,0 +1,31 @@
|
|||
<header id="global-header" class="navbar">
|
||||
<a class="ghost-logo" href="/" data-off-canvas="left" title="/">
|
||||
<span class="hidden">Ghost </span>
|
||||
</a>
|
||||
<nav id="global-nav" role="navigation">
|
||||
<ul id="main-menu" >
|
||||
{{activating-list-item route="posts" title="Content" classNames="content"}}
|
||||
{{activating-list-item route="new" title="New post" classNames="content"}}
|
||||
{{activating-list-item route="settings" title="Settings" classNames="content"}}
|
||||
|
||||
<li id="usermenu" class="usermenu subnav">
|
||||
<a href="" {{action 'toggleMenu'}} class="dropdown">
|
||||
{{!-- @TODO: show avatar of logged in user --}}
|
||||
<img class="avatar" src="/shared/img/user-image.png" alt="Avatar" />
|
||||
{{!-- @TODO: show logged in user name or email --}}
|
||||
<span class="name">Fake Ghost</span>
|
||||
</a>
|
||||
{{!-- @TODO: add functionality to allow for dropdown to work --}}
|
||||
{{#ghost-popover open=showMenu}}
|
||||
<ul class="overlay">
|
||||
<li class="usermenu-profile"><a href="#">Your Profile</a></li>
|
||||
<li class="divider"></li>
|
||||
<li class="usermenu-help"><a href="http://ghost.org/forum/">Help / Support</a></li>
|
||||
<li class="divider"></li>
|
||||
<li class="usermenu-signout"><a href="#">Sign Out</a></li>
|
||||
</ul>
|
||||
{{/ghost-popover}}
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
29
core/client/templates/-publish-bar.hbs
Normal file
29
core/client/templates/-publish-bar.hbs
Normal file
|
@ -0,0 +1,29 @@
|
|||
<footer id="publish-bar">
|
||||
<nav>
|
||||
<section id="entry-tags" href="#" class="left">
|
||||
<label class="tag-label" for="tags" title="Tags"><span class="hidden">Tags</span></label>
|
||||
<div class="tags"></div>
|
||||
<input type="hidden" class="tags-holder" id="tags-holder">
|
||||
<input class="tag-input" id="tags" type="text" data-input-behaviour="tag" />
|
||||
<ul class="suggestions overlay"></ul>
|
||||
</section>
|
||||
<div class="right">
|
||||
|
||||
<section id="entry-controls">
|
||||
<a class="post-settings" title="Post Settings" {{action 'editSettings'}}><span class="hidden">Post Settings</span></a>
|
||||
<!-- @TODO Use Ghost Popover (#2565) and style arrow down -->
|
||||
{{view "post-settings-menu-view"}}
|
||||
</section>
|
||||
|
||||
<section id="entry-actions" class="js-publish-splitbutton splitbutton-save">
|
||||
<button type="button" class="js-publish-button button-save">Save Draft</button>
|
||||
<a class="options up" data-toggle="ul" href="#" title="Post Settings"><span class="hidden">Post Settings</span></a>
|
||||
{{!-- @TODO: implement popover --}}
|
||||
<ul class="editor-options overlay" style="display:none">
|
||||
<li data-set-status="published"><a href="#"></a></li>
|
||||
<li data-set-status="draft"><a href="#"></a></li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</nav>
|
||||
</footer>
|
10
core/client/templates/application.hbs
Normal file
10
core/client/templates/application.hbs
Normal file
|
@ -0,0 +1,10 @@
|
|||
{{#unless isLoggedOut}}
|
||||
{{partial "navbar"}}
|
||||
{{/unless}}
|
||||
|
||||
<main role="main" id="main">
|
||||
{{ghost-notifications}}
|
||||
|
||||
{{outlet}}
|
||||
</main>
|
||||
{{outlet modal}}
|
3
core/client/templates/components/-markdown.hbs
Normal file
3
core/client/templates/components/-markdown.hbs
Normal file
|
@ -0,0 +1,3 @@
|
|||
<div class="rendered-markdown">
|
||||
{{format-markdown markdown}}
|
||||
</div>
|
|
@ -0,0 +1 @@
|
|||
{{#link-to route alternateActive=active}}{{title}}{{yield}}{{/link-to}}
|
2
core/client/templates/components/file-upload.hbs
Normal file
2
core/client/templates/components/file-upload.hbs
Normal file
|
@ -0,0 +1,2 @@
|
|||
<input type="file" class="button-add" />
|
||||
<button type="submit" class="button-save" {{bind-attr disabled=uploadButtonDisabled}} {{action "upload"}}>{{uploadButtonText}}</button>
|
4
core/client/templates/components/ghost-notification.hbs
Normal file
4
core/client/templates/components/ghost-notification.hbs
Normal file
|
@ -0,0 +1,4 @@
|
|||
<section {{bind-attr class=":js-notification message.typeClass"}}>
|
||||
{{message.message}}
|
||||
<a class="close" {{action "closeNotification"}}><span class="hidden">Close</span></a>
|
||||
</section>
|
3
core/client/templates/components/ghost-notifications.hbs
Normal file
3
core/client/templates/components/ghost-notifications.hbs
Normal file
|
@ -0,0 +1,3 @@
|
|||
{{#each messages}}
|
||||
{{ghost-notification message=this}}
|
||||
{{/each}}
|
22
core/client/templates/components/modal-dialog.hbs
Normal file
22
core/client/templates/components/modal-dialog.hbs
Normal file
|
@ -0,0 +1,22 @@
|
|||
<div id="modal-container" {{action bubbles=false preventDefault=false}}>
|
||||
<article {{bind-attr class="klass :js-modal"}}>
|
||||
<section class="modal-content">
|
||||
{{#if title}}<header class="modal-header"><h1>{{title}}</h1></header>{{/if}}
|
||||
{{#if showClose}}<a class="close" href="" title="Close" {{action "closeModal"}}><span class="hidden">Close</span></a>{{/if}}
|
||||
<section class="modal-body">
|
||||
{{yield}}
|
||||
</section>
|
||||
{{#if confirm}}
|
||||
<footer class="modal-footer">
|
||||
<button {{bind-attr class="acceptButtonClass :js-button-accept"}} {{action "confirm" "accept"}}>
|
||||
{{confirm.accept.text}}
|
||||
</button>
|
||||
<button {{bind-attr class="rejectButtonClass :js-button-reject"}} {{action "confirm" "reject"}}>
|
||||
{{confirm.reject.text}}
|
||||
</button>
|
||||
</footer>
|
||||
{{/if}}
|
||||
</section>
|
||||
</article>
|
||||
</div>
|
||||
<div class="modal-background fade" {{action "closeModal"}}></div>
|
28
core/client/templates/editor.hbs
Normal file
28
core/client/templates/editor.hbs
Normal file
|
@ -0,0 +1,28 @@
|
|||
<section class="entry-container">
|
||||
<header>
|
||||
<section class="box entry-title">
|
||||
{{input type="text" id="entry-title" placeholder="Your Post Title" value=title tabindex="1"}}
|
||||
</section>
|
||||
</header>
|
||||
|
||||
<section class="entry-markdown active">
|
||||
<header class="floatingheader">
|
||||
<small>Markdown</small>
|
||||
<a class="markdown-help" href="" {{action "openModal" "markdown"}}><span class="hidden">What is Markdown?</span></a>
|
||||
</header>
|
||||
<section id="entry-markdown-content" class="entry-markdown-content">
|
||||
{{-codemirror value=markdown scrollPosition=view.scrollPosition}}
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section class="entry-preview">
|
||||
<header class="floatingheader">
|
||||
<small>Preview <span class="entry-word-count js-entry-word-count">{{count-words markdown}} words</span></small>
|
||||
</header>
|
||||
<section class="entry-preview-content">
|
||||
{{-markdown markdown=markdown scrollPosition=view.scrollPosition}}
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
{{partial 'publish-bar'}}
|
5
core/client/templates/error.hbs
Normal file
5
core/client/templates/error.hbs
Normal file
|
@ -0,0 +1,5 @@
|
|||
<h1>Sorry, Something went wrong</h1>
|
||||
{{message}}
|
||||
<pre>
|
||||
{{stack}}
|
||||
</pre>
|
8
core/client/templates/forgotten.hbs
Normal file
8
core/client/templates/forgotten.hbs
Normal file
|
@ -0,0 +1,8 @@
|
|||
<section class="forgotten-box js-forgotten-box fade-in">
|
||||
<form id="forgotten" class="forgotten-form" method="post" novalidate="novalidate" {{action "submit" on="submit"}}>
|
||||
<div class="email-wrap">
|
||||
{{input value=email class="email" type="email" placeholder="Email Address" name="email" autofocus="autofocus" autocapitalize="off" autocorrect="off"}}
|
||||
</div>
|
||||
<button class="button-save" type="submit">Send new password</button>
|
||||
</form>
|
||||
</section>
|
1
core/client/templates/loading.hbs
Normal file
1
core/client/templates/loading.hbs
Normal file
|
@ -0,0 +1 @@
|
|||
<h1>Loading...</h1>
|
6
core/client/templates/modals/delete-all.hbs
Normal file
6
core/client/templates/modals/delete-all.hbs
Normal file
|
@ -0,0 +1,6 @@
|
|||
{{#modal-dialog action="closeModal" type="action" style="wide,centered" animation="fade"
|
||||
title="Would you really like to delete all content from your blog?" confirm=confirm}}
|
||||
|
||||
<p>This is permanent! No backups, no restores, no magic undo button. <br /> We warned you, ok?</p>
|
||||
|
||||
{{/modal-dialog}}
|
6
core/client/templates/modals/delete-post.hbs
Normal file
6
core/client/templates/modals/delete-post.hbs
Normal file
|
@ -0,0 +1,6 @@
|
|||
{{#modal-dialog action="closeModal" showClose=true type="action" style="wide,centered" animation="fade"
|
||||
title="Are you sure you want to delete this post?" confirm=confirm}}
|
||||
|
||||
<p>This is permanent! No backups, no restores, no magic undo button. <br /> We warned you, ok?</p>
|
||||
|
||||
{{/modal-dialog}}
|
|
@ -1,13 +1,15 @@
|
|||
<section class="markdown-help-container">
|
||||
<table class="modal-markdown-help-table">
|
||||
<thead>
|
||||
{{#modal-dialog action="closeModal" showClose=true style="wide" animation="fade"
|
||||
title="Markdown Help"}}
|
||||
<section class="markdown-help-container">
|
||||
<table class="modal-markdown-help-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Result</th>
|
||||
<th>Markdown</th>
|
||||
<th>Shortcut</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>Bold</strong></td>
|
||||
<td>**text**</td>
|
||||
|
@ -63,7 +65,8 @@
|
|||
<td>`code`</td>
|
||||
<td>Cmd + K / Ctrl + Shift + K</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
For further Markdown syntax reference: <a href="http://daringfireball.net/projects/markdown/syntax" target="_blank">Markdown Documentation</a>
|
||||
</section>
|
||||
</tbody>
|
||||
</table>
|
||||
For further Markdown syntax reference: <a href="http://daringfireball.net/projects/markdown/syntax" target="_blank">Markdown Documentation</a>
|
||||
</section>
|
||||
{{/modal-dialog}}
|
9
core/client/templates/modals/upload.hbs
Normal file
9
core/client/templates/modals/upload.hbs
Normal file
|
@ -0,0 +1,9 @@
|
|||
{{#upload-modal action="closeModal" close=true type="action" style="wide"
|
||||
animation="fade"}}
|
||||
|
||||
<section class="js-drop-zone">
|
||||
<img class="js-upload-target" {{bind-attr src=src}} alt="logo">
|
||||
<input data-url="upload" class="js-fileupload main" type="file" name="uploadimage" {{#if options.acceptEncoding}}accept="{{options.acceptEncoding}}"{{/if}}>
|
||||
</section>
|
||||
|
||||
{{/upload-modal}}
|
1
core/client/templates/new.hbs
Normal file
1
core/client/templates/new.hbs
Normal file
|
@ -0,0 +1 @@
|
|||
TODO
|
32
core/client/templates/post-settings-menu.hbs
Normal file
32
core/client/templates/post-settings-menu.hbs
Normal file
|
@ -0,0 +1,32 @@
|
|||
<form>
|
||||
<table class="plain">
|
||||
<tbody>
|
||||
<tr class="post-setting">
|
||||
<td class="post-setting-label">
|
||||
<label for="url">URL</label>
|
||||
</td>
|
||||
<td class="post-setting-field">
|
||||
{{blur-text-field class="post-setting-slug" id="url" value=newSlug action="updateSlug" placeholder=slugPlaceholder selectOnClick="true"}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="post-setting">
|
||||
<td class="post-setting-label">
|
||||
<label for="pub-date">Pub Date</label>
|
||||
</td>
|
||||
<td class="post-setting-field">
|
||||
{{blur-text-field class="post-setting-date" value=view.publishedAt action="updatePublishedAt" placeholder=view.datePlaceholder}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="post-setting">
|
||||
<td class="post-setting-label">
|
||||
<label class="label" for="static-page">Static Page</label>
|
||||
</td>
|
||||
<td class="post-setting-item">
|
||||
{{input type="checkbox" name="static-page" id="static-page" class="post-setting-static-page" checked=isStaticPage}}
|
||||
<label class="checkbox" for="static-page"></label>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
<a class="delete" {{action 'openModal' 'delete-post' post}}>Delete This Post</a>
|
38
core/client/templates/posts.hbs
Normal file
38
core/client/templates/posts.hbs
Normal file
|
@ -0,0 +1,38 @@
|
|||
<section class="content-view-container">
|
||||
<section class="content-list js-content-list">
|
||||
<header class="floatingheader">
|
||||
<section class="content-filter">
|
||||
<small>All Posts</small>
|
||||
</section>
|
||||
{{#link-to "new" class="button button-add" title="New Post"}}<span class="hidden">New Post</span>{{/link-to}}
|
||||
</header>
|
||||
<section class="content-list-content">
|
||||
<ol class="posts-list">
|
||||
{{#each itemController="posts/post" itemView="post-item-view" itemTagName="li"}}
|
||||
{{!-- @TODO: Restore functionality where 'featured' and 'page' classes are added for proper posts --}}
|
||||
{{#link-to 'posts.post' this class="permalink" title="Edit this post"}}
|
||||
<h3 class="entry-title">{{title}}</h3>
|
||||
<section class="entry-meta">
|
||||
<span class="status">
|
||||
{{#if isPublished}}
|
||||
{{#if page}}
|
||||
<span class="page">Page</span>
|
||||
{{else}}
|
||||
<time datetime="{{unbound published_at}}" class="date published">
|
||||
Published {{format-timeago published_at}}
|
||||
</time>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<span class="draft">Draft</span>
|
||||
{{/if}}
|
||||
</span>
|
||||
</section>
|
||||
{{/link-to}}
|
||||
{{/each}}
|
||||
</ol>
|
||||
</section>
|
||||
</section>
|
||||
<section class="content-preview js-content-preview">
|
||||
{{outlet}}
|
||||
</section>
|
||||
</section>
|
21
core/client/templates/posts/post.hbs
Normal file
21
core/client/templates/posts/post.hbs
Normal file
|
@ -0,0 +1,21 @@
|
|||
{{#if title}}
|
||||
|
||||
{{partial "floating-header"}}
|
||||
|
||||
<section class="content-preview-content">
|
||||
<div class="wrapper">
|
||||
<h1>{{title}}</h1>
|
||||
{{format-markdown markdown}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{else}}
|
||||
|
||||
<div class="no-posts-box">
|
||||
<div class="no-posts">
|
||||
<h3>You Haven't Written Any Posts Yet!</h3>
|
||||
{{#link-to 'new'}}<button class="button-add large" title="New Post">Write a new Post</button>{{/link-to}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{/if}}
|
11
core/client/templates/reset.hbs
Normal file
11
core/client/templates/reset.hbs
Normal file
|
@ -0,0 +1,11 @@
|
|||
<section class="reset-box js-reset-box fade-in">
|
||||
<form id="reset" class="reset-form" method="post" novalidate="novalidate" {{action "submit" on="submit"}}>
|
||||
<div class="password-wrap">
|
||||
{{input value=passwords.newPassword class="password" type="password" placeholder="Password" name="newpassword" autofocus="autofocus" }}
|
||||
</div>
|
||||
<div class="password-wrap">
|
||||
{{input value=passwords.ne2Password class="password" type="password" placeholder="Confirm Password" name="ne2password" }}
|
||||
</div>
|
||||
<button class="button-save" type="submit" {{bind-attr disabled='submitButtonDisabled'}}>Reset Password</button>
|
||||
</form>
|
||||
</section>
|
18
core/client/templates/settings.hbs
Normal file
18
core/client/templates/settings.hbs
Normal file
|
@ -0,0 +1,18 @@
|
|||
<div class="wrapper">
|
||||
<aside class="settings-sidebar" role="complementary">
|
||||
<header>
|
||||
<h1 class="title">Settings</h1>
|
||||
</header>
|
||||
<nav class="settings-menu">
|
||||
<ul>
|
||||
<li class="general">{{#link-to 'settings.general'}}General{{/link-to}}</li>
|
||||
<li class="users">{{#link-to 'settings.user'}}User{{/link-to}}</li>
|
||||
<li class="apps">{{#link-to 'settings.apps'}}Apps{{/link-to}}</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<section class="settings-content active">
|
||||
{{outlet}}
|
||||
</section>
|
||||
</div>
|
41
core/client/templates/settings/debug.hbs
Normal file
41
core/client/templates/settings/debug.hbs
Normal file
|
@ -0,0 +1,41 @@
|
|||
<header>
|
||||
<h2 class="title">General</h2>
|
||||
</header>
|
||||
<section class="content">
|
||||
<form id="settings-export">
|
||||
<fieldset>
|
||||
<div class="form-group">
|
||||
<label>Export</label>
|
||||
<a class="button-save" {{bind-attr href=model.exportPath}}>Export</a>
|
||||
<p>Export the blog settings and data.</p>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
{{#gh-form id="settings-import" enctype="multipart/form-data"}}
|
||||
<fieldset>
|
||||
<div class="form-group">
|
||||
<label>Import</label>
|
||||
{{file-upload onUpload="importData" uploadButtonText=uploadButtonText}}
|
||||
<p>Import from another Ghost installation. If you import a user, this will replace the current user & log you out.</p>
|
||||
</div>
|
||||
</fieldset>
|
||||
{{/gh-form}}
|
||||
<form id="settings-resetdb">
|
||||
<fieldset>
|
||||
<div class="form-group">
|
||||
<label>Delete all Content</label>
|
||||
<a href="javascript:void(0);" class="button-delete js-delete" {{action "openModal" "deleteAll"}}>Delete</a>
|
||||
<p>Delete all posts and tags from the database.</p>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
<form id="settings-testmail">
|
||||
<fieldset>
|
||||
<div class="form-group">
|
||||
<label>Send a test email</label>
|
||||
<button type="submit" id="sendtestmail" class="button-save" {{action "sendTestEmail"}}>Send</button>
|
||||
<p>Sends a test email to your address.</p>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</section>
|
80
core/client/templates/settings/general.hbs
Normal file
80
core/client/templates/settings/general.hbs
Normal file
|
@ -0,0 +1,80 @@
|
|||
<header>
|
||||
<button class="button-back">Back</button>
|
||||
<h2 class="title">General</h2>
|
||||
<section class="page-actions">
|
||||
<button class="button-save" {{action 'save'}}>Save</button>
|
||||
</section>
|
||||
</header>
|
||||
|
||||
<section class="content">
|
||||
<form id="settings-general" novalidate="novalidate">
|
||||
<fieldset>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="blog-title">Blog Title</label>
|
||||
{{input id="blog-title" name="general[title]" type="text" value=title}}
|
||||
<p>The name of your blog</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group description-container">
|
||||
<label for="blog-description">Blog Description</label>
|
||||
{{textarea id="blog-description" value=description}}
|
||||
<p>
|
||||
Describe what your blog is about
|
||||
<span class="word-count">{{count-words description}}</span>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="form-group">
|
||||
<label for="blog-logo">Blog Logo</label>
|
||||
{{#if logo}}
|
||||
<a class="js-modal-logo" href="#" {{action 'openModal' 'upload'}}><img id="blog-logo" {{bind-attr src=logo}} alt="logo"></a>
|
||||
{{else}}
|
||||
<a class="button-add js-modal-logo" {{action 'openModal' 'upload'}}>Upload Image</a>
|
||||
{{/if}}
|
||||
<p>Display a sexy logo for your publication</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="blog-cover">Blog Cover</label>
|
||||
{{#if cover}}
|
||||
<a class="js-modal-cover" href="#" {{action 'openModal' 'upload'}}><img id="blog-cover" {{bind-attr src=logo}} alt="cover photo"></a>
|
||||
{{else}}
|
||||
<a class="button-add js-modal-cover" {{action 'openModal' 'upload'}}>Upload Image</a>
|
||||
{{/if}}
|
||||
<p>Display a cover image on your site</p>
|
||||
</div>
|
||||
<fieldset>
|
||||
<div class="form-group">
|
||||
<label for="email-address">Email Address</label>
|
||||
{{input id="email-address" name="general[email-address]" type="email" value=email autocapitalize="off" autocorrect="off"}}
|
||||
<p>Address to use for admin notifications</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="postsPerPage">Posts per page</label>
|
||||
{{input id="postsPerPage" name="general[postsPerPage]" type="number" value=postsPerPage}}
|
||||
<p>How many posts should be displayed on each page</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="permalinks">Dated Permalinks</label>
|
||||
{{input id="permalinks" name="general[permalinks]" type="checkbox" checked=isDatedPermalinks}}
|
||||
<label class="checkbox" for="permalinks"></label>
|
||||
<p>Include the date in your post URLs</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="activeTheme">Theme</label>
|
||||
<select id="activeTheme" name="general[activeTheme]">
|
||||
{{#each availableThemes}}
|
||||
<option value="{{name}}" {{#if active}}selected{{/if}}>{{#if package}}{{package.name}} - {{package.version}}{{else}}{{name}}{{/if}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
<p>Select a theme for your blog</p>
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
</form>
|
||||
</section>
|
90
core/client/templates/settings/user.hbs
Normal file
90
core/client/templates/settings/user.hbs
Normal file
|
@ -0,0 +1,90 @@
|
|||
<header>
|
||||
<button class="button-back">Back</button>
|
||||
<h2 class="title">Your Profile</h2>
|
||||
<section class="page-actions">
|
||||
<button class="button-save" {{action 'save'}}>Save</button>
|
||||
</section>
|
||||
</header>
|
||||
|
||||
<section class="content no-padding">
|
||||
|
||||
<header class="user-profile-header">
|
||||
<img id="user-cover" class="cover-image" {{bind-attr src=cover title=coverTitle}} />
|
||||
|
||||
<a class="edit-cover-image js-modal-cover button" {{action 'openModal' 'upload'}}>Change Cover</a>
|
||||
</header>
|
||||
|
||||
<form class="user-profile" novalidate="novalidate">
|
||||
|
||||
<fieldset class="user-details-top">
|
||||
|
||||
<figure class="user-image">
|
||||
<div id="user-image" class="img" {{bind-attr style=image}} href="#"><span class="hidden">{{name}}'s Picture</span></div>
|
||||
<a href="" {{action 'openModal' 'upload'}} class="edit-user-image js-modal-image">Edit Picture</a>
|
||||
</figure>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="user-name" class="hidden">Full Name</label>
|
||||
{{input value=user.name id="user-name" placeholder="Full Name" autocorrect="off"}}
|
||||
<p>Use your real name so people can recognise you</p>
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="user-details-bottom">
|
||||
|
||||
<div class="form-group">
|
||||
<label for"user-email">Email</label>
|
||||
{{input type="email" value=user.email id="user-email" placeholder="Email Address" autocapitalize="off" autocorrect="off"}}
|
||||
<p>Used for notifications</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="user-location">Location</label>
|
||||
{{input type="text" value=user.location id="user-location"}}
|
||||
<p>Where in the world do you live?</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="user-website">Website</label>
|
||||
{{input type="url" value=user.website id="user-website" autocapitalize="off" autocorrect="off"}}
|
||||
<p>Have a website or blog other than this one? Link it!</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group bio-container">
|
||||
<label for="user-bio">Bio</label>
|
||||
{{textarea id="user-bio" value=user.bio}}
|
||||
<p>
|
||||
Write about you, in 200 characters or less.
|
||||
<span class="word-count">{{count-words user.bio}}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="user-password-old">Old Password</label>
|
||||
{{input value=password type="password" id="user-password-old"}}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="user-password-new">New Password</label>
|
||||
{{input value=newPassword type="password" id="user-password-new"}}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="user-new-password-verification">Verify Password</label>
|
||||
{{input value=ne2Password type="password" id="user-new-password-verification"}}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="button" class="button-delete button-change-password" {{action 'password'}}>Change Password</button>
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
|
||||
</form>
|
||||
</section>
|
14
core/client/templates/signin.hbs
Normal file
14
core/client/templates/signin.hbs
Normal file
|
@ -0,0 +1,14 @@
|
|||
<section class="login-box js-login-box fade-in">
|
||||
<form id="login" class="login-form" method="post" novalidate="novalidate">
|
||||
<div class="email-wrap">
|
||||
{{input class="email" type="email" placeholder="Email Address" name="email" autofocus="autofocus" autocapitalize="off" autocorrect="off" value=email}}
|
||||
</div>
|
||||
<div class="password-wrap">
|
||||
{{input class="password" type="password" placeholder="Password" name="password" value=password}}
|
||||
</div>
|
||||
<button class="button-save" type="submit" {{action "login"}}>Log in</button>
|
||||
<section class="meta">
|
||||
{{#link-to 'forgotten' class="forgotten-password"}}Forgotten password?{{/link-to}}
|
||||
</section>
|
||||
</form>
|
||||
</section>
|
14
core/client/templates/signup.hbs
Normal file
14
core/client/templates/signup.hbs
Normal file
|
@ -0,0 +1,14 @@
|
|||
<section class="signup-box js-signup-box fade-in">
|
||||
<form id="signup" class="signup-form" method="post" novalidate="novalidate">
|
||||
<div class="name-wrap">
|
||||
<input class="name" type="text" placeholder="Full Name" name="name" autofocus autocorrect="off" />
|
||||
</div>
|
||||
<div class="email-wrap">
|
||||
<input class="email" type="email" placeholder="Email Address" name="email" autocapitalize="off" autocorrect="off" />
|
||||
</div>
|
||||
<div class="password-wrap">
|
||||
<input class="password" type="password" placeholder="Password" name="password" />
|
||||
</div>
|
||||
<button class="button-save" type="submit">Sign Up</button>
|
||||
</form>
|
||||
</section>
|
4
core/client/utils/ajax.js
Normal file
4
core/client/utils/ajax.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
/* global ic */
|
||||
export default window.ajax = function () {
|
||||
return ic.ajax.request.apply(null, arguments);
|
||||
};
|
21
core/client/utils/date-formatting.js
Normal file
21
core/client/utils/date-formatting.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
/* global moment */
|
||||
var parseDateFormats = ["DD MMM YY HH:mm",
|
||||
"DD MMM YYYY HH:mm",
|
||||
"DD/MM/YY HH:mm",
|
||||
"DD/MM/YYYY HH:mm",
|
||||
"DD-MM-YY HH:mm",
|
||||
"DD-MM-YYYY HH:mm",
|
||||
"YYYY-MM-DD HH:mm"],
|
||||
displayDateFormat = 'DD MMM YY @ HH:mm';
|
||||
|
||||
//Parses a string to a Moment
|
||||
var parseDateString = function (value) {
|
||||
return value ? moment(value, parseDateFormats) : '';
|
||||
};
|
||||
|
||||
//Formats a Date or Moment
|
||||
var formatDate = function (value) {
|
||||
return value ? moment(value).format(displayDateFormat) : '';
|
||||
};
|
||||
|
||||
export {parseDateString, formatDate};
|
9
core/client/utils/link-view.js
Normal file
9
core/client/utils/link-view.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
Ember.LinkView.reopen({
|
||||
active: Ember.computed('resolvedParams', 'routeArgs', function () {
|
||||
var isActive = this._super();
|
||||
|
||||
Ember.set(this, 'alternateActive', isActive);
|
||||
|
||||
return isActive;
|
||||
})
|
||||
});
|
43
core/client/utils/notifications.js
Normal file
43
core/client/utils/notifications.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
var Notifications = Ember.ArrayProxy.extend({
|
||||
content: Ember.A(),
|
||||
timeout: 3000,
|
||||
pushObject: function (object) {
|
||||
object.typeClass = 'notification-' + object.type;
|
||||
// This should be somewhere else.
|
||||
if (object.type === 'success') {
|
||||
object.typeClass = object.typeClass + " notification-passive";
|
||||
}
|
||||
this._super(object);
|
||||
},
|
||||
showError: function (message) {
|
||||
this.pushObject({
|
||||
type: 'error',
|
||||
message: message
|
||||
});
|
||||
},
|
||||
showErrors: function (errors) {
|
||||
for (var i = 0; i < errors.length; i += 1) {
|
||||
this.showError(errors[i].message || errors[i]);
|
||||
}
|
||||
},
|
||||
showInfo: function (message) {
|
||||
this.pushObject({
|
||||
type: 'info',
|
||||
message: message
|
||||
});
|
||||
},
|
||||
showSuccess: function (message) {
|
||||
this.pushObject({
|
||||
type: 'success',
|
||||
message: message
|
||||
});
|
||||
},
|
||||
showWarn: function (message) {
|
||||
this.pushObject({
|
||||
type: 'warn',
|
||||
message: message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default Notifications;
|
3
core/client/utils/text-field.js
Normal file
3
core/client/utils/text-field.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
Ember.TextField.reopen({
|
||||
attributeBindings: ['autofocus']
|
||||
});
|
6
core/client/utils/word-count.js
Normal file
6
core/client/utils/word-count.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
export default function (s) {
|
||||
s = s.replace(/(^\s*)|(\s*$)/gi, ""); // exclude start and end white-space
|
||||
s = s.replace(/[ ]{2,}/gi, " "); // 2 or more space to 1
|
||||
s = s.replace(/\n /, "\n"); // exclude newline with a start spacing
|
||||
return s.split(' ').length;
|
||||
}
|
|
@ -1,158 +1,3 @@
|
|||
// # Article Editor
|
||||
|
||||
/*global document, setTimeout, navigator, $, Backbone, Ghost, shortcut */
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var PublishBar;
|
||||
|
||||
// The publish bar associated with a post, which has the TagWidget and
|
||||
// Save button and options and such.
|
||||
// ----------------------------------------
|
||||
PublishBar = Ghost.View.extend({
|
||||
|
||||
initialize: function () {
|
||||
|
||||
this.addSubview(new Ghost.View.EditorTagWidget(
|
||||
{el: this.$('#entry-tags'), model: this.model}
|
||||
)).render();
|
||||
this.addSubview(new Ghost.View.PostSettings(
|
||||
{el: $('#entry-controls'), model: this.model}
|
||||
)).render();
|
||||
|
||||
// Pass the Actions widget references to the title and editor so that it can get
|
||||
// the values that need to be saved
|
||||
this.addSubview(new Ghost.View.EditorActionsWidget(
|
||||
{
|
||||
el: this.$('#entry-actions'),
|
||||
model: this.model,
|
||||
$title: this.options.$title,
|
||||
editor: this.options.editor
|
||||
}
|
||||
)).render();
|
||||
|
||||
},
|
||||
|
||||
render: function () { return this; }
|
||||
});
|
||||
|
||||
|
||||
// The entire /editor page's route
|
||||
// ----------------------------------------
|
||||
Ghost.Views.Editor = Ghost.View.extend({
|
||||
|
||||
events: {
|
||||
'click .markdown-help': 'showHelp',
|
||||
'blur #entry-title': 'trimTitle',
|
||||
'orientationchange': 'orientationChange'
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
this.$title = this.$('#entry-title');
|
||||
this.$editor = this.$('#entry-markdown');
|
||||
|
||||
this.$title.val(this.model.get('title')).focus();
|
||||
this.$editor.text(this.model.get('markdown'));
|
||||
|
||||
// Create a new editor
|
||||
this.editor = new Ghost.Editor.Main();
|
||||
|
||||
// Add the container view for the Publish Bar
|
||||
// Passing reference to the title and editor
|
||||
this.addSubview(new PublishBar(
|
||||
{el: '#publish-bar', model: this.model, $title: this.$title, editor: this.editor}
|
||||
)).render();
|
||||
|
||||
this.listenTo(this.model, 'change:title', this.renderTitle);
|
||||
this.listenTo(this.model, 'change:id', this.handleIdChange);
|
||||
|
||||
this.bindShortcuts();
|
||||
|
||||
$('.entry-markdown header, .entry-preview header').on('click', function (e) {
|
||||
$('.entry-markdown, .entry-preview').removeClass('active');
|
||||
$(e.currentTarget).closest('section').addClass('active');
|
||||
});
|
||||
},
|
||||
|
||||
bindShortcuts: function () {
|
||||
var self = this;
|
||||
|
||||
// Zen writing mode shortcut - full editor view
|
||||
shortcut.add('Alt+Shift+Z', function () {
|
||||
$('body').toggleClass('zen');
|
||||
});
|
||||
|
||||
// HTML copy & paste
|
||||
shortcut.add('Ctrl+Alt+C', function () {
|
||||
self.showHTML();
|
||||
});
|
||||
},
|
||||
|
||||
trimTitle: function () {
|
||||
var rawTitle = this.$title.val(),
|
||||
trimmedTitle = $.trim(rawTitle);
|
||||
|
||||
if (rawTitle !== trimmedTitle) {
|
||||
this.$title.val(trimmedTitle);
|
||||
}
|
||||
|
||||
// Trigger title change for post-settings.js
|
||||
this.model.set('title', trimmedTitle);
|
||||
},
|
||||
|
||||
renderTitle: function () {
|
||||
this.$title.val(this.model.get('title'));
|
||||
},
|
||||
|
||||
handleIdChange: function (m) {
|
||||
// This is a special case for browsers which fire an unload event when using navigate. The id change
|
||||
// happens before the save success and can cause the unload alert to appear incorrectly on first save
|
||||
// The id only changes in the event that the save has been successful, so this workaround is safes
|
||||
this.editor.setDirty(false);
|
||||
Backbone.history.navigate('/editor/' + m.id + '/');
|
||||
},
|
||||
|
||||
// This is a hack to remove iOS6 white space on orientation change bug
|
||||
// See: http://cl.ly/RGx9
|
||||
orientationChange: function () {
|
||||
if (/iPhone/.test(navigator.userAgent) && !/Opera Mini/.test(navigator.userAgent)) {
|
||||
var focusedElement = document.activeElement,
|
||||
s = document.documentElement.style;
|
||||
focusedElement.blur();
|
||||
s.display = 'none';
|
||||
setTimeout(function () { s.display = 'block'; focusedElement.focus(); }, 0);
|
||||
}
|
||||
},
|
||||
|
||||
showEditorModal: function (content) {
|
||||
this.addSubview(new Ghost.Views.Modal({
|
||||
model: {
|
||||
options: {
|
||||
close: true,
|
||||
style: ['wide'],
|
||||
animation: 'fade'
|
||||
},
|
||||
content: content
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
||||
showHelp: function () {
|
||||
var content = {
|
||||
template: 'markdown',
|
||||
title: 'Markdown Help'
|
||||
};
|
||||
this.showEditorModal(content);
|
||||
},
|
||||
|
||||
showHTML: function () {
|
||||
var content = {
|
||||
template: 'copyToHTML',
|
||||
title: 'Copied HTML'
|
||||
};
|
||||
this.showEditorModal(content);
|
||||
},
|
||||
|
||||
render: function () { return this; }
|
||||
});
|
||||
}());
|
||||
export default Ember.View.extend({
|
||||
scrollPosition: 0 // percentage of scroll position
|
||||
});
|
7
core/client/views/item-view.js
Normal file
7
core/client/views/item-view.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
export default Ember.View.extend({
|
||||
classNameBindings: ['active'],
|
||||
|
||||
active: function () {
|
||||
return this.get('childViews.firstObject.active');
|
||||
}.property('childViews.firstObject.active')
|
||||
});
|
9
core/client/views/post-item-view.js
Normal file
9
core/client/views/post-item-view.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
import itemView from 'ghost/views/item-view';
|
||||
|
||||
var PostItemView = itemView.extend({
|
||||
openEditor: function () {
|
||||
this.get('controller').send('openEditor', this.get('controller.model')); // send action to handle transition to editor route
|
||||
}.on("doubleClick")
|
||||
});
|
||||
|
||||
export default PostItemView;
|
18
core/client/views/post-settings-menu-view.js
Normal file
18
core/client/views/post-settings-menu-view.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
/* global moment */
|
||||
import {formatDate} from 'ghost/utils/date-formatting';
|
||||
|
||||
var PostSettingsMenuView = Ember.View.extend({
|
||||
templateName: 'post-settings-menu',
|
||||
classNames: ['post-settings-menu', 'menu-drop-right', 'overlay'],
|
||||
classNameBindings: ['controller.isEditingSettings::hidden'],
|
||||
publishedAtBinding: Ember.Binding.oneWay('controller.publishedAt'),
|
||||
click: function (event) {
|
||||
//Stop click propagation to prevent window closing
|
||||
event.stopPropagation();
|
||||
},
|
||||
datePlaceholder: function () {
|
||||
return formatDate(moment());
|
||||
}.property('controller.publishedAt')
|
||||
});
|
||||
|
||||
export default PostSettingsMenuView;
|
BIN
core/clientold/assets/img/404-ghost.png
Normal file
BIN
core/clientold/assets/img/404-ghost.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.6 KiB |
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue