mirror of
https://github.com/flarum/framework.git
synced 2025-06-04 06:44:33 +08:00
Merge branch 'master' into compact-posts
This commit is contained in:
@ -44,6 +44,7 @@ export default class AppearancePage extends Component {
|
||||
|
||||
{Button.component({
|
||||
className: 'Button Button--primary',
|
||||
type: 'submit',
|
||||
children: 'Save Changes',
|
||||
loading: this.loading
|
||||
})}
|
||||
|
@ -30,6 +30,7 @@ export default class EditCustomCssModal extends Modal {
|
||||
<div className="Form-group">
|
||||
{Button.component({
|
||||
className: 'Button Button--primary',
|
||||
type: 'submit',
|
||||
children: 'Save Changes',
|
||||
loading: this.loading
|
||||
})}
|
||||
|
@ -4,6 +4,7 @@ import Search from 'flarum/components/Search';
|
||||
import Composer from 'flarum/components/Composer';
|
||||
import ReplyComposer from 'flarum/components/ReplyComposer';
|
||||
import DiscussionPage from 'flarum/components/DiscussionPage';
|
||||
import SignUpModal from 'flarum/components/SignUpModal';
|
||||
|
||||
export default class ForumApp extends App {
|
||||
constructor(...args) {
|
||||
@ -76,4 +77,27 @@ export default class ForumApp extends App {
|
||||
return this.current instanceof DiscussionPage &&
|
||||
this.current.discussion === discussion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for when an external authenticator (social login) action has
|
||||
* completed.
|
||||
*
|
||||
* If the payload indicates that the user has been logged in, then the page
|
||||
* will be reloaded. Otherwise, a SignUpModal will be opened, prefilled
|
||||
* with the provided details.
|
||||
*
|
||||
* @param {Object} payload A dictionary of props to pass into the sign up
|
||||
* modal. A truthy `authenticated` prop indicates that the user has logged
|
||||
* in, and thus the page is reloaded.
|
||||
* @public
|
||||
*/
|
||||
authenticationComplete(payload) {
|
||||
if (payload.authenticated) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
const modal = new SignUpModal(payload);
|
||||
this.modal.show(modal);
|
||||
modal.$('[name=password]').focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,66 +0,0 @@
|
||||
import Component from 'flarum/Component';
|
||||
import humanTime from 'flarum/helpers/humanTime';
|
||||
import avatar from 'flarum/helpers/avatar';
|
||||
|
||||
/**
|
||||
* The `Activity` component represents a piece of activity of a user's activity
|
||||
* feed. Subclasses should implement the `description` and `content` methods.
|
||||
*
|
||||
* ### Props
|
||||
*
|
||||
* - `activity`
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
export default class Activity extends Component {
|
||||
view() {
|
||||
const activity = this.props.activity;
|
||||
|
||||
return (
|
||||
<div className="Activity">
|
||||
{avatar(this.user(), {className: 'Activity-avatar'})}
|
||||
|
||||
<div className="Activity-header">
|
||||
<strong className="Activity-description">{this.description()}</strong>
|
||||
{humanTime(this.time())}
|
||||
</div>
|
||||
|
||||
{this.content()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user whose avatar should be displayed.
|
||||
*
|
||||
* @return {User}
|
||||
* @abstract
|
||||
*/
|
||||
user() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the time of the activity.
|
||||
*
|
||||
* @return {Date}
|
||||
* @abstract
|
||||
*/
|
||||
time() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the description of the activity.
|
||||
*
|
||||
* @return {VirtualElement}
|
||||
*/
|
||||
description() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content to show below the activity description.
|
||||
*
|
||||
* @return {VirtualElement}
|
||||
*/
|
||||
content() {
|
||||
}
|
||||
}
|
30
js/forum/src/components/LogInButton.js
Normal file
30
js/forum/src/components/LogInButton.js
Normal file
@ -0,0 +1,30 @@
|
||||
import Button from 'flarum/components/Button';
|
||||
|
||||
/**
|
||||
* The `LogInButton` component displays a social login button which will open
|
||||
* a popup window containing the specified path.
|
||||
*
|
||||
* ### Props
|
||||
*
|
||||
* - `path`
|
||||
*/
|
||||
export default class LogInButton extends Button {
|
||||
static initProps(props) {
|
||||
props.className = (props.className || '') + ' LogInButton';
|
||||
|
||||
props.onclick = function() {
|
||||
const width = 620;
|
||||
const height = 400;
|
||||
const $window = $(window);
|
||||
|
||||
window.open(app.forum.attribute('baseUrl') + props.path, 'logInPopup',
|
||||
`width=${width},` +
|
||||
`height=${height},` +
|
||||
`top=${$window.height() / 2 - height / 2},` +
|
||||
`left=${$window.width() / 2 - width / 2},` +
|
||||
'status=no,scrollbars=no,resizable=no');
|
||||
};
|
||||
|
||||
super.initProps(props);
|
||||
}
|
||||
}
|
25
js/forum/src/components/LogInButtons.js
Normal file
25
js/forum/src/components/LogInButtons.js
Normal file
@ -0,0 +1,25 @@
|
||||
import Component from 'flarum/Component';
|
||||
import ItemList from 'flarum/utils/ItemList';
|
||||
|
||||
/**
|
||||
* The `LogInButtons` component displays a collection of social login buttons.
|
||||
*/
|
||||
export default class LogInButtons extends Component {
|
||||
view() {
|
||||
return (
|
||||
<div className="LogInButtons">
|
||||
{this.items().toArray()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a list of LogInButton components.
|
||||
*
|
||||
* @return {ItemList}
|
||||
* @public
|
||||
*/
|
||||
items() {
|
||||
return new ItemList();
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ import ForgotPasswordModal from 'flarum/components/ForgotPasswordModal';
|
||||
import SignUpModal from 'flarum/components/SignUpModal';
|
||||
import Alert from 'flarum/components/Alert';
|
||||
import Button from 'flarum/components/Button';
|
||||
import LogInButtons from 'flarum/components/LogInButtons';
|
||||
|
||||
/**
|
||||
* The `LogInModal` component displays a modal dialog with a login form.
|
||||
@ -42,6 +43,8 @@ export default class LogInModal extends Modal {
|
||||
content() {
|
||||
return [
|
||||
<div className="Modal-body">
|
||||
<LogInButtons/>
|
||||
|
||||
<div className="Form Form--centered">
|
||||
<div className="Form-group">
|
||||
<input className="FormControl" name="email" placeholder={app.trans('core.username_or_email')}
|
||||
@ -71,6 +74,7 @@ export default class LogInModal extends Modal {
|
||||
<p className="LogInModal-forgotPassword">
|
||||
<a onclick={this.forgotPassword.bind(this)}>{app.trans('core.forgot_password_link')}</a>
|
||||
</p>
|
||||
|
||||
{app.forum.attribute('allowSignUp') ? (
|
||||
<p className="LogInModal-signUp">
|
||||
{app.trans('core.before_sign_up_link')}{' '}
|
||||
@ -84,6 +88,8 @@ export default class LogInModal extends Modal {
|
||||
/**
|
||||
* Open the forgot password modal, prefilling it with an email if the user has
|
||||
* entered one.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
forgotPassword() {
|
||||
const email = this.email();
|
||||
@ -95,6 +101,8 @@ export default class LogInModal extends Modal {
|
||||
/**
|
||||
* 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()};
|
||||
|
@ -2,6 +2,7 @@ import Modal from 'flarum/components/Modal';
|
||||
import LogInModal from 'flarum/components/LogInModal';
|
||||
import avatar from 'flarum/helpers/avatar';
|
||||
import Button from 'flarum/components/Button';
|
||||
import LogInButtons from 'flarum/components/LogInButtons';
|
||||
|
||||
/**
|
||||
* The `SignUpModal` component displays a modal dialog with a singup form.
|
||||
@ -11,6 +12,7 @@ import Button from 'flarum/components/Button';
|
||||
* - `username`
|
||||
* - `email`
|
||||
* - `password`
|
||||
* - `token` An email token to sign up with.
|
||||
*/
|
||||
export default class SignUpModal extends Modal {
|
||||
constructor(...args) {
|
||||
@ -65,7 +67,9 @@ export default class SignUpModal extends Modal {
|
||||
}
|
||||
|
||||
body() {
|
||||
const body = [(
|
||||
const body = [
|
||||
this.props.token ? '' : <LogInButtons/>,
|
||||
|
||||
<div className="Form Form--centered">
|
||||
<div className="Form-group">
|
||||
<input className="FormControl" name="username" placeholder={app.trans('core.username')}
|
||||
@ -78,26 +82,28 @@ export default class SignUpModal extends Modal {
|
||||
<input className="FormControl" name="email" type="email" placeholder={app.trans('core.email')}
|
||||
value={this.email()}
|
||||
onchange={m.withAttr('value', this.email)}
|
||||
disabled={this.loading} />
|
||||
disabled={this.loading || this.props.token} />
|
||||
</div>
|
||||
|
||||
<div className="Form-group">
|
||||
<input className="FormControl" name="password" type="password" placeholder={app.trans('core.password')}
|
||||
value={this.password()}
|
||||
onchange={m.withAttr('value', this.password)}
|
||||
disabled={this.loading} />
|
||||
</div>
|
||||
{this.props.token ? '' : (
|
||||
<div className="Form-group">
|
||||
<input className="FormControl" name="password" type="password" placeholder={app.trans('core.password')}
|
||||
value={this.password()}
|
||||
onchange={m.withAttr('value', this.password)}
|
||||
disabled={this.loading} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="Form-group">
|
||||
{Button.component({
|
||||
className: 'Button Button--primary Button--block',
|
||||
type: 'submit',
|
||||
loading: this.loading,
|
||||
children: app.trans('core.sign_up')
|
||||
})}
|
||||
<Button
|
||||
className="Button Button--primary Button--block"
|
||||
type="submit"
|
||||
loading={this.loading}>
|
||||
{app.trans('core.sign_up')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)];
|
||||
];
|
||||
|
||||
if (this.welcomeUser) {
|
||||
const user = this.welcomeUser;
|
||||
@ -115,20 +121,12 @@ export default class SignUpModal extends Modal {
|
||||
{avatar(user)}
|
||||
<h3>{app.trans('core.welcome_user', {user})}</h3>
|
||||
|
||||
{!user.isActivated() ? [
|
||||
<p>{app.trans('core.confirmation_email_sent', {email: <strong>{user.email()}</strong>})}</p>,
|
||||
<p>
|
||||
<a href={`http://${emailProviderName}`} className="Button Button--primary" target="_blank">
|
||||
{app.trans('core.go_to', {location: emailProviderName})}
|
||||
</a>
|
||||
</p>
|
||||
] : (
|
||||
<p>
|
||||
<button className="Button Button--primary" onclick={this.hide.bind(this)}>
|
||||
{app.trans('core.dismiss')}
|
||||
</button>
|
||||
</p>
|
||||
)}
|
||||
<p>{app.trans('core.confirmation_email_sent', {email: <strong>{user.email()}</strong>})}</p>,
|
||||
<p>
|
||||
<a href={`http://${emailProviderName}`} className="Button Button--primary" target="_blank">
|
||||
{app.trans('core.go_to', {location: emailProviderName})}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -150,6 +148,8 @@ export default class SignUpModal extends Modal {
|
||||
/**
|
||||
* Open the log in modal, prefilling it with an email/username/password if
|
||||
* the user has entered one.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
logIn() {
|
||||
const props = {
|
||||
@ -161,7 +161,7 @@ export default class SignUpModal extends Modal {
|
||||
}
|
||||
|
||||
onready() {
|
||||
if (this.props.username) {
|
||||
if (this.props.username && !this.props.token) {
|
||||
this.$('[name=email]').select();
|
||||
} else {
|
||||
super.onready();
|
||||
@ -175,24 +175,50 @@ export default class SignUpModal extends Modal {
|
||||
|
||||
const data = this.submitData();
|
||||
|
||||
app.store.createRecord('users').save(data).then(
|
||||
user => {
|
||||
this.welcomeUser = user;
|
||||
this.loading = false;
|
||||
m.redraw();
|
||||
app.request({
|
||||
url: app.forum.attribute('baseUrl') + '/register',
|
||||
method: 'POST',
|
||||
data
|
||||
}).then(
|
||||
payload => {
|
||||
const user = app.store.pushPayload(payload);
|
||||
|
||||
// If the user's new account has been activated, then we can assume
|
||||
// that they have been logged in too. Thus, we will reload the page.
|
||||
// Otherwise, we will show a message asking them to check their email.
|
||||
if (user.isActivated()) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
this.welcomeUser = user;
|
||||
this.loading = false;
|
||||
m.redraw();
|
||||
}
|
||||
},
|
||||
response => {
|
||||
this.loading = false;
|
||||
this.handleErrors(response.errors);
|
||||
this.handleErrors(response);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data that should be submitted in the sign-up request.
|
||||
*
|
||||
* @return {Object}
|
||||
* @public
|
||||
*/
|
||||
submitData() {
|
||||
return {
|
||||
const data = {
|
||||
username: this.username(),
|
||||
email: this.email(),
|
||||
password: this.password()
|
||||
email: this.email()
|
||||
};
|
||||
|
||||
if (this.props.token) {
|
||||
data.token = this.props.token;
|
||||
} else {
|
||||
data.password = this.password();
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ export default class Session {
|
||||
* The token that was used for authentication.
|
||||
*
|
||||
* @type {String|null}
|
||||
* @public
|
||||
*/
|
||||
this.token = token;
|
||||
}
|
||||
@ -26,6 +27,7 @@ export default class Session {
|
||||
* @param {String} identification The username/email.
|
||||
* @param {String} password
|
||||
* @return {Promise}
|
||||
* @public
|
||||
*/
|
||||
login(identification, password) {
|
||||
return app.request({
|
||||
@ -38,6 +40,8 @@ export default class Session {
|
||||
|
||||
/**
|
||||
* Log the user out.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
logout() {
|
||||
window.location = app.forum.attribute('baseUrl') + '/logout?token=' + this.token;
|
||||
@ -48,8 +52,11 @@ export default class Session {
|
||||
* XMLHttpRequest object.
|
||||
*
|
||||
* @param {XMLHttpRequest} xhr
|
||||
* @public
|
||||
*/
|
||||
authorize(xhr) {
|
||||
xhr.setRequestHeader('Authorization', 'Token ' + this.token);
|
||||
if (this.token) {
|
||||
xhr.setRequestHeader('Authorization', 'Token ' + this.token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,8 @@ export default class Button extends Component {
|
||||
|
||||
delete attrs.children;
|
||||
|
||||
attrs.className = (attrs.className || '');
|
||||
attrs.className = attrs.className || '';
|
||||
attrs.type = attrs.type || 'button';
|
||||
|
||||
const iconName = extract(attrs, 'icon');
|
||||
if (iconName) attrs.className += ' hasIcon';
|
||||
|
@ -129,7 +129,7 @@ export default class Modal extends Component {
|
||||
m.redraw();
|
||||
|
||||
if (errors) {
|
||||
this.$('form [name=' + errors[0].path + ']').select();
|
||||
this.$('form [name=' + errors[0].source.pointer.replace('/data/attributes/', '') + ']').select();
|
||||
} else {
|
||||
this.$('form :input:first').select();
|
||||
}
|
||||
|
Reference in New Issue
Block a user