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;
264 {community_view.subscribed == "NotSubscribed" && (
266 className="btn btn-secondary d-block mb-2 w-100"
267 onClick={linkEvent(this, this.handleFollowCommunity)}
269 {this.state.followCommunityLoading ? (
272 I18NextService.i18n.t("subscribe")
281 const { subscribed, blocked } = this.props.community_view;
285 {subscribed == "NotSubscribed" && (
287 className="btn btn-danger d-block mb-2 w-100"
288 onClick={linkEvent(this, this.handleBlockCommunity)}
290 {I18NextService.i18n.t(
291 blocked ? "unblock_community" : "block_community"
300 const desc = this.props.community_view.community.description;
303 <div className="md-div" dangerouslySetInnerHTML={mdToHtml(desc)} />
309 const community_view = this.props.community_view;
312 <ul className="list-inline mb-1 text-muted fw-bold">
313 {amMod(this.props.moderators) && (
315 <li className="list-inline-item-action">
317 className="btn btn-link text-muted d-inline-block"
318 onClick={linkEvent(this, this.handleEditClick)}
319 data-tippy-content={I18NextService.i18n.t("edit")}
320 aria-label={I18NextService.i18n.t("edit")}
322 <Icon icon="edit" classes="icon-inline" />
325 {!amTopMod(this.props.moderators) &&
326 (!this.state.showConfirmLeaveModTeam ? (
327 <li className="list-inline-item-action">
329 className="btn btn-link text-muted d-inline-block"
332 this.handleShowConfirmLeaveModTeamClick
335 {I18NextService.i18n.t("leave_mod_team")}
340 <li className="list-inline-item-action">
341 {I18NextService.i18n.t("are_you_sure")}
343 <li className="list-inline-item-action">
345 className="btn btn-link text-muted d-inline-block"
346 onClick={linkEvent(this, this.handleLeaveModTeam)}
348 {I18NextService.i18n.t("yes")}
351 <li className="list-inline-item-action">
353 className="btn btn-link text-muted d-inline-block"
356 this.handleCancelLeaveModTeamClick
359 {I18NextService.i18n.t("no")}
364 {amTopMod(this.props.moderators) && (
365 <li className="list-inline-item-action">
367 className="btn btn-link text-muted d-inline-block"
368 onClick={linkEvent(this, this.handleDeleteCommunity)}
370 !community_view.community.deleted
371 ? I18NextService.i18n.t("delete")
372 : I18NextService.i18n.t("restore")
375 !community_view.community.deleted
376 ? I18NextService.i18n.t("delete")
377 : I18NextService.i18n.t("restore")
380 {this.state.deleteCommunityLoading ? (
385 classes={`icon-inline ${
386 community_view.community.deleted && "text-danger"
396 <li className="list-inline-item">
397 {!this.props.community_view.community.removed ? (
399 className="btn btn-link text-muted d-inline-block"
400 onClick={linkEvent(this, this.handleModRemoveShow)}
402 {I18NextService.i18n.t("remove")}
406 className="btn btn-link text-muted d-inline-block"
407 onClick={linkEvent(this, this.handleRemoveCommunity)}
409 {this.state.removeCommunityLoading ? (
412 I18NextService.i18n.t("restore")
417 className="btn btn-link text-muted d-inline-block"
418 onClick={linkEvent(this, this.handlePurgeCommunityShow)}
419 aria-label={I18NextService.i18n.t("purge_community")}
421 {I18NextService.i18n.t("purge_community")}
426 {this.state.showRemoveDialog && (
427 <form onSubmit={linkEvent(this, this.handleRemoveCommunity)}>
428 <div className="input-group mb-3">
429 <label className="col-form-label" htmlFor="remove-reason">
430 {I18NextService.i18n.t("reason")}
435 className="form-control me-2"
436 placeholder={I18NextService.i18n.t("optional")}
437 value={this.state.removeReason}
438 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
441 {/* TODO hold off on expires for now */}
442 {/* <div class="mb-3 row"> */}
443 {/* <label class="col-form-label">Expires</label> */}
444 {/* <input type="date" class="form-control me-2" placeholder={I18NextService.i18n.t('expires')} value={this.state.removeExpires} onInput={linkEvent(this, this.handleModRemoveExpiresChange)} /> */}
446 <div className="input-group mb-3">
447 <button type="submit" className="btn btn-secondary">
448 {this.state.removeCommunityLoading ? (
451 I18NextService.i18n.t("remove_community")
457 {this.state.showPurgeDialog && (
458 <form onSubmit={linkEvent(this, this.handlePurgeCommunity)}>
459 <div className="input-group mb-3">
462 <div className="input-group mb-3">
463 <label className="visually-hidden" htmlFor="purge-reason">
464 {I18NextService.i18n.t("reason")}
469 className="form-control me-2"
470 placeholder={I18NextService.i18n.t("reason")}
471 value={this.state.purgeReason}
472 onInput={linkEvent(this, this.handlePurgeReasonChange)}
475 <div className="input-group mb-3">
476 {this.state.purgeCommunityLoading ? (
481 className="btn btn-secondary"
482 aria-label={I18NextService.i18n.t("purge_community")}
484 {I18NextService.i18n.t("purge_community")}
494 handleEditClick(i: Sidebar) {
495 i.setState({ showEdit: true });
499 this.setState({ showEdit: false });
502 handleShowConfirmLeaveModTeamClick(i: Sidebar) {
503 i.setState({ showConfirmLeaveModTeam: true });
506 handleCancelLeaveModTeamClick(i: Sidebar) {
507 i.setState({ showConfirmLeaveModTeam: false });
510 get canPost(): boolean {
512 !this.props.community_view.community.posting_restricted_to_mods ||
513 amMod(this.props.moderators) ||
518 handleModRemoveShow(i: Sidebar) {
519 i.setState({ showRemoveDialog: true });
522 handleModRemoveReasonChange(i: Sidebar, event: any) {
523 i.setState({ removeReason: event.target.value });
526 handleModRemoveExpiresChange(i: Sidebar, event: any) {
527 i.setState({ removeExpires: event.target.value });
530 handlePurgeCommunityShow(i: Sidebar) {
531 i.setState({ showPurgeDialog: true, showRemoveDialog: false });
534 handlePurgeReasonChange(i: Sidebar, event: any) {
535 i.setState({ purgeReason: event.target.value });
538 // TODO Do we need two of these?
539 handleUnfollowCommunity(i: Sidebar) {
540 i.setState({ followCommunityLoading: true });
541 i.props.onFollowCommunity({
542 community_id: i.props.community_view.community.id,
544 auth: myAuthRequired(),
548 handleFollowCommunity(i: Sidebar) {
549 i.setState({ followCommunityLoading: true });
550 i.props.onFollowCommunity({
551 community_id: i.props.community_view.community.id,
553 auth: myAuthRequired(),
557 handleBlockCommunity(i: Sidebar) {
558 const { community, blocked } = i.props.community_view;
560 i.props.onBlockCommunity({
561 community_id: community.id,
563 auth: myAuthRequired(),
567 handleLeaveModTeam(i: Sidebar) {
568 const myId = UserService.Instance.myUserInfo?.local_user_view.person.id;
570 i.setState({ leaveModTeamLoading: true });
571 i.props.onLeaveModTeam({
572 community_id: i.props.community_view.community.id,
575 auth: myAuthRequired(),
580 handleDeleteCommunity(i: Sidebar) {
581 i.setState({ deleteCommunityLoading: true });
582 i.props.onDeleteCommunity({
583 community_id: i.props.community_view.community.id,
584 deleted: !i.props.community_view.community.deleted,
585 auth: myAuthRequired(),
589 handleRemoveCommunity(i: Sidebar, event: any) {
590 event.preventDefault();
591 i.setState({ removeCommunityLoading: true });
592 i.props.onRemoveCommunity({
593 community_id: i.props.community_view.community.id,
594 removed: !i.props.community_view.community.removed,
595 reason: i.state.removeReason,
596 expires: getUnixTime(i.state.removeExpires), // TODO fix this
597 auth: myAuthRequired(),
601 handlePurgeCommunity(i: Sidebar, event: any) {
602 event.preventDefault();
603 i.setState({ purgeCommunityLoading: true });
604 i.props.onPurgeCommunity({
605 community_id: i.props.community_view.community.id,
606 reason: i.state.purgeReason,
607 auth: myAuthRequired(),