]> Untitled Git - lemmy-ui.git/blobdiff - src/shared/components/app/navbar.tsx
Fixing nav-link
[lemmy-ui.git] / src / shared / components / app / navbar.tsx
index 00381f63aad8b14903de42a5029a4b27db358c2b..0f8d64713a22d43b040e2e81af0fd7b9f8a5b299 100644 (file)
@@ -1,18 +1,13 @@
 import { Component, createRef, linkEvent, RefObject } from "inferno";
-import { Link } from "inferno-router";
+import { NavLink } from "inferno-router";
 import {
   CommentResponse,
-  CommentView,
-  GetPersonMentions,
-  GetPersonMentionsResponse,
-  GetPrivateMessages,
-  GetReplies,
-  GetRepliesResponse,
+  GetReportCount,
+  GetReportCountResponse,
   GetSiteResponse,
+  GetUnreadCount,
+  GetUnreadCountResponse,
   PrivateMessageResponse,
-  PrivateMessagesResponse,
-  PrivateMessageView,
-  SortType,
   UserOperation,
 } from "lemmy-js-client";
 import { Subscription } from "rxjs";
@@ -20,14 +15,14 @@ import { i18n } from "../../i18next";
 import { UserService, WebSocketService } from "../../services";
 import {
   authField,
-  fetchLimit,
+  donateLemmyUrl,
   getLanguage,
   isBrowser,
   notifyComment,
   notifyPrivateMessage,
+  numToSI,
   setTheme,
   showAvatars,
-  supportLemmyUrl,
   toast,
   wsClient,
   wsJsonToRes,
@@ -44,29 +39,28 @@ interface NavbarProps {
 interface NavbarState {
   isLoggedIn: boolean;
   expanded: boolean;
-  replies: CommentView[];
-  mentions: CommentView[];
-  messages: PrivateMessageView[];
-  unreadCount: number;
+  unreadInboxCount: number;
+  unreadReportCount: number;
   searchParam: string;
   toggleSearch: boolean;
+  showDropdown: boolean;
   onSiteBanner?(url: string): any;
 }
 
 export class Navbar extends Component<NavbarProps, NavbarState> {
   private wsSub: Subscription;
   private userSub: Subscription;
-  private unreadCountSub: Subscription;
+  private unreadInboxCountSub: Subscription;
+  private unreadReportCountSub: Subscription;
   private searchTextField: RefObject<HTMLInputElement>;
   emptyState: NavbarState = {
     isLoggedIn: !!this.props.site_res.my_user,
-    unreadCount: 0,
-    replies: [],
-    mentions: [],
-    messages: [],
+    unreadInboxCount: 0,
+    unreadReportCount: 0,
     expanded: false,
     searchParam: "",
     toggleSearch: false,
+    showDropdown: false,
   };
   subscription: any;
 
@@ -114,23 +108,30 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
       });
 
       // Subscribe to unread count changes
-      this.unreadCountSub = UserService.Instance.unreadCountSub.subscribe(
-        res => {
-          this.setState({ unreadCount: res });
-        }
-      );
+      this.unreadInboxCountSub =
+        UserService.Instance.unreadInboxCountSub.subscribe(res => {
+          this.setState({ unreadInboxCount: res });
+        });
+      // Subscribe to unread report count changes
+      this.unreadReportCountSub =
+        UserService.Instance.unreadReportCountSub.subscribe(res => {
+          this.setState({ unreadReportCount: res });
+        });
     }
   }
 
-  handleSearchParam(i: Navbar, event: any) {
-    i.state.searchParam = event.target.value;
-    i.setState(i.state);
+  componentWillUnmount() {
+    this.wsSub.unsubscribe();
+    this.userSub.unsubscribe();
+    this.unreadInboxCountSub.unsubscribe();
+    this.unreadReportCountSub.unsubscribe();
   }
 
   updateUrl() {
     const searchParam = this.state.searchParam;
     this.setState({ searchParam: "" });
     this.setState({ toggleSearch: false });
+    this.setState({ showDropdown: false, expanded: false });
     if (searchParam === "") {
       this.context.router.history.push(`/search/`);
     } else {
@@ -141,54 +142,27 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
     }
   }
 
-  handleSearchSubmit(i: Navbar, event: any) {
-    event.preventDefault();
-    i.updateUrl();
-  }
-
-  handleSearchBtn(i: Navbar, event: any) {
-    event.preventDefault();
-    i.setState({ toggleSearch: true });
-
-    i.searchTextField.current.focus();
-    const offsetWidth = i.searchTextField.current.offsetWidth;
-    if (i.state.searchParam && offsetWidth > 100) {
-      i.updateUrl();
-    }
-  }
-
-  handleSearchBlur(i: Navbar, event: any) {
-    if (!(event.relatedTarget && event.relatedTarget.name !== "search-btn")) {
-      i.state.toggleSearch = false;
-      i.setState(i.state);
-    }
-  }
-
   render() {
     return this.navbar();
   }
 
-  componentWillUnmount() {
-    this.wsSub.unsubscribe();
-    this.userSub.unsubscribe();
-    this.unreadCountSub.unsubscribe();
-  }
-
   // TODO class active corresponding to current page
   navbar() {
-    let localUserView =
-      UserService.Instance.localUserView || this.props.site_res.my_user;
+    let myUserInfo =
+      UserService.Instance.myUserInfo || this.props.site_res.my_user;
+    let person = myUserInfo?.local_user_view.person;
     return (
       <nav class="navbar navbar-expand-lg navbar-light shadow-sm p-0 px-3">
         <div class="container">
           {this.props.site_res.site_view && (
-            <Link
+            <NavLink
+              to="/"
+              onMouseUp={linkEvent(this, this.handleHideExpandNavbar)}
               title={
                 this.props.site_res.site_view.site.description ||
                 this.props.site_res.site_view.site.name
               }
               className="d-flex align-items-center navbar-brand mr-md-3"
-              to="/"
             >
               {this.props.site_res.site_view.site.icon && showAvatars() && (
                 <PictrsImage
@@ -197,32 +171,59 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
                 />
               )}
               {this.props.site_res.site_view.site.name}
-            </Link>
+            </NavLink>
           )}
           {this.state.isLoggedIn && (
-            <Link
-              className="ml-auto p-1 navbar-toggler nav-link border-0"
-              to="/inbox"
-              title={i18n.t("inbox")}
-            >
-              <Icon icon="bell" />
-              {this.state.unreadCount > 0 && (
-                <span
-                  class="mx-1 badge badge-light"
-                  aria-label={`${this.state.unreadCount} ${i18n.t(
-                    "unread_messages"
-                  )}`}
-                >
-                  {this.state.unreadCount}
-                </span>
+            <>
+              <ul class="navbar-nav ml-auto">
+                <li className="nav-item">
+                  <NavLink
+                    to="/inbox"
+                    className="p-1 navbar-toggler nav-link border-0"
+                    onMouseUp={linkEvent(this, this.handleHideExpandNavbar)}
+                    title={i18n.t("unread_messages", {
+                      count: this.state.unreadInboxCount,
+                      formattedCount: numToSI(this.state.unreadInboxCount),
+                    })}
+                  >
+                    <Icon icon="bell" />
+                    {this.state.unreadInboxCount > 0 && (
+                      <span class="mx-1 badge badge-light">
+                        {numToSI(this.state.unreadInboxCount)}
+                      </span>
+                    )}
+                  </NavLink>
+                </li>
+              </ul>
+              {UserService.Instance.myUserInfo?.moderates.length > 0 && (
+                <ul class="navbar-nav ml-1">
+                  <li className="nav-item">
+                    <NavLink
+                      to="/reports"
+                      className="p-1 navbar-toggler nav-link border-0"
+                      onMouseUp={linkEvent(this, this.handleHideExpandNavbar)}
+                      title={i18n.t("unread_reports", {
+                        count: this.state.unreadReportCount,
+                        formattedCount: numToSI(this.state.unreadReportCount),
+                      })}
+                    >
+                      <Icon icon="shield" />
+                      {this.state.unreadReportCount > 0 && (
+                        <span class="mx-1 badge badge-light">
+                          {numToSI(this.state.unreadReportCount)}
+                        </span>
+                      )}
+                    </NavLink>
+                  </li>
+                </ul>
               )}
-            </Link>
+            </>
           )}
           <button
             class="navbar-toggler border-0 p-1"
             type="button"
             aria-label="menu"
-            onClick={linkEvent(this, this.expandNavbar)}
+            onClick={linkEvent(this, this.handleToggleExpandNavbar)}
             data-tippy-content={i18n.t("expand_here")}
           >
             <Icon icon="menu" />
@@ -232,42 +233,45 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
           >
             <ul class="navbar-nav my-2 mr-auto">
               <li class="nav-item">
-                <Link
-                  className="nav-link"
+                <NavLink
                   to="/communities"
+                  className="nav-link"
+                  onMouseUp={linkEvent(this, this.handleHideExpandNavbar)}
                   title={i18n.t("communities")}
                 >
                   {i18n.t("communities")}
-                </Link>
+                </NavLink>
               </li>
               <li class="nav-item">
-                <Link
-                  className="nav-link"
+                <NavLink
                   to={{
                     pathname: "/create_post",
-                    state: { prevPath: this.currentLocation },
+                    prevPath: this.currentLocation,
                   }}
+                  className="nav-link"
+                  onMouseUp={linkEvent(this, this.handleHideExpandNavbar)}
                   title={i18n.t("create_post")}
                 >
                   {i18n.t("create_post")}
-                </Link>
+                </NavLink>
               </li>
               {this.canCreateCommunity && (
                 <li class="nav-item">
-                  <Link
-                    className="nav-link"
+                  <NavLink
                     to="/create_community"
+                    className="nav-link"
+                    onMouseUp={linkEvent(this, this.handleHideExpandNavbar)}
                     title={i18n.t("create_community")}
                   >
                     {i18n.t("create_community")}
-                  </Link>
+                  </NavLink>
                 </li>
               )}
               <li class="nav-item">
                 <a
                   className="nav-link"
                   title={i18n.t("support_lemmy")}
-                  href={supportLemmyUrl}
+                  href={donateLemmyUrl}
                 >
                   <Icon icon="heart" classes="small" />
                 </a>
@@ -276,13 +280,14 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
             <ul class="navbar-nav my-2">
               {this.canAdmin && (
                 <li className="nav-item">
-                  <Link
+                  <NavLink
+                    to="/admin"
                     className="nav-link"
-                    to={`/admin`}
+                    onMouseUp={linkEvent(this, this.handleHideExpandNavbar)}
                     title={i18n.t("admin_settings")}
                   >
                     <Icon icon="settings" />
-                  </Link>
+                  </NavLink>
                 </li>
               )}
             </ul>
@@ -290,7 +295,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
               /^\/search/
             ) && (
               <form
-                class="form-inline"
+                class="form-inline mr-2"
                 onSubmit={linkEvent(this, this.handleSearchSubmit)}
               >
                 <input
@@ -323,54 +328,131 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
               <>
                 <ul class="navbar-nav my-2">
                   <li className="nav-item">
-                    <Link
+                    <NavLink
                       className="nav-link"
                       to="/inbox"
-                      title={i18n.t("inbox")}
+                      onMouseUp={linkEvent(this, this.handleHideExpandNavbar)}
+                      title={i18n.t("unread_messages", {
+                        count: this.state.unreadInboxCount,
+                        formattedCount: numToSI(this.state.unreadInboxCount),
+                      })}
                     >
                       <Icon icon="bell" />
-                      {this.state.unreadCount > 0 && (
-                        <span
-                          class="ml-1 badge badge-light"
-                          aria-label={`${this.state.unreadCount} ${i18n.t(
-                            "unread_messages"
-                          )}`}
-                        >
-                          {this.state.unreadCount}
+                      {this.state.unreadInboxCount > 0 && (
+                        <span class="ml-1 badge badge-light">
+                          {numToSI(this.state.unreadInboxCount)}
                         </span>
                       )}
-                    </Link>
+                    </NavLink>
                   </li>
                 </ul>
+                {UserService.Instance.myUserInfo?.moderates.length > 0 && (
+                  <ul class="navbar-nav my-2">
+                    <li className="nav-item">
+                      <NavLink
+                        className="nav-link"
+                        to="/reports"
+                        onMouseUp={linkEvent(this, this.handleHideExpandNavbar)}
+                        title={i18n.t("unread_reports", {
+                          count: this.state.unreadReportCount,
+                          formattedCount: numToSI(this.state.unreadReportCount),
+                        })}
+                      >
+                        <Icon icon="shield" />
+                        {this.state.unreadReportCount > 0 && (
+                          <span class="ml-1 badge badge-light">
+                            {numToSI(this.state.unreadReportCount)}
+                          </span>
+                        )}
+                      </NavLink>
+                    </li>
+                  </ul>
+                )}
                 <ul class="navbar-nav">
-                  <li className="nav-item">
-                    <Link
-                      className="nav-link"
-                      to={`/u/${localUserView.person.name}`}
-                      title={i18n.t("settings")}
+                  <li class="nav-item dropdown">
+                    <button
+                      class="nav-link btn btn-link dropdown-toggle"
+                      onClick={linkEvent(this, this.handleToggleDropdown)}
+                      id="navbarDropdown"
+                      role="button"
+                      aria-expanded="false"
                     >
                       <span>
-                        {localUserView.person.avatar && showAvatars() && (
-                          <PictrsImage src={localUserView.person.avatar} icon />
+                        {person.avatar && showAvatars() && (
+                          <PictrsImage src={person.avatar} icon />
                         )}
-                        {localUserView.person.display_name
-                          ? localUserView.person.display_name
-                          : localUserView.person.name}
+                        {person.display_name
+                          ? person.display_name
+                          : person.name}
                       </span>
-                    </Link>
+                    </button>
+                    {this.state.showDropdown && (
+                      <div
+                        class="dropdown-content"
+                        onMouseLeave={linkEvent(
+                          this,
+                          this.handleToggleDropdown
+                        )}
+                      >
+                        <li className="nav-item">
+                          <NavLink
+                            to={`/u/${UserService.Instance.myUserInfo.local_user_view.person.name}`}
+                            className="nav-link"
+                            title={i18n.t("profile")}
+                          >
+                            <Icon icon="user" classes="mr-1" />
+                            {i18n.t("profile")}
+                          </NavLink>
+                        </li>
+                        <li className="nav-item">
+                          <NavLink
+                            to="/settings"
+                            className="nav-link"
+                            title={i18n.t("settings")}
+                          >
+                            <Icon icon="settings" classes="mr-1" />
+                            {i18n.t("settings")}
+                          </NavLink>
+                        </li>
+                        <li>
+                          <hr class="dropdown-divider" />
+                        </li>
+                        <li className="nav-item">
+                          <button
+                            className="nav-link btn btn-link"
+                            onClick={linkEvent(this, this.handleLogoutClick)}
+                            title="test"
+                          >
+                            <Icon icon="log-out" classes="mr-1" />
+                            {i18n.t("logout")}
+                          </button>
+                        </li>
+                      </div>
+                    )}
                   </li>
                 </ul>
               </>
             ) : (
               <ul class="navbar-nav my-2">
-                <li className="ml-2 nav-item">
-                  <Link
-                    className="btn btn-success"
+                <li className="nav-item">
+                  <NavLink
                     to="/login"
-                    title={i18n.t("login_sign_up")}
+                    className="nav-link"
+                    onMouseUp={linkEvent(this, this.handleHideExpandNavbar)}
+                    title={i18n.t("login")}
+                  >
+                    {i18n.t("login")}
+                  </NavLink>
+                </li>
+                <li className="nav-item">
+                  <NavLink
+                    to="/signup"
+                    className="nav-link"
+                    onMouseUp={linkEvent(this, this.handleHideExpandNavbar)}
+                    title={i18n.t("sign_up")}
                   >
-                    {i18n.t("login_sign_up")}
-                  </Link>
+                    {i18n.t("sign_up")}
+                  </NavLink>
                 </li>
               </ul>
             )}
@@ -380,11 +462,55 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
     );
   }
 
-  expandNavbar(i: Navbar) {
+  handleToggleExpandNavbar(i: Navbar) {
     i.state.expanded = !i.state.expanded;
     i.setState(i.state);
   }
 
+  handleHideExpandNavbar(i: Navbar) {
+    i.setState({ expanded: false, showDropdown: false });
+  }
+
+  handleSearchParam(i: Navbar, event: any) {
+    i.state.searchParam = event.target.value;
+    i.setState(i.state);
+  }
+
+  handleSearchSubmit(i: Navbar, event: any) {
+    event.preventDefault();
+    i.updateUrl();
+  }
+
+  handleSearchBtn(i: Navbar, event: any) {
+    event.preventDefault();
+    i.setState({ toggleSearch: true });
+
+    i.searchTextField.current.focus();
+    const offsetWidth = i.searchTextField.current.offsetWidth;
+    if (i.state.searchParam && offsetWidth > 100) {
+      i.updateUrl();
+    }
+  }
+
+  handleSearchBlur(i: Navbar, event: any) {
+    if (!(event.relatedTarget && event.relatedTarget.name !== "search-btn")) {
+      i.state.toggleSearch = false;
+      i.setState(i.state);
+    }
+  }
+
+  handleLogoutClick(i: Navbar) {
+    i.setState({ showDropdown: false, expanded: false });
+    UserService.Instance.logout();
+    window.location.href = "/";
+    location.reload();
+  }
+
+  handleToggleDropdown(i: Navbar) {
+    i.state.showDropdown = !i.state.showDropdown;
+    i.setState(i.state);
+  }
+
   parseMessage(msg: any) {
     let op = wsUserOp(msg);
     console.log(msg);
@@ -395,45 +521,32 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
       }
       return;
     } else if (msg.reconnect) {
-      toast(i18n.t("websocket_reconnected"), "success");
+      console.log(i18n.t("websocket_reconnected"));
       WebSocketService.Instance.send(
         wsClient.userJoin({
           auth: authField(),
         })
       );
       this.fetchUnreads();
-    } else if (op == UserOperation.GetReplies) {
-      let data = wsJsonToRes<GetRepliesResponse>(msg).data;
-      let unreadReplies = data.replies.filter(r => !r.comment.read);
-
-      this.state.replies = unreadReplies;
-      this.state.unreadCount = this.calculateUnreadCount();
-      this.setState(this.state);
-      this.sendUnreadCount();
-    } else if (op == UserOperation.GetPersonMentions) {
-      let data = wsJsonToRes<GetPersonMentionsResponse>(msg).data;
-      let unreadMentions = data.mentions.filter(r => !r.comment.read);
-
-      this.state.mentions = unreadMentions;
-      this.state.unreadCount = this.calculateUnreadCount();
+    } else if (op == UserOperation.GetUnreadCount) {
+      let data = wsJsonToRes<GetUnreadCountResponse>(msg).data;
+      this.state.unreadInboxCount =
+        data.replies + data.mentions + data.private_messages;
       this.setState(this.state);
       this.sendUnreadCount();
-    } else if (op == UserOperation.GetPrivateMessages) {
-      let data = wsJsonToRes<PrivateMessagesResponse>(msg).data;
-      let unreadMessages = data.private_messages.filter(
-        r => !r.private_message.read
-      );
-
-      this.state.messages = unreadMessages;
-      this.state.unreadCount = this.calculateUnreadCount();
+    } else if (op == UserOperation.GetReportCount) {
+      let data = wsJsonToRes<GetReportCountResponse>(msg).data;
+      this.state.unreadReportCount = data.post_reports + data.comment_reports;
       this.setState(this.state);
-      this.sendUnreadCount();
+      this.sendReportUnread();
     } else if (op == UserOperation.GetSite) {
       // This is only called on a successful login
       let data = wsJsonToRes<GetSiteResponse>(msg).data;
       console.log(data.my_user);
-      UserService.Instance.localUserView = data.my_user;
-      setTheme(UserService.Instance.localUserView.local_user.theme);
+      UserService.Instance.myUserInfo = data.my_user;
+      setTheme(
+        UserService.Instance.myUserInfo.local_user_view.local_user.theme
+      );
       i18n.changeLanguage(getLanguage());
       this.state.isLoggedIn = true;
       this.setState(this.state);
@@ -443,11 +556,10 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
       if (this.state.isLoggedIn) {
         if (
           data.recipient_ids.includes(
-            UserService.Instance.localUserView.local_user.id
+            UserService.Instance.myUserInfo.local_user_view.local_user.id
           )
         ) {
-          this.state.replies.push(data.comment_view);
-          this.state.unreadCount++;
+          this.state.unreadInboxCount++;
           this.setState(this.state);
           this.sendUnreadCount();
           notifyComment(data.comment_view, this.context.router);
@@ -459,10 +571,9 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
       if (this.state.isLoggedIn) {
         if (
           data.private_message_view.recipient.id ==
-          UserService.Instance.localUserView.person.id
+          UserService.Instance.myUserInfo.local_user_view.person.id
         ) {
-          this.state.messages.push(data.private_message_view);
-          this.state.unreadCount++;
+          this.state.unreadInboxCount++;
           this.setState(this.state);
           this.sendUnreadCount();
           notifyPrivateMessage(data.private_message_view, this.context.router);
@@ -472,39 +583,21 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
   }
 
   fetchUnreads() {
-    console.log("Fetching unreads...");
-    let repliesForm: GetReplies = {
-      sort: SortType.New,
-      unread_only: true,
-      page: 1,
-      limit: fetchLimit,
-      auth: authField(),
-    };
+    console.log("Fetching inbox unreads...");
 
-    let personMentionsForm: GetPersonMentions = {
-      sort: SortType.New,
-      unread_only: true,
-      page: 1,
-      limit: fetchLimit,
+    let unreadForm: GetUnreadCount = {
       auth: authField(),
     };
 
-    let privateMessagesForm: GetPrivateMessages = {
-      unread_only: true,
-      page: 1,
-      limit: fetchLimit,
+    WebSocketService.Instance.send(wsClient.getUnreadCount(unreadForm));
+
+    console.log("Fetching reports...");
+
+    let reportCountForm: GetReportCount = {
       auth: authField(),
     };
 
-    if (this.currentLocation !== "/inbox") {
-      WebSocketService.Instance.send(wsClient.getReplies(repliesForm));
-      WebSocketService.Instance.send(
-        wsClient.getPersonMentions(personMentionsForm)
-      );
-      WebSocketService.Instance.send(
-        wsClient.getPrivateMessages(privateMessagesForm)
-      );
-    }
+    WebSocketService.Instance.send(wsClient.getReportCount(reportCountForm));
   }
 
   get currentLocation() {
@@ -512,23 +605,21 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
   }
 
   sendUnreadCount() {
-    UserService.Instance.unreadCountSub.next(this.state.unreadCount);
+    UserService.Instance.unreadInboxCountSub.next(this.state.unreadInboxCount);
   }
 
-  calculateUnreadCount(): number {
-    return (
-      this.state.replies.filter(r => !r.comment.read).length +
-      this.state.mentions.filter(r => !r.comment.read).length +
-      this.state.messages.filter(r => !r.private_message.read).length
+  sendReportUnread() {
+    UserService.Instance.unreadReportCountSub.next(
+      this.state.unreadReportCount
     );
   }
 
   get canAdmin(): boolean {
     return (
-      UserService.Instance.localUserView &&
+      UserService.Instance.myUserInfo &&
       this.props.site_res.admins
         .map(a => a.person.id)
-        .includes(UserService.Instance.localUserView.person.id)
+        .includes(UserService.Instance.myUserInfo.local_user_view.person.id)
     );
   }
 
@@ -542,12 +633,12 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
   websocketEvents() {
     let msg = i18n.t("websocket_disconnected");
     WebSocketService.Instance.closeEventListener(() => {
-      toast(msg, "danger");
+      console.error(msg);
     });
   }
 
   requestNotificationPermission() {
-    if (UserService.Instance.localUserView) {
+    if (UserService.Instance.myUserInfo) {
       document.addEventListener("DOMContentLoaded", function () {
         if (!Notification) {
           toast(i18n.t("notifications_error"), "danger");