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 && (
134 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;
277 cv.subscribed == SubscribedType.Subscribed && (
279 className={`btn btn-secondary btn-block mb-2 ${
280 cv.community.deleted || cv.community.removed ? "no-click" : ""
282 to={`/create_post?community_id=${cv.community.id}`}
284 {i18n.t("create_a_post")}
291 let community_view = this.props.community_view;
294 {community_view.subscribed == SubscribedType.NotSubscribed && (
296 class="btn btn-secondary btn-block"
298 onClick={linkEvent(this, this.handleSubscribe)}
300 {i18n.t("subscribe")}
308 let description = this.props.community_view.community.description;
309 return description.match({
311 <div className="md-div" dangerouslySetInnerHTML={mdToHtml(desc)} />
318 let community_view = this.props.community_view;
321 <ul class="list-inline mb-1 text-muted font-weight-bold">
322 {amMod(Some(this.props.moderators)) && (
324 <li className="list-inline-item-action">
326 class="btn btn-link text-muted d-inline-block"
327 onClick={linkEvent(this, this.handleEditClick)}
328 data-tippy-content={i18n.t("edit")}
329 aria-label={i18n.t("edit")}
331 <Icon icon="edit" classes="icon-inline" />
334 {!amTopMod(Some(this.props.moderators)) &&
335 (!this.state.showConfirmLeaveModTeam ? (
336 <li className="list-inline-item-action">
338 class="btn btn-link text-muted d-inline-block"
341 this.handleShowConfirmLeaveModTeamClick
344 {i18n.t("leave_mod_team")}
349 <li className="list-inline-item-action">
350 {i18n.t("are_you_sure")}
352 <li className="list-inline-item-action">
354 class="btn btn-link text-muted d-inline-block"
355 onClick={linkEvent(this, this.handleLeaveModTeamClick)}
360 <li className="list-inline-item-action">
362 class="btn btn-link text-muted d-inline-block"
365 this.handleCancelLeaveModTeamClick
373 {amTopMod(Some(this.props.moderators)) && (
374 <li className="list-inline-item-action">
376 class="btn btn-link text-muted d-inline-block"
377 onClick={linkEvent(this, this.handleDeleteClick)}
379 !community_view.community.deleted
384 !community_view.community.deleted
391 classes={`icon-inline ${
392 community_view.community.deleted && "text-danger"
400 {amAdmin(Some(this.props.admins)) && (
401 <li className="list-inline-item">
402 {!this.props.community_view.community.removed ? (
404 class="btn btn-link text-muted d-inline-block"
405 onClick={linkEvent(this, this.handleModRemoveShow)}
411 class="btn btn-link text-muted d-inline-block"
412 onClick={linkEvent(this, this.handleModRemoveSubmit)}
418 class="btn btn-link text-muted d-inline-block"
419 onClick={linkEvent(this, this.handlePurgeCommunityShow)}
420 aria-label={i18n.t("purge_community")}
422 {i18n.t("purge_community")}
427 {this.state.showRemoveDialog && (
428 <form onSubmit={linkEvent(this, this.handleModRemoveSubmit)}>
429 <div class="form-group">
430 <label class="col-form-label" htmlFor="remove-reason">
436 class="form-control mr-2"
437 placeholder={i18n.t("optional")}
438 value={toUndefined(this.state.removeReason)}
439 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
442 {/* TODO hold off on expires for now */}
443 {/* <div class="form-group row"> */}
444 {/* <label class="col-form-label">Expires</label> */}
445 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.removeExpires} onInput={linkEvent(this, this.handleModRemoveExpiresChange)} /> */}
447 <div class="form-group">
448 <button type="submit" class="btn btn-secondary">
449 {i18n.t("remove_community")}
454 {this.state.showPurgeDialog && (
455 <form onSubmit={linkEvent(this, this.handlePurgeSubmit)}>
456 <div class="form-group">
459 <div class="form-group">
460 <label class="sr-only" htmlFor="purge-reason">
466 class="form-control mr-2"
467 placeholder={i18n.t("reason")}
468 value={toUndefined(this.state.purgeReason)}
469 onInput={linkEvent(this, this.handlePurgeReasonChange)}
472 <div class="form-group">
473 {this.state.purgeLoading ? (
478 class="btn btn-secondary"
479 aria-label={i18n.t("purge_community")}
481 {i18n.t("purge_community")}
491 handleEditClick(i: Sidebar) {
492 i.state.showEdit = true;
496 handleEditCommunity() {
497 this.state.showEdit = false;
498 this.setState(this.state);
502 this.state.showEdit = false;
503 this.setState(this.state);
506 handleDeleteClick(i: Sidebar, event: any) {
507 event.preventDefault();
508 let deleteForm = new DeleteCommunity({
509 community_id: i.props.community_view.community.id,
510 deleted: !i.props.community_view.community.deleted,
511 auth: auth().unwrap(),
513 WebSocketService.Instance.send(wsClient.deleteCommunity(deleteForm));
516 handleShowConfirmLeaveModTeamClick(i: Sidebar) {
517 i.state.showConfirmLeaveModTeam = true;
521 handleLeaveModTeamClick(i: Sidebar) {
522 UserService.Instance.myUserInfo.match({
524 let form = new AddModToCommunity({
525 person_id: mui.local_user_view.person.id,
526 community_id: i.props.community_view.community.id,
528 auth: auth().unwrap(),
530 WebSocketService.Instance.send(wsClient.addModToCommunity(form));
531 i.state.showConfirmLeaveModTeam = false;
538 handleCancelLeaveModTeamClick(i: Sidebar) {
539 i.state.showConfirmLeaveModTeam = false;
543 handleUnsubscribe(i: Sidebar, event: any) {
544 event.preventDefault();
545 let community_id = i.props.community_view.community.id;
546 let form = new FollowCommunity({
549 auth: auth().unwrap(),
551 WebSocketService.Instance.send(wsClient.followCommunity(form));
554 UserService.Instance.myUserInfo.match({
556 (mui.follows = mui.follows.filter(i => i.community.id != community_id)),
561 handleSubscribe(i: Sidebar, event: any) {
562 event.preventDefault();
563 let community_id = i.props.community_view.community.id;
564 let form = new FollowCommunity({
567 auth: auth().unwrap(),
569 WebSocketService.Instance.send(wsClient.followCommunity(form));
572 UserService.Instance.myUserInfo.match({
575 community: i.props.community_view.community,
576 follower: mui.local_user_view.person,
582 get canPost(): boolean {
584 !this.props.community_view.community.posting_restricted_to_mods ||
585 amMod(Some(this.props.moderators)) ||
586 amAdmin(Some(this.props.admins))
590 handleModRemoveShow(i: Sidebar) {
591 i.state.showRemoveDialog = true;
595 handleModRemoveReasonChange(i: Sidebar, event: any) {
596 i.state.removeReason = Some(event.target.value);
600 handleModRemoveExpiresChange(i: Sidebar, event: any) {
601 i.state.removeExpires = Some(event.target.value);
605 handleModRemoveSubmit(i: Sidebar, event: any) {
606 event.preventDefault();
607 let removeForm = new RemoveCommunity({
608 community_id: i.props.community_view.community.id,
609 removed: !i.props.community_view.community.removed,
610 reason: i.state.removeReason,
611 expires: i.state.removeExpires.map(getUnixTime),
612 auth: auth().unwrap(),
614 WebSocketService.Instance.send(wsClient.removeCommunity(removeForm));
616 i.state.showRemoveDialog = false;
620 handlePurgeCommunityShow(i: Sidebar) {
621 i.state.showPurgeDialog = true;
622 i.state.showRemoveDialog = false;
626 handlePurgeReasonChange(i: Sidebar, event: any) {
627 i.state.purgeReason = Some(event.target.value);
631 handlePurgeSubmit(i: Sidebar, event: any) {
632 event.preventDefault();
634 let form = new PurgeCommunity({
635 community_id: i.props.community_view.community.id,
636 reason: i.state.purgeReason,
637 auth: auth().unwrap(),
639 WebSocketService.Instance.send(wsClient.purgeCommunity(form));
641 i.state.purgeLoading = true;