1 import { myAuthRequired } from "@utils/app";
2 import { getUnixTime, hostname } from "@utils/helpers";
3 import { amAdmin, amMod, amTopMod } from "@utils/roles";
4 import { Component, InfernoNode, linkEvent } from "inferno";
5 import { T } from "inferno-i18next-dess";
6 import { Link } from "inferno-router";
10 CommunityModeratorView,
19 } from "lemmy-js-client";
20 import { mdToHtml } from "../../markdown";
21 import { I18NextService, UserService } from "../../services";
22 import { Badges } from "../common/badges";
23 import { BannerIconHeader } from "../common/banner-icon-header";
24 import { Icon, PurgeWarning, Spinner } from "../common/icon";
25 import { CommunityForm } from "../community/community-form";
26 import { CommunityLink } from "../community/community-link";
27 import { PersonListing } from "../person/person-listing";
29 interface SidebarProps {
30 community_view: CommunityView;
31 moderators: CommunityModeratorView[];
33 allLanguages: Language[];
34 siteLanguages: number[];
35 communityLanguages?: number[];
39 onDeleteCommunity(form: DeleteCommunity): void;
40 onRemoveCommunity(form: RemoveCommunity): void;
41 onLeaveModTeam(form: AddModToCommunity): void;
42 onFollowCommunity(form: FollowCommunity): void;
43 onBlockCommunity(form: BlockCommunity): void;
44 onPurgeCommunity(form: PurgeCommunity): void;
45 onEditCommunity(form: EditCommunity): void;
48 interface SidebarState {
49 removeReason?: string;
50 removeExpires?: string;
52 showRemoveDialog: boolean;
53 showPurgeDialog: boolean;
55 showConfirmLeaveModTeam: boolean;
56 deleteCommunityLoading: boolean;
57 removeCommunityLoading: boolean;
58 leaveModTeamLoading: boolean;
59 followCommunityLoading: boolean;
60 purgeCommunityLoading: boolean;
63 export class Sidebar extends Component<SidebarProps, SidebarState> {
64 state: SidebarState = {
66 showRemoveDialog: false,
67 showPurgeDialog: false,
68 showConfirmLeaveModTeam: false,
69 deleteCommunityLoading: false,
70 removeCommunityLoading: false,
71 leaveModTeamLoading: false,
72 followCommunityLoading: false,
73 purgeCommunityLoading: false,
76 constructor(props: any, context: any) {
77 super(props, context);
78 this.handleEditCancel = this.handleEditCancel.bind(this);
81 componentWillReceiveProps(
82 nextProps: Readonly<{ children?: InfernoNode } & SidebarProps>
84 if (this.props.moderators != nextProps.moderators) {
86 showConfirmLeaveModTeam: false,
90 if (this.props.community_view != nextProps.community_view) {
93 showPurgeDialog: false,
94 showRemoveDialog: false,
95 deleteCommunityLoading: false,
96 removeCommunityLoading: false,
97 leaveModTeamLoading: false,
98 followCommunityLoading: false,
99 purgeCommunityLoading: false,
106 <div className="community-sidebar">
107 {!this.state.showEdit ? (
111 community_view={this.props.community_view}
112 allLanguages={this.props.allLanguages}
113 siteLanguages={this.props.siteLanguages}
114 communityLanguages={this.props.communityLanguages}
115 onUpsertCommunity={this.props.onEditCommunity}
116 onCancel={this.handleEditCancel}
117 enableNsfw={this.props.enableNsfw}
125 const myUSerInfo = UserService.Instance.myUserInfo;
126 const { name, actor_id } = this.props.community_view.community;
128 <aside className="mb-3">
129 <div id="sidebarContainer">
130 <section id="sidebarMain" className="card border-secondary mb-3">
131 <div className="card-body">
132 {this.communityTitle()}
133 {this.props.editable && this.adminButtons()}
134 {myUSerInfo && this.subscribe()}
135 {this.canPost && this.createPost()}
136 {myUSerInfo && this.blockCommunity()}
138 <div className="alert alert-info" role="alert">
140 i18nKey="community_not_logged_in_alert"
143 instance: hostname(actor_id),
146 #<code className="user-select-all">#</code>#
152 <section id="sidebarInfo" className="card border-secondary mb-3">
153 <div className="card-body">
156 communityId={this.props.community_view.community.id}
157 counts={this.props.community_view.counts}
168 const community = this.props.community_view.community;
172 <h5 className="mb-0">
173 {this.props.showIcon && !community.removed && (
174 <BannerIconHeader icon={community.icon} banner={community.banner} />
176 <span className="me-2">
177 <CommunityLink community={community} hideAvatar />
179 {community.removed && (
180 <small className="me-2 text-muted fst-italic">
181 {I18NextService.i18n.t("removed")}
184 {community.deleted && (
185 <small className="me-2 text-muted fst-italic">
186 {I18NextService.i18n.t("deleted")}
190 <small className="me-2 text-muted fst-italic">
191 {I18NextService.i18n.t("nsfw")}
196 community={community}
208 <ul className="list-inline small">
209 <li className="list-inline-item">{I18NextService.i18n.t("mods")}: </li>
210 {this.props.moderators.map(mod => (
211 <li key={mod.moderator.id} className="list-inline-item">
212 <PersonListing person={mod.moderator} />
220 const cv = this.props.community_view;
223 className={`btn btn-secondary d-block mb-2 w-100 ${
224 cv.community.deleted || cv.community.removed ? "no-click" : ""
226 to={`/create_post?communityId=${cv.community.id}`}
228 {I18NextService.i18n.t("create_a_post")}
234 const community_view = this.props.community_view;
236 if (community_view.subscribed === "NotSubscribed") {
239 className="btn btn-secondary d-block mb-2 w-100"
240 onClick={linkEvent(this, this.handleFollowCommunity)}
242 {this.state.followCommunityLoading ? (
245 I18NextService.i18n.t("subscribe")
251 if (community_view.subscribed === "Subscribed") {
254 className="btn btn-secondary d-block mb-2 w-100"
255 onClick={linkEvent(this, this.handleUnfollowCommunity)}
257 {this.state.followCommunityLoading ? (
261 <Icon icon="check" classes="icon-inline me-1" />
262 {I18NextService.i18n.t("joined")}
269 if (community_view.subscribed === "Pending") {
272 className="btn btn-warning d-block mb-2 w-100"
273 onClick={linkEvent(this, this.handleUnfollowCommunity)}
275 {this.state.followCommunityLoading ? (
278 I18NextService.i18n.t("subscribe_pending")
286 const { subscribed, blocked } = this.props.community_view;
289 subscribed === "NotSubscribed" && (
291 className="btn btn-danger d-block mb-2 w-100"
292 onClick={linkEvent(this, this.handleBlockCommunity)}
294 {I18NextService.i18n.t(
295 blocked ? "unblock_community" : "block_community"
303 const desc = this.props.community_view.community.description;
306 <div className="md-div" dangerouslySetInnerHTML={mdToHtml(desc)} />
312 const community_view = this.props.community_view;
315 <ul className="list-inline mb-1 text-muted fw-bold">
316 {amMod(this.props.moderators) && (
318 <li className="list-inline-item-action">
320 className="btn btn-link text-muted d-inline-block"
321 onClick={linkEvent(this, this.handleEditClick)}
322 data-tippy-content={I18NextService.i18n.t("edit")}
323 aria-label={I18NextService.i18n.t("edit")}
325 <Icon icon="edit" classes="icon-inline" />
328 {!amTopMod(this.props.moderators) &&
329 (!this.state.showConfirmLeaveModTeam ? (
330 <li className="list-inline-item-action">
332 className="btn btn-link text-muted d-inline-block"
335 this.handleShowConfirmLeaveModTeamClick
338 {I18NextService.i18n.t("leave_mod_team")}
343 <li className="list-inline-item-action">
344 {I18NextService.i18n.t("are_you_sure")}
346 <li className="list-inline-item-action">
348 className="btn btn-link text-muted d-inline-block"
349 onClick={linkEvent(this, this.handleLeaveModTeam)}
351 {I18NextService.i18n.t("yes")}
354 <li className="list-inline-item-action">
356 className="btn btn-link text-muted d-inline-block"
359 this.handleCancelLeaveModTeamClick
362 {I18NextService.i18n.t("no")}
367 {amTopMod(this.props.moderators) && (
368 <li className="list-inline-item-action">
370 className="btn btn-link text-muted d-inline-block"
371 onClick={linkEvent(this, this.handleDeleteCommunity)}
373 !community_view.community.deleted
374 ? I18NextService.i18n.t("delete")
375 : I18NextService.i18n.t("restore")
378 !community_view.community.deleted
379 ? I18NextService.i18n.t("delete")
380 : I18NextService.i18n.t("restore")
383 {this.state.deleteCommunityLoading ? (
388 classes={`icon-inline ${
389 community_view.community.deleted && "text-danger"
399 <li className="list-inline-item">
400 {!this.props.community_view.community.removed ? (
402 className="btn btn-link text-muted d-inline-block"
403 onClick={linkEvent(this, this.handleModRemoveShow)}
405 {I18NextService.i18n.t("remove")}
409 className="btn btn-link text-muted d-inline-block"
410 onClick={linkEvent(this, this.handleRemoveCommunity)}
412 {this.state.removeCommunityLoading ? (
415 I18NextService.i18n.t("restore")
420 className="btn btn-link text-muted d-inline-block"
421 onClick={linkEvent(this, this.handlePurgeCommunityShow)}
422 aria-label={I18NextService.i18n.t("purge_community")}
424 {I18NextService.i18n.t("purge_community")}
429 {this.state.showRemoveDialog && (
430 <form onSubmit={linkEvent(this, this.handleRemoveCommunity)}>
431 <div className="input-group mb-3">
432 <label className="col-form-label" htmlFor="remove-reason">
433 {I18NextService.i18n.t("reason")}
438 className="form-control me-2"
439 placeholder={I18NextService.i18n.t("optional")}
440 value={this.state.removeReason}
441 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
444 {/* TODO hold off on expires for now */}
445 {/* <div class="mb-3 row"> */}
446 {/* <label class="col-form-label">Expires</label> */}
447 {/* <input type="date" class="form-control me-2" placeholder={I18NextService.i18n.t('expires')} value={this.state.removeExpires} onInput={linkEvent(this, this.handleModRemoveExpiresChange)} /> */}
449 <div className="input-group mb-3">
450 <button type="submit" className="btn btn-secondary">
451 {this.state.removeCommunityLoading ? (
454 I18NextService.i18n.t("remove_community")
460 {this.state.showPurgeDialog && (
461 <form onSubmit={linkEvent(this, this.handlePurgeCommunity)}>
462 <div className="input-group mb-3">
465 <div className="input-group mb-3">
466 <label className="visually-hidden" htmlFor="purge-reason">
467 {I18NextService.i18n.t("reason")}
472 className="form-control me-2"
473 placeholder={I18NextService.i18n.t("reason")}
474 value={this.state.purgeReason}
475 onInput={linkEvent(this, this.handlePurgeReasonChange)}
478 <div className="input-group mb-3">
479 {this.state.purgeCommunityLoading ? (
484 className="btn btn-secondary"
485 aria-label={I18NextService.i18n.t("purge_community")}
487 {I18NextService.i18n.t("purge_community")}
497 handleEditClick(i: Sidebar) {
498 i.setState({ showEdit: true });
502 this.setState({ showEdit: false });
505 handleShowConfirmLeaveModTeamClick(i: Sidebar) {
506 i.setState({ showConfirmLeaveModTeam: true });
509 handleCancelLeaveModTeamClick(i: Sidebar) {
510 i.setState({ showConfirmLeaveModTeam: false });
513 get canPost(): boolean {
515 !this.props.community_view.community.posting_restricted_to_mods ||
516 amMod(this.props.moderators) ||
521 handleModRemoveShow(i: Sidebar) {
522 i.setState({ showRemoveDialog: true });
525 handleModRemoveReasonChange(i: Sidebar, event: any) {
526 i.setState({ removeReason: event.target.value });
529 handleModRemoveExpiresChange(i: Sidebar, event: any) {
530 i.setState({ removeExpires: event.target.value });
533 handlePurgeCommunityShow(i: Sidebar) {
534 i.setState({ showPurgeDialog: true, showRemoveDialog: false });
537 handlePurgeReasonChange(i: Sidebar, event: any) {
538 i.setState({ purgeReason: event.target.value });
541 // TODO Do we need two of these?
542 handleUnfollowCommunity(i: Sidebar) {
543 i.setState({ followCommunityLoading: true });
544 i.props.onFollowCommunity({
545 community_id: i.props.community_view.community.id,
547 auth: myAuthRequired(),
551 handleFollowCommunity(i: Sidebar) {
552 i.setState({ followCommunityLoading: true });
553 i.props.onFollowCommunity({
554 community_id: i.props.community_view.community.id,
556 auth: myAuthRequired(),
560 handleBlockCommunity(i: Sidebar) {
561 const { community, blocked } = i.props.community_view;
563 i.props.onBlockCommunity({
564 community_id: community.id,
566 auth: myAuthRequired(),
570 handleLeaveModTeam(i: Sidebar) {
571 const myId = UserService.Instance.myUserInfo?.local_user_view.person.id;
573 i.setState({ leaveModTeamLoading: true });
574 i.props.onLeaveModTeam({
575 community_id: i.props.community_view.community.id,
578 auth: myAuthRequired(),
583 handleDeleteCommunity(i: Sidebar) {
584 i.setState({ deleteCommunityLoading: true });
585 i.props.onDeleteCommunity({
586 community_id: i.props.community_view.community.id,
587 deleted: !i.props.community_view.community.deleted,
588 auth: myAuthRequired(),
592 handleRemoveCommunity(i: Sidebar, event: any) {
593 event.preventDefault();
594 i.setState({ removeCommunityLoading: true });
595 i.props.onRemoveCommunity({
596 community_id: i.props.community_view.community.id,
597 removed: !i.props.community_view.community.removed,
598 reason: i.state.removeReason,
599 expires: getUnixTime(i.state.removeExpires), // TODO fix this
600 auth: myAuthRequired(),
604 handlePurgeCommunity(i: Sidebar, event: any) {
605 event.preventDefault();
606 i.setState({ purgeCommunityLoading: true });
607 i.props.onPurgeCommunity({
608 community_id: i.props.community_view.community.id,
609 reason: i.state.purgeReason,
610 auth: myAuthRequired(),