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()}
102 <div class="card border-secondary mb-3">
103 <div class="card-body">
114 let community = this.props.community_view.community;
115 let subscribed = this.props.community_view.subscribed;
118 <h5 className="mb-0">
119 {this.props.showIcon && (
120 <BannerIconHeader icon={community.icon} banner={community.banner} />
122 <span class="mr-2">{community.title}</span>
123 {subscribed == SubscribedType.Subscribed && (
125 class="btn btn-secondary btn-sm mr-2"
126 onClick={linkEvent(this, this.handleUnsubscribe)}
128 <Icon icon="check" classes="icon-inline text-success mr-1" />
132 {subscribed == SubscribedType.Pending && (
134 class="btn btn-warning mr-2"
135 onClick={linkEvent(this, this.handleUnsubscribe)}
137 {i18n.t("subscribe_pending")}
140 {community.removed && (
141 <small className="mr-2 text-muted font-italic">
145 {community.deleted && (
146 <small className="mr-2 text-muted font-italic">
151 <small className="mr-2 text-muted font-italic">
157 community={community}
168 let community_view = this.props.community_view;
169 let counts = community_view.counts;
171 <ul class="my-1 list-inline">
172 <li className="list-inline-item badge badge-secondary">
173 {i18n.t("number_online", {
174 count: this.props.online,
175 formattedCount: numToSI(this.props.online),
179 className="list-inline-item badge badge-secondary pointer"
180 data-tippy-content={i18n.t("active_users_in_the_last_day", {
181 count: counts.users_active_day,
182 formattedCount: counts.users_active_day,
185 {i18n.t("number_of_users", {
186 count: counts.users_active_day,
187 formattedCount: numToSI(counts.users_active_day),
192 className="list-inline-item badge badge-secondary pointer"
193 data-tippy-content={i18n.t("active_users_in_the_last_week", {
194 count: counts.users_active_week,
195 formattedCount: counts.users_active_week,
198 {i18n.t("number_of_users", {
199 count: counts.users_active_week,
200 formattedCount: numToSI(counts.users_active_week),
205 className="list-inline-item badge badge-secondary pointer"
206 data-tippy-content={i18n.t("active_users_in_the_last_month", {
207 count: counts.users_active_month,
208 formattedCount: counts.users_active_month,
211 {i18n.t("number_of_users", {
212 count: counts.users_active_month,
213 formattedCount: numToSI(counts.users_active_month),
218 className="list-inline-item badge badge-secondary pointer"
219 data-tippy-content={i18n.t("active_users_in_the_last_six_months", {
220 count: counts.users_active_half_year,
221 formattedCount: counts.users_active_half_year,
224 {i18n.t("number_of_users", {
225 count: counts.users_active_half_year,
226 formattedCount: numToSI(counts.users_active_half_year),
228 / {i18n.t("number_of_months", { count: 6, formattedCount: 6 })}
230 <li className="list-inline-item badge badge-secondary">
231 {i18n.t("number_of_subscribers", {
232 count: counts.subscribers,
233 formattedCount: numToSI(counts.subscribers),
236 <li className="list-inline-item badge badge-secondary">
237 {i18n.t("number_of_posts", {
239 formattedCount: numToSI(counts.posts),
242 <li className="list-inline-item badge badge-secondary">
243 {i18n.t("number_of_comments", {
244 count: counts.comments,
245 formattedCount: numToSI(counts.comments),
248 <li className="list-inline-item">
250 className="badge badge-primary"
251 to={`/modlog/community/${this.props.community_view.community.id}`}
262 <ul class="list-inline small">
263 <li class="list-inline-item">{i18n.t("mods")}: </li>
264 {this.props.moderators.map(mod => (
265 <li class="list-inline-item">
266 <PersonListing person={mod.moderator} />
274 let cv = this.props.community_view;
276 cv.subscribed == SubscribedType.Subscribed && (
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")}
290 let community_view = this.props.community_view;
291 let blocked = this.props.community_view.blocked;
294 {community_view.subscribed == SubscribedType.NotSubscribed && (
297 class="btn btn-secondary btn-block"
298 onClick={linkEvent(this, this.handleSubscribe)}
300 {i18n.t("subscribe")}
304 class="btn btn-danger btn-block"
305 onClick={linkEvent(this, this.handleUnblock)}
307 {i18n.t("unblock_community")}
311 class="btn btn-danger btn-block"
312 onClick={linkEvent(this, this.handleBlock)}
314 {i18n.t("block_community")}
324 let description = this.props.community_view.community.description;
325 return description.match({
327 <div className="md-div" dangerouslySetInnerHTML={mdToHtml(desc)} />
334 let community_view = this.props.community_view;
337 <ul class="list-inline mb-1 text-muted font-weight-bold">
338 {amMod(Some(this.props.moderators)) && (
340 <li className="list-inline-item-action">
342 class="btn btn-link text-muted d-inline-block"
343 onClick={linkEvent(this, this.handleEditClick)}
344 data-tippy-content={i18n.t("edit")}
345 aria-label={i18n.t("edit")}
347 <Icon icon="edit" classes="icon-inline" />
350 {!amTopMod(Some(this.props.moderators)) &&
351 (!this.state.showConfirmLeaveModTeam ? (
352 <li className="list-inline-item-action">
354 class="btn btn-link text-muted d-inline-block"
357 this.handleShowConfirmLeaveModTeamClick
360 {i18n.t("leave_mod_team")}
365 <li className="list-inline-item-action">
366 {i18n.t("are_you_sure")}
368 <li className="list-inline-item-action">
370 class="btn btn-link text-muted d-inline-block"
371 onClick={linkEvent(this, this.handleLeaveModTeamClick)}
376 <li className="list-inline-item-action">
378 class="btn btn-link text-muted d-inline-block"
381 this.handleCancelLeaveModTeamClick
389 {amTopMod(Some(this.props.moderators)) && (
390 <li className="list-inline-item-action">
392 class="btn btn-link text-muted d-inline-block"
393 onClick={linkEvent(this, this.handleDeleteClick)}
395 !community_view.community.deleted
400 !community_view.community.deleted
407 classes={`icon-inline ${
408 community_view.community.deleted && "text-danger"
416 {amAdmin(Some(this.props.admins)) && (
417 <li className="list-inline-item">
418 {!this.props.community_view.community.removed ? (
420 class="btn btn-link text-muted d-inline-block"
421 onClick={linkEvent(this, this.handleModRemoveShow)}
427 class="btn btn-link text-muted d-inline-block"
428 onClick={linkEvent(this, this.handleModRemoveSubmit)}
434 class="btn btn-link text-muted d-inline-block"
435 onClick={linkEvent(this, this.handlePurgeCommunityShow)}
436 aria-label={i18n.t("purge_community")}
438 {i18n.t("purge_community")}
443 {this.state.showRemoveDialog && (
444 <form onSubmit={linkEvent(this, this.handleModRemoveSubmit)}>
445 <div class="form-group">
446 <label class="col-form-label" htmlFor="remove-reason">
452 class="form-control mr-2"
453 placeholder={i18n.t("optional")}
454 value={toUndefined(this.state.removeReason)}
455 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
458 {/* TODO hold off on expires for now */}
459 {/* <div class="form-group row"> */}
460 {/* <label class="col-form-label">Expires</label> */}
461 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.removeExpires} onInput={linkEvent(this, this.handleModRemoveExpiresChange)} /> */}
463 <div class="form-group">
464 <button type="submit" class="btn btn-secondary">
465 {i18n.t("remove_community")}
470 {this.state.showPurgeDialog && (
471 <form onSubmit={linkEvent(this, this.handlePurgeSubmit)}>
472 <div class="form-group">
475 <div class="form-group">
476 <label class="sr-only" htmlFor="purge-reason">
482 class="form-control mr-2"
483 placeholder={i18n.t("reason")}
484 value={toUndefined(this.state.purgeReason)}
485 onInput={linkEvent(this, this.handlePurgeReasonChange)}
488 <div class="form-group">
489 {this.state.purgeLoading ? (
494 class="btn btn-secondary"
495 aria-label={i18n.t("purge_community")}
497 {i18n.t("purge_community")}
507 handleEditClick(i: Sidebar) {
508 i.state.showEdit = true;
512 handleEditCommunity() {
513 this.state.showEdit = false;
514 this.setState(this.state);
518 this.state.showEdit = false;
519 this.setState(this.state);
522 handleDeleteClick(i: Sidebar, event: any) {
523 event.preventDefault();
524 let deleteForm = new DeleteCommunity({
525 community_id: i.props.community_view.community.id,
526 deleted: !i.props.community_view.community.deleted,
527 auth: auth().unwrap(),
529 WebSocketService.Instance.send(wsClient.deleteCommunity(deleteForm));
532 handleShowConfirmLeaveModTeamClick(i: Sidebar) {
533 i.state.showConfirmLeaveModTeam = true;
537 handleLeaveModTeamClick(i: Sidebar) {
538 UserService.Instance.myUserInfo.match({
540 let form = new AddModToCommunity({
541 person_id: mui.local_user_view.person.id,
542 community_id: i.props.community_view.community.id,
544 auth: auth().unwrap(),
546 WebSocketService.Instance.send(wsClient.addModToCommunity(form));
547 i.state.showConfirmLeaveModTeam = false;
554 handleCancelLeaveModTeamClick(i: Sidebar) {
555 i.state.showConfirmLeaveModTeam = false;
559 handleUnsubscribe(i: Sidebar, event: any) {
560 event.preventDefault();
561 let community_id = i.props.community_view.community.id;
562 let form = new FollowCommunity({
565 auth: auth().unwrap(),
567 WebSocketService.Instance.send(wsClient.followCommunity(form));
570 UserService.Instance.myUserInfo.match({
572 (mui.follows = mui.follows.filter(i => i.community.id != community_id)),
577 handleSubscribe(i: Sidebar, event: any) {
578 event.preventDefault();
579 let community_id = i.props.community_view.community.id;
580 let form = new FollowCommunity({
583 auth: auth().unwrap(),
585 WebSocketService.Instance.send(wsClient.followCommunity(form));
588 UserService.Instance.myUserInfo.match({
591 community: i.props.community_view.community,
592 follower: mui.local_user_view.person,
598 get canPost(): boolean {
600 !this.props.community_view.community.posting_restricted_to_mods ||
601 amMod(Some(this.props.moderators)) ||
602 amAdmin(Some(this.props.admins))
606 handleModRemoveShow(i: Sidebar) {
607 i.state.showRemoveDialog = true;
611 handleModRemoveReasonChange(i: Sidebar, event: any) {
612 i.state.removeReason = Some(event.target.value);
616 handleModRemoveExpiresChange(i: Sidebar, event: any) {
617 i.state.removeExpires = Some(event.target.value);
621 handleModRemoveSubmit(i: Sidebar, event: any) {
622 event.preventDefault();
623 let removeForm = new RemoveCommunity({
624 community_id: i.props.community_view.community.id,
625 removed: !i.props.community_view.community.removed,
626 reason: i.state.removeReason,
627 expires: i.state.removeExpires.map(getUnixTime),
628 auth: auth().unwrap(),
630 WebSocketService.Instance.send(wsClient.removeCommunity(removeForm));
632 i.state.showRemoveDialog = false;
636 handlePurgeCommunityShow(i: Sidebar) {
637 i.state.showPurgeDialog = true;
638 i.state.showRemoveDialog = false;
642 handlePurgeReasonChange(i: Sidebar, event: any) {
643 i.state.purgeReason = Some(event.target.value);
647 handlePurgeSubmit(i: Sidebar, event: any) {
648 event.preventDefault();
650 let form = new PurgeCommunity({
651 community_id: i.props.community_view.community.id,
652 reason: i.state.purgeReason,
653 auth: auth().unwrap(),
655 WebSocketService.Instance.send(wsClient.purgeCommunity(form));
657 i.state.purgeLoading = true;
661 handleBlock(i: Sidebar, event: any) {
662 event.preventDefault();
663 let blockCommunityForm = new BlockCommunity({
664 community_id: i.props.community_view.community.id,
666 auth: auth().unwrap(),
668 WebSocketService.Instance.send(wsClient.blockCommunity(blockCommunityForm));
671 handleUnblock(i: Sidebar, event: any) {
672 event.preventDefault();
673 let blockCommunityForm = new BlockCommunity({
674 community_id: i.props.community_view.community.id,
676 auth: auth().unwrap(),
678 WebSocketService.Instance.send(wsClient.blockCommunity(blockCommunityForm));