From 571dfd537895a5dbfaeaf6d249c72a654a2ac3b3 Mon Sep 17 00:00:00 2001 From: SRHTNIN Date: Mon, 21 Sep 2026 19:40:05 +0200 Subject: [PATCH] Added dynamic urls instead of a static ones. --- Backend/Main.py | 15 ++++++-- Frontend/src/App.jsx | 83 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 92 insertions(+), 6 deletions(-) diff --git a/Backend/Main.py b/Backend/Main.py index f11b922..4512fd8 100644 --- a/Backend/Main.py +++ b/Backend/Main.py @@ -10,8 +10,7 @@ if str(ApplicationDirectory) not in sys.path: from fastapi import Depends, FastAPI, File, HTTPException, Request, Response, UploadFile from fastapi.middleware.cors import CORSMiddleware -from fastapi.responses import PlainTextResponse, StreamingResponse -from fastapi.staticfiles import StaticFiles +from fastapi.responses import FileResponse, PlainTextResponse, StreamingResponse from Auth import CreateSession, DeleteSession, GetCurrentUser, RequireAdmin, SessionCookieName from BookReader import ReadBookForFile @@ -704,5 +703,15 @@ def DeleteThemeEndpoint(Name: str, CurrentUser: dict = Depends(GetCurrentUser)) FrontendDist = ApplicationDirectory.parent / "Frontend" / "dist" + if FrontendDist.is_dir(): - Application.mount("/", StaticFiles(directory=str(FrontendDist), html=True), name="Frontend") + + @Application.get("/{FullPath:path}") + def ServeFrontendEndpoint(FullPath: str): + CandidatePath = (FrontendDist / FullPath).resolve() + IsWithinDist = CandidatePath == FrontendDist or FrontendDist in CandidatePath.parents + + if FullPath and IsWithinDist and CandidatePath.is_file(): + return FileResponse(CandidatePath) + + return FileResponse(FrontendDist / "index.html") diff --git a/Frontend/src/App.jsx b/Frontend/src/App.jsx index 8ae53a6..1d8b69a 100644 --- a/Frontend/src/App.jsx +++ b/Frontend/src/App.jsx @@ -14,6 +14,7 @@ import { FetchRoots, FetchSettings, FetchTheme, + FetchTree, } from "./Api.js"; import { PaletteRoles } from "./ThemeRoles.js"; @@ -39,6 +40,19 @@ function GetParentPath(FilePath) { return FilePath.includes("/") ? FilePath.slice(0, FilePath.lastIndexOf("/")) : ""; } +function BuildLocationPath(RootName, ViewPath, ViewIsDirectory) { + const Segments = [RootName, ...ViewPath.split("/").filter(Boolean)]; + const Encoded = "/" + Segments.map(encodeURIComponent).join("/"); + return ViewIsDirectory ? `${Encoded}/` : Encoded; +} + +function ParseLocationPath(Pathname) { + const IsDirectory = Pathname.endsWith("/"); + const Segments = Pathname.split("/").filter(Boolean).map(decodeURIComponent); + const [RootName, ...RestSegments] = Segments; + return { RootName: RootName || null, Path: RestSegments.join("/"), IsDirectory }; +} + export default function App() { const [AuthState, SetAuthState] = useState("Loading"); const [CurrentUser, SetCurrentUser] = useState(null); @@ -75,9 +89,7 @@ export default function App() { FetchRoots().then((Fetched) => { SetRoots(Fetched); - if (Fetched.length > 0) { - SetActiveRoot(Fetched[0].Name); - } + RestoreFromLocation(Fetched); }); FetchSettings().then((FetchedSettings) => { @@ -87,6 +99,71 @@ export default function App() { }); }, [AuthState]); + useEffect(() => { + if (AuthState !== "LoggedIn" || !ActiveRoot) return; + + const Url = BuildLocationPath(ActiveRoot, ViewPath, ViewIsDirectory); + if (window.location.pathname !== Url) { + window.history.pushState({}, "", Url); + } + }, [AuthState, ActiveRoot, ViewPath, ViewIsDirectory]); + + useEffect(() => { + if (AuthState !== "LoggedIn") return; + + function HandlePopState() { + RestoreFromLocation(Roots); + } + + window.addEventListener("popstate", HandlePopState); + return () => window.removeEventListener("popstate", HandlePopState); + }, [AuthState, Roots]); + + async function RestoreFromLocation(RootsList) { + const { RootName, Path, IsDirectory } = ParseLocationPath(window.location.pathname); + const MatchedRoot = RootsList.find((Root) => Root.Name === RootName); + + if (!MatchedRoot) { + if (RootsList.length > 0) { + SetActiveRoot(RootsList[0].Name); + } + return; + } + + SetActiveRoot(RootName); + + if (!Path) { + HandleNavigateDirectory(""); + return; + } + + const LastSlash = Path.lastIndexOf("/"); + const ParentPath = LastSlash === -1 ? "" : Path.slice(0, LastSlash); + const EntryName = LastSlash === -1 ? Path : Path.slice(LastSlash + 1); + + try { + const Entries = await FetchTree(RootName, ParentPath); + const Entry = Entries.find((Candidate) => Candidate.Name === EntryName); + + if (!Entry) { + HandleNavigateDirectory(""); + return; + } + + if (Entry.IsDirectory) { + if (IsDirectory) { + HandleNavigateDirectory(Path); + } else { + OpenDirectoryNote(Path, Entry.Note); + } + } else { + HandleSelectFile(Path, Entry.FileType); + } + } catch (Error) { + HandleNavigateDirectory(""); + } + } + function HandleAuthenticated(User) { SetCurrentUser(User); SetAuthState("LoggedIn");