1
0
mirror of https://github.com/flarum/core.git synced 2025-08-11 10:55:47 +02:00

infrastructure: revert to using this.attrs

This commit is contained in:
Matthew Kilgore
2020-08-07 19:25:34 -04:00
committed by Franz Liedke
parent edeaa5855c
commit dcd14821c2
11 changed files with 118 additions and 92 deletions

View File

@@ -1,4 +1,10 @@
import { ClassComponent, VnodeDOM } from 'mithril'; import * as Mithril from 'mithril';
export type ComponentAttrs = {
className?: string;
[key: string]: any;
};
/** /**
* The `Component` class defines a user interface 'building block'. A component * The `Component` class defines a user interface 'building block'. A component
@@ -10,15 +16,29 @@ import { ClassComponent, VnodeDOM } from 'mithril';
* *
* @see https://mithril.js.org/components.html * @see https://mithril.js.org/components.html
*/ */
export default abstract class Component implements ClassComponent { export default abstract class Component<T extends ComponentAttrs = any> implements Mithril.ClassComponent<T> {
element!: Element; element!: Element;
abstract view(); attrs: T;
oncreate(vnode: VnodeDOM) { abstract view(vnode: Mithril.Vnode<T, this>): Mithril.Children;
oninit(vnode: Mithril.Vnode<T, this>) {
this.initAttrs(vnode.attrs);
this.attrs = vnode.attrs;
}
oncreate(vnode: Mithril.VnodeDOM<T, this>) {
this.element = vnode.dom; this.element = vnode.dom;
} }
onbeforeupdate(vnode: Mithril.VnodeDOM<T, this>) {
this.initAttrs(vnode.attrs);
this.attrs = vnode.attrs;
}
/** /**
* Returns a jQuery object for this component's element. If you pass in a * Returns a jQuery object for this component's element. If you pass in a
* selector string, this method will return a jQuery object, using the current * selector string, this method will return a jQuery object, using the current
@@ -46,4 +66,8 @@ export default abstract class Component implements ClassComponent {
return m(this as any, componentProps, children); return m(this as any, componentProps, children);
} }
protected initAttrs(attrs: T): T {
return attrs;
}
} }

View File

@@ -22,7 +22,7 @@ import LoadingIndicator from './LoadingIndicator';
*/ */
export default class Button extends Component { export default class Button extends Component {
view(vnode) { view(vnode) {
const attrs = Object.assign({}, vnode.attrs); const attrs = Object.assign({}, this.attrs);
attrs.className = attrs.className || ''; attrs.className = attrs.className || '';
attrs.type = attrs.type || 'button'; attrs.type = attrs.type || 'button';
@@ -47,7 +47,7 @@ export default class Button extends Component {
delete attrs.onclick; delete attrs.onclick;
} }
return <button {...attrs}>{this.getButtonContent(vnode.attrs, vnode.children)}</button>; return <button {...attrs}>{this.getButtonContent(vnode.children)}</button>;
} }
/** /**
@@ -56,13 +56,13 @@ export default class Button extends Component {
* @return {*} * @return {*}
* @protected * @protected
*/ */
getButtonContent(attrs, children) { getButtonContent(children) {
const iconName = attrs.icon; const iconName = this.attrs.icon;
return [ return [
iconName && iconName !== true ? icon(iconName, { className: 'Button-icon' }) : '', iconName && iconName !== true ? icon(iconName, { className: 'Button-icon' }) : '',
children ? <span className="Button-label">{children}</span> : '', children ? <span className="Button-label">{children}</span> : '',
attrs.loading ? <LoadingIndicator size="tiny" className="LoadingIndicator--inline" /> : '', this.attrs.loading ? <LoadingIndicator size="tiny" className="LoadingIndicator--inline" /> : '',
]; ];
} }
} }

View File

@@ -19,19 +19,27 @@ import listItems from '../helpers/listItems';
* The children will be displayed as a list inside of the dropdown menu. * The children will be displayed as a list inside of the dropdown menu.
*/ */
export default class Dropdown extends Component { export default class Dropdown extends Component {
initAttrs(attrs) {
attrs.className = attrs.className || '';
attrs.buttonClassName = attrs.buttonClassName || '';
attrs.menuClassName = attrs.menuClassName || '';
attrs.label = attrs.label || '';
attrs.caretIcon = typeof attrs.caretIcon !== 'undefined' ? attrs.caretIcon : 'fas fa-caret-down';
}
oninit(vnode) { oninit(vnode) {
super.oninit(vnode);
this.showing = false; this.showing = false;
} }
view(vnode) { view(vnode) {
const items = vnode.children ? listItems(vnode.children) : []; const items = vnode.children ? listItems(vnode.children) : [];
this.initAttrs(vnode.attrs);
return ( return (
<div className={'ButtonGroup Dropdown dropdown ' + vnode.attrs.className + ' itemCount' + items.length + (this.showing ? ' open' : '')}> <div className={'ButtonGroup Dropdown dropdown ' + this.attrs.className + ' itemCount' + items.length + (this.showing ? ' open' : '')}>
{this.getButton(vnode.attrs, vnode.children)} {this.getButton(vnode.children)}
{this.getMenu(vnode.attrs.menuClassName, items)} {this.getMenu(items)}
</div> </div>
); );
} }
@@ -45,8 +53,8 @@ export default class Dropdown extends Component {
this.$().on('shown.bs.dropdown', () => { this.$().on('shown.bs.dropdown', () => {
this.showing = true; this.showing = true;
if (vnode.attrs.onshow) { if (this.attrs.onshow) {
vnode.attrs.onshow(); this.attrs.onshow();
} }
m.redraw(); m.redraw();
@@ -68,32 +76,24 @@ export default class Dropdown extends Component {
this.$().on('hidden.bs.dropdown', () => { this.$().on('hidden.bs.dropdown', () => {
this.showing = false; this.showing = false;
if (vnode.attrs.onhide) { if (this.attrs.onhide) {
vnode.attrs.onhide(); this.attrs.onhide();
} }
m.redraw(); m.redraw();
}); });
} }
initAttrs(attrs) {
attrs.className = attrs.className || '';
attrs.buttonClassName = attrs.buttonClassName || '';
attrs.menuClassName = attrs.menuClassName || '';
attrs.label = attrs.label || '';
attrs.caretIcon = typeof attrs.caretIcon !== 'undefined' ? attrs.caretIcon : 'fas fa-caret-down';
}
/** /**
* Get the template for the button. * Get the template for the button.
* *
* @return {*} * @return {*}
* @protected * @protected
*/ */
getButton(attrs, children) { getButton(children) {
return ( return (
<button className={'Dropdown-toggle ' + attrs.buttonClassName} data-toggle="dropdown" onclick={attrs.onclick}> <button className={'Dropdown-toggle ' + this.attrs.buttonClassName} data-toggle="dropdown" onclick={this.attrs.onclick}>
{this.getButtonContent(attrs, children)} {this.getButtonContent(children)}
</button> </button>
); );
} }
@@ -104,15 +104,15 @@ export default class Dropdown extends Component {
* @return {*} * @return {*}
* @protected * @protected
*/ */
getButtonContent(attrs, children) { getButtonContent(children) {
return [ return [
attrs.icon ? icon(attrs.icon, { className: 'Button-icon' }) : '', this.attrs.icon ? icon(this.attrs.icon, { className: 'Button-icon' }) : '',
<span className="Button-label">{attrs.label}</span>, <span className="Button-label">{this.attrs.label}</span>,
attrs.caretIcon ? icon(attrs.caretIcon, { className: 'Button-caret' }) : '', this.attrs.caretIcon ? icon(this.attrs.caretIcon, { className: 'Button-caret' }) : '',
]; ];
} }
getMenu(menuClassName, items) { getMenu(items) {
return <ul className={'Dropdown-menu dropdown-menu ' + menuClassName}>{items}</ul>; return <ul className={'Dropdown-menu dropdown-menu ' + this.attrs.menuClassName}>{items}</ul>;
} }
} }

View File

@@ -10,8 +10,8 @@ import { Spinner } from 'spin.js';
* All other props will be assigned as attributes on the element. * All other props will be assigned as attributes on the element.
*/ */
export default class LoadingIndicator extends Component { export default class LoadingIndicator extends Component {
view(vnode) { view() {
const attrs = Object.assign({}, vnode.attrs); const attrs = Object.assign({}, this.attrs);
attrs.className = 'LoadingIndicator ' + (attrs.className || ''); attrs.className = 'LoadingIndicator ' + (attrs.className || '');
delete attrs.size; delete attrs.size;
@@ -24,7 +24,7 @@ export default class LoadingIndicator extends Component {
const options = { zIndex: 'auto', color: this.$().css('color') }; const options = { zIndex: 'auto', color: this.$().css('color') };
switch (vnode.size) { switch (this.attrs.size) {
case 'large': case 'large':
Object.assign(options, { lines: 10, length: 8, width: 4, radius: 8 }); Object.assign(options, { lines: 10, length: 8, width: 4, radius: 8 });
break; break;

View File

@@ -14,17 +14,17 @@ export default class Modal extends Component {
*/ */
static isDismissible = true; static isDismissible = true;
oninit() { /**
/** * Attributes for an alert component to show below the header.
* Attributes for an alert component to show below the header. *
* * @type {object}
* @type {object} */
*/ alertAttrs = null;
this.alertAttrs = null;
}
oncreate(vnode) { oncreate(vnode) {
vnode.attrs.onshow(() => this.onready(vnode.attrs)); super.oncreate(vnode);
this.attrs.onshow(() => this.onready());
} }
view(vnode) { view(vnode) {
@@ -39,7 +39,7 @@ export default class Modal extends Component {
<div className="Modal-close App-backControl"> <div className="Modal-close App-backControl">
{Button.component({ {Button.component({
icon: 'fas fa-times', icon: 'fas fa-times',
onclick: this.hide.bind(this, vnode.attrs), onclick: this.hide.bind(this),
className: 'Button Button--icon Button--link', className: 'Button Button--icon Button--link',
})} })}
</div> </div>
@@ -54,7 +54,7 @@ export default class Modal extends Component {
{this.alertAttrs ? <div className="Modal-alert">{Alert.component(this.alertAttrs)}</div> : ''} {this.alertAttrs ? <div className="Modal-alert">{Alert.component(this.alertAttrs)}</div> : ''}
{this.content(vnode.attrs)} {this.content(this.attrs)}
</form> </form>
</div> </div>
</div> </div>
@@ -83,7 +83,7 @@ export default class Modal extends Component {
* @return {VirtualElement} * @return {VirtualElement}
* @abstract * @abstract
*/ */
content(attrs) {} content() {}
/** /**
* Handle the modal form's submit event. * Handle the modal form's submit event.
@@ -95,15 +95,15 @@ export default class Modal extends Component {
/** /**
* Focus on the first input when the modal is ready to be used. * Focus on the first input when the modal is ready to be used.
*/ */
onready(attrs) { onready() {
this.$('form').find('input, select, textarea').first().focus().select(); this.$('form').find('input, select, textarea').first().focus().select();
} }
/** /**
* Hide the modal. * Hide the modal.
*/ */
hide(attrs) { hide() {
attrs.onhide(); this.attrs.onhide();
} }
/** /**

View File

@@ -6,12 +6,8 @@ import Component from '../Component';
* overwrite the previous one. * overwrite the previous one.
*/ */
export default class ModalManager extends Component { export default class ModalManager extends Component {
oninit(vnode) {
this.state = vnode.attrs.state;
}
view(vnode) { view(vnode) {
const modal = vnode.attrs.state.modal; const modal = this.attrs.state.modal;
return ( return (
<div className="ModalManager modal fade"> <div className="ModalManager modal fade">
@@ -26,11 +22,11 @@ export default class ModalManager extends Component {
// Ensure the modal state is notified about a closed modal, even when the // Ensure the modal state is notified about a closed modal, even when the
// DOM-based Bootstrap JavaScript code triggered the closing of the modal, // DOM-based Bootstrap JavaScript code triggered the closing of the modal,
// e.g. via ESC key or a click on the modal backdrop. // e.g. via ESC key or a click on the modal backdrop.
this.$().on('hidden.bs.modal', this.state.close.bind(this.state)); this.$().on('hidden.bs.modal', this.attrs.state.close.bind(this.attrs.state));
} }
animateShow(readyCallback) { animateShow(readyCallback) {
const dismissible = !!this.state.modal.componentClass.isDismissible; const dismissible = !!this.attrs.state.modal.componentClass.isDismissible;
this.$() this.$()
.one('shown.bs.modal', readyCallback) .one('shown.bs.modal', readyCallback)

View File

@@ -20,12 +20,12 @@ export default class SelectDropdown extends Dropdown {
attrs.className += ' Dropdown--select'; attrs.className += ' Dropdown--select';
} }
getButtonContent(attrs, children) { getButtonContent(children) {
const activeChild = children.filter((child) => child.attrs.active)[0]; const activeChild = children.filter((child) => child.attrs.active)[0];
let label = (activeChild && activeChild.children) || attrs.defaultLabel; let label = (activeChild && activeChild.children) || this.attrs.defaultLabel;
if (label instanceof Array) label = label[0]; if (label instanceof Array) label = label[0];
return [<span className="Button-label">{label}</span>, icon(attrs.caretIcon, { className: 'Button-caret' })]; return [<span className="Button-label">{label}</span>, icon(this.attrs.caretIcon, { className: 'Button-caret' })];
} }
} }

View File

@@ -7,11 +7,11 @@ import icon from '../helpers/icon';
* is displayed as its own button prior to the toggle button. * is displayed as its own button prior to the toggle button.
*/ */
export default class SplitDropdown extends Dropdown { export default class SplitDropdown extends Dropdown {
static initProps(props) { initAttrs(attrs) {
super.initProps(props); super.initAttrs(attrs);
props.className += ' Dropdown--split'; attrs.className += ' Dropdown--split';
props.menuClassName += ' Dropdown-menu--right'; attrs.menuClassName += ' Dropdown-menu--right';
} }
getButton() { getButton() {

View File

@@ -12,13 +12,15 @@ import extractText from '../../common/utils/extractText';
* - `email` * - `email`
*/ */
export default class ForgotPasswordModal extends Modal { export default class ForgotPasswordModal extends Modal {
oninit(attrs) { oninit(vnode) {
super.oninit(vnode);
/** /**
* The value of the email input. * The value of the email input.
* *
* @type {Function} * @type {Function}
*/ */
this.email = Stream(attrs.email || ''); this.email = Stream(this.attrs.email || '');
/** /**
* Whether or not the password reset email was sent successfully. * Whether or not the password reset email was sent successfully.
@@ -91,7 +93,7 @@ export default class ForgotPasswordModal extends Modal {
.request({ .request({
method: 'POST', method: 'POST',
url: app.forum.attribute('apiUrl') + '/forgot', url: app.forum.attribute('apiUrl') + '/forgot',
data: { email: this.email() }, body: { email: this.email() },
errorHandler: this.onerror.bind(this), errorHandler: this.onerror.bind(this),
}) })
.then(() => { .then(() => {

View File

@@ -17,26 +17,28 @@ import ItemList from '../../common/utils/ItemList';
*/ */
export default class LogInModal extends Modal { export default class LogInModal extends Modal {
oninit(vnode) { oninit(vnode) {
super.oninit(vnode);
/** /**
* The value of the identification input. * The value of the identification input.
* *
* @type {Function} * @type {Function}
*/ */
this.identification = Stream(vnode.attrs.identification || ''); this.identification = Stream(this.attrs.identification || '');
/** /**
* The value of the password input. * The value of the password input.
* *
* @type {Function} * @type {Function}
*/ */
this.password = Stream(vnode.attrs.password || ''); this.password = Stream(this.attrs.password || '');
/** /**
* The value of the remember me input. * The value of the remember me input.
* *
* @type {Function} * @type {Function}
*/ */
this.remember = Stream(!!vnode.attrs.remember); this.remember = Stream(!!this.attrs.remember);
} }
className() { className() {

View File

@@ -18,26 +18,28 @@ import ItemList from '../../common/utils/ItemList';
*/ */
export default class SignUpModal extends Modal { export default class SignUpModal extends Modal {
oninit(vnode) { oninit(vnode) {
super.oninit(vnode);
/** /**
* The value of the username input. * The value of the username input.
* *
* @type {Function} * @type {Function}
*/ */
this.username = Stream(vnode.attrs.username || ''); this.username = Stream(this.attrs.username || '');
/** /**
* The value of the email input. * The value of the email input.
* *
* @type {Function} * @type {Function}
*/ */
this.email = Stream(vnode.attrs.email || ''); this.email = Stream(this.attrs.email || '');
/** /**
* The value of the password input. * The value of the password input.
* *
* @type {Function} * @type {Function}
*/ */
this.password = Stream(vnode.attrs.password || ''); this.password = Stream(this.attrs.password || '');
} }
className() { className() {
@@ -48,19 +50,19 @@ export default class SignUpModal extends Modal {
return app.translator.trans('core.forum.sign_up.title'); return app.translator.trans('core.forum.sign_up.title');
} }
content(attrs) { content() {
return [<div className="Modal-body">{this.body(attrs)}</div>, <div className="Modal-footer">{this.footer()}</div>]; return [<div className="Modal-body">{this.body()}</div>, <div className="Modal-footer">{this.footer()}</div>];
} }
isProvided(field, attrs) { isProvided(field) {
return attrs.provided && attrs.provided.indexOf(field) !== -1; return this.attrs.provided && this.attrs.provided.indexOf(field) !== -1;
} }
body(attrs) { body() {
return [attrs.token ? '' : <LogInButtons />, <div className="Form Form--centered">{this.fields(attrs).toArray()}</div>]; return [this.attrs.token ? '' : <LogInButtons />, <div className="Form Form--centered">{this.fields().toArray()}</div>];
} }
fields(attrs) { fields() {
const items = new ItemList(); const items = new ItemList();
items.add( items.add(
@@ -73,7 +75,7 @@ export default class SignUpModal extends Modal {
placeholder={extractText(app.translator.trans('core.forum.sign_up.username_placeholder'))} placeholder={extractText(app.translator.trans('core.forum.sign_up.username_placeholder'))}
value={this.username()} value={this.username()}
bidi={this.username} bidi={this.username}
disabled={this.loading || this.isProvided('username', attrs)} disabled={this.loading || this.isProvided('username', this.attrs)}
/> />
</div>, </div>,
30 30
@@ -89,13 +91,13 @@ export default class SignUpModal extends Modal {
placeholder={extractText(app.translator.trans('core.forum.sign_up.email_placeholder'))} placeholder={extractText(app.translator.trans('core.forum.sign_up.email_placeholder'))}
value={this.email()} value={this.email()}
bidi={this.email} bidi={this.email}
disabled={this.loading || this.isProvided('email', attrs)} disabled={this.loading || this.isProvided('email', this.attrs)}
/> />
</div>, </div>,
20 20
); );
if (!attrs.token) { if (!this.attrs.token) {
items.add( items.add(
'password', 'password',
<div className="Form-group"> <div className="Form-group">
@@ -147,8 +149,8 @@ export default class SignUpModal extends Modal {
app.modal.show(LogInModal, props); app.modal.show(LogInModal, props);
} }
onready(attrs) { onready() {
if (attrs.username && !attrs.email) { if (this.attrs.username && !this.attrs.email) {
this.$('[name=email]').select(); this.$('[name=email]').select();
} else { } else {
this.$('[name=username]').select(); this.$('[name=username]').select();
@@ -160,7 +162,7 @@ export default class SignUpModal extends Modal {
this.loading = true; this.loading = true;
const body = this.submitData(attrs); const body = this.submitData();
app app
.request({ .request({
@@ -178,14 +180,14 @@ export default class SignUpModal extends Modal {
* @return {Object} * @return {Object}
* @protected * @protected
*/ */
submitData(attrs) { submitData() {
const data = { const data = {
username: this.username(), username: this.username(),
email: this.email(), email: this.email(),
}; };
if (attrs.token) { if (this.attrs.token) {
data.token = attrs.token; data.token = this.attrs.token;
} else { } else {
data.password = this.password(); data.password = this.password();
} }