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:
committed by
Franz Liedke
parent
edeaa5855c
commit
dcd14821c2
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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" /> : '',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -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>;
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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)
|
||||
|
@@ -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' })];
|
||||
}
|
||||
}
|
||||
|
@@ -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() {
|
||||
|
@@ -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(() => {
|
||||
|
@@ -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() {
|
||||
|
@@ -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();
|
||||
}
|
||||
|
Reference in New Issue
Block a user