mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-28 13:52:25 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			175 lines
		
	
	
	
		
			4.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			175 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>
 | |
| 	);
 | |
| }
 |