mirror of
https://github.com/flarum/core.git
synced 2025-08-08 01:16:52 +02:00
add prettier config and prettify javascript
This commit is contained in:
2
js/dist/admin.js.map
vendored
2
js/dist/admin.js.map
vendored
File diff suppressed because one or more lines are too long
2
js/dist/forum.js
vendored
2
js/dist/forum.js
vendored
File diff suppressed because one or more lines are too long
2
js/dist/forum.js.map
vendored
2
js/dist/forum.js.map
vendored
File diff suppressed because one or more lines are too long
@@ -27,7 +27,7 @@
|
||||
"scripts": {
|
||||
"dev": "webpack --mode development --watch",
|
||||
"build": "webpack --mode production",
|
||||
"lint": "prettier --single-quote --trailing-comma es5 --print-width 150 --tab-width 4 --write \"src/**/*\""
|
||||
"lint": "prettier --single-quote --trailing-comma es5 --print-width 150 --tab-width 4 --write \"src/**/*\" \"*.{ts,js}\""
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/classnames": "^2.2.9",
|
||||
|
@@ -4,7 +4,7 @@ import Bus from './Bus';
|
||||
import Translator from './Translator';
|
||||
import Session from './Session';
|
||||
import Store from './Store';
|
||||
import {extend} from './extend';
|
||||
import { extend } from './extend';
|
||||
|
||||
import extract from './utils/extract';
|
||||
import mapRoutes from './utils/mapRoutes';
|
||||
@@ -56,7 +56,7 @@ export default abstract class Application {
|
||||
discussions: Discussion,
|
||||
posts: Post,
|
||||
groups: Group,
|
||||
notifications: Notification
|
||||
notifications: Notification,
|
||||
});
|
||||
|
||||
drawer = new Drawer();
|
||||
@@ -97,10 +97,7 @@ export default abstract class Application {
|
||||
|
||||
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.locale();
|
||||
this.plugins();
|
||||
@@ -156,9 +153,7 @@ export default abstract 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');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -181,7 +176,7 @@ export default abstract class Application {
|
||||
*
|
||||
* @see https://mithril.js.org/request.html
|
||||
*/
|
||||
request(originalOptions: Mithril.RequestOptions|any): Promise<any> {
|
||||
request(originalOptions: Mithril.RequestOptions | any): Promise<any> {
|
||||
const options: Mithril.RequestOptions = Object.assign({}, originalOptions);
|
||||
|
||||
// Set some default options if they haven't been overridden. We want to
|
||||
@@ -206,7 +201,9 @@ export default abstract class Application {
|
||||
// error message to the user instead.
|
||||
options.deserialize = options.deserialize || (responseText => responseText);
|
||||
|
||||
options.errorHandler = options.errorHandler || (error => {
|
||||
options.errorHandler =
|
||||
options.errorHandler ||
|
||||
(error => {
|
||||
throw error;
|
||||
});
|
||||
|
||||
@@ -250,8 +247,9 @@ export default abstract class Application {
|
||||
|
||||
// return new Promise((resolve, reject) => )
|
||||
|
||||
return m.request(options)
|
||||
.then(res => res, error => {
|
||||
return m.request(options).then(
|
||||
res => res,
|
||||
error => {
|
||||
this.requestError = error;
|
||||
|
||||
let children;
|
||||
@@ -292,8 +290,8 @@ export default abstract class Application {
|
||||
className: 'Button Button--link',
|
||||
onclick: this.showDebug.bind(this, error),
|
||||
children: 'DEBUG', // TODO make translatable
|
||||
})
|
||||
]
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -304,12 +302,13 @@ export default abstract class Application {
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private showDebug(error: RequestError) {
|
||||
// this.alerts.dismiss(this.requestError.alert);
|
||||
|
||||
this.modal.show(RequestErrorModal.component({error}));
|
||||
this.modal.show(RequestErrorModal.component({ error }));
|
||||
}
|
||||
}
|
||||
|
@@ -1,17 +1,17 @@
|
||||
import Mithril from 'mithril';
|
||||
|
||||
export type ComponentProps = {
|
||||
children?: Mithril.Children,
|
||||
children?: Mithril.Children;
|
||||
|
||||
className?: string;
|
||||
|
||||
[key: string]: any;
|
||||
}
|
||||
};
|
||||
|
||||
export default class Component<T extends ComponentProps = any> {
|
||||
element: HTMLElement;
|
||||
|
||||
props = <T> {};
|
||||
props = <T>{};
|
||||
|
||||
view(vnode) {
|
||||
throw new Error('Component#view must be implemented by subclass');
|
||||
@@ -60,7 +60,7 @@ export default class Component<T extends ComponentProps = any> {
|
||||
return selector ? $element.find(selector) : $element;
|
||||
}
|
||||
|
||||
static component(props: ComponentProps|any = {}, children?: Mithril.Children) {
|
||||
static component(props: ComponentProps | any = {}, children?: Mithril.Children) {
|
||||
const componentProps: ComponentProps = Object.assign({}, props);
|
||||
|
||||
if (children) componentProps.children = children;
|
||||
|
@@ -4,7 +4,7 @@
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
import Store from "./Store";
|
||||
import Store from './Store';
|
||||
|
||||
export default class Model {
|
||||
/**
|
||||
@@ -45,7 +45,7 @@ export default class Model {
|
||||
* Get the model's ID.
|
||||
* @final
|
||||
*/
|
||||
id(): string|number {
|
||||
id(): string | number {
|
||||
return this.data.id;
|
||||
}
|
||||
|
||||
@@ -76,7 +76,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];
|
||||
}
|
||||
@@ -96,7 +96,7 @@ export default class Model {
|
||||
* @param {Object} attributes The attributes to merge.
|
||||
*/
|
||||
pushAttributes(attributes: any) {
|
||||
this.pushData({attributes});
|
||||
this.pushData({ attributes });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,12 +107,12 @@ export default class Model {
|
||||
* @param [options]
|
||||
* @return {Promise}
|
||||
*/
|
||||
save(attributes: any, options: any = {}): Promise<Model|Model[]> {
|
||||
save(attributes: any, options: any = {}): Promise<Model | Model[]> {
|
||||
const data = {
|
||||
type: this.data.type,
|
||||
id: this.data.id,
|
||||
attributes,
|
||||
relationships: undefined
|
||||
relationships: undefined,
|
||||
};
|
||||
|
||||
// If a 'relationships' key exists, extract it from the attributes hash and
|
||||
@@ -125,9 +125,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),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -141,14 +139,21 @@ 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(),
|
||||
body: request
|
||||
}, options)).then(
|
||||
body: 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.
|
||||
@@ -179,11 +184,18 @@ export default class Model {
|
||||
delete(body = {}, options = {}) {
|
||||
if (!this.exists) return Promise.resolve();
|
||||
|
||||
return app.request(Object.assign({
|
||||
return app
|
||||
.request(
|
||||
Object.assign(
|
||||
{
|
||||
method: 'DELETE',
|
||||
url: app.forum.attribute('apiUrl') + this.apiEndpoint(),
|
||||
body
|
||||
}, options)).then(() => {
|
||||
body,
|
||||
},
|
||||
options
|
||||
)
|
||||
)
|
||||
.then(() => {
|
||||
this.exists = false;
|
||||
this.store.remove(this);
|
||||
});
|
||||
@@ -225,7 +237,7 @@ export default class Model {
|
||||
* relationship exists; undefined if the relationship exists but the model
|
||||
* has not been loaded; or the model if it has been loaded.
|
||||
*/
|
||||
static hasOne(name: string): () => Model|boolean {
|
||||
static hasOne(name: string): () => Model | boolean {
|
||||
return function() {
|
||||
if (this.data.relationships) {
|
||||
const relationship = this.data.relationships[name];
|
||||
@@ -247,7 +259,7 @@ export default class Model {
|
||||
* exists; an array if it does, containing models if they have been
|
||||
* loaded, and undefined for those that have not.
|
||||
*/
|
||||
static hasMany(name: string): () => []|boolean {
|
||||
static hasMany(name: string): () => [] | boolean {
|
||||
return function() {
|
||||
if (this.data.relationships) {
|
||||
const relationship = this.data.relationships[name];
|
||||
@@ -271,10 +283,10 @@ export default class Model {
|
||||
/**
|
||||
* Get a resource identifier object for the given model.
|
||||
*/
|
||||
protected static getIdentifier(model: Model): { type: string, id: string } {
|
||||
protected static getIdentifier(model: Model): { type: string; id: string } {
|
||||
return {
|
||||
type: model.data.type,
|
||||
id: model.data.id
|
||||
id: model.data.id,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -24,12 +24,17 @@ export default class Session {
|
||||
/**
|
||||
* Attempt to log in a user.
|
||||
*/
|
||||
login(body: { identification: string, password: string, remember?: string }, options = {}) {
|
||||
return app.request(Object.assign({
|
||||
login(body: { identification: string; password: string; remember?: string }, options = {}) {
|
||||
return app.request(
|
||||
Object.assign(
|
||||
{
|
||||
method: 'POST',
|
||||
url: `${app.forum.attribute('baseUrl')}/login`,
|
||||
body
|
||||
}, options));
|
||||
body,
|
||||
},
|
||||
options
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -9,7 +9,7 @@ export default class Store {
|
||||
* The local data store. A tree of resource types to IDs, such that
|
||||
* accessing data[type][id] will return the model for that type/ID.
|
||||
*/
|
||||
data: { [key: string]: { [key: number]: Model }} = {};
|
||||
data: { [key: string]: { [key: number]: Model } } = {};
|
||||
|
||||
/**
|
||||
* The model registry. A map of resource types to the model class that
|
||||
@@ -28,12 +28,10 @@ export default class Store {
|
||||
* @return The model(s) representing the resource(s) contained
|
||||
* within the 'data' key of the payload.
|
||||
*/
|
||||
pushPayload(payload: { included?: {}[], data?: {}|{}[] }): Model|Model[] {
|
||||
pushPayload(payload: { included?: {}[]; data?: {} | {}[] }): Model | Model[] {
|
||||
if (payload.included) payload.included.map(this.pushObject.bind(this));
|
||||
|
||||
const result: any = payload.data instanceof Array
|
||||
? payload.data.map(this.pushObject.bind(this))
|
||||
: this.pushObject(payload.data);
|
||||
const result: any = 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
|
||||
@@ -54,7 +52,7 @@ export default class Store {
|
||||
pushObject(data): Model {
|
||||
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);
|
||||
@@ -77,7 +75,7 @@ export default class Store {
|
||||
* @param query
|
||||
* @param options
|
||||
*/
|
||||
find<T extends Model = Model>(type: string, id?: number|number[]|any, query = {}, options = {}): Promise<T[]> {
|
||||
find<T extends Model = Model>(type: string, id?: number | number[] | any, query = {}, options = {}): Promise<T[]> {
|
||||
let params = query;
|
||||
let url = `${app.forum.attribute('apiUrl')}/${type}`;
|
||||
|
||||
@@ -89,11 +87,18 @@ export default class Store {
|
||||
url += `/${id}`;
|
||||
}
|
||||
|
||||
return app.request(Object.assign({
|
||||
return app
|
||||
.request(
|
||||
Object.assign(
|
||||
{
|
||||
method: 'GET',
|
||||
url,
|
||||
params
|
||||
}, options)).then(this.pushPayload.bind(this));
|
||||
params,
|
||||
},
|
||||
options
|
||||
)
|
||||
)
|
||||
.then(this.pushPayload.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,7 +108,7 @@ export default class Store {
|
||||
* @param id The resource ID.
|
||||
*/
|
||||
getById<T extends Model = Model>(type: string, id: number): T {
|
||||
return this.data[type] && this.data[type][id] as T;
|
||||
return this.data[type] && (this.data[type][id] as T);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,6 +147,6 @@ export default class Store {
|
||||
createRecord<T extends Model = Model>(type: string, data: any = {}): T {
|
||||
data.type = data.type || type;
|
||||
|
||||
return new (this.models[type])(data, this);
|
||||
return new this.models[type](data, this);
|
||||
}
|
||||
}
|
||||
|
@@ -5,5 +5,5 @@ import Modal from './components/Modal';
|
||||
export default {
|
||||
extend: extend,
|
||||
|
||||
'components/Modal': Modal
|
||||
'components/Modal': Modal,
|
||||
};
|
||||
|
@@ -1,15 +1,15 @@
|
||||
import Component, {ComponentProps} from '../Component';
|
||||
import Component, { ComponentProps } from '../Component';
|
||||
import Button from './Button';
|
||||
import listItems from '../helpers/listItems';
|
||||
import extract from '../utils/extract';
|
||||
import * as Mithril from "mithril";
|
||||
import * as Mithril from 'mithril';
|
||||
|
||||
export interface AlertProps extends ComponentProps {
|
||||
controls?: Mithril.ChildArray,
|
||||
type?: string,
|
||||
dismissible?: boolean,
|
||||
controls?: Mithril.ChildArray;
|
||||
type?: string;
|
||||
dismissible?: boolean;
|
||||
|
||||
ondismiss?: () => any,
|
||||
ondismiss?: () => any;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,27 +39,18 @@ export default class Alert extends Component<AlertProps> {
|
||||
// If the alert is meant to be dismissible (which is the case by default),
|
||||
// then we will create a dismiss button to append as the final control in
|
||||
// the alert.
|
||||
const dismissible: boolean|undefined = extract(attrs, 'dismissible');
|
||||
const dismissible: boolean | undefined = extract(attrs, 'dismissible');
|
||||
const ondismiss: () => any = extract(attrs, 'ondismiss');
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
@@ -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(' ')}
|
||||
</span>
|
||||
);
|
||||
return <span {...attrs}>{iconName ? icon(iconName, { className: 'Badge-icon' }) : m.trust(' ')}</span>;
|
||||
}
|
||||
|
||||
oncreate(vnode) {
|
||||
super.oncreate(vnode);
|
||||
|
||||
if (this.props.label) this.$().tooltip({container: 'body'});
|
||||
if (this.props.label) this.$().tooltip({ container: 'body' });
|
||||
}
|
||||
}
|
||||
|
@@ -1,17 +1,17 @@
|
||||
import Component, {ComponentProps} from '../Component';
|
||||
import Component, { ComponentProps } from '../Component';
|
||||
import icon from '../helpers/icon';
|
||||
import extract from '../utils/extract';
|
||||
import extractText from '../utils/extractText';
|
||||
import LoadingIndicator from './LoadingIndicator';
|
||||
|
||||
export interface ButtonProps extends ComponentProps {
|
||||
title?: string,
|
||||
type?: string,
|
||||
icon?: string,
|
||||
title?: string;
|
||||
type?: string;
|
||||
icon?: string;
|
||||
|
||||
loading?: boolean,
|
||||
disabled?: boolean,
|
||||
onclick?: Function
|
||||
loading?: boolean;
|
||||
disabled?: boolean;
|
||||
onclick?: Function;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -32,7 +32,7 @@ export interface ButtonProps extends ComponentProps {
|
||||
*/
|
||||
export default class Button<T extends ButtonProps = ButtonProps> extends Component<T> {
|
||||
view() {
|
||||
const { children, ...attrs} = this.props;
|
||||
const { children, ...attrs } = this.props;
|
||||
|
||||
attrs.className = attrs.className || '';
|
||||
attrs.type = attrs.type || 'button';
|
||||
@@ -63,11 +63,11 @@ export default class Button<T extends ButtonProps = ButtonProps> extends Compone
|
||||
/**
|
||||
* Get the template for the button's content.
|
||||
*/
|
||||
protected getButtonContent(iconName?: string|boolean, loading?: boolean, children?: any) : any[] {
|
||||
protected getButtonContent(iconName?: string | boolean, loading?: boolean, children?: any): any[] {
|
||||
return [
|
||||
iconName && iconName !== true ? icon(iconName, {className: 'Button-icon'}) : '',
|
||||
iconName && iconName !== true ? icon(iconName, { className: 'Button-icon' }) : '',
|
||||
children ? <span className="Button-label">{children}</span> : '',
|
||||
loading ? LoadingIndicator.component({size: 'tiny', className: 'LoadingIndicator--inline'}) : ''
|
||||
loading ? LoadingIndicator.component({ size: 'tiny', className: 'LoadingIndicator--inline' }) : '',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import Component, {ComponentProps} from '../Component';
|
||||
import Component, { ComponentProps } from '../Component';
|
||||
import icon from '../helpers/icon';
|
||||
import listItems from '../helpers/listItems';
|
||||
|
||||
@@ -7,7 +7,7 @@ export interface DropdownProps extends ComponentProps {
|
||||
menuClassName?: string;
|
||||
label?: string;
|
||||
icon?: string;
|
||||
caretIcon?: undefined|string;
|
||||
caretIcon?: undefined | string;
|
||||
|
||||
onhide?: Function;
|
||||
onshow?: Function;
|
||||
@@ -74,19 +74,13 @@ export default class Dropdown<T extends DropdownProps = DropdownProps> extends C
|
||||
|
||||
$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.element.addEventListener('hidden.bs.dropdown', () => {
|
||||
@@ -105,10 +99,7 @@ export default class Dropdown<T extends DropdownProps = DropdownProps> extends C
|
||||
*/
|
||||
protected getButton(): any {
|
||||
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>
|
||||
);
|
||||
@@ -123,17 +114,13 @@ export default class Dropdown<T extends DropdownProps = DropdownProps> extends C
|
||||
const attrs = this.props;
|
||||
|
||||
return [
|
||||
attrs.icon ? icon(attrs.icon, {className: 'Button-icon'}) : '',
|
||||
attrs.icon ? icon(attrs.icon, { className: 'Button-icon' }) : '',
|
||||
<span className="Button-label">{attrs.label}</span>,
|
||||
attrs.caretIcon ? icon(attrs.caretIcon, {className: 'Button-caret'}) : ''
|
||||
attrs.caretIcon ? icon(attrs.caretIcon, { className: 'Button-caret' }) : '',
|
||||
];
|
||||
}
|
||||
|
||||
protected 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>;
|
||||
}
|
||||
}
|
||||
|
@@ -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()}`;
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import Button, {ButtonProps} from './Button';
|
||||
import Button, { ButtonProps } from './Button';
|
||||
|
||||
interface LinkButtonProps extends ButtonProps {
|
||||
active: boolean;
|
||||
@@ -36,8 +36,6 @@ export default class LinkButton extends Button<LinkButtonProps> {
|
||||
* Determine whether a component with the given props is 'active'.
|
||||
*/
|
||||
static isActive(props: LinkButtonProps): boolean {
|
||||
return typeof props.active !== 'undefined'
|
||||
? props.active
|
||||
: m.route.get() === props.href;
|
||||
return typeof props.active !== 'undefined' ? props.active : m.route.get() === props.href;
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import Component from '../Component';
|
||||
import {Spinner, SpinnerOptions} from 'spin.js';
|
||||
import { Spinner, SpinnerOptions } from 'spin.js';
|
||||
|
||||
/**
|
||||
* The `LoadingIndicator` component displays a loading spinner with spin.js. It
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import Mithril from 'mithril';
|
||||
|
||||
import Component, {ComponentProps} from '../Component';
|
||||
import Component, { ComponentProps } from '../Component';
|
||||
import Button from './Button';
|
||||
import RequestError from "../utils/RequestError";
|
||||
import RequestError from '../utils/RequestError';
|
||||
|
||||
/**
|
||||
* The `Modal` component displays a modal dialog, wrapped in a form. Subclasses
|
||||
@@ -29,10 +29,12 @@ export default abstract class Modal<T extends ComponentProps = ComponentProps> e
|
||||
{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">
|
||||
@@ -85,7 +87,11 @@ export default abstract class Modal<T extends ComponentProps = ComponentProps> e
|
||||
* Focus on the first input when the modal is ready to be used.
|
||||
*/
|
||||
onready() {
|
||||
this.$('form').find('input, select, textarea').first().focus().select();
|
||||
this.$('form')
|
||||
.find('input, select, textarea')
|
||||
.first()
|
||||
.focus()
|
||||
.select();
|
||||
}
|
||||
|
||||
onhide() {}
|
||||
|
@@ -2,7 +2,7 @@ import MicroModal from 'micromodal';
|
||||
|
||||
import Component from '../Component';
|
||||
import Modal from './Modal';
|
||||
import {Vnode} from "mithril";
|
||||
import { Vnode } from 'mithril';
|
||||
|
||||
/**
|
||||
* The `ModalManager` component manages a modal dialog. Only one modal dialog
|
||||
@@ -48,19 +48,20 @@ export default class ModalManager extends Component {
|
||||
m.redraw();
|
||||
|
||||
if (!$('.modal-backdrop').length) {
|
||||
$('<div />').addClass('modal-backdrop')
|
||||
$('<div />')
|
||||
.addClass('modal-backdrop')
|
||||
.appendTo('body');
|
||||
}
|
||||
|
||||
MicroModal.show('Modal', {
|
||||
awaitCloseAnimation: true,
|
||||
onClose: () => {
|
||||
$('.modal-backdrop').fadeOut(200, function () {
|
||||
$('.modal-backdrop').fadeOut(200, function() {
|
||||
this.remove();
|
||||
});
|
||||
|
||||
this.showing = false;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
this.onready();
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import Component, {ComponentProps} from '../Component';
|
||||
import Component, { ComponentProps } from '../Component';
|
||||
|
||||
export interface PlaceholderProps extends ComponentProps {
|
||||
text: string
|
||||
text: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import Modal from './Modal';
|
||||
import {ComponentProps} from '../Component';
|
||||
import { ComponentProps } from '../Component';
|
||||
import RequestError from '../utils/RequestError';
|
||||
|
||||
export interface RequestErrorModalProps extends ComponentProps {
|
||||
error: RequestError,
|
||||
error: RequestError;
|
||||
}
|
||||
|
||||
export default class RequestErrorModal<T extends RequestErrorModalProps = RequestErrorModalProps> extends Modal<T> {
|
||||
@@ -12,9 +12,7 @@ export default class RequestErrorModal<T extends RequestErrorModalProps = Reques
|
||||
}
|
||||
|
||||
title(): string {
|
||||
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() {
|
||||
@@ -26,11 +24,15 @@ export default class RequestErrorModal<T extends RequestErrorModalProps = Reques
|
||||
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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import Dropdown, {DropdownProps} from './Dropdown';
|
||||
import Dropdown, { DropdownProps } from './Dropdown';
|
||||
import icon from '../helpers/icon';
|
||||
|
||||
export interface SelectDropdownProps extends DropdownProps {
|
||||
@@ -26,13 +26,10 @@ export default class SelectDropdown extends Dropdown<SelectDropdownProps> {
|
||||
|
||||
getButtonContent() {
|
||||
const activeChild = this.props.children.filter(child => child.attrs.active)[0];
|
||||
let label = activeChild && activeChild.attrs.children || this.props.defaultLabel;
|
||||
let label = (activeChild && activeChild.attrs.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' })];
|
||||
}
|
||||
}
|
||||
|
@@ -7,6 +7,6 @@ export default class Separator extends Component {
|
||||
static isListItem = true;
|
||||
|
||||
view() {
|
||||
return <li className="Dropdown-separator"/>;
|
||||
return <li className="Dropdown-separator" />;
|
||||
}
|
||||
}
|
||||
|
@@ -25,11 +25,11 @@ export default function avatar(user, attrs: any = {}) {
|
||||
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>;
|
||||
|
@@ -9,7 +9,7 @@ import { truncate } from '../utils/string';
|
||||
* @param {Integer} [length] The number of characters to truncate the string to.
|
||||
* The string will be truncated surrounding the first match.
|
||||
*/
|
||||
export default function highlight(string: string, phrase: string|RegExp, length?: number): any {
|
||||
export default function highlight(string: string, phrase: string | RegExp, length?: number): any {
|
||||
if (!phrase && !length) return string;
|
||||
|
||||
// Convert the word phrase into a global regular expression (if it isn't
|
||||
@@ -28,7 +28,9 @@ export default function highlight(string: string, phrase: string|RegExp, length?
|
||||
|
||||
// Convert the string into HTML entities, then highlight all matches with
|
||||
// <mark> tags. Then we will return the result as a trusted HTML string.
|
||||
highlighted = $('<div/>').text(highlighted).html();
|
||||
highlighted = $('<div/>')
|
||||
.text(highlighted)
|
||||
.html();
|
||||
|
||||
if (phrase) highlighted = highlighted.replace(regexp, '<mark>$&</mark>');
|
||||
|
||||
|
@@ -7,5 +7,5 @@
|
||||
export default function icon(fontClass: string, attrs: any = {}) {
|
||||
attrs.className = 'icon ' + fontClass + ' ' + (attrs.className || '');
|
||||
|
||||
return <i {...attrs}/>;
|
||||
return <i {...attrs} />;
|
||||
}
|
||||
|
@@ -39,16 +39,16 @@ export default function listItems(items) {
|
||||
item.key = item.attrs.key;
|
||||
}
|
||||
|
||||
const node = isListItem
|
||||
? item
|
||||
: <li className={classNames(className, [
|
||||
(item.itemName && `item-${item.itemName}`),
|
||||
active && 'active',
|
||||
])}
|
||||
key={item.attrs?.key || item.itemName}>
|
||||
const node = isListItem ? (
|
||||
item
|
||||
) : (
|
||||
<li
|
||||
className={classNames(className, [item.itemName && `item-${item.itemName}`, active && 'active'])}
|
||||
key={item.attrs?.key || item.itemName}
|
||||
>
|
||||
{item}
|
||||
</li>;
|
||||
|
||||
</li>
|
||||
);
|
||||
|
||||
node.state = node.state || {};
|
||||
|
||||
|
@@ -78,7 +78,7 @@ export default class Discussion extends Model {
|
||||
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;
|
||||
|
@@ -3,13 +3,13 @@ import computed from '../utils/computed';
|
||||
import { getPlainContent } from '../utils/string';
|
||||
|
||||
import Discussion from './Discussion';
|
||||
import User from "./User";
|
||||
import User from './User';
|
||||
|
||||
export default class Post extends Model {
|
||||
number = Model.attribute('number') as () => number;
|
||||
discussion = Model.hasOne('discussion') as () => Discussion;
|
||||
|
||||
createdAt= Model.attribute('createdAt', Model.transformDate) as () => Date;
|
||||
createdAt = Model.attribute('createdAt', Model.transformDate) as () => Date;
|
||||
user = Model.hasOne('user') as () => User;
|
||||
contentType = Model.attribute('contentType') as () => string;
|
||||
content = Model.attribute('content') as () => string;
|
||||
|
@@ -46,7 +46,12 @@ export default class User extends Model {
|
||||
}) as () => string;
|
||||
|
||||
isOnline(): boolean {
|
||||
return this.lastSeenAt() > dayjs().subtract(5, 'minutes').toDate();
|
||||
return (
|
||||
this.lastSeenAt() >
|
||||
dayjs()
|
||||
.subtract(5, 'minutes')
|
||||
.toDate()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,7 +63,7 @@ export default class User extends Model {
|
||||
|
||||
if (groups) {
|
||||
groups.forEach(group => {
|
||||
items.add('group' + group.id(), GroupBadge.component({group}));
|
||||
items.add('group' + group.id(), GroupBadge.component({ group }));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -93,6 +98,6 @@ export default class User extends Model {
|
||||
|
||||
Object.assign(preferences, newPreferences);
|
||||
|
||||
return <Promise<User>> this.save({preferences});
|
||||
return <Promise<User>>this.save({ preferences });
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import Mithril from "mithril";
|
||||
import Mithril from 'mithril';
|
||||
|
||||
export interface RequestErrorResponse extends JSON {
|
||||
errors?: {
|
||||
|
@@ -6,7 +6,7 @@
|
||||
* @param {function} compute The function which computes the value using the
|
||||
* dependent values.
|
||||
*/
|
||||
export default function computed(dependentKeys: string|string[], compute: Function): () => any {
|
||||
export default function computed(dependentKeys: string | string[], compute: Function): () => any {
|
||||
const keys = Array.from(dependentKeys);
|
||||
|
||||
const dependentValues = {};
|
||||
@@ -27,7 +27,10 @@ export default function computed(dependentKeys: string|string[], compute: Functi
|
||||
});
|
||||
|
||||
if (recompute) {
|
||||
computedValue = compute.apply(this, keys.map(key => dependentValues[key]));
|
||||
computedValue = compute.apply(
|
||||
this,
|
||||
keys.map(key => dependentValues[key])
|
||||
);
|
||||
}
|
||||
|
||||
return computedValue;
|
||||
|
@@ -23,11 +23,11 @@ export default function humanTime(time: Date): string {
|
||||
if (m.year() === dayjs().year()) {
|
||||
ago = m.format('D MMM');
|
||||
} else {
|
||||
ago = m.format('MMM \'YY');
|
||||
ago = m.format("MMM 'YY");
|
||||
}
|
||||
} else {
|
||||
ago = m.fromNow();
|
||||
}
|
||||
|
||||
return ago;
|
||||
};
|
||||
}
|
||||
|
@@ -6,7 +6,7 @@ import Component from '../Component';
|
||||
export default () => {
|
||||
const mo = window['m'];
|
||||
|
||||
const _m = function (comp, ...args) {
|
||||
const _m = function(comp, ...args) {
|
||||
if (!arguments[1]) arguments[1] = {};
|
||||
|
||||
if (comp.prototype && comp.prototype instanceof Component) {
|
||||
@@ -42,13 +42,14 @@ export default () => {
|
||||
return node;
|
||||
};
|
||||
|
||||
Object.keys(mo).forEach(key => _m[key] = mo[key]);
|
||||
Object.keys(mo).forEach(key => (_m[key] = mo[key]));
|
||||
|
||||
_m.withAttr = (key: string, cb: Function) => function () {
|
||||
_m.withAttr = (key: string, cb: Function) =>
|
||||
function() {
|
||||
cb(this.getAttribute(key) || this[key]);
|
||||
};
|
||||
|
||||
_m.prop = prop;
|
||||
|
||||
window['m'] = _m;
|
||||
}
|
||||
};
|
||||
|
@@ -2,11 +2,11 @@ import jump from 'jump.js';
|
||||
import Tooltip from 'tooltip.js';
|
||||
|
||||
// add $.fn.tooltip
|
||||
$.fn.tooltip = function (option) {
|
||||
return this.each(function () {
|
||||
$.fn.tooltip = function(option) {
|
||||
return this.each(function() {
|
||||
const $this = $(this);
|
||||
let data = $this.data('bs.tooltip');
|
||||
const options = typeof option === 'object' && option || {};
|
||||
const options = (typeof option === 'object' && option) || {};
|
||||
|
||||
if ($this.attr('title')) {
|
||||
options.title = $this.attr('title');
|
||||
@@ -25,15 +25,15 @@ $.fn.tooltip = function (option) {
|
||||
|
||||
// add $.fn.outerWidth and $.fn.outerHeight
|
||||
['width', 'height'].forEach(function(dimension) {
|
||||
const Dimension = dimension.replace(/./, function (m) {
|
||||
return m[0].toUpperCase()
|
||||
const Dimension = dimension.replace(/./, function(m) {
|
||||
return m[0].toUpperCase();
|
||||
});
|
||||
|
||||
$.fn[`outer${Dimension}`] = function(margin) {
|
||||
const elem = this;
|
||||
|
||||
if (elem) {
|
||||
const sides = {'width': ['left', 'right'], 'height': ['top', 'bottom']};
|
||||
const sides = { width: ['left', 'right'], height: ['top', 'bottom'] };
|
||||
let size = elem[dimension]();
|
||||
|
||||
sides[dimension].forEach(function(side) {
|
||||
@@ -50,23 +50,21 @@ $.fn.tooltip = function (option) {
|
||||
// allow use of $(':input')
|
||||
// @ts-ignore
|
||||
$.expr[':']['input'] = function() {
|
||||
if (('disabled' in this) || ['INPUT', 'SELECT', 'TEXTAREA', 'BUTTON'].includes(this.tagName)) return this;
|
||||
if ('disabled' in this || ['INPUT', 'SELECT', 'TEXTAREA', 'BUTTON'].includes(this.tagName)) return this;
|
||||
};
|
||||
|
||||
// add $().hover() method
|
||||
$.fn.hover = function(hover, leave) {
|
||||
return this
|
||||
.on('mouseenter', hover)
|
||||
.on('mouseleave', leave || hover);
|
||||
return this.on('mouseenter', hover).on('mouseleave', leave || hover);
|
||||
};
|
||||
|
||||
// add animated scroll
|
||||
$.fn.animatedScrollTop = function (to, duration = $.fx.speeds._default, callback) {
|
||||
if (typeof to === 'number') to -= (window.scrollY || window.pageYOffset);
|
||||
$.fn.animatedScrollTop = function(to, duration = $.fx.speeds._default, callback) {
|
||||
if (typeof to === 'number') to -= window.scrollY || window.pageYOffset;
|
||||
|
||||
jump(to, {
|
||||
duration: $.fx.speeds[duration] || duration,
|
||||
callback
|
||||
callback,
|
||||
});
|
||||
|
||||
return this;
|
||||
@@ -88,43 +86,41 @@ const bindBeforeSpecialEvents = $.fn.bind;
|
||||
$.fn.bind = function(eventName, data, callback) {
|
||||
const el = this;
|
||||
|
||||
if (!callback){
|
||||
if (!callback) {
|
||||
callback = data;
|
||||
data = null;
|
||||
}
|
||||
|
||||
$.each(eventName.split(/\s/), (key: string, value: any) : boolean => {
|
||||
$.each(eventName.split(/\s/), (key: string, value: any): boolean => {
|
||||
value = value.split(/\./)[0];
|
||||
|
||||
if(value in $.event.special){
|
||||
if (value in $.event.special) {
|
||||
let specialEvent = $.event.special[value];
|
||||
|
||||
/// init enable special events on Zepto
|
||||
if(!specialEvent._init) {
|
||||
if (!specialEvent._init) {
|
||||
specialEvent._init = true;
|
||||
|
||||
/// intercept and replace the special event handler to add functionality
|
||||
specialEvent.originalHandler = specialEvent.handler;
|
||||
specialEvent.handler = function(){
|
||||
|
||||
specialEvent.handler = function() {
|
||||
/// make event argument writable, like on jQuery
|
||||
const args = Array.prototype.slice.call(arguments);
|
||||
|
||||
args[0] = $.extend({},args[0]);
|
||||
args[0] = $.extend({}, args[0]);
|
||||
|
||||
/// define the event handle, $.event.dispatch is only for newer versions of jQuery
|
||||
$.event.handle = function(){
|
||||
|
||||
$.event.handle = function() {
|
||||
/// make context of trigger the event element
|
||||
const args = Array.prototype.slice.call(arguments);
|
||||
const event = args[0];
|
||||
const $target = $(event.target);
|
||||
|
||||
$target.trigger.apply( $target, arguments );
|
||||
$target.trigger.apply($target, arguments);
|
||||
};
|
||||
|
||||
specialEvent.originalHandler.apply(this,args);
|
||||
}
|
||||
specialEvent.originalHandler.apply(this, args);
|
||||
};
|
||||
}
|
||||
|
||||
/// setup special events on Zepto
|
||||
|
@@ -2,9 +2,7 @@
|
||||
* Truncate a string to the given length, appending ellipses if necessary.
|
||||
*/
|
||||
export function truncate(string: string, length: number, start = 0): string {
|
||||
return (start > 0 ? '...' : '') +
|
||||
string.substring(start, start + length) +
|
||||
(string.length > start + length ? '...' : '');
|
||||
return (start > 0 ? '...' : '') + string.substring(start, start + length) + (string.length > start + length ? '...' : '');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -12,7 +10,8 @@ export function truncate(string: string, length: number, start = 0): string {
|
||||
* converted to hyphens.
|
||||
*/
|
||||
export function slug(string: string): string {
|
||||
return string.toLowerCase()
|
||||
return string
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]/gi, '-')
|
||||
.replace(/-+/g, '-')
|
||||
.replace(/-$|^-/g, '');
|
||||
@@ -23,15 +22,16 @@ export function slug(string: string): string {
|
||||
* meaningful punctuation.
|
||||
*/
|
||||
export function getPlainContent(string: string): string {
|
||||
const html = string
|
||||
.replace(/(<\/p>|<br>)/g, '$1 ')
|
||||
.replace(/<img\b[^>]*>/ig, ' ');
|
||||
const html = string.replace(/(<\/p>|<br>)/g, '$1 ').replace(/<img\b[^>]*>/gi, ' ');
|
||||
|
||||
const dom = $('<div/>').html(html);
|
||||
|
||||
dom.find(getPlainContent.removeSelectors.join(',')).remove();
|
||||
|
||||
return dom.text().replace(/\s+/g, ' ').trim();
|
||||
return dom
|
||||
.text()
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -10,18 +10,42 @@ export function hsvToRgb(h: number, s: number, v: number) {
|
||||
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),
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -7,22 +7,22 @@ import HeaderSecondary from './components/HeaderSecondary';
|
||||
import Page from './components/Page';
|
||||
import IndexPage from './components/IndexPage';
|
||||
import PostsUserPage from './components/PostsUserPage';
|
||||
import User from "../common/models/User";
|
||||
import Post from "../common/models/Post";
|
||||
import Discussion from "../common/models/Discussion";
|
||||
import User from '../common/models/User';
|
||||
import Post from '../common/models/Post';
|
||||
import Discussion from '../common/models/Discussion';
|
||||
|
||||
export default class Forum extends Application {
|
||||
routes = {
|
||||
'index': { path: '/all', component: IndexPage },
|
||||
index: { path: '/all', component: IndexPage },
|
||||
|
||||
'discussion': { path: '/d/:id', component: IndexPage },
|
||||
discussion: { path: '/d/:id', component: IndexPage },
|
||||
'discussion.near': { path: '/d/:id/:near', component: IndexPage },
|
||||
|
||||
'user': { path: '/u/:username', component: PostsUserPage },
|
||||
user: { path: '/u/:username', component: PostsUserPage },
|
||||
'user.posts': { path: '/u/:username', component: PostsUserPage },
|
||||
'user.discussions': { path: '/u/:username/discussions', component: PostsUserPage },
|
||||
|
||||
'settings': { path: '/settings', component: PostsUserPage },
|
||||
settings: { path: '/settings', component: PostsUserPage },
|
||||
|
||||
'index.filter': { path: '/:filter', component: IndexPage },
|
||||
};
|
||||
@@ -86,7 +86,7 @@ export default class Forum extends Application {
|
||||
const slug = discussion.slug();
|
||||
return this.route(near && near !== 1 ? 'discussion.near' : 'discussion', {
|
||||
id: discussion.id() + (slug.trim() ? '-' + slug : ''),
|
||||
near: near && near !== 1 ? near : undefined
|
||||
near: near && near !== 1 ? near : undefined,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -102,7 +102,7 @@ export default class Forum extends Application {
|
||||
*/
|
||||
this.route.user = (user: User): string => {
|
||||
return this.route('user', {
|
||||
username: user.username()
|
||||
username: user.username(),
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import Component, {ComponentProps} from '../../common/Component';
|
||||
import Component, { ComponentProps } from '../../common/Component';
|
||||
import avatar from '../../common/helpers/avatar';
|
||||
import icon from '../../common/helpers/icon';
|
||||
import listItems from '../../common/helpers/listItems';
|
||||
@@ -36,9 +36,14 @@ export default class AvatarEditor extends Component<AvatarEditorProps> {
|
||||
const user = this.props.user;
|
||||
|
||||
return (
|
||||
<div className={'AvatarEditor Dropdown ' + this.props.className + (this.loading ? ' loading' : '') + (this.isDraggedOver ? ' dragover' : '')}>
|
||||
<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)}
|
||||
@@ -46,12 +51,11 @@ export default class AvatarEditor extends Component<AvatarEditorProps> {
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -64,19 +68,21 @@ export default class AvatarEditor extends Component<AvatarEditorProps> {
|
||||
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),
|
||||
})
|
||||
);
|
||||
|
||||
@@ -144,7 +150,11 @@ export default class AvatarEditor extends Component<AvatarEditorProps> {
|
||||
const user = this.props.user;
|
||||
const $input = $('<input type="file">');
|
||||
|
||||
$input.appendTo('body').hide().click().on('change', e => {
|
||||
$input
|
||||
.appendTo('body')
|
||||
.hide()
|
||||
.click()
|
||||
.on('change', e => {
|
||||
this.upload($(e.target)[0].files[0]);
|
||||
});
|
||||
}
|
||||
@@ -168,11 +178,8 @@ export default class AvatarEditor extends Component<AvatarEditorProps> {
|
||||
method: 'POST',
|
||||
url: `${app.forum.attribute('apiUrl')}/users/${user.id()}/avatar`,
|
||||
serialize: raw => raw,
|
||||
body
|
||||
}).then(
|
||||
this.success.bind(this),
|
||||
this.failure.bind(this)
|
||||
);
|
||||
body,
|
||||
}).then(this.success.bind(this), this.failure.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,11 +193,8 @@ export default class AvatarEditor extends Component<AvatarEditorProps> {
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -26,7 +26,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 = PostUser.component({post: this.props.post});
|
||||
this.postUser = PostUser.component({ post: this.props.post });
|
||||
|
||||
this.subtree.check(
|
||||
() => this.postUser.cardVisible,
|
||||
@@ -38,13 +38,17 @@ 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>,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -77,12 +81,15 @@ export default class CommentPost extends Post {
|
||||
const post = this.props.post;
|
||||
const attrs = super.attrs();
|
||||
|
||||
attrs.className = (attrs.className || '') + ' ' + classNames({
|
||||
'CommentPost': true,
|
||||
attrs.className =
|
||||
(attrs.className || '') +
|
||||
' ' +
|
||||
classNames({
|
||||
CommentPost: true,
|
||||
'Post--hidden': post.isHidden(),
|
||||
'Post--edited': post.isEdited(),
|
||||
'revealContent': this.revealContent,
|
||||
'editing': this.isEditing()
|
||||
revealContent: this.revealContent,
|
||||
editing: this.isEditing(),
|
||||
});
|
||||
|
||||
return attrs;
|
||||
@@ -125,7 +132,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, 100);
|
||||
// items.add('meta', PostMeta.component(props));
|
||||
@@ -137,13 +144,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;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import highlight from '../../common/helpers/highlight';
|
||||
import LinkButton from '../../common/components/LinkButton';
|
||||
import SearchSource from "./SearchSource";
|
||||
import Discussion from "../../common/models/Discussion";
|
||||
import SearchSource from './SearchSource';
|
||||
import Discussion from '../../common/models/Discussion';
|
||||
|
||||
/**
|
||||
* The `DiscussionsSearchSource` finds and displays discussion search results in
|
||||
@@ -16,12 +16,12 @@ export default class DiscussionsSearchSource extends SearchSource {
|
||||
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<Discussion>('discussions', params).then(results => this.results[query] = results);
|
||||
return app.store.find<Discussion>('discussions', params).then(results => (this.results[query] = results));
|
||||
}
|
||||
|
||||
view(query: string) {
|
||||
@@ -34,8 +34,8 @@ export default class DiscussionsSearchSource extends SearchSource {
|
||||
<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 => {
|
||||
@@ -45,11 +45,15 @@ export default class DiscussionsSearchSource extends SearchSource {
|
||||
<li className="DiscussionSearchResult" data-index={'discussions' + discussion.id()}>
|
||||
<m.route.Link href={app.route.discussion(discussion, mostRelevantPost && mostRelevantPost.number())}>
|
||||
<div className="DiscussionSearchResult-title">{highlight(discussion.title(), query)}</div>
|
||||
{mostRelevantPost ? <div className="DiscussionSearchResult-excerpt">{highlight(mostRelevantPost.contentPlain(), query, 100)}</div> : ''}
|
||||
{mostRelevantPost ? (
|
||||
<div className="DiscussionSearchResult-excerpt">{highlight(mostRelevantPost.contentPlain(), query, 100)}</div>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</m.route.Link>
|
||||
</li>
|
||||
);
|
||||
})
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -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>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -17,11 +17,7 @@ import Search from './Search';
|
||||
*/
|
||||
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>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -32,29 +28,35 @@ export default class HeaderSecondary extends Component {
|
||||
|
||||
items.add('search', Search.component(), 30);
|
||||
|
||||
if (app.forum.attribute("showLanguageSelector") && Object.keys(app.data.locales).length > 1) {
|
||||
if (app.forum.attribute('showLanguageSelector') && Object.keys(app.data.locales).length > 1) {
|
||||
const locales = [];
|
||||
|
||||
for (const locale in app.data.locales) {
|
||||
locales.push(Button.component({
|
||||
locales.push(
|
||||
Button.component({
|
||||
active: app.data.locale === locale,
|
||||
children: app.data.locales[locale],
|
||||
icon: app.data.locale === locale ? 'fas fa-check' : true,
|
||||
onclick: () => {
|
||||
if (app.session.user) {
|
||||
app.session.user.savePreferences({locale}).then(() => window.location.reload());
|
||||
app.session.user.savePreferences({ locale }).then(() => window.location.reload());
|
||||
} else {
|
||||
document.cookie = `locale=${locale}; path=/; expires=Tue, 19 Jan 2038 03:14:07 GMT`;
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
}));
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
items.add('locale', SelectDropdown.component({
|
||||
items.add(
|
||||
'locale',
|
||||
SelectDropdown.component({
|
||||
children: locales,
|
||||
buttonClassName: 'Button Button--link'
|
||||
}), 20);
|
||||
buttonClassName: 'Button Button--link',
|
||||
}),
|
||||
20
|
||||
);
|
||||
}
|
||||
|
||||
if (app.session.user) {
|
||||
@@ -62,21 +64,25 @@ export default class HeaderSecondary extends Component {
|
||||
items.add('session', SessionDropdown.component(), 0);
|
||||
} else {
|
||||
if (app.forum.attribute('allowSignUp')) {
|
||||
items.add('signUp',
|
||||
items.add(
|
||||
'signUp',
|
||||
Button.component({
|
||||
children: app.translator.trans('core.forum.header.sign_up_link'),
|
||||
className: 'Button Button--link',
|
||||
onclick: () => app.modal.show(new SignUpModal())
|
||||
}), 10
|
||||
onclick: () => app.modal.show(new SignUpModal()),
|
||||
}),
|
||||
10
|
||||
);
|
||||
}
|
||||
|
||||
items.add('logIn',
|
||||
items.add(
|
||||
'logIn',
|
||||
Button.component({
|
||||
children: app.translator.trans('core.forum.header.log_in_link'),
|
||||
className: 'Button Button--link',
|
||||
onclick: () => app.modal.show(new LogInModal())
|
||||
}), 0
|
||||
onclick: () => app.modal.show(new LogInModal()),
|
||||
}),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -6,9 +6,7 @@ import ItemList from '../../common/utils/ItemList';
|
||||
*/
|
||||
export default class LogInButtons extends Component {
|
||||
view() {
|
||||
return <div className="LogInButtons">
|
||||
{this.items().toArray()}
|
||||
</div>
|
||||
return <div className="LogInButtons">{this.items().toArray()}</div>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import Stream from 'mithril/stream';
|
||||
|
||||
import {ComponentProps} from '../../common/Component';
|
||||
import { ComponentProps } from '../../common/Component';
|
||||
import Modal from '../../common/components/Modal';
|
||||
import ItemList from "../../common/utils/ItemList";
|
||||
import Button from "../../common/components/Button";
|
||||
import ItemList from '../../common/utils/ItemList';
|
||||
import Button from '../../common/components/Button';
|
||||
|
||||
import LogInButtons from './LogInButtons';
|
||||
|
||||
@@ -51,58 +51,71 @@ export default class LogInModal extends Modal<LogInModalProps> {
|
||||
}
|
||||
|
||||
content() {
|
||||
return [
|
||||
<div className="Modal-body">
|
||||
{this.body()}
|
||||
</div>,
|
||||
<div className="Modal-footer">
|
||||
{this.footer()}
|
||||
</div>
|
||||
];
|
||||
return [<div className="Modal-body">{this.body()}</div>, <div className="Modal-footer">{this.footer()}</div>];
|
||||
}
|
||||
|
||||
body() {
|
||||
return [
|
||||
<LogInButtons/>,
|
||||
|
||||
<div className="Form Form--centered">
|
||||
{this.fields().toArray()}
|
||||
</div>
|
||||
];
|
||||
return [<LogInButtons />, <div className="Form Form--centered">{this.fields().toArray()}</div>];
|
||||
}
|
||||
|
||||
fields() {
|
||||
const items = new ItemList();
|
||||
|
||||
items.add('identification', <div className="Form-group">
|
||||
<input className="FormControl" name="identification" type="text" placeholder={app.translator.transText('core.forum.log_in.username_or_email_placeholder')}
|
||||
items.add(
|
||||
'identification',
|
||||
<div className="Form-group">
|
||||
<input
|
||||
className="FormControl"
|
||||
name="identification"
|
||||
type="text"
|
||||
placeholder={app.translator.transText('core.forum.log_in.username_or_email_placeholder')}
|
||||
bidi={this.identification}
|
||||
disabled={this.loading} />
|
||||
</div>, 30);
|
||||
disabled={this.loading}
|
||||
/>
|
||||
</div>,
|
||||
30
|
||||
);
|
||||
|
||||
items.add('password', <div className="Form-group">
|
||||
<input className="FormControl" name="password" type="password" placeholder={app.translator.transText('core.forum.log_in.password_placeholder')}
|
||||
items.add(
|
||||
'password',
|
||||
<div className="Form-group">
|
||||
<input
|
||||
className="FormControl"
|
||||
name="password"
|
||||
type="password"
|
||||
placeholder={app.translator.transText('core.forum.log_in.password_placeholder')}
|
||||
bidi={this.password}
|
||||
disabled={this.loading} />
|
||||
</div>, 20);
|
||||
disabled={this.loading}
|
||||
/>
|
||||
</div>,
|
||||
20
|
||||
);
|
||||
|
||||
items.add('remember', <div className="Form-group">
|
||||
items.add(
|
||||
'remember',
|
||||
<div className="Form-group">
|
||||
<div>
|
||||
<label className="checkbox">
|
||||
<input type="checkbox" bidi={this.remember} disabled={this.loading} />
|
||||
{app.translator.trans('core.forum.log_in.remember_me_label')}
|
||||
</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 Button--block',
|
||||
type: 'submit',
|
||||
loading: this.loading,
|
||||
children: app.translator.trans('core.forum.log_in.submit_button')
|
||||
children: app.translator.trans('core.forum.log_in.submit_button'),
|
||||
})}
|
||||
</div>, -10);
|
||||
</div>,
|
||||
-10
|
||||
);
|
||||
|
||||
return items;
|
||||
}
|
||||
@@ -115,9 +128,9 @@ export default class LogInModal extends Modal<LogInModalProps> {
|
||||
|
||||
app.forum.attribute('allowSignUp') && (
|
||||
<p className="LogInModal-signUp">
|
||||
{app.translator.trans('core.forum.log_in.sign_up_text', {a: <a onclick={this.signUp.bind(this)}/> })}
|
||||
{app.translator.trans('core.forum.log_in.sign_up_text', { a: <a onclick={this.signUp.bind(this)} /> })}
|
||||
</p>
|
||||
)
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -129,7 +142,7 @@ export default class LogInModal extends Modal<LogInModalProps> {
|
||||
*/
|
||||
forgotPassword() {
|
||||
const email = this.identification();
|
||||
const props = email.indexOf('@') !== -1 ? {email} : undefined;
|
||||
const props = email.indexOf('@') !== -1 ? { email } : undefined;
|
||||
|
||||
app.modal.show(new ForgotPasswordModal(props));
|
||||
}
|
||||
@@ -141,7 +154,7 @@ export default class LogInModal extends Modal<LogInModalProps> {
|
||||
* @public
|
||||
*/
|
||||
signUp() {
|
||||
const props = {password: this.password()};
|
||||
const props = { password: this.password() };
|
||||
const identification = this.identification();
|
||||
props[identification.indexOf('@') !== -1 ? 'email' : 'username'] = identification;
|
||||
|
||||
@@ -163,11 +176,9 @@ export default class LogInModal extends Modal<LogInModalProps> {
|
||||
const password = this.password();
|
||||
const remember = this.remember();
|
||||
|
||||
app.session.login({identification, password, remember}, {errorHandler: this.onerror.bind(this)})
|
||||
.then(
|
||||
() => window.location.reload(),
|
||||
this.loaded.bind(this)
|
||||
);
|
||||
app.session
|
||||
.login({ identification, password, remember }, { errorHandler: this.onerror.bind(this) })
|
||||
.then(() => window.location.reload(), this.loaded.bind(this));
|
||||
}
|
||||
|
||||
onerror(error) {
|
||||
|
@@ -34,7 +34,7 @@ export default class NotificationList extends Component {
|
||||
className: 'Button Button--icon Button--link',
|
||||
icon: 'fas fa-check',
|
||||
title: app.translator.transText('core.forum.notifications.mark_all_as_read_tooltip'),
|
||||
onclick: this.markAllAsRead.bind(this)
|
||||
onclick: this.markAllAsRead.bind(this),
|
||||
})}
|
||||
</div>
|
||||
|
||||
@@ -42,7 +42,8 @@ export default class NotificationList extends Component {
|
||||
</div>
|
||||
|
||||
<div className="NotificationList-content">
|
||||
{pages.length ? pages.map(notifications => {
|
||||
{pages.length
|
||||
? pages.map(notifications => {
|
||||
const groups = [];
|
||||
const discussions = {};
|
||||
|
||||
@@ -61,7 +62,7 @@ export default class NotificationList extends Component {
|
||||
// If the notification is not related to a discussion directly or
|
||||
// indirectly, then we will assign it to a neutral group.
|
||||
const key = discussion ? discussion.id() : 0;
|
||||
discussions[key] = discussions[key] || {discussion: discussion, notifications: []};
|
||||
discussions[key] = discussions[key] || { discussion: discussion, notifications: [] };
|
||||
discussions[key].notifications.push(notification);
|
||||
|
||||
if (groups.indexOf(discussions[key]) === -1) {
|
||||
@@ -74,32 +75,37 @@ export default class NotificationList extends Component {
|
||||
|
||||
return (
|
||||
<div className="NotificationGroup">
|
||||
{group.discussion
|
||||
? (
|
||||
<m.route.Link className="NotificationGroup-header"
|
||||
href={app.route.discussion(group.discussion)}>
|
||||
{badges && badges.length ? <ul className="NotificationGroup-badges badges">{listItems(badges)}</ul> : ''}
|
||||
{group.discussion ? (
|
||||
<m.route.Link className="NotificationGroup-header" href={app.route.discussion(group.discussion)}>
|
||||
{badges && badges.length ? (
|
||||
<ul className="NotificationGroup-badges badges">{listItems(badges)}</ul>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
{group.discussion.title()}
|
||||
</m.route.Link>
|
||||
) : (
|
||||
<div className="NotificationGroup-header">
|
||||
{app.forum.attribute('title')}
|
||||
</div>
|
||||
<div className="NotificationGroup-header">{app.forum.attribute('title')}</div>
|
||||
)}
|
||||
|
||||
<ul className="NotificationGroup-content">
|
||||
{group.notifications.map(notification => {
|
||||
const NotificationComponent = app.notificationComponents[notification.contentType()];
|
||||
return NotificationComponent ? <li>{NotificationComponent.component({notification})}</li> : '';
|
||||
return NotificationComponent ? <li>{NotificationComponent.component({ notification })}</li> : '';
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}) : ''}
|
||||
{this.loading
|
||||
? <LoadingIndicator className="LoadingIndicator--block" />
|
||||
: (pages.length ? '' : <div className="NotificationList-empty">{app.translator.trans('core.forum.notifications.empty_text')}</div>)}
|
||||
})
|
||||
: ''}
|
||||
{this.loading ? (
|
||||
<LoadingIndicator className="LoadingIndicator--block" />
|
||||
) : pages.length ? (
|
||||
''
|
||||
) : (
|
||||
<div className="NotificationList-empty">{app.translator.trans('core.forum.notifications.empty_text')}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -109,7 +115,7 @@ export default class NotificationList extends Component {
|
||||
super.oncreate(vnode);
|
||||
|
||||
const $notifications = this.$('.NotificationList-content');
|
||||
const $scrollParent = this.$scrollParent = $notifications.css('overflow') === 'auto' ? $notifications : $(window);
|
||||
const $scrollParent = (this.$scrollParent = $notifications.css('overflow') === 'auto' ? $notifications : $(window));
|
||||
|
||||
this.scrollHandler = () => {
|
||||
const scrollTop = $scrollParent.scrollTop();
|
||||
@@ -144,7 +150,7 @@ export default class NotificationList extends Component {
|
||||
return;
|
||||
}
|
||||
|
||||
app.session.user.pushAttributes({newNotificationCount: 0});
|
||||
app.session.user.pushAttributes({ newNotificationCount: 0 });
|
||||
|
||||
this.loadMore();
|
||||
}
|
||||
@@ -156,9 +162,10 @@ export default class NotificationList extends Component {
|
||||
this.loading = true;
|
||||
m.redraw();
|
||||
|
||||
const params = app.cache.notifications ? {page: {offset: app.cache.notifications.length * 10}} : null;
|
||||
const params = app.cache.notifications ? { page: { offset: app.cache.notifications.length * 10 } } : null;
|
||||
|
||||
return app.store.find<Notification>('notifications', params)
|
||||
return app.store
|
||||
.find<Notification>('notifications', params)
|
||||
.then(this.parseResults.bind(this))
|
||||
.catch(() => {})
|
||||
.then(() => {
|
||||
@@ -186,15 +193,15 @@ export default class NotificationList extends Component {
|
||||
markAllAsRead() {
|
||||
if (!app.cache.notifications) return;
|
||||
|
||||
app.session.user.pushAttributes({unreadNotificationCount: 0});
|
||||
app.session.user.pushAttributes({ unreadNotificationCount: 0 });
|
||||
|
||||
app.cache.notifications.forEach(notifications => {
|
||||
notifications.forEach(notification => notification.pushAttributes({isRead: true}))
|
||||
notifications.forEach(notification => notification.pushAttributes({ isRead: true }));
|
||||
});
|
||||
|
||||
app.request({
|
||||
url: `${app.forum.attribute('apiUrl')}/notifications/read`,
|
||||
method: 'POST'
|
||||
method: 'POST',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -21,7 +21,7 @@ export default class NotificationsDropdown extends Dropdown {
|
||||
|
||||
vdom.attrs.title = this.props.label;
|
||||
|
||||
vdom.attrs.className += (newNotifications ? ' new' : '');
|
||||
vdom.attrs.className += newNotifications ? ' new' : '';
|
||||
vdom.attrs.onclick = this.onclick.bind(this);
|
||||
|
||||
return vdom;
|
||||
@@ -31,9 +31,9 @@ export default class NotificationsDropdown extends Dropdown {
|
||||
const unread = this.getUnreadCount();
|
||||
|
||||
return [
|
||||
icon(this.props.icon, {className: 'Button-icon'}),
|
||||
icon(this.props.icon, { className: 'Button-icon' }),
|
||||
unread ? <span className="NotificationsDropdown-unread">{unread}</span> : '',
|
||||
<span className="Button-label">{this.props.label}</span>
|
||||
<span className="Button-label">{this.props.label}</span>,
|
||||
];
|
||||
}
|
||||
|
||||
|
@@ -1,13 +1,13 @@
|
||||
import Component, {ComponentProps} from '../../common/Component';
|
||||
import Component, { ComponentProps } from '../../common/Component';
|
||||
import Dropdown from '../../common/components/Dropdown';
|
||||
import PostControls from '../utils/PostControls';
|
||||
import listItems from '../../common/helpers/listItems';
|
||||
import ItemList from '../../common/utils/ItemList';
|
||||
import SubtreeRetainer from "../../common/utils/SubtreeRetainer";
|
||||
import SubtreeRetainer from '../../common/utils/SubtreeRetainer';
|
||||
import PostModel from '../../common/models/Post';
|
||||
|
||||
export interface PostProps extends ComponentProps {
|
||||
post: PostModel
|
||||
post: PostModel;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,20 +53,27 @@ export default class Post<T extends PostProps = PostProps> extends Component<Pos
|
||||
<aside className="Post-actions">
|
||||
<ul>
|
||||
{listItems(this.actionItems().toArray())}
|
||||
{controls.length ? <li>
|
||||
{controls.length ? (
|
||||
<li>
|
||||
<Dropdown
|
||||
className="Post-controls"
|
||||
buttonClassName="Button Button--icon Button--flat"
|
||||
menuClassName="Dropdown-menu--right"
|
||||
icon="fas fa-ellipsis-h"
|
||||
onshow={() => this.$('.Post-actions').addClass('open')}
|
||||
onhide={() => this.$('.Post-actions').removeClass('open')}>
|
||||
onhide={() => this.$('.Post-actions').removeClass('open')}
|
||||
>
|
||||
{controls}
|
||||
</Dropdown>
|
||||
</li> : ''}
|
||||
</li>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</ul>
|
||||
</aside>
|
||||
<footer className="Post-footer"><ul>{listItems(this.footerItems().toArray())}</ul></footer>
|
||||
<footer className="Post-footer">
|
||||
<ul>{listItems(this.footerItems().toArray())}</ul>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
</article>
|
||||
|
@@ -4,8 +4,8 @@ import avatar from '../../common/helpers/avatar';
|
||||
import username from '../../common/helpers/username';
|
||||
import userOnline from '../../common/helpers/userOnline';
|
||||
import listItems from '../../common/helpers/listItems';
|
||||
import {PostProps} from "./Post";
|
||||
import LinkButton from "../../common/components/LinkButton";
|
||||
import { PostProps } from './Post';
|
||||
import LinkButton from '../../common/components/LinkButton';
|
||||
|
||||
/**
|
||||
* The `PostUser` component shows the avatar and username of a post's author.
|
||||
@@ -23,7 +23,9 @@ export default class PostUser extends Component<PostProps> {
|
||||
if (!user) {
|
||||
return (
|
||||
<div className="PostUser">
|
||||
<h3>{avatar(user, {className: 'PostUser-avatar'})} {username(user)}</h3>
|
||||
<h3>
|
||||
{avatar(user, { className: 'PostUser-avatar' })} {username(user)}
|
||||
</h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -34,7 +36,7 @@ export default class PostUser extends Component<PostProps> {
|
||||
card = UserCard.component({
|
||||
user,
|
||||
className: 'UserCard--popover',
|
||||
controlsButtonClassName: 'Button Button--icon Button--flat'
|
||||
controlsButtonClassName: 'Button Button--icon Button--flat',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -42,14 +44,12 @@ export default class PostUser extends Component<PostProps> {
|
||||
<div className="PostUser">
|
||||
<h3>
|
||||
<LinkButton href={app.route.user(user)}>
|
||||
{avatar(user, {className: 'PostUser-avatar'})}
|
||||
{avatar(user, { className: 'PostUser-avatar' })}
|
||||
{userOnline(user)}
|
||||
{username(user)}
|
||||
</LinkButton>
|
||||
</h3>
|
||||
<ul className="PostUser-badges badges">
|
||||
{listItems(user.badges().toArray())}
|
||||
</ul>
|
||||
<ul className="PostUser-badges badges">{listItems(user.badges().toArray())}</ul>
|
||||
{card}
|
||||
</div>
|
||||
);
|
||||
@@ -86,7 +86,8 @@ export default class PostUser extends Component<PostProps> {
|
||||
* Hide the user card.
|
||||
*/
|
||||
hideCard() {
|
||||
this.$('.UserCard').removeClass('in')
|
||||
this.$('.UserCard')
|
||||
.removeClass('in')
|
||||
.one('transitionend webkitTransitionEnd oTransitionEnd', () => {
|
||||
this.cardVisible = false;
|
||||
m.redraw();
|
||||
|
@@ -43,7 +43,7 @@ export default class PostsUserPage extends UserPage {
|
||||
}
|
||||
|
||||
content() {
|
||||
if (this.posts.length === 0 && ! this.loading) {
|
||||
if (this.posts.length === 0 && !this.loading) {
|
||||
return (
|
||||
<div className="PostsUserPage">
|
||||
<Placeholder text={app.translator.trans('core.forum.user.posts_empty_text')} />
|
||||
@@ -61,7 +61,7 @@ export default class PostsUserPage extends UserPage {
|
||||
{Button.component({
|
||||
children: app.translator.trans('core.forum.user.posts_load_more_button'),
|
||||
className: 'Button',
|
||||
onclick: this.loadMore.bind(this)
|
||||
onclick: this.loadMore.bind(this),
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
@@ -73,15 +73,15 @@ export default class PostsUserPage extends UserPage {
|
||||
{this.posts.map(post => (
|
||||
<li>
|
||||
<div className="PostsUserPage-discussion">
|
||||
{app.translator.trans('core.forum.user.in_discussion_text', {discussion: <m.route.Link href={app.route.post(post)}>{post.discussion().title()}</m.route.Link>})}
|
||||
{app.translator.trans('core.forum.user.in_discussion_text', {
|
||||
discussion: <m.route.Link href={app.route.post(post)}>{post.discussion().title()}</m.route.Link>,
|
||||
})}
|
||||
</div>
|
||||
{CommentPost.component({post})}
|
||||
{CommentPost.component({ post })}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="PostsUserPage-loadMore">
|
||||
{footer}
|
||||
</div>
|
||||
<div className="PostsUserPage-loadMore">{footer}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -117,10 +117,10 @@ export default class PostsUserPage extends UserPage {
|
||||
return app.store.find<Post>('posts', {
|
||||
filter: {
|
||||
user: this.user.id(),
|
||||
type: 'comment'
|
||||
type: 'comment',
|
||||
},
|
||||
page: {offset, limit: this.loadLimit},
|
||||
sort: '-createdAt'
|
||||
page: { offset, limit: this.loadLimit },
|
||||
sort: '-createdAt',
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -50,7 +50,7 @@ export default class Search extends Component {
|
||||
* around as new results load), but otherwise it will be numeric (the
|
||||
* sequential position within the list).
|
||||
*/
|
||||
index: string|number = 0;
|
||||
index: string | number = 0;
|
||||
|
||||
navigator: KeyboardNavigatable;
|
||||
|
||||
@@ -72,33 +72,42 @@ export default class Search extends Component {
|
||||
}
|
||||
|
||||
// Hide the search view if no sources were loaded
|
||||
if (!this.sources.length) return <div/>;
|
||||
if (!this.sources.length) return <div />;
|
||||
|
||||
return (
|
||||
<div className={'Search ' + classNames({
|
||||
<div
|
||||
className={
|
||||
'Search ' +
|
||||
classNames({
|
||||
open: this.value() && this.hasFocus,
|
||||
focused: this.hasFocus,
|
||||
active: !!currentSearch,
|
||||
loading: !!this.loadingSources
|
||||
})}>
|
||||
loading: !!this.loadingSources,
|
||||
})
|
||||
}
|
||||
>
|
||||
<div className="Search-input">
|
||||
<input className="FormControl"
|
||||
<input
|
||||
className="FormControl"
|
||||
type="search"
|
||||
placeholder={app.translator.transText('core.forum.header.search_placeholder')}
|
||||
value={this.value()}
|
||||
oninput={m.withAttr('value', this.value)}
|
||||
onfocus={() => this.hasFocus = true}
|
||||
onblur={() => this.hasFocus = false}/>
|
||||
{this.loadingSources
|
||||
? LoadingIndicator.component({size: 'tiny', className: 'Button Button--icon Button--link'})
|
||||
: currentSearch
|
||||
? <button className="Search-clear Button Button--icon Button--link" onclick={this.clear.bind(this)}>{icon('fas fa-times-circle')}</button>
|
||||
: ''}
|
||||
onfocus={() => (this.hasFocus = true)}
|
||||
onblur={() => (this.hasFocus = false)}
|
||||
/>
|
||||
{this.loadingSources ? (
|
||||
LoadingIndicator.component({ size: 'tiny', className: 'Button Button--icon Button--link' })
|
||||
) : currentSearch ? (
|
||||
<button className="Search-clear Button Button--icon Button--link" onclick={this.clear.bind(this)}>
|
||||
{icon('fas fa-times-circle')}
|
||||
</button>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
<ul className="Dropdown-menu Search-results">
|
||||
{this.value() && this.hasFocus
|
||||
? this.sources.map(source => source.view(this.value()))
|
||||
: ''}
|
||||
{this.value() && this.hasFocus ? this.sources.map(source => source.view(this.value())) : ''}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
@@ -118,9 +127,7 @@ export default class Search extends Component {
|
||||
|
||||
// Whenever the mouse is hovered over a search result, highlight it.
|
||||
.on('mouseenter', '> li:not(.Dropdown-header)', function() {
|
||||
search.setIndex(
|
||||
search.selectableItems().index(this)
|
||||
);
|
||||
search.setIndex(search.selectableItems().index(this));
|
||||
});
|
||||
|
||||
const $input = this.$('input');
|
||||
@@ -164,7 +171,9 @@ export default class Search extends Component {
|
||||
})
|
||||
|
||||
.on('focus', function() {
|
||||
$(this).one('mouseup', e => e.preventDefault()).select();
|
||||
$(this)
|
||||
.one('mouseup', e => e.preventDefault())
|
||||
.select();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -185,7 +194,11 @@ export default class Search extends Component {
|
||||
this.loadingSources = 0;
|
||||
|
||||
if (this.value()) {
|
||||
m.route.set(this.getItem(this.index).find('a').attr('href'));
|
||||
m.route.set(
|
||||
this.getItem(this.index)
|
||||
.find('a')
|
||||
.attr('href')
|
||||
);
|
||||
} else {
|
||||
this.clear();
|
||||
}
|
||||
@@ -235,9 +248,7 @@ export default class Search extends Component {
|
||||
* @return {Integer}
|
||||
*/
|
||||
getCurrentNumericIndex() {
|
||||
return this.selectableItems().index(
|
||||
this.getItem(this.index)
|
||||
);
|
||||
return this.selectableItems().index(this.getItem(this.index));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -276,7 +287,10 @@ export default class Search extends Component {
|
||||
fixedIndex = 0;
|
||||
}
|
||||
|
||||
const $item = $items.removeClass('active').eq(fixedIndex).addClass('active');
|
||||
const $item = $items
|
||||
.removeClass('active')
|
||||
.eq(fixedIndex)
|
||||
.addClass('active');
|
||||
|
||||
this.index = $item.attr('data-index') || fixedIndex;
|
||||
|
||||
@@ -295,7 +309,7 @@ export default class Search extends Component {
|
||||
}
|
||||
|
||||
if (typeof scrollTop !== 'undefined') {
|
||||
$dropdown.animate({scrollTop}, 100);
|
||||
$dropdown.animate({ scrollTop }, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -29,10 +29,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>];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,26 +39,29 @@ export default class SessionDropdown extends Dropdown {
|
||||
const items = new ItemList();
|
||||
const user = app.session.user;
|
||||
|
||||
items.add('profile',
|
||||
items.add(
|
||||
'profile',
|
||||
LinkButton.component({
|
||||
icon: 'fas fa-user',
|
||||
children: app.translator.trans('core.forum.header.profile_button'),
|
||||
href: app.route.user(user)
|
||||
href: app.route.user(user),
|
||||
}),
|
||||
100
|
||||
);
|
||||
|
||||
items.add('settings',
|
||||
items.add(
|
||||
'settings',
|
||||
LinkButton.component({
|
||||
icon: 'fas fa-cog',
|
||||
children: app.translator.trans('core.forum.header.settings_button'),
|
||||
href: app.route('settings')
|
||||
href: app.route('settings'),
|
||||
}),
|
||||
50
|
||||
);
|
||||
|
||||
if (app.forum.attribute('adminUrl')) {
|
||||
items.add('administration',
|
||||
items.add(
|
||||
'administration',
|
||||
LinkButton.component({
|
||||
icon: 'fas fa-wrench',
|
||||
children: app.translator.trans('core.forum.header.admin_button'),
|
||||
@@ -74,11 +74,12 @@ export default class SessionDropdown extends Dropdown {
|
||||
|
||||
items.add('separator', Separator.component(), -90);
|
||||
|
||||
items.add('logOut',
|
||||
items.add(
|
||||
'logOut',
|
||||
Button.component({
|
||||
icon: 'fas fa-sign-out-alt',
|
||||
children: app.translator.trans('core.forum.header.log_out_button'),
|
||||
onclick: app.session.logout.bind(app.session)
|
||||
onclick: app.session.logout.bind(app.session),
|
||||
}),
|
||||
-100
|
||||
);
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import Component, {ComponentProps} from '../../common/Component';
|
||||
import Component, { ComponentProps } from '../../common/Component';
|
||||
import humanTime from '../../common/utils/humanTime';
|
||||
import ItemList from '../../common/utils/ItemList';
|
||||
import UserControls from '../utils/UserControls';
|
||||
@@ -8,7 +8,7 @@ import icon from '../../common/helpers/icon';
|
||||
import Dropdown from '../../common/components/Dropdown';
|
||||
import AvatarEditor from './AvatarEditor';
|
||||
import listItems from '../../common/helpers/listItems';
|
||||
import User from "../../common/models/User";
|
||||
import User from '../../common/models/User';
|
||||
|
||||
export interface UserCardProps extends ComponentProps {
|
||||
user: User;
|
||||
@@ -29,25 +29,25 @@ export default class UserCard extends Component<UserCardProps> {
|
||||
const badges = user.badges().toArray();
|
||||
|
||||
return (
|
||||
<div className={'UserCard ' + (this.props.className || '')}
|
||||
style={color ? {backgroundColor: color} : ''}>
|
||||
<div className={'UserCard ' + (this.props.className || '')} style={color ? { backgroundColor: color } : ''}>
|
||||
<div className="darkenBackground">
|
||||
|
||||
<div className="container">
|
||||
{controls.length ? Dropdown.component({
|
||||
{controls.length
|
||||
? Dropdown.component({
|
||||
children: controls,
|
||||
className: 'UserCard-controls App-primaryControl',
|
||||
menuClassName: 'Dropdown-menu--right',
|
||||
buttonClassName: this.props.controlsButtonClassName,
|
||||
label: app.translator.trans('core.forum.user_controls.button'),
|
||||
icon: 'fas fa-ellipsis-v'
|
||||
}) : ''}
|
||||
icon: 'fas fa-ellipsis-v',
|
||||
})
|
||||
: ''}
|
||||
|
||||
<div className="UserCard-profile">
|
||||
<h2 className="UserCard-identity">
|
||||
{this.props.editable
|
||||
? [AvatarEditor.component({user, className: 'UserCard-avatar'}), username(user)]
|
||||
: (
|
||||
{this.props.editable ? (
|
||||
[AvatarEditor.component({ user, className: 'UserCard-avatar' }), username(user)]
|
||||
) : (
|
||||
<m.route.Link href={app.route.user(user)}>
|
||||
<div className="UserCard-avatar">{avatar(user)}</div>
|
||||
{username(user)}
|
||||
@@ -55,15 +55,9 @@ export default class UserCard extends Component<UserCardProps> {
|
||||
)}
|
||||
</h2>
|
||||
|
||||
{badges.length ? (
|
||||
<ul className="UserCard-badges badges">
|
||||
{listItems(badges)}
|
||||
</ul>
|
||||
) : ''}
|
||||
{badges.length ? <ul className="UserCard-badges badges">{listItems(badges)}</ul> : ''}
|
||||
|
||||
<ul className="UserCard-info">
|
||||
{listItems(this.infoItems().toArray())}
|
||||
</ul>
|
||||
<ul className="UserCard-info">{listItems(this.infoItems().toArray())}</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -84,16 +78,17 @@ export default class UserCard extends Component<UserCardProps> {
|
||||
if (lastSeenAt) {
|
||||
const online = user.isOnline();
|
||||
|
||||
items.add('lastSeen', (
|
||||
items.add(
|
||||
'lastSeen',
|
||||
<span className={'UserCard-lastSeen' + (online ? ' online' : '')}>
|
||||
{online
|
||||
? [icon('fas fa-circle'), ' ', app.translator.trans('core.forum.user.online_text')]
|
||||
: [icon('far fa-clock'), ' ', humanTime(lastSeenAt)]}
|
||||
</span>
|
||||
));
|
||||
);
|
||||
}
|
||||
|
||||
items.add('joined', app.translator.trans('core.forum.user.joined_date_text', {ago: humanTime(user.joinTime())}));
|
||||
items.add('joined', app.translator.trans('core.forum.user.joined_date_text', { ago: humanTime(user.joinTime()) }));
|
||||
|
||||
return items;
|
||||
}
|
||||
|
@@ -30,26 +30,24 @@ export default abstract class UserPage extends Page {
|
||||
view() {
|
||||
return (
|
||||
<div className="UserPage">
|
||||
{this.user ? [
|
||||
{this.user
|
||||
? [
|
||||
UserCard.component({
|
||||
user: this.user,
|
||||
className: 'Hero UserHero',
|
||||
editable: this.user.canEdit() || this.user === app.session.user,
|
||||
controlsButtonClassName: 'Button'
|
||||
controlsButtonClassName: 'Button',
|
||||
}),
|
||||
<div className="container">
|
||||
<div className="sideNavContainer">
|
||||
<nav className="sideNav UserPage-nav" config={affixSidebar}>
|
||||
<ul>{listItems(this.sidebarItems().toArray())}</ul>
|
||||
</nav>
|
||||
<div className="sideNavOffset UserPage-content">
|
||||
{this.content()}
|
||||
<div className="sideNavOffset UserPage-content">{this.content()}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
] : [
|
||||
LoadingIndicator.component({lassName: 'LoadingIndicator--block'})
|
||||
]}
|
||||
</div>,
|
||||
]
|
||||
: [LoadingIndicator.component({ lassName: 'LoadingIndicator--block' })]}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -105,11 +103,12 @@ export default abstract class UserPage extends Page {
|
||||
sidebarItems() {
|
||||
const items = new ItemList();
|
||||
|
||||
items.add('nav',
|
||||
items.add(
|
||||
'nav',
|
||||
SelectDropdown.component({
|
||||
children: this.navItems().toArray(),
|
||||
className: 'App-titleControl',
|
||||
buttonClassName: 'Button'
|
||||
buttonClassName: 'Button',
|
||||
})
|
||||
);
|
||||
|
||||
@@ -123,31 +122,34 @@ export default abstract class UserPage extends Page {
|
||||
const items = new ItemList();
|
||||
const user = this.user;
|
||||
|
||||
items.add('posts',
|
||||
items.add(
|
||||
'posts',
|
||||
LinkButton.component({
|
||||
href: app.route('user.posts', {username: user.username()}),
|
||||
href: app.route('user.posts', { username: user.username() }),
|
||||
children: [app.translator.trans('core.forum.user.posts_link'), <span className="Button-badge">{user.commentCount()}</span>],
|
||||
icon: 'far fa-comment'
|
||||
icon: 'far fa-comment',
|
||||
}),
|
||||
100
|
||||
);
|
||||
|
||||
items.add('discussions',
|
||||
items.add(
|
||||
'discussions',
|
||||
LinkButton.component({
|
||||
href: app.route('user.discussions', {username: user.username()}),
|
||||
href: app.route('user.discussions', { username: user.username() }),
|
||||
children: [app.translator.trans('core.forum.user.discussions_link'), <span className="Button-badge">{user.discussionCount()}</span>],
|
||||
icon: 'fas fa-bars'
|
||||
icon: 'fas fa-bars',
|
||||
}),
|
||||
90
|
||||
);
|
||||
|
||||
if (app.session.user === user) {
|
||||
items.add('separator', Separator.component(), -90);
|
||||
items.add('settings',
|
||||
items.add(
|
||||
'settings',
|
||||
LinkButton.component({
|
||||
href: app.route('settings'),
|
||||
children: app.translator.trans('core.forum.user.settings_link'),
|
||||
icon: 'fas fa-cog'
|
||||
icon: 'fas fa-cog',
|
||||
}),
|
||||
-100
|
||||
);
|
||||
|
@@ -14,10 +14,12 @@ export default class UsersSearchSource extends SearchSource {
|
||||
protected results: { [key: string]: User[] } = {};
|
||||
|
||||
search(query: string) {
|
||||
return app.store.find<User>('users', {
|
||||
filter: {q: query},
|
||||
page: {limit: 5}
|
||||
}).then(results => {
|
||||
return app.store
|
||||
.find<User>('users', {
|
||||
filter: { q: query },
|
||||
page: { limit: 5 },
|
||||
})
|
||||
.then(results => {
|
||||
this.results[query] = results;
|
||||
m.redraw();
|
||||
});
|
||||
@@ -27,7 +29,11 @@ export default class UsersSearchSource extends SearchSource {
|
||||
query = query.toLowerCase();
|
||||
|
||||
const results = (this.results[query] || [])
|
||||
.concat(app.store.all<User>('users').filter(user => [user.username(), user.displayName()].some(value => value.toLowerCase().substr(0, query.length) === query)))
|
||||
.concat(
|
||||
app.store
|
||||
.all<User>('users')
|
||||
.filter(user => [user.username(), user.displayName()].some(value => value.toLowerCase().substr(0, query.length) === query))
|
||||
)
|
||||
.filter((e, i, arr) => arr.lastIndexOf(e) === i)
|
||||
.sort((a, b) => a.displayName().localeCompare(b.displayName()));
|
||||
|
||||
@@ -53,7 +59,7 @@ export default class UsersSearchSource extends SearchSource {
|
||||
</m.route.Link>
|
||||
</li>
|
||||
);
|
||||
})
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -58,9 +58,9 @@ export default class History {
|
||||
// then we'll overwrite it with the new URL.
|
||||
const top = this.getCurrent();
|
||||
if (top && top.name === name) {
|
||||
Object.assign(top, {url, title});
|
||||
Object.assign(top, { url, title });
|
||||
} else {
|
||||
this.stack.push({name, url, title});
|
||||
this.stack.push({ name, url, title });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ export default class History {
|
||||
* Go back to the previous route in the history stack.
|
||||
*/
|
||||
back() {
|
||||
if (! this.canGoBack()) {
|
||||
if (!this.canGoBack()) {
|
||||
return this.home();
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
export type KeyboardEventCallback = (KeyboardEvent) => boolean|void;
|
||||
export type KeyboardEventCallback = (KeyboardEvent) => boolean | void;
|
||||
|
||||
/**
|
||||
* The `KeyboardNavigatable` class manages lists that can be navigated with the
|
||||
|
@@ -1,11 +1,11 @@
|
||||
import {Vnode} from "mithril";
|
||||
import { Vnode } from 'mithril';
|
||||
|
||||
// import EditPostComposer from '../components/EditPostComposer';
|
||||
import Button from '../../common/components/Button';
|
||||
import Separator from '../../common/components/Separator';
|
||||
import ItemList from '../../common/utils/ItemList';
|
||||
import Post from "../../common/models/Post";
|
||||
import PostComponent from "../../forum/components/Post";
|
||||
import Post from '../../common/models/Post';
|
||||
import PostComponent from '../../forum/components/Post';
|
||||
|
||||
/**
|
||||
* The `PostControls` utility constructs a list of buttons for a post which
|
||||
@@ -60,10 +60,16 @@ export default {
|
||||
|
||||
if (post.contentType() === 'comment' && post.canEdit()) {
|
||||
if (!post.isHidden()) {
|
||||
items.add('edit', Button.component({
|
||||
items.add(
|
||||
'edit',
|
||||
Button.component(
|
||||
{
|
||||
icon: 'fas fa-pencil-alt',
|
||||
onclick: this.editAction.bind(post)
|
||||
}, app.translator.trans('core.forum.post_controls.edit_button')));
|
||||
onclick: this.editAction.bind(post),
|
||||
},
|
||||
app.translator.trans('core.forum.post_controls.edit_button')
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,26 +89,35 @@ export default {
|
||||
|
||||
if (post.contentType() === 'comment' && !post.isHidden()) {
|
||||
if (post.canHide()) {
|
||||
items.add('hide', Button.component({
|
||||
items.add(
|
||||
'hide',
|
||||
Button.component({
|
||||
icon: 'far fa-trash-alt',
|
||||
children: app.translator.trans('core.forum.post_controls.delete_button'),
|
||||
onclick: this.hideAction.bind(post)
|
||||
}));
|
||||
onclick: this.hideAction.bind(post),
|
||||
})
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (post.contentType() === 'comment' && post.canHide()) {
|
||||
items.add('restore', Button.component({
|
||||
items.add(
|
||||
'restore',
|
||||
Button.component({
|
||||
icon: 'fas fa-reply',
|
||||
children: app.translator.trans('core.forum.post_controls.restore_button'),
|
||||
onclick: this.restoreAction.bind(post)
|
||||
}));
|
||||
onclick: this.restoreAction.bind(post),
|
||||
})
|
||||
);
|
||||
}
|
||||
if (post.canDelete()) {
|
||||
items.add('delete', Button.component({
|
||||
items.add(
|
||||
'delete',
|
||||
Button.component({
|
||||
icon: 'fas fa-times',
|
||||
children: app.translator.trans('core.forum.post_controls.delete_forever_button'),
|
||||
onclick: this.deleteAction.bind(post, context)
|
||||
}));
|
||||
onclick: this.deleteAction.bind(post, context),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,5 +186,5 @@ export default {
|
||||
if (context) context.loading = false;
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@@ -4,7 +4,7 @@ import Separator from '../../common/components/Separator';
|
||||
// import EditUserModal from '../components/EditUserModal';
|
||||
import UserPage from '../components/UserPage';
|
||||
import ItemList from '../../common/utils/ItemList';
|
||||
import User from "../../common/models/User";
|
||||
import User from '../../common/models/User';
|
||||
|
||||
/**
|
||||
* The `UserControls` utility constructs a list of buttons for a user which
|
||||
@@ -46,11 +46,14 @@ export default {
|
||||
const items = new ItemList();
|
||||
|
||||
if (user.canEdit()) {
|
||||
items.add('edit', Button.component({
|
||||
items.add(
|
||||
'edit',
|
||||
Button.component({
|
||||
icon: 'fas fa-pencil-alt',
|
||||
children: app.translator.trans('core.forum.user_controls.edit_button'),
|
||||
onclick: this.editAction.bind(this, user)
|
||||
}));
|
||||
onclick: this.editAction.bind(this, user),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return items;
|
||||
@@ -63,11 +66,14 @@ export default {
|
||||
const items = new ItemList();
|
||||
|
||||
if (user.id() !== '1' && user.canDelete()) {
|
||||
items.add('delete', Button.component({
|
||||
items.add(
|
||||
'delete',
|
||||
Button.component({
|
||||
icon: 'fas fa-times',
|
||||
children: app.translator.trans('core.forum.user_controls.delete_button'),
|
||||
onclick: this.deleteAction.bind(this, user)
|
||||
}));
|
||||
onclick: this.deleteAction.bind(this, user),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return items;
|
||||
@@ -81,14 +87,16 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
user.delete().then(() => {
|
||||
user.delete()
|
||||
.then(() => {
|
||||
this.showDeletionAlert(user, 'success');
|
||||
if (app.current instanceof UserPage && app.current.user === user) {
|
||||
app.history.back();
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
}).catch(() => this.showDeletionAlert(user, 'error'));
|
||||
})
|
||||
.catch(() => this.showDeletionAlert(user, 'error'));
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -98,18 +106,18 @@ export default {
|
||||
const { username, email } = user.data.attributes;
|
||||
const message = `core.forum.user_controls.delete_${type}_message`;
|
||||
|
||||
app.alerts.show(Alert.component({
|
||||
app.alerts.show(
|
||||
Alert.component({
|
||||
type,
|
||||
children: app.translator.trans(
|
||||
message, { username, email }
|
||||
)
|
||||
}));
|
||||
children: app.translator.trans(message, { username, email }),
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Edit the user.
|
||||
*/
|
||||
editAction(user: User) {
|
||||
app.modal.show(new EditUserModal({user}));
|
||||
}
|
||||
app.modal.show(new EditUserModal({ user }));
|
||||
},
|
||||
};
|
||||
|
@@ -4,9 +4,21 @@ const merge = require('webpack-merge');
|
||||
|
||||
module.exports = merge(config(), {
|
||||
output: {
|
||||
library: 'flarum.core'
|
||||
library: 'flarum.core',
|
||||
},
|
||||
|
||||
// use zepto instead of jquery
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: require.resolve('zepto'),
|
||||
use: 'imports-loader?this=>window',
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
jquery: 'zepto',
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
|
||||
]
|
||||
});
|
||||
|
Reference in New Issue
Block a user