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;
169 const subscribed = this.props.community_view.subscribed;
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 {subscribed === "Subscribed" && (
181 className="btn btn-secondary btn-sm me-2"
182 onClick={linkEvent(this, this.handleUnfollowCommunity)}
184 {this.state.followCommunityLoading ? (
188 <Icon icon="check" classes="icon-inline text-success me-1" />
189 {I18NextService.i18n.t("joined")}
194 {subscribed === "Pending" && (
196 className="btn btn-warning me-2"
197 onClick={linkEvent(this, this.handleUnfollowCommunity)}
199 {this.state.followCommunityLoading ? (
202 I18NextService.i18n.t("subscribe_pending")
206 {community.removed && (
207 <small className="me-2 text-muted fst-italic">
208 {I18NextService.i18n.t("removed")}
211 {community.deleted && (
212 <small className="me-2 text-muted fst-italic">
213 {I18NextService.i18n.t("deleted")}
217 <small className="me-2 text-muted fst-italic">
218 {I18NextService.i18n.t("nsfw")}
223 community={community}
235 <ul className="list-inline small">
236 <li className="list-inline-item">{I18NextService.i18n.t("mods")}: </li>
237 {this.props.moderators.map(mod => (
238 <li key={mod.moderator.id} className="list-inline-item">
239 <PersonListing person={mod.moderator} />
247 const cv = this.props.community_view;
250 className={`btn btn-secondary d-block mb-2 w-100 ${
251 cv.community.deleted || cv.community.removed ? "no-click" : ""
253 to={`/create_post?communityId=${cv.community.id}`}
255 {I18NextService.i18n.t("create_a_post")}
261 const community_view = this.props.community_view;
263 community_view.subscribed === "NotSubscribed" && (
265 className="btn btn-secondary d-block mb-2 w-100"
266 onClick={linkEvent(this, this.handleFollowCommunity)}
268 {this.state.followCommunityLoading ? (
271 I18NextService.i18n.t("subscribe")
279 const { subscribed, blocked } = this.props.community_view;
282 subscribed === "NotSubscribed" && (
284 className="btn btn-danger d-block mb-2 w-100"
285 onClick={linkEvent(this, this.handleBlockCommunity)}
287 {I18NextService.i18n.t(
288 blocked ? "unblock_community" : "block_community"
296 const desc = this.props.community_view.community.description;
299 <div className="md-div" dangerouslySetInnerHTML={mdToHtml(desc)} />
305 const community_view = this.props.community_view;
308 <ul className="list-inline mb-1 text-muted fw-bold">
309 {amMod(this.props.moderators) && (
311 <li className="list-inline-item-action">
313 className="btn btn-link text-muted d-inline-block"
314 onClick={linkEvent(this, this.handleEditClick)}
315 data-tippy-content={I18NextService.i18n.t("edit")}
316 aria-label={I18NextService.i18n.t("edit")}
318 <Icon icon="edit" classes="icon-inline" />
321 {!amTopMod(this.props.moderators) &&
322 (!this.state.showConfirmLeaveModTeam ? (
323 <li className="list-inline-item-action">
325 className="btn btn-link text-muted d-inline-block"
328 this.handleShowConfirmLeaveModTeamClick
331 {I18NextService.i18n.t("leave_mod_team")}
336 <li className="list-inline-item-action">
337 {I18NextService.i18n.t("are_you_sure")}
339 <li className="list-inline-item-action">
341 className="btn btn-link text-muted d-inline-block"
342 onClick={linkEvent(this, this.handleLeaveModTeam)}
344 {I18NextService.i18n.t("yes")}
347 <li className="list-inline-item-action">
349 className="btn btn-link text-muted d-inline-block"
352 this.handleCancelLeaveModTeamClick
355 {I18NextService.i18n.t("no")}
360 {amTopMod(this.props.moderators) && (
361 <li className="list-inline-item-action">
363 className="btn btn-link text-muted d-inline-block"
364 onClick={linkEvent(this, this.handleDeleteCommunity)}
366 !community_view.community.deleted
367 ? I18NextService.i18n.t("delete")
368 : I18NextService.i18n.t("restore")
371 !community_view.community.deleted
372 ? I18NextService.i18n.t("delete")
373 : I18NextService.i18n.t("restore")
376 {this.state.deleteCommunityLoading ? (
381 classes={`icon-inline ${
382 community_view.community.deleted && "text-danger"
392 <li className="list-inline-item">
393 {!this.props.community_view.community.removed ? (
395 className="btn btn-link text-muted d-inline-block"
396 onClick={linkEvent(this, this.handleModRemoveShow)}
398 {I18NextService.i18n.t("remove")}
402 className="btn btn-link text-muted d-inline-block"
403 onClick={linkEvent(this, this.handleRemoveCommunity)}
405 {this.state.removeCommunityLoading ? (
408 I18NextService.i18n.t("restore")
413 className="btn btn-link text-muted d-inline-block"
414 onClick={linkEvent(this, this.handlePurgeCommunityShow)}
415 aria-label={I18NextService.i18n.t("purge_community")}
417 {I18NextService.i18n.t("purge_community")}
422 {this.state.showRemoveDialog && (
423 <form onSubmit={linkEvent(this, this.handleRemoveCommunity)}>
424 <div className="input-group mb-3">
425 <label className="col-form-label" htmlFor="remove-reason">
426 {I18NextService.i18n.t("reason")}
431 className="form-control me-2"
432 placeholder={I18NextService.i18n.t("optional")}
433 value={this.state.removeReason}
434 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
437 {/* TODO hold off on expires for now */}
438 {/* <div class="mb-3 row"> */}
439 {/* <label class="col-form-label">Expires</label> */}
440 {/* <input type="date" class="form-control me-2" placeholder={I18NextService.i18n.t('expires')} value={this.state.removeExpires} onInput={linkEvent(this, this.handleModRemoveExpiresChange)} /> */}
442 <div className="input-group mb-3">
443 <button type="submit" className="btn btn-secondary">
444 {this.state.removeCommunityLoading ? (
447 I18NextService.i18n.t("remove_community")
453 {this.state.showPurgeDialog && (
454 <form onSubmit={linkEvent(this, this.handlePurgeCommunity)}>
455 <div className="input-group mb-3">
458 <div className="input-group mb-3">
459 <label className="visually-hidden" htmlFor="purge-reason">
460 {I18NextService.i18n.t("reason")}
465 className="form-control me-2"
466 placeholder={I18NextService.i18n.t("reason")}
467 value={this.state.purgeReason}
468 onInput={linkEvent(this, this.handlePurgeReasonChange)}
471 <div className="input-group mb-3">
472 {this.state.purgeCommunityLoading ? (
477 className="btn btn-secondary"
478 aria-label={I18NextService.i18n.t("purge_community")}
480 {I18NextService.i18n.t("purge_community")}
490 handleEditClick(i: Sidebar) {
491 i.setState({ showEdit: true });
495 this.setState({ showEdit: false });
498 handleShowConfirmLeaveModTeamClick(i: Sidebar) {
499 i.setState({ showConfirmLeaveModTeam: true });
502 handleCancelLeaveModTeamClick(i: Sidebar) {
503 i.setState({ showConfirmLeaveModTeam: false });
506 get canPost(): boolean {
508 !this.props.community_view.community.posting_restricted_to_mods ||
509 amMod(this.props.moderators) ||
514 handleModRemoveShow(i: Sidebar) {
515 i.setState({ showRemoveDialog: true });
518 handleModRemoveReasonChange(i: Sidebar, event: any) {
519 i.setState({ removeReason: event.target.value });
522 handleModRemoveExpiresChange(i: Sidebar, event: any) {
523 i.setState({ removeExpires: event.target.value });
526 handlePurgeCommunityShow(i: Sidebar) {
527 i.setState({ showPurgeDialog: true, showRemoveDialog: false });
530 handlePurgeReasonChange(i: Sidebar, event: any) {
531 i.setState({ purgeReason: event.target.value });
534 // TODO Do we need two of these?
535 handleUnfollowCommunity(i: Sidebar) {
536 i.setState({ followCommunityLoading: true });
537 i.props.onFollowCommunity({
538 community_id: i.props.community_view.community.id,
540 auth: myAuthRequired(),
544 handleFollowCommunity(i: Sidebar) {
545 i.setState({ followCommunityLoading: true });
546 i.props.onFollowCommunity({
547 community_id: i.props.community_view.community.id,
549 auth: myAuthRequired(),
553 handleBlockCommunity(i: Sidebar) {
554 const { community, blocked } = i.props.community_view;
556 i.props.onBlockCommunity({
557 community_id: community.id,
559 auth: myAuthRequired(),
563 handleLeaveModTeam(i: Sidebar) {
564 const myId = UserService.Instance.myUserInfo?.local_user_view.person.id;
566 i.setState({ leaveModTeamLoading: true });
567 i.props.onLeaveModTeam({
568 community_id: i.props.community_view.community.id,
571 auth: myAuthRequired(),
576 handleDeleteCommunity(i: Sidebar) {
577 i.setState({ deleteCommunityLoading: true });
578 i.props.onDeleteCommunity({
579 community_id: i.props.community_view.community.id,
580 deleted: !i.props.community_view.community.deleted,
581 auth: myAuthRequired(),
585 handleRemoveCommunity(i: Sidebar, event: any) {
586 event.preventDefault();
587 i.setState({ removeCommunityLoading: true });
588 i.props.onRemoveCommunity({
589 community_id: i.props.community_view.community.id,
590 removed: !i.props.community_view.community.removed,
591 reason: i.state.removeReason,
592 expires: getUnixTime(i.state.removeExpires), // TODO fix this
593 auth: myAuthRequired(),
597 handlePurgeCommunity(i: Sidebar, event: any) {
598 event.preventDefault();
599 i.setState({ purgeCommunityLoading: true });
600 i.props.onPurgeCommunity({
601 community_id: i.props.community_view.community.id,
602 reason: i.state.purgeReason,
603 auth: myAuthRequired(),