mirror of
https://github.com/flarum/core.git
synced 2025-07-28 04:00:40 +02:00
chore: extensibility improvements (#3729)
* chore: improve tags page extensibility * chore: improve discussion list item extensibility * chore: improve change password modal extensibility * chore: item-listify tags page * chore: item-listify change email modal * chore: simplify data flow Signed-off-by: Sami Mazouz <sychocouldy@gmail.com>
This commit is contained in:
@@ -2,8 +2,9 @@ import app from '../../forum/app';
|
||||
import Modal, { IInternalModalAttrs } from '../../common/components/Modal';
|
||||
import Button from '../../common/components/Button';
|
||||
import Stream from '../../common/utils/Stream';
|
||||
import Mithril from 'mithril';
|
||||
import type Mithril from 'mithril';
|
||||
import RequestError from '../../common/utils/RequestError';
|
||||
import ItemList from '../../common/utils/ItemList';
|
||||
|
||||
/**
|
||||
* The `ChangeEmailModal` component shows a modal dialog which allows the user
|
||||
@@ -41,60 +42,75 @@ export default class ChangeEmailModal<CustomAttrs extends IInternalModalAttrs =
|
||||
}
|
||||
|
||||
content() {
|
||||
return (
|
||||
<div className="Modal-body">
|
||||
<div className="Form Form--centered">{this.fields().toArray()}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
fields(): ItemList<Mithril.Children> {
|
||||
const items = new ItemList<Mithril.Children>();
|
||||
|
||||
if (this.success) {
|
||||
return (
|
||||
<div className="Modal-body">
|
||||
<div className="Form Form--centered">
|
||||
<p className="helpText">
|
||||
{app.translator.trans('core.forum.change_email.confirmation_message', { email: <strong>{this.email()}</strong> })}
|
||||
</p>
|
||||
<div className="Form-group">
|
||||
<Button className="Button Button--primary Button--block" onclick={this.hide.bind(this)}>
|
||||
{app.translator.trans('core.forum.change_email.dismiss_button')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
items.add(
|
||||
'help',
|
||||
<p className="helpText">
|
||||
{app.translator.trans('core.forum.change_email.confirmation_message', {
|
||||
email: <strong>{this.email()}</strong>,
|
||||
})}
|
||||
</p>
|
||||
);
|
||||
|
||||
items.add(
|
||||
'dismiss',
|
||||
<div className="Form-group">
|
||||
<Button className="Button Button--primary Button--block" onclick={this.hide.bind(this)}>
|
||||
{app.translator.trans('core.forum.change_email.dismiss_button')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
items.add(
|
||||
'email',
|
||||
<div className="Form-group">
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
className="FormControl"
|
||||
placeholder={app.session.user!.email()}
|
||||
bidi={this.email}
|
||||
disabled={this.loading}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
items.add(
|
||||
'password',
|
||||
<div className="Form-group">
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
className="FormControl"
|
||||
autocomplete="current-password"
|
||||
placeholder={app.translator.trans('core.forum.change_email.confirm_password_placeholder')}
|
||||
bidi={this.password}
|
||||
disabled={this.loading}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
items.add(
|
||||
'submit',
|
||||
<div className="Form-group">
|
||||
<Button className="Button Button--primary Button--block" type="submit" loading={this.loading}>
|
||||
{app.translator.trans('core.forum.change_email.submit_button')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="Modal-body">
|
||||
<div className="Form Form--centered">
|
||||
<div className="Form-group">
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
className="FormControl"
|
||||
placeholder={app.session.user!.email()}
|
||||
bidi={this.email}
|
||||
disabled={this.loading}
|
||||
/>
|
||||
</div>
|
||||
<div className="Form-group">
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
className="FormControl"
|
||||
autocomplete="current-password"
|
||||
placeholder={app.translator.trans('core.forum.change_email.confirm_password_placeholder')}
|
||||
bidi={this.password}
|
||||
disabled={this.loading}
|
||||
/>
|
||||
</div>
|
||||
<div className="Form-group">
|
||||
{Button.component(
|
||||
{
|
||||
className: 'Button Button--primary Button--block',
|
||||
type: 'submit',
|
||||
loading: this.loading,
|
||||
},
|
||||
app.translator.trans('core.forum.change_email.submit_button')
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return items;
|
||||
}
|
||||
|
||||
onsubmit(e: SubmitEvent) {
|
||||
@@ -111,13 +127,10 @@ export default class ChangeEmailModal<CustomAttrs extends IInternalModalAttrs =
|
||||
this.alertAttrs = null;
|
||||
|
||||
app.session
|
||||
.user!.save(
|
||||
{ email: this.email() },
|
||||
{
|
||||
errorHandler: this.onerror.bind(this),
|
||||
meta: { password: this.password() },
|
||||
}
|
||||
)
|
||||
.user!.save(this.requestAttributes(), {
|
||||
errorHandler: this.onerror.bind(this),
|
||||
meta: { password: this.password() },
|
||||
})
|
||||
.then(() => {
|
||||
this.success = true;
|
||||
})
|
||||
@@ -125,6 +138,10 @@ export default class ChangeEmailModal<CustomAttrs extends IInternalModalAttrs =
|
||||
.then(this.loaded.bind(this));
|
||||
}
|
||||
|
||||
requestAttributes() {
|
||||
return { email: this.email() };
|
||||
}
|
||||
|
||||
onerror(error: RequestError) {
|
||||
if (error.status === 401 && error.alert) {
|
||||
error.alert.content = app.translator.trans('core.forum.change_email.incorrect_password_message');
|
||||
|
@@ -1,6 +1,8 @@
|
||||
import app from '../../forum/app';
|
||||
import Modal, { IInternalModalAttrs } from '../../common/components/Modal';
|
||||
import Button from '../../common/components/Button';
|
||||
import Mithril from 'mithril';
|
||||
import ItemList from '../../common/utils/ItemList';
|
||||
|
||||
/**
|
||||
* The `ChangePasswordModal` component shows a modal dialog which allows the
|
||||
@@ -18,23 +20,28 @@ export default class ChangePasswordModal<CustomAttrs extends IInternalModalAttrs
|
||||
content() {
|
||||
return (
|
||||
<div className="Modal-body">
|
||||
<div className="Form Form--centered">
|
||||
<p className="helpText">{app.translator.trans('core.forum.change_password.text')}</p>
|
||||
<div className="Form-group">
|
||||
{Button.component(
|
||||
{
|
||||
className: 'Button Button--primary Button--block',
|
||||
type: 'submit',
|
||||
loading: this.loading,
|
||||
},
|
||||
app.translator.trans('core.forum.change_password.send_button')
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="Form Form--centered">{this.fields().toArray()}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
fields() {
|
||||
const fields = new ItemList<Mithril.Children>();
|
||||
|
||||
fields.add('help', <p className="helpText">{app.translator.trans('core.forum.change_password.text')}</p>);
|
||||
|
||||
fields.add(
|
||||
'submit',
|
||||
<div className="Form-group">
|
||||
<Button className="Button Button--primary Button--block" type="submit" loading={this.loading}>
|
||||
{app.translator.trans('core.forum.change_password.send_button')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
onsubmit(e: SubmitEvent) {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -44,8 +51,12 @@ export default class ChangePasswordModal<CustomAttrs extends IInternalModalAttrs
|
||||
.request({
|
||||
method: 'POST',
|
||||
url: app.forum.attribute('apiUrl') + '/forgot',
|
||||
body: { email: app.session.user!.email() },
|
||||
body: this.requestBody(),
|
||||
})
|
||||
.then(this.hide.bind(this), this.loaded.bind(this));
|
||||
}
|
||||
|
||||
requestBody() {
|
||||
return { email: app.session.user!.email() };
|
||||
}
|
||||
}
|
||||
|
@@ -64,13 +64,101 @@ export default class DiscussionListItem<CustomAttrs extends IDiscussionListItemA
|
||||
|
||||
view() {
|
||||
const discussion = this.attrs.discussion;
|
||||
const user = discussion.user();
|
||||
|
||||
const controls = DiscussionControls.controls(discussion, this).toArray();
|
||||
const attrs = this.elementAttrs();
|
||||
|
||||
return (
|
||||
<div {...attrs}>
|
||||
{this.controlsView(controls)}
|
||||
{this.slidableUnderneathView()}
|
||||
{this.contentView()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
controlsView(controls: Mithril.ChildArray): Mithril.Children {
|
||||
return (
|
||||
(controls.length > 0 &&
|
||||
Dropdown.component(
|
||||
{
|
||||
icon: 'fas fa-ellipsis-v',
|
||||
className: 'DiscussionListItem-controls',
|
||||
buttonClassName: 'Button Button--icon Button--flat Slidable-underneath Slidable-underneath--right',
|
||||
accessibleToggleLabel: app.translator.trans('core.forum.discussion_controls.toggle_dropdown_accessible_label'),
|
||||
},
|
||||
controls
|
||||
)) ||
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
slidableUnderneathView(): Mithril.Children {
|
||||
const discussion = this.attrs.discussion;
|
||||
const isUnread = discussion.isUnread();
|
||||
|
||||
return (
|
||||
<span
|
||||
className={'Slidable-underneath Slidable-underneath--left Slidable-underneath--elastic' + (isUnread ? '' : ' disabled')}
|
||||
onclick={this.markAsRead.bind(this)}
|
||||
>
|
||||
{icon('fas fa-check')}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
contentView(): Mithril.Children {
|
||||
const discussion = this.attrs.discussion;
|
||||
const isUnread = discussion.isUnread();
|
||||
const isRead = discussion.isRead();
|
||||
|
||||
return (
|
||||
<div className={classList('DiscussionListItem-content', 'Slidable-content', { unread: isUnread, read: isRead })}>
|
||||
{this.authorAvatarView()}
|
||||
{this.badgesView()}
|
||||
{this.mainView()}
|
||||
{this.replyCountItem()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
authorAvatarView(): Mithril.Children {
|
||||
const discussion = this.attrs.discussion;
|
||||
const user = discussion.user();
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
text={app.translator.trans('core.forum.discussion_list.started_text', { user, ago: humanTime(discussion.createdAt()) })}
|
||||
position="right"
|
||||
>
|
||||
<Link className="DiscussionListItem-author" href={user ? app.route.user(user) : '#'}>
|
||||
{avatar(user || null, { title: '' })}
|
||||
</Link>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
badgesView(): Mithril.Children {
|
||||
const discussion = this.attrs.discussion;
|
||||
|
||||
return <ul className="DiscussionListItem-badges badges">{listItems(discussion.badges().toArray())}</ul>;
|
||||
}
|
||||
|
||||
mainView(): Mithril.Children {
|
||||
const discussion = this.attrs.discussion;
|
||||
const jumpTo = this.getJumpTo();
|
||||
|
||||
return (
|
||||
<Link href={app.route.discussion(discussion, jumpTo)} className="DiscussionListItem-main">
|
||||
<h2 className="DiscussionListItem-title">{highlight(discussion.title(), this.highlightRegExp)}</h2>
|
||||
<ul className="DiscussionListItem-info">{listItems(this.infoItems().toArray())}</ul>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
getJumpTo() {
|
||||
const discussion = this.attrs.discussion;
|
||||
let jumpTo = 0;
|
||||
const controls = DiscussionControls.controls(discussion, this).toArray();
|
||||
const attrs = this.elementAttrs();
|
||||
|
||||
if (this.attrs.params.q) {
|
||||
const post = discussion.mostRelevantPost();
|
||||
@@ -84,46 +172,7 @@ export default class DiscussionListItem<CustomAttrs extends IDiscussionListItemA
|
||||
jumpTo = Math.min(discussion.lastPostNumber() ?? 0, (discussion.lastReadPostNumber() || 0) + 1);
|
||||
}
|
||||
|
||||
return (
|
||||
<div {...attrs}>
|
||||
{controls.length > 0 &&
|
||||
Dropdown.component(
|
||||
{
|
||||
icon: 'fas fa-ellipsis-v',
|
||||
className: 'DiscussionListItem-controls',
|
||||
buttonClassName: 'Button Button--icon Button--flat Slidable-underneath Slidable-underneath--right',
|
||||
accessibleToggleLabel: app.translator.trans('core.forum.discussion_controls.toggle_dropdown_accessible_label'),
|
||||
},
|
||||
controls
|
||||
)}
|
||||
|
||||
<span
|
||||
className={'Slidable-underneath Slidable-underneath--left Slidable-underneath--elastic' + (isUnread ? '' : ' disabled')}
|
||||
onclick={this.markAsRead.bind(this)}
|
||||
>
|
||||
{icon('fas fa-check')}
|
||||
</span>
|
||||
|
||||
<div className={classList('DiscussionListItem-content', 'Slidable-content', { unread: isUnread, read: isRead })}>
|
||||
<Tooltip
|
||||
text={app.translator.trans('core.forum.discussion_list.started_text', { user, ago: humanTime(discussion.createdAt()) })}
|
||||
position="right"
|
||||
>
|
||||
<Link className="DiscussionListItem-author" href={user ? app.route.user(user) : '#'}>
|
||||
{avatar(user || null, { title: '' })}
|
||||
</Link>
|
||||
</Tooltip>
|
||||
|
||||
<ul className="DiscussionListItem-badges badges">{listItems(discussion.badges().toArray())}</ul>
|
||||
|
||||
<Link href={app.route.discussion(discussion, jumpTo)} className="DiscussionListItem-main">
|
||||
<h2 className="DiscussionListItem-title">{highlight(discussion.title(), this.highlightRegExp)}</h2>
|
||||
<ul className="DiscussionListItem-info">{listItems(this.infoItems().toArray())}</ul>
|
||||
</Link>
|
||||
{this.replyCountItem()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return jumpTo;
|
||||
}
|
||||
|
||||
oncreate(vnode: Mithril.VnodeDOM<CustomAttrs, this>) {
|
||||
|
Reference in New Issue
Block a user