1 import { None, Option, Some } from "@sniptt/monads";
2 import { Component, linkEvent } from "inferno";
3 import { Link } from "inferno-router";
7 CommunityModeratorView,
16 } from "lemmy-js-client";
17 import { i18n } from "../../i18next";
18 import { UserService, WebSocketService } from "../../services";
29 import { BannerIconHeader } from "../common/banner-icon-header";
30 import { Icon, PurgeWarning, Spinner } from "../common/icon";
31 import { CommunityForm } from "../community/community-form";
32 import { CommunityLink } from "../community/community-link";
33 import { PersonListing } from "../person/person-listing";
35 interface SidebarProps {
36 community_view: CommunityView;
37 moderators: CommunityModeratorView[];
38 admins: PersonViewSafe[];
44 interface SidebarState {
45 removeReason: Option<string>;
46 removeExpires: Option<string>;
48 showRemoveDialog: boolean;
49 showPurgeDialog: boolean;
50 purgeReason: Option<string>;
51 purgeLoading: boolean;
52 showConfirmLeaveModTeam: boolean;
55 export class Sidebar extends Component<SidebarProps, SidebarState> {
56 private emptyState: SidebarState = {
58 showRemoveDialog: false,
61 showPurgeDialog: false,
64 showConfirmLeaveModTeam: false,
67 constructor(props: any, context: any) {
68 super(props, context);
69 this.state = this.emptyState;
70 this.handleEditCommunity = this.handleEditCommunity.bind(this);
71 this.handleEditCancel = this.handleEditCancel.bind(this);
77 {!this.state.showEdit ? (
81 community_view={Some(this.props.community_view)}
82 onEdit={this.handleEditCommunity}
83 onCancel={this.handleEditCancel}
84 enableNsfw={this.props.enableNsfw}
94 <div class="card border-secondary mb-3">
95 <div class="card-body">
96 {this.communityTitle()}
99 {this.canPost && this.createPost()}
100 {this.blockCommunity()}
103 <div class="card border-secondary mb-3">
104 <div class="card-body">
115 let community = this.props.community_view.community;
116 let subscribed = this.props.community_view.subscribed;
119 <h5 className="mb-0">
120 {this.props.showIcon && (
121 <BannerIconHeader icon={community.icon} banner={community.banner} />
123 <span class="mr-2">{community.title}</span>
124 {subscribed == SubscribedType.Subscribed && (
126 class="btn btn-secondary btn-sm mr-2"
127 onClick={linkEvent(this, this.handleUnsubscribe)}
129 <Icon icon="check" classes="icon-inline text-success mr-1" />
133 {subscribed == SubscribedType.Pending && (
135 class="btn btn-warning mr-2"
136 onClick={linkEvent(this, this.handleUnsubscribe)}
138 {i18n.t("subscribe_pending")}
141 {community.removed && (
142 <small className="mr-2 text-muted font-italic">
146 {community.deleted && (
147 <small className="mr-2 text-muted font-italic">
152 <small className="mr-2 text-muted font-italic">
158 community={community}
169 let community_view = this.props.community_view;
170 let counts = community_view.counts;
172 <ul class="my-1 list-inline">
173 <li className="list-inline-item badge badge-secondary">
174 {i18n.t("number_online", {
175 count: this.props.online,
176 formattedCount: numToSI(this.props.online),
180 className="list-inline-item badge badge-secondary pointer"
181 data-tippy-content={i18n.t("active_users_in_the_last_day", {
182 count: counts.users_active_day,
183 formattedCount: counts.users_active_day,
186 {i18n.t("number_of_users", {
187 count: counts.users_active_day,
188 formattedCount: numToSI(counts.users_active_day),
193 className="list-inline-item badge badge-secondary pointer"
194 data-tippy-content={i18n.t("active_users_in_the_last_week", {
195 count: counts.users_active_week,
196 formattedCount: counts.users_active_week,
199 {i18n.t("number_of_users", {
200 count: counts.users_active_week,
201 formattedCount: numToSI(counts.users_active_week),
206 className="list-inline-item badge badge-secondary pointer"
207 data-tippy-content={i18n.t("active_users_in_the_last_month", {
208 count: counts.users_active_month,
209 formattedCount: counts.users_active_month,
212 {i18n.t("number_of_users", {
213 count: counts.users_active_month,
214 formattedCount: numToSI(counts.users_active_month),
219 className="list-inline-item badge badge-secondary pointer"
220 data-tippy-content={i18n.t("active_users_in_the_last_six_months", {
221 count: counts.users_active_half_year,
222 formattedCount: counts.users_active_half_year,
225 {i18n.t("number_of_users", {
226 count: counts.users_active_half_year,
227 formattedCount: numToSI(counts.users_active_half_year),
229 / {i18n.t("number_of_months", { count: 6, formattedCount: 6 })}
231 <li className="list-inline-item badge badge-secondary">
232 {i18n.t("number_of_subscribers", {
233 count: counts.subscribers,
234 formattedCount: numToSI(counts.subscribers),
237 <li className="list-inline-item badge badge-secondary">
238 {i18n.t("number_of_posts", {
240 formattedCount: numToSI(counts.posts),
243 <li className="list-inline-item badge badge-secondary">
244 {i18n.t("number_of_comments", {
245 count: counts.comments,
246 formattedCount: numToSI(counts.comments),
249 <li className="list-inline-item">
251 className="badge badge-primary"
252 to={`/modlog/community/${this.props.community_view.community.id}`}
263 <ul class="list-inline small">
264 <li class="list-inline-item">{i18n.t("mods")}: </li>
265 {this.props.moderators.map(mod => (
266 <li class="list-inline-item">
267 <PersonListing person={mod.moderator} />
275 let cv = this.props.community_view;
278 className={`btn btn-secondary btn-block mb-2 ${
279 cv.community.deleted || cv.community.removed ? "no-click" : ""
281 to={`/create_post?community_id=${cv.community.id}`}
283 {i18n.t("create_a_post")}
289 let community_view = this.props.community_view;
292 {community_view.subscribed == SubscribedType.NotSubscribed && (
294 class="btn btn-secondary btn-block"
295 onClick={linkEvent(this, this.handleSubscribe)}
297 {i18n.t("subscribe")}
305 let community_view = this.props.community_view;
306 let blocked = this.props.community_view.blocked;
310 {community_view.subscribed == SubscribedType.NotSubscribed &&
313 class="btn btn-danger btn-block"
314 onClick={linkEvent(this, this.handleUnblock)}
316 {i18n.t("unblock_community")}
320 class="btn btn-danger btn-block"
321 onClick={linkEvent(this, this.handleBlock)}
323 {i18n.t("block_community")}
331 let description = this.props.community_view.community.description;
332 return description.match({
334 <div className="md-div" dangerouslySetInnerHTML={mdToHtml(desc)} />
341 let community_view = this.props.community_view;
344 <ul class="list-inline mb-1 text-muted font-weight-bold">
345 {amMod(Some(this.props.moderators)) && (
347 <li className="list-inline-item-action">
349 class="btn btn-link text-muted d-inline-block"
350 onClick={linkEvent(this, this.handleEditClick)}
351 data-tippy-content={i18n.t("edit")}
352 aria-label={i18n.t("edit")}
354 <Icon icon="edit" classes="icon-inline" />
357 {!amTopMod(Some(this.props.moderators)) &&
358 (!this.state.showConfirmLeaveModTeam ? (
359 <li className="list-inline-item-action">
361 class="btn btn-link text-muted d-inline-block"
364 this.handleShowConfirmLeaveModTeamClick
367 {i18n.t("leave_mod_team")}
372 <li className="list-inline-item-action">
373 {i18n.t("are_you_sure")}
375 <li className="list-inline-item-action">
377 class="btn btn-link text-muted d-inline-block"
378 onClick={linkEvent(this, this.handleLeaveModTeamClick)}
383 <li className="list-inline-item-action">
385 class="btn btn-link text-muted d-inline-block"
388 this.handleCancelLeaveModTeamClick
396 {amTopMod(Some(this.props.moderators)) && (
397 <li className="list-inline-item-action">
399 class="btn btn-link text-muted d-inline-block"
400 onClick={linkEvent(this, this.handleDeleteClick)}
402 !community_view.community.deleted
407 !community_view.community.deleted
414 classes={`icon-inline ${
415 community_view.community.deleted && "text-danger"
423 {amAdmin(Some(this.props.admins)) && (
424 <li className="list-inline-item">
425 {!this.props.community_view.community.removed ? (
427 class="btn btn-link text-muted d-inline-block"
428 onClick={linkEvent(this, this.handleModRemoveShow)}
434 class="btn btn-link text-muted d-inline-block"
435 onClick={linkEvent(this, this.handleModRemoveSubmit)}
441 class="btn btn-link text-muted d-inline-block"
442 onClick={linkEvent(this, this.handlePurgeCommunityShow)}
443 aria-label={i18n.t("purge_community")}
445 {i18n.t("purge_community")}
450 {this.state.showRemoveDialog && (
451 <form onSubmit={linkEvent(this, this.handleModRemoveSubmit)}>
452 <div class="form-group">
453 <label class="col-form-label" htmlFor="remove-reason">
459 class="form-control mr-2"
460 placeholder={i18n.t("optional")}
461 value={toUndefined(this.state.removeReason)}
462 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
465 {/* TODO hold off on expires for now */}
466 {/* <div class="form-group row"> */}
467 {/* <label class="col-form-label">Expires</label> */}
468 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.removeExpires} onInput={linkEvent(this, this.handleModRemoveExpiresChange)} /> */}
470 <div class="form-group">
471 <button type="submit" class="btn btn-secondary">
472 {i18n.t("remove_community")}
477 {this.state.showPurgeDialog && (
478 <form onSubmit={linkEvent(this, this.handlePurgeSubmit)}>
479 <div class="form-group">
482 <div class="form-group">
483 <label class="sr-only" htmlFor="purge-reason">
489 class="form-control mr-2"
490 placeholder={i18n.t("reason")}
491 value={toUndefined(this.state.purgeReason)}
492 onInput={linkEvent(this, this.handlePurgeReasonChange)}
495 <div class="form-group">
496 {this.state.purgeLoading ? (
501 class="btn btn-secondary"
502 aria-label={i18n.t("purge_community")}
504 {i18n.t("purge_community")}
514 handleEditClick(i: Sidebar) {
515 i.state.showEdit = true;
519 handleEditCommunity() {
520 this.state.showEdit = false;
521 this.setState(this.state);
525 this.state.showEdit = false;
526 this.setState(this.state);
529 handleDeleteClick(i: Sidebar, event: any) {
530 event.preventDefault();
531 let deleteForm = new DeleteCommunity({
532 community_id: i.props.community_view.community.id,
533 deleted: !i.props.community_view.community.deleted,
534 auth: auth().unwrap(),
536 WebSocketService.Instance.send(wsClient.deleteCommunity(deleteForm));
539 handleShowConfirmLeaveModTeamClick(i: Sidebar) {
540 i.state.showConfirmLeaveModTeam = true;
544 handleLeaveModTeamClick(i: Sidebar) {
545 UserService.Instance.myUserInfo.match({
547 let form = new AddModToCommunity({
548 person_id: mui.local_user_view.person.id,
549 community_id: i.props.community_view.community.id,
551 auth: auth().unwrap(),
553 WebSocketService.Instance.send(wsClient.addModToCommunity(form));
554 i.state.showConfirmLeaveModTeam = false;
561 handleCancelLeaveModTeamClick(i: Sidebar) {
562 i.state.showConfirmLeaveModTeam = false;
566 handleUnsubscribe(i: Sidebar, event: any) {
567 event.preventDefault();
568 let community_id = i.props.community_view.community.id;
569 let form = new FollowCommunity({
572 auth: auth().unwrap(),
574 WebSocketService.Instance.send(wsClient.followCommunity(form));
577 UserService.Instance.myUserInfo.match({
579 (mui.follows = mui.follows.filter(i => i.community.id != community_id)),
584 handleSubscribe(i: Sidebar, event: any) {
585 event.preventDefault();
586 let community_id = i.props.community_view.community.id;
587 let form = new FollowCommunity({
590 auth: auth().unwrap(),
592 WebSocketService.Instance.send(wsClient.followCommunity(form));
595 UserService.Instance.myUserInfo.match({
598 community: i.props.community_view.community,
599 follower: mui.local_user_view.person,
605 get canPost(): boolean {
607 !this.props.community_view.community.posting_restricted_to_mods ||
608 amMod(Some(this.props.moderators)) ||
609 amAdmin(Some(this.props.admins))
613 handleModRemoveShow(i: Sidebar) {
614 i.state.showRemoveDialog = true;
618 handleModRemoveReasonChange(i: Sidebar, event: any) {
619 i.state.removeReason = Some(event.target.value);
623 handleModRemoveExpiresChange(i: Sidebar, event: any) {
624 i.state.removeExpires = Some(event.target.value);
628 handleModRemoveSubmit(i: Sidebar, event: any) {
629 event.preventDefault();
630 let removeForm = new RemoveCommunity({
631 community_id: i.props.community_view.community.id,
632 removed: !i.props.community_view.community.removed,
633 reason: i.state.removeReason,
634 expires: i.state.removeExpires.map(getUnixTime),
635 auth: auth().unwrap(),
637 WebSocketService.Instance.send(wsClient.removeCommunity(removeForm));
639 i.state.showRemoveDialog = false;
643 handlePurgeCommunityShow(i: Sidebar) {
644 i.state.showPurgeDialog = true;
645 i.state.showRemoveDialog = false;
649 handlePurgeReasonChange(i: Sidebar, event: any) {
650 i.state.purgeReason = Some(event.target.value);
654 handlePurgeSubmit(i: Sidebar, event: any) {
655 event.preventDefault();
657 let form = new PurgeCommunity({
658 community_id: i.props.community_view.community.id,
659 reason: i.state.purgeReason,
660 auth: auth().unwrap(),
662 WebSocketService.Instance.send(wsClient.purgeCommunity(form));
664 i.state.purgeLoading = true;
668 handleBlock(i: Sidebar, event: any) {
669 event.preventDefault();
670 let blockCommunityForm = new BlockCommunity({
671 community_id: i.props.community_view.community.id,
673 auth: auth().unwrap(),
675 WebSocketService.Instance.send(wsClient.blockCommunity(blockCommunityForm));
678 handleUnblock(i: Sidebar, event: any) {
679 event.preventDefault();
680 let blockCommunityForm = new BlockCommunity({
681 community_id: i.props.community_view.community.id,
683 auth: auth().unwrap(),
685 WebSocketService.Instance.send(wsClient.blockCommunity(blockCommunityForm));