Merge pull request #6020 from kevinansfield/suave

Use ember-suave and standardise client's es6 usage
This commit is contained in:
Sebastian Gierlinger 2015-11-30 12:46:39 +01:00
commit e5d9865792
262 changed files with 3560 additions and 3159 deletions

View File

@ -126,8 +126,7 @@ var _ = require('lodash'),
client: {
options: {
esnext: true,
disallowObjectController: true
config: 'core/client/.jscsrc'
},
files: {
@ -135,6 +134,7 @@ var _ = require('lodash'),
'core/client/**/*.js',
'!core/client/node_modules/**/*.js',
'!core/client/bower_components/**/*.js',
'!core/client/tests/**/*.js',
'!core/client/tmp/**/*.js',
'!core/client/dist/**/*.js',
'!core/client/vendor/**/*.js'
@ -142,6 +142,18 @@ var _ = require('lodash'),
}
},
client_tests: {
options: {
config: 'core/client/tests/.jscsrc'
},
files: {
src: [
'core/client/tests/**/*.js'
]
}
},
server: {
files: {
src: [

14
core/client/.jscsrc Normal file
View File

@ -0,0 +1,14 @@
{
"preset": "ember-suave",
"validateIndentation": 4,
"disallowSpacesInFunction": null,
"disallowSpacesInNamedFunctionExpression": {
"beforeOpeningRoundBrace": true
},
"disallowSpacesInFunctionDeclaration": {
"beforeOpeningRoundBrace": true
},
"disallowSpacesInsideObjectBrackets": "all",
"requireCommentsToIncludeAccess": null,
"requireSpacesInsideObjectBrackets": null
}

View File

@ -2,7 +2,7 @@ import EmbeddedRelationAdapter from 'ghost/adapters/embedded-relation-adapter';
export default EmbeddedRelationAdapter.extend({
shouldBackgroundReloadRecord: function () {
shouldBackgroundReloadRecord() {
return false;
}

View File

@ -1,21 +1,22 @@
import Ember from 'ember';
import DS from 'ember-data';
import ghostPaths from 'ghost/utils/ghost-paths';
import Ember from 'ember';
const {inject} = Ember;
const {RESTAdapter} = DS;
export default DS.RESTAdapter.extend({
export default RESTAdapter.extend({
host: window.location.origin,
namespace: ghostPaths().apiRoot.slice(1),
session: inject.service('session'),
shouldBackgroundReloadRecord: function () {
shouldBackgroundReloadRecord() {
return false;
},
query: function (store, type, query) {
var id;
query(store, type, query) {
let id;
if (query.id) {
id = query.id;
@ -25,9 +26,9 @@ export default DS.RESTAdapter.extend({
return this.ajax(this.buildURL(type.modelName, id), 'GET', {data: query});
},
buildURL: function (type, id) {
buildURL(type, id) {
// Ensure trailing slashes
var url = this._super(type, id);
let url = this._super(type, id);
if (url.slice(-1) !== '/') {
url += '/';
@ -42,15 +43,15 @@ export default DS.RESTAdapter.extend({
// response body for successful DELETEs.
// Non-2xx (failure) responses will still work correctly as Ember will turn
// them into rejected promises.
deleteRecord: function () {
var response = this._super.apply(this, arguments);
deleteRecord() {
let response = this._super(...arguments);
return response.then(function () {
return response.then(() => {
return null;
});
},
handleResponse: function (status) {
handleResponse(status) {
if (status === 401) {
if (this.get('session.isAuthenticated')) {
this.get('session').invalidate();

View File

@ -1,6 +1,8 @@
import Ember from 'ember';
import BaseAdapter from 'ghost/adapters/base';
const {get, isNone} = Ember;
// EmbeddedRelationAdapter will augment the query object in calls made to
// DS.Store#findRecord, findAll, query, and queryRecord with the correct "includes"
// (?include=relatedType) by introspecting on the provided subclass of the DS.Model.
@ -12,16 +14,16 @@ import BaseAdapter from 'ghost/adapters/base';
// roles: DS.hasMany('role', { embedded: 'always' }) => ?include=roles
export default BaseAdapter.extend({
find: function (store, type, id, snapshot) {
find(store, type, id, snapshot) {
return this.ajax(this.buildIncludeURL(store, type.modelName, id, snapshot, 'find'), 'GET');
},
findRecord: function (store, type, id, snapshot) {
findRecord(store, type, id, snapshot) {
return this.ajax(this.buildIncludeURL(store, type.modelName, id, snapshot, 'findRecord'), 'GET');
},
findAll: function (store, type, sinceToken) {
var query, url;
findAll(store, type, sinceToken) {
let query, url;
if (sinceToken) {
query = {since: sinceToken};
@ -32,60 +34,59 @@ export default BaseAdapter.extend({
return this.ajax(url, 'GET', {data: query});
},
query: function (store, type, query) {
query(store, type, query) {
return this._super(store, type, this.buildQuery(store, type.modelName, query));
},
queryRecord: function (store, type, query) {
queryRecord(store, type, query) {
return this._super(store, type, this.buildQuery(store, type.modelName, query));
},
createRecord: function (store, type, snapshot) {
createRecord(store, type, snapshot) {
return this.saveRecord(store, type, snapshot, {method: 'POST'});
},
updateRecord: function (store, type, snapshot) {
var options = {
updateRecord(store, type, snapshot) {
let options = {
method: 'PUT',
id: Ember.get(snapshot, 'id')
id: get(snapshot, 'id')
};
return this.saveRecord(store, type, snapshot, options);
},
saveRecord: function (store, type, snapshot, options) {
options = options || {};
saveRecord(store, type, snapshot, options) {
let _options = options || {};
let url = this.buildIncludeURL(store, type.modelName, _options.id, snapshot, 'createRecord');
let payload = this.preparePayload(store, type, snapshot);
var url = this.buildIncludeURL(store, type.modelName, options.id, snapshot, 'createRecord'),
payload = this.preparePayload(store, type, snapshot);
return this.ajax(url, options.method, payload);
return this.ajax(url, _options.method, payload);
},
preparePayload: function (store, type, snapshot) {
var serializer = store.serializerFor(type.modelName),
payload = {};
preparePayload(store, type, snapshot) {
let serializer = store.serializerFor(type.modelName);
let payload = {};
serializer.serializeIntoHash(payload, type, snapshot);
return {data: payload};
},
buildIncludeURL: function (store, modelName, id, snapshot, requestType, query) {
var url = this.buildURL(modelName, id, snapshot, requestType, query),
includes = this.getEmbeddedRelations(store, modelName);
buildIncludeURL(store, modelName, id, snapshot, requestType, query) {
let includes = this.getEmbeddedRelations(store, modelName);
let url = this.buildURL(modelName, id, snapshot, requestType, query);
if (includes.length) {
url += '?include=' + includes.join(',');
url += `?include=${includes.join(',')}`;
}
return url;
},
buildQuery: function (store, modelName, options) {
var toInclude = this.getEmbeddedRelations(store, modelName),
query = options || {},
deDupe = {};
buildQuery(store, modelName, options) {
let deDupe = {};
let toInclude = this.getEmbeddedRelations(store, modelName);
let query = options || {};
if (toInclude.length) {
// If this is a find by id, build a query object and attach the includes
@ -93,7 +94,7 @@ export default BaseAdapter.extend({
query = {};
query.id = options;
query.include = toInclude.join(',');
} else if (typeof options === 'object' || Ember.isNone(options)) {
} else if (typeof options === 'object' || isNone(options)) {
// If this is a find all (no existing query object) build one and attach
// the includes.
// If this is a find with an existing query object then merge the includes
@ -101,7 +102,7 @@ export default BaseAdapter.extend({
query = query || {};
toInclude = toInclude.concat(query.include ? query.include.split(',') : []);
toInclude.forEach(function (include) {
toInclude.forEach((include) => {
deDupe[include] = true;
});
@ -112,9 +113,9 @@ export default BaseAdapter.extend({
return query;
},
getEmbeddedRelations: function (store, modelName) {
var model = store.modelFor(modelName),
ret = [];
getEmbeddedRelations(store, modelName) {
let model = store.modelFor(modelName);
let ret = [];
// Iterate through the model's relationships and build a list
// of those that need to be pulled in via "include" from the API

View File

@ -1,9 +1,9 @@
import ApplicationAdapter from 'ghost/adapters/application';
export default ApplicationAdapter.extend({
updateRecord: function (store, type, record) {
var data = {},
serializer = store.serializerFor(type.modelName);
updateRecord(store, type, record) {
let data = {};
let serializer = store.serializerFor(type.modelName);
// remove the fake id that we added onto the model.
delete record.id;
@ -14,6 +14,6 @@ export default ApplicationAdapter.extend({
// use the ApplicationAdapter's buildURL method but do not
// pass in an id.
return this.ajax(this.buildURL(type.modelName), 'PUT', {data: data});
return this.ajax(this.buildURL(type.modelName), 'PUT', {data});
}
});

View File

@ -2,14 +2,14 @@ import ApplicationAdapter from 'ghost/adapters/application';
import SlugUrl from 'ghost/mixins/slug-url';
export default ApplicationAdapter.extend(SlugUrl, {
find: function (store, type, id) {
return this.findQuery(store, type, {id: id, status: 'all'});
find(store, type, id) {
return this.findQuery(store, type, {id, status: 'all'});
},
// TODO: This is needed because the API currently expects you to know the
// status of the record before retrieving by ID. Quick fix is to always
// include status=all in the query
findRecord: function (store, type, id, snapshot) {
findRecord(store, type, id, snapshot) {
let url = this.buildIncludeURL(store, type.modelName, id, snapshot, 'findRecord');
url += '&status=all';
@ -17,7 +17,7 @@ export default ApplicationAdapter.extend(SlugUrl, {
return this.ajax(url, 'GET');
},
findAll: function (store, type, id) {
return this.query(store, type, {id: id, status: 'all'});
findAll(store, type, id) {
return this.query(store, type, {id, status: 'all'});
}
});

View File

@ -5,12 +5,14 @@ import 'ghost/utils/link-component';
import 'ghost/utils/text-field';
import config from './config/environment';
const {Application} = Ember;
Ember.MODEL_FACTORY_INJECTIONS = true;
var App = Ember.Application.extend({
let App = Application.extend({
Resolver,
modulePrefix: config.modulePrefix,
podModulePrefix: config.podModulePrefix,
Resolver: Resolver
podModulePrefix: config.podModulePrefix
});
loadInitializers(App, config.modulePrefix);

View File

@ -1,12 +1,11 @@
import ghostPaths from 'ghost/utils/ghost-paths';
var UploadUi,
Ghost = ghostPaths();
let Ghost = ghostPaths();
UploadUi = function ($dropzone, settings) {
var $url = '<div class="js-url"><input class="url js-upload-url gh-input" type="url" placeholder="http://"/></div>',
$cancel = '<a class="image-cancel icon-trash js-cancel" title="Delete"><span class="hidden">Delete</span></a>',
$progress = $('<div />', {
let UploadUi = function ($dropzone, settings) {
let $url = '<div class="js-url"><input class="url js-upload-url gh-input" type="url" placeholder="http://"/></div>';
let $cancel = '<a class="image-cancel icon-trash js-cancel" title="Delete"><span class="hidden">Delete</span></a>';
let $progress = $('<div />', {
class: 'js-upload-progress progress progress-success active',
role: 'progressbar',
'aria-valuemin': '0',
@ -17,38 +16,36 @@ UploadUi = function ($dropzone, settings) {
}));
$.extend(this, {
complete: function (result) {
var self = this;
complete(result) {
function showImage(width, height) {
$dropzone.find('img.js-upload-target').attr({width: width, height: height}).css({display: 'block'});
$dropzone.find('img.js-upload-target').attr({width, height}).css({display: 'block'});
$dropzone.find('.fileupload-loading').remove();
$dropzone.css({height: 'auto'});
$dropzone.delay(250).animate({opacity: 100}, 1000, function () {
$dropzone.delay(250).animate({opacity: 100}, 1000, () => {
$('.js-button-accept').prop('disabled', false);
self.init();
this.init();
});
}
function animateDropzone($img) {
$dropzone.animate({opacity: 0}, 250, function () {
$dropzone.animate({opacity: 0}, 250, () => {
$dropzone.removeClass('image-uploader').addClass('pre-image-uploader');
$dropzone.css({minHeight: 0});
self.removeExtras();
$dropzone.animate({height: $img.height()}, 250, function () {
this.removeExtras();
$dropzone.animate({height: $img.height()}, 250, () => {
showImage($img.width(), $img.height());
});
});
}
function preLoadImage() {
var $img = $dropzone.find('img.js-upload-target')
let $img = $dropzone.find('img.js-upload-target')
.attr({src: '', width: 'auto', height: 'auto'});
$progress.animate({opacity: 0}, 250, function () {
$dropzone.find('span.media').after('<img class="fileupload-loading" src="' + Ghost.subdir + '/ghost/img/loadingcat.gif" />');
$progress.animate({opacity: 0}, 250, () => {
$dropzone.find('span.media').after(`<img class="fileupload-loading" src="${Ghost.subdir}/ghost/img/loadingcat.gif" />`);
});
$img.one('load', function () {
$img.one('load', () => {
$dropzone.trigger('uploadsuccess', [result]);
animateDropzone($img);
}).attr('src', result);
@ -56,12 +53,10 @@ UploadUi = function ($dropzone, settings) {
preLoadImage();
},
bindFileUpload: function () {
var self = this;
bindFileUpload() {
$dropzone.find('.js-fileupload').fileupload().fileupload('option', {
url: Ghost.apiRoot + '/uploads/',
add: function (e, data) {
url: `${Ghost.apiRoot}/uploads/`,
add(e, data) {
/*jshint unused:false*/
$('.js-button-accept').prop('disabled', true);
$dropzone.find('.js-fileupload').removeClass('right');
@ -69,7 +64,7 @@ UploadUi = function ($dropzone, settings) {
$progress.find('.js-upload-progress-bar').removeClass('fail');
$dropzone.trigger('uploadstart', [$dropzone.attr('id')]);
$dropzone.find('span.media, div.description, a.image-url, a.image-webcam')
.animate({opacity: 0}, 250, function () {
.animate({opacity: 0}, 250, () => {
$dropzone.find('div.description').hide().css({opacity: 100});
if (settings.progressbar) {
$dropzone.find('div.js-fail').after($progress);
@ -79,15 +74,15 @@ UploadUi = function ($dropzone, settings) {
});
},
dropZone: settings.fileStorage ? $dropzone : null,
progressall: function (e, data) {
progressall(e, data) {
/*jshint unused:false*/
var progress = parseInt(data.loaded / data.total * 100, 10);
let progress = parseInt(data.loaded / data.total * 100, 10);
if (settings.progressbar) {
$dropzone.trigger('uploadprogress', [progress, data]);
$progress.find('.js-upload-progress-bar').css('width', progress + '%');
$progress.find('.js-upload-progress-bar').css('width', `${progress}%`);
}
},
fail: function (e, data) {
fail(e, data) {
/*jshint unused:false*/
$('.js-button-accept').prop('disabled', false);
$dropzone.trigger('uploadfailure', [data.result]);
@ -100,21 +95,21 @@ UploadUi = function ($dropzone, settings) {
$dropzone.find('div.js-fail').text('Something went wrong :(');
}
$dropzone.find('div.js-fail, button.js-fail').fadeIn(1500);
$dropzone.find('button.js-fail').on('click', function () {
$dropzone.find('button.js-fail').on('click', () => {
$dropzone.css({minHeight: 0});
$dropzone.find('div.description').show();
self.removeExtras();
self.init();
this.removeExtras();
this.init();
});
},
done: function (e, data) {
done(e, data) {
/*jshint unused:false*/
self.complete(data.result);
this.complete(data.result);
}
});
},
buildExtras: function () {
buildExtras() {
if (!$dropzone.find('span.media')[0]) {
$dropzone.prepend('<span class="media"><span class="hidden">Image Upload</span></span>');
}
@ -135,13 +130,11 @@ UploadUi = function ($dropzone, settings) {
// }
},
removeExtras: function () {
removeExtras() {
$dropzone.find('span.media, div.js-upload-progress, a.image-url, a.image-upload, a.image-webcam, div.js-fail, button.js-fail, a.js-cancel').remove();
},
initWithDropzone: function () {
var self = this;
initWithDropzone() {
// This is the start point if no image exists
$dropzone.find('img.js-upload-target').css({display: 'none'});
$dropzone.find('div.description').show();
@ -150,24 +143,23 @@ UploadUi = function ($dropzone, settings) {
this.buildExtras();
this.bindFileUpload();
if (!settings.fileStorage) {
self.initUrl();
this.initUrl();
return;
}
$dropzone.find('a.image-url').on('click', function () {
self.initUrl();
$dropzone.find('a.image-url').on('click', () => {
this.initUrl();
});
},
initUrl: function () {
var self = this, val;
initUrl() {
this.removeExtras();
$dropzone.addClass('image-uploader-url').removeClass('pre-image-uploader');
$dropzone.find('.js-fileupload').addClass('right');
$dropzone.find('.js-cancel').on('click', function () {
$dropzone.find('.js-cancel').on('click', () => {
$dropzone.find('.js-url').remove();
$dropzone.find('.js-fileupload').removeClass('right');
$dropzone.trigger('imagecleared');
self.removeExtras();
self.initWithDropzone();
this.removeExtras();
this.initWithDropzone();
});
$dropzone.find('div.description').before($url);
@ -177,16 +169,17 @@ UploadUi = function ($dropzone, settings) {
$dropzone.find('div.description').hide();
}
$dropzone.find('.js-button-accept').on('click', function () {
val = $dropzone.find('.js-upload-url').val();
$dropzone.find('.js-button-accept').on('click', () => {
let val = $dropzone.find('.js-upload-url').val();
$dropzone.find('div.description').hide();
$dropzone.find('.js-fileupload').removeClass('right');
$dropzone.find('.js-url').remove();
if (val === '') {
$dropzone.trigger('uploadsuccess', 'http://');
self.initWithDropzone();
this.initWithDropzone();
} else {
self.complete(val);
this.complete(val);
}
});
@ -195,37 +188,35 @@ UploadUi = function ($dropzone, settings) {
$dropzone.append('<a class="image-upload icon-photos" title="Add image"><span class="hidden">Upload</span></a>');
}
$dropzone.find('a.image-upload').on('click', function () {
$dropzone.find('a.image-upload').on('click', () => {
$dropzone.find('.js-url').remove();
$dropzone.find('.js-fileupload').removeClass('right');
self.initWithDropzone();
this.initWithDropzone();
});
},
initWithImage: function () {
var self = this;
initWithImage() {
// This is the start point if an image already exists
this.removeExtras();
$dropzone.removeClass('image-uploader image-uploader-url').addClass('pre-image-uploader');
$dropzone.find('div.description').hide();
$dropzone.find('img.js-upload-target').show();
$dropzone.append($cancel);
$dropzone.find('.js-cancel').on('click', function () {
$dropzone.find('.js-cancel').on('click', () => {
$dropzone.find('img.js-upload-target').attr({src: ''});
$dropzone.find('div.description').show();
$dropzone.trigger('imagecleared');
$dropzone.delay(250).animate({opacity: 100}, 1000, function () {
self.init();
$dropzone.delay(250).animate({opacity: 100}, 1000, () => {
this.init();
});
$dropzone.trigger('uploadsuccess', 'http://');
self.initWithDropzone();
this.initWithDropzone();
});
},
init: function () {
var imageTarget = $dropzone.find('img.js-upload-target');
init() {
let imageTarget = $dropzone.find('img.js-upload-target');
// First check if field image is defined by checking for js-upload-target class
if (!imageTarget[0]) {
// This ensures there is an image we can hook into to display uploaded image
@ -239,7 +230,7 @@ UploadUi = function ($dropzone, settings) {
}
},
reset: function () {
reset() {
$dropzone.find('.js-url').remove();
$dropzone.find('.js-fileupload').removeClass('right');
this.removeExtras();
@ -249,17 +240,15 @@ UploadUi = function ($dropzone, settings) {
};
export default function (options) {
var settings = $.extend({
let settings = $.extend({
progressbar: true,
editor: false,
fileStorage: true
}, options);
return this.each(function () {
var $dropzone = $(this),
ui;
ui = new UploadUi($dropzone, settings);
let $dropzone = $(this);
let ui = new UploadUi($dropzone, settings);
$(this).attr('data-uploaderui', true);
this.uploaderUi = ui;
ui.init();

View File

@ -1,21 +1,25 @@
import Ember from 'ember';
import Authenticator from 'ember-simple-auth/authenticators/oauth2-password-grant';
const {computed, inject} = Ember;
export default Authenticator.extend({
config: Ember.inject.service(),
ghostPaths: Ember.inject.service('ghost-paths'),
config: inject.service(),
ghostPaths: inject.service('ghost-paths'),
serverTokenEndpoint: Ember.computed('ghostPaths.apiRoot', function () {
return this.get('ghostPaths.apiRoot') + '/authentication/token';
serverTokenEndpoint: computed('ghostPaths.apiRoot', function () {
return `${this.get('ghostPaths.apiRoot')}/authentication/token`;
}),
serverTokenRevocationEndpoint: Ember.computed('ghostPaths.apiRoot', function () {
return this.get('ghostPaths.apiRoot') + '/authentication/revoke';
serverTokenRevocationEndpoint: computed('ghostPaths.apiRoot', function () {
return `${this.get('ghostPaths.apiRoot')}/authentication/revoke`;
}),
makeRequest: function (url, data) {
makeRequest(url, data) {
/* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */
data.client_id = this.get('config.clientId');
data.client_secret = this.get('config.clientSecret');
/* jscs:enable requireCamelCaseOrUpperCaseIdentifiers */
return this._super(url, data);
}
});

View File

@ -1,18 +1,20 @@
import Ember from 'ember';
export default Ember.Component.extend({
const {Component, on, run} = Ember;
export default Component.extend({
tagName: 'li',
classNameBindings: ['active'],
active: false,
linkClasses: null,
unfocusLink: Ember.on('click', function () {
unfocusLink: on('click', function () {
this.$('a').blur();
}),
actions: {
setActive: function (value) {
Ember.run.schedule('afterRender', this, function () {
setActive(value) {
run.schedule('afterRender', this, function () {
this.set('active', value);
});
}

View File

@ -1,16 +1,18 @@
import Ember from 'ember';
export default Ember.Component.extend({
const {Component, computed, inject} = Ember;
export default Component.extend({
tagName: 'article',
classNames: ['gh-alert'],
classNameBindings: ['typeClass'],
notifications: Ember.inject.service(),
notifications: inject.service(),
typeClass: Ember.computed('message.type', function () {
var classes = '',
type = this.get('message.type'),
typeMapping;
typeClass: computed('message.type', function () {
let type = this.get('message.type');
let classes = '';
let typeMapping;
typeMapping = {
success: 'green',
@ -20,14 +22,14 @@ export default Ember.Component.extend({
};
if (typeMapping[type] !== undefined) {
classes += 'gh-alert-' + typeMapping[type];
classes += `gh-alert-${typeMapping[type]}`;
}
return classes;
}),
actions: {
closeNotification: function () {
closeNotification() {
this.get('notifications').closeNotification(this.get('message'));
}
}

View File

@ -1,14 +1,17 @@
import Ember from 'ember';
export default Ember.Component.extend({
const {Component, computed, inject, observer} = Ember;
const {alias} = computed;
export default Component.extend({
tagName: 'aside',
classNames: 'gh-alerts',
notifications: Ember.inject.service(),
notifications: inject.service(),
messages: Ember.computed.alias('notifications.alerts'),
messages: alias('notifications.alerts'),
messageCountObserver: Ember.observer('messages.[]', function () {
messageCountObserver: observer('messages.[]', function () {
this.sendAction('notify', this.get('messages').length);
})
});

View File

@ -1,12 +1,14 @@
import Ember from 'ember';
export default Ember.Component.extend({
const {Component, observer} = Ember;
export default Component.extend({
classNames: ['gh-app'],
showSettingsMenu: false,
toggleSettingsMenuBodyClass: Ember.observer('showSettingsMenu', function () {
var showSettingsMenu = this.get('showSettingsMenu');
toggleSettingsMenuBodyClass: observer('showSettingsMenu', function () {
let showSettingsMenu = this.get('showSettingsMenu');
Ember.$('body').toggleClass('settings-menu-expanded', showSettingsMenu);
})

View File

@ -1,6 +1,9 @@
import Ember from 'ember';
export default Ember.Component.extend({
const {Component, inject} = Ember;
export default Component.extend({
tagName: '',
config: Ember.inject.service()
config: inject.service()
});

View File

@ -1,14 +1,13 @@
/* global CodeMirror */
import Ember from 'ember';
export default Ember.Component.extend({
const {Component} = Ember;
// DOM stuff
export default Component.extend({
classNameBindings: ['isFocused:focused'],
isFocused: false,
value: '', // make sure a value exists
_editor: null, // reference to CodeMirror editor
isFocused: false,
// options for the editor
lineNumbers: true,
@ -16,9 +15,11 @@ export default Ember.Component.extend({
mode: 'htmlmixed',
theme: 'xq-light',
didInsertElement: function () {
var options = this.getProperties('lineNumbers', 'indentUnit', 'mode', 'theme'),
editor = new CodeMirror(this.get('element'), options);
_editor: null, // reference to CodeMirror editor
didInsertElement() {
let options = this.getProperties('lineNumbers', 'indentUnit', 'mode', 'theme');
let editor = new CodeMirror(this.get('element'), options);
editor.getDoc().setValue(this.get('value'));
@ -34,10 +35,9 @@ export default Ember.Component.extend({
this._editor = editor;
},
willDestroyElement: function () {
var editor = this._editor.getWrapperElement();
willDestroyElement() {
let editor = this._editor.getWrapperElement();
editor.parentNode.removeChild(editor);
this._editor = null;
}
});

View File

@ -12,17 +12,19 @@ Example:
import Ember from 'ember';
export default Ember.Component.extend({
const {Component} = Ember;
export default Component.extend({
classNames: ['content-cover'],
onClick: null,
onMouseEnter: null,
click: function () {
click() {
this.sendAction('onClick');
},
mouseEnter: function () {
mouseEnter() {
this.sendAction('onMouseEnter');
}
});

View File

@ -1,21 +1,23 @@
import Ember from 'ember';
import setScrollClassName from 'ghost/utils/set-scroll-classname';
export default Ember.Component.extend({
const {Component, run} = Ember;
export default Component.extend({
classNames: ['content-preview-content'],
content: null,
didInsertElement: function () {
var el = this.$();
didInsertElement() {
let el = this.$();
el.on('scroll', Ember.run.bind(el, setScrollClassName, {
el.on('scroll', run.bind(el, setScrollClassName, {
target: el.closest('.content-preview'),
offset: 10
}));
},
didReceiveAttrs: function (options) {
didReceiveAttrs(options) {
// adjust when didReceiveAttrs gets both newAttrs and oldAttrs
if (options.newAttrs.content && this.get('content') !== options.newAttrs.content.value) {
let el = this.$();
@ -26,8 +28,8 @@ export default Ember.Component.extend({
}
},
willDestroyElement: function () {
var el = this.$();
willDestroyElement() {
let el = this.$();
el.off('scroll');
}

View File

@ -1,9 +1,11 @@
import Ember from 'ember';
export default Ember.Component.extend({
const {Component, computed, inject} = Ember;
export default Component.extend({
tagName: 'section',
classNames: ['gh-view', 'content-view-container'],
mediaQueries: Ember.inject.service(),
previewIsHidden: Ember.computed.reads('mediaQueries.maxWidth900')
mediaQueries: inject.service(),
previewIsHidden: computed.reads('mediaQueries.maxWidth900')
});

View File

@ -1,7 +1,9 @@
import Ember from 'ember';
import DropdownMixin from 'ghost/mixins/dropdown-mixin';
export default Ember.Component.extend(DropdownMixin, {
const {Component, inject} = Ember;
export default Component.extend(DropdownMixin, {
tagName: 'button',
attributeBindings: 'role',
role: 'button',
@ -9,10 +11,10 @@ export default Ember.Component.extend(DropdownMixin, {
// matches with the dropdown this button toggles
dropdownName: null,
dropdown: Ember.inject.service(),
dropdown: inject.service(),
// Notify dropdown service this dropdown should be toggled
click: function (event) {
click(event) {
this._super(event);
this.get('dropdown').toggleDropdown(this.get('dropdownName'), this);
}

View File

@ -1,7 +1,9 @@
import Ember from 'ember';
import DropdownMixin from 'ghost/mixins/dropdown-mixin';
export default Ember.Component.extend(DropdownMixin, {
const {Component, computed, inject} = Ember;
export default Component.extend(DropdownMixin, {
classNames: 'dropdown',
classNameBindings: ['fadeIn:fade-in-scale:fade-out', 'isOpen:open:closed'],
@ -15,29 +17,28 @@ export default Ember.Component.extend(DropdownMixin, {
isOpen: false,
// Managed the toggle between the fade-in and fade-out classes
fadeIn: Ember.computed('isOpen', 'closing', function () {
fadeIn: computed('isOpen', 'closing', function () {
return this.get('isOpen') && !this.get('closing');
}),
dropdown: Ember.inject.service(),
dropdown: inject.service(),
open: function () {
open() {
this.set('isOpen', true);
this.set('closing', false);
this.set('button.isOpen', true);
},
close: function () {
var self = this;
close() {
this.set('closing', true);
if (this.get('button')) {
this.set('button.isOpen', false);
}
this.$().on('animationend webkitAnimationEnd oanimationend MSAnimationEnd', function (event) {
this.$().on('animationend webkitAnimationEnd oanimationend MSAnimationEnd', (event) => {
if (event.originalEvent.animationName === 'fade-out') {
Ember.run(self, function () {
Ember.run(this, function () {
if (this.get('closing')) {
this.set('isOpen', false);
this.set('closing', false);
@ -48,12 +49,12 @@ export default Ember.Component.extend(DropdownMixin, {
},
// Called by the dropdown service when any dropdown button is clicked.
toggle: function (options) {
var isClosing = this.get('closing'),
isOpen = this.get('isOpen'),
name = this.get('name'),
button = this.get('button'),
targetDropdownName = options.target;
toggle(options) {
let isClosing = this.get('closing');
let isOpen = this.get('isOpen');
let name = this.get('name');
let targetDropdownName = options.target;
let button = this.get('button');
if (name === targetDropdownName && (!isOpen || isClosing)) {
if (!button) {
@ -66,7 +67,7 @@ export default Ember.Component.extend(DropdownMixin, {
}
},
click: function (event) {
click(event) {
this._super(event);
if (this.get('closeOnClick')) {
@ -74,19 +75,19 @@ export default Ember.Component.extend(DropdownMixin, {
}
},
didInsertElement: function () {
this._super();
didInsertElement() {
let dropdownService = this.get('dropdown');
var dropdownService = this.get('dropdown');
this._super(...arguments);
dropdownService.on('close', this, this.close);
dropdownService.on('toggle', this, this.toggle);
},
willDestroyElement: function () {
this._super();
willDestroyElement() {
let dropdownService = this.get('dropdown');
var dropdownService = this.get('dropdown');
this._super(...arguments);
dropdownService.off('close', this, this.close);
dropdownService.off('toggle', this, this.toggle);

View File

@ -3,20 +3,22 @@ import EditorAPI from 'ghost/mixins/ed-editor-api';
import EditorShortcuts from 'ghost/mixins/ed-editor-shortcuts';
import EditorScroll from 'ghost/mixins/ed-editor-scroll';
export default Ember.TextArea.extend(EditorAPI, EditorShortcuts, EditorScroll, {
const {TextArea, run} = Ember;
export default TextArea.extend(EditorAPI, EditorShortcuts, EditorScroll, {
focus: false,
/**
* Tell the controller about focusIn events, will trigger an autosave on a new document
*/
focusIn: function () {
focusIn() {
this.sendAction('onFocusIn');
},
/**
* Sets the focus of the textarea if needed
*/
setFocus: function () {
setFocus() {
if (this.get('focus')) {
this.$().val(this.$().val()).focus();
}
@ -25,17 +27,17 @@ export default Ember.TextArea.extend(EditorAPI, EditorShortcuts, EditorScroll, {
/**
* Sets up properties at render time
*/
didInsertElement: function () {
this._super();
didInsertElement() {
this._super(...arguments);
this.setFocus();
this.sendAction('setEditor', this);
Ember.run.scheduleOnce('afterRender', this, this.afterRenderEvent);
run.scheduleOnce('afterRender', this, this.afterRenderEvent);
},
afterRenderEvent: function () {
afterRenderEvent() {
if (this.get('focus') && this.get('focusCursorAtEnd')) {
this.setSelection('end');
}
@ -44,16 +46,16 @@ export default Ember.TextArea.extend(EditorAPI, EditorShortcuts, EditorScroll, {
/**
* Disable editing in the textarea (used while an upload is in progress)
*/
disable: function () {
var textarea = this.get('element');
disable() {
let textarea = this.get('element');
textarea.setAttribute('readonly', 'readonly');
},
/**
* Reenable editing in the textarea
*/
enable: function () {
var textarea = this.get('element');
enable() {
let textarea = this.get('element');
textarea.removeAttribute('readonly');
}
});

View File

@ -1,39 +1,43 @@
import Ember from 'ember';
import uploader from 'ghost/assets/lib/uploader';
export default Ember.Component.extend({
config: Ember.inject.service(),
const {Component, inject, run} = Ember;
export default Component.extend({
config: inject.service(),
_scrollWrapper: null,
didInsertElement: function () {
didInsertElement() {
this._scrollWrapper = this.$().closest('.entry-preview-content');
this.adjustScrollPosition(this.get('scrollPosition'));
Ember.run.scheduleOnce('afterRender', this, this.dropzoneHandler);
run.scheduleOnce('afterRender', this, this.dropzoneHandler);
},
didReceiveAttrs: function (attrs) {
if (!attrs.oldAttrs) { return; }
didReceiveAttrs(attrs) {
if (!attrs.oldAttrs) {
return;
}
if (attrs.newAttrs.scrollPosition && attrs.newAttrs.scrollPosition.value !== attrs.oldAttrs.scrollPosition.value) {
this.adjustScrollPosition(attrs.newAttrs.scrollPosition.value);
}
if (attrs.newAttrs.markdown.value !== attrs.oldAttrs.markdown.value) {
Ember.run.scheduleOnce('afterRender', this, this.dropzoneHandler);
run.scheduleOnce('afterRender', this, this.dropzoneHandler);
}
},
adjustScrollPosition: function (scrollPosition) {
var scrollWrapper = this._scrollWrapper;
adjustScrollPosition(scrollPosition) {
let scrollWrapper = this._scrollWrapper;
if (scrollWrapper) {
scrollWrapper.scrollTop(scrollPosition);
}
},
dropzoneHandler: function () {
var dropzones = $('.js-drop-zone[data-uploaderui!="true"]');
dropzoneHandler() {
let dropzones = $('.js-drop-zone[data-uploaderui!="true"]');
if (dropzones.length) {
uploader.call(dropzones, {
@ -41,10 +45,10 @@ export default Ember.Component.extend({
fileStorage: this.get('config.fileStorage')
});
dropzones.on('uploadstart', Ember.run.bind(this, 'sendAction', 'uploadStarted'));
dropzones.on('uploadfailure', Ember.run.bind(this, 'sendAction', 'uploadFinished'));
dropzones.on('uploadsuccess', Ember.run.bind(this, 'sendAction', 'uploadFinished'));
dropzones.on('uploadsuccess', Ember.run.bind(this, 'sendAction', 'uploadSuccess'));
dropzones.on('uploadstart', run.bind(this, 'sendAction', 'uploadStarted'));
dropzones.on('uploadfailure', run.bind(this, 'sendAction', 'uploadFinished'));
dropzones.on('uploadsuccess', run.bind(this, 'sendAction', 'uploadFinished'));
dropzones.on('uploadsuccess', run.bind(this, 'sendAction', 'uploadSuccess'));
// Set the current height so we can listen
this.sendAction('updateHeight', this.$().height());

View File

@ -1,6 +1,8 @@
import Ember from 'ember';
export default Ember.Component.extend({
const {Component, computed} = Ember;
export default Component.extend({
tagName: 'section',
classNames: ['splitbtn', 'js-publish-splitbutton'],
classNameBindings: ['isNew:unsaved'],
@ -12,36 +14,36 @@ export default Ember.Component.extend({
submitting: false,
// Tracks whether we're going to change the state of the post on save
isDangerous: Ember.computed('isPublished', 'willPublish', function () {
isDangerous: computed('isPublished', 'willPublish', function () {
return this.get('isPublished') !== this.get('willPublish');
}),
publishText: Ember.computed('isPublished', 'postOrPage', function () {
return this.get('isPublished') ? 'Update ' + this.get('postOrPage') : 'Publish Now';
publishText: computed('isPublished', 'postOrPage', function () {
return this.get('isPublished') ? `Update ${this.get('postOrPage')}` : 'Publish Now';
}),
draftText: Ember.computed('isPublished', function () {
draftText: computed('isPublished', function () {
return this.get('isPublished') ? 'Unpublish' : 'Save Draft';
}),
deleteText: Ember.computed('postOrPage', function () {
return 'Delete ' + this.get('postOrPage');
deleteText: computed('postOrPage', function () {
return `Delete ${this.get('postOrPage')}`;
}),
saveText: Ember.computed('willPublish', 'publishText', 'draftText', function () {
saveText: computed('willPublish', 'publishText', 'draftText', function () {
return this.get('willPublish') ? this.get('publishText') : this.get('draftText');
}),
actions: {
save: function () {
save() {
this.sendAction('save');
},
setSaveType: function (saveType) {
setSaveType(saveType) {
this.sendAction('setSaveType', saveType);
},
delete: function () {
delete() {
this.sendAction('delete');
}
}

View File

@ -1,54 +1,37 @@
import Ember from 'ember';
import setScrollClassName from 'ghost/utils/set-scroll-classname';
export default Ember.Component.extend({
const {Component, computed, run} = Ember;
const {equal} = computed;
export default Component.extend({
tagName: 'section',
classNames: ['gh-view'],
scheduleAfterRender: function () {
Ember.run.scheduleOnce('afterRender', this, this.afterRenderEvent);
},
didInsertElement: function () {
this.scheduleAfterRender();
},
afterRenderEvent: function () {
var $previewViewPort = this.$('.js-entry-preview-content');
// cache these elements for use in other methods
this.set('$previewViewPort', $previewViewPort);
this.set('$previewContent', this.$('.js-rendered-markdown'));
$previewViewPort.on('scroll', Ember.run.bind($previewViewPort, setScrollClassName, {
target: this.$('.js-entry-preview'),
offset: 10
}));
},
willDestroyElement: function () {
// removes scroll handlers from the view
this.get('$previewViewPort').off('scroll');
},
// updated when gh-ed-editor component scrolls
editorScrollInfo: null,
// updated when markdown is rendered
height: null,
activeTab: 'markdown',
markdownActive: equal('activeTab', 'markdown'),
previewActive: equal('activeTab', 'preview'),
// HTML Preview listens to scrollPosition and updates its scrollTop value
// This property receives scrollInfo from the textEditor, and height from the preview pane, and will update the
// scrollPosition value such that when either scrolling or typing-at-the-end of the text editor the preview pane
// stays in sync
scrollPosition: Ember.computed('editorScrollInfo', 'height', function () {
if (!this.get('editorScrollInfo') || !this.get('$previewContent') || !this.get('$previewViewPort')) {
scrollPosition: computed('editorScrollInfo', 'height', function () {
let scrollInfo = this.get('editorScrollInfo');
let $previewContent = this.get('$previewContent');
let $previewViewPort = this.get('$previewViewPort');
if (!scrollInfo || !$previewContent || !$previewViewPort) {
return 0;
}
var scrollInfo = this.get('editorScrollInfo'),
previewHeight = this.get('$previewContent').height() - this.get('$previewViewPort').height(),
previewPosition,
ratio;
let previewHeight = $previewContent.height() - $previewViewPort.height();
let previewPosition, ratio;
ratio = previewHeight / scrollInfo.diff;
previewPosition = scrollInfo.top * ratio;
@ -56,12 +39,34 @@ export default Ember.Component.extend({
return previewPosition;
}),
activeTab: 'markdown',
markdownActive: Ember.computed.equal('activeTab', 'markdown'),
previewActive: Ember.computed.equal('activeTab', 'preview'),
scheduleAfterRender() {
run.scheduleOnce('afterRender', this, this.afterRenderEvent);
},
didInsertElement() {
this.scheduleAfterRender();
},
afterRenderEvent() {
let $previewViewPort = this.$('.js-entry-preview-content');
// cache these elements for use in other methods
this.set('$previewViewPort', $previewViewPort);
this.set('$previewContent', this.$('.js-rendered-markdown'));
$previewViewPort.on('scroll', run.bind($previewViewPort, setScrollClassName, {
target: this.$('.js-entry-preview'),
offset: 10
}));
},
willDestroyElement() {
// removes scroll handlers from the view
this.get('$previewViewPort').off('scroll');
},
actions: {
selectTab: function (tab) {
selectTab(tab) {
this.set('activeTab', tab);
}
}

View File

@ -1,5 +1,7 @@
import Ember from 'ember';
const {Component, computed, isEmpty} = Ember;
/**
* Renders one random error message when passed a DS.Errors object
* and a property name. The message will be one of the ones associated with
@ -8,23 +10,23 @@ import Ember from 'ember';
* @param {DS.Errors} errors The DS.Errors object
* @param {string} property The property name
*/
export default Ember.Component.extend({
export default Component.extend({
tagName: 'p',
classNames: ['response'],
errors: null,
property: '',
isVisible: Ember.computed.notEmpty('errors'),
isVisible: computed.notEmpty('errors'),
message: Ember.computed('errors.[]', 'property', function () {
var property = this.get('property'),
errors = this.get('errors'),
messages = [],
index;
message: computed('errors.[]', 'property', function () {
let property = this.get('property');
let errors = this.get('errors');
let messages = [];
let index;
if (!Ember.isEmpty(errors) && errors.get(property)) {
errors.get(property).forEach(function (error) {
if (!isEmpty(errors) && errors.get(property)) {
errors.get(property).forEach((error) => {
messages.push(error);
});
index = Math.floor(Math.random() * messages.length);

View File

@ -1,10 +1,11 @@
import Ember from 'ember';
export default Ember.Component.extend({
const {Component} = Ember;
export default Component.extend({
_file: null,
uploadButtonText: 'Text',
uploadButtonDisabled: true,
onUpload: null,
@ -12,14 +13,14 @@ export default Ember.Component.extend({
shouldResetForm: true,
change: function (event) {
change(event) {
this.set('uploadButtonDisabled', false);
this.sendAction('onAdd');
this._file = event.target.files[0];
},
actions: {
upload: function () {
upload() {
if (!this.get('uploadButtonDisabled') && this._file) {
this.sendAction('onUpload', this._file);
}
@ -29,7 +30,7 @@ export default Ember.Component.extend({
// Reset form
if (this.get('shouldResetForm')) {
this.$().closest('form').get(0).reset();
this.$().closest('form')[0].reset();
}
}
}

View File

@ -2,21 +2,22 @@ import Ember from 'ember';
import InfiniteScrollMixin from 'ghost/mixins/infinite-scroll';
import setScrollClassName from 'ghost/utils/set-scroll-classname';
export default Ember.Component.extend(InfiniteScrollMixin, {
didRender: function () {
this._super();
const {Component, run} = Ember;
var el = this.$();
export default Component.extend(InfiniteScrollMixin, {
didRender() {
let el = this.$();
el.on('scroll', Ember.run.bind(el, setScrollClassName, {
this._super(...arguments);
el.on('scroll', run.bind(el, setScrollClassName, {
target: el.closest('.content-list'),
offset: 10
}));
},
willDestroyElement: function () {
this._super();
willDestroyElement() {
this._super(...arguments);
this.$().off('scroll');
}
});

View File

@ -1,4 +1,6 @@
import Ember from 'ember';
import InfiniteScrollMixin from 'ghost/mixins/infinite-scroll';
export default Ember.Component.extend(InfiniteScrollMixin);
const {Component} = Ember;
export default Component.extend(InfiniteScrollMixin);

View File

@ -1,6 +1,8 @@
import Ember from 'ember';
import TextInputMixin from 'ghost/mixins/text-input';
export default Ember.TextField.extend(TextInputMixin, {
const {TextField} = Ember;
export default TextField.extend(TextInputMixin, {
classNames: 'gh-input'
});

View File

@ -1,11 +1,13 @@
import Ember from 'ember';
export default Ember.Component.extend({
const {Component} = Ember;
export default Component.extend({
tagName: 'main',
classNames: ['gh-main'],
ariaRole: 'main',
mouseEnter: function () {
mouseEnter() {
this.sendAction('onMouseEnter');
}
});

View File

@ -1,3 +1,7 @@
import Ember from 'ember';
const {Component, computed, inject} = Ember;
/*
This cute little component has two jobs.
@ -8,17 +12,14 @@ state to render the appropriate icon.
On mobile, it renders a closing icon, and clicking it
closes the mobile menu
*/
import Ember from 'ember';
export default Ember.Component.extend({
export default Component.extend({
classNames: ['gh-menu-toggle'],
mediaQueries: Ember.inject.service(),
isMobile: Ember.computed.reads('mediaQueries.isMobile'),
mediaQueries: inject.service(),
isMobile: computed.reads('mediaQueries.isMobile'),
maximise: false,
iconClass: Ember.computed('maximise', 'isMobile', function () {
iconClass: computed('maximise', 'isMobile', function () {
if (this.get('maximise') && !this.get('isMobile')) {
return 'icon-maximise';
} else {
@ -26,7 +27,7 @@ export default Ember.Component.extend({
}
}),
click: function () {
click() {
if (this.get('isMobile')) {
this.sendAction('mobileAction');
} else {

View File

@ -1,59 +1,66 @@
import Ember from 'ember';
export default Ember.Component.extend({
didInsertElement: function () {
this.$('.js-modal-container, .js-modal-background').addClass('fade-in open');
this.$('.js-modal').addClass('open');
},
const {Component, computed} = Ember;
close: function () {
var self = this;
this.$('.js-modal, .js-modal-background').removeClass('fade-in').addClass('fade-out');
// The background should always be the last thing to fade out, so check on that instead of the content
this.$('.js-modal-background').on('animationend webkitAnimationEnd oanimationend MSAnimationEnd', function (event) {
if (event.originalEvent.animationName === 'fade-out') {
self.$('.js-modal, .js-modal-background').removeClass('open');
function K() {
return this;
}
});
this.sendAction();
},
export default Component.extend({
confirmaccept: 'confirmAccept',
confirmreject: 'confirmReject',
actions: {
closeModal: function () {
this.close();
},
confirm: function (type) {
this.sendAction('confirm' + type);
this.close();
},
noBubble: Ember.K
},
klass: computed('type', 'style', function () {
let classNames = [];
klass: Ember.computed('type', 'style', function () {
var classNames = [];
classNames.push(this.get('type') ? 'modal-' + this.get('type') : 'modal');
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);
this.get('style').split(',').forEach((style) => {
classNames.push(`modal-style-${style}`);
});
}
return classNames.join(' ');
}),
acceptButtonClass: Ember.computed('confirm.accept.buttonClass', function () {
acceptButtonClass: computed('confirm.accept.buttonClass', function () {
return this.get('confirm.accept.buttonClass') ? this.get('confirm.accept.buttonClass') : 'btn btn-green';
}),
rejectButtonClass: Ember.computed('confirm.reject.buttonClass', function () {
rejectButtonClass: computed('confirm.reject.buttonClass', function () {
return this.get('confirm.reject.buttonClass') ? this.get('confirm.reject.buttonClass') : 'btn btn-red';
})
}),
didInsertElement() {
this.$('.js-modal-container, .js-modal-background').addClass('fade-in open');
this.$('.js-modal').addClass('open');
},
close() {
this.$('.js-modal, .js-modal-background').removeClass('fade-in').addClass('fade-out');
// The background should always be the last thing to fade out, so check on that instead of the content
this.$('.js-modal-background').on('animationend webkitAnimationEnd oanimationend MSAnimationEnd', (event) => {
if (event.originalEvent.animationName === 'fade-out') {
this.$('.js-modal, .js-modal-background').removeClass('open');
}
});
this.sendAction();
},
actions: {
closeModal() {
this.close();
},
confirm(type) {
this.sendAction(`confirm${type}`);
this.close();
},
noBubble: K
}
});

View File

@ -1,33 +1,35 @@
import Ember from 'ember';
export default Ember.Component.extend({
const {Component, inject} = Ember;
export default Component.extend({
tagName: 'nav',
classNames: ['gh-nav'],
classNameBindings: ['open'],
config: Ember.inject.service(),
session: Ember.inject.service(),
open: false,
mouseEnter: function () {
config: inject.service(),
session: inject.service(),
mouseEnter() {
this.sendAction('onMouseEnter');
},
actions: {
toggleAutoNav: function () {
toggleAutoNav() {
this.sendAction('toggleMaximise');
},
openModal: function (modal) {
openModal(modal) {
this.sendAction('openModal', modal);
},
closeMobileMenu: function () {
closeMobileMenu() {
this.sendAction('closeMobileMenu');
},
openAutoNav: function () {
openAutoNav() {
this.sendAction('openAutoNav');
}
}

View File

@ -1,13 +1,14 @@
import Ember from 'ember';
export default Ember.Component.extend({
const {Component, run} = Ember;
export default Component.extend({
tagName: 'section',
classNames: 'gh-view',
didInsertElement: function () {
var navContainer = this.$('.js-gh-blognav'),
navElements = '.gh-blognav-item:not(.gh-blognav-item:last-child)',
self = this;
didInsertElement() {
let navContainer = this.$('.js-gh-blognav');
let navElements = '.gh-blognav-item:not(.gh-blognav-item:last-child)';
this._super(...arguments);
@ -15,21 +16,21 @@ export default Ember.Component.extend({
handle: '.gh-blognav-grab',
items: navElements,
start: function (event, ui) {
Ember.run(function () {
start(event, ui) {
run(() => {
ui.item.data('start-index', ui.item.index());
});
},
update: function (event, ui) {
Ember.run(function () {
self.sendAction('moveItem', ui.item.data('start-index'), ui.item.index());
update(event, ui) {
run(() => {
this.sendAction('moveItem', ui.item.data('start-index'), ui.item.index());
});
}
});
},
willDestroyElement: function () {
willDestroyElement() {
this.$('.ui-sortable').sortable('destroy');
}
});

View File

@ -1,11 +1,10 @@
import Ember from 'ember';
var joinUrlParts,
isRelative;
const {TextField, computed, run} = Ember;
joinUrlParts = function (url, path) {
let joinUrlParts = function (url, path) {
if (path[0] !== '/' && url.slice(-1) !== '/') {
path = '/' + path;
path = `/${path}`;
} else if (path[0] === '/' && url.slice(-1) === '/') {
path = path.slice(1);
}
@ -13,19 +12,27 @@ joinUrlParts = function (url, path) {
return url + path;
};
isRelative = function (url) {
let isRelative = function (url) {
// "protocol://", "//example.com", "scheme:", "#anchor", & invalid paths
// should all be treated as absolute
return !url.match(/\s/) && !validator.isURL(url) && !url.match(/^(\/\/|#|[a-zA-Z0-9\-]+:)/);
};
export default Ember.TextField.extend({
export default TextField.extend({
classNames: 'gh-input',
classNameBindings: ['fakePlaceholder'],
didReceiveAttrs: function () {
var url = this.get('url'),
baseUrl = this.get('baseUrl');
isBaseUrl: computed('baseUrl', 'value', function () {
return this.get('baseUrl') === this.get('value');
}),
fakePlaceholder: computed('isBaseUrl', 'hasFocus', function () {
return this.get('isBaseUrl') && this.get('last') && !this.get('hasFocus');
}),
didReceiveAttrs() {
let baseUrl = this.get('baseUrl');
let url = this.get('url');
// if we have a relative url, create the absolute url to be displayed in the input
if (isRelative(url)) {
@ -35,28 +42,20 @@ export default Ember.TextField.extend({
this.set('value', url);
},
isBaseUrl: Ember.computed('baseUrl', 'value', function () {
return this.get('baseUrl') === this.get('value');
}),
fakePlaceholder: Ember.computed('isBaseUrl', 'hasFocus', function () {
return this.get('isBaseUrl') && this.get('last') && !this.get('hasFocus');
}),
focusIn: function (event) {
focusIn(event) {
this.set('hasFocus', true);
if (this.get('isBaseUrl')) {
// position the cursor at the end of the input
Ember.run.next(function (el) {
var length = el.value.length;
run.next(function (el) {
let {length} = el.value;
el.setSelectionRange(length, length);
}, event.target);
}
},
keyDown: function (event) {
keyDown(event) {
// delete the "placeholder" value all at once
if (this.get('isBaseUrl') && (event.keyCode === 8 || event.keyCode === 46)) {
this.set('value', '');
@ -70,7 +69,7 @@ export default Ember.TextField.extend({
}
},
keyPress: function (event) {
keyPress(event) {
// enter key
if (event.keyCode === 13) {
event.preventDefault();
@ -80,19 +79,21 @@ export default Ember.TextField.extend({
return true;
},
focusOut: function () {
focusOut() {
this.set('hasFocus', false);
this.notifyUrlChanged();
},
notifyUrlChanged: function () {
this.set('value', this.get('value').trim());
notifyUrlChanged() {
let value = this.get('value').trim();
let urlParts = document.createElement('a');
let baseUrl = this.get('baseUrl');
let baseUrlParts = document.createElement('a');
let url = value;
var url = this.get('value'),
urlParts = document.createElement('a'),
baseUrl = this.get('baseUrl'),
baseUrlParts = document.createElement('a');
// ensure value property is trimmed
this.set('value', value);
// leverage the browser's native URI parsing
urlParts.href = url;
@ -117,7 +118,7 @@ export default Ember.TextField.extend({
url = url.replace(baseUrlParts.host, '');
url = url.replace(baseUrlParts.pathname, '');
if (!url.match(/^\//)) {
url = '/' + url;
url = `/${url}`;
}
}

View File

@ -1,21 +1,23 @@
import Ember from 'ember';
import ValidationStateMixin from 'ghost/mixins/validation-state';
export default Ember.Component.extend(ValidationStateMixin, {
const {Component, computed} = Ember;
export default Component.extend(ValidationStateMixin, {
classNames: 'gh-blognav-item',
classNameBindings: ['errorClass'],
attributeBindings: ['order:data-order'],
order: Ember.computed.readOnly('navItem.order'),
errors: Ember.computed.readOnly('navItem.errors'),
order: computed.readOnly('navItem.order'),
errors: computed.readOnly('navItem.errors'),
errorClass: Ember.computed('hasError', function () {
errorClass: computed('hasError', function () {
if (this.get('hasError')) {
return 'gh-blognav-item--error';
}
}),
keyPress: function (event) {
keyPress(event) {
// enter key
if (event.keyCode === 13) {
event.preventDefault();
@ -26,15 +28,15 @@ export default Ember.Component.extend(ValidationStateMixin, {
},
actions: {
addItem: function () {
addItem() {
this.sendAction('addItem');
},
deleteItem: function (item) {
deleteItem(item) {
this.sendAction('deleteItem', item);
},
updateUrl: function (value) {
updateUrl(value) {
this.sendAction('updateUrl', value, this.get('navItem'));
}
}

View File

@ -1,18 +1,20 @@
import Ember from 'ember';
export default Ember.Component.extend({
const {Component, computed, inject} = Ember;
export default Component.extend({
tagName: 'article',
classNames: ['gh-notification', 'gh-notification-passive'],
classNameBindings: ['typeClass'],
message: null,
notifications: Ember.inject.service(),
notifications: inject.service(),
typeClass: Ember.computed('message.type', function () {
var classes = '',
type = this.get('message.type'),
typeMapping;
typeClass: computed('message.type', function () {
let type = this.get('message.type');
let classes = '';
let typeMapping;
typeMapping = {
success: 'green',
@ -21,28 +23,26 @@ export default Ember.Component.extend({
};
if (typeMapping[type] !== undefined) {
classes += 'gh-notification-' + typeMapping[type];
classes += `gh-notification-${typeMapping[type]}`;
}
return classes;
}),
didInsertElement: function () {
var self = this;
self.$().on('animationend webkitAnimationEnd oanimationend MSAnimationEnd', function (event) {
didInsertElement() {
this.$().on('animationend webkitAnimationEnd oanimationend MSAnimationEnd', (event) => {
if (event.originalEvent.animationName === 'fade-out') {
self.get('notifications').closeNotification(self.get('message'));
this.get('notifications').closeNotification(this.get('message'));
}
});
},
willDestroyElement: function () {
willDestroyElement() {
this.$().off('animationend webkitAnimationEnd oanimationend MSAnimationEnd');
},
actions: {
closeNotification: function () {
closeNotification() {
this.get('notifications').closeNotification(this.get('message'));
}
}

View File

@ -1,10 +1,13 @@
import Ember from 'ember';
export default Ember.Component.extend({
const {Component, computed, inject} = Ember;
const {alias} = computed;
export default Component.extend({
tagName: 'aside',
classNames: 'gh-notifications',
notifications: Ember.inject.service(),
notifications: inject.service(),
messages: Ember.computed.alias('notifications.notifications')
messages: alias('notifications.notifications')
});

View File

@ -1,18 +1,24 @@
import Ember from 'ember';
import DropdownButton from 'ghost/components/gh-dropdown-button';
const {inject} = Ember;
function K() {
return this;
}
export default DropdownButton.extend({
dropdown: Ember.inject.service(),
dropdown: inject.service(),
click: Ember.K,
click: K,
mouseEnter: function (event) {
this._super(event);
mouseEnter() {
this._super(...arguments);
this.get('dropdown').toggleDropdown(this.get('popoverName'), this);
},
mouseLeave: function (event) {
this._super(event);
mouseLeave() {
this._super(...arguments);
this.get('dropdown').toggleDropdown(this.get('popoverName'), this);
}
});

View File

@ -1,7 +1,9 @@
import Ember from 'ember';
import GhostDropdown from 'ghost/components/gh-dropdown';
const {inject} = Ember;
export default GhostDropdown.extend({
classNames: 'ghost-popover',
dropdown: Ember.inject.service()
dropdown: inject.service()
});

View File

@ -1,6 +1,9 @@
import Ember from 'ember';
export default Ember.Component.extend({
const {Component, computed, inject} = Ember;
const {alias, equal} = computed;
export default Component.extend({
tagName: 'li',
classNameBindings: ['active', 'isFeatured:featured', 'isPage:page'],
@ -8,60 +11,56 @@ export default Ember.Component.extend({
active: false,
previewIsHidden: false,
ghostPaths: Ember.inject.service('ghost-paths'),
isFeatured: alias('post.featured'),
isPage: alias('post.page'),
isPublished: equal('post.status', 'published'),
isFeatured: Ember.computed.alias('post.featured'),
ghostPaths: inject.service('ghost-paths'),
isPage: Ember.computed.alias('post.page'),
isPublished: Ember.computed.equal('post.status', 'published'),
authorName: Ember.computed('post.author.name', 'post.author.email', function () {
authorName: computed('post.author.name', 'post.author.email', function () {
return this.get('post.author.name') || this.get('post.author.email');
}),
authorAvatar: Ember.computed('post.author.image', function () {
authorAvatar: computed('post.author.image', function () {
return this.get('post.author.image') || this.get('ghostPaths.url').asset('/shared/img/user-image.png');
}),
authorAvatarBackground: Ember.computed('authorAvatar', function () {
authorAvatarBackground: computed('authorAvatar', function () {
return Ember.String.htmlSafe(`background-image: url(${this.get('authorAvatar')})`);
}),
viewOrEdit: Ember.computed('previewIsHidden', function () {
viewOrEdit: computed('previewIsHidden', function () {
return this.get('previewIsHidden') ? 'editor.edit' : 'posts.post';
}),
click: function () {
click() {
this.sendAction('onClick', this.get('post'));
},
doubleClick: function () {
doubleClick() {
this.sendAction('onDoubleClick', this.get('post'));
},
didInsertElement: function () {
didInsertElement() {
this.addObserver('active', this, this.scrollIntoView);
},
willDestroyElement: function () {
willDestroyElement() {
this.removeObserver('active', this, this.scrollIntoView);
},
scrollIntoView: function () {
scrollIntoView() {
if (!this.get('active')) {
return;
}
var element = this.$(),
offset = element.offset().top,
elementHeight = element.height(),
container = Ember.$('.js-content-scrollbox'),
containerHeight = container.height(),
currentScroll = container.scrollTop(),
isBelowTop,
isAboveBottom,
isOnScreen;
let element = this.$();
let offset = element.offset().top;
let elementHeight = element.height();
let container = $('.js-content-scrollbox');
let containerHeight = container.height();
let currentScroll = container.scrollTop();
let isBelowTop, isAboveBottom, isOnScreen;
isAboveBottom = offset < containerHeight;
isBelowTop = offset > elementHeight;

View File

@ -1,5 +1,8 @@
import Ember from 'ember';
const {Component, computed, inject, run} = Ember;
const {notEmpty} = computed;
/**
* A component to manage a user profile image. By default it just handles picture uploads,
* but if passed a bound 'email' property it will render the user's gravatar image
@ -14,7 +17,7 @@ import Ember from 'ember';
* @property {String} defaultImage String containing the background-image css property of the default user profile image
* @property {String} imageBackground String containing the background-image css property with the gravatar url
*/
export default Ember.Component.extend({
export default Component.extend({
email: '',
size: 90,
debounce: 300,
@ -23,35 +26,35 @@ export default Ember.Component.extend({
hasUploadedImage: false,
fileStorage: true,
ghostPaths: Ember.inject.service('ghost-paths'),
displayGravatar: Ember.computed.notEmpty('validEmail'),
ghostPaths: inject.service('ghost-paths'),
displayGravatar: notEmpty('validEmail'),
init: function () {
init() {
this._super(...arguments);
// Fire this immediately in case we're initialized with a valid email
this.trySetValidEmail();
},
defaultImage: Ember.computed('ghostPaths', function () {
const url = this.get('ghostPaths.url').asset('/shared/img/user-image.png');
defaultImage: computed('ghostPaths', function () {
let url = this.get('ghostPaths.url').asset('/shared/img/user-image.png');
return Ember.String.htmlSafe(`background-image: url(${url})`);
}),
trySetValidEmail: function () {
trySetValidEmail() {
if (!this.get('isDestroyed')) {
const email = this.get('email');
let email = this.get('email');
this.set('validEmail', validator.isEmail(email) ? email : '');
}
},
didReceiveAttrs: function (attrs) {
const timeout = parseInt(attrs.newAttrs.throttle || this.get('debounce'));
Ember.run.debounce(this, 'trySetValidEmail', timeout);
didReceiveAttrs(attrs) {
let timeout = parseInt(attrs.newAttrs.throttle || this.get('debounce'));
run.debounce(this, 'trySetValidEmail', timeout);
},
imageBackground: Ember.computed('validEmail', 'size', function () {
const email = this.get('validEmail'),
size = this.get('size');
imageBackground: computed('validEmail', 'size', function () {
let email = this.get('validEmail');
let size = this.get('size');
let style = '';
if (email) {
@ -61,9 +64,9 @@ export default Ember.Component.extend({
return Ember.String.htmlSafe(style);
}),
didInsertElement: function () {
var size = this.get('size'),
uploadElement = this.$('.js-file-input');
didInsertElement() {
let size = this.get('size');
let uploadElement = this.$('.js-file-input');
// while theoretically the 'add' and 'processalways' functions could be
// added as properties of the hash passed to fileupload(), for some reason
@ -77,26 +80,27 @@ export default Ember.Component.extend({
maxNumberOfFiles: 1,
autoUpload: false
})
.on('fileuploadadd', Ember.run.bind(this, this.queueFile))
.on('fileuploadprocessalways', Ember.run.bind(this, this.triggerPreview));
.on('fileuploadadd', run.bind(this, this.queueFile))
.on('fileuploadprocessalways', run.bind(this, this.triggerPreview));
},
willDestroyElement: function () {
willDestroyElement() {
if (this.$('.js-file-input').data()['blueimp-fileupload']) {
this.$('.js-file-input').fileupload('destroy');
}
},
queueFile: function (e, data) {
const fileName = data.files[0].name;
queueFile(e, data) {
let fileName = data.files[0].name;
if ((/\.(gif|jpe?g|png|svg?z)$/i).test(fileName)) {
this.sendAction('setImage', data);
}
},
triggerPreview: function (e, data) {
const file = data.files[data.index];
triggerPreview(e, data) {
let file = data.files[data.index];
if (file.preview) {
this.set('hasUploadedImage', true);
// necessary jQuery code because file.preview is a raw DOM object

View File

@ -1,8 +1,12 @@
/* global key */
/* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */
import Ember from 'ember';
import {request as ajax} from 'ic-ajax';
/* global key */
export default Ember.Component.extend({
const {Component, RSVP, computed, inject, observer} = Ember;
const {filterBy} = computed;
export default Component.extend({
selection: null,
content: [],
@ -10,48 +14,49 @@ export default Ember.Component.extend({
contentExpiry: 10 * 1000,
contentExpiresAt: false,
posts: Ember.computed.filterBy('content', 'category', 'Posts'),
pages: Ember.computed.filterBy('content', 'category', 'Pages'),
users: Ember.computed.filterBy('content', 'category', 'Users'),
tags: Ember.computed.filterBy('content', 'category', 'Tags'),
posts: filterBy('content', 'category', 'Posts'),
pages: filterBy('content', 'category', 'Pages'),
users: filterBy('content', 'category', 'Users'),
tags: filterBy('content', 'category', 'Tags'),
_store: Ember.inject.service('store'),
_routing: Ember.inject.service('-routing'),
_selectize: Ember.computed(function () {
_store: inject.service('store'),
_routing: inject.service('-routing'),
_selectize: computed(function () {
return this.$('select')[0].selectize;
}),
refreshContent: function () {
var promises = [],
now = new Date(),
contentExpiry = this.get('contentExpiry'),
contentExpiresAt = this.get('contentExpiresAt'),
self = this;
refreshContent() {
let promises = [];
let now = new Date();
let contentExpiry = this.get('contentExpiry');
let contentExpiresAt = this.get('contentExpiresAt');
if (self.get('isLoading') || contentExpiresAt > now) { return; }
if (this.get('isLoading') || contentExpiresAt > now) {
return;
}
self.set('isLoading', true);
this.set('isLoading', true);
promises.pushObject(this._loadPosts());
promises.pushObject(this._loadUsers());
promises.pushObject(this._loadTags());
Ember.RSVP.all(promises).then(function () { }).finally(function () {
self.set('isLoading', false);
self.set('contentExpiresAt', new Date(now.getTime() + contentExpiry));
RSVP.all(promises).then(() => { }).finally(() => {
this.set('isLoading', false);
this.set('contentExpiresAt', new Date(now.getTime() + contentExpiry));
});
},
_loadPosts: function () {
var store = this.get('_store'),
postsUrl = store.adapterFor('post').urlForQuery({}, 'post') + '/',
postsQuery = {fields: 'id,title,page', limit: 'all', status: 'all', staticPages: 'all'},
content = this.get('content'),
self = this;
_loadPosts() {
let store = this.get('_store');
let postsUrl = `${store.adapterFor('post').urlForQuery({}, 'post')}/`;
let postsQuery = {fields: 'id,title,page', limit: 'all', status: 'all', staticPages: 'all'};
let content = this.get('content');
return ajax(postsUrl, {data: postsQuery}).then(function (posts) {
content.removeObjects(self.get('posts'));
content.removeObjects(self.get('pages'));
content.pushObjects(posts.posts.map(function (post) {
return ajax(postsUrl, {data: postsQuery}).then((posts) => {
content.removeObjects(this.get('posts'));
content.removeObjects(this.get('pages'));
content.pushObjects(posts.posts.map((post) => {
return {
id: `post.${post.id}`,
title: post.title,
@ -61,16 +66,15 @@ export default Ember.Component.extend({
});
},
_loadUsers: function () {
var store = this.get('_store'),
usersUrl = store.adapterFor('user').urlForQuery({}, 'user') + '/',
usersQuery = {fields: 'name,slug', limit: 'all'},
content = this.get('content'),
self = this;
_loadUsers() {
let store = this.get('_store');
let usersUrl = `${store.adapterFor('user').urlForQuery({}, 'user')}/`;
let usersQuery = {fields: 'name,slug', limit: 'all'};
let content = this.get('content');
return ajax(usersUrl, {data: usersQuery}).then(function (users) {
content.removeObjects(self.get('users'));
content.pushObjects(users.users.map(function (user) {
return ajax(usersUrl, {data: usersQuery}).then((users) => {
content.removeObjects(this.get('users'));
content.pushObjects(users.users.map((user) => {
return {
id: `user.${user.slug}`,
title: user.name,
@ -80,16 +84,15 @@ export default Ember.Component.extend({
});
},
_loadTags: function () {
var store = this.get('_store'),
tagsUrl = store.adapterFor('tag').urlForQuery({}, 'tag') + '/',
tagsQuery = {fields: 'name,slug', limit: 'all'},
content = this.get('content'),
self = this;
_loadTags() {
let store = this.get('_store');
let tagsUrl = `${store.adapterFor('tag').urlForQuery({}, 'tag')}/`;
let tagsQuery = {fields: 'name,slug', limit: 'all'};
let content = this.get('content');
return ajax(tagsUrl, {data: tagsQuery}).then(function (tags) {
content.removeObjects(self.get('tags'));
content.pushObjects(tags.tags.map(function (tag) {
return ajax(tagsUrl, {data: tagsQuery}).then((tags) => {
content.removeObjects(this.get('tags'));
content.pushObjects(tags.tags.map((tag) => {
return {
id: `tag.${tag.slug}`,
title: tag.name,
@ -99,80 +102,81 @@ export default Ember.Component.extend({
});
},
_keepSelectionClear: Ember.observer('selection', function () {
_keepSelectionClear: observer('selection', function () {
if (this.get('selection') !== null) {
this.set('selection', null);
}
}),
_setKeymasterScope: function () {
_setKeymasterScope() {
key.setScope('search-input');
},
_resetKeymasterScope: function () {
_resetKeymasterScope() {
key.setScope('default');
},
willDestroy: function () {
willDestroy() {
this._resetKeymasterScope();
},
actions: {
openSelected: function (selected) {
var transition = null,
self = this;
openSelected(selected) {
let transition = null;
if (!selected) { return; }
if (!selected) {
return;
}
if (selected.category === 'Posts' || selected.category === 'Pages') {
let id = selected.id.replace('post.', '');
transition = self.get('_routing.router').transitionTo('editor.edit', id);
transition = this.get('_routing.router').transitionTo('editor.edit', id);
}
if (selected.category === 'Users') {
let id = selected.id.replace('user.', '');
transition = self.get('_routing.router').transitionTo('team.user', id);
transition = this.get('_routing.router').transitionTo('team.user', id);
}
if (selected.category === 'Tags') {
let id = selected.id.replace('tag.', '');
transition = self.get('_routing.router').transitionTo('settings.tags.tag', id);
transition = this.get('_routing.router').transitionTo('settings.tags.tag', id);
}
transition.then(function () {
if (self.get('_selectize').$control_input.is(':focus')) {
self._setKeymasterScope();
transition.then(() => {
if (this.get('_selectize').$control_input.is(':focus')) {
this._setKeymasterScope();
}
});
},
focusInput: function () {
focusInput() {
this.get('_selectize').focus();
},
onInit: function () {
var selectize = this.get('_selectize'),
html = '<div class="dropdown-empty-message">Nothing found&hellip;</div>';
onInit() {
let selectize = this.get('_selectize');
let html = '<div class="dropdown-empty-message">Nothing found&hellip;</div>';
selectize.$empty_results_container = $(html);
selectize.$empty_results_container.hide();
selectize.$dropdown.append(selectize.$empty_results_container);
},
onFocus: function () {
onFocus() {
this._setKeymasterScope();
this.refreshContent();
},
onBlur: function () {
var selectize = this.get('_selectize');
onBlur() {
let selectize = this.get('_selectize');
this._resetKeymasterScope();
selectize.$empty_results_container.hide();
},
onType: function () {
var selectize = this.get('_selectize');
onType() {
let selectize = this.get('_selectize');
if (!selectize.hasOptions) {
selectize.open();

View File

@ -1,28 +1,34 @@
import Ember from 'ember';
export default Ember.Component.extend({
const {Component, computed} = Ember;
const {reads} = computed;
function K() {
return this;
}
export default Component.extend({
content: null,
prompt: null,
optionValuePath: 'id',
optionLabelPath: 'title',
selection: null,
action: Ember.K, // action to fire on change
action: K, // action to fire on change
// shadow the passed-in `selection` to avoid
// leaking changes to it via a 2-way binding
_selection: Ember.computed.reads('selection'),
_selection: reads('selection'),
actions: {
change: function () {
var selectEl = this.$('select')[0],
selectedIndex = selectEl.selectedIndex,
content = this.get('content'),
change() {
let [selectEl] = this.$('select');
let {selectedIndex} = selectEl;
// decrement index by 1 if we have a prompt
hasPrompt = !!this.get('prompt'),
contentIndex = hasPrompt ? selectedIndex - 1 : selectedIndex,
let hasPrompt = !!this.get('prompt');
let contentIndex = hasPrompt ? selectedIndex - 1 : selectedIndex;
selection = content.objectAt(contentIndex);
let selection = this.get('content').objectAt(contentIndex);
// set the local, shadowed selection to avoid leaking
// changes to `selection` out via 2-way binding

View File

@ -1,30 +1,34 @@
/* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */
import Ember from 'ember';
import EmberSelectizeComponent from 'ember-cli-selectize/components/ember-selectize';
const {computed, isBlank, get, on, run} = Ember;
const emberA = Ember.A;
export default EmberSelectizeComponent.extend({
selectizeOptions: Ember.computed(function () {
const options = this._super(...arguments);
selectizeOptions: computed(function () {
let options = this._super(...arguments);
options.onChange = Ember.run.bind(this, '_onChange');
options.onChange = run.bind(this, '_onChange');
return options;
}),
_dontOpenWhenBlank: Ember.on('didInsertElement', function () {
var openOnFocus = this.get('openOnFocus');
_dontOpenWhenBlank: on('didInsertElement', function () {
let openOnFocus = this.get('openOnFocus');
if (!openOnFocus) {
Ember.run.schedule('afterRender', this, function () {
var selectize = this._selectize;
run.schedule('afterRender', this, function () {
let selectize = this._selectize;
if (selectize) {
selectize.on('dropdown_open', function () {
if (Ember.isBlank(selectize.$control_input.val())) {
if (isBlank(selectize.$control_input.val())) {
selectize.close();
}
});
selectize.on('type', function (filter) {
if (Ember.isBlank(filter)) {
if (isBlank(filter)) {
selectize.close();
}
});
@ -37,15 +41,15 @@ export default EmberSelectizeComponent.extend({
* Event callback that is triggered when user creates a tag
* - modified to pass the caret position to the action
*/
_create: function (input, callback) {
var caret = this._selectize.caretPos;
_create(input, callback) {
let caret = this._selectize.caretPos;
// Delete user entered text
this._selectize.setTextboxValue('');
// Send create action
// allow the observers and computed properties to run first
Ember.run.schedule('actions', this, function () {
run.schedule('actions', this, function () {
this.sendAction('create-item', input, caret);
});
// We cancel the creation here, so it's up to you to include the created element
@ -53,10 +57,10 @@ export default EmberSelectizeComponent.extend({
callback(null);
},
_addSelection: function (obj) {
var _valuePath = this.get('_valuePath'),
val = Ember.get(obj, _valuePath),
caret = this._selectize.caretPos;
_addSelection(obj) {
let _valuePath = this.get('_valuePath');
let val = get(obj, _valuePath);
let caret = this._selectize.caretPos;
// caret position is always 1 more than the desired index as this method
// is called after selectize has inserted the item and the caret has moved
@ -65,15 +69,15 @@ export default EmberSelectizeComponent.extend({
this.get('selection').insertAt(caret, obj);
Ember.run.schedule('actions', this, function () {
run.schedule('actions', this, function () {
this.sendAction('add-item', obj);
this.sendAction('add-value', val);
});
},
_onChange: function (args) {
const selection = Ember.get(this, 'selection'),
valuePath = Ember.get(this, '_valuePath');
_onChange(args) {
let selection = Ember.get(this, 'selection');
let valuePath = Ember.get(this, '_valuePath');
if (!args || !selection || !Ember.isArray(selection) || args.length !== selection.length) {
return;
@ -87,11 +91,13 @@ export default EmberSelectizeComponent.extend({
return;
}
let reorderedSelection = Ember.A([]);
let reorderedSelection = emberA([]);
args.forEach(function (value) {
const obj = selection.find(function (item) {
return (Ember.get(item, valuePath) + '') === value;
args.forEach((value) => {
let obj = selection.find(function (item) {
// jscs:disable
return (get(item, valuePath) + '') === value;
// jscs:enable
});
if (obj) {

View File

@ -1,7 +1,9 @@
/*jshint scripturl:true*/
import Ember from 'ember';
export default Ember.Component.extend({
const {Component, on} = Ember;
export default Component.extend({
tagName: 'a',
anchor: '',
classNames: ['sr-only', 'sr-only-focusable'],
@ -13,9 +15,9 @@ export default Ember.Component.extend({
// anchor behaviors or ignored
href: Ember.String.htmlSafe('javascript:;'),
scrollTo: Ember.on('click', function () {
var anchor = this.get('anchor'),
$el = Ember.$(anchor);
scrollTo: on('click', function () {
let anchor = this.get('anchor');
let $el = Ember.$(anchor);
if ($el) {
// Scrolls to the top of main content or whatever

View File

@ -1,6 +1,9 @@
import Ember from 'ember';
export default Ember.Component.extend({
const {Component, computed, observer, run} = Ember;
const {equal} = computed;
export default Component.extend({
tagName: 'button',
buttonText: '',
submitting: false,
@ -12,9 +15,9 @@ export default Ember.Component.extend({
attributeBindings: ['disabled', 'type', 'tabindex'],
// Must be set on the controller
disabled: Ember.computed.equal('showSpinner', true),
disabled: equal('showSpinner', true),
click: function () {
click() {
if (this.get('action')) {
this.sendAction('action');
return false;
@ -22,13 +25,13 @@ export default Ember.Component.extend({
return true;
},
toggleSpinner: Ember.observer('submitting', function () {
var submitting = this.get('submitting'),
timeout = this.get('showSpinnerTimeout');
toggleSpinner: observer('submitting', function () {
let submitting = this.get('submitting');
let timeout = this.get('showSpinnerTimeout');
if (submitting) {
this.set('showSpinner', true);
this.set('showSpinnerTimeout', Ember.run.later(this, function () {
this.set('showSpinnerTimeout', run.later(this, function () {
if (!this.get('submitting')) {
this.set('showSpinner', false);
}
@ -39,7 +42,7 @@ export default Ember.Component.extend({
}
}),
setSize: Ember.observer('showSpinner', function () {
setSize: observer('showSpinner', function () {
if (this.get('showSpinner') && this.get('autoWidth')) {
this.$().width(this.$().width());
this.$().height(this.$().height());
@ -49,7 +52,7 @@ export default Ember.Component.extend({
}
}),
willDestroy: function () {
Ember.run.cancel(this.get('showSpinnerTimeout'));
willDestroy() {
run.cancel(this.get('showSpinnerTimeout'));
}
});

View File

@ -1,28 +1,31 @@
import Ember from 'ember';
const {Component, computed} = Ember;
const {alias} = computed;
// See gh-tabs-manager.js for use
export default Ember.Component.extend({
export default Component.extend({
classNameBindings: ['active'],
tabsManager: Ember.computed(function () {
tabsManager: computed(function () {
return this.nearestWithProperty('isTabsManager');
}),
tab: Ember.computed('tabsManager.tabs.[]', 'tabsManager.tabPanes.[]', function () {
var index = this.get('tabsManager.tabPanes').indexOf(this),
tabs = this.get('tabsManager.tabs');
tab: computed('tabsManager.tabs.[]', 'tabsManager.tabPanes.[]', function () {
let index = this.get('tabsManager.tabPanes').indexOf(this);
let tabs = this.get('tabsManager.tabs');
return tabs && tabs.objectAt(index);
}),
active: Ember.computed.alias('tab.active'),
active: alias('tab.active'),
willRender: function () {
willRender() {
// Register with the tabs manager
this.get('tabsManager').registerTabPane(this);
},
willDestroyElement: function () {
willDestroyElement() {
// Deregister with the tabs manager
this.get('tabsManager').unregisterTabPane(this);
}

View File

@ -1,30 +1,32 @@
import Ember from 'ember';
const {Component, computed} = Ember;
// See gh-tabs-manager.js for use
export default Ember.Component.extend({
tabsManager: Ember.computed(function () {
export default Component.extend({
tabsManager: computed(function () {
return this.nearestWithProperty('isTabsManager');
}),
active: Ember.computed('tabsManager.activeTab', function () {
active: computed('tabsManager.activeTab', function () {
return this.get('tabsManager.activeTab') === this;
}),
index: Ember.computed('tabsManager.tabs.[]', function () {
index: computed('tabsManager.tabs.[]', function () {
return this.get('tabsManager.tabs').indexOf(this);
}),
// Select on click
click: function () {
click() {
this.get('tabsManager').select(this);
},
willRender: function () {
willRender() {
// register the tabs with the tab manager
this.get('tabsManager').registerTab(this);
},
willDestroyElement: function () {
willDestroyElement() {
// unregister the tabs with the tab manager
this.get('tabsManager').unregisterTab(this);
}

View File

@ -1,4 +1,7 @@
import Ember from 'ember';
const {Component} = Ember;
/**
Heavily inspired by ic-tabs (https://github.com/instructure/ic-tabs)
@ -48,35 +51,35 @@ Both tab and tab-pane elements have an "active"
class applied when they are active.
*/
export default Ember.Component.extend({
export default Component.extend({
activeTab: null,
tabs: [],
tabPanes: [],
// Used by children to find this tabsManager
isTabsManager: true,
// Called when a gh-tab is clicked.
select: function (tab) {
select(tab) {
this.set('activeTab', tab);
this.sendAction('selected');
},
// Used by children to find this tabsManager
isTabsManager: true,
// Register tabs and their panes to allow for
// interaction between components.
registerTab: function (tab) {
registerTab(tab) {
this.get('tabs').addObject(tab);
},
unregisterTab: function (tab) {
unregisterTab(tab) {
this.get('tabs').removeObject(tab);
},
registerTabPane: function (tabPane) {
registerTabPane(tabPane) {
this.get('tabPanes').addObject(tabPane);
},
unregisterTabPane: function (tabPane) {
unregisterTabPane(tabPane) {
this.get('tabPanes').removeObject(tabPane);
}
});

View File

@ -2,9 +2,9 @@
import Ember from 'ember';
import boundOneWay from 'ghost/utils/bound-one-way';
const {get} = Ember;
const {Component, Handlebars, computed, get, inject} = Ember;
export default Ember.Component.extend({
export default Component.extend({
tag: null,
@ -16,12 +16,12 @@ export default Ember.Component.extend({
isViewingSubview: false,
config: Ember.inject.service(),
config: inject.service(),
mediaQueries: Ember.inject.service(),
isMobile: Ember.computed.reads('mediaQueries.maxWidth600'),
title: Ember.computed('tag.isNew', function () {
title: computed('tag.isNew', function () {
if (this.get('tag.isNew')) {
return 'New Tag';
} else {
@ -29,25 +29,25 @@ export default Ember.Component.extend({
}
}),
seoTitle: Ember.computed('scratchName', 'scratchMetaTitle', function () {
seoTitle: computed('scratchName', 'scratchMetaTitle', function () {
let metaTitle = this.get('scratchMetaTitle') || '';
metaTitle = metaTitle.length > 0 ? metaTitle : this.get('scratchName');
if (metaTitle && metaTitle.length > 70) {
metaTitle = metaTitle.substring(0, 70).trim();
metaTitle = Ember.Handlebars.Utils.escapeExpression(metaTitle);
metaTitle = Ember.String.htmlSafe(metaTitle + '&hellip;');
metaTitle = Handlebars.Utils.escapeExpression(metaTitle);
metaTitle = Ember.String.htmlSafe(`${metaTitle}&hellip;`);
}
return metaTitle;
}),
seoURL: Ember.computed('scratchSlug', function () {
const blogUrl = this.get('config.blogUrl'),
seoSlug = this.get('scratchSlug') || '';
seoURL: computed('scratchSlug', function () {
let blogUrl = this.get('config.blogUrl');
let seoSlug = this.get('scratchSlug') || '';
let seoURL = blogUrl + '/tag/' + seoSlug;
let seoURL = `${blogUrl}/tag/${seoSlug}`;
// only append a slash to the URL if the slug exists
if (seoSlug) {
@ -56,73 +56,73 @@ export default Ember.Component.extend({
if (seoURL.length > 70) {
seoURL = seoURL.substring(0, 70).trim();
seoURL = Ember.String.htmlSafe(seoURL + '&hellip;');
seoURL = Ember.String.htmlSafe(`${seoURL}&hellip;`);
}
return seoURL;
}),
seoDescription: Ember.computed('scratchDescription', 'scratchMetaDescription', function () {
seoDescription: computed('scratchDescription', 'scratchMetaDescription', function () {
let metaDescription = this.get('scratchMetaDescription') || '';
metaDescription = metaDescription.length > 0 ? metaDescription : this.get('scratchDescription');
if (metaDescription && metaDescription.length > 156) {
metaDescription = metaDescription.substring(0, 156).trim();
metaDescription = Ember.Handlebars.Utils.escapeExpression(metaDescription);
metaDescription = Ember.String.htmlSafe(metaDescription + '&hellip;');
metaDescription = Handlebars.Utils.escapeExpression(metaDescription);
metaDescription = Ember.String.htmlSafe(`${metaDescription}&hellip;`);
}
return metaDescription;
}),
didReceiveAttrs: function (attrs) {
didReceiveAttrs(attrs) {
if (get(attrs, 'newAttrs.tag.value.id') !== get(attrs, 'oldAttrs.tag.value.id')) {
this.reset();
}
},
reset: function () {
reset() {
this.set('isViewingSubview', false);
if (this.$()) {
this.$('.settings-menu-pane').scrollTop(0);
}
},
focusIn: function () {
focusIn() {
key.setScope('tag-settings-form');
},
focusOut: function () {
focusOut() {
key.setScope('default');
},
actions: {
setProperty: function (property, value) {
setProperty(property, value) {
this.attrs.setProperty(property, value);
},
setCoverImage: function (image) {
setCoverImage(image) {
this.attrs.setProperty('image', image);
},
clearCoverImage: function () {
clearCoverImage() {
this.attrs.setProperty('image', '');
},
setUploaderReference: function () {
setUploaderReference() {
// noop
},
openMeta: function () {
openMeta() {
this.set('isViewingSubview', true);
},
closeMeta: function () {
closeMeta() {
this.set('isViewingSubview', false);
},
deleteTag: function () {
deleteTag() {
this.sendAction('openModal', 'delete-tag', this.get('tag'));
}
}

View File

@ -1,28 +1,29 @@
import Ember from 'ember';
const {isBlank} = Ember;
const {Component, computed, inject, isBlank, observer, run} = Ember;
const {equal, reads} = computed;
export default Ember.Component.extend({
export default Component.extend({
classNames: ['view-container'],
classNameBindings: ['isMobile'],
mediaQueries: Ember.inject.service(),
mediaQueries: inject.service(),
tags: null,
selectedTag: null,
isMobile: Ember.computed.reads('mediaQueries.maxWidth600'),
isEmpty: Ember.computed.equal('tags.length', 0),
isMobile: reads('mediaQueries.maxWidth600'),
isEmpty: equal('tags.length', 0),
init: function () {
init() {
this._super(...arguments);
Ember.run.schedule('actions', this, this.fireMobileChangeActions);
run.schedule('actions', this, this.fireMobileChangeActions);
},
displaySettingsPane: Ember.computed('isEmpty', 'selectedTag', 'isMobile', function () {
const isEmpty = this.get('isEmpty'),
selectedTag = this.get('selectedTag'),
isMobile = this.get('isMobile');
displaySettingsPane: computed('isEmpty', 'selectedTag', 'isMobile', function () {
let isEmpty = this.get('isEmpty');
let selectedTag = this.get('selectedTag');
let isMobile = this.get('isMobile');
// always display settings pane for blank-slate on mobile
if (isMobile && isEmpty) {
@ -38,7 +39,7 @@ export default Ember.Component.extend({
return true;
}),
fireMobileChangeActions: Ember.observer('isMobile', function () {
fireMobileChangeActions: observer('isMobile', function () {
if (!this.get('isMobile')) {
this.sendAction('leftMobile');
}

View File

@ -1,6 +1,8 @@
import Ember from 'ember';
import TextInputMixin from 'ghost/mixins/text-input';
export default Ember.TextArea.extend(TextInputMixin, {
const {TextArea} = Ember;
export default TextArea.extend(TextInputMixin, {
classNames: 'gh-input'
});

View File

@ -1,12 +1,14 @@
import Ember from 'ember';
/*global device*/
import Ember from 'ember';
export default Ember.TextField.extend({
const {TextField, computed, on} = Ember;
export default TextField.extend({
focus: true,
classNames: 'gh-input',
attributeBindings: ['autofocus'],
autofocus: Ember.computed(function () {
autofocus: computed(function () {
if (this.get('focus')) {
return (device.ios()) ? false : 'autofocus';
}
@ -14,7 +16,7 @@ export default Ember.TextField.extend({
return false;
}),
focusField: Ember.on('didInsertElement', function () {
focusField: on('didInsertElement', function () {
// This fix is required until Mobile Safari has reliable
// autofocus, select() or focus() support
if (this.get('focus') && !device.ios()) {
@ -22,8 +24,8 @@ export default Ember.TextField.extend({
}
}),
trimValue: Ember.on('focusOut', function () {
var text = this.$().val();
trimValue: on('focusOut', function () {
let text = this.$().val();
this.$().val(text.trim());
})
});

View File

@ -3,44 +3,50 @@ import ModalDialog from 'ghost/components/gh-modal-dialog';
import upload from 'ghost/assets/lib/uploader';
import cajaSanitizers from 'ghost/utils/caja-sanitizers';
const {inject, isEmpty} = Ember;
export default ModalDialog.extend({
layoutName: 'components/gh-modal-dialog',
config: Ember.inject.service(),
config: inject.service(),
didInsertElement: function () {
this._super();
didInsertElement() {
this._super(...arguments);
upload.call(this.$('.js-drop-zone'), {fileStorage: this.get('config.fileStorage')});
},
keyDown: function () {
keyDown() {
this.setErrorState(false);
},
setErrorState: function (state) {
setErrorState(state) {
if (state) {
this.$('.js-upload-url').addClass('error');
} else {
this.$('.js-upload-url').removeClass('error');
}
},
confirm: {
reject: {
func: function () { // The function called on rejection
return true;
},
buttonClass: 'btn btn-default',
text: 'Cancel' // The reject button text
text: 'Cancel', // The reject button text
func() { // The function called on rejection
return true;
}
},
accept: {
buttonClass: 'btn btn-blue right',
text: 'Save', // The accept button text: 'Save'
func: function () {
var imageType = 'model.' + this.get('imageType'),
value;
func() {
let imageType = `model.${this.get('imageType')}`;
let value;
if (this.$('.js-upload-url').val()) {
value = this.$('.js-upload-url').val();
if (!Ember.isEmpty(value) && !cajaSanitizers.url(value)) {
if (!isEmpty(value) && !cajaSanitizers.url(value)) {
this.setErrorState(true);
return {message: 'Image URI is not valid'};
}
@ -55,12 +61,13 @@ export default ModalDialog.extend({
},
actions: {
closeModal: function () {
closeModal() {
this.sendAction();
},
confirm: function (type) {
var result,
func = this.get('confirm.' + type + '.func');
confirm(type) {
let func = this.get(`confirm.${type}.func`);
let result;
if (typeof func === 'function') {
result = func.apply(this);
@ -68,7 +75,7 @@ export default ModalDialog.extend({
if (!result.message) {
this.sendAction();
this.sendAction('confirm' + type);
this.sendAction(`confirm${type}`);
}
}
}

View File

@ -1,18 +1,20 @@
import Ember from 'ember';
import uploader from 'ghost/assets/lib/uploader';
export default Ember.Component.extend({
const {Component, computed, get, inject, isEmpty, run} = Ember;
export default Component.extend({
classNames: ['image-uploader', 'js-post-image-upload'],
config: Ember.inject.service(),
config: inject.service(),
imageSource: Ember.computed('image', function () {
imageSource: computed('image', function () {
return this.get('image') || '';
}),
// removes event listeners from the uploader
removeListeners: function () {
var $this = this.$();
removeListeners() {
let $this = this.$();
$this.off();
$this.find('.js-cancel').off();
@ -22,20 +24,19 @@ export default Ember.Component.extend({
// between transitions Glimmer will re-use the existing elements including
// those that arealready decorated by jQuery. The following works around
// situations where the image is changed without a full teardown/rebuild
didReceiveAttrs: function (attrs) {
var oldValue = attrs.oldAttrs && Ember.get(attrs.oldAttrs, 'image.value'),
newValue = attrs.newAttrs && Ember.get(attrs.newAttrs, 'image.value'),
self = this;
didReceiveAttrs(attrs) {
let oldValue = attrs.oldAttrs && get(attrs.oldAttrs, 'image.value');
let newValue = attrs.newAttrs && get(attrs.newAttrs, 'image.value');
// always reset when we receive a blank image
// - handles navigating to populated image from blank image
if (Ember.isEmpty(newValue) && !Ember.isEmpty(oldValue)) {
self.$()[0].uploaderUi.reset();
if (isEmpty(newValue) && !isEmpty(oldValue)) {
this.$()[0].uploaderUi.reset();
}
// re-init if we receive a new image but the uploader is blank
// - handles back button navigating from blank image to populated image
if (!Ember.isEmpty(newValue) && this.$()) {
if (!isEmpty(newValue) && this.$()) {
if (this.$('.js-upload-target').attr('src') === '') {
this.$()[0].uploaderUi.reset();
this.$()[0].uploaderUi.initWithImage();
@ -43,34 +44,31 @@ export default Ember.Component.extend({
}
},
didInsertElement: function () {
didInsertElement() {
this.send('initUploader');
},
willDestroyElement: function () {
willDestroyElement() {
this.removeListeners();
},
actions: {
initUploader: function () {
var ref,
el = this.$(),
self = this;
ref = uploader.call(el, {
initUploader() {
let el = this.$();
let ref = uploader.call(el, {
editor: true,
fileStorage: this.get('config.fileStorage')
});
el.on('uploadsuccess', function (event, result) {
el.on('uploadsuccess', (event, result) => {
if (result && result !== '' && result !== 'http://') {
Ember.run(self, function () {
run(this, function () {
this.sendAction('uploaded', result);
});
}
});
el.on('imagecleared', Ember.run.bind(self, 'sendAction', 'canceled'));
el.on('imagecleared', run.bind(this, 'sendAction', 'canceled'));
this.sendAction('initUploader', ref);
}

View File

@ -1,26 +1,30 @@
import Ember from 'ember';
const {Component, computed, inject} = Ember;
/*
Example usage:
{{gh-url-preview prefix="tag" slug=theSlugValue tagName="p" classNames="description"}}
*/
export default Ember.Component.extend({
export default Component.extend({
classNames: 'ghost-url-preview',
prefix: null,
slug: null,
config: Ember.inject.service(),
config: inject.service(),
url: Ember.computed('slug', function () {
url: computed('slug', function () {
// Get the blog URL and strip the scheme
var blogUrl = this.get('config.blogUrl'),
noSchemeBlogUrl = blogUrl.substr(blogUrl.indexOf('://') + 3), // Remove `http[s]://`
let blogUrl = this.get('config.blogUrl');
// Remove `http[s]://`
let noSchemeBlogUrl = blogUrl.substr(blogUrl.indexOf('://') + 3);
// Get the prefix and slug values
prefix = this.get('prefix') ? this.get('prefix') + '/' : '',
slug = this.get('slug') ? this.get('slug') + '/' : '',
let prefix = this.get('prefix') ? `${this.get('prefix')}/` : '';
let slug = this.get('slug') ? `${this.get('slug')}/` : '';
// Join parts of the URL together with slashes
theUrl = noSchemeBlogUrl + '/' + prefix + slug;
let theUrl = `${noSchemeBlogUrl}/${prefix}${slug}`;
return theUrl;
})

View File

@ -1,24 +1,26 @@
import Ember from 'ember';
export default Ember.Component.extend({
const {Component, computed, inject} = Ember;
export default Component.extend({
tagName: '',
user: null,
ghostPaths: Ember.inject.service('ghost-paths'),
ghostPaths: inject.service('ghost-paths'),
userDefault: Ember.computed('ghostPaths', function () {
userDefault: computed('ghostPaths', function () {
return this.get('ghostPaths.url').asset('/shared/img/user-image.png');
}),
userImageBackground: Ember.computed('user.image', 'userDefault', function () {
var url = this.get('user.image') || this.get('userDefault');
userImageBackground: computed('user.image', 'userDefault', function () {
let url = this.get('user.image') || this.get('userDefault');
return Ember.String.htmlSafe(`background-image: url(${url})`);
}),
lastLogin: Ember.computed('user.last_login', function () {
var lastLogin = this.get('user.last_login');
lastLogin: computed('user.last_login', function () {
let lastLogin = this.get('user.last_login');
return lastLogin ? lastLogin.fromNow() : '(Never)';
})

View File

@ -1,28 +1,29 @@
import Ember from 'ember';
export default Ember.Component.extend({
const {Component, computed, inject} = Ember;
export default Component.extend({
tagName: '',
user: null,
isSending: false,
notifications: Ember.inject.service(),
notifications: inject.service(),
createdAt: Ember.computed('user.created_at', function () {
var createdAt = this.get('user.created_at');
createdAt: computed('user.created_at', function () {
let createdAt = this.get('user.created_at');
return createdAt ? createdAt.fromNow() : '';
}),
actions: {
resend: function () {
var user = this.get('user'),
notifications = this.get('notifications'),
self = this;
resend() {
let user = this.get('user');
let notifications = this.get('notifications');
this.set('isSending', true);
user.resendInvite().then(function (result) {
var notificationText = 'Invitation resent! (' + user.get('email') + ')';
user.resendInvite().then((result) => {
let notificationText = `Invitation resent! (${user.get('email')})`;
// If sending the invitation email fails, the API will still return a status of 201
// but the user's status in the response object will be 'invited-pending'.
@ -33,32 +34,31 @@ export default Ember.Component.extend({
notifications.showNotification(notificationText);
notifications.closeAlerts('invite.resend');
}
}).catch(function (error) {
}).catch((error) => {
notifications.showAPIError(error, {key: 'invite.resend'});
}).finally(function () {
self.set('isSending', false);
}).finally(() => {
this.set('isSending', false);
});
},
revoke: function () {
var user = this.get('user'),
email = user.get('email'),
notifications = this.get('notifications'),
self = this;
revoke() {
let user = this.get('user');
let email = user.get('email');
let notifications = this.get('notifications');
// reload the user to get the most up-to-date information
user.reload().then(function () {
user.reload().then(() => {
if (user.get('invited')) {
user.destroyRecord().then(function () {
var notificationText = 'Invitation revoked. (' + email + ')';
user.destroyRecord().then(() => {
let notificationText = `Invitation revoked. (${email})`;
notifications.showNotification(notificationText);
notifications.closeAlerts('invite.revoke');
}).catch(function (error) {
}).catch((error) => {
notifications.showAPIError(error, {key: 'invite.revoke'});
});
} else {
// if the user is no longer marked as "invited", then show a warning and reload the route
self.sendAction('reload');
this.sendAction('reload');
notifications.showAlert('This user has already accepted the invitation.', {type: 'error', delayed: true, key: 'invite.revoke.already-accepted'});
}
});

View File

@ -1,6 +1,8 @@
import Ember from 'ember';
import ValidationStateMixin from 'ghost/mixins/validation-state';
const {Component, computed} = Ember;
/**
* Handles the CSS necessary to show a specific property state. When passed a
* DS.Errors object and a property name, if the DS.Errors object has errors for
@ -8,12 +10,12 @@ import ValidationStateMixin from 'ghost/mixins/validation-state';
* @param {DS.Errors} errors The DS.Errors object
* @param {string} property Name of the property
*/
export default Ember.Component.extend(ValidationStateMixin, {
export default Component.extend(ValidationStateMixin, {
classNameBindings: ['errorClass'],
errorClass: Ember.computed('property', 'hasError', 'hasValidated.[]', function () {
let hasValidated = this.get('hasValidated'),
property = this.get('property');
errorClass: computed('property', 'hasError', 'hasValidated.[]', function () {
let hasValidated = this.get('hasValidated');
let property = this.get('property');
if (hasValidated && hasValidated.contains(property)) {
return this.get('hasError') ? 'error' : 'success';

View File

@ -1,10 +1,13 @@
import Ember from 'ember';
export default Ember.Component.extend({
const {Component} = Ember;
export default Component.extend({
tagName: 'h2',
classNames: ['view-title'],
actions: {
openMobileMenu: function () {
openMobileMenu() {
this.sendAction('openMobileMenu');
}
}

View File

@ -1,10 +1,12 @@
import Ember from 'ember';
export default Ember.Controller.extend({
const {Controller} = Ember;
export default Controller.extend({
updateNotificationCount: 0,
actions: {
updateNotificationChange: function (count) {
updateNotificationChange(count) {
this.set('updateNotificationCount', count);
}
}

View File

@ -1,22 +1,22 @@
import Ember from 'ember';
export default Ember.Controller.extend({
dropdown: Ember.inject.service(),
const {Controller, computed, inject} = Ember;
// jscs: disable
signedOut: Ember.computed.match('currentPath', /(signin|signup|setup|reset)/),
// jscs: enable
export default Controller.extend({
dropdown: inject.service(),
signedOut: computed.match('currentPath', /(signin|signup|setup|reset)/),
topNotificationCount: 0,
showMobileMenu: false,
showSettingsMenu: false,
autoNav: false,
autoNavOpen: Ember.computed('autoNav', {
get: function () {
autoNavOpen: computed('autoNav', {
get() {
return false;
},
set: function (key, value) {
set(key, value) {
if (this.get('autoNav')) {
return value;
}
@ -25,23 +25,23 @@ export default Ember.Controller.extend({
}),
actions: {
topNotificationChange: function (count) {
topNotificationChange(count) {
this.set('topNotificationCount', count);
},
toggleAutoNav: function () {
toggleAutoNav() {
this.toggleProperty('autoNav');
},
openAutoNav: function () {
openAutoNav() {
this.set('autoNavOpen', true);
},
closeAutoNav: function () {
closeAutoNav() {
this.set('autoNavOpen', false);
},
closeMobileMenu: function () {
closeMobileMenu() {
this.set('showMobileMenu', false);
}
}

View File

@ -1,9 +1,11 @@
import Ember from 'ember';
import EditorControllerMixin from 'ghost/mixins/editor-base-controller';
export default Ember.Controller.extend(EditorControllerMixin, {
const {Controller} = Ember;
export default Controller.extend(EditorControllerMixin, {
actions: {
openDeleteModal: function () {
openDeleteModal() {
this.send('openModal', 'delete-post', this.get('model'));
}
}

View File

@ -1,18 +1,23 @@
import Ember from 'ember';
import EditorControllerMixin from 'ghost/mixins/editor-base-controller';
export default Ember.Controller.extend(EditorControllerMixin, {
const {Controller} = Ember;
function K() {
return this;
}
export default Controller.extend(EditorControllerMixin, {
// Overriding autoSave on the base controller, as the new controller shouldn't be autosaving
autoSave: Ember.K,
autoSave: K,
actions: {
/**
* Redirect to editor after the first save
*/
save: function (options) {
var self = this;
return this._super(options).then(function (model) {
save(options) {
return this._super(options).then((model) => {
if (model.get('id')) {
self.replaceRoute('editor.edit', model);
this.replaceRoute('editor.edit', model);
}
});
}

View File

@ -1,15 +1,20 @@
import Ember from 'ember';
export default Ember.Controller.extend({
code: Ember.computed('content.status', function () {
const {Controller, computed} = Ember;
export default Controller.extend({
stack: false,
code: computed('content.status', function () {
return this.get('content.status') > 200 ? this.get('content.status') : 500;
}),
message: Ember.computed('content.statusText', function () {
message: computed('content.statusText', function () {
if (this.get('code') === 404) {
return 'Page not found';
}
return this.get('content.statusText') !== 'error' ? this.get('content.statusText') : 'Internal Server Error';
}),
stack: false
})
});

View File

@ -1,20 +1,14 @@
import Ember from 'ember';
export default Ember.Controller.extend(Ember.PromiseProxyMixin, {
init: function () {
var promise;
const {Controller, PromiseProxyMixin, computed} = Ember;
const {alias} = computed;
promise = this.store.query('setting', {type: 'blog,theme'}).then(function (settings) {
return settings.get('firstObject');
});
export default Controller.extend(PromiseProxyMixin, {
this.set('promise', promise);
},
setting: alias('content'),
setting: Ember.computed.alias('content'),
labs: Ember.computed('isSettled', 'setting.labs', function () {
var value = {};
labs: computed('isSettled', 'setting.labs', function () {
let value = {};
if (this.get('isFulfilled')) {
try {
@ -27,7 +21,15 @@ export default Ember.Controller.extend(Ember.PromiseProxyMixin, {
return value;
}),
publicAPI: Ember.computed('config.publicAPI', 'labs.publicAPI', function () {
publicAPI: computed('config.publicAPI', 'labs.publicAPI', function () {
return this.get('config.publicAPI') || this.get('labs.publicAPI');
})
}),
init() {
let promise = this.store.query('setting', {type: 'blog,theme'}).then((settings) => {
return settings.get('firstObject');
});
this.set('promise', promise);
}
});

View File

@ -1,5 +1,8 @@
import Ember from 'ember';
export default Ember.Controller.extend({
generatedHTML: Ember.computed.alias('model.generatedHTML')
const {Controller, computed} = Ember;
const {alias} = computed;
export default Controller.extend({
generatedHTML: alias('model.generatedHTML')
});

View File

@ -1,29 +1,11 @@
import Ember from 'ember';
import {request as ajax} from 'ic-ajax';
export default Ember.Controller.extend({
ghostPaths: Ember.inject.service('ghost-paths'),
notifications: Ember.inject.service(),
const {Controller, inject} = Ember;
actions: {
confirmAccept: function () {
var self = this;
ajax(this.get('ghostPaths.url').api('db'), {
type: 'DELETE'
}).then(function () {
self.get('notifications').showAlert('All content deleted from database.', {type: 'success', key: 'all-content.delete.success'});
self.store.unloadAll('post');
self.store.unloadAll('tag');
}).catch(function (response) {
self.get('notifications').showAPIError(response, {key: 'all-content.delete'});
});
},
confirmReject: function () {
return false;
}
},
export default Controller.extend({
ghostPaths: inject.service('ghost-paths'),
notifications: inject.service(),
confirm: {
accept: {
@ -34,5 +16,23 @@ export default Ember.Controller.extend({
text: 'Cancel',
buttonClass: 'btn btn-default btn-minor'
}
},
actions: {
confirmAccept() {
ajax(this.get('ghostPaths.url').api('db'), {
type: 'DELETE'
}).then(() => {
this.get('notifications').showAlert('All content deleted from database.', {type: 'success', key: 'all-content.delete.success'});
this.store.unloadAll('post');
this.store.unloadAll('tag');
}).catch((response) => {
this.get('notifications').showAPIError(response, {key: 'all-content.delete'});
});
},
confirmReject() {
return false;
}
}
});

View File

@ -1,30 +1,10 @@
import Ember from 'ember';
export default Ember.Controller.extend({
dropdown: Ember.inject.service(),
notifications: Ember.inject.service(),
const {Controller, inject} = Ember;
actions: {
confirmAccept: function () {
var self = this,
model = this.get('model');
// definitely want to clear the data store and post of any unsaved, client-generated tags
model.updateTags();
model.destroyRecord().then(function () {
self.get('dropdown').closeDropdowns();
self.get('notifications').closeAlerts('post.delete');
self.transitionToRoute('posts.index');
}, function () {
self.get('notifications').showAlert('Your post could not be deleted. Please try again.', {type: 'error', key: 'post.delete.failed'});
});
},
confirmReject: function () {
return false;
}
},
export default Controller.extend({
dropdown: inject.service(),
notifications: inject.service(),
confirm: {
accept: {
@ -35,5 +15,26 @@ export default Ember.Controller.extend({
text: 'Cancel',
buttonClass: 'btn btn-default btn-minor'
}
},
actions: {
confirmAccept() {
let model = this.get('model');
// definitely want to clear the data store and post of any unsaved, client-generated tags
model.updateTags();
model.destroyRecord().then(() => {
this.get('dropdown').closeDropdowns();
this.get('notifications').closeAlerts('post.delete');
this.transitionToRoute('posts.index');
}, () => {
this.get('notifications').showAlert('Your post could not be deleted. Please try again.', {type: 'error', key: 'post.delete.failed'});
});
},
confirmReject() {
return false;
}
}
});

View File

@ -11,7 +11,7 @@ export default Controller.extend({
}),
actions: {
confirmAccept: function () {
confirmAccept() {
let tag = this.get('model');
this.send('closeMenus');
@ -27,7 +27,7 @@ export default Controller.extend({
});
},
confirmReject: function () {
confirmReject() {
return false;
}
},

View File

@ -1,47 +1,30 @@
import Ember from 'ember';
export default Ember.Controller.extend({
notifications: Ember.inject.service(),
const {Controller, PromiseProxyMixin, computed, inject} = Ember;
const {alias} = computed;
userPostCount: Ember.computed('model.id', function () {
var promise,
query = {
export default Controller.extend({
notifications: inject.service(),
userPostCount: computed('model.id', function () {
let query = {
filter: `author:${this.get('model.slug')}`,
status: 'all'
};
promise = this.store.query('post', query).then(function (results) {
let promise = this.store.query('post', query).then((results) => {
return results.meta.pagination.total;
});
return Ember.Object.extend(Ember.PromiseProxyMixin, {
count: Ember.computed.alias('content'),
return Ember.Object.extend(PromiseProxyMixin, {
count: alias('content'),
inflection: Ember.computed('count', function () {
inflection: computed('count', function () {
return this.get('count') > 1 ? 'posts' : 'post';
})
}).create({promise: promise});
}).create({promise});
}),
actions: {
confirmAccept: function () {
var self = this,
user = this.get('model');
user.destroyRecord().then(function () {
self.get('notifications').closeAlerts('user.delete');
self.store.unloadAll('post');
self.transitionToRoute('team');
}, function () {
self.get('notifications').showAlert('The user could not be deleted. Please try again.', {type: 'error', key: 'user.delete.failed'});
});
},
confirmReject: function () {
return false;
}
},
confirm: {
accept: {
text: 'Delete User',
@ -51,5 +34,23 @@ export default Ember.Controller.extend({
text: 'Cancel',
buttonClass: 'btn btn-default btn-minor'
}
},
actions: {
confirmAccept() {
let user = this.get('model');
user.destroyRecord().then(() => {
this.get('notifications').closeAlerts('user.delete');
this.store.unloadAll('post');
this.transitionToRoute('team');
}, () => {
this.get('notifications').showAlert('The user could not be deleted. Please try again.', {type: 'error', key: 'user.delete.failed'});
});
},
confirmReject() {
return false;
}
}
});

View File

@ -1,29 +1,29 @@
import Ember from 'ember';
import ValidationEngine from 'ghost/mixins/validation-engine';
export default Ember.Controller.extend(ValidationEngine, {
notifications: Ember.inject.service(),
const {Controller, computed, inject, observer} = Ember;
export default Controller.extend(ValidationEngine, {
notifications: inject.service(),
validationType: 'signup',
role: null,
authorRole: null,
roles: Ember.computed(function () {
roles: computed(function () {
return this.store.query('role', {permissions: 'assign'});
}),
// Used to set the initial value for the dropdown
authorRoleObserver: Ember.observer('roles.@each.role', function () {
var self = this;
authorRoleObserver: observer('roles.@each.role', function () {
this.get('roles').then((roles) => {
let authorRole = roles.findBy('name', 'Author');
this.get('roles').then(function (roles) {
var authorRole = roles.findBy('name', 'Author');
this.set('authorRole', authorRole);
self.set('authorRole', authorRole);
if (!self.get('role')) {
self.set('role', authorRole);
if (!this.get('role')) {
this.set('role', authorRole);
}
});
}),
@ -37,69 +37,68 @@ export default Ember.Controller.extend(ValidationEngine, {
}
},
confirmReject() {
return false;
},
actions: {
setRole: function (role) {
setRole(role) {
this.set('role', role);
},
confirmAccept: function () {
var email = this.get('email'),
role = this.get('role'),
validationErrors = this.get('errors.messages'),
self = this,
newUser;
confirmAccept() {
let email = this.get('email');
let role = this.get('role');
let validationErrors = this.get('errors.messages');
let newUser;
// reset the form and close the modal
this.set('email', '');
this.set('role', self.get('authorRole'));
this.set('role', this.get('authorRole'));
this.store.findAll('user', {reload: true}).then(function (result) {
var invitedUser = result.findBy('email', email);
this.store.findAll('user', {reload: true}).then((result) => {
let invitedUser = result.findBy('email', email);
if (invitedUser) {
if (invitedUser.get('status') === 'invited' || invitedUser.get('status') === 'invited-pending') {
self.get('notifications').showAlert('A user with that email address was already invited.', {type: 'warn', key: 'invite.send.already-invited'});
this.get('notifications').showAlert('A user with that email address was already invited.', {type: 'warn', key: 'invite.send.already-invited'});
} else {
self.get('notifications').showAlert('A user with that email address already exists.', {type: 'warn', key: 'invite.send.user-exists'});
this.get('notifications').showAlert('A user with that email address already exists.', {type: 'warn', key: 'invite.send.user-exists'});
}
} else {
newUser = self.store.createRecord('user', {
email: email,
status: 'invited',
role: role
newUser = this.store.createRecord('user', {
email,
role,
status: 'invited'
});
newUser.save().then(function () {
var notificationText = 'Invitation sent! (' + email + ')';
newUser.save().then(() => {
let notificationText = `Invitation sent! (${email})`;
// If sending the invitation email fails, the API will still return a status of 201
// but the user's status in the response object will be 'invited-pending'.
if (newUser.get('status') === 'invited-pending') {
self.get('notifications').showAlert('Invitation email was not sent. Please try resending.', {type: 'error', key: 'invite.send.failed'});
this.get('notifications').showAlert('Invitation email was not sent. Please try resending.', {type: 'error', key: 'invite.send.failed'});
} else {
self.get('notifications').closeAlerts('invite.send');
self.get('notifications').showNotification(notificationText);
this.get('notifications').closeAlerts('invite.send');
this.get('notifications').showNotification(notificationText);
}
}).catch(function (errors) {
}).catch((errors) => {
newUser.deleteRecord();
// TODO: user model includes ValidationEngine mixin so
// save is overridden in order to validate, we probably
// want to use inline-validations here and only show an
// alert if we have an actual error
if (errors) {
self.get('notifications').showErrors(errors, {key: 'invite.send'});
this.get('notifications').showErrors(errors, {key: 'invite.send'});
} else if (validationErrors) {
self.get('notifications').showAlert(validationErrors.toString(), {type: 'error', key: 'invite.send.validation-error'});
this.get('notifications').showAlert(validationErrors.toString(), {type: 'error', key: 'invite.send.validation-error'});
}
}).finally(function () {
self.get('errors').clear();
}).finally(() => {
this.get('errors').clear();
});
}
});
},
confirmReject: function () {
return false;
}
}
});

View File

@ -1,18 +1,32 @@
import Ember from 'ember';
export default Ember.Controller.extend({
notifications: Ember.inject.service(),
const {Controller, computed, inject, isArray} = Ember;
const {alias} = computed;
args: Ember.computed.alias('model'),
export default Controller.extend({
notifications: inject.service(),
args: alias('model'),
confirm: {
accept: {
text: 'Leave',
buttonClass: 'btn btn-red'
},
reject: {
text: 'Stay',
buttonClass: 'btn btn-default btn-minor'
}
},
actions: {
confirmAccept: function () {
var args = this.get('args'),
editorController,
confirmAccept() {
let args = this.get('args');
let editorController,
model,
transition;
if (Ember.isArray(args)) {
if (isArray(args)) {
editorController = args[0];
transition = args[1];
model = editorController.get('model');
@ -44,18 +58,7 @@ export default Ember.Controller.extend({
transition.retry();
},
confirmReject: function () {
}
},
confirm: {
accept: {
text: 'Leave',
buttonClass: 'btn btn-red'
},
reject: {
text: 'Stay',
buttonClass: 'btn btn-default btn-minor'
confirmReject() {
}
}
});

View File

@ -1,57 +1,56 @@
import Ember from 'ember';
import ValidationEngine from 'ghost/mixins/validation-engine';
export default Ember.Controller.extend(ValidationEngine, {
const {Controller, computed, inject} = Ember;
export default Controller.extend(ValidationEngine, {
validationType: 'signin',
submitting: false,
application: Ember.inject.controller(),
notifications: Ember.inject.service(),
session: Ember.inject.service(),
application: inject.controller(),
notifications: inject.service(),
session: inject.service(),
identification: Ember.computed('session.user.email', function () {
identification: computed('session.user.email', function () {
return this.get('session.user.email');
}),
actions: {
authenticate: function () {
var appController = this.get('application'),
authStrategy = 'authenticator:oauth2',
self = this;
authenticate() {
let appController = this.get('application');
let authStrategy = 'authenticator:oauth2';
appController.set('skipAuthSuccessHandler', true);
this.get('session').authenticate(authStrategy, this.get('identification'), this.get('password')).then(function () {
self.send('closeModal');
self.set('password', '');
self.get('notifications').closeAlerts('post.save');
}).catch(function () {
this.get('session').authenticate(authStrategy, this.get('identification'), this.get('password')).then(() => {
this.send('closeModal');
this.set('password', '');
this.get('notifications').closeAlerts('post.save');
}).catch(() => {
// if authentication fails a rejected promise will be returned.
// it needs to be caught so it doesn't generate an exception in the console,
// but it's actually "handled" by the sessionAuthenticationFailed action handler.
}).finally(function () {
self.toggleProperty('submitting');
}).finally(() => {
this.toggleProperty('submitting');
appController.set('skipAuthSuccessHandler', undefined);
});
},
validateAndAuthenticate: function () {
var self = this;
validateAndAuthenticate() {
this.toggleProperty('submitting');
// Manually trigger events for input fields, ensuring legacy compatibility with
// browsers and password managers that don't send proper events on autofill
$('#login').find('input').trigger('change');
this.validate({format: false}).then(function () {
self.send('authenticate');
}).catch(function (errors) {
self.get('notifications').showErrors(errors);
this.validate({format: false}).then(() => {
this.send('authenticate');
}).catch((errors) => {
this.get('notifications').showErrors(errors);
});
},
confirmAccept: function () {
confirmAccept() {
this.send('validateAndAuthenticate');
}
}

View File

@ -1,48 +1,12 @@
import Ember from 'ember';
import {request as ajax} from 'ic-ajax';
export default Ember.Controller.extend({
dropdown: Ember.inject.service(),
ghostPaths: Ember.inject.service('ghost-paths'),
notifications: Ember.inject.service(),
const {Controller, inject, isArray} = Ember;
actions: {
confirmAccept: function () {
var user = this.get('model'),
url = this.get('ghostPaths.url').api('users', 'owner'),
self = this;
self.get('dropdown').closeDropdowns();
ajax(url, {
type: 'PUT',
data: {
owner: [{
id: user.get('id')
}]
}
}).then(function (response) {
// manually update the roles for the users that just changed roles
// because store.pushPayload is not working with embedded relations
if (response && Ember.isArray(response.users)) {
response.users.forEach(function (userJSON) {
var user = self.store.peekRecord('user', userJSON.id),
role = self.store.peekRecord('role', userJSON.roles[0].id);
user.set('role', role);
});
}
self.get('notifications').showAlert('Ownership successfully transferred to ' + user.get('name'), {type: 'success', key: 'owner.transfer.success'});
}).catch(function (error) {
self.get('notifications').showAPIError(error, {key: 'owner.transfer'});
});
},
confirmReject: function () {
return false;
}
},
export default Controller.extend({
dropdown: inject.service(),
ghostPaths: inject.service('ghost-paths'),
notifications: inject.service(),
confirm: {
accept: {
@ -53,5 +17,42 @@ export default Ember.Controller.extend({
text: 'Cancel',
buttonClass: 'btn btn-default btn-minor'
}
},
actions: {
confirmAccept() {
let user = this.get('model');
let url = this.get('ghostPaths.url').api('users', 'owner');
this.get('dropdown').closeDropdowns();
ajax(url, {
type: 'PUT',
data: {
owner: [{
id: user.get('id')
}]
}
}).then((response) => {
// manually update the roles for the users that just changed roles
// because store.pushPayload is not working with embedded relations
if (response && isArray(response.users)) {
response.users.forEach((userJSON) => {
let user = this.store.peekRecord('user', userJSON.id);
let role = this.store.peekRecord('role', userJSON.roles[0].id);
user.set('role', role);
});
}
this.get('notifications').showAlert(`Ownership successfully transferred to ${user.get('name')}`, {type: 'success', key: 'owner.transfer.success'});
}).catch((error) => {
this.get('notifications').showAPIError(error, {key: 'owner.transfer'});
});
},
confirmReject() {
return false;
}
}
});

View File

@ -1,22 +1,24 @@
import Ember from 'ember';
export default Ember.Controller.extend({
notifications: Ember.inject.service(),
const {Controller, inject} = Ember;
export default Controller.extend({
notifications: inject.service(),
acceptEncoding: 'image/*',
actions: {
confirmAccept: function () {
var notifications = this.get('notifications');
confirmAccept() {
let notifications = this.get('notifications');
this.get('model').save().then(function (model) {
this.get('model').save().then((model) => {
return model;
}).catch(function (err) {
}).catch((err) => {
notifications.showAPIError(err, {key: 'image.upload'});
});
},
confirmReject: function () {
confirmReject() {
return false;
}
}

View File

@ -5,48 +5,48 @@ import SlugGenerator from 'ghost/models/slug-generator';
import boundOneWay from 'ghost/utils/bound-one-way';
import isNumber from 'ghost/utils/isNumber';
export default Ember.Controller.extend(SettingsMenuMixin, {
const {ArrayProxy, Controller, Handlebars, PromiseProxyMixin, RSVP, computed, guidFor, inject, isArray, observer, run} = Ember;
export default Controller.extend(SettingsMenuMixin, {
debounceId: null,
lastPromise: null,
selectedAuthor: null,
uploaderReference: null,
application: Ember.inject.controller(),
config: Ember.inject.service(),
ghostPaths: Ember.inject.service('ghost-paths'),
notifications: Ember.inject.service(),
session: Ember.inject.service(),
application: inject.controller(),
config: inject.service(),
ghostPaths: inject.service('ghost-paths'),
notifications: inject.service(),
session: inject.service(),
initializeSelectedAuthor: Ember.observer('model', function () {
var self = this;
return this.get('model.author').then(function (author) {
self.set('selectedAuthor', author);
initializeSelectedAuthor: observer('model', function () {
return this.get('model.author').then((author) => {
this.set('selectedAuthor', author);
return author;
});
}),
authors: Ember.computed(function () {
authors: computed(function () {
// Loaded asynchronously, so must use promise proxies.
var deferred = {};
let deferred = {};
deferred.promise = this.store.query('user', {limit: 'all'}).then(function (users) {
deferred.promise = this.store.query('user', {limit: 'all'}).then((users) => {
return users.rejectBy('id', 'me').sortBy('name');
}).then(function (users) {
return users.filter(function (user) {
}).then((users) => {
return users.filter((user) => {
return user.get('active');
});
});
return Ember.ArrayProxy
.extend(Ember.PromiseProxyMixin)
return ArrayProxy
.extend(PromiseProxyMixin)
.create(deferred);
}),
/*jshint unused:false */
publishedAtValue: Ember.computed('model.published_at', {
get: function () {
var pubDate = this.get('model.published_at');
publishedAtValue: computed('model.published_at', {
get() {
let pubDate = this.get('model.published_at');
if (pubDate) {
return formatDate(pubDate);
@ -54,7 +54,7 @@ export default Ember.Controller.extend(SettingsMenuMixin, {
return formatDate(moment());
},
set: function (key, value) {
set(key, value) {
// We're using a fake setter to reset
// the cache for this property
return formatDate(moment());
@ -65,7 +65,7 @@ export default Ember.Controller.extend(SettingsMenuMixin, {
slugValue: boundOneWay('model.slug'),
// Lazy load the slug generator
slugGenerator: Ember.computed(function () {
slugGenerator: computed(function () {
return SlugGenerator.create({
ghostPaths: this.get('ghostPaths'),
slugType: 'post'
@ -73,21 +73,20 @@ export default Ember.Controller.extend(SettingsMenuMixin, {
}),
// Requests slug from title
generateAndSetSlug: function (destination) {
var self = this,
title = this.get('model.titleScratch'),
afterSave = this.get('lastPromise'),
promise;
generateAndSetSlug(destination) {
let title = this.get('model.titleScratch');
let afterSave = this.get('lastPromise');
let promise;
// Only set an "untitled" slug once per post
if (title === '(Untitled)' && this.get('model.slug')) {
return;
}
promise = Ember.RSVP.resolve(afterSave).then(function () {
return self.get('slugGenerator').generateSlug(title).then(function (slug) {
self.set(destination, slug);
}).catch(function () {
promise = RSVP.resolve(afterSave).then(() => {
return this.get('slugGenerator').generateSlug(title).then((slug) => {
this.set(destination, slug);
}).catch(() => {
// Nothing to do (would be nice to log this somewhere though),
// but a rejected promise needs to be handled here so that a resolved
// promise is returned.
@ -100,25 +99,24 @@ export default Ember.Controller.extend(SettingsMenuMixin, {
metaTitleScratch: boundOneWay('model.meta_title'),
metaDescriptionScratch: boundOneWay('model.meta_description'),
seoTitle: Ember.computed('model.titleScratch', 'metaTitleScratch', function () {
var metaTitle = this.get('metaTitleScratch') || '';
seoTitle: computed('model.titleScratch', 'metaTitleScratch', function () {
let metaTitle = this.get('metaTitleScratch') || '';
metaTitle = metaTitle.length > 0 ? metaTitle : this.get('model.titleScratch');
if (metaTitle.length > 70) {
metaTitle = metaTitle.substring(0, 70).trim();
metaTitle = Ember.Handlebars.Utils.escapeExpression(metaTitle);
metaTitle = Ember.String.htmlSafe(metaTitle + '&hellip;');
metaTitle = Handlebars.Utils.escapeExpression(metaTitle);
metaTitle = Ember.String.htmlSafe(`${metaTitle}&hellip;`);
}
return metaTitle;
}),
seoDescription: Ember.computed('model.scratch', 'metaDescriptionScratch', function () {
var metaDescription = this.get('metaDescriptionScratch') || '',
el,
html = '',
placeholder;
seoDescription: computed('model.scratch', 'metaDescriptionScratch', function () {
let metaDescription = this.get('metaDescriptionScratch') || '';
let html = '';
let el, placeholder;
if (metaDescription.length > 0) {
placeholder = metaDescription;
@ -133,27 +131,25 @@ export default Ember.Controller.extend(SettingsMenuMixin, {
}
// Strip HTML
placeholder = $('<div />', {html: html}).text();
placeholder = $('<div />', {html}).text();
// Replace new lines and trim
// jscs: disable
placeholder = placeholder.replace(/\n+/g, ' ').trim();
// jscs: enable
}
if (placeholder.length > 156) {
// Limit to 156 characters
placeholder = placeholder.substring(0, 156).trim();
placeholder = Ember.Handlebars.Utils.escapeExpression(placeholder);
placeholder = Ember.String.htmlSafe(placeholder + '&hellip;');
placeholder = Handlebars.Utils.escapeExpression(placeholder);
placeholder = Ember.String.htmlSafe(`${placeholder}&hellip;`);
}
return placeholder;
}),
seoURL: Ember.computed('model.slug', 'config.blogUrl', function () {
var blogUrl = this.get('config.blogUrl'),
seoSlug = this.get('model.slug') ? this.get('model.slug') : '',
seoURL = blogUrl + '/' + seoSlug;
seoURL: computed('model.slug', 'config.blogUrl', function () {
let blogUrl = this.get('config.blogUrl');
let seoSlug = this.get('model.slug') ? this.get('model.slug') : '';
let seoURL = `${blogUrl}/${seoSlug}`;
// only append a slash to the URL if the slug exists
if (seoSlug) {
@ -162,7 +158,7 @@ export default Ember.Controller.extend(SettingsMenuMixin, {
if (seoURL.length > 70) {
seoURL = seoURL.substring(0, 70).trim();
seoURL = Ember.String.htmlSafe(seoURL + '&hellip;');
seoURL = Ember.String.htmlSafe(`${seoURL}&hellip;`);
}
return seoURL;
@ -170,61 +166,58 @@ export default Ember.Controller.extend(SettingsMenuMixin, {
// observe titleScratch, keeping the post's slug in sync
// with it until saved for the first time.
addTitleObserver: Ember.observer('model', function () {
addTitleObserver: observer('model', function () {
if (this.get('model.isNew') || this.get('model.title') === '(Untitled)') {
this.addObserver('model.titleScratch', this, 'titleObserver');
}
}),
titleObserver: function () {
var debounceId,
title = this.get('model.title');
titleObserver() {
let title = this.get('model.title');
let debounceId;
// generate a slug if a post is new and doesn't have a title yet or
// if the title is still '(Untitled)' and the slug is unaltered.
if ((this.get('model.isNew') && !title) || title === '(Untitled)') {
debounceId = Ember.run.debounce(this, 'generateAndSetSlug', 'model.slug', 700);
debounceId = run.debounce(this, 'generateAndSetSlug', 'model.slug', 700);
}
this.set('debounceId', debounceId);
},
// live-query of all tags for tag input autocomplete
availableTags: Ember.computed(function () {
return this.get('store').filter('tag', {limit: 'all'}, function () {
availableTags: computed(function () {
return this.get('store').filter('tag', {limit: 'all'}, () => {
return true;
});
}),
showErrors: function (errors) {
errors = Ember.isArray(errors) ? errors : [errors];
showErrors(errors) {
errors = isArray(errors) ? errors : [errors];
this.get('notifications').showErrors(errors);
},
actions: {
discardEnter: function () {
discardEnter() {
return false;
},
togglePage: function () {
var self = this;
togglePage() {
this.toggleProperty('model.page');
// If this is a new post. Don't save the model. Defer the save
// to the user pressing the save button
if (this.get('model.isNew')) {
return;
}
this.get('model').save().catch(function (errors) {
self.showErrors(errors);
self.get('model').rollbackAttributes();
this.get('model').save().catch((errors) => {
this.showErrors(errors);
this.get('model').rollbackAttributes();
});
},
toggleFeatured: function () {
var self = this;
toggleFeatured() {
this.toggleProperty('model.featured');
// If this is a new post. Don't save the model. Defer the save
@ -233,21 +226,19 @@ export default Ember.Controller.extend(SettingsMenuMixin, {
return;
}
this.get('model').save(this.get('saveOptions')).catch(function (errors) {
self.showErrors(errors);
self.get('model').rollbackAttributes();
this.get('model').save(this.get('saveOptions')).catch((errors) => {
this.showErrors(errors);
this.get('model').rollbackAttributes();
});
},
/**
* triggered by user manually changing slug
*/
updateSlug: function (newSlug) {
var slug = this.get('model.slug'),
self = this;
updateSlug(newSlug) {
let slug = this.get('model.slug');
newSlug = newSlug || slug;
newSlug = newSlug && newSlug.trim();
// Ignore unchanged slugs or candidate slugs that are empty
@ -258,7 +249,7 @@ export default Ember.Controller.extend(SettingsMenuMixin, {
return;
}
this.get('slugGenerator').generateSlug(newSlug).then(function (serverSlug) {
this.get('slugGenerator').generateSlug(newSlug).then((serverSlug) => {
// If after getting the sanitized and unique slug back from the API
// we end up with a slug that matches the existing slug, abort the change
if (serverSlug === slug) {
@ -272,35 +263,35 @@ export default Ember.Controller.extend(SettingsMenuMixin, {
// the trailing incrementor (e.g., this-is-a-slug and this-is-a-slug-2)
// get the last token out of the slug candidate and see if it's a number
var slugTokens = serverSlug.split('-'),
check = Number(slugTokens.pop());
let slugTokens = serverSlug.split('-');
let check = Number(slugTokens.pop());
// if the candidate slug is the same as the existing slug except
// for the incrementor then the existing slug should be used
if (isNumber(check) && check > 0) {
if (slug === slugTokens.join('-') && serverSlug !== newSlug) {
self.set('slugValue', slug);
this.set('slugValue', slug);
return;
}
}
self.set('model.slug', serverSlug);
this.set('model.slug', serverSlug);
if (self.hasObserverFor('model.titleScratch')) {
self.removeObserver('model.titleScratch', self, 'titleObserver');
if (this.hasObserverFor('model.titleScratch')) {
this.removeObserver('model.titleScratch', this, 'titleObserver');
}
// If this is a new post. Don't save the model. Defer the save
// to the user pressing the save button
if (self.get('model.isNew')) {
if (this.get('model.isNew')) {
return;
}
return self.get('model').save();
}).catch(function (errors) {
self.showErrors(errors);
self.get('model').rollbackAttributes();
return this.get('model').save();
}).catch((errors) => {
this.showErrors(errors);
this.get('model').rollbackAttributes();
});
},
@ -309,11 +300,10 @@ export default Ember.Controller.extend(SettingsMenuMixin, {
* Action sent by post settings menu view.
* (#1351)
*/
setPublishedAt: function (userInput) {
var errMessage = '',
newPublishedAt = parseDateString(userInput),
publishedAt = this.get('model.published_at'),
self = this;
setPublishedAt(userInput) {
let newPublishedAt = parseDateString(userInput);
let publishedAt = this.get('model.published_at');
let errMessage = '';
if (!userInput) {
// Clear out the published_at field for a draft
@ -354,16 +344,16 @@ export default Ember.Controller.extend(SettingsMenuMixin, {
return;
}
this.get('model').save().catch(function (errors) {
self.showErrors(errors);
self.get('model').rollbackAttributes();
this.get('model').save().catch((errors) => {
this.showErrors(errors);
this.get('model').rollbackAttributes();
});
},
setMetaTitle: function (metaTitle) {
var property = 'meta_title',
model = this.get('model'),
currentTitle = model.get(property) || '';
setMetaTitle(metaTitle) {
let property = 'meta_title';
let model = this.get('model');
let currentTitle = model.get(property) || '';
// Only update if the title has changed
if (currentTitle === metaTitle) {
@ -381,10 +371,10 @@ export default Ember.Controller.extend(SettingsMenuMixin, {
model.save();
},
setMetaDescription: function (metaDescription) {
var property = 'meta_description',
model = this.get('model'),
currentDescription = model.get(property) || '';
setMetaDescription(metaDescription) {
let property = 'meta_description';
let model = this.get('model');
let currentDescription = model.get(property) || '';
// Only update if the description has changed
if (currentDescription === metaDescription) {
@ -402,56 +392,51 @@ export default Ember.Controller.extend(SettingsMenuMixin, {
model.save();
},
setCoverImage: function (image) {
var self = this;
setCoverImage(image) {
this.set('model.image', image);
if (this.get('model.isNew')) {
return;
}
this.get('model').save().catch(function (errors) {
self.showErrors(errors);
self.get('model').rollbackAttributes();
this.get('model').save().catch((errors) => {
this.showErrors(errors);
this.get('model').rollbackAttributes();
});
},
clearCoverImage: function () {
var self = this;
clearCoverImage() {
this.set('model.image', '');
if (this.get('model.isNew')) {
return;
}
this.get('model').save().catch(function (errors) {
self.showErrors(errors);
self.get('model').rollbackAttributes();
this.get('model').save().catch((errors) => {
this.showErrors(errors);
this.get('model').rollbackAttributes();
});
},
resetUploader: function () {
var uploader = this.get('uploaderReference');
resetUploader() {
let uploader = this.get('uploaderReference');
if (uploader && uploader[0]) {
uploader[0].uploaderUi.reset();
}
},
resetPubDate: function () {
resetPubDate() {
this.set('publishedAtValue', '');
},
closeNavMenu: function () {
closeNavMenu() {
this.get('application').send('closeNavMenu');
},
changeAuthor: function (newAuthor) {
var author = this.get('model.author'),
model = this.get('model'),
self = this;
changeAuthor(newAuthor) {
let author = this.get('model.author');
let model = this.get('model');
// return if nothing changed
if (newAuthor.get('id') === author.get('id')) {
@ -465,19 +450,20 @@ export default Ember.Controller.extend(SettingsMenuMixin, {
return;
}
model.save().catch(function (errors) {
self.showErrors(errors);
self.set('selectedAuthor', author);
model.save().catch((errors) => {
this.showErrors(errors);
this.set('selectedAuthor', author);
model.rollbackAttributes();
});
},
addTag: function (tagName, index) {
var self = this,
currentTags = this.get('model.tags'),
currentTagNames = currentTags.map(function (tag) { return tag.get('name').toLowerCase(); }),
availableTagNames = null,
tagToAdd = null;
addTag(tagName, index) {
let currentTags = this.get('model.tags');
let currentTagNames = currentTags.map((tag) => {
return tag.get('name').toLowerCase();
});
let availableTagNames,
tagToAdd;
tagName = tagName.trim();
@ -486,30 +472,34 @@ export default Ember.Controller.extend(SettingsMenuMixin, {
return;
}
this.get('availableTags').then(function (availableTags) {
availableTagNames = availableTags.map(function (tag) { return tag.get('name').toLowerCase(); });
this.get('availableTags').then((availableTags) => {
availableTagNames = availableTags.map((tag) => {
return tag.get('name').toLowerCase();
});
// find existing tag or create new
if (availableTagNames.contains(tagName.toLowerCase())) {
tagToAdd = availableTags.find(function (tag) {
tagToAdd = availableTags.find((tag) => {
return tag.get('name').toLowerCase() === tagName.toLowerCase();
});
} else {
tagToAdd = self.get('store').createRecord('tag', {
tagToAdd = this.get('store').createRecord('tag', {
name: tagName
});
// we need to set a UUID so that selectize has a unique value
// it will be ignored when sent to the server
tagToAdd.set('uuid', Ember.guidFor(tagToAdd));
tagToAdd.set('uuid', guidFor(tagToAdd));
}
// push tag onto post relationship
if (tagToAdd) { self.get('model.tags').insertAt(index, tagToAdd); }
if (tagToAdd) {
this.get('model.tags').insertAt(index, tagToAdd);
}
});
},
removeTag: function (tag) {
removeTag(tag) {
this.get('model.tags').removeObject(tag);
if (tag.get('isNew')) {

View File

@ -1,17 +1,20 @@
import Ember from 'ember';
const {Controller, compare, computed} = Ember;
const {equal} = computed;
// a custom sort function is needed in order to sort the posts list the same way the server would:
// status: ASC
// published_at: DESC
// updated_at: DESC
// id: DESC
function comparator(item1, item2) {
var updated1 = item1.get('updated_at'),
updated2 = item2.get('updated_at'),
idResult,
let updated1 = item1.get('updated_at');
let updated2 = item2.get('updated_at');
let idResult,
publishedAtResult,
statusResult,
updatedAtResult,
publishedAtResult;
updatedAtResult;
// when `updated_at` is undefined, the model is still
// being written to with the results from the server
@ -23,9 +26,9 @@ function comparator(item1, item2) {
return 1;
}
idResult = Ember.compare(parseInt(item1.get('id')), parseInt(item2.get('id')));
statusResult = Ember.compare(item1.get('status'), item2.get('status'));
updatedAtResult = Ember.compare(updated1.valueOf(), updated2.valueOf());
idResult = compare(parseInt(item1.get('id')), parseInt(item2.get('id')));
statusResult = compare(item1.get('status'), item2.get('status'));
updatedAtResult = compare(updated1.valueOf(), updated2.valueOf());
publishedAtResult = publishedAtCompare(item1, item2);
if (statusResult === 0) {
@ -45,8 +48,8 @@ function comparator(item1, item2) {
}
function publishedAtCompare(item1, item2) {
var published1 = item1.get('published_at'),
published2 = item2.get('published_at');
let published1 = item1.get('published_at');
let published2 = item2.get('published_at');
if (!published1 && !published2) {
return 0;
@ -60,23 +63,23 @@ function publishedAtCompare(item1, item2) {
return 1;
}
return Ember.compare(published1.valueOf(), published2.valueOf());
return compare(published1.valueOf(), published2.valueOf());
}
export default Ember.Controller.extend({
export default Controller.extend({
// See PostsRoute's shortcuts
postListFocused: Ember.computed.equal('keyboardFocus', 'postList'),
postContentFocused: Ember.computed.equal('keyboardFocus', 'postContent'),
postListFocused: equal('keyboardFocus', 'postList'),
postContentFocused: equal('keyboardFocus', 'postContent'),
sortedPosts: Ember.computed('model.@each.status', 'model.@each.published_at', 'model.@each.isNew', 'model.@each.updated_at', function () {
var postsArray = this.get('model').toArray();
sortedPosts: computed('model.@each.status', 'model.@each.published_at', 'model.@each.isNew', 'model.@each.updated_at', function () {
let postsArray = this.get('model').toArray();
return postsArray.sort(comparator);
}),
actions: {
showPostContent: function (post) {
showPostContent(post) {
if (!post) {
return;
}

View File

@ -2,7 +2,9 @@ import Ember from 'ember';
import {request as ajax} from 'ic-ajax';
import ValidationEngine from 'ghost/mixins/validation-engine';
export default Ember.Controller.extend(ValidationEngine, {
const {Controller, computed, inject} = Ember;
export default Controller.extend(ValidationEngine, {
newPassword: '',
ne2Password: '',
token: '',
@ -11,18 +13,18 @@ export default Ember.Controller.extend(ValidationEngine, {
validationType: 'reset',
ghostPaths: Ember.inject.service('ghost-paths'),
notifications: Ember.inject.service(),
session: Ember.inject.service(),
ghostPaths: inject.service('ghost-paths'),
notifications: inject.service(),
session: inject.service(),
email: Ember.computed('token', function () {
email: computed('token', function () {
// The token base64 encodes the email (and some other stuff),
// each section is divided by a '|'. Email comes second.
return atob(this.get('token')).split('|')[1];
}),
// Used to clear sensitive information
clearData: function () {
clearData() {
this.setProperties({
newPassword: '',
ne2Password: '',
@ -31,34 +33,34 @@ export default Ember.Controller.extend(ValidationEngine, {
},
actions: {
submit: function () {
var credentials = this.getProperties('newPassword', 'ne2Password', 'token'),
self = this;
submit() {
let credentials = this.getProperties('newPassword', 'ne2Password', 'token');
this.set('flowErrors', '');
this.get('hasValidated').addObjects((['newPassword', 'ne2Password']));
this.validate().then(function () {
self.toggleProperty('submitting');
this.validate().then(() => {
this.toggleProperty('submitting');
ajax({
url: self.get('ghostPaths.url').api('authentication', 'passwordreset'),
url: this.get('ghostPaths.url').api('authentication', 'passwordreset'),
type: 'PUT',
data: {
passwordreset: [credentials]
}
}).then(function (resp) {
self.toggleProperty('submitting');
self.get('notifications').showAlert(resp.passwordreset[0].message, {type: 'warn', delayed: true, key: 'password.reset'});
self.get('session').authenticate('authenticator:oauth2', self.get('email'), credentials.newPassword);
}).catch(function (response) {
self.get('notifications').showAPIError(response, {key: 'password.reset'});
self.toggleProperty('submitting');
}).then((resp) => {
this.toggleProperty('submitting');
this.get('notifications').showAlert(resp.passwordreset[0].message, {type: 'warn', delayed: true, key: 'password.reset'});
this.get('session').authenticate('authenticator:oauth2', this.get('email'), credentials.newPassword);
}).catch((response) => {
this.get('notifications').showAPIError(response, {key: 'password.reset'});
this.toggleProperty('submitting');
});
}).catch(function () {
if (self.get('errors.newPassword')) {
self.set('flowErrors', self.get('errors.newPassword')[0].message);
}).catch(() => {
if (this.get('errors.newPassword')) {
this.set('flowErrors', this.get('errors.newPassword')[0].message);
}
if (self.get('errors.ne2Password')) {
self.set('flowErrors', self.get('errors.ne2Password')[0].message);
if (this.get('errors.ne2Password')) {
this.set('flowErrors', this.get('errors.ne2Password')[0].message);
}
});
}

View File

@ -1,13 +1,15 @@
import Ember from 'ember';
import SettingsSaveMixin from 'ghost/mixins/settings-save';
export default Ember.Controller.extend(SettingsSaveMixin, {
notifications: Ember.inject.service(),
const {Controller, inject} = Ember;
save: function () {
var notifications = this.get('notifications');
export default Controller.extend(SettingsSaveMixin, {
notifications: inject.service(),
return this.get('model').save().catch(function (error) {
save() {
let notifications = this.get('notifications');
return this.get('model').save().catch((error) => {
notifications.showAPIError(error, {key: 'code-injection.save'});
});
}

View File

@ -2,16 +2,18 @@ import Ember from 'ember';
import SettingsSaveMixin from 'ghost/mixins/settings-save';
import randomPassword from 'ghost/utils/random-password';
export default Ember.Controller.extend(SettingsSaveMixin, {
notifications: Ember.inject.service(),
config: Ember.inject.service(),
const {Controller, computed, inject, observer} = Ember;
selectedTheme: Ember.computed('model.activeTheme', 'themes', function () {
var activeTheme = this.get('model.activeTheme'),
themes = this.get('themes'),
selectedTheme;
export default Controller.extend(SettingsSaveMixin, {
notifications: inject.service(),
config: inject.service(),
themes.forEach(function (theme) {
selectedTheme: computed('model.activeTheme', 'themes', function () {
let activeTheme = this.get('model.activeTheme');
let themes = this.get('themes');
let selectedTheme;
themes.forEach((theme) => {
if (theme.name === activeTheme) {
selectedTheme = theme;
}
@ -20,34 +22,35 @@ export default Ember.Controller.extend(SettingsSaveMixin, {
return selectedTheme;
}),
logoImageSource: Ember.computed('model.logo', function () {
logoImageSource: computed('model.logo', function () {
return this.get('model.logo') || '';
}),
coverImageSource: Ember.computed('model.cover', function () {
coverImageSource: computed('model.cover', function () {
return this.get('model.cover') || '';
}),
isDatedPermalinks: Ember.computed('model.permalinks', {
set: function (key, value) {
isDatedPermalinks: computed('model.permalinks', {
set(key, value) {
this.set('model.permalinks', value ? '/:year/:month/:day/:slug/' : '/:slug/');
var slugForm = this.get('model.permalinks');
let slugForm = this.get('model.permalinks');
return slugForm !== '/:slug/';
},
get: function () {
var slugForm = this.get('model.permalinks');
get() {
let slugForm = this.get('model.permalinks');
return slugForm !== '/:slug/';
}
}),
themes: Ember.computed(function () {
themes: computed(function () {
return this.get('model.availableThemes').reduce(function (themes, t) {
var theme = {};
let theme = {};
theme.name = t.name;
theme.label = t.package ? t.package.name + ' - ' + t.package.version : t.name;
theme.label = t.package ? `${t.package.name} - ${t.package.version}` : t.name;
theme.package = t.package;
theme.active = !!t.active;
@ -57,22 +60,22 @@ export default Ember.Controller.extend(SettingsSaveMixin, {
}, []);
}).readOnly(),
generatePassword: Ember.observer('model.isPrivate', function () {
generatePassword: observer('model.isPrivate', function () {
this.get('model.errors').remove('password');
if (this.get('model.isPrivate') && this.get('model.hasDirtyAttributes')) {
this.get('model').set('password', randomPassword());
}
}),
save: function () {
var notifications = this.get('notifications'),
config = this.get('config');
save() {
let notifications = this.get('notifications');
let config = this.get('config');
return this.get('model').save().then(function (model) {
return this.get('model').save().then((model) => {
config.set('blogTitle', model.get('title'));
return model;
}).catch(function (error) {
}).catch((error) => {
if (error) {
notifications.showAPIError(error, {key: 'settings.save'});
}
@ -80,19 +83,19 @@ export default Ember.Controller.extend(SettingsSaveMixin, {
},
actions: {
validate: function (property) {
this.get('model').validate({property: property});
validate(property) {
this.get('model').validate({property});
},
checkPostsPerPage: function () {
var postsPerPage = this.get('model.postsPerPage');
checkPostsPerPage() {
let postsPerPage = this.get('model.postsPerPage');
if (postsPerPage < 1 || postsPerPage > 1000 || isNaN(postsPerPage)) {
this.set('model.postsPerPage', 5);
}
},
setTheme: function (theme) {
setTheme(theme) {
this.set('model.activeTheme', theme.name);
}
}

View File

@ -1,51 +1,51 @@
import Ember from 'ember';
import {request as ajax} from 'ic-ajax';
export default Ember.Controller.extend({
const {Controller, computed, inject} = Ember;
export default Controller.extend({
uploadButtonText: 'Import',
importErrors: '',
submitting: false,
ghostPaths: Ember.inject.service('ghost-paths'),
notifications: Ember.inject.service(),
session: Ember.inject.service(),
feature: Ember.inject.controller(),
ghostPaths: inject.service('ghost-paths'),
notifications: inject.service(),
session: inject.service(),
feature: inject.controller(),
labsJSON: Ember.computed('model.labs', function () {
labsJSON: computed('model.labs', function () {
return JSON.parse(this.get('model.labs') || {});
}),
saveLabs: function (optionName, optionValue) {
var self = this,
labsJSON = this.get('labsJSON');
saveLabs(optionName, optionValue) {
let labsJSON = this.get('labsJSON');
// Set new value in the JSON object
labsJSON[optionName] = optionValue;
this.set('model.labs', JSON.stringify(labsJSON));
this.get('model').save().catch(function (errors) {
self.showErrors(errors);
self.get('model').rollbackAttributes();
this.get('model').save().catch((errors) => {
this.showErrors(errors);
this.get('model').rollbackAttributes();
});
},
usePublicAPI: Ember.computed('feature.publicAPI', {
get: function () {
usePublicAPI: computed('feature.publicAPI', {
get() {
return this.get('feature.publicAPI');
},
set: function (key, value) {
set(key, value) {
this.saveLabs('publicAPI', value);
return value;
}
}),
actions: {
onUpload: function (file) {
var self = this,
formData = new FormData(),
notifications = this.get('notifications'),
currentUserId = this.get('session.user.id');
onUpload(file) {
let formData = new FormData();
let notifications = this.get('notifications');
let currentUserId = this.get('session.user.id');
this.set('uploadButtonText', 'Importing');
this.set('importErrors', '');
@ -59,29 +59,30 @@ export default Ember.Controller.extend({
cache: false,
contentType: false,
processData: false
}).then(function () {
}).then(() => {
// Clear the store, so that all the new data gets fetched correctly.
self.store.unloadAll();
this.store.unloadAll();
// Reload currentUser and set session
self.set('session.user', self.store.findRecord('user', currentUserId));
this.set('session.user', this.store.findRecord('user', currentUserId));
// TODO: keep as notification, add link to view content
notifications.showNotification('Import successful.');
notifications.closeAlerts('import.upload');
}).catch(function (response) {
}).catch((response) => {
if (response && response.jqXHR && response.jqXHR.responseJSON && response.jqXHR.responseJSON.errors) {
self.set('importErrors', response.jqXHR.responseJSON.errors);
this.set('importErrors', response.jqXHR.responseJSON.errors);
}
notifications.showAlert('Import Failed', {type: 'error', key: 'import.upload.failed'});
}).finally(function () {
self.set('uploadButtonText', 'Import');
}).finally(() => {
this.set('uploadButtonText', 'Import');
});
},
exportData: function () {
var iframe = $('#iframeDownload'),
downloadURL = this.get('ghostPaths.url').api('db') +
'?access_token=' + this.get('session.data.authenticated.access_token');
exportData() {
let dbUrl = this.get('ghostPaths.url').api('db');
let accessToken = this.get('session.data.authenticated.access_token');
let downloadURL = `${dbUrl}?access_token=${accessToken}`;
let iframe = $('#iframeDownload');
if (iframe.length === 0) {
iframe = $('<iframe>', {id: 'iframeDownload'}).hide().appendTo('body');
@ -90,24 +91,23 @@ export default Ember.Controller.extend({
iframe.attr('src', downloadURL);
},
sendTestEmail: function () {
var notifications = this.get('notifications'),
self = this;
sendTestEmail() {
let notifications = this.get('notifications');
this.toggleProperty('submitting');
ajax(this.get('ghostPaths.url').api('mail', 'test'), {
type: 'POST'
}).then(function () {
}).then(() => {
notifications.showAlert('Check your email for the test message.', {type: 'info', key: 'test-email.send.success'});
self.toggleProperty('submitting');
}).catch(function (error) {
this.toggleProperty('submitting');
}).catch((error) => {
if (typeof error.jqXHR !== 'undefined') {
notifications.showAPIError(error, {key: 'test-email.send'});
} else {
notifications.showErrors(error, {key: 'test-email.send'});
}
self.toggleProperty('submitting');
this.toggleProperty('submitting');
});
}
}

View File

@ -3,6 +3,10 @@ import DS from 'ember-data';
import SettingsSaveMixin from 'ghost/mixins/settings-save';
import ValidationEngine from 'ghost/mixins/validation-engine';
const {Controller, RSVP, computed, inject, isBlank, observer} = Ember;
const {Errors} = DS;
const emberA = Ember.A;
export const NavItem = Ember.Object.extend(ValidationEngine, {
label: '',
url: '',
@ -10,30 +14,30 @@ export const NavItem = Ember.Object.extend(ValidationEngine, {
validationType: 'navItem',
isComplete: Ember.computed('label', 'url', function () {
return !(Ember.isBlank(this.get('label').trim()) || Ember.isBlank(this.get('url')));
isComplete: computed('label', 'url', function () {
return !(isBlank(this.get('label').trim()) || isBlank(this.get('url')));
}),
init: function () {
init() {
this._super(...arguments);
this.set('errors', DS.Errors.create());
this.set('hasValidated', Ember.A());
this.set('errors', Errors.create());
this.set('hasValidated', emberA());
}
});
export default Ember.Controller.extend(SettingsSaveMixin, {
config: Ember.inject.service(),
notifications: Ember.inject.service(),
export default Controller.extend(SettingsSaveMixin, {
config: inject.service(),
notifications: inject.service(),
blogUrl: Ember.computed('config.blogUrl', function () {
var url = this.get('config.blogUrl');
blogUrl: computed('config.blogUrl', function () {
let url = this.get('config.blogUrl');
return url.slice(-1) !== '/' ? url + '/' : url;
return url.slice(-1) !== '/' ? `${url}/` : url;
}),
navigationItems: Ember.computed('model.navigation', function () {
var navItems,
lastItem;
navigationItems: computed('model.navigation', function () {
let lastItem,
navItems;
try {
navItems = JSON.parse(this.get('model.navigation') || [{}]);
@ -41,7 +45,7 @@ export default Ember.Controller.extend(SettingsSaveMixin, {
navItems = [{}];
}
navItems = navItems.map(function (item) {
navItems = navItems.map((item) => {
return NavItem.create(item);
});
@ -53,10 +57,10 @@ export default Ember.Controller.extend(SettingsSaveMixin, {
return navItems;
}),
updateLastNavItem: Ember.observer('navigationItems.[]', function () {
var navItems = this.get('navigationItems');
updateLastNavItem: observer('navigationItems.[]', function () {
let navItems = this.get('navigationItems');
navItems.forEach(function (item, index, items) {
navItems.forEach((item, index, items) => {
if (index === (items.length - 1)) {
item.set('last', true);
} else {
@ -65,72 +69,72 @@ export default Ember.Controller.extend(SettingsSaveMixin, {
});
}),
save: function () {
var navSetting,
navItems = this.get('navigationItems'),
notifications = this.get('notifications'),
validationPromises,
self = this;
save() {
let navItems = this.get('navigationItems');
let notifications = this.get('notifications');
let navSetting,
validationPromises;
validationPromises = navItems.map(function (item) {
validationPromises = navItems.map((item) => {
return item.validate();
});
return Ember.RSVP.all(validationPromises).then(function () {
navSetting = navItems.map(function (item) {
var label = item.get('label').trim(),
url = item.get('url').trim();
return RSVP.all(validationPromises).then(() => {
navSetting = navItems.map((item) => {
let label = item.get('label').trim();
let url = item.get('url').trim();
if (item.get('last') && !item.get('isComplete')) {
return null;
}
return {label: label, url: url};
return {label, url};
}).compact();
self.set('model.navigation', JSON.stringify(navSetting));
this.set('model.navigation', JSON.stringify(navSetting));
// trigger change event because even if the final JSON is unchanged
// we need to have navigationItems recomputed.
self.get('model').notifyPropertyChange('navigation');
this.get('model').notifyPropertyChange('navigation');
return self.get('model').save().catch(function (err) {
return this.get('model').save().catch((err) => {
notifications.showErrors(err);
});
}).catch(function () {
}).catch(() => {
// TODO: noop - needed to satisfy spinner button
});
},
actions: {
addItem: function () {
var navItems = this.get('navigationItems'),
lastItem = navItems.get('lastObject');
addItem() {
let navItems = this.get('navigationItems');
let lastItem = navItems.get('lastObject');
if (lastItem && lastItem.get('isComplete')) {
navItems.addObject(NavItem.create({last: true})); // Adds new blank navItem
// Add new blank navItem
navItems.addObject(NavItem.create({last: true}));
}
},
deleteItem: function (item) {
deleteItem(item) {
if (!item) {
return;
}
var navItems = this.get('navigationItems');
let navItems = this.get('navigationItems');
navItems.removeObject(item);
},
moveItem: function (index, newIndex) {
var navItems = this.get('navigationItems'),
item = navItems.objectAt(index);
moveItem(index, newIndex) {
let navItems = this.get('navigationItems');
let item = navItems.objectAt(index);
navItems.removeAt(index);
navItems.insertAt(newIndex, item);
},
updateUrl: function (url, navItem) {
updateUrl(url, navItem) {
if (!navItem) {
return;
}

View File

@ -1,7 +1,7 @@
import Ember from 'ember';
const {computed, inject} = Ember,
{alias, equal, sort} = computed;
const {computed, inject} = Ember;
const {alias, equal, sort} = computed;
export default Ember.Controller.extend({
@ -14,8 +14,8 @@ export default Ember.Controller.extend({
// TODO: replace with ordering by page count once supported by the API
tags: sort('model', function (a, b) {
const idA = +a.get('id'),
idB = +b.get('id');
let idA = +a.get('id');
let idB = +b.get('id');
if (idA > idB) {
return 1;
@ -27,7 +27,7 @@ export default Ember.Controller.extend({
}),
actions: {
leftMobile: function () {
leftMobile() {
let firstTag = this.get('tags.firstObject');
// redirect to first tag if possible so that you're not left with
// tag settings blank slate when switching from portrait to landscape

View File

@ -1,9 +1,9 @@
import Ember from 'ember';
const {computed, inject} = Ember,
{alias} = computed;
const {Controller, computed, inject} = Ember;
const {alias} = computed;
export default Ember.Controller.extend({
export default Controller.extend({
tag: alias('model'),
isMobile: alias('tagsController.isMobile'),
@ -11,9 +11,9 @@ export default Ember.Controller.extend({
tagsController: inject.controller('settings.tags'),
notifications: inject.service(),
saveTagProperty: function (propKey, newValue) {
const tag = this.get('tag'),
currentValue = tag.get(propKey);
saveTagProperty(propKey, newValue) {
let tag = this.get('tag');
let currentValue = tag.get(propKey);
newValue = newValue.trim();
@ -37,7 +37,7 @@ export default Ember.Controller.extend({
},
actions: {
setProperty: function (propKey, value) {
setProperty(propKey, value) {
this.saveTagProperty(propKey, value);
}
}

View File

@ -1,14 +1,17 @@
import Ember from 'ember';
export default Ember.Controller.extend({
appController: Ember.inject.controller('application'),
ghostPaths: Ember.inject.service('ghost-paths'),
const {Controller, computed, get, inject} = Ember;
const {match} = computed;
showBackLink: Ember.computed.match('appController.currentRouteName', /^setup\.(two|three)$/),
export default Controller.extend({
appController: inject.controller('application'),
ghostPaths: inject.service('ghost-paths'),
backRoute: Ember.computed('appController.currentRouteName', function () {
var appController = this.get('appController'),
currentRoute = Ember.get(appController, 'currentRouteName');
showBackLink: match('appController.currentRouteName', /^setup\.(two|three)$/),
backRoute: computed('appController.currentRouteName', function () {
let appController = this.get('appController');
let currentRoute = get(appController, 'currentRouteName');
return currentRoute === 'setup.two' ? 'setup.one' : 'setup.two';
})

View File

@ -1,19 +1,24 @@
import Ember from 'ember';
import DS from 'ember-data';
export default Ember.Controller.extend({
notifications: Ember.inject.service(),
two: Ember.inject.controller('setup/two'),
const {Controller, RSVP, computed, inject} = Ember;
const {Errors} = DS;
const {alias} = computed;
const emberA = Ember.A;
errors: DS.Errors.create(),
hasValidated: Ember.A(),
export default Controller.extend({
notifications: inject.service(),
two: inject.controller('setup/two'),
errors: Errors.create(),
hasValidated: emberA(),
users: '',
ownerEmail: Ember.computed.alias('two.email'),
ownerEmail: alias('two.email'),
submitting: false,
usersArray: Ember.computed('users', function () {
var errors = this.get('errors'),
users = this.get('users').split('\n').filter(function (email) {
usersArray: computed('users', function () {
let errors = this.get('errors');
let users = this.get('users').split('\n').filter(function (email) {
return email.trim().length > 0;
});
@ -27,28 +32,28 @@ export default Ember.Controller.extend({
return users.uniq();
}),
validUsersArray: Ember.computed('usersArray', 'ownerEmail', function () {
var ownerEmail = this.get('ownerEmail');
validUsersArray: computed('usersArray', 'ownerEmail', function () {
let ownerEmail = this.get('ownerEmail');
return this.get('usersArray').filter(function (user) {
return validator.isEmail(user) && user !== ownerEmail;
});
}),
invalidUsersArray: Ember.computed('usersArray', 'ownerEmail', function () {
var ownerEmail = this.get('ownerEmail');
invalidUsersArray: computed('usersArray', 'ownerEmail', function () {
let ownerEmail = this.get('ownerEmail');
return this.get('usersArray').reject(function (user) {
return this.get('usersArray').reject((user) => {
return validator.isEmail(user) || user === ownerEmail;
});
}),
validationResult: Ember.computed('invalidUsersArray', function () {
var errors = [];
validationResult: computed('invalidUsersArray', function () {
let errors = [];
this.get('invalidUsersArray').forEach(function (user) {
this.get('invalidUsersArray').forEach((user) => {
errors.push({
user: user,
user,
error: 'email'
});
});
@ -62,34 +67,36 @@ export default Ember.Controller.extend({
}
}),
validate: function () {
var errors = this.get('errors'),
validationResult = this.get('validationResult'),
property = 'users';
validate() {
let errors = this.get('errors');
let validationResult = this.get('validationResult');
let property = 'users';
errors.clear();
// If property isn't in the `hasValidated` array, add it to mark that this field can show a validation result
this.get('hasValidated').addObject(property);
if (validationResult === true) { return true; }
if (validationResult === true) {
return true;
}
validationResult.forEach(function (error) {
validationResult.forEach((error) => {
// Only one error type here so far, but one day the errors might be more detailed
switch (error.error) {
case 'email':
errors.add(property, error.user + ' is not a valid email.');
errors.add(property, `${error.user} is not a valid email.`);
}
});
return false;
},
buttonText: Ember.computed('errors.users', 'validUsersArray', 'invalidUsersArray', function () {
var usersError = this.get('errors.users.firstObject.message'),
validNum = this.get('validUsersArray').length,
invalidNum = this.get('invalidUsersArray').length,
userCount;
buttonText: computed('errors.users', 'validUsersArray', 'invalidUsersArray', function () {
let usersError = this.get('errors.users.firstObject.message');
let validNum = this.get('validUsersArray').length;
let invalidNum = this.get('invalidUsersArray').length;
let userCount;
if (usersError && usersError.match(/no users/i)) {
return usersError;
@ -102,15 +109,15 @@ export default Ember.Controller.extend({
if (validNum > 0) {
userCount = validNum === 1 ? 'user' : 'users';
userCount = validNum + ' ' + userCount;
userCount = `${validNum} ${userCount}`;
} else {
userCount = 'some users';
}
return 'Invite ' + userCount;
return `Invite ${userCount}`;
}),
buttonClass: Ember.computed('validationResult', 'usersArray.length', function () {
buttonClass: computed('validationResult', 'usersArray.length', function () {
if (this.get('validationResult') === true && this.get('usersArray.length') > 0) {
return 'btn-green';
} else {
@ -118,52 +125,51 @@ export default Ember.Controller.extend({
}
}),
authorRole: Ember.computed(function () {
return this.store.findAll('role', {reload: true}).then(function (roles) {
authorRole: computed(function () {
return this.store.findAll('role', {reload: true}).then((roles) => {
return roles.findBy('name', 'Author');
});
}),
actions: {
validate: function () {
validate() {
this.validate();
},
invite: function () {
var self = this,
users = this.get('usersArray'),
notifications = this.get('notifications'),
invitationsString;
invite() {
let users = this.get('usersArray');
let notifications = this.get('notifications');
let invitationsString;
if (this.validate() && users.length > 0) {
this.toggleProperty('submitting');
this.get('authorRole').then(function (authorRole) {
Ember.RSVP.Promise.all(
users.map(function (user) {
var newUser = self.store.createRecord('user', {
this.get('authorRole').then((authorRole) => {
RSVP.Promise.all(
users.map((user) => {
let newUser = this.store.createRecord('user', {
email: user,
status: 'invited',
role: authorRole
});
return newUser.save().then(function () {
return newUser.save().then(() => {
return {
email: user,
success: newUser.get('status') === 'invited'
};
}).catch(function () {
}).catch(() => {
return {
email: user,
success: false
};
});
})
).then(function (invites) {
var successCount = 0,
erroredEmails = [],
message;
).then((invites) => {
let erroredEmails = [];
let successCount = 0;
let message;
invites.forEach(function (invite) {
invites.forEach((invite) => {
if (invite.success) {
successCount++;
} else {
@ -173,7 +179,7 @@ export default Ember.Controller.extend({
if (erroredEmails.length > 0) {
invitationsString = erroredEmails.length > 1 ? ' invitations: ' : ' invitation: ';
message = 'Failed to send ' + erroredEmails.length + invitationsString;
message = `Failed to send ${erroredEmails.length} ${invitationsString}`;
message += erroredEmails.join(', ');
notifications.showAlert(message, {type: 'error', delayed: successCount > 0, key: 'signup.send-invitations.failed'});
}
@ -181,11 +187,11 @@ export default Ember.Controller.extend({
if (successCount > 0) {
// pluralize
invitationsString = successCount > 1 ? 'invitations' : 'invitation';
notifications.showAlert(successCount + ' ' + invitationsString + ' sent!', {type: 'success', delayed: true, key: 'signup.send-invitations.success'});
notifications.showAlert(`${successCount} ${invitationsString} sent!`, {type: 'success', delayed: true, key: 'signup.send-invitations.success'});
}
self.send('loadServerNotifications');
self.toggleProperty('submitting');
self.transitionToRoute('posts.index');
this.send('loadServerNotifications');
this.toggleProperty('submitting');
this.transitionToRoute('posts.index');
});
});
} else if (users.length === 0) {
@ -193,7 +199,7 @@ export default Ember.Controller.extend({
}
},
skipInvite: function () {
skipInvite() {
this.send('loadServerNotifications');
this.transitionToRoute('posts.index');
}

View File

@ -2,7 +2,9 @@ import Ember from 'ember';
import {request as ajax} from 'ic-ajax';
import ValidationEngine from 'ghost/mixins/validation-engine';
export default Ember.Controller.extend(ValidationEngine, {
const {Controller, RSVP, inject} = Ember;
export default Controller.extend(ValidationEngine, {
size: 90,
blogTitle: null,
name: null,
@ -13,11 +15,11 @@ export default Ember.Controller.extend(ValidationEngine, {
submitting: false,
flowErrors: '',
ghostPaths: Ember.inject.service('ghost-paths'),
notifications: Ember.inject.service(),
application: Ember.inject.controller(),
config: Ember.inject.service(),
session: Ember.inject.service(),
ghostPaths: inject.service('ghost-paths'),
notifications: inject.service(),
application: inject.controller(),
config: inject.service(),
session: inject.service(),
// ValidationEngine settings
validationType: 'setup',
@ -27,17 +29,16 @@ export default Ember.Controller.extend(ValidationEngine, {
* @param {Object} user User object, returned from the 'setup' api call
* @return {Ember.RSVP.Promise} A promise that takes care of both calls
*/
sendImage: function (user) {
var self = this,
image = this.get('image');
sendImage(user) {
let image = this.get('image');
return new Ember.RSVP.Promise(function (resolve, reject) {
return new RSVP.Promise((resolve, reject) => {
image.formData = {};
image.submit()
.success(function (response) {
user.image = response;
ajax({
url: self.get('ghostPaths.url').api('users', user.id.toString()),
url: this.get('ghostPaths.url').api('users', user.id.toString()),
type: 'PUT',
data: {
users: [user]
@ -48,7 +49,7 @@ export default Ember.Controller.extend(ValidationEngine, {
});
},
_handleSaveError: function (resp) {
_handleSaveError(resp) {
this.toggleProperty('submitting');
if (resp && resp.jqXHR && resp.jqXHR.responseJSON && resp.jqXHR.responseJSON.errors) {
this.set('flowErrors', resp.jqXHR.responseJSON.errors[0].message);
@ -57,7 +58,7 @@ export default Ember.Controller.extend(ValidationEngine, {
}
},
_handleAuthenticationError: function (error) {
_handleAuthenticationError(error) {
this.toggleProperty('submitting');
if (error && error.errors) {
this.set('flowErrors', error.errors[0].message);
@ -68,28 +69,27 @@ export default Ember.Controller.extend(ValidationEngine, {
},
actions: {
preValidate: function (model) {
preValidate(model) {
// Only triggers validation if a value has been entered, preventing empty errors on focusOut
if (this.get(model)) {
this.validate({property: model});
}
},
setup: function () {
var self = this,
setupProperties = ['blogTitle', 'name', 'email', 'password', 'image'],
data = self.getProperties(setupProperties),
notifications = this.get('notifications'),
config = this.get('config'),
method = this.get('blogCreated') ? 'PUT' : 'POST';
setup() {
let setupProperties = ['blogTitle', 'name', 'email', 'password', 'image'];
let data = this.getProperties(setupProperties);
let notifications = this.get('notifications');
let config = this.get('config');
let method = this.get('blogCreated') ? 'PUT' : 'POST';
this.toggleProperty('submitting');
this.set('flowErrors', '');
this.get('hasValidated').addObjects(setupProperties);
this.validate().then(function () {
this.validate().then(() => {
ajax({
url: self.get('ghostPaths.url').api('authentication', 'setup'),
url: this.get('ghostPaths.url').api('authentication', 'setup'),
type: method,
data: {
setup: [{
@ -99,37 +99,38 @@ export default Ember.Controller.extend(ValidationEngine, {
blogTitle: data.blogTitle
}]
}
}).then(function (result) {
}).then((result) => {
config.set('blogTitle', data.blogTitle);
// Don't call the success handler, otherwise we will be redirected to admin
self.get('application').set('skipAuthSuccessHandler', true);
self.get('session').authenticate('authenticator:oauth2', self.get('email'), self.get('password')).then(function () {
self.set('blogCreated', true);
this.get('application').set('skipAuthSuccessHandler', true);
this.get('session').authenticate('authenticator:oauth2', this.get('email'), this.get('password')).then(() => {
this.set('blogCreated', true);
if (data.image) {
self.sendImage(result.users[0])
.then(function () {
self.toggleProperty('submitting');
self.transitionToRoute('setup.three');
}).catch(function (resp) {
self.toggleProperty('submitting');
this.sendImage(result.users[0])
.then(() => {
this.toggleProperty('submitting');
this.transitionToRoute('setup.three');
}).catch((resp) => {
this.toggleProperty('submitting');
notifications.showAPIError(resp, {key: 'setup.blog-details'});
});
} else {
self.toggleProperty('submitting');
self.transitionToRoute('setup.three');
this.toggleProperty('submitting');
this.transitionToRoute('setup.three');
}
}).catch(function (error) {
self._handleAuthenticationError(error);
}).catch((error) => {
this._handleAuthenticationError(error);
});
}).catch(function (error) {
self._handleSaveError(error);
}).catch((error) => {
this._handleSaveError(error);
});
}).catch(function () {
self.toggleProperty('submitting');
self.set('flowErrors', 'Please fill out the form to setup your blog.');
}).catch(() => {
this.toggleProperty('submitting');
this.set('flowErrors', 'Please fill out the form to setup your blog.');
});
},
setImage: function (image) {
setImage(image) {
this.set('image', image);
}
}

View File

@ -2,53 +2,53 @@ import Ember from 'ember';
import ValidationEngine from 'ghost/mixins/validation-engine';
import {request as ajax} from 'ic-ajax';
export default Ember.Controller.extend(ValidationEngine, {
const {Controller, inject} = Ember;
export default Controller.extend(ValidationEngine, {
submitting: false,
loggingIn: false,
authProperties: ['identification', 'password'],
ghostPaths: Ember.inject.service('ghost-paths'),
notifications: Ember.inject.service(),
session: Ember.inject.service(),
application: Ember.inject.controller(),
ghostPaths: inject.service('ghost-paths'),
notifications: inject.service(),
session: inject.service(),
application: inject.controller(),
flowErrors: '',
// ValidationEngine settings
validationType: 'signin',
actions: {
authenticate: function () {
var self = this,
model = this.get('model'),
authStrategy = 'authenticator:oauth2';
authenticate() {
let model = this.get('model');
let authStrategy = 'authenticator:oauth2';
// Authentication transitions to posts.index, we can leave spinner running unless there is an error
this.get('session').authenticate(authStrategy, model.get('identification'), model.get('password')).catch(function (error) {
self.toggleProperty('loggingIn');
this.get('session').authenticate(authStrategy, model.get('identification'), model.get('password')).catch((error) => {
this.toggleProperty('loggingIn');
if (error && error.errors) {
error.errors.forEach(function (err) {
error.errors.forEach((err) => {
err.message = err.message.htmlSafe();
});
self.set('flowErrors', error.errors[0].message.string);
this.set('flowErrors', error.errors[0].message.string);
if (error.errors[0].message.string.match(/user with that email/)) {
self.get('model.errors').add('identification', '');
this.get('model.errors').add('identification', '');
}
if (error.errors[0].message.string.match(/password is incorrect/)) {
self.get('model.errors').add('password', '');
this.get('model.errors').add('password', '');
}
} else {
// Connection errors don't return proper status message, only req.body
self.get('notifications').showAlert('There was a problem on the server.', {type: 'error', key: 'session.authenticate.failed'});
this.get('notifications').showAlert('There was a problem on the server.', {type: 'error', key: 'session.authenticate.failed'});
}
});
},
validateAndAuthenticate: function () {
var self = this;
validateAndAuthenticate() {
this.set('flowErrors', '');
// Manually trigger events for input fields, ensuring legacy compatibility with
// browsers and password managers that don't send proper events on autofill
@ -56,56 +56,54 @@ export default Ember.Controller.extend(ValidationEngine, {
// This is a bit dirty, but there's no other way to ensure the properties are set as well as 'signin'
this.get('hasValidated').addObjects(this.authProperties);
this.validate({property: 'signin'}).then(function () {
self.toggleProperty('loggingIn');
self.send('authenticate');
}).catch(function (error) {
this.validate({property: 'signin'}).then(() => {
this.toggleProperty('loggingIn');
this.send('authenticate');
}).catch((error) => {
if (error) {
self.get('notifications').showAPIError(error, {key: 'signin.authenticate'});
this.get('notifications').showAPIError(error, {key: 'signin.authenticate'});
} else {
self.set('flowErrors', 'Please fill out the form to sign in.');
this.set('flowErrors', 'Please fill out the form to sign in.');
}
});
},
forgotten: function () {
var email = this.get('model.identification'),
notifications = this.get('notifications'),
self = this;
forgotten() {
let email = this.get('model.identification');
let notifications = this.get('notifications');
this.set('flowErrors', '');
// This is a bit dirty, but there's no other way to ensure the properties are set as well as 'forgotPassword'
this.get('hasValidated').addObject('identification');
this.validate({property: 'forgotPassword'}).then(function () {
self.toggleProperty('submitting');
this.validate({property: 'forgotPassword'}).then(() => {
this.toggleProperty('submitting');
ajax({
url: self.get('ghostPaths.url').api('authentication', 'passwordreset'),
url: this.get('ghostPaths.url').api('authentication', 'passwordreset'),
type: 'POST',
data: {
passwordreset: [{
email: email
}]
passwordreset: [{email}]
}
}).then(function () {
self.toggleProperty('submitting');
}).then(() => {
this.toggleProperty('submitting');
notifications.showAlert('Please check your email for instructions.', {type: 'info', key: 'forgot-password.send.success'});
}).catch(function (resp) {
self.toggleProperty('submitting');
}).catch((resp) => {
this.toggleProperty('submitting');
if (resp && resp.jqXHR && resp.jqXHR.responseJSON && resp.jqXHR.responseJSON.errors) {
var message = resp.jqXHR.responseJSON.errors[0].message;
let [error] = resp.jqXHR.responseJSON.errors;
let {message} = error;
self.set('flowErrors', message);
this.set('flowErrors', message);
if (message.match(/no user with that email/)) {
self.get('model.errors').add('identification', '');
this.get('model.errors').add('identification', '');
}
} else {
notifications.showAPIError(resp, {defaultErrorText: 'There was a problem with the reset, please try again.', key: 'forgot-password.send'});
}
});
}).catch(function () {
self.set('flowErrors', 'We need your email address to reset your password!');
}).catch(() => {
this.set('flowErrors', 'We need your email address to reset your password!');
});
}
}

View File

@ -2,7 +2,9 @@ import Ember from 'ember';
import {request as ajax} from 'ic-ajax';
import ValidationEngine from 'ghost/mixins/validation-engine';
export default Ember.Controller.extend(ValidationEngine, {
const {Controller, RSVP, inject} = Ember;
export default Controller.extend(ValidationEngine, {
// ValidationEngine settings
validationType: 'signup',
@ -10,23 +12,22 @@ export default Ember.Controller.extend(ValidationEngine, {
flowErrors: '',
image: null,
ghostPaths: Ember.inject.service('ghost-paths'),
config: Ember.inject.service(),
notifications: Ember.inject.service(),
session: Ember.inject.service(),
ghostPaths: inject.service('ghost-paths'),
config: inject.service(),
notifications: inject.service(),
session: inject.service(),
sendImage: function () {
var self = this,
image = this.get('image');
sendImage() {
let image = this.get('image');
this.get('session.user').then(function (user) {
return new Ember.RSVP.Promise(function (resolve, reject) {
this.get('session.user').then((user) => {
return new RSVP.Promise((resolve, reject) => {
image.formData = {};
image.submit()
.success(function (response) {
.success((response) => {
user.image = response;
ajax({
url: self.get('ghostPaths.url').api('users', user.id.toString()),
url: this.get('ghostPaths.url').api('users', user.id.toString()),
type: 'PUT',
data: {
users: [user]
@ -39,22 +40,20 @@ export default Ember.Controller.extend(ValidationEngine, {
},
actions: {
signup: function () {
var self = this,
model = this.get('model'),
setupProperties = ['name', 'email', 'password', 'token'],
data = model.getProperties(setupProperties),
image = this.get('image'),
notifications = this.get('notifications');
signup() {
let model = this.get('model');
let setupProperties = ['name', 'email', 'password', 'token'];
let data = model.getProperties(setupProperties);
let image = this.get('image');
let notifications = this.get('notifications');
this.set('flowErrors', '');
this.get('hasValidated').addObjects(setupProperties);
this.validate().then(function () {
self.toggleProperty('submitting');
this.validate().then(() => {
this.toggleProperty('submitting');
ajax({
url: self.get('ghostPaths.url').api('authentication', 'invitation'),
url: this.get('ghostPaths.url').api('authentication', 'invitation'),
type: 'POST',
dataType: 'json',
data: {
@ -65,27 +64,28 @@ export default Ember.Controller.extend(ValidationEngine, {
token: data.token
}]
}
}).then(function () {
self.get('session').authenticate('authenticator:oauth2', self.get('model.email'), self.get('model.password')).then(function () {
}).then(() => {
this.get('session').authenticate('authenticator:oauth2', this.get('model.email'), this.get('model.password')).then(() => {
if (image) {
self.sendImage();
this.sendImage();
}
}).catch(function (resp) {
}).catch((resp) => {
notifications.showAPIError(resp, {key: 'signup.complete'});
});
}).catch(function (resp) {
self.toggleProperty('submitting');
}).catch((resp) => {
this.toggleProperty('submitting');
if (resp && resp.jqXHR && resp.jqXHR.responseJSON && resp.jqXHR.responseJSON.errors) {
self.set('flowErrors', resp.jqXHR.responseJSON.errors[0].message);
this.set('flowErrors', resp.jqXHR.responseJSON.errors[0].message);
} else {
notifications.showAPIError(resp, {key: 'signup.complete'});
}
});
}).catch(function () {
self.set('flowErrors', 'Please fill out the form to complete your sign-up');
}).catch(() => {
this.set('flowErrors', 'Please fill out the form to complete your sign-up');
});
},
setImage: function (image) {
setImage(image) {
this.set('image', image);
}
}

View File

@ -1,17 +1,20 @@
import Ember from 'ember';
export default Ember.Controller.extend({
const {Controller, computed, inject} = Ember;
const {alias, filter} = computed;
session: Ember.inject.service(),
export default Controller.extend({
users: Ember.computed.alias('model'),
session: inject.service(),
activeUsers: Ember.computed.filter('users', function (user) {
users: alias('model'),
activeUsers: filter('users', function (user) {
return /^active|warn-[1-4]|locked$/.test(user.get('status'));
}),
invitedUsers: Ember.computed.filter('users', function (user) {
var status = user.get('status');
invitedUsers: filter('users', function (user) {
let status = user.get('status');
return status === 'invited' || status === 'invited-pending';
})

View File

@ -4,32 +4,40 @@ import isNumber from 'ghost/utils/isNumber';
import boundOneWay from 'ghost/utils/bound-one-way';
import ValidationEngine from 'ghost/mixins/validation-engine';
export default Ember.Controller.extend(ValidationEngine, {
const {Controller, RSVP, computed, inject} = Ember;
const {alias, and, not, or, readOnly} = computed;
export default Controller.extend(ValidationEngine, {
// ValidationEngine settings
validationType: 'user',
submitting: false,
ghostPaths: Ember.inject.service('ghost-paths'),
notifications: Ember.inject.service(),
session: Ember.inject.service(),
ghostPaths: inject.service('ghost-paths'),
notifications: inject.service(),
session: inject.service(),
currentUser: Ember.computed.alias('session.user'),
lastPromise: null,
isNotOwnProfile: Ember.computed('user.id', 'currentUser.id', function () {
currentUser: alias('session.user'),
user: alias('model'),
email: readOnly('user.email'),
slugValue: boundOneWay('user.slug'),
isNotOwnProfile: computed('user.id', 'currentUser.id', function () {
return this.get('user.id') !== this.get('currentUser.id');
}),
isNotOwnersProfile: Ember.computed.not('user.isOwner'),
isNotOwnersProfile: not('user.isOwner'),
isAdminUserOnOwnerProfile: Ember.computed.and('currentUser.isAdmin', 'user.isOwner'),
isAdminUserOnOwnerProfile: and('currentUser.isAdmin', 'user.isOwner'),
canAssignRoles: Ember.computed.or('currentUser.isAdmin', 'currentUser.isOwner'),
canAssignRoles: or('currentUser.isAdmin', 'currentUser.isOwner'),
canMakeOwner: Ember.computed.and('currentUser.isOwner', 'isNotOwnProfile', 'user.isAdmin'),
canMakeOwner: and('currentUser.isOwner', 'isNotOwnProfile', 'user.isAdmin'),
rolesDropdownIsVisible: Ember.computed.and('isNotOwnProfile', 'canAssignRoles', 'isNotOwnersProfile'),
rolesDropdownIsVisible: and('isNotOwnProfile', 'canAssignRoles', 'isNotOwnersProfile'),
deleteUserActionIsVisible: Ember.computed('currentUser', 'canAssignRoles', 'user', function () {
deleteUserActionIsVisible: computed('currentUser', 'canAssignRoles', 'user', function () {
if ((this.get('canAssignRoles') && this.get('isNotOwnProfile') && !this.get('user.isOwner')) ||
(this.get('currentUser.isEditor') && (this.get('isNotOwnProfile') ||
this.get('user.isAuthor')))) {
@ -37,68 +45,57 @@ export default Ember.Controller.extend(ValidationEngine, {
}
}),
userActionsAreVisible: Ember.computed.or('deleteUserActionIsVisible', 'canMakeOwner'),
user: Ember.computed.alias('model'),
email: Ember.computed.readOnly('model.email'),
slugValue: boundOneWay('model.slug'),
lastPromise: null,
userActionsAreVisible: or('deleteUserActionIsVisible', 'canMakeOwner'),
// duplicated in gh-user-active -- find a better home and consolidate?
userDefault: Ember.computed('ghostPaths', function () {
userDefault: computed('ghostPaths', function () {
return this.get('ghostPaths.url').asset('/shared/img/user-image.png');
}),
userImageBackground: Ember.computed('user.image', 'userDefault', function () {
var url = this.get('user.image') || this.get('userDefault');
userImageBackground: computed('user.image', 'userDefault', function () {
let url = this.get('user.image') || this.get('userDefault');
return Ember.String.htmlSafe(`background-image: url(${url})`);
}),
// end duplicated
coverDefault: Ember.computed('ghostPaths', function () {
coverDefault: computed('ghostPaths', function () {
return this.get('ghostPaths.url').asset('/shared/img/user-cover.png');
}),
coverImageBackground: Ember.computed('user.cover', 'coverDefault', function () {
var url = this.get('user.cover') || this.get('coverDefault');
coverImageBackground: computed('user.cover', 'coverDefault', function () {
let url = this.get('user.cover') || this.get('coverDefault');
return Ember.String.htmlSafe(`background-image: url(${url})`);
}),
coverTitle: Ember.computed('user.name', function () {
return this.get('user.name') + '\'s Cover Image';
coverTitle: computed('user.name', function () {
return `${this.get('user.name')}'s Cover Image`;
}),
// Lazy load the slug generator for slugPlaceholder
slugGenerator: Ember.computed(function () {
slugGenerator: computed(function () {
return SlugGenerator.create({
ghostPaths: this.get('ghostPaths'),
slugType: 'user'
});
}),
roles: Ember.computed(function () {
roles: computed(function () {
return this.store.query('role', {permissions: 'assign'});
}),
actions: {
changeRole: function (newRole) {
changeRole(newRole) {
this.set('model.role', newRole);
},
save: function () {
var user = this.get('user'),
slugValue = this.get('slugValue'),
afterUpdateSlug = this.get('lastPromise'),
promise,
slugChanged,
self = this;
save() {
let user = this.get('user');
let slugValue = this.get('slugValue');
let afterUpdateSlug = this.get('lastPromise');
let promise,
slugChanged;
if (user.get('slug') !== slugValue) {
slugChanged = true;
@ -107,10 +104,10 @@ export default Ember.Controller.extend(ValidationEngine, {
this.toggleProperty('submitting');
promise = Ember.RSVP.resolve(afterUpdateSlug).then(function () {
promise = RSVP.resolve(afterUpdateSlug).then(() => {
return user.save({format: false});
}).then(function (model) {
var currentPath,
}).then((model) => {
let currentPath,
newPath;
// If the user's slug has changed, change the URL and replace
@ -125,27 +122,26 @@ export default Ember.Controller.extend(ValidationEngine, {
window.history.replaceState({path: newPath}, '', newPath);
}
self.toggleProperty('submitting');
self.get('notifications').closeAlerts('user.update');
this.toggleProperty('submitting');
this.get('notifications').closeAlerts('user.update');
return model;
}).catch(function (errors) {
}).catch((errors) => {
if (errors) {
self.get('notifications').showErrors(errors, {key: 'user.update'});
this.get('notifications').showErrors(errors, {key: 'user.update'});
}
self.toggleProperty('submitting');
this.toggleProperty('submitting');
});
this.set('lastPromise', promise);
},
password: function () {
var user = this.get('user'),
self = this;
password() {
let user = this.get('user');
if (user.get('isPasswordValid')) {
user.saveNewPassword().then(function (model) {
user.saveNewPassword().then((model) => {
// Clear properties from view
user.setProperties({
password: '',
@ -153,38 +149,36 @@ export default Ember.Controller.extend(ValidationEngine, {
ne2Password: ''
});
self.get('notifications').showAlert('Password updated.', {type: 'success', key: 'user.change-password.success'});
this.get('notifications').showAlert('Password updated.', {type: 'success', key: 'user.change-password.success'});
return model;
}).catch(function (errors) {
self.get('notifications').showAPIError(errors, {key: 'user.change-password'});
}).catch((errors) => {
this.get('notifications').showAPIError(errors, {key: 'user.change-password'});
});
} else {
// TODO: switch to in-line validation
self.get('notifications').showErrors(user.get('passwordValidationErrors'), {key: 'user.change-password'});
this.get('notifications').showErrors(user.get('passwordValidationErrors'), {key: 'user.change-password'});
}
},
updateSlug: function (newSlug) {
var self = this,
afterSave = this.get('lastPromise'),
promise;
updateSlug(newSlug) {
let afterSave = this.get('lastPromise');
let promise;
promise = Ember.RSVP.resolve(afterSave).then(function () {
var slug = self.get('model.slug');
promise = RSVP.resolve(afterSave).then(() => {
let slug = this.get('model.slug');
newSlug = newSlug || slug;
newSlug = newSlug.trim();
// Ignore unchanged slugs or candidate slugs that are empty
if (!newSlug || slug === newSlug) {
self.set('slugValue', slug);
this.set('slugValue', slug);
return;
}
return self.get('slugGenerator').generateSlug(newSlug).then(function (serverSlug) {
return this.get('slugGenerator').generateSlug(newSlug).then((serverSlug) => {
// If after getting the sanitized and unique slug back from the API
// we end up with a slug that matches the existing slug, abort the change
if (serverSlug === slug) {
@ -198,20 +192,20 @@ export default Ember.Controller.extend(ValidationEngine, {
// the trailing incrementor (e.g., this-is-a-slug and this-is-a-slug-2)
// get the last token out of the slug candidate and see if it's a number
var slugTokens = serverSlug.split('-'),
check = Number(slugTokens.pop());
let slugTokens = serverSlug.split('-');
let check = Number(slugTokens.pop());
// if the candidate slug is the same as the existing slug except
// for the incrementor then the existing slug should be used
if (isNumber(check) && check > 0) {
if (slug === slugTokens.join('-') && serverSlug !== newSlug) {
self.set('slugValue', slug);
this.set('slugValue', slug);
return;
}
}
self.set('slugValue', serverSlug);
this.set('slugValue', serverSlug);
});
});

View File

@ -1,16 +1,15 @@
import Ember from 'ember';
export default Ember.Helper.helper(function (params) {
var el = document.createElement('span'),
length,
content;
const {Helper} = Ember;
export default Helper.helper(function (params) {
if (!params || !params.length) {
return;
}
content = params[0] || '';
length = content.length;
let el = document.createElement('span');
let content = params[0] || '';
let {length} = content;
el.className = 'word-count';

View File

@ -1,17 +1,17 @@
import Ember from 'ember';
export default Ember.Helper.helper(function (params) {
var el = document.createElement('span'),
content,
maxCharacters,
length;
const {Helper} = Ember;
export default Helper.helper(function (params) {
if (!params || params.length < 2) {
return;
}
content = params[0] || '';
maxCharacters = params[1];
let el = document.createElement('span');
let [content, maxCharacters] = params;
let length;
content = content || '';
length = content.length;
el.className = 'word-count';

View File

@ -1,21 +1,20 @@
import Ember from 'ember';
import counter from 'ghost/utils/word-count';
export default Ember.Helper.helper(function (params) {
const {Helper} = Ember;
export default Helper.helper(function (params) {
if (!params || !params.length) {
return;
}
var markdown,
count;
markdown = params[0] || '';
let markdown = params[0] || '';
if (/^\s*$/.test(markdown)) {
return '0 words';
}
count = counter(markdown);
let count = counter(markdown);
return count + (count === 1 ? ' word' : ' words');
});

View File

@ -1,13 +1,15 @@
import Ember from 'ember';
/* global html_sanitize*/
import Ember from 'ember';
import cajaSanitizers from 'ghost/utils/caja-sanitizers';
export default Ember.Helper.helper(function (params) {
const {Helper} = Ember;
export default Helper.helper(function (params) {
if (!params || !params.length) {
return;
}
var escapedhtml = params[0] || '';
let escapedhtml = params[0] || '';
// replace script and iFrame
escapedhtml = escapedhtml.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,

View File

@ -1,16 +1,18 @@
import Ember from 'ember';
/* global Showdown, html_sanitize*/
import Ember from 'ember';
import cajaSanitizers from 'ghost/utils/caja-sanitizers';
var showdown = new Showdown.converter({extensions: ['ghostimagepreview', 'ghostgfm', 'footnotes', 'highlight']});
const {Helper} = Ember;
export default Ember.Helper.helper(function (params) {
let showdown = new Showdown.converter({extensions: ['ghostimagepreview', 'ghostgfm', 'footnotes', 'highlight']});
export default Helper.helper(function (params) {
if (!params || !params.length) {
return;
}
var escapedhtml = '',
markdown = params[0] || '';
let markdown = params[0] || '';
let escapedhtml = '';
// convert markdown to HTML
escapedhtml = showdown.makeHtml(markdown);

View File

@ -1,11 +1,13 @@
import Ember from 'ember';
export default Ember.Helper.helper(function (params) {
const {Helper} = Ember;
export default Helper.helper(function (params) {
if (!params || !params.length) {
return;
}
var timeago = params[0];
let [timeago] = params;
return moment(timeago).fromNow();
// stefanpenner says cool for small number of timeagos.

Some files were not shown because too many files have changed in this diff Show More