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 className="card border-secondary mb-3">
95 <div className="card-body">
96 {this.communityTitle()}
99 {this.canPost && this.createPost()}
100 {this.blockCommunity()}
103 <div className="card border-secondary mb-3">
104 <div className="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 className="mr-2">{community.title}</span>
124 {subscribed == SubscribedType.Subscribed && (
126 className="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 className="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 className="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 className="list-inline small">
264 <li className="list-inline-item">{i18n.t("mods")}: </li>
265 {this.props.moderators.map(mod => (
266 <li key={mod.moderator.id} className="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;
291 <div className="mb-2">
292 {community_view.subscribed == SubscribedType.NotSubscribed && (
294 className="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;
309 <div className="mb-2">
310 {community_view.subscribed == SubscribedType.NotSubscribed &&
313 className="btn btn-danger btn-block"
314 onClick={linkEvent(this, this.handleUnblock)}
316 {i18n.t("unblock_community")}
320 className="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 className="list-inline mb-1 text-muted font-weight-bold">
345 {amMod(Some(this.props.moderators)) && (
347 <li className="list-inline-item-action">
349 className="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 className="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 className="btn btn-link text-muted d-inline-block"
378 onClick={linkEvent(this, this.handleLeaveModTeamClick)}
383 <li className="list-inline-item-action">
385 className="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 className="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 className="btn btn-link text-muted d-inline-block"
428 onClick={linkEvent(this, this.handleModRemoveShow)}
434 className="btn btn-link text-muted d-inline-block"
435 onClick={linkEvent(this, this.handleModRemoveSubmit)}
441 className="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 className="form-group">
453 <label className="col-form-label" htmlFor="remove-reason">
459 className="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 className="form-group">
471 <button type="submit" className="btn btn-secondary">
472 {i18n.t("remove_community")}
477 {this.state.showPurgeDialog && (
478 <form onSubmit={linkEvent(this, this.handlePurgeSubmit)}>
479 <div className="form-group">
482 <div className="form-group">
483 <label className="sr-only" htmlFor="purge-reason">
489 className="form-control mr-2"
490 placeholder={i18n.t("reason")}
491 value={toUndefined(this.state.purgeReason)}
492 onInput={linkEvent(this, this.handlePurgeReasonChange)}
495 <div className="form-group">
496 {this.state.purgeLoading ? (
501 className="btn btn-secondary"
502 aria-label={i18n.t("purge_community")}
504 {i18n.t("purge_community")}
514 handleEditClick(i: Sidebar) {
515 i.setState({ showEdit: true });
518 handleEditCommunity() {
519 this.setState({ showEdit: false });
523 this.setState({ showEdit: false });
526 handleDeleteClick(i: Sidebar, event: any) {
527 event.preventDefault();
528 let deleteForm = new DeleteCommunity({
529 community_id: i.props.community_view.community.id,
530 deleted: !i.props.community_view.community.deleted,
531 auth: auth().unwrap(),
533 WebSocketService.Instance.send(wsClient.deleteCommunity(deleteForm));
536 handleShowConfirmLeaveModTeamClick(i: Sidebar) {
537 i.setState({ showConfirmLeaveModTeam: true });
540 handleLeaveModTeamClick(i: Sidebar) {
541 UserService.Instance.myUserInfo.match({
543 let form = new AddModToCommunity({
544 person_id: mui.local_user_view.person.id,
545 community_id: i.props.community_view.community.id,
547 auth: auth().unwrap(),
549 WebSocketService.Instance.send(wsClient.addModToCommunity(form));
550 i.setState({ showConfirmLeaveModTeam: false });
556 handleCancelLeaveModTeamClick(i: Sidebar) {
557 i.setState({ showConfirmLeaveModTeam: false });
560 handleUnsubscribe(i: Sidebar, event: any) {
561 event.preventDefault();
562 let community_id = i.props.community_view.community.id;
563 let form = new FollowCommunity({
566 auth: auth().unwrap(),
568 WebSocketService.Instance.send(wsClient.followCommunity(form));
571 UserService.Instance.myUserInfo.match({
573 (mui.follows = mui.follows.filter(i => i.community.id != community_id)),
578 handleSubscribe(i: Sidebar, event: any) {
579 event.preventDefault();
580 let community_id = i.props.community_view.community.id;
581 let form = new FollowCommunity({
584 auth: auth().unwrap(),
586 WebSocketService.Instance.send(wsClient.followCommunity(form));
589 UserService.Instance.myUserInfo.match({
592 community: i.props.community_view.community,
593 follower: mui.local_user_view.person,
599 get canPost(): boolean {
601 !this.props.community_view.community.posting_restricted_to_mods ||
602 amMod(Some(this.props.moderators)) ||
603 amAdmin(Some(this.props.admins))
607 handleModRemoveShow(i: Sidebar) {
608 i.setState({ showRemoveDialog: true });
611 handleModRemoveReasonChange(i: Sidebar, event: any) {
612 i.setState({ removeReason: Some(event.target.value) });
615 handleModRemoveExpiresChange(i: Sidebar, event: any) {
616 i.setState({ removeExpires: Some(event.target.value) });
619 handleModRemoveSubmit(i: Sidebar, event: any) {
620 event.preventDefault();
621 let removeForm = new RemoveCommunity({
622 community_id: i.props.community_view.community.id,
623 removed: !i.props.community_view.community.removed,
624 reason: i.state.removeReason,
625 expires: i.state.removeExpires.map(getUnixTime),
626 auth: auth().unwrap(),
628 WebSocketService.Instance.send(wsClient.removeCommunity(removeForm));
630 i.setState({ showRemoveDialog: false });
633 handlePurgeCommunityShow(i: Sidebar) {
634 i.setState({ showPurgeDialog: true, showRemoveDialog: false });
637 handlePurgeReasonChange(i: Sidebar, event: any) {
638 i.setState({ purgeReason: Some(event.target.value) });
641 handlePurgeSubmit(i: Sidebar, event: any) {
642 event.preventDefault();
644 let form = new PurgeCommunity({
645 community_id: i.props.community_view.community.id,
646 reason: i.state.purgeReason,
647 auth: auth().unwrap(),
649 WebSocketService.Instance.send(wsClient.purgeCommunity(form));
651 i.setState({ purgeLoading: true });
654 handleBlock(i: Sidebar, event: any) {
655 event.preventDefault();
656 let blockCommunityForm = new BlockCommunity({
657 community_id: i.props.community_view.community.id,
659 auth: auth().unwrap(),
661 WebSocketService.Instance.send(wsClient.blockCommunity(blockCommunityForm));
664 handleUnblock(i: Sidebar, event: any) {
665 event.preventDefault();
666 let blockCommunityForm = new BlockCommunity({
667 community_id: i.props.community_view.community.id,
669 auth: auth().unwrap(),
671 WebSocketService.Instance.send(wsClient.blockCommunity(blockCommunityForm));