mirror of
https://github.com/TryGhost/Ghost-Admin.git
synced 2023-12-14 02:33:04 +01:00
🐛 Make cmd+s
work for all save-buttons (#700)
closes TryGhost/Ghost#8443 - Fixes a bug where the keyboard shortcut `cmd+s` would cause a `Maximum call stack size` error and not save. - Wherever there is a `save` button, the keyboard shortcut to save works now.
This commit is contained in:
parent
ef9381b489
commit
371b9fd78e
17 changed files with 229 additions and 18 deletions
|
@ -27,6 +27,10 @@ export default Controller.extend({
|
|||
actions: {
|
||||
update(value) {
|
||||
this.set('model', value);
|
||||
},
|
||||
|
||||
save() {
|
||||
this.get('save').perform();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -52,7 +52,7 @@ export default Controller.extend({
|
|||
|
||||
actions: {
|
||||
save() {
|
||||
return this.get('save').perform();
|
||||
this.get('save').perform();
|
||||
},
|
||||
|
||||
updateURL(value) {
|
||||
|
|
|
@ -14,5 +14,11 @@ export default Controller.extend({
|
|||
notifications.showAPIError(error, {key: 'code-injection.save'});
|
||||
throw error;
|
||||
}
|
||||
})
|
||||
}),
|
||||
|
||||
actions: {
|
||||
save() {
|
||||
this.get('save').perform();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -79,6 +79,10 @@ export default Controller.extend({
|
|||
},
|
||||
|
||||
actions: {
|
||||
save() {
|
||||
this.get('save').perform();
|
||||
},
|
||||
|
||||
addNavItem() {
|
||||
let newNavItem = this.get('newNavItem');
|
||||
|
||||
|
|
|
@ -81,6 +81,10 @@ export default Controller.extend({
|
|||
}),
|
||||
|
||||
actions: {
|
||||
save() {
|
||||
this.get('save').perform();
|
||||
},
|
||||
|
||||
setTimezone(timezone) {
|
||||
this.set('model.activeTimezone', timezone.name);
|
||||
},
|
||||
|
|
|
@ -4,5 +4,12 @@ import styleBody from 'ghost-admin/mixins/style-body';
|
|||
export default AuthenticatedRoute.extend(styleBody, {
|
||||
titleToken: 'Settings - Apps - AMP',
|
||||
|
||||
classNames: ['settings-view-apps-amp']
|
||||
classNames: ['settings-view-apps-amp'],
|
||||
|
||||
actions: {
|
||||
save() {
|
||||
this.get('controller').send('save');
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
@ -4,5 +4,11 @@ import styleBody from 'ghost-admin/mixins/style-body';
|
|||
export default AuthenticatedRoute.extend(styleBody, {
|
||||
titleToken: 'Settings - Apps - Slack',
|
||||
|
||||
classNames: ['settings-view-apps-slack']
|
||||
classNames: ['settings-view-apps-slack'],
|
||||
|
||||
actions: {
|
||||
save() {
|
||||
this.get('controller').send('save');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
<div class="form-group for-checkbox">
|
||||
<label for="amp">AMP support for your publications</label>
|
||||
<label class="checkbox" for="amp">
|
||||
{{one-way-checkbox model id="amp" name="amp" type="checkbox" update=(action "update")}}
|
||||
{{one-way-checkbox model id="amp" name="amp" type="checkbox" update=(action "update") data-test-amp-checkbox=true}}
|
||||
<span class="input-toggle-component"></span>
|
||||
<p>Enable AMP support</p>
|
||||
</label>
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
<form class="app-config-form" id="slack-settings" novalidate="novalidate" {{action "save" on="submit"}}>
|
||||
{{#gh-form-group errors=model.errors hasValidated=model.hasValidated property="url"}}
|
||||
<label for="url">Webhook URL</label>
|
||||
{{gh-input model.url name="slack[url]" update=(action "updateURL") onenter=(action "save") placeholder="https://hooks.slack.com/services/..."}}
|
||||
{{gh-input model.url name="slack[url]" update=(action "updateURL") onenter=(action "save") placeholder="https://hooks.slack.com/services/..." data-test-slack-url-input=true}}
|
||||
{{#unless model.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}}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<header class="gh-canvas-header">
|
||||
<h2 class="gh-canvas-title">Design</h2>
|
||||
<section class="view-actions">
|
||||
{{gh-task-button task=save class="gh-btn gh-btn-blue gh-btn-icon"}}
|
||||
{{gh-task-button task=save class="gh-btn gh-btn-blue gh-btn-icon" data-test-save-button=true}}
|
||||
</section>
|
||||
</header>
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
</span>
|
||||
{{/if}}
|
||||
|
||||
{{gh-task-button class="gh-btn gh-btn-blue gh-btn-icon" task=save}}
|
||||
{{gh-task-button class="gh-btn gh-btn-blue gh-btn-icon" task=save data-test-save-button=true}}
|
||||
|
||||
{{#if showDeleteUserModal}}
|
||||
{{gh-fullscreen-modal "delete-user"
|
||||
|
|
98
tests/acceptance/settings/amp-test.js
Normal file
98
tests/acceptance/settings/amp-test.js
Normal file
|
@ -0,0 +1,98 @@
|
|||
/* jshint expr:true */
|
||||
import {
|
||||
describe,
|
||||
it,
|
||||
beforeEach,
|
||||
afterEach
|
||||
} from 'mocha';
|
||||
import {expect} from 'chai';
|
||||
import startApp from '../../helpers/start-app';
|
||||
import destroyApp from '../../helpers/destroy-app';
|
||||
import {invalidateSession, authenticateSession} from 'ghost-admin/tests/helpers/ember-simple-auth';
|
||||
import testSelector from 'ember-test-selectors';
|
||||
import ctrlOrCmd from 'ghost-admin/utils/ctrl-or-cmd';
|
||||
|
||||
describe('Acceptance: Settings - Apps - AMP', function () {
|
||||
let application;
|
||||
|
||||
beforeEach(function() {
|
||||
application = startApp();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
destroyApp(application);
|
||||
});
|
||||
|
||||
it('redirects to signin when not authenticated', async function () {
|
||||
invalidateSession(application);
|
||||
await visit('/settings/apps/amp');
|
||||
|
||||
expect(currentURL(), 'currentURL').to.equal('/signin');
|
||||
});
|
||||
|
||||
it('redirects to team page when authenticated as author', async function () {
|
||||
let role = server.create('role', {name: 'Author'});
|
||||
server.create('user', {roles: [role], slug: 'test-user'});
|
||||
|
||||
authenticateSession(application);
|
||||
await visit('/settings/apps/amp');
|
||||
|
||||
expect(currentURL(), 'currentURL').to.equal('/team/test-user');
|
||||
});
|
||||
|
||||
it('redirects to team page when authenticated as editor', async function () {
|
||||
let role = server.create('role', {name: 'Editor'});
|
||||
server.create('user', {roles: [role], slug: 'test-user'});
|
||||
|
||||
authenticateSession(application);
|
||||
await visit('/settings/apps/amp');
|
||||
|
||||
expect(currentURL(), 'currentURL').to.equal('/team');
|
||||
});
|
||||
|
||||
describe('when logged in', function () {
|
||||
beforeEach(function () {
|
||||
let role = server.create('role', {name: 'Administrator'});
|
||||
server.create('user', {roles: [role]});
|
||||
|
||||
return authenticateSession(application);
|
||||
});
|
||||
|
||||
it('it enables or disables AMP properly and saves it', async function () {
|
||||
await visit('/settings/apps/amp');
|
||||
|
||||
// has correct url
|
||||
expect(currentURL(), 'currentURL').to.equal('/settings/apps/amp');
|
||||
|
||||
// AMP is enabled by default
|
||||
expect(find(testSelector('amp-checkbox')).prop('checked'), 'AMP checkbox').to.be.true;
|
||||
|
||||
await click(testSelector('amp-checkbox'));
|
||||
|
||||
expect(find(testSelector('amp-checkbox')).prop('checked'), 'AMP checkbox').to.be.false;
|
||||
|
||||
await click(testSelector('save-button'));
|
||||
|
||||
let [lastRequest] = server.pretender.handledRequests.slice(-1);
|
||||
let params = JSON.parse(lastRequest.requestBody);
|
||||
|
||||
expect(params.settings.findBy('key', 'amp').value).to.equal(false);
|
||||
|
||||
// CMD-S shortcut works
|
||||
await click(testSelector('amp-checkbox'));
|
||||
await triggerEvent('.gh-app', 'keydown', {
|
||||
keyCode: 83, // s
|
||||
metaKey: ctrlOrCmd === 'command',
|
||||
ctrlKey: ctrlOrCmd === 'ctrl'
|
||||
});
|
||||
|
||||
// we've already saved in this test so there's no on-screen indication
|
||||
// that we've had another save, check the request was fired instead
|
||||
let [newRequest] = server.pretender.handledRequests.slice(-1);
|
||||
params = JSON.parse(newRequest.requestBody);
|
||||
|
||||
expect(find(testSelector('amp-checkbox')).prop('checked'), 'AMP checkbox').to.be.true;
|
||||
expect(params.settings.findBy('key', 'amp').value).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -11,6 +11,7 @@ import startApp from '../../helpers/start-app';
|
|||
import destroyApp from '../../helpers/destroy-app';
|
||||
import {invalidateSession, authenticateSession} from 'ghost-admin/tests/helpers/ember-simple-auth';
|
||||
import testSelector from 'ember-test-selectors';
|
||||
import ctrlOrCmd from 'ghost-admin/utils/ctrl-or-cmd';
|
||||
|
||||
describe('Acceptance: Settings - Code-Injection', function() {
|
||||
let application;
|
||||
|
@ -58,7 +59,7 @@ describe('Acceptance: Settings - Code-Injection', function() {
|
|||
return authenticateSession(application);
|
||||
});
|
||||
|
||||
it('it renders, loads editors correctly', async function () {
|
||||
it('it renders, loads and saves editors correctly', async function () {
|
||||
await visit('/settings/code-injection');
|
||||
|
||||
// has correct url
|
||||
|
@ -78,6 +79,28 @@ describe('Acceptance: Settings - Code-Injection', function() {
|
|||
|
||||
expect(find('#ghost-foot .CodeMirror').length, 'ghost head codemirror element').to.equal(1);
|
||||
expect($('#ghost-foot .CodeMirror').hasClass('cm-s-xq-light'), 'ghost head editor theme').to.be.true;
|
||||
|
||||
await click(testSelector('save-button'));
|
||||
|
||||
let [lastRequest] = server.pretender.handledRequests.slice(-1);
|
||||
let params = JSON.parse(lastRequest.requestBody);
|
||||
|
||||
expect(params.settings.findBy('key', 'ghost_head').value).to.equal('');
|
||||
expect(find(testSelector('save-button')).text().trim(), 'save button text').to.equal('Saved');
|
||||
|
||||
// CMD-S shortcut works
|
||||
await triggerEvent('.gh-app', 'keydown', {
|
||||
keyCode: 83, // s
|
||||
metaKey: ctrlOrCmd === 'command',
|
||||
ctrlKey: ctrlOrCmd === 'ctrl'
|
||||
});
|
||||
// we've already saved in this test so there's no on-screen indication
|
||||
// that we've had another save, check the request was fired instead
|
||||
let [newRequest] = server.pretender.handledRequests.slice(-1);
|
||||
params = JSON.parse(newRequest.requestBody);
|
||||
|
||||
expect(params.settings.findBy('key', 'ghost_head').value).to.equal('');
|
||||
expect(find(testSelector('save-button')).text().trim(), 'save button text').to.equal('Saved');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,6 +13,7 @@ import {invalidateSession, authenticateSession} from 'ghost-admin/tests/helpers/
|
|||
import Mirage from 'ember-cli-mirage';
|
||||
import mockThemes from 'ghost-admin/mirage/config/themes';
|
||||
import testSelector from 'ember-test-selectors';
|
||||
import ctrlOrCmd from 'ghost-admin/utils/ctrl-or-cmd';
|
||||
|
||||
describe('Acceptance: Settings - Design', function () {
|
||||
let application;
|
||||
|
@ -54,6 +55,7 @@ describe('Acceptance: Settings - Design', function () {
|
|||
await visit('/settings/design');
|
||||
|
||||
expect(currentPath()).to.equal('settings.design.index');
|
||||
expect(find(testSelector('save-button')).text().trim(), 'save button text').to.equal('Save');
|
||||
|
||||
// fixtures contain two nav items, check for three rows as we
|
||||
// should have one extra that's blank
|
||||
|
@ -69,7 +71,7 @@ describe('Acceptance: Settings - Design', function () {
|
|||
await fillIn('.gh-blognav-url:first input', '/test');
|
||||
await triggerEvent('.gh-blognav-url:first input', 'blur');
|
||||
|
||||
await click('.gh-btn-blue');
|
||||
await click(testSelector('save-button'));
|
||||
|
||||
let [navSetting] = server.db.settings.where({key: 'navigation'});
|
||||
|
||||
|
@ -86,7 +88,7 @@ describe('Acceptance: Settings - Design', function () {
|
|||
it('validates new item correctly on save', async function () {
|
||||
await visit('/settings/design');
|
||||
|
||||
await click('.gh-btn-blue');
|
||||
await click(testSelector('save-button'));
|
||||
|
||||
expect(
|
||||
find('.gh-blognav-item').length,
|
||||
|
@ -97,7 +99,7 @@ describe('Acceptance: Settings - Design', function () {
|
|||
await fillIn('.gh-blognav-url:last input', 'http://invalid domain/');
|
||||
await triggerEvent('.gh-blognav-url:last input', 'blur');
|
||||
|
||||
await click('.gh-btn-blue');
|
||||
await click(testSelector('save-button'));
|
||||
|
||||
expect(
|
||||
find('.gh-blognav-item').length,
|
||||
|
@ -182,7 +184,12 @@ describe('Acceptance: Settings - Design', function () {
|
|||
'number of nav items after successful remove'
|
||||
).to.equal(3);
|
||||
|
||||
await click('.gh-btn-blue');
|
||||
// CMD-S shortcut works
|
||||
await triggerEvent('.gh-app', 'keydown', {
|
||||
keyCode: 83, // s
|
||||
metaKey: ctrlOrCmd === 'command',
|
||||
ctrlKey: ctrlOrCmd === 'ctrl'
|
||||
});
|
||||
|
||||
let [navSetting] = server.db.settings.where({key: 'navigation'});
|
||||
|
||||
|
@ -649,6 +656,5 @@ describe('Acceptance: Settings - Design', function () {
|
|||
// restore default mirage handlers
|
||||
mockThemes(server);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
|
|
@ -11,6 +11,7 @@ import $ from 'jquery';
|
|||
import startApp from '../../helpers/start-app';
|
||||
import destroyApp from '../../helpers/destroy-app';
|
||||
import {invalidateSession, authenticateSession} from 'ghost-admin/tests/helpers/ember-simple-auth';
|
||||
import ctrlOrCmd from 'ghost-admin/utils/ctrl-or-cmd';
|
||||
|
||||
describe('Acceptance: Settings - General', function () {
|
||||
let application;
|
||||
|
@ -105,6 +106,19 @@ describe('Acceptance: Settings - General', function () {
|
|||
|
||||
await click(testSelector('modal-accept-button'));
|
||||
expect(find('.fullscreen-modal').length).to.equal(0);
|
||||
|
||||
// CMD-S shortcut works
|
||||
await fillIn(testSelector('title-input'), 'CMD-S Test');
|
||||
await triggerEvent('.gh-app', 'keydown', {
|
||||
keyCode: 83, // s
|
||||
metaKey: ctrlOrCmd === 'command',
|
||||
ctrlKey: ctrlOrCmd === 'ctrl'
|
||||
});
|
||||
// we've already saved in this test so there's no on-screen indication
|
||||
// that we've had another save, check the request was fired instead
|
||||
let [lastRequest] = server.pretender.handledRequests.slice(-1);
|
||||
let params = JSON.parse(lastRequest.requestBody);
|
||||
expect(params.settings.findBy('key', 'title').value).to.equal('CMD-S Test');
|
||||
});
|
||||
|
||||
it('renders timezone selector correctly', async function () {
|
||||
|
|
|
@ -11,6 +11,7 @@ import destroyApp from '../../helpers/destroy-app';
|
|||
import Mirage from 'ember-cli-mirage';
|
||||
import {invalidateSession, authenticateSession} from 'ghost-admin/tests/helpers/ember-simple-auth';
|
||||
import testSelector from 'ember-test-selectors';
|
||||
import ctrlOrCmd from 'ghost-admin/utils/ctrl-or-cmd';
|
||||
|
||||
describe('Acceptance: Settings - Apps - Slack', function () {
|
||||
let application;
|
||||
|
@ -70,6 +71,22 @@ describe('Acceptance: Settings - Apps - Slack', function () {
|
|||
expect(find('#slack-settings .error .response').text().trim(), 'inline validation response')
|
||||
.to.equal('The URL must be in a format like https://hooks.slack.com/services/<your personal key>');
|
||||
|
||||
// CMD-S shortcut works
|
||||
await fillIn(testSelector('slack-url-input'), 'https://hooks.slack.com/services/1275958430');
|
||||
await triggerEvent('.gh-app', 'keydown', {
|
||||
keyCode: 83, // s
|
||||
metaKey: ctrlOrCmd === 'command',
|
||||
ctrlKey: ctrlOrCmd === 'ctrl'
|
||||
});
|
||||
|
||||
let [newRequest] = server.pretender.handledRequests.slice(-1);
|
||||
let params = JSON.parse(newRequest.requestBody);
|
||||
let [result] = JSON.parse(params.settings.findBy('key', 'slack').value);
|
||||
|
||||
expect(result.url).to.equal('https://hooks.slack.com/services/1275958430');
|
||||
expect(find('#slack-settings .error .response').text().trim(), 'inline validation response')
|
||||
.to.equal('');
|
||||
|
||||
await fillIn('#slack-settings input[name="slack[url]"]', 'https://hooks.slack.com/services/1275958430');
|
||||
await click(testSelector('send-notification-button'));
|
||||
|
||||
|
@ -96,6 +113,5 @@ describe('Acceptance: Settings - Apps - Slack', function () {
|
|||
expect(lastRequest.url).to.not.match(/\/slack\/test/);
|
||||
expect(find('.gh-alert-blue').length, 'check slack alert after api validation error').to.equal(0);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,6 +14,7 @@ import {enableGhostOAuth} from '../helpers/configuration';
|
|||
import {Response} from 'ember-cli-mirage';
|
||||
import testSelector from 'ember-test-selectors';
|
||||
import moment from 'moment';
|
||||
import ctrlOrCmd from 'ghost-admin/utils/ctrl-or-cmd';
|
||||
|
||||
describe('Acceptance: Team', function () {
|
||||
let application;
|
||||
|
@ -471,6 +472,8 @@ describe('Acceptance: Team', function () {
|
|||
expect(currentURL(), 'currentURL').to.equal('/team/test-1');
|
||||
expect(find('.user-details-bottom .first-form-group input.user-name').val(), 'current user name').to.equal('Test User');
|
||||
|
||||
expect(find(testSelector('save-button')).text().trim(), 'save button text').to.equal('Save');
|
||||
|
||||
// test empty user name
|
||||
await fillIn('.user-details-bottom .first-form-group input.user-name', '');
|
||||
await triggerEvent('.user-details-bottom .first-form-group input.user-name', 'blur');
|
||||
|
@ -493,6 +496,26 @@ describe('Acceptance: Team', function () {
|
|||
|
||||
expect(find('.user-details-bottom input[name="user"]').val(), 'slug value is reset to original upon empty string').to.equal('test-1');
|
||||
|
||||
// Save changes
|
||||
await click(testSelector('save-button'));
|
||||
|
||||
expect(find(testSelector('save-button')).text().trim(), 'save button text').to.equal('Saved');
|
||||
|
||||
// CMD-S shortcut works
|
||||
await fillIn('.user-details-bottom input[name="user"]', 'Test User');
|
||||
await triggerEvent('.gh-app', 'keydown', {
|
||||
keyCode: 83, // s
|
||||
metaKey: ctrlOrCmd === 'command',
|
||||
ctrlKey: ctrlOrCmd === 'ctrl'
|
||||
});
|
||||
|
||||
// we've already saved in this test so there's no on-screen indication
|
||||
// that we've had another save, check the request was fired instead
|
||||
let [lastRequest] = server.pretender.handledRequests.slice(-1);
|
||||
let params = JSON.parse(lastRequest.requestBody);
|
||||
|
||||
expect(params.users[0].name).to.equal('Test User');
|
||||
|
||||
await fillIn('.user-details-bottom input[name="user"]', 'white space');
|
||||
await triggerEvent('.user-details-bottom input[name="user"]', 'blur');
|
||||
|
||||
|
@ -666,10 +689,10 @@ describe('Acceptance: Team', function () {
|
|||
await click('.button-change-password');
|
||||
|
||||
// hits the endpoint
|
||||
let [lastRequest] = server.pretender.handledRequests.slice(-1);
|
||||
let params = JSON.parse(lastRequest.requestBody);
|
||||
let [newRequest] = server.pretender.handledRequests.slice(-1);
|
||||
params = JSON.parse(newRequest.requestBody);
|
||||
|
||||
expect(lastRequest.url, 'password request URL')
|
||||
expect(newRequest.url, 'password request URL')
|
||||
.to.match(/\/users\/password/);
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
|
|
Loading…
Reference in a new issue