mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-01 05:50:35 +03:00
525fb2a720
closes https://github.com/TryGhost/Ghost/issues/9868 - moved submit action to the `<form>` which allowed removal of the per-input enter key handlers - changed submit button to trigger the form submit - improved markup for password managers - linked labels with inputs - changed button to a "submit" type and linked it with the form - added hidden email input at the bottom of the form in case password managers do not take disabled inputs into account
146 lines
4.5 KiB
JavaScript
146 lines
4.5 KiB
JavaScript
import Component from '@ember/component';
|
|
import {computed} from '@ember/object';
|
|
import {isBlank} from '@ember/utils';
|
|
import {reads} from '@ember/object/computed';
|
|
import {task, timeout} from 'ember-concurrency';
|
|
|
|
/**
|
|
* Task Button works exactly like Spin button, but with one major difference:
|
|
*
|
|
* Instead of passing a "submitting" parameter (which is bound to the parent object),
|
|
* you pass an ember-concurrency task. All of the "submitting" behavior is handled automatically.
|
|
*
|
|
* As another bonus, there's no need to handle canceling the promises when something
|
|
* like a controller changes. Because the only task running is handled through this
|
|
* component, all running promises will automatically be cancelled when this
|
|
* component is removed from the DOM
|
|
*/
|
|
const GhTaskButton = Component.extend({
|
|
tagName: 'button',
|
|
classNameBindings: [
|
|
'isRunning:appear-disabled',
|
|
'isIdleClass',
|
|
'isRunningClass',
|
|
'isSuccessClass',
|
|
'isFailureClass'
|
|
],
|
|
attributeBindings: ['disabled', 'form', 'type', 'tabindex'],
|
|
|
|
task: null,
|
|
disabled: false,
|
|
buttonText: 'Save',
|
|
idleClass: '',
|
|
runningClass: '',
|
|
successText: 'Saved',
|
|
successClass: 'gh-btn-green',
|
|
failureText: 'Retry',
|
|
failureClass: 'gh-btn-red',
|
|
|
|
// Allowed actions
|
|
action: () => {},
|
|
|
|
isRunning: reads('task.last.isRunning'),
|
|
runningText: reads('buttonText'),
|
|
|
|
// hasRun is needed so that a newly rendered button does not show the last
|
|
// state of the associated task
|
|
hasRun: computed('task.performCount', function () {
|
|
return this.get('task.performCount') > this._initialPerformCount;
|
|
}),
|
|
|
|
isIdleClass: computed('isIdle', function () {
|
|
if (this.get('isIdle')) {
|
|
return this.get('idleClass');
|
|
}
|
|
}),
|
|
|
|
isRunningClass: computed('isRunning', function () {
|
|
if (this.get('isRunning')) {
|
|
return this.get('runningClass') || this.get('idleClass');
|
|
}
|
|
}),
|
|
|
|
isSuccess: computed('hasRun', 'isRunning', 'task.last.value', function () {
|
|
if (!this.get('hasRun') || this.get('isRunning')) {
|
|
return false;
|
|
}
|
|
|
|
let value = this.get('task.last.value');
|
|
return !isBlank(value) && value !== false;
|
|
}),
|
|
|
|
isSuccessClass: computed('isSuccess', function () {
|
|
if (this.get('isSuccess')) {
|
|
return this.get('successClass');
|
|
}
|
|
}),
|
|
|
|
isFailure: computed('hasRun', 'isRunning', 'isSuccess', 'task.last.error', function () {
|
|
if (!this.get('hasRun') || this.get('isRunning') || this.get('isSuccess')) {
|
|
return false;
|
|
}
|
|
|
|
return this.get('task.last.error') !== undefined;
|
|
}),
|
|
|
|
isFailureClass: computed('isFailure', function () {
|
|
if (this.get('isFailure')) {
|
|
return this.get('failureClass');
|
|
}
|
|
}),
|
|
|
|
isIdle: computed('isRunning', 'isSuccess', 'isFailure', function () {
|
|
return !this.get('isRunning') && !this.get('isSuccess') && !this.get('isFailure');
|
|
}),
|
|
|
|
init() {
|
|
this._super(...arguments);
|
|
this._initialPerformCount = this.get('task.performCount');
|
|
},
|
|
|
|
click() {
|
|
// do nothing if disabled externally
|
|
if (this.get('disabled')) {
|
|
return false;
|
|
}
|
|
|
|
let task = this.get('task');
|
|
let taskName = this.get('task.name');
|
|
let lastTaskName = this.get('task.last.task.name');
|
|
|
|
// task-buttons are never disabled whilst running so that clicks when a
|
|
// taskGroup is running don't get dropped BUT that means we need to check
|
|
// here to avoid spamming actions from multiple clicks
|
|
if (this.get('isRunning') && taskName === lastTaskName) {
|
|
return;
|
|
}
|
|
|
|
this.action();
|
|
task.perform();
|
|
|
|
this.get('_restartAnimation').perform();
|
|
|
|
// prevent the click from bubbling and triggering form actions
|
|
return false;
|
|
},
|
|
|
|
// when local validation fails there's no transition from failed->running
|
|
// so we want to restart the retry spinner animation to show something
|
|
// has happened when the button is clicked
|
|
_restartAnimation: task(function* () {
|
|
if (this.$('.retry-animated').length) {
|
|
// eslint-disable-next-line
|
|
let elem = this.$('.retry-animated')[0];
|
|
elem.classList.remove('retry-animated');
|
|
yield timeout(10);
|
|
elem.classList.add('retry-animated');
|
|
}
|
|
})
|
|
});
|
|
|
|
GhTaskButton.reopenClass({
|
|
positionalParams: ['buttonText']
|
|
});
|
|
|
|
export default GhTaskButton;
|