1 import { None, Option, Some } from "@sniptt/monads";
2 import { Component, linkEvent } from "inferno";
3 import { Link } from "inferno-router";
6 CommunityModeratorView,
15 } from "lemmy-js-client";
16 import { i18n } from "../../i18next";
17 import { UserService, WebSocketService } from "../../services";
28 import { BannerIconHeader } from "../common/banner-icon-header";
29 import { Icon, PurgeWarning, Spinner } from "../common/icon";
30 import { CommunityForm } from "../community/community-form";
31 import { CommunityLink } from "../community/community-link";
32 import { PersonListing } from "../person/person-listing";
34 interface SidebarProps {
35 community_view: CommunityView;
36 moderators: CommunityModeratorView[];
37 admins: PersonViewSafe[];
43 interface SidebarState {
44 removeReason: Option<string>;
45 removeExpires: Option<string>;
47 showRemoveDialog: boolean;
48 showPurgeDialog: boolean;
49 purgeReason: Option<string>;
50 purgeLoading: boolean;
51 showConfirmLeaveModTeam: boolean;
54 export class Sidebar extends Component<SidebarProps, SidebarState> {
55 private emptyState: SidebarState = {
57 showRemoveDialog: false,
60 showPurgeDialog: false,
63 showConfirmLeaveModTeam: false,
66 constructor(props: any, context: any) {
67 super(props, context);
68 this.state = this.emptyState;
69 this.handleEditCommunity = this.handleEditCommunity.bind(this);
70 this.handleEditCancel = this.handleEditCancel.bind(this);
76 {!this.state.showEdit ? (
80 community_view={Some(this.props.community_view)}
81 onEdit={this.handleEditCommunity}
82 onCancel={this.handleEditCancel}
83 enableNsfw={this.props.enableNsfw}
93 <div class="card border-secondary mb-3">
94 <div class="card-body">
95 {this.communityTitle()}
98 {this.canPost && this.createPost()}
101 <div class="card border-secondary mb-3">
102 <div class="card-body">
113 let community = this.props.community_view.community;
114 let subscribed = this.props.community_view.subscribed;
117 <h5 className="mb-0">
118 {this.props.showIcon && (
119 <BannerIconHeader icon={community.icon} banner={community.banner} />
121 <span class="mr-2">{community.title}</span>
122 {subscribed == SubscribedType.Subscribed && (
124 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 && (
133 <div class="badge badge-warning mr-2">
134 {i18n.t("subscribe_pending")}
137 {community.removed && (
138 <small className="mr-2 text-muted font-italic">
142 {community.deleted && (
143 <small className="mr-2 text-muted font-italic">
148 <small className="mr-2 text-muted font-italic">
154 community={community}
165 let community_view = this.props.community_view;
166 let counts = community_view.counts;
168 <ul class="my-1 list-inline">
169 <li className="list-inline-item badge badge-secondary">
170 {i18n.t("number_online", {
171 count: this.props.online,
172 formattedCount: numToSI(this.props.online),
176 className="list-inline-item badge badge-secondary pointer"
177 data-tippy-content={i18n.t("active_users_in_the_last_day", {
178 count: counts.users_active_day,
179 formattedCount: counts.users_active_day,
182 {i18n.t("number_of_users", {
183 count: counts.users_active_day,
184 formattedCount: numToSI(counts.users_active_day),
189 className="list-inline-item badge badge-secondary pointer"
190 data-tippy-content={i18n.t("active_users_in_the_last_week", {
191 count: counts.users_active_week,
192 formattedCount: counts.users_active_week,
195 {i18n.t("number_of_users", {
196 count: counts.users_active_week,
197 formattedCount: numToSI(counts.users_active_week),
202 className="list-inline-item badge badge-secondary pointer"
203 data-tippy-content={i18n.t("active_users_in_the_last_month", {
204 count: counts.users_active_month,
205 formattedCount: counts.users_active_month,
208 {i18n.t("number_of_users", {
209 count: counts.users_active_month,
210 formattedCount: numToSI(counts.users_active_month),
215 className="list-inline-item badge badge-secondary pointer"
216 data-tippy-content={i18n.t("active_users_in_the_last_six_months", {
217 count: counts.users_active_half_year,
218 formattedCount: counts.users_active_half_year,
221 {i18n.t("number_of_users", {
222 count: counts.users_active_half_year,
223 formattedCount: numToSI(counts.users_active_half_year),
225 / {i18n.t("number_of_months", { count: 6, formattedCount: 6 })}
227 <li className="list-inline-item badge badge-secondary">
228 {i18n.t("number_of_subscribers", {
229 count: counts.subscribers,
230 formattedCount: numToSI(counts.subscribers),
233 <li className="list-inline-item badge badge-secondary">
234 {i18n.t("number_of_posts", {
236 formattedCount: numToSI(counts.posts),
239 <li className="list-inline-item badge badge-secondary">
240 {i18n.t("number_of_comments", {
241 count: counts.comments,
242 formattedCount: numToSI(counts.comments),
245 <li className="list-inline-item">
247 className="badge badge-primary"
248 to={`/modlog/community/${this.props.community_view.community.id}`}
259 <ul class="list-inline small">
260 <li class="list-inline-item">{i18n.t("mods")}: </li>
261 {this.props.moderators.map(mod => (
262 <li class="list-inline-item">
263 <PersonListing person={mod.moderator} />
271 let cv = this.props.community_view;
273 cv.subscribed == SubscribedType.Subscribed && (
275 className={`btn btn-secondary btn-block mb-2 ${
276 cv.community.deleted || cv.community.removed ? "no-click" : ""
278 to={`/create_post?community_id=${cv.community.id}`}
280 {i18n.t("create_a_post")}
287 let community_view = this.props.community_view;
290 {community_view.subscribed == SubscribedType.NotSubscribed && (
292 class="btn btn-secondary btn-block"
294 onClick={linkEvent(this, this.handleSubscribe)}
296 {i18n.t("subscribe")}
304 let description = this.props.community_view.community.description;
305 return description.match({
307 <div className="md-div" dangerouslySetInnerHTML={mdToHtml(desc)} />
314 let community_view = this.props.community_view;
317 <ul class="list-inline mb-1 text-muted font-weight-bold">
318 {amMod(Some(this.props.moderators)) && (
320 <li className="list-inline-item-action">
322 class="btn btn-link text-muted d-inline-block"
323 onClick={linkEvent(this, this.handleEditClick)}
324 data-tippy-content={i18n.t("edit")}
325 aria-label={i18n.t("edit")}
327 <Icon icon="edit" classes="icon-inline" />
330 {!amTopMod(Some(this.props.moderators)) &&
331 (!this.state.showConfirmLeaveModTeam ? (
332 <li className="list-inline-item-action">
334 class="btn btn-link text-muted d-inline-block"
337 this.handleShowConfirmLeaveModTeamClick
340 {i18n.t("leave_mod_team")}
345 <li className="list-inline-item-action">
346 {i18n.t("are_you_sure")}
348 <li className="list-inline-item-action">
350 class="btn btn-link text-muted d-inline-block"
351 onClick={linkEvent(this, this.handleLeaveModTeamClick)}
356 <li className="list-inline-item-action">
358 class="btn btn-link text-muted d-inline-block"
361 this.handleCancelLeaveModTeamClick
369 {amTopMod(Some(this.props.moderators)) && (
370 <li className="list-inline-item-action">
372 class="btn btn-link text-muted d-inline-block"
373 onClick={linkEvent(this, this.handleDeleteClick)}
375 !community_view.community.deleted
380 !community_view.community.deleted
387 classes={`icon-inline ${
388 community_view.community.deleted && "text-danger"
396 {amAdmin(Some(this.props.admins)) && (
397 <li className="list-inline-item">
398 {!this.props.community_view.community.removed ? (
400 class="btn btn-link text-muted d-inline-block"
401 onClick={linkEvent(this, this.handleModRemoveShow)}
407 class="btn btn-link text-muted d-inline-block"
408 onClick={linkEvent(this, this.handleModRemoveSubmit)}
414 class="btn btn-link text-muted d-inline-block"
415 onClick={linkEvent(this, this.handlePurgeCommunityShow)}
416 aria-label={i18n.t("purge_community")}
418 {i18n.t("purge_community")}
423 {this.state.showRemoveDialog && (
424 <form onSubmit={linkEvent(this, this.handleModRemoveSubmit)}>
425 <div class="form-group">
426 <label class="col-form-label" htmlFor="remove-reason">
432 class="form-control mr-2"
433 placeholder={i18n.t("optional")}
434 value={toUndefined(this.state.removeReason)}
435 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
438 {/* TODO hold off on expires for now */}
439 {/* <div class="form-group row"> */}
440 {/* <label class="col-form-label">Expires</label> */}
441 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.removeExpires} onInput={linkEvent(this, this.handleModRemoveExpiresChange)} /> */}
443 <div class="form-group">
444 <button type="submit" class="btn btn-secondary">
445 {i18n.t("remove_community")}
450 {this.state.showPurgeDialog && (
451 <form onSubmit={linkEvent(this, this.handlePurgeSubmit)}>
452 <div class="form-group">
455 <div class="form-group">
456 <label class="sr-only" htmlFor="purge-reason">
462 class="form-control mr-2"
463 placeholder={i18n.t("reason")}
464 value={toUndefined(this.state.purgeReason)}
465 onInput={linkEvent(this, this.handlePurgeReasonChange)}
468 <div class="form-group">
469 {this.state.purgeLoading ? (
474 class="btn btn-secondary"
475 aria-label={i18n.t("purge_community")}
477 {i18n.t("purge_community")}
487 handleEditClick(i: Sidebar) {
488 i.state.showEdit = true;
492 handleEditCommunity() {
493 this.state.showEdit = false;
494 this.setState(this.state);
498 this.state.showEdit = false;
499 this.setState(this.state);
502 handleDeleteClick(i: Sidebar, event: any) {
503 event.preventDefault();
504 let deleteForm = new DeleteCommunity({
505 community_id: i.props.community_view.community.id,
506 deleted: !i.props.community_view.community.deleted,
507 auth: auth().unwrap(),
509 WebSocketService.Instance.send(wsClient.deleteCommunity(deleteForm));
512 handleShowConfirmLeaveModTeamClick(i: Sidebar) {
513 i.state.showConfirmLeaveModTeam = true;
517 handleLeaveModTeamClick(i: Sidebar) {
518 UserService.Instance.myUserInfo.match({
520 let form = new AddModToCommunity({
521 person_id: mui.local_user_view.person.id,
522 community_id: i.props.community_view.community.id,
524 auth: auth().unwrap(),
526 WebSocketService.Instance.send(wsClient.addModToCommunity(form));
527 i.state.showConfirmLeaveModTeam = false;
534 handleCancelLeaveModTeamClick(i: Sidebar) {
535 i.state.showConfirmLeaveModTeam = false;
539 handleUnsubscribe(i: Sidebar, event: any) {
540 event.preventDefault();
541 let community_id = i.props.community_view.community.id;
542 let form = new FollowCommunity({
545 auth: auth().unwrap(),
547 WebSocketService.Instance.send(wsClient.followCommunity(form));
550 UserService.Instance.myUserInfo.match({
552 (mui.follows = mui.follows.filter(i => i.community.id != community_id)),
557 handleSubscribe(i: Sidebar, event: any) {
558 event.preventDefault();
559 let community_id = i.props.community_view.community.id;
560 let form = new FollowCommunity({
563 auth: auth().unwrap(),
565 WebSocketService.Instance.send(wsClient.followCommunity(form));
568 UserService.Instance.myUserInfo.match({
571 community: i.props.community_view.community,
572 follower: mui.local_user_view.person,
578 get canPost(): boolean {
580 !this.props.community_view.community.posting_restricted_to_mods ||
581 amMod(Some(this.props.moderators)) ||
582 amAdmin(Some(this.props.admins))
586 handleModRemoveShow(i: Sidebar) {
587 i.state.showRemoveDialog = true;
591 handleModRemoveReasonChange(i: Sidebar, event: any) {
592 i.state.removeReason = Some(event.target.value);
596 handleModRemoveExpiresChange(i: Sidebar, event: any) {
597 i.state.removeExpires = Some(event.target.value);
601 handleModRemoveSubmit(i: Sidebar, event: any) {
602 event.preventDefault();
603 let removeForm = new RemoveCommunity({
604 community_id: i.props.community_view.community.id,
605 removed: !i.props.community_view.community.removed,
606 reason: i.state.removeReason,
607 expires: i.state.removeExpires.map(getUnixTime),
608 auth: auth().unwrap(),
610 WebSocketService.Instance.send(wsClient.removeCommunity(removeForm));
612 i.state.showRemoveDialog = false;
616 handlePurgeCommunityShow(i: Sidebar) {
617 i.state.showPurgeDialog = true;
618 i.state.showRemoveDialog = false;
622 handlePurgeReasonChange(i: Sidebar, event: any) {
623 i.state.purgeReason = Some(event.target.value);
627 handlePurgeSubmit(i: Sidebar, event: any) {
628 event.preventDefault();
630 let form = new PurgeCommunity({
631 community_id: i.props.community_view.community.id,
632 reason: i.state.purgeReason,
633 auth: auth().unwrap(),
635 WebSocketService.Instance.send(wsClient.purgeCommunity(form));
637 i.state.purgeLoading = true;