🐛 Fixed missing unsaved changes modal for member newsletters (#15564)

closes: https://github.com/TryGhost/Ghost/issues/15507

- manually handle relationship changes detection labels and newsletters
- add `dirtyAttributes` controller property - return newsletters and labels dirty attributes status
This commit is contained in:
Hakim Razalan 2022-10-22 04:05:14 +08:00 committed by GitHub
parent b88a54fb71
commit 48b033f1e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 77 additions and 23 deletions

View File

@ -24,6 +24,9 @@ export default class MemberController extends Controller {
@tracked modalLabel = null;
@tracked showLabelModal = false;
_previousLabels = null;
_previousNewsletters = null;
constructor() {
super(...arguments);
this._availableLabels = this.store.peekAll('label');
@ -39,6 +42,18 @@ export default class MemberController extends Controller {
this.model = member;
}
get dirtyAttributes() {
return this._hasDirtyAttributes();
}
get _labels() {
return this.member.get('labels').map(label => label.name);
}
get _newsletters() {
return this.member.get('newsletters').map(newsletter => newsletter.id);
}
get labelModalData() {
let label = this.modalLabel;
let labels = this.availableLabels;
@ -75,6 +90,12 @@ export default class MemberController extends Controller {
// Actions -----------------------------------------------------------------
@action
setInitialRelationshipValues() {
this._previousLabels = this._labels;
this._previousNewsletters = this._newsletters;
}
@action
toggleLabelModal() {
this.showLabelModal = !this.showLabelModal;
@ -139,6 +160,8 @@ export default class MemberController extends Controller {
member.updateLabels();
this.members.refreshData();
this.setInitialRelationshipValues();
// replace 'member.new' route with 'member' route
this.replaceRoute('member', member);
@ -171,6 +194,8 @@ export default class MemberController extends Controller {
include: 'tiers'
});
this.setInitialRelationshipValues();
this.isLoading = false;
}
@ -190,4 +215,36 @@ export default class MemberController extends Controller {
this.member[propKey] = newValue;
}
_hasDirtyAttributes() {
let member = this.member;
if (!member) {
return false;
}
// member.labels is an array so hasDirtyAttributes doesn't pick up
// changes unless the array ref is changed.
// use sort() to sort of detect same item is re-added
let currentLabels = (this._labels.sort() || []).join(', ');
let previousLabels = (this._previousLabels.sort() || []).join(', ');
if (currentLabels !== previousLabels) {
return true;
}
// member.newsletters is an array so hasDirtyAttributes doesn't pick up
// changes unless the array ref is changed
// use sort() to sort of detect same item is re-enabled
let currentNewsletters = (this._newsletters.sort() || []).join(', ');
let previousNewsletters = (this._previousNewsletters.sort() || []).join(', ');
if (currentNewsletters !== previousNewsletters) {
return true;
}
// we've covered all the non-tracked cases we care about so fall
// back on Ember Data's default dirty attribute checks
let {hasDirtyAttributes} = member;
return hasDirtyAttributes;
}
}

View File

@ -48,42 +48,39 @@ export default class MembersRoute extends AdminRoute {
@action
async willTransition(transition) {
if (this.hasConfirmed) {
return true;
}
transition.abort();
let hasDirtyAttributes = this.controller.dirtyAttributes;
// wait for any existing confirm modal to be closed before allowing transition
if (this.confirmModal) {
return;
}
if (this.controller.saveTask?.isRunning) {
await this.controller.saveTask.last;
}
if (!this.hasConfirmed && hasDirtyAttributes) {
transition.abort();
const shouldLeave = await this.confirmUnsavedChanges();
if (this.controller.saveTask?.isRunning) {
await this.controller.saveTask.last;
transition.retry();
}
if (shouldLeave) {
this.controller.model.rollbackAttributes();
this.hasConfirmed = true;
return transition.retry();
const shouldLeave = await this.confirmUnsavedChanges();
if (shouldLeave) {
this.controller.model.rollbackAttributes();
this.hasConfirmed = true;
return transition.retry();
}
}
}
async confirmUnsavedChanges() {
if (this.controller.model?.hasDirtyAttributes) {
this.confirmModal = this.modals
.open(ConfirmUnsavedChangesModal)
.finally(() => {
this.confirmModal = null;
});
this.confirmModal = this.modals
.open(ConfirmUnsavedChangesModal)
.finally(() => {
this.confirmModal = null;
});
return this.confirmModal;
}
return true;
return this.confirmModal;
}
closeImpersonateModal(transition) {