mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-03 20:42:26 -06:00 
			
		
		
		
	
		
			
	
	
		
			176 lines
		
	
	
	
		
			4.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			176 lines
		
	
	
	
		
			4.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| 
								 | 
							
								/*
							 | 
						||
| 
								 | 
							
									GoToSocial
							 | 
						||
| 
								 | 
							
									Copyright (C) GoToSocial Authors admin@gotosocial.org
							 | 
						||
| 
								 | 
							
									SPDX-License-Identifier: AGPL-3.0-or-later
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									This program is free software: you can redistribute it and/or modify
							 | 
						||
| 
								 | 
							
									it under the terms of the GNU Affero General Public License as published by
							 | 
						||
| 
								 | 
							
									the Free Software Foundation, either version 3 of the License, or
							 | 
						||
| 
								 | 
							
									(at your option) any later version.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									This program is distributed in the hope that it will be useful,
							 | 
						||
| 
								 | 
							
									but WITHOUT ANY WARRANTY; without even the implied warranty of
							 | 
						||
| 
								 | 
							
									MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
							 | 
						||
| 
								 | 
							
									GNU Affero General Public License for more details.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									You should have received a copy of the GNU Affero General Public License
							 | 
						||
| 
								 | 
							
									along with this program.  If not, see <http://www.gnu.org/licenses/>.
							 | 
						||
| 
								 | 
							
								*/
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import React, { PropsWithChildren } from "react";
							 | 
						||
| 
								 | 
							
								import { Link, useRoute } from "wouter";
							 | 
						||
| 
								 | 
							
								import {
							 | 
						||
| 
								 | 
							
									BaseUrlContext,
							 | 
						||
| 
								 | 
							
									MenuLevelContext,
							 | 
						||
| 
								 | 
							
									useBaseUrl,
							 | 
						||
| 
								 | 
							
									useHasPermission,
							 | 
						||
| 
								 | 
							
									useMenuLevel,
							 | 
						||
| 
								 | 
							
								} from "./util";
							 | 
						||
| 
								 | 
							
								import UserLogoutCard from "../../components/user-logout-card";
							 | 
						||
| 
								 | 
							
								import { nanoid } from "nanoid";
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export interface MenuItemProps {
							 | 
						||
| 
								 | 
							
									/**
							 | 
						||
| 
								 | 
							
									 * Name / title of this menu item.
							 | 
						||
| 
								 | 
							
									 */
							 | 
						||
| 
								 | 
							
									name?: string;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									/**
							 | 
						||
| 
								 | 
							
									 * Url path component for this menu item.
							 | 
						||
| 
								 | 
							
									 */
							 | 
						||
| 
								 | 
							
									itemUrl: string;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									/**
							 | 
						||
| 
								 | 
							
									 * If this menu item is a category containing
							 | 
						||
| 
								 | 
							
									 * children, which child should be selected by
							 | 
						||
| 
								 | 
							
									 * default when category title is clicked.
							 | 
						||
| 
								 | 
							
									 * 
							 | 
						||
| 
								 | 
							
									 * Optional, use for categories only.
							 | 
						||
| 
								 | 
							
									 */
							 | 
						||
| 
								 | 
							
									defaultChild?: string;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									/**
							 | 
						||
| 
								 | 
							
									 * Permissions required to access this
							 | 
						||
| 
								 | 
							
									 * menu item (none, "moderator", "admin").
							 | 
						||
| 
								 | 
							
									 */
							 | 
						||
| 
								 | 
							
									permissions?: string[];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									/**
							 | 
						||
| 
								 | 
							
									 * Fork-awesome string to render
							 | 
						||
| 
								 | 
							
									 * icon for this menu item.
							 | 
						||
| 
								 | 
							
									 */
							 | 
						||
| 
								 | 
							
									icon?: string;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function MenuItem(props: PropsWithChildren<MenuItemProps>) {
							 | 
						||
| 
								 | 
							
									const {
							 | 
						||
| 
								 | 
							
										name,
							 | 
						||
| 
								 | 
							
										itemUrl,
							 | 
						||
| 
								 | 
							
										defaultChild,
							 | 
						||
| 
								 | 
							
										permissions,
							 | 
						||
| 
								 | 
							
										icon,
							 | 
						||
| 
								 | 
							
										children,
							 | 
						||
| 
								 | 
							
									} = props;
							 | 
						||
| 
								 | 
							
									
							 | 
						||
| 
								 | 
							
									// Derive where this item is
							 | 
						||
| 
								 | 
							
									// in terms of URL routing.
							 | 
						||
| 
								 | 
							
									const baseUrl = useBaseUrl();
							 | 
						||
| 
								 | 
							
									const thisUrl = [ baseUrl, itemUrl ].join('/');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Derive where this item is in
							 | 
						||
| 
								 | 
							
									// terms of nesting within the menu.
							 | 
						||
| 
								 | 
							
									const thisLevel = useMenuLevel();
							 | 
						||
| 
								 | 
							
									const nextLevel = thisLevel+1;
							 | 
						||
| 
								 | 
							
									const topLevel = thisLevel === 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Check whether this item is currently active
							 | 
						||
| 
								 | 
							
									// (ie., user has selected it in the menu).
							 | 
						||
| 
								 | 
							
									//
							 | 
						||
| 
								 | 
							
									// This uses a wildcard to mark both parent
							 | 
						||
| 
								 | 
							
									// and relevant child as active.
							 | 
						||
| 
								 | 
							
									//
							 | 
						||
| 
								 | 
							
									// See:
							 | 
						||
| 
								 | 
							
									// https://github.com/molefrog/wouter?tab=readme-ov-file#useroute-route-matching-and-parameters
							 | 
						||
| 
								 | 
							
									const [isActive] = useRoute([ thisUrl, "*?" ].join("/"));
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Don't render item if logged-in user
							 | 
						||
| 
								 | 
							
									// doesn't have permissions to use it.
							 | 
						||
| 
								 | 
							
									if (!useHasPermission(permissions)) {
							 | 
						||
| 
								 | 
							
										return null;
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Check whether this item has children.
							 | 
						||
| 
								 | 
							
									const hasChildren = children !== undefined;
							 | 
						||
| 
								 | 
							
									const childrenArray = hasChildren && Array.isArray(children);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Class name of the item varies depending
							 | 
						||
| 
								 | 
							
									// on where it is in the menu, and whether
							 | 
						||
| 
								 | 
							
									// it has children beneath it or not.
							 | 
						||
| 
								 | 
							
									const classNames: string[] = [];
							 | 
						||
| 
								 | 
							
									if (topLevel) {
							 | 
						||
| 
								 | 
							
										classNames.push("category", "top-level");
							 | 
						||
| 
								 | 
							
									} else {
							 | 
						||
| 
								 | 
							
										if (thisLevel === 1 && hasChildren) {
							 | 
						||
| 
								 | 
							
											classNames.push("category", "expanding");
							 | 
						||
| 
								 | 
							
										} else if (thisLevel === 1 && !hasChildren) {
							 | 
						||
| 
								 | 
							
											classNames.push("view", "expanding");
							 | 
						||
| 
								 | 
							
										} else if (thisLevel === 2) {
							 | 
						||
| 
								 | 
							
											classNames.push("view", "nested");
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									if (isActive) {
							 | 
						||
| 
								 | 
							
										classNames.push("active");
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									let content: React.JSX.Element | null;
							 | 
						||
| 
								 | 
							
									if ((isActive || topLevel) && childrenArray) {
							 | 
						||
| 
								 | 
							
										// Render children as a nested list.
							 | 
						||
| 
								 | 
							
										content = <ul>{children}</ul>;
							 | 
						||
| 
								 | 
							
									} else if (isActive && hasChildren) {
							 | 
						||
| 
								 | 
							
										// Render child as solo element.
							 | 
						||
| 
								 | 
							
										content = <>{children}</>;
							 | 
						||
| 
								 | 
							
									} else {
							 | 
						||
| 
								 | 
							
										// Not active: hide children.
							 | 
						||
| 
								 | 
							
										content = null;
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// If a default child is defined, this item should point to that.
							 | 
						||
| 
								 | 
							
									const href = defaultChild ? [ thisUrl, defaultChild ].join("/") : thisUrl;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									return (
							 | 
						||
| 
								 | 
							
										<li key={nanoid()} className={classNames.join(" ")}>
							 | 
						||
| 
								 | 
							
											<Link href={href} className="title">
							 | 
						||
| 
								 | 
							
												<span>
							 | 
						||
| 
								 | 
							
													{icon && <i className={`icon fa fa-fw ${icon}`} aria-hidden="true" />}
							 | 
						||
| 
								 | 
							
													{name}
							 | 
						||
| 
								 | 
							
												</span>
							 | 
						||
| 
								 | 
							
											</Link>
							 | 
						||
| 
								 | 
							
											{ content && 
							 | 
						||
| 
								 | 
							
												<BaseUrlContext.Provider value={thisUrl}>
							 | 
						||
| 
								 | 
							
													<MenuLevelContext.Provider value={nextLevel}>
							 | 
						||
| 
								 | 
							
														{content}
							 | 
						||
| 
								 | 
							
													</MenuLevelContext.Provider>
							 | 
						||
| 
								 | 
							
												</BaseUrlContext.Provider>
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										</li>
							 | 
						||
| 
								 | 
							
									);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export interface SidebarMenuProps{}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function SidebarMenu({ children }: PropsWithChildren<SidebarMenuProps>) {
							 | 
						||
| 
								 | 
							
									return (
							 | 
						||
| 
								 | 
							
										<div className="sidebar">
							 | 
						||
| 
								 | 
							
											<UserLogoutCard />
							 | 
						||
| 
								 | 
							
											<nav className="menu-tree">
							 | 
						||
| 
								 | 
							
												<MenuLevelContext.Provider value={0}>
							 | 
						||
| 
								 | 
							
													<ul className="top-level">
							 | 
						||
| 
								 | 
							
														{children}
							 | 
						||
| 
								 | 
							
													</ul>
							 | 
						||
| 
								 | 
							
												</MenuLevelContext.Provider>
							 | 
						||
| 
								 | 
							
											</nav>
							 | 
						||
| 
								 | 
							
										</div>
							 | 
						||
| 
								 | 
							
									);
							 | 
						||
| 
								 | 
							
								}
							 |