Massive JavaScript cleanup

- Use JSX for templates
- Docblock/comment everything
- Mostly passes ESLint (still some work to do)
- Lots of renaming, refactoring, etc.

CSS hasn't been updated yet.
This commit is contained in:
Toby Zerner
2015-07-15 14:00:11 +09:30
parent 4480e0a83f
commit ab6c03c0cc
220 changed files with 9785 additions and 5919 deletions

View File

@ -0,0 +1,55 @@
import Component from 'flarum/Component';
import icon from 'flarum/helpers/icon';
import extract from 'flarum/utils/extract';
/**
* The `Button` component defines an element which, when clicked, performs an
* action. The button may have the following special props:
*
* - `icon` The name of the icon class. If specified, the button will be given a
* 'has-icon' class name.
* - `disabled` Whether or not the button is disabled. If truthy, the button
* will be given a 'disabled' class name, and any `onclick` handler will be
* removed.
*
* All other props will be assigned as attributes on the button element.
*
* Note that a Button has no default class names. This is because a Button can
* be used to represent any generic clickable control, like a menu item.
*/
export default class Button extends Component {
view() {
const attrs = Object.assign({}, this.props);
delete attrs.children;
attrs.className = (attrs.className || '');
attrs.href = attrs.href || 'javascript:;';
const iconName = extract(attrs, 'icon');
if (iconName) attrs.className += ' has-icon';
const disabled = extract(attrs, 'disabled');
if (disabled) {
attrs.className += ' disabled';
delete attrs.onclick;
}
return <a {...attrs}>{this.getButtonContent()}</a>;
}
/**
* Get the template for the button's content.
*
* @return {*}
* @protected
*/
getButtonContent() {
const iconName = this.props.icon;
return [
iconName ? icon(iconName) : '',
<span className="label">{this.props.children}</span>
];
}
}

View File

@ -0,0 +1,69 @@
import Component from 'flarum/Component';
import LoadingIndicator from 'flarum/components/LoadingIndicator';
import icon from 'flarum/helpers/icon';
/**
* The `Checkbox` component defines a checkbox input.
*
* ### Props
*
* - `state` Whether or not the checkbox is checked.
* - `className` The class name for the root element.
* - `disabled` Whether or not the checkbox is disabled.
* - `onchange` A callback to run when the checkbox is checked/unchecked.
* - `children` A text label to display next to the checkbox.
*/
export default class Checkbox extends Component {
constructor(...args) {
super(...args);
/**
* Whether or not the checkbox's value is in the process of being saved.
*
* @type {Boolean}
* @public
*/
this.loading = false;
}
view() {
let className = 'checkbox ' + (this.props.state ? 'on' : 'off') + ' ' + (this.props.className || '');
if (this.loading) className += ' loading';
if (this.props.disabled) className += ' disabled';
return (
<label className={className}>
<input type="checkbox"
checked={this.props.state}
disabled={this.props.disabled}
onchange={m.withAttr('checked', this.onchange.bind(this))}/>
<div className="checkbox-display">
{this.getDisplay()}
</div>
{this.props.children}
</label>
);
}
/**
* Get the template for the checkbox's display (tick/cross icon).
*
* @return {*}
* @protected
*/
getDisplay() {
return this.loading
? LoadingIndicator.component({size: 'tiny'})
: icon(this.props.state ? 'check' : 'times');
}
/**
* Run a callback when the state of the checkbox is changed.
*
* @param {Boolean} checked
* @protected
*/
onchange(checked) {
if (this.props.onchange) this.props.onchange(checked, this);
}
}

View File

@ -0,0 +1,69 @@
import Component from 'flarum/Component';
import icon from 'flarum/helpers/icon';
import listItems from 'flarum/helpers/listItems';
/**
* The `Dropdown` component displays a button which, when clicked, shows a
* dropdown menu beneath it.
*
* ### Props
*
* - `buttonClassName` A class name to apply to the dropdown toggle button.
* - `menuClassName` A class name to apply to the dropdown menu.
* - `icon` The name of an icon to show in the dropdown toggle button. Defaults
* to 'ellipsis-v'.
* - `label` The label of the dropdown toggle button. Defaults to 'Controls'.
*
* The children will be displayed as a list inside of the dropdown menu.
*/
export default class Dropdown extends Component {
static initProps(props) {
props.className = props.className || '';
props.buttonClassName = props.buttonClassName || '';
props.contentClassName = props.contentClassName || '';
props.icon = props.icon || 'ellipsis-v';
props.label = props.label || app.trans('controls');
}
view() {
return (
<div className={'dropdown btn-group ' + this.props.className}>
{this.getButton()}
<ul className={'dropdown-menu ' + this.props.menuClassName}>
{listItems(this.props.children)}
</ul>
</div>
);
}
/**
* Get the template for the button.
*
* @return {*}
* @protected
*/
getButton() {
return (
<a href="javascript:;"
className={'dropdown-toggle ' + this.props.buttonClassName}
data-toggle="dropdown"
onclick={this.props.onclick}>
{this.getButtonContent()}
</a>
);
}
/**
* Get the template for the button's content.
*
* @return {*}
* @protected
*/
getButtonContent() {
return [
icon(this.props.icon),
<span className="label">{this.props.label}</span>,
icon('caret-down', {className: 'caret'})
];
}
}

View File

@ -0,0 +1,22 @@
import Component from 'flarum/Component';
import listItems from 'flarum/helpers/listItems';
/**
* The `FieldSet` component defines a collection of fields, displayed in a list
* underneath a title. Accepted properties are:
*
* - `className` The class name for the fieldset.
* - `label` The title of this group of fields.
*
* The children should be an array of items to show in the fieldset.
*/
export default class FieldSet extends Component {
view() {
return (
<fieldset className={this.props.className}>
<legend>{this.props.label}</legend>
<ul>{listItems(this.props.children)}</ul>
</fieldset>
);
}
}

View File

@ -0,0 +1,32 @@
import Button from 'flarum/components/Button';
/**
* The `LinkButton` component defines a `Button` which links to a route.
*
* ### Props
*
* All of the props accepted by `Button`, plus:
*
* - `active` Whether or not the page that this button links to is currently
* active.
* - `href` The URL to link to. If the current URL `m.route()` matches this,
* the `active` prop will automatically be set to true.
*/
export default class LinkButton extends Button {
static initProps(props) {
props.active = this.isActive(props);
props.config = props.config || m.route;
}
/**
* Determine whether a component with the given props is 'active'.
*
* @param {Object} props
* @return {Boolean}
*/
static isActive(props) {
return typeof props.active !== 'undefined'
? props.active
: m.route() === props.href;
}
}

View File

@ -0,0 +1,27 @@
import Component from 'flarum/Component';
/**
* The `LoadingIndicator` component displays a loading spinner with spin.js. It
* may have the following special props:
*
* - `size` The spin.js size preset to use. Defaults to 'small'.
*
* All other props will be assigned as attributes on the element.
*/
export default class LoadingIndicator extends Component {
view() {
const attrs = Object.assign({}, this.props);
attrs.className = 'loading-indicator ' + (attrs.className || '');
delete attrs.size;
return <div {...attrs}>{m.trust('&nbsp;')}</div>;
}
config() {
const size = this.props.size || 'small';
$.fn.spin.presets[size].zIndex = 'auto';
this.$().spin(size);
}
}

View File

@ -0,0 +1,82 @@
import Component from 'flarum/Component';
import Modal from 'flarum/components/Modal';
/**
* The `ModalManager` component manages a modal dialog. Only one modal dialog
* can be shown at once; loading a new component into the ModalManager will
* overwrite the previous one.
*/
export default class ModalManager extends Component {
view() {
return (
<div className="modal">
{this.component && this.component.render()}
</div>
);
}
config(isInitialized) {
if (isInitialized) return;
this.$()
.on('hidden.bs.modal', this.clear.bind(this))
.on('shown.bs.modal', this.onready.bind(this));
}
/**
* Show a modal dialog.
*
* @param {Modal} component
* @public
*/
show(component) {
if (!(component instanceof Modal)) {
throw new Error('The ModalManager component can only show Modal components');
}
clearTimeout(this.hideTimeout);
this.component = component;
m.redraw(true);
this.$().modal('show');
this.onready();
}
/**
* Close the modal dialog.
*
* @public
*/
close() {
// Don't hide the modal immediately, because if the consumer happens to call
// the `show` method straight after to show another modal dialog, it will
// cause Bootstrap's modal JS to misbehave. Instead we will wait for a tiny
// bit to give the `show` method the opportunity to prevent this from going
// ahead.
this.hideTimeout = setTimeout(() => this.$().modal('hide'));
}
/**
* Clear content from the modal area.
*
* @protected
*/
clear() {
this.component = null;
m.redraw();
}
/**
* When the modal dialog is ready to be used, tell it!
*
* @protected
*/
onready() {
if (this.component && this.component.onready) {
this.component.onready(this.$());
}
}
}

View File

@ -0,0 +1,96 @@
import Component from 'flarum/Component';
import Button from 'flarum/components/Button';
/**
* The `Navigation` component displays a set of navigation buttons. Typically
* this is just a back button which pops the app's History. If the user is on
* the root page and there is no history to pop, then in some instances it may
* show a button that toggles the app's drawer.
*
* If the app has a pane, it will also include a 'pin' button which toggles the
* pinned state of the pane.
*
* Accepts the following props:
*
* - `className` The name of a class to set on the root element.
* - `drawer` Whether or not to show a button to toggle the app's drawer if
* there is no more history to pop.
*/
export default class Navigation extends Component {
view() {
const {history, pane} = app;
return (
<div className={'navigation ' + (this.props.className || '')}
onmouseenter={pane && pane.show.bind(pane)}
onmouseleave={pane && pane.onmouseleave.bind(pane)}>
<div className="btn-group">
{history.canGoBack()
? [this.getBackButton(), this.getPaneButton()]
: this.getDrawerButton()}
</div>
</div>
);
}
config(isInitialized, context) {
// Since this component is 'above' the content of the page (that is, it is a
// part of the global UI that persists between routes), we will flag the DOM
// to be retained across route changes.
context.retain = true;
}
/**
* Get the back button.
*
* @return {Object}
* @protected
*/
getBackButton() {
const {history} = app;
return Button.component({
className: 'btn btn-default btn-icon navigation-back',
onclick: history.back.bind(history),
icon: 'chevron-left'
});
}
/**
* Get the pane pinned toggle button.
*
* @return {Object|String}
* @protected
*/
getPaneButton() {
const {pane} = app;
if (!pane || !pane.active) return '';
return Button.component({
className: 'btn btn-default btn-icon navigation-pin' + (pane.pinned ? ' active' : ''),
onclick: pane.togglePinned.bind(pane),
icon: 'thumb-tack'
});
}
/**
* Get the drawer toggle button.
*
* @return {Object|String}
* @protected
*/
getDrawerButton() {
if (!this.props.drawer) return '';
const {drawer} = app;
const user = app.session.user;
return Button.component({
className: 'btn btn-default btn-icon navigation-drawer' +
(user && user.unreadNotificationsCount() ? ' unread' : ''),
onclick: drawer.toggle.bind(drawer),
icon: 'reorder'
});
}
}

View File

@ -0,0 +1,25 @@
import Component from 'flarum/Component';
import icon from 'flarum/helpers/icon';
/**
* The `Select` component displays a <select> input, surrounded with some extra
* elements for styling. It accepts the following props:
*
* - `options` A map of option values to labels.
* - `onchange` A callback to run when the selected value is changed.
* - `value` The value of the selected option.
*/
export default class Select extends Component {
view() {
const {options, onchange, value} = this.props;
return (
<span className="select">
<select className="form-control" onchange={m.withAttr('value', onchange.bind(this))} value={value}>
{Object.keys(options).map(key => <option value={key}>{options[key]}</option>)}
</select>
{icon('sort', {className: 'caret'})}
</span>
);
}
}

View File

@ -0,0 +1,25 @@
import Dropdown from 'flarum/components/Dropdown';
import icon from 'flarum/helpers/icon';
/**
* The `SelectDropdown` component is the same as a `Dropdown`, except the toggle
* button's label is set as the label of the first child which has a truthy
* `active` prop.
*/
export default class SelectDropdown extends Dropdown {
static initProps(props) {
super.initProps(props);
props.className += ' select-dropdown';
}
getButtonContent() {
const activeChild = this.props.children.filter(child => child.props.active)[0];
const label = activeChild && activeChild.props.label;
return [
<span className="label">{label}</span>,
icon('sort', {className: 'caret'})
];
}
}

View File

@ -0,0 +1,50 @@
import Dropdown from 'flarum/components/Dropdown';
import Button from 'flarum/components/Button';
import icon from 'flarum/helpers/icon';
/**
* The `SplitDropdown` component is similar to `Dropdown`, but the first child
* is displayed as its own button prior to the toggle button.
*/
export default class SplitDropdown extends Dropdown {
static initProps(props) {
super.initProps(props);
props.className += ' split-dropdown';
props.menuClassName += ' dropdown-menu-right';
}
getButton() {
// Make a copy of the props of the first child component. We will assign
// these props to a new button, so that it has exactly the same behaviour as
// the first child.
const firstChild = this.getFirstChild();
const buttonProps = Object.assign({}, firstChild.props);
buttonProps.className = (buttonProps.className || '') + ' ' + this.props.buttonClassName;
return [
Button.component(buttonProps),
<a href="javascript:;"
className={'dropdown-toggle btn-icon ' + this.props.buttonClassName}
data-toggle="dropdown">
{icon(this.props.icon)}
{icon('caret-down', {className: 'caret'})}
</a>
];
}
/**
* Get the first child. If the first child is an array, the first item in that
* array will be returned.
*
* @return {*}
* @protected
*/
getFirstChild() {
let firstChild = this.props.children;
while (firstChild instanceof Array) firstChild = firstChild[0];
return firstChild;
}
}

View File

@ -0,0 +1,17 @@
import Checkbox from 'flarum/components/Checkbox';
/**
* The `Switch` component is a `Checkbox`, but with a switch display instead of
* a tick/cross one.
*/
export default class Switch extends Checkbox {
static initProps(props) {
super.initProps(props);
props.className += ' switch';
}
getDisplay() {
return '';
}
}

View File

@ -1,27 +0,0 @@
import Component from 'flarum/component';
import icon from 'flarum/helpers/icon';
export default class ActionButton extends Component {
view() {
var attrs = {};
for (var i in this.props) { attrs[i] = this.props[i]; }
var iconName = attrs.icon;
delete attrs.icon;
var label = attrs.label;
delete attrs.label;
if (attrs.disabled) {
attrs.className = (attrs.className || '')+' disabled';
delete attrs.onclick;
delete attrs.disabled;
}
attrs.href = attrs.href || 'javascript:;';
return m('a'+(iconName ? '.has-icon' : ''), attrs, [
iconName ? icon(iconName+' icon') : '', ' ',
m('span.label', label)
]);
}
}

View File

@ -1,33 +1,56 @@
import Component from 'flarum/component';
import ActionButton from 'flarum/components/action-button';
import listItems from 'flarum/helpers/list-items';
import Component from 'flarum/Component';
import Button from 'flarum/components/Button';
import listItems from 'flarum/helpers/listItems';
import extract from 'flarum/utils/extract';
/**
* The `Alert` component represents an alert box, which contains a message,
* some controls, and may be dismissible.
*
* The alert may have the following special props:
*
* - `type` The type of alert this is. Will be used to give the alert a class
* name of `alert-{type}`.
* - `controls` An array of controls to show in the alert.
* - `dismissible` Whether or not the alert can be dismissed.
* - `ondismiss` A callback to run when the alert is dismissed.
*
* All other props will be assigned as attributes on the alert element.
*/
export default class Alert extends Component {
view() {
var attrs = {};
for (var i in this.props) { attrs[i] = this.props[i]; }
const attrs = Object.assign({}, this.props);
attrs.className = (attrs.className || '') + ' alert-'+attrs.type;
delete attrs.type;
const type = extract(attrs, 'type');
attrs.className = 'alert alert-' + type + ' ' + (attrs.className || '');
var message = attrs.message;
delete attrs.message;
const children = extract(attrs, 'children');
const controls = extract(attrs, 'controls') || [];
var controlItems = attrs.controls ? attrs.controls.slice() : [];
delete attrs.controls;
// If the alert is meant to be dismissible (which is the case by default),
// then we will create a dismiss button to append as the final control in
// the alert.
const dismissible = extract(attrs, 'dismissible');
const ondismiss = extract(attrs, 'ondismiss');
const dismissControl = [];
if (attrs.dismissible || attrs.dismissible === undefined) {
controlItems.push(ActionButton.component({
if (dismissible || dismissible === undefined) {
dismissControl.push(Button.component({
icon: 'times',
className: 'btn btn-icon btn-link',
onclick: attrs.ondismiss.bind(this)
className: 'btn btn-link btn-icon dismiss',
onclick: ondismiss
}));
}
delete attrs.dismissible;
return m('div.alert', attrs, [
m('span.alert-text', message),
m('ul.alert-controls', listItems(controlItems))
]);
return (
<div {...attrs}>
<span className="alert-body">
{children}
</span>
<ul className="alert-controls">
{listItems(controls.concat(dismissControl))}
</ul>
</div>
);
}
}

View File

@ -1,32 +1,68 @@
import Component from 'flarum/component';
import Component from 'flarum/Component';
import Alert from 'flarum/components/Alert';
/**
* The `Alerts` component provides an area in which `Alert` components can be
* shown and dismissed.
*/
export default class Alerts extends Component {
constructor(props) {
super(props);
constructor(...args) {
super(...args);
/**
* An array of Alert components which are currently showing.
*
* @type {Alert[]}
* @protected
*/
this.components = [];
}
view() {
return m('div.alerts', this.components.map((component) => {
component.props.ondismiss = this.dismiss.bind(this, component);
return m('div.alert-wrapper', component);
}));
return (
<div className="alerts">
{this.components.map(component => <div className="alerts-item">{component}</div>)}
</div>
);
}
/**
* Show an Alert in the alerts area.
*
* @param {Alert} component
* @public
*/
show(component) {
if (!(component instanceof Alert)) {
throw new Error('The Alerts 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) {
var index = this.components.indexOf(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();

View File

@ -1,31 +0,0 @@
import Component from 'flarum/component';
import icon from 'flarum/helpers/icon';
/**
The back/pin button group in the top-left corner of Flarum's interface.
*/
export default class BackButton extends Component {
view() {
var history = app.history;
var pane = app.pane;
return m('div.back-button', {
className: this.props.className || '',
onmouseenter: pane && pane.show.bind(pane),
onmouseleave: pane && pane.onmouseleave.bind(pane),
config: this.onload.bind(this)
}, history.canGoBack() ? m('div.btn-group', [
m('button.btn.btn-default.btn-icon.back', {onclick: history.back.bind(history)}, icon('chevron-left icon')),
pane && pane.active ? m('button.btn.btn-default.btn-icon.pin'+(pane.pinned ? '.active' : ''), {onclick: pane.togglePinned.bind(pane)}, icon('thumb-tack icon')) : '',
]) : (this.props.drawer ? [
m('button.btn.btn-default.btn-icon.drawer-toggle', {
onclick: app.drawer.toggle.bind(app.drawer),
className: app.session.user() && app.session.user().unreadNotificationsCount() ? 'unread-notifications' : ''
}, icon('reorder icon'))
] : ''));
}
onload(element, isInitialized, context) {
context.retain = true;
}
}

View File

@ -1,21 +1,42 @@
import Component from 'flarum/component';
import Component from 'flarum/Component';
import icon from 'flarum/helpers/icon';
import extract from 'flarum/utils/extract';
/**
* The `Badge` component represents a user/discussion badge, indicating some
* status (e.g. a discussion is stickied, a user is an admin).
*
* A badge may have the following special props:
*
* - `type` The type of badge this is. This will be used to give the badge a
* class name of `badge-{type}`.
* - `icon` The name of an icon to show inside the badge.
*
* All other props will be assigned as attributes on the badge element.
*/
export default class Badge extends Component {
view(ctrl) {
var iconName = this.props.icon;
var label = this.props.title = this.props.label;
delete this.props.icon, this.props.label;
this.props.config = function(element, isInitialized) {
if (isInitialized) return;
$(element).tooltip();
};
this.props.className = 'badge '+(this.props.className || '');
this.props.key = this.props.className;
view() {
const attrs = Object.assign({}, this.props);
const type = extract(attrs, 'type');
const iconName = extract(attrs, 'icon');
return m('span', this.props, [
icon(iconName+' icon-glyph'),
m('span.label', label)
]);
attrs.className = 'badge badge-' + type + ' ' + (attrs.className || '');
// Give the badge a unique key so that when badges are displayed together,
// and then one is added/removed, Mithril will correctly redraw the series
// of badges.
attrs.key = attrs.className;
return (
<span {...attrs}>
{iconName ? icon(iconName, {className: 'icon'}) : ''}
</span>
);
}
config(isInitialized) {
if (isInitialized) return;
this.$().tooltip();
}
}

View File

@ -1,20 +0,0 @@
import Component from 'flarum/component';
import icon from 'flarum/helpers/icon';
import listItems from 'flarum/helpers/list-items';
export default class DropdownButton extends Component {
view() {
return m('div', {className: 'dropdown btn-group '+(this.props.items ? 'item-count-'+this.props.items.length : '')+' '+(this.props.className || '')}, [
m('a[href=javascript:;]', {
className: 'dropdown-toggle '+(this.props.buttonClass || 'btn btn-default'),
'data-toggle': 'dropdown',
onclick: this.props.buttonClick
}, this.props.buttonContent || [
icon((this.props.icon || 'ellipsis-v')+' icon-glyph icon'),
m('span.label', this.props.label || 'Controls'),
icon('caret-down icon-caret')
]),
m(this.props.menuContent ? 'div' : 'ul', {className: 'dropdown-menu '+(this.props.menuClass || '')}, this.props.menuContent || listItems(this.props.items))
]);
}
}

View File

@ -1,18 +0,0 @@
import Component from 'flarum/component'
import icon from 'flarum/helpers/icon'
import listItems from 'flarum/helpers/list-items';
export default class DropdownSelect extends Component {
view() {
var activeItem = this.props.items.filter((item) => item.component.active && item.component.active(item.props))[0];
var label = activeItem && activeItem.props.label;
return m('div', {className: 'dropdown dropdown-select btn-group item-count-'+this.props.items.length+' '+this.props.className}, [
m('a[href=javascript:;]', {className: 'dropdown-toggle '+(this.props.buttonClass || 'btn btn-default'), 'data-toggle': 'dropdown'}, [
m('span.label', label), ' ',
icon('sort icon-caret')
]),
m('ul', {className: 'dropdown-menu '+this.props.menuClass}, listItems(this.props.items, true))
])
}
}

View File

@ -1,35 +0,0 @@
import Component from 'flarum/component';
import icon from 'flarum/helpers/icon';
import listItems from 'flarum/helpers/list-items';
import ActionButton from 'flarum/components/action-button';
/**
Given a list of items, this component displays a split button: the left side
is the first item in the list, while the right side is a dropdown-toggle
which shows a dropdown menu containing all of the items.
*/
export default class DropdownSplit extends Component {
view() {
var firstItem = this.props.items;
while (firstItem instanceof Array) {
firstItem = firstItem[0];
}
var items = listItems(this.props.items);
var buttonProps = {};
for (var i in firstItem.props) {
buttonProps[i] = firstItem.props[i];
}
buttonProps.className = (buttonProps.className || '')+' '+(this.props.buttonClass || 'btn btn-default');
return m('div', {className: 'dropdown dropdown-split btn-group item-count-'+(items.length)+' '+this.props.className}, [
ActionButton.component(buttonProps),
m('a[href=javascript:;]', {className: 'dropdown-toggle btn-icon '+this.props.buttonClass, 'data-toggle': 'dropdown'}, [
icon('caret-down icon-caret'),
icon((this.props.icon || 'ellipsis-v')+' icon'),
]),
m('ul', {className: 'dropdown-menu '+(this.props.menuClass || 'pull-right')}, items)
])
}
}

View File

@ -1,11 +0,0 @@
import Component from 'flarum/component';
import listItems from 'flarum/helpers/list-items';
export default class FieldSet extends Component {
view() {
return m('fieldset', {className: this.props.className}, [
m('legend', this.props.label),
m('ul', listItems(this.props.fields))
]);
}
}

View File

@ -1,15 +0,0 @@
import Component from 'flarum/component';
export default class LoadingIndicator extends Component {
view() {
var size = this.props.size || 'small';
delete this.props.size;
this.props.config = function(element) {
$.fn.spin.presets[size].zIndex = 'auto';
$(element).spin(size);
};
return m('div.loading-indicator', this.props, m.trust('&nbsp;'));
}
}

View File

@ -1,38 +0,0 @@
import Component from 'flarum/component';
export default class Modal extends Component {
view() {
return m('div.modal.fade', {config: this.onload.bind(this)}, this.component && this.component.render())
}
onload(element, isInitialized) {
if (isInitialized) { return; }
this.element(element);
this.$()
.on('hidden.bs.modal', this.destroy.bind(this))
.on('shown.bs.modal', this.ready.bind(this));
}
show(component) {
clearTimeout(this.hideTimeout);
this.component = component;
m.redraw(true);
this.$().modal('show');
this.ready();
}
close() {
this.hideTimeout = setTimeout(() => this.$().modal('hide'));
}
destroy() {
this.component = null;
m.redraw();
}
ready() {
this.component && this.component.ready && this.component.ready(this.$());
}
}

View File

@ -1,21 +0,0 @@
import Component from 'flarum/component'
import icon from 'flarum/helpers/icon'
export default class NavItem extends Component {
view() {
var active = this.constructor.active(this.props);
return m('li'+(active ? '.active' : ''), m('a.has-icon', {
href: this.props.href,
onclick: this.props.onclick,
config: m.route
}, [
icon(this.props.icon+' icon'),
this.props.label, ' ',
this.props.badge ? m('span.count', this.props.badge) : ''
]))
}
static active(props) {
return typeof props.active !== 'undefined' ? props.active : m.route() === props.href;
}
}

View File

@ -1,13 +0,0 @@
import Component from 'flarum/component'
import icon from 'flarum/helpers/icon';
export default class SelectInput extends Component {
view(ctrl) {
return m('span.select-input', [
m('select.form-control', {onchange: m.withAttr('value', this.props.onchange.bind(ctrl)), value: this.props.value}, [
Object.keys(this.props.options).map(key => m('option', {value: key}, this.props.options[key]))
]),
icon('sort')
])
}
}

View File

@ -1,14 +1,14 @@
import Component from 'flarum/component';
import Component from 'flarum/Component';
/**
* The `Separator` component defines a menu separator item.
*/
class Separator extends Component {
view() {
return m('span');
return <li className="divider"/>;
}
}
Separator.wrapperClass = 'divider';
Separator.isListItem = true;
export default Separator;

View File

@ -1,30 +0,0 @@
import Component from 'flarum/component';
import LoadingIndicator from 'flarum/components/loading-indicator';
export default class SwitchInput extends Component {
constructor(props) {
super(props);
this.loading = m.prop(false);
}
view() {
return m('div.checkbox.checkbox-switch', [
m('label', [
m('div.switch-control', [
m('input[type=checkbox]', {
checked: this.props.state,
onchange: m.withAttr('checked', this.onchange.bind(this))
}),
m('div.switch', {className: this.loading() && 'loading'})
]),
this.props.label, ' ',
this.loading() ? LoadingIndicator.component({size: 'tiny'}) : ''
])
])
}
onchange(checked) {
this.props.onchange && this.props.onchange(checked, this);
}
}

View File

@ -1,90 +0,0 @@
import Component from 'flarum/component';
import ItemList from 'flarum/utils/item-list';
import listItems from 'flarum/helpers/list-items';
import ActionButton from 'flarum/components/action-button';
/**
A text editor. Contains a textarea and an item list of `controls`, including
a submit button.
*/
export default class TextEditor extends Component {
constructor(props) {
props.submitLabel = props.submitLabel || 'Submit';
super(props);
this.value = m.prop(this.props.value || '');
}
view() {
return m('div.text-editor', [
m('textarea.form-control.flexible-height', {
config: this.configTextarea.bind(this),
oninput: m.withAttr('value', this.oninput.bind(this)),
placeholder: this.props.placeholder || '',
disabled: !!this.props.disabled,
value: this.value()
}),
m('ul.text-editor-controls', listItems(this.controlItems().toArray()))
]);
}
configTextarea(element, isInitialized) {
if (isInitialized) { return; }
$(element).bind('keydown', 'meta+return', this.onsubmit.bind(this));
}
controlItems() {
var items = new ItemList();
items.add('submit',
ActionButton.component({
label: this.props.submitLabel,
icon: 'check',
className: 'btn btn-primary',
onclick: this.onsubmit.bind(this)
})
);
return items;
}
setContent(content) {
this.value(content);
this.$('textarea').val(content).trigger('input');
}
setSelectionRange(start, end) {
var $textarea = this.$('textarea');
$textarea[0].setSelectionRange(start, end);
$textarea.focus();
}
getSelectionRange() {
var $textarea = this.$('textarea');
return [$textarea[0].selectionStart, $textarea[0].selectionEnd];
}
insertAtCursor(insert) {
var textarea = this.$('textarea')[0];
var content = this.value();
var index = textarea ? textarea.selectionStart : content.length;
this.setContent(content.slice(0, index)+insert+content.slice(index));
if (textarea) {
var pos = index + insert.length;
this.setSelectionRange(pos, pos);
}
}
oninput(value) {
this.value(value);
this.props.onchange(this.value());
m.redraw.strategy('none');
}
onsubmit() {
this.props.onsubmit(this.value());
}
}

View File

@ -1,35 +0,0 @@
import Component from 'flarum/component';
import LoadingIndicator from 'flarum/components/loading-indicator';
import classList from 'flarum/utils/class-list';
import icon from 'flarum/helpers/icon';
export default class YesNoInput extends Component {
constructor(props) {
super(props);
this.loading = m.prop(false);
}
view() {
return m('label.yesno-control', [
m('input[type=checkbox]', {
checked: this.props.state,
disabled: this.props.disabled,
onchange: m.withAttr('checked', this.onchange.bind(this))
}),
m('div.yesno', {className: classList({
loading: this.loading(),
disabled: this.props.disabled,
state: this.props.state ? 'yes' : 'no'
})}, [
this.loading()
? LoadingIndicator.component({size: 'tiny'})
: icon(this.props.state ? 'check' : 'times')
])
]);
}
onchange(checked) {
this.props.onchange && this.props.onchange(checked, this);
}
}