Merge pull request #4335 from jaswilli/jscs

Enable JSCS checking on client.
This commit is contained in:
Hannah Wolfe 2014-10-25 19:44:16 +03:00
commit 20fbcc1e59
114 changed files with 618 additions and 436 deletions

View File

@ -189,7 +189,8 @@ var _ = require('lodash'),
},
client: {
options: {
config: '.jscsrc'
config: '.jscsrc',
esnext: true
}
},
test: {
@ -199,12 +200,6 @@ var _ = require('lodash'),
}
}, lintFiles);
// JSCS depends on Esprima which doesn't yet support ES6 module
// syntax. As such we cannot run JSCS on the client code yet.
// Related JSCS issue: https://github.com/jscs-dev/node-jscs/issues/561
// @TODO(hswolff): remove this once JSCS supports ES6.
delete jscsConfig.client;
return jscsConfig;
})(),

View File

@ -12,7 +12,7 @@ var ApplicationAdapter = DS.RESTAdapter.extend({
delete query.id;
}
return this.ajax(this.buildURL(type.typeKey, id), 'GET', { data: query });
return this.ajax(this.buildURL(type.typeKey, id), 'GET', {data: query});
},
buildURL: function (type, id) {

View File

@ -36,7 +36,6 @@ var EmbeddedRelationAdapter = ApplicationAdapter.extend({
if (meta.kind === 'hasMany' &&
Object.prototype.hasOwnProperty.call(meta.options, 'embedded') &&
meta.options.embedded === 'always') {
toInclude.push(name);
}
});
@ -46,12 +45,11 @@ var EmbeddedRelationAdapter = ApplicationAdapter.extend({
if (typeof options === 'string' || typeof options === 'number') {
query.id = options;
query.include = toInclude.join(',');
}
// 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
// into the existing object. Existing properties and includes are preserved.
else if (typeof options === 'object' || Ember.isNone(options)) {
} else if (typeof options === 'object' || Ember.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
// into the existing object. Existing properties and includes are preserved.
query = options || query;
toInclude = toInclude.concat(query.include ? query.include.split(',') : []);

View File

@ -13,7 +13,7 @@ var PostAdapter = EmbeddedRelationAdapter.extend({
// an array with a post object like the API expects
serializer.serializeIntoHash(data, type, record);
return this.ajax(url, 'POST', { data: data });
return this.ajax(url, 'POST', {data: data});
},
updateRecord: function (store, type, record) {
@ -30,7 +30,7 @@ var PostAdapter = EmbeddedRelationAdapter.extend({
serializer.serializeIntoHash(data, type, record);
// use the ApplicationAdapter's buildURL method
return this.ajax(url, 'PUT', { data: data });
return this.ajax(url, 'PUT', {data: data});
}
});

View File

@ -14,7 +14,7 @@ var SettingAdapter = ApplicationAdapter.extend({
// use the ApplicationAdapter's buildURL method but do not
// pass in an id.
return this.ajax(this.buildURL(type.typeKey), 'PUT', { data: data });
return this.ajax(this.buildURL(type.typeKey), 'PUT', {data: data});
}
});

View File

@ -14,7 +14,7 @@ var UserAdapter = EmbeddedRelationAdapter.extend({
serializer.serializeIntoHash(data, type, record);
// Use the url from the ApplicationAdapter's buildURL method
return this.ajax(url, 'POST', { data: data });
return this.ajax(url, 'POST', {data: data});
},
updateRecord: function (store, type, record) {
@ -31,7 +31,7 @@ var UserAdapter = EmbeddedRelationAdapter.extend({
serializer.serializeIntoHash(data, type, record);
// Use the url from the ApplicationAdapter's buildURL method
return this.ajax(url, 'PUT', { data: data });
return this.ajax(url, 'PUT', {data: data});
},
find: function (store, type, id) {

View File

@ -5,11 +5,12 @@ var createTouchEditor = function createTouchEditor() {
TouchEditor = function (el, options) {
/*jshint unused:false*/
this.textarea = el;
this.win = { document : this.textarea };
this.win = {document: this.textarea};
this.ready = true;
this.wrapping = document.createElement('div');
var textareaParent = this.textarea.parentNode;
this.wrapping.appendChild(this.textarea);
textareaParent.appendChild(this.wrapping);
@ -33,14 +34,14 @@ var createTouchEditor = function createTouchEditor() {
},
focus: noop,
getCursor: function () {
return { line: 0, ch: 0 };
return {line: 0, ch: 0};
},
setCursor: noop,
currentLine: function () {
return 0;
},
cursorPosition: function () {
return { character: 0 };
return {character: 0};
},
addMarkdown: noop,
nthLine: noop,

View File

@ -4,18 +4,17 @@ var UploadUi,
upload,
Ghost = ghostPaths();
UploadUi = function ($dropzone, settings) {
var $url = '<div class="js-url"><input class="url js-upload-url" type="url" placeholder="http://"/></div>',
$cancel = '<a class="image-cancel js-cancel" title="Delete"><span class="hidden">Delete</span></a>',
$progress = $('<div />', {
'class' : 'js-upload-progress progress progress-success active',
'role': 'progressbar',
class: 'js-upload-progress progress progress-success active',
role: 'progressbar',
'aria-valuemin': '0',
'aria-valuemax': '100'
}).append($('<div />', {
'class': 'js-upload-progress-bar bar',
'style': 'width:0%'
class: 'js-upload-progress-bar bar',
style: 'width:0%'
}));
$.extend(this, {
@ -23,9 +22,9 @@ UploadUi = function ($dropzone, settings) {
var self = this;
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: width, height: height}).css({display: 'block'});
$dropzone.find('.fileupload-loading').remove();
$dropzone.css({'height': 'auto'});
$dropzone.css({height: 'auto'});
$dropzone.delay(250).animate({opacity: 100}, 1000, function () {
$('.js-button-accept').prop('disabled', false);
self.init();
@ -45,11 +44,11 @@ UploadUi = function ($dropzone, settings) {
function preLoadImage() {
var $img = $dropzone.find('img.js-upload-target')
.attr({'src': '', 'width': 'auto', 'height': 'auto'});
.attr({src: '', width: 'auto', height: 'auto'});
$progress.animate({'opacity': 0}, 250, function () {
$progress.animate({opacity: 0}, 250, function () {
$dropzone.find('span.media').after('<img class="fileupload-loading" src="' + Ghost.subdir + '/ghost/img/loadingcat.gif" />');
if (!settings.editor) {$progress.find('.fileupload-loading').css({'top': '56px'}); }
if (!settings.editor) {$progress.find('.fileupload-loading').css({top: '56px'}); }
});
$dropzone.trigger('uploadsuccess', [result]);
$img.one('load', function () {
@ -73,7 +72,7 @@ UploadUi = function ($dropzone, settings) {
$dropzone.trigger('uploadstart', [$dropzone.attr('id')]);
$dropzone.find('span.media, div.description, a.image-url, a.image-webcam')
.animate({opacity: 0}, 250, function () {
$dropzone.find('div.description').hide().css({'opacity': 100});
$dropzone.find('div.description').hide().css({opacity: 100});
if (settings.progressbar) {
$dropzone.find('div.js-fail').after($progress);
$progress.animate({opacity: 100}, 250);
@ -85,7 +84,7 @@ UploadUi = function ($dropzone, settings) {
progressall: function (e, data) {
/*jshint unused:false*/
var progress = parseInt(data.loaded / data.total * 100, 10);
if (!settings.editor) {$progress.find('div.js-progress').css({'position': 'absolute', 'top': '40px'}); }
if (!settings.editor) {$progress.find('div.js-progress').css({position: 'absolute', top: '40px'}); }
if (settings.progressbar) {
$dropzone.trigger('uploadprogress', [progress, data]);
$progress.find('.js-upload-progress-bar').css('width', progress + '%');
@ -134,9 +133,9 @@ UploadUi = function ($dropzone, settings) {
if (!$dropzone.find('a.image-url')[0]) {
$dropzone.append('<a class="image-url" title="Add image from URL"><span class="hidden">URL</span></a>');
}
// if (!$dropzone.find('a.image-webcam')[0]) {
// $dropzone.append('<a class="image-webcam" title="Add image from webcam"><span class="hidden">Webcam</span></a>');
// }
// if (!$dropzone.find('a.image-webcam')[0]) {
// $dropzone.append('<a class="image-webcam" title="Add image from webcam"><span class="hidden">Webcam</span></a>');
// }
},
removeExtras: function () {
@ -145,8 +144,9 @@ UploadUi = function ($dropzone, settings) {
initWithDropzone: function () {
var self = this;
//This is the start point if no image exists
$dropzone.find('img.js-upload-target').css({'display': 'none'});
// This is the start point if no image exists
$dropzone.find('img.js-upload-target').css({display: 'none'});
$dropzone.removeClass('pre-image-uploader image-uploader-url').addClass('image-uploader');
this.removeExtras();
this.buildExtras();
@ -203,16 +203,17 @@ UploadUi = function ($dropzone, settings) {
$dropzone.find('.js-fileupload').removeClass('right');
self.initWithDropzone();
});
},
initWithImage: function () {
var self = this;
// This is the start point if an image already exists
$dropzone.removeClass('image-uploader image-uploader-url').addClass('pre-image-uploader');
$dropzone.find('div.description').hide();
$dropzone.append($cancel);
$dropzone.find('.js-cancel').on('click', function () {
$dropzone.find('img.js-upload-target').attr({'src': ''});
$dropzone.find('img.js-upload-target').attr({src: ''});
$dropzone.find('div.description').show();
$dropzone.delay(2500).animate({opacity: 100}, 1000, function () {
self.init();
@ -240,13 +241,13 @@ UploadUi = function ($dropzone, settings) {
});
};
upload = function (options) {
var settings = $.extend({
progressbar: true,
editor: false,
fileStorage: true
}, options);
return this.each(function () {
var $dropzone = $(this),
ui;

View File

@ -5,9 +5,13 @@ import mobileCodeMirror from 'ghost/utils/codemirror-mobile';
import setScrollClassName from 'ghost/utils/set-scroll-classname';
import codeMirrorShortcuts from 'ghost/utils/codemirror-shortcuts';
var onChangeHandler,
onScrollHandler,
Codemirror;
codeMirrorShortcuts.init();
var onChangeHandler = function (cm, changeObj) {
onChangeHandler = function (cm, changeObj) {
var line,
component = cm.component;
@ -24,7 +28,7 @@ var onChangeHandler = function (cm, changeObj) {
component.sendAction('typingPause');
};
var onScrollHandler = function (cm) {
onScrollHandler = function (cm) {
var scrollInfo = cm.getScrollInfo(),
component = cm.component;
@ -36,7 +40,7 @@ var onScrollHandler = function (cm) {
}, 10);
};
var Codemirror = Ember.TextArea.extend(MarkerManager, {
Codemirror = Ember.TextArea.extend(MarkerManager, {
focus: true,
setFocus: function () {
@ -51,8 +55,9 @@ var Codemirror = Ember.TextArea.extend(MarkerManager, {
afterRenderEvent: function () {
var self = this;
function initMarkers () {
self.initMarkers.apply(self, arguments);
function initMarkers() {
self.initMarkers.apply(self, arguments);
}
// replaces CodeMirror with TouchEditor only if we're on mobile

View File

@ -2,13 +2,15 @@ import DropdownMixin from 'ghost/mixins/dropdown-mixin';
var DropdownButton = Ember.Component.extend(DropdownMixin, {
tagName: 'button',
/*matches with the dropdown this button toggles*/
// matches with the dropdown this button toggles
dropdownName: null,
/*Notify dropdown service this dropdown should be toggled*/
// Notify dropdown service this dropdown should be toggled
click: function (event) {
this._super(event);
this.get('dropdown').toggleDropdown(this.get('dropdownName'), this);
}
});
export default DropdownButton;
export default DropdownButton;

View File

@ -4,11 +4,14 @@ var GhostDropdown = Ember.Component.extend(DropdownMixin, {
classNames: 'ghost-dropdown',
name: null,
closeOnClick: false,
//Helps track the user re-opening the menu while it's fading out.
// Helps track the user re-opening the menu while it's fading out.
closing: false,
//Helps track whether the dropdown is open or closes, or in a transition to either
// Helps track whether the dropdown is open or closes, or in a transition to either
isOpen: false,
//Managed the toggle between the fade-in and fade-out classes
// Managed the toggle between the fade-in and fade-out classes
fadeIn: Ember.computed('isOpen', 'closing', function () {
return this.get('isOpen') && !this.get('closing');
}),
@ -20,9 +23,12 @@ var GhostDropdown = Ember.Component.extend(DropdownMixin, {
this.set('closing', false);
this.set('button.isOpen', true);
},
close: function () {
var self = this;
this.set('closing', true);
if (this.get('button')) {
this.set('button.isOpen', false);
}
@ -35,7 +41,8 @@ var GhostDropdown = Ember.Component.extend(DropdownMixin, {
}
});
},
//Called by the dropdown service when any dropdown button is clicked.
// Called by the dropdown service when any dropdown button is clicked.
toggle: function (options) {
var isClosing = this.get('closing'),
isOpen = this.get('isOpen'),
@ -56,6 +63,7 @@ var GhostDropdown = Ember.Component.extend(DropdownMixin, {
click: function (event) {
this._super(event);
if (this.get('closeOnClick')) {
return this.close();
}
@ -63,13 +71,16 @@ var GhostDropdown = Ember.Component.extend(DropdownMixin, {
didInsertElement: function () {
this._super();
var dropdownService = this.get('dropdown');
dropdownService.on('close', this, this.close);
dropdownService.on('toggle', this, this.toggle);
},
willDestroyElement: function () {
this._super();
var dropdownService = this.get('dropdown');
dropdownService.off('close', this, this.close);
@ -77,4 +88,4 @@ var GhostDropdown = Ember.Component.extend(DropdownMixin, {
}
});
export default GhostDropdown;
export default GhostDropdown;

View File

@ -10,7 +10,6 @@ var ModalDialog = Ember.Component.extend({
},
willDestroyElement: function () {
this.$('.js-modal').removeClass('in');
this.$('.js-modal-background').removeClass('in');

View File

@ -11,8 +11,7 @@ var NotificationComponent = Ember.Component.extend({
if (typeof message.toJSON === 'function') {
type = message.get('type');
dismissible = message.get('dismissible');
}
else {
} else {
type = message.type;
dismissible = message.dismissible;
}

View File

@ -2,14 +2,16 @@ import DropdownButton from 'ghost/components/gh-dropdown-button';
var PopoverButton = DropdownButton.extend({
click: Ember.K, // We don't want clicks on popovers, but dropdowns have them. So `K`ill them here.
mouseEnter: function (event) {
this._super(event);
this.get('dropdown').toggleDropdown(this.get('popoverName'), this);
},
mouseLeave: function (event) {
this._super(event);
this.get('dropdown').toggleDropdown(this.get('popoverName'), this);
}
});
export default PopoverButton;
export default PopoverButton;

View File

@ -4,4 +4,4 @@ var GhostPopover = GhostDropdown.extend({
classNames: 'ghost-popover'
});
export default GhostPopover;
export default GhostPopover;

View File

@ -2,8 +2,9 @@ import GhostSelect from 'ghost/components/gh-select';
var RolesSelector = GhostSelect.extend({
roles: Ember.computed.alias('options'),
options: Ember.computed(function () {
var rolesPromise = this.store.find('role', { permissions: 'assign' });
var rolesPromise = this.store.find('role', {permissions: 'assign'});
return Ember.ArrayProxy.extend(Ember.PromiseProxyMixin)
.create({promise: rolesPromise});

View File

@ -1,17 +1,17 @@
//GhostSelect is a solution to Ember.Select being evil and worthless.
// GhostSelect is a solution to Ember.Select being evil and worthless.
// (Namely, this solves problems with async data in Ember.Select)
//Inspired by (that is, totally ripped off from) this JSBin
//http://emberjs.jsbin.com/rwjblue/40/edit
// Inspired by (that is, totally ripped off from) this JSBin
// http://emberjs.jsbin.com/rwjblue/40/edit
//Usage:
//Extend this component and create a template for your component.
//Your component must define the `options` property.
//Optionally use `initialValue` to set the object
// Usage:
// Extend this component and create a template for your component.
// Your component must define the `options` property.
// Optionally use `initialValue` to set the object
// you want to have selected to start with.
//Both options and initalValue are promise safe.
//Set onChange in your template to be the name
// Both options and initalValue are promise safe.
// Set onChange in your template to be the name
// of the action you want called in your
//For an example, see gh-roles-selector
// For an example, see gh-roles-selector
var GhostSelect = Ember.Component.extend({
tagName: 'span',
@ -26,9 +26,10 @@ var GhostSelect = Ember.Component.extend({
resolvedOptions: null,
resolvedInitialValue: null,
//Convert promises to their values
// Convert promises to their values
init: function () {
var self = this;
this._super.apply(this, arguments);
Ember.RSVP.hash({
@ -37,7 +38,7 @@ var GhostSelect = Ember.Component.extend({
}).then(function (resolvedHash) {
self.setProperties(resolvedHash);
//Run after render to ensure the <option>s have rendered
// Run after render to ensure the <option>s have rendered
Ember.run.schedule('afterRender', function () {
self.setInitialValue();
});
@ -48,20 +49,25 @@ var GhostSelect = Ember.Component.extend({
var initialValue = this.get('resolvedInitialValue'),
options = this.get('resolvedOptions'),
initialValueIndex = options.indexOf(initialValue);
if (initialValueIndex > -1) {
this.$('option:eq(' + initialValueIndex + ')').prop('selected', true);
}
},
//Called by DOM events, weee!
// Called by DOM events
change: function () {
this._changeSelection();
},
//Send value to specified action
// Send value to specified action
_changeSelection: function () {
var value = this._selectedValue();
Ember.set(this, 'value', value);
this.sendAction('onChange', value);
},
_selectedValue: function () {
var selectedIndex = this.$('select')[0].selectedIndex;

View File

@ -1,4 +1,4 @@
//See gh-tabs-manager.js for use
// See gh-tabs-manager.js for use
var TabPane = Ember.Component.extend({
classNameBindings: ['active'],
@ -6,8 +6,7 @@ var TabPane = Ember.Component.extend({
return this.nearestWithProperty('isTabsManager');
}),
tab: Ember.computed('tabsManager.tabs.[]', 'tabsManager.tabPanes.[]',
function () {
tab: Ember.computed('tabsManager.tabs.[]', 'tabsManager.tabPanes.[]', function () {
var index = this.get('tabsManager.tabPanes').indexOf(this),
tabs = this.get('tabsManager.tabs');
@ -20,6 +19,7 @@ var TabPane = Ember.Component.extend({
registerWithTabs: function () {
this.get('tabsManager').registerTabPane(this);
}.on('didInsertElement'),
unregisterWithTabs: function () {
this.get('tabsManager').unregisterTabPane(this);
}.on('willDestroyElement')

View File

@ -1,4 +1,4 @@
//See gh-tabs-manager.js for use
// See gh-tabs-manager.js for use
var Tab = Ember.Component.extend({
tabsManager: Ember.computed(function () {
return this.nearestWithProperty('isTabsManager');

View File

@ -32,7 +32,6 @@ the second pane within that manager.
{{/gh-tab-pane}}
{{/gh-tabs-manager}}
```
## Options:
the tabs-manager will send a "selected" action whenever one of its
@ -59,19 +58,23 @@ var TabsManager = Ember.Component.extend({
this.sendAction('selected');
},
//Used by children to find this tabsManager
// Used by children to find this tabsManager
isTabsManager: true,
// Register tabs and their panes to allow for
// interaction between components.
registerTab: function (tab) {
this.get('tabs').addObject(tab);
},
unregisterTab: function (tab) {
this.get('tabs').removeObject(tab);
},
registerTabPane: function (tabPane) {
this.get('tabPanes').addObject(tabPane);
},
unregisterTabPane: function (tabPane) {
this.get('tabPanes').removeObject(tabPane);
}

View File

@ -25,9 +25,10 @@ var PostImageUploader = Ember.Component.extend({
removeListeners: function () {
var $this = this.$();
$this.off();
$this.find('.js-cancel').off();
}.on('willDestroyElement')
});
export default PostImageUploader;
export default PostImageUploader;

View File

@ -1,5 +1,7 @@
var ApplicationController = Ember.Controller.extend({
// jscs: disable
hideNav: Ember.computed.match('currentPath', /(error|signin|signup|setup|forgotten|reset)/),
// jscs: enable
topNotificationCount: 0,
showGlobalMobileNav: false,

View File

@ -25,6 +25,7 @@ var DebugController = Ember.Controller.extend(Ember.Evented, {
if (response && response.jqXHR && response.jqXHR.responseJSON && response.jqXHR.responseJSON.errors) {
self.set('importErrors', response.jqXHR.responseJSON.errors);
}
self.notifications.showError('Import Failed');
}).finally(function () {
self.set('uploadButtonText', 'Import');
@ -38,7 +39,7 @@ var DebugController = Ember.Controller.extend(Ember.Evented, {
'?access_token=' + this.get('session.access_token');
if (iframe.length === 0) {
iframe = $('<iframe>', { id: 'iframeDownload' }).hide().appendTo('body');
iframe = $('<iframe>', {id: 'iframeDownload'}).hide().appendTo('body');
}
iframe.attr('src', downloadURL);

View File

@ -1,44 +1,44 @@
/* jshint unused: false */
import ajax from 'ghost/utils/ajax';
import ValidationEngine from 'ghost/mixins/validation-engine';
var ForgottenController = Ember.Controller.extend(ValidationEngine, {
email: '',
submitting: false,
// ValidationEngine settings
validationType: 'forgotten',
actions: {
submit: function () {
var self = this,
data = self.getProperties('email');
this.toggleProperty('submitting');
this.validate({ format: false }).then(function () {
ajax({
url: self.get('ghostPaths.url').api('authentication', 'passwordreset'),
type: 'POST',
data: {
passwordreset: [{
email: data.email
}]
}
}).then(function (resp) {
self.toggleProperty('submitting');
self.notifications.showSuccess('Please check your email for instructions.', {delayed: true});
self.set('email', '');
self.transitionToRoute('signin');
}).catch(function (resp) {
self.toggleProperty('submitting');
self.notifications.showAPIError(resp, { defaultErrorText: 'There was a problem logging in, please try again.' });
});
}).catch(function (errors) {
self.toggleProperty('submitting');
self.notifications.showErrors(errors);
});
}
}
});
export default ForgottenController;
/* jshint unused: false */
import ajax from 'ghost/utils/ajax';
import ValidationEngine from 'ghost/mixins/validation-engine';
var ForgottenController = Ember.Controller.extend(ValidationEngine, {
email: '',
submitting: false,
// ValidationEngine settings
validationType: 'forgotten',
actions: {
submit: function () {
var self = this,
data = self.getProperties('email');
this.toggleProperty('submitting');
this.validate({format: false}).then(function () {
ajax({
url: self.get('ghostPaths.url').api('authentication', 'passwordreset'),
type: 'POST',
data: {
passwordreset: [{
email: data.email
}]
}
}).then(function (resp) {
self.toggleProperty('submitting');
self.notifications.showSuccess('Please check your email for instructions.', {delayed: true});
self.set('email', '');
self.transitionToRoute('signin');
}).catch(function (resp) {
self.toggleProperty('submitting');
self.notifications.showAPIError(resp, {defaultErrorText: 'There was a problem logging in, please try again.'});
});
}).catch(function (errors) {
self.toggleProperty('submitting');
self.notifications.showErrors(errors);
});
}
}
});
export default ForgottenController;

View File

@ -15,7 +15,6 @@ var AuthFailedUnsavedController = Ember.Controller.extend({
},
confirmReject: function () {
}
},

View File

@ -10,17 +10,17 @@ var DeletePostController = Ember.Controller.extend({
model.destroyRecord().then(function () {
self.get('dropdown').closeDropdowns();
self.transitionToRoute('posts.index');
self.notifications.showSuccess('Your post has been deleted.', { delayed: true });
self.notifications.showSuccess('Your post has been deleted.', {delayed: true});
}, function () {
self.notifications.showError('Your post could not be deleted. Please try again.');
});
},
confirmReject: function () {
return false;
}
},
confirm: {
accept: {
text: 'Delete',

View File

@ -7,17 +7,17 @@ var DeleteUserController = Ember.Controller.extend({
user.destroyRecord().then(function () {
self.store.unloadAll('post');
self.transitionToRoute('settings.users');
self.notifications.showSuccess('The user has been deleted.', { delayed: true });
self.notifications.showSuccess('The user has been deleted.', {delayed: true});
}, function () {
self.notifications.showError('The user could not be deleted. Please try again.');
});
},
confirmReject: function () {
return false;
}
},
confirm: {
accept: {
text: 'Delete User',

View File

@ -1,16 +1,19 @@
var InviteNewUserController = Ember.Controller.extend({
//Used to set the initial value for the dropdown
// Used to set the initial value for the dropdown
authorRole: Ember.computed(function () {
var self = this;
return this.store.find('role').then(function (roles) {
var authorRole = roles.findBy('name', 'Author');
//Initialize role as well.
// Initialize role as well.
self.set('role', authorRole);
self.set('authorRole', authorRole);
return authorRole;
});
}),
confirm: {
accept: {
text: 'send invitation now'
@ -19,7 +22,7 @@ var InviteNewUserController = Ember.Controller.extend({
buttonClass: 'hidden'
}
},
actions: {
setRole: function (role) {
this.set('role', role);
@ -38,13 +41,13 @@ var InviteNewUserController = Ember.Controller.extend({
this.store.find('user').then(function (result) {
var invitedUser = result.findBy('email', email);
if (invitedUser) {
if (invitedUser.get('status') === 'invited' || invitedUser.get('status') === 'invited-pending') {
self.notifications.showWarn('A user with that email address was already invited.');
} else {
self.notifications.showWarn('A user with that email address already exists.');
}
} else {
newUser = self.store.createRecord('user', {
email: email,

View File

@ -16,6 +16,7 @@ var LeaveEditorController = Ember.Controller.extend({
if (!transition || !editorController) {
this.notifications.showError('Sorry, there was an error in the application. Please let the Ghost team know what happened.');
return true;
}
@ -40,7 +41,6 @@ var LeaveEditorController = Ember.Controller.extend({
},
confirmReject: function () {
}
},

View File

@ -11,7 +11,7 @@ var TransferOwnerController = Ember.Controller.extend({
type: 'PUT',
data: {
owner: [{
'id': user.get('id')
id: user.get('id')
}]
}
}).then(function (response) {
@ -49,4 +49,4 @@ var TransferOwnerController = Ember.Controller.extend({
}
});
export default TransferOwnerController;
export default TransferOwnerController;

View File

@ -5,7 +5,7 @@ import boundOneWay from 'ghost/utils/bound-one-way';
import isNumber from 'ghost/utils/isNumber';
var PostSettingsMenuController = Ember.ObjectController.extend({
//State for if the user is viewing a tab's pane.
// State for if the user is viewing a tab's pane.
needs: 'application',
lastPromise: null,
@ -18,8 +18,10 @@ var PostSettingsMenuController = Ember.ObjectController.extend({
if (arguments.length > 1) {
return value;
}
return false;
}),
selectedAuthor: null,
initializeSelectedAuthor: function () {
var self = this;
@ -35,13 +37,15 @@ var PostSettingsMenuController = Ember.ObjectController.extend({
selectedAuthor = this.get('selectedAuthor'),
model = this.get('model'),
self = this;
//return if nothing changed
// return if nothing changed
if (selectedAuthor.get('id') === author.get('id')) {
return;
}
model.set('author', selectedAuthor);
//if this is a new post (never been saved before), don't try to save it
// if this is a new post (never been saved before), don't try to save it
if (this.get('isNew')) {
return;
}
@ -52,8 +56,9 @@ var PostSettingsMenuController = Ember.ObjectController.extend({
model.rollback();
});
}.observes('selectedAuthor'),
authors: Ember.computed(function () {
//Loaded asynchronously, so must use promise proxies.
// Loaded asynchronously, so must use promise proxies.
var deferred = {};
deferred.promise = this.store.find('user', {limit: 'all'}).then(function (users) {
@ -71,22 +76,25 @@ var PostSettingsMenuController = Ember.ObjectController.extend({
publishedAtValue: Ember.computed('published_at', function () {
var pubDate = this.get('published_at');
if (pubDate) {
return formatDate(pubDate);
}
return formatDate(moment());
}),
slugValue: boundOneWay('slug'),
//Lazy load the slug generator
// Lazy load the slug generator
slugGenerator: Ember.computed(function () {
return SlugGenerator.create({
ghostPaths: this.get('ghostPaths'),
slugType: 'post'
});
}),
//Requests slug from title
// Requests slug from title
generateAndSetSlug: function (destination) {
var self = this,
title = this.get('titleScratch'),
@ -143,9 +151,11 @@ var PostSettingsMenuController = Ember.ObjectController.extend({
}
// Strip HTML
placeholder = $('<div />', { html: html }).text();
placeholder = $('<div />', {html: html}).text();
// Replace new lines and trim
// jscs: disable
placeholder = placeholder.replace(/\n+/g, ' ').trim();
// jscs: enable
}
if (placeholder.length > 156) {
@ -189,7 +199,7 @@ var PostSettingsMenuController = Ember.ObjectController.extend({
title = this.get('title'),
slug = this.get('slug');
// generate a slug if a post is new and doesn't have a title yet or
// 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('isNew') && !title) || title === '(Untitled)' && /^untitled(-\d+){0,1}$/.test(slug)) {
debounceId = Ember.run.debounce(this, 'generateAndSetSlug', ['slug'], 700);
@ -202,9 +212,11 @@ var PostSettingsMenuController = Ember.ObjectController.extend({
errors = Ember.isArray(errors) ? errors : [errors];
this.notifications.showErrors(errors);
},
showSuccess: function (message) {
this.notifications.showSuccess(message);
},
actions: {
togglePage: function () {
var self = this;
@ -226,6 +238,7 @@ var PostSettingsMenuController = Ember.ObjectController.extend({
var self = this;
this.toggleProperty('featured');
// If this is a new post. Don't save the model. Defer the save
// to the user pressing the save button
if (this.get('isNew')) {
@ -237,6 +250,7 @@ var PostSettingsMenuController = Ember.ObjectController.extend({
self.get('model').rollback();
});
},
/**
* triggered by user manually changing slug
*/
@ -314,10 +328,11 @@ var PostSettingsMenuController = Ember.ObjectController.extend({
self = this;
if (!userInput) {
//Clear out the published_at field for a draft
// Clear out the published_at field for a draft
if (this.get('isDraft')) {
this.set('published_at', null);
}
return;
}
@ -330,9 +345,10 @@ var PostSettingsMenuController = Ember.ObjectController.extend({
errMessage = 'Published Date cannot currently be in the future.';
}
//If errors, notify and exit.
// If errors, notify and exit.
if (errMessage) {
this.showErrors(errMessage);
return;
}
@ -341,7 +357,7 @@ var PostSettingsMenuController = Ember.ObjectController.extend({
return;
}
//Validation complete
// Validation complete
this.set('published_at', newPublishedAt);
// If this is a new post. Don't save the model. Defer the save

View File

@ -1,5 +1,4 @@
var PostTagsInputController = Ember.Controller.extend({
tagEnteredOrder: Ember.A(),
tags: Ember.computed('parentController.tags', function () {
@ -132,7 +131,10 @@ var PostTagsInputController = Ember.Controller.extend({
addSelectedSuggestion: function () {
var suggestion = this.get('selectedSuggestion');
if (Ember.isEmpty(suggestion)) { return; }
if (Ember.isEmpty(suggestion)) {
return;
}
this.send('addTag', suggestion.get('tag'));
},
@ -143,9 +145,9 @@ var PostTagsInputController = Ember.Controller.extend({
}
},
selectedSuggestion: Ember.computed('suggestions.@each.selected', function () {
var suggestions = this.get('suggestions');
if (suggestions && suggestions.get('length')) {
return suggestions.filterBy('selected').get('firstObject');
} else {
@ -153,7 +155,6 @@ var PostTagsInputController = Ember.Controller.extend({
}
}),
updateSuggestionsList: function () {
var searchTerm = this.get('newTagText'),
matchingTags,
@ -178,7 +179,6 @@ var PostTagsInputController = Ember.Controller.extend({
this.set('suggestions', suggestions);
}.observes('newTagText'),
findMatchingTags: function (searchTerm) {
var matchingTags,
self = this,
@ -209,7 +209,9 @@ var PostTagsInputController = Ember.Controller.extend({
makeSuggestionObject: function (matchingTag, _searchTerm) {
var searchTerm = Ember.Handlebars.Utils.escapeExpression(_searchTerm),
// jscs:disable
regexEscapedSearchTerm = searchTerm.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'),
// jscs:enable
tagName = Ember.Handlebars.Utils.escapeExpression(matchingTag.get('name')),
regex = new RegExp('(' + regexEscapedSearchTerm + ')', 'gi'),
highlightedName,
@ -222,8 +224,7 @@ var PostTagsInputController = Ember.Controller.extend({
suggestion.set('highlightedName', highlightedName);
return suggestion;
},
}
});
export default PostTagsInputController;

View File

@ -19,7 +19,6 @@ function publishedAtCompare(item1, item2) {
return Ember.compare(published1.valueOf(), published2.valueOf());
}
var PostsController = Ember.ArrayController.extend(PaginationControllerMixin, {
// this will cause the list to re-sort when any of these properties change on any of the models
sortProperties: ['status', 'published_at', 'updated_at'],
@ -68,9 +67,9 @@ var PostsController = Ember.ArrayController.extend(PaginationControllerMixin, {
},
init: function () {
//let the PaginationControllerMixin know what type of model we will be paginating
//this is necesariy because we do not have access to the model inside the Controller::init method
this._super({'modelType': 'post'});
// let the PaginationControllerMixin know what type of model we will be paginating
// this is necesariy because we do not have access to the model inside the Controller::init method
this._super({modelType: 'post'});
}
});

View File

@ -1,48 +1,52 @@
/*global alert */
var AppStates = {
var appStates,
SettingsAppController;
appStates = {
active: 'active',
working: 'working',
inactive: 'inactive'
};
var SettingsAppController = Ember.ObjectController.extend({
appState: AppStates.active,
SettingsAppController = Ember.ObjectController.extend({
appState: appStates.active,
buttonText: '',
setAppState: function () {
this.set('appState', this.get('active') ? AppStates.active : AppStates.inactive);
this.set('appState', this.get('active') ? appStates.active : appStates.inactive);
}.on('init'),
buttonTextSetter: function () {
switch (this.get('appState')) {
case AppStates.active:
case appStates.active:
this.set('buttonText', 'Deactivate');
break;
case AppStates.inactive:
case appStates.inactive:
this.set('buttonText', 'Activate');
break;
case AppStates.working:
case appStates.working:
this.set('buttonText', 'Working');
break;
}
}.observes('appState').on('init'),
activeClass: Ember.computed('appState', function () {
return this.appState === AppStates.active ? true : false;
return this.appState === appStates.active ? true : false;
}),
inactiveClass: Ember.computed('appState', function () {
return this.appState === AppStates.inactive ? true : false;
return this.appState === appStates.inactive ? true : false;
}),
actions: {
toggleApp: function (app) {
var self = this;
this.set('appState', AppStates.working);
this.set('appState', appStates.working);
app.set('active', !app.get('active'));
app.save().then(function () {
self.setAppState();
})

View File

@ -2,9 +2,9 @@ import PaginationControllerMixin from 'ghost/mixins/pagination-controller';
var UsersIndexController = Ember.ArrayController.extend(PaginationControllerMixin, {
init: function () {
//let the PaginationControllerMixin know what type of model we will be paginating
//this is necessary because we do not have access to the model inside the Controller::init method
this._super({'modelType': 'user'});
// let the PaginationControllerMixin know what type of model we will be paginating
// this is necessary because we do not have access to the model inside the Controller::init method
this._super({modelType: 'user'});
},
users: Ember.computed.alias('model'),

View File

@ -21,9 +21,11 @@ var SettingsUserController = Ember.ObjectController.extend({
cover: Ember.computed('user.cover', 'coverDefault', function () {
var cover = this.get('user.cover');
if (Ember.isBlank(cover)) {
cover = this.get('coverDefault');
}
return 'background-image: url(' + cover + ')';
}),
@ -32,7 +34,7 @@ var SettingsUserController = Ember.ObjectController.extend({
}),
image: Ember.computed('imageUrl', function () {
return 'background-image: url(' + this.get('imageUrl') + ')';
return 'background-image: url(' + this.get('imageUrl') + ')';
}),
imageUrl: Ember.computed('user.image', function () {
@ -51,7 +53,7 @@ var SettingsUserController = Ember.ObjectController.extend({
return createdAt ? createdAt.fromNow() : '';
}),
//Lazy load the slug generator for slugPlaceholder
// Lazy load the slug generator for slugPlaceholder
slugGenerator: Ember.computed(function () {
return SlugGenerator.create({
ghostPaths: this.get('ghostPaths'),
@ -63,12 +65,13 @@ var SettingsUserController = Ember.ObjectController.extend({
changeRole: function (newRole) {
this.set('model.role', newRole);
},
revoke: function () {
var self = this,
model = this.get('model'),
email = this.get('email');
//reload the model to get the most up-to-date user information
// reload the model to get the most up-to-date user information
model.reload().then(function () {
if (self.get('invited')) {
model.destroyRecord().then(function () {
@ -78,7 +81,7 @@ var SettingsUserController = Ember.ObjectController.extend({
self.notifications.showAPIError(error);
});
} else {
//if the user is no longer marked as "invited", then show a warning and reload the route
// if the user is no longer marked as "invited", then show a warning and reload the route
self.get('target').send('reload');
self.notifications.showError('This user has already accepted the invitation.', {delayed: 500});
}
@ -117,7 +120,7 @@ var SettingsUserController = Ember.ObjectController.extend({
}
promise = Ember.RSVP.resolve(afterUpdateSlug).then(function () {
return user.save({ format: false });
return user.save({format: false});
}).then(function (model) {
var currentPath,
newPath;
@ -133,7 +136,7 @@ var SettingsUserController = Ember.ObjectController.extend({
newPath[newPath.length - 2] = model.get('slug');
newPath = newPath.join('/');
window.history.replaceState({ path: newPath }, '', newPath);
window.history.replaceState({path: newPath}, '', newPath);
}
return model;
@ -150,12 +153,11 @@ var SettingsUserController = Ember.ObjectController.extend({
if (user.get('isPasswordValid')) {
user.saveNewPassword().then(function (model) {
// Clear properties from view
user.setProperties({
'password': '',
'newPassword': '',
'ne2Password': ''
password: '',
newPassword: '',
ne2Password: ''
});
self.notifications.showSuccess('Password updated.');
@ -189,7 +191,6 @@ var SettingsUserController = Ember.ObjectController.extend({
}
return self.get('slugGenerator').generateSlug(newSlug).then(function (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) {

View File

@ -19,7 +19,7 @@ var SetupController = Ember.ObjectController.extend(ValidationEngine, {
self.notifications.closePassive();
this.toggleProperty('submitting');
this.validate({ format: false }).then(function () {
this.validate({format: false}).then(function () {
ajax({
url: self.get('ghostPaths.url').api('authentication', 'setup'),
type: 'POST',

View File

@ -15,7 +15,7 @@ var SigninController = Ember.Controller.extend(SimpleAuth.AuthenticationControll
validateAndAuthenticate: function () {
var self = this;
this.validate({ format: false }).then(function () {
this.validate({format: false}).then(function () {
self.notifications.closePassive();
self.send('authenticate');
}).catch(function (errors) {

View File

@ -15,7 +15,7 @@ var SignupController = Ember.ObjectController.extend(ValidationEngine, {
self.notifications.closePassive();
this.toggleProperty('submitting');
this.validate({ format: false }).then(function () {
this.validate({format: false}).then(function () {
ajax({
url: self.get('ghostPaths.url').api('authentication', 'invitation'),
type: 'POST',

View File

@ -1,6 +1,5 @@
var blogUrl = Ember.Handlebars.makeBoundHelper(function () {
return new Ember.Handlebars.SafeString(this.get('config.blogUrl'));
});
export default blogUrl;
export default blogUrl;

View File

@ -3,6 +3,7 @@ var countCharacters = Ember.Handlebars.makeBoundHelper(function (content) {
length = content ? content.length : 0;
el.className = 'word-count';
if (length > 180) {
el.style.color = '#E25440';
} else {
@ -14,4 +15,4 @@ var countCharacters = Ember.Handlebars.makeBoundHelper(function (content) {
return new Ember.Handlebars.SafeString(el.outerHTML);
});
export default countCharacters;
export default countCharacters;

View File

@ -3,6 +3,7 @@ var countDownCharacters = Ember.Handlebars.makeBoundHelper(function (content, ma
length = content ? content.length : 0;
el.className = 'word-count';
if (length > maxCharacters) {
el.style.color = '#E25440';
} else {
@ -14,4 +15,4 @@ var countDownCharacters = Ember.Handlebars.makeBoundHelper(function (content, ma
return new Ember.Handlebars.SafeString(el.outerHTML);
});
export default countDownCharacters;
export default countDownCharacters;

View File

@ -6,7 +6,8 @@ var countWords = Ember.Handlebars.makeBoundHelper(function (markdown) {
}
var count = counter(markdown || '');
return count + (count === 1 ? ' word' : ' words');
});
export default countWords;
export default countWords;

View File

@ -5,14 +5,19 @@ var formatHTML = Ember.Handlebars.makeBoundHelper(function (html) {
var escapedhtml = html || '';
// replace script and iFrame
// jscs:disable
escapedhtml = escapedhtml.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
'<pre class="js-embed-placeholder">Embedded JavaScript</pre>');
escapedhtml = escapedhtml.replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi,
'<pre class="iframe-embed-placeholder">Embedded iFrame</pre>');
// jscs:enable
// sanitize HTML
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
escapedhtml = html_sanitize(escapedhtml, cajaSanitizers.url, cajaSanitizers.id);
// jscs:enable requireCamelCaseOrUpperCaseIdentifiers
return new Handlebars.SafeString(escapedhtml);
});
export default formatHTML;
export default formatHTML;

View File

@ -1,23 +1,31 @@
/* global Showdown, Handlebars, html_sanitize*/
import cajaSanitizers from 'ghost/utils/caja-sanitizers';
var showdown = new Showdown.converter({extensions: ['ghostimagepreview', 'ghostgfm']});
var showdown,
formatMarkdown;
var formatMarkdown = Ember.Handlebars.makeBoundHelper(function (markdown) {
showdown = new Showdown.converter({extensions: ['ghostimagepreview', 'ghostgfm']});
formatMarkdown = Ember.Handlebars.makeBoundHelper(function (markdown) {
var escapedhtml = '';
// convert markdown to HTML
escapedhtml = showdown.makeHtml(markdown || '');
// replace script and iFrame
// jscs:disable
escapedhtml = escapedhtml.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
'<pre class="js-embed-placeholder">Embedded JavaScript</pre>');
escapedhtml = escapedhtml.replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi,
'<pre class="iframe-embed-placeholder">Embedded iFrame</pre>');
// jscs:enable
// sanitize html
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
escapedhtml = html_sanitize(escapedhtml, cajaSanitizers.url, cajaSanitizers.id);
// jscs:enable requireCamelCaseOrUpperCaseIdentifiers
return new Handlebars.SafeString(escapedhtml);
});
export default formatMarkdown;
export default formatMarkdown;

View File

@ -6,4 +6,4 @@ var formatTimeago = Ember.Handlebars.makeBoundHelper(function (timeago) {
// https://github.com/manuelmitasch/ghost-admin-ember-demo/commit/fba3ab0a59238290c85d4fa0d7c6ed1be2a8a82e#commitcomment-5396524
});
export default formatTimeago;
export default formatTimeago;

View File

@ -6,8 +6,7 @@
// {{gh-path 'admin' '/assets/hi.png'}} for resolved url (/myblog/ghost/assets/hi.png)
import ghostPaths from 'ghost/utils/ghost-paths';
export default function (path, url) {
function ghostPathsHelper(path, url) {
var base;
switch (path.toString()) {
@ -30,5 +29,6 @@ export default function (path, url) {
}
return new Ember.Handlebars.SafeString(base);
}
}
export default ghostPathsHelper;

View File

@ -1,25 +1,30 @@
import ghostPaths from 'ghost/utils/ghost-paths';
var Ghost = ghostPaths();
var Ghost,
AuthenticationInitializer;
var AuthenticationInitializer = {
Ghost = ghostPaths();
AuthenticationInitializer = {
name: 'authentication',
before: 'simple-auth',
after: 'registerTrailingLocationHistory',
initialize: function (container) {
window.ENV = window.ENV || {};
window.ENV['simple-auth'] = {
authenticationRoute: 'signin',
routeAfterAuthentication: 'content',
authorizer: 'simple-auth-authorizer:oauth2-bearer'
};
SimpleAuth.Session.reopen({
user: Ember.computed(function () {
return container.lookup('store:main').find('user', 'me');
})
});
SimpleAuth.Authenticators.OAuth2.reopen({
serverTokenEndpoint: Ghost.apiRoot + '/authentication/token',
serverTokenRevocationEndpoint: Ghost.apiRoot + '/authentication/revoke',
@ -29,6 +34,7 @@ var AuthenticationInitializer = {
return this._super(url, data);
}
});
SimpleAuth.Stores.LocalStorage.reopen({
key: 'ghost' + (Ghost.subdir.indexOf('/') === 0 ? '-' + Ghost.subdir.substr(1) : '') + ':session'
});

View File

@ -20,4 +20,4 @@ var dropdownInitializer = {
}
};
export default dropdownInitializer;
export default dropdownInitializer;

View File

@ -5,7 +5,7 @@ var ghostPathsInitializer = {
after: 'store',
initialize: function (container, application) {
application.register('ghost:paths', ghostPaths(), { instantiate: false });
application.register('ghost:paths', ghostPaths(), {instantiate: false});
application.inject('route', 'ghostPaths', 'ghost:paths');
application.inject('model', 'ghostPaths', 'ghost:paths');

View File

@ -1,7 +1,7 @@
//Used to surgically insert the store into things that wouldn't normally have them.
var StoreInjector = {
name: 'store-injector',
after: 'store',
initialize: function (container, application) {
application.inject('component:gh-role-selector', 'store', 'store:main');
}

View File

@ -1,12 +1,17 @@
/*global Ember */
var trailingHistory = Ember.HistoryLocation.extend({
var trailingHistory,
registerTrailingLocationHistory;
trailingHistory = Ember.HistoryLocation.extend({
formatURL: function () {
// jscs: disable
return this._super.apply(this, arguments).replace(/\/?$/, '/');
// jscs: enable
}
});
var registerTrailingLocationHistory = {
registerTrailingLocationHistory = {
name: 'registerTrailingLocationHistory',
initialize: function (container, application) {
@ -14,4 +19,4 @@ var registerTrailingLocationHistory = {
}
};
export default registerTrailingLocationHistory;
export default registerTrailingLocationHistory;

View File

@ -1,38 +1,46 @@
/*
Code modified from Addepar/ember-widgets
https://github.com/Addepar/ember-widgets/blob/master/src/mixins.coffee#L39
*/
// Code modified from Addepar/ember-widgets
// https://github.com/Addepar/ember-widgets/blob/master/src/mixins.coffee#L39
var BodyEventListener = Ember.Mixin.create({
bodyElementSelector: 'html',
bodyClick: Ember.K,
init: function () {
this._super();
return Ember.run.next(this, this._setupDocumentHandlers);
},
willDestroy: function () {
this._super();
return this._removeDocumentHandlers();
},
_setupDocumentHandlers: function () {
if (this._clickHandler) {
return;
}
var self = this;
this._clickHandler = function () {
return self.bodyClick();
};
return $(this.get('bodyElementSelector')).on('click', this._clickHandler);
},
_removeDocumentHandlers: function () {
$(this.get('bodyElementSelector')).off('click', this._clickHandler);
this._clickHandler = null;
},
/*
http://stackoverflow.com/questions/152975/how-to-detect-a-click-outside-an-element
*/
// http://stackoverflow.com/questions/152975/how-to-detect-a-click-outside-an-element
click: function (event) {
return event.stopPropagation();
}
});
export default BodyEventListener;
export default BodyEventListener;

View File

@ -1,31 +1,31 @@
var CurrentUserSettings = Ember.Mixin.create({
currentUser: function () {
return this.store.find('user', 'me');
},
currentUser: function () {
return this.store.find('user', 'me');
},
transitionAuthor: function () {
var self = this;
transitionAuthor: function () {
var self = this;
return function (user) {
if (user.get('isAuthor')) {
return self.transitionTo('settings.users.user', user);
}
return function (user) {
if (user.get('isAuthor')) {
return self.transitionTo('settings.users.user', user);
}
return user;
};
},
return user;
};
},
transitionEditor: function () {
var self = this;
transitionEditor: function () {
var self = this;
return function (user) {
if (user.get('isEditor')) {
return self.transitionTo('settings.users');
}
return function (user) {
if (user.get('isEditor')) {
return self.transitionTo('settings.users');
}
return user;
};
}
return user;
};
}
});
export default CurrentUserSettings;
export default CurrentUserSettings;

View File

@ -4,10 +4,12 @@
var DropdownMixin = Ember.Mixin.create(Ember.Evented, {
classNameBindings: ['isOpen:open:closed'],
isOpen: false,
click: function (event) {
this._super(event);
return event.stopPropagation();
}
});
export default DropdownMixin;
export default DropdownMixin;

View File

@ -3,15 +3,18 @@ import MarkerManager from 'ghost/mixins/marker-manager';
import PostModel from 'ghost/models/post';
import boundOneWay from 'ghost/utils/bound-one-way';
var watchedProps,
EditorControllerMixin;
// this array will hold properties we need to watch
// to know if the model has been changed (`controller.isDirty`)
var watchedProps = ['scratch', 'titleScratch', 'model.isDirty', 'tags.[]'];
watchedProps = ['scratch', 'titleScratch', 'model.isDirty', 'tags.[]'];
PostModel.eachAttribute(function (name) {
watchedProps.push('model.' + name);
});
var EditorControllerMixin = Ember.Mixin.create(MarkerManager, {
EditorControllerMixin = Ember.Mixin.create(MarkerManager, {
needs: ['post-tags-input', 'post-settings-menu'],
init: function () {
@ -23,6 +26,7 @@ var EditorControllerMixin = Ember.Mixin.create(MarkerManager, {
return self.get('isDirty') ? self.unloadDirtyMessage() : null;
};
},
/**
* By default, a post will not change its publish state.
* Only with a user-set value (via setSaveType action)
@ -80,7 +84,6 @@ var EditorControllerMixin = Ember.Mixin.create(MarkerManager, {
// it's ok to set isDirty to false
if (this.get('titleScratch') === model.get('title') &&
this.get('scratch') === model.get('markdown')) {
this.set('isDirty', false);
}
},
@ -141,8 +144,8 @@ var EditorControllerMixin = Ember.Mixin.create(MarkerManager, {
'==============================';
},
//TODO: This has to be moved to the I18n localization file.
//This structure is supposed to be close to the i18n-localization which will be used soon.
// TODO: This has to be moved to the I18n localization file.
// This structure is supposed to be close to the i18n-localization which will be used soon.
messageMap: {
errors: {
post: {
@ -175,7 +178,7 @@ var EditorControllerMixin = Ember.Mixin.create(MarkerManager, {
showSaveNotification: function (prevStatus, status, delay) {
var message = this.messageMap.success.post[prevStatus][status];
this.notifications.showSuccess(message, { delayed: delay });
this.notifications.showSuccess(message, {delayed: delay});
},
showErrorNotification: function (prevStatus, status, errors, delay) {
@ -183,7 +186,7 @@ var EditorControllerMixin = Ember.Mixin.create(MarkerManager, {
message += '<br />' + errors[0].message;
this.notifications.showError(message, { delayed: delay });
this.notifications.showError(message, {delayed: delay});
},
shouldFocusTitle: Ember.computed.alias('model.isNew'),
@ -201,7 +204,7 @@ var EditorControllerMixin = Ember.Mixin.create(MarkerManager, {
options = options || {};
if(autoSaveId) {
if (autoSaveId) {
Ember.run.cancel(autoSaveId);
this.set('autoSaveId', null);
}
@ -236,12 +239,14 @@ var EditorControllerMixin = Ember.Mixin.create(MarkerManager, {
if (!options.silent) {
self.showSaveNotification(prevStatus, model.get('status'), isNew ? true : false);
}
return model;
});
}).catch(function (errors) {
if (!options.silent) {
self.showErrorNotification(prevStatus, self.get('status'), errors);
}
self.set('status', prevStatus);
return Ember.RSVP.reject(errors);
@ -283,11 +288,13 @@ var EditorControllerMixin = Ember.Mixin.create(MarkerManager, {
// Match the uploaded file to a line in the editor, and update that line with a path reference
// ensuring that everything ends up in the correct place and format.
handleImgUpload: function (e, result_src) {
handleImgUpload: function (e, resultSrc) {
var editor = this.get('codemirror'),
line = this.findLine(Ember.$(e.currentTarget).attr('id')),
lineNumber = editor.getLineNumber(line),
// jscs:disable
match = line.text.match(/\([^\n]*\)?/),
// jscs:enable
replacement = '(http://)';
if (match) {
@ -297,7 +304,9 @@ var EditorControllerMixin = Ember.Mixin.create(MarkerManager, {
{line: lineNumber, ch: match.index + match[0].length - 1}
);
} else {
// jscs:disable
match = line.text.match(/\]/);
// jscs:enable
if (match) {
editor.replaceRange(
replacement,
@ -306,11 +315,12 @@ var EditorControllerMixin = Ember.Mixin.create(MarkerManager, {
);
editor.setSelection(
{line: lineNumber, ch: match.index + 2},
{line: lineNumber, ch: match.index + replacement.length }
{line: lineNumber, ch: match.index + replacement.length}
);
}
}
editor.replaceSelection(result_src);
editor.replaceSelection(resultSrc);
},
togglePreview: function (preview) {

View File

@ -4,20 +4,23 @@ import loadingIndicator from 'ghost/mixins/loading-indicator';
import editorShortcuts from 'ghost/utils/editor-shortcuts';
var EditorRouteBase = Ember.Mixin.create(styleBody, ShortcutsRoute, loadingIndicator, {
actions: {
save: function () {
this.get('controller').send('save');
},
publish: function () {
var controller = this.get('controller');
controller.send('setSaveType', 'publish');
controller.send('save');
},
toggleZenMode: function () {
Ember.$('body').toggleClass('zen');
},
//The actual functionality is implemented in utils/codemirror-shortcuts
// The actual functionality is implemented in utils/codemirror-shortcuts
codeMirrorShortcut: function (options) {
this.get('controller.codemirror').shortcut(options.type);
}

View File

@ -1,10 +1,14 @@
// mixin used for routes to display a loading indicator when there is network activity
var loaderOptions = {
'showSpinner': false
var loaderOptions,
loadingIndicator;
loaderOptions = {
showSpinner: false
};
NProgress.configure(loaderOptions);
var loadingIndicator = Ember.Mixin.create({
loadingIndicator = Ember.Mixin.create({
actions: {
loading: function () {
@ -12,14 +16,16 @@ var loadingIndicator = Ember.Mixin.create({
this.router.one('didTransition', function () {
NProgress.done();
});
return true;
},
error: function () {
NProgress.done();
return true;
}
}
});
export default loadingIndicator;
export default loadingIndicator;

View File

@ -1,6 +1,8 @@
var MarkerManager = Ember.Mixin.create({
// jscs:disable
imageMarkdownRegex: /^(?:\{<(.*?)>\})?!(?:\[([^\n\]]*)\])(?:\(([^\n\]]*)\))?$/gim,
markerRegex: /\{<([\w\W]*?)>\}/,
// jscs:enable
uploadId: 1,
@ -161,9 +163,12 @@ var MarkerManager = Ember.Mixin.create({
stripMarkerFromLine: function (line) {
var editor = this.get('codemirror'),
ln = editor.getLineNumber(line),
markerRegex = /\{<([\w\W]*?)>\}/,
markerText = line.text.match(markerRegex);
// jscs:disable
markerRegex = /\{<([\w\W]*?)>\}/,
// jscs:enable
markerText = line.text.match(markerRegex);
if (markerText) {
editor.replaceRange(
@ -202,13 +207,13 @@ var MarkerManager = Ember.Mixin.create({
},
// Find the line with the marker which matches
findLine: function (result_id) {
findLine: function (resultId) {
var editor = this.get('codemirror'),
markers = this.get('markers');
// try to find the right line to replace
if (markers.hasOwnProperty(result_id) && markers[result_id].find()) {
return editor.getLineHandle(markers[result_id].find().from.line);
if (markers.hasOwnProperty(resultId) && markers[resultId].find()) {
return editor.getLineHandle(markers[resultId].find().from.line);
}
return false;

View File

@ -3,16 +3,19 @@ var NProgressSaveMixin = Ember.Mixin.create({
if (options && options.disableNProgress) {
return this._super(options);
}
NProgress.start();
return this._super(options).then(function (value) {
NProgress.done();
return value;
}).catch(function (error) {
NProgress.done();
return Ember.RSVP.reject(error);
});
}
});
export default NProgressSaveMixin;
export default NProgressSaveMixin;

View File

@ -1,7 +1,6 @@
import { getRequestErrorMessage } from 'ghost/utils/ajax';
var PaginationControllerMixin = Ember.Mixin.create({
// set from PaginationRouteMixin
paginationSettings: null,
@ -13,7 +12,7 @@ var PaginationControllerMixin = Ember.Mixin.create({
/**
*
* @param options: {
* @param {object} options: {
* modelType: <String> name of the model that will be paginated
* }
*/
@ -21,10 +20,10 @@ var PaginationControllerMixin = Ember.Mixin.create({
this._super();
var metadata = this.store.metadataFor(options.modelType);
this.set('nextPage', metadata.pagination.next);
},
/**
* Takes an ajax response, concatenates any error messages, then generates an error notification.
* @param {jqXHR} response The jQuery ajax reponse object.
@ -49,7 +48,6 @@ var PaginationControllerMixin = Ember.Mixin.create({
* @return
*/
loadNextPage: function () {
var self = this,
store = this.get('store'),
recordType = this.get('model').get('type'),
@ -59,6 +57,7 @@ var PaginationControllerMixin = Ember.Mixin.create({
if (nextPage) {
this.set('isLoading', true);
this.set('paginationSettings.page', nextPage);
store.find(recordType, paginationSettings).then(function () {
var metadata = store.metadataFor(recordType);
@ -70,7 +69,6 @@ var PaginationControllerMixin = Ember.Mixin.create({
}
}
}
});
export default PaginationControllerMixin;

View File

@ -1,16 +1,17 @@
var defaultPaginationSettings = {
var defaultPaginationSettings,
PaginationRoute;
defaultPaginationSettings = {
page: 1,
limit: 15
};
var PaginationRoute = Ember.Mixin.create({
PaginationRoute = Ember.Mixin.create({
/**
* Sets up pagination details
* @param {settings}: object that specifies additional pagination details
* @param {object} settings specifies additional pagination details
*/
setupPagination: function (settings) {
settings = settings || {};
for (var key in defaultPaginationSettings) {
if (defaultPaginationSettings.hasOwnProperty(key)) {
@ -23,7 +24,6 @@ var PaginationRoute = Ember.Mixin.create({
this.set('paginationSettings', settings);
this.controller.set('paginationSettings', settings);
}
});
export default PaginationRoute;

View File

@ -2,7 +2,7 @@ var PaginationViewInfiniteScrollMixin = Ember.Mixin.create({
/**
* Determines if we are past a scroll point where we need to fetch the next page
* @param event The scroll event
* @param {object} event The scroll event
*/
checkScroll: function (event) {
var element = event.target,

View File

@ -1,8 +1,8 @@
/* global key */
//Configure KeyMaster to respond to all shortcuts,
//even inside of
//input, textarea, and select.
// Configure KeyMaster to respond to all shortcuts,
// even inside of
// input, textarea, and select.
key.filter = function () {
return true;
};
@ -57,12 +57,13 @@ var ShortcutsRoute = Ember.Mixin.create({
}
key(shortcut, scope, function (event) {
//stop things like ctrl+s from actually opening a save dialogue
// stop things like ctrl+s from actually opening a save dialogue
event.preventDefault();
self.send(action, options);
});
});
},
removeShortcuts: function () {
var shortcuts = this.get('shortcuts');
@ -70,13 +71,17 @@ var ShortcutsRoute = Ember.Mixin.create({
key.unbind(shortcut);
});
},
activate: function () {
this._super();
if (!this.shortcuts) {
return;
}
this.registerShortcuts();
},
deactivate: function () {
this._super();
this.removeShortcuts();

View File

@ -3,6 +3,7 @@
var styleBody = Ember.Mixin.create({
activate: function () {
this._super();
var cssClasses = this.get('classNames');
if (cssClasses) {
@ -16,6 +17,7 @@ var styleBody = Ember.Mixin.create({
deactivate: function () {
this._super();
var cssClasses = this.get('classNames');
Ember.run.schedule('afterRender', null, function () {
@ -26,4 +28,4 @@ var styleBody = Ember.Mixin.create({
}
});
export default styleBody;
export default styleBody;

View File

@ -1,20 +1,23 @@
var BlurField = Ember.Mixin.create({
selectOnClick: false,
stopEnterKeyDownPropagation: false,
click: function (event) {
if (this.get('selectOnClick')) {
event.currentTarget.select();
}
},
keyDown: function (event) {
// stop event propagation when pressing "enter"
// most useful in the case when undesired (global) keyboard shortcuts are getting triggered while interacting
// with this particular input element.
if (this.get('stopEnterKeyDownPropagation') && event.keyCode === 13) {
event.stopPropagation();
return true;
}
}
});
export default BlurField;
export default BlurField;

View File

@ -1,4 +1,4 @@
import { getRequestErrorMessage } from 'ghost/utils/ajax';
import {getRequestErrorMessage} from 'ghost/utils/ajax';
import ValidatorExtensions from 'ghost/utils/validator-extensions';
import PostValidator from 'ghost/validators/post';
@ -14,7 +14,7 @@ import UserValidator from 'ghost/validators/user';
ValidatorExtensions.init();
// format errors to be used in `notifications.showErrors`.
// result is [{ message: 'concatenated error messages' }]
// result is [{message: 'concatenated error messages'}]
function formatErrors(errors, opts) {
var message = 'There was an error';
@ -46,12 +46,11 @@ function formatErrors(errors, opts) {
}
// set format for notifications.showErrors
message = [{ message: message }];
message = [{message: message}];
return message;
}
/**
* The class that gets this mixin will receive these properties and functions.
* It will be able to validate any properties on itself (or the model it passes to validate())
@ -143,9 +142,9 @@ var ValidationEngine = Ember.Mixin.create({
return _super.call(self, options);
}).catch(function (result) {
// server save failed - validate() would have given back an array
if (! Ember.isArray(result)) {
if (!Ember.isArray(result)) {
if (options.format !== false) {
// concatenate all errors into an array with a single object: [{ message: 'concatted message' }]
// concatenate all errors into an array with a single object: [{message: 'concatted message'}]
result = formatErrors(result, options);
} else {
// return the array of errors from the server

View File

@ -16,13 +16,15 @@ var Post = DS.Model.extend(NProgressSaveMixin, ValidationEngine, {
language: DS.attr('string', {defaultValue: 'en_US'}),
meta_title: DS.attr('string'),
meta_description: DS.attr('string'),
author: DS.belongsTo('user', { async: true }),
author: DS.belongsTo('user', {async: true}),
author_id: DS.attr('number'),
updated_at: DS.attr('moment-date'),
published_at: DS.attr('moment-date'),
published_by: DS.belongsTo('user', { async: true }),
tags: DS.hasMany('tag', { embedded: 'always' }),
//## Computed post properties
published_by: DS.belongsTo('user', {async: true}),
tags: DS.hasMany('tag', {embedded: 'always'}),
// Computed post properties
isPublished: Ember.computed.equal('status', 'published'),
isDraft: Ember.computed.equal('status', 'draft'),
@ -31,7 +33,7 @@ var Post = DS.Model.extend(NProgressSaveMixin, ValidationEngine, {
// when returned from the server with ids.
updateTags: function () {
var tags = this.get('tags'),
oldTags = tags.filterBy('id', null);
oldTags = tags.filterBy('id', null);
tags.removeObjects(oldTags);
oldTags.invoke('deleteRecord');

View File

@ -5,7 +5,7 @@ var Tag = DS.Model.extend({
description: DS.attr('string'),
parent_id: DS.attr('number'),
meta_title: DS.attr('string'),
meta_description: DS.attr('string'),
meta_description: DS.attr('string')
});
export default Tag;
export default Tag;

View File

@ -24,15 +24,17 @@ var User = DS.Model.extend(NProgressSaveMixin, SelectiveSaveMixin, ValidationEng
created_by: DS.attr('number'),
updated_at: DS.attr('moment-date'),
updated_by: DS.attr('number'),
roles: DS.hasMany('role', { embedded: 'always' }),
roles: DS.hasMany('role', {embedded: 'always'}),
role: Ember.computed('roles', function (name, value) {
if (arguments.length > 1) {
//Only one role per user, so remove any old data.
// Only one role per user, so remove any old data.
this.get('roles').clear();
this.get('roles').pushObject(value);
return value;
}
return this.get('roles.firstObject');
}),
@ -45,13 +47,14 @@ var User = DS.Model.extend(NProgressSaveMixin, SelectiveSaveMixin, ValidationEng
saveNewPassword: function () {
var url = this.get('ghostPaths.url').api('users', 'password');
return ic.ajax.request(url, {
type: 'PUT',
data: {
password: [{
'oldPassword': this.get('password'),
'newPassword': this.get('newPassword'),
'ne2Password': this.get('ne2Password')
oldPassword: this.get('password'),
newPassword: this.get('newPassword'),
ne2Password: this.get('ne2Password')
}]
}
});
@ -60,9 +63,9 @@ var User = DS.Model.extend(NProgressSaveMixin, SelectiveSaveMixin, ValidationEng
resendInvite: function () {
var fullUserData = this.toJSON(),
userData = {
email: fullUserData.email,
roles: fullUserData.roles
};
email: fullUserData.email,
roles: fullUserData.roles
};
return ic.ajax.request(this.get('ghostPaths.url').api('users'), {
type: 'POST',

View File

@ -18,29 +18,35 @@ Router.map(function () {
this.route('setup');
this.route('signin');
this.route('signout');
this.route('signup', { path: '/signup/:token' });
this.route('signup', {path: '/signup/:token'});
this.route('forgotten');
this.route('reset', { path: '/reset/:token' });
this.resource('posts', { path: '/' }, function () {
this.route('post', { path: ':post_id' });
this.route('reset', {path: '/reset/:token'});
this.resource('posts', {path: '/'}, function () {
this.route('post', {path: ':post_id'});
});
this.resource('editor', function () {
this.route('new', { path: '' });
this.route('edit', { path: ':post_id' });
this.route('new', {path: ''});
this.route('edit', {path: ':post_id'});
});
this.resource('settings', function () {
this.route('general');
this.resource('settings.users', { path: '/users' }, function () {
this.route('user', { path: '/:slug' });
this.resource('settings.users', {path: '/users'}, function () {
this.route('user', {path: '/:slug'});
});
this.route('about');
});
this.route('debug');
//Redirect legacy content to posts
// Redirect legacy content to posts
this.route('content');
this.route('error404', { path: '/*path' });
this.route('error404', {path: '/*path'});
});
export default Router;

View File

@ -10,8 +10,8 @@ var ApplicationRoute = Ember.Route.extend(SimpleAuth.ApplicationRouteMixin, Shor
},
shortcuts: {
'esc': {action: 'closePopups', scope: 'all'},
'enter': {action: 'confirmModal', scope: 'modal'}
esc: {action: 'closePopups', scope: 'all'},
enter: {action: 'confirmModal', scope: 'modal'}
},
actions: {
@ -84,6 +84,7 @@ var ApplicationRoute = Ember.Route.extend(SimpleAuth.ApplicationRouteMixin, Shor
key.setScope('modal');
modalName = 'modals/' + modalName;
this.set('modalName', modalName);
// We don't always require a modal to have a controller
// so we're skipping asserting if one exists
if (this.controllerFor(modalName, true)) {
@ -101,9 +102,11 @@ var ApplicationRoute = Ember.Route.extend(SimpleAuth.ApplicationRouteMixin, Shor
});
},
confirmModal : function () {
confirmModal: function () {
var modalName = this.get('modalName');
this.send('closeModal');
if (this.controllerFor(modalName, true)) {
this.controllerFor(modalName).send('confirmAccept');
}
@ -114,11 +117,13 @@ var ApplicationRoute = Ember.Route.extend(SimpleAuth.ApplicationRouteMixin, Shor
outlet: 'modal',
parentView: 'application'
});
key.setScope('default');
},
loadServerNotifications: function (isDelayed) {
var self = this;
if (this.session.isAuthenticated) {
this.store.findAll('notification').then(function (serverNotifications) {
serverNotifications.forEach(function (notification) {
@ -130,6 +135,7 @@ var ApplicationRoute = Ember.Route.extend(SimpleAuth.ApplicationRouteMixin, Shor
handleErrors: function (errors) {
var self = this;
this.notifications.clear();
errors.forEach(function (errorObj) {
self.notifications.showError(errorObj.message || errorObj);

View File

@ -4,4 +4,4 @@ var ContentRoute = Ember.Route.extend({
}
});
export default ContentRoute;
export default ContentRoute;

View File

@ -14,7 +14,7 @@ var DebugRoute = Ember.Route.extend(SimpleAuth.AuthenticatedRouteMixin, styleBod
},
model: function () {
return this.store.find('setting', { type: 'blog,theme' }).then(function (records) {
return this.store.find('setting', {type: 'blog,theme'}).then(function (records) {
return records.get('firstObject');
});
}

View File

@ -9,4 +9,4 @@ var Error404Route = Ember.Route.extend({
}
});
export default Error404Route;
export default Error404Route;

View File

@ -1,6 +1,6 @@
import mobileQuery from 'ghost/utils/mobile';
//Routes that extend MobileIndexRoute need to implement
// Routes that extend MobileIndexRoute need to implement
// desktopTransition, a function which is called when
// the user resizes to desktop levels.
var MobileIndexRoute = Ember.Route.extend({

View File

@ -3,13 +3,16 @@ import ShortcutsRoute from 'ghost/mixins/shortcuts-route';
import loadingIndicator from 'ghost/mixins/loading-indicator';
import PaginationRouteMixin from 'ghost/mixins/pagination-route';
var paginationSettings = {
var paginationSettings,
PostsRoute;
paginationSettings = {
status: 'all',
staticPages: 'all',
page: 1
};
var PostsRoute = Ember.Route.extend(SimpleAuth.AuthenticatedRouteMixin, ShortcutsRoute, styleBody, loadingIndicator, PaginationRouteMixin, {
PostsRoute = Ember.Route.extend(SimpleAuth.AuthenticatedRouteMixin, ShortcutsRoute, styleBody, loadingIndicator, PaginationRouteMixin, {
classNames: ['manage'],
model: function () {
@ -19,6 +22,7 @@ var PostsRoute = Ember.Route.extend(SimpleAuth.AuthenticatedRouteMixin, Shortcut
if (user.get('isAuthor')) {
paginationSettings.author = user.get('slug');
}
// using `.filter` allows the template to auto-update when new models are pulled in from the server.
// we just need to 'return true' to allow all models by default.
return self.store.filter('post', paginationSettings, function (post) {
@ -52,21 +56,25 @@ var PostsRoute = Ember.Route.extend(SimpleAuth.AuthenticatedRouteMixin, Shortcut
} else if (newPosition < 0) {
return;
}
this.transitionTo('posts.post', posts.objectAt(newPosition));
},
shortcuts: {
'up, k': 'moveUp',
'down, j': 'moveDown',
'c': 'newPost'
c: 'newPost'
},
actions: {
newPost: function () {
this.transitionTo('editor.new');
},
moveUp: function () {
this.stepThroughPosts(-1);
},
moveDown: function () {
this.stepThroughPosts(1);
}

View File

@ -4,6 +4,7 @@ import mobileQuery from 'ghost/utils/mobile';
var PostsIndexRoute = MobileIndexRoute.extend(SimpleAuth.AuthenticatedRouteMixin, loadingIndicator, {
noPosts: false,
// Transition to a specific post if we're not on mobile
beforeModel: function () {
if (!mobileQuery.matches) {
@ -21,22 +22,26 @@ var PostsIndexRoute = MobileIndexRoute.extend(SimpleAuth.AuthenticatedRouteMixin
// the store has been populated by PostsRoute
posts = this.store.all('post'),
post;
return this.store.find('user', 'me').then(function (user) {
post = posts.find(function (post) {
// Authors can only see posts they've written
if (user.get('isAuthor')) {
return post.isAuthoredByUser(user);
}
return true;
});
if (post) {
return self.transitionTo('posts.post', post);
}
self.set('noPosts', true);
});
},
//Mobile posts route callback
// Mobile posts route callback
desktopTransition: function () {
this.goToPost();
}

View File

@ -12,8 +12,7 @@ var PostsPostRoute = Ember.Route.extend(SimpleAuth.AuthenticatedRouteMixin, load
postId = Number(params.post_id);
if (!isNumber(postId) || !isFinite(postId) || postId % 1 !== 0 || postId <= 0)
{
if (!isNumber(postId) || !isFinite(postId) || postId % 1 !== 0 || postId <= 0) {
return this.transitionTo('error404', params.post_id);
}
@ -50,6 +49,7 @@ var PostsPostRoute = Ember.Route.extend(SimpleAuth.AuthenticatedRouteMixin, load
});
});
},
setupController: function (controller, model) {
this._super(controller, model);
@ -60,10 +60,12 @@ var PostsPostRoute = Ember.Route.extend(SimpleAuth.AuthenticatedRouteMixin, load
'enter, o': 'openEditor',
'command+backspace, ctrl+backspace': 'deletePost'
},
actions: {
openEditor: function () {
this.transitionTo('editor.edit', this.get('controller.model'));
},
deletePost: function () {
this.send('openModal', 'delete-post', this.get('controller.model'));
}

View File

@ -3,15 +3,18 @@ import loadingIndicator from 'ghost/mixins/loading-indicator';
var ResetRoute = Ember.Route.extend(styleBody, loadingIndicator, {
classNames: ['ghost-reset'],
beforeModel: function () {
if (this.get('session').isAuthenticated) {
this.notifications.showWarn('You can\'t reset your password while you\'re signed in.', { delayed: true });
this.notifications.showWarn('You can\'t reset your password while you\'re signed in.', {delayed: true});
this.transitionTo(SimpleAuth.Configuration.routeAfterAuthentication);
}
},
setupController: function (controller, params) {
controller.token = params.token;
},
// Clear out any sensitive information
deactivate: function () {
this._super();

View File

@ -5,4 +5,4 @@ var SettingsRoute = Ember.Route.extend(SimpleAuth.AuthenticatedRouteMixin, style
classNames: ['settings']
});
export default SettingsRoute;
export default SettingsRoute;

View File

@ -13,7 +13,7 @@ var AppsRoute = Ember.Route.extend(SimpleAuth.AuthenticatedRouteMixin, styleBody
.then(this.transitionAuthor())
.then(this.transitionEditor());
},
model: function () {
return this.store.find('app');
}

View File

@ -12,7 +12,7 @@ var SettingsGeneralRoute = Ember.Route.extend(SimpleAuth.AuthenticatedRouteMixin
},
model: function () {
return this.store.find('setting', { type: 'blog,theme' }).then(function (records) {
return this.store.find('setting', {type: 'blog,theme'}).then(function (records) {
return records.get('firstObject');
});
}

View File

@ -1,13 +1,16 @@
import PaginationRouteMixin from 'ghost/mixins/pagination-route';
import styleBody from 'ghost/mixins/style-body';
var paginationSettings = {
var paginationSettings,
UsersIndexRoute;
paginationSettings = {
page: 1,
limit: 20,
status: 'active'
};
var UsersIndexRoute = Ember.Route.extend(SimpleAuth.AuthenticatedRouteMixin, styleBody, PaginationRouteMixin, {
UsersIndexRoute = Ember.Route.extend(SimpleAuth.AuthenticatedRouteMixin, styleBody, PaginationRouteMixin, {
classNames: ['settings-view-users'],
setupController: function (controller, model) {

View File

@ -15,7 +15,7 @@ var SigninRoute = Ember.Route.extend(styleBody, loadingIndicator, {
// clear the properties that hold the credentials from the controller
// when we're no longer on the signin screen
this.controllerFor('signin').setProperties({ identification: '', password: '' });
this.controllerFor('signin').setProperties({identification: '', password: ''});
}
});

View File

@ -12,7 +12,7 @@ var SignoutRoute = Ember.Route.extend(SimpleAuth.AuthenticatedRouteMixin, styleB
} else {
this.send('invalidateSession');
}
},
}
});
export default SignoutRoute;

View File

@ -5,7 +5,7 @@ var SignupRoute = Ember.Route.extend(styleBody, loadingIndicator, {
classNames: ['ghost-signup'],
beforeModel: function () {
if (this.get('session').isAuthenticated) {
this.notifications.showWarn('You need to sign out to register as a new user.', { delayed: true });
this.notifications.showWarn('You need to sign out to register as a new user.', {delayed: true});
this.transitionTo(SimpleAuth.Configuration.routeAfterAuthentication);
}
},
@ -19,7 +19,7 @@ var SignupRoute = Ember.Route.extend(styleBody, loadingIndicator, {
return new Ember.RSVP.Promise(function (resolve) {
if (!re.test(params.token)) {
self.notifications.showError('Invalid token.', { delayed: true });
self.notifications.showError('Invalid token.', {delayed: true});
return resolve(self.transitionTo('signin'));
}
@ -39,7 +39,7 @@ var SignupRoute = Ember.Route.extend(styleBody, loadingIndicator, {
}
}).then(function (response) {
if (response && response.invitation && response.invitation[0].valid === false) {
self.notifications.showError('The invitation does not exist or is no longer valid.', { delayed: true });
self.notifications.showError('The invitation does not exist or is no longer valid.', {delayed: true});
return resolve(self.transitionTo('signin'));
}
@ -55,7 +55,7 @@ var SignupRoute = Ember.Route.extend(styleBody, loadingIndicator, {
this._super();
// clear the properties that hold the sensitive data from the controller
this.controllerFor('signup').setProperties({ email: '', password: '', token: '' });
this.controllerFor('signup').setProperties({email: '', password: '', token: ''});
}
});

View File

@ -3,7 +3,7 @@ import ApplicationSerializer from 'ghost/serializers/application';
var PostSerializer = ApplicationSerializer.extend(DS.EmbeddedRecordsMixin, {
// settings for the EmbeddedRecordsMixin.
attrs: {
tags: { embedded: 'always' }
tags: {embedded: 'always'}
},
normalize: function (type, hash) {

View File

@ -13,14 +13,14 @@ var SettingSerializer = ApplicationSerializer.extend({
delete data.id;
Object.keys(data).forEach(function (k) {
payload.push({ key: k, value: data[k] });
payload.push({key: k, value: data[k]});
});
hash[root] = payload;
},
extractArray: function (store, type, _payload) {
var payload = { id: '0' };
var payload = {id: '0'};
_payload.settings.forEach(function (setting) {
payload[setting.key] = setting.value;

View File

@ -2,7 +2,7 @@ import ApplicationSerializer from 'ghost/serializers/application';
var UserSerializer = ApplicationSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
roles: { embedded: 'always' }
roles: {embedded: 'always'}
},
extractSingle: function (store, primaryType, payload) {

View File

@ -6,7 +6,7 @@ var ajax = window.ajax = function () {
// Used in API request fail handlers to parse a standard api error
// response json for the message to display
var getRequestErrorMessage = function (request, performConcat) {
function getRequestErrorMessage(request, performConcat) {
var message,
msgDetail;
@ -23,7 +23,6 @@ var getRequestErrorMessage = function (request, performConcat) {
try {
// Try to parse out the error, or default to 'Unknown'
if (request.responseJSON.errors && Ember.isArray(request.responseJSON.errors)) {
message = request.responseJSON.errors.map(function (errorItem) {
return errorItem.message;
});
@ -46,7 +45,7 @@ var getRequestErrorMessage = function (request, performConcat) {
}
return message;
};
}
export { getRequestErrorMessage, ajax };
export {getRequestErrorMessage, ajax};
export default ajax;

View File

@ -5,13 +5,15 @@
*
* This is an ideal tool for working with values inside of {{input}}
* elements.
* @param transform: a function to transform the **upstream** value.
* @param {*} upstream
* @param {function} transform a function to transform the **upstream** value.
*/
var BoundOneWay = function (upstream, transform) {
if (typeof transform !== 'function') {
//default to the identity function
// default to the identity function
transform = function (value) { return value; };
}
return Ember.computed(upstream, function (key, value) {
return arguments.length > 1 ? value : transform(this.get(upstream));
});

View File

@ -8,22 +8,24 @@ var url,
* Check if URL is allowed
* URLs are allowed if they start with http://, https://, or /.
*/
var url = function (url) {
url = url.toString().replace(/['"]+/g, '');
url = function (url) {
// jscs:disable
url = url.toString().replace(/['"]+/g, '');
if (/^https?:\/\//.test(url) || /^\//.test(url)) {
return url;
}
// jscs:enable
};
/**
* Check if ID is allowed
* All ids are allowed at the moment.
*/
var id = function (id) {
id = function (id) {
return id;
};
export default {
url: url,
id: id
};
};

View File

@ -19,20 +19,21 @@ setupMobileCodeMirror = function setupMobileCodeMirror() {
return new TouchEditor(el, options);
};
CodeMirror.keyMap = { basic: {} };
CodeMirror.keyMap = {basic: {}};
};
init = function init() {
//Codemirror does not function on mobile devices,
// nor on any iDevice.
// Codemirror does not function on mobile devices, or on any iDevice
if (device.mobile() || (device.tablet() && device.ios())) {
$('body').addClass('touch-editor');
Ember.touchEditor = true;
//initialize FastClick to remove touch delays
// initialize FastClick to remove touch delays
Ember.run.scheduleOnce('afterRender', null, function () {
FastClick.attach(document.body);
});
TouchEditor = createTouchEditor();
setupMobileCodeMirror();
}

View File

@ -1,4 +1,6 @@
/* global CodeMirror, moment, Showdown */
// jscs:disable disallowSpacesInsideParentheses
/** Set up a shortcut function to be called via router actions.
* See editor-route-base
*/
@ -9,8 +11,8 @@ function init() {
// remove predefined `ctrl+h` shortcut
delete CodeMirror.keyMap.emacsy['Ctrl-H'];
//Used for simple, noncomputational replace-and-go! shortcuts.
// See default case in shortcut function below.
// Used for simple, noncomputational replace-and-go! shortcuts.
// See default case in shortcut function below.
CodeMirror.prototype.simpleShortcutSyntax = {
bold: '**$1**',
italic: '*$1*',
@ -20,6 +22,7 @@ function init() {
image: '![$1](http://)',
blockquote: '> $1'
};
CodeMirror.prototype.shortcut = function (type) {
var text = this.getSelection(),
cursor = this.getCursor(),
@ -40,14 +43,20 @@ function init() {
currentHeaderLevel = match[0].length;
}
if (currentHeaderLevel > 2) { currentHeaderLevel = 1; }
if (currentHeaderLevel > 2) {
currentHeaderLevel = 1;
}
hashPrefix = new Array(currentHeaderLevel + 2).join('#');
// jscs:disable
replacementLine = hashPrefix + ' ' + line.replace(/^#* /, '');
// jscs:enable
this.replaceRange(replacementLine, fromLineStart, toLineEnd);
this.setCursor(cursor.line, cursor.ch + replacementLine.length);
break;
case 'link':
md = this.simpleShortcutSyntax.link.replace('$1', text);
this.replaceSelection(md, 'end');
@ -65,6 +74,7 @@ function init() {
});
}
return;
case 'image':
md = this.simpleShortcutSyntax.image.replace('$1', text);
if (line !== '') {
@ -74,23 +84,31 @@ function init() {
cursor = this.getCursor();
this.setSelection({line: cursor.line, ch: cursor.ch - 8}, {line: cursor.line, ch: cursor.ch - 1});
return;
case 'list':
// jscs:disable
md = text.replace(/^(\s*)(\w\W*)/gm, '$1* $2');
// jscs:enable
this.replaceSelection(md, 'end');
return;
case 'currentDate':
md = moment(new Date()).format('D MMMM YYYY');
this.replaceSelection(md, 'end');
return;
case 'uppercase':
md = text.toLocaleUpperCase();
break;
case 'lowercase':
md = text.toLocaleLowerCase();
break;
case 'titlecase':
md = titleize(text);
break;
case 'copyHTML':
converter = new Showdown.converter();
@ -101,9 +119,10 @@ function init() {
}
// Talk to Ember
this.component.sendAction('openModal', 'copy-html', { generatedHTML: generatedHTML });
this.component.sendAction('openModal', 'copy-html', {generatedHTML: generatedHTML});
break;
default:
if (this.simpleShortcutSyntax[type]) {
md = this.simpleShortcutSyntax[type].replace('$1', text);

View File

@ -1,32 +1,39 @@
/* global moment */
var parseDateFormats = ['DD MMM YY @ HH:mm', 'DD MMM YY HH:mm',
// jscs: disable disallowSpacesInsideParentheses
var parseDateFormats,
displayDateFormat,
verifyTimeStamp,
parseDateString,
formatDate;
parseDateFormats = ['DD MMM YY @ HH:mm', 'DD MMM YY HH:mm',
'DD MMM YYYY @ HH:mm', 'DD MMM YYYY HH:mm',
'DD/MM/YY @ HH:mm', 'DD/MM/YY HH:mm',
'DD/MM/YYYY @ HH:mm', 'DD/MM/YYYY HH:mm',
'DD-MM-YY @ HH:mm', 'DD-MM-YY HH:mm',
'DD-MM-YYYY @ HH:mm', 'DD-MM-YYYY HH:mm',
'YYYY-MM-DD @ HH:mm', 'YYYY-MM-DD HH:mm',
'DD MMM @ HH:mm', 'DD MMM HH:mm'],
displayDateFormat = 'DD MMM YY @ HH:mm';
'DD MMM @ HH:mm', 'DD MMM HH:mm'];
/**
* Add missing timestamps
*/
var verifyTimeStamp = function (dateString) {
displayDateFormat = 'DD MMM YY @ HH:mm';
// Add missing timestamps
verifyTimeStamp = function (dateString) {
if (dateString && !dateString.slice(-5).match(/\d+:\d\d/)) {
dateString += ' 12:00';
}
return dateString;
};
//Parses a string to a Moment
var parseDateString = function (value) {
// Parses a string to a Moment
parseDateString = function (value) {
return value ? moment(verifyTimeStamp(value), parseDateFormats, true) : undefined;
};
//Formats a Date or Moment
var formatDate = function (value) {
// Formats a Date or Moment
formatDate = function (value) {
return verifyTimeStamp(value ? moment(value).format(displayDateFormat) : '');
};
export {parseDateString, formatDate};
export {parseDateString, formatDate};

View File

@ -1,4 +1,4 @@
// This is used by the dropdown initializer (and subsequently popovers) to manage closing & toggeling
// This is used by the dropdown initializer (and subsequently popovers) to manage closing & toggling
import BodyEventListener from 'ghost/mixins/body-event-listener';
var DropdownService = Ember.Object.extend(Ember.Evented, BodyEventListener, {
@ -14,4 +14,4 @@ var DropdownService = Ember.Object.extend(Ember.Evented, BodyEventListener, {
}
});
export default DropdownService;
export default DropdownService;

View File

@ -1,18 +1,14 @@
var shortcuts = {},
ctrlOrCmd = navigator.userAgent.indexOf('Mac') !== -1 ? 'command' : 'ctrl';
//
//General editor shortcuts
//
// General editor shortcuts
shortcuts[ctrlOrCmd + '+s'] = 'save';
shortcuts[ctrlOrCmd + '+alt+p'] = 'publish';
shortcuts['alt+shift+z'] = 'toggleZenMode';
//
//CodeMirror Markdown Shortcuts
//
// CodeMirror Markdown Shortcuts
//Text
// Text
shortcuts['ctrl+alt+u'] = {action: 'codeMirrorShortcut', options: {type: 'strike'}};
shortcuts[ctrlOrCmd + '+b'] = {action: 'codeMirrorShortcut', options: {type: 'bold'}};
shortcuts[ctrlOrCmd + '+i'] = {action: 'codeMirrorShortcut', options: {type: 'italic'}};
@ -23,11 +19,11 @@ shortcuts['ctrl+alt+shift+u'] = {action: 'codeMirrorShortcut', options: {type: '
shortcuts[ctrlOrCmd + '+shift+c'] = {action: 'codeMirrorShortcut', options: {type: 'copyHTML'}};
shortcuts[ctrlOrCmd + '+h'] = {action: 'codeMirrorShortcut', options: {type: 'cycleHeaderLevel'}};
//Formatting
// Formatting
shortcuts['ctrl+q'] = {action: 'codeMirrorShortcut', options: {type: 'blockquote'}};
shortcuts['ctrl+l'] = {action: 'codeMirrorShortcut', options: {type: 'list'}};
//Insert content
// Insert content
shortcuts['ctrl+shift+1'] = {action: 'codeMirrorShortcut', options: {type: 'currentDate'}};
shortcuts[ctrlOrCmd + '+k'] = {action: 'codeMirrorShortcut', options: {type: 'link'}};
shortcuts[ctrlOrCmd + '+shift+i'] = {action: 'codeMirrorShortcut', options: {type: 'image'}};

View File

@ -9,7 +9,6 @@ var makeRoute = function (root, args) {
return route;
};
function ghostPaths() {
var path = window.location.pathname,
subdir = path.substr(0, path.search('/ghost/')),

View File

@ -16,8 +16,7 @@ var Notifications = Ember.ArrayProxy.extend({
if (object.get('location') === '') {
object.set('location', 'bottom');
}
}
else {
} else {
if (!object.location) {
object.location = 'bottom';
}
@ -56,7 +55,7 @@ var Notifications = Ember.ArrayProxy.extend({
}
for (var i = 0; i < errors.length; i += 1) {
this.showError(errors[i].message || errors[i], { doNotClosePassive: true });
this.showError(errors[i].message || errors[i], {doNotClosePassive: true});
}
},
showAPIError: function (resp, options) {
@ -75,7 +74,7 @@ var Notifications = Ember.ArrayProxy.extend({
} else if (resp && resp.jqXHR && resp.jqXHR.responseJSON && resp.jqXHR.responseJSON.message) {
this.showError(resp.jqXHR.responseJSON.message, options);
} else {
this.showError(options.defaultErrorText, { doNotClosePassive: true });
this.showError(options.defaultErrorText, {doNotClosePassive: true});
}
},
showInfo: function (message, options) {
@ -102,7 +101,6 @@ var Notifications = Ember.ArrayProxy.extend({
message: message
}, options.delayed);
},
// @Todo this function isn't referenced anywhere. Should it be removed?
showWarn: function (message, options) {
options = options || {};

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