mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-22 23:31:12 +02:00
Merge remote-tracking branch 'upstream/dev' into feat/local-folder-sync
This commit is contained in:
commit
62b44889d1
66 changed files with 3359 additions and 2626 deletions
|
|
@ -29,7 +29,7 @@ interface ChangelogPageItem {
|
|||
|
||||
export default async function ChangelogPage() {
|
||||
const allPages = source.getPages() as ChangelogPageItem[];
|
||||
const sortedChangelogs = allPages.sort((a, b) => {
|
||||
const sortedChangelogs = allPages.toSorted((a, b) => {
|
||||
const dateA = new Date(a.data.date).getTime();
|
||||
const dateB = new Date(b.data.date).getTime();
|
||||
return dateB - dateA;
|
||||
|
|
|
|||
|
|
@ -329,14 +329,15 @@ export function DocumentsTableShell({
|
|||
|
||||
const handleViewDocument = useCallback(async (doc: Document) => {
|
||||
setViewingDoc(doc);
|
||||
if (doc.content) {
|
||||
setViewingContent(doc.content);
|
||||
const preview = doc.content_preview || doc.content;
|
||||
if (preview) {
|
||||
setViewingContent(preview);
|
||||
return;
|
||||
}
|
||||
setViewingLoading(true);
|
||||
try {
|
||||
const fullDoc = await documentsApiService.getDocument({ id: doc.id });
|
||||
setViewingContent(fullDoc.content);
|
||||
setViewingContent(fullDoc.content_preview || fullDoc.content);
|
||||
} catch (err) {
|
||||
console.error("[DocumentsTableShell] Failed to fetch document content:", err);
|
||||
setViewingContent("Failed to load document content.");
|
||||
|
|
@ -630,7 +631,7 @@ export function DocumentsTableShell({
|
|||
return (
|
||||
<tr
|
||||
key={doc.id}
|
||||
className={`group border-b border-border/50 transition-colors ${
|
||||
className={`list-item-lazy group border-b border-border/50 transition-colors ${
|
||||
isMentioned ? "bg-primary/5 hover:bg-primary/8" : "hover:bg-muted/30"
|
||||
} ${canInteract && hasChatMode ? "cursor-pointer" : ""}`}
|
||||
onClick={handleRowClick}
|
||||
|
|
@ -872,7 +873,7 @@ export function DocumentsTableShell({
|
|||
return (
|
||||
<MobileCardWrapper key={doc.id} onLongPress={() => setMobileActionDoc(doc)}>
|
||||
<div
|
||||
className={`relative px-3 py-2 transition-colors ${
|
||||
className={`list-item-lazy relative px-3 py-2 transition-colors ${
|
||||
isMentioned ? "bg-primary/5" : "hover:bg-muted/20"
|
||||
} ${canInteract && hasChatMode ? "cursor-pointer" : ""}`}
|
||||
>
|
||||
|
|
@ -952,7 +953,30 @@ export function DocumentsTableShell({
|
|||
<Spinner size="lg" className="text-muted-foreground" />
|
||||
</div>
|
||||
) : (
|
||||
<MarkdownViewer content={viewingContent} />
|
||||
<>
|
||||
<MarkdownViewer content={viewingContent} maxLength={50_000} />
|
||||
{viewingDoc && (
|
||||
<div className="mt-4 flex justify-center">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
if (viewingDoc) {
|
||||
openEditor({
|
||||
documentId: viewingDoc.id,
|
||||
searchSpaceId: Number(searchSpaceId),
|
||||
title: viewingDoc.title,
|
||||
});
|
||||
handleCloseViewer();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Eye className="h-3.5 w-3.5 mr-1.5" />
|
||||
View full document
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</DrawerContent>
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ export type Document = {
|
|||
id: number;
|
||||
title: string;
|
||||
document_type: DocumentType;
|
||||
// Optional: Only needed when viewing document details (lazy loaded)
|
||||
document_metadata?: any;
|
||||
content?: string;
|
||||
content_preview?: string;
|
||||
created_at: string;
|
||||
search_space_id: number;
|
||||
created_by_id?: string | null;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
} from "@assistant-ui/react";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useAtomValue, useSetAtom } from "jotai";
|
||||
import { useParams, useSearchParams } from "next/navigation";
|
||||
import { useParams } from "next/navigation";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
|
@ -228,13 +228,14 @@ export default function NewChatPage() {
|
|||
return prev;
|
||||
}
|
||||
|
||||
const memberById = new Map(membersData?.map((m) => [m.user_id, m]) ?? []);
|
||||
const prevById = new Map(prev.map((m) => [m.id, m]));
|
||||
|
||||
return syncedMessages.map((msg) => {
|
||||
const member = msg.author_id
|
||||
? membersData?.find((m) => m.user_id === msg.author_id)
|
||||
: null;
|
||||
const member = msg.author_id ? (memberById.get(msg.author_id) ?? null) : null;
|
||||
|
||||
// Preserve existing author info if member lookup fails (e.g., cloned chats)
|
||||
const existingMsg = prev.find((m) => m.id === `msg-${msg.id}`);
|
||||
const existingMsg = prevById.get(`msg-${msg.id}`);
|
||||
const existingAuthor = existingMsg?.metadata?.custom?.author as
|
||||
| { displayName?: string | null; avatarUrl?: string | null }
|
||||
| undefined;
|
||||
|
|
@ -388,22 +389,32 @@ export default function NewChatPage() {
|
|||
}, [searchSpaceId, queryClient]);
|
||||
|
||||
// Handle scroll to comment from URL query params (e.g., from inbox item click)
|
||||
const searchParams = useSearchParams();
|
||||
const targetCommentIdParam = searchParams.get("commentId");
|
||||
|
||||
// Set target comment ID from URL param - the AssistantMessage and CommentItem
|
||||
// components will handle scrolling and highlighting once comments are loaded
|
||||
// Read from window.location.search inside the effect instead of subscribing via
|
||||
// useSearchParams() — avoids re-rendering this heavy component tree on every
|
||||
// unrelated query-string change. (Vercel Best Practice: rerender-defer-reads 5.2)
|
||||
useEffect(() => {
|
||||
if (targetCommentIdParam && !isInitializing) {
|
||||
const commentId = Number.parseInt(targetCommentIdParam, 10);
|
||||
if (!Number.isNaN(commentId)) {
|
||||
setTargetCommentId(commentId);
|
||||
const readAndApplyCommentId = () => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const raw = params.get("commentId");
|
||||
if (raw && !isInitializing) {
|
||||
const commentId = Number.parseInt(raw, 10);
|
||||
if (!Number.isNaN(commentId)) {
|
||||
setTargetCommentId(commentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
readAndApplyCommentId();
|
||||
|
||||
// Also respond to SPA navigations (back/forward) that change the query string
|
||||
window.addEventListener("popstate", readAndApplyCommentId);
|
||||
|
||||
// Cleanup on unmount or when navigating away
|
||||
return () => clearTargetCommentId();
|
||||
}, [targetCommentIdParam, isInitializing, setTargetCommentId, clearTargetCommentId]);
|
||||
return () => {
|
||||
window.removeEventListener("popstate", readAndApplyCommentId);
|
||||
clearTargetCommentId();
|
||||
};
|
||||
}, [isInitializing, setTargetCommentId, clearTargetCommentId]);
|
||||
|
||||
// Sync current thread state to atom
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import { useAtomValue } from "jotai";
|
||||
import { AlertCircle, Plus, Search } from "lucide-react";
|
||||
import { motion } from "motion/react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useEffect, useState } from "react";
|
||||
import { searchSpacesAtom } from "@/atoms/search-spaces/search-space-query.atoms";
|
||||
|
|
@ -89,7 +89,6 @@ function EmptyState({ onCreateClick }: { onCreateClick: () => void }) {
|
|||
|
||||
export default function DashboardPage() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const [showCreateDialog, setShowCreateDialog] = useState(false);
|
||||
|
||||
const t = useTranslations("dashboard");
|
||||
|
|
@ -99,11 +98,12 @@ export default function DashboardPage() {
|
|||
if (isLoading) return;
|
||||
|
||||
if (searchSpaces.length > 0) {
|
||||
const params = searchParams.toString();
|
||||
const query = params ? `?${params}` : "";
|
||||
// Read the query string at the time of redirect — no subscription needed.
|
||||
// (Vercel Best Practice: rerender-defer-reads 5.2)
|
||||
const query = window.location.search;
|
||||
router.replace(`/dashboard/${searchSpaces[0].id}/new-chat${query}`);
|
||||
}
|
||||
}, [isLoading, searchSpaces, router, searchParams]);
|
||||
}, [isLoading, searchSpaces, router]);
|
||||
|
||||
// Show loading while fetching or while we have spaces and are about to redirect
|
||||
const shouldShowLoading = isLoading || searchSpaces.length > 0;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,16 @@
|
|||
import { DocsBody, DocsDescription, DocsPage, DocsTitle } from "fumadocs-ui/page";
|
||||
import { notFound } from "next/navigation";
|
||||
import { cache } from "react";
|
||||
import { source } from "@/lib/source";
|
||||
import { getMDXComponents } from "@/mdx-components";
|
||||
|
||||
const getDocPage = cache((slug?: string[]) => {
|
||||
return source.getPage(slug);
|
||||
});
|
||||
|
||||
export default async function Page(props: { params: Promise<{ slug?: string[] }> }) {
|
||||
const params = await props.params;
|
||||
const page = source.getPage(params.slug);
|
||||
const page = getDocPage(params.slug);
|
||||
if (!page) notFound();
|
||||
|
||||
const MDX = page.data.body;
|
||||
|
|
@ -37,7 +42,7 @@ export async function generateStaticParams() {
|
|||
|
||||
export async function generateMetadata(props: { params: Promise<{ slug?: string[] }> }) {
|
||||
const params = await props.params;
|
||||
const page = source.getPage(params.slug);
|
||||
const page = getDocPage(params.slug);
|
||||
if (!page) notFound();
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import posthog from "posthog-js";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export default function ErrorPage({
|
||||
|
|
@ -11,7 +10,11 @@ export default function ErrorPage({
|
|||
reset: () => void;
|
||||
}) {
|
||||
useEffect(() => {
|
||||
posthog.captureException(error);
|
||||
import("posthog-js")
|
||||
.then(({ default: posthog }) => {
|
||||
posthog.captureException(error);
|
||||
})
|
||||
.catch(() => {});
|
||||
}, [error]);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -246,6 +246,17 @@ button {
|
|||
}
|
||||
}
|
||||
|
||||
/* content-visibility utilities — skip layout/paint for off-screen list items */
|
||||
.list-item-lazy {
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-size: 0 48px;
|
||||
}
|
||||
|
||||
.sidebar-item-lazy {
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-size: 0 40px;
|
||||
}
|
||||
|
||||
@source "../node_modules/@llamaindex/chat-ui/**/*.{ts,tsx}";
|
||||
@source "../node_modules/streamdown/dist/*.js";
|
||||
@source "../node_modules/@streamdown/code/dist/*.js";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue