mirror of
https://github.com/flarum/core.git
synced 2025-08-07 08:56:38 +02:00
Add ModalManager & LogInModal, add bidi attribute, fix Translator issues with text and vnodes
This commit is contained in:
@@ -15,5 +15,5 @@ indent_size = 2
|
|||||||
[*.{diff,md}]
|
[*.{diff,md}]
|
||||||
trim_trailing_whitespace = false
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
[*.{php,xml}]
|
[*.{php,xml,ts,tsx}]
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
11733
js/dist/admin.js
vendored
11733
js/dist/admin.js
vendored
File diff suppressed because one or more lines are too long
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
19251
js/dist/forum.js
vendored
19251
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
@@ -19,12 +19,13 @@ import Notification from './models/Notification';
|
|||||||
|
|
||||||
import RequestError from './utils/RequestError';
|
import RequestError from './utils/RequestError';
|
||||||
import Alert from './components/Alert';
|
import Alert from './components/Alert';
|
||||||
|
import ModalManager from './components/ModalManager';
|
||||||
|
|
||||||
export type ApplicationData = {
|
export type ApplicationData = {
|
||||||
apiDocument: any;
|
apiDocument: any;
|
||||||
locale: string;
|
locale: string;
|
||||||
locales: any;
|
locales: any;
|
||||||
resources: Array<any>;
|
resources: any[];
|
||||||
session: any;
|
session: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -42,7 +43,7 @@ export default abstract class Application {
|
|||||||
/**
|
/**
|
||||||
* The app's session.
|
* The app's session.
|
||||||
*/
|
*/
|
||||||
session?: Session;
|
session: Session;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The app's data store.
|
* The app's data store.
|
||||||
@@ -58,6 +59,8 @@ export default abstract class Application {
|
|||||||
|
|
||||||
drawer = new Drawer();
|
drawer = new Drawer();
|
||||||
|
|
||||||
|
modal: ModalManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A local cache that can be used to store data at the application level, so
|
* A local cache that can be used to store data at the application level, so
|
||||||
* that is persists between different routes.
|
* that is persists between different routes.
|
||||||
@@ -76,7 +79,8 @@ export default abstract class Application {
|
|||||||
private requestError: Alert = null;
|
private requestError: Alert = null;
|
||||||
|
|
||||||
mount(basePath = '') {
|
mount(basePath = '') {
|
||||||
// this.modal = m.mount(document.getElementById('modal'), <ModalManager />);
|
m.mount(document.getElementById('modal'), new ModalManager());
|
||||||
|
|
||||||
// this.alerts = m.mount(document.getElementById('alerts'), <AlertManager />);
|
// this.alerts = m.mount(document.getElementById('alerts'), <AlertManager />);
|
||||||
|
|
||||||
m.route(document.getElementById('content'), basePath + '/', mapRoutes(this.routes, basePath));
|
m.route(document.getElementById('content'), basePath + '/', mapRoutes(this.routes, basePath));
|
||||||
|
@@ -9,9 +9,9 @@ export type ComponentProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default class Component<T extends ComponentProps = any> {
|
export default class Component<T extends ComponentProps = any> {
|
||||||
protected element: HTMLElement;
|
element: HTMLElement;
|
||||||
|
|
||||||
protected props = <T> {};
|
props = <T> {};
|
||||||
|
|
||||||
view(vnode) {
|
view(vnode) {
|
||||||
throw new Error('Component#view must be implemented by subclass');
|
throw new Error('Component#view must be implemented by subclass');
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
import User from './models/User';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `Session` class defines the current user session. It stores a reference
|
* The `Session` class defines the current user session. It stores a reference
|
||||||
* to the current authenticated user, and provides methods to log in/out.
|
* to the current authenticated user, and provides methods to log in/out.
|
||||||
@@ -22,11 +24,11 @@ export default class Session {
|
|||||||
/**
|
/**
|
||||||
* Attempt to log in a user.
|
* Attempt to log in a user.
|
||||||
*/
|
*/
|
||||||
login(data: { identification: string, password: string }, options = {}): Promise {
|
login(body: { identification: string, password: string }, options = {}) {
|
||||||
return app.request(Object.assign({
|
return app.request(Object.assign({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: app.forum.attribute('baseUrl') + '/login',
|
url: app.forum.attribute('baseUrl') + '/login',
|
||||||
data
|
body
|
||||||
}, options));
|
}, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,6 +38,6 @@ export default class Session {
|
|||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
logout() {
|
logout() {
|
||||||
window.location = app.forum.attribute('baseUrl') + '/logout?token=' + this.csrfToken;
|
window.location.href = `${app.forum.attribute('baseUrl')}/logout?token=${this.csrfToken}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -77,7 +77,7 @@ export default class Store {
|
|||||||
* @param query
|
* @param query
|
||||||
* @param options
|
* @param options
|
||||||
*/
|
*/
|
||||||
find<T extends Model = Model>(type: string, id?: number|Array<number>|any, query = {}, options = {}): Promise<T[]> {
|
find<T extends Model = Model>(type: string, id?: number|number[]|any, query = {}, options = {}): Promise<T[]> {
|
||||||
let data = query;
|
let data = query;
|
||||||
let url = `${app.forum.attribute('apiUrl')}/${type}`;
|
let url = `${app.forum.attribute('apiUrl')}/${type}`;
|
||||||
|
|
||||||
|
@@ -65,7 +65,7 @@ export default class Translator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
open[0].push(part);
|
open[0].push({ tag: 'span', text: part });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -54,7 +54,7 @@ export default class Button<T extends ButtonProps = ButtonProps> extends Compone
|
|||||||
delete attrs.onclick;
|
delete attrs.onclick;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <button {...attrs}>{this.getButtonContent(attrs.icon, attrs.loading, children)}</button>;
|
return <button {...attrs}>{this.getButtonContent(iconName, attrs.loading, children)}</button>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
116
js/src/common/components/Modal.tsx
Normal file
116
js/src/common/components/Modal.tsx
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
import Component, {ComponentProps} from '../Component';
|
||||||
|
import Alert from './Alert';
|
||||||
|
import Button from './Button';
|
||||||
|
import RequestError from "../utils/RequestError";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `Modal` component displays a modal dialog, wrapped in a form. Subclasses
|
||||||
|
* should implement the `className`, `title`, and `content` methods.
|
||||||
|
*/
|
||||||
|
export default abstract class Modal<T extends ComponentProps = ComponentProps> extends Component<T> {
|
||||||
|
/**
|
||||||
|
* An alert component to show below the header.
|
||||||
|
*/
|
||||||
|
alert: Alert;
|
||||||
|
|
||||||
|
loading: boolean;
|
||||||
|
|
||||||
|
view() {
|
||||||
|
if (this.alert) {
|
||||||
|
this.alert.props.dismissible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`Modal modal-dialog ${this.className()}`}>
|
||||||
|
<div className="Modal-content">
|
||||||
|
{this.isDismissible() ? (
|
||||||
|
<div className="Modal-close App-backControl">
|
||||||
|
{Button.component({
|
||||||
|
icon: 'fas fa-times',
|
||||||
|
onclick: this.hide.bind(this),
|
||||||
|
className: 'Button Button--icon Button--link'
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
) : ''}
|
||||||
|
|
||||||
|
<form onsubmit={this.onsubmit.bind(this)}>
|
||||||
|
<div className="Modal-header">
|
||||||
|
<h3 className="App-titleControl App-titleControl--text">{this.title()}</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{alert && <div className="Modal-alert">{this.alert}</div>}
|
||||||
|
|
||||||
|
{this.content()}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether or not the modal should be dismissible via an 'x' button.
|
||||||
|
*/
|
||||||
|
isDismissible(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the class name to apply to the modal.
|
||||||
|
*/
|
||||||
|
abstract className(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the title of the modal dialog.
|
||||||
|
*/
|
||||||
|
abstract title(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the content of the modal.
|
||||||
|
*/
|
||||||
|
abstract content();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the modal form's submit event.
|
||||||
|
*/
|
||||||
|
onsubmit(e: Event) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Focus on the first input when the modal is ready to be used.
|
||||||
|
*/
|
||||||
|
onready() {
|
||||||
|
this.$('form').find('input, select, textarea').first().focus().select();
|
||||||
|
}
|
||||||
|
|
||||||
|
onhide() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the modal.
|
||||||
|
*/
|
||||||
|
hide() {
|
||||||
|
app.modal.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop loading.
|
||||||
|
*/
|
||||||
|
loaded() {
|
||||||
|
this.loading = false;
|
||||||
|
m.redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show an alert describing an error returned from the API, and give focus to
|
||||||
|
* the first relevant field.
|
||||||
|
*/
|
||||||
|
onerror(error: RequestError) {
|
||||||
|
this.alert = error.alert;
|
||||||
|
|
||||||
|
m.redraw();
|
||||||
|
|
||||||
|
if (error.status === 422 && error.response.errors) {
|
||||||
|
this.$(`form [name="${error.response.errors[0].source.pointer.replace('/data/attributes/', '')}"]`).select();
|
||||||
|
} else {
|
||||||
|
this.onready();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
115
js/src/common/components/ModalManager.tsx
Normal file
115
js/src/common/components/ModalManager.tsx
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import MicroModal from 'micromodal';
|
||||||
|
|
||||||
|
import Component from "../Component";
|
||||||
|
import Modal from './Modal';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `ModalManager` component manages a modal dialog. Only one modal dialog
|
||||||
|
* can be shown at once; loading a new component into the ModalManager will
|
||||||
|
* overwrite the previous one.
|
||||||
|
*/
|
||||||
|
export default class ModalManager extends Component {
|
||||||
|
showing: boolean;
|
||||||
|
component: Modal;
|
||||||
|
hideTimeout: number;
|
||||||
|
|
||||||
|
oncreate(vnode) {
|
||||||
|
super.oncreate(vnode);
|
||||||
|
|
||||||
|
app.modal = this;
|
||||||
|
|
||||||
|
window.vnode = vnode;
|
||||||
|
}
|
||||||
|
|
||||||
|
view() {
|
||||||
|
return (
|
||||||
|
<div className="ModalManager modal" id="Modal" onclick={this.onclick.bind(this)} key="modal">
|
||||||
|
{this.component && m(this.component)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a modal dialog.
|
||||||
|
*/
|
||||||
|
show(component: Modal) {
|
||||||
|
if (!(component instanceof Modal)) {
|
||||||
|
throw new Error('The ModalManager component can only show Modal components');
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTimeout(this.hideTimeout);
|
||||||
|
|
||||||
|
this.showing = true;
|
||||||
|
this.component = component;
|
||||||
|
|
||||||
|
// if (app.current) app.current.retain = true;
|
||||||
|
|
||||||
|
m.redraw(true);
|
||||||
|
|
||||||
|
if (!$('.modal-backdrop').length) {
|
||||||
|
$('<div />').addClass('modal-backdrop')
|
||||||
|
.appendTo('body');
|
||||||
|
}
|
||||||
|
|
||||||
|
MicroModal.show('Modal', {
|
||||||
|
awaitCloseAnimation: true,
|
||||||
|
onClose: () => {
|
||||||
|
$('.modal-backdrop').fadeOut(200, function () {
|
||||||
|
this.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.showing = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.onready();
|
||||||
|
}
|
||||||
|
|
||||||
|
onclick(e) {
|
||||||
|
if (e.target === this.element) {
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the modal dialog.
|
||||||
|
*/
|
||||||
|
close() {
|
||||||
|
if (!this.showing) return;
|
||||||
|
|
||||||
|
// Don't hide the modal immediately, because if the consumer happens to call
|
||||||
|
// the `show` method straight after to show another modal dialog, it will
|
||||||
|
// cause the new modal dialog to disappear. Instead we will wait for a tiny
|
||||||
|
// bit to give the `show` method the opportunity to prevent this from going
|
||||||
|
// ahead.
|
||||||
|
this.hideTimeout = setTimeout(() => MicroModal.close('Modal'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear content from the modal area.
|
||||||
|
*
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
clear() {
|
||||||
|
if (this.component) {
|
||||||
|
this.component.onhide();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.component = null;
|
||||||
|
|
||||||
|
app.current.retain = false;
|
||||||
|
|
||||||
|
m.lazyRedraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the modal dialog is ready to be used, tell it!
|
||||||
|
*
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
onready() {
|
||||||
|
if (this.component && this.component.onready) {
|
||||||
|
this.component.onready(this.$());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -2,12 +2,21 @@ import Mithril from "mithril";
|
|||||||
|
|
||||||
import Alert from "../components/Alert";
|
import Alert from "../components/Alert";
|
||||||
|
|
||||||
|
export interface RequestErrorResponse extends JSON {
|
||||||
|
errors?: {
|
||||||
|
code: string;
|
||||||
|
source?: {
|
||||||
|
pointer: string;
|
||||||
|
};
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
export default class RequestError {
|
export default class RequestError {
|
||||||
status: number;
|
status: number;
|
||||||
responseText: string;
|
responseText: string;
|
||||||
options: Mithril.RequestOptions;
|
options: Mithril.RequestOptions;
|
||||||
xhr: XMLHttpRequest;
|
xhr: XMLHttpRequest;
|
||||||
response?: JSON;
|
response?: RequestErrorResponse;
|
||||||
alert?: Alert;
|
alert?: Alert;
|
||||||
|
|
||||||
constructor(status, responseText, options, xhr) {
|
constructor(status, responseText, options, xhr) {
|
||||||
|
@@ -6,7 +6,7 @@
|
|||||||
* @param {function} compute The function which computes the value using the
|
* @param {function} compute The function which computes the value using the
|
||||||
* dependent values.
|
* dependent values.
|
||||||
*/
|
*/
|
||||||
export default function computed(...dependentKeys: Array<string|Function>): () => any {
|
export default function computed(...dependentKeys: string[]|Function[]): () => any {
|
||||||
const keys = <string[]> dependentKeys.slice(0, -1);
|
const keys = <string[]> dependentKeys.slice(0, -1);
|
||||||
const compute = <Function> dependentKeys.slice(-1)[0];
|
const compute = <Function> dependentKeys.slice(-1)[0];
|
||||||
|
|
||||||
|
@@ -8,7 +8,7 @@ export default function extractText(vdom: any): string {
|
|||||||
if (vdom instanceof Array) {
|
if (vdom instanceof Array) {
|
||||||
return vdom.map(element => extractText(element)).join('');
|
return vdom.map(element => extractText(element)).join('');
|
||||||
} else if (typeof vdom === 'object' && vdom !== null) {
|
} else if (typeof vdom === 'object' && vdom !== null) {
|
||||||
return extractText(vdom.children);
|
return vdom.text || extractText(vdom.children);
|
||||||
} else {
|
} else {
|
||||||
return vdom;
|
return vdom;
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,34 @@
|
|||||||
import prop from 'mithril/stream';
|
import prop from 'mithril/stream';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
m.withAttr = (key: string, cb: Function) => function () {
|
const mo = global.m;
|
||||||
cb(this.getAttribute(key) || this[key]);
|
|
||||||
};
|
|
||||||
|
|
||||||
m.prop = prop;
|
const m = function (comp, ...args) {
|
||||||
|
const node = mo.apply(this, arguments);
|
||||||
|
|
||||||
|
if (!node.attrs) node.attrs = {};
|
||||||
|
|
||||||
|
if (node.attrs.bidi) {
|
||||||
|
m.bidi(node, node.attrs.bidi);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.attrs.route) {
|
||||||
|
node.attrs.href = node.attrs.route;
|
||||||
|
node.attrs.tag = m.route.Link;
|
||||||
|
|
||||||
|
delete node.attrs.route;
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.keys(mo).forEach(key => m[key] = mo[key]);
|
||||||
|
|
||||||
|
m.withAttr = (key: string, cb: Function) => function () {
|
||||||
|
cb(this.getAttribute(key) || this[key]);
|
||||||
|
};
|
||||||
|
|
||||||
|
m.prop = prop;
|
||||||
|
|
||||||
|
global.m = m;
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import Component from '../../common/Component';
|
import Component from '../../common/Component';
|
||||||
import Button from '../../common/components/Button';
|
import Button from '../../common/components/Button';
|
||||||
// import LogInModal from './LogInModal';
|
import LogInModal from './LogInModal';
|
||||||
// import SignUpModal from './SignUpModal';
|
// import SignUpModal from './SignUpModal';
|
||||||
import SessionDropdown from './SessionDropdown';
|
import SessionDropdown from './SessionDropdown';
|
||||||
import SelectDropdown from '../../common/components/SelectDropdown';
|
import SelectDropdown from '../../common/components/SelectDropdown';
|
||||||
|
186
js/src/forum/components/LogInModal.tsx
Normal file
186
js/src/forum/components/LogInModal.tsx
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
import Stream from 'mithril/stream';
|
||||||
|
|
||||||
|
import Modal from '../../common/components/Modal';
|
||||||
|
import {ComponentProps} from '../../common/Component';
|
||||||
|
import ItemList from "../../common/utils/ItemList";
|
||||||
|
import Button from "../../common/components/Button";
|
||||||
|
|
||||||
|
export interface LogInModalProps extends ComponentProps {
|
||||||
|
username?: string;
|
||||||
|
password?: string;
|
||||||
|
remember?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `LogInModal` component displays a modal dialog with a login form.
|
||||||
|
*
|
||||||
|
* ### Props
|
||||||
|
*
|
||||||
|
* - `identification`
|
||||||
|
* - `password`
|
||||||
|
* - `remember`
|
||||||
|
*/
|
||||||
|
export default class LogInModal extends Modal<LogInModalProps> {
|
||||||
|
/**
|
||||||
|
* The value of the identification input.
|
||||||
|
*/
|
||||||
|
identification: Stream<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The value of the password input.
|
||||||
|
*/
|
||||||
|
password: Stream<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The value of the remember me input.
|
||||||
|
*/
|
||||||
|
remember: Stream<string>;
|
||||||
|
|
||||||
|
oninit(vnode) {
|
||||||
|
super.oninit(vnode);
|
||||||
|
|
||||||
|
console.log('#oninit');
|
||||||
|
|
||||||
|
this.identification = m.prop(this.props.identification || '');
|
||||||
|
|
||||||
|
this.password = m.prop(this.props.password || '');
|
||||||
|
|
||||||
|
this.remember = m.prop(!!this.props.remember);
|
||||||
|
}
|
||||||
|
|
||||||
|
className(): string {
|
||||||
|
return 'LogInModal Modal--small';
|
||||||
|
}
|
||||||
|
|
||||||
|
title(): string {
|
||||||
|
return app.translator.transText('core.forum.log_in.title');
|
||||||
|
}
|
||||||
|
|
||||||
|
content() {
|
||||||
|
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>
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
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')}
|
||||||
|
bidi={this.identification}
|
||||||
|
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')}
|
||||||
|
bidi={this.password}
|
||||||
|
disabled={this.loading} />
|
||||||
|
</div>, 20);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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')
|
||||||
|
})}
|
||||||
|
</div>, -10);
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer() {
|
||||||
|
return [
|
||||||
|
<p className="LogInModal-forgotPassword">
|
||||||
|
<a onclick={this.forgotPassword.bind(this)}>{app.translator.trans('core.forum.log_in.forgot_password_link')}</a>
|
||||||
|
</p>,
|
||||||
|
|
||||||
|
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)}/> })}
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the forgot password modal, prefilling it with an email if the user has
|
||||||
|
* entered one.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
forgotPassword() {
|
||||||
|
const email = this.identification();
|
||||||
|
const props = email.indexOf('@') !== -1 ? {email} : undefined;
|
||||||
|
|
||||||
|
app.modal.show(new ForgotPasswordModal(props));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the sign up modal, prefilling it with an email/username/password if
|
||||||
|
* the user has entered one.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
signUp() {
|
||||||
|
const props = {password: this.password()};
|
||||||
|
const identification = this.identification();
|
||||||
|
props[identification.indexOf('@') !== -1 ? 'email' : 'username'] = identification;
|
||||||
|
|
||||||
|
// app.modal.show(new SignUpModal(props));
|
||||||
|
}
|
||||||
|
|
||||||
|
oncreate(vnode) {
|
||||||
|
super.oncreate(vnode);
|
||||||
|
|
||||||
|
this.$(`[name="${this.identification() ? 'password' : 'identification'}"]`).select();
|
||||||
|
}
|
||||||
|
|
||||||
|
onsubmit(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
this.loading = true;
|
||||||
|
|
||||||
|
const identification = this.identification();
|
||||||
|
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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onerror(error) {
|
||||||
|
if (error.status === 401) {
|
||||||
|
error.alert.props.children = app.translator.trans('core.forum.log_in.invalid_login_message');
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onerror(error);
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user