]> Untitled Git - lemmy-ui.git/blobdiff - src/shared/components/app/navbar.tsx
Merge branch 'main' into nicer-error-hnadling
[lemmy-ui.git] / src / shared / components / app / navbar.tsx
index f9497c7b09c2d6dc1ad216c9912483132cf6cc10..d508c4abae7db2d9085a9eb2332aa9084b8f4aea 100644 (file)
@@ -1,4 +1,4 @@
-import { Component, createRef, linkEvent } from "inferno";
+import { Component, linkEvent } from "inferno";
 import { NavLink } from "inferno-router";
 import {
   CommentResponse,
@@ -35,26 +35,18 @@ import { Icon } from "../common/icon";
 import { PictrsImage } from "../common/pictrs-image";
 
 interface NavbarProps {
-  siteRes: GetSiteResponse;
+  siteRes?: GetSiteResponse;
 }
 
 interface NavbarState {
+  expanded: boolean;
   unreadInboxCount: number;
   unreadReportCount: number;
   unreadApplicationCount: number;
+  showDropdown: boolean;
   onSiteBanner?(url: string): any;
 }
 
-function handleCollapseClick(i: Navbar) {
-  if (i.collapseButtonRef.current?.ariaExpanded === "true") {
-    i.collapseButtonRef.current?.click();
-  }
-}
-
-function handleLogOut() {
-  UserService.Instance.logout();
-}
-
 export class Navbar extends Component<NavbarProps, NavbarState> {
   private wsSub: Subscription;
   private userSub: Subscription;
@@ -65,9 +57,10 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
     unreadInboxCount: 0,
     unreadReportCount: 0,
     unreadApplicationCount: 0,
+    expanded: false,
+    showDropdown: false,
   };
   subscription: any;
-  collapseButtonRef = createRef<HTMLButtonElement>();
 
   constructor(props: any, context: any) {
     super(props, context);
@@ -120,228 +113,85 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
     this.unreadApplicationCountSub.unsubscribe();
   }
 
-  // TODO class active corresponding to current page
   render() {
-    const siteView = this.props.siteRes.site_view;
-    const person = UserService.Instance.myUserInfo?.local_user_view.person;
+    return this.navbar();
+  }
+
+  // TODO class active corresponding to current page
+  navbar() {
+    let siteView = this.props.siteRes?.site_view;
+    let person = UserService.Instance.myUserInfo?.local_user_view.person;
     return (
-      <nav className="navbar navbar-expand-md navbar-light shadow-sm p-0 px-3 container-lg">
-        <NavLink
-          to="/"
-          title={siteView.site.description ?? siteView.site.name}
-          className="d-flex align-items-center navbar-brand mr-md-3"
-          onMouseUp={linkEvent(this, handleCollapseClick)}
-        >
-          {siteView.site.icon && showAvatars() && (
-            <PictrsImage src={siteView.site.icon} icon />
-          )}
-          {siteView.site.name}
-        </NavLink>
-        {person && (
-          <ul className="navbar-nav d-flex flex-row ml-auto d-md-none">
-            <li className="nav-item">
-              <NavLink
-                to="/inbox"
-                className="p-1 nav-link border-0"
-                title={i18n.t("unread_messages", {
-                  count: Number(this.state.unreadInboxCount),
-                  formattedCount: numToSI(this.state.unreadInboxCount),
-                })}
-                onMouseUp={linkEvent(this, handleCollapseClick)}
-              >
-                <Icon icon="bell" />
-                {this.state.unreadInboxCount > 0 && (
-                  <span className="mx-1 badge badge-light">
-                    {numToSI(this.state.unreadInboxCount)}
-                  </span>
-                )}
-              </NavLink>
-            </li>
-            {this.moderatesSomething && (
-              <li className="nav-item">
-                <NavLink
-                  to="/reports"
-                  className="p-1 nav-link border-0"
-                  title={i18n.t("unread_reports", {
-                    count: Number(this.state.unreadReportCount),
-                    formattedCount: numToSI(this.state.unreadReportCount),
-                  })}
-                  onMouseUp={linkEvent(this, handleCollapseClick)}
-                >
-                  <Icon icon="shield" />
-                  {this.state.unreadReportCount > 0 && (
-                    <span className="mx-1 badge badge-light">
-                      {numToSI(this.state.unreadReportCount)}
-                    </span>
-                  )}
-                </NavLink>
-              </li>
+      <nav className="navbar navbar-expand-md navbar-light shadow-sm p-0 px-3">
+        <div className="container-lg">
+          <NavLink
+            to="/"
+            onMouseUp={linkEvent(this, this.handleHideExpandNavbar)}
+            title={siteView?.site.description ?? siteView?.site.name ?? "Lemmy"}
+            className="d-flex align-items-center navbar-brand mr-md-3"
+          >
+            {siteView?.site.icon && showAvatars() && (
+              <PictrsImage src={siteView.site.icon} icon />
             )}
-            {amAdmin() && (
-              <li className="nav-item">
-                <NavLink
-                  to="/registration_applications"
-                  className="p-1 nav-link border-0"
-                  title={i18n.t("unread_registration_applications", {
-                    count: Number(this.state.unreadApplicationCount),
-                    formattedCount: numToSI(this.state.unreadApplicationCount),
-                  })}
-                  onMouseUp={linkEvent(this, handleCollapseClick)}
-                >
-                  <Icon icon="clipboard" />
-                  {this.state.unreadApplicationCount > 0 && (
-                    <span className="mx-1 badge badge-light">
-                      {numToSI(this.state.unreadApplicationCount)}
-                    </span>
-                  )}
-                </NavLink>
-              </li>
-            )}
-          </ul>
-        )}
-        <button
-          className="navbar-toggler border-0 p-1"
-          type="button"
-          aria-label="menu"
-          data-tippy-content={i18n.t("expand_here")}
-          data-bs-toggle="collapse"
-          data-bs-target="#navbarDropdown"
-          aria-controls="navbarDropdown"
-          aria-expanded="false"
-          ref={this.collapseButtonRef}
-        >
-          <Icon icon="menu" />
-        </button>
-        <div className="collapse navbar-collapse my-2" id="navbarDropdown">
-          <ul className="mr-auto navbar-nav">
-            <li className="nav-item">
-              <NavLink
-                to="/communities"
-                className="nav-link"
-                title={i18n.t("communities")}
-                onMouseUp={linkEvent(this, handleCollapseClick)}
-              >
-                {i18n.t("communities")}
-              </NavLink>
-            </li>
-            <li className="nav-item">
-              {/* TODO make sure this works: https://github.com/infernojs/inferno/issues/1608 */}
-              <NavLink
-                to={{
-                  pathname: "/create_post",
-                  search: "",
-                  hash: "",
-                  key: "",
-                  state: { prevPath: this.currentLocation },
-                }}
-                className="nav-link"
-                title={i18n.t("create_post")}
-                onMouseUp={linkEvent(this, handleCollapseClick)}
-              >
-                {i18n.t("create_post")}
-              </NavLink>
-            </li>
-            {canCreateCommunity(this.props.siteRes) && (
-              <li className="nav-item">
-                <NavLink
-                  to="/create_community"
-                  className="nav-link"
-                  title={i18n.t("create_community")}
-                  onMouseUp={linkEvent(this, handleCollapseClick)}
-                >
-                  {i18n.t("create_community")}
-                </NavLink>
-              </li>
-            )}
-            <li className="nav-item">
-              <a
-                className="nav-link"
-                title={i18n.t("support_lemmy")}
-                href={donateLemmyUrl}
-              >
-                <Icon icon="heart" classes="small" />
-              </a>
-            </li>
-          </ul>
-          <ul className="navbar-nav">
-            {!this.context.router.history.location.pathname.match(
-              /^\/search/
-            ) && (
-              <li className="nav-item">
-                <NavLink
-                  to="/search"
-                  className="nav-link"
-                  title={i18n.t("search")}
-                  onMouseUp={linkEvent(this, handleCollapseClick)}
-                >
-                  <Icon icon="search" />
-                </NavLink>
-              </li>
-            )}
-            {amAdmin() && (
-              <li className="nav-item">
-                <NavLink
-                  to="/admin"
-                  className="nav-link"
-                  title={i18n.t("admin_settings")}
-                  onMouseUp={linkEvent(this, handleCollapseClick)}
-                >
-                  <Icon icon="settings" />
-                </NavLink>
-              </li>
-            )}
-            {person ? (
-              <>
+            {siteView?.site.name ?? "Lemmy"}
+          </NavLink>
+          {UserService.Instance.myUserInfo && (
+            <>
+              <ul className="navbar-nav ml-auto">
                 <li className="nav-item">
                   <NavLink
-                    className="nav-link"
                     to="/inbox"
+                    className="p-1 navbar-toggler nav-link border-0"
+                    onMouseUp={linkEvent(this, this.handleHideExpandNavbar)}
                     title={i18n.t("unread_messages", {
                       count: Number(this.state.unreadInboxCount),
                       formattedCount: numToSI(this.state.unreadInboxCount),
                     })}
-                    onMouseUp={linkEvent(this, handleCollapseClick)}
                   >
                     <Icon icon="bell" />
                     {this.state.unreadInboxCount > 0 && (
-                      <span className="ml-1 badge badge-light">
+                      <span className="mx-1 badge badge-light">
                         {numToSI(this.state.unreadInboxCount)}
                       </span>
                     )}
                   </NavLink>
                 </li>
-                {this.moderatesSomething && (
+              </ul>
+              {this.moderatesSomething && (
+                <ul className="navbar-nav ml-1">
                   <li className="nav-item">
                     <NavLink
-                      className="nav-link"
                       to="/reports"
+                      className="p-1 navbar-toggler nav-link border-0"
+                      onMouseUp={linkEvent(this, this.handleHideExpandNavbar)}
                       title={i18n.t("unread_reports", {
                         count: Number(this.state.unreadReportCount),
                         formattedCount: numToSI(this.state.unreadReportCount),
                       })}
-                      onMouseUp={linkEvent(this, handleCollapseClick)}
                     >
                       <Icon icon="shield" />
                       {this.state.unreadReportCount > 0 && (
-                        <span className="ml-1 badge badge-light">
+                        <span className="mx-1 badge badge-light">
                           {numToSI(this.state.unreadReportCount)}
                         </span>
                       )}
                     </NavLink>
                   </li>
-                )}
-                {amAdmin() && (
+                </ul>
+              )}
+              {amAdmin() && (
+                <ul className="navbar-nav ml-1">
                   <li className="nav-item">
                     <NavLink
                       to="/registration_applications"
-                      className="nav-link"
+                      className="p-1 navbar-toggler nav-link border-0"
+                      onMouseUp={linkEvent(this, this.handleHideExpandNavbar)}
                       title={i18n.t("unread_registration_applications", {
                         count: Number(this.state.unreadApplicationCount),
                         formattedCount: numToSI(
                           this.state.unreadApplicationCount
                         ),
                       })}
-                      onMouseUp={linkEvent(this, handleCollapseClick)}
                     >
                       <Icon icon="clipboard" />
                       {this.state.unreadApplicationCount > 0 && (
@@ -351,70 +201,241 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
                       )}
                     </NavLink>
                   </li>
-                )}
-                {person && (
-                  <div className="dropdown">
-                    <button
-                      className="btn dropdown-toggle"
-                      role="button"
-                      aria-expanded="false"
-                      data-bs-toggle="dropdown"
+                </ul>
+              )}
+            </>
+          )}
+          <button
+            className="navbar-toggler border-0 p-1"
+            type="button"
+            aria-label="menu"
+            onClick={linkEvent(this, this.handleToggleExpandNavbar)}
+            data-tippy-content={i18n.t("expand_here")}
+          >
+            <Icon icon="menu" />
+          </button>
+          <div
+            className={`${!this.state.expanded && "collapse"} navbar-collapse`}
+          >
+            <ul className="navbar-nav my-2 mr-auto">
+              <li className="nav-item">
+                <NavLink
+                  to="/communities"
+                  className="nav-link"
+                  onMouseUp={linkEvent(this, this.handleHideExpandNavbar)}
+                  title={i18n.t("communities")}
+                >
+                  {i18n.t("communities")}
+                </NavLink>
+              </li>
+              <li className="nav-item">
+                {/* TODO make sure this works: https://github.com/infernojs/inferno/issues/1608 */}
+                <NavLink
+                  to={{
+                    pathname: "/create_post",
+                    search: "",
+                    hash: "",
+                    key: "",
+                    state: { prevPath: this.currentLocation },
+                  }}
+                  className="nav-link"
+                  onMouseUp={linkEvent(this, this.handleHideExpandNavbar)}
+                  title={i18n.t("create_post")}
+                >
+                  {i18n.t("create_post")}
+                </NavLink>
+              </li>
+              {this.props.siteRes && canCreateCommunity(this.props.siteRes) && (
+                <li className="nav-item">
+                  <NavLink
+                    to="/create_community"
+                    className="nav-link"
+                    onMouseUp={linkEvent(this, this.handleHideExpandNavbar)}
+                    title={i18n.t("create_community")}
+                  >
+                    {i18n.t("create_community")}
+                  </NavLink>
+                </li>
+              )}
+              <li className="nav-item">
+                <a
+                  className="nav-link"
+                  title={i18n.t("support_lemmy")}
+                  href={donateLemmyUrl}
+                >
+                  <Icon icon="heart" classes="small" />
+                </a>
+              </li>
+            </ul>
+            <ul className="navbar-nav my-2">
+              {amAdmin() && (
+                <li className="nav-item">
+                  <NavLink
+                    to="/admin"
+                    className="nav-link"
+                    onMouseUp={linkEvent(this, this.handleHideExpandNavbar)}
+                    title={i18n.t("admin_settings")}
+                  >
+                    <Icon icon="settings" />
+                  </NavLink>
+                </li>
+              )}
+            </ul>
+            {!this.context.router.history.location.pathname.match(
+              /^\/search/
+            ) && (
+              <ul className="navbar-nav">
+                <li className="nav-item">
+                  <NavLink
+                    to="/search"
+                    className="nav-link"
+                    title={i18n.t("search")}
+                  >
+                    <Icon icon="search" />
+                  </NavLink>
+                </li>
+              </ul>
+            )}
+            {UserService.Instance.myUserInfo ? (
+              <>
+                <ul className="navbar-nav my-2">
+                  <li className="nav-item">
+                    <NavLink
+                      className="nav-link"
+                      to="/inbox"
+                      onMouseUp={linkEvent(this, this.handleHideExpandNavbar)}
+                      title={i18n.t("unread_messages", {
+                        count: Number(this.state.unreadInboxCount),
+                        formattedCount: numToSI(this.state.unreadInboxCount),
+                      })}
                     >
-                      {showAvatars() && person.avatar && (
-                        <PictrsImage src={person.avatar} icon />
+                      <Icon icon="bell" />
+                      {this.state.unreadInboxCount > 0 && (
+                        <span className="ml-1 badge badge-light">
+                          {numToSI(this.state.unreadInboxCount)}
+                        </span>
                       )}
-                      {person.display_name ?? person.name}
-                    </button>
-                    <ul
-                      className="dropdown-menu"
-                      style={{ "min-width": "fit-content" }}
-                    >
-                      <li>
-                        <NavLink
-                          to={`/u/${person.name}`}
-                          className="dropdown-item px-2"
-                          title={i18n.t("profile")}
-                          onMouseUp={linkEvent(this, handleCollapseClick)}
-                        >
-                          <Icon icon="user" classes="mr-1" />
-                          {i18n.t("profile")}
-                        </NavLink>
-                      </li>
-                      <li>
-                        <NavLink
-                          to="/settings"
-                          className="dropdown-item px-2"
-                          title={i18n.t("settings")}
-                          onMouseUp={linkEvent(this, handleCollapseClick)}
-                        >
-                          <Icon icon="settings" classes="mr-1" />
-                          {i18n.t("settings")}
-                        </NavLink>
-                      </li>
-                      <li>
-                        <hr className="dropdown-divider" />
-                      </li>
-                      <li>
-                        <button
-                          className="dropdown-item btn btn-link px-2"
-                          onClick={handleLogOut}
+                    </NavLink>
+                  </li>
+                </ul>
+                {this.moderatesSomething && (
+                  <ul className="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: Number(this.state.unreadReportCount),
+                          formattedCount: numToSI(this.state.unreadReportCount),
+                        })}
+                      >
+                        <Icon icon="shield" />
+                        {this.state.unreadReportCount > 0 && (
+                          <span className="ml-1 badge badge-light">
+                            {numToSI(this.state.unreadReportCount)}
+                          </span>
+                        )}
+                      </NavLink>
+                    </li>
+                  </ul>
+                )}
+                {amAdmin() && (
+                  <ul className="navbar-nav my-2">
+                    <li className="nav-item">
+                      <NavLink
+                        to="/registration_applications"
+                        className="nav-link"
+                        onMouseUp={linkEvent(this, this.handleHideExpandNavbar)}
+                        title={i18n.t("unread_registration_applications", {
+                          count: Number(this.state.unreadApplicationCount),
+                          formattedCount: numToSI(
+                            this.state.unreadApplicationCount
+                          ),
+                        })}
+                      >
+                        <Icon icon="clipboard" />
+                        {this.state.unreadApplicationCount > 0 && (
+                          <span className="mx-1 badge badge-light">
+                            {numToSI(this.state.unreadApplicationCount)}
+                          </span>
+                        )}
+                      </NavLink>
+                    </li>
+                  </ul>
+                )}
+                {person && (
+                  <ul className="navbar-nav">
+                    <li className="nav-item dropdown">
+                      <button
+                        className="nav-link btn btn-link dropdown-toggle"
+                        onClick={linkEvent(this, this.handleToggleDropdown)}
+                        id="navbarDropdown"
+                        role="button"
+                        aria-expanded="false"
+                      >
+                        <span>
+                          {showAvatars() && person.avatar && (
+                            <PictrsImage src={person.avatar} icon />
+                          )}
+                          {person.display_name ?? person.name}
+                        </span>
+                      </button>
+                      {this.state.showDropdown && (
+                        <div
+                          className="dropdown-content"
+                          onMouseLeave={linkEvent(
+                            this,
+                            this.handleToggleDropdown
+                          )}
                         >
-                          <Icon icon="log-out" classes="mr-1" />
-                          {i18n.t("logout")}
-                        </button>
-                      </li>
-                    </ul>
-                  </div>
+                          <li className="nav-item">
+                            <NavLink
+                              to={`/u/${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 className="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 className="navbar-nav my-2">
                 <li className="nav-item">
                   <NavLink
                     to="/login"
                     className="nav-link"
+                    onMouseUp={linkEvent(this, this.handleHideExpandNavbar)}
                     title={i18n.t("login")}
-                    onMouseUp={linkEvent(this, handleCollapseClick)}
                   >
                     {i18n.t("login")}
                   </NavLink>
@@ -423,15 +444,15 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
                   <NavLink
                     to="/signup"
                     className="nav-link"
+                    onMouseUp={linkEvent(this, this.handleHideExpandNavbar)}
                     title={i18n.t("sign_up")}
-                    onMouseUp={linkEvent(this, handleCollapseClick)}
                   >
                     {i18n.t("sign_up")}
                   </NavLink>
                 </li>
-              </>
+              </ul>
             )}
-          </ul>
+          </div>
         </div>
       </nav>
     );
@@ -443,6 +464,23 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
     return amAdmin() || moderatesS;
   }
 
+  handleToggleExpandNavbar(i: Navbar) {
+    i.setState({ expanded: !i.state.expanded });
+  }
+
+  handleHideExpandNavbar(i: Navbar) {
+    i.setState({ expanded: false, showDropdown: false });
+  }
+
+  handleLogoutClick(i: Navbar) {
+    i.setState({ showDropdown: false, expanded: false });
+    UserService.Instance.logout();
+  }
+
+  handleToggleDropdown(i: Navbar) {
+    i.setState({ showDropdown: !i.state.showDropdown });
+  }
+
   parseMessage(msg: any) {
     let op = wsUserOp(msg);
     console.log(msg);
@@ -574,4 +612,4 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
       });
     }
   }
-}
+}
\ No newline at end of file