Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"@radix-ui/react-tooltip": "^1.2.7",
"@tailwindcss/typography": "^0.5.16",
"@tailwindcss/vite": "^4.0.6",
"@tanstack/react-query": "^5.85.5",
"@tanstack/react-router": "^1.131.27",
"@tauri-apps/api": "^2",
"@tauri-apps/plugin-clipboard-manager": "~2.3.0",
Expand Down Expand Up @@ -62,6 +63,7 @@
"sonner": "^2.0.3",
"tailwind-merge": "^3.2.0",
"tailwindcss": "^4.0.6",
"typescript-result": "^3.5.2",
"zod": "^4.0.0",
"zustand": "^5.0.4"
},
Expand Down
81 changes: 28 additions & 53 deletions apps/client/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,17 @@
import { QueryClientProvider } from "@tanstack/react-query";
import { RouterProvider } from "@tanstack/react-router";
import { getCurrent } from "@tauri-apps/plugin-deep-link";
import { useEffect, useState } from "react";
import "./App.css";
import { ThemeProvider } from "./components/core/theme-provider";
import { TopBar } from "./components/core/TopBar";
import { TopBarProvider } from "./components/core/TopBarContext";
import { GitHubIdentitySetupModal } from "./components/identity/GitHubIdentitySetupModal";
import { SidebarProvider, useSidebar } from "./components/ui/sidebar";
import { Toaster } from "./components/ui/sonner";
import { useConfigInitialization, useConfigSection } from "./lib/config/hooks";
import { testDeepLink } from "./lib/deeplink-simple";
import { simpleDeepLinkManager, testDeepLink } from "./lib/deeplink-simple";
import { KeyboardProvider } from "./lib/keyboard/KeyboardProvider";
import { createShortcut } from "./lib/keyboard/types";
import { useKeyboardShortcuts } from "./lib/keyboard/useKeyboardShortcuts";
import { useAppStore } from "./lib/store";
import { RouterProvider } from "@tanstack/react-router";
import { queryClient } from "./lib/query";
import { router } from "./lib/router";

// Component that handles global keyboard shortcuts within the sidebar context
function GlobalKeyboardShortcuts() {
const { toggleSidebar } = useSidebar();

const globalShortcuts = [
createShortcut("b", () => toggleSidebar(), "Toggle Sidebar", {
cmd: true
})
];

useKeyboardShortcuts(globalShortcuts, {
enabled: true,
context: "global"
});

return null;
}
import { useAppStore } from "./lib/store";

function App() {
const { initialize } = useAppStore((state) => state);
Expand All @@ -46,7 +25,6 @@ function App() {

// Initialize simplified deep-link manager
useEffect(() => {
const { simpleDeepLinkManager } = require("./lib/deeplink-simple");
simpleDeepLinkManager.initialize(router);
simpleDeepLinkManager.startListening();

Expand Down Expand Up @@ -119,32 +97,29 @@ function App() {
return (
<ThemeProvider>
<KeyboardProvider>
<div className="h-screen overflow-hidden overscroll-none">
{/* TODO: Maybe make this MacOS-only? */}
{/* <div
data-tauri-drag-region
className="fixed top-0 left-0 right-0 z-[99]! h-[20px]"
onDoubleClick={() => {
getCurrentWindow().maximize();
}}
></div> */}

<SidebarProvider className="h-screen">
<TopBarProvider>
<GlobalKeyboardShortcuts />
<TopBar />
{/* Router renders AppSidebar + route content via file-based Root route */}
<RouterProvider router={router} />
</TopBarProvider>
</SidebarProvider>
<Toaster />

{/* Identity Setup Modal - Use GitHub OAuth modal if detected */}
<GitHubIdentitySetupModal
open={!isSetupCompleted}
onComplete={handleSetupComplete}
/>
</div>
<QueryClientProvider client={queryClient}>
<div className="h-screen overflow-hidden overscroll-none">
{/* TODO: Maybe make this MacOS-only? */}
{/* <div
data-tauri-drag-region
className="fixed top-0 left-0 right-0 z-[99]! h-[20px]"
onDoubleClick={() => {
getCurrentWindow().maximize();
}}
></div> */}

{/* Router renders AppSidebar + route content via file-based Root route */}
<RouterProvider router={router} context={{ queryClient }} />

<Toaster />

{/* Identity Setup Modal - Use GitHub OAuth modal if detected */}
<GitHubIdentitySetupModal
open={!isSetupCompleted}
onComplete={handleSetupComplete}
/>
</div>
</QueryClientProvider>
</KeyboardProvider>
</ThemeProvider>
);
Expand Down
21 changes: 21 additions & 0 deletions apps/client/src/components/core/Loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ReactNode } from "react";
import { Card, CardContent } from "../ui/card";

export function Loading(): ReactNode {
return (
<div className="p-6 min-h-calc(100vh - var(--top-bar-height)) w-full">
<div className="w-full">
<Card>
<CardContent className="pt-6">
<div className="flex items-center justify-center py-12">
<div className="text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"></div>
Loading...
</div>
</div>
</CardContent>
</Card>
</div>
</div>
);
}
114 changes: 30 additions & 84 deletions apps/client/src/components/documents/DocumentDetailView.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { AlertCircleIcon } from "lucide-react";
import { useRef } from "react";
import { DocumentVerificationResult } from "@/lib/documentApi";
import { Route } from "@/routes/documents/document/$documentId";
import { Await } from "@tanstack/react-router";
import { useRef, useState } from "react";
import { useDocumentActions } from "../../hooks/useDocumentActions";
import { useDocumentData } from "../../hooks/useDocumentData";
import { useDocumentSidebarState } from "../../hooks/useDocumentSidebarState";
import { useFileDownload } from "../../hooks/useFileDownload";
import { formatBlockQuotes, groupAdjacentBlocks } from "../../lib/blockUtils";
import { useDocuments } from "../../lib/store";
import { Card, CardContent } from "../ui/card";
import { useSidebar } from "../ui/sidebar";
import { DocumentContent } from "./DocumentContent";
import { DocumentHeader } from "./DocumentHeader";
Expand All @@ -15,49 +15,30 @@ import { RepliesSection } from "./RepliesSection";
import { TableOfContents } from "./TableOfContents";
import { VerificationDisplay } from "./VerificationDisplay";

interface DocumentDetailViewProps {
documentId: number;
}
export function DocumentDetailView() {
const { documentId } = Route.useParams();
const loaderData = Route.useLoaderData();

export function DocumentDetailView({ documentId }: DocumentDetailViewProps) {
// Get block selection from store
const { selectedBlockIndices, selectedBlockTexts } = useDocuments();

const contentRef = useRef<HTMLDivElement>(null);
const scrollContainerRef = useRef<HTMLDivElement>(null);
const { state: appSidebarState } = useSidebar();

// Custom hooks for data and state management
const {
currentDocument,
loading,
error,
verificationResult,
replyTree,
repliesLoading,
repliesError,
currentUsername,
upvoteCount,
setUpvoteCount,
setVerificationResult
} = useDocumentData(documentId);
const [verificationResult, setVerificationResult] =
useState<DocumentVerificationResult | null>(null);

const {
isVerifying,
verificationError,
isUpvoting,
isDeleting,
handleVerifyDocument,
handleUpvote,
handleDeleteDocument,
handleReplyToDocument: handleReplyToDocumentBase,
handleEditDocument,
handleQuoteAndReply
} = useDocumentActions(
currentDocument,
setVerificationResult,
setUpvoteCount
);
} = useDocumentActions(loaderData.document, setVerificationResult);

const { downloadingFiles, handleDownloadFile } = useFileDownload();

Expand Down Expand Up @@ -105,46 +86,10 @@ export function DocumentDetailView({ documentId }: DocumentDetailViewProps) {
verificationResult.upvote_count_verified
);

if (loading) {
return (
<div className="p-6 min-h-screen w-full">
<div className="w-full">
<Card>
<CardContent className="pt-6">
<div className="flex items-center justify-center py-12">
<div className="text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"></div>
Loading document...
</div>
</div>
</CardContent>
</Card>
</div>
</div>
);
}

if (error || !currentDocument) {
return (
<div className="p-6 min-h-screen w-full">
<div className="w-full">
<Card className="border-destructive">
<CardContent className="pt-6">
<div className="flex items-center gap-2 text-destructive">
<AlertCircleIcon className="h-5 w-5" />
<span>{error || "Document not found"}</span>
</div>
</CardContent>
</Card>
</div>
</div>
);
}

return (
<div className="flex min-h-screen w-full">
<div className="flex min-h-calc(100vh - var(--top-bar-height)) w-full">
{/* Left Sidebar - Table of Contents (Fixed) - Only show for message documents */}
{currentDocument.content.message && (
{loaderData.document.content.message && (
<div
className={`hidden lg:flex flex-col border-r bg-background fixed h-[calc(100vh-var(--top-bar-height))] z-10 ${
leftSidebarCollapsed ? "w-0 overflow-hidden" : "w-64"
Expand All @@ -171,23 +116,20 @@ export function DocumentDetailView({ documentId }: DocumentDetailViewProps) {
<div
ref={scrollContainerRef}
className={`flex-1 min-w-0 p-6 ${
leftSidebarCollapsed || !currentDocument.content.message
leftSidebarCollapsed || !loaderData.document.content.message
? "lg:ml-0"
: "lg:ml-64"
} ${rightSidebarCollapsed ? "lg:mr-0" : "lg:mr-64"}`}
>
<div className="w-full max-w-4xl mx-auto">
{/* Document Header */}
<DocumentHeader
currentDocument={currentDocument}
upvoteCount={upvoteCount}
currentUsername={currentUsername}
isUpvoting={isUpvoting}
currentDocument={loaderData.document}
upvoteCount={loaderData.document.metadata.upvote_count}
isVerifying={isVerifying}
isDeleting={isDeleting}
isVerified={isVerified}
verificationResult={verificationResult}
onUpvote={handleUpvote}
onReply={handleReplyToDocument}
onVerify={handleVerifyDocument}
onEdit={handleEditDocument}
Expand All @@ -200,25 +142,29 @@ export function DocumentDetailView({ documentId }: DocumentDetailViewProps) {
{/* Document Content - Main Focus */}
<div ref={contentRef}>
<DocumentContent
document={currentDocument}
document={loaderData.document}
downloadingFiles={downloadingFiles}
onDownloadFile={handleDownloadFile}
onQuoteText={handleQuoteAndReply}
/>
</div>

{/* Replies Section */}
<RepliesSection
replyTree={replyTree}
repliesLoading={repliesLoading}
repliesError={repliesError}
documentId={documentId}
postId={currentDocument.metadata.post_id}
rootPostTitle={currentDocument.metadata.title}
/>
<Await promise={loaderData.replyTree}>
{(replyTree) => (
// TODO Scroll to new reply if it exists
<RepliesSection
replyTree={replyTree}
repliesLoading={false}
repliesError={null}
documentId={Number(documentId)}
postId={loaderData.document.metadata.post_id}
rootPostTitle={loaderData.document.metadata.title}
/>
)}
</Await>

{/* Technical Details - Moved to Bottom */}
<DocumentMetadata document={currentDocument} />
<DocumentMetadata document={loaderData.document} />
</div>
</div>

Expand Down
Loading