1 import { Component, linkEvent } from "inferno";
2 import { Link } from "inferno-router";
6 CommunityModeratorView,
15 } from "lemmy-js-client";
16 import { i18n } from "../../i18next";
17 import { UserService, WebSocketService } 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[];
38 admins: PersonViewSafe[];
39 allLanguages: Language[];
40 siteLanguages: number[];
41 communityLanguages?: number[];
48 interface SidebarState {
49 removeReason?: string;
50 removeExpires?: string;
52 showRemoveDialog: boolean;
53 showPurgeDialog: boolean;
55 purgeLoading: boolean;
56 showConfirmLeaveModTeam: boolean;
59 export class Sidebar extends Component<SidebarProps, SidebarState> {
60 state: SidebarState = {
62 showRemoveDialog: false,
63 showPurgeDialog: false,
65 showConfirmLeaveModTeam: false,
68 constructor(props: any, context: any) {
69 super(props, context);
70 this.handleEditCommunity = this.handleEditCommunity.bind(this);
71 this.handleEditCancel = this.handleEditCancel.bind(this);
77 {!this.state.showEdit ? (
81 community_view={this.props.community_view}
82 allLanguages={this.props.allLanguages}
83 siteLanguages={this.props.siteLanguages}
84 communityLanguages={this.props.communityLanguages}
85 onEdit={this.handleEditCommunity}
86 onCancel={this.handleEditCancel}
87 enableNsfw={this.props.enableNsfw}
95 const myUSerInfo = UserService.Instance.myUserInfo;
96 const { name, actor_id } = this.props.community_view.community;
99 <div className="card border-secondary mb-3">
100 <div className="card-body">
101 {this.communityTitle()}
102 {this.props.editable && this.adminButtons()}
103 {myUSerInfo && this.subscribe()}
104 {this.canPost && this.createPost()}
105 {myUSerInfo && this.blockCommunity()}
107 <div className="alert alert-info" role="alert">
108 {i18n.t("community_not_logged_in_alert", {
110 instance: hostname(actor_id),
116 <div className="card border-secondary mb-3">
117 <div className="card-body">
128 let community = this.props.community_view.community;
129 let subscribed = this.props.community_view.subscribed;
132 <h5 className="mb-0">
133 {this.props.showIcon && !community.removed && (
134 <BannerIconHeader icon={community.icon} banner={community.banner} />
136 <span className="mr-2">{community.title}</span>
137 {subscribed === SubscribedType.Subscribed && (
139 className="btn btn-secondary btn-sm mr-2"
140 onClick={linkEvent(this, this.handleUnsubscribe)}
142 <Icon icon="check" classes="icon-inline text-success mr-1" />
146 {subscribed === SubscribedType.Pending && (
148 className="btn btn-warning mr-2"
149 onClick={linkEvent(this, this.handleUnsubscribe)}
151 {i18n.t("subscribe_pending")}
154 {community.removed && (
155 <small className="mr-2 text-muted font-italic">
159 {community.deleted && (
160 <small className="mr-2 text-muted font-italic">
165 <small className="mr-2 text-muted font-italic">
171 community={community}
182 let community_view = this.props.community_view;
183 let counts = community_view.counts;
185 <ul className="my-1 list-inline">
186 <li className="list-inline-item badge badge-secondary">
187 {i18n.t("number_online", {
188 count: this.props.online,
189 formattedCount: numToSI(this.props.online),
193 className="list-inline-item badge badge-secondary pointer"
194 data-tippy-content={i18n.t("active_users_in_the_last_day", {
195 count: counts.users_active_day,
196 formattedCount: counts.users_active_day,
199 {i18n.t("number_of_users", {
200 count: counts.users_active_day,
201 formattedCount: numToSI(counts.users_active_day),
206 className="list-inline-item badge badge-secondary pointer"
207 data-tippy-content={i18n.t("active_users_in_the_last_week", {
208 count: counts.users_active_week,
209 formattedCount: counts.users_active_week,
212 {i18n.t("number_of_users", {
213 count: counts.users_active_week,
214 formattedCount: numToSI(counts.users_active_week),
219 className="list-inline-item badge badge-secondary pointer"
220 data-tippy-content={i18n.t("active_users_in_the_last_month", {
221 count: counts.users_active_month,
222 formattedCount: counts.users_active_month,
225 {i18n.t("number_of_users", {
226 count: counts.users_active_month,
227 formattedCount: numToSI(counts.users_active_month),
232 className="list-inline-item badge badge-secondary pointer"
233 data-tippy-content={i18n.t("active_users_in_the_last_six_months", {
234 count: counts.users_active_half_year,
235 formattedCount: counts.users_active_half_year,
238 {i18n.t("number_of_users", {
239 count: counts.users_active_half_year,
240 formattedCount: numToSI(counts.users_active_half_year),
242 / {i18n.t("number_of_months", { count: 6, formattedCount: 6 })}
244 <li className="list-inline-item badge badge-secondary">
245 {i18n.t("number_of_subscribers", {
246 count: counts.subscribers,
247 formattedCount: numToSI(counts.subscribers),
250 <li className="list-inline-item badge badge-secondary">
251 {i18n.t("number_of_posts", {
253 formattedCount: numToSI(counts.posts),
256 <li className="list-inline-item badge badge-secondary">
257 {i18n.t("number_of_comments", {
258 count: counts.comments,
259 formattedCount: numToSI(counts.comments),
262 <li className="list-inline-item">
264 className="badge badge-primary"
265 to={`/modlog/${this.props.community_view.community.id}`}
276 <ul className="list-inline small">
277 <li className="list-inline-item">{i18n.t("mods")}: </li>
278 {this.props.moderators.map(mod => (
279 <li key={mod.moderator.id} className="list-inline-item">
280 <PersonListing person={mod.moderator} />
288 let cv = this.props.community_view;
291 className={`btn btn-secondary btn-block mb-2 ${
292 cv.community.deleted || cv.community.removed ? "no-click" : ""
294 to={`/create_post?communityId=${cv.community.id}`}
296 {i18n.t("create_a_post")}
302 let community_view = this.props.community_view;
304 <div className="mb-2">
305 {community_view.subscribed == SubscribedType.NotSubscribed && (
307 className="btn btn-secondary btn-block"
308 onClick={linkEvent(this, this.handleSubscribe)}
310 {i18n.t("subscribe")}
318 let community_view = this.props.community_view;
319 let blocked = this.props.community_view.blocked;
322 <div className="mb-2">
323 {community_view.subscribed == SubscribedType.NotSubscribed &&
326 className="btn btn-danger btn-block"
327 onClick={linkEvent(this, this.handleUnblock)}
329 {i18n.t("unblock_community")}
333 className="btn btn-danger btn-block"
334 onClick={linkEvent(this, this.handleBlock)}
336 {i18n.t("block_community")}
344 let desc = this.props.community_view.community.description;
347 <div className="md-div" dangerouslySetInnerHTML={mdToHtml(desc)} />
353 let community_view = this.props.community_view;
356 <ul className="list-inline mb-1 text-muted font-weight-bold">
357 {amMod(this.props.moderators) && (
359 <li className="list-inline-item-action">
361 className="btn btn-link text-muted d-inline-block"
362 onClick={linkEvent(this, this.handleEditClick)}
363 data-tippy-content={i18n.t("edit")}
364 aria-label={i18n.t("edit")}
366 <Icon icon="edit" classes="icon-inline" />
369 {!amTopMod(this.props.moderators) &&
370 (!this.state.showConfirmLeaveModTeam ? (
371 <li className="list-inline-item-action">
373 className="btn btn-link text-muted d-inline-block"
376 this.handleShowConfirmLeaveModTeamClick
379 {i18n.t("leave_mod_team")}
384 <li className="list-inline-item-action">
385 {i18n.t("are_you_sure")}
387 <li className="list-inline-item-action">
389 className="btn btn-link text-muted d-inline-block"
390 onClick={linkEvent(this, this.handleLeaveModTeamClick)}
395 <li className="list-inline-item-action">
397 className="btn btn-link text-muted d-inline-block"
400 this.handleCancelLeaveModTeamClick
408 {amTopMod(this.props.moderators) && (
409 <li className="list-inline-item-action">
411 className="btn btn-link text-muted d-inline-block"
412 onClick={linkEvent(this, this.handleDeleteClick)}
414 !community_view.community.deleted
419 !community_view.community.deleted
426 classes={`icon-inline ${
427 community_view.community.deleted && "text-danger"
436 <li className="list-inline-item">
437 {!this.props.community_view.community.removed ? (
439 className="btn btn-link text-muted d-inline-block"
440 onClick={linkEvent(this, this.handleModRemoveShow)}
446 className="btn btn-link text-muted d-inline-block"
447 onClick={linkEvent(this, this.handleModRemoveSubmit)}
453 className="btn btn-link text-muted d-inline-block"
454 onClick={linkEvent(this, this.handlePurgeCommunityShow)}
455 aria-label={i18n.t("purge_community")}
457 {i18n.t("purge_community")}
462 {this.state.showRemoveDialog && (
463 <form onSubmit={linkEvent(this, this.handleModRemoveSubmit)}>
464 <div className="form-group">
465 <label className="col-form-label" htmlFor="remove-reason">
471 className="form-control mr-2"
472 placeholder={i18n.t("optional")}
473 value={this.state.removeReason}
474 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
477 {/* TODO hold off on expires for now */}
478 {/* <div class="form-group row"> */}
479 {/* <label class="col-form-label">Expires</label> */}
480 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.removeExpires} onInput={linkEvent(this, this.handleModRemoveExpiresChange)} /> */}
482 <div className="form-group">
483 <button type="submit" className="btn btn-secondary">
484 {i18n.t("remove_community")}
489 {this.state.showPurgeDialog && (
490 <form onSubmit={linkEvent(this, this.handlePurgeSubmit)}>
491 <div className="form-group">
494 <div className="form-group">
495 <label className="sr-only" htmlFor="purge-reason">
501 className="form-control mr-2"
502 placeholder={i18n.t("reason")}
503 value={this.state.purgeReason}
504 onInput={linkEvent(this, this.handlePurgeReasonChange)}
507 <div className="form-group">
508 {this.state.purgeLoading ? (
513 className="btn btn-secondary"
514 aria-label={i18n.t("purge_community")}
516 {i18n.t("purge_community")}
526 handleEditClick(i: Sidebar) {
527 i.setState({ showEdit: true });
530 handleEditCommunity() {
531 this.setState({ showEdit: false });
535 this.setState({ showEdit: false });
538 handleDeleteClick(i: Sidebar, event: any) {
539 event.preventDefault();
542 let deleteForm: DeleteCommunity = {
543 community_id: i.props.community_view.community.id,
544 deleted: !i.props.community_view.community.deleted,
547 WebSocketService.Instance.send(wsClient.deleteCommunity(deleteForm));
551 handleShowConfirmLeaveModTeamClick(i: Sidebar) {
552 i.setState({ showConfirmLeaveModTeam: true });
555 handleLeaveModTeamClick(i: Sidebar) {
556 let mui = UserService.Instance.myUserInfo;
559 let form: AddModToCommunity = {
560 person_id: mui.local_user_view.person.id,
561 community_id: i.props.community_view.community.id,
565 WebSocketService.Instance.send(wsClient.addModToCommunity(form));
566 i.setState({ showConfirmLeaveModTeam: false });
570 handleCancelLeaveModTeamClick(i: Sidebar) {
571 i.setState({ showConfirmLeaveModTeam: false });
574 handleUnsubscribe(i: Sidebar, event: any) {
575 event.preventDefault();
576 let community_id = i.props.community_view.community.id;
579 let form: FollowCommunity = {
584 WebSocketService.Instance.send(wsClient.followCommunity(form));
588 let mui = UserService.Instance.myUserInfo;
590 mui.follows = mui.follows.filter(i => i.community.id != community_id);
594 handleSubscribe(i: Sidebar, event: any) {
595 event.preventDefault();
596 let community_id = i.props.community_view.community.id;
599 let form: FollowCommunity = {
604 WebSocketService.Instance.send(wsClient.followCommunity(form));
608 let mui = UserService.Instance.myUserInfo;
611 community: i.props.community_view.community,
612 follower: mui.local_user_view.person,
617 get canPost(): boolean {
619 !this.props.community_view.community.posting_restricted_to_mods ||
620 amMod(this.props.moderators) ||
625 handleModRemoveShow(i: Sidebar) {
626 i.setState({ showRemoveDialog: true });
629 handleModRemoveReasonChange(i: Sidebar, event: any) {
630 i.setState({ removeReason: event.target.value });
633 handleModRemoveExpiresChange(i: Sidebar, event: any) {
634 i.setState({ removeExpires: event.target.value });
637 handleModRemoveSubmit(i: Sidebar, event: any) {
638 event.preventDefault();
641 let removeForm: RemoveCommunity = {
642 community_id: i.props.community_view.community.id,
643 removed: !i.props.community_view.community.removed,
644 reason: i.state.removeReason,
645 expires: getUnixTime(i.state.removeExpires),
648 WebSocketService.Instance.send(wsClient.removeCommunity(removeForm));
650 i.setState({ showRemoveDialog: false });
654 handlePurgeCommunityShow(i: Sidebar) {
655 i.setState({ showPurgeDialog: true, showRemoveDialog: false });
658 handlePurgeReasonChange(i: Sidebar, event: any) {
659 i.setState({ purgeReason: event.target.value });
662 handlePurgeSubmit(i: Sidebar, event: any) {
663 event.preventDefault();
667 let form: PurgeCommunity = {
668 community_id: i.props.community_view.community.id,
669 reason: i.state.purgeReason,
672 WebSocketService.Instance.send(wsClient.purgeCommunity(form));
673 i.setState({ purgeLoading: true });
677 handleBlock(i: Sidebar, event: any) {
678 event.preventDefault();
681 let blockCommunityForm: BlockCommunity = {
682 community_id: i.props.community_view.community.id,
686 WebSocketService.Instance.send(
687 wsClient.blockCommunity(blockCommunityForm)
692 handleUnblock(i: Sidebar, event: any) {
693 event.preventDefault();
696 let blockCommunityForm: BlockCommunity = {
697 community_id: i.props.community_view.community.id,
701 WebSocketService.Instance.send(
702 wsClient.blockCommunity(blockCommunityForm)