mirror of
https://github.com/flarum/core.git
synced 2025-07-28 20:20:34 +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:
@@ -34,7 +34,8 @@ export default function tagLabel(tag, attrs = {}) {
|
|||||||
link ? Link : 'span',
|
link ? Link : 'span',
|
||||||
attrs,
|
attrs,
|
||||||
<span className="TagLabel-text">
|
<span className="TagLabel-text">
|
||||||
{tag && tag.icon() && tagIcon(tag, {}, { useColor: false })} {tagText}
|
{tag && tag.icon() && tagIcon(tag, { className: 'TagLabel-icon' }, { useColor: false })}
|
||||||
|
<span className="TagLabel-name">{tagText}</span>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,7 @@ import IndexPage from 'flarum/forum/components/IndexPage';
|
|||||||
import Link from 'flarum/common/components/Link';
|
import Link from 'flarum/common/components/Link';
|
||||||
import LoadingIndicator from 'flarum/common/components/LoadingIndicator';
|
import LoadingIndicator from 'flarum/common/components/LoadingIndicator';
|
||||||
import listItems from 'flarum/common/helpers/listItems';
|
import listItems from 'flarum/common/helpers/listItems';
|
||||||
|
import ItemList from 'flarum/common/utils/ItemList';
|
||||||
import humanTime from 'flarum/common/helpers/humanTime';
|
import humanTime from 'flarum/common/helpers/humanTime';
|
||||||
import textContrastClass from 'flarum/common/helpers/textContrastClass';
|
import textContrastClass from 'flarum/common/helpers/textContrastClass';
|
||||||
import classList from 'flarum/common/utils/classList';
|
import classList from 'flarum/common/utils/classList';
|
||||||
@@ -37,25 +38,79 @@ export default class TagsPage extends Page {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
view() {
|
oncreate(vnode) {
|
||||||
if (this.loading) {
|
super.oncreate(vnode);
|
||||||
return <LoadingIndicator />;
|
|
||||||
|
app.setTitle(app.translator.trans('flarum-tags.forum.all_tags.meta_title_text'));
|
||||||
|
app.setTitleCount(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
view() {
|
||||||
|
return <div className="TagsPage">{this.pageContent().toArray()}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pageContent() {
|
||||||
|
const items = new ItemList();
|
||||||
|
|
||||||
|
items.add('hero', this.hero(), 100);
|
||||||
|
items.add('main', <div className="container">{this.mainContent().toArray()}</div>, 10);
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
mainContent() {
|
||||||
|
const items = new ItemList();
|
||||||
|
|
||||||
|
items.add('sidebar', this.sidebar(), 100);
|
||||||
|
items.add('content', this.content(), 10);
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
content() {
|
||||||
|
return <div className="TagsPage-content sideNavOffset">{this.contentItems().toArray()}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItems() {
|
||||||
|
const items = new ItemList();
|
||||||
|
|
||||||
|
if (this.loading) {
|
||||||
|
items.add('loading', <LoadingIndicator />);
|
||||||
|
} else {
|
||||||
const pinned = this.tags.filter((tag) => tag.position() !== null);
|
const pinned = this.tags.filter((tag) => tag.position() !== null);
|
||||||
const cloud = this.tags.filter((tag) => tag.position() === null);
|
const cloud = this.tags.filter((tag) => tag.position() === null);
|
||||||
|
|
||||||
return (
|
items.add('tagTiles', this.tagTileListView(pinned), 100);
|
||||||
<div className="TagsPage">
|
|
||||||
{IndexPage.prototype.hero()}
|
|
||||||
<div className="container">
|
|
||||||
<nav className="TagsPage-nav IndexPage-nav sideNav">
|
|
||||||
<ul>{listItems(IndexPage.prototype.sidebarItems().toArray())}</ul>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div className="TagsPage-content sideNavOffset">
|
if (cloud.length) {
|
||||||
<ul className="TagTiles">
|
items.add('cloud', this.cloudView(cloud), 10);
|
||||||
{pinned.map((tag) => {
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
hero() {
|
||||||
|
return IndexPage.prototype.hero();
|
||||||
|
}
|
||||||
|
|
||||||
|
sidebar() {
|
||||||
|
return (
|
||||||
|
<nav className="TagsPage-nav IndexPage-nav sideNav">
|
||||||
|
<ul>{listItems(this.sidebarItems().toArray())}</ul>
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
sidebarItems() {
|
||||||
|
return IndexPage.prototype.sidebarItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
tagTileListView(pinned) {
|
||||||
|
return <ul className="TagTiles">{pinned.map(this.tagTileView.bind(this))}</ul>;
|
||||||
|
}
|
||||||
|
|
||||||
|
tagTileView(tag) {
|
||||||
const lastPostedDiscussion = tag.lastPostedDiscussion();
|
const lastPostedDiscussion = tag.lastPostedDiscussion();
|
||||||
const children = sortTags(tag.children() || []);
|
const children = sortTags(tag.children() || []);
|
||||||
|
|
||||||
@@ -66,18 +121,13 @@ export default class TagsPage extends Page {
|
|||||||
<h3 className="TagTile-name">{tag.name()}</h3>
|
<h3 className="TagTile-name">{tag.name()}</h3>
|
||||||
<p className="TagTile-description">{tag.description()}</p>
|
<p className="TagTile-description">{tag.description()}</p>
|
||||||
{children ? (
|
{children ? (
|
||||||
<div className="TagTile-children">
|
<div className="TagTile-children">{children.map((child) => [<Link href={app.route.tag(child)}>{child.name()}</Link>, ' '])}</div>
|
||||||
{children.map((child) => [<Link href={app.route.tag(child)}>{child.name()}</Link>, ' '])}
|
|
||||||
</div>
|
|
||||||
) : (
|
) : (
|
||||||
''
|
''
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
{lastPostedDiscussion ? (
|
{lastPostedDiscussion ? (
|
||||||
<Link
|
<Link className="TagTile-lastPostedDiscussion" href={app.route.discussion(lastPostedDiscussion, lastPostedDiscussion.lastPostNumber())}>
|
||||||
className="TagTile-lastPostedDiscussion"
|
|
||||||
href={app.route.discussion(lastPostedDiscussion, lastPostedDiscussion.lastPostNumber())}
|
|
||||||
>
|
|
||||||
<span className="TagTile-lastPostedDiscussion-title">{lastPostedDiscussion.title()}</span>
|
<span className="TagTile-lastPostedDiscussion-title">{lastPostedDiscussion.title()}</span>
|
||||||
{humanTime(lastPostedDiscussion.lastPostedAt())}
|
{humanTime(lastPostedDiscussion.lastPostedAt())}
|
||||||
</Link>
|
</Link>
|
||||||
@@ -86,20 +136,9 @@ export default class TagsPage extends Page {
|
|||||||
)}
|
)}
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
{cloud.length ? <div className="TagCloud">{cloud.map((tag) => [tagLabel(tag, { link: true }), ' '])}</div> : ''}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
oncreate(vnode) {
|
cloudView(cloud) {
|
||||||
super.oncreate(vnode);
|
return <div className="TagCloud">{cloud.map((tag) => [tagLabel(tag, { link: true }), ' '])}</div>;
|
||||||
|
|
||||||
app.setTitle(app.translator.trans('flarum-tags.forum.all_tags.meta_title_text'));
|
|
||||||
app.setTitleCount(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,8 +2,9 @@ import app from '../../forum/app';
|
|||||||
import Modal, { IInternalModalAttrs } from '../../common/components/Modal';
|
import Modal, { IInternalModalAttrs } from '../../common/components/Modal';
|
||||||
import Button from '../../common/components/Button';
|
import Button from '../../common/components/Button';
|
||||||
import Stream from '../../common/utils/Stream';
|
import Stream from '../../common/utils/Stream';
|
||||||
import Mithril from 'mithril';
|
import type Mithril from 'mithril';
|
||||||
import RequestError from '../../common/utils/RequestError';
|
import RequestError from '../../common/utils/RequestError';
|
||||||
|
import ItemList from '../../common/utils/ItemList';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `ChangeEmailModal` component shows a modal dialog which allows the user
|
* The `ChangeEmailModal` component shows a modal dialog which allows the user
|
||||||
@@ -41,26 +42,37 @@ export default class ChangeEmailModal<CustomAttrs extends IInternalModalAttrs =
|
|||||||
}
|
}
|
||||||
|
|
||||||
content() {
|
content() {
|
||||||
if (this.success) {
|
|
||||||
return (
|
return (
|
||||||
<div className="Modal-body">
|
<div className="Modal-body">
|
||||||
<div className="Form Form--centered">
|
<div className="Form Form--centered">{this.fields().toArray()}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fields(): ItemList<Mithril.Children> {
|
||||||
|
const items = new ItemList<Mithril.Children>();
|
||||||
|
|
||||||
|
if (this.success) {
|
||||||
|
items.add(
|
||||||
|
'help',
|
||||||
<p className="helpText">
|
<p className="helpText">
|
||||||
{app.translator.trans('core.forum.change_email.confirmation_message', { email: <strong>{this.email()}</strong> })}
|
{app.translator.trans('core.forum.change_email.confirmation_message', {
|
||||||
|
email: <strong>{this.email()}</strong>,
|
||||||
|
})}
|
||||||
</p>
|
</p>
|
||||||
|
);
|
||||||
|
|
||||||
|
items.add(
|
||||||
|
'dismiss',
|
||||||
<div className="Form-group">
|
<div className="Form-group">
|
||||||
<Button className="Button Button--primary Button--block" onclick={this.hide.bind(this)}>
|
<Button className="Button Button--primary Button--block" onclick={this.hide.bind(this)}>
|
||||||
{app.translator.trans('core.forum.change_email.dismiss_button')}
|
{app.translator.trans('core.forum.change_email.dismiss_button')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
} else {
|
||||||
|
items.add(
|
||||||
return (
|
'email',
|
||||||
<div className="Modal-body">
|
|
||||||
<div className="Form Form--centered">
|
|
||||||
<div className="Form-group">
|
<div className="Form-group">
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
@@ -71,6 +83,10 @@ export default class ChangeEmailModal<CustomAttrs extends IInternalModalAttrs =
|
|||||||
disabled={this.loading}
|
disabled={this.loading}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
items.add(
|
||||||
|
'password',
|
||||||
<div className="Form-group">
|
<div className="Form-group">
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
@@ -82,21 +98,21 @@ export default class ChangeEmailModal<CustomAttrs extends IInternalModalAttrs =
|
|||||||
disabled={this.loading}
|
disabled={this.loading}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
items.add(
|
||||||
|
'submit',
|
||||||
<div className="Form-group">
|
<div className="Form-group">
|
||||||
{Button.component(
|
<Button className="Button Button--primary Button--block" type="submit" loading={this.loading}>
|
||||||
{
|
{app.translator.trans('core.forum.change_email.submit_button')}
|
||||||
className: 'Button Button--primary Button--block',
|
</Button>
|
||||||
type: 'submit',
|
|
||||||
loading: this.loading,
|
|
||||||
},
|
|
||||||
app.translator.trans('core.forum.change_email.submit_button')
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
onsubmit(e: SubmitEvent) {
|
onsubmit(e: SubmitEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
@@ -111,13 +127,10 @@ export default class ChangeEmailModal<CustomAttrs extends IInternalModalAttrs =
|
|||||||
this.alertAttrs = null;
|
this.alertAttrs = null;
|
||||||
|
|
||||||
app.session
|
app.session
|
||||||
.user!.save(
|
.user!.save(this.requestAttributes(), {
|
||||||
{ email: this.email() },
|
|
||||||
{
|
|
||||||
errorHandler: this.onerror.bind(this),
|
errorHandler: this.onerror.bind(this),
|
||||||
meta: { password: this.password() },
|
meta: { password: this.password() },
|
||||||
}
|
})
|
||||||
)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.success = true;
|
this.success = true;
|
||||||
})
|
})
|
||||||
@@ -125,6 +138,10 @@ export default class ChangeEmailModal<CustomAttrs extends IInternalModalAttrs =
|
|||||||
.then(this.loaded.bind(this));
|
.then(this.loaded.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
requestAttributes() {
|
||||||
|
return { email: this.email() };
|
||||||
|
}
|
||||||
|
|
||||||
onerror(error: RequestError) {
|
onerror(error: RequestError) {
|
||||||
if (error.status === 401 && error.alert) {
|
if (error.status === 401 && error.alert) {
|
||||||
error.alert.content = app.translator.trans('core.forum.change_email.incorrect_password_message');
|
error.alert.content = app.translator.trans('core.forum.change_email.incorrect_password_message');
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
import app from '../../forum/app';
|
import app from '../../forum/app';
|
||||||
import Modal, { IInternalModalAttrs } from '../../common/components/Modal';
|
import Modal, { IInternalModalAttrs } from '../../common/components/Modal';
|
||||||
import Button from '../../common/components/Button';
|
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
|
* The `ChangePasswordModal` component shows a modal dialog which allows the
|
||||||
@@ -18,23 +20,28 @@ export default class ChangePasswordModal<CustomAttrs extends IInternalModalAttrs
|
|||||||
content() {
|
content() {
|
||||||
return (
|
return (
|
||||||
<div className="Modal-body">
|
<div className="Modal-body">
|
||||||
<div className="Form Form--centered">
|
<div className="Form Form--centered">{this.fields().toArray()}</div>
|
||||||
<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>
|
</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) {
|
onsubmit(e: SubmitEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
@@ -44,8 +51,12 @@ export default class ChangePasswordModal<CustomAttrs extends IInternalModalAttrs
|
|||||||
.request({
|
.request({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: app.forum.attribute('apiUrl') + '/forgot',
|
url: app.forum.attribute('apiUrl') + '/forgot',
|
||||||
body: { email: app.session.user!.email() },
|
body: this.requestBody(),
|
||||||
})
|
})
|
||||||
.then(this.hide.bind(this), this.loaded.bind(this));
|
.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() {
|
view() {
|
||||||
const discussion = this.attrs.discussion;
|
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 isUnread = discussion.isUnread();
|
||||||
const isRead = discussion.isRead();
|
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;
|
let jumpTo = 0;
|
||||||
const controls = DiscussionControls.controls(discussion, this).toArray();
|
|
||||||
const attrs = this.elementAttrs();
|
|
||||||
|
|
||||||
if (this.attrs.params.q) {
|
if (this.attrs.params.q) {
|
||||||
const post = discussion.mostRelevantPost();
|
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);
|
jumpTo = Math.min(discussion.lastPostNumber() ?? 0, (discussion.lastReadPostNumber() || 0) + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return jumpTo;
|
||||||
<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>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
oncreate(vnode: Mithril.VnodeDOM<CustomAttrs, this>) {
|
oncreate(vnode: Mithril.VnodeDOM<CustomAttrs, this>) {
|
||||||
|
Reference in New Issue
Block a user