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
@@ -10,15 +16,29 @@ import { ClassComponent, VnodeDOM } from 'mithril';
*
* @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;
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;
}
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
* 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);
}
protected initAttrs(attrs: T): T {
return attrs;
}
}

View File

@@ -22,7 +22,7 @@ import LoadingIndicator from './LoadingIndicator';
*/
export default class Button extends Component {
view(vnode) {
const attrs = Object.assign({}, vnode.attrs);
const attrs = Object.assign({}, this.attrs);
attrs.className = attrs.className || '';
attrs.type = attrs.type || 'button';
@@ -47,7 +47,7 @@ export default class Button extends Component {
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 {*}
* @protected
*/
getButtonContent(attrs, children) {
const iconName = attrs.icon;
getButtonContent(children) {
const iconName = this.attrs.icon;
return [
iconName && iconName !== true ? icon(iconName, { className: 'Button-icon' }) : '',
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.
*/
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) {
super.oninit(vnode);
this.showing = false;
}
view(vnode) {
const items = vnode.children ? listItems(vnode.children) : [];
this.initAttrs(vnode.attrs);
return (
<div className={'ButtonGroup Dropdown dropdown ' + vnode.attrs.className + ' itemCount' + items.length + (this.showing ? ' open' : '')}>
{this.getButton(vnode.attrs, vnode.children)}
{this.getMenu(vnode.attrs.menuClassName, items)}
<div className={'ButtonGroup Dropdown dropdown ' + this.attrs.className + ' itemCount' + items.length + (this.showing ? ' open' : '')}>
{this.getButton(vnode.children)}
{this.getMenu(items)}
</div>
);
}
@@ -45,8 +53,8 @@ export default class Dropdown extends Component {
this.$().on('shown.bs.dropdown', () => {
this.showing = true;
if (vnode.attrs.onshow) {
vnode.attrs.onshow();
if (this.attrs.onshow) {
this.attrs.onshow();
}
m.redraw();
@@ -68,32 +76,24 @@ export default class Dropdown extends Component {
this.$().on('hidden.bs.dropdown', () => {
this.showing = false;
if (vnode.attrs.onhide) {
vnode.attrs.onhide();
if (this.attrs.onhide) {
this.attrs.onhide();
}
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.
*
* @return {*}
* @protected
*/
getButton(attrs, children) {
getButton(children) {
return (
<button className={'Dropdown-toggle ' + attrs.buttonClassName} data-toggle="dropdown" onclick={attrs.onclick}>
{this.getButtonContent(attrs, children)}
<button className={'Dropdown-toggle ' + this.attrs.buttonClassName} data-toggle="dropdown" onclick={this.attrs.onclick}>
{this.getButtonContent(children)}
</button>
);
}
@@ -104,15 +104,15 @@ export default class Dropdown extends Component {
* @return {*}
* @protected
*/
getButtonContent(attrs, children) {
getButtonContent(children) {
return [
attrs.icon ? icon(attrs.icon, { className: 'Button-icon' }) : '',
<span className="Button-label">{attrs.label}</span>,
attrs.caretIcon ? icon(attrs.caretIcon, { className: 'Button-caret' }) : '',
this.attrs.icon ? icon(this.attrs.icon, { className: 'Button-icon' }) : '',
<span className="Button-label">{this.attrs.label}</span>,
this.attrs.caretIcon ? icon(this.attrs.caretIcon, { className: 'Button-caret' }) : '',
];
}
getMenu(menuClassName, items) {
return <ul className={'Dropdown-menu dropdown-menu ' + menuClassName}>{items}</ul>;
getMenu(items) {
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.
*/
export default class LoadingIndicator extends Component {
view(vnode) {
const attrs = Object.assign({}, vnode.attrs);
view() {
const attrs = Object.assign({}, this.attrs);
attrs.className = 'LoadingIndicator ' + (attrs.className || '');
delete attrs.size;
@@ -24,7 +24,7 @@ export default class LoadingIndicator extends Component {
const options = { zIndex: 'auto', color: this.$().css('color') };
switch (vnode.size) {
switch (this.attrs.size) {
case 'large':
Object.assign(options, { lines: 10, length: 8, width: 4, radius: 8 });
break;

View File

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

View File

@@ -6,12 +6,8 @@ import Component from '../Component';
* overwrite the previous one.
*/
export default class ModalManager extends Component {
oninit(vnode) {
this.state = vnode.attrs.state;
}
view(vnode) {
const modal = vnode.attrs.state.modal;
const modal = this.attrs.state.modal;
return (
<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
// DOM-based Bootstrap JavaScript code triggered the closing of the modal,
// 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) {
const dismissible = !!this.state.modal.componentClass.isDismissible;
const dismissible = !!this.attrs.state.modal.componentClass.isDismissible;
this.$()
.one('shown.bs.modal', readyCallback)

View File

@@ -20,12 +20,12 @@ export default class SelectDropdown extends Dropdown {
attrs.className += ' Dropdown--select';
}
getButtonContent(attrs, children) {
getButtonContent(children) {
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];
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.
*/
export default class SplitDropdown extends Dropdown {
static initProps(props) {
super.initProps(props);
initAttrs(attrs) {
super.initAttrs(attrs);
props.className += ' Dropdown--split';
props.menuClassName += ' Dropdown-menu--right';
attrs.className += ' Dropdown--split';
attrs.menuClassName += ' Dropdown-menu--right';
}
getButton() {

View File

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

View File

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

View File

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