1 import { Component, InfernoNode, linkEvent } from "inferno";
2 import { T } from "inferno-i18next-dess";
3 import { Link } from "inferno-router";
7 CommunityModeratorView,
16 } from "lemmy-js-client";
17 import { i18n } from "../../i18next";
18 import { UserService } 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[];
39 allLanguages: Language[];
40 siteLanguages: number[];
41 communityLanguages?: number[];
46 onDeleteCommunity(form: DeleteCommunity): void;
47 onRemoveCommunity(form: RemoveCommunity): void;
48 onLeaveModTeam(form: AddModToCommunity): void;
49 onFollowCommunity(form: FollowCommunity): void;
50 onBlockCommunity(form: BlockCommunity): void;
51 onPurgeCommunity(form: PurgeCommunity): void;
52 onEditCommunity(form: EditCommunity): void;
55 interface SidebarState {
56 removeReason?: string;
57 removeExpires?: string;
59 showRemoveDialog: boolean;
60 showPurgeDialog: boolean;
62 showConfirmLeaveModTeam: boolean;
63 deleteCommunityLoading: boolean;
64 removeCommunityLoading: boolean;
65 leaveModTeamLoading: boolean;
66 followCommunityLoading: boolean;
67 blockCommunityLoading: boolean;
68 purgeCommunityLoading: boolean;
71 export class Sidebar extends Component<SidebarProps, SidebarState> {
72 state: SidebarState = {
74 showRemoveDialog: false,
75 showPurgeDialog: false,
76 showConfirmLeaveModTeam: false,
77 deleteCommunityLoading: false,
78 removeCommunityLoading: false,
79 leaveModTeamLoading: false,
80 followCommunityLoading: false,
81 blockCommunityLoading: false,
82 purgeCommunityLoading: false,
85 constructor(props: any, context: any) {
86 super(props, context);
87 this.handleEditCancel = this.handleEditCancel.bind(this);
90 componentWillReceiveProps(
91 nextProps: Readonly<{ children?: InfernoNode } & SidebarProps>
93 if (this.props.moderators != nextProps.moderators) {
95 showConfirmLeaveModTeam: false,
99 if (this.props.community_view != nextProps.community_view) {
102 showPurgeDialog: false,
103 showRemoveDialog: false,
104 deleteCommunityLoading: false,
105 removeCommunityLoading: false,
106 leaveModTeamLoading: false,
107 followCommunityLoading: false,
108 blockCommunityLoading: false,
109 purgeCommunityLoading: false,
117 {!this.state.showEdit ? (
121 community_view={this.props.community_view}
122 allLanguages={this.props.allLanguages}
123 siteLanguages={this.props.siteLanguages}
124 communityLanguages={this.props.communityLanguages}
125 onUpsertCommunity={this.props.onEditCommunity}
126 onCancel={this.handleEditCancel}
127 enableNsfw={this.props.enableNsfw}
135 const myUSerInfo = UserService.Instance.myUserInfo;
136 const { name, actor_id } = this.props.community_view.community;
138 <div id="sidebarContainer">
139 <div id="sidebarMain" className="card border-secondary mb-3">
140 <div className="card-body">
141 {this.communityTitle()}
142 {this.props.editable && this.adminButtons()}
143 {myUSerInfo && this.subscribe()}
144 {this.canPost && this.createPost()}
145 {myUSerInfo && this.blockCommunity()}
147 <div className="alert alert-info" role="alert">
149 i18nKey="community_not_logged_in_alert"
152 instance: hostname(actor_id),
155 #<code className="user-select-all">#</code>#
161 <div id="sidebarInfo" className="card border-secondary mb-3">
162 <div className="card-body">
173 const community = this.props.community_view.community;
174 const subscribed = this.props.community_view.subscribed;
177 <h5 className="mb-0">
178 {this.props.showIcon && !community.removed && (
179 <BannerIconHeader icon={community.icon} banner={community.banner} />
181 <span className="mr-2">
182 <CommunityLink community={community} hideAvatar />
184 {subscribed === "Subscribed" && (
186 className="btn btn-secondary btn-sm mr-2"
187 onClick={linkEvent(this, this.handleUnfollowCommunity)}
189 {this.state.followCommunityLoading ? (
193 <Icon icon="check" classes="icon-inline text-success mr-1" />
199 {subscribed === "Pending" && (
201 className="btn btn-warning mr-2"
202 onClick={linkEvent(this, this.handleUnfollowCommunity)}
204 {this.state.followCommunityLoading ? (
207 i18n.t("subscribe_pending")
211 {community.removed && (
212 <small className="mr-2 text-muted font-italic">
216 {community.deleted && (
217 <small className="mr-2 text-muted font-italic">
222 <small className="mr-2 text-muted font-italic">
228 community={community}
239 const community_view = this.props.community_view;
240 const counts = community_view.counts;
242 <ul className="my-1 list-inline">
243 <li className="list-inline-item badge badge-secondary">
244 {i18n.t("number_online", {
245 count: this.props.online,
246 formattedCount: numToSI(this.props.online),
250 className="list-inline-item badge badge-secondary pointer"
251 data-tippy-content={i18n.t("active_users_in_the_last_day", {
252 count: Number(counts.users_active_day),
253 formattedCount: numToSI(counts.users_active_day),
256 {i18n.t("number_of_users", {
257 count: Number(counts.users_active_day),
258 formattedCount: numToSI(counts.users_active_day),
263 className="list-inline-item badge badge-secondary pointer"
264 data-tippy-content={i18n.t("active_users_in_the_last_week", {
265 count: Number(counts.users_active_week),
266 formattedCount: numToSI(counts.users_active_week),
269 {i18n.t("number_of_users", {
270 count: Number(counts.users_active_week),
271 formattedCount: numToSI(counts.users_active_week),
276 className="list-inline-item badge badge-secondary pointer"
277 data-tippy-content={i18n.t("active_users_in_the_last_month", {
278 count: Number(counts.users_active_month),
279 formattedCount: numToSI(counts.users_active_month),
282 {i18n.t("number_of_users", {
283 count: Number(counts.users_active_month),
284 formattedCount: numToSI(counts.users_active_month),
289 className="list-inline-item badge badge-secondary pointer"
290 data-tippy-content={i18n.t("active_users_in_the_last_six_months", {
291 count: Number(counts.users_active_half_year),
292 formattedCount: numToSI(counts.users_active_half_year),
295 {i18n.t("number_of_users", {
296 count: Number(counts.users_active_half_year),
297 formattedCount: numToSI(counts.users_active_half_year),
299 / {i18n.t("number_of_months", { count: 6, formattedCount: 6 })}
301 <li className="list-inline-item badge badge-secondary">
302 {i18n.t("number_of_subscribers", {
303 count: Number(counts.subscribers),
304 formattedCount: numToSI(counts.subscribers),
307 <li className="list-inline-item badge badge-secondary">
308 {i18n.t("number_of_posts", {
309 count: Number(counts.posts),
310 formattedCount: numToSI(counts.posts),
313 <li className="list-inline-item badge badge-secondary">
314 {i18n.t("number_of_comments", {
315 count: Number(counts.comments),
316 formattedCount: numToSI(counts.comments),
319 <li className="list-inline-item">
321 className="badge badge-primary"
322 to={`/modlog/${this.props.community_view.community.id}`}
333 <ul className="list-inline small">
334 <li className="list-inline-item">{i18n.t("mods")}: </li>
335 {this.props.moderators.map(mod => (
336 <li key={mod.moderator.id} className="list-inline-item">
337 <PersonListing person={mod.moderator} />
345 const cv = this.props.community_view;
348 className={`btn btn-secondary btn-block mb-2 ${
349 cv.community.deleted || cv.community.removed ? "no-click" : ""
351 to={`/create_post?communityId=${cv.community.id}`}
353 {i18n.t("create_a_post")}
359 const community_view = this.props.community_view;
361 <div className="mb-2">
362 {community_view.subscribed == "NotSubscribed" && (
364 className="btn btn-secondary btn-block"
365 onClick={linkEvent(this, this.handleFollowCommunity)}
367 {this.state.followCommunityLoading ? (
379 const community_view = this.props.community_view;
380 const blocked = this.props.community_view.blocked;
383 <div className="mb-2">
384 {community_view.subscribed == "NotSubscribed" &&
387 className="btn btn-danger btn-block"
388 onClick={linkEvent(this, this.handleBlockCommunity)}
390 {this.state.blockCommunityLoading ? (
393 i18n.t("unblock_community")
398 className="btn btn-danger btn-block"
399 onClick={linkEvent(this, this.handleBlockCommunity)}
401 {this.state.blockCommunityLoading ? (
404 i18n.t("block_community")
413 const desc = this.props.community_view.community.description;
416 <div className="md-div" dangerouslySetInnerHTML={mdToHtml(desc)} />
422 const community_view = this.props.community_view;
425 <ul className="list-inline mb-1 text-muted font-weight-bold">
426 {amMod(this.props.moderators) && (
428 <li className="list-inline-item-action">
430 className="btn btn-link text-muted d-inline-block"
431 onClick={linkEvent(this, this.handleEditClick)}
432 data-tippy-content={i18n.t("edit")}
433 aria-label={i18n.t("edit")}
435 <Icon icon="edit" classes="icon-inline" />
438 {!amTopMod(this.props.moderators) &&
439 (!this.state.showConfirmLeaveModTeam ? (
440 <li className="list-inline-item-action">
442 className="btn btn-link text-muted d-inline-block"
445 this.handleShowConfirmLeaveModTeamClick
448 {i18n.t("leave_mod_team")}
453 <li className="list-inline-item-action">
454 {i18n.t("are_you_sure")}
456 <li className="list-inline-item-action">
458 className="btn btn-link text-muted d-inline-block"
459 onClick={linkEvent(this, this.handleLeaveModTeam)}
464 <li className="list-inline-item-action">
466 className="btn btn-link text-muted d-inline-block"
469 this.handleCancelLeaveModTeamClick
477 {amTopMod(this.props.moderators) && (
478 <li className="list-inline-item-action">
480 className="btn btn-link text-muted d-inline-block"
481 onClick={linkEvent(this, this.handleDeleteCommunity)}
483 !community_view.community.deleted
488 !community_view.community.deleted
493 {this.state.deleteCommunityLoading ? (
498 classes={`icon-inline ${
499 community_view.community.deleted && "text-danger"
509 <li className="list-inline-item">
510 {!this.props.community_view.community.removed ? (
512 className="btn btn-link text-muted d-inline-block"
513 onClick={linkEvent(this, this.handleModRemoveShow)}
519 className="btn btn-link text-muted d-inline-block"
520 onClick={linkEvent(this, this.handleRemoveCommunity)}
522 {this.state.removeCommunityLoading ? (
530 className="btn btn-link text-muted d-inline-block"
531 onClick={linkEvent(this, this.handlePurgeCommunityShow)}
532 aria-label={i18n.t("purge_community")}
534 {i18n.t("purge_community")}
539 {this.state.showRemoveDialog && (
540 <form onSubmit={linkEvent(this, this.handleRemoveCommunity)}>
541 <div className="form-group">
542 <label className="col-form-label" htmlFor="remove-reason">
548 className="form-control mr-2"
549 placeholder={i18n.t("optional")}
550 value={this.state.removeReason}
551 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
554 {/* TODO hold off on expires for now */}
555 {/* <div class="form-group row"> */}
556 {/* <label class="col-form-label">Expires</label> */}
557 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.removeExpires} onInput={linkEvent(this, this.handleModRemoveExpiresChange)} /> */}
559 <div className="form-group">
560 <button type="submit" className="btn btn-secondary">
561 {this.state.removeCommunityLoading ? (
564 i18n.t("remove_community")
570 {this.state.showPurgeDialog && (
571 <form onSubmit={linkEvent(this, this.handlePurgeCommunity)}>
572 <div className="form-group">
575 <div className="form-group">
576 <label className="sr-only" htmlFor="purge-reason">
582 className="form-control mr-2"
583 placeholder={i18n.t("reason")}
584 value={this.state.purgeReason}
585 onInput={linkEvent(this, this.handlePurgeReasonChange)}
588 <div className="form-group">
589 {this.state.purgeCommunityLoading ? (
594 className="btn btn-secondary"
595 aria-label={i18n.t("purge_community")}
597 {i18n.t("purge_community")}
607 handleEditClick(i: Sidebar) {
608 i.setState({ showEdit: true });
612 this.setState({ showEdit: false });
615 handleShowConfirmLeaveModTeamClick(i: Sidebar) {
616 i.setState({ showConfirmLeaveModTeam: true });
619 handleCancelLeaveModTeamClick(i: Sidebar) {
620 i.setState({ showConfirmLeaveModTeam: false });
623 get canPost(): boolean {
625 !this.props.community_view.community.posting_restricted_to_mods ||
626 amMod(this.props.moderators) ||
631 handleModRemoveShow(i: Sidebar) {
632 i.setState({ showRemoveDialog: true });
635 handleModRemoveReasonChange(i: Sidebar, event: any) {
636 i.setState({ removeReason: event.target.value });
639 handleModRemoveExpiresChange(i: Sidebar, event: any) {
640 i.setState({ removeExpires: event.target.value });
643 handlePurgeCommunityShow(i: Sidebar) {
644 i.setState({ showPurgeDialog: true, showRemoveDialog: false });
647 handlePurgeReasonChange(i: Sidebar, event: any) {
648 i.setState({ purgeReason: event.target.value });
651 // TODO Do we need two of these?
652 handleUnfollowCommunity(i: Sidebar) {
653 i.setState({ followCommunityLoading: true });
654 i.props.onFollowCommunity({
655 community_id: i.props.community_view.community.id,
657 auth: myAuthRequired(),
661 handleFollowCommunity(i: Sidebar) {
662 i.setState({ followCommunityLoading: true });
663 i.props.onFollowCommunity({
664 community_id: i.props.community_view.community.id,
666 auth: myAuthRequired(),
670 handleBlockCommunity(i: Sidebar) {
671 i.setState({ blockCommunityLoading: true });
672 i.props.onBlockCommunity({
674 block: !i.props.community_view.blocked,
675 auth: myAuthRequired(),
679 handleLeaveModTeam(i: Sidebar) {
680 const myId = UserService.Instance.myUserInfo?.local_user_view.person.id;
682 i.setState({ leaveModTeamLoading: true });
683 i.props.onLeaveModTeam({
684 community_id: i.props.community_view.community.id,
687 auth: myAuthRequired(),
692 handleDeleteCommunity(i: Sidebar) {
693 i.setState({ deleteCommunityLoading: true });
694 i.props.onDeleteCommunity({
695 community_id: i.props.community_view.community.id,
696 deleted: !i.props.community_view.community.deleted,
697 auth: myAuthRequired(),
701 handleRemoveCommunity(i: Sidebar, event: any) {
702 event.preventDefault();
703 i.setState({ removeCommunityLoading: true });
704 i.props.onRemoveCommunity({
705 community_id: i.props.community_view.community.id,
706 removed: !i.props.community_view.community.removed,
707 reason: i.state.removeReason,
708 expires: getUnixTime(i.state.removeExpires), // TODO fix this
709 auth: myAuthRequired(),
713 handlePurgeCommunity(i: Sidebar, event: any) {
714 event.preventDefault();
715 i.setState({ purgeCommunityLoading: true });
716 i.props.onPurgeCommunity({
717 community_id: i.props.community_view.community.id,
718 reason: i.state.purgeReason,
719 auth: myAuthRequired(),