mirror of
https://github.com/flarum/framework.git
synced 2025-03-19 11:35:18 +08:00
chore: major frontend JS cleanup (#3609)
This commit is contained in:
parent
3264455068
commit
e63e161be6
@ -80,7 +80,7 @@ export default function addComposerAutocomplete() {
|
||||
dropdown.setIndex($(this).parent().index() - 1);
|
||||
}}
|
||||
>
|
||||
<img alt={emoji} class="emoji" draggable="false" loading="lazy" src={`${cdn}72x72/${code}.png`} />
|
||||
<img alt={emoji} className="emoji" draggable="false" loading="lazy" src={`${cdn}72x72/${code}.png`} />
|
||||
{name}
|
||||
</button>
|
||||
);
|
||||
|
@ -108,7 +108,7 @@ export default function () {
|
||||
user,
|
||||
reason,
|
||||
}),
|
||||
detail ? <span className="Post-flagged-detail">{detail}</span> : '',
|
||||
!!detail && <span className="Post-flagged-detail">{detail}</span>,
|
||||
];
|
||||
}
|
||||
};
|
||||
|
@ -55,7 +55,7 @@ export default class FlagList extends Component {
|
||||
) : !this.state.loading ? (
|
||||
<div className="NotificationList-empty">{app.translator.trans('flarum-flags.forum.flagged_posts.empty_text')}</div>
|
||||
) : (
|
||||
LoadingIndicator.component({ className: 'LoadingIndicator--block' })
|
||||
<LoadingIndicator className="LoadingIndicator--block" />
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -67,15 +67,13 @@ export default class FlagPostModal extends Modal {
|
||||
<input type="radio" name="reason" checked={this.reason() === 'off_topic'} value="off_topic" onclick={withAttr('value', this.reason)} />
|
||||
<strong>{app.translator.trans('flarum-flags.forum.flag_post.reason_off_topic_label')}</strong>
|
||||
{app.translator.trans('flarum-flags.forum.flag_post.reason_off_topic_text')}
|
||||
{this.reason() === 'off_topic' ? (
|
||||
{this.reason() === 'off_topic' && (
|
||||
<textarea
|
||||
className="FormControl"
|
||||
placeholder={app.translator.trans('flarum-flags.forum.flag_post.reason_details_placeholder')}
|
||||
value={this.reasonDetail()}
|
||||
oninput={withAttr('value', this.reasonDetail)}
|
||||
></textarea>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</label>,
|
||||
70
|
||||
@ -95,15 +93,13 @@ export default class FlagPostModal extends Modal {
|
||||
{app.translator.trans('flarum-flags.forum.flag_post.reason_inappropriate_text', {
|
||||
a: guidelinesUrl ? <a href={guidelinesUrl} target="_blank" /> : undefined,
|
||||
})}
|
||||
{this.reason() === 'inappropriate' ? (
|
||||
{this.reason() === 'inappropriate' && (
|
||||
<textarea
|
||||
className="FormControl"
|
||||
placeholder={app.translator.trans('flarum-flags.forum.flag_post.reason_details_placeholder')}
|
||||
value={this.reasonDetail()}
|
||||
oninput={withAttr('value', this.reasonDetail)}
|
||||
></textarea>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</label>,
|
||||
60
|
||||
@ -115,15 +111,13 @@ export default class FlagPostModal extends Modal {
|
||||
<input type="radio" name="reason" checked={this.reason() === 'spam'} value="spam" onclick={withAttr('value', this.reason)} />
|
||||
<strong>{app.translator.trans('flarum-flags.forum.flag_post.reason_spam_label')}</strong>
|
||||
{app.translator.trans('flarum-flags.forum.flag_post.reason_spam_text')}
|
||||
{this.reason() === 'spam' ? (
|
||||
{this.reason() === 'spam' && (
|
||||
<textarea
|
||||
className="FormControl"
|
||||
placeholder={app.translator.trans('flarum-flags.forum.flag_post.reason_details_placeholder')}
|
||||
value={this.reasonDetail()}
|
||||
oninput={withAttr('value', this.reasonDetail)}
|
||||
></textarea>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</label>,
|
||||
50
|
||||
@ -134,10 +128,8 @@ export default class FlagPostModal extends Modal {
|
||||
<label className="checkbox">
|
||||
<input type="radio" name="reason" checked={this.reason() === 'other'} value="other" onclick={withAttr('value', this.reason)} />
|
||||
<strong>{app.translator.trans('flarum-flags.forum.flag_post.reason_other_label')}</strong>
|
||||
{this.reason() === 'other' ? (
|
||||
{this.reason() === 'other' && (
|
||||
<textarea className="FormControl" value={this.reasonDetail()} oninput={withAttr('value', this.reasonDetail)}></textarea>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</label>,
|
||||
10
|
||||
|
@ -1,5 +1,5 @@
|
||||
import app from 'flarum/forum/app';
|
||||
import NotificationsDropdown from 'flarum/components/NotificationsDropdown';
|
||||
import NotificationsDropdown from 'flarum/forum/components/NotificationsDropdown';
|
||||
|
||||
import FlagList from './FlagList';
|
||||
|
||||
@ -14,7 +14,7 @@ export default class FlagsDropdown extends NotificationsDropdown {
|
||||
getMenu() {
|
||||
return (
|
||||
<div className={'Dropdown-menu ' + this.attrs.menuClassName} onclick={this.menuClick.bind(this)}>
|
||||
{this.showing ? FlagList.component({ state: this.attrs.state }) : ''}
|
||||
{this.showing && <FlagList state={this.attrs.state} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -15,32 +15,31 @@ export default function () {
|
||||
|
||||
items.add(
|
||||
'like',
|
||||
Button.component(
|
||||
{
|
||||
className: 'Button Button--link',
|
||||
onclick: () => {
|
||||
isLiked = !isLiked;
|
||||
<Button
|
||||
className="Button Button--link"
|
||||
onclick={() => {
|
||||
isLiked = !isLiked;
|
||||
|
||||
post.save({ isLiked });
|
||||
post.save({ isLiked });
|
||||
|
||||
// We've saved the fact that we do or don't like the post, but in order
|
||||
// to provide instantaneous feedback to the user, we'll need to add or
|
||||
// remove the like from the relationship data manually.
|
||||
const data = post.data.relationships.likes.data;
|
||||
data.some((like, i) => {
|
||||
if (like.id === app.session.user.id()) {
|
||||
data.splice(i, 1);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (isLiked) {
|
||||
data.unshift({ type: 'users', id: app.session.user.id() });
|
||||
// We've saved the fact that we do or don't like the post, but in order
|
||||
// to provide instantaneous feedback to the user, we'll need to add or
|
||||
// remove the like from the relationship data manually.
|
||||
const data = post.data.relationships.likes.data;
|
||||
data.some((like, i) => {
|
||||
if (like.id === app.session.user.id()) {
|
||||
data.splice(i, 1);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
},
|
||||
app.translator.trans(isLiked ? 'flarum-likes.forum.post.unlike_link' : 'flarum-likes.forum.post.like_link')
|
||||
)
|
||||
});
|
||||
|
||||
if (isLiked) {
|
||||
data.unshift({ type: 'users', id: app.session.user.id() });
|
||||
}
|
||||
}}
|
||||
>
|
||||
{app.translator.trans(isLiked ? 'flarum-likes.forum.post.unlike_link' : 'flarum-likes.forum.post.like_link')}
|
||||
</Button>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ export default function () {
|
||||
'liked',
|
||||
<div className="Post-likedBy">
|
||||
{icon('far fa-thumbs-up')}
|
||||
{app.translator.trans('flarum-likes.forum.post.liked_by' + (likes[0] === app.session.user ? '_self' : '') + '_text', {
|
||||
{app.translator.trans(`flarum-likes.forum.post.liked_by${likes[0] === app.session.user ? '_self' : ''}_text`, {
|
||||
count: names.length,
|
||||
users: punctuateSeries(names),
|
||||
})}
|
||||
|
@ -6,14 +6,7 @@ import Badge from 'flarum/common/components/Badge';
|
||||
export default function addLockBadge() {
|
||||
extend(Discussion.prototype, 'badges', function (badges) {
|
||||
if (this.isLocked()) {
|
||||
badges.add(
|
||||
'locked',
|
||||
Badge.component({
|
||||
type: 'locked',
|
||||
label: app.translator.trans('flarum-lock.forum.badge.locked_tooltip'),
|
||||
icon: 'fas fa-lock',
|
||||
})
|
||||
);
|
||||
badges.add('locked', <Badge type="locked" label={app.translator.trans('flarum-lock.forum.badge.locked_tooltip')} icon="fas fa-lock" />);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -9,15 +9,9 @@ export default function addLockControl() {
|
||||
if (discussion.canLock()) {
|
||||
items.add(
|
||||
'lock',
|
||||
Button.component(
|
||||
{
|
||||
icon: 'fas fa-lock',
|
||||
onclick: this.lockAction.bind(discussion),
|
||||
},
|
||||
app.translator.trans(
|
||||
discussion.isLocked() ? 'flarum-lock.forum.discussion_controls.unlock_button' : 'flarum-lock.forum.discussion_controls.lock_button'
|
||||
)
|
||||
)
|
||||
<Button icon="fas fa-lock" onclick={this.lockAction.bind(discussion)}>
|
||||
{app.translator.trans(`flarum-lock.forum.discussion_controls.${discussion.isLocked() ? 'unlock' : 'lock'}_button`)}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -2,6 +2,6 @@ import Component from 'flarum/common/Component';
|
||||
|
||||
export default class MarkdownToolbar extends Component {
|
||||
view(vnode) {
|
||||
return <div class="MarkdownToolbar">{vnode.children}</div>;
|
||||
return <div className="MarkdownToolbar">{vnode.children}</div>;
|
||||
}
|
||||
}
|
||||
|
@ -41,13 +41,10 @@ export default function addMentionedByList() {
|
||||
<>
|
||||
{replies.map((reply) => (
|
||||
<li data-number={reply.number()}>
|
||||
{PostPreview.component({
|
||||
post: reply,
|
||||
onclick: hidePreview.bind(this),
|
||||
})}
|
||||
<PostPreview post={reply} onclick={hidePreview.bind(this)} />
|
||||
</li>
|
||||
))}
|
||||
{replies.length < post.mentionedByCount() ? (
|
||||
{replies.length < post.mentionedByCount() && (
|
||||
<li className="Post-mentionedBy-preview-more">
|
||||
<Button
|
||||
className="PostPreview Button"
|
||||
@ -64,7 +61,7 @@ export default function addMentionedByList() {
|
||||
</span>
|
||||
</Button>
|
||||
</li>
|
||||
) : null}
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@ -149,7 +146,7 @@ export default function addMentionedByList() {
|
||||
<div className="Post-mentionedBy">
|
||||
<span className="Post-mentionedBy-summary">
|
||||
{icon('fas fa-reply')}
|
||||
{app.translator.trans('flarum-mentions.forum.post.mentioned_by' + (repliers[0].user() === app.session.user ? '_self' : '') + '_text', {
|
||||
{app.translator.trans(`flarum-mentions.forum.post.mentioned_by${repliers[0].user() === app.session.user ? '_self' : ''}_text`, {
|
||||
count: names.length,
|
||||
users: punctuateSeries(names),
|
||||
})}
|
||||
|
@ -80,14 +80,14 @@ export default function addPostMentionPreviews() {
|
||||
const discussion = post.discussion();
|
||||
|
||||
m.render($preview[0], [
|
||||
discussion !== parentPost.discussion() ? (
|
||||
discussion !== parentPost.discussion() && (
|
||||
<li>
|
||||
<span className="PostMention-preview-discussion">{discussion.title()}</span>
|
||||
</li>
|
||||
) : (
|
||||
''
|
||||
),
|
||||
<li>{PostPreview.component({ post })}</li>,
|
||||
<li>
|
||||
<PostPreview post={post} />
|
||||
</li>,
|
||||
]);
|
||||
positionPreview();
|
||||
};
|
||||
@ -96,7 +96,7 @@ export default function addPostMentionPreviews() {
|
||||
if (post && post.discussion()) {
|
||||
showPost(post);
|
||||
} else {
|
||||
m.render($preview[0], LoadingIndicator.component());
|
||||
m.render($preview[0], <LoadingIndicator />);
|
||||
app.store.find('posts', id).then(showPost);
|
||||
positionPreview();
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ export default class PostQuoteButton extends Fragment {
|
||||
view() {
|
||||
return (
|
||||
<button
|
||||
class="Button PostQuoteButton"
|
||||
className="Button PostQuoteButton"
|
||||
onclick={() => {
|
||||
reply(this.post, this.content);
|
||||
}}
|
||||
|
@ -70,14 +70,9 @@ app.initializers.add('flarum-mentions', function () {
|
||||
const user = this.user;
|
||||
items.add(
|
||||
'mentions',
|
||||
LinkButton.component(
|
||||
{
|
||||
href: app.route('user.mentions', { username: user.slug() }),
|
||||
name: 'mentions',
|
||||
icon: 'fas fa-at',
|
||||
},
|
||||
app.translator.trans('flarum-mentions.forum.user.mentions_link')
|
||||
),
|
||||
<LinkButton href={app.route('user.mentions', { username: user.slug() })} name="mentions" icon="fas fa-at">
|
||||
{app.translator.trans('flarum-mentions.forum.user.mentions_link')}
|
||||
</LinkButton>,
|
||||
80
|
||||
);
|
||||
});
|
||||
|
@ -25,14 +25,9 @@ export default class NicknameModal extends Modal {
|
||||
<input type="text" autocomplete="off" name="nickname" className="FormControl" bidi={this.nickname} disabled={this.loading} />
|
||||
</div>
|
||||
<div className="Form-group">
|
||||
{Button.component(
|
||||
{
|
||||
className: 'Button Button--primary Button--block',
|
||||
type: 'submit',
|
||||
loading: this.loading,
|
||||
},
|
||||
app.translator.trans('flarum-nicknames.forum.change_nickname.submit_button')
|
||||
)}
|
||||
<Button className="Button Button--primary Button--block" type="submit" loading={this.loading}>
|
||||
{app.translator.trans('flarum-nicknames.forum.change_nickname.submit_button')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -13,7 +13,7 @@ interface PaginationAttrs extends ComponentAttrs {
|
||||
export default class Pagination extends Component<PaginationAttrs> {
|
||||
view() {
|
||||
return (
|
||||
<nav class="Pagination UserListPage-gridPagination">
|
||||
<nav className="Pagination UserListPage-gridPagination">
|
||||
<Button
|
||||
disabled={!this.attrs.list.hasPrev()}
|
||||
title={app.translator.trans('core.admin.users.pagination.back_button')}
|
||||
@ -21,7 +21,7 @@ export default class Pagination extends Component<PaginationAttrs> {
|
||||
icon="fas fa-chevron-left"
|
||||
className="Button Button--icon UserListPage-backBtn"
|
||||
/>
|
||||
<span class="UserListPage-pageNumber">
|
||||
<span className="UserListPage-pageNumber">
|
||||
{app.translator.trans('core.admin.users.pagination.page_counter', {
|
||||
current: this.attrs.list.pageNumber() + 1,
|
||||
total: this.attrs.list.getTotalPages(),
|
||||
|
@ -75,7 +75,7 @@ export default class QueueSection extends Component<{}> {
|
||||
return extension ? (
|
||||
<div className="PackageManager-queueTable-package">
|
||||
<div className="PackageManager-queueTable-package-icon ExtensionIcon" style={extension.icon}>
|
||||
{extension.icon ? icon(extension.icon.name) : ''}
|
||||
{!!extension.icon && icon(extension.icon.name)}
|
||||
</div>
|
||||
<div className="PackageManager-queueTable-package-details">
|
||||
<span className="PackageManager-queueTable-package-title">{extension.extra['flarum-extension'].title}</span>
|
||||
|
@ -87,22 +87,21 @@ app.initializers.add('flarum-pusher', () => {
|
||||
|
||||
if (count && typeof vdom === 'object' && vdom && 'children' in vdom && vdom.children instanceof Array) {
|
||||
vdom.children.unshift(
|
||||
Button.component(
|
||||
{
|
||||
className: 'Button Button--block DiscussionList-update',
|
||||
onclick: () => {
|
||||
this.attrs.state.refresh().then(() => {
|
||||
this.loadingUpdated = false;
|
||||
app.pushedUpdates = [];
|
||||
app.setTitleCount(0);
|
||||
m.redraw();
|
||||
});
|
||||
this.loadingUpdated = true;
|
||||
},
|
||||
loading: this.loadingUpdated,
|
||||
},
|
||||
app.translator.trans('flarum-pusher.forum.discussion_list.show_updates_text', { count })
|
||||
)
|
||||
<Button
|
||||
className="Button Button--block DiscussionList-update"
|
||||
onclick={() => {
|
||||
this.attrs.state.refresh().then(() => {
|
||||
this.loadingUpdated = false;
|
||||
app.pushedUpdates = [];
|
||||
app.setTitleCount(0);
|
||||
m.redraw();
|
||||
});
|
||||
this.loadingUpdated = true;
|
||||
}}
|
||||
loading={this.loadingUpdated}
|
||||
>
|
||||
{app.translator.trans('flarum-pusher.forum.discussion_list.show_updates_text', { count })}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ import extractText from 'flarum/common/utils/extractText';
|
||||
import LoadingIndicator from 'flarum/common/components/LoadingIndicator';
|
||||
import Placeholder from 'flarum/common/components/Placeholder';
|
||||
import icon from 'flarum/common/helpers/icon';
|
||||
import classList from 'flarum/common/utils/classList';
|
||||
|
||||
import DashboardWidget, { IDashboardWidgetAttrs } from 'flarum/admin/components/DashboardWidget';
|
||||
|
||||
@ -277,7 +278,7 @@ export default class StatisticsWidget extends DashboardWidget {
|
||||
|
||||
return (
|
||||
<button
|
||||
className={'Button--ua-reset StatisticsWidget-entity' + (this.selectedEntity === entity ? ' active' : '')}
|
||||
className={classList('Button--ua-reset StatisticsWidget-entity', { active: this.selectedEntity === entity })}
|
||||
onclick={this.changeEntity.bind(this, entity)}
|
||||
>
|
||||
<h3 className="StatisticsWidget-heading">{app.translator.trans('flarum-statistics.admin.statistics.' + entity + '_heading')}</h3>
|
||||
|
@ -71,7 +71,7 @@ export default class StatisticsWidgetDateSelectionModal extends Modal<IStatistic
|
||||
}
|
||||
|
||||
content(): Mithril.Children {
|
||||
return <div class="Modal-body">{this.items().toArray()}</div>;
|
||||
return <div className="Modal-body">{this.items().toArray()}</div>;
|
||||
}
|
||||
|
||||
items(): ItemList<Mithril.Children> {
|
||||
@ -81,7 +81,7 @@ export default class StatisticsWidgetDateSelectionModal extends Modal<IStatistic
|
||||
|
||||
items.add(
|
||||
'date_start',
|
||||
<div class="Form-group">
|
||||
<div className="Form-group">
|
||||
<label htmlFor={this.state.ids.startDate}>{app.translator.trans('flarum-statistics.admin.date_selection_modal.start_date')}</label>
|
||||
<input
|
||||
type="date"
|
||||
@ -96,7 +96,7 @@ export default class StatisticsWidgetDateSelectionModal extends Modal<IStatistic
|
||||
|
||||
items.add(
|
||||
'date_end',
|
||||
<div class="Form-group">
|
||||
<div className="Form-group">
|
||||
<label htmlFor={this.state.ids.endDate}>{app.translator.trans('flarum-statistics.admin.date_selection_modal.end_date')}</label>
|
||||
<input
|
||||
type="date"
|
||||
@ -111,7 +111,7 @@ export default class StatisticsWidgetDateSelectionModal extends Modal<IStatistic
|
||||
|
||||
items.add(
|
||||
'submit',
|
||||
<Button class="Button Button--primary" type="submit">
|
||||
<Button className="Button Button--primary" type="submit">
|
||||
{app.translator.trans('flarum-statistics.admin.date_selection_modal.submit_button')}
|
||||
</Button>,
|
||||
0
|
||||
|
@ -8,11 +8,7 @@ export default function addStickyBadge() {
|
||||
if (this.isSticky()) {
|
||||
badges.add(
|
||||
'sticky',
|
||||
Badge.component({
|
||||
type: 'sticky',
|
||||
label: app.translator.trans('flarum-sticky.forum.badge.sticky_tooltip'),
|
||||
icon: 'fas fa-thumbtack',
|
||||
}),
|
||||
<Badge type="sticky" label={app.translator.trans('flarum-sticky.forum.badge.sticky_tooltip')} icon="fas fa-thumbtack" />,
|
||||
10
|
||||
);
|
||||
}
|
||||
|
@ -9,17 +9,9 @@ export default function addStickyControl() {
|
||||
if (discussion.canSticky()) {
|
||||
items.add(
|
||||
'sticky',
|
||||
Button.component(
|
||||
{
|
||||
icon: 'fas fa-thumbtack',
|
||||
onclick: this.stickyAction.bind(discussion),
|
||||
},
|
||||
app.translator.trans(
|
||||
discussion.isSticky()
|
||||
? 'flarum-sticky.forum.discussion_controls.unsticky_button'
|
||||
: 'flarum-sticky.forum.discussion_controls.sticky_button'
|
||||
)
|
||||
)
|
||||
<Button icon="fas fa-thumbtack" onclick={this.stickyAction.bind(discussion)}>
|
||||
{app.translator.trans(`flarum-sticky.forum.discussion_controls.${discussion.isSticky() ? 'unsticky' : 'sticky'}_button`)}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -9,23 +9,12 @@ export default function addSubscriptionBadge() {
|
||||
|
||||
switch (this.subscription()) {
|
||||
case 'follow':
|
||||
badge = Badge.component({
|
||||
label: app.translator.trans('flarum-subscriptions.forum.badge.following_tooltip'),
|
||||
icon: 'fas fa-star',
|
||||
type: 'following',
|
||||
});
|
||||
badge = <Badge label={app.translator.trans('flarum-subscriptions.forum.badge.following_tooltip')} icon="fas fa-star" type="following" />;
|
||||
break;
|
||||
|
||||
case 'ignore':
|
||||
badge = Badge.component({
|
||||
label: app.translator.trans('flarum-subscriptions.forum.badge.ignoring_tooltip'),
|
||||
icon: 'far fa-eye-slash',
|
||||
type: 'ignoring',
|
||||
});
|
||||
badge = <Badge label={app.translator.trans('flarum-subscriptions.forum.badge.ignoring_tooltip')} icon="far fa-eye-slash" type="ignoring" />;
|
||||
break;
|
||||
|
||||
default:
|
||||
// no default
|
||||
}
|
||||
|
||||
if (badge) {
|
||||
|
@ -19,13 +19,9 @@ export default function addSubscriptionControls() {
|
||||
|
||||
items.add(
|
||||
'subscription',
|
||||
Button.component(
|
||||
{
|
||||
icon: states[subscription].icon,
|
||||
onclick: discussion.save.bind(discussion, { subscription: states[subscription].save }),
|
||||
},
|
||||
states[subscription].label
|
||||
)
|
||||
<Button icon={states[subscription].icon} onclick={discussion.save.bind(discussion, { subscription: states[subscription].save })}>
|
||||
{states[subscription].label}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
});
|
||||
@ -34,7 +30,7 @@ export default function addSubscriptionControls() {
|
||||
if (app.session.user) {
|
||||
const discussion = this.discussion;
|
||||
|
||||
items.add('subscription', SubscriptionMenu.component({ discussion }), 80);
|
||||
items.add('subscription', <SubscriptionMenu discussion={discussion} />, 80);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -12,13 +12,9 @@ export default function addSubscriptionFilter() {
|
||||
|
||||
items.add(
|
||||
'following',
|
||||
LinkButton.component(
|
||||
{
|
||||
href: app.route('following', params),
|
||||
icon: 'fas fa-star',
|
||||
},
|
||||
app.translator.trans('flarum-subscriptions.forum.index.following_link')
|
||||
),
|
||||
<LinkButton href={app.route('following', params)} icon="fas fa-star">
|
||||
{app.translator.trans('flarum-subscriptions.forum.index.following_link')}
|
||||
</LinkButton>,
|
||||
50
|
||||
);
|
||||
}
|
||||
|
@ -7,21 +7,20 @@ export default function () {
|
||||
extend(SettingsPage.prototype, 'notificationsItems', function (this: SettingsPage, items) {
|
||||
items.add(
|
||||
'followAfterReply',
|
||||
Switch.component(
|
||||
{
|
||||
state: this.user.preferences().followAfterReply,
|
||||
onchange: (value) => {
|
||||
this.followAfterReplyLoading = true;
|
||||
<Switch
|
||||
state={this.user.preferences().followAfterReply}
|
||||
onchange={(value) => {
|
||||
this.followAfterReplyLoading = true;
|
||||
|
||||
this.user.savePreferences({ followAfterReply: value }).then(() => {
|
||||
this.followAfterReplyLoading = false;
|
||||
m.redraw();
|
||||
});
|
||||
},
|
||||
loading: this.followAfterReplyLoading,
|
||||
},
|
||||
app.translator.trans('flarum-subscriptions.forum.settings.follow_after_reply_label')
|
||||
)
|
||||
this.user.savePreferences({ followAfterReply: value }).then(() => {
|
||||
this.followAfterReplyLoading = false;
|
||||
m.redraw();
|
||||
});
|
||||
}}
|
||||
loading={this.followAfterReplyLoading}
|
||||
>
|
||||
{app.translator.trans('flarum-subscriptions.forum.settings.follow_after_reply_label')}
|
||||
</Switch>
|
||||
);
|
||||
|
||||
items.add(
|
||||
|
@ -95,11 +95,11 @@ export default class SubscriptionMenu extends Dropdown {
|
||||
<ul className="Dropdown-menu dropdown-menu Dropdown-menu--right">
|
||||
{this.options.map((attrs) => (
|
||||
<li>
|
||||
{SubscriptionMenuItem.component({
|
||||
...attrs,
|
||||
onclick: this.saveSubscription.bind(this, discussion, attrs.subscription),
|
||||
active: subscription === attrs.subscription,
|
||||
})}
|
||||
<SubscriptionMenuItem
|
||||
{...attrs}
|
||||
onclick={this.saveSubscription.bind(this, discussion, attrs.subscription)}
|
||||
active={subscription === attrs.subscription}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
@ -5,7 +5,7 @@ export default class SubscriptionMenuItem extends Component {
|
||||
view() {
|
||||
return (
|
||||
<button className="SubscriptionMenuItem hasIcon" onclick={this.attrs.onclick}>
|
||||
{this.attrs.active ? icon('fas fa-check', { className: 'Button-icon' }) : ''}
|
||||
{this.attrs.active && icon('fas fa-check', { className: 'Button-icon' })}
|
||||
<span className="SubscriptionMenuItem-label">
|
||||
{icon(this.attrs.icon, { className: 'Button-icon' })}
|
||||
<strong>{this.attrs.label}</strong>
|
||||
|
@ -20,13 +20,9 @@ app.initializers.add('flarum-suspend', () => {
|
||||
if (user.canSuspend()) {
|
||||
items.add(
|
||||
'suspend',
|
||||
Button.component(
|
||||
{
|
||||
icon: 'fas fa-ban',
|
||||
onclick: () => app.modal.show(SuspendUserModal, { user }),
|
||||
},
|
||||
app.translator.trans('flarum-suspend.forum.user_controls.suspend_button')
|
||||
)
|
||||
<Button icon="fas fa-ban" onclick={() => app.modal.show(SuspendUserModal, { user })}>
|
||||
{app.translator.trans('flarum-suspend.forum.user_controls.suspend_button')}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
});
|
||||
@ -37,11 +33,8 @@ app.initializers.add('flarum-suspend', () => {
|
||||
if (new Date() < until) {
|
||||
items.add(
|
||||
'suspended',
|
||||
Badge.component({
|
||||
icon: 'fas fa-ban',
|
||||
type: 'suspended',
|
||||
label: app.translator.trans('flarum-suspend.forum.user_badge.suspended_tooltip'),
|
||||
})
|
||||
<Badge icon="fas fa-ban" type="suspended" label={app.translator.trans('flarum-suspend.forum.user_badge.suspended_tooltip')} />,
|
||||
100
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -1,3 +1,4 @@
|
||||
import app from 'flarum/admin/app';
|
||||
import { extend } from 'flarum/common/extend';
|
||||
import PermissionGrid from 'flarum/admin/components/PermissionGrid';
|
||||
import SettingDropdown from 'flarum/admin/components/SettingDropdown';
|
||||
@ -12,17 +13,21 @@ export default function () {
|
||||
setting: () => {
|
||||
const minutes = parseInt(app.data.settings.allow_tag_change, 10);
|
||||
|
||||
return SettingDropdown.component({
|
||||
defaultLabel: minutes
|
||||
? app.translator.trans('core.admin.permissions_controls.allow_some_minutes_button', { count: minutes })
|
||||
: app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button'),
|
||||
key: 'allow_tag_change',
|
||||
options: [
|
||||
{ value: '-1', label: app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button') },
|
||||
{ value: '10', label: app.translator.trans('core.admin.permissions_controls.allow_ten_minutes_button') },
|
||||
{ value: 'reply', label: app.translator.trans('core.admin.permissions_controls.allow_until_reply_button') },
|
||||
],
|
||||
});
|
||||
return (
|
||||
<SettingDropdown
|
||||
defaultLabel={
|
||||
minutes
|
||||
? app.translator.trans('core.admin.permissions_controls.allow_some_minutes_button', { count: minutes })
|
||||
: app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button')
|
||||
}
|
||||
key="allow_tag_change"
|
||||
options={[
|
||||
{ value: '-1', label: app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button') },
|
||||
{ value: '10', label: app.translator.trans('core.admin.permissions_controls.allow_ten_minutes_button') },
|
||||
{ value: 'reply', label: app.translator.trans('core.admin.permissions_controls.allow_until_reply_button') },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
90
|
||||
|
@ -53,21 +53,18 @@ export default function () {
|
||||
label: tagLabel(tag),
|
||||
onremove: () => tag.save({ isRestricted: false }),
|
||||
render: (item) => {
|
||||
if ('setting' in item) return '';
|
||||
if ('setting' in item) return null;
|
||||
|
||||
if (
|
||||
item.permission === 'viewForum' ||
|
||||
item.permission === 'startDiscussion' ||
|
||||
(item.permission && item.permission.indexOf('discussion.') === 0 && item.tagScoped !== false) ||
|
||||
(item.permission.startsWith('discussion.') && item.tagScoped !== false) ||
|
||||
item.tagScoped
|
||||
) {
|
||||
return PermissionDropdown.component({
|
||||
permission: 'tag' + tag.id() + '.' + item.permission,
|
||||
allowGuest: item.allowGuest,
|
||||
});
|
||||
return <PermissionDropdown permission={`tag${tag.id()}.${item.permission}`} allowGuest={item.allowGuest} />;
|
||||
}
|
||||
|
||||
return '';
|
||||
return null;
|
||||
},
|
||||
})
|
||||
);
|
||||
|
@ -140,20 +140,14 @@ export default class EditTagModal extends Modal<EditTagModalAttrs> {
|
||||
items.add(
|
||||
'submit',
|
||||
<div className="Form-group">
|
||||
{Button.component(
|
||||
{
|
||||
type: 'submit',
|
||||
className: 'Button Button--primary EditTagModal-save',
|
||||
loading: this.loading,
|
||||
},
|
||||
app.translator.trans('flarum-tags.admin.edit_tag.submit_button')
|
||||
)}
|
||||
{this.tag.exists ? (
|
||||
<Button type="submit" className="Button Button--primary EditTagModal-save" loading={this.loading}>
|
||||
{app.translator.trans('flarum-tags.admin.edit_tag.submit_button')}
|
||||
</Button>
|
||||
|
||||
{this.tag.exists && (
|
||||
<button type="button" className="Button EditTagModal-delete" onclick={this.delete.bind(this)}>
|
||||
{app.translator.trans('flarum-tags.admin.edit_tag.delete_tag_button')}
|
||||
</button>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>,
|
||||
-10
|
||||
|
@ -16,20 +16,14 @@ function tagItem(tag) {
|
||||
<div className="TagListItem-info">
|
||||
{tagIcon(tag)}
|
||||
<span className="TagListItem-name">{tag.name()}</span>
|
||||
{Button.component({
|
||||
className: 'Button Button--link',
|
||||
icon: 'fas fa-pencil-alt',
|
||||
onclick: () => app.modal.show(EditTagModal, { model: tag }),
|
||||
})}
|
||||
<Button className="Button Button--link" icon="fas fa-pencil-alt" onclick={() => app.modal.show(EditTagModal, { model: tag })} />
|
||||
</div>
|
||||
{!tag.isChild() && tag.position() !== null ? (
|
||||
{!tag.isChild() && tag.position() !== null && (
|
||||
<ol className="TagListItem-children TagList">
|
||||
{sortTags(app.store.all('tags'))
|
||||
.filter((child) => child.parent() === tag)
|
||||
.map(tagItem)}
|
||||
</ol>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
@ -75,14 +69,9 @@ export default class TagsPage extends ExtensionPage {
|
||||
<div className="TagGroup">
|
||||
<label>{app.translator.trans('flarum-tags.admin.tags.primary_heading')}</label>
|
||||
<ol className="TagList TagList--primary">{tags.filter((tag) => tag.position() !== null && !tag.isChild()).map(tagItem)}</ol>
|
||||
{Button.component(
|
||||
{
|
||||
className: 'Button TagList-button',
|
||||
icon: 'fas fa-plus',
|
||||
onclick: () => app.modal.show(EditTagModal, { primary: true }),
|
||||
},
|
||||
app.translator.trans('flarum-tags.admin.tags.create_primary_tag_button')
|
||||
)}
|
||||
<Button className="Button TagList-button" icon="fas fa-plus" onclick={() => app.modal.show(EditTagModal, { primary: true })}>
|
||||
{app.translator.trans('flarum-tags.admin.tags.create_primary_tag_button')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="TagGroup TagGroup--secondary">
|
||||
@ -93,14 +82,9 @@ export default class TagsPage extends ExtensionPage {
|
||||
.sort((a, b) => a.name().localeCompare(b.name()))
|
||||
.map(tagItem)}
|
||||
</ul>
|
||||
{Button.component(
|
||||
{
|
||||
className: 'Button TagList-button',
|
||||
icon: 'fas fa-plus',
|
||||
onclick: () => app.modal.show(EditTagModal, { primary: false }),
|
||||
},
|
||||
app.translator.trans('flarum-tags.admin.tags.create_secondary_tag_button')
|
||||
)}
|
||||
<Button className="Button TagList-button" icon="fas fa-plus" onclick={() => app.modal.show(EditTagModal, { primary: false })}>
|
||||
{app.translator.trans('flarum-tags.admin.tags.create_secondary_tag_button')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="Form">
|
||||
<label>{app.translator.trans('flarum-tags.admin.tags.settings_heading')}</label>
|
||||
|
@ -1,12 +1,13 @@
|
||||
import extract from 'flarum/common/utils/extract';
|
||||
import tagLabel from './tagLabel';
|
||||
import sortTags from '../utils/sortTags';
|
||||
import classList from '@flarum/core/src/common/utils/classList';
|
||||
|
||||
export default function tagsLabel(tags, attrs = {}) {
|
||||
const children = [];
|
||||
const link = extract(attrs, 'link');
|
||||
const { link, ...otherAttrs } = attrs;
|
||||
|
||||
attrs.className = 'TagsLabel ' + (attrs.className || '');
|
||||
attrs.className = classList('TagsLabel', attrs.className);
|
||||
|
||||
if (tags) {
|
||||
sortTags(tags).forEach((tag) => {
|
||||
@ -18,5 +19,5 @@ export default function tagsLabel(tags, attrs = {}) {
|
||||
children.push(tagLabel());
|
||||
}
|
||||
|
||||
return <span {...attrs}>{children}</span>;
|
||||
return <span {...otherAttrs}>{children}</span>;
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import LinkButton from 'flarum/common/components/LinkButton';
|
||||
|
||||
import TagLinkButton from './components/TagLinkButton';
|
||||
import TagsPage from './components/TagsPage';
|
||||
import app from 'flarum/admin/app';
|
||||
import sortTags from '../common/utils/sortTags';
|
||||
|
||||
export default function () {
|
||||
@ -21,7 +22,7 @@ export default function () {
|
||||
|
||||
if (app.current.matches(TagsPage)) return;
|
||||
|
||||
items.add('separator', Separator.component(), -12);
|
||||
items.add('separator', <Separator />, -12);
|
||||
|
||||
const params = app.search.stickyParams();
|
||||
const tags = app.store.all('tags');
|
||||
@ -39,7 +40,13 @@ export default function () {
|
||||
// use its children to populate the dropdown. The problem here is that `view`
|
||||
// on TagLinkButton is only called AFTER SelectDropdown, so no children are available
|
||||
// for SelectDropdown to use at the time.
|
||||
items.add('tag' + tag.id(), TagLinkButton.component({ model: tag, params, active }, tag?.name()), -14);
|
||||
items.add(
|
||||
'tag' + tag.id(),
|
||||
<TagLinkButton model={tag} params={params} active={active}>
|
||||
{tag?.name()}
|
||||
</TagLinkButton>,
|
||||
-14
|
||||
);
|
||||
};
|
||||
|
||||
sortTags(tags)
|
||||
|
@ -11,7 +11,7 @@ export default class TagHero extends Component {
|
||||
return (
|
||||
<header
|
||||
className={classList('Hero', 'TagHero', { 'TagHero--colored': color, [textContrastClass(color)]: color })}
|
||||
style={color ? { '--hero-bg': color } : ''}
|
||||
style={color ? { '--hero-bg': color } : undefined}
|
||||
>
|
||||
<div className="container">
|
||||
<div className="containerNarrow">
|
||||
|
@ -6,12 +6,11 @@ import tagIcon from '../../common/helpers/tagIcon';
|
||||
export default class TagLinkButton extends LinkButton {
|
||||
view(vnode) {
|
||||
const tag = this.attrs.model;
|
||||
const active = this.constructor.isActive(this.attrs);
|
||||
const description = tag && tag.description();
|
||||
const className = classList(['TagLinkButton', 'hasIcon', this.attrs.className, tag.isChild() && 'child']);
|
||||
const className = classList('TagLinkButton hasIcon', { child: tag.isChild() }, this.attrs.className);
|
||||
|
||||
return (
|
||||
<Link className={className} href={this.attrs.route} style={tag ? { '--color': tag.color() } : ''} title={description || ''}>
|
||||
<Link className={className} href={this.attrs.route} style={tag ? { '--color': tag.color() } : undefined} title={description || undefined}>
|
||||
{tagIcon(tag, { className: 'Button-icon' })}
|
||||
<span className="Button-label">{tag ? tag.name() : app.translator.trans('flarum-tags.forum.index.untagged_link')}</span>
|
||||
</Link>
|
||||
|
@ -120,10 +120,8 @@ export default class TagsPage extends Page {
|
||||
{tag.icon() && tagIcon(tag, {}, { useColor: false })}
|
||||
<h3 className="TagTile-name">{tag.name()}</h3>
|
||||
<p className="TagTile-description">{tag.description()}</p>
|
||||
{children ? (
|
||||
{!!children && (
|
||||
<div className="TagTile-children">{children.map((child) => [<Link href={app.route.tag(child)}>{child.name()}</Link>, ' '])}</div>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</Link>
|
||||
{lastPostedDiscussion ? (
|
||||
|
@ -96,11 +96,7 @@ export default class AdminApplication extends Application {
|
||||
super.mount();
|
||||
|
||||
m.mount(document.getElementById('app-navigation')!, {
|
||||
view: () =>
|
||||
Navigation.component({
|
||||
className: 'App-backControl',
|
||||
drawer: true,
|
||||
}),
|
||||
view: () => <Navigation className="App-backControl" drawer />,
|
||||
});
|
||||
m.mount(document.getElementById('header-navigation')!, Navigation);
|
||||
m.mount(document.getElementById('header-primary')!, HeaderPrimary);
|
@ -217,7 +217,7 @@ export default abstract class AdminPage<CustomAttrs extends IPageAttrs = IPageAt
|
||||
* return (
|
||||
* <div className={attrs.className}>
|
||||
* <label>{attrs.label}</label>
|
||||
* {attrs.help && <p class="helpText">{attrs.help}</p>}
|
||||
* {attrs.help && <p className="helpText">{attrs.help}</p>}
|
||||
*
|
||||
* My setting component!
|
||||
* </div>
|
||||
|
@ -19,62 +19,52 @@ export default class AppearancePage extends AdminPage {
|
||||
}
|
||||
|
||||
content() {
|
||||
return [
|
||||
<div className="Form">
|
||||
<fieldset className="AppearancePage-colors">
|
||||
<legend>{app.translator.trans('core.admin.appearance.colors_heading')}</legend>
|
||||
{this.colorItems().toArray()}
|
||||
return (
|
||||
<>
|
||||
<div className="Form">
|
||||
<fieldset className="AppearancePage-colors">
|
||||
<legend>{app.translator.trans('core.admin.appearance.colors_heading')}</legend>
|
||||
{this.colorItems().toArray()}
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<legend>{app.translator.trans('core.admin.appearance.logo_heading')}</legend>
|
||||
<div className="helpText">{app.translator.trans('core.admin.appearance.logo_text')}</div>
|
||||
<UploadImageButton name="logo" />
|
||||
</fieldset>
|
||||
</div>,
|
||||
|
||||
<fieldset>
|
||||
<legend>{app.translator.trans('core.admin.appearance.logo_heading')}</legend>
|
||||
<div className="helpText">{app.translator.trans('core.admin.appearance.logo_text')}</div>
|
||||
<UploadImageButton name="logo" />
|
||||
</fieldset>,
|
||||
<fieldset>
|
||||
<legend>{app.translator.trans('core.admin.appearance.favicon_heading')}</legend>
|
||||
<div className="helpText">{app.translator.trans('core.admin.appearance.favicon_text')}</div>
|
||||
<UploadImageButton name="favicon" />
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>{app.translator.trans('core.admin.appearance.favicon_heading')}</legend>
|
||||
<div className="helpText">{app.translator.trans('core.admin.appearance.favicon_text')}</div>
|
||||
<UploadImageButton name="favicon" />
|
||||
</fieldset>,
|
||||
<fieldset>
|
||||
<legend>{app.translator.trans('core.admin.appearance.custom_header_heading')}</legend>
|
||||
<div className="helpText">{app.translator.trans('core.admin.appearance.custom_header_text')}</div>
|
||||
<Button className="Button" onclick={() => app.modal.show(EditCustomHeaderModal)}>
|
||||
{app.translator.trans('core.admin.appearance.edit_header_button')}
|
||||
</Button>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>{app.translator.trans('core.admin.appearance.custom_header_heading')}</legend>
|
||||
<div className="helpText">{app.translator.trans('core.admin.appearance.custom_header_text')}</div>
|
||||
{Button.component(
|
||||
{
|
||||
className: 'Button',
|
||||
onclick: () => app.modal.show(EditCustomHeaderModal),
|
||||
},
|
||||
app.translator.trans('core.admin.appearance.edit_header_button')
|
||||
)}
|
||||
</fieldset>,
|
||||
<fieldset>
|
||||
<legend>{app.translator.trans('core.admin.appearance.custom_footer_heading')}</legend>
|
||||
<div className="helpText">{app.translator.trans('core.admin.appearance.custom_footer_text')}</div>
|
||||
<Button className="Button" onclick={() => app.modal.show(EditCustomFooterModal)}>
|
||||
{app.translator.trans('core.admin.appearance.edit_footer_button')}
|
||||
</Button>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>{app.translator.trans('core.admin.appearance.custom_footer_heading')}</legend>
|
||||
<div className="helpText">{app.translator.trans('core.admin.appearance.custom_footer_text')}</div>
|
||||
{Button.component(
|
||||
{
|
||||
className: 'Button',
|
||||
onclick: () => app.modal.show(EditCustomFooterModal),
|
||||
},
|
||||
app.translator.trans('core.admin.appearance.edit_footer_button')
|
||||
)}
|
||||
</fieldset>,
|
||||
|
||||
<fieldset>
|
||||
<legend>{app.translator.trans('core.admin.appearance.custom_styles_heading')}</legend>
|
||||
<div className="helpText">{app.translator.trans('core.admin.appearance.custom_styles_text')}</div>
|
||||
{Button.component(
|
||||
{
|
||||
className: 'Button',
|
||||
onclick: () => app.modal.show(EditCustomCssModal),
|
||||
},
|
||||
app.translator.trans('core.admin.appearance.edit_css_button')
|
||||
)}
|
||||
</fieldset>,
|
||||
];
|
||||
<fieldset>
|
||||
<legend>{app.translator.trans('core.admin.appearance.custom_styles_heading')}</legend>
|
||||
<div className="helpText">{app.translator.trans('core.admin.appearance.custom_styles_text')}</div>
|
||||
<Button className="Button" onclick={() => app.modal.show(EditCustomCssModal)}>
|
||||
{app.translator.trans('core.admin.appearance.edit_css_button')}
|
||||
</Button>
|
||||
</fieldset>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
colorItems() {
|
||||
|
@ -56,21 +56,21 @@ export default class BasicsPage<CustomAttrs extends IPageAttrs = IPageAttrs> ext
|
||||
help: app.translator.trans('core.admin.basics.forum_description_text'),
|
||||
})}
|
||||
|
||||
{Object.keys(this.localeOptions).length > 1
|
||||
? [
|
||||
this.buildSettingComponent({
|
||||
type: 'select',
|
||||
setting: 'default_locale',
|
||||
options: this.localeOptions,
|
||||
label: app.translator.trans('core.admin.basics.default_language_heading'),
|
||||
}),
|
||||
this.buildSettingComponent({
|
||||
type: 'switch',
|
||||
setting: 'show_language_selector',
|
||||
label: app.translator.trans('core.admin.basics.show_language_selector_label'),
|
||||
}),
|
||||
]
|
||||
: ''}
|
||||
{Object.keys(this.localeOptions).length > 1 && (
|
||||
<>
|
||||
{this.buildSettingComponent({
|
||||
type: 'select',
|
||||
setting: 'default_locale',
|
||||
options: this.localeOptions,
|
||||
label: app.translator.trans('core.admin.basics.default_language_heading'),
|
||||
})}
|
||||
{this.buildSettingComponent({
|
||||
type: 'switch',
|
||||
setting: 'show_language_selector',
|
||||
label: app.translator.trans('core.admin.basics.show_language_selector_label'),
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
|
||||
<FieldSet className="BasicsPage-homePage Form-group" label={app.translator.trans('core.admin.basics.home_page_heading')}>
|
||||
<div className="helpText">{app.translator.trans('core.admin.basics.home_page_text')}</div>
|
||||
@ -91,15 +91,14 @@ export default class BasicsPage<CustomAttrs extends IPageAttrs = IPageAttrs> ext
|
||||
<textarea className="FormControl" bidi={this.setting('welcome_message')} />
|
||||
</div>
|
||||
|
||||
{Object.keys(this.displayNameOptions).length > 1
|
||||
? this.buildSettingComponent({
|
||||
type: 'select',
|
||||
setting: 'display_name_driver',
|
||||
options: this.displayNameOptions,
|
||||
label: app.translator.trans('core.admin.basics.display_name_heading'),
|
||||
help: app.translator.trans('core.admin.basics.display_name_text'),
|
||||
})
|
||||
: ''}
|
||||
{Object.keys(this.displayNameOptions).length > 1 &&
|
||||
this.buildSettingComponent({
|
||||
type: 'select',
|
||||
setting: 'display_name_driver',
|
||||
options: this.displayNameOptions,
|
||||
label: app.translator.trans('core.admin.basics.display_name_heading'),
|
||||
help: app.translator.trans('core.admin.basics.display_name_text'),
|
||||
})}
|
||||
|
||||
{Object.keys(this.slugDriverOptions).map((model) => {
|
||||
const options = this.slugDriverOptions[model];
|
||||
|
@ -43,16 +43,12 @@ export default class EditGroupModal<CustomAttrs extends IEditGroupModalAttrs = I
|
||||
}
|
||||
|
||||
title() {
|
||||
return [
|
||||
this.color() || this.icon()
|
||||
? Badge.component({
|
||||
icon: this.icon(),
|
||||
color: this.color(),
|
||||
})
|
||||
: '',
|
||||
' ',
|
||||
this.namePlural() || app.translator.trans('core.admin.edit_group.title'),
|
||||
];
|
||||
return (
|
||||
<>
|
||||
{!!(this.color() || this.icon()) && <Badge icon={this.icon()} color={this.color()} />}{' '}
|
||||
{this.namePlural() || app.translator.trans('core.admin.edit_group.title')}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
content() {
|
||||
@ -63,8 +59,8 @@ export default class EditGroupModal<CustomAttrs extends IEditGroupModalAttrs = I
|
||||
);
|
||||
}
|
||||
|
||||
fields() {
|
||||
const items = new ItemList();
|
||||
fields(): ItemList<Mithril.Children> {
|
||||
const items = new ItemList<Mithril.Children>();
|
||||
|
||||
items.add(
|
||||
'name',
|
||||
@ -102,13 +98,9 @@ export default class EditGroupModal<CustomAttrs extends IEditGroupModalAttrs = I
|
||||
items.add(
|
||||
'hidden',
|
||||
<div className="Form-group">
|
||||
{Switch.component(
|
||||
{
|
||||
state: !!Number(this.isHidden()),
|
||||
onchange: this.isHidden,
|
||||
},
|
||||
app.translator.trans('core.admin.edit_group.hide_label')
|
||||
)}
|
||||
<Switch state={this.isHidden()} onchange={this.isHidden}>
|
||||
{app.translator.trans('core.admin.edit_group.hide_label')}
|
||||
</Switch>
|
||||
</div>,
|
||||
10
|
||||
);
|
||||
@ -116,20 +108,14 @@ export default class EditGroupModal<CustomAttrs extends IEditGroupModalAttrs = I
|
||||
items.add(
|
||||
'submit',
|
||||
<div className="Form-group">
|
||||
{Button.component(
|
||||
{
|
||||
type: 'submit',
|
||||
className: 'Button Button--primary EditGroupModal-save',
|
||||
loading: this.loading,
|
||||
},
|
||||
app.translator.trans('core.admin.edit_group.submit_button')
|
||||
)}
|
||||
{this.group.exists && this.group.id() !== Group.ADMINISTRATOR_ID ? (
|
||||
<Button type="submit" className="Button Button--primary EditGroupModal-save" loading={this.loading}>
|
||||
{app.translator.trans('core.admin.edit_group.submit_button')}
|
||||
</Button>
|
||||
|
||||
{this.group.exists && this.group.id() !== Group.ADMINISTRATOR_ID && (
|
||||
<button type="button" className="Button EditGroupModal-delete" onclick={this.deleteGroup.bind(this)}>
|
||||
{app.translator.trans('core.admin.edit_group.delete_button')}
|
||||
</button>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>,
|
||||
-10
|
||||
|
@ -12,7 +12,7 @@ export default class ExtensionLinkButton extends LinkButton {
|
||||
|
||||
content.unshift(
|
||||
<span className="ExtensionListItem-icon ExtensionIcon" style={extension.icon}>
|
||||
{extension.icon ? icon(extension.icon.name) : ''}
|
||||
{!!extension.icon && icon(extension.icon.name)}
|
||||
</span>
|
||||
);
|
||||
content.push(statuses);
|
||||
@ -23,7 +23,7 @@ export default class ExtensionLinkButton extends LinkButton {
|
||||
statusItems(name) {
|
||||
const items = new ItemList();
|
||||
|
||||
items.add('enabled', <span class={'ExtensionListItem-Dot ' + (isExtensionEnabled(name) ? 'enabled' : 'disabled')} />);
|
||||
items.add('enabled', <span className={'ExtensionListItem-Dot ' + (isExtensionEnabled(name) ? 'enabled' : 'disabled')} />);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ export default class ExtensionPage<Attrs extends ExtensionPageAttrs = ExtensionP
|
||||
<div className="container">
|
||||
<div className="ExtensionTitle">
|
||||
<span className="ExtensionIcon" style={this.extension.icon}>
|
||||
{this.extension.icon ? icon(this.extension.icon.name) : ''}
|
||||
{!!this.extension.icon && icon(this.extension.icon.name)}
|
||||
</span>
|
||||
<div className="ExtensionName">
|
||||
<h2>{this.extension.extra['flarum-extension'].title}</h2>
|
||||
@ -111,7 +111,8 @@ export default class ExtensionPage<Attrs extends ExtensionPageAttrs = ExtensionP
|
||||
|
||||
items.add('content', this.content(vnode));
|
||||
|
||||
items.add('permissions', [
|
||||
items.add(
|
||||
'permissions',
|
||||
<div className="ExtensionPage-permissions">
|
||||
<div className="ExtensionPage-permissions-header">
|
||||
<div className="container">
|
||||
@ -120,13 +121,13 @@ export default class ExtensionPage<Attrs extends ExtensionPageAttrs = ExtensionP
|
||||
</div>
|
||||
<div className="container">
|
||||
{app.extensionData.extensionHasPermissions(this.extension.id) ? (
|
||||
ExtensionPermissionGrid.component({ extensionId: this.extension.id })
|
||||
<ExtensionPermissionGrid extensionId={this.extension.id} />
|
||||
) : (
|
||||
<h3 className="ExtensionPage-subHeader">{app.translator.trans('core.admin.extension.no_permissions')}</h3>
|
||||
)}
|
||||
</div>
|
||||
</div>,
|
||||
]);
|
||||
</div>
|
||||
);
|
||||
|
||||
return items;
|
||||
}
|
||||
@ -210,16 +211,9 @@ export default class ExtensionPage<Attrs extends ExtensionPageAttrs = ExtensionP
|
||||
const extension = this.extension;
|
||||
items.add(
|
||||
'readme',
|
||||
Button.component(
|
||||
{
|
||||
icon: 'fab fa-readme',
|
||||
class: 'Button Button--text',
|
||||
onclick() {
|
||||
app.modal.show(ReadmeModal, { extension });
|
||||
},
|
||||
},
|
||||
app.translator.trans('core.admin.extension.readme.button_label')
|
||||
),
|
||||
<Button icon="fab fa-readme" className="Button Button--text" onclick={() => app.modal.show(ReadmeModal, { extension })}>
|
||||
{app.translator.trans('core.admin.extension.readme.button_label')}
|
||||
</Button>,
|
||||
10
|
||||
);
|
||||
|
||||
|
@ -4,6 +4,7 @@ import isExtensionEnabled from '../utils/isExtensionEnabled';
|
||||
import getCategorizedExtensions from '../utils/getCategorizedExtensions';
|
||||
import Link from '../../common/components/Link';
|
||||
import icon from '../../common/helpers/icon';
|
||||
import classList from '../../common/utils/classList';
|
||||
|
||||
export default class ExtensionsWidget extends DashboardWidget {
|
||||
oninit(vnode) {
|
||||
@ -21,7 +22,7 @@ export default class ExtensionsWidget extends DashboardWidget {
|
||||
|
||||
return (
|
||||
<div className="ExtensionsWidget-list">
|
||||
{Object.keys(categories).map((category) => (this.categorizedExtensions[category] ? this.extensionCategory(category) : ''))}
|
||||
{Object.keys(categories).map((category) => !!this.categorizedExtensions[category] && this.extensionCategory(category))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -37,11 +38,11 @@ export default class ExtensionsWidget extends DashboardWidget {
|
||||
|
||||
extensionWidget(extension) {
|
||||
return (
|
||||
<li className={'ExtensionListItem ' + (!isExtensionEnabled(extension.id) ? 'disabled' : '')}>
|
||||
<li className={classList('ExtensionListItem', { disabled: !isExtensionEnabled(extension.id) })}>
|
||||
<Link href={app.route('extension', { id: extension.id })}>
|
||||
<div className="ExtensionListItem-content">
|
||||
<span className="ExtensionListItem-icon ExtensionIcon" style={extension.icon}>
|
||||
{extension.icon ? icon(extension.icon.name) : ''}
|
||||
{!!extension.icon && icon(extension.icon.name)}
|
||||
</span>
|
||||
<span className="ExtensionListItem-title">{extension.extra['flarum-extension'].title}</span>
|
||||
</div>
|
||||
|
@ -18,7 +18,7 @@ export default class LoadingModal<ModalAttrs extends ILoadingModalAttrs = ILoadi
|
||||
}
|
||||
|
||||
content() {
|
||||
return '';
|
||||
return null;
|
||||
}
|
||||
|
||||
onsubmit(e: Event): void {
|
||||
|
@ -81,29 +81,25 @@ export default class MailPage<CustomAttrs extends IPageAttrs = IPageAttrs> exten
|
||||
options: Object.keys(this.driverFields!).reduce((memo, val) => ({ ...memo, [val]: val }), {}),
|
||||
label: app.translator.trans('core.admin.email.driver_heading'),
|
||||
})}
|
||||
{this.status!.sending ||
|
||||
Alert.component(
|
||||
{
|
||||
dismissible: false,
|
||||
},
|
||||
app.translator.trans('core.admin.email.not_sending_message')
|
||||
)}
|
||||
{this.status!.sending || <Alert dismissible={false}>{app.translator.trans('core.admin.email.not_sending_message')}</Alert>}
|
||||
|
||||
{fieldKeys.length > 0 && (
|
||||
{!!fieldKeys.length && (
|
||||
<FieldSet label={app.translator.trans(`core.admin.email.${this.setting('mail_driver')()}_heading`)} className="MailPage-MailSettings">
|
||||
<div className="MailPage-MailSettings-input">
|
||||
{fieldKeys.map((field) => {
|
||||
const fieldInfo = fields[field];
|
||||
|
||||
return [
|
||||
this.buildSettingComponent({
|
||||
type: typeof fieldInfo === 'string' ? 'text' : 'select',
|
||||
label: app.translator.trans(`core.admin.email.${field}_label`),
|
||||
setting: field,
|
||||
options: fieldInfo,
|
||||
}),
|
||||
this.status!.errors[field] && <p className="ValidationError">{this.status!.errors[field]}</p>,
|
||||
];
|
||||
return (
|
||||
<>
|
||||
{this.buildSettingComponent({
|
||||
type: typeof fieldInfo === 'string' ? 'text' : 'select',
|
||||
label: app.translator.trans(`core.admin.email.${field}_label`),
|
||||
setting: field,
|
||||
options: fieldInfo,
|
||||
})}
|
||||
{this.status!.errors[field] && <p className="ValidationError">{this.status!.errors[field]}</p>}
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</FieldSet>
|
||||
@ -112,14 +108,9 @@ export default class MailPage<CustomAttrs extends IPageAttrs = IPageAttrs> exten
|
||||
|
||||
<FieldSet label={app.translator.trans('core.admin.email.send_test_mail_heading')} className="MailPage-MailSettings">
|
||||
<div className="helpText">{app.translator.trans('core.admin.email.send_test_mail_text', { email: app.session.user!.email() })}</div>
|
||||
{Button.component(
|
||||
{
|
||||
className: 'Button Button--primary',
|
||||
disabled: this.sendingTest || this.isChanged(),
|
||||
onclick: () => this.sendTestEmail(),
|
||||
},
|
||||
app.translator.trans('core.admin.email.send_test_mail_button')
|
||||
)}
|
||||
<Button className="Button Button--primary" disabled={this.sendingTest || this.isChanged()} onclick={() => this.sendTestEmail()}>
|
||||
{app.translator.trans('core.admin.email.send_test_mail_button')}
|
||||
</Button>
|
||||
</FieldSet>
|
||||
</div>
|
||||
);
|
||||
|
@ -10,7 +10,7 @@ import Mithril from 'mithril';
|
||||
function badgeForId(id: string) {
|
||||
const group = app.store.getById('groups', id);
|
||||
|
||||
return group ? GroupBadge.component({ group, label: null }) : '';
|
||||
return !!group && <GroupBadge group={group} label={null} />;
|
||||
}
|
||||
|
||||
function filterByRequiredPermissions(groupIds: string[], permission: string) {
|
||||
@ -57,9 +57,9 @@ export default class PermissionDropdown<CustomAttrs extends IPermissionDropdownA
|
||||
const adminGroup = app.store.getById<Group>('groups', Group.ADMINISTRATOR_ID)!;
|
||||
|
||||
if (everyone) {
|
||||
this.attrs.label = Badge.component({ icon: 'fas fa-globe' });
|
||||
this.attrs.label = <Badge icon="fas fa-globe" />;
|
||||
} else if (members) {
|
||||
this.attrs.label = Badge.component({ icon: 'fas fa-user' });
|
||||
this.attrs.label = <Badge icon="fas fa-user" />;
|
||||
} else {
|
||||
this.attrs.label = [badgeForId(Group.ADMINISTRATOR_ID), groupIds.map(badgeForId)];
|
||||
}
|
||||
@ -67,40 +67,29 @@ export default class PermissionDropdown<CustomAttrs extends IPermissionDropdownA
|
||||
if (this.showing) {
|
||||
if (this.attrs.allowGuest) {
|
||||
children.push(
|
||||
Button.component(
|
||||
{
|
||||
icon: everyone ? 'fas fa-check' : true,
|
||||
onclick: () => this.save([Group.GUEST_ID]),
|
||||
disabled: this.isGroupDisabled(Group.GUEST_ID),
|
||||
},
|
||||
[Badge.component({ icon: 'fas fa-globe' }), ' ', app.translator.trans('core.admin.permissions_controls.everyone_button')]
|
||||
)
|
||||
<Button icon={everyone ? 'fas fa-check' : true} onclick={() => this.save([Group.GUEST_ID])} disabled={this.isGroupDisabled(Group.GUEST_ID)}>
|
||||
<Badge icon="fas fa-globe" /> {app.translator.trans('core.admin.permissions_controls.everyone_button')}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
children.push(
|
||||
Button.component(
|
||||
{
|
||||
icon: members ? 'fas fa-check' : true,
|
||||
onclick: () => this.save([Group.MEMBER_ID]),
|
||||
disabled: this.isGroupDisabled(Group.MEMBER_ID),
|
||||
},
|
||||
[Badge.component({ icon: 'fas fa-user' }), ' ', app.translator.trans('core.admin.permissions_controls.members_button')]
|
||||
),
|
||||
<Button icon={members ? 'fas fa-check' : true} onclick={() => this.save([Group.MEMBER_ID])} disabled={this.isGroupDisabled(Group.MEMBER_ID)}>
|
||||
<Badge icon="fas fa-user" /> {app.translator.trans('core.admin.permissions_controls.members_button')}
|
||||
</Button>,
|
||||
|
||||
Separator.component(),
|
||||
<Separator />,
|
||||
|
||||
Button.component(
|
||||
{
|
||||
icon: !everyone && !members ? 'fas fa-check' : true,
|
||||
disabled: !everyone && !members,
|
||||
onclick: (e: MouseEvent) => {
|
||||
if (e.shiftKey) e.stopPropagation();
|
||||
this.save([]);
|
||||
},
|
||||
},
|
||||
[badgeForId(adminGroup.id()!), ' ', adminGroup.namePlural()]
|
||||
)
|
||||
<Button
|
||||
icon={!everyone && !members ? 'fas fa-check' : true}
|
||||
disabled={!everyone && !members}
|
||||
onclick={(e: MouseEvent) => {
|
||||
if (e.shiftKey) e.stopPropagation();
|
||||
this.save([]);
|
||||
}}
|
||||
>
|
||||
{badgeForId(adminGroup.id()!)} {adminGroup.namePlural()}
|
||||
</Button>
|
||||
);
|
||||
|
||||
// These groups are defined above, appearing first in the list.
|
||||
@ -109,19 +98,18 @@ export default class PermissionDropdown<CustomAttrs extends IPermissionDropdownA
|
||||
const groupButtons = app.store
|
||||
.all<Group>('groups')
|
||||
.filter((group) => !excludedGroups.includes(group.id()!))
|
||||
.map((group) =>
|
||||
Button.component(
|
||||
{
|
||||
icon: groupIds.includes(group.id()!) ? 'fas fa-check' : true,
|
||||
onclick: (e: MouseEvent) => {
|
||||
if (e.shiftKey) e.stopPropagation();
|
||||
this.toggle(group.id()!);
|
||||
},
|
||||
disabled: this.isGroupDisabled(group.id()!) && this.isGroupDisabled(Group.MEMBER_ID) && this.isGroupDisabled(Group.GUEST_ID),
|
||||
},
|
||||
[badgeForId(group.id()!), ' ', group.namePlural()]
|
||||
)
|
||||
);
|
||||
.map((group) => (
|
||||
<Button
|
||||
icon={groupIds.includes(group.id()!) ? 'fas fa-check' : true}
|
||||
onclick={(e: MouseEvent) => {
|
||||
if (e.shiftKey) e.stopPropagation();
|
||||
this.toggle(group.id()!);
|
||||
}}
|
||||
disabled={this.isGroupDisabled(group.id()!) && this.isGroupDisabled(Group.MEMBER_ID) && this.isGroupDisabled(Group.GUEST_ID)}
|
||||
>
|
||||
{badgeForId(group.id()!)} {group.namePlural()}
|
||||
</Button>
|
||||
));
|
||||
|
||||
children.push(...groupButtons);
|
||||
}
|
||||
|
@ -56,9 +56,9 @@ export default class PermissionGrid<CustomAttrs extends IPermissionGridAttrs = I
|
||||
{scopes.map((scope) => (
|
||||
<th>
|
||||
{scope.label}{' '}
|
||||
{scope.onremove
|
||||
? Button.component({ icon: 'fas fa-times', className: 'Button Button--text PermissionGrid-removeScope', onclick: scope.onremove })
|
||||
: ''}
|
||||
{!!scope.onremove && (
|
||||
<Button icon="fas fa-times" className="Button Button--text PermissionGrid-removeScope" onclick={scope.onremove} />
|
||||
)}
|
||||
</th>
|
||||
))}
|
||||
<th>{this.scopeControlItems().toArray()}</th>
|
||||
@ -174,15 +174,16 @@ export default class PermissionGrid<CustomAttrs extends IPermissionGridAttrs = I
|
||||
{
|
||||
icon: 'fas fa-user-plus',
|
||||
label: app.translator.trans('core.admin.permissions.sign_up_label'),
|
||||
setting: () =>
|
||||
SettingDropdown.component({
|
||||
key: 'allow_sign_up',
|
||||
options: [
|
||||
setting: () => (
|
||||
<SettingDropdown
|
||||
key="allow_sign_up"
|
||||
options={[
|
||||
{ value: '1', label: app.translator.trans('core.admin.permissions_controls.signup_open_button') },
|
||||
{ value: '0', label: app.translator.trans('core.admin.permissions_controls.signup_closed_button') },
|
||||
],
|
||||
lazyDraw: true,
|
||||
}),
|
||||
]}
|
||||
lazyDraw
|
||||
/>
|
||||
),
|
||||
},
|
||||
90
|
||||
);
|
||||
@ -219,18 +220,22 @@ export default class PermissionGrid<CustomAttrs extends IPermissionGridAttrs = I
|
||||
setting: () => {
|
||||
const minutes = parseInt(app.data.settings.allow_renaming, 10);
|
||||
|
||||
return SettingDropdown.component({
|
||||
defaultLabel: minutes
|
||||
? app.translator.trans('core.admin.permissions_controls.allow_some_minutes_button', { count: minutes })
|
||||
: app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button'),
|
||||
key: 'allow_renaming',
|
||||
options: [
|
||||
{ value: '-1', label: app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button') },
|
||||
{ value: '10', label: app.translator.trans('core.admin.permissions_controls.allow_ten_minutes_button') },
|
||||
{ value: 'reply', label: app.translator.trans('core.admin.permissions_controls.allow_until_reply_button') },
|
||||
],
|
||||
lazyDraw: true,
|
||||
});
|
||||
return (
|
||||
<SettingDropdown
|
||||
defaultLabel={
|
||||
minutes
|
||||
? app.translator.trans('core.admin.permissions_controls.allow_some_minutes_button', { count: minutes })
|
||||
: app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button')
|
||||
}
|
||||
key="allow_renaming"
|
||||
options={[
|
||||
{ value: '-1', label: app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button') },
|
||||
{ value: '10', label: app.translator.trans('core.admin.permissions_controls.allow_ten_minutes_button') },
|
||||
{ value: 'reply', label: app.translator.trans('core.admin.permissions_controls.allow_until_reply_button') },
|
||||
]}
|
||||
lazyDraw
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
90
|
||||
@ -272,17 +277,21 @@ export default class PermissionGrid<CustomAttrs extends IPermissionGridAttrs = I
|
||||
setting: () => {
|
||||
const minutes = parseInt(app.data.settings.allow_post_editing, 10);
|
||||
|
||||
return SettingDropdown.component({
|
||||
defaultLabel: minutes
|
||||
? app.translator.trans('core.admin.permissions_controls.allow_some_minutes_button', { count: minutes })
|
||||
: app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button'),
|
||||
key: 'allow_post_editing',
|
||||
options: [
|
||||
{ value: '-1', label: app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button') },
|
||||
{ value: '10', label: app.translator.trans('core.admin.permissions_controls.allow_ten_minutes_button') },
|
||||
{ value: 'reply', label: app.translator.trans('core.admin.permissions_controls.allow_until_reply_button') },
|
||||
],
|
||||
});
|
||||
return (
|
||||
<SettingDropdown
|
||||
defaultLabel={
|
||||
minutes
|
||||
? app.translator.trans('core.admin.permissions_controls.allow_some_minutes_button', { count: minutes })
|
||||
: app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button')
|
||||
}
|
||||
key="allow_post_editing"
|
||||
options={[
|
||||
{ value: '-1', label: app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button') },
|
||||
{ value: '10', label: app.translator.trans('core.admin.permissions_controls.allow_ten_minutes_button') },
|
||||
{ value: 'reply', label: app.translator.trans('core.admin.permissions_controls.allow_until_reply_button') },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
90
|
||||
@ -457,10 +466,7 @@ export default class PermissionGrid<CustomAttrs extends IPermissionGridAttrs = I
|
||||
if ('setting' in item) {
|
||||
return item.setting();
|
||||
} else if ('permission' in item) {
|
||||
return PermissionDropdown.component({
|
||||
permission: item.permission,
|
||||
allowGuest: item.allowGuest,
|
||||
});
|
||||
return <PermissionDropdown permission={item.permission} allowGuest={item.allowGuest} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -17,28 +17,28 @@ export default class PermissionsPage extends AdminPage {
|
||||
}
|
||||
|
||||
content() {
|
||||
return [
|
||||
<div className="PermissionsPage-groups">
|
||||
{app.store
|
||||
.all<Group>('groups')
|
||||
.filter((group) => [Group.GUEST_ID, Group.MEMBER_ID].indexOf(group.id()!) === -1)
|
||||
.map((group) => (
|
||||
<button className="Button Group" onclick={() => app.modal.show(EditGroupModal, { group })}>
|
||||
{GroupBadge.component({
|
||||
group,
|
||||
className: 'Group-icon',
|
||||
label: null,
|
||||
})}
|
||||
<span className="Group-name">{group.namePlural()}</span>
|
||||
</button>
|
||||
))}
|
||||
<button className="Button Group Group--add" onclick={() => app.modal.show(EditGroupModal)}>
|
||||
{icon('fas fa-plus', { className: 'Group-icon' })}
|
||||
<span className="Group-name">{app.translator.trans('core.admin.permissions.new_group_button')}</span>
|
||||
</button>
|
||||
</div>,
|
||||
return (
|
||||
<>
|
||||
<div className="PermissionsPage-groups">
|
||||
{app.store
|
||||
.all<Group>('groups')
|
||||
.filter((group) => [Group.GUEST_ID, Group.MEMBER_ID].indexOf(group.id()!) === -1)
|
||||
.map((group) => (
|
||||
<button className="Button Group" onclick={() => app.modal.show(EditGroupModal, { group })}>
|
||||
<GroupBadge group={group} className="Group-icon" label={null} />
|
||||
<span className="Group-name">{group.namePlural()}</span>
|
||||
</button>
|
||||
))}
|
||||
<button className="Button Group Group--add" onclick={() => app.modal.show(EditGroupModal)}>
|
||||
{icon('fas fa-plus', { className: 'Group-icon' })}
|
||||
<span className="Group-name">{app.translator.trans('core.admin.permissions.new_group_button')}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="PermissionsPage-permissions">{PermissionGrid.component()}</div>,
|
||||
];
|
||||
<div className="PermissionsPage-permissions">
|
||||
<PermissionGrid />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -39,14 +39,20 @@ export default class ReadmeModal<CustomAttrs extends IReadmeModalAttrs = IReadme
|
||||
}
|
||||
|
||||
content() {
|
||||
const text = app.translator.trans('core.admin.extension.readme.no_readme');
|
||||
|
||||
return (
|
||||
<div className="Modal-body">
|
||||
{this.loading ? (
|
||||
<div className="ReadmeModal-loading">{LoadingIndicator.component()}</div>
|
||||
<div className="ReadmeModal-loading">
|
||||
<LoadingIndicator />
|
||||
</div>
|
||||
) : (
|
||||
<div>{this.readme.content() ? m.trust(this.readme.content()) : Placeholder.component({ text })}</div>
|
||||
<div>
|
||||
{this.readme.content() ? (
|
||||
m.trust(this.readme.content())
|
||||
) : (
|
||||
<Placeholder text={app.translator.trans('core.admin.extension.readme.no_readme')} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
@ -39,13 +39,9 @@ export default class SessionDropdown<CustomAttrs extends ISessionDropdownAttrs =
|
||||
|
||||
items.add(
|
||||
'logOut',
|
||||
Button.component(
|
||||
{
|
||||
icon: 'fas fa-sign-out-alt',
|
||||
onclick: app.session.logout.bind(app.session),
|
||||
},
|
||||
app.translator.trans('core.admin.header.log_out_button')
|
||||
),
|
||||
<Button icon="fas fa-sign-out-alt" onclick={app.session.logout.bind(app.session)}>
|
||||
{app.translator.trans('core.admin.header.log_out_button')}
|
||||
</Button>,
|
||||
-100
|
||||
);
|
||||
|
||||
|
@ -35,13 +35,10 @@ export default class SettingDropdown<CustomAttrs extends ISettingDropdownAttrs =
|
||||
children: this.attrs.options.map(({ value, label }) => {
|
||||
const active = app.data.settings[this.attrs.setting!] === value;
|
||||
|
||||
return Button.component(
|
||||
{
|
||||
icon: active ? 'fas fa-check' : true,
|
||||
onclick: saveSettings.bind(this, { [this.attrs.setting!]: value }),
|
||||
active,
|
||||
},
|
||||
label
|
||||
return (
|
||||
<Button icon={active ? 'fas fa-check' : true} onclick={saveSettings.bind(this, { [this.attrs.setting!]: value })} active={active}>
|
||||
{label}
|
||||
</Button>
|
||||
);
|
||||
}),
|
||||
});
|
||||
|
@ -13,7 +13,7 @@ export default abstract class SettingsModal<CustomAttrs extends ISettingsModalAt
|
||||
loading: boolean = false;
|
||||
|
||||
form(): Mithril.Children {
|
||||
return '';
|
||||
return null;
|
||||
}
|
||||
|
||||
content() {
|
||||
|
@ -1,12 +1,13 @@
|
||||
import app from '../../admin/app';
|
||||
import Button from '../../common/components/Button';
|
||||
import classList from '../../common/utils/classList';
|
||||
|
||||
export default class UploadImageButton extends Button {
|
||||
loading = false;
|
||||
|
||||
view(vnode) {
|
||||
this.attrs.loading = this.loading;
|
||||
this.attrs.className = (this.attrs.className || '') + ' Button';
|
||||
this.attrs.className = classList(this.attrs.className, 'Button');
|
||||
|
||||
if (app.data.settings[this.attrs.name + '_path']) {
|
||||
this.attrs.onclick = this.remove.bind(this);
|
||||
@ -37,7 +38,7 @@ export default class UploadImageButton extends Button {
|
||||
$input
|
||||
.appendTo('body')
|
||||
.hide()
|
||||
.click()
|
||||
.trigger('click')
|
||||
.on('change', (e) => {
|
||||
const body = new FormData();
|
||||
body.append(this.attrs.name, $(e.target)[0].files[0]);
|
||||
|
@ -107,7 +107,7 @@ export default class UserListPage extends AdminPage {
|
||||
this.loadPage(this.pageNumber);
|
||||
|
||||
return [
|
||||
<section class="UserListPage-grid UserListPage-grid--loading">
|
||||
<section className="UserListPage-grid UserListPage-grid--loading">
|
||||
<LoadingIndicator containerClassName="LoadingIndicator--block" size="large" />
|
||||
</section>,
|
||||
];
|
||||
@ -128,9 +128,9 @@ export default class UserListPage extends AdminPage {
|
||||
}}
|
||||
/>
|
||||
</div>,
|
||||
<p class="UserListPage-totalUsers">{app.translator.trans('core.admin.users.total_users', { count: this.userCount })}</p>,
|
||||
<p className="UserListPage-totalUsers">{app.translator.trans('core.admin.users.total_users', { count: this.userCount })}</p>,
|
||||
<section
|
||||
class={classList(['UserListPage-grid', this.isLoadingPage ? 'UserListPage-grid--loadingPage' : 'UserListPage-grid--loaded'])}
|
||||
className={classList(['UserListPage-grid', this.isLoadingPage ? 'UserListPage-grid--loadingPage' : 'UserListPage-grid--loaded'])}
|
||||
style={{ '--columns': columns.length }}
|
||||
role="table"
|
||||
// +1 to account for header
|
||||
@ -141,7 +141,7 @@ export default class UserListPage extends AdminPage {
|
||||
>
|
||||
{/* Render columns */}
|
||||
{columns.map((column, colIndex) => (
|
||||
<div class="UserListPage-grid-header" role="columnheader" aria-colindex={colIndex + 1} aria-rowindex={1}>
|
||||
<div className="UserListPage-grid-header" role="columnheader" aria-colindex={colIndex + 1} aria-rowindex={1}>
|
||||
{column.name}
|
||||
</div>
|
||||
))}
|
||||
@ -153,7 +153,7 @@ export default class UserListPage extends AdminPage {
|
||||
|
||||
return (
|
||||
<div
|
||||
class={classList(['UserListPage-grid-rowItem', rowIndex % 2 > 0 && 'UserListPage-grid-rowItem--shaded'])}
|
||||
className={classList(['UserListPage-grid-rowItem', rowIndex % 2 > 0 && 'UserListPage-grid-rowItem--shaded'])}
|
||||
data-user-id={user.id()}
|
||||
data-column-name={col.itemName}
|
||||
aria-colindex={colIndex + 1}
|
||||
@ -170,7 +170,7 @@ export default class UserListPage extends AdminPage {
|
||||
{/* Loading spinner that shows when a new page is being loaded */}
|
||||
{this.isLoadingPage && <LoadingIndicator size="large" />}
|
||||
</section>,
|
||||
<nav class="UserListPage-gridPagination">
|
||||
<nav className="UserListPage-gridPagination">
|
||||
<Button
|
||||
disabled={this.pageNumber === 0}
|
||||
title={app.translator.trans('core.admin.users.pagination.first_page_button')}
|
||||
@ -185,7 +185,7 @@ export default class UserListPage extends AdminPage {
|
||||
icon="fas fa-chevron-left"
|
||||
className="Button Button--icon UserListPage-backBtn"
|
||||
/>
|
||||
<span class="UserListPage-pageNumber">
|
||||
<span className="UserListPage-pageNumber">
|
||||
{app.translator.trans('core.admin.users.pagination.page_counter', {
|
||||
// https://technology.blog.gov.uk/2020/02/24/why-the-gov-uk-design-system-team-changed-the-input-type-for-numbers/
|
||||
current: (
|
||||
@ -260,7 +260,7 @@ export default class UserListPage extends AdminPage {
|
||||
'id',
|
||||
{
|
||||
name: app.translator.trans('core.admin.users.grid.columns.user_id.title'),
|
||||
content: (user: User) => user.id() ?? '',
|
||||
content: (user: User) => user.id() ?? null,
|
||||
},
|
||||
100
|
||||
);
|
||||
@ -300,7 +300,7 @@ export default class UserListPage extends AdminPage {
|
||||
{
|
||||
name: app.translator.trans('core.admin.users.grid.columns.join_time.title'),
|
||||
content: (user: User) => (
|
||||
<span class="UserList-joinDate" title={user.joinTime()}>
|
||||
<span className="UserList-joinDate" title={user.joinTime()}>
|
||||
{dayjs(user.joinTime()).format('LLL')}
|
||||
</span>
|
||||
),
|
||||
@ -372,13 +372,13 @@ export default class UserListPage extends AdminPage {
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="UserList-email" key={user.id()} data-email-shown="false">
|
||||
<span class="UserList-emailAddress" aria-hidden="true" onclick={() => setEmailVisibility(true)}>
|
||||
<div className="UserList-email" key={user.id()} data-email-shown="false">
|
||||
<span className="UserList-emailAddress" aria-hidden="true" onclick={() => setEmailVisibility(true)}>
|
||||
{user.email()}
|
||||
</span>
|
||||
<button
|
||||
onclick={toggleEmailVisibility}
|
||||
class="Button Button--text UserList-emailIconBtn"
|
||||
className="Button Button--text UserList-emailIconBtn"
|
||||
title={app.translator.trans('core.admin.users.grid.columns.email.visibility_show')}
|
||||
>
|
||||
{icon('far fa-eye-slash fa-fw', { className: 'icon' })}
|
||||
|
@ -50,7 +50,7 @@ export default class Alert<T extends AlertAttrs = AlertAttrs> extends Component<
|
||||
<Button
|
||||
aria-label={app.translator.trans('core.lib.alert.dismiss_a11y_label')}
|
||||
icon="fas fa-times"
|
||||
class="Button Button--link Button--icon Alert-dismiss"
|
||||
className="Button Button--link Button--icon Alert-dismiss"
|
||||
onclick={ondismiss}
|
||||
/>
|
||||
);
|
||||
@ -59,13 +59,13 @@ export default class Alert<T extends AlertAttrs = AlertAttrs> extends Component<
|
||||
return (
|
||||
<div {...attrs}>
|
||||
{!!title && (
|
||||
<div class="Alert-title">
|
||||
{!!icon && <span class="Alert-title-icon">{iconHelper(icon)}</span>}
|
||||
<span class="Alert-title-text">{title}</span>
|
||||
<div className="Alert-title">
|
||||
{!!icon && <span className="Alert-title-icon">{iconHelper(icon)}</span>}
|
||||
<span className="Alert-title-text">{title}</span>
|
||||
</div>
|
||||
)}
|
||||
<span class="Alert-body">{content}</span>
|
||||
<ul class="Alert-controls">{listItems(controls.concat(dismissControl))}</ul>
|
||||
<span className="Alert-body">{content}</span>
|
||||
<ul className="Alert-controls">{listItems(controls.concat(dismissControl))}</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ export default class AlertManager<CustomAttrs extends IAlertManagerAttrs = IAler
|
||||
const activeAlerts = this.state.getActiveAlerts();
|
||||
|
||||
return (
|
||||
<div class="AlertManager">
|
||||
<div className="AlertManager">
|
||||
{Object.keys(activeAlerts)
|
||||
.map(Number)
|
||||
.map((key) => {
|
||||
@ -29,7 +29,7 @@ export default class AlertManager<CustomAttrs extends IAlertManagerAttrs = IAler
|
||||
const urgent = alert.attrs.type === 'error';
|
||||
|
||||
return (
|
||||
<div class="AlertManager-alert" role="alert" aria-live={urgent ? 'assertive' : 'polite'}>
|
||||
<div className="AlertManager-alert" role="alert" aria-live={urgent ? 'assertive' : 'polite'}>
|
||||
<alert.componentClass {...alert.attrs} ondismiss={this.state.dismiss.bind(this.state, key)}>
|
||||
{alert.children}
|
||||
</alert.componentClass>
|
||||
|
@ -89,19 +89,12 @@ export default class EditUserModal<CustomAttrs extends IEditUserModalAttrs = IEd
|
||||
disabled={this.nonAdminEditingAdmin()}
|
||||
/>
|
||||
</div>
|
||||
{!this.isEmailConfirmed() && this.userIsAdmin(app.session.user) ? (
|
||||
{!this.isEmailConfirmed() && this.userIsAdmin(app.session.user) && (
|
||||
<div>
|
||||
{Button.component(
|
||||
{
|
||||
className: 'Button Button--block',
|
||||
loading: this.loading,
|
||||
onclick: this.activate.bind(this),
|
||||
},
|
||||
app.translator.trans('core.lib.edit_user.activate_button')
|
||||
)}
|
||||
<Button className="Button Button--block" loading={this.loading} onclick={this.activate.bind(this)}>
|
||||
{app.translator.trans('core.lib.edit_user.activate_button')}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>,
|
||||
30
|
||||
@ -126,7 +119,7 @@ export default class EditUserModal<CustomAttrs extends IEditUserModalAttrs = IEd
|
||||
/>
|
||||
{app.translator.trans('core.lib.edit_user.set_password_label')}
|
||||
</label>
|
||||
{this.setPassword() ? (
|
||||
{this.setPassword() && (
|
||||
<input
|
||||
className="FormControl"
|
||||
type="password"
|
||||
@ -135,8 +128,6 @@ export default class EditUserModal<CustomAttrs extends IEditUserModalAttrs = IEd
|
||||
bidi={this.password}
|
||||
disabled={this.nonAdminEditingAdmin()}
|
||||
/>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
</div>,
|
||||
@ -166,7 +157,7 @@ export default class EditUserModal<CustomAttrs extends IEditUserModalAttrs = IEd
|
||||
group.id() === Group.ADMINISTRATOR_ID && (this.attrs.user === app.session.user || !this.userIsAdmin(app.session.user))
|
||||
}
|
||||
/>
|
||||
{GroupBadge.component({ group, label: '' })} {group.nameSingular()}
|
||||
<GroupBadge group={group} label={null} /> {group.nameSingular()}
|
||||
</label>
|
||||
)
|
||||
)}
|
||||
@ -179,14 +170,9 @@ export default class EditUserModal<CustomAttrs extends IEditUserModalAttrs = IEd
|
||||
items.add(
|
||||
'submit',
|
||||
<div className="Form-group">
|
||||
{Button.component(
|
||||
{
|
||||
className: 'Button Button--primary',
|
||||
type: 'submit',
|
||||
loading: this.loading,
|
||||
},
|
||||
app.translator.trans('core.lib.edit_user.submit_button')
|
||||
)}
|
||||
<Button className="Button Button--primary" type="submit" loading={this.loading}>
|
||||
{app.translator.trans('core.lib.edit_user.submit_button')}
|
||||
</Button>
|
||||
</div>,
|
||||
-10
|
||||
);
|
||||
@ -255,14 +241,14 @@ export default class EditUserModal<CustomAttrs extends IEditUserModalAttrs = IEd
|
||||
});
|
||||
}
|
||||
|
||||
nonAdminEditingAdmin() {
|
||||
nonAdminEditingAdmin(): boolean {
|
||||
return this.userIsAdmin(this.attrs.user) && !this.userIsAdmin(app.session.user);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected userIsAdmin(user: User | null) {
|
||||
return user && (user.groups() || []).some((g) => g?.id() === Group.ADMINISTRATOR_ID);
|
||||
protected userIsAdmin(user: User | null): boolean {
|
||||
return !!(user?.groups() || []).some((g) => g?.id() === Group.ADMINISTRATOR_ID);
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ export default class Link extends Component {
|
||||
view(vnode) {
|
||||
let { options = {}, ...attrs } = vnode.attrs;
|
||||
|
||||
attrs.href = attrs.href || '';
|
||||
attrs.href ||= '';
|
||||
|
||||
// For some reason, m.route.Link does not like vnode.text, so if present, we
|
||||
// need to convert it to text vnodes and store it in children.
|
||||
|
@ -155,7 +155,11 @@ export default abstract class Modal<ModalAttrs extends IInternalModalAttrs = IIn
|
||||
<h3 className="App-titleControl App-titleControl--text">{this.title()}</h3>
|
||||
</div>
|
||||
|
||||
{this.alertAttrs ? <div className="Modal-alert">{Alert.component(this.alertAttrs)}</div> : ''}
|
||||
{!!this.alertAttrs && (
|
||||
<div className="Modal-alert">
|
||||
<Alert {...this.alertAttrs} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{this.content()}
|
||||
</form>
|
||||
|
@ -37,7 +37,7 @@ export default class ModalManager extends Component<IModalManagerAttrs> {
|
||||
return (
|
||||
<div
|
||||
key={modal.key}
|
||||
class="ModalManager modal"
|
||||
className="ModalManager modal"
|
||||
data-modal-key={modal.key}
|
||||
data-modal-number={i}
|
||||
role="dialog"
|
||||
@ -62,7 +62,7 @@ export default class ModalManager extends Component<IModalManagerAttrs> {
|
||||
|
||||
{this.attrs.state.backdropShown && (
|
||||
<div
|
||||
class="Modal-backdrop backdrop"
|
||||
className="Modal-backdrop backdrop"
|
||||
ontransitionend={this.onBackdropTransitionEnd.bind(this)}
|
||||
data-showing={!!this.attrs.state.modalList.length}
|
||||
style={{ '--modal-count': this.attrs.state.modalList.length }}
|
||||
|
@ -3,6 +3,7 @@ import Component from '../Component';
|
||||
import Button from './Button';
|
||||
import LinkButton from './LinkButton';
|
||||
import type Mithril from 'mithril';
|
||||
import classList from '../utils/classList';
|
||||
|
||||
/**
|
||||
* The `Navigation` component displays a set of navigation buttons. Typically
|
||||
@ -25,7 +26,7 @@ export default class Navigation extends Component {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={'Navigation ButtonGroup ' + (this.attrs.className || '')}
|
||||
className={classList('Navigation ButtonGroup', this.attrs.className)}
|
||||
onmouseenter={pane && pane.show.bind(pane)}
|
||||
onmouseleave={pane && pane.onmouseleave.bind(pane)}
|
||||
>
|
||||
@ -41,17 +42,19 @@ export default class Navigation extends Component {
|
||||
const { history } = app;
|
||||
const previous = history?.getPrevious();
|
||||
|
||||
return LinkButton.component({
|
||||
className: 'Button Navigation-back Button--icon',
|
||||
href: history?.backUrl(),
|
||||
icon: 'fas fa-chevron-left',
|
||||
'aria-label': previous?.title,
|
||||
onclick: (e: MouseEvent) => {
|
||||
if (e.shiftKey || e.ctrlKey || e.metaKey || e.button === 1) return;
|
||||
e.preventDefault();
|
||||
history?.back();
|
||||
},
|
||||
});
|
||||
return (
|
||||
<LinkButton
|
||||
className="Button Navigation-back Button--icon"
|
||||
href={history?.backUrl()}
|
||||
icon="fas fa-chevron-left"
|
||||
aria-label={previous?.title}
|
||||
onclick={(e: MouseEvent) => {
|
||||
if (e.shiftKey || e.ctrlKey || e.metaKey || e.which === 2) return;
|
||||
e.preventDefault();
|
||||
history?.back();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -60,32 +63,36 @@ export default class Navigation extends Component {
|
||||
protected getPaneButton(): Mithril.Children {
|
||||
const { pane } = app;
|
||||
|
||||
if (!pane || !pane.active) return '';
|
||||
if (!pane || !pane.active) return null;
|
||||
|
||||
return Button.component({
|
||||
className: 'Button Button--icon Navigation-pin' + (pane.pinned ? ' active' : ''),
|
||||
onclick: pane.togglePinned.bind(pane),
|
||||
icon: 'fas fa-thumbtack',
|
||||
});
|
||||
return (
|
||||
<Button
|
||||
className={classList('Button Button--icon Navigation-pin', { active: pane.pinned })}
|
||||
onclick={pane.togglePinned.bind(pane)}
|
||||
icon="fas fa-thumbtack"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the drawer toggle button.
|
||||
*/
|
||||
protected getDrawerButton(): Mithril.Children {
|
||||
if (!this.attrs.drawer) return '';
|
||||
if (!this.attrs.drawer) return null;
|
||||
|
||||
const { drawer } = app;
|
||||
const user = app.session.user;
|
||||
|
||||
return Button.component({
|
||||
className: 'Button Button--icon Navigation-drawer' + (user && user.newNotificationCount() ? ' new' : ''),
|
||||
onclick: (e: MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
drawer.show();
|
||||
},
|
||||
icon: 'fas fa-bars',
|
||||
'aria-label': app.translator.trans('core.lib.nav.drawer_button'),
|
||||
});
|
||||
return (
|
||||
<Button
|
||||
className={classList('Button Button--icon Navigation-drawer', { new: user?.newNotificationCount() })}
|
||||
onclick={(e: MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
drawer.show();
|
||||
}}
|
||||
icon="fas fa-bars"
|
||||
aria-label={app.translator.trans('core.lib.nav.drawer_button')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ export default class RequestErrorModal<CustomAttrs extends IRequestErrorModalAtt
|
||||
}
|
||||
|
||||
title() {
|
||||
return this.attrs.error.xhr ? `${this.attrs.error.xhr.status} ${this.attrs.error.xhr.statusText}` : '';
|
||||
return !!this.attrs.error.xhr && `${this.attrs.error.xhr.status} ${this.attrs.error.xhr.statusText}`;
|
||||
}
|
||||
|
||||
content() {
|
||||
|
@ -28,7 +28,7 @@ export default class SplitDropdown extends Dropdown {
|
||||
|
||||
return (
|
||||
<>
|
||||
{Button.component(buttonAttrs, firstChild.children)}
|
||||
<Button {...buttonAttrs}>{firstChild.children}</Button>
|
||||
<button
|
||||
className={'Dropdown-toggle Button Button--icon ' + this.attrs.buttonClassName}
|
||||
aria-haspopup="menu"
|
||||
|
@ -1,3 +1,4 @@
|
||||
import classList from '../utils/classList';
|
||||
import Checkbox, { ICheckboxAttrs } from './Checkbox';
|
||||
|
||||
/**
|
||||
@ -8,10 +9,10 @@ export default class Switch extends Checkbox {
|
||||
static initAttrs(attrs: ICheckboxAttrs) {
|
||||
super.initAttrs(attrs);
|
||||
|
||||
attrs.className = (attrs.className || '') + ' Checkbox--switch';
|
||||
attrs.className = classList(attrs.className, 'Checkbox--switch');
|
||||
}
|
||||
|
||||
getDisplay() {
|
||||
return this.attrs.loading ? super.getDisplay() : '';
|
||||
return !!this.attrs.loading && super.getDisplay();
|
||||
}
|
||||
}
|
||||
|
@ -97,15 +97,9 @@ export default class TextEditor extends Component {
|
||||
|
||||
items.add(
|
||||
'submit',
|
||||
Button.component(
|
||||
{
|
||||
icon: 'fas fa-paper-plane',
|
||||
className: 'Button Button--primary',
|
||||
itemClassName: 'App-primaryControl',
|
||||
onclick: this.onsubmit.bind(this),
|
||||
},
|
||||
this.attrs.submitLabel
|
||||
)
|
||||
<Button icon="fas fa-paper-plane" className="Button Button--primary" itemClassName="App-primaryControl" onclick={this.onsubmit.bind(this)}>
|
||||
{this.attrs.submitLabel}
|
||||
</Button>
|
||||
);
|
||||
|
||||
if (this.attrs.preview) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import type Mithril from 'mithril';
|
||||
import type { ComponentAttrs } from '../Component';
|
||||
import User from '../models/User';
|
||||
import classList from '../utils/classList';
|
||||
|
||||
export interface AvatarAttrs extends ComponentAttrs {}
|
||||
|
||||
@ -11,7 +12,7 @@ export interface AvatarAttrs extends ComponentAttrs {}
|
||||
* @param attrs Attributes to apply to the avatar element
|
||||
*/
|
||||
export default function avatar(user: User | null, attrs: ComponentAttrs = {}): Mithril.Vnode {
|
||||
attrs.className = 'Avatar ' + (attrs.className || '');
|
||||
attrs.className = classList('Avatar', attrs.className);
|
||||
attrs.loading ??= 'lazy';
|
||||
let content: string = '';
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import type Mithril from 'mithril';
|
||||
import classList from '../utils/classList';
|
||||
|
||||
/**
|
||||
* The `icon` helper displays an icon.
|
||||
@ -7,7 +8,7 @@ import type Mithril from 'mithril';
|
||||
* @param attrs Any other attributes to apply.
|
||||
*/
|
||||
export default function icon(fontClass: string, attrs: Mithril.Attributes = {}): Mithril.Vnode {
|
||||
attrs.className = 'icon ' + fontClass + ' ' + (attrs.className || '');
|
||||
attrs.className = classList('icon', fontClass, attrs.className);
|
||||
|
||||
return <i aria-hidden="true" {...attrs} />;
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import type Mithril from 'mithril';
|
||||
import User from '../models/User';
|
||||
|
||||
/**
|
||||
* The `username` helper displays a user's username in a <span class="username">
|
||||
* The `username` helper displays a user's username in a <span className="username">
|
||||
* tag. If the user doesn't exist, the username will be displayed as [deleted].
|
||||
*/
|
||||
export default function username(user: User | null | undefined | false): Mithril.Vnode {
|
||||
|
@ -161,7 +161,7 @@ export default class User extends Model {
|
||||
savePreferences(newPreferences: Record<string, unknown>): Promise<this> {
|
||||
const preferences = this.preferences();
|
||||
|
||||
Object.assign(preferences || {}, newPreferences);
|
||||
Object.assign(preferences ?? {}, newPreferences);
|
||||
|
||||
return this.save({ preferences });
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import app from '../forum/app';
|
||||
import app from './app';
|
||||
|
||||
import History from './utils/History';
|
||||
import Pane from './utils/Pane';
|
||||
@ -115,11 +115,11 @@ export default class ForumApplication extends Application {
|
||||
|
||||
// We mount navigation and header components after the page, so components
|
||||
// like the back button can access the updated state when rendering.
|
||||
m.mount(document.getElementById('app-navigation')!, { view: () => Navigation.component({ className: 'App-backControl', drawer: true }) });
|
||||
m.mount(document.getElementById('app-navigation')!, { view: () => <Navigation className="App-backControl" drawer /> });
|
||||
m.mount(document.getElementById('header-navigation')!, Navigation);
|
||||
m.mount(document.getElementById('header-primary')!, HeaderPrimary);
|
||||
m.mount(document.getElementById('header-secondary')!, HeaderSecondary);
|
||||
m.mount(document.getElementById('composer')!, { view: () => Composer.component({ state: this.composer }) });
|
||||
m.mount(document.getElementById('composer')!, { view: () => <Composer state={this.composer} /> });
|
||||
|
||||
alertEmailConfirmation(this);
|
||||
|
@ -95,17 +95,13 @@ export default class CommentPost extends Post {
|
||||
const post = this.attrs.post;
|
||||
const attrs = super.elementAttrs();
|
||||
|
||||
attrs.className =
|
||||
(attrs.className || '') +
|
||||
' ' +
|
||||
classList({
|
||||
CommentPost: true,
|
||||
'Post--renderFailed': post.renderFailed(),
|
||||
'Post--hidden': post.isHidden(),
|
||||
'Post--edited': post.isEdited(),
|
||||
revealContent: this.revealContent,
|
||||
editing: this.isEditing(),
|
||||
});
|
||||
attrs.className = classList(attrs.className, 'CommentPost', {
|
||||
'Post--renderFailed': post.renderFailed(),
|
||||
'Post--hidden': post.isHidden(),
|
||||
'Post--edited': post.isEdited(),
|
||||
revealContent: this.revealContent,
|
||||
editing: this.isEditing(),
|
||||
});
|
||||
|
||||
if (this.isEditing()) attrs['aria-busy'] = 'true';
|
||||
|
||||
@ -130,24 +126,24 @@ export default class CommentPost extends Post {
|
||||
|
||||
items.add(
|
||||
'user',
|
||||
PostUser.component({
|
||||
post,
|
||||
cardVisible: this.cardVisible,
|
||||
oncardshow: () => {
|
||||
<PostUser
|
||||
post={post}
|
||||
cardVisible={this.cardVisible}
|
||||
oncardshow={() => {
|
||||
this.cardVisible = true;
|
||||
m.redraw();
|
||||
},
|
||||
oncardhide: () => {
|
||||
}}
|
||||
oncardhide={() => {
|
||||
this.cardVisible = false;
|
||||
m.redraw();
|
||||
},
|
||||
}),
|
||||
}}
|
||||
/>,
|
||||
100
|
||||
);
|
||||
items.add('meta', PostMeta.component({ post }));
|
||||
items.add('meta', <PostMeta post={post} />);
|
||||
|
||||
if (post.isEdited() && !post.isHidden()) {
|
||||
items.add('edited', PostEdited.component({ post }));
|
||||
items.add('edited', <PostEdited post={post} />);
|
||||
}
|
||||
|
||||
// If the post is hidden, add a button that allows toggling the visibility
|
||||
@ -155,11 +151,7 @@ export default class CommentPost extends Post {
|
||||
if (post.isHidden()) {
|
||||
items.add(
|
||||
'toggle',
|
||||
Button.component({
|
||||
className: 'Button Button--default Button--more',
|
||||
icon: 'fas fa-ellipsis-h',
|
||||
onclick: this.toggleContent.bind(this),
|
||||
})
|
||||
<Button className="Button Button--default Button--more" icon="fas fa-ellipsis-h" onclick={this.toggleContent.bind(this)} />
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -46,12 +46,14 @@ export default class Composer extends Component {
|
||||
// Set up a handler so that clicks on the content will show the composer.
|
||||
const showIfMinimized = this.state.position === ComposerState.Position.MINIMIZED ? this.state.show.bind(this.state) : undefined;
|
||||
|
||||
const ComposerBody = body.componentClass;
|
||||
|
||||
return (
|
||||
<div className={'Composer ' + classList(classes)}>
|
||||
<div className="Composer-handle" oncreate={this.configHandle.bind(this)} />
|
||||
<ul className="Composer-controls">{listItems(this.controlItems().toArray())}</ul>
|
||||
<div className="Composer-content" onclick={showIfMinimized}>
|
||||
{body.componentClass ? body.componentClass.component({ ...body.attrs, composer: this.state, disabled: classes.minimized }) : ''}
|
||||
{ComposerBody && <ComposerBody {...body.attrs} composer={this.state} disabled={classes.minimized} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -325,41 +327,41 @@ export default class Composer extends Component {
|
||||
if (this.state.position === ComposerState.Position.FULLSCREEN) {
|
||||
items.add(
|
||||
'exitFullScreen',
|
||||
ComposerButton.component({
|
||||
icon: 'fas fa-compress',
|
||||
title: app.translator.trans('core.forum.composer.exit_full_screen_tooltip'),
|
||||
onclick: this.state.exitFullScreen.bind(this.state),
|
||||
})
|
||||
<ComposerButton
|
||||
icon="fas fa-compress"
|
||||
title={app.translator.trans('core.forum.composer.exit_full_screen_tooltip')}
|
||||
onclick={this.state.exitFullScreen.bind(this.state)}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
if (this.state.position !== ComposerState.Position.MINIMIZED) {
|
||||
items.add(
|
||||
'minimize',
|
||||
ComposerButton.component({
|
||||
icon: 'fas fa-minus minimize',
|
||||
title: app.translator.trans('core.forum.composer.minimize_tooltip'),
|
||||
onclick: this.state.minimize.bind(this.state),
|
||||
itemClassName: 'App-backControl',
|
||||
})
|
||||
<ComposerButton
|
||||
icon="fas fa-minus minimize"
|
||||
title={app.translator.trans('core.forum.composer.minimize_tooltip')}
|
||||
onclick={this.state.minimize.bind(this.state)}
|
||||
itemClassName="App-backControl"
|
||||
/>
|
||||
);
|
||||
|
||||
items.add(
|
||||
'fullScreen',
|
||||
ComposerButton.component({
|
||||
icon: 'fas fa-expand',
|
||||
title: app.translator.trans('core.forum.composer.full_screen_tooltip'),
|
||||
onclick: this.state.fullScreen.bind(this.state),
|
||||
})
|
||||
<ComposerButton
|
||||
icon="fas fa-expand"
|
||||
title={app.translator.trans('core.forum.composer.full_screen_tooltip')}
|
||||
onclick={this.state.fullScreen.bind(this.state)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
items.add(
|
||||
'close',
|
||||
ComposerButton.component({
|
||||
icon: 'fas fa-times',
|
||||
title: app.translator.trans('core.forum.composer.close_tooltip'),
|
||||
onclick: this.state.close.bind(this.state),
|
||||
})
|
||||
<ComposerButton
|
||||
icon="fas fa-times"
|
||||
title={app.translator.trans('core.forum.composer.close_tooltip')}
|
||||
onclick={this.state.close.bind(this.state)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -50,21 +50,21 @@ export default class ComposerBody extends Component {
|
||||
view() {
|
||||
return (
|
||||
<ConfirmDocumentUnload when={this.hasChanges.bind(this)}>
|
||||
<div className={'ComposerBody ' + (this.attrs.className || '')}>
|
||||
<div className={classList('ComposerBody', this.attrs.className)}>
|
||||
{avatar(this.attrs.user, { className: 'ComposerBody-avatar' })}
|
||||
<div className="ComposerBody-content">
|
||||
<ul className="ComposerBody-header">{listItems(this.headerItems().toArray())}</ul>
|
||||
<div className="ComposerBody-editor">
|
||||
{TextEditor.component({
|
||||
submitLabel: this.attrs.submitLabel,
|
||||
placeholder: this.attrs.placeholder,
|
||||
disabled: this.loading || this.attrs.disabled,
|
||||
composer: this.composer,
|
||||
preview: this.jumpToPreview && this.jumpToPreview.bind(this),
|
||||
onchange: this.composer.fields.content,
|
||||
onsubmit: this.onsubmit.bind(this),
|
||||
value: this.composer.fields.content(),
|
||||
})}
|
||||
<TextEditor
|
||||
submitLabel={this.attrs.submitLabel}
|
||||
placeholder={this.attrs.placeholder}
|
||||
disabled={this.loading || this.attrs.disabled}
|
||||
composer={this.composer}
|
||||
preview={this.jumpToPreview?.bind(this)}
|
||||
onchange={this.composer.fields.content}
|
||||
onsubmit={this.onsubmit.bind(this)}
|
||||
value={this.composer.fields.content()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<LoadingIndicator display="unset" containerClassName={classList('ComposerBody-loading', this.loading && 'active')} size="large" />
|
||||
|
@ -28,24 +28,26 @@ export default class DiscussionList extends Component {
|
||||
if (isLoading) {
|
||||
loading = <LoadingIndicator />;
|
||||
} else if (state.hasNext()) {
|
||||
loading = Button.component(
|
||||
{
|
||||
className: 'Button',
|
||||
onclick: state.loadNext.bind(state),
|
||||
},
|
||||
app.translator.trans('core.forum.discussion_list.load_more_button')
|
||||
loading = (
|
||||
<Button className="Button" onclick={state.loadNext.bind(state)}>
|
||||
{app.translator.trans('core.forum.discussion_list.load_more_button')}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
if (state.isEmpty()) {
|
||||
const text = app.translator.trans('core.forum.discussion_list.empty_text');
|
||||
return <div className="DiscussionList">{Placeholder.component({ text })}</div>;
|
||||
return (
|
||||
<div className="DiscussionList">
|
||||
<Placeholder text={text} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const pageSize = state.pageSize;
|
||||
|
||||
return (
|
||||
<div class={classList('DiscussionList', { 'DiscussionList--searchResults': state.isSearchResults() })}>
|
||||
<div className={classList('DiscussionList', { 'DiscussionList--searchResults': state.isSearchResults() })}>
|
||||
<ul role="feed" aria-busy={isLoading} className="DiscussionList-discussions">
|
||||
{state.getPages().map((pg, pageNum) => {
|
||||
return pg.items.map((discussion, itemNum) => (
|
||||
|
@ -79,17 +79,16 @@ export default class DiscussionListItem<CustomAttrs extends IDiscussionListItemA
|
||||
|
||||
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
|
||||
!!controls.length && (
|
||||
<Dropdown
|
||||
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}
|
||||
</Dropdown>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -99,7 +98,7 @@ export default class DiscussionListItem<CustomAttrs extends IDiscussionListItemA
|
||||
|
||||
return (
|
||||
<span
|
||||
className={'Slidable-underneath Slidable-underneath--left Slidable-underneath--elastic' + (isUnread ? '' : ' disabled')}
|
||||
className={classList('Slidable-underneath Slidable-underneath--left Slidable-underneath--elastic', { disabled: isUnread })}
|
||||
onclick={this.markAsRead.bind(this)}
|
||||
>
|
||||
{icon('fas fa-check')}
|
||||
@ -247,13 +246,7 @@ export default class DiscussionListItem<CustomAttrs extends IDiscussionListItemA
|
||||
items.add('excerpt', excerpt, -100);
|
||||
}
|
||||
} else {
|
||||
items.add(
|
||||
'terminalPost',
|
||||
TerminalPost.component({
|
||||
discussion: this.attrs.discussion,
|
||||
lastPost: !this.showFirstPost(),
|
||||
})
|
||||
);
|
||||
items.add('terminalPost', <TerminalPost discussion={this.attrs.discussion} lastPost={!this.showFirstPost()} />);
|
||||
}
|
||||
|
||||
return items;
|
||||
@ -268,7 +261,7 @@ export default class DiscussionListItem<CustomAttrs extends IDiscussionListItemA
|
||||
<button className="Button--ua-reset DiscussionListItem-count" onclick={this.markAsRead.bind(this)}>
|
||||
<span aria-hidden="true">{abbreviateNumber(discussion.unreadCount())}</span>
|
||||
|
||||
<span class="visually-hidden">
|
||||
<span className="visually-hidden">
|
||||
{app.translator.trans('core.forum.discussion_list.unread_replies_a11y_label', { count: discussion.replyCount() })}
|
||||
</span>
|
||||
</button>
|
||||
@ -279,7 +272,7 @@ export default class DiscussionListItem<CustomAttrs extends IDiscussionListItemA
|
||||
<span className="DiscussionListItem-count">
|
||||
<span aria-hidden="true">{abbreviateNumber(discussion.replyCount())}</span>
|
||||
|
||||
<span class="visually-hidden">
|
||||
<span className="visually-hidden">
|
||||
{app.translator.trans('core.forum.discussion_list.total_replies_a11y_label', { count: discussion.replyCount() })}
|
||||
</span>
|
||||
</span>
|
||||
|
@ -140,11 +140,7 @@ export default class DiscussionPage<CustomAttrs extends IDiscussionPageAttrs = I
|
||||
items.add(
|
||||
'poststream',
|
||||
<div className="DiscussionPage-stream">
|
||||
{PostStream.component({
|
||||
discussion: this.discussion,
|
||||
stream: this.stream,
|
||||
onPositionChange: this.positionChanged.bind(this),
|
||||
})}
|
||||
<PostStream discussion={this.discussion} stream={this.stream} onPositionChange={this.positionChanged.bind(this)} />
|
||||
</div>,
|
||||
10
|
||||
);
|
||||
@ -241,27 +237,19 @@ export default class DiscussionPage<CustomAttrs extends IDiscussionPageAttrs = I
|
||||
if (this.discussion) {
|
||||
items.add(
|
||||
'controls',
|
||||
SplitDropdown.component(
|
||||
{
|
||||
icon: 'fas fa-ellipsis-v',
|
||||
className: 'App-primaryControl',
|
||||
buttonClassName: 'Button--primary',
|
||||
accessibleToggleLabel: app.translator.trans('core.forum.discussion_controls.toggle_dropdown_accessible_label'),
|
||||
},
|
||||
DiscussionControls.controls(this.discussion, this).toArray()
|
||||
),
|
||||
<SplitDropdown
|
||||
icon="fas fa-ellipsis-v"
|
||||
className="App-primaryControl"
|
||||
buttonClassName="Button--primary"
|
||||
accessibleToggleLabel={app.translator.trans('core.forum.discussion_controls.toggle_dropdown_accessible_label')}
|
||||
>
|
||||
{DiscussionControls.controls(this.discussion, this).toArray()}
|
||||
</SplitDropdown>,
|
||||
100
|
||||
);
|
||||
}
|
||||
|
||||
items.add(
|
||||
'scrubber',
|
||||
PostStreamScrubber.component({
|
||||
stream: this.stream,
|
||||
className: 'App-titleControl',
|
||||
}),
|
||||
-100
|
||||
);
|
||||
items.add('scrubber', <PostStreamScrubber stream={this.stream} className="App-titleControl" />, -100);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
@ -40,10 +40,8 @@ export default class DiscussionsSearchSource implements SearchSource {
|
||||
<li className="DiscussionSearchResult" data-index={'discussions' + discussion.id()}>
|
||||
<Link href={app.route.discussion(discussion, (mostRelevantPost && mostRelevantPost.number()) || 0)}>
|
||||
<div className="DiscussionSearchResult-title">{highlight(discussion.title(), query)}</div>
|
||||
{mostRelevantPost ? (
|
||||
{!!mostRelevantPost && (
|
||||
<div className="DiscussionSearchResult-excerpt">{highlight(mostRelevantPost.contentPlain() ?? '', query, 100)}</div>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</Link>
|
||||
</li>
|
||||
|
@ -27,6 +27,10 @@ export default class DiscussionsUserPage extends UserPage<IUserPageAttrs, Discus
|
||||
}
|
||||
|
||||
content() {
|
||||
return <div className="DiscussionsUserPage">{DiscussionList.component({ state: this.state })}</div>;
|
||||
return (
|
||||
<div className="DiscussionsUserPage">
|
||||
<DiscussionList state={this.state} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -86,21 +86,20 @@ export default class EditPostComposer extends ComposerBody {
|
||||
// Otherwise, we'll create an alert message to inform the user that
|
||||
// their edit has been made, containing a button which will
|
||||
// transition to their edited post when clicked.
|
||||
let alert;
|
||||
const viewButton = Button.component(
|
||||
{
|
||||
className: 'Button Button--link',
|
||||
onclick: () => {
|
||||
m.route.set(app.route.post(post));
|
||||
app.alerts.dismiss(alert);
|
||||
},
|
||||
},
|
||||
app.translator.trans('core.forum.composer_edit.view_button')
|
||||
);
|
||||
alert = app.alerts.show(
|
||||
const alert = app.alerts.show(
|
||||
{
|
||||
type: 'success',
|
||||
controls: [viewButton],
|
||||
controls: [
|
||||
<Button
|
||||
className="Button Button--link"
|
||||
onclick={() => {
|
||||
m.route.set(app.route.post(post));
|
||||
app.alerts.dismiss(alert);
|
||||
}}
|
||||
>
|
||||
{app.translator.trans('core.forum.composer_edit.view_button')}
|
||||
</Button>,
|
||||
],
|
||||
},
|
||||
app.translator.trans('core.forum.composer_edit.edited_message')
|
||||
);
|
||||
|
@ -5,6 +5,7 @@ import usernameHelper from '../../common/helpers/username';
|
||||
import icon from '../../common/helpers/icon';
|
||||
import Link from '../../common/components/Link';
|
||||
import humanTime from '../../common/helpers/humanTime';
|
||||
import classList from '../../common/utils/classList';
|
||||
|
||||
/**
|
||||
* The `EventPost` component displays a post which indicating a discussion
|
||||
@ -21,7 +22,7 @@ export default class EventPost extends Post {
|
||||
elementAttrs() {
|
||||
const attrs = super.elementAttrs();
|
||||
|
||||
attrs.className = (attrs.className || '') + ' EventPost ' + ucfirst(this.attrs.post.contentType()) + 'Post';
|
||||
attrs.className = classList(attrs.className, 'EventPost', ucfirst(this.attrs.post.contentType()) + 'Post');
|
||||
|
||||
return attrs;
|
||||
}
|
||||
@ -41,7 +42,9 @@ export default class EventPost extends Post {
|
||||
time: humanTime(this.attrs.post.createdAt()),
|
||||
});
|
||||
|
||||
return super.content().concat([icon(this.icon(), { className: 'EventPost-icon' }), <div class="EventPost-info">{this.description(data)}</div>]);
|
||||
return super
|
||||
.content()
|
||||
.concat([icon(this.icon(), { className: 'EventPost-icon' }), <div className="EventPost-info">{this.description(data)}</div>]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -87,14 +87,9 @@ export default class ForgotPasswordModal<CustomAttrs extends IForgotPasswordModa
|
||||
items.add(
|
||||
'submit',
|
||||
<div className="Form-group">
|
||||
{Button.component(
|
||||
{
|
||||
className: 'Button Button--primary Button--block',
|
||||
type: 'submit',
|
||||
loading: this.loading,
|
||||
},
|
||||
app.translator.trans('core.forum.forgot_password.submit_button')
|
||||
)}
|
||||
<Button className="Button Button--primary Button--block" type="submit" loading={this.loading}>
|
||||
{app.translator.trans('core.forum.forgot_password.submit_button')}
|
||||
</Button>
|
||||
</div>,
|
||||
-10
|
||||
);
|
||||
|
@ -28,71 +28,61 @@ export default class HeaderSecondary extends Component {
|
||||
items() {
|
||||
const items = new ItemList();
|
||||
|
||||
items.add('search', Search.component({ state: app.search }), 30);
|
||||
items.add('search', <Search state={app.search} />, 30);
|
||||
|
||||
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(
|
||||
{
|
||||
active: app.data.locale === locale,
|
||||
icon: app.data.locale === locale ? 'fas fa-check' : true,
|
||||
onclick: () => {
|
||||
if (app.session.user) {
|
||||
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();
|
||||
}
|
||||
},
|
||||
},
|
||||
app.data.locales[locale]
|
||||
)
|
||||
<Button
|
||||
active={app.data.locale === locale}
|
||||
icon={app.data.locale === locale ? 'fas fa-check' : true}
|
||||
onclick={() => {
|
||||
if (app.session.user) {
|
||||
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();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{app.data.locales[locale]}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
items.add(
|
||||
'locale',
|
||||
SelectDropdown.component(
|
||||
{
|
||||
buttonClassName: 'Button Button--link',
|
||||
accessibleToggleLabel: app.translator.trans('core.forum.header.locale_dropdown_accessible_label'),
|
||||
},
|
||||
locales
|
||||
),
|
||||
<SelectDropdown
|
||||
buttonClassName="Button Button--link"
|
||||
accessibleToggleLabel={app.translator.trans('core.forum.header.locale_dropdown_accessible_label')}
|
||||
>
|
||||
{locales}
|
||||
</SelectDropdown>,
|
||||
20
|
||||
);
|
||||
}
|
||||
|
||||
if (app.session.user) {
|
||||
items.add('notifications', NotificationsDropdown.component({ state: app.notifications }), 10);
|
||||
items.add('session', SessionDropdown.component(), 0);
|
||||
items.add('notifications', <NotificationsDropdown state={app.notifications} />, 10);
|
||||
items.add('session', <SessionDropdown />, 0);
|
||||
} else {
|
||||
if (app.forum.attribute('allowSignUp')) {
|
||||
items.add(
|
||||
'signUp',
|
||||
Button.component(
|
||||
{
|
||||
className: 'Button Button--link',
|
||||
onclick: () => app.modal.show(SignUpModal),
|
||||
},
|
||||
app.translator.trans('core.forum.header.sign_up_link')
|
||||
),
|
||||
<Button className="Button Button--link" onclick={() => app.modal.show(SignUpModal)}>
|
||||
{app.translator.trans('core.forum.header.sign_up_link')}
|
||||
</Button>,
|
||||
10
|
||||
);
|
||||
}
|
||||
|
||||
items.add(
|
||||
'logIn',
|
||||
Button.component(
|
||||
{
|
||||
className: 'Button Button--link',
|
||||
onclick: () => app.modal.show(LogInModal),
|
||||
},
|
||||
app.translator.trans('core.forum.header.log_in_link')
|
||||
),
|
||||
<Button className="Button Button--link" onclick={() => app.modal.show(LogInModal)}>
|
||||
{app.translator.trans('core.forum.header.log_in_link')}
|
||||
</Button>,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
@ -141,7 +141,7 @@ export default class IndexPage<CustomAttrs extends IIndexPageAttrs = IIndexPageA
|
||||
* Get the component to display as the hero.
|
||||
*/
|
||||
hero() {
|
||||
return WelcomeHero.component();
|
||||
return <WelcomeHero />;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -155,32 +155,30 @@ export default class IndexPage<CustomAttrs extends IIndexPageAttrs = IIndexPageA
|
||||
|
||||
items.add(
|
||||
'newDiscussion',
|
||||
Button.component(
|
||||
{
|
||||
icon: 'fas fa-edit',
|
||||
className: 'Button Button--primary IndexPage-newDiscussion',
|
||||
itemClassName: 'App-primaryControl',
|
||||
onclick: () => {
|
||||
// If the user is not logged in, the promise rejects, and a login modal shows up.
|
||||
// Since that's already handled, we dont need to show an error message in the console.
|
||||
return this.newDiscussionAction().catch(() => {});
|
||||
},
|
||||
disabled: !canStartDiscussion,
|
||||
},
|
||||
app.translator.trans(canStartDiscussion ? 'core.forum.index.start_discussion_button' : 'core.forum.index.cannot_start_discussion_button')
|
||||
)
|
||||
<Button
|
||||
icon="fas fa-edit"
|
||||
className="Button Button--primary IndexPage-newDiscussion"
|
||||
itemClassName="App-primaryControl"
|
||||
onclick={() => {
|
||||
// If the user is not logged in, the promise rejects, and a login modal shows up.
|
||||
// Since that's already handled, we dont need to show an error message in the console.
|
||||
return this.newDiscussionAction().catch(() => {});
|
||||
}}
|
||||
disabled={!canStartDiscussion}
|
||||
>
|
||||
{app.translator.trans(`core.forum.index.${canStartDiscussion ? 'start_discussion_button' : 'cannot_start_discussion_button'}`)}
|
||||
</Button>
|
||||
);
|
||||
|
||||
items.add(
|
||||
'nav',
|
||||
SelectDropdown.component(
|
||||
{
|
||||
buttonClassName: 'Button',
|
||||
className: 'App-titleControl',
|
||||
accessibleToggleLabel: app.translator.trans('core.forum.index.toggle_sidenav_dropdown_accessible_label'),
|
||||
},
|
||||
this.navItems().toArray()
|
||||
)
|
||||
<SelectDropdown
|
||||
buttonClassName="Button"
|
||||
className="App-titleControl"
|
||||
accessibleToggleLabel={app.translator.trans('core.forum.index.toggle_sidenav_dropdown_accessible_label')}
|
||||
>
|
||||
{this.navItems().toArray()}
|
||||
</SelectDropdown>
|
||||
);
|
||||
|
||||
return items;
|
||||
@ -196,13 +194,9 @@ export default class IndexPage<CustomAttrs extends IIndexPageAttrs = IIndexPageA
|
||||
|
||||
items.add(
|
||||
'allDiscussions',
|
||||
LinkButton.component(
|
||||
{
|
||||
href: app.route('index', params),
|
||||
icon: 'far fa-comments',
|
||||
},
|
||||
app.translator.trans('core.forum.index.all_discussions_link')
|
||||
),
|
||||
<LinkButton href={app.route('index', params)} icon="far fa-comments">
|
||||
{app.translator.trans('core.forum.index.all_discussions_link')}
|
||||
</LinkButton>,
|
||||
100
|
||||
);
|
||||
|
||||
@ -225,26 +219,22 @@ export default class IndexPage<CustomAttrs extends IIndexPageAttrs = IIndexPageA
|
||||
|
||||
items.add(
|
||||
'sort',
|
||||
Dropdown.component(
|
||||
{
|
||||
buttonClassName: 'Button',
|
||||
label: sortOptions[app.search.params().sort] || Object.keys(sortMap).map((key) => sortOptions[key])[0],
|
||||
accessibleToggleLabel: app.translator.trans('core.forum.index_sort.toggle_dropdown_accessible_label'),
|
||||
},
|
||||
Object.keys(sortOptions).map((value) => {
|
||||
<Dropdown
|
||||
buttonClassName="Button"
|
||||
label={sortOptions[app.search.params().sort] || Object.keys(sortMap).map((key) => sortOptions[key])[0]}
|
||||
accessibleToggleLabel={app.translator.trans('core.forum.index_sort.toggle_dropdown_accessible_label')}
|
||||
>
|
||||
{Object.keys(sortOptions).map((value) => {
|
||||
const label = sortOptions[value];
|
||||
const active = (app.search.params().sort || Object.keys(sortMap)[0]) === value;
|
||||
|
||||
return Button.component(
|
||||
{
|
||||
icon: active ? 'fas fa-check' : true,
|
||||
onclick: app.search.changeSort.bind(app.search, value),
|
||||
active: active,
|
||||
},
|
||||
label
|
||||
return (
|
||||
<Button icon={active ? 'fas fa-check' : true} onclick={app.search.changeSort.bind(app.search, value)} active={active}>
|
||||
{label}
|
||||
</Button>
|
||||
);
|
||||
})
|
||||
)
|
||||
})}
|
||||
</Dropdown>
|
||||
);
|
||||
|
||||
return items;
|
||||
@ -259,29 +249,29 @@ export default class IndexPage<CustomAttrs extends IIndexPageAttrs = IIndexPageA
|
||||
|
||||
items.add(
|
||||
'refresh',
|
||||
Button.component({
|
||||
title: app.translator.trans('core.forum.index.refresh_tooltip'),
|
||||
icon: 'fas fa-sync',
|
||||
className: 'Button Button--icon',
|
||||
onclick: () => {
|
||||
<Button
|
||||
title={app.translator.trans('core.forum.index.refresh_tooltip')}
|
||||
icon="fas fa-sync"
|
||||
className="Button Button--icon"
|
||||
onclick={() => {
|
||||
app.discussions.refresh();
|
||||
if (app.session.user) {
|
||||
app.store.find('users', app.session.user.id()!);
|
||||
m.redraw();
|
||||
}
|
||||
},
|
||||
})
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
if (app.session.user) {
|
||||
items.add(
|
||||
'markAllAsRead',
|
||||
Button.component({
|
||||
title: app.translator.trans('core.forum.index.mark_all_as_read_tooltip'),
|
||||
icon: 'fas fa-check',
|
||||
className: 'Button Button--icon',
|
||||
onclick: this.markAllAsRead.bind(this),
|
||||
})
|
||||
<Button
|
||||
title={app.translator.trans('core.forum.index.mark_all_as_read_tooltip')}
|
||||
icon="fas fa-check"
|
||||
className="Button Button--icon"
|
||||
onclick={this.markAllAsRead.bind(this)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import app from '../../forum/app';
|
||||
import Button from '../../common/components/Button';
|
||||
import classList from '../../common/utils/classList';
|
||||
|
||||
/**
|
||||
* The `LogInButton` component displays a social login button which will open
|
||||
@ -11,7 +12,7 @@ import Button from '../../common/components/Button';
|
||||
*/
|
||||
export default class LogInButton extends Button {
|
||||
static initAttrs(attrs) {
|
||||
attrs.className = (attrs.className || '') + ' LogInButton';
|
||||
attrs.className = classList(attrs.className, 'LogInButton');
|
||||
|
||||
attrs.onclick = function () {
|
||||
const width = 580;
|
||||
|
@ -110,14 +110,9 @@ export default class LogInModal<CustomAttrs extends ILoginModalAttrs = ILoginMod
|
||||
items.add(
|
||||
'submit',
|
||||
<div className="Form-group">
|
||||
{Button.component(
|
||||
{
|
||||
className: 'Button Button--primary Button--block',
|
||||
type: 'submit',
|
||||
loading: this.loading,
|
||||
},
|
||||
app.translator.trans('core.forum.log_in.submit_button')
|
||||
)}
|
||||
<Button className="Button Button--primary Button--block" type="submit" loading={this.loading}>
|
||||
{app.translator.trans('core.forum.log_in.submit_button')}
|
||||
</Button>
|
||||
</div>,
|
||||
-10
|
||||
);
|
||||
@ -126,17 +121,16 @@ export default class LogInModal<CustomAttrs extends ILoginModalAttrs = ILoginMod
|
||||
}
|
||||
|
||||
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>
|
||||
) : (
|
||||
''
|
||||
),
|
||||
];
|
||||
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<boolean>('allowSignUp') && (
|
||||
<p className="LogInModal-signUp">{app.translator.trans('core.forum.log_in.sign_up_text', { a: <a onclick={this.signUp.bind(this)} /> })}</p>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -165,7 +159,7 @@ export default class LogInModal<CustomAttrs extends ILoginModalAttrs = ILoginMod
|
||||
}
|
||||
|
||||
onready() {
|
||||
this.$('[name=' + (this.identification() ? 'password' : 'identification') + ']').select();
|
||||
this.$('[name=' + (this.identification() ? 'password' : 'identification') + ']').trigger('select');
|
||||
}
|
||||
|
||||
onsubmit(e: SubmitEvent) {
|
||||
|
@ -71,7 +71,7 @@ export default class NotificationGrid extends Component {
|
||||
disabled={!(key in preferences)}
|
||||
onchange={this.toggle.bind(this, [key])}
|
||||
>
|
||||
<span class="sr-only">
|
||||
<span className="sr-only">
|
||||
{app.translator.trans('core.forum.settings.notification_checkbox_a11y_label_template', {
|
||||
description: type.label,
|
||||
method: method.label,
|
||||
|
@ -118,7 +118,13 @@ export default class NotificationList extends Component {
|
||||
<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 notification={notification} />
|
||||
</li>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -48,7 +48,7 @@ export default class NotificationsDropdown<CustomAttrs extends IDropdownAttrs =
|
||||
getMenu() {
|
||||
return (
|
||||
<div className={classList('Dropdown-menu', this.attrs.menuClassName)} onclick={this.menuClick.bind(this)}>
|
||||
{this.showing && NotificationList.component({ state: this.attrs.state })}
|
||||
{this.showing && <NotificationList state={this.attrs.state} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ export default abstract class Post<CustomAttrs extends IPostAttrs = IPostAttrs>
|
||||
<aside className="Post-actions">
|
||||
<ul>
|
||||
{listItems(this.actionItems().toArray())}
|
||||
{controls.length ? (
|
||||
{!!controls.length && (
|
||||
<li>
|
||||
<Dropdown
|
||||
className="Post-controls"
|
||||
@ -74,8 +74,6 @@ export default abstract class Post<CustomAttrs extends IPostAttrs = IPostAttrs>
|
||||
{controls}
|
||||
</Dropdown>
|
||||
</li>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</ul>
|
||||
</aside>
|
||||
|
@ -23,7 +23,7 @@ export default class PostEdited extends Component {
|
||||
|
||||
return (
|
||||
<Tooltip text={editedInfo}>
|
||||
<span class="PostEdited">{app.translator.trans('core.forum.post.edited_text')}</span>
|
||||
<span className="PostEdited">{app.translator.trans('core.forum.post.edited_text')}</span>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ export default class PostStream extends Component {
|
||||
if (post) {
|
||||
const time = post.createdAt();
|
||||
const PostComponent = app.postComponents[post.contentType()];
|
||||
content = PostComponent ? PostComponent.component({ post }) : '';
|
||||
content = !!PostComponent && <PostComponent post={post} />;
|
||||
|
||||
attrs.key = 'post' + post.id();
|
||||
attrs.oncreate = postFadeIn;
|
||||
@ -75,7 +75,7 @@ export default class PostStream extends Component {
|
||||
} else {
|
||||
attrs.key = 'post' + postIds[this.stream.visibleStart + i];
|
||||
|
||||
content = PostLoading.component();
|
||||
content = <PostLoading />;
|
||||
}
|
||||
|
||||
return (
|
||||
@ -105,7 +105,7 @@ export default class PostStream extends Component {
|
||||
if (viewingEnd && (!app.session.user || this.discussion.canReply())) {
|
||||
items.push(
|
||||
<div className="PostStream-item" key="reply" data-index={this.stream.count()} oncreate={postFadeIn}>
|
||||
{ReplyPlaceholder.component({ discussion: this.discussion })}
|
||||
<ReplyPlaceholder discussion={this.discussion} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -22,26 +22,16 @@ export default class PostUser extends Component {
|
||||
if (!user) {
|
||||
return (
|
||||
<div className="PostUser">
|
||||
<h3 class="PostUser-name">
|
||||
<h3 className="PostUser-name">
|
||||
{avatar(user, { className: 'PostUser-avatar' })} {username(user)}
|
||||
</h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let card = '';
|
||||
|
||||
if (!post.isHidden() && this.attrs.cardVisible) {
|
||||
card = UserCard.component({
|
||||
user,
|
||||
className: 'UserCard--popover',
|
||||
controlsButtonClassName: 'Button Button--icon Button--flat',
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="PostUser">
|
||||
<h3 class="PostUser-name">
|
||||
<h3 className="PostUser-name">
|
||||
<Link href={app.route.user(user)}>
|
||||
{avatar(user, { className: 'PostUser-avatar' })}
|
||||
{userOnline(user)}
|
||||
@ -49,7 +39,10 @@ export default class PostUser extends Component {
|
||||
</Link>
|
||||
</h3>
|
||||
<ul className="PostUser-badges badges">{listItems(user.badges().toArray())}</ul>
|
||||
{card}
|
||||
|
||||
{!post.isHidden() && this.attrs.cardVisible && (
|
||||
<UserCard user={user} className="UserCard--popover" controlsButtonClassName="Button Button--icon Button--flat" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -42,14 +42,9 @@ export default class RenameDiscussionModal<CustomAttrs extends IRenameDiscussion
|
||||
<input className="FormControl" bidi={this.newTitle} type="text" />
|
||||
</div>
|
||||
<div className="Form-group">
|
||||
{Button.component(
|
||||
{
|
||||
className: 'Button Button--primary Button--block',
|
||||
type: 'submit',
|
||||
loading: this.loading,
|
||||
},
|
||||
app.translator.trans('core.forum.rename_discussion.submit_button')
|
||||
)}
|
||||
<Button className="Button Button--primary Button--block" type="submit" loading={this.loading}>
|
||||
{app.translator.trans('core.forum.rename_discussion.submit_button')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -90,15 +90,16 @@ export default class ReplyComposer extends ComposerBody {
|
||||
// their reply has been posted, containing a button which will
|
||||
// transition to their new post when clicked.
|
||||
let alert;
|
||||
const viewButton = Button.component(
|
||||
{
|
||||
className: 'Button Button--link',
|
||||
onclick: () => {
|
||||
const viewButton = (
|
||||
<Button
|
||||
className="Button Button--link"
|
||||
onclick={() => {
|
||||
m.route.set(app.route.post(post));
|
||||
app.alerts.dismiss(alert);
|
||||
},
|
||||
},
|
||||
app.translator.trans('core.forum.composer_reply.view_button')
|
||||
}}
|
||||
>
|
||||
{app.translator.trans('core.forum.composer_reply.view_button')}
|
||||
</Button>
|
||||
);
|
||||
alert = app.alerts.show(
|
||||
{
|
||||
|
@ -21,7 +21,7 @@ export default class ReplyPlaceholder extends Component {
|
||||
<article className="Post CommentPost editing" aria-busy="true">
|
||||
<header className="Post-header">
|
||||
<div className="PostUser">
|
||||
<h3 class="PostUser-name">
|
||||
<h3 className="PostUser-name">
|
||||
{avatar(app.session.user, { className: 'PostUser-avatar' })}
|
||||
{username(app.session.user)}
|
||||
</h3>
|
||||
|
@ -45,54 +45,37 @@ export default class SessionDropdown<CustomAttrs extends ISessionDropdownAttrs =
|
||||
|
||||
items.add(
|
||||
'profile',
|
||||
LinkButton.component(
|
||||
{
|
||||
icon: 'fas fa-user',
|
||||
href: app.route.user(user),
|
||||
},
|
||||
app.translator.trans('core.forum.header.profile_button')
|
||||
),
|
||||
<LinkButton icon="fas fa-user" href={app.route.user(user)}>
|
||||
{app.translator.trans('core.forum.header.profile_button')}
|
||||
</LinkButton>,
|
||||
100
|
||||
);
|
||||
|
||||
items.add(
|
||||
'settings',
|
||||
LinkButton.component(
|
||||
{
|
||||
icon: 'fas fa-cog',
|
||||
href: app.route('settings'),
|
||||
},
|
||||
app.translator.trans('core.forum.header.settings_button')
|
||||
),
|
||||
<LinkButton icon="fas fa-cog" href={app.route('settings')}>
|
||||
{app.translator.trans('core.forum.header.settings_button')}
|
||||
</LinkButton>,
|
||||
50
|
||||
);
|
||||
|
||||
if (app.forum.attribute('adminUrl')) {
|
||||
items.add(
|
||||
'administration',
|
||||
LinkButton.component(
|
||||
{
|
||||
icon: 'fas fa-wrench',
|
||||
href: app.forum.attribute('adminUrl'),
|
||||
target: '_blank',
|
||||
},
|
||||
app.translator.trans('core.forum.header.admin_button')
|
||||
),
|
||||
<LinkButton icon="fas fa-wrench" href={app.forum.attribute('adminUrl')} target="_blank">
|
||||
{app.translator.trans('core.forum.header.admin_button')}
|
||||
</LinkButton>,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
items.add('separator', Separator.component(), -90);
|
||||
items.add('separator', <Separator />, -90);
|
||||
|
||||
items.add(
|
||||
'logOut',
|
||||
Button.component(
|
||||
{
|
||||
icon: 'fas fa-sign-out-alt',
|
||||
onclick: app.session.logout.bind(app.session),
|
||||
},
|
||||
app.translator.trans('core.forum.header.log_out_button')
|
||||
),
|
||||
<Button icon="fas fa-sign-out-alt" onclick={app.session.logout.bind(app.session)}>
|
||||
{app.translator.trans('core.forum.header.log_out_button')}
|
||||
</Button>,
|
||||
-100
|
||||
);
|
||||
|
||||
|
@ -21,7 +21,7 @@ export default class TerminalPost extends Component {
|
||||
|
||||
return (
|
||||
<span>
|
||||
{lastPost ? icon('fas fa-reply') : ''}{' '}
|
||||
{!!lastPost && icon('fas fa-reply')}{' '}
|
||||
{app.translator.trans('core.forum.discussion_list.' + (lastPost ? 'replied' : 'started') + '_text', {
|
||||
user,
|
||||
ago: humanTime(time),
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user