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";
26 import { amAdmin } from "../../utils/roles/am-admin";
27 import { amMod } from "../../utils/roles/am-mod";
28 import { amTopMod } from "../../utils/roles/am-top-mod";
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[];
45 onDeleteCommunity(form: DeleteCommunity): void;
46 onRemoveCommunity(form: RemoveCommunity): void;
47 onLeaveModTeam(form: AddModToCommunity): void;
48 onFollowCommunity(form: FollowCommunity): void;
49 onBlockCommunity(form: BlockCommunity): void;
50 onPurgeCommunity(form: PurgeCommunity): void;
51 onEditCommunity(form: EditCommunity): void;
54 interface SidebarState {
55 removeReason?: string;
56 removeExpires?: string;
58 showRemoveDialog: boolean;
59 showPurgeDialog: boolean;
61 showConfirmLeaveModTeam: boolean;
62 deleteCommunityLoading: boolean;
63 removeCommunityLoading: boolean;
64 leaveModTeamLoading: boolean;
65 followCommunityLoading: boolean;
66 purgeCommunityLoading: boolean;
69 export class Sidebar extends Component<SidebarProps, SidebarState> {
70 state: SidebarState = {
72 showRemoveDialog: false,
73 showPurgeDialog: false,
74 showConfirmLeaveModTeam: false,
75 deleteCommunityLoading: false,
76 removeCommunityLoading: false,
77 leaveModTeamLoading: false,
78 followCommunityLoading: false,
79 purgeCommunityLoading: false,
82 constructor(props: any, context: any) {
83 super(props, context);
84 this.handleEditCancel = this.handleEditCancel.bind(this);
87 componentWillReceiveProps(
88 nextProps: Readonly<{ children?: InfernoNode } & SidebarProps>
90 if (this.props.moderators != nextProps.moderators) {
92 showConfirmLeaveModTeam: false,
96 if (this.props.community_view != nextProps.community_view) {
99 showPurgeDialog: false,
100 showRemoveDialog: false,
101 deleteCommunityLoading: false,
102 removeCommunityLoading: false,
103 leaveModTeamLoading: false,
104 followCommunityLoading: false,
105 purgeCommunityLoading: false,
113 {!this.state.showEdit ? (
117 community_view={this.props.community_view}
118 allLanguages={this.props.allLanguages}
119 siteLanguages={this.props.siteLanguages}
120 communityLanguages={this.props.communityLanguages}
121 onUpsertCommunity={this.props.onEditCommunity}
122 onCancel={this.handleEditCancel}
123 enableNsfw={this.props.enableNsfw}
131 const myUSerInfo = UserService.Instance.myUserInfo;
132 const { name, actor_id } = this.props.community_view.community;
135 <div className="card border-secondary mb-3">
136 <div className="card-body">
137 {this.communityTitle()}
138 {this.props.editable && this.adminButtons()}
139 {myUSerInfo && this.subscribe()}
140 {this.canPost && this.createPost()}
141 {myUSerInfo && this.blockCommunity()}
143 <div className="alert alert-info" role="alert">
145 i18nKey="community_not_logged_in_alert"
148 instance: hostname(actor_id),
151 #<code className="user-select-all">#</code>#
157 <div className="card border-secondary mb-3">
158 <div className="card-body">
169 const community = this.props.community_view.community;
170 const subscribed = this.props.community_view.subscribed;
173 <h5 className="mb-0">
174 {this.props.showIcon && !community.removed && (
175 <BannerIconHeader icon={community.icon} banner={community.banner} />
177 <span className="mr-2">
178 <CommunityLink community={community} hideAvatar />
180 {subscribed === "Subscribed" && (
182 className="btn btn-secondary btn-sm mr-2"
183 onClick={linkEvent(this, this.handleUnfollowCommunity)}
185 {this.state.followCommunityLoading ? (
189 <Icon icon="check" classes="icon-inline text-success mr-1" />
195 {subscribed === "Pending" && (
197 className="btn btn-warning mr-2"
198 onClick={linkEvent(this, this.handleUnfollowCommunity)}
200 {this.state.followCommunityLoading ? (
203 i18n.t("subscribe_pending")
207 {community.removed && (
208 <small className="mr-2 text-muted font-italic">
212 {community.deleted && (
213 <small className="mr-2 text-muted font-italic">
218 <small className="mr-2 text-muted font-italic">
224 community={community}
235 const community_view = this.props.community_view;
236 const counts = community_view.counts;
238 <ul className="my-1 list-inline">
240 className="list-inline-item badge badge-secondary pointer"
241 data-tippy-content={i18n.t("active_users_in_the_last_day", {
242 count: Number(counts.users_active_day),
243 formattedCount: numToSI(counts.users_active_day),
246 {i18n.t("number_of_users", {
247 count: Number(counts.users_active_day),
248 formattedCount: numToSI(counts.users_active_day),
253 className="list-inline-item badge badge-secondary pointer"
254 data-tippy-content={i18n.t("active_users_in_the_last_week", {
255 count: Number(counts.users_active_week),
256 formattedCount: numToSI(counts.users_active_week),
259 {i18n.t("number_of_users", {
260 count: Number(counts.users_active_week),
261 formattedCount: numToSI(counts.users_active_week),
266 className="list-inline-item badge badge-secondary pointer"
267 data-tippy-content={i18n.t("active_users_in_the_last_month", {
268 count: Number(counts.users_active_month),
269 formattedCount: numToSI(counts.users_active_month),
272 {i18n.t("number_of_users", {
273 count: Number(counts.users_active_month),
274 formattedCount: numToSI(counts.users_active_month),
279 className="list-inline-item badge badge-secondary pointer"
280 data-tippy-content={i18n.t("active_users_in_the_last_six_months", {
281 count: Number(counts.users_active_half_year),
282 formattedCount: numToSI(counts.users_active_half_year),
285 {i18n.t("number_of_users", {
286 count: Number(counts.users_active_half_year),
287 formattedCount: numToSI(counts.users_active_half_year),
289 / {i18n.t("number_of_months", { count: 6, formattedCount: 6 })}
291 <li className="list-inline-item badge badge-secondary">
292 {i18n.t("number_of_subscribers", {
293 count: Number(counts.subscribers),
294 formattedCount: numToSI(counts.subscribers),
297 <li className="list-inline-item badge badge-secondary">
298 {i18n.t("number_of_posts", {
299 count: Number(counts.posts),
300 formattedCount: numToSI(counts.posts),
303 <li className="list-inline-item badge badge-secondary">
304 {i18n.t("number_of_comments", {
305 count: Number(counts.comments),
306 formattedCount: numToSI(counts.comments),
309 <li className="list-inline-item">
311 className="badge badge-primary"
312 to={`/modlog/${this.props.community_view.community.id}`}
323 <ul className="list-inline small">
324 <li className="list-inline-item">{i18n.t("mods")}: </li>
325 {this.props.moderators.map(mod => (
326 <li key={mod.moderator.id} className="list-inline-item">
327 <PersonListing person={mod.moderator} />
335 const cv = this.props.community_view;
338 className={`btn btn-secondary btn-block mb-2 ${
339 cv.community.deleted || cv.community.removed ? "no-click" : ""
341 to={`/create_post?communityId=${cv.community.id}`}
343 {i18n.t("create_a_post")}
349 const community_view = this.props.community_view;
351 <div className="mb-2">
352 {community_view.subscribed == "NotSubscribed" && (
354 className="btn btn-secondary btn-block"
355 onClick={linkEvent(this, this.handleFollowCommunity)}
357 {this.state.followCommunityLoading ? (
369 const { subscribed, blocked } = this.props.community_view;
372 <div className="mb-2">
373 {subscribed == "NotSubscribed" && (
375 className="btn btn-danger btn-block"
376 onClick={linkEvent(this, this.handleBlockCommunity)}
378 {i18n.t(blocked ? "unblock_community" : "block_community")}
386 const desc = this.props.community_view.community.description;
389 <div className="md-div" dangerouslySetInnerHTML={mdToHtml(desc)} />
395 const community_view = this.props.community_view;
398 <ul className="list-inline mb-1 text-muted font-weight-bold">
399 {amMod(this.props.moderators) && (
401 <li className="list-inline-item-action">
403 className="btn btn-link text-muted d-inline-block"
404 onClick={linkEvent(this, this.handleEditClick)}
405 data-tippy-content={i18n.t("edit")}
406 aria-label={i18n.t("edit")}
408 <Icon icon="edit" classes="icon-inline" />
411 {!amTopMod(this.props.moderators) &&
412 (!this.state.showConfirmLeaveModTeam ? (
413 <li className="list-inline-item-action">
415 className="btn btn-link text-muted d-inline-block"
418 this.handleShowConfirmLeaveModTeamClick
421 {i18n.t("leave_mod_team")}
426 <li className="list-inline-item-action">
427 {i18n.t("are_you_sure")}
429 <li className="list-inline-item-action">
431 className="btn btn-link text-muted d-inline-block"
432 onClick={linkEvent(this, this.handleLeaveModTeam)}
437 <li className="list-inline-item-action">
439 className="btn btn-link text-muted d-inline-block"
442 this.handleCancelLeaveModTeamClick
450 {amTopMod(this.props.moderators) && (
451 <li className="list-inline-item-action">
453 className="btn btn-link text-muted d-inline-block"
454 onClick={linkEvent(this, this.handleDeleteCommunity)}
456 !community_view.community.deleted
461 !community_view.community.deleted
466 {this.state.deleteCommunityLoading ? (
471 classes={`icon-inline ${
472 community_view.community.deleted && "text-danger"
482 <li className="list-inline-item">
483 {!this.props.community_view.community.removed ? (
485 className="btn btn-link text-muted d-inline-block"
486 onClick={linkEvent(this, this.handleModRemoveShow)}
492 className="btn btn-link text-muted d-inline-block"
493 onClick={linkEvent(this, this.handleRemoveCommunity)}
495 {this.state.removeCommunityLoading ? (
503 className="btn btn-link text-muted d-inline-block"
504 onClick={linkEvent(this, this.handlePurgeCommunityShow)}
505 aria-label={i18n.t("purge_community")}
507 {i18n.t("purge_community")}
512 {this.state.showRemoveDialog && (
513 <form onSubmit={linkEvent(this, this.handleRemoveCommunity)}>
514 <div className="form-group">
515 <label className="col-form-label" htmlFor="remove-reason">
521 className="form-control mr-2"
522 placeholder={i18n.t("optional")}
523 value={this.state.removeReason}
524 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
527 {/* TODO hold off on expires for now */}
528 {/* <div class="form-group row"> */}
529 {/* <label class="col-form-label">Expires</label> */}
530 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.removeExpires} onInput={linkEvent(this, this.handleModRemoveExpiresChange)} /> */}
532 <div className="form-group">
533 <button type="submit" className="btn btn-secondary">
534 {this.state.removeCommunityLoading ? (
537 i18n.t("remove_community")
543 {this.state.showPurgeDialog && (
544 <form onSubmit={linkEvent(this, this.handlePurgeCommunity)}>
545 <div className="form-group">
548 <div className="form-group">
549 <label className="sr-only" htmlFor="purge-reason">
555 className="form-control mr-2"
556 placeholder={i18n.t("reason")}
557 value={this.state.purgeReason}
558 onInput={linkEvent(this, this.handlePurgeReasonChange)}
561 <div className="form-group">
562 {this.state.purgeCommunityLoading ? (
567 className="btn btn-secondary"
568 aria-label={i18n.t("purge_community")}
570 {i18n.t("purge_community")}
580 handleEditClick(i: Sidebar) {
581 i.setState({ showEdit: true });
585 this.setState({ showEdit: false });
588 handleShowConfirmLeaveModTeamClick(i: Sidebar) {
589 i.setState({ showConfirmLeaveModTeam: true });
592 handleCancelLeaveModTeamClick(i: Sidebar) {
593 i.setState({ showConfirmLeaveModTeam: false });
596 get canPost(): boolean {
598 !this.props.community_view.community.posting_restricted_to_mods ||
599 amMod(this.props.moderators) ||
604 handleModRemoveShow(i: Sidebar) {
605 i.setState({ showRemoveDialog: true });
608 handleModRemoveReasonChange(i: Sidebar, event: any) {
609 i.setState({ removeReason: event.target.value });
612 handleModRemoveExpiresChange(i: Sidebar, event: any) {
613 i.setState({ removeExpires: event.target.value });
616 handlePurgeCommunityShow(i: Sidebar) {
617 i.setState({ showPurgeDialog: true, showRemoveDialog: false });
620 handlePurgeReasonChange(i: Sidebar, event: any) {
621 i.setState({ purgeReason: event.target.value });
624 // TODO Do we need two of these?
625 handleUnfollowCommunity(i: Sidebar) {
626 i.setState({ followCommunityLoading: true });
627 i.props.onFollowCommunity({
628 community_id: i.props.community_view.community.id,
630 auth: myAuthRequired(),
634 handleFollowCommunity(i: Sidebar) {
635 i.setState({ followCommunityLoading: true });
636 i.props.onFollowCommunity({
637 community_id: i.props.community_view.community.id,
639 auth: myAuthRequired(),
643 handleBlockCommunity(i: Sidebar) {
644 const { community, blocked } = i.props.community_view;
646 i.props.onBlockCommunity({
647 community_id: community.id,
649 auth: myAuthRequired(),
653 handleLeaveModTeam(i: Sidebar) {
654 const myId = UserService.Instance.myUserInfo?.local_user_view.person.id;
656 i.setState({ leaveModTeamLoading: true });
657 i.props.onLeaveModTeam({
658 community_id: i.props.community_view.community.id,
661 auth: myAuthRequired(),
666 handleDeleteCommunity(i: Sidebar) {
667 i.setState({ deleteCommunityLoading: true });
668 i.props.onDeleteCommunity({
669 community_id: i.props.community_view.community.id,
670 deleted: !i.props.community_view.community.deleted,
671 auth: myAuthRequired(),
675 handleRemoveCommunity(i: Sidebar, event: any) {
676 event.preventDefault();
677 i.setState({ removeCommunityLoading: true });
678 i.props.onRemoveCommunity({
679 community_id: i.props.community_view.community.id,
680 removed: !i.props.community_view.community.removed,
681 reason: i.state.removeReason,
682 expires: getUnixTime(i.state.removeExpires), // TODO fix this
683 auth: myAuthRequired(),
687 handlePurgeCommunity(i: Sidebar, event: any) {
688 event.preventDefault();
689 i.setState({ purgeCommunityLoading: true });
690 i.props.onPurgeCommunity({
691 community_id: i.props.community_view.community.id,
692 reason: i.state.purgeReason,
693 auth: myAuthRequired(),