index.js (3233B)
1 /* 2 GoToSocial 3 Copyright (C) GoToSocial Authors admin@gotosocial.org 4 SPDX-License-Identifier: AGPL-3.0-or-later 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 "use strict"; 21 22 const React = require("react"); 23 const { nanoid } = require("nanoid"); 24 const { Redirect } = require("wouter"); 25 26 const { urlSafe } = require("./util"); 27 28 const { 29 Sidebar, 30 ViewRouter, 31 MenuComponent 32 } = require("./components"); 33 34 function createNavigation(rootUrl, menus) { 35 const root = { 36 url: rootUrl, 37 links: [], 38 }; 39 40 const routing = []; 41 42 const menuTree = menus.map((creatorFunc) => 43 creatorFunc(root, routing) 44 ); 45 46 return { 47 Sidebar: Sidebar(menuTree, routing), 48 ViewRouter: ViewRouter(routing, root.redirectUrl) 49 }; 50 } 51 52 function MenuEntry(name, opts, contents) { 53 if (contents == undefined) { // opts argument is optional 54 contents = opts; 55 opts = {}; 56 } 57 58 return function createMenuEntry(root, routing) { 59 const type = Array.isArray(contents) ? "category" : "view"; 60 61 let urlParts = [root.url]; 62 if (opts.url != "") { 63 urlParts.push(opts.url ?? urlSafe(name)); 64 } 65 66 const url = urlParts.join("/"); 67 let routingUrl = url; 68 69 if (opts.wildcard) { 70 routingUrl += "/:wildcard*"; 71 } 72 73 const entry = { 74 name, type, 75 url, routingUrl, 76 key: nanoid(), 77 permissions: opts.permissions ?? false, 78 icon: opts.icon, 79 links: [routingUrl], 80 level: (root.level ?? -1) + 1, 81 redirectUrl: opts.defaultUrl 82 }; 83 84 if (type == "category") { 85 let entries = contents.map((creatorFunc) => creatorFunc(entry, routing)); 86 let routes = []; 87 88 entries.forEach((e) => { 89 // move empty wildcard routes to end of category, to prevent overlap 90 if (e.url == entry.url) { 91 routes.unshift(e); 92 } else { 93 routes.push(e); 94 } 95 }); 96 routes.reverse(); 97 98 routing.push(...routes); 99 100 if (opts.redirectUrl != entry.url) { 101 routing.push({ 102 key: entry.key, 103 url: entry.url, 104 permissions: entry.permissions, 105 routingUrl: entry.redirectUrl + "/:fallback*", 106 view: React.createElement(Redirect, { to: entry.redirectUrl }) 107 }); 108 entry.url = entry.redirectUrl; 109 } 110 111 root.links.push(...entry.links); 112 113 entry.MenuEntry = React.createElement( 114 MenuComponent, 115 entry, 116 entries.map((e) => e.MenuEntry) 117 ); 118 } else { 119 entry.links.push(routingUrl); 120 root.links.push(routingUrl); 121 122 entry.view = React.createElement(contents, { baseUrl: url }); 123 entry.MenuEntry = React.createElement(MenuComponent, entry); 124 } 125 126 if (root.redirectUrl == undefined) { 127 root.redirectUrl = entry.url; 128 } 129 130 return entry; 131 }; 132 } 133 134 module.exports = { 135 createNavigation, 136 Menu: MenuEntry, 137 Item: MenuEntry 138 };