diff --git a/framework/core/js/src/admin/components/BasicsPage.js b/framework/core/js/src/admin/components/BasicsPage.js index 497a0fddb..68f9ba142 100644 --- a/framework/core/js/src/admin/components/BasicsPage.js +++ b/framework/core/js/src/admin/components/BasicsPage.js @@ -2,7 +2,6 @@ import Page from '../../common/components/Page'; import FieldSet from '../../common/components/FieldSet'; import Select from '../../common/components/Select'; import Button from '../../common/components/Button'; -import Alert from '../../common/components/Alert'; import saveSettings from '../utils/saveSettings'; import ItemList from '../../common/utils/ItemList'; import Switch from '../../common/components/Switch'; @@ -186,7 +185,10 @@ export default class BasicsPage extends Page { saveSettings(settings) .then(() => { - app.alerts.show((this.successAlert = new Alert({ type: 'success', children: app.translator.trans('core.admin.basics.saved_message') }))); + this.successAlert = app.alerts.show({ + type: 'success', + children: app.translator.trans('core.admin.basics.saved_message'), + }); }) .catch(() => {}) .then(() => { diff --git a/framework/core/js/src/admin/components/MailPage.js b/framework/core/js/src/admin/components/MailPage.js index cbec021b3..13ad25dfc 100644 --- a/framework/core/js/src/admin/components/MailPage.js +++ b/framework/core/js/src/admin/components/MailPage.js @@ -179,9 +179,10 @@ export default class MailPage extends Page { }) .then((response) => { this.sendingTest = false; - app.alerts.show( - (this.testEmailSuccessAlert = new Alert({ type: 'success', children: app.translator.trans('core.admin.email.send_test_mail_success') })) - ); + this.testEmailSuccessAlert = app.alerts.show({ + type: 'success', + children: app.translator.trans('core.admin.email.send_test_mail_success'), + }); }) .catch((error) => { this.sendingTest = false; @@ -204,7 +205,10 @@ export default class MailPage extends Page { saveSettings(settings) .then(() => { - app.alerts.show((this.successAlert = new Alert({ type: 'success', children: app.translator.trans('core.admin.basics.saved_message') }))); + this.successAlert = app.alerts.show({ + type: 'success', + children: app.translator.trans('core.admin.basics.saved_message'), + }); }) .catch(() => {}) .then(() => { diff --git a/framework/core/js/src/common/Application.js b/framework/core/js/src/common/Application.js index 72a88cdf7..77d08181d 100644 --- a/framework/core/js/src/common/Application.js +++ b/framework/core/js/src/common/Application.js @@ -1,5 +1,4 @@ import ItemList from './utils/ItemList'; -import Alert from './components/Alert'; import Button from './components/Button'; import ModalManager from './components/ModalManager'; import AlertManager from './components/AlertManager'; @@ -23,6 +22,7 @@ import Group from './models/Group'; import Notification from './models/Notification'; import { flattenDeep } from 'lodash-es'; import PageState from './states/PageState'; +import AlertManagerState from './states/AlertManagerState'; /** * The `App` class provides a container for an application, as well as various @@ -109,13 +109,13 @@ export default class Application { booted = false; /** - * An Alert that was shown as a result of an AJAX request error. If present, - * it will be dismissed on the next successful request. + * The key for an Alert that was shown as a result of an AJAX request error. + * If present, it will be dismissed on the next successful request. * - * @type {null|Alert} + * @type {int} * @private */ - requestError = null; + requestErrorAlert = null; /** * The page the app is currently on. @@ -139,6 +139,11 @@ export default class Application { */ previous = new PageState(null); + /* + * An object that manages the state of active alerts. + */ + alerts = new AlertManagerState(); + data; title = ''; @@ -175,7 +180,7 @@ export default class Application { mount(basePath = '') { this.modal = m.mount(document.getElementById('modal'), ); - this.alerts = m.mount(document.getElementById('alerts'), ); + m.mount(document.getElementById('alerts'), ); this.drawer = new Drawer(); @@ -313,7 +318,7 @@ export default class Application { } }; - if (this.requestError) this.alerts.dismiss(this.requestError.alert); + if (this.requestErrorAlert) this.alerts.dismiss(this.requestErrorAlert); // Now make the request. If it's a failure, inspect the error that was // returned and show an alert containing its contents. @@ -322,8 +327,6 @@ export default class Application { m.request(options).then( (response) => deferred.resolve(response), (error) => { - this.requestError = error; - let children; switch (error.status) { @@ -357,7 +360,7 @@ export default class Application { // the details property is decoded to transform escaped characters such as '\n' const formattedError = error.response && Array.isArray(error.response.errors) && error.response.errors.map((e) => decodeURI(e.detail)); - error.alert = new Alert({ + error.alert = { type: 'error', children, controls: isDebug && [ @@ -365,7 +368,7 @@ export default class Application { Debug , ], - }); + }; try { options.errorHandler(error); @@ -381,7 +384,7 @@ export default class Application { console.groupEnd(); } - this.alerts.show(error.alert); + this.requestErrorAlert = this.alerts.show(error.alert); } deferred.reject(error); @@ -397,7 +400,7 @@ export default class Application { * @private */ showDebug(error, formattedError) { - this.alerts.dismiss(this.requestError.alert); + this.alerts.dismiss(this.requestErrorAlert); this.modal.show(new RequestErrorModal({ error, formattedError })); } diff --git a/framework/core/js/src/common/components/AlertManager.js b/framework/core/js/src/common/components/AlertManager.js index 1592e57fc..614e01b57 100644 --- a/framework/core/js/src/common/components/AlertManager.js +++ b/framework/core/js/src/common/components/AlertManager.js @@ -7,20 +7,16 @@ import Alert from './Alert'; */ export default class AlertManager extends Component { init() { - /** - * An array of Alert components which are currently showing. - * - * @type {Alert[]} - * @protected - */ - this.components = []; + this.state = this.props.state; } view() { return (
- {this.components.map((component) => ( -
{component}
+ {Object.entries(this.state.getActiveAlerts()).map(([key, alert]) => ( +
+ {(alert.componentClass || Alert).component({ ...alert.attrs, ondismiss: this.state.dismiss.bind(this.state, key) })} +
))}
); @@ -32,46 +28,4 @@ export default class AlertManager extends Component { // to be retained across route changes. context.retain = true; } - - /** - * Show an Alert in the alerts area. - * - * @param {Alert} component - * @public - */ - show(component) { - if (!(component instanceof Alert)) { - throw new Error('The AlertManager component can only show Alert components'); - } - - component.props.ondismiss = this.dismiss.bind(this, component); - - this.components.push(component); - m.redraw(); - } - - /** - * Dismiss an alert. - * - * @param {Alert} component - * @public - */ - dismiss(component) { - const index = this.components.indexOf(component); - - if (index !== -1) { - this.components.splice(index, 1); - m.redraw(); - } - } - - /** - * Clear all alerts. - * - * @public - */ - clear() { - this.components = []; - m.redraw(); - } } diff --git a/framework/core/js/src/common/components/Modal.js b/framework/core/js/src/common/components/Modal.js index 872bb1b6a..86cef3031 100644 --- a/framework/core/js/src/common/components/Modal.js +++ b/framework/core/js/src/common/components/Modal.js @@ -11,16 +11,16 @@ import Button from './Button'; export default class Modal extends Component { init() { /** - * An alert component to show below the header. + * Attributes for an alert component to show below the header. * - * @type {Alert} + * @type {object} */ - this.alert = null; + this.alertAttrs = null; } view() { - if (this.alert) { - this.alert.props.dismissible = false; + if (this.alertAttrs) { + this.alertAttrs.dismissible = false; } return ( @@ -43,7 +43,7 @@ export default class Modal extends Component {

{this.title()}

- {alert ?
{this.alert}
: ''} + {this.alertAttrs ?
{Alert.component(this.alertAttrs)}
: ''} {this.content()} @@ -123,7 +123,7 @@ export default class Modal extends Component { * @param {RequestError} error */ onerror(error) { - this.alert = error.alert; + this.alertAttrs = error.alert; m.redraw(); diff --git a/framework/core/js/src/common/states/AlertManagerState.js b/framework/core/js/src/common/states/AlertManagerState.js new file mode 100644 index 000000000..6dd2a0f29 --- /dev/null +++ b/framework/core/js/src/common/states/AlertManagerState.js @@ -0,0 +1,50 @@ +import Alert from '../components/Alert'; + +export default class AlertManagerState { + constructor() { + this.activeAlerts = {}; + this.alertId = 0; + } + + getActiveAlerts() { + return this.activeAlerts; + } + + /** + * Show an Alert in the alerts area. + */ + show(attrs, componentClass = Alert) { + // Breaking Change Compliance Warning, Remove in Beta 15. + // This is applied to the first argument (attrs) because previously, the alert was passed as the first argument. + if (attrs === Alert || attrs instanceof Alert) { + // This is duplicated so that if the error is caught, an error message still shows up in the debug console. + console.error('The AlertManager can only show Alerts. Whichever extension triggered this alert should be updated to comply with beta 14.'); + throw new Error('The AlertManager can only show Alerts. Whichever extension triggered this alert should be updated to comply with beta 14.'); + } + // End Change Compliance Warning, Remove in Beta 15 + this.activeAlerts[++this.alertId] = { attrs, componentClass }; + m.redraw(); + + return this.alertId; + } + + /** + * Dismiss an alert. + */ + dismiss(key) { + if (!key || !(key in this.activeAlerts)) return; + + delete this.activeAlerts[key]; + m.redraw(); + } + + /** + * Clear all alerts. + * + * @public + */ + clear() { + this.activeAlerts = {}; + m.redraw(); + } +} diff --git a/framework/core/js/src/forum/components/ChangeEmailModal.js b/framework/core/js/src/forum/components/ChangeEmailModal.js index ce1b1592a..ff45dbede 100644 --- a/framework/core/js/src/forum/components/ChangeEmailModal.js +++ b/framework/core/js/src/forum/components/ChangeEmailModal.js @@ -122,7 +122,7 @@ export default class ChangeEmailModal extends Modal { onerror(error) { if (error.status === 401) { - error.alert.props.children = app.translator.trans('core.forum.change_email.incorrect_password_message'); + error.alert.children = app.translator.trans('core.forum.change_email.incorrect_password_message'); } super.onerror(error); diff --git a/framework/core/js/src/forum/components/EditPostComposer.js b/framework/core/js/src/forum/components/EditPostComposer.js index 86b9703e5..258e8ecc3 100644 --- a/framework/core/js/src/forum/components/EditPostComposer.js +++ b/framework/core/js/src/forum/components/EditPostComposer.js @@ -1,5 +1,4 @@ import ComposerBody from './ComposerBody'; -import Alert from '../../common/components/Alert'; import Button from '../../common/components/Button'; import icon from '../../common/helpers/icon'; @@ -101,13 +100,11 @@ export default class EditPostComposer extends ComposerBody { app.alerts.dismiss(alert); }, }); - app.alerts.show( - (alert = new Alert({ - type: 'success', - children: app.translator.trans('core.forum.composer_edit.edited_message'), - controls: [viewButton], - })) - ); + alert = app.alerts.show({ + type: 'success', + children: app.translator.trans('core.forum.composer_edit.edited_message'), + controls: [viewButton], + }); } app.composer.hide(); diff --git a/framework/core/js/src/forum/components/ForgotPasswordModal.js b/framework/core/js/src/forum/components/ForgotPasswordModal.js index 4b443e92e..7ccdcfd1a 100644 --- a/framework/core/js/src/forum/components/ForgotPasswordModal.js +++ b/framework/core/js/src/forum/components/ForgotPasswordModal.js @@ -104,7 +104,7 @@ export default class ForgotPasswordModal extends Modal { onerror(error) { if (error.status === 404) { - error.alert.props.children = app.translator.trans('core.forum.forgot_password.not_found_message'); + error.alert.children = app.translator.trans('core.forum.forgot_password.not_found_message'); } super.onerror(error); diff --git a/framework/core/js/src/forum/components/LogInModal.js b/framework/core/js/src/forum/components/LogInModal.js index a6d87230e..cd029f30d 100644 --- a/framework/core/js/src/forum/components/LogInModal.js +++ b/framework/core/js/src/forum/components/LogInModal.js @@ -179,7 +179,7 @@ export default class LogInModal extends Modal { onerror(error) { if (error.status === 401) { - error.alert.props.children = app.translator.trans('core.forum.log_in.invalid_login_message'); + error.alert.children = app.translator.trans('core.forum.log_in.invalid_login_message'); } super.onerror(error); diff --git a/framework/core/js/src/forum/components/ReplyComposer.js b/framework/core/js/src/forum/components/ReplyComposer.js index fd6de3c18..af942d186 100644 --- a/framework/core/js/src/forum/components/ReplyComposer.js +++ b/framework/core/js/src/forum/components/ReplyComposer.js @@ -1,5 +1,4 @@ import ComposerBody from './ComposerBody'; -import Alert from '../../common/components/Alert'; import Button from '../../common/components/Button'; import icon from '../../common/helpers/icon'; import extractText from '../../common/utils/extractText'; @@ -104,13 +103,11 @@ export default class ReplyComposer extends ComposerBody { app.alerts.dismiss(alert); }, }); - app.alerts.show( - (alert = new Alert({ - type: 'success', - children: app.translator.trans('core.forum.composer_reply.posted_message'), - controls: [viewButton], - })) - ); + alert = app.alerts.show({ + type: 'success', + children: app.translator.trans('core.forum.composer_reply.posted_message'), + controls: [viewButton], + }); } app.composer.hide(); diff --git a/framework/core/js/src/forum/utils/UserControls.js b/framework/core/js/src/forum/utils/UserControls.js index ae1863213..7271f1fc5 100644 --- a/framework/core/js/src/forum/utils/UserControls.js +++ b/framework/core/js/src/forum/utils/UserControls.js @@ -1,4 +1,3 @@ -import Alert from '../../common/components/Alert'; import Button from '../../common/components/Button'; import Separator from '../../common/components/Separator'; import EditUserModal from '../components/EditUserModal'; @@ -134,12 +133,10 @@ export default { error: 'core.forum.user_controls.delete_error_message', }[type]; - app.alerts.show( - new Alert({ - type, - children: app.translator.trans(message, { username, email }), - }) - ); + app.alerts.show({ + type, + children: app.translator.trans(message, { username, email }), + }); }, /**