1
0
mirror of https://github.com/flarum/core.git synced 2025-07-12 12:26:23 +02:00

Run prettier for all JS files

This commit is contained in:
Franz Liedke
2020-04-17 11:57:55 +02:00
parent 84cf938379
commit 89ef14faf1
145 changed files with 2374 additions and 2050 deletions

View File

@ -12,9 +12,9 @@ export default class AdminApplication extends Application {
canGoBack: () => true,
getPrevious: () => {},
backUrl: () => this.forum.attribute('baseUrl'),
back: function() {
back: function () {
window.location = this.backUrl();
}
},
};
constructor() {
@ -27,7 +27,7 @@ export default class AdminApplication extends Application {
* @inheritdoc
*/
mount() {
m.mount(document.getElementById('app-navigation'), Navigation.component({className: 'App-backControl', drawer: true}));
m.mount(document.getElementById('app-navigation'), Navigation.component({ className: 'App-backControl', drawer: true }));
m.mount(document.getElementById('header-navigation'), Navigation.component());
m.mount(document.getElementById('header-primary'), HeaderPrimary.component());
m.mount(document.getElementById('header-secondary'), HeaderSecondary.component());
@ -59,5 +59,5 @@ export default class AdminApplication extends Application {
}
return required;
};
}
}

View File

@ -58,6 +58,6 @@ export default Object.assign(compat, {
'components/AdminNav': AdminNav,
'components/EditCustomCssModal': EditCustomCssModal,
'components/EditGroupModal': EditGroupModal,
'routes': routes,
'AdminApplication': AdminApplication
routes: routes,
AdminApplication: AdminApplication,
});

View File

@ -22,8 +22,10 @@ export default class AddExtensionModal extends Modal {
return (
<div className="Modal-body">
<p>{app.translator.trans('core.admin.add_extension.temporary_text')}</p>
<p>{app.translator.trans('core.admin.add_extension.install_text', {a: <a href="https://discuss.flarum.org/t/extensions" target="_blank"/>})}</p>
<p>{app.translator.trans('core.admin.add_extension.developer_text', {a: <a href="http://flarum.org/docs/extend" target="_blank"/>})}</p>
<p>
{app.translator.trans('core.admin.add_extension.install_text', { a: <a href="https://discuss.flarum.org/t/extensions" target="_blank" /> })}
</p>
<p>{app.translator.trans('core.admin.add_extension.developer_text', { a: <a href="http://flarum.org/docs/extend" target="_blank" /> })}</p>
</div>
);
}

View File

@ -13,11 +13,7 @@ export default class AdminLinkButton extends LinkButton {
getButtonContent() {
const content = super.getButtonContent();
content.push(
<div className="AdminLinkButton-description">
{this.props.description}
</div>
);
content.push(<div className="AdminLinkButton-description">{this.props.description}</div>);
return content;
}

View File

@ -15,9 +15,7 @@ import ItemList from '../../common/utils/ItemList';
export default class AdminNav extends Component {
view() {
return (
<SelectDropdown
className="AdminNav App-titleControl"
buttonClassName="Button">
<SelectDropdown className="AdminNav App-titleControl" buttonClassName="Button">
{this.items().toArray()}
</SelectDropdown>
);
@ -31,47 +29,65 @@ export default class AdminNav extends Component {
items() {
const items = new ItemList();
items.add('dashboard', AdminLinkButton.component({
items.add(
'dashboard',
AdminLinkButton.component({
href: app.route('dashboard'),
icon: 'far fa-chart-bar',
children: app.translator.trans('core.admin.nav.dashboard_button'),
description: app.translator.trans('core.admin.nav.dashboard_text')
}));
description: app.translator.trans('core.admin.nav.dashboard_text'),
})
);
items.add('basics', AdminLinkButton.component({
items.add(
'basics',
AdminLinkButton.component({
href: app.route('basics'),
icon: 'fas fa-pencil-alt',
children: app.translator.trans('core.admin.nav.basics_button'),
description: app.translator.trans('core.admin.nav.basics_text')
}));
description: app.translator.trans('core.admin.nav.basics_text'),
})
);
items.add('mail', AdminLinkButton.component({
items.add(
'mail',
AdminLinkButton.component({
href: app.route('mail'),
icon: 'fas fa-envelope',
children: app.translator.trans('core.admin.nav.email_button'),
description: app.translator.trans('core.admin.nav.email_text')
}));
description: app.translator.trans('core.admin.nav.email_text'),
})
);
items.add('permissions', AdminLinkButton.component({
items.add(
'permissions',
AdminLinkButton.component({
href: app.route('permissions'),
icon: 'fas fa-key',
children: app.translator.trans('core.admin.nav.permissions_button'),
description: app.translator.trans('core.admin.nav.permissions_text')
}));
description: app.translator.trans('core.admin.nav.permissions_text'),
})
);
items.add('appearance', AdminLinkButton.component({
items.add(
'appearance',
AdminLinkButton.component({
href: app.route('appearance'),
icon: 'fas fa-paint-brush',
children: app.translator.trans('core.admin.nav.appearance_button'),
description: app.translator.trans('core.admin.nav.appearance_text')
}));
description: app.translator.trans('core.admin.nav.appearance_text'),
})
);
items.add('extensions', AdminLinkButton.component({
items.add(
'extensions',
AdminLinkButton.component({
href: app.route('extensions'),
icon: 'fas fa-puzzle-piece',
children: app.translator.trans('core.admin.nav.extensions_button'),
description: app.translator.trans('core.admin.nav.extensions_text')
}));
description: app.translator.trans('core.admin.nav.extensions_text'),
})
);
return items;
}

View File

@ -24,85 +24,85 @@ export default class AppearancePage extends Page {
<form onsubmit={this.onsubmit.bind(this)}>
<fieldset className="AppearancePage-colors">
<legend>{app.translator.trans('core.admin.appearance.colors_heading')}</legend>
<div className="helpText">
{app.translator.trans('core.admin.appearance.colors_text')}
</div>
<div className="helpText">{app.translator.trans('core.admin.appearance.colors_text')}</div>
<div className="AppearancePage-colors-input">
<input className="FormControl" type="text" placeholder="#aaaaaa" value={this.primaryColor()} onchange={m.withAttr('value', this.primaryColor)}/>
<input className="FormControl" type="text" placeholder="#aaaaaa" value={this.secondaryColor()} onchange={m.withAttr('value', this.secondaryColor)}/>
<input
className="FormControl"
type="text"
placeholder="#aaaaaa"
value={this.primaryColor()}
onchange={m.withAttr('value', this.primaryColor)}
/>
<input
className="FormControl"
type="text"
placeholder="#aaaaaa"
value={this.secondaryColor()}
onchange={m.withAttr('value', this.secondaryColor)}
/>
</div>
{Switch.component({
state: this.darkMode(),
children: app.translator.trans('core.admin.appearance.dark_mode_label'),
onchange: this.darkMode
onchange: this.darkMode,
})}
{Switch.component({
state: this.coloredHeader(),
children: app.translator.trans('core.admin.appearance.colored_header_label'),
onchange: this.coloredHeader
onchange: this.coloredHeader,
})}
{Button.component({
className: 'Button Button--primary',
type: 'submit',
children: app.translator.trans('core.admin.appearance.submit_button'),
loading: this.loading
loading: this.loading,
})}
</fieldset>
</form>
<fieldset>
<legend>{app.translator.trans('core.admin.appearance.logo_heading')}</legend>
<div className="helpText">
{app.translator.trans('core.admin.appearance.logo_text')}
</div>
<UploadImageButton name="logo"/>
<div className="helpText">{app.translator.trans('core.admin.appearance.logo_text')}</div>
<UploadImageButton name="logo" />
</fieldset>
<fieldset>
<legend>{app.translator.trans('core.admin.appearance.favicon_heading')}</legend>
<div className="helpText">
{app.translator.trans('core.admin.appearance.favicon_text')}
</div>
<UploadImageButton name="favicon"/>
<div className="helpText">{app.translator.trans('core.admin.appearance.favicon_text')}</div>
<UploadImageButton name="favicon" />
</fieldset>
<fieldset>
<legend>{app.translator.trans('core.admin.appearance.custom_header_heading')}</legend>
<div className="helpText">
{app.translator.trans('core.admin.appearance.custom_header_text')}
</div>
<div className="helpText">{app.translator.trans('core.admin.appearance.custom_header_text')}</div>
{Button.component({
className: 'Button',
children: app.translator.trans('core.admin.appearance.edit_header_button'),
onclick: () => app.modal.show(new EditCustomHeaderModal())
onclick: () => app.modal.show(new EditCustomHeaderModal()),
})}
</fieldset>
<fieldset>
<legend>{app.translator.trans('core.admin.appearance.custom_footer_heading')}</legend>
<div className="helpText">
{app.translator.trans('core.admin.appearance.custom_footer_text')}
</div>
<div className="helpText">{app.translator.trans('core.admin.appearance.custom_footer_text')}</div>
{Button.component({
className: 'Button',
children: app.translator.trans('core.admin.appearance.edit_footer_button'),
onclick: () => app.modal.show(new EditCustomFooterModal())
onclick: () => app.modal.show(new EditCustomFooterModal()),
})}
</fieldset>
<fieldset>
<legend>{app.translator.trans('core.admin.appearance.custom_styles_heading')}</legend>
<div className="helpText">
{app.translator.trans('core.admin.appearance.custom_styles_text')}
</div>
<div className="helpText">{app.translator.trans('core.admin.appearance.custom_styles_text')}</div>
{Button.component({
className: 'Button',
children: app.translator.trans('core.admin.appearance.edit_css_button'),
onclick: () => app.modal.show(new EditCustomCssModal())
onclick: () => app.modal.show(new EditCustomCssModal()),
})}
</fieldset>
</div>
@ -126,7 +126,7 @@ export default class AppearancePage extends Page {
theme_primary_color: this.primaryColor(),
theme_secondary_color: this.secondaryColor(),
theme_dark_mode: this.darkMode(),
theme_colored_header: this.coloredHeader()
theme_colored_header: this.coloredHeader(),
}).then(() => window.location.reload());
}
}

View File

@ -20,12 +20,12 @@ export default class BasicsPage extends Page {
'show_language_selector',
'default_route',
'welcome_title',
'welcome_message'
'welcome_message',
];
this.values = {};
const settings = app.data.settings;
this.fields.forEach(key => this.values[key] = m.prop(settings[key]));
this.fields.forEach((key) => (this.values[key] = m.prop(settings[key])));
this.localeOptions = {};
const locales = app.data.locales;
@ -33,7 +33,7 @@ export default class BasicsPage extends Page {
this.localeOptions[i] = `${locales[i]} (${i})`;
}
if (typeof this.values.show_language_selector() !== "number") this.values.show_language_selector(1);
if (typeof this.values.show_language_selector() !== 'number') this.values.show_language_selector(1);
}
view() {
@ -43,19 +43,19 @@ export default class BasicsPage extends Page {
<form onsubmit={this.onsubmit.bind(this)}>
{FieldSet.component({
label: app.translator.trans('core.admin.basics.forum_title_heading'),
children: [
<input className="FormControl" value={this.values.forum_title()} oninput={m.withAttr('value', this.values.forum_title)}/>
]
children: [<input className="FormControl" value={this.values.forum_title()} oninput={m.withAttr('value', this.values.forum_title)} />],
})}
{FieldSet.component({
label: app.translator.trans('core.admin.basics.forum_description_heading'),
children: [
<div className="helpText">
{app.translator.trans('core.admin.basics.forum_description_text')}
</div>,
<textarea className="FormControl" value={this.values.forum_description()} oninput={m.withAttr('value', this.values.forum_description)}/>
]
<div className="helpText">{app.translator.trans('core.admin.basics.forum_description_text')}</div>,
<textarea
className="FormControl"
value={this.values.forum_description()}
oninput={m.withAttr('value', this.values.forum_description)}
/>,
],
})}
{Object.keys(this.localeOptions).length > 1
@ -65,14 +65,14 @@ export default class BasicsPage extends Page {
Select.component({
options: this.localeOptions,
value: this.values.default_locale(),
onchange: this.values.default_locale
onchange: this.values.default_locale,
}),
Switch.component({
state: this.values.show_language_selector(),
onchange: this.values.show_language_selector,
children: app.translator.trans('core.admin.basics.show_language_selector_label'),
})
]
}),
],
})
: ''}
@ -80,30 +80,38 @@ export default class BasicsPage extends Page {
label: app.translator.trans('core.admin.basics.home_page_heading'),
className: 'BasicsPage-homePage',
children: [
<div className="helpText">
{app.translator.trans('core.admin.basics.home_page_text')}
</div>,
this.homePageItems().toArray().map(({path, label}) =>
<div className="helpText">{app.translator.trans('core.admin.basics.home_page_text')}</div>,
this.homePageItems()
.toArray()
.map(({ path, label }) => (
<label className="checkbox">
<input type="radio" name="homePage" value={path} checked={this.values.default_route() === path} onclick={m.withAttr('value', this.values.default_route)}/>
<input
type="radio"
name="homePage"
value={path}
checked={this.values.default_route() === path}
onclick={m.withAttr('value', this.values.default_route)}
/>
{label}
</label>
)
]
)),
],
})}
{FieldSet.component({
label: app.translator.trans('core.admin.basics.welcome_banner_heading'),
className: 'BasicsPage-welcomeBanner',
children: [
<div className="helpText">
{app.translator.trans('core.admin.basics.welcome_banner_text')}
</div>,
<div className="helpText">{app.translator.trans('core.admin.basics.welcome_banner_text')}</div>,
<div className="BasicsPage-welcomeBanner-input">
<input className="FormControl" value={this.values.welcome_title()} oninput={m.withAttr('value', this.values.welcome_title)}/>
<textarea className="FormControl" value={this.values.welcome_message()} oninput={m.withAttr('value', this.values.welcome_message)}/>
</div>
]
<input className="FormControl" value={this.values.welcome_title()} oninput={m.withAttr('value', this.values.welcome_title)} />
<textarea
className="FormControl"
value={this.values.welcome_message()}
oninput={m.withAttr('value', this.values.welcome_message)}
/>
</div>,
],
})}
{Button.component({
@ -111,7 +119,7 @@ export default class BasicsPage extends Page {
className: 'Button Button--primary',
children: app.translator.trans('core.admin.basics.submit_button'),
loading: this.loading,
disabled: !this.changed()
disabled: !this.changed(),
})}
</form>
</div>
@ -120,7 +128,7 @@ export default class BasicsPage extends Page {
}
changed() {
return this.fields.some(key => this.values[key]() !== app.data.settings[key]);
return this.fields.some((key) => this.values[key]() !== app.data.settings[key]);
}
/**
@ -135,7 +143,7 @@ export default class BasicsPage extends Page {
items.add('allDiscussions', {
path: '/all',
label: app.translator.trans('core.admin.basics.all_discussions_label')
label: app.translator.trans('core.admin.basics.all_discussions_label'),
});
return items;
@ -151,11 +159,11 @@ export default class BasicsPage extends Page {
const settings = {};
this.fields.forEach(key => settings[key] = this.values[key]());
this.fields.forEach((key) => (settings[key] = this.values[key]()));
saveSettings(settings)
.then(() => {
app.alerts.show(this.successAlert = new Alert({type: 'success', children: app.translator.trans('core.admin.basics.saved_message')}));
app.alerts.show((this.successAlert = new Alert({ type: 'success', children: app.translator.trans('core.admin.basics.saved_message') })));
})
.catch(() => {})
.then(() => {

View File

@ -5,14 +5,12 @@ export default class DashboardPage extends Page {
view() {
return (
<div className="DashboardPage">
<div className="container">
{this.availableWidgets()}
</div>
<div className="container">{this.availableWidgets()}</div>
</div>
);
}
availableWidgets() {
return [<StatusWidget/>];
return [<StatusWidget />];
}
}

View File

@ -11,11 +11,7 @@ import Component from '../../common/Component';
export default class Widget extends Component {
view() {
return (
<div className={"Widget "+this.className()}>
{this.content()}
</div>
);
return <div className={'Widget ' + this.className()}>{this.content()}</div>;
}
/**

View File

@ -11,10 +11,14 @@ export default class EditCustomCssModal extends SettingsModal {
form() {
return [
<p>{app.translator.trans('core.admin.edit_css.customize_text', {a: <a href="https://github.com/flarum/core/tree/master/less" target="_blank"/>})}</p>,
<p>
{app.translator.trans('core.admin.edit_css.customize_text', {
a: <a href="https://github.com/flarum/core/tree/master/less" target="_blank" />,
})}
</p>,
<div className="Form-group">
<textarea className="FormControl" rows="30" bidi={this.setting('custom_less')}/>
</div>
<textarea className="FormControl" rows="30" bidi={this.setting('custom_less')} />
</div>,
];
}

View File

@ -13,8 +13,8 @@ export default class EditCustomFooterModal extends SettingsModal {
return [
<p>{app.translator.trans('core.admin.edit_footer.customize_text')}</p>,
<div className="Form-group">
<textarea className="FormControl" rows="30" bidi={this.setting('custom_footer')}/>
</div>
<textarea className="FormControl" rows="30" bidi={this.setting('custom_footer')} />
</div>,
];
}

View File

@ -13,8 +13,8 @@ export default class EditCustomHeaderModal extends SettingsModal {
return [
<p>{app.translator.trans('core.admin.edit_header.customize_text')}</p>,
<div className="Form-group">
<textarea className="FormControl" rows="30" bidi={this.setting('custom_header')}/>
</div>
<textarea className="FormControl" rows="30" bidi={this.setting('custom_header')} />
</div>,
];
}

View File

@ -24,21 +24,21 @@ export default class EditGroupModal extends Modal {
title() {
return [
this.color() || this.icon() ? Badge.component({
this.color() || this.icon()
? Badge.component({
icon: this.icon(),
style: {backgroundColor: this.color()}
}) : '',
style: { backgroundColor: this.color() },
})
: '',
' ',
this.namePlural() || app.translator.trans('core.admin.edit_group.title')
this.namePlural() || app.translator.trans('core.admin.edit_group.title'),
];
}
content() {
return (
<div className="Modal-body">
<div className="Form">
{this.fields().toArray()}
</div>
<div className="Form">{this.fields().toArray()}</div>
</div>
);
}
@ -46,40 +46,68 @@ export default class EditGroupModal extends Modal {
fields() {
const items = new ItemList();
items.add('name', <div className="Form-group">
items.add(
'name',
<div className="Form-group">
<label>{app.translator.trans('core.admin.edit_group.name_label')}</label>
<div className="EditGroupModal-name-input">
<input className="FormControl" placeholder={app.translator.trans('core.admin.edit_group.singular_placeholder')} value={this.nameSingular()} oninput={m.withAttr('value', this.nameSingular)}/>
<input className="FormControl" placeholder={app.translator.trans('core.admin.edit_group.plural_placeholder')} value={this.namePlural()} oninput={m.withAttr('value', this.namePlural)}/>
<input
className="FormControl"
placeholder={app.translator.trans('core.admin.edit_group.singular_placeholder')}
value={this.nameSingular()}
oninput={m.withAttr('value', this.nameSingular)}
/>
<input
className="FormControl"
placeholder={app.translator.trans('core.admin.edit_group.plural_placeholder')}
value={this.namePlural()}
oninput={m.withAttr('value', this.namePlural)}
/>
</div>
</div>, 30);
</div>,
30
);
items.add('color', <div className="Form-group">
items.add(
'color',
<div className="Form-group">
<label>{app.translator.trans('core.admin.edit_group.color_label')}</label>
<input className="FormControl" placeholder="#aaaaaa" value={this.color()} oninput={m.withAttr('value', this.color)}/>
</div>, 20);
<input className="FormControl" placeholder="#aaaaaa" value={this.color()} oninput={m.withAttr('value', this.color)} />
</div>,
20
);
items.add('icon', <div className="Form-group">
items.add(
'icon',
<div className="Form-group">
<label>{app.translator.trans('core.admin.edit_group.icon_label')}</label>
<div className="helpText">
{app.translator.trans('core.admin.edit_group.icon_text', {a: <a href="https://fontawesome.com/icons?m=free" tabindex="-1"/>})}
{app.translator.trans('core.admin.edit_group.icon_text', { a: <a href="https://fontawesome.com/icons?m=free" tabindex="-1" /> })}
</div>
<input className="FormControl" placeholder="fas fa-bolt" value={this.icon()} oninput={m.withAttr('value', this.icon)}/>
</div>, 10);
<input className="FormControl" placeholder="fas fa-bolt" value={this.icon()} oninput={m.withAttr('value', this.icon)} />
</div>,
10
);
items.add('submit', <div className="Form-group">
items.add(
'submit',
<div className="Form-group">
{Button.component({
type: 'submit',
className: 'Button Button--primary EditGroupModal-save',
loading: this.loading,
children: app.translator.trans('core.admin.edit_group.submit_button')
children: app.translator.trans('core.admin.edit_group.submit_button'),
})}
{this.group.exists && this.group.id() !== Group.ADMINISTRATOR_ID ? (
<button type="button" className="Button EditGroupModal-delete" onclick={this.deleteGroup.bind(this)}>
{app.translator.trans('core.admin.edit_group.delete_button')}
</button>
) : ''}
</div>, -10);
) : (
''
)}
</div>,
-10
);
return items;
}
@ -89,7 +117,7 @@ export default class EditGroupModal extends Modal {
nameSingular: this.nameSingular(),
namePlural: this.namePlural(),
color: this.color(),
icon: this.icon()
icon: this.icon(),
};
}
@ -98,7 +126,8 @@ export default class EditGroupModal extends Modal {
this.loading = true;
this.group.save(this.submitData(), {errorHandler: this.onerror.bind(this)})
this.group
.save(this.submitData(), { errorHandler: this.onerror.bind(this) })
.then(this.hide.bind(this))
.catch(() => {
this.loading = false;

View File

@ -19,7 +19,7 @@ export default class ExtensionsPage extends Page {
children: app.translator.trans('core.admin.extensions.add_button'),
icon: 'fas fa-plus',
className: 'Button Button--primary',
onclick: () => app.modal.show(new AddExtensionModal())
onclick: () => app.modal.show(new AddExtensionModal()),
})}
</div>
</div>
@ -27,12 +27,12 @@ export default class ExtensionsPage extends Page {
<div className="ExtensionsPage-list">
<div className="container">
<ul className="ExtensionList">
{Object.keys(app.data.extensions)
.map(id => {
{Object.keys(app.data.extensions).map((id) => {
const extension = app.data.extensions[id];
const controls = this.controlItems(extension.id).toArray();
return <li className={'ExtensionListItem ' + (!this.isEnabled(extension.id) ? 'disabled' : '')}>
return (
<li className={'ExtensionListItem ' + (!this.isEnabled(extension.id) ? 'disabled' : '')}>
<div className="ExtensionListItem-content">
<span className="ExtensionListItem-icon ExtensionIcon" style={extension.icon}>
{extension.icon ? icon(extension.icon.name) : ''}
@ -42,20 +42,24 @@ export default class ExtensionsPage extends Page {
className="ExtensionListItem-controls"
buttonClassName="Button Button--icon Button--flat"
menuClassName="Dropdown-menu--right"
icon="fas fa-ellipsis-h">
icon="fas fa-ellipsis-h"
>
{controls}
</Dropdown>
) : ''}
) : (
''
)}
<div className="ExtensionListItem-main">
<label className="ExtensionListItem-title">
<input type="checkbox" checked={this.isEnabled(extension.id)} onclick={this.toggle.bind(this, extension.id)}/> {' '}
<input type="checkbox" checked={this.isEnabled(extension.id)} onclick={this.toggle.bind(this, extension.id)} />{' '}
{extension.extra['flarum-extension'].title}
</label>
<div className="ExtensionListItem-version">{extension.version}</div>
<div className="ExtensionListItem-description">{extension.description}</div>
</div>
</div>
</li>;
</li>
);
})}
</ul>
</div>
@ -69,26 +73,34 @@ export default class ExtensionsPage extends Page {
const enabled = this.isEnabled(name);
if (app.extensionSettings[name]) {
items.add('settings', Button.component({
items.add(
'settings',
Button.component({
icon: 'fas fa-cog',
children: app.translator.trans('core.admin.extensions.settings_button'),
onclick: app.extensionSettings[name]
}));
onclick: app.extensionSettings[name],
})
);
}
if (!enabled) {
items.add('uninstall', Button.component({
items.add(
'uninstall',
Button.component({
icon: 'far fa-trash-alt',
children: app.translator.trans('core.admin.extensions.uninstall_button'),
onclick: () => {
app.request({
app
.request({
url: app.forum.attribute('apiUrl') + '/extensions/' + name,
method: 'DELETE'
}).then(() => window.location.reload());
method: 'DELETE',
})
.then(() => window.location.reload());
app.modal.show(new LoadingModal());
}
}));
},
})
);
}
return items;
@ -103,11 +115,13 @@ export default class ExtensionsPage extends Page {
toggle(id) {
const enabled = this.isEnabled(id);
app.request({
app
.request({
url: app.forum.attribute('apiUrl') + '/extensions/' + id,
method: 'PATCH',
data: {enabled: !enabled}
}).then(() => {
data: { enabled: !enabled },
})
.then(() => {
if (!enabled) localStorage.setItem('enabledExtension', id);
window.location.reload();
});

View File

@ -8,11 +8,7 @@ import listItems from '../../common/helpers/listItems';
*/
export default class HeaderPrimary extends Component {
view() {
return (
<ul className="Header-controls">
{listItems(this.items().toArray())}
</ul>
);
return <ul className="Header-controls">{listItems(this.items().toArray())}</ul>;
}
config(isInitialized, context) {

View File

@ -8,11 +8,7 @@ import listItems from '../../common/helpers/listItems';
*/
export default class HeaderSecondary extends Component {
view() {
return (
<ul className="Header-controls">
{listItems(this.items().toArray())}
</ul>
);
return <ul className="Header-controls">{listItems(this.items().toArray())}</ul>;
}
config(isInitialized, context) {

View File

@ -20,15 +20,17 @@ export default class MailPage extends Page {
this.driverFields = {};
this.fields = ['mail_driver', 'mail_from'];
this.values = {};
this.status = {sending: false, errors: {}};
this.status = { sending: false, errors: {} };
const settings = app.data.settings;
this.fields.forEach(key => this.values[key] = m.prop(settings[key]));
this.fields.forEach((key) => (this.values[key] = m.prop(settings[key])));
app.request({
app
.request({
method: 'GET',
url: app.forum.attribute('apiUrl') + '/mail-settings'
}).then(response => {
url: app.forum.attribute('apiUrl') + '/mail-settings',
})
.then((response) => {
this.driverFields = response['data']['attributes']['fields'];
this.status.sending = response['data']['attributes']['sending'];
this.status.errors = response['data']['attributes']['errors'];
@ -64,9 +66,7 @@ export default class MailPage extends Page {
<div className="container">
<form onsubmit={this.onsubmit.bind(this)}>
<h2>{app.translator.trans('core.admin.email.heading')}</h2>
<div className="helpText">
{app.translator.trans('core.admin.email.text')}
</div>
<div className="helpText">{app.translator.trans('core.admin.email.text')}</div>
{FieldSet.component({
label: app.translator.trans('core.admin.email.addresses_heading'),
@ -77,8 +77,8 @@ export default class MailPage extends Page {
{app.translator.trans('core.admin.email.from_label')}
<input className="FormControl" value={this.values.mail_from() || ''} oninput={m.withAttr('value', this.values.mail_from)} />
</label>
</div>
]
</div>,
],
})}
{FieldSet.component({
@ -88,38 +88,44 @@ export default class MailPage extends Page {
<div className="MailPage-MailSettings-input">
<label>
{app.translator.trans('core.admin.email.driver_label')}
<Select value={this.values.mail_driver()} options={Object.keys(this.driverFields).reduce((memo, val) => ({...memo, [val]: val}), {})} onchange={this.values.mail_driver} />
<Select
value={this.values.mail_driver()}
options={Object.keys(this.driverFields).reduce((memo, val) => ({ ...memo, [val]: val }), {})}
onchange={this.values.mail_driver}
/>
</label>
</div>
]
</div>,
],
})}
{this.status.sending || Alert.component({
{this.status.sending ||
Alert.component({
children: app.translator.trans('core.admin.email.not_sending_message'),
dismissible: false,
})}
{fieldKeys.length > 0 && FieldSet.component({
{fieldKeys.length > 0 &&
FieldSet.component({
label: app.translator.trans(`core.admin.email.${this.values.mail_driver()}_heading`),
className: 'MailPage-MailSettings',
children: [
<div className="MailPage-MailSettings-input">
{fieldKeys.map(field => [
{fieldKeys.map((field) => [
<label>
{app.translator.trans(`core.admin.email.${field}_label`)}
{this.renderField(field)}
</label>,
this.status.errors[field] && <p className='ValidationError'>{this.status.errors[field]}</p>,
this.status.errors[field] && <p className="ValidationError">{this.status.errors[field]}</p>,
])}
</div>
]
</div>,
],
})}
{Button.component({
type: 'submit',
className: 'Button Button--primary',
children: app.translator.trans('core.admin.email.submit_button'),
disabled: !this.changed()
disabled: !this.changed(),
})}
</form>
</div>
@ -140,7 +146,7 @@ export default class MailPage extends Page {
}
changed() {
return this.fields.some(key => this.values[key]() !== app.data.settings[key]);
return this.fields.some((key) => this.values[key]() !== app.data.settings[key]);
}
onsubmit(e) {
@ -153,11 +159,11 @@ export default class MailPage extends Page {
const settings = {};
this.fields.forEach(key => settings[key] = this.values[key]());
this.fields.forEach((key) => (settings[key] = this.values[key]()));
saveSettings(settings)
.then(() => {
app.alerts.show(this.successAlert = new Alert({type: 'success', children: app.translator.trans('core.admin.basics.saved_message')}));
app.alerts.show((this.successAlert = new Alert({ type: 'success', children: app.translator.trans('core.admin.basics.saved_message') })));
})
.catch(() => {})
.then(() => {

View File

@ -8,22 +8,21 @@ import GroupBadge from '../../common/components/GroupBadge';
function badgeForId(id) {
const group = app.store.getById('groups', id);
return group ? GroupBadge.component({group, label: null}) : '';
return group ? GroupBadge.component({ group, label: null }) : '';
}
function filterByRequiredPermissions(groupIds, permission) {
app.getRequiredPermissions(permission)
.forEach(required => {
app.getRequiredPermissions(permission).forEach((required) => {
const restrictToGroupIds = app.data.permissions[required] || [];
if (restrictToGroupIds.indexOf(Group.GUEST_ID) !== -1) {
// do nothing
} else if (restrictToGroupIds.indexOf(Group.MEMBER_ID) !== -1) {
groupIds = groupIds.filter(id => id !== Group.GUEST_ID);
groupIds = groupIds.filter((id) => id !== Group.GUEST_ID);
} else if (groupIds.indexOf(Group.MEMBER_ID) !== -1) {
groupIds = restrictToGroupIds;
} else {
groupIds = restrictToGroupIds.filter(id => groupIds.indexOf(id) !== -1);
groupIds = restrictToGroupIds.filter((id) => groupIds.indexOf(id) !== -1);
}
groupIds = filterByRequiredPermissions(groupIds, required);
@ -52,34 +51,31 @@ export default class PermissionDropdown extends Dropdown {
const adminGroup = app.store.getById('groups', Group.ADMINISTRATOR_ID);
if (everyone) {
this.props.label = Badge.component({icon: 'fas fa-globe'});
this.props.label = Badge.component({ icon: 'fas fa-globe' });
} else if (members) {
this.props.label = Badge.component({icon: 'fas fa-user'});
this.props.label = Badge.component({ icon: 'fas fa-user' });
} else {
this.props.label = [
badgeForId(Group.ADMINISTRATOR_ID),
groupIds.map(badgeForId)
];
this.props.label = [badgeForId(Group.ADMINISTRATOR_ID), groupIds.map(badgeForId)];
}
if (this.showing) {
if (this.props.allowGuest) {
this.props.children.push(
Button.component({
children: [Badge.component({icon: 'fas fa-globe'}), ' ', app.translator.trans('core.admin.permissions_controls.everyone_button')],
children: [Badge.component({ icon: 'fas fa-globe' }), ' ', app.translator.trans('core.admin.permissions_controls.everyone_button')],
icon: everyone ? 'fas fa-check' : true,
onclick: () => this.save([Group.GUEST_ID]),
disabled: this.isGroupDisabled(Group.GUEST_ID)
disabled: this.isGroupDisabled(Group.GUEST_ID),
})
);
}
this.props.children.push(
Button.component({
children: [Badge.component({icon: 'fas fa-user'}), ' ', app.translator.trans('core.admin.permissions_controls.members_button')],
children: [Badge.component({ icon: 'fas fa-user' }), ' ', app.translator.trans('core.admin.permissions_controls.members_button')],
icon: members ? 'fas fa-check' : true,
onclick: () => this.save([Group.MEMBER_ID]),
disabled: this.isGroupDisabled(Group.MEMBER_ID)
disabled: this.isGroupDisabled(Group.MEMBER_ID),
}),
Separator.component(),
@ -88,26 +84,29 @@ export default class PermissionDropdown extends Dropdown {
children: [badgeForId(adminGroup.id()), ' ', adminGroup.namePlural()],
icon: !everyone && !members ? 'fas fa-check' : true,
disabled: !everyone && !members,
onclick: e => {
onclick: (e) => {
if (e.shiftKey) e.stopPropagation();
this.save([]);
}
},
})
);
[].push.apply(
this.props.children,
app.store.all('groups')
.filter(group => [Group.ADMINISTRATOR_ID, Group.GUEST_ID, Group.MEMBER_ID].indexOf(group.id()) === -1)
.map(group => Button.component({
app.store
.all('groups')
.filter((group) => [Group.ADMINISTRATOR_ID, Group.GUEST_ID, Group.MEMBER_ID].indexOf(group.id()) === -1)
.map((group) =>
Button.component({
children: [badgeForId(group.id()), ' ', group.namePlural()],
icon: groupIds.indexOf(group.id()) !== -1 ? 'fas fa-check' : true,
onclick: (e) => {
if (e.shiftKey) e.stopPropagation();
this.toggle(group.id());
},
disabled: this.isGroupDisabled(group.id()) && this.isGroupDisabled(Group.MEMBER_ID) && this.isGroupDisabled(Group.GUEST_ID)
}))
disabled: this.isGroupDisabled(group.id()) && this.isGroupDisabled(Group.MEMBER_ID) && this.isGroupDisabled(Group.GUEST_ID),
})
)
);
}
@ -122,7 +121,7 @@ export default class PermissionDropdown extends Dropdown {
app.request({
method: 'POST',
url: app.forum.attribute('apiUrl') + '/permission',
data: {permission, groupIds}
data: { permission, groupIds },
});
}
@ -137,7 +136,7 @@ export default class PermissionDropdown extends Dropdown {
groupIds.splice(index, 1);
} else {
groupIds.push(groupId);
groupIds = groupIds.filter(id => [Group.GUEST_ID, Group.MEMBER_ID].indexOf(id) === -1);
groupIds = groupIds.filter((id) => [Group.GUEST_ID, Group.MEMBER_ID].indexOf(id) === -1);
}
this.save(groupIds);

View File

@ -13,12 +13,8 @@ export default class PermissionGrid extends Component {
view() {
const scopes = this.scopeItems().toArray();
const permissionCells = permission => {
return scopes.map(scope => (
<td>
{scope.render(permission)}
</td>
));
const permissionCells = (permission) => {
return scopes.map((scope) => <td>{scope.render(permission)}</td>);
};
return (
@ -26,27 +22,32 @@ export default class PermissionGrid extends Component {
<thead>
<tr>
<td></td>
{scopes.map(scope => (
{scopes.map((scope) => (
<th>
{scope.label}{' '}
{scope.onremove ? Button.component({icon: 'fas fa-times', className: 'Button Button--text PermissionGrid-removeScope', onclick: scope.onremove}) : ''}
{scope.onremove
? Button.component({ icon: 'fas fa-times', className: 'Button Button--text PermissionGrid-removeScope', onclick: scope.onremove })
: ''}
</th>
))}
<th>{this.scopeControlItems().toArray()}</th>
</tr>
</thead>
{this.permissions.map(section => (
{this.permissions.map((section) => (
<tbody>
<tr className="PermissionGrid-section">
<th>{section.label}</th>
{permissionCells(section)}
<td/>
<td />
</tr>
{section.children.map(child => (
{section.children.map((child) => (
<tr className="PermissionGrid-child">
<th>{icon(child.icon)}{child.label}</th>
<th>
{icon(child.icon)}
{child.label}
</th>
{permissionCells(child)}
<td/>
<td />
</tr>
))}
</tbody>
@ -58,25 +59,41 @@ export default class PermissionGrid extends Component {
permissionItems() {
const items = new ItemList();
items.add('view', {
items.add(
'view',
{
label: app.translator.trans('core.admin.permissions.read_heading'),
children: this.viewItems().toArray()
}, 100);
children: this.viewItems().toArray(),
},
100
);
items.add('start', {
items.add(
'start',
{
label: app.translator.trans('core.admin.permissions.create_heading'),
children: this.startItems().toArray()
}, 90);
children: this.startItems().toArray(),
},
90
);
items.add('reply', {
items.add(
'reply',
{
label: app.translator.trans('core.admin.permissions.participate_heading'),
children: this.replyItems().toArray()
}, 80);
children: this.replyItems().toArray(),
},
80
);
items.add('moderate', {
items.add(
'moderate',
{
label: app.translator.trans('core.admin.permissions.moderate_heading'),
children: this.moderateItems().toArray()
}, 70);
children: this.moderateItems().toArray(),
},
70
);
return items;
}
@ -84,31 +101,44 @@ export default class PermissionGrid extends Component {
viewItems() {
const items = new ItemList();
items.add('viewDiscussions', {
items.add(
'viewDiscussions',
{
icon: 'fas fa-eye',
label: app.translator.trans('core.admin.permissions.view_discussions_label'),
permission: 'viewDiscussions',
allowGuest: true
}, 100);
allowGuest: true,
},
100
);
items.add('viewUserList', {
items.add(
'viewUserList',
{
icon: 'fas fa-users',
label: app.translator.trans('core.admin.permissions.view_user_list_label'),
permission: 'viewUserList',
allowGuest: true
}, 100);
allowGuest: true,
},
100
);
items.add('signUp', {
items.add(
'signUp',
{
icon: 'fas fa-user-plus',
label: app.translator.trans('core.admin.permissions.sign_up_label'),
setting: () => SettingDropdown.component({
setting: () =>
SettingDropdown.component({
key: 'allow_sign_up',
options: [
{value: '1', label: app.translator.trans('core.admin.permissions_controls.signup_open_button')},
{value: '0', label: app.translator.trans('core.admin.permissions_controls.signup_closed_button')}
]
})
}, 90);
{ value: '1', label: app.translator.trans('core.admin.permissions_controls.signup_open_button') },
{ value: '0', label: app.translator.trans('core.admin.permissions_controls.signup_closed_button') },
],
}),
},
90
);
items.add('viewLastSeenAt', {
icon: 'far fa-clock',
@ -122,13 +152,19 @@ export default class PermissionGrid extends Component {
startItems() {
const items = new ItemList();
items.add('start', {
items.add(
'start',
{
icon: 'fas fa-edit',
label: app.translator.trans('core.admin.permissions.start_discussions_label'),
permission: 'startDiscussion'
}, 100);
permission: 'startDiscussion',
},
100
);
items.add('allowRenaming', {
items.add(
'allowRenaming',
{
icon: 'fas fa-i-cursor',
label: app.translator.trans('core.admin.permissions.allow_renaming_label'),
setting: () => {
@ -136,17 +172,19 @@ export default class PermissionGrid extends Component {
return SettingDropdown.component({
defaultLabel: minutes
? app.translator.transChoice('core.admin.permissions_controls.allow_some_minutes_button', minutes, {count: minutes})
? app.translator.transChoice('core.admin.permissions_controls.allow_some_minutes_button', minutes, { count: minutes })
: app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button'),
key: 'allow_renaming',
options: [
{value: '-1', label: app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button')},
{value: '10', label: app.translator.trans('core.admin.permissions_controls.allow_ten_minutes_button')},
{value: 'reply', label: app.translator.trans('core.admin.permissions_controls.allow_until_reply_button')}
]
{ value: '-1', label: app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button') },
{ value: '10', label: app.translator.trans('core.admin.permissions_controls.allow_ten_minutes_button') },
{ value: 'reply', label: app.translator.trans('core.admin.permissions_controls.allow_until_reply_button') },
],
});
}
}, 90);
},
},
90
);
return items;
}
@ -154,13 +192,19 @@ export default class PermissionGrid extends Component {
replyItems() {
const items = new ItemList();
items.add('reply', {
items.add(
'reply',
{
icon: 'fas fa-reply',
label: app.translator.trans('core.admin.permissions.reply_to_discussions_label'),
permission: 'discussion.reply'
}, 100);
permission: 'discussion.reply',
},
100
);
items.add('allowPostEditing', {
items.add(
'allowPostEditing',
{
icon: 'fas fa-pencil-alt',
label: app.translator.trans('core.admin.permissions.allow_post_editing_label'),
setting: () => {
@ -168,17 +212,19 @@ export default class PermissionGrid extends Component {
return SettingDropdown.component({
defaultLabel: minutes
? app.translator.transChoice('core.admin.permissions_controls.allow_some_minutes_button', minutes, {count: minutes})
? app.translator.transChoice('core.admin.permissions_controls.allow_some_minutes_button', minutes, { count: minutes })
: app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button'),
key: 'allow_post_editing',
options: [
{value: '-1', label: app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button')},
{value: '10', label: app.translator.trans('core.admin.permissions_controls.allow_ten_minutes_button')},
{value: 'reply', label: app.translator.trans('core.admin.permissions_controls.allow_until_reply_button')}
]
{ value: '-1', label: app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button') },
{ value: '10', label: app.translator.trans('core.admin.permissions_controls.allow_ten_minutes_button') },
{ value: 'reply', label: app.translator.trans('core.admin.permissions_controls.allow_until_reply_button') },
],
});
}
}, 90);
},
},
90
);
return items;
}
@ -186,59 +232,95 @@ export default class PermissionGrid extends Component {
moderateItems() {
const items = new ItemList();
items.add('viewIpsPosts', {
items.add(
'viewIpsPosts',
{
icon: 'fas fa-bullseye',
label: app.translator.trans('core.admin.permissions.view_post_ips_label'),
permission: 'discussion.viewIpsPosts'
}, 110);
permission: 'discussion.viewIpsPosts',
},
110
);
items.add('renameDiscussions', {
items.add(
'renameDiscussions',
{
icon: 'fas fa-i-cursor',
label: app.translator.trans('core.admin.permissions.rename_discussions_label'),
permission: 'discussion.rename'
}, 100);
permission: 'discussion.rename',
},
100
);
items.add('hideDiscussions', {
items.add(
'hideDiscussions',
{
icon: 'far fa-trash-alt',
label: app.translator.trans('core.admin.permissions.delete_discussions_label'),
permission: 'discussion.hide'
}, 90);
permission: 'discussion.hide',
},
90
);
items.add('deleteDiscussions', {
items.add(
'deleteDiscussions',
{
icon: 'fas fa-times',
label: app.translator.trans('core.admin.permissions.delete_discussions_forever_label'),
permission: 'discussion.delete'
}, 80);
permission: 'discussion.delete',
},
80
);
items.add('postWithoutThrottle', {
items.add(
'postWithoutThrottle',
{
icon: 'fas fa-swimmer',
label: app.translator.trans('core.admin.permissions.post_without_throttle_label'),
permission: 'postWithoutThrottle'
}, 70);
permission: 'postWithoutThrottle',
},
70
);
items.add('editPosts', {
items.add(
'editPosts',
{
icon: 'fas fa-pencil-alt',
label: app.translator.trans('core.admin.permissions.edit_posts_label'),
permission: 'discussion.editPosts'
}, 70);
permission: 'discussion.editPosts',
},
70
);
items.add('hidePosts', {
items.add(
'hidePosts',
{
icon: 'far fa-trash-alt',
label: app.translator.trans('core.admin.permissions.delete_posts_label'),
permission: 'discussion.hidePosts'
}, 60);
permission: 'discussion.hidePosts',
},
60
);
items.add('deletePosts', {
items.add(
'deletePosts',
{
icon: 'fas fa-times',
label: app.translator.trans('core.admin.permissions.delete_posts_forever_label'),
permission: 'discussion.deletePosts'
}, 60);
permission: 'discussion.deletePosts',
},
60
);
items.add('userEdit', {
items.add(
'userEdit',
{
icon: 'fas fa-user-cog',
label: app.translator.trans('core.admin.permissions.edit_users_label'),
permission: 'user.edit'
}, 60);
permission: 'user.edit',
},
60
);
return items;
}
@ -246,21 +328,25 @@ export default class PermissionGrid extends Component {
scopeItems() {
const items = new ItemList();
items.add('global', {
items.add(
'global',
{
label: app.translator.trans('core.admin.permissions.global_heading'),
render: item => {
render: (item) => {
if (item.setting) {
return item.setting();
} else if (item.permission) {
return PermissionDropdown.component({
permission: item.permission,
allowGuest: item.allowGuest
allowGuest: item.allowGuest,
});
}
return '';
}
}, 100);
},
},
100
);
return items;
}

View File

@ -11,29 +11,28 @@ export default class PermissionsPage extends Page {
<div className="PermissionsPage">
<div className="PermissionsPage-groups">
<div className="container">
{app.store.all('groups')
.filter(group => [Group.GUEST_ID, Group.MEMBER_ID].indexOf(group.id()) === -1)
.map(group => (
<button className="Button Group" onclick={() => app.modal.show(new EditGroupModal({group}))}>
{app.store
.all('groups')
.filter((group) => [Group.GUEST_ID, Group.MEMBER_ID].indexOf(group.id()) === -1)
.map((group) => (
<button className="Button Group" onclick={() => app.modal.show(new EditGroupModal({ group }))}>
{GroupBadge.component({
group,
className: 'Group-icon',
label: null
label: null,
})}
<span className="Group-name">{group.namePlural()}</span>
</button>
))}
<button className="Button Group Group--add" onclick={() => app.modal.show(new EditGroupModal())}>
{icon('fas fa-plus', {className: 'Group-icon'})}
{icon('fas fa-plus', { className: 'Group-icon' })}
<span className="Group-name">{app.translator.trans('core.admin.permissions.new_group_button')}</span>
</button>
</div>
</div>
<div className="PermissionsPage-permissions">
<div className="container">
{PermissionGrid.component()}
</div>
<div className="container">{PermissionGrid.component()}</div>
</div>
</div>
);

View File

@ -26,10 +26,7 @@ export default class SessionDropdown extends Dropdown {
getButtonContent() {
const user = app.session.user;
return [
avatar(user), ' ',
<span className="Button-label">{username(user)}</span>
];
return [avatar(user), ' ', <span className="Button-label">{username(user)}</span>];
}
/**
@ -40,11 +37,12 @@ export default class SessionDropdown extends Dropdown {
items() {
const items = new ItemList();
items.add('logOut',
items.add(
'logOut',
Button.component({
icon: 'fas fa-sign-out-alt',
children: app.translator.trans('core.admin.header.log_out_button'),
onclick: app.session.logout.bind(app.session)
onclick: app.session.logout.bind(app.session),
}),
-100
);

View File

@ -11,14 +11,14 @@ export default class SettingDropdown extends SelectDropdown {
props.caretIcon = 'fas fa-caret-down';
props.defaultLabel = 'Custom';
props.children = props.options.map(({value, label}) => {
props.children = props.options.map(({ value, label }) => {
const active = app.data.settings[props.key] === value;
return Button.component({
children: label,
icon: active ? 'fas fa-check' : true,
onclick: saveSettings.bind(this, {[props.key]: value}),
active
onclick: saveSettings.bind(this, { [props.key]: value }),
active,
});
});
}

View File

@ -18,9 +18,7 @@ export default class SettingsModal extends Modal {
<div className="Form">
{this.form()}
<div className="Form-group">
{this.submitButton()}
</div>
<div className="Form-group">{this.submitButton()}</div>
</div>
</div>
);
@ -28,11 +26,7 @@ export default class SettingsModal extends Modal {
submitButton() {
return (
<Button
type="submit"
className="Button Button--primary"
loading={this.loading}
disabled={!this.changed()}>
<Button type="submit" className="Button Button--primary" loading={this.loading} disabled={!this.changed()}>
{app.translator.trans('core.admin.settings.submit_button')}
</Button>
);
@ -47,7 +41,7 @@ export default class SettingsModal extends Modal {
dirty() {
const dirty = {};
Object.keys(this.settings).forEach(key => {
Object.keys(this.settings).forEach((key) => {
const value = this.settings[key]();
if (value !== app.data.settings[key]) {
@ -67,10 +61,7 @@ export default class SettingsModal extends Modal {
this.loading = true;
saveSettings(this.dirty()).then(
this.onsaved.bind(this),
this.loaded.bind(this)
);
saveSettings(this.dirty()).then(this.onsaved.bind(this), this.loaded.bind(this));
}
onsaved() {

View File

@ -20,29 +20,27 @@ export default class StatusWidget extends DashboardWidget {
}
content() {
return (
<ul>{listItems(this.items().toArray())}</ul>
);
return <ul>{listItems(this.items().toArray())}</ul>;
}
items() {
const items = new ItemList();
items.add('tools', (
items.add(
'tools',
<Dropdown
label={app.translator.trans('core.admin.dashboard.tools_button')}
icon="fas fa-cog"
buttonClassName="Button"
menuClassName="Dropdown-menu--right">
<Button onclick={this.handleClearCache.bind(this)}>
{app.translator.trans('core.admin.dashboard.clear_cache_button')}
</Button>
menuClassName="Dropdown-menu--right"
>
<Button onclick={this.handleClearCache.bind(this)}>{app.translator.trans('core.admin.dashboard.clear_cache_button')}</Button>
</Dropdown>
));
);
items.add('version-flarum', [<strong>Flarum</strong>, <br/>, app.forum.attribute('version')]);
items.add('version-php', [<strong>PHP</strong>, <br/>, app.data.phpVersion]);
items.add('version-mysql', [<strong>MySQL</strong>, <br/>, app.data.mysqlVersion]);
items.add('version-flarum', [<strong>Flarum</strong>, <br />, app.forum.attribute('version')]);
items.add('version-php', [<strong>PHP</strong>, <br />, app.data.phpVersion]);
items.add('version-mysql', [<strong>MySQL</strong>, <br />, app.data.mysqlVersion]);
return items;
}
@ -50,9 +48,11 @@ export default class StatusWidget extends DashboardWidget {
handleClearCache(e) {
app.modal.show(new LoadingModal());
app.request({
app
.request({
method: 'DELETE',
url: app.forum.attribute('apiUrl') + '/cache'
}).then(() => window.location.reload());
url: app.forum.attribute('apiUrl') + '/cache',
})
.then(() => window.location.reload());
}
}

View File

@ -15,7 +15,9 @@ export default class UploadImageButton extends Button {
return (
<div>
<p><img src={app.forum.attribute(this.props.name+'Url')} alt=""/></p>
<p>
<img src={app.forum.attribute(this.props.name + 'Url')} alt="" />
</p>
<p>{super.view()}</p>
</div>
);
@ -35,22 +37,25 @@ export default class UploadImageButton extends Button {
const $input = $('<input type="file">');
$input.appendTo('body').hide().click().on('change', e => {
$input
.appendTo('body')
.hide()
.click()
.on('change', (e) => {
const data = new FormData();
data.append(this.props.name, $(e.target)[0].files[0]);
this.loading = true;
m.redraw();
app.request({
app
.request({
method: 'POST',
url: this.resourceUrl(),
serialize: raw => raw,
data
}).then(
this.success.bind(this),
this.failure.bind(this)
);
serialize: (raw) => raw,
data,
})
.then(this.success.bind(this), this.failure.bind(this));
});
}
@ -61,13 +66,12 @@ export default class UploadImageButton extends Button {
this.loading = true;
m.redraw();
app.request({
app
.request({
method: 'DELETE',
url: this.resourceUrl()
}).then(
this.success.bind(this),
this.failure.bind(this)
);
url: this.resourceUrl(),
})
.then(this.success.bind(this), this.failure.bind(this));
}
resourceUrl() {

View File

@ -11,11 +11,7 @@ import Component from '../../common/Component';
export default class DashboardWidget extends Component {
view() {
return (
<div className={"DashboardWidget "+this.className()}>
{this.content()}
</div>
);
return <div className={'DashboardWidget ' + this.className()}>{this.content()}</div>;
}
/**

View File

@ -9,7 +9,6 @@ export { app };
// Export public API
// Export compat API
import compat from './compat';

View File

@ -10,13 +10,13 @@ import MailPage from './components/MailPage';
*
* @param {App} app
*/
export default function(app) {
export default function (app) {
app.routes = {
'dashboard': {path: '/', component: DashboardPage.component()},
'basics': {path: '/basics', component: BasicsPage.component()},
'permissions': {path: '/permissions', component: PermissionsPage.component()},
'appearance': {path: '/appearance', component: AppearancePage.component()},
'extensions': {path: '/extensions', component: ExtensionsPage.component()},
'mail': {path: '/mail', component: MailPage.component()}
dashboard: { path: '/', component: DashboardPage.component() },
basics: { path: '/basics', component: BasicsPage.component() },
permissions: { path: '/permissions', component: PermissionsPage.component() },
appearance: { path: '/appearance', component: AppearancePage.component() },
extensions: { path: '/extensions', component: ExtensionsPage.component() },
mail: { path: '/mail', component: MailPage.component() },
};
}

View File

@ -3,11 +3,13 @@ export default function saveSettings(settings) {
Object.assign(app.data.settings, settings);
return app.request({
return app
.request({
method: 'POST',
url: app.forum.attribute('apiUrl') + '/settings',
data: settings
}).catch(error => {
data: settings,
})
.catch((error) => {
app.data.settings = oldSettings;
throw error;
});

View File

@ -86,7 +86,7 @@ export default class Application {
discussions: Discussion,
posts: Post,
groups: Group,
notifications: Notification
notifications: Notification,
});
/**
@ -126,22 +126,19 @@ export default class Application {
}
boot() {
this.initializers.toArray().forEach(initializer => initializer(this));
this.initializers.toArray().forEach((initializer) => initializer(this));
this.store.pushPayload({data: this.data.resources});
this.store.pushPayload({ data: this.data.resources });
this.forum = this.store.getById('forums', 1);
this.session = new Session(
this.store.getById('users', this.data.session.userId),
this.data.session.csrfToken
);
this.session = new Session(this.store.getById('users', this.data.session.userId), this.data.session.csrfToken);
this.mount();
}
bootExtensions(extensions) {
Object.keys(extensions).forEach(name => {
Object.keys(extensions).forEach((name) => {
const extension = extensions[name];
const extenders = flattenDeep(extension.extend);
@ -153,26 +150,20 @@ export default class Application {
}
mount(basePath = '') {
this.modal = m.mount(document.getElementById('modal'), <ModalManager/>);
this.alerts = m.mount(document.getElementById('alerts'), <AlertManager/>);
this.modal = m.mount(document.getElementById('modal'), <ModalManager />);
this.alerts = m.mount(document.getElementById('alerts'), <AlertManager />);
this.drawer = new Drawer();
m.route(
document.getElementById('content'),
basePath + '/',
mapRoutes(this.routes, basePath)
);
m.route(document.getElementById('content'), basePath + '/', mapRoutes(this.routes, basePath));
// Add a class to the body which indicates that the page has been scrolled
// down.
new ScrollListener(top => {
new ScrollListener((top) => {
const $app = $('#app');
const offset = $app.offset().top;
$app
.toggleClass('affix', top >= offset)
.toggleClass('scrolled', top > offset);
$app.toggleClass('affix', top >= offset).toggleClass('scrolled', top > offset);
}).start();
$(() => {
@ -220,9 +211,7 @@ export default class Application {
}
updateTitle() {
document.title = (this.titleCount ? `(${this.titleCount}) ` : '') +
(this.title ? this.title + ' - ' : '') +
this.forum.attribute('title');
document.title = (this.titleCount ? `(${this.titleCount}) ` : '') + (this.title ? this.title + ' - ' : '') + this.forum.attribute('title');
}
/**
@ -256,9 +245,11 @@ export default class Application {
// When we deserialize JSON data, if for some reason the server has provided
// a dud response, we don't want the application to crash. We'll show an
// error message to the user instead.
options.deserialize = options.deserialize || (responseText => responseText);
options.deserialize = options.deserialize || ((responseText) => responseText);
options.errorHandler = options.errorHandler || (error => {
options.errorHandler =
options.errorHandler ||
((error) => {
throw error;
});
@ -266,7 +257,7 @@ export default class Application {
// response code and show an error message to the user if something's gone
// awry.
const original = options.extract;
options.extract = xhr => {
options.extract = (xhr) => {
let responseText;
if (original) {
@ -299,7 +290,9 @@ export default class Application {
// returned and show an alert containing its contents.
const deferred = m.deferred();
m.request(options).then(response => deferred.resolve(response), error => {
m.request(options).then(
(response) => deferred.resolve(response),
(error) => {
this.requestError = error;
let children;
@ -307,7 +300,7 @@ export default class Application {
switch (error.status) {
case 422:
children = error.response.errors
.map(error => [error.detail, <br/>])
.map((error) => [error.detail, <br />])
.reduce((a, b) => a.concat(b), [])
.slice(0, -1);
break;
@ -336,8 +329,10 @@ export default class Application {
type: 'error',
children,
controls: isDebug && [
<Button className="Button Button--link" onclick={this.showDebug.bind(this, error)}>Debug</Button>
]
<Button className="Button Button--link" onclick={this.showDebug.bind(this, error)}>
Debug
</Button>,
],
});
try {
@ -347,7 +342,8 @@ export default class Application {
}
deferred.reject(error);
});
}
);
return deferred.promise;
}
@ -359,7 +355,7 @@ export default class Application {
showDebug(error) {
this.alerts.dismiss(this.requestError.alert);
this.modal.show(new RequestErrorModal({error}));
this.modal.show(new RequestErrorModal({ error }));
}
/**

View File

@ -70,8 +70,7 @@ export default class Component {
*
* @protected
*/
init() {
}
init() {}
/**
* Called when the component is destroyed, i.e. after a redraw where it is no
@ -81,8 +80,7 @@ export default class Component {
* @param {Object} e
* @public
*/
onunload() {
}
onunload() {}
/**
* Get the renderable virtual DOM that represents the component's view.
@ -99,7 +97,7 @@ export default class Component {
* @public
*/
render() {
const vdom = this.retain ? {subtree: 'retain'} : this.view();
const vdom = this.retain ? { subtree: 'retain' } : this.view();
// Override the root element's config attribute with our own function, which
// will set the component instance's element property to the root DOM
@ -148,8 +146,7 @@ export default class Component {
* @param {Object} vdom
* @public
*/
config() {
}
config() {}
/**
* Get the virtual DOM that represents the component's view.
@ -201,14 +198,14 @@ export default class Component {
controller: this.bind(undefined, componentProps),
view: view,
props: componentProps,
component: this
component: this,
};
// If a `key` prop was set, then we'll assume that we want that to actually
// show up as an attribute on the component object so that Mithril's key
// algorithm can be applied.
if (componentProps.key) {
output.attrs = {key: componentProps.key};
output.attrs = { key: componentProps.key };
}
return output;
@ -220,6 +217,5 @@ export default class Component {
* @param {Object} props
* @public
*/
static initProps(props) {
}
static initProps(props) {}
}

View File

@ -88,7 +88,7 @@ export default class Model {
// relationship data object.
for (const innerKey in data[key]) {
if (data[key][innerKey] instanceof Model) {
data[key][innerKey] = {data: Model.getIdentifier(data[key][innerKey])};
data[key][innerKey] = { data: Model.getIdentifier(data[key][innerKey]) };
}
this.data[key][innerKey] = data[key][innerKey];
}
@ -109,7 +109,7 @@ export default class Model {
* @public
*/
pushAttributes(attributes) {
this.pushData({attributes});
this.pushData({ attributes });
}
/**
@ -125,7 +125,7 @@ export default class Model {
const data = {
type: this.data.type,
id: this.data.id,
attributes
attributes,
};
// If a 'relationships' key exists, extract it from the attributes hash and
@ -138,9 +138,7 @@ export default class Model {
const model = attributes.relationships[key];
data.relationships[key] = {
data: model instanceof Array
? model.map(Model.getIdentifier)
: Model.getIdentifier(model)
data: model instanceof Array ? model.map(Model.getIdentifier) : Model.getIdentifier(model),
};
}
@ -154,18 +152,25 @@ export default class Model {
this.pushData(data);
const request = {data};
const request = { data };
if (options.meta) request.meta = options.meta;
return app.request(Object.assign({
return app
.request(
Object.assign(
{
method: this.exists ? 'PATCH' : 'POST',
url: app.forum.attribute('apiUrl') + this.apiEndpoint(),
data: request
}, options)).then(
data: request,
},
options
)
)
.then(
// If everything went well, we'll make sure the store knows that this
// model exists now (if it didn't already), and we'll push the data that
// the API returned into the store.
payload => {
(payload) => {
this.store.data[payload.data.type] = this.store.data[payload.data.type] || {};
this.store.data[payload.data.type][payload.data.id] = this;
return this.store.pushPayload(payload);
@ -173,7 +178,7 @@ export default class Model {
// If something went wrong, though... good thing we backed up our model's
// old data! We'll revert to that and let others handle the error.
response => {
(response) => {
this.pushData(oldData);
m.lazyRedraw();
throw response;
@ -192,11 +197,18 @@ export default class Model {
delete(data, options = {}) {
if (!this.exists) return m.deferred().resolve().promise;
return app.request(Object.assign({
return app
.request(
Object.assign(
{
method: 'DELETE',
url: app.forum.attribute('apiUrl') + this.apiEndpoint(),
data
}, options)).then(() => {
data,
},
options
)
)
.then(() => {
this.exists = false;
this.store.remove(this);
});
@ -225,7 +237,7 @@ export default class Model {
* @public
*/
static attribute(name, transform) {
return function() {
return function () {
const value = this.data.attributes && this.data.attributes[name];
return transform ? transform(value) : value;
@ -243,7 +255,7 @@ export default class Model {
* @public
*/
static hasOne(name) {
return function() {
return function () {
if (this.data.relationships) {
const relationship = this.data.relationships[name];
@ -267,12 +279,12 @@ export default class Model {
* @public
*/
static hasMany(name) {
return function() {
return function () {
if (this.data.relationships) {
const relationship = this.data.relationships[name];
if (relationship) {
return relationship.data.map(data => app.store.getById(data.type, data.id));
return relationship.data.map((data) => app.store.getById(data.type, data.id));
}
}
@ -301,7 +313,7 @@ export default class Model {
static getIdentifier(model) {
return {
type: model.data.type,
id: model.data.id
id: model.data.id,
};
}
}

View File

@ -31,11 +31,16 @@ export default class Session {
* @public
*/
login(data, options = {}) {
return app.request(Object.assign({
return app.request(
Object.assign(
{
method: 'POST',
url: app.forum.attribute('baseUrl') + '/login',
data
}, options));
data,
},
options
)
);
}
/**

View File

@ -34,9 +34,7 @@ export default class Store {
pushPayload(payload) {
if (payload.included) payload.included.map(this.pushObject.bind(this));
const result = payload.data instanceof Array
? payload.data.map(this.pushObject.bind(this))
: this.pushObject(payload.data);
const result = payload.data instanceof Array ? payload.data.map(this.pushObject.bind(this)) : this.pushObject(payload.data);
// Attach the original payload to the model that we give back. This is
// useful to consumers as it allows them to access meta information
@ -58,7 +56,7 @@ export default class Store {
pushObject(data) {
if (!this.models[data.type]) return null;
const type = this.data[data.type] = this.data[data.type] || {};
const type = (this.data[data.type] = this.data[data.type] || {});
if (type[data.id]) {
type[data.id].pushData(data);
@ -95,11 +93,18 @@ export default class Store {
url += '/' + id;
}
return app.request(Object.assign({
return app
.request(
Object.assign(
{
method: 'GET',
url,
data
}, options)).then(this.pushPayload.bind(this));
data,
},
options
)
)
.then(this.pushPayload.bind(this));
}
/**
@ -124,7 +129,7 @@ export default class Store {
* @public
*/
getBy(type, key, value) {
return this.all(type).filter(model => model[key]() === value)[0];
return this.all(type).filter((model) => model[key]() === value)[0];
}
/**
@ -137,7 +142,7 @@ export default class Store {
all(type) {
const records = this.data[type];
return records ? Object.keys(records).map(id => records[id]) : [];
return records ? Object.keys(records).map((id) => records[id]) : [];
}
/**
@ -160,6 +165,6 @@ export default class Store {
createRecord(type, data = {}) {
data.type = data.type || type;
return new (this.models[type])(data, this);
return new this.models[type](data, this);
}
}

View File

@ -67,7 +67,7 @@ export default class Translator {
const hydrated = [];
const open = [hydrated];
translation.forEach(part => {
translation.forEach((part) => {
const match = part.match(new RegExp('{([a-z0-9_]+)}|<(/?)([a-z0-9_]+)>', 'i'));
if (match) {
@ -77,7 +77,7 @@ export default class Translator {
if (match[2]) {
open.shift();
} else {
let tag = input[match[3]] || {tag: match[3], children: []};
let tag = input[match[3]] || { tag: match[3], children: [] };
open[0].push(tag);
open.unshift(tag.children || tag);
}
@ -87,7 +87,7 @@ export default class Translator {
}
});
return hydrated.filter(part => part);
return hydrated.filter((part) => part);
}
pluralize(translation, number) {
@ -97,7 +97,7 @@ export default class Translator {
standardRules = [],
explicitRules = [];
translation.split('|').forEach(part => {
translation.split('|').forEach((part) => {
if (cPluralRegex.test(part)) {
const matches = part.match(cPluralRegex);
explicitRules[matches[0]] = matches[matches.length - 1];
@ -125,8 +125,10 @@ export default class Translator {
var leftNumber = this.convertNumber(matches[4]);
var rightNumber = this.convertNumber(matches[5]);
if (('[' === matches[3] ? number >= leftNumber : number > leftNumber) &&
(']' === matches[6] ? number <= rightNumber : number < rightNumber)) {
if (
('[' === matches[3] ? number >= leftNumber : number > leftNumber) &&
(']' === matches[6] ? number <= rightNumber : number < rightNumber)
) {
return explicitRules[e];
}
}
@ -223,7 +225,7 @@ export default class Translator {
case 'tr':
case 'ur':
case 'zu':
return (number == 1) ? 0 : 1;
return number == 1 ? 0 : 1;
case 'am':
case 'bh':
@ -237,7 +239,7 @@ export default class Translator {
case 'xbr':
case 'ti':
case 'wa':
return ((number === 0) || (number == 1)) ? 0 : 1;
return number === 0 || number == 1 ? 0 : 1;
case 'be':
case 'bs':
@ -245,41 +247,41 @@ export default class Translator {
case 'ru':
case 'sr':
case 'uk':
return ((number % 10 == 1) && (number % 100 != 11)) ? 0 : (((number % 10 >= 2) && (number % 10 <= 4) && ((number % 100 < 10) || (number % 100 >= 20))) ? 1 : 2);
return number % 10 == 1 && number % 100 != 11 ? 0 : number % 10 >= 2 && number % 10 <= 4 && (number % 100 < 10 || number % 100 >= 20) ? 1 : 2;
case 'cs':
case 'sk':
return (number == 1) ? 0 : (((number >= 2) && (number <= 4)) ? 1 : 2);
return number == 1 ? 0 : number >= 2 && number <= 4 ? 1 : 2;
case 'ga':
return (number == 1) ? 0 : ((number == 2) ? 1 : 2);
return number == 1 ? 0 : number == 2 ? 1 : 2;
case 'lt':
return ((number % 10 == 1) && (number % 100 != 11)) ? 0 : (((number % 10 >= 2) && ((number % 100 < 10) || (number % 100 >= 20))) ? 1 : 2);
return number % 10 == 1 && number % 100 != 11 ? 0 : number % 10 >= 2 && (number % 100 < 10 || number % 100 >= 20) ? 1 : 2;
case 'sl':
return (number % 100 == 1) ? 0 : ((number % 100 == 2) ? 1 : (((number % 100 == 3) || (number % 100 == 4)) ? 2 : 3));
return number % 100 == 1 ? 0 : number % 100 == 2 ? 1 : number % 100 == 3 || number % 100 == 4 ? 2 : 3;
case 'mk':
return (number % 10 == 1) ? 0 : 1;
return number % 10 == 1 ? 0 : 1;
case 'mt':
return (number == 1) ? 0 : (((number === 0) || ((number % 100 > 1) && (number % 100 < 11))) ? 1 : (((number % 100 > 10) && (number % 100 < 20)) ? 2 : 3));
return number == 1 ? 0 : number === 0 || (number % 100 > 1 && number % 100 < 11) ? 1 : number % 100 > 10 && number % 100 < 20 ? 2 : 3;
case 'lv':
return (number === 0) ? 0 : (((number % 10 == 1) && (number % 100 != 11)) ? 1 : 2);
return number === 0 ? 0 : number % 10 == 1 && number % 100 != 11 ? 1 : 2;
case 'pl':
return (number == 1) ? 0 : (((number % 10 >= 2) && (number % 10 <= 4) && ((number % 100 < 12) || (number % 100 > 14))) ? 1 : 2);
return number == 1 ? 0 : number % 10 >= 2 && number % 10 <= 4 && (number % 100 < 12 || number % 100 > 14) ? 1 : 2;
case 'cy':
return (number == 1) ? 0 : ((number == 2) ? 1 : (((number == 8) || (number == 11)) ? 2 : 3));
return number == 1 ? 0 : number == 2 ? 1 : number == 8 || number == 11 ? 2 : 3;
case 'ro':
return (number == 1) ? 0 : (((number === 0) || ((number % 100 > 0) && (number % 100 < 20))) ? 1 : 2);
return number == 1 ? 0 : number === 0 || (number % 100 > 0 && number % 100 < 20) ? 1 : 2;
case 'ar':
return (number === 0) ? 0 : ((number == 1) ? 1 : ((number == 2) ? 2 : (((number >= 3) && (number <= 10)) ? 3 : (((number >= 11) && (number <= 99)) ? 4 : 5))));
return number === 0 ? 0 : number == 1 ? 1 : number == 2 ? 2 : number >= 3 && number <= 10 ? 3 : number >= 11 && number <= 99 ? 4 : 5;
default:
return 0;

View File

@ -62,9 +62,9 @@ import userOnline from './helpers/userOnline';
import listItems from './helpers/listItems';
export default {
'extend': extend,
'Session': Session,
'Store': Store,
extend: extend,
Session: Session,
Store: Store,
'utils/evented': evented,
'utils/liveHumanTimes': liveHumanTimes,
'utils/ItemList': ItemList,
@ -91,8 +91,8 @@ export default {
'models/Discussion': Discussion,
'models/Group': Group,
'models/Forum': Forum,
'Component': Component,
'Translator': Translator,
Component: Component,
Translator: Translator,
'components/AlertManager': AlertManager,
'components/Switch': Switch,
'components/Badge': Badge,
@ -113,8 +113,8 @@ export default {
'components/Button': Button,
'components/Modal': Modal,
'components/GroupBadge': GroupBadge,
'Model': Model,
'Application': Application,
Model: Model,
Application: Application,
'helpers/fullTime': fullTime,
'helpers/avatar': avatar,
'helpers/icon': icon,
@ -123,5 +123,5 @@ export default {
'helpers/highlight': highlight,
'helpers/username': username,
'helpers/userOnline': userOnline,
'helpers/listItems': listItems
'helpers/listItems': listItems,
};

View File

@ -35,22 +35,13 @@ export default class Alert extends Component {
const dismissControl = [];
if (dismissible || dismissible === undefined) {
dismissControl.push(
<Button
icon="fas fa-times"
className="Button Button--link Button--icon Alert-dismiss"
onclick={ondismiss}/>
);
dismissControl.push(<Button icon="fas fa-times" className="Button Button--link Button--icon Alert-dismiss" onclick={ondismiss} />);
}
return (
<div {...attrs}>
<span className="Alert-body">
{children}
</span>
<ul className="Alert-controls">
{listItems(controls.concat(dismissControl))}
</ul>
<span className="Alert-body">{children}</span>
<ul className="Alert-controls">{listItems(controls.concat(dismissControl))}</ul>
</div>
);
}

View File

@ -19,7 +19,9 @@ export default class AlertManager extends Component {
view() {
return (
<div className="AlertManager">
{this.components.map(component => <div className="AlertManager-alert">{component}</div>)}
{this.components.map((component) => (
<div className="AlertManager-alert">{component}</div>
))}
</div>
);
}

View File

@ -24,16 +24,12 @@ export default class Badge extends Component {
attrs.className = 'Badge ' + (type ? 'Badge--' + type : '') + ' ' + (attrs.className || '');
attrs.title = extract(attrs, 'label') || '';
return (
<span {...attrs}>
{iconName ? icon(iconName, {className: 'Badge-icon'}) : m.trust('&nbsp;')}
</span>
);
return <span {...attrs}>{iconName ? icon(iconName, { className: 'Badge-icon' }) : m.trust('&nbsp;')}</span>;
}
config(isInitialized) {
if (isInitialized) return;
if (this.props.label) this.$().tooltip({container: 'body'});
if (this.props.label) this.$().tooltip({ container: 'body' });
}
}

View File

@ -62,9 +62,9 @@ export default class Button extends Component {
const iconName = this.props.icon;
return [
iconName && iconName !== true ? icon(iconName, {className: 'Button-icon'}) : '',
iconName && iconName !== true ? icon(iconName, { className: 'Button-icon' }) : '',
this.props.children ? <span className="Button-label">{this.props.children}</span> : '',
this.props.loading ? LoadingIndicator.component({size: 'tiny', className: 'LoadingIndicator--inline'}) : ''
this.props.loading ? LoadingIndicator.component({ size: 'tiny', className: 'LoadingIndicator--inline' }) : '',
];
}
}

View File

@ -31,13 +31,8 @@ export default class Checkbox extends Component {
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>
<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>
);
@ -50,9 +45,7 @@ export default class Checkbox extends Component {
* @protected
*/
getDisplay() {
return this.loading
? LoadingIndicator.component({size: 'tiny'})
: icon(this.props.state ? 'fas fa-check' : 'fas fa-times');
return this.loading ? LoadingIndicator.component({ size: 'tiny' }) : icon(this.props.state ? 'fas fa-check' : 'fas fa-times');
}
/**

View File

@ -64,19 +64,13 @@ export default class Dropdown extends Component {
$menu.removeClass('Dropdown-menu--top Dropdown-menu--right');
$menu.toggleClass(
'Dropdown-menu--top',
$menu.offset().top + $menu.height() > $(window).scrollTop() + $(window).height()
);
$menu.toggleClass('Dropdown-menu--top', $menu.offset().top + $menu.height() > $(window).scrollTop() + $(window).height());
if ($menu.offset().top < 0) {
$menu.removeClass('Dropdown-menu--top');
}
$menu.toggleClass(
'Dropdown-menu--right',
isRight || $menu.offset().left + $menu.width() > $(window).scrollLeft() + $(window).width()
);
$menu.toggleClass('Dropdown-menu--right', isRight || $menu.offset().left + $menu.width() > $(window).scrollLeft() + $(window).width());
});
this.$().on('hidden.bs.dropdown', () => {
@ -98,10 +92,7 @@ export default class Dropdown extends Component {
*/
getButton() {
return (
<button
className={'Dropdown-toggle ' + this.props.buttonClassName}
data-toggle="dropdown"
onclick={this.props.onclick}>
<button className={'Dropdown-toggle ' + this.props.buttonClassName} data-toggle="dropdown" onclick={this.props.onclick}>
{this.getButtonContent()}
</button>
);
@ -115,17 +106,13 @@ export default class Dropdown extends Component {
*/
getButtonContent() {
return [
this.props.icon ? icon(this.props.icon, {className: 'Button-icon'}) : '',
this.props.icon ? icon(this.props.icon, { className: 'Button-icon' }) : '',
<span className="Button-label">{this.props.label}</span>,
this.props.caretIcon ? icon(this.props.caretIcon, {className: 'Button-caret'}) : ''
this.props.caretIcon ? icon(this.props.caretIcon, { className: 'Button-caret' }) : '',
];
}
getMenu(items) {
return (
<ul className={'Dropdown-menu dropdown-menu ' + this.props.menuClassName}>
{items}
</ul>
);
return <ul className={'Dropdown-menu dropdown-menu ' + this.props.menuClassName}>{items}</ul>;
}
}

View File

@ -6,7 +6,7 @@ export default class GroupBadge extends Badge {
if (props.group) {
props.icon = props.group.icon();
props.style = {backgroundColor: props.group.color()};
props.style = { backgroundColor: props.group.color() };
props.label = typeof props.label === 'undefined' ? props.group.nameSingular() : props.label;
props.type = 'group--' + props.group.id();

View File

@ -33,8 +33,6 @@ export default class LinkButton extends Button {
* @return {Boolean}
*/
static isActive(props) {
return typeof props.active !== 'undefined'
? props.active
: m.route() === props.href;
return typeof props.active !== 'undefined' ? props.active : m.route() === props.href;
}
}

View File

@ -31,10 +31,12 @@ export default class Modal extends Component {
{Button.component({
icon: 'fas fa-times',
onclick: this.hide.bind(this),
className: 'Button Button--icon Button--link'
className: 'Button Button--icon Button--link',
})}
</div>
) : ''}
) : (
''
)}
<form onsubmit={this.onsubmit.bind(this)}>
<div className="Modal-header">
@ -65,8 +67,7 @@ export default class Modal extends Component {
* @return {String}
* @abstract
*/
className() {
}
className() {}
/**
* Get the title of the modal dialog.
@ -74,8 +75,7 @@ export default class Modal extends Component {
* @return {String}
* @abstract
*/
title() {
}
title() {}
/**
* Get the content of the modal.
@ -83,16 +83,14 @@ export default class Modal extends Component {
* @return {VirtualElement}
* @abstract
*/
content() {
}
content() {}
/**
* Handle the modal form's submit event.
*
* @param {Event} e
*/
onsubmit() {
}
onsubmit() {}
/**
* Focus on the first input when the modal is ready to be used.
@ -101,8 +99,7 @@ export default class Modal extends Component {
this.$('form').find('input, select, textarea').first().focus().select();
}
onhide() {
}
onhide() {}
/**
* Hide the modal.

View File

@ -13,11 +13,7 @@ export default class ModalManager extends Component {
}
view() {
return (
<div className="ModalManager modal fade">
{this.component && this.component.render()}
</div>
);
return <div className="ModalManager modal fade">{this.component && this.component.render()}</div>;
}
config(isInitialized, context) {
@ -28,9 +24,7 @@ export default class ModalManager extends Component {
// to be retained across route changes.
context.retain = true;
this.$()
.on('hidden.bs.modal', this.clear.bind(this))
.on('shown.bs.modal', this.onready.bind(this));
this.$().on('hidden.bs.modal', this.clear.bind(this)).on('shown.bs.modal', this.onready.bind(this));
}
/**
@ -54,10 +48,12 @@ export default class ModalManager extends Component {
m.redraw(true);
const dismissible = !!this.component.isDismissible();
this.$().modal({
this.$()
.modal({
backdrop: dismissible || 'static',
keyboard: dismissible
}).modal('show');
keyboard: dismissible,
})
.modal('show');
}
/**

View File

@ -19,15 +19,15 @@ import LinkButton from './LinkButton';
*/
export default class Navigation extends Component {
view() {
const {history, pane} = app;
const { history, pane } = app;
return (
<div className={'Navigation ButtonGroup ' + (this.props.className || '')}
<div
className={'Navigation ButtonGroup ' + (this.props.className || '')}
onmouseenter={pane && pane.show.bind(pane)}
onmouseleave={pane && pane.onmouseleave.bind(pane)}>
{history.canGoBack()
? [this.getBackButton(), this.getPaneButton()]
: this.getDrawerButton()}
onmouseleave={pane && pane.onmouseleave.bind(pane)}
>
{history.canGoBack() ? [this.getBackButton(), this.getPaneButton()] : this.getDrawerButton()}
</div>
);
}
@ -46,7 +46,7 @@ export default class Navigation extends Component {
* @protected
*/
getBackButton() {
const {history} = app;
const { history } = app;
const previous = history.getPrevious() || {};
return LinkButton.component({
@ -55,11 +55,11 @@ export default class Navigation extends Component {
icon: 'fas fa-chevron-left',
title: previous.title,
config: () => {},
onclick: e => {
onclick: (e) => {
if (e.shiftKey || e.ctrlKey || e.metaKey || e.which === 2) return;
e.preventDefault();
history.back();
}
},
});
}
@ -70,14 +70,14 @@ export default class Navigation extends Component {
* @protected
*/
getPaneButton() {
const {pane} = app;
const { pane } = app;
if (!pane || !pane.active) return '';
return Button.component({
className: 'Button Button--icon Navigation-pin' + (pane.pinned ? ' active' : ''),
onclick: pane.togglePinned.bind(pane),
icon: 'fas fa-thumbtack'
icon: 'fas fa-thumbtack',
});
}
@ -90,17 +90,16 @@ export default class Navigation extends Component {
getDrawerButton() {
if (!this.props.drawer) return '';
const {drawer} = app;
const { drawer } = app;
const user = app.session.user;
return Button.component({
className: 'Button Button--icon Navigation-drawer' +
(user && user.newNotificationCount() ? ' new' : ''),
onclick: e => {
className: 'Button Button--icon Navigation-drawer' + (user && user.newNotificationCount() ? ' new' : ''),
onclick: (e) => {
e.stopPropagation();
drawer.show();
},
icon: 'fas fa-bars'
icon: 'fas fa-bars',
});
}
}

View File

@ -6,9 +6,7 @@ export default class RequestErrorModal extends Modal {
}
title() {
return this.props.error.xhr
? this.props.error.xhr.status+' '+this.props.error.xhr.statusText
: '';
return this.props.error.xhr ? this.props.error.xhr.status + ' ' + this.props.error.xhr.statusText : '';
}
content() {
@ -20,11 +18,15 @@ export default class RequestErrorModal extends Modal {
responseText = this.props.error.responseText;
}
return <div className="Modal-body">
return (
<div className="Modal-body">
<pre>
{this.props.error.options.method} {this.props.error.options.url}<br/><br/>
{this.props.error.options.method} {this.props.error.options.url}
<br />
<br />
{responseText}
</pre>
</div>;
</div>
);
}
}

View File

@ -12,14 +12,21 @@ import icon from '../helpers/icon';
*/
export default class Select extends Component {
view() {
const {options, onchange, value, disabled} = this.props;
const { options, onchange, value, disabled } = this.props;
return (
<span className="Select">
<select className="Select-input FormControl" onchange={onchange ? m.withAttr('value', onchange.bind(this)) : undefined} value={value} disabled={disabled}>
{Object.keys(options).map(key => <option value={key}>{options[key]}</option>)}
<select
className="Select-input FormControl"
onchange={onchange ? m.withAttr('value', onchange.bind(this)) : undefined}
value={value}
disabled={disabled}
>
{Object.keys(options).map((key) => (
<option value={key}>{options[key]}</option>
))}
</select>
{icon('fas fa-sort', {className: 'Select-caret'})}
{icon('fas fa-sort', { className: 'Select-caret' })}
</span>
);
}

View File

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

View File

@ -5,7 +5,7 @@ import Component from '../Component';
*/
class Separator extends Component {
view() {
return <li className="Dropdown-separator"/>;
return <li className="Dropdown-separator" />;
}
}

View File

@ -24,12 +24,10 @@ export default class SplitDropdown extends Dropdown {
return [
Button.component(buttonProps),
<button
className={'Dropdown-toggle Button Button--icon ' + this.props.buttonClassName}
data-toggle="dropdown">
{icon(this.props.icon, {className: 'Button-icon'})}
{icon('fas fa-caret-down', {className: 'Button-caret'})}
</button>
<button className={'Dropdown-toggle Button Button--icon ' + this.props.buttonClassName} data-toggle="dropdown">
{icon(this.props.icon, { className: 'Button-icon' })}
{icon('fas fa-caret-down', { className: 'Button-caret' })}
</button>,
];
}

View File

@ -21,7 +21,7 @@
export function extend(object, method, callback) {
const original = object[method];
object[method] = function(...args) {
object[method] = function (...args) {
const value = original ? original.apply(this, args) : undefined;
callback.apply(this, [value].concat(args));
@ -57,7 +57,7 @@ export function extend(object, method, callback) {
export function override(object, method, newMethod) {
const original = object[method];
object[method] = function(...args) {
object[method] = function (...args) {
return newMethod.apply(this, [original.bind(this)].concat(args));
};

View File

@ -34,8 +34,8 @@ export default class Routes {
const model = app.store.models[this.type];
this.attributes.forEach(name => model.prototype[name] = model.attribute(name));
this.hasOnes.forEach(name => model.prototype[name] = model.hasOne(name));
this.hasManys.forEach(name => model.prototype[name] = model.hasMany(name));
this.attributes.forEach((name) => (model.prototype[name] = model.attribute(name)));
this.hasOnes.forEach((name) => (model.prototype[name] = model.hasOne(name)));
this.hasManys.forEach((name) => (model.prototype[name] = model.hasMany(name)));
}
}

View File

@ -25,11 +25,11 @@ export default function avatar(user, attrs = {}) {
if (hasTitle) attrs.title = attrs.title || username;
if (avatarUrl) {
return <img {...attrs} src={avatarUrl}/>;
return <img {...attrs} src={avatarUrl} />;
}
content = username.charAt(0).toUpperCase();
attrs.style = {background: user.color()};
attrs.style = { background: user.color() };
}
return <span {...attrs}>{content}</span>;

View File

@ -11,5 +11,9 @@ export default function fullTime(time) {
const datetime = mo.format();
const full = mo.format('LLLL');
return <time pubdate datetime={datetime}>{full}</time>;
return (
<time pubdate datetime={datetime}>
{full}
</time>
);
}

View File

@ -15,5 +15,9 @@ export default function humanTime(time) {
const full = mo.format('LLLL');
const ago = humanTimeUtil(time);
return <time pubdate datetime={datetime} title={full} data-humantime>{ago}</time>;
return (
<time pubdate datetime={datetime} title={full} data-humantime>
{ago}
</time>
);
}

View File

@ -8,5 +8,5 @@
export default function icon(fontClass, attrs = {}) {
attrs.className = 'icon ' + fontClass + ' ' + (attrs.className || '');
return <i {...attrs}/>;
return <i {...attrs} />;
}

View File

@ -29,7 +29,7 @@ function withoutUnnecessarySeparators(items) {
export default function listItems(items) {
if (!(items instanceof Array)) items = [items];
return withoutUnnecessarySeparators(items).map(item => {
return withoutUnnecessarySeparators(items).map((item) => {
const isListItem = item.component && item.component.isListItem;
const active = item.component && item.component.isActive && item.component.isActive(item.props);
const className = item.props ? item.props.itemClassName : item.itemClassName;
@ -39,15 +39,12 @@ export default function listItems(items) {
item.attrs.key = item.attrs.key || item.itemName;
}
return isListItem
? item
: <li className={classList([
(item.itemName ? 'item-' + item.itemName : ''),
className,
(active ? 'active' : '')
])}
key={item.itemName}>
return isListItem ? (
item
) : (
<li className={classList([item.itemName ? 'item-' + item.itemName : '', className, active ? 'active' : ''])} key={item.itemName}>
{item}
</li>;
</li>
);
});
}

View File

@ -13,7 +13,7 @@ export default function punctuateSeries(items) {
if (items.length === 2) {
return app.translator.trans('core.lib.series.two_text', {
first: items[0],
second: items[1]
second: items[1],
});
} else if (items.length >= 3) {
// If there are three or more items, we will join all but the first and
@ -27,7 +27,7 @@ export default function punctuateSeries(items) {
return app.translator.trans('core.lib.series.three_text', {
first: items[0],
second,
third: items[items.length - 1]
third: items[items.length - 1],
});
}

View File

@ -19,18 +19,18 @@ Object.assign(Discussion.prototype, {
lastPostNumber: Model.attribute('lastPostNumber'),
commentCount: Model.attribute('commentCount'),
replyCount: computed('commentCount', commentCount => Math.max(0, commentCount - 1)),
replyCount: computed('commentCount', (commentCount) => Math.max(0, commentCount - 1)),
posts: Model.hasMany('posts'),
mostRelevantPost: Model.hasOne('mostRelevantPost'),
lastReadAt: Model.attribute('lastReadAt', Model.transformDate),
lastReadPostNumber: Model.attribute('lastReadPostNumber'),
isUnread: computed('unreadCount', unreadCount => !!unreadCount),
isRead: computed('unreadCount', unreadCount => app.session.user && !unreadCount),
isUnread: computed('unreadCount', (unreadCount) => !!unreadCount),
isRead: computed('unreadCount', (unreadCount) => app.session.user && !unreadCount),
hiddenAt: Model.attribute('hiddenAt', Model.transformDate),
hiddenUser: Model.hasOne('hiddenUser'),
isHidden: computed('hiddenAt', hiddenAt => !!hiddenAt),
isHidden: computed('hiddenAt', (hiddenAt) => !!hiddenAt),
canReply: Model.attribute('canReply'),
canRename: Model.attribute('canRename'),
@ -84,7 +84,7 @@ Object.assign(Discussion.prototype, {
const items = new ItemList();
if (this.isHidden()) {
items.add('hidden', <Badge type="hidden" icon="fas fa-trash" label={app.translator.trans('core.lib.badge.hidden_tooltip')}/>);
items.add('hidden', <Badge type="hidden" icon="fas fa-trash" label={app.translator.trans('core.lib.badge.hidden_tooltip')} />);
}
return items;
@ -99,6 +99,6 @@ Object.assign(Discussion.prototype, {
postIds() {
const posts = this.data.relationships.posts;
return posts ? posts.data.map(link => link.id) : [];
}
return posts ? posts.data.map((link) => link.id) : [];
},
});

View File

@ -6,7 +6,7 @@ Object.assign(Group.prototype, {
nameSingular: Model.attribute('nameSingular'),
namePlural: Model.attribute('namePlural'),
color: Model.attribute('color'),
icon: Model.attribute('icon')
icon: Model.attribute('icon'),
});
Group.ADMINISTRATOR_ID = '1';

View File

@ -11,5 +11,5 @@ Object.assign(Notification.prototype, {
user: Model.hasOne('user'),
fromUser: Model.hasOne('fromUser'),
subject: Model.hasOne('subject')
subject: Model.hasOne('subject'),
});

View File

@ -17,13 +17,13 @@ Object.assign(Post.prototype, {
editedAt: Model.attribute('editedAt', Model.transformDate),
editedUser: Model.hasOne('editedUser'),
isEdited: computed('editedAt', editedAt => !!editedAt),
isEdited: computed('editedAt', (editedAt) => !!editedAt),
hiddenAt: Model.attribute('hiddenAt', Model.transformDate),
hiddenUser: Model.hasOne('hiddenUser'),
isHidden: computed('hiddenAt', hiddenAt => !!hiddenAt),
isHidden: computed('hiddenAt', (hiddenAt) => !!hiddenAt),
canEdit: Model.attribute('canEdit'),
canHide: Model.attribute('canHide'),
canDelete: Model.attribute('canDelete')
canDelete: Model.attribute('canDelete'),
});

View File

@ -32,7 +32,7 @@ Object.assign(User.prototype, {
canDelete: Model.attribute('canDelete'),
avatarColor: null,
color: computed('username', 'avatarUrl', 'avatarColor', function(username, avatarUrl, avatarColor) {
color: computed('username', 'avatarUrl', 'avatarColor', function (username, avatarUrl, avatarColor) {
// If we've already calculated and cached the dominant color of the user's
// avatar, then we can return that in RGB format. If we haven't, we'll want
// to calculate it. Unless the user doesn't have an avatar, in which case
@ -67,8 +67,8 @@ Object.assign(User.prototype, {
const groups = this.groups();
if (groups) {
groups.forEach(group => {
items.add('group' + group.id(), GroupBadge.component({group}));
groups.forEach((group) => {
items.add('group' + group.id(), GroupBadge.component({ group }));
});
}
@ -85,7 +85,7 @@ Object.assign(User.prototype, {
const image = new Image();
const user = this;
image.onload = function() {
image.onload = function () {
const colorThief = new ColorThief();
user.avatarColor = colorThief.getColor(this);
user.freshness = new Date();
@ -106,6 +106,6 @@ Object.assign(User.prototype, {
Object.assign(preferences, newPreferences);
return this.save({preferences});
}
return this.save({ preferences });
},
});

View File

@ -7,7 +7,7 @@ export default class Drawer {
constructor() {
// Set up an event handler so that whenever the content area is tapped,
// the drawer will close.
$('#content').click(e => {
$('#content').click((e) => {
if (this.isOpen()) {
e.preventDefault();
this.hide();

View File

@ -28,7 +28,7 @@ export default class ItemList {
*/
isEmpty() {
for (const i in this.items) {
if(this.items.hasOwnProperty(i)) {
if (this.items.hasOwnProperty(i)) {
return false;
}
}
@ -147,14 +147,15 @@ export default class ItemList {
}
}
return items.sort((a, b) => {
return items
.sort((a, b) => {
if (a.priority === b.priority) {
return a.key - b.key;
} else if (a.priority > b.priority) {
return -1;
}
return 1;
}).map(item => item.content);
})
.map((item) => item.content);
}
}

View File

@ -1,9 +1,10 @@
const later = window.requestAnimationFrame ||
const later =
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.msRequestAnimationFrame ||
window.oRequestAnimationFrame ||
(callback => window.setTimeout(callback, 1000 / 60));
((callback) => window.setTimeout(callback, 1000 / 60));
/**
* The `ScrollListener` class sets up a listener that handles window scroll
@ -57,10 +58,7 @@ export default class ScrollListener {
*/
start() {
if (!this.active) {
window.addEventListener(
'scroll',
this.active = this.loop.bind(this)
);
window.addEventListener('scroll', (this.active = this.loop.bind(this)));
}
}

View File

@ -44,7 +44,7 @@ export default class SubtreeRetainer {
}
});
return needsRebuild ? false : {subtree: 'retain'};
return needsRebuild ? false : { subtree: 'retain' };
}
/**

View File

@ -13,7 +13,7 @@ export default function classList(classes) {
let classNames;
if (classes instanceof Array) {
classNames = classes.filter(name => name);
classNames = classes.filter((name) => name);
} else {
classNames = [];

View File

@ -14,12 +14,12 @@ export default function computed(...dependentKeys) {
const dependentValues = {};
let computedValue;
return function() {
return function () {
let recompute = false;
// Read all of the dependent values. If any of them have changed since last
// time, then we'll want to recompute our output.
keys.forEach(key => {
keys.forEach((key) => {
const value = typeof this[key] === 'function' ? this[key]() : this[key];
if (dependentValues[key] !== value) {
@ -29,7 +29,10 @@ export default function computed(...dependentKeys) {
});
if (recompute) {
computedValue = compute.apply(this, keys.map(key => dependentValues[key]));
computedValue = compute.apply(
this,
keys.map((key) => dependentValues[key])
);
}
return computedValue;

View File

@ -34,7 +34,7 @@ export default {
* @public
*/
trigger(event, ...args) {
this.getHandlers(event).forEach(handler => handler.apply(this, args));
this.getHandlers(event).forEach((handler) => handler.apply(this, args));
},
/**
@ -55,7 +55,7 @@ export default {
* @param {function} handler The function to handle the event.
*/
one(event, handler) {
const wrapper = function() {
const wrapper = function () {
handler.apply(this, arguments);
this.off(event, wrapper);
@ -77,5 +77,5 @@ export default {
if (index !== -1) {
handlers.splice(index, 1);
}
}
}
},
};

View File

@ -6,7 +6,7 @@
*/
export default function extractText(vdom) {
if (vdom instanceof Array) {
return vdom.map(element => extractText(element)).join('');
return vdom.map((element) => extractText(element)).join('');
} else if (typeof vdom === 'object' && vdom !== null) {
return extractText(vdom.children);
} else {

View File

@ -33,4 +33,4 @@ export default function humanTime(time) {
}
return ago;
};
}

View File

@ -1,7 +1,7 @@
import humanTimeUtil from './humanTime';
function updateHumanTimes() {
$('[data-humantime]').each(function() {
$('[data-humantime]').each(function () {
const $this = $(this);
const ago = humanTimeUtil($this.attr('datetime'));

View File

@ -12,7 +12,7 @@
export default function mixin(Parent, ...mixins) {
class Mixed extends Parent {}
mixins.forEach(object => {
mixins.forEach((object) => {
Object.assign(Mixed.prototype, object);
});

View File

@ -3,11 +3,11 @@ import Component from '../Component';
export default function patchMithril(global) {
const mo = global.m;
const m = function(comp, ...args) {
const m = function (comp, ...args) {
if (comp.prototype && comp.prototype instanceof Component) {
let children = args.slice(1);
if (children.length === 1 && Array.isArray(children[0])) {
children = children[0]
children = children[0];
}
return comp.component(args[0], children);
@ -29,14 +29,14 @@ export default function patchMithril(global) {
return node;
};
Object.keys(mo).forEach(key => m[key] = mo[key]);
Object.keys(mo).forEach((key) => (m[key] = mo[key]));
/**
* Redraw only if not in the middle of a computation (e.g. a route change).
*
* @return {void}
*/
m.lazyRedraw = function() {
m.lazyRedraw = function () {
m.startComputation();
m.endComputation();
};

View File

@ -7,9 +7,7 @@
* @return {String}
*/
export function truncate(string, length, start = 0) {
return (start > 0 ? '...' : '') +
string.substring(start, start + length) +
(string.length > start + length ? '...' : '');
return (start > 0 ? '...' : '') + string.substring(start, start + length) + (string.length > start + length ? '...' : '');
}
/**
@ -24,7 +22,8 @@ export function truncate(string, length, start = 0) {
* @return {String}
*/
export function slug(string) {
return string.toLowerCase()
return string
.toLowerCase()
.replace(/[^a-z0-9]/gi, '-')
.replace(/-+/g, '-')
.replace(/-$|^-/g, '');
@ -38,9 +37,7 @@ export function slug(string) {
* @return {String}
*/
export function getPlainContent(string) {
const html = string
.replace(/(<\/p>|<br>)/g, '$1 &nbsp;')
.replace(/<img\b[^>]*>/ig, ' ');
const html = string.replace(/(<\/p>|<br>)/g, '$1 &nbsp;').replace(/<img\b[^>]*>/gi, ' ');
const dom = $('<div/>').html(html);

View File

@ -10,18 +10,42 @@ function hsvToRgb(h, s, v) {
const t = v * (1 - (1 - f) * s);
switch (i % 6) {
case 0: r = v; g = t; b = p; break;
case 1: r = q; g = v; b = p; break;
case 2: r = p; g = v; b = t; break;
case 3: r = p; g = q; b = v; break;
case 4: r = t; g = p; b = v; break;
case 5: r = v; g = p; b = q; break;
case 0:
r = v;
g = t;
b = p;
break;
case 1:
r = q;
g = v;
b = p;
break;
case 2:
r = p;
g = v;
b = t;
break;
case 3:
r = p;
g = q;
b = v;
break;
case 4:
r = t;
g = p;
b = v;
break;
case 5:
r = v;
g = p;
b = q;
break;
}
return {
r: Math.floor(r * 255),
g: Math.floor(g * 255),
b: Math.floor(b * 255)
b: Math.floor(b * 255),
};
}

View File

@ -22,7 +22,7 @@ export default class ForumApplication extends Application {
* @type {Object}
*/
notificationComponents = {
discussionRenamed: DiscussionRenamedNotification
discussionRenamed: DiscussionRenamedNotification,
};
/**
* A map of post types to their components.
@ -31,7 +31,7 @@ export default class ForumApplication extends Application {
*/
postComponents = {
comment: CommentPost,
discussionRenamed: DiscussionRenamedPost
discussionRenamed: DiscussionRenamedPost,
};
/**
@ -87,7 +87,7 @@ export default class ForumApplication extends Application {
this.routes[defaultAction].path = '/';
this.history.push(defaultAction, this.translator.trans('core.forum.header.back_to_index_tooltip'), '/');
m.mount(document.getElementById('app-navigation'), Navigation.component({className: 'App-backControl', drawer: true}));
m.mount(document.getElementById('app-navigation'), Navigation.component({ className: 'App-backControl', drawer: true }));
m.mount(document.getElementById('header-navigation'), Navigation.component());
m.mount(document.getElementById('header-primary'), HeaderPrimary.component());
m.mount(document.getElementById('header-secondary'), HeaderSecondary.component());
@ -102,7 +102,7 @@ export default class ForumApplication extends Application {
// Route the home link back home when clicked. We do not want it to register
// if the user is opening it in a new tab, however.
$('#home-link').click(e => {
$('#home-link').click((e) => {
if (e.ctrlKey || e.metaKey || e.which === 2) return;
e.preventDefault();
app.history.home();
@ -123,9 +123,11 @@ export default class ForumApplication extends Application {
* @return {Boolean}
*/
composingReplyTo(discussion) {
return this.composer.component instanceof ReplyComposer &&
return (
this.composer.component instanceof ReplyComposer &&
this.composer.component.props.discussion === discussion &&
this.composer.position !== Composer.PositionEnum.HIDDEN;
this.composer.position !== Composer.PositionEnum.HIDDEN
);
}
/**
@ -135,8 +137,7 @@ export default class ForumApplication extends Application {
* @return {Boolean}
*/
viewingDiscussion(discussion) {
return this.current instanceof DiscussionPage &&
this.current.discussion === discussion;
return this.current instanceof DiscussionPage && this.current.discussion === discussion;
}
/**

View File

@ -134,6 +134,6 @@ export default Object.assign(compat, {
'components/DiscussionListItem': DiscussionListItem,
'components/LoadingPost': LoadingPost,
'components/PostsUserPage': PostsUserPage,
'routes': routes,
'ForumApplication': ForumApplication
routes: routes,
ForumApplication: ForumApplication,
});

View File

@ -44,7 +44,8 @@ export default class AvatarEditor extends Component {
return (
<div className={'AvatarEditor Dropdown ' + this.props.className + (this.loading ? ' loading' : '') + (this.isDraggedOver ? ' dragover' : '')}>
{avatar(user)}
<a className={ user.avatarUrl() ? "Dropdown-toggle" : "Dropdown-toggle AvatarEditor--noAvatar" }
<a
className={user.avatarUrl() ? 'Dropdown-toggle' : 'Dropdown-toggle AvatarEditor--noAvatar'}
title={app.translator.trans('core.forum.user.avatar_upload_tooltip')}
data-toggle="dropdown"
onclick={this.quickUpload.bind(this)}
@ -52,12 +53,11 @@ export default class AvatarEditor extends Component {
ondragenter={this.enableDragover.bind(this)}
ondragleave={this.disableDragover.bind(this)}
ondragend={this.disableDragover.bind(this)}
ondrop={this.dropUpload.bind(this)}>
{this.loading ? LoadingIndicator.component() : (user.avatarUrl() ? icon('fas fa-pencil-alt') : icon('fas fa-plus-circle'))}
ondrop={this.dropUpload.bind(this)}
>
{this.loading ? LoadingIndicator.component() : user.avatarUrl() ? icon('fas fa-pencil-alt') : icon('fas fa-plus-circle')}
</a>
<ul className="Dropdown-menu Menu">
{listItems(this.controlItems().toArray())}
</ul>
<ul className="Dropdown-menu Menu">{listItems(this.controlItems().toArray())}</ul>
</div>
);
}
@ -70,19 +70,21 @@ export default class AvatarEditor extends Component {
controlItems() {
const items = new ItemList();
items.add('upload',
items.add(
'upload',
Button.component({
icon: 'fas fa-upload',
children: app.translator.trans('core.forum.user.avatar_upload_button'),
onclick: this.openPicker.bind(this)
onclick: this.openPicker.bind(this),
})
);
items.add('remove',
items.add(
'remove',
Button.component({
icon: 'fas fa-times',
children: app.translator.trans('core.forum.user.avatar_remove_button'),
onclick: this.remove.bind(this)
onclick: this.remove.bind(this),
})
);
@ -150,7 +152,11 @@ export default class AvatarEditor extends Component {
const user = this.props.user;
const $input = $('<input type="file">');
$input.appendTo('body').hide().click().on('input', e => {
$input
.appendTo('body')
.hide()
.click()
.on('input', (e) => {
this.upload($(e.target)[0].files[0]);
});
}
@ -170,15 +176,14 @@ export default class AvatarEditor extends Component {
this.loading = true;
m.redraw();
app.request({
app
.request({
method: 'POST',
url: app.forum.attribute('apiUrl') + '/users/' + user.id() + '/avatar',
serialize: raw => raw,
data
}).then(
this.success.bind(this),
this.failure.bind(this)
);
serialize: (raw) => raw,
data,
})
.then(this.success.bind(this), this.failure.bind(this));
}
/**
@ -190,13 +195,12 @@ export default class AvatarEditor extends Component {
this.loading = true;
m.redraw();
app.request({
app
.request({
method: 'DELETE',
url: app.forum.attribute('apiUrl') + '/users/' + user.id() + '/avatar'
}).then(
this.success.bind(this),
this.failure.bind(this)
);
url: app.forum.attribute('apiUrl') + '/users/' + user.id() + '/avatar',
})
.then(this.success.bind(this), this.failure.bind(this));
}
/**

View File

@ -44,7 +44,9 @@ export default class ChangeEmailModal extends Modal {
return (
<div className="Modal-body">
<div className="Form Form--centered">
<p className="helpText">{app.translator.trans('core.forum.change_email.confirmation_message', {email: <strong>{this.email()}</strong>})}</p>
<p className="helpText">
{app.translator.trans('core.forum.change_email.confirmation_message', { email: <strong>{this.email()}</strong> })}
</p>
<div className="Form-group">
<Button className="Button Button--primary Button--block" onclick={this.hide.bind(this)}>
{app.translator.trans('core.forum.change_email.dismiss_button')}
@ -59,23 +61,31 @@ export default class ChangeEmailModal extends Modal {
<div className="Modal-body">
<div className="Form Form--centered">
<div className="Form-group">
<input type="email" name="email" className="FormControl"
<input
type="email"
name="email"
className="FormControl"
placeholder={app.session.user.email()}
bidi={this.email}
disabled={this.loading}/>
disabled={this.loading}
/>
</div>
<div className="Form-group">
<input type="password" name="password" className="FormControl"
<input
type="password"
name="password"
className="FormControl"
placeholder={app.translator.trans('core.forum.change_email.confirm_password_placeholder')}
bidi={this.password}
disabled={this.loading}/>
disabled={this.loading}
/>
</div>
<div className="Form-group">
{Button.component({
className: 'Button Button--primary Button--block',
type: 'submit',
loading: this.loading,
children: app.translator.trans('core.forum.change_email.submit_button')
children: app.translator.trans('core.forum.change_email.submit_button'),
})}
</div>
</div>
@ -97,11 +107,15 @@ export default class ChangeEmailModal extends Modal {
this.loading = true;
app.session.user.save({email: this.email()}, {
app.session.user
.save(
{ email: this.email() },
{
errorHandler: this.onerror.bind(this),
meta: {password: this.password()}
})
.then(() => this.success = true)
meta: { password: this.password() },
}
)
.then(() => (this.success = true))
.catch(() => {})
.then(this.loaded.bind(this));
}

View File

@ -24,7 +24,7 @@ export default class ChangePasswordModal extends Modal {
className: 'Button Button--primary Button--block',
type: 'submit',
loading: this.loading,
children: app.translator.trans('core.forum.change_password.send_button')
children: app.translator.trans('core.forum.change_password.send_button'),
})}
</div>
</div>
@ -37,13 +37,12 @@ export default class ChangePasswordModal extends Modal {
this.loading = true;
app.request({
app
.request({
method: 'POST',
url: app.forum.attribute('apiUrl') + '/forgot',
data: {email: app.session.user.email()}
}).then(
this.hide.bind(this),
this.loaded.bind(this)
);
data: { email: app.session.user.email() },
})
.then(this.hide.bind(this), this.loaded.bind(this));
}
}

View File

@ -33,7 +33,7 @@ export default class CommentPost extends Post {
// Create an instance of the component that displays the post's author so
// that we can force the post to rerender when the user card is shown.
this.postUser = new PostUser({post: this.props.post});
this.postUser = new PostUser({ post: this.props.post });
this.subtree.check(
() => this.postUser.cardVisible,
() => this.isEditing()
@ -44,13 +44,13 @@ export default class CommentPost extends Post {
// Note: we avoid using JSX for the <ul> below because it results in some
// weirdness in Mithril.js 0.1.x (see flarum/core#975). This workaround can
// be reverted when we upgrade to Mithril 1.0.
return super.content().concat([
return super
.content()
.concat([
<header className="Post-header">{m('ul', listItems(this.headerItems().toArray()))}</header>,
<div className="Post-body">
{this.isEditing()
? <div className="Post-preview" config={this.configPreview.bind(this)}/>
: m.trust(this.props.post.contentHtml())}
</div>
{this.isEditing() ? <div className="Post-preview" config={this.configPreview.bind(this)} /> : m.trust(this.props.post.contentHtml())}
</div>,
]);
}
@ -63,7 +63,7 @@ export default class CommentPost extends Post {
// all of the <script> tags in the content and evaluate them. This is
// necessary because TextFormatter outputs them for e.g. syntax highlighting.
if (context.contentHtml !== contentHtml) {
this.$('.Post-body script').each(function() {
this.$('.Post-body script').each(function () {
eval.call(window, $(this).text());
});
}
@ -72,20 +72,22 @@ export default class CommentPost extends Post {
}
isEditing() {
return app.composer.component instanceof EditPostComposer &&
app.composer.component.props.post === this.props.post;
return app.composer.component instanceof EditPostComposer && app.composer.component.props.post === this.props.post;
}
attrs() {
const post = this.props.post;
const attrs = super.attrs();
attrs.className = (attrs.className || '') + ' ' + classList({
'CommentPost': true,
attrs.className =
(attrs.className || '') +
' ' +
classList({
CommentPost: true,
'Post--hidden': post.isHidden(),
'Post--edited': post.isEdited(),
'revealContent': this.revealContent,
'editing': this.isEditing()
revealContent: this.revealContent,
editing: this.isEditing(),
});
return attrs;
@ -127,7 +129,7 @@ export default class CommentPost extends Post {
headerItems() {
const items = new ItemList();
const post = this.props.post;
const props = {post};
const props = { post };
items.add('user', this.postUser.render(), 100);
items.add('meta', PostMeta.component(props));
@ -139,13 +141,14 @@ export default class CommentPost extends Post {
// If the post is hidden, add a button that allows toggling the visibility
// of the post's content.
if (post.isHidden()) {
items.add('toggle', (
items.add(
'toggle',
Button.component({
className: 'Button Button--default Button--more',
icon: 'fas fa-ellipsis-h',
onclick: this.toggleContent.bind(this)
onclick: this.toggleContent.bind(this),
})
));
);
}
return items;

View File

@ -36,10 +36,10 @@ class Composer extends Component {
view() {
const classes = {
'normal': this.position === Composer.PositionEnum.NORMAL,
'minimized': this.position === Composer.PositionEnum.MINIMIZED,
'fullScreen': this.position === Composer.PositionEnum.FULLSCREEN,
'active': this.active
normal: this.position === Composer.PositionEnum.NORMAL,
minimized: this.position === Composer.PositionEnum.MINIMIZED,
fullScreen: this.position === Composer.PositionEnum.FULLSCREEN,
active: this.active,
};
classes.visible = classes.normal || classes.minimized || classes.fullScreen;
@ -52,7 +52,7 @@ class Composer extends Component {
return (
<div className={'Composer ' + classList(classes)}>
<div className="Composer-handle" config={this.configHandle.bind(this)}/>
<div className="Composer-handle" config={this.configHandle.bind(this)} />
<ul className="Composer-controls">{listItems(this.controlItems().toArray())}</ul>
<div className="Composer-content" onclick={showIfMinimized}>
{this.component ? this.component.render() : ''}
@ -77,7 +77,7 @@ class Composer extends Component {
// Whenever any of the inputs inside the composer are have focus, we want to
// add a class to the composer to draw attention to it.
this.$().on('focus blur', ':input', e => {
this.$().on('focus blur', ':input', (e) => {
this.active = e.type === 'focusin';
m.redraw();
});
@ -94,18 +94,18 @@ class Composer extends Component {
const handlers = {};
$(window).on('resize', handlers.onresize = this.updateHeight.bind(this)).resize();
$(window)
.on('resize', (handlers.onresize = this.updateHeight.bind(this)))
.resize();
$(document)
.on('mousemove', handlers.onmousemove = this.onmousemove.bind(this))
.on('mouseup', handlers.onmouseup = this.onmouseup.bind(this));
.on('mousemove', (handlers.onmousemove = this.onmousemove.bind(this)))
.on('mouseup', (handlers.onmouseup = this.onmouseup.bind(this)));
context.onunload = () => {
$(window).off('resize', handlers.onresize);
$(document)
.off('mousemove', handlers.onmousemove)
.off('mouseup', handlers.onmouseup);
$(document).off('mousemove', handlers.onmousemove).off('mouseup', handlers.onmouseup);
};
}
@ -121,9 +121,10 @@ class Composer extends Component {
const composer = this;
$(element).css('cursor', 'row-resize')
.bind('dragstart mousedown', e => e.preventDefault())
.mousedown(function(e) {
$(element)
.css('cursor', 'row-resize')
.bind('dragstart mousedown', (e) => e.preventDefault())
.mousedown(function (e) {
composer.mouseStart = e.clientY;
composer.heightStart = composer.$().height();
composer.handle = $(this);
@ -191,15 +192,12 @@ class Composer extends Component {
* scrolled right to the bottom.
*/
updateBodyPadding() {
const visible = this.position !== Composer.PositionEnum.HIDDEN &&
this.position !== Composer.PositionEnum.MINIMIZED &&
this.$().css('position') !== 'absolute';
const visible =
this.position !== Composer.PositionEnum.HIDDEN && this.position !== Composer.PositionEnum.MINIMIZED && this.$().css('position') !== 'absolute';
const paddingBottom = visible
? this.computedHeight() - parseInt($('#app').css('padding-bottom'), 10)
: 0;
const paddingBottom = visible ? this.computedHeight() - parseInt($('#app').css('padding-bottom'), 10) : 0;
$('#content').css({paddingBottom});
$('#content').css({ paddingBottom });
}
/**
@ -289,12 +287,12 @@ class Composer extends Component {
const newHeight = $composer.outerHeight();
if (oldPosition === Composer.PositionEnum.HIDDEN) {
$composer.css({bottom: -newHeight, height: newHeight});
$composer.css({ bottom: -newHeight, height: newHeight });
} else {
$composer.css({height: oldHeight});
$composer.css({ height: oldHeight });
}
$composer.animate({bottom: 0, height: newHeight}, 'fast', () => this.component.focus());
$composer.animate({ bottom: 0, height: newHeight }, 'fast', () => this.component.focus());
this.updateBodyPadding();
$(window).scrollTop(scrollTop);
@ -304,9 +302,7 @@ class Composer extends Component {
* Show the Composer backdrop.
*/
showBackdrop() {
this.$backdrop = $('<div/>')
.addClass('composer-backdrop')
.appendTo('body');
this.$backdrop = $('<div/>').addClass('composer-backdrop').appendTo('body');
}
/**
@ -346,7 +342,7 @@ class Composer extends Component {
// Animate the composer sliding down off the bottom edge of the viewport.
// Only when the animation is completed, update the Composer state flag and
// other elements on the page.
$composer.stop(true).animate({bottom: -$composer.height()}, 'fast', () => {
$composer.stop(true).animate({ bottom: -$composer.height() }, 'fast', () => {
this.position = Composer.PositionEnum.HIDDEN;
this.clear();
m.redraw();
@ -421,32 +417,44 @@ class Composer extends Component {
const items = new ItemList();
if (this.position === Composer.PositionEnum.FULLSCREEN) {
items.add('exitFullScreen', ComposerButton.component({
items.add(
'exitFullScreen',
ComposerButton.component({
icon: 'fas fa-compress',
title: app.translator.trans('core.forum.composer.exit_full_screen_tooltip'),
onclick: this.exitFullScreen.bind(this)
}));
onclick: this.exitFullScreen.bind(this),
})
);
} else {
if (this.position !== Composer.PositionEnum.MINIMIZED) {
items.add('minimize', ComposerButton.component({
items.add(
'minimize',
ComposerButton.component({
icon: 'fas fa-minus minimize',
title: app.translator.trans('core.forum.composer.minimize_tooltip'),
onclick: this.minimize.bind(this),
itemClassName: 'App-backControl'
}));
itemClassName: 'App-backControl',
})
);
items.add('fullScreen', ComposerButton.component({
items.add(
'fullScreen',
ComposerButton.component({
icon: 'fas fa-expand',
title: app.translator.trans('core.forum.composer.full_screen_tooltip'),
onclick: this.fullScreen.bind(this)
}));
onclick: this.fullScreen.bind(this),
})
);
}
items.add('close', ComposerButton.component({
items.add(
'close',
ComposerButton.component({
icon: 'fas fa-times',
title: app.translator.trans('core.forum.composer.close_tooltip'),
onclick: this.close.bind(this)
}));
onclick: this.close.bind(this),
})
);
}
return items;
@ -524,7 +532,7 @@ Composer.PositionEnum = {
HIDDEN: 'hidden',
NORMAL: 'normal',
MINIMIZED: 'minimized',
FULLSCREEN: 'fullScreen'
FULLSCREEN: 'fullScreen',
};
export default Composer;

View File

@ -47,7 +47,7 @@ export default class ComposerBody extends Component {
placeholder: this.props.placeholder,
onchange: this.content,
onsubmit: this.onsubmit.bind(this),
value: this.content()
value: this.content(),
});
}
@ -57,12 +57,12 @@ export default class ComposerBody extends Component {
return (
<div className={'ComposerBody ' + (this.props.className || '')}>
{avatar(this.props.user, {className: 'ComposerBody-avatar'})}
{avatar(this.props.user, { className: 'ComposerBody-avatar' })}
<div className="ComposerBody-content">
<ul className="ComposerBody-header">{listItems(this.headerItems().toArray())}</ul>
<div className="ComposerBody-editor">{this.editor.render()}</div>
</div>
{LoadingIndicator.component({className: 'ComposerBody-loading' + (this.loading ? ' active' : '')})}
{LoadingIndicator.component({ className: 'ComposerBody-loading' + (this.loading ? ' active' : '') })}
</div>
);
}
@ -100,8 +100,7 @@ export default class ComposerBody extends Component {
*
* @abstract
*/
onsubmit() {
}
onsubmit() {}
/**
* Stop loading.

View File

@ -39,16 +39,19 @@ export default class DiscussionComposer extends ComposerBody {
items.add('title', <h3>{app.translator.trans('core.forum.composer_discussion.title')}</h3>, 100);
items.add('discussionTitle', (
items.add(
'discussionTitle',
<h3>
<input className="FormControl"
<input
className="FormControl"
value={this.title()}
oninput={m.withAttr('value', this.title)}
placeholder={this.props.titlePlaceholder}
disabled={!!this.props.disabled}
onkeydown={this.onkeydown.bind(this)}/>
onkeydown={this.onkeydown.bind(this)}
/>
</h3>
));
);
return items;
}
@ -60,7 +63,8 @@ export default class DiscussionComposer extends ComposerBody {
* @param {Event} e
*/
onkeydown(e) {
if (e.which === 13) { // Return
if (e.which === 13) {
// Return
e.preventDefault();
this.editor.setSelectionRange(0, 0);
}
@ -80,7 +84,7 @@ export default class DiscussionComposer extends ComposerBody {
data() {
return {
title: this.title(),
content: this.content()
content: this.content(),
};
}
@ -89,13 +93,13 @@ export default class DiscussionComposer extends ComposerBody {
const data = this.data();
app.store.createRecord('discussions').save(data).then(
discussion => {
app.store
.createRecord('discussions')
.save(data)
.then((discussion) => {
app.composer.hide();
app.cache.discussionList.refresh();
m.route(app.route.discussion(discussion));
},
this.loaded.bind(this)
);
}, this.loaded.bind(this));
}
}

View File

@ -48,33 +48,27 @@ export default class DiscussionList extends Component {
loading = Button.component({
children: app.translator.trans('core.forum.discussion_list.load_more_button'),
className: 'Button',
onclick: this.loadMore.bind(this)
onclick: this.loadMore.bind(this),
});
}
if (this.discussions.length === 0 && !this.loading) {
const text = app.translator.trans('core.forum.discussion_list.empty_text');
return (
<div className="DiscussionList">
{Placeholder.component({text})}
</div>
);
return <div className="DiscussionList">{Placeholder.component({ text })}</div>;
}
return (
<div className={'DiscussionList'+(this.props.params.q ? ' DiscussionList--searchResults' : '')}>
<div className={'DiscussionList' + (this.props.params.q ? ' DiscussionList--searchResults' : '')}>
<ul className="DiscussionList-discussions">
{this.discussions.map(discussion => {
{this.discussions.map((discussion) => {
return (
<li key={discussion.id()} data-id={discussion.id()}>
{DiscussionListItem.component({discussion, params})}
{DiscussionListItem.component({ discussion, params })}
</li>
);
})}
</ul>
<div className="DiscussionList-loadMore">
{loading}
</div>
<div className="DiscussionList-loadMore">{loading}</div>
</div>
);
}
@ -87,7 +81,7 @@ export default class DiscussionList extends Component {
* @api
*/
requestParams() {
const params = {include: ['user', 'lastPostedUser'], filter: {}};
const params = { include: ['user', 'lastPostedUser'], filter: {} };
params.sort = this.sortMap()[this.props.params.sort];
@ -132,7 +126,7 @@ export default class DiscussionList extends Component {
}
return this.loadResults().then(
results => {
(results) => {
this.discussions = [];
this.parseResults(results);
},
@ -157,7 +151,7 @@ export default class DiscussionList extends Component {
}
const params = this.requestParams();
params.page = {offset};
params.page = { offset };
params.include = params.include.join(',');
return app.store.find('discussions', params);
@ -171,8 +165,7 @@ export default class DiscussionList extends Component {
loadMore() {
this.loading = true;
this.loadResults(this.discussions.length)
.then(this.parseResults.bind(this));
this.loadResults(this.discussions.length).then(this.parseResults.bind(this));
}
/**

View File

@ -47,8 +47,8 @@ export default class DiscussionListItem extends Component {
className: classList([
'DiscussionListItem',
this.active() ? 'active' : '',
this.props.discussion.isHidden() ? 'DiscussionListItem--hidden' : ''
])
this.props.discussion.isHidden() ? 'DiscussionListItem--hidden' : '',
]),
};
}
@ -73,50 +73,56 @@ export default class DiscussionListItem extends Component {
}
const phrase = this.props.params.q;
this.highlightRegExp = new RegExp(phrase+'|'+phrase.trim().replace(/\s+/g, '|'), 'gi');
this.highlightRegExp = new RegExp(phrase + '|' + phrase.trim().replace(/\s+/g, '|'), 'gi');
} else {
jumpTo = Math.min(discussion.lastPostNumber(), (discussion.lastReadPostNumber() || 0) + 1);
}
return (
<div {...attrs}>
{controls.length ? Dropdown.component({
{controls.length
? Dropdown.component({
icon: 'fas fa-ellipsis-v',
children: controls,
className: 'DiscussionListItem-controls',
buttonClassName: 'Button Button--icon Button--flat Slidable-underneath Slidable-underneath--right'
}) : ''}
buttonClassName: 'Button Button--icon Button--flat Slidable-underneath Slidable-underneath--right',
})
: ''}
<a className={'Slidable-underneath Slidable-underneath--left Slidable-underneath--elastic' + (isUnread ? '' : ' disabled')}
onclick={this.markAsRead.bind(this)}>
<a
className={'Slidable-underneath Slidable-underneath--left Slidable-underneath--elastic' + (isUnread ? '' : ' disabled')}
onclick={this.markAsRead.bind(this)}
>
{icon('fas fa-check')}
</a>
<div className={'DiscussionListItem-content Slidable-content' + (isUnread ? ' unread' : '') + (isRead ? ' read' : '')}>
<a href={user ? app.route.user(user) : '#'}
<a
href={user ? app.route.user(user) : '#'}
className="DiscussionListItem-author"
title={extractText(app.translator.trans('core.forum.discussion_list.started_text', {user: user, ago: humanTime(discussion.createdAt())}))}
config={function(element) {
$(element).tooltip({placement: 'right'});
title={extractText(
app.translator.trans('core.forum.discussion_list.started_text', { user: user, ago: humanTime(discussion.createdAt()) })
)}
config={function (element) {
$(element).tooltip({ placement: 'right' });
m.route.apply(this, arguments);
}}>
{avatar(user, {title: ''})}
}}
>
{avatar(user, { title: '' })}
</a>
<ul className="DiscussionListItem-badges badges">
{listItems(discussion.badges().toArray())}
</ul>
<ul className="DiscussionListItem-badges badges">{listItems(discussion.badges().toArray())}</ul>
<a href={app.route.discussion(discussion, jumpTo)}
config={m.route}
className="DiscussionListItem-main">
<a href={app.route.discussion(discussion, jumpTo)} config={m.route} className="DiscussionListItem-main">
<h3 className="DiscussionListItem-title">{highlight(discussion.title(), this.highlightRegExp)}</h3>
<ul className="DiscussionListItem-info">{listItems(this.infoItems().toArray())}</ul>
</a>
<span className="DiscussionListItem-count"
<span
className="DiscussionListItem-count"
onclick={this.markAsRead.bind(this)}
title={showUnread ? app.translator.trans('core.forum.discussion_list.mark_as_read_tooltip') : ''}>
title={showUnread ? app.translator.trans('core.forum.discussion_list.mark_as_read_tooltip') : ''}
>
{abbreviateNumber(discussion[showUnread ? 'unreadCount' : 'replyCount']())}
</span>
</div>
@ -133,8 +139,7 @@ export default class DiscussionListItem extends Component {
if ('ontouchstart' in window) {
const slidableInstance = slidable(this.$().addClass('Slidable'));
this.$('.DiscussionListItem-controls')
.on('hidden.bs.dropdown', () => slidableInstance.reset());
this.$('.DiscussionListItem-controls').on('hidden.bs.dropdown', () => slidableInstance.reset());
}
}
@ -177,7 +182,7 @@ export default class DiscussionListItem extends Component {
const discussion = this.props.discussion;
if (discussion.isUnread()) {
discussion.save({lastReadPostNumber: discussion.lastPostNumber()});
discussion.save({ lastReadPostNumber: discussion.lastPostNumber() });
m.redraw();
}
}
@ -199,10 +204,11 @@ export default class DiscussionListItem extends Component {
items.add('excerpt', excerpt, -100);
}
} else {
items.add('terminalPost',
items.add(
'terminalPost',
TerminalPost.component({
discussion: this.props.discussion,
lastPost: !this.showFirstPost()
lastPost: !this.showFirstPost(),
})
);
}

View File

@ -90,26 +90,26 @@ export default class DiscussionPage extends Page {
return (
<div className="DiscussionPage">
{app.cache.discussionList
? <div className="DiscussionPage-list" config={this.configPane.bind(this)}>
{app.cache.discussionList ? (
<div className="DiscussionPage-list" config={this.configPane.bind(this)}>
{!$('.App-navigation').is(':visible') ? app.cache.discussionList.render() : ''}
</div>
: ''}
) : (
''
)}
<div className="DiscussionPage-discussion">
{discussion
? [
DiscussionHero.component({discussion}),
DiscussionHero.component({ discussion }),
<div className="container">
<nav className="DiscussionPage-nav">
<ul>{listItems(this.sidebarItems().toArray())}</ul>
</nav>
<div className="DiscussionPage-stream">
{this.stream.render()}
</div>
</div>
<div className="DiscussionPage-stream">{this.stream.render()}</div>
</div>,
]
: LoadingIndicator.component({className: 'LoadingIndicator--block'})}
: LoadingIndicator.component({ className: 'LoadingIndicator--block' })}
</div>
</div>
);
@ -140,8 +140,7 @@ export default class DiscussionPage extends Page {
} else {
const params = this.requestParams();
app.store.find('discussions', m.route.param('id').split('-')[0], params)
.then(this.show.bind(this));
app.store.find('discussions', m.route.param('id').split('-')[0], params).then(this.show.bind(this));
}
m.lazyRedraw();
@ -155,7 +154,7 @@ export default class DiscussionPage extends Page {
*/
requestParams() {
return {
page: {near: this.near}
page: { near: this.near },
};
}
@ -182,11 +181,14 @@ export default class DiscussionPage extends Page {
const discussionId = discussion.id();
includedPosts = discussion.payload.included
.filter(record => record.type === 'posts'
&& record.relationships
&& record.relationships.discussion
&& record.relationships.discussion.data.id === discussionId)
.map(record => app.store.getById('posts', record.id))
.filter(
(record) =>
record.type === 'posts' &&
record.relationships &&
record.relationships.discussion &&
record.relationships.discussion.data.id === discussionId
)
.map((record) => app.store.getById('posts', record.id))
.sort((a, b) => a.id() - b.id())
.slice(0, 20);
}
@ -194,7 +196,7 @@ export default class DiscussionPage extends Page {
// Set up the post stream for this discussion, along with the first page of
// posts we want to display. Tell the stream to scroll down and highlight
// the specific post that was routed to.
this.stream = new PostStream({discussion, includedPosts});
this.stream = new PostStream({ discussion, includedPosts });
this.stream.on('positionChanged', this.positionChanged.bind(this));
this.stream.goToNumber(m.route.param('near') || (includedPosts[0] && includedPosts[0].number()), true);
}
@ -219,7 +221,7 @@ export default class DiscussionPage extends Page {
const pane = app.pane;
$list.hover(pane.show.bind(pane), pane.onmouseleave.bind(pane));
const hotEdge = e => {
const hotEdge = (e) => {
if (e.pageX < 10) pane.show();
};
$(document).on('mousemove', hotEdge);
@ -249,19 +251,21 @@ export default class DiscussionPage extends Page {
sidebarItems() {
const items = new ItemList();
items.add('controls',
items.add(
'controls',
SplitDropdown.component({
children: DiscussionControls.controls(this.discussion, this).toArray(),
icon: 'fas fa-ellipsis-v',
className: 'App-primaryControl',
buttonClassName: 'Button--primary'
buttonClassName: 'Button--primary',
})
);
items.add('scrubber',
items.add(
'scrubber',
PostStreamScrubber.component({
stream: this.stream,
className: 'App-titleControl'
className: 'App-titleControl',
}),
-100
);
@ -281,7 +285,7 @@ export default class DiscussionPage extends Page {
// Construct a URL to this discussion with the updated position, then
// replace it into the window's history and our own history stack.
const url = app.route.discussion(discussion, this.near = startNumber);
const url = app.route.discussion(discussion, (this.near = startNumber));
m.route(url, true);
window.history.replaceState(null, document.title, url);
@ -291,7 +295,7 @@ export default class DiscussionPage extends Page {
// If the user hasn't read past here before, then we'll update their read
// state and redraw.
if (app.session.user && endNumber > (discussion.lastReadPostNumber() || 0)) {
discussion.save({lastReadPostNumber: endNumber});
discussion.save({ lastReadPostNumber: endNumber });
m.redraw();
}
}

View File

@ -20,6 +20,6 @@ export default class DiscussionRenamedNotification extends Notification {
}
content() {
return app.translator.trans('core.forum.notifications.discussion_renamed_text', {user: this.props.notification.fromUser()});
return app.translator.trans('core.forum.notifications.discussion_renamed_text', { user: this.props.notification.fromUser() });
}
}

View File

@ -27,8 +27,8 @@ export default class DiscussionRenamedPost extends EventPost {
const newTitle = post.content()[1];
return {
'old': oldTitle,
'new': <strong className="DiscussionRenamedPost-new">{newTitle}</strong>
old: oldTitle,
new: <strong className="DiscussionRenamedPost-new">{newTitle}</strong>,
};
}
}

View File

@ -18,12 +18,12 @@ export default class DiscussionsSearchSource {
this.results[query] = [];
const params = {
filter: {q: query},
page: {limit: 3},
include: 'mostRelevantPost'
filter: { q: query },
page: { limit: 3 },
include: 'mostRelevantPost',
};
return app.store.find('discussions', params).then(results => this.results[query] = results);
return app.store.find('discussions', params).then((results) => (this.results[query] = results));
}
view(query) {
@ -36,11 +36,11 @@ export default class DiscussionsSearchSource {
<li>
{LinkButton.component({
icon: 'fas fa-search',
children: app.translator.trans('core.forum.search.all_discussions_button', {query}),
href: app.route('index', {q: query})
children: app.translator.trans('core.forum.search.all_discussions_button', { query }),
href: app.route('index', { q: query }),
})}
</li>,
results.map(discussion => {
results.map((discussion) => {
const mostRelevantPost = discussion.mostRelevantPost();
return (
@ -51,7 +51,7 @@ export default class DiscussionsSearchSource {
</a>
</li>
);
})
}),
];
}
}

View File

@ -18,8 +18,8 @@ export default class DiscussionsUserPage extends UserPage {
{DiscussionList.component({
params: {
q: 'author:' + this.user.username(),
sort: 'newest'
}
sort: 'newest',
},
})}
</div>
);

View File

@ -22,7 +22,7 @@ export default class EditPostComposer extends ComposerBody {
init() {
super.init();
this.editor.props.preview = e => {
this.editor.props.preview = (e) => {
minimizeComposerIfFullScreen(e);
m.route(app.route.post(this.props.post));
@ -44,20 +44,21 @@ export default class EditPostComposer extends ComposerBody {
const items = super.headerItems();
const post = this.props.post;
const routeAndMinimize = function(element, isInitialized) {
const routeAndMinimize = function (element, isInitialized) {
if (isInitialized) return;
$(element).on('click', minimizeComposerIfFullScreen);
m.route.apply(this, arguments);
};
items.add('title', (
items.add(
'title',
<h3>
{icon('fas fa-pencil-alt')} {' '}
{icon('fas fa-pencil-alt')}{' '}
<a href={app.route.discussion(post.discussion(), post.number())} config={routeAndMinimize}>
{app.translator.trans('core.forum.composer_edit.post_link', {number: post.number(), discussion: post.discussion().title()})}
{app.translator.trans('core.forum.composer_edit.post_link', { number: post.number(), discussion: post.discussion().title() })}
</a>
</h3>
));
);
return items;
}
@ -69,7 +70,7 @@ export default class EditPostComposer extends ComposerBody {
*/
data() {
return {
content: this.content()
content: this.content(),
};
}
@ -78,9 +79,6 @@ export default class EditPostComposer extends ComposerBody {
const data = this.data();
this.props.post.save(data).then(
() => app.composer.hide(),
this.loaded.bind(this)
);
this.props.post.save(data).then(() => app.composer.hide(), this.loaded.bind(this));
}
}

View File

@ -21,9 +21,10 @@ export default class EditUserModal extends Modal {
this.password = m.prop(user.password() || '');
this.groups = {};
app.store.all('groups')
.filter(group => [Group.GUEST_ID, Group.MEMBER_ID].indexOf(group.id()) === -1)
.forEach(group => this.groups[group.id()] = m.prop(user.groups().indexOf(group) !== -1));
app.store
.all('groups')
.filter((group) => [Group.GUEST_ID, Group.MEMBER_ID].indexOf(group.id()) === -1)
.forEach((group) => (this.groups[group.id()] = m.prop(user.groups().indexOf(group) !== -1)));
}
className() {
@ -37,9 +38,7 @@ export default class EditUserModal extends Modal {
content() {
return (
<div className="Modal-body">
<div className="Form">
{this.fields().toArray()}
</div>
<div className="Form">{this.fields().toArray()}</div>
</div>
);
}
@ -47,19 +46,22 @@ export default class EditUserModal extends Modal {
fields() {
const items = new ItemList();
items.add('username', <div className="Form-group">
items.add(
'username',
<div className="Form-group">
<label>{app.translator.trans('core.forum.edit_user.username_heading')}</label>
<input className="FormControl" placeholder={extractText(app.translator.trans('core.forum.edit_user.username_label'))}
bidi={this.username} />
</div>, 40);
<input className="FormControl" placeholder={extractText(app.translator.trans('core.forum.edit_user.username_label'))} bidi={this.username} />
</div>,
40
);
if (app.session.user !== this.props.user) {
items.add('email', <div className="Form-group">
items.add(
'email',
<div className="Form-group">
<label>{app.translator.trans('core.forum.edit_user.email_heading')}</label>
<div>
<input className="FormControl"
placeholder={extractText(app.translator.trans('core.forum.edit_user.email_label'))}
bidi={this.email}/>
<input className="FormControl" placeholder={extractText(app.translator.trans('core.forum.edit_user.email_label'))} bidi={this.email} />
</div>
{!this.isEmailConfirmed() ? (
<div>
@ -67,58 +69,84 @@ export default class EditUserModal extends Modal {
className: 'Button Button--block',
children: app.translator.trans('core.forum.edit_user.activate_button'),
loading: this.loading,
onclick: this.activate.bind(this)
onclick: this.activate.bind(this),
})}
</div>
) : ''}
</div>, 30);
) : (
''
)}
</div>,
30
);
items.add('password', <div className="Form-group">
items.add(
'password',
<div className="Form-group">
<label>{app.translator.trans('core.forum.edit_user.password_heading')}</label>
<div>
<label className="checkbox">
<input type="checkbox" onchange={e => {
<input
type="checkbox"
onchange={(e) => {
this.setPassword(e.target.checked);
m.redraw(true);
if (e.target.checked) this.$('[name=password]').select();
m.redraw.strategy('none');
}}/>
}}
/>
{app.translator.trans('core.forum.edit_user.set_password_label')}
</label>
{this.setPassword() ? (
<input className="FormControl" type="password" name="password"
<input
className="FormControl"
type="password"
name="password"
placeholder={extractText(app.translator.trans('core.forum.edit_user.password_label'))}
bidi={this.password}/>
) : ''}
bidi={this.password}
/>
) : (
''
)}
</div>
</div>, 20);
</div>,
20
);
}
items.add('groups', <div className="Form-group EditUserModal-groups">
items.add(
'groups',
<div className="Form-group EditUserModal-groups">
<label>{app.translator.trans('core.forum.edit_user.groups_heading')}</label>
<div>
{Object.keys(this.groups)
.map(id => app.store.getById('groups', id))
.map(group => (
.map((id) => app.store.getById('groups', id))
.map((group) => (
<label className="checkbox">
<input type="checkbox"
<input
type="checkbox"
bidi={this.groups[group.id()]}
disabled={this.props.user.id() === '1' && group.id() === Group.ADMINISTRATOR_ID} />
{GroupBadge.component({group, label: ''})} {group.nameSingular()}
disabled={this.props.user.id() === '1' && group.id() === Group.ADMINISTRATOR_ID}
/>
{GroupBadge.component({ group, label: '' })} {group.nameSingular()}
</label>
))}
</div>
</div>, 10);
</div>,
10
);
items.add('submit', <div className="Form-group">
items.add(
'submit',
<div className="Form-group">
{Button.component({
className: 'Button Button--primary',
type: 'submit',
loading: this.loading,
children: app.translator.trans('core.forum.edit_user.submit_button')
children: app.translator.trans('core.forum.edit_user.submit_button'),
})}
</div>, -10);
</div>,
-10
);
return items;
}
@ -129,7 +157,8 @@ export default class EditUserModal extends Modal {
username: this.username(),
isEmailConfirmed: true,
};
this.props.user.save(data, {errorHandler: this.onerror.bind(this)})
this.props.user
.save(data, { errorHandler: this.onerror.bind(this) })
.then(() => {
this.isEmailConfirmed(true);
this.loading = false;
@ -143,12 +172,12 @@ export default class EditUserModal extends Modal {
data() {
const groups = Object.keys(this.groups)
.filter(id => this.groups[id]())
.map(id => app.store.getById('groups', id));
.filter((id) => this.groups[id]())
.map((id) => app.store.getById('groups', id));
const data = {
username: this.username(),
relationships: {groups}
relationships: { groups },
};
if (app.session.user !== this.props.user) {
@ -167,7 +196,8 @@ export default class EditUserModal extends Modal {
this.loading = true;
this.props.user.save(this.data(), {errorHandler: this.onerror.bind(this)})
this.props.user
.save(this.data(), { errorHandler: this.onerror.bind(this) })
.then(this.hide.bind(this))
.catch(() => {
this.loading = false;

View File

@ -28,17 +28,16 @@ export default class EventPost extends Post {
const username = usernameHelper(user);
const data = Object.assign(this.descriptionData(), {
user,
username: user
? <a className="EventPost-user" href={app.route.user(user)} config={m.route}>{username}</a>
: username
username: user ? (
<a className="EventPost-user" href={app.route.user(user)} config={m.route}>
{username}
</a>
) : (
username
),
});
return super.content().concat([
icon(this.icon(), {className: 'EventPost-icon'}),
<div class="EventPost-info">
{this.description(data)}
</div>
]);
return super.content().concat([icon(this.icon(), { className: 'EventPost-icon' }), <div class="EventPost-info">{this.description(data)}</div>]);
}
/**

View File

@ -59,17 +59,22 @@ export default class ForgotPasswordModal extends Modal {
<div className="Form Form--centered">
<p className="helpText">{app.translator.trans('core.forum.forgot_password.text')}</p>
<div className="Form-group">
<input className="FormControl" name="email" type="email" placeholder={extractText(app.translator.trans('core.forum.forgot_password.email_placeholder'))}
<input
className="FormControl"
name="email"
type="email"
placeholder={extractText(app.translator.trans('core.forum.forgot_password.email_placeholder'))}
value={this.email()}
onchange={m.withAttr('value', this.email)}
disabled={this.loading} />
disabled={this.loading}
/>
</div>
<div className="Form-group">
{Button.component({
className: 'Button Button--primary Button--block',
type: 'submit',
loading: this.loading,
children: app.translator.trans('core.forum.forgot_password.submit_button')
children: app.translator.trans('core.forum.forgot_password.submit_button'),
})}
</div>
</div>
@ -82,11 +87,12 @@ export default class ForgotPasswordModal extends Modal {
this.loading = true;
app.request({
app
.request({
method: 'POST',
url: app.forum.attribute('apiUrl') + '/forgot',
data: {email: this.email()},
errorHandler: this.onerror.bind(this)
data: { email: this.email() },
errorHandler: this.onerror.bind(this),
})
.then(() => {
this.success = true;

View File

@ -8,11 +8,7 @@ import listItems from '../../common/helpers/listItems';
*/
export default class HeaderPrimary extends Component {
view() {
return (
<ul className="Header-controls">
{listItems(this.items().toArray())}
</ul>
);
return <ul className="Header-controls">{listItems(this.items().toArray())}</ul>;
}
config(isInitialized, context) {

Some files were not shown because too many files have changed in this diff Show More