Remove ObjectController proxying behavior.

Ember.ObjectController (and Ember.ArrayController) will be deprecated in
Ember 1.11 (and removed from core in Ember 2.0). The reasoning is
detailed in the Ember 2.0 RFC.

This PR does the following:

* Updates templates/controllers/views to explicitly reference model
  properties (instead of relying on proxying behavior).
* Clearly delineate where certain properties are being set or retrieved
  from (for example it was not clear exactly where `scratch` and
  `titleScratch` were stored).
* Remove usage of `Ember.ObjectController`.
* Add JSCS rule to prevent future PR's from adding regressions.
This commit is contained in:
Robert Jackson 2014-12-29 21:11:24 -05:00
parent 25d90c01c9
commit a1ed9adf92
No known key found for this signature in database
GPG Key ID: B3D10EF8171F7219
38 changed files with 219 additions and 170 deletions

View File

@ -1,4 +1,5 @@
{
"additionalRules": [ "core/test/utils/jscs-rules/*.js" ],
"requireCurlyBraces": [
"if",
"else",

View File

@ -203,13 +203,15 @@ var _ = require('lodash'),
client: {
options: {
config: '.jscsrc',
esnext: true
esnext: true,
disallowObjectController: true
}
},
clientTests: {
options: {
config: '.jscsrc',
esnext: true
esnext: true,
disallowObjectController: true
}
},
test: {

View File

@ -1,5 +1,5 @@
import EditorControllerMixin from 'ghost/mixins/editor-base-controller';
var EditorEditController = Ember.ObjectController.extend(EditorControllerMixin);
var EditorEditController = Ember.Controller.extend(EditorControllerMixin);
export default EditorEditController;

View File

@ -1,6 +1,6 @@
import EditorControllerMixin from 'ghost/mixins/editor-base-controller';
var EditorNewController = Ember.ObjectController.extend(EditorControllerMixin, {
var EditorNewController = Ember.Controller.extend(EditorControllerMixin, {
actions: {
/**
* Redirect to editor after the first save

View File

@ -1,6 +1,6 @@
var DeleteTagController = Ember.ObjectController.extend({
postInflection: Ember.computed('post_count', function () {
return this.get('post_count') > 1 ? 'posts' : 'post';
var DeleteTagController = Ember.Controller.extend({
postInflection: Ember.computed('model.post_count', function () {
return this.get('model.post_count') > 1 ? 'posts' : 'post';
}),
actions: {

View File

@ -1,8 +1,8 @@
var DeleteUserController = Ember.ObjectController.extend({
userPostCount: Ember.computed('id', function () {
var DeleteUserController = Ember.Controller.extend({
userPostCount: Ember.computed('model.id', function () {
var promise,
query = {
author: this.get('slug'),
author: this.get('model.slug'),
status: 'all'
};

View File

@ -5,21 +5,23 @@ import SlugGenerator from 'ghost/models/slug-generator';
import boundOneWay from 'ghost/utils/bound-one-way';
import isNumber from 'ghost/utils/isNumber';
var PostSettingsMenuController = Ember.ObjectController.extend(SettingsMenuMixin, {
var PostSettingsMenuController = Ember.Controller.extend(SettingsMenuMixin, {
debounceId: null,
lastPromise: null,
selectedAuthor: null,
uploaderReference: null,
initializeSelectedAuthor: function () {
var self = this;
return this.get('author').then(function (author) {
return this.get('model.author').then(function (author) {
self.set('selectedAuthor', author);
return author;
});
}.observes('model'),
changeAuthor: function () {
var author = this.get('author'),
var author = this.get('model.author'),
selectedAuthor = this.get('selectedAuthor'),
model = this.get('model'),
self = this;
@ -32,7 +34,7 @@ var PostSettingsMenuController = Ember.ObjectController.extend(SettingsMenuMixin
model.set('author', selectedAuthor);
// if this is a new post (never been saved before), don't try to save it
if (this.get('isNew')) {
if (this.get('model.isNew')) {
return;
}
@ -61,8 +63,8 @@ var PostSettingsMenuController = Ember.ObjectController.extend(SettingsMenuMixin
}),
/*jshint unused:false */
publishedAtValue: Ember.computed('published_at', function (key, value) {
var pubDate = this.get('published_at');
publishedAtValue: Ember.computed('model.published_at', function (key, value) {
var pubDate = this.get('model.published_at');
// We're using a fake setter to reset
// the cache for this property
@ -78,7 +80,7 @@ var PostSettingsMenuController = Ember.ObjectController.extend(SettingsMenuMixin
}),
/*jshint unused:true */
slugValue: boundOneWay('slug'),
slugValue: boundOneWay('model.slug'),
// Lazy load the slug generator
slugGenerator: Ember.computed(function () {
@ -91,12 +93,12 @@ var PostSettingsMenuController = Ember.ObjectController.extend(SettingsMenuMixin
// Requests slug from title
generateAndSetSlug: function (destination) {
var self = this,
title = this.get('titleScratch'),
title = this.get('model.titleScratch'),
afterSave = this.get('lastPromise'),
promise;
// Only set an "untitled" slug once per post
if (title === '(Untitled)' && this.get('slug')) {
if (title === '(Untitled)' && this.get('model.slug')) {
return;
}
@ -113,13 +115,13 @@ var PostSettingsMenuController = Ember.ObjectController.extend(SettingsMenuMixin
this.set('lastPromise', promise);
},
metaTitleScratch: boundOneWay('meta_title'),
metaDescriptionScratch: boundOneWay('meta_description'),
metaTitleScratch: boundOneWay('model.meta_title'),
metaDescriptionScratch: boundOneWay('model.meta_description'),
seoTitle: Ember.computed('titleScratch', 'metaTitleScratch', function () {
seoTitle: Ember.computed('model.titleScratch', 'metaTitleScratch', function () {
var metaTitle = this.get('metaTitleScratch') || '';
metaTitle = metaTitle.length > 0 ? metaTitle : this.get('titleScratch');
metaTitle = metaTitle.length > 0 ? metaTitle : this.get('model.titleScratch');
if (metaTitle.length > 70) {
metaTitle = metaTitle.substring(0, 70).trim();
@ -130,7 +132,7 @@ var PostSettingsMenuController = Ember.ObjectController.extend(SettingsMenuMixin
return metaTitle;
}),
seoDescription: Ember.computed('scratch', 'metaDescriptionScratch', function () {
seoDescription: Ember.computed('model.scratch', 'metaDescriptionScratch', function () {
var metaDescription = this.get('metaDescriptionScratch') || '',
el,
html = '',
@ -166,9 +168,9 @@ var PostSettingsMenuController = Ember.ObjectController.extend(SettingsMenuMixin
return placeholder;
}),
seoURL: Ember.computed('slug', function () {
seoURL: Ember.computed('model.slug', function () {
var blogUrl = this.get('config').blogUrl,
seoSlug = this.get('slug') ? this.get('slug') : '',
seoSlug = this.get('model.slug') ? this.get('model.slug') : '',
seoURL = blogUrl + '/' + seoSlug;
// only append a slash to the URL if the slug exists
@ -187,18 +189,18 @@ var PostSettingsMenuController = Ember.ObjectController.extend(SettingsMenuMixin
// observe titleScratch, keeping the post's slug in sync
// with it until saved for the first time.
addTitleObserver: function () {
if (this.get('isNew') || this.get('title') === '(Untitled)') {
this.addObserver('titleScratch', this, 'titleObserver');
if (this.get('model.isNew') || this.get('model.title') === '(Untitled)') {
this.addObserver('model.titleScratch', this, 'titleObserver');
}
}.observes('model'),
titleObserver: function () {
var debounceId,
title = this.get('title');
title = this.get('model.title');
// 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)') {
if ((this.get('model.isNew') && !title) || title === '(Untitled)') {
debounceId = Ember.run.debounce(this, 'generateAndSetSlug', ['slug'], 700);
}
@ -218,10 +220,10 @@ var PostSettingsMenuController = Ember.ObjectController.extend(SettingsMenuMixin
togglePage: function () {
var self = this;
this.toggleProperty('page');
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('isNew')) {
if (this.get('model.isNew')) {
return;
}
@ -234,11 +236,11 @@ var PostSettingsMenuController = Ember.ObjectController.extend(SettingsMenuMixin
toggleFeatured: function () {
var self = this;
this.toggleProperty('featured');
this.toggleProperty('model.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')) {
if (this.get('model.isNew')) {
return;
}
@ -252,7 +254,7 @@ var PostSettingsMenuController = Ember.ObjectController.extend(SettingsMenuMixin
* triggered by user manually changing slug
*/
updateSlug: function (newSlug) {
var slug = this.get('slug'),
var slug = this.get('model.slug'),
self = this;
newSlug = newSlug || slug;
@ -294,15 +296,15 @@ var PostSettingsMenuController = Ember.ObjectController.extend(SettingsMenuMixin
}
}
self.set('slug', serverSlug);
self.set('model.slug', serverSlug);
if (self.hasObserverFor('titleScratch')) {
self.removeObserver('titleScratch', self, 'titleObserver');
if (self.hasObserverFor('model.titleScratch')) {
self.removeObserver('model.titleScratch', self, '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('isNew')) {
if (self.get('model.isNew')) {
return;
}
@ -321,13 +323,13 @@ var PostSettingsMenuController = Ember.ObjectController.extend(SettingsMenuMixin
setPublishedAt: function (userInput) {
var errMessage = '',
newPublishedAt = parseDateString(userInput),
publishedAt = this.get('published_at'),
publishedAt = this.get('model.published_at'),
self = this;
if (!userInput) {
// Clear out the published_at field for a draft
if (this.get('isDraft')) {
this.set('published_at', null);
if (this.get('model.isDraft')) {
this.set('model.published_at', null);
}
return;
@ -355,11 +357,11 @@ var PostSettingsMenuController = Ember.ObjectController.extend(SettingsMenuMixin
}
// Validation complete
this.set('published_at', newPublishedAt);
this.set('model.published_at', newPublishedAt);
// 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')) {
if (this.get('model.isNew')) {
return;
}
@ -371,18 +373,18 @@ var PostSettingsMenuController = Ember.ObjectController.extend(SettingsMenuMixin
setMetaTitle: function (metaTitle) {
var self = this,
currentTitle = this.get('meta_title') || '';
currentTitle = this.get('model.meta_title') || '';
// Only update if the title has changed
if (currentTitle === metaTitle) {
return;
}
this.set('meta_title', metaTitle);
this.set('model.meta_title', metaTitle);
// 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')) {
if (this.get('model.isNew')) {
return;
}
@ -393,18 +395,18 @@ var PostSettingsMenuController = Ember.ObjectController.extend(SettingsMenuMixin
setMetaDescription: function (metaDescription) {
var self = this,
currentDescription = this.get('meta_description') || '';
currentDescription = this.get('model.meta_description') || '';
// Only update if the description has changed
if (currentDescription === metaDescription) {
return;
}
this.set('meta_description', metaDescription);
this.set('model.meta_description', metaDescription);
// 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')) {
if (this.get('model.isNew')) {
return;
}
@ -416,9 +418,9 @@ var PostSettingsMenuController = Ember.ObjectController.extend(SettingsMenuMixin
setCoverImage: function (image) {
var self = this;
this.set('image', image);
this.set('model.image', image);
if (this.get('isNew')) {
if (this.get('model.isNew')) {
return;
}
@ -431,9 +433,9 @@ var PostSettingsMenuController = Ember.ObjectController.extend(SettingsMenuMixin
clearCoverImage: function () {
var self = this;
this.set('image', '');
this.set('model.image', '');
if (this.get('isNew')) {
if (this.get('model.isNew')) {
return;
}

View File

@ -1,9 +1,9 @@
var PostTagsInputController = Ember.Controller.extend({
tagEnteredOrder: Ember.A(),
tags: Ember.computed('parentController.tags', function () {
tags: Ember.computed('parentController.model.tags', function () {
var proxyTags = Ember.ArrayProxy.create({
content: this.get('parentController.tags')
content: this.get('parentController.model.tags')
}),
temp = proxyTags.get('arrangedContent').slice();

View File

@ -1,13 +1,13 @@
var PostController = Ember.ObjectController.extend({
isPublished: Ember.computed.equal('status', 'published'),
classNameBindings: ['featured'],
var PostController = Ember.Controller.extend({
isPublished: Ember.computed.equal('model.status', 'published'),
classNameBindings: ['model.featured'],
actions: {
toggleFeatured: function () {
var options = {disableNProgress: true},
self = this;
this.toggleProperty('featured');
this.toggleProperty('model.featured');
this.get('model').save(options).catch(function (errors) {
self.notifications.showErrors(errors);
});

View File

@ -9,7 +9,7 @@ appStates = {
inactive: 'inactive'
};
SettingsAppController = Ember.ObjectController.extend({
SettingsAppController = Ember.Controller.extend({
appState: appStates.active,
buttonText: '',

View File

@ -1,4 +1,4 @@
var SettingsCodeInjectionController = Ember.ObjectController.extend({
var SettingsCodeInjectionController = Ember.Controller.extend({
actions: {
save: function () {
var self = this;

View File

@ -1,18 +1,20 @@
var SettingsGeneralController = Ember.ObjectController.extend({
isDatedPermalinks: Ember.computed('permalinks', function (key, value) {
var SettingsGeneralController = Ember.Controller.extend({
selectedTheme: null,
isDatedPermalinks: Ember.computed('model.permalinks', function (key, value) {
// setter
if (arguments.length > 1) {
this.set('permalinks', value ? '/:year/:month/:day/:slug/' : '/:slug/');
this.set('model.permalinks', value ? '/:year/:month/:day/:slug/' : '/:slug/');
}
// getter
var slugForm = this.get('permalinks');
var slugForm = this.get('model.permalinks');
return slugForm !== '/:slug/';
}),
themes: Ember.computed(function () {
return this.get('availableThemes').reduce(function (themes, t) {
return this.get('model.availableThemes').reduce(function (themes, t) {
var theme = {};
theme.name = t.name;
@ -40,8 +42,10 @@ var SettingsGeneralController = Ember.ObjectController.extend({
},
checkPostsPerPage: function () {
if (this.get('postsPerPage') < 1 || this.get('postsPerPage') > 1000 || isNaN(this.get('postsPerPage'))) {
this.set('postsPerPage', 5);
var postsPerPage = this.get('model.postsPerPage');
if (postsPerPage < 1 || postsPerPage > 1000 || isNaN(postsPerPage)) {
this.set('model.postsPerPage', 5);
}
}
}

View File

@ -2,13 +2,13 @@ import SlugGenerator from 'ghost/models/slug-generator';
import isNumber from 'ghost/utils/isNumber';
import boundOneWay from 'ghost/utils/bound-one-way';
var SettingsUserController = Ember.ObjectController.extend({
var SettingsUserController = Ember.Controller.extend({
user: Ember.computed.alias('model'),
email: Ember.computed.readOnly('user.email'),
email: Ember.computed.readOnly('model.email'),
slugValue: boundOneWay('user.slug'),
slugValue: boundOneWay('model.slug'),
lastPromise: null,
@ -74,7 +74,7 @@ var SettingsUserController = Ember.ObjectController.extend({
// reload the model to get the most up-to-date user information
model.reload().then(function () {
if (self.get('invited')) {
if (model.get('invited')) {
model.destroyRecord().then(function () {
var notificationText = 'Invitation revoked. (' + email + ')';
self.notifications.showSuccess(notificationText, false);
@ -178,7 +178,7 @@ var SettingsUserController = Ember.ObjectController.extend({
promise;
promise = Ember.RSVP.resolve(afterSave).then(function () {
var slug = self.get('slug');
var slug = self.get('model.slug');
newSlug = newSlug || slug;

View File

@ -1,7 +1,7 @@
import ajax from 'ghost/utils/ajax';
import ValidationEngine from 'ghost/mixins/validation-engine';
var SetupController = Ember.ObjectController.extend(ValidationEngine, {
var SetupController = Ember.Controller.extend(ValidationEngine, {
blogTitle: null,
name: null,
email: null,

View File

@ -1,7 +1,7 @@
import ajax from 'ghost/utils/ajax';
import ValidationEngine from 'ghost/mixins/validation-engine';
var SignupController = Ember.ObjectController.extend(ValidationEngine, {
var SignupController = Ember.Controller.extend(ValidationEngine, {
submitting: false,
// ValidationEngine settings
@ -10,7 +10,7 @@ var SignupController = Ember.ObjectController.extend(ValidationEngine, {
actions: {
signup: function () {
var self = this,
data = self.getProperties('name', 'email', 'password', 'token');
data = self.getProperties('model.name', 'model.email', 'model.password', 'model.token');
self.notifications.closePassive();
@ -30,8 +30,8 @@ var SignupController = Ember.ObjectController.extend(ValidationEngine, {
}
}).then(function () {
self.get('session').authenticate('simple-auth-authenticator:oauth2-password-grant', {
identification: self.get('email'),
password: self.get('password')
identification: self.get('model.email'),
password: self.get('model.password')
});
}, function (resp) {
self.toggleProperty('submitting');

View File

@ -8,7 +8,7 @@ var watchedProps,
// this array will hold properties we need to watch
// to know if the model has been changed (`controller.isDirty`)
watchedProps = ['scratch', 'titleScratch', 'model.isDirty', 'tags.[]'];
watchedProps = ['model.scratch', 'model.titleScratch', 'model.isDirty', 'model.tags.[]'];
PostModel.eachAttribute(function (name) {
watchedProps.push('model.' + name);
@ -17,6 +17,11 @@ PostModel.eachAttribute(function (name) {
EditorControllerMixin = Ember.Mixin.create(MarkerManager, {
needs: ['post-tags-input', 'post-settings-menu'],
autoSaveId: null,
timedSaveId: null,
codemirror: null,
codemirrorComponent: null,
init: function () {
var self = this;
@ -32,7 +37,7 @@ EditorControllerMixin = Ember.Mixin.create(MarkerManager, {
* Only with a user-set value (via setSaveType action)
* can the post's status change.
*/
willPublish: boundOneWay('isPublished'),
willPublish: boundOneWay('model.isPublished'),
// Make sure editor starts with markdown shown
isPreview: false,
@ -41,8 +46,8 @@ EditorControllerMixin = Ember.Mixin.create(MarkerManager, {
// whether the number of tags has changed for `isDirty`.
previousTagNames: null,
tagNames: Ember.computed('tags.@each.name', function () {
return this.get('tags').mapBy('name');
tagNames: Ember.computed('model.tags.@each.name', function () {
return this.get('model.tags').mapBy('name');
}),
// compares previousTagNames to tagNames
@ -82,8 +87,8 @@ EditorControllerMixin = Ember.Mixin.create(MarkerManager, {
// if the two "scratch" properties (title and content) match the model, then
// it's ok to set isDirty to false
if (this.get('titleScratch') === model.get('title') &&
this.get('scratch') === model.get('markdown')) {
if (model.get('titleScratch') === model.get('title') &&
model.get('scratch') === model.get('markdown')) {
this.set('isDirty', false);
}
},
@ -96,9 +101,9 @@ EditorControllerMixin = Ember.Mixin.create(MarkerManager, {
}
var model = this.get('model'),
markdown = this.get('markdown'),
title = this.get('title'),
titleScratch = this.get('titleScratch'),
markdown = model.get('markdown'),
title = model.get('title'),
titleScratch = model.get('titleScratch'),
scratch = this.getMarkdown().withoutMarkers,
changedAttributes;
@ -183,7 +188,7 @@ EditorControllerMixin = Ember.Mixin.create(MarkerManager, {
showSaveNotification: function (prevStatus, status, delay) {
var message = this.messageMap.success.post[prevStatus][status],
path = this.get('ghostPaths.url').join(this.get('config.blogUrl'), this.get('url'));
path = this.get('ghostPaths.url').join(this.get('config.blogUrl'), this.get('model.url'));
if (status === 'published') {
message += '&nbsp;<a href="' + path + '">View Post</a>';
@ -206,8 +211,8 @@ EditorControllerMixin = Ember.Mixin.create(MarkerManager, {
actions: {
save: function (options) {
var status = this.get('willPublish') ? 'published' : 'draft',
prevStatus = this.get('status'),
isNew = this.get('isNew'),
prevStatus = this.get('model.status'),
isNew = this.get('model.isNew'),
autoSaveId = this.get('autoSaveId'),
timedSaveId = this.get('timedSaveId'),
self = this,
@ -233,24 +238,24 @@ EditorControllerMixin = Ember.Mixin.create(MarkerManager, {
// Set the properties that are indirected
// set markdown equal to what's in the editor, minus the image markers.
this.set('markdown', this.getMarkdown().withoutMarkers);
this.set('status', status);
this.set('model.markdown', this.getMarkdown().withoutMarkers);
this.set('model.status', status);
// Set a default title
if (!this.get('titleScratch').trim()) {
this.set('titleScratch', '(Untitled)');
if (!this.get('model.titleScratch').trim()) {
this.set('model.titleScratch', '(Untitled)');
}
this.set('title', this.get('titleScratch'));
this.set('meta_title', psmController.get('metaTitleScratch'));
this.set('meta_description', psmController.get('metaDescriptionScratch'));
this.set('model.title', this.get('model.titleScratch'));
this.set('model.meta_title', psmController.get('metaTitleScratch'));
this.set('model.meta_description', psmController.get('metaDescriptionScratch'));
if (!this.get('slug')) {
if (!this.get('model.slug')) {
// Cancel any pending slug generation that may still be queued in the
// run loop because we need to run it before the post is saved.
Ember.run.cancel(psmController.get('debounceId'));
psmController.generateAndSetSlug('slug');
psmController.generateAndSetSlug('model.slug');
}
promise = Ember.RSVP.resolve(psmController.get('lastPromise')).then(function () {
@ -263,10 +268,10 @@ EditorControllerMixin = Ember.Mixin.create(MarkerManager, {
});
}).catch(function (errors) {
if (!options.silent) {
self.showErrorNotification(prevStatus, self.get('status'), errors);
self.showErrorNotification(prevStatus, self.get('model.status'), errors);
}
self.set('status', prevStatus);
self.set('model.status', prevStatus);
return self.get('model');
});
@ -360,7 +365,7 @@ EditorControllerMixin = Ember.Mixin.create(MarkerManager, {
},
autoSaveNew: function () {
if (this.get('isNew')) {
if (this.get('model.isNew')) {
this.send('save', {silent: true, disableNProgress: true});
}
}

View File

@ -32,7 +32,7 @@ var EditorBaseRoute = Ember.Mixin.create(styleBody, ShortcutsRoute, loadingIndic
willTransition: function (transition) {
var controller = this.get('controller'),
scratch = controller.get('scratch'),
scratch = controller.get('model.scratch'),
controllerIsDirty = controller.get('isDirty'),
model = controller.get('model'),
state = model.getProperties('isDeleted', 'isSaving', 'isDirty', 'isNew'),
@ -112,9 +112,9 @@ var EditorBaseRoute = Ember.Mixin.create(styleBody, ShortcutsRoute, loadingIndic
this._super(controller, model);
var tags = model.get('tags');
controller.set('scratch', model.get('markdown'));
controller.set('model.scratch', model.get('markdown'));
controller.set('titleScratch', model.get('title'));
controller.set('model.titleScratch', model.get('title'));
if (tags) {
// used to check if anything has changed in the editor

View File

@ -27,6 +27,9 @@ var Post = DS.Model.extend(NProgressSaveMixin, ValidationEngine, {
tags: DS.hasMany('tag', {embedded: 'always'}),
url: DS.attr('string'),
scratch: null,
titleScratch: null,
// Computed post properties
isPublished: Ember.computed.equal('status', 'published'),

View File

@ -4,7 +4,7 @@
<div class="publish-bar-actions">
<button type="button" class="post-settings" {{action "toggleSettingsMenu"}}></button>
{{view "editor-save-button" id="entry-actions" classNameBindings="isNew:unsaved"}}
{{view "editor-save-button" id="entry-actions" classNameBindings="model.isNew:unsaved"}}
</div>
</div>
</footer>

View File

@ -6,7 +6,7 @@
<div class="page-content">
<header>
<section class="box entry-title">
{{gh-trim-focus-input type="text" id="entry-title" placeholder="Your Post Title" value=titleScratch
{{gh-trim-focus-input type="text" id="entry-title" placeholder="Your Post Title" value=model.titleScratch
tabindex="1" focus=shouldFocusTitle}}
</section>
</header>
@ -17,7 +17,7 @@
<a class="markdown-help" href="" {{action "openModal" "markdown"}}><span class="hidden">What is Markdown?</span></a>
</header>
<section id="entry-markdown-content" class="entry-markdown-content">
{{gh-codemirror value=scratch scrollInfo=view.markdownScrollInfo
{{gh-codemirror value=model.scratch scrollInfo=view.markdownScrollInfo
setCodeMirror="setCodeMirror" openModal="openModal" typingPause="autoSave"
focus=shouldFocusEditor focusCursorAtEnd=model.isDirty onFocusIn="autoSaveNew"}}
</section>
@ -25,11 +25,11 @@
<section {{bind-attr class=":entry-preview :js-entry-preview isPreview:active"}}>
<header {{action "togglePreview" true}} class="floatingheader">
<small>Preview <span class="entry-word-count js-entry-word-count">{{gh-count-words scratch}}</span></small>
<small>Preview <span class="entry-word-count js-entry-word-count">{{gh-count-words model.scratch}}</span></small>
</header>
<section class="entry-preview-content js-entry-preview-content">
{{gh-markdown classNames="rendered-markdown js-rendered-markdown"
markdown=scratch scrollPosition=view.scrollPosition
markdown=model.scratch scrollPosition=view.scrollPosition
uploadStarted="disableCodeMirror" uploadFinished="enableCodeMirror" uploadSuccess="handleImgUpload"}}
</section>
</section>

View File

@ -1,9 +1,9 @@
{{#gh-modal-dialog action="closeModal" showClose=true type="action" style="wide"
title="Are you sure you want to delete this tag?" confirm=confirm}}
{{#if post_count}}
<strong>WARNING:</strong> <span class="red">This tag is attached to {{post_count}} {{postInflection}}.</span> You're about to delete "<strong>{{name}}</strong>". This is permanent! No backups, no restores, no magic undo button. We warned you, ok?
{{#if model.post_count}}
<strong>WARNING:</strong> <span class="red">This tag is attached to {{model.post_count}} {{postInflection}}.</span> You're about to delete "<strong>{{model.name}}</strong>". This is permanent! No backups, no restores, no magic undo button. We warned you, ok?
{{else}}
<strong>WARNING:</strong> You're about to delete "<strong>{{name}}</strong>". This is permanent! No backups, no restores, no magic undo button. We warned you, ok?
<strong>WARNING:</strong> You're about to delete "<strong>{{model.name}}</strong>". This is permanent! No backups, no restores, no magic undo button. We warned you, ok?
{{/if}}
{{/gh-modal-dialog}}

View File

@ -7,7 +7,7 @@
<button class="close icon-x settings-menu-header-action" {{action "closeSettingsMenu"}}><span class="hidden">Close</span></button>
</div>
<div class="settings-menu-content">
{{gh-uploader uploaded="setCoverImage" canceled="clearCoverImage" description="Add post image" image=image uploaderReference=uploaderReference tagName="section"}}
{{gh-uploader uploaded="setCoverImage" canceled="clearCoverImage" description="Add post image" image=model.image uploaderReference=uploaderReference tagName="section"}}
<form>
<div class="form-group">
<label for="url">Post URL</label>
@ -52,13 +52,13 @@
<div class="form-group for-checkbox">
<label class="checkbox" for="static-page" {{action "togglePage" bubbles="false"}}>
{{input type="checkbox" name="static-page" id="static-page" class="post-setting-static-page" checked=page}}
{{input type="checkbox" name="static-page" id="static-page" class="post-setting-static-page" checked=model.page}}
<span class="input-toggle-component"></span>
<p>Turn this post into a static page</p>
</label>
<label class="checkbox" for="featured" {{action "toggleFeatured" bubbles="false"}}>
{{input type="checkbox" name="featured" id="featured" class="post-setting-featured" checked=featured}}
{{input type="checkbox" name="featured" id="featured" class="post-setting-featured" checked=model.featured}}
<span class="input-toggle-component"></span>
<p>Feature this post</p>
</label>

View File

@ -15,15 +15,15 @@
<ol class="posts-list">
{{#each post in model itemController="posts/post" itemView="post-item-view" itemTagName="li"}}
{{#link-to "posts.post" post class="permalink" alternateActive=view.active title="Edit this post"}}
<h3 class="entry-title">{{post.title}}</h3>
<h3 class="entry-title">{{post.model.title}}</h3>
<section class="entry-meta">
<span class="status">
{{#if post.isPublished}}
{{#if post.page}}
{{#if post.model.page}}
<span class="page">Page</span>
{{else}}
<time datetime="{{unbound post.published_at}}" class="date published">
Published {{gh-format-timeago post.published_at}}
<time datetime="{{unbound post.model.published_at}}" class="date published">
Published {{gh-format-timeago post.model.published_at}}
</time>
{{/if}}
{{else}}

View File

@ -1,13 +1,13 @@
<header class="post-preview-header clearfix">
{{#link-to "posts" tagName="button" class="btn btn-default btn-back"}}Back{{/link-to}}
<h2 class="page-title">Preview</h2>
<button type="button" {{bind-attr class="featured:featured:unfeatured"}} title="Feature this post" {{action "toggleFeatured"}}>
<button type="button" {{bind-attr class="model.featured:featured:unfeatured"}} title="Feature this post" {{action "toggleFeatured"}}>
<span class="hidden">Star</span>
</button>
<small class="post-published-by">
<span class="status">{{#if isPublished}}Published{{else}}Written{{/if}}</span>
<span class="normal">by</span>
<span class="author">{{#if author.name}}{{author.name}}{{else}}{{author.email}}{{/if}}</span>
<span class="author">{{#if model.author.name}}{{model.author.name}}{{else}}{{model.author.email}}{{/if}}</span>
</small>
<section class="post-controls">
{{#link-to "editor.edit" this class="btn btn-default post-edit"}} Edit{{/link-to}}
@ -16,7 +16,7 @@
{{#view "content-preview-content-view" tagName="section"}}
<div class="wrapper">
<h1>{{title}}</h1>
{{gh-format-html html}}
<h1>{{model.title}}</h1>
{{gh-format-html model.html}}
</div>
{{/view}}

View File

@ -9,7 +9,7 @@
<span class="ghost_logo">
<span class="hidden">Ghost</span>
</span>
<span class="version blue">v{{version}}</span>
<span class="version blue">v{{model.version}}</span>
</h1>
<p>A free, open, simple publishing platform</p>
@ -17,13 +17,13 @@
<div class="about-environment">
<dl>
<dt>Version:</dt>
<dd class="about-environment-detail">{{version}}</dd>
<dd class="about-environment-detail">{{model.version}}</dd>
<dt>Environment:</dt>
<dd class="about-environment-detail">{{environment}}</dd>
<dd class="about-environment-detail">{{model.environment}}</dd>
<dt>Database:</dt>
<dd class="about-environment-detail about-environment-database">{{database}}</dd>
<dd class="about-environment-detail about-environment-database">{{model.database}}</dd>
<dt>Mail:</dt>
<dd class="about-environment-detail">{{#if mail}}{{mail}}{{else}}Native{{/if}}</dd>
<dd class="about-environment-detail">{{#if model.mail}}{{model.mail}}{{else}}Native{{/if}}</dd>
</dl>
</div>
<div class="about-help">

View File

@ -10,14 +10,14 @@
<th>Status</th>
</thead>
<tbody>
{{#each app in model itemController="settings/app"}}
{{#each appController in model itemController="settings/app"}}
<tr>
<td>
{{#if app.package}}{{app.package.name}} - {{app.package.version}}{{else}}{{app.name}} - package.json missing :({{/if}}
{{#if appController.model.package}}{{appController.model.package.name}} - {{appController.model.package.version}}{{else}}{{appController.model.name}} - package.json missing :({{/if}}
</td>
<td>
<button type="button" {{action toggleApp app}} {{bind-attr class=":btn :js-button-active activeClass:btn-red inactiveClass:btn-green activeClass:js-button-deactivate"}}>
{{app.buttonText}}
<button type="button" {{action toggleApp appController}} {{bind-attr class=":btn :js-button-active activeClass:btn-red inactiveClass:btn-green activeClass:js-button-deactivate"}}>
{{appController.buttonText}}
</button>
</td>
</tr>

View File

@ -18,13 +18,13 @@
<div class="form-group">
<label for="ghost-head">Blog Header</label>
<p>Code here will be injected to the \{{ghost_head}} helper at the top of your page</p>
{{textarea id="ghost-head" name="codeInjection[ghost_head]" type="text" value=ghost_head}}
{{textarea id="ghost-head" name="codeInjection[ghost_head]" type="text" value=model.ghost_head}}
</div>
<div class="form-group">
<label for="ghost-foot">Blog Footer</label>
<p>Code here will be injected to the \{{ghost_foot}} helper at the bottom of your page</p>
{{textarea id="ghost-foot" name="codeInjection[ghost_foot]" type="text" value=ghost_foot}}
{{textarea id="ghost-foot" name="codeInjection[ghost_foot]" type="text" value=model.ghost_foot}}
</div>
</fieldset>
</form>

View File

@ -12,16 +12,16 @@
<div class="form-group">
<label for="blog-title">Blog Title</label>
{{input id="blog-title" name="general[title]" type="text" value=title}}
{{input id="blog-title" name="general[title]" type="text" value=model.title}}
<p>The name of your blog</p>
</div>
<div class="form-group description-container">
<label for="blog-description">Blog Description</label>
{{textarea id="blog-description" name="general[description]" value=description}}
{{textarea id="blog-description" name="general[description]" value=model.description}}
<p>
Describe what your blog is about
{{gh-count-characters description}}
{{gh-count-characters model.description}}
</p>
</div>
@ -29,8 +29,8 @@
<div class="form-group">
<label for="blog-logo">Blog Logo</label>
{{#if logo}}
<button type="button" class="js-modal-logo" {{action "openModal" "upload" this "logo"}}><img id="blog-logo" {{bind-attr src=logo}} alt="logo"></button>
{{#if model.logo}}
<button type="button" class="js-modal-logo" {{action "openModal" "upload" this "logo"}}><img id="blog-logo" {{bind-attr src=model.logo}} alt="logo"></button>
{{else}}
<button type="button" class="btn btn-green js-modal-logo" {{action "openModal" "upload" this "logo"}}>Upload Image</button>
{{/if}}
@ -39,8 +39,8 @@
<div class="form-group">
<label for="blog-cover">Blog Cover</label>
{{#if cover}}
<button type="button" class="js-modal-cover" {{action "openModal" "upload" this "cover"}}><img id="blog-cover" {{bind-attr src=cover}} alt="cover photo"></button>
{{#if model.cover}}
<button type="button" class="js-modal-cover" {{action "openModal" "upload" this "cover"}}><img id="blog-cover" {{bind-attr src=model.cover}} alt="cover photo"></button>
{{else}}
<button type="button" class="btn btn-green js-modal-cover" {{action "openModal" "upload" this "cover"}}>Upload Image</button>
{{/if}}
@ -50,14 +50,14 @@
<fieldset>
<div class="form-group">
<label for="email-address">Email Address</label>
{{input id="email-address" name="general[email-address]" type="email" value=email autocapitalize="off" autocorrect="off"}}
{{input id="email-address" name="general[email-address]" type="email" value=model.email autocapitalize="off" autocorrect="off"}}
<p>Address to use for admin notifications</p>
</div>
<div class="form-group">
<label for="postsPerPage">Posts per page</label>
{{! `pattern` brings up numeric keypad allowing any number of digits}}
{{input id="postsPerPage" name="general[postsPerPage]" focus-out="checkPostsPerPage" value=postsPerPage min="1" max="1000" type="number" pattern="[0-9]*"}}
{{input id="postsPerPage" name="general[postsPerPage]" focus-out="checkPostsPerPage" value=model.postsPerPage min="1" max="1000" type="number" pattern="[0-9]*"}}
<p>How many posts should be displayed on each page</p>
</div>
@ -79,7 +79,7 @@
content=themes
optionValuePath="content.name"
optionLabelPath="content.label"
value=activeTheme
value=model.activeTheme
selection=selectedTheme}}
</span>
<p>Select a theme for your blog</p>

View File

@ -26,10 +26,10 @@
<div class="user-list-item-body">
<span class="name">{{user.email}}</span><br>
{{#if user.pending}}
{{#if user.model.pending}}
<span class="red">Invitation not sent - please try again</span>
{{else}}
<span class="description">Invitation sent: {{user.created_at}}</span>
<span class="description">Invitation sent: {{user.model.created_at}}</span>
{{/if}}
</div>
<aside class="user-list-item-aside">
@ -50,19 +50,19 @@
{{#each user in activeUsers itemController="settings/users/user"}}
{{#link-to 'settings.users.user' user class="user-list-item" }}
<span class="user-list-item-figure" {{bind-attr style=user.image}}>
<span class="hidden">Photo of {{unbound user.name}}</span>
<span class="hidden">Photo of {{unbound user.model.name}}</span>
</span>
<div class="user-list-item-body">
<span class="name">
{{user.name}}
{{user.model.name}}
</span>
<br>
<span class="description">Last seen: {{unbound user.last_login}}</span>
</div>
<aside class="user-list-item-aside">
{{#unless user.isAuthor}}
{{#each role in user.roles}}
{{#unless user.model.isAuthor}}
{{#each role in user.model.roles}}
<span class="role-label {{unbound role.lowerCaseName}}">{{role.name}}</span>
{{/each}}
{{/unless}}

View File

@ -35,7 +35,7 @@
<fieldset class="user-details-top">
<figure class="user-image">
<div id="user-image" class="img" {{bind-attr style=image}} href="#"><span class="hidden">{{name}}"s Picture</span></div>
<div id="user-image" class="img" {{bind-attr style=image}} href="#"><span class="hidden">{{user.name}}"s Picture</span></div>
<button type="button" {{action "openModal" "upload" user "image"}} class="edit-user-image js-modal-image">Edit Picture</button>
</figure>

View File

@ -12,17 +12,17 @@
</header>
<div class="form-group">
<label for="email">Email Address</label>
{{input type="email" name="email" autocorrect="off" value=email }}
{{input type="email" name="email" autocorrect="off" value=model.email }}
<p>Used for important notifications</p>
</div>
<div class="form-group">
<label for="name">Full Name</label>
{{gh-trim-focus-input type="text" name="name" autofocus="autofocus" autocorrect="off" value=name }}
{{gh-trim-focus-input type="text" name="name" autofocus="autofocus" autocorrect="off" value=model.name }}
<p>The name that you will sign your posts with</p>
</div>
<div class="form-group">
<label for="password">Password</label>
{{input type="password" name="password" autofocus="autofocus" autocorrect="off" value=password }}
{{input type="password" name="password" autofocus="autofocus" autocorrect="off" value=model.password }}
<p>Must be at least 8 characters</p>
</div>
<footer>

View File

@ -1,5 +1,5 @@
Ember.LinkView.reopen({
active: Ember.computed('resolvedParams', 'routeArgs', function () {
active: Ember.computed('loadedParams', 'resolvedParams', 'routeArgs', function () {
var isActive = this._super();
Ember.set(this, 'alternateActive', isActive);

View File

@ -4,16 +4,16 @@ var EditorSaveButtonView = Ember.View.extend({
classNames: ['splitbtn', 'js-publish-splitbutton'],
// Tracks whether we're going to change the state of the post on save
isDangerous: Ember.computed('controller.isPublished', 'controller.willPublish', function () {
return this.get('controller.isPublished') !== this.get('controller.willPublish');
isDangerous: Ember.computed('controller.model.isPublished', 'controller.willPublish', function () {
return this.get('controller.model.isPublished') !== this.get('controller.willPublish');
}),
publishText: Ember.computed('controller.isPublished', function () {
return this.get('controller.isPublished') ? 'Update Post' : 'Publish Now';
publishText: Ember.computed('controller.model.isPublished', function () {
return this.get('controller.model.isPublished') ? 'Update Post' : 'Publish Now';
}),
draftText: Ember.computed('controller.isPublished', function () {
return this.get('controller.isPublished') ? 'Unpublish' : 'Save Draft';
draftText: Ember.computed('controller.model.isPublished', function () {
return this.get('controller.model.isPublished') ? 'Unpublish' : 'Save Draft';
}),
saveText: Ember.computed('controller.willPublish', function () {

View File

@ -4,8 +4,6 @@ var PostTagsInputView = Ember.View.extend({
classNames: 'publish-bar-inner',
classNameBindings: ['hasFocus:focused'],
templateName: 'post-tags-input',
hasFocus: false,
keys: {

View File

@ -290,6 +290,8 @@ CasperTest.begin('Tag editor', 7, function suite(test) {
casper.thenClick('#entry-tags input.tag-input');
casper.then(function () {
casper.sendKeys('#entry-tags input.tag-input', tagName, {keepFocus: true});
});
casper.then(function () {
casper.sendKeys('#entry-tags input.tag-input', casper.page.event.key.Enter);
});

View File

@ -0,0 +1,32 @@
var assert = require('assert');
module.exports = function () {};
module.exports.prototype = {
configure: function (disallowObjectController) {
assert(
typeof disallowObjectController === 'boolean',
'disallowObjectController option requires boolean value'
);
assert(
disallowObjectController === true,
'disallowObjectController option requires true value or should be removed'
);
},
getOptionName: function () {
return 'disallowObjectController';
},
check: function (file, errors) {
var lines = file.getLines();
lines.forEach(function (line, index) {
var location = line.indexOf(/ObjectController.extend/);
if (location !== -1) {
errors.add('Ember.ObjectController is deprecated, please use Ember.Controller and access model properties directly.', index + 1, location + 1);
}
});
}
};