diff --git a/ghost/admin/app/controllers/member.js b/ghost/admin/app/controllers/member.js index 9a7d0da2d4..cb2e36dbe1 100644 --- a/ghost/admin/app/controllers/member.js +++ b/ghost/admin/app/controllers/member.js @@ -8,13 +8,12 @@ import {task} from 'ember-concurrency'; export default Controller.extend({ members: controller(), + notifications: service(), + router: service(), store: service(), - router: service(), - - notifications: service(), - member: alias('model'), + subscribedAt: computed('member.createdAtUTC', function () { let memberSince = moment(this.member.createdAtUTC).from(moment()); let createdDate = moment(this.member.createdAtUTC).format('MMM DD, YYYY'); @@ -25,9 +24,15 @@ export default Controller.extend({ setProperty(propKey, value) { this._saveMemberProperty(propKey, value); }, + toggleDeleteMemberModal() { this.toggleProperty('showDeleteMemberModal'); }, + + save() { + return this.save.perform(); + }, + finaliseDeletion() { // decrement the total member count manually so there's no flash // when transitioning back to the members list @@ -62,21 +67,8 @@ export default Controller.extend({ }, leaveScreen() { - let transition = this.leaveScreenTransition; - - if (!transition) { - this.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 this.member.rollbackAttributes(); - - return transition.retry(); - }, - - save() { - return this.save.perform(); + return this.leaveScreenTransition.retry(); } }, @@ -98,11 +90,13 @@ export default Controller.extend({ fetchMember: task(function* (memberId) { this.set('isLoading', true); + yield this.store.findRecord('member', memberId, { reload: true - }).then((data) => { - this.set('member', data); + }).then((member) => { + this.set('member', member); this.set('isLoading', false); + return member; }); }) diff --git a/ghost/admin/app/controllers/member/new.js b/ghost/admin/app/controllers/member/new.js deleted file mode 100644 index 6eb1e66719..0000000000 --- a/ghost/admin/app/controllers/member/new.js +++ /dev/null @@ -1,104 +0,0 @@ -import Controller from '@ember/controller'; -import moment from 'moment'; -import {alias} from '@ember/object/computed'; -import {computed} from '@ember/object'; -import {inject as controller} from '@ember/controller'; -import {inject as service} from '@ember/service'; -import {task} from 'ember-concurrency'; - -export default Controller.extend({ - members: controller(), - store: service(), - - router: service(), - - notifications: service(), - - member: alias('model'), - - displayName: computed('member.{name,email}', function () { - return this.member.name || this.member.email || 'New member'; - }), - subscribedAt: computed('member.createdAtUTC', function () { - let memberSince = moment(this.member.createdAtUTC).from(moment()); - let createdDate = moment(this.member.createdAtUTC).format('MMM DD, YYYY'); - return `${createdDate} (${memberSince})`; - }), - - actions: { - setProperty(propKey, value) { - this._saveMemberProperty(propKey, value); - }, - - toggleDeleteMemberModal() { - this.toggleProperty('showDeleteMemberModal'); - }, - - finaliseDeletion() { - // decrement the total member count manually so there's no flash - // when transitioning back to the members list - if (this.members.memberCount) { - this.members.decrementProperty('memberCount'); - } - this.router.transitionTo('members'); - }, - - toggleUnsavedChangesModal(transition) { - let leaveTransition = this.leaveScreenTransition; - - if (!transition && this.showUnsavedChangesModal) { - this.set('leaveScreenTransition', null); - this.set('showUnsavedChangesModal', false); - return; - } - - if (!leaveTransition || transition.targetName === leaveTransition.targetName) { - this.set('leaveScreenTransition', transition); - - // if a save is running, wait for it to finish then transition - if (this.save.isRunning) { - return this.save.last.then(() => { - transition.retry(); - }); - } - - // we genuinely have unsaved data, show the modal - this.set('showUnsavedChangesModal', true); - } - }, - - leaveScreen() { - let transition = this.leaveScreenTransition; - - if (!transition) { - this.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 - this.member.rollbackAttributes(); - - return transition.retry(); - }, - - save() { - return this.save.perform(); - } - }, - - save: task(function* () { - let member = this.member; - try { - return yield member.save(); - } catch (error) { - if (error) { - this.notifications.showAPIError(error, {key: 'member.save'}); - } - } - }).drop(), - - _saveMemberProperty(propKey, newValue) { - let member = this.member; - member.set(propKey, newValue); - } -}); diff --git a/ghost/admin/app/controllers/tag.js b/ghost/admin/app/controllers/tag.js index 03af43dfeb..9c74d71c3d 100644 --- a/ghost/admin/app/controllers/tag.js +++ b/ghost/admin/app/controllers/tag.js @@ -23,7 +23,11 @@ export default Controller.extend({ }, deleteTag() { - return this._deleteTag(); + return this.tag.destroyRecord().then(() => { + return this.transitionToRoute('tags'); + }, (error) => { + return this.notifications.showAPIError(error, {key: 'tag.delete'}); + }); }, save() { @@ -55,17 +59,8 @@ export default Controller.extend({ }, leaveScreen() { - let transition = this.leaveScreenTransition; - - if (!transition) { - this.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 this.tag.rollbackAttributes(); - - return transition.retry(); + return this.leaveScreenTransition.retry(); } }, @@ -93,6 +88,7 @@ export default Controller.extend({ } tag.set('slug', slugValue); } + // TODO: This is required until .validate/.save mark fields as validated tag.get('hasValidated').addObject(propKey); }, @@ -125,11 +121,13 @@ export default Controller.extend({ } }), - _deleteTag() { - return this.tag.destroyRecord().then(() => { - return this.transitionToRoute('tags'); - }, (error) => { - return this.notifications.showAPIError(error, {key: 'tag.delete'}); + fetchTag: task(function* (slug) { + this.set('isLoading', true); + + yield this.store.queryRecord('tag', {slug}).then((tag) => { + this.set('tag', tag); + this.set('isLoading', false); + return tag; }); - } + }) }); diff --git a/ghost/admin/app/routes/member.js b/ghost/admin/app/routes/member.js index 97e474af9b..f3f14e9dfb 100644 --- a/ghost/admin/app/routes/member.js +++ b/ghost/admin/app/routes/member.js @@ -3,9 +3,10 @@ import CurrentUserSettings from 'ghost-admin/mixins/current-user-settings'; import {inject as service} from '@ember/service'; export default AuthenticatedRoute.extend(CurrentUserSettings, { - router: service(), + _requiresBackgroundRefresh: true, + init() { this._super(...arguments); this.router.on('routeWillChange', (transition) => { @@ -20,27 +21,29 @@ export default AuthenticatedRoute.extend(CurrentUserSettings, { }, model(params) { - this._isMemberUpdated = true; - return this.store.findRecord('member', params.member_id, { - reload: true - }); + this._requiresBackgroundRefresh = false; + + if (params.member_id) { + return this.store.findRecord('member', params.member_id, {reload: true}); + } else { + return this.store.createRecord('member'); + } }, - setupController(controller, model) { + setupController(controller, member) { this._super(...arguments); - if (!this._isMemberUpdated) { - controller.fetchMember.perform(model.get('id')); + if (this._requiresBackgroundRefresh) { + controller.fetchMember.perform(member.get('id')); } }, deactivate() { this._super(...arguments); - // clear the properties - let {controller} = this; - controller.model.rollbackAttributes(); - this.set('controller.model', null); - this._isMemberUpdated = false; + // clean up newly created records and revert unsaved changes to existing + this.controller.member.rollbackAttributes(); + + this._requiresBackgroundRefresh = true; }, actions: { @@ -54,10 +57,13 @@ export default AuthenticatedRoute.extend(CurrentUserSettings, { }, showUnsavedChangesModal(transition) { - if (transition.from && transition.from.name.match(/^member$/) && transition.targetName) { + if (transition.from && transition.from.name === this.routeName && transition.targetName) { let {controller} = this; - if (!controller.member.isDeleted && controller.member.hasDirtyAttributes) { + // member.changedAttributes is always true for new members but number of changed attrs is reliable + let isChanged = Object.keys(controller.member.changedAttributes()).length > 0; + + if (!controller.member.isDeleted && isChanged) { transition.abort(); controller.send('toggleUnsavedChangesModal', transition); return; diff --git a/ghost/admin/app/routes/member/new.js b/ghost/admin/app/routes/member/new.js index 87709594aa..a7bfc5325a 100644 --- a/ghost/admin/app/routes/member/new.js +++ b/ghost/admin/app/routes/member/new.js @@ -1,45 +1,6 @@ -import AuthenticatedRoute from 'ghost-admin/routes/authenticated'; -import CurrentUserSettings from 'ghost-admin/mixins/current-user-settings'; -import {isEmpty} from '@ember/utils'; -import {inject as service} from '@ember/service'; - -export default AuthenticatedRoute.extend(CurrentUserSettings, { - - router: service(), - - controllerName: 'member.new', - templateName: 'member/new', - - init() { - this._super(...arguments); - this.router.on('routeWillChange', (transition) => { - this.showUnsavedChangesModal(transition); - }); - }, - - model() { - return this.store.createRecord('member'); - }, - - // reset the model so that mobile screens react to an empty selectedMember - deactivate() { - this._super(...arguments); - - let {controller} = this; - controller.model.rollbackAttributes(); - controller.set('model', null); - }, - - showUnsavedChangesModal(transition) { - if (transition.from && transition.from.name.match(/^members\.new/) && transition.targetName) { - let {controller} = this; - let isUnchanged = isEmpty(Object.keys(controller.member.changedAttributes())); - if (!controller.member.isDeleted && !isUnchanged) { - transition.abort(); - controller.send('toggleUnsavedChangesModal', transition); - return; - } - } - } +import MemberRoute from '../member'; +export default MemberRoute.extend({ + controllerName: 'member', + templateName: 'member' }); diff --git a/ghost/admin/app/routes/tag.js b/ghost/admin/app/routes/tag.js index dba03dc7b4..aca92a8d11 100644 --- a/ghost/admin/app/routes/tag.js +++ b/ghost/admin/app/routes/tag.js @@ -6,6 +6,8 @@ import {inject as service} from '@ember/service'; export default AuthenticatedRoute.extend(CurrentUserSettings, { router: service(), + _requiresBackgroundRefresh: true, + init() { this._super(...arguments); this.router.on('routeWillChange', (transition) => { @@ -20,6 +22,8 @@ export default AuthenticatedRoute.extend(CurrentUserSettings, { }, model(params) { + this._requiresBackgroundRefresh = false; + if (params.tag_slug) { return this.store.queryRecord('tag', {slug: params.tag_slug}); } else { @@ -27,8 +31,24 @@ export default AuthenticatedRoute.extend(CurrentUserSettings, { } }, - serialize(model) { - return {tag_slug: model.get('slug')}; + serialize(tag) { + return {tag_slug: tag.get('slug')}; + }, + + setupController(controller, tag) { + this._super(...arguments); + if (this._requiresBackgroundRefresh) { + controller.fetchTag.perform(tag.get('slug')); + } + }, + + deactivate() { + this._super(...arguments); + + // clean up newly created records and revert unsaved changes to existing + this.controller.tag.rollbackAttributes(); + + this._requiresBackgroundRefresh = true; }, actions: { @@ -42,7 +62,9 @@ export default AuthenticatedRoute.extend(CurrentUserSettings, { let {controller} = this; // tag.changedAttributes is always true for new tags but number of changed attrs is reliable - if (!controller.tag.isDeleted && Object.keys(controller.tag.changedAttributes()).length > 0) { + let isChanged = Object.keys(controller.tag.changedAttributes()).length > 0; + + if (!controller.tag.isDeleted && isChanged) { transition.abort(); controller.send('toggleUnsavedChangesModal', transition); return; diff --git a/ghost/admin/app/templates/member.hbs b/ghost/admin/app/templates/member.hbs index 81cbb5da12..0dbc6abf35 100644 --- a/ghost/admin/app/templates/member.hbs +++ b/ghost/admin/app/templates/member.hbs @@ -2,58 +2,78 @@
- + {{#unless this.member.isNew}} + + {{/unless}} -{{#if showUnsavedChangesModal}} - {{gh-fullscreen-modal "leave-settings" - confirm=(action "leaveScreen") - close=(action "toggleUnsavedChangesModal") - modifier="action wide"}} +{{#if this.showUnsavedChangesModal}} +