mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-28 22:43:30 +03:00
🎨 Added confirmation dialog when leaving settings screen with unsaved changes (#871)
closes TryGhost/Ghost#8483 - Added a new modal component that gets rendered when leaving general/settings after changes have been done but not saved - Removed independent saving logic for social URL for consistent UX
This commit is contained in:
parent
51c3b25bee
commit
da38f0db19
12
ghost/admin/app/components/modal-leave-settings.js
Normal file
12
ghost/admin/app/components/modal-leave-settings.js
Normal file
@ -0,0 +1,12 @@
|
||||
import ModalComponent from 'ghost-admin/components/modal-base';
|
||||
import {invokeAction} from 'ember-invoke-action';
|
||||
|
||||
export default ModalComponent.extend({
|
||||
actions: {
|
||||
confirm() {
|
||||
invokeAction(this, 'confirm').finally(() => {
|
||||
this.send('closeModal');
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
@ -134,6 +134,45 @@ export default Controller.extend({
|
||||
}
|
||||
},
|
||||
|
||||
toggleLeaveSettingsModal(transition) {
|
||||
let leaveTransition = this.get('leaveSettingsTransition');
|
||||
|
||||
if (!transition && this.get('showLeaveSettingsModal')) {
|
||||
this.set('leaveSettingsTransition', null);
|
||||
this.set('showLeaveSettingsModal', false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!leaveTransition || transition.targetName === leaveTransition.targetName) {
|
||||
this.set('leaveSettingsTransition', transition);
|
||||
|
||||
// if a save is running, wait for it to finish then transition
|
||||
if (this.get('save.isRunning')) {
|
||||
return this.get('save.last').then(() => {
|
||||
transition.retry();
|
||||
});
|
||||
}
|
||||
|
||||
// we genuinely have unsaved data, show the modal
|
||||
this.set('showLeaveSettingsModal', true);
|
||||
}
|
||||
},
|
||||
|
||||
leaveSettings() {
|
||||
let transition = this.get('leaveSettingsTransition');
|
||||
let model = this.get('model');
|
||||
|
||||
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();
|
||||
|
||||
return transition.retry();
|
||||
},
|
||||
|
||||
validateFacebookUrl() {
|
||||
let newUrl = this.get('_scratchFacebook');
|
||||
let oldUrl = this.get('model.facebook');
|
||||
@ -180,17 +219,13 @@ export default Controller.extend({
|
||||
}
|
||||
|
||||
newUrl = `https://www.facebook.com/${username}`;
|
||||
this.set('model.facebook', newUrl);
|
||||
|
||||
this.get('model.errors').remove('facebook');
|
||||
this.get('model.hasValidated').pushObject('facebook');
|
||||
|
||||
// User input is validated
|
||||
return this.get('save').perform().then(() => {
|
||||
this.set('model.facebook', '');
|
||||
run.schedule('afterRender', this, function () {
|
||||
this.set('model.facebook', newUrl);
|
||||
});
|
||||
this.set('model.facebook', '');
|
||||
run.schedule('afterRender', this, function () {
|
||||
this.set('model.facebook', newUrl);
|
||||
});
|
||||
} else {
|
||||
errMessage = 'The URL must be in a format like '
|
||||
@ -243,17 +278,13 @@ export default Controller.extend({
|
||||
}
|
||||
|
||||
newUrl = `https://twitter.com/${username}`;
|
||||
this.set('model.twitter', newUrl);
|
||||
|
||||
this.get('model.errors').remove('twitter');
|
||||
this.get('model.hasValidated').pushObject('twitter');
|
||||
|
||||
// User input is validated
|
||||
return this.get('save').perform().then(() => {
|
||||
this.set('model.twitter', '');
|
||||
run.schedule('afterRender', this, function () {
|
||||
this.set('model.twitter', newUrl);
|
||||
});
|
||||
this.set('model.twitter', '');
|
||||
run.schedule('afterRender', this, function () {
|
||||
this.set('model.twitter', newUrl);
|
||||
});
|
||||
} else {
|
||||
errMessage = 'The URL must be in a format like '
|
||||
|
@ -26,6 +26,8 @@ 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);
|
||||
@ -38,6 +40,19 @@ export default AuthenticatedRoute.extend(styleBody, CurrentUserSettings, {
|
||||
|
||||
reloadSettings() {
|
||||
return this.get('settings').reload();
|
||||
},
|
||||
|
||||
willTransition(transition) {
|
||||
let controller = this.get('controller');
|
||||
let model = controller.get('model');
|
||||
let modelIsDirty = model.get('hasDirtyAttributes');
|
||||
|
||||
if (modelIsDirty) {
|
||||
transition.abort();
|
||||
controller.send('toggleLeaveSettingsModal', transition);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
@ -0,0 +1,17 @@
|
||||
<header class="modal-header">
|
||||
<h1>Are you sure you want to leave this page?</h1>
|
||||
</header>
|
||||
<a class="close" href="" title="Close" {{action "closeModal"}}>{{inline-svg "close"}}<span class="hidden">Close</span></a>
|
||||
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
Hey there! It looks like you didn't save the changes you made.
|
||||
</p>
|
||||
|
||||
<p>Save before you go!</p>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button {{action "closeModal"}} class="gh-btn" data-test-stay-button><span>Stay</span></button>
|
||||
<button {{action "confirm"}} class="gh-btn gh-btn-red" data-test-leave-button><span>Leave</span></button>
|
||||
</div>
|
@ -6,6 +6,13 @@
|
||||
</section>
|
||||
</header>
|
||||
|
||||
{{#if showLeaveSettingsModal}}
|
||||
{{gh-fullscreen-modal "leave-settings"
|
||||
confirm=(action "leaveSettings")
|
||||
close=(action "toggleLeaveSettingsModal")
|
||||
modifier="action wide"}}
|
||||
{{/if}}
|
||||
|
||||
<section class="view-container">
|
||||
|
||||
<div class="gh-setting-header">Publication info</div>
|
||||
|
@ -472,5 +472,48 @@ describe('Acceptance: Settings - General', function () {
|
||||
expect(find('[data-test-twitter-error]').text().trim(), 'inline validation response')
|
||||
.to.equal('');
|
||||
});
|
||||
|
||||
it('warns when leaving without saving', async function () {
|
||||
await visit('/settings/general');
|
||||
|
||||
expect(
|
||||
find('[data-test-dated-permalinks-checkbox]').prop('checked'),
|
||||
'date permalinks checkbox'
|
||||
).to.be.false;
|
||||
|
||||
await click('[data-test-toggle-pub-info]');
|
||||
await fillIn('[data-test-title-input]', 'New Blog Title');
|
||||
|
||||
await click('[data-test-dated-permalinks-checkbox]');
|
||||
|
||||
expect(
|
||||
find('[data-test-dated-permalinks-checkbox]').prop('checked'),
|
||||
'dated permalink checkbox'
|
||||
).to.be.true;
|
||||
|
||||
await visit('/settings/team');
|
||||
|
||||
expect(find('.fullscreen-modal').length, 'modal exists').to.equal(1);
|
||||
|
||||
// Leave without saving
|
||||
await(click('.fullscreen-modal [data-test-leave-button]'), 'leave without saving');
|
||||
|
||||
expect(currentURL(), 'currentURL').to.equal('/settings/team');
|
||||
|
||||
await visit('/settings/general');
|
||||
|
||||
expect(currentURL(), 'currentURL').to.equal('/settings/general');
|
||||
|
||||
// settings were not saved
|
||||
expect(
|
||||
find('[data-test-dated-permalinks-checkbox]').prop('checked'),
|
||||
'date permalinks checkbox'
|
||||
).to.be.false;
|
||||
|
||||
expect(
|
||||
find('[data-test-title-input]').text().trim(),
|
||||
'Blog title'
|
||||
).to.equal('');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user