ESLint: Alias model in controllers

no issue
- https://github.com/ember-cli/eslint-plugin-ember/blob/HEAD/docs/rules/alias-model-in-controller.md
- replace `model` with a meaningful property name everywhere possible
- refactor `design` and `general` settings controllers to use a directly injected settings service rather than passing it in as a "model" to be more explicit
This commit is contained in:
Kevin Ansfield 2018-01-10 22:57:43 +00:00
parent a9fcddaf3f
commit 050f4d99b6
59 changed files with 649 additions and 606 deletions

View File

@ -1,7 +1,7 @@
import Component from '@ember/component';
import ValidationState from 'ghost-admin/mixins/validation-state';
import {alias, readOnly} from '@ember/object/computed';
import {computed} from '@ember/object';
import {readOnly} from '@ember/object/computed';
import {run} from '@ember/runloop';
export default Component.extend(ValidationState, {
@ -10,7 +10,6 @@ export default Component.extend(ValidationState, {
new: false,
model: alias('navItem'),
errors: readOnly('navItem.errors'),
errorClass: computed('hasError', function () {

View File

@ -25,25 +25,25 @@ export default Component.extend(SettingsMenuMixin, {
settings: service(),
ui: service(),
model: null,
post: null,
customExcerptScratch: alias('model.customExcerptScratch'),
codeinjectionFootScratch: alias('model.codeinjectionFootScratch'),
codeinjectionHeadScratch: alias('model.codeinjectionHeadScratch'),
metaDescriptionScratch: alias('model.metaDescriptionScratch'),
metaTitleScratch: alias('model.metaTitleScratch'),
ogDescriptionScratch: alias('model.ogDescriptionScratch'),
ogTitleScratch: alias('model.ogTitleScratch'),
twitterDescriptionScratch: alias('model.twitterDescriptionScratch'),
twitterTitleScratch: alias('model.twitterTitleScratch'),
slugValue: boundOneWay('model.slug'),
customExcerptScratch: alias('post.customExcerptScratch'),
codeinjectionFootScratch: alias('post.codeinjectionFootScratch'),
codeinjectionHeadScratch: alias('post.codeinjectionHeadScratch'),
metaDescriptionScratch: alias('post.metaDescriptionScratch'),
metaTitleScratch: alias('post.metaTitleScratch'),
ogDescriptionScratch: alias('post.ogDescriptionScratch'),
ogTitleScratch: alias('post.ogTitleScratch'),
twitterDescriptionScratch: alias('post.twitterDescriptionScratch'),
twitterTitleScratch: alias('post.twitterTitleScratch'),
slugValue: boundOneWay('post.slug'),
facebookDescription: or('ogDescriptionScratch', 'customExcerptScratch', 'seoDescription'),
facebookImage: or('model.ogImage', 'model.featureImage'),
facebookImage: or('post.ogImage', 'post.featureImage'),
facebookTitle: or('ogTitleScratch', 'seoTitle'),
seoTitle: or('metaTitleScratch', 'model.titleScratch'),
seoTitle: or('metaTitleScratch', 'post.titleScratch'),
twitterDescription: or('twitterDescriptionScratch', 'customExcerptScratch', 'seoDescription'),
twitterImage: or('model.twitterImage', 'model.featureImage'),
twitterImage: or('post.twitterImage', 'post.featureImage'),
twitterTitle: or('twitterTitleScratch', 'seoTitle'),
_showSettingsMenu: false,
@ -63,7 +63,7 @@ export default Component.extend(SettingsMenuMixin, {
}
});
this.get('model.author').then((author) => {
this.get('post.author').then((author) => {
this.set('selectedAuthor', author);
});
@ -77,7 +77,7 @@ export default Component.extend(SettingsMenuMixin, {
// fired when menu is closed
if (!this.get('showSettingsMenu') && this._showSettingsMenu) {
let post = this.get('model');
let post = this.get('post');
let errors = post.get('errors');
// reset the publish date if it has an error
@ -108,9 +108,9 @@ export default Component.extend(SettingsMenuMixin, {
this.set('_showThrobbers', true);
}).restartable(),
seoDescription: computed('model.scratch', 'metaDescriptionScratch', function () {
seoDescription: computed('post.scratch', 'metaDescriptionScratch', function () {
let metaDescription = this.get('metaDescriptionScratch') || '';
let mobiledoc = this.get('model.scratch');
let mobiledoc = this.get('post.scratch');
let markdown = mobiledoc.cards && mobiledoc.cards[0][1].markdown;
let placeholder;
@ -129,9 +129,9 @@ export default Component.extend(SettingsMenuMixin, {
return placeholder;
}),
seoURL: computed('model.slug', 'config.blogUrl', function () {
seoURL: computed('post.slug', 'config.blogUrl', function () {
let blogUrl = this.get('config.blogUrl');
let seoSlug = this.get('model.slug') ? this.get('model.slug') : '';
let seoSlug = this.get('post.slug') ? this.get('post.slug') : '';
let seoURL = `${blogUrl}/${seoSlug}`;
// only append a slash to the URL if the slug exists
@ -175,32 +175,32 @@ export default Component.extend(SettingsMenuMixin, {
},
togglePage() {
this.toggleProperty('model.page');
this.toggleProperty('post.page');
// If this is a new post. Don't save the model. Defer the save
// If this is a new post. Don't save the post. Defer the save
// to the user pressing the save button
if (this.get('model.isNew')) {
if (this.get('post.isNew')) {
return;
}
this.get('savePost').perform().catch((error) => {
this.showError(error);
this.get('model').rollbackAttributes();
this.get('post').rollbackAttributes();
});
},
toggleFeatured() {
this.toggleProperty('model.featured');
this.toggleProperty('post.featured');
// If this is a new post. Don't save the model. Defer the save
// If this is a new post. Don't save the post. Defer the save
// to the user pressing the save button
if (this.get('model.isNew')) {
if (this.get('post.isNew')) {
return;
}
this.get('savePost').perform().catch((error) => {
this.showError(error);
this.get('model').rollbackAttributes();
this.get('post').rollbackAttributes();
});
},
@ -212,12 +212,12 @@ export default Component.extend(SettingsMenuMixin, {
.perform(newSlug)
.catch((error) => {
this.showError(error);
this.get('model').rollbackAttributes();
this.get('post').rollbackAttributes();
});
},
setPublishedAtBlogDate(date) {
let post = this.get('model');
let post = this.get('post');
let dateString = moment(date).format('YYYY-MM-DD');
post.get('errors').remove('publishedAtBlogDate');
@ -231,7 +231,7 @@ export default Component.extend(SettingsMenuMixin, {
},
setPublishedAtBlogTime(time) {
let post = this.get('model');
let post = this.get('post');
post.get('errors').remove('publishedAtBlogDate');
@ -244,48 +244,48 @@ export default Component.extend(SettingsMenuMixin, {
},
setCustomExcerpt(excerpt) {
let model = this.get('model');
let currentExcerpt = model.get('customExcerpt');
let post = this.get('post');
let currentExcerpt = post.get('customExcerpt');
if (excerpt === currentExcerpt) {
return;
}
model.set('customExcerpt', excerpt);
post.set('customExcerpt', excerpt);
return model.validate({property: 'customExcerpt'}).then(() => this.get('savePost').perform());
return post.validate({property: 'customExcerpt'}).then(() => this.get('savePost').perform());
},
setHeaderInjection(code) {
let model = this.get('model');
let currentCode = model.get('codeinjectionHead');
let post = this.get('post');
let currentCode = post.get('codeinjectionHead');
if (code === currentCode) {
return;
}
model.set('codeinjectionHead', code);
post.set('codeinjectionHead', code);
return model.validate({property: 'codeinjectionHead'}).then(() => this.get('savePost').perform());
return post.validate({property: 'codeinjectionHead'}).then(() => this.get('savePost').perform());
},
setFooterInjection(code) {
let model = this.get('model');
let currentCode = model.get('codeinjectionFoot');
let post = this.get('post');
let currentCode = post.get('codeinjectionFoot');
if (code === currentCode) {
return;
}
model.set('codeinjectionFoot', code);
post.set('codeinjectionFoot', code);
return model.validate({property: 'codeinjectionFoot'}).then(() => this.get('savePost').perform());
return post.validate({property: 'codeinjectionFoot'}).then(() => this.get('savePost').perform());
},
setMetaTitle(metaTitle) {
// Grab the model and current stored meta title
let model = this.get('model');
let currentTitle = model.get('metaTitle');
// Grab the post and current stored meta title
let post = this.get('post');
let currentTitle = post.get('metaTitle');
// If the title entered matches the stored meta title, do nothing
if (currentTitle === metaTitle) {
@ -293,11 +293,11 @@ export default Component.extend(SettingsMenuMixin, {
}
// If the title entered is different, set it as the new meta title
model.set('metaTitle', metaTitle);
post.set('metaTitle', metaTitle);
// Make sure the meta title is valid and if so, save it into the model
return model.validate({property: 'metaTitle'}).then(() => {
if (model.get('isNew')) {
// Make sure the meta title is valid and if so, save it into the post
return post.validate({property: 'metaTitle'}).then(() => {
if (post.get('isNew')) {
return;
}
@ -306,9 +306,9 @@ export default Component.extend(SettingsMenuMixin, {
},
setMetaDescription(metaDescription) {
// Grab the model and current stored meta description
let model = this.get('model');
let currentDescription = model.get('metaDescription');
// Grab the post and current stored meta description
let post = this.get('post');
let currentDescription = post.get('metaDescription');
// If the title entered matches the stored meta title, do nothing
if (currentDescription === metaDescription) {
@ -316,11 +316,11 @@ export default Component.extend(SettingsMenuMixin, {
}
// If the title entered is different, set it as the new meta title
model.set('metaDescription', metaDescription);
post.set('metaDescription', metaDescription);
// Make sure the meta title is valid and if so, save it into the model
return model.validate({property: 'metaDescription'}).then(() => {
if (model.get('isNew')) {
// Make sure the meta title is valid and if so, save it into the post
return post.validate({property: 'metaDescription'}).then(() => {
if (post.get('isNew')) {
return;
}
@ -329,9 +329,9 @@ export default Component.extend(SettingsMenuMixin, {
},
setOgTitle(ogTitle) {
// Grab the model and current stored facebook title
let model = this.get('model');
let currentTitle = model.get('ogTitle');
// Grab the post and current stored facebook title
let post = this.get('post');
let currentTitle = post.get('ogTitle');
// If the title entered matches the stored facebook title, do nothing
if (currentTitle === ogTitle) {
@ -339,11 +339,11 @@ export default Component.extend(SettingsMenuMixin, {
}
// If the title entered is different, set it as the new facebook title
model.set('ogTitle', ogTitle);
post.set('ogTitle', ogTitle);
// Make sure the facebook title is valid and if so, save it into the model
return model.validate({property: 'ogTitle'}).then(() => {
if (model.get('isNew')) {
// Make sure the facebook title is valid and if so, save it into the post
return post.validate({property: 'ogTitle'}).then(() => {
if (post.get('isNew')) {
return;
}
@ -352,9 +352,9 @@ export default Component.extend(SettingsMenuMixin, {
},
setOgDescription(ogDescription) {
// Grab the model and current stored facebook description
let model = this.get('model');
let currentDescription = model.get('ogDescription');
// Grab the post and current stored facebook description
let post = this.get('post');
let currentDescription = post.get('ogDescription');
// If the title entered matches the stored facebook description, do nothing
if (currentDescription === ogDescription) {
@ -362,11 +362,11 @@ export default Component.extend(SettingsMenuMixin, {
}
// If the description entered is different, set it as the new facebook description
model.set('ogDescription', ogDescription);
post.set('ogDescription', ogDescription);
// Make sure the facebook description is valid and if so, save it into the model
return model.validate({property: 'ogDescription'}).then(() => {
if (model.get('isNew')) {
// Make sure the facebook description is valid and if so, save it into the post
return post.validate({property: 'ogDescription'}).then(() => {
if (post.get('isNew')) {
return;
}
@ -375,9 +375,9 @@ export default Component.extend(SettingsMenuMixin, {
},
setTwitterTitle(twitterTitle) {
// Grab the model and current stored twitter title
let model = this.get('model');
let currentTitle = model.get('twitterTitle');
// Grab the post and current stored twitter title
let post = this.get('post');
let currentTitle = post.get('twitterTitle');
// If the title entered matches the stored twitter title, do nothing
if (currentTitle === twitterTitle) {
@ -385,11 +385,11 @@ export default Component.extend(SettingsMenuMixin, {
}
// If the title entered is different, set it as the new twitter title
model.set('twitterTitle', twitterTitle);
post.set('twitterTitle', twitterTitle);
// Make sure the twitter title is valid and if so, save it into the model
return model.validate({property: 'twitterTitle'}).then(() => {
if (model.get('isNew')) {
// Make sure the twitter title is valid and if so, save it into the post
return post.validate({property: 'twitterTitle'}).then(() => {
if (post.get('isNew')) {
return;
}
@ -398,9 +398,9 @@ export default Component.extend(SettingsMenuMixin, {
},
setTwitterDescription(twitterDescription) {
// Grab the model and current stored twitter description
let model = this.get('model');
let currentDescription = model.get('twitterDescription');
// Grab the post and current stored twitter description
let post = this.get('post');
let currentDescription = post.get('twitterDescription');
// If the description entered matches the stored twitter description, do nothing
if (currentDescription === twitterDescription) {
@ -408,11 +408,11 @@ export default Component.extend(SettingsMenuMixin, {
}
// If the description entered is different, set it as the new twitter description
model.set('twitterDescription', twitterDescription);
post.set('twitterDescription', twitterDescription);
// Make sure the twitter description is valid and if so, save it into the model
return model.validate({property: 'twitterDescription'}).then(() => {
if (model.get('isNew')) {
// Make sure the twitter description is valid and if so, save it into the post
return post.validate({property: 'twitterDescription'}).then(() => {
if (post.get('isNew')) {
return;
}
@ -421,103 +421,103 @@ export default Component.extend(SettingsMenuMixin, {
},
setCoverImage(image) {
this.set('model.featureImage', image);
this.set('post.featureImage', image);
if (this.get('model.isNew')) {
if (this.get('post.isNew')) {
return;
}
this.get('savePost').perform().catch((error) => {
this.showError(error);
this.get('model').rollbackAttributes();
this.get('post').rollbackAttributes();
});
},
clearCoverImage() {
this.set('model.featureImage', '');
this.set('post.featureImage', '');
if (this.get('model.isNew')) {
if (this.get('post.isNew')) {
return;
}
this.get('savePost').perform().catch((error) => {
this.showError(error);
this.get('model').rollbackAttributes();
this.get('post').rollbackAttributes();
});
},
setOgImage(image) {
this.set('model.ogImage', image);
this.set('post.ogImage', image);
if (this.get('model.isNew')) {
if (this.get('post.isNew')) {
return;
}
this.get('savePost').perform().catch((error) => {
this.showError(error);
this.get('model').rollbackAttributes();
this.get('post').rollbackAttributes();
});
},
clearOgImage() {
this.set('model.ogImage', '');
this.set('post.ogImage', '');
if (this.get('model.isNew')) {
if (this.get('post.isNew')) {
return;
}
this.get('savePost').perform().catch((error) => {
this.showError(error);
this.get('model').rollbackAttributes();
this.get('post').rollbackAttributes();
});
},
setTwitterImage(image) {
this.set('model.twitterImage', image);
this.set('post.twitterImage', image);
if (this.get('model.isNew')) {
if (this.get('post.isNew')) {
return;
}
this.get('savePost').perform().catch((error) => {
this.showError(error);
this.get('model').rollbackAttributes();
this.get('post').rollbackAttributes();
});
},
clearTwitterImage() {
this.set('model.twitterImage', '');
this.set('post.twitterImage', '');
if (this.get('model.isNew')) {
if (this.get('post.isNew')) {
return;
}
this.get('savePost').perform().catch((error) => {
this.showError(error);
this.get('model').rollbackAttributes();
this.get('post').rollbackAttributes();
});
},
changeAuthor(newAuthor) {
let author = this.get('model.author');
let model = this.get('model');
let author = this.get('post.author');
let post = this.get('post');
// return if nothing changed
if (newAuthor.get('id') === author.get('id')) {
return;
}
model.set('author', newAuthor);
post.set('author', newAuthor);
// if this is a new post (never been saved before), don't try to save it
if (this.get('model.isNew')) {
if (this.get('post.isNew')) {
return;
}
this.get('savePost').perform().catch((error) => {
this.showError(error);
this.set('selectedAuthor', author);
model.rollbackAttributes();
post.rollbackAttributes();
});
},

View File

@ -22,7 +22,7 @@ export default TextArea.extend({
_editor: null,
// default SimpleMDE options, see docs for available config:
// https://github.com/NextStepWebs/simplemde-markdown-editor#configuration
// https://github.com/sparksuite/simplemde-markdown-editor#configuration
defaultOptions: computed(function () {
return {
autofocus: this.get('autofocus'),

View File

@ -1,10 +1,13 @@
import ModalComponent from 'ghost-admin/components/modal-base';
import {alias} from '@ember/object/computed';
import {A as emberA} from '@ember/array';
import {isInvalidError} from 'ember-ajax/errors';
import {task} from 'ember-concurrency';
export default ModalComponent.extend({
subscriber: alias('model'),
addSubscriber: task(function* () {
try {
yield this.get('confirm')();
@ -17,8 +20,8 @@ export default ModalComponent.extend({
let {message} = firstError;
if (message && message.match(/email/i)) {
this.get('model.errors').add('email', message);
this.get('model.hasValidated').pushObject('email');
this.get('subscriber.errors').add('email', message);
this.get('subscriber.hasValidated').pushObject('email');
return;
}
}
@ -32,9 +35,9 @@ export default ModalComponent.extend({
actions: {
updateEmail(newEmail) {
this.set('model.email', newEmail);
this.set('model.hasValidated', emberA());
this.get('model.errors').clear();
this.set('subscriber.email', newEmail);
this.set('subscriber.hasValidated', emberA());
this.get('subscriber.errors').clear();
},
confirm() {

View File

@ -1,10 +1,13 @@
import Controller from '@ember/controller';
import {computed} from '@ember/object';
import {readOnly} from '@ember/object/computed';
import {inject as service} from '@ember/service';
export default Controller.extend({
upgradeStatus: service(),
about: readOnly('model'),
copyrightYear: computed(function () {
let date = new Date();
return date.getFullYear();

View File

@ -1,3 +1,4 @@
/* eslint-disable ghost/ember/alias-model-in-controller */
import Controller from '@ember/controller';
import {computed} from '@ember/object';
import {inject as service} from '@ember/service';

View File

@ -1,3 +1,4 @@
/* eslint-disable ghost/ember/alias-model-in-controller */
import Controller from '@ember/controller';
import EditorControllerMixin from 'ghost-admin/mixins/editor-base-controller';

View File

@ -1,6 +1,5 @@
/* eslint-disable ghost/ember/alias-model-in-controller */
import Controller from '@ember/controller';
import EditorControllerMixin from 'ghost-admin/mixins/editor-base-controller';
export default Controller.extend(EditorControllerMixin, {
});
export default Controller.extend(EditorControllerMixin);

View File

@ -1,19 +1,21 @@
import Controller from '@ember/controller';
import {computed} from '@ember/object';
import {readOnly} from '@ember/object/computed';
export default Controller.extend({
error: readOnly('model'),
stack: false,
code: computed('model.status', function () {
return this.get('model.status') > 200 ? this.get('model.status') : 500;
code: computed('error.status', function () {
return this.get('error.status') > 200 ? this.get('error.status') : 500;
}),
message: computed('model.statusText', function () {
message: computed('error.statusText', function () {
if (this.get('code') === 404) {
return 'Page not found';
}
return this.get('model.statusText') !== 'error' ? this.get('model.statusText') : 'Internal Server Error';
return this.get('error.statusText') !== 'error' ? this.get('error.statusText') : 'Internal Server Error';
})
});

View File

@ -1,3 +1,4 @@
/* eslint-disable ghost/ember/alias-model-in-controller */
import Controller, {inject as controller} from '@ember/controller';
import {readOnly} from '@ember/object/computed';
import {inject as service} from '@ember/service';

View File

@ -1,4 +1,5 @@
import Controller from '@ember/controller';
import {alias} from '@ember/object/computed';
import {computed} from '@ember/object';
import {get} from '@ember/object';
import {inject as service} from '@ember/service';
@ -36,6 +37,8 @@ export default Controller.extend({
session: service(),
store: service(),
postsInfinityModel: alias('model'),
queryParams: ['type', 'author', 'tag', 'order'],
type: null,
author: null,

View File

@ -1,3 +1,4 @@
/* eslint-disable ghost/ember/alias-model-in-controller */
import Controller from '@ember/controller';
import ValidationEngine from 'ghost-admin/mixins/validation-engine';
import {computed} from '@ember/object';

View File

@ -1,3 +1,4 @@
/* eslint-disable ghost/ember/alias-model-in-controller */
import Controller from '@ember/controller';
import {alias} from '@ember/object/computed';
import {inject as service} from '@ember/service';
@ -7,12 +8,12 @@ export default Controller.extend({
notifications: service(),
settings: service(),
model: alias('settings.amp'),
ampSettings: alias('settings.amp'),
leaveSettingsTransition: null,
save: task(function* () {
let amp = this.get('model');
let amp = this.get('ampSettings');
let settings = this.get('settings');
settings.set('amp', amp);
@ -27,7 +28,7 @@ export default Controller.extend({
actions: {
update(value) {
this.set('model', value);
this.set('ampSettings', value);
},
save() {
@ -67,7 +68,7 @@ export default Controller.extend({
return;
}
// roll back changes on model props
// roll back changes on settings model
settings.rollbackAttributes();
return transition.retry();

View File

@ -1,3 +1,4 @@
/* eslint-disable ghost/ember/alias-model-in-controller */
import Controller from '@ember/controller';
import {inject as service} from '@ember/service';

View File

@ -1,3 +1,4 @@
/* eslint-disable ghost/ember/alias-model-in-controller */
import Controller from '@ember/controller';
import boundOneWay from 'ghost-admin/utils/bound-one-way';
import {empty} from '@ember/object/computed';
@ -11,8 +12,8 @@ export default Controller.extend({
notifications: service(),
settings: service(),
model: boundOneWay('settings.slack.firstObject'),
testNotificationDisabled: empty('model.url'),
slackSettings: boundOneWay('settings.slack.firstObject'),
testNotificationDisabled: empty('slackSettings.url'),
leaveSettingsTransition: null,
slackArray: null,
@ -23,7 +24,7 @@ export default Controller.extend({
},
save: task(function* () {
let slack = this.get('model');
let slack = this.get('slackSettings');
let settings = this.get('settings');
let slackArray = this.get('slackArray');
@ -66,12 +67,12 @@ export default Controller.extend({
updateURL(value) {
value = typeof value === 'string' ? value.trim() : value;
this.set('model.url', value);
this.get('model.errors').clear();
this.set('slackSettings.url', value);
this.get('slackSettings.errors').clear();
},
triggerDirtyState() {
let slack = this.get('model');
let slack = this.get('slackSettings');
let slackArray = this.get('slackArray');
let settings = this.get('settings');

View File

@ -1,3 +1,4 @@
/* eslint-disable ghost/ember/alias-model-in-controller */
import Controller from '@ember/controller';
import {alias} from '@ember/object/computed';
import {inject as service} from '@ember/service';
@ -7,14 +8,14 @@ export default Controller.extend({
notifications: service(),
settings: service(),
model: alias('settings.unsplash'),
unsplashSettings: alias('settings.unsplash'),
dirtyAttributes: null,
rollbackValue: null,
leaveSettingsTransition: null,
save: task(function* () {
let unsplash = this.get('model');
let unsplash = this.get('unsplashSettings');
let settings = this.get('settings');
try {
@ -37,9 +38,9 @@ export default Controller.extend({
update(value) {
if (!this.get('dirtyAttributes')) {
this.set('rollbackValue', this.get('model.isActive'));
this.set('rollbackValue', this.get('unsplashSettings.isActive'));
}
this.set('model.isActive', value);
this.set('unsplashSettings.isActive', value);
this.set('dirtyAttributes', true);
},
@ -76,7 +77,7 @@ export default Controller.extend({
}
// roll back changes on model props
this.set('model.isActive', this.get('rollbackValue'));
this.set('unsplashSettings.isActive', this.get('rollbackValue'));
this.set('dirtyAttributes', false);
this.set('rollbackValue', null);

View File

@ -1,3 +1,4 @@
/* eslint-disable ghost/ember/alias-model-in-controller */
import Controller from '@ember/controller';
export default Controller.extend({

View File

@ -1,15 +1,17 @@
/* eslint-disable ghost/ember/alias-model-in-controller */
import Controller from '@ember/controller';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency';
export default Controller.extend({
notifications: service(),
settings: service(),
save: task(function* () {
let notifications = this.get('notifications');
try {
return yield this.get('model').save();
return yield this.get('settings').save();
} catch (error) {
notifications.showAPIError(error, {key: 'code-injection.save'});
throw error;
@ -47,14 +49,14 @@ export default Controller.extend({
leaveSettings() {
let transition = this.get('leaveSettingsTransition');
let settings = this.get('model');
let settings = this.get('settings');
if (!transition) {
this.get('notifications').showAlert('Sorry, there was an error in the application. Please let the Ghost team know what happened.', {type: 'error'});
return;
}
// roll back changes on model props
// roll back changes on settings props
settings.rollbackAttributes();
return transition.retry();

View File

@ -1,3 +1,4 @@
/* eslint-disable ghost/ember/alias-model-in-controller */
import $ from 'jquery';
import Controller from '@ember/controller';
import NavigationItem from 'ghost-admin/models/navigation-item';
@ -14,6 +15,7 @@ export default Controller.extend({
ghostPaths: service(),
notifications: service(),
session: service(),
settings: service(),
newNavItem: null,
@ -35,7 +37,7 @@ export default Controller.extend({
},
save: task(function* () {
let navItems = this.get('model.navigation');
let navItems = this.get('settings.navigation');
let newNavItem = this.get('newNavItem');
let notifications = this.get('notifications');
let validationPromises = [];
@ -51,7 +53,7 @@ export default Controller.extend({
try {
yield RSVP.all(validationPromises);
this.set('dirtyAttributes', false);
return yield this.get('model').save();
return yield this.get('settings').save();
} catch (error) {
if (error) {
notifications.showAPIError(error);
@ -61,7 +63,7 @@ export default Controller.extend({
}),
addNewNavItem() {
let navItems = this.get('model.navigation');
let navItems = this.get('settings.navigation');
let newNavItem = this.get('newNavItem');
newNavItem.set('isNew', false);
@ -111,7 +113,7 @@ export default Controller.extend({
return;
}
let navItems = this.get('model.navigation');
let navItems = this.get('settings.navigation');
navItems.removeObject(item);
this.set('dirtyAttributes', true);
@ -161,15 +163,15 @@ export default Controller.extend({
leaveSettings() {
let transition = this.get('leaveSettingsTransition');
let model = this.get('model');
let settings = this.get('settings');
if (!transition) {
this.get('notifications').showAlert('Sorry, there was an error in the application. Please let the Ghost team know what happened.', {type: 'error'});
return;
}
// roll back changes on model props
model.rollbackAttributes();
// roll back changes on settings props
settings.rollbackAttributes();
this.set('dirtyAttributes', false);
return transition.retry();

View File

@ -1,3 +1,4 @@
/* eslint-disable ghost/ember/alias-model-in-controller */
import $ from 'jquery';
import Controller from '@ember/controller';
import randomPassword from 'ghost-admin/utils/random-password';
@ -18,6 +19,7 @@ export default Controller.extend({
ghostPaths: service(),
notifications: service(),
session: service(),
settings: service(),
availableTimezones: null,
iconExtensions: null,
@ -28,31 +30,31 @@ export default Controller.extend({
_scratchFacebook: null,
_scratchTwitter: null,
isDatedPermalinks: computed('model.permalinks', {
isDatedPermalinks: computed('settings.permalinks', {
set(key, value) {
this.set('model.permalinks', value ? '/:year/:month/:day/:slug/' : '/:slug/');
this.set('settings.permalinks', value ? '/:year/:month/:day/:slug/' : '/:slug/');
let slugForm = this.get('model.permalinks');
let slugForm = this.get('settings.permalinks');
return slugForm !== '/:slug/';
},
get() {
let slugForm = this.get('model.permalinks');
let slugForm = this.get('settings.permalinks');
return slugForm !== '/:slug/';
}
}),
generatePassword: observer('model.isPrivate', function () {
this.get('model.errors').remove('password');
if (this.get('model.isPrivate') && this.get('model.hasDirtyAttributes')) {
this.get('model').set('password', randomPassword());
generatePassword: observer('settings.isPrivate', function () {
this.get('settings.errors').remove('password');
if (this.get('settings.isPrivate') && this.get('settings.hasDirtyAttributes')) {
this.get('settings').set('password', randomPassword());
}
}),
privateRSSUrl: computed('config.blogUrl', 'model.publicHash', function () {
privateRSSUrl: computed('config.blogUrl', 'settings.publicHash', function () {
let blogUrl = this.get('config.blogUrl');
let publicHash = this.get('model.publicHash');
let publicHash = this.get('settings.publicHash');
return `${blogUrl}/${publicHash}/rss`;
}),
@ -79,14 +81,14 @@ export default Controller.extend({
let config = this.get('config');
try {
let model = yield this.get('model').save();
config.set('blogTitle', model.get('title'));
let settings = yield this.get('settings').save();
config.set('blogTitle', settings.get('title'));
// this forces the document title to recompute after
// a blog title change
this.send('collectTitleTokens', []);
return model;
return settings;
} catch (error) {
if (error) {
notifications.showAPIError(error, {key: 'settings.save'});
@ -101,16 +103,16 @@ export default Controller.extend({
},
setTimezone(timezone) {
this.set('model.activeTimezone', timezone.name);
this.set('settings.activeTimezone', timezone.name);
},
removeImage(image) {
// setting `null` here will error as the server treats it as "null"
this.get('model').set(image, '');
this.get('settings').set(image, '');
},
updateImage(property, image, resetInput) {
this.get('model').set(property, image);
this.get('settings').set(property, image);
resetInput();
},
@ -131,13 +133,13 @@ export default Controller.extend({
/**
* Fired after an image upload completes
* @param {string} property - Property name to be set on `this.model`
* @param {string} property - Property name to be set on `this.settings`
* @param {UploadResult[]} results - Array of UploadResult objects
* @return {string} The URL that was set on `this.model.property`
* @return {string} The URL that was set on `this.settings.property`
*/
imageUploaded(property, results) {
if (results[0]) {
return this.get('model').set(property, results[0].url);
return this.get('settings').set(property, results[0].url);
}
},
@ -167,31 +169,31 @@ export default Controller.extend({
leaveSettings() {
let transition = this.get('leaveSettingsTransition');
let model = this.get('model');
let settings = this.get('settings');
if (!transition) {
this.get('notifications').showAlert('Sorry, there was an error in the application. Please let the Ghost team know what happened.', {type: 'error'});
return;
}
// roll back changes on model props
model.rollbackAttributes();
// roll back changes on settings props
settings.rollbackAttributes();
return transition.retry();
},
validateFacebookUrl() {
let newUrl = this.get('_scratchFacebook');
let oldUrl = this.get('model.facebook');
let oldUrl = this.get('settings.facebook');
let errMessage = '';
// reset errors and validation
this.get('model.errors').remove('facebook');
this.get('model.hasValidated').removeObject('facebook');
this.get('settings.errors').remove('facebook');
this.get('settings.hasValidated').removeObject('facebook');
if (newUrl === '') {
// Clear out the Facebook url
this.set('model.facebook', '');
this.set('settings.facebook', '');
return;
}
@ -218,36 +220,36 @@ export default Controller.extend({
throw 'invalid url';
}
this.set('model.facebook', '');
this.set('settings.facebook', '');
run.schedule('afterRender', this, function () {
this.set('model.facebook', newUrl);
this.set('settings.facebook', newUrl);
});
} catch (e) {
if (e === 'invalid url') {
errMessage = 'The URL must be in a format like '
+ 'https://www.facebook.com/yourPage';
this.get('model.errors').add('facebook', errMessage);
this.get('settings.errors').add('facebook', errMessage);
return;
}
throw e;
} finally {
this.get('model.hasValidated').pushObject('facebook');
this.get('settings.hasValidated').pushObject('facebook');
}
},
validateTwitterUrl() {
let newUrl = this.get('_scratchTwitter');
let oldUrl = this.get('model.twitter');
let oldUrl = this.get('settings.twitter');
let errMessage = '';
// reset errors and validation
this.get('model.errors').remove('twitter');
this.get('model.hasValidated').removeObject('twitter');
this.get('settings.errors').remove('twitter');
this.get('settings.hasValidated').removeObject('twitter');
if (newUrl === '') {
// Clear out the Twitter url
this.set('model.twitter', '');
this.set('settings.twitter', '');
return;
}
@ -269,24 +271,24 @@ export default Controller.extend({
if (username.match(/^(http|www)|(\/)/) || !username.match(/^[a-z\d._]{1,15}$/mi)) {
errMessage = !username.match(/^[a-z\d._]{1,15}$/mi) ? 'Your Username is not a valid Twitter Username' : 'The URL must be in a format like https://twitter.com/yourUsername';
this.get('model.errors').add('twitter', errMessage);
this.get('model.hasValidated').pushObject('twitter');
this.get('settings.errors').add('twitter', errMessage);
this.get('settings.hasValidated').pushObject('twitter');
return;
}
newUrl = `https://twitter.com/${username}`;
this.get('model.hasValidated').pushObject('twitter');
this.get('settings.hasValidated').pushObject('twitter');
this.set('model.twitter', '');
this.set('settings.twitter', '');
run.schedule('afterRender', this, function () {
this.set('model.twitter', newUrl);
this.set('settings.twitter', newUrl);
});
} else {
errMessage = 'The URL must be in a format like '
+ 'https://twitter.com/yourUsername';
this.get('model.errors').add('twitter', errMessage);
this.get('model.hasValidated').pushObject('twitter');
this.get('settings.errors').add('twitter', errMessage);
this.get('settings.hasValidated').pushObject('twitter');
return;
}
}

View File

@ -1,3 +1,4 @@
/* eslint-disable ghost/ember/alias-model-in-controller */
import $ from 'jquery';
import Controller from '@ember/controller';
import Ember from 'ember';

View File

@ -6,13 +6,14 @@ export default Controller.extend({
tagController: controller('settings.tags.tag'),
tags: alias('model'),
selectedTag: alias('tagController.tag'),
tagListFocused: equal('keyboardFocus', 'tagList'),
tagContentFocused: equal('keyboardFocus', 'tagContent'),
// TODO: replace with ordering by page count once supported by the API
sortedTags: sort('model', function (a, b) {
sortedTags: sort('tags', function (a, b) {
let idA = +a.get('id');
let idB = +b.get('id');

View File

@ -1,3 +1,4 @@
/* eslint-disable ghost/ember/alias-model-in-controller */
import Controller, {inject as controller} from '@ember/controller';
import {computed} from '@ember/object';
import {match} from '@ember/object/computed';

View File

@ -1,3 +1,4 @@
/* eslint-disable ghost/ember/alias-model-in-controller */
import Controller, {inject as controller} from '@ember/controller';
import DS from 'ember-data';
import RSVP from 'rsvp';

View File

@ -1,4 +1,4 @@
/* eslint-disable camelcase */
/* eslint-disable camelcase, ghost/ember/alias-model-in-controller */
import Controller, {inject as controller} from '@ember/controller';
import RSVP from 'rsvp';
import ValidationEngine from 'ghost-admin/mixins/validation-engine';

View File

@ -2,6 +2,7 @@ import $ from 'jquery';
import Controller, {inject as controller} from '@ember/controller';
import RSVP from 'rsvp';
import ValidationEngine from 'ghost-admin/mixins/validation-engine';
import {alias} from '@ember/object/computed';
import {isArray as isEmberArray} from '@ember/array';
import {isVersionMismatchError} from 'ghost-admin/services/ajax';
import {inject as service} from '@ember/service';
@ -21,6 +22,7 @@ export default Controller.extend(ValidationEngine, {
settings: service(),
flowErrors: '',
signin: alias('model'),
// ValidationEngine settings
validationType: 'signin',
@ -56,11 +58,11 @@ export default Controller.extend(ValidationEngine, {
this.set('flowErrors', error.payload.errors[0].message.string);
if (error.payload.errors[0].message.string.match(/user with that email/)) {
this.get('model.errors').add('identification', '');
this.get('signin.errors').add('identification', '');
}
if (error.payload.errors[0].message.string.match(/password is incorrect/)) {
this.get('model.errors').add('password', '');
this.get('signin.errors').add('password', '');
}
} else {
// Connection errors don't return proper status message, only req.body
@ -70,7 +72,7 @@ export default Controller.extend(ValidationEngine, {
}).drop(),
validateAndAuthenticate: task(function* () {
let model = this.get('model');
let signin = this.get('signin');
let authStrategy = 'authenticator:oauth2';
this.set('flowErrors', '');
@ -84,14 +86,14 @@ export default Controller.extend(ValidationEngine, {
try {
yield this.validate({property: 'signin'});
return yield this.get('authenticate')
.perform(authStrategy, [model.get('identification'), model.get('password')]);
.perform(authStrategy, [signin.get('identification'), signin.get('password')]);
} catch (error) {
this.set('flowErrors', 'Please fill out the form to sign in.');
}
}).drop(),
forgotten: task(function* () {
let email = this.get('model.identification');
let email = this.get('signin.identification');
let forgottenUrl = this.get('ghostPaths.url').api('authentication', 'passwordreset');
let notifications = this.get('notifications');
@ -123,7 +125,7 @@ export default Controller.extend(ValidationEngine, {
this.set('flowErrors', message);
if (message.match(/no user with that email/)) {
this.get('model.errors').add('identification', '');
this.get('signin.errors').add('identification', '');
}
} else {
notifications.showAPIError(error, {defaultErrorText: 'There was a problem with the reset, please try again.', key: 'forgot-password.send'});

View File

@ -5,6 +5,7 @@ import {
VersionMismatchError,
isVersionMismatchError
} from 'ghost-admin/services/ajax';
import {alias} from '@ember/object/computed';
import {isArray as isEmberArray} from '@ember/array';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency';
@ -18,6 +19,7 @@ export default Controller.extend(ValidationEngine, {
settings: service(),
// ValidationEngine settings
signupDetails: alias('model'),
validationType: 'signup',
flowErrors: '',
@ -53,11 +55,11 @@ export default Controller.extend(ValidationEngine, {
this.set('flowErrors', error.payload.errors[0].message.string);
if (error.payload.errors[0].message.string.match(/user with that email/)) {
this.get('model.errors').add('identification', '');
this.get('signupDetails.errors').add('email', '');
}
if (error.payload.errors[0].message.string.match(/password is incorrect/)) {
this.get('model.errors').add('password', '');
this.get('signupDetails.errors').add('password', '');
}
} else {
// Connection errors don't return proper status message, only req.body
@ -103,24 +105,24 @@ export default Controller.extend(ValidationEngine, {
_completeInvitation() {
let authUrl = this.get('ghostPaths.url').api('authentication', 'invitation');
let model = this.get('model');
let signupDetails = this.get('signupDetails');
return this.get('ajax').post(authUrl, {
dataType: 'json',
data: {
invitation: [{
name: model.get('name'),
email: model.get('email'),
password: model.get('password'),
token: model.get('token')
name: signupDetails.get('name'),
email: signupDetails.get('email'),
password: signupDetails.get('password'),
token: signupDetails.get('token')
}]
}
});
},
_authenticateWithPassword() {
let email = this.get('model.email');
let password = this.get('model.password');
let email = this.get('signupDetails.email');
let password = this.get('signupDetails.password');
return this.get('session')
.authenticate('authenticator:oauth2', email, password);

View File

@ -1,3 +1,4 @@
/* eslint-disable ghost/ember/alias-model-in-controller */
import $ from 'jquery';
import Controller from '@ember/controller';
import PaginationMixin from 'ghost-admin/mixins/pagination';

View File

@ -1,3 +1,4 @@
/* eslint-disable ghost/ember/alias-model-in-controller */
import Controller from '@ember/controller';
import {inject as service} from '@ember/service';
import {sort} from '@ember/object/computed';

View File

@ -36,8 +36,8 @@ export default Controller.extend({
user: alias('model'),
currentUser: alias('session.user'),
email: readOnly('model.email'),
slugValue: boundOneWay('model.slug'),
email: readOnly('user.email'),
slugValue: boundOneWay('user.slug'),
canAssignRoles: or('currentUser.isAdmin', 'currentUser.isOwner'),
canChangeEmail: not('isAdminUserOnOwnerProfile'),
@ -170,17 +170,18 @@ export default Controller.extend({
}
try {
let model = yield user.save({format: false});
let currentPath,
newPath;
user = yield user.save({format: false});
// If the user's slug has changed, change the URL and replace
// the history so refresh and back button still work
if (slugChanged) {
currentPath = window.location.hash;
newPath = currentPath.split('/');
newPath[newPath.length - 1] = model.get('slug');
newPath[newPath.length - 1] = user.get('slug');
newPath = newPath.join('/');
windowProxy.replaceState({path: newPath}, '', newPath);
@ -189,7 +190,7 @@ export default Controller.extend({
this.set('dirtyAttributes', false);
this.get('notifications').closeAlerts('user.update');
return model;
return user;
} catch (error) {
// validation engine returns undefined so we have to check
// before treating the failure as an API error
@ -415,7 +416,7 @@ export default Controller.extend({
return;
}
// roll back changes on model props
// roll back changes on user props
user.rollbackAttributes();
// roll back the slugValue property
if (this.get('dirtyAttributes')) {

View File

@ -5,6 +5,7 @@ import boundOneWay from 'ghost-admin/utils/bound-one-way';
import ghostPaths from 'ghost-admin/utils/ghost-paths';
import isNumber from 'ghost-admin/utils/isNumber';
import moment from 'moment';
import {alias} from '@ember/object/computed';
import {computed} from '@ember/object';
import {inject as controller} from '@ember/controller';
import {htmlSafe} from '@ember/string';
@ -17,8 +18,8 @@ import {inject as service} from '@ember/service';
import {task, taskGroup, timeout} from 'ember-concurrency';
// this array will hold properties we need to watch
// to know if the model has been changed (`controller.hasDirtyAttributes`)
const watchedProps = ['model.scratch', 'model.titleScratch', 'model.hasDirtyAttributes', 'model.tags.[]'];
// to know if the post has been changed (`controller.hasDirtyAttributes`)
const watchedProps = ['post.scratch', 'post.titleScratch', 'post.hasDirtyAttributes', 'post.tags.[]'];
const DEFAULT_TITLE = '(Untitled)';
@ -28,11 +29,13 @@ const AUTOSAVE_TIMEOUT = 3000;
const TIMEDSAVE_TIMEOUT = 60000;
PostModel.eachAttribute(function (name) {
watchedProps.push(`model.${name}`);
watchedProps.push(`post.${name}`);
});
export default Mixin.create({
post: alias('model'),
showLeaveEditorModal: false,
showReAuthenticateModal: false,
showDeletePostModal: false,
@ -64,8 +67,8 @@ export default Mixin.create({
};
},
_canAutosave: computed('model.isDraft', function () {
return !Ember.testing && this.get('model.isDraft'); // eslint-disable-line
_canAutosave: computed('post.isDraft', function () {
return !Ember.testing && this.get('post.isDraft'); // eslint-disable-line
}),
// save 3 seconds after the last edit
@ -75,7 +78,7 @@ export default Mixin.create({
}
// force an instant save on first body edit for new posts
if (this.get('model.isNew')) {
if (this.get('post.isNew')) {
return this.get('autosave').perform();
}
@ -122,8 +125,8 @@ export default Mixin.create({
// save tasks cancels autosave before running, although this cancels the
// _xSave tasks that will also cancel the autosave task
save: task(function* (options = {}) {
let prevStatus = this.get('model.status');
let isNew = this.get('model.isNew');
let prevStatus = this.get('post.status');
let isNew = this.get('post.isNew');
let status;
this.send('cancelAutosave');
@ -139,9 +142,9 @@ export default Mixin.create({
if (this.get('post.pastScheduledTime')) {
status = (!this.get('willSchedule') && !this.get('willPublish')) ? 'draft' : 'published';
} else {
if (this.get('willPublish') && !this.get('model.isScheduled') && !this.get('statusFreeze')) {
if (this.get('willPublish') && !this.get('post.isScheduled') && !this.get('statusFreeze')) {
status = 'published';
} else if (this.get('willSchedule') && !this.get('model.isPublished') && !this.get('statusFreeze')) {
} else if (this.get('willSchedule') && !this.get('post.isPublished') && !this.get('statusFreeze')) {
status = 'scheduled';
} else {
status = 'draft';
@ -151,49 +154,49 @@ export default Mixin.create({
// Set the properties that are indirected
// set mobiledoc equal to what's in the editor, minus the image markers.
this.set('model.mobiledoc', this.get('model.scratch'));
this.set('model.status', status);
this.set('post.mobiledoc', this.get('post.scratch'));
this.set('post.status', status);
// Set a default title
if (!this.get('model.titleScratch').trim()) {
this.set('model.titleScratch', DEFAULT_TITLE);
if (!this.get('post.titleScratch').trim()) {
this.set('post.titleScratch', DEFAULT_TITLE);
}
this.set('model.title', this.get('model.titleScratch'));
this.set('model.customExcerpt', this.get('model.customExcerptScratch'));
this.set('model.footerInjection', this.get('model.footerExcerptScratch'));
this.set('model.headerInjection', this.get('model.headerExcerptScratch'));
this.set('model.metaTitle', this.get('model.metaTitleScratch'));
this.set('model.metaDescription', this.get('model.metaDescriptionScratch'));
this.set('model.ogTitle', this.get('model.ogTitleScratch'));
this.set('model.ogDescription', this.get('model.ogDescriptionScratch'));
this.set('model.twitterTitle', this.get('model.twitterTitleScratch'));
this.set('model.twitterDescription', this.get('model.twitterDescriptionScratch'));
this.set('post.title', this.get('post.titleScratch'));
this.set('post.customExcerpt', this.get('post.customExcerptScratch'));
this.set('post.footerInjection', this.get('post.footerExcerptScratch'));
this.set('post.headerInjection', this.get('post.headerExcerptScratch'));
this.set('post.metaTitle', this.get('post.metaTitleScratch'));
this.set('post.metaDescription', this.get('post.metaDescriptionScratch'));
this.set('post.ogTitle', this.get('post.ogTitleScratch'));
this.set('post.ogDescription', this.get('post.ogDescriptionScratch'));
this.set('post.twitterTitle', this.get('post.twitterTitleScratch'));
this.set('post.twitterDescription', this.get('post.twitterDescriptionScratch'));
if (!this.get('model.slug')) {
if (!this.get('post.slug')) {
this.get('saveTitle').cancelAll();
yield this.get('generateSlug').perform();
}
try {
let model = yield this.get('model').save(options);
let post = yield this.get('post').save(options);
if (!options.silent) {
this.showSaveNotification(prevStatus, model.get('status'), isNew ? true : false);
this.showSaveNotification(prevStatus, post.get('status'), isNew ? true : false);
}
this.get('model').set('statusScratch', null);
this.get('post').set('statusScratch', null);
// redirect to edit route if saving a new record
if (isNew && model.get('id')) {
if (isNew && post.get('id')) {
if (!this.get('leaveEditorTransition')) {
this.replaceRoute('editor.edit', model);
this.replaceRoute('editor.edit', post);
}
return true;
}
return model;
return post;
} catch (error) {
// re-throw if we have a general server error
if (error && !isInvalidError(error)) {
@ -201,16 +204,16 @@ export default Mixin.create({
return;
}
this.set('model.status', prevStatus);
this.set('post.status', prevStatus);
if (!options.silent) {
let errorOrMessages = error || this.get('model.errors.messages');
this.showErrorAlert(prevStatus, this.get('model.status'), errorOrMessages);
let errorOrMessages = error || this.get('post.errors.messages');
this.showErrorAlert(prevStatus, this.get('post.status'), errorOrMessages);
// simulate a validation error for upstream tasks
throw undefined;
}
return this.get('model');
return this.get('post');
}
}).group('saveTasks'),
@ -218,7 +221,7 @@ export default Mixin.create({
* triggered by a user manually changing slug
*/
updateSlug: task(function* (_newSlug) {
let slug = this.get('model.slug');
let slug = this.get('post.slug');
let newSlug, serverSlug;
newSlug = _newSlug || slug;
@ -259,25 +262,25 @@ export default Mixin.create({
}
}
this.set('model.slug', serverSlug);
this.set('post.slug', serverSlug);
// If this is a new post. Don't save the model. Defer the save
// If this is a new post. Don't save the post. Defer the save
// to the user pressing the save button
if (this.get('model.isNew')) {
if (this.get('post.isNew')) {
return;
}
return yield this.get('model').save();
return yield this.get('post').save();
}).group('saveTasks'),
// used in the PSM so that saves are sequential and don't trigger collision
// detection errors
savePost: task(function* () {
try {
return yield this.get('model').save();
return yield this.get('post').save();
} catch (error) {
if (error) {
let status = this.get('model.status');
let status = this.get('post.status');
this.showErrorAlert(status, status, error);
}
@ -290,24 +293,24 @@ export default Mixin.create({
* Only with a user-set value (via setSaveType action)
* can the post's status change.
*/
willPublish: boundOneWay('model.isPublished'),
willSchedule: boundOneWay('model.isScheduled'),
willPublish: boundOneWay('post.isPublished'),
willSchedule: boundOneWay('post.isScheduled'),
// set by the editor route and `hasDirtyAttributes`. useful when checking
// whether the number of tags has changed for `hasDirtyAttributes`.
previousTagNames: null,
tagNames: mapBy('model.tags', 'name'),
tagNames: mapBy('post.tags', 'name'),
postOrPage: computed('model.page', function () {
return this.get('model.page') ? 'Page' : 'Post';
postOrPage: computed('post.page', function () {
return this.get('post.page') ? 'Page' : 'Post';
}),
// countdown timer to show the time left until publish time for a scheduled post
// starts 15 minutes before scheduled time
scheduleCountdown: computed('model.{publishedAtUTC,isScheduled}', 'clock.second', function () {
let isScheduled = this.get('model.isScheduled');
let publishTime = this.get('model.publishedAtUTC') || moment.utc();
scheduleCountdown: computed('post.{publishedAtUTC,isScheduled}', 'clock.second', function () {
let isScheduled = this.get('post.isScheduled');
let publishTime = this.get('post.publishedAtUTC') || moment.utc();
let timeUntilPublished = publishTime.diff(moment.utc(), 'minutes', true);
let isPublishedSoon = timeUntilPublished > 0 && timeUntilPublished < 15;
@ -343,41 +346,41 @@ export default Mixin.create({
},
// a hook created in editor-base-route's setupController
modelSaved() {
let model = this.get('model');
postSaved() {
let post = this.get('post');
// safer to updateTags on save in one place
// rather than in all other places save is called
model.updateTags();
post.updateTags();
// set previousTagNames to current tagNames for hasDirtyAttributes check
this.set('previousTagNames', this.get('tagNames'));
// `updateTags` triggers `hasDirtyAttributes => true`.
// for a saved model it would otherwise be false.
// for a saved post it would otherwise be false.
// if the two "scratch" properties (title and content) match the model, then
// if the two "scratch" properties (title and content) match the post, then
// it's ok to set hasDirtyAttributes to false
if (model.get('titleScratch') === model.get('title')
&& JSON.stringify(model.get('scratch')) === JSON.stringify(model.get('mobiledoc'))) {
if (post.get('titleScratch') === post.get('title')
&& JSON.stringify(post.get('scratch')) === JSON.stringify(post.get('mobiledoc'))) {
this.set('hasDirtyAttributes', false);
}
},
// an ugly hack, but necessary to watch all the model's properties
// an ugly hack, but necessary to watch all the post's properties
// and more, without having to be explicit and do it manually
hasDirtyAttributes: computed.apply(Ember, watchedProps.concat({
get() {
let model = this.get('model');
let post = this.get('post');
if (!model) {
if (!post) {
return false;
}
let mobiledoc = JSON.stringify(model.get('mobiledoc'));
let scratch = JSON.stringify(model.get('scratch'));
let title = model.get('title');
let titleScratch = model.get('titleScratch');
let mobiledoc = JSON.stringify(post.get('mobiledoc'));
let scratch = JSON.stringify(post.get('scratch'));
let title = post.get('title');
let titleScratch = post.get('titleScratch');
let changedAttributes;
if (!this.tagNamesEqual()) {
@ -388,22 +391,22 @@ export default Mixin.create({
return true;
}
// since `scratch` is not model property, we need to check
// it explicitly against the model's mobiledoc attribute
// since `scratch` is not post property, we need to check
// it explicitly against the post's mobiledoc attribute
if (mobiledoc !== scratch) {
return true;
}
// if the Adapter failed to save the model isError will be true
// and we should consider the model still dirty.
if (model.get('isError')) {
// if the Adapter failed to save the post isError will be true
// and we should consider the post still dirty.
if (post.get('isError')) {
return true;
}
// models created on the client always return `hasDirtyAttributes: true`,
// posts created on the client always return `hasDirtyAttributes: true`,
// so we need to see which properties have actually changed.
if (model.get('isNew')) {
changedAttributes = Object.keys(model.changedAttributes());
if (post.get('isNew')) {
changedAttributes = Object.keys(post.changedAttributes());
if (changedAttributes.length) {
return true;
@ -413,10 +416,10 @@ export default Mixin.create({
}
// even though we use the `scratch` prop to show edits,
// which does *not* change the model's `hasDirtyAttributes` property,
// which does *not* change the post's `hasDirtyAttributes` property,
// `hasDirtyAttributes` will tell us if the other props have changed,
// as long as the model is not new (model.isNew === false).
return model.get('hasDirtyAttributes');
// as long as the post is not new (post.isNew === false).
return post.get('hasDirtyAttributes');
},
set(key, value) {
return value;
@ -485,10 +488,10 @@ export default Mixin.create({
if (status === 'published') {
type = this.get('postOrPage');
path = this.get('model.absoluteUrl');
path = this.get('post.absoluteUrl');
} else {
type = 'Preview';
path = this.get('model.previewUrl');
path = this.get('post.previewUrl');
}
message += `&nbsp;<a href="${path}" target="_blank">View ${type}</a>`;
@ -525,9 +528,9 @@ export default Mixin.create({
},
saveTitle: task(function* () {
let model = this.get('model');
let currentTitle = model.get('title');
let newTitle = model.get('titleScratch').trim();
let post = this.get('post');
let currentTitle = post.get('title');
let newTitle = post.get('titleScratch').trim();
if (currentTitle && newTitle && newTitle === currentTitle) {
return;
@ -538,20 +541,20 @@ export default Mixin.create({
// generate a slug if a post is new and doesn't have a title yet or
// if the title is still '(Untitled)'
if ((model.get('isNew') && !currentTitle) || currentTitle === DEFAULT_TITLE) {
if ((post.get('isNew') && !currentTitle) || currentTitle === DEFAULT_TITLE) {
yield this.get('generateSlug').perform();
}
if (this.get('model.isDraft')) {
if (this.get('post.isDraft')) {
yield this.get('autosave').perform();
}
}),
generateSlug: task(function* () {
let title = this.get('model.titleScratch');
let title = this.get('post.titleScratch');
// Only set an "untitled" slug once per post
if (title === DEFAULT_TITLE && this.get('model.slug')) {
if (title === DEFAULT_TITLE && this.get('post.slug')) {
return;
}
@ -559,7 +562,7 @@ export default Mixin.create({
let slug = yield this.get('slugGenerator').generateSlug('post', title);
if (!isBlank(slug)) {
this.set('model.slug', slug);
this.set('post.slug', slug);
}
} catch (error) {
// Nothing to do (would be nice to log this somewhere though),
@ -573,7 +576,7 @@ export default Mixin.create({
actions: {
updateScratch(value) {
this.set('model.scratch', value);
this.set('post.scratch', value);
// save 3 seconds after last edit
this.get('_autosave').perform();
@ -639,7 +642,7 @@ export default Mixin.create({
leaveEditor() {
let transition = this.get('leaveEditorTransition');
let model = this.get('model');
let post = this.get('post');
if (!transition) {
this.get('notifications').showAlert('Sorry, there was an error in the application. Please let the Ghost team know what happened.', {type: 'error'});
@ -647,14 +650,14 @@ export default Mixin.create({
}
// definitely want to clear the data store and post of any unsaved, client-generated tags
model.updateTags();
post.updateTags();
if (model.get('isNew')) {
if (post.get('isNew')) {
// the user doesn't want to save the new, unsaved post, so delete it.
model.deleteRecord();
post.deleteRecord();
} else {
// roll back changes on model props
model.rollbackAttributes();
// roll back changes on post props
post.rollbackAttributes();
}
// setting hasDirtyAttributes to false here allows willTransition on the editor route to succeed
@ -667,11 +670,11 @@ export default Mixin.create({
},
updateTitle(newTitle) {
this.set('model.titleScratch', newTitle);
this.set('post.titleScratch', newTitle);
},
toggleDeletePostModal() {
if (!this.get('model.isNew')) {
if (!this.get('post.isNew')) {
this.toggleProperty('showDeletePostModal');
}
},

View File

@ -36,10 +36,10 @@ export default Mixin.create(styleBody, ShortcutsRoute, {
willTransition(transition) {
let controller = this.get('controller');
let scratch = controller.get('model.scratch');
let scratch = controller.get('post.scratch');
let controllerIsDirty = controller.get('hasDirtyAttributes');
let model = controller.get('model');
let state = model.getProperties('isDeleted', 'isSaving', 'hasDirtyAttributes', 'isNew');
let post = controller.get('post');
let state = post.getProperties('isDeleted', 'isSaving', 'hasDirtyAttributes', 'isNew');
if (this.get('upgradeStatus.isRequired')) {
return this._super(...arguments);
@ -49,7 +49,7 @@ export default Mixin.create(styleBody, ShortcutsRoute, {
&& transition.targetName === 'editor.edit'
&& transition.intent.contexts
&& transition.intent.contexts[0]
&& transition.intent.contexts[0].id === model.get('id');
&& transition.intent.contexts[0].id === post.get('id');
let deletedWithoutChanges = state.isDeleted
&& (state.isSaving || !state.hasDirtyAttributes);
@ -60,11 +60,11 @@ export default Mixin.create(styleBody, ShortcutsRoute, {
return;
}
// The controller may hold model state that will be lost in the
// The controller may hold post state that will be lost in the
// new->edit transition, so we need to apply it now.
if (fromNewToEdit && controllerIsDirty) {
if (scratch !== model.get('mobiledoc')) {
model.set('mobiledoc', scratch);
if (scratch !== post.get('mobiledoc')) {
post.set('mobiledoc', scratch);
}
}
@ -75,42 +75,42 @@ export default Mixin.create(styleBody, ShortcutsRoute, {
controller.send('cancelAutosave');
if (state.isNew) {
model.deleteRecord();
post.deleteRecord();
}
// since the transition is now certain to complete..
window.onbeforeunload = null;
// remove model-related listeners created in editor-base-route
this.detachModelHooks(controller, model);
// remove post-related listeners created in editor-base-route
this.detachModelHooks(controller, post);
}
},
attachModelHooks(controller, model) {
// this will allow us to track when the model is saved and update the controller
attachModelHooks(controller, post) {
// this will allow us to track when the post is saved and update the controller
// so that we can be sure controller.hasDirtyAttributes is correct, without having to update the
// controller on each instance of `model.save()`.
// controller on each instance of `post.save()`.
//
// another reason we can't do this on `model.save().then()` is because the post-settings-menu
// also saves the model, and passing messages is difficult because we have two
// another reason we can't do this on `post.save().then()` is because the post-settings-menu
// also saves the post, and passing messages is difficult because we have two
// types of editor controllers, and the PSM also exists on the posts.post route.
//
// The reason we can't just keep this functionality in the editor controller is
// because we need to remove these handlers on `willTransition` in the editor route.
model.on('didCreate', controller, controller.get('modelSaved'));
model.on('didUpdate', controller, controller.get('modelSaved'));
post.on('didCreate', controller, controller.get('postSaved'));
post.on('didUpdate', controller, controller.get('postSaved'));
},
detachModelHooks(controller, model) {
model.off('didCreate', controller, controller.get('modelSaved'));
model.off('didUpdate', controller, controller.get('modelSaved'));
detachModelHooks(controller, post) {
post.off('didCreate', controller, controller.get('postSaved'));
post.off('didUpdate', controller, controller.get('postSaved'));
},
setupController(controller, model) {
let tags = model.get('tags');
setupController(controller, post) {
let tags = post.get('tags');
model.set('scratch', model.get('mobiledoc'));
model.set('titleScratch', model.get('title'));
post.set('scratch', post.get('mobiledoc'));
post.set('titleScratch', post.get('title'));
// reset the leave editor transition so new->edit will still work
controller.set('leaveEditorTransition', null);
@ -124,18 +124,18 @@ export default Mixin.create(styleBody, ShortcutsRoute, {
controller.set('previousTagNames', []);
}
// trigger an immediate autosave timeout if model has changed between
// trigger an immediate autosave timeout if post has changed between
// new->edit (typical as first save will only contain the first char)
// so that leaving the route waits for save instead of showing the
// "Are you sure you want to leave?" modal unexpectedly
if (!model.get('isNew') && model.get('hasDirtyAttributes')) {
if (!post.get('isNew') && post.get('hasDirtyAttributes')) {
controller.get('_autosave').perform();
}
// reset save-on-first-change (gh-koenig specific)
// controller._hasChanged = false;
// attach model-related listeners created in editor-base-route
this.attachModelHooks(controller, model);
// attach post-related listeners created in editor-base-route
this.attachModelHooks(controller, post);
}
});

View File

@ -24,10 +24,9 @@ export default AuthenticatedRoute.extend(styleBody, CurrentUserSettings, {
});
},
setupController(controller, models) {
setupController(controller) {
// reset the leave setting transition
controller.set('leaveSettingsTransition', null);
controller.set('model', models.settings);
controller.set('themes', this.get('store').peekAll('theme'));
this.get('controller').send('reset');
},

View File

@ -6,6 +6,10 @@ export default AuthenticatedRoute.extend({
return this.get('store').findAll('theme');
},
setupController(controller, model) {
controller.set('themes', model);
},
actions: {
cancel() {
this.transitionTo('settings.design');

View File

@ -28,8 +28,6 @@ export default AuthenticatedRoute.extend(styleBody, CurrentUserSettings, {
setupController(controller, models) {
// reset the leave setting transition
controller.set('leaveSettingsTransition', null);
controller.set('model', models.settings);
controller.set('themes', this.get('store').peekAll('theme'));
controller.set('availableTimezones', models.availableTimezones);
},
@ -44,10 +42,10 @@ export default AuthenticatedRoute.extend(styleBody, CurrentUserSettings, {
willTransition(transition) {
let controller = this.get('controller');
let model = controller.get('model');
let modelIsDirty = model.get('hasDirtyAttributes');
let settings = this.get('settings');
let settingsIsDirty = settings.get('hasDirtyAttributes');
if (modelIsDirty) {
if (settingsIsDirty) {
transition.abort();
controller.send('toggleLeaveSettingsModal', transition);
return;

View File

@ -26,7 +26,7 @@ export default Route.extend(UnauthenticatedRouteMixin, styleBody, {
this._super(...arguments);
// clear the properties that hold the credentials when we're no longer on the signin screen
controller.set('model.identification', '');
controller.set('model.password', '');
controller.set('signin.identification', '');
controller.set('signin.password', '');
}
});

View File

@ -27,7 +27,7 @@ export default Route.extend(styleBody, UnauthenticatedRouteMixin, {
},
model(params) {
let model = EmberObject.create();
let signupDetails = EmberObject.create();
let re = /^(?:[A-Za-z0-9_-]{4})*(?:[A-Za-z0-9_-]{2}|[A-Za-z0-9_-]{3})?$/;
let email,
tokenText;
@ -42,9 +42,9 @@ export default Route.extend(styleBody, UnauthenticatedRouteMixin, {
tokenText = atob(params.token);
email = tokenText.split('|')[1];
model.set('email', email);
model.set('token', params.token);
model.set('errors', Errors.create());
signupDetails.set('email', email);
signupDetails.set('token', params.token);
signupDetails.set('errors', Errors.create());
let authUrl = this.get('ghostPaths.url').api('authentication', 'invitation');
@ -60,14 +60,14 @@ export default Route.extend(styleBody, UnauthenticatedRouteMixin, {
return resolve(this.transitionTo('signin'));
}
model.set('invitedBy', response.invitation[0].invitedBy);
signupDetails.set('invitedBy', response.invitation[0].invitedBy);
// set blogTitle, so password validation has access to it
model.set('blogTitle', this.get('config.blogTitle'));
signupDetails.set('blogTitle', this.get('config.blogTitle'));
resolve(model);
resolve(signupDetails);
}).catch(() => {
resolve(model);
resolve(signupDetails);
});
});
},
@ -76,6 +76,6 @@ export default Route.extend(styleBody, UnauthenticatedRouteMixin, {
this._super(...arguments);
// clear the properties that hold the sensitive data from the controller
this.controllerFor('signup').setProperties({email: '', password: '', token: ''});
this.controllerFor('signup').get('signupDetails').setProperties({email: '', password: '', token: ''});
}
});

View File

@ -5,8 +5,12 @@ export default Route.extend({
return this.get('store').createRecord('subscriber');
},
setupController(controller, model) {
controller.set('subscriber', model);
},
deactivate() {
let subscriber = this.controller.get('model');
let subscriber = this.controller.get('subscriber');
this._super(...arguments);
@ -16,13 +20,13 @@ export default Route.extend({
},
rollbackModel() {
let subscriber = this.controller.get('model');
let subscriber = this.controller.get('subscriber');
subscriber.rollbackAttributes();
},
actions: {
save() {
let subscriber = this.controller.get('model');
let subscriber = this.controller.get('subscriber');
return subscriber.save().then((saved) => {
this.send('addSubscriber', saved);
return saved;

View File

@ -5,10 +5,10 @@
<section class="view-container">
<section class="gh-env-details">
<ul class="gh-env-list">
<li class="gh-env-list-version"><strong>Version</strong> {{model.version}}</li>
<li><strong>Environment</strong> {{model.environment}}</li>
<li class="gh-env-list-database-type"><strong>Database</strong> {{model.database}}</li>
<li><strong>Mail</strong> {{#if model.mail}}{{model.mail}}{{else}}Native{{/if}}</li>
<li class="gh-env-list-version"><strong>Version</strong> {{about.version}}</li>
<li><strong>Environment</strong> {{about.environment}}</li>
<li class="gh-env-list-database-type"><strong>Database</strong> {{about.database}}</li>
<li><strong>Mail</strong> {{#if about.mail}}{{about.mail}}{{else}}Native{{/if}}</li>
</ul>
<div class="gh-env-help">
<a class="gh-btn" href="https://help.ghost.org" target="_blank"><span>User Documentation</span></a>

View File

@ -9,7 +9,7 @@
</div>
<div class="settings-menu-content">
{{gh-image-uploader-with-preview
image=model.featureImage
image=post.featureImage
text="Upload post image"
allowUnsplash=true
update=(action "setCoverImage")
@ -19,13 +19,13 @@
<div class="form-group">
<label for="url">Post URL</label>
{{!-- new posts don't have a preview link --}}
{{#unless model.isNew}}
{{#if model.isPublished}}
<a class="post-view-link" target="_blank" href="{{model.absoluteUrl}}">
{{#unless post.isNew}}
{{#if post.isPublished}}
<a class="post-view-link" target="_blank" href="{{post.absoluteUrl}}">
View post {{inline-svg "external"}}
</a>
{{else}}
<a class="post-view-link" target="_blank" href="{{model.previewUrl}}">
<a class="post-view-link" target="_blank" href="{{post.previewUrl}}">
Preview {{inline-svg "external"}}
</a>
{{/if}}
@ -45,32 +45,32 @@
</div>
<div class="form-group">
{{#if (or model.isDraft model.isPublished model.pastScheduledTime)}}
{{#if (or post.isDraft post.isPublished post.pastScheduledTime)}}
<label>Publish Date</label>
{{else}}
<label>Scheduled Date</label>
<p>Use the publish menu to re-schedule</p>
{{/if}}
{{gh-date-time-picker
date=model.publishedAtBlogDate
time=model.publishedAtBlogTime
date=post.publishedAtBlogDate
time=post.publishedAtBlogTime
setDate=(action "setPublishedAtBlogDate")
setTime=(action "setPublishedAtBlogTime")
errors=model.errors
errors=post.errors
dateErrorProperty="publishedAtBlogDate"
timeErrorProperty="publishedAtBlogTime"
maxDate='now'
disabled=model.isScheduled
disabled=post.isScheduled
static=true
}}
</div>
<div class="form-group">
<label for="tag-input">Tags</label>
{{gh-psm-tags-input post=model triggerId="tag-input"}}
{{gh-psm-tags-input post=post triggerId="tag-input"}}
</div>
{{#gh-form-group errors=model.errors hasValidated=model.hasValidated property="customExcerpt"}}
{{#gh-form-group errors=post.errors hasValidated=post.hasValidated property="customExcerpt"}}
<label for="custom-excerpt">Excerpt</label>
{{gh-textarea customExcerptScratch
class="post-setting-custom-excerpt"
@ -80,7 +80,7 @@
stopEnterKeyDownPropagation="true"
update=(action (mut customExcerptScratch))
data-test-field="custom-excerpt"}}
{{gh-error-message errors=model.errors property="customExcerpt" data-test-error="custom-excerpt"}}
{{gh-error-message errors=post.errors property="customExcerpt" data-test-error="custom-excerpt"}}
{{/gh-form-group}}
{{#unless session.user.isAuthor}}
@ -137,11 +137,11 @@
<div class="form-group for-checkbox">
<label class="checkbox" for="static-page" {{action "togglePage" bubbles="false"}}>
{{one-way-checkbox model.page
{{one-way-checkbox post.page
name="static-page"
id="static-page"
class="gh-input post-setting-static-page"
update=(action (mut model.page))
update=(action (mut post.page))
data-test-checkbox="static-page"
}}
<span class="input-toggle-component"></span>
@ -149,11 +149,11 @@
</label>
<label class="checkbox" for="featured" {{action "toggleFeatured" bubbles="false"}}>
{{one-way-checkbox model.featured
{{one-way-checkbox post.featured
name="featured"
id="featured"
class="gh-input post-setting-featured"
update=(action (mut model.featured))
update=(action (mut post.featured))
data-test-checkbox="featured"
}}
<span class="input-toggle-component"></span>
@ -162,10 +162,10 @@
</div>
{{gh-psm-template-select
post=model
onTemplateSelect=(action (mut model.customTemplate))}}
post=post
onTemplateSelect=(action (mut post.customTemplate))}}
{{#unless model.isNew}}
{{#unless post.isNew}}
<button type="button" class="gh-btn gh-btn-link gh-btn-icon settings-menu-delete-button" {{action "deletePost"}}><span>{{inline-svg "trash"}} Delete Post</span></button>
{{/unless}}
@ -185,7 +185,7 @@
<div class="settings-menu-content">
<form {{action "discardEnter" on="submit"}}>
{{#gh-form-group errors=model.errors hasValidated=model.hasValidated property="metaTitle"}}
{{#gh-form-group errors=post.errors hasValidated=post.hasValidated property="metaTitle"}}
<label for="meta-title">Meta Title</label>
{{gh-input metaTitleScratch
class="post-setting-meta-title"
@ -196,10 +196,10 @@
update=(action (mut metaTitleScratch))
data-test-field="meta-title"}}
<p>Recommended: <b>70</b> characters. Youve used {{gh-count-down-characters metaTitleScratch 70}}</p>
{{gh-error-message errors=model.errors property="meta-title"}}
{{gh-error-message errors=post.errors property="meta-title"}}
{{/gh-form-group}}
{{#gh-form-group errors=model.errors hasValidated=model.hasValidated property="metaDescription"}}
{{#gh-form-group errors=post.errors hasValidated=post.hasValidated property="metaDescription"}}
<label for="meta-description">Meta Description</label>
{{gh-textarea metaDescriptionScratch
class="post-setting-meta-description"
@ -210,7 +210,7 @@
update=(action (mut metaDescriptionScratch))
data-test-field="meta-description"}}
<p>Recommended: <b>156</b> characters. Youve used {{gh-count-down-characters metaDescriptionScratch 156}}</p>
{{gh-error-message errors=model.errors property="meta-description"}}
{{gh-error-message errors=post.errors property="meta-description"}}
{{/gh-form-group}}
<div class="form-group">
@ -236,13 +236,13 @@
<form {{action "discardEnter" on="submit"}}>
{{gh-image-uploader-with-preview
image=model.twitterImage
image=post.twitterImage
text="Add Twitter image"
allowUnsplash=true
update=(action "setTwitterImage")
remove=(action "clearTwitterImage")
}}
{{#gh-form-group errors=model.errors hasValidated=model.hasValidated property="twitterTitle"}}
{{#gh-form-group errors=post.errors hasValidated=post.hasValidated property="twitterTitle"}}
<label for="twitter-title">Twitter Title</label>
{{gh-input twitterTitleScratch
class="post-setting-twitter-title"
@ -253,10 +253,10 @@
stopEnterKeyDownPropagation="true"
update=(action (mut twitterTitleScratch))
data-test-field="twitter-title"}}
{{gh-error-message errors=model.errors property="twitterTitle" data-test-error="twitter-title"}}
{{gh-error-message errors=post.errors property="twitterTitle" data-test-error="twitter-title"}}
{{/gh-form-group}}
{{#gh-form-group errors=model.errors hasValidated=model.hasValidated property="twitterDescription"}}
{{#gh-form-group errors=post.errors hasValidated=post.hasValidated property="twitterDescription"}}
<label for="twitter-description">Twitter Description</label>
{{gh-textarea twitterDescriptionScratch
class="post-setting-twitter-description"
@ -267,7 +267,7 @@
stopEnterKeyDownPropagation="true"
update=(action (mut twitterDescriptionScratch))
data-test-field="twitter-description"}}
{{gh-error-message errors=model.errors property="twitterDescription" data-test-error="twitter-description"}}
{{gh-error-message errors=post.errors property="twitterDescription" data-test-error="twitter-description"}}
{{/gh-form-group}}
<div class="form-group">
@ -304,13 +304,13 @@
<div class="settings-menu-content">
<form {{action "discardEnter" on="submit"}}>
{{gh-image-uploader-with-preview
image=model.ogImage
image=post.ogImage
text="Add Facebook image"
allowUnsplash=true
update=(action "setOgImage")
remove=(action "clearOgImage")
}}
{{#gh-form-group errors=model.errors hasValidated=model.hasValidated property="ogTitle"}}
{{#gh-form-group errors=post.errors hasValidated=post.hasValidated property="ogTitle"}}
<label for="og-title">Facebook Title</label>
{{gh-input ogTitleScratch
class="post-setting-og-title"
@ -321,10 +321,10 @@
stopEnterKeyDownPropagation="true"
update=(action (mut ogTitleScratch))
data-test-field="og-title"}}
{{gh-error-message errors=model.errors property="ogTitle" data-test-error="og-title"}}
{{gh-error-message errors=post.errors property="ogTitle" data-test-error="og-title"}}
{{/gh-form-group}}
{{#gh-form-group errors=model.errors hasValidated=model.hasValidated property="ogDescription"}}
{{#gh-form-group errors=post.errors hasValidated=post.hasValidated property="ogDescription"}}
<label for="og-description">Facebook Description</label>
{{gh-textarea ogDescriptionScratch
class="post-setting-og-description"
@ -335,7 +335,7 @@
stopEnterKeyDownPropagation="true"
update=(action (mut ogDescriptionScratch))
data-test-field="og-description"}}
{{gh-error-message errors=model.errors property="ogDescription" data-test-error="og-description"}}
{{gh-error-message errors=post.errors property="ogDescription" data-test-error="og-description"}}
{{/gh-form-group}}
<div class="form-group">
@ -349,7 +349,7 @@
<div class="gh-og-preview-description">{{truncate facebookDescription 300}}</div>
<div class="gh-og-preview-footer">
<div class="gh-og-preview-footer-left">
{{config.blogDomain}} <span class="gh-og-preview-footer-left-divider">|</span> by <span class="gh-og-preview-footer-author">{{model.author.name}}</span>
{{config.blogDomain}} <span class="gh-og-preview-footer-left-divider">|</span> by <span class="gh-og-preview-footer-author">{{post.author.name}}</span>
</div>
<div class="gh-og-preview-footer-right">
</div>
@ -371,7 +371,7 @@
<div class="settings-menu-content settings-menu-content-codeinjection">
<form {{action "discardEnter" on="submit"}}>
{{#gh-form-group errors=model.errors hasValidated=model.hasValidated property="codeinjectionHead"}}
{{#gh-form-group errors=post.errors hasValidated=post.hasValidated property="codeinjectionHead"}}
<label for="codeinjection-head">Post Header <code>\{{ghost_head}}</code></label>
{{gh-cm-editor codeinjectionHeadScratch
id="post-setting-codeinjection-head"
@ -381,10 +381,10 @@
stopEnterKeyDownPropagation="true"
update=(action (mut codeinjectionHeadScratch))
data-test-field="codeinjection-head"}}
{{gh-error-message errors=model.errors property="codeinjectionHead" data-test-error="codeinjection-head"}}
{{gh-error-message errors=post.errors property="codeinjectionHead" data-test-error="codeinjection-head"}}
{{/gh-form-group}}
{{#gh-form-group errors=model.errors hasValidated=model.hasValidated property="codeinjectionFoot"}}
{{#gh-form-group errors=post.errors hasValidated=post.hasValidated property="codeinjectionFoot"}}
<label for="codeinjection-foot">Post Footer <code>\{{ghost_foot}}</code></label>
{{gh-cm-editor codeinjectionFootScratch
id="post-setting-codeinjection-foot"
@ -394,7 +394,7 @@
stopEnterKeyDownPropagation="true"
update=(action (mut codeinjectionFootScratch))
data-test-field="codeinjection-foot"}}
{{gh-error-message errors=model.errors property="codeinjectionFoot" data-test-error="codeinjection-foot"}}
{{gh-error-message errors=post.errors property="codeinjectionFoot" data-test-error="codeinjection-foot"}}
{{/gh-form-group}}
</form>
</div>

View File

@ -5,10 +5,10 @@
<div class="modal-body">
<fieldset>
{{#gh-form-group errors=model.errors hasValidated=model.hasValidated property="email"}}
{{#gh-form-group errors=subscriber.errors hasValidated=subscriber.hasValidated property="email"}}
<label for="new-subscriber-email">Email Address</label>
<input type="email"
value={{model.email}}
value={{subscriber.email}}
oninput={{action "updateEmail" value="target.value"}}
id="new-subscriber-email"
class="gh-input email"
@ -17,7 +17,7 @@
autofocus="autofocus"
autocapitalize="off"
autocorrect="off">
{{gh-error-message errors=model.errors property="email"}}
{{gh-error-message errors=subscriber.errors property="email"}}
{{/gh-form-group}}
</fieldset>

View File

@ -7,7 +7,7 @@
<header class="gh-editor-header {{editor.headerClass}}">
<div class="gh-editor-status">
{{gh-editor-post-status
post=model
post=post
isSaving=(or autosave.isRunning saveTasks.isRunning)
}}
</div>
@ -17,9 +17,9 @@
</time>
{{/if}}
<section class="view-actions">
{{#unless model.isNew}}
{{#unless post.isNew}}
{{gh-publishmenu
post=model
post=post
saveTask=save
setSaveType=(action "setSaveType")
onOpen=(action "cancelAutosave")}}
@ -40,7 +40,7 @@
placeholder="Begin writing your story..."
autofocus=shouldFocusEditor
uploadedImageUrls=editor.uploadedImageUrls
mobiledoc=(readonly model.scratch)
mobiledoc=(readonly post.scratch)
isFullScreen=editor.isFullScreen
onChange=(action "updateScratch")
onFullScreenToggle=(action editor.toggleFullScreen)
@ -51,7 +51,7 @@
as |markdown|
}}
<div class="gh-markdown-editor-pane">
{{gh-textarea model.titleScratch
{{gh-textarea post.titleScratch
class="gh-editor-title"
placeholder="Post Title"
tabindex="1"
@ -69,7 +69,7 @@
{{#if markdown.isSplitScreen}}
<div class="gh-markdown-editor-preview">
<h1 class="gh-markdown-editor-preview-title">{{model.titleScratch}}</h1>
<h1 class="gh-markdown-editor-preview-title">{{post.titleScratch}}</h1>
<div class="gh-markdown-editor-preview-content"></div>
</div>
{{/if}}
@ -129,7 +129,7 @@
{{#if showDeletePostModal}}
{{gh-fullscreen-modal "delete-post"
model=(hash post=model onSuccess=(route-action 'redirectToContentScreen'))
model=(hash post=post onSuccess=(route-action 'redirectToContentScreen'))
close=(action "toggleDeletePostModal")
modifier="action wide"}}
{{/if}}
@ -149,7 +149,7 @@
{{#liquid-wormhole}}
{{gh-post-settings-menu
model=model
post=post
showSettingsMenu=ui.showSettingsMenu
deletePost=(action "toggleDeletePostModal")
updateSlug=updateSlug

View File

@ -84,7 +84,7 @@
<section class="content-list">
<ol class="posts-list">
{{#each model as |post|}}
{{#each postsInfinityModel as |post|}}
{{gh-posts-list-item
post=post
onDoubleClick=(action "openEditor")
@ -105,7 +105,7 @@
</ol>
{{infinity-loader
infinityModel=model
infinityModel=postsInfinityModel
scrollable=".gh-main"
triggerOffset=1000}}
</section>

View File

@ -38,7 +38,7 @@
<div class="gh-setting-action">
<div class="for-checkbox">
<label for="amp" class="checkbox">
{{one-way-checkbox model id="amp" name="amp" type="checkbox" update=(action "update") data-test-amp-checkbox=true}}
{{one-way-checkbox ampSettings id="amp" name="amp" type="checkbox" update=(action "update") data-test-amp-checkbox=true}}
<span class="input-toggle-component"></span>
</label>
</div>

View File

@ -36,12 +36,12 @@
<div class="gh-setting-title">Webhook URL</div>
<div class="gh-setting-desc">Automatically send newly published posts to a channel in Slack or any Slack-compatible service like Discord or Mattermost.</div>
<div class="gh-setting-content-extended">
{{#gh-form-group errors=model.errors hasValidated=model.hasValidated property="url"}}
{{gh-input model.url name="slack[url]" update=(action "updateURL") onenter=(action "save") focusOut=(action "triggerDirtyState") placeholder="https://hooks.slack.com/services/..." data-test-slack-url-input=true}}
{{#unless model.errors.url}}
{{#gh-form-group errors=slackSettings.errors hasValidated=slackSettings.hasValidated property="url"}}
{{gh-input slackSettings.url name="slack[url]" update=(action "updateURL") onenter=(action "save") focusOut=(action "triggerDirtyState") placeholder="https://hooks.slack.com/services/..." data-test-slack-url-input=true}}
{{#unless slackSettings.errors.url}}
<p>Set up a new incoming webhook <a href="https://my.slack.com/apps/new/A0F7XDUAZ-incoming-webhooks" target="_blank">here</a>, and grab the URL.</p>
{{else}}
{{gh-error-message errors=model.errors property="url"}}
{{gh-error-message errors=slackSettings.errors property="url"}}
{{/unless}}
{{/gh-form-group}}
</div>

View File

@ -39,7 +39,7 @@
<div class="form-group right">
<div class="for-checkbox">
<label for="isActive" class="checkbox">
{{one-way-checkbox model.isActive id="isActive" name="isActive" type="checkbox" update=(action "update") data-test-checkbox="unsplash"}}
{{one-way-checkbox unsplashSettings.isActive id="isActive" name="isActive" type="checkbox" update=(action "update") data-test-checkbox="unsplash"}}
<span class="input-toggle-component"></span>
</label>
</div>

View File

@ -23,13 +23,13 @@
<div class="form-group settings-code">
<label for="ghost-head">Blog Header</label>
<p>Code here will be injected into the <code>\{{ghost_head}}</code> tag on every page of your blog</p>
{{gh-cm-editor model.ghostHead id="ghost-head" class="gh-input settings-code-editor" name="codeInjection[ghost_head]" type="text" update=(action (mut model.ghostHead))}}
{{gh-cm-editor settings.ghostHead id="ghost-head" class="gh-input settings-code-editor" name="codeInjection[ghost_head]" type="text" update=(action (mut settings.ghostHead))}}
</div>
<div class="form-group settings-code">
<label for="ghost-foot">Blog Footer</label>
<p>Code here will be injected into the <code>\{{ghost_foot}}</code> tag on every page of your blog</p>
{{gh-cm-editor model.ghostFoot id="ghost-foot" class="gh-input settings-code-editor" name="codeInjection[ghost_foot]" type="text" update=(action (mut model.ghostFoot))}}
{{gh-cm-editor settings.ghostFoot id="ghost-foot" class="gh-input settings-code-editor" name="codeInjection[ghost_foot]" type="text" update=(action (mut settings.ghostFoot))}}
</div>
</fieldset>
</form>

View File

@ -17,8 +17,8 @@
<div class="gh-setting-header">Navigation</div>
<div class="gh-blognav-container">
<form id="settings-navigation" class="gh-blognav" novalidate="novalidate">
{{#sortable-objects sortableObjectList=model.navigation useSwap=false}}
{{#each model.navigation as |navItem|}}
{{#sortable-objects sortableObjectList=settings.navigation useSwap=false}}
{{#each settings.navigation as |navItem|}}
{{#draggable-object content=navItem dragHandle=".gh-blognav-grab" isSortable=true}}
{{gh-navitem navItem=navItem baseUrl=blogUrl addItem=(action "addNavItem") deleteItem=(action "deleteNavItem") updateUrl=(action "updateUrl") updateLabel=(action "updateLabel")}}
{{/draggable-object}}

View File

@ -1,6 +1,6 @@
{{gh-fullscreen-modal "upload-theme"
model=(hash
themes=model
themes=themes
activate=(route-action 'activateTheme')
)
close=(route-action "cancel")

View File

@ -22,15 +22,15 @@
<div class="gh-setting-desc">The details used to identify your publication around the web</div>
{{#liquid-if pubInfoOpen}}
<div class="gh-setting-content-extended">
{{#gh-form-group errors=model.errors hasValidated=model.hasValidated property="title"}}
{{gh-input model.title type="text" focusOut=(action "validate" "title" target=model) update=(action (mut model.title)) data-test-title-input=true}}
{{gh-error-message errors=model.errors property="title"}}
{{#gh-form-group errors=settings.errors hasValidated=settings.hasValidated property="title"}}
{{gh-input settings.title type="text" focusOut=(action "validate" "title" target=settings) update=(action (mut settings.title)) data-test-title-input=true}}
{{gh-error-message errors=settings.errors property="title"}}
<p>The name of your site</p>
{{/gh-form-group}}
{{#gh-form-group errors=model.errors hasValidated=model.hasValidated property="description" class="description-container"}}
{{gh-input model.description type="text" focusOut=(action "validate" "description" target=model) update=(action (mut model.description)) data-test-description-input=true}}
{{gh-error-message errors=model.errors property="description"}}
{{#gh-form-group errors=settings.errors hasValidated=settings.hasValidated property="description" class="description-container"}}
{{gh-input settings.description type="text" focusOut=(action "validate" "description" target=settings) update=(action (mut settings.description)) data-test-description-input=true}}
{{gh-error-message errors=settings.errors property="description"}}
<p>Used in your theme, meta data and search results</p>
{{/gh-form-group}}
</div>
@ -47,7 +47,7 @@
{{#liquid-if timezoneOpen}}
<div class="gh-setting-content-extended">
{{gh-timezone-select
activeTimezone=model.activeTimezone
activeTimezone=settings.activeTimezone
availableTimezones=availableTimezones
update=(action "setTimezone")}}
</div>
@ -63,9 +63,9 @@
<div class="gh-setting-desc">Set the language/locale which is used on your site</div>
{{#liquid-if defaultLocaleOpen}}
<div class="gh-setting-content-extended">
{{#gh-form-group errors=model.errors hasValidated=model.hasValidated property="defaultLocale"}}
{{gh-input model.defaultLocale type="text" focusOut=(action "validate" "defaultLocale" target=model) update=(action (mut model.defaultLocale)) data-test-default-locale-input=true}}
{{gh-error-message errors=model.errors property="defaultLocale"}}
{{#gh-form-group errors=settings.errors hasValidated=settings.hasValidated property="defaultLocale"}}
{{gh-input settings.defaultLocale type="text" focusOut=(action "validate" "defaultLocale" target=settings) update=(action (mut settings.defaultLocale)) data-test-default-locale-input=true}}
{{gh-error-message errors=settings.errors property="defaultLocale"}}
<p>Default: English (<strong>en</strong>); you can add translation files to your theme for <a href="https://themes.ghost.org/v1.20.0/docs/i18n" target="_blank" rel="noopener">any language</a></p>
{{/gh-form-group}}
</div>
@ -94,8 +94,8 @@
<div class="gh-setting-action gh-setting-action-smallimg">
{{#if uploader.isUploading}}
{{uploader.progressBar}}
{{else if model.icon}}
<img class="blog-icon" src="{{model.icon}}" onclick={{action "triggerFileDialog"}} alt="icon" data-test-icon-img>
{{else if settings.icon}}
<img class="blog-icon" src="{{settings.icon}}" onclick={{action "triggerFileDialog"}} alt="icon" data-test-icon-img>
<button type="button" class="gh-setting-action-smallimg-delete" {{action "removeImage" "icon"}} data-test-delete-image="icon">
<span>delete</span>
</button>
@ -126,8 +126,8 @@
<div class="gh-setting-action gh-setting-action-smallimg">
{{#if uploader.isUploading}}
{{uploader.progressBar}}
{{else if model.logo}}
<img class="blog-logo" src="{{model.logo}}" onclick={{action "triggerFileDialog"}} alt="logo" data-test-logo-img>
{{else if settings.logo}}
<img class="blog-logo" src="{{settings.logo}}" onclick={{action "triggerFileDialog"}} alt="logo" data-test-logo-img>
<button type="button" class="gh-setting-action-smallimg-delete" {{action "removeImage" "logo"}} data-test-delete-image="logo">
<span>delete</span>
</button>
@ -158,8 +158,8 @@
<div class="gh-setting-action gh-setting-action-largeimg">
{{#if uploader.isUploading}}
{{uploader.progressBar}}
{{else if model.coverImage}}
<img class="blog-cover" src="{{model.coverImage}}" onclick={{action "triggerFileDialog"}} alt="cover photo" data-test-cover-img>
{{else if settings.coverImage}}
<img class="blog-cover" src="{{settings.coverImage}}" onclick={{action "triggerFileDialog"}} alt="cover photo" data-test-cover-img>
<button type="button" class="gh-setting-action-largeimg-delete" {{action "removeImage" "coverImage"}} data-test-delete-image="coverImage">
<span>delete</span>
</button>
@ -182,14 +182,14 @@
<div class="gh-setting-desc">Link your social accounts for full structured data and rich card support</div>
{{#liquid-if socialOpen}}
<div class="gh-setting-content-extended">
{{#gh-form-group errors=model.errors hasValidated=model.hasValidated property="facebook"}}
<input value={{model.facebook}} oninput={{action (mut _scratchFacebook) value="target.value"}} {{action "validateFacebookUrl" on="focusOut"}} type="url" class="gh-input" placeholder="https://www.facebook.com/ghost" autocorrect="off" data-test-facebook-input />
{{gh-error-message errors=model.errors property="facebook" data-test-facebook-error=true}}
{{#gh-form-group errors=settings.errors hasValidated=settings.hasValidated property="facebook"}}
<input value={{settings.facebook}} oninput={{action (mut _scratchFacebook) value="target.value"}} {{action "validateFacebookUrl" on="focusOut"}} type="url" class="gh-input" placeholder="https://www.facebook.com/ghost" autocorrect="off" data-test-facebook-input />
{{gh-error-message errors=settings.errors property="facebook" data-test-facebook-error=true}}
<p>URL of your publication's Facebook Page</p>
{{/gh-form-group}}
{{#gh-form-group errors=model.errors hasValidated=model.hasValidated property="twitter"}}
<input value={{model.twitter}} oninput={{action (mut _scratchTwitter) value="target.value"}} {{action "validateTwitterUrl" on="focusOut"}} type="url" class="gh-input" placeholder="https://twitter.com/tryghost" autocorrect="off" data-test-twitter-input />
{{gh-error-message errors=model.errors property="twitter" data-test-twitter-error=true}}
{{#gh-form-group errors=settings.errors hasValidated=settings.hasValidated property="twitter"}}
<input value={{settings.twitter}} oninput={{action (mut _scratchTwitter) value="target.value"}} {{action "validateTwitterUrl" on="focusOut"}} type="url" class="gh-input" placeholder="https://twitter.com/tryghost" autocorrect="off" data-test-twitter-input />
{{gh-error-message errors=settings.errors property="twitter" data-test-twitter-error=true}}
<p>URL of your publication's Twitter profile</p>
{{/gh-form-group}}
</div>
@ -221,7 +221,7 @@
<div class="gh-setting-desc">
Enable protection with simple shared password, All search engine optimization and social features will be disabled.
{{#if model.isPrivate}}
{{#if settings.isPrivate}}
<span class="avoid-break-out">
<br><br>
A private RSS feed is available at
@ -230,11 +230,11 @@
{{/if}}
</div>
{{#if model.isPrivate}}
{{#if settings.isPrivate}}
<div class="gh-setting-content-extended">
{{#gh-form-group errors=model.errors hasValidated=model.hasValidated property="password"}}
{{gh-input model.password name="general[password]" type="text" focusOut=(action "validate" "password" target=model) update=(action (mut model.password)) data-test-password-input=true}}
{{gh-error-message errors=model.errors property="password" data-test-password-error=true}}
{{#gh-form-group errors=settings.errors hasValidated=settings.hasValidated property="password"}}
{{gh-input settings.password name="general[password]" type="text" focusOut=(action "validate" "password" target=settings) update=(action (mut settings.password)) data-test-password-input=true}}
{{gh-error-message errors=settings.errors property="password" data-test-password-error=true}}
<p>Set the password for this site</p>
{{/gh-form-group}}
</div>
@ -243,7 +243,7 @@
<div class="gh-setting-action">
<div class="for-checkbox">
<label class="checkbox" for="settings-private">
{{one-way-checkbox model.isPrivate id="settings-private" type="checkbox" update=(action (mut model.isPrivate)) data-test-private-checkbox=true}}
{{one-way-checkbox settings.isPrivate id="settings-private" type="checkbox" update=(action (mut settings.isPrivate)) data-test-private-checkbox=true}}
<span class="input-toggle-component"></span>
</label>
</div>

View File

@ -2,10 +2,10 @@
<div class="gh-flow-content-wrap">
<section class="gh-flow-content">
<form id="login" method="post" class="gh-signin" novalidate="novalidate" {{action "authenticate" on="submit"}}>
{{#gh-form-group errors=model.errors hasValidated=hasValidated property="identification"}}
{{#gh-form-group errors=signin.errors hasValidated=hasValidated property="identification"}}
<span class="gh-input-icon gh-icon-mail">
{{inline-svg "email"}}
{{gh-trim-focus-input model.identification
{{gh-trim-focus-input signin.identification
class="email"
type="email"
placeholder="Email Address"
@ -14,20 +14,20 @@
autocorrect="off"
tabindex="1"
focusOut=(action "validate" "identification")
update=(action (mut model.identification))}}
update=(action (mut signin.identification))}}
</span>
{{/gh-form-group}}
{{#gh-form-group errors=model.errors hasValidated=hasValidated property="password"}}
{{#gh-form-group errors=signin.errors hasValidated=hasValidated property="password"}}
<span class="gh-input-icon gh-icon-lock forgotten-wrap">
{{inline-svg "lock"}}
{{gh-input model.password
{{gh-input signin.password
class="password"
type="password"
placeholder="Password"
name="password"
tabindex="2"
autocorrect="off"
update=(action (mut model.password))}}
update=(action (mut signin.password))}}
{{#gh-task-button
task=forgotten

View File

@ -11,13 +11,13 @@
<input style="display:none;" type="text" name="fakeusernameremembered"/>
<input style="display:none;" type="password" name="fakepasswordremembered"/>
{{gh-profile-image email=model.email setImage=(action "setImage")}}
{{gh-profile-image email=signupDetails.email setImage=(action "setImage")}}
{{#gh-form-group}}
{{#gh-form-group errors=signupDetails.errors hasValidated=hasValidated property="email"}}
<label for="email-address">Email address</label>
<span class="gh-input-icon gh-icon-mail">
{{inline-svg "email"}}
{{gh-input model.email
{{gh-input signupDetails.email
type="email"
name="email"
placeholder="Eg. john@example.com"
@ -26,11 +26,11 @@
</span>
{{/gh-form-group}}
{{#gh-form-group errors=model.errors hasValidated=hasValidated property="name"}}
{{#gh-form-group errors=signupDetails.errors hasValidated=hasValidated property="name"}}
<label for="full-name">Full name</label>
<span class="gh-input-icon gh-icon-user">
{{inline-svg "user-circle"}}
{{gh-trim-focus-input model.name
{{gh-trim-focus-input signupDetails.name
tabindex="1"
type="text"
name="name"
@ -38,16 +38,16 @@
autocorrect="off"
onenter=(action "signup")
focusOut=(action "validate" "name")
update=(action (mut model.name))}}
update=(action (mut signupDetails.name))}}
</span>
{{gh-error-message errors=model.errors property="name"}}
{{gh-error-message errors=signupDetails.errors property="name"}}
{{/gh-form-group}}
{{#gh-form-group errors=model.errors hasValidated=hasValidated property="password"}}
{{#gh-form-group errors=signupDetails.errors hasValidated=hasValidated property="password"}}
<label for="password">Password</label>
<span class="gh-input-icon gh-icon-lock">
{{inline-svg "lock"}}
{{gh-input model.password
{{gh-input signupDetails.password
tabindex="2"
type="password"
name="password"
@ -55,9 +55,9 @@
onenter=(action "signup")
autocorrect="off"
focusOut=(action "validate" "password")
update=(action (mut model.password))}}
update=(action (mut signupDetails.password))}}
</span>
{{gh-error-message errors=model.errors property="password"}}
{{gh-error-message errors=signupDetails.errors property="password"}}
{{/gh-form-group}}
</form>

View File

@ -1,5 +1,5 @@
{{gh-fullscreen-modal "new-subscriber"
model=model
model=subscriber
confirm=(route-action "save")
close=(route-action "cancel")
modifier="action wide"}}

View File

@ -156,7 +156,7 @@ describe('Acceptance: Settings - Design', function () {
expect(
find('.gh-blognav-url:last input').val()
).to.equal(`${window.location.protocol}//${window.location.host}/new`);
).to.equal(`${window.location.origin}/new`);
await click('.gh-blognav-add');
@ -173,7 +173,7 @@ describe('Acceptance: Settings - Design', function () {
expect(
find('.gh-blognav-url:last input').val(),
'new item url value after successful add'
).to.equal(`${window.location.protocol}//${window.location.host}/`);
).to.equal(`${window.location.origin}/`);
expect(
find('.gh-blognav-item .response:visible').length,

View File

@ -362,7 +362,7 @@ describe('Acceptance: Team', function () {
await visit('/team');
await click(`[data-test-user-id="${suspendedUser.id}"]`);
expect('[data-test-suspended-badge]').to.exist;
expect(find('[data-test-suspended-badge]')).to.exist;
await click('[data-test-user-actions]');
await click('[data-test-unsuspend-button]');
@ -376,7 +376,6 @@ describe('Acceptance: Team', function () {
// });
await click('[data-test-team-link]');
// suspendedUser is now in active list
expect(
find(`[data-test-active-users] [data-test-user-id="${suspendedUser.id}"]`)
@ -392,8 +391,7 @@ describe('Acceptance: Team', function () {
await click('[data-test-user-actions]');
await click('[data-test-suspend-button]');
await click('[data-test-modal-confirm]');
expect('[data-test-suspended-badge]').to.exist;
expect(find('[data-test-suspended-badge]')).to.exist;
});
it('can delete users', async function () {

View File

@ -17,18 +17,18 @@ describe.skip('Unit: Component: post-settings-menu', function () {
needs: ['service:notifications', 'service:slug-generator', 'service:settings']
});
it('slugValue is one-way bound to model.slug', function () {
it('slugValue is one-way bound to post.slug', function () {
let component = this.subject({
model: EmberObject.create({
post: EmberObject.create({
slug: 'a-slug'
})
});
expect(component.get('model.slug')).to.equal('a-slug');
expect(component.get('post.slug')).to.equal('a-slug');
expect(component.get('slugValue')).to.equal('a-slug');
run(function () {
component.set('model.slug', 'changed-slug');
component.set('post.slug', 'changed-slug');
expect(component.get('slugValue')).to.equal('changed-slug');
});
@ -36,31 +36,31 @@ describe.skip('Unit: Component: post-settings-menu', function () {
run(function () {
component.set('slugValue', 'changed-directly');
expect(component.get('model.slug')).to.equal('changed-slug');
expect(component.get('post.slug')).to.equal('changed-slug');
expect(component.get('slugValue')).to.equal('changed-directly');
});
run(function () {
// test that the one-way binding is still in place
component.set('model.slug', 'should-update');
component.set('post.slug', 'should-update');
expect(component.get('slugValue')).to.equal('should-update');
});
});
it('metaTitleScratch is one-way bound to model.metaTitle', function () {
it('metaTitleScratch is one-way bound to post.metaTitle', function () {
let component = this.subject({
model: EmberObject.extend({
post: EmberObject.extend({
metaTitle: 'a title',
metaTitleScratch: boundOneWay('metaTitle')
}).create()
});
expect(component.get('model.metaTitle')).to.equal('a title');
expect(component.get('post.metaTitle')).to.equal('a title');
expect(component.get('metaTitleScratch')).to.equal('a title');
run(function () {
component.set('model.metaTitle', 'a different title');
component.set('post.metaTitle', 'a different title');
expect(component.get('metaTitleScratch')).to.equal('a different title');
});
@ -68,31 +68,31 @@ describe.skip('Unit: Component: post-settings-menu', function () {
run(function () {
component.set('metaTitleScratch', 'changed directly');
expect(component.get('model.metaTitle')).to.equal('a different title');
expect(component.get('model.metaTitleScratch')).to.equal('changed directly');
expect(component.get('post.metaTitle')).to.equal('a different title');
expect(component.get('post.metaTitleScratch')).to.equal('changed directly');
});
run(function () {
// test that the one-way binding is still in place
component.set('model.metaTitle', 'should update');
component.set('post.metaTitle', 'should update');
expect(component.get('metaTitleScratch')).to.equal('should update');
});
});
it('metaDescriptionScratch is one-way bound to model.metaDescription', function () {
it('metaDescriptionScratch is one-way bound to post.metaDescription', function () {
let component = this.subject({
model: EmberObject.extend({
post: EmberObject.extend({
metaDescription: 'a description',
metaDescriptionScratch: boundOneWay('metaDescription')
}).create()
});
expect(component.get('model.metaDescription')).to.equal('a description');
expect(component.get('post.metaDescription')).to.equal('a description');
expect(component.get('metaDescriptionScratch')).to.equal('a description');
run(function () {
component.set('model.metaDescription', 'a different description');
component.set('post.metaDescription', 'a different description');
expect(component.get('metaDescriptionScratch')).to.equal('a different description');
});
@ -100,13 +100,13 @@ describe.skip('Unit: Component: post-settings-menu', function () {
run(function () {
component.set('metaDescriptionScratch', 'changed directly');
expect(component.get('model.metaDescription')).to.equal('a different description');
expect(component.get('post.metaDescription')).to.equal('a different description');
expect(component.get('metaDescriptionScratch')).to.equal('changed directly');
});
run(function () {
// test that the one-way binding is still in place
component.set('model.metaDescription', 'should update');
component.set('post.metaDescription', 'should update');
expect(component.get('metaDescriptionScratch')).to.equal('should update');
});
@ -115,7 +115,7 @@ describe.skip('Unit: Component: post-settings-menu', function () {
describe('seoTitle', function () {
it('should be the metaTitle if one exists', function () {
let component = this.subject({
model: EmberObject.extend({
post: EmberObject.extend({
metaTitle: 'a meta-title',
metaTitleScratch: boundOneWay('metaTitle'),
titleScratch: 'should not be used'
@ -127,7 +127,7 @@ describe.skip('Unit: Component: post-settings-menu', function () {
it('should default to the title if an explicit meta-title does not exist', function () {
let component = this.subject({
model: EmberObject.create({
post: EmberObject.create({
titleScratch: 'should be the meta-title'
})
});
@ -137,7 +137,7 @@ describe.skip('Unit: Component: post-settings-menu', function () {
it('should be the metaTitle if both title and metaTitle exist', function () {
let component = this.subject({
model: EmberObject.extend({
post: EmberObject.extend({
metaTitle: 'a meta-title',
metaTitleScratch: boundOneWay('metaTitle'),
titleScratch: 'a title'
@ -149,7 +149,7 @@ describe.skip('Unit: Component: post-settings-menu', function () {
it('should revert to the title if explicit metaTitle is removed', function () {
let component = this.subject({
model: EmberObject.extend({
post: EmberObject.extend({
metaTitle: 'a meta-title',
metaTitleScratch: boundOneWay('metaTitle'),
titleScratch: 'a title'
@ -159,7 +159,7 @@ describe.skip('Unit: Component: post-settings-menu', function () {
expect(component.get('seoTitle')).to.equal('a meta-title');
run(function () {
component.set('model.metaTitle', '');
component.set('post.metaTitle', '');
expect(component.get('seoTitle')).to.equal('a title');
});
@ -168,7 +168,7 @@ describe.skip('Unit: Component: post-settings-menu', function () {
it('should truncate to 70 characters with an appended ellipsis', function () {
let longTitle = new Array(100).join('a');
let component = this.subject({
model: EmberObject.create()
post: EmberObject.create()
});
expect(longTitle.length).to.equal(99);
@ -187,7 +187,7 @@ describe.skip('Unit: Component: post-settings-menu', function () {
describe('seoDescription', function () {
it('should be the metaDescription if one exists', function () {
let component = this.subject({
model: EmberObject.extend({
post: EmberObject.extend({
metaDescription: 'a description',
metaDescriptionScratch: boundOneWay('metaDescription')
}).create()
@ -198,7 +198,7 @@ describe.skip('Unit: Component: post-settings-menu', function () {
it('should be generated from the rendered mobiledoc if not explicitly set', function () {
let component = this.subject({
model: EmberObject.extend({
post: EmberObject.extend({
author: RSVP.resolve(),
metaDescription: null,
metaDescriptionScratch: boundOneWay('metaDescription'),
@ -222,7 +222,7 @@ describe.skip('Unit: Component: post-settings-menu', function () {
it('should truncate to 156 characters with an appended ellipsis', function () {
let longDescription = new Array(200).join('a');
let component = this.subject({
model: EmberObject.create()
post: EmberObject.create()
});
expect(longDescription.length).to.equal(199);
@ -242,7 +242,7 @@ describe.skip('Unit: Component: post-settings-menu', function () {
it('should be the URL of the blog if no post slug exists', function () {
let component = this.subject({
config: EmberObject.create({blogUrl: 'http://my-ghost-blog.com'}),
model: EmberObject.create()
post: EmberObject.create()
});
expect(component.get('seoURL')).to.equal('http://my-ghost-blog.com/');
@ -251,7 +251,7 @@ describe.skip('Unit: Component: post-settings-menu', function () {
it('should be the URL of the blog plus the post slug', function () {
let component = this.subject({
config: EmberObject.create({blogUrl: 'http://my-ghost-blog.com'}),
model: EmberObject.create({slug: 'post-slug'})
post: EmberObject.create({slug: 'post-slug'})
});
expect(component.get('seoURL')).to.equal('http://my-ghost-blog.com/post-slug/');
@ -260,13 +260,13 @@ describe.skip('Unit: Component: post-settings-menu', function () {
it('should update when the post slug changes', function () {
let component = this.subject({
config: EmberObject.create({blogUrl: 'http://my-ghost-blog.com'}),
model: EmberObject.create({slug: 'post-slug'})
post: EmberObject.create({slug: 'post-slug'})
});
expect(component.get('seoURL')).to.equal('http://my-ghost-blog.com/post-slug/');
run(function () {
component.set('model.slug', 'changed-slug');
component.set('post.slug', 'changed-slug');
expect(component.get('seoURL')).to.equal('http://my-ghost-blog.com/changed-slug/');
});
@ -277,7 +277,7 @@ describe.skip('Unit: Component: post-settings-menu', function () {
let longSlug = new Array(75).join('a');
let component = this.subject({
config: EmberObject.create({blogUrl: blogURL}),
model: EmberObject.create({slug: longSlug})
post: EmberObject.create({slug: longSlug})
});
let expected;
@ -294,24 +294,24 @@ describe.skip('Unit: Component: post-settings-menu', function () {
describe('togglePage', function () {
it('should toggle the page property', function () {
let component = this.subject({
model: EmberObject.create({
post: EmberObject.create({
page: false,
isNew: true
})
});
expect(component.get('model.page')).to.not.be.ok;
expect(component.get('post.page')).to.not.be.ok;
run(function () {
component.send('togglePage');
expect(component.get('model.page')).to.be.ok;
expect(component.get('post.page')).to.be.ok;
});
});
it('should not save the post if it is still new', function () {
let component = this.subject({
model: EmberObject.create({
post: EmberObject.create({
page: false,
isNew: true,
save() {
@ -324,14 +324,14 @@ describe.skip('Unit: Component: post-settings-menu', function () {
run(function () {
component.send('togglePage');
expect(component.get('model.page')).to.be.ok;
expect(component.get('model.saved')).to.not.be.ok;
expect(component.get('post.page')).to.be.ok;
expect(component.get('post.saved')).to.not.be.ok;
});
});
it('should save the post if it is not new', function () {
let component = this.subject({
model: EmberObject.create({
post: EmberObject.create({
page: false,
isNew: false,
save() {
@ -344,8 +344,8 @@ describe.skip('Unit: Component: post-settings-menu', function () {
run(function () {
component.send('togglePage');
expect(component.get('model.page')).to.be.ok;
expect(component.get('model.saved')).to.equal(1);
expect(component.get('post.page')).to.be.ok;
expect(component.get('post.saved')).to.equal(1);
});
});
});
@ -353,7 +353,7 @@ describe.skip('Unit: Component: post-settings-menu', function () {
describe('toggleFeatured', function () {
it('should toggle the featured property', function () {
let component = this.subject({
model: EmberObject.create({
post: EmberObject.create({
featured: false,
isNew: true
})
@ -362,13 +362,13 @@ describe.skip('Unit: Component: post-settings-menu', function () {
run(function () {
component.send('toggleFeatured');
expect(component.get('model.featured')).to.be.ok;
expect(component.get('post.featured')).to.be.ok;
});
});
it('should not save the post if it is still new', function () {
let component = this.subject({
model: EmberObject.create({
post: EmberObject.create({
featured: false,
isNew: true,
save() {
@ -381,14 +381,14 @@ describe.skip('Unit: Component: post-settings-menu', function () {
run(function () {
component.send('toggleFeatured');
expect(component.get('model.featured')).to.be.ok;
expect(component.get('model.saved')).to.not.be.ok;
expect(component.get('post.featured')).to.be.ok;
expect(component.get('post.saved')).to.not.be.ok;
});
});
it('should save the post if it is not new', function () {
let component = this.subject({
model: EmberObject.create({
post: EmberObject.create({
featured: false,
isNew: false,
save() {
@ -401,8 +401,8 @@ describe.skip('Unit: Component: post-settings-menu', function () {
run(function () {
component.send('toggleFeatured');
expect(component.get('model.featured')).to.be.ok;
expect(component.get('model.saved')).to.equal(1);
expect(component.get('post.featured')).to.be.ok;
expect(component.get('post.saved')).to.equal(1);
});
});
});
@ -410,7 +410,7 @@ describe.skip('Unit: Component: post-settings-menu', function () {
describe('updateSlug', function () {
it('should reset slugValue to the previous slug when the new slug is blank or unchanged', function () {
let component = this.subject({
model: EmberObject.create({
post: EmberObject.create({
slug: 'slug'
})
});
@ -420,7 +420,7 @@ describe.skip('Unit: Component: post-settings-menu', function () {
component.set('slugValue', 'slug');
component.send('updateSlug', component.get('slugValue'));
expect(component.get('model.slug')).to.equal('slug');
expect(component.get('post.slug')).to.equal('slug');
expect(component.get('slugValue')).to.equal('slug');
});
@ -429,7 +429,7 @@ describe.skip('Unit: Component: post-settings-menu', function () {
component.set('slugValue', 'slug ');
component.send('updateSlug', component.get('slugValue'));
expect(component.get('model.slug')).to.equal('slug');
expect(component.get('post.slug')).to.equal('slug');
expect(component.get('slugValue')).to.equal('slug');
});
@ -438,7 +438,7 @@ describe.skip('Unit: Component: post-settings-menu', function () {
component.set('slugValue', '');
component.send('updateSlug', component.get('slugValue'));
expect(component.get('model.slug')).to.equal('slug');
expect(component.get('post.slug')).to.equal('slug');
expect(component.get('slugValue')).to.equal('slug');
});
});
@ -452,7 +452,7 @@ describe.skip('Unit: Component: post-settings-menu', function () {
return promise;
}
}),
model: EmberObject.create({
post: EmberObject.create({
slug: 'whatever'
})
});
@ -462,7 +462,7 @@ describe.skip('Unit: Component: post-settings-menu', function () {
component.send('updateSlug', component.get('slugValue'));
RSVP.resolve(component.get('lastPromise')).then(function () {
expect(component.get('model.slug')).to.equal('whatever');
expect(component.get('post.slug')).to.equal('whatever');
done();
}).catch(done);
@ -479,7 +479,7 @@ describe.skip('Unit: Component: post-settings-menu', function () {
return promise;
}
}),
model: EmberObject.create({
post: EmberObject.create({
slug: 'whatever'
})
});
@ -489,7 +489,7 @@ describe.skip('Unit: Component: post-settings-menu', function () {
component.send('updateSlug', component.get('slugValue'));
RSVP.resolve(component.get('lastPromise')).then(function () {
expect(component.get('model.slug')).to.equal('whatever');
expect(component.get('post.slug')).to.equal('whatever');
done();
}).catch(done);
@ -505,7 +505,7 @@ describe.skip('Unit: Component: post-settings-menu', function () {
return promise;
}
}),
model: EmberObject.create({
post: EmberObject.create({
slug: 'whatever',
save: K
})
@ -516,7 +516,7 @@ describe.skip('Unit: Component: post-settings-menu', function () {
component.send('updateSlug', component.get('slugValue'));
RSVP.resolve(component.get('lastPromise')).then(function () {
expect(component.get('model.slug')).to.equal('changed');
expect(component.get('post.slug')).to.equal('changed');
done();
}).catch(done);
@ -532,7 +532,7 @@ describe.skip('Unit: Component: post-settings-menu', function () {
return promise;
}
}),
model: EmberObject.create({
post: EmberObject.create({
slug: 'whatever',
saved: 0,
isNew: false,
@ -547,8 +547,8 @@ describe.skip('Unit: Component: post-settings-menu', function () {
component.send('updateSlug', component.get('slugValue'));
RSVP.resolve(component.get('lastPromise')).then(function () {
expect(component.get('model.slug')).to.equal('changed');
expect(component.get('model.saved')).to.equal(1);
expect(component.get('post.slug')).to.equal('changed');
expect(component.get('post.saved')).to.equal(1);
done();
}).catch(done);
@ -564,7 +564,7 @@ describe.skip('Unit: Component: post-settings-menu', function () {
return promise;
}
}),
model: EmberObject.create({
post: EmberObject.create({
slug: 'whatever',
saved: 0,
isNew: true,
@ -579,8 +579,8 @@ describe.skip('Unit: Component: post-settings-menu', function () {
component.send('updateSlug', component.get('slugValue'));
RSVP.resolve(component.get('lastPromise')).then(function () {
expect(component.get('model.slug')).to.equal('changed');
expect(component.get('model.saved')).to.equal(0);
expect(component.get('post.slug')).to.equal('changed');
expect(component.get('post.saved')).to.equal(0);
done();
}).catch(done);

View File

@ -26,7 +26,8 @@ describe('Unit: Controller: settings/design', function () {
'service:ghostPaths',
'service:notifications',
'service:session',
'service:upgrade-status'
'service:upgrade-status',
'service:settings'
]
});
@ -55,19 +56,19 @@ describe('Unit: Controller: settings/design', function () {
let ctrl = this.subject();
run(() => {
ctrl.set('model', EmberObject.create({navigation: [
ctrl.set('settings', EmberObject.create({navigation: [
NavItem.create({label: 'First', url: '/'}),
NavItem.create({label: '', url: '/second'}),
NavItem.create({label: 'Third', url: ''})
]}));
// blank item won't get added because the last item is incomplete
expect(ctrl.get('model.navigation.length')).to.equal(3);
expect(ctrl.get('settings.navigation.length')).to.equal(3);
ctrl.get('save').perform().then(function passedValidation() {
assert(false, 'navigationItems weren\'t validated on save');
done();
}).catch(function failedValidation() {
let navItems = ctrl.get('model.navigation');
let navItems = ctrl.get('settings.navigation');
expect(navItems[0].get('errors').toArray()).to.be.empty;
expect(navItems[1].get('errors.firstObject.attribute')).to.equal('label');
expect(navItems[2].get('errors.firstObject.attribute')).to.equal('url');
@ -80,18 +81,18 @@ describe('Unit: Controller: settings/design', function () {
let ctrl = this.subject();
run(() => {
ctrl.set('model', EmberObject.create({navigation: [
ctrl.set('settings', EmberObject.create({navigation: [
NavItem.create({label: 'First', url: '/'}),
NavItem.create({label: '', url: ''})
]}));
expect(ctrl.get('model.navigation.length')).to.equal(2);
expect(ctrl.get('settings.navigation.length')).to.equal(2);
ctrl.get('save').perform().then(function passedValidation() {
assert(false, 'navigationItems weren\'t validated on save');
done();
}).catch(function failedValidation() {
let navItems = ctrl.get('model.navigation');
let navItems = ctrl.get('settings.navigation');
expect(navItems[0].get('errors').toArray()).to.be.empty;
done();
});
@ -102,12 +103,12 @@ describe('Unit: Controller: settings/design', function () {
let ctrl = this.subject();
run(() => {
ctrl.set('model', EmberObject.create({navigation: [
ctrl.set('settings', EmberObject.create({navigation: [
NavItem.create({label: 'First', url: '/first', last: true})
]}));
});
expect(ctrl.get('model.navigation.length')).to.equal(1);
expect(ctrl.get('settings.navigation.length')).to.equal(1);
ctrl.set('newNavItem.label', 'New');
ctrl.set('newNavItem.url', '/new');
@ -116,10 +117,10 @@ describe('Unit: Controller: settings/design', function () {
ctrl.send('addNavItem');
});
expect(ctrl.get('model.navigation.length')).to.equal(2);
expect(ctrl.get('model.navigation.lastObject.label')).to.equal('New');
expect(ctrl.get('model.navigation.lastObject.url')).to.equal('/new');
expect(ctrl.get('model.navigation.lastObject.isNew')).to.be.false;
expect(ctrl.get('settings.navigation.length')).to.equal(2);
expect(ctrl.get('settings.navigation.lastObject.label')).to.equal('New');
expect(ctrl.get('settings.navigation.lastObject.url')).to.equal('/new');
expect(ctrl.get('settings.navigation.lastObject.isNew')).to.be.false;
expect(ctrl.get('newNavItem.label')).to.be.blank;
expect(ctrl.get('newNavItem.url')).to.be.blank;
expect(ctrl.get('newNavItem.isNew')).to.be.true;
@ -129,12 +130,12 @@ describe('Unit: Controller: settings/design', function () {
let ctrl = this.subject();
run(() => {
ctrl.set('model', EmberObject.create({navigation: [
ctrl.set('settings', EmberObject.create({navigation: [
NavItem.create({label: '', url: '', last: true})
]}));
expect(ctrl.get('model.navigation.length')).to.equal(1);
expect(ctrl.get('settings.navigation.length')).to.equal(1);
ctrl.send('addNavItem');
expect(ctrl.get('model.navigation.length')).to.equal(1);
expect(ctrl.get('settings.navigation.length')).to.equal(1);
});
});
@ -146,10 +147,10 @@ describe('Unit: Controller: settings/design', function () {
];
run(() => {
ctrl.set('model', EmberObject.create({navigation: navItems}));
expect(ctrl.get('model.navigation').mapBy('label')).to.deep.equal(['First', 'Second']);
ctrl.send('deleteNavItem', ctrl.get('model.navigation.firstObject'));
expect(ctrl.get('model.navigation').mapBy('label')).to.deep.equal(['Second']);
ctrl.set('settings', EmberObject.create({navigation: navItems}));
expect(ctrl.get('settings.navigation').mapBy('label')).to.deep.equal(['First', 'Second']);
ctrl.send('deleteNavItem', ctrl.get('settings.navigation.firstObject'));
expect(ctrl.get('settings.navigation').mapBy('label')).to.deep.equal(['Second']);
});
});
@ -161,10 +162,10 @@ describe('Unit: Controller: settings/design', function () {
];
run(() => {
ctrl.set('model', EmberObject.create({navigation: navItems}));
expect(ctrl.get('model.navigation').mapBy('url')).to.deep.equal(['/first', '/second']);
ctrl.send('updateUrl', '/new', ctrl.get('model.navigation.firstObject'));
expect(ctrl.get('model.navigation').mapBy('url')).to.deep.equal(['/new', '/second']);
ctrl.set('settings', EmberObject.create({navigation: navItems}));
expect(ctrl.get('settings.navigation').mapBy('url')).to.deep.equal(['/first', '/second']);
ctrl.send('updateUrl', '/new', ctrl.get('settings.navigation.firstObject'));
expect(ctrl.get('settings.navigation').mapBy('url')).to.deep.equal(['/new', '/second']);
});
});
});

View File

@ -9,13 +9,14 @@ describe('Unit: Controller: settings/general', function () {
'service:config',
'service:ghostPaths',
'service:notifications',
'service:session'
'service:session',
'service:settings'
]
});
it('isDatedPermalinks should be correct', function () {
let controller = this.subject({
model: EmberObject.create({
settings: EmberObject.create({
permalinks: '/:year/:month/:day/:slug/'
})
});
@ -23,7 +24,7 @@ describe('Unit: Controller: settings/general', function () {
expect(controller.get('isDatedPermalinks')).to.be.ok;
run(function () {
controller.set('model.permalinks', '/:slug/');
controller.set('settings.permalinks', '/:slug/');
expect(controller.get('isDatedPermalinks')).to.not.be.ok;
});
@ -31,7 +32,7 @@ describe('Unit: Controller: settings/general', function () {
it('setting isDatedPermalinks should switch between dated and slug', function () {
let controller = this.subject({
model: EmberObject.create({
settings: EmberObject.create({
permalinks: '/:year/:month/:day/:slug/'
})
});
@ -40,14 +41,14 @@ describe('Unit: Controller: settings/general', function () {
controller.set('isDatedPermalinks', false);
expect(controller.get('isDatedPermalinks')).to.not.be.ok;
expect(controller.get('model.permalinks')).to.equal('/:slug/');
expect(controller.get('settings.permalinks')).to.equal('/:slug/');
});
run(function () {
controller.set('isDatedPermalinks', true);
expect(controller.get('isDatedPermalinks')).to.be.ok;
expect(controller.get('model.permalinks')).to.equal('/:year/:month/:day/:slug/');
expect(controller.get('settings.permalinks')).to.equal('/:year/:month/:day/:slug/');
});
});
});

View File

@ -12,7 +12,7 @@ import {task} from 'ember-concurrency';
describe('Unit: Mixin: editor-base-controller', function () {
describe('generateSlug', function () {
it('should generate a slug and set it on the model', function (done) {
it('should generate a slug and set it on the post', function (done) {
let object;
run(() => {
@ -22,19 +22,19 @@ describe('Unit: Mixin: editor-base-controller', function () {
return RSVP.resolve(`${str}-slug`);
}
}),
model: EmberObject.create({slug: ''})
post: EmberObject.create({slug: ''})
}).create();
object.set('model.titleScratch', 'title');
object.set('post.titleScratch', 'title');
expect(object.get('model.slug')).to.equal('');
expect(object.get('post.slug')).to.equal('');
run(() => {
object.get('generateSlug').perform();
});
wait().then(() => {
expect(object.get('model.slug')).to.equal('title-slug');
expect(object.get('post.slug')).to.equal('title-slug');
done();
});
});
@ -50,22 +50,22 @@ describe('Unit: Mixin: editor-base-controller', function () {
return RSVP.resolve(`${str}-slug`);
}
}),
model: EmberObject.create({
post: EmberObject.create({
slug: 'whatever'
})
}).create();
});
expect(object.get('model.slug')).to.equal('whatever');
expect(object.get('post.slug')).to.equal('whatever');
object.set('model.titleScratch', '(Untitled)');
object.set('post.titleScratch', '(Untitled)');
run(() => {
object.get('generateSlug').perform();
});
wait().then(() => {
expect(object.get('model.slug')).to.equal('whatever');
expect(object.get('post.slug')).to.equal('whatever');
done();
});
});
@ -77,26 +77,26 @@ describe('Unit: Mixin: editor-base-controller', function () {
run(() => {
object = EmberObject.extend(EditorBaseControllerMixin, {
model: EmberObject.create({isNew: true}),
post: EmberObject.create({isNew: true}),
generateSlug: task(function* () {
this.set('model.slug', 'test-slug');
this.set('post.slug', 'test-slug');
yield RSVP.resolve();
})
}).create();
});
expect(object.get('model.isNew')).to.be.true;
expect(object.get('model.titleScratch')).to.not.be.ok;
expect(object.get('post.isNew')).to.be.true;
expect(object.get('post.titleScratch')).to.not.be.ok;
object.set('model.titleScratch', 'test');
object.set('post.titleScratch', 'test');
run(() => {
object.get('saveTitle').perform();
});
wait().then(() => {
expect(object.get('model.titleScratch')).to.equal('test');
expect(object.get('model.slug')).to.equal('test-slug');
expect(object.get('post.titleScratch')).to.equal('test');
expect(object.get('post.slug')).to.equal('test-slug');
done();
});
});
@ -106,26 +106,26 @@ describe('Unit: Mixin: editor-base-controller', function () {
run(() => {
object = EmberObject.extend(EditorBaseControllerMixin, {
model: EmberObject.create({isNew: false, title: '(Untitled)'}),
post: EmberObject.create({isNew: false, title: '(Untitled)'}),
generateSlug: task(function* () {
this.set('model.slug', 'test-slug');
this.set('post.slug', 'test-slug');
yield RSVP.resolve();
})
}).create();
});
expect(object.get('model.isNew')).to.be.false;
expect(object.get('model.titleScratch')).to.not.be.ok;
expect(object.get('post.isNew')).to.be.false;
expect(object.get('post.titleScratch')).to.not.be.ok;
object.set('model.titleScratch', 'New Title');
object.set('post.titleScratch', 'New Title');
run(() => {
object.get('saveTitle').perform();
});
wait().then(() => {
expect(object.get('model.titleScratch')).to.equal('New Title');
expect(object.get('model.slug')).to.equal('test-slug');
expect(object.get('post.titleScratch')).to.equal('New Title');
expect(object.get('post.slug')).to.equal('test-slug');
done();
});
});
@ -135,7 +135,7 @@ describe('Unit: Mixin: editor-base-controller', function () {
run(() => {
object = EmberObject.extend(EditorBaseControllerMixin, {
model: EmberObject.create({
post: EmberObject.create({
isNew: true,
title: 'a title'
}),
@ -147,19 +147,19 @@ describe('Unit: Mixin: editor-base-controller', function () {
}).create();
});
expect(object.get('model.isNew')).to.be.true;
expect(object.get('model.title')).to.equal('a title');
expect(object.get('model.titleScratch')).to.not.be.ok;
expect(object.get('post.isNew')).to.be.true;
expect(object.get('post.title')).to.equal('a title');
expect(object.get('post.titleScratch')).to.not.be.ok;
object.set('model.titleScratch', 'test');
object.set('post.titleScratch', 'test');
run(() => {
object.get('saveTitle').perform();
});
wait().then(() => {
expect(object.get('model.titleScratch')).to.equal('test');
expect(object.get('model.slug')).to.not.be.ok;
expect(object.get('post.titleScratch')).to.equal('test');
expect(object.get('post.slug')).to.not.be.ok;
done();
});
});
@ -169,7 +169,7 @@ describe('Unit: Mixin: editor-base-controller', function () {
run(() => {
object = EmberObject.extend(EditorBaseControllerMixin, {
model: EmberObject.create({isNew: false}),
post: EmberObject.create({isNew: false}),
generateSlug: task(function* () {
expect(false, 'generateSlug should not be called').to.equal(true);
@ -178,18 +178,18 @@ describe('Unit: Mixin: editor-base-controller', function () {
}).create();
});
expect(object.get('model.isNew')).to.be.false;
expect(object.get('model.title')).to.not.be.ok;
expect(object.get('post.isNew')).to.be.false;
expect(object.get('post.title')).to.not.be.ok;
object.set('model.titleScratch', 'title');
object.set('post.titleScratch', 'title');
run(() => {
object.get('saveTitle').perform();
});
wait().then(() => {
expect(object.get('model.titleScratch')).to.equal('title');
expect(object.get('model.slug')).to.not.be.ok;
expect(object.get('post.titleScratch')).to.equal('title');
expect(object.get('post.slug')).to.not.be.ok;
done();
});
});