0.3.1 UI修复

This commit is contained in:
liaibo
2026-01-18 19:01:31 +08:00
parent 90a38b48cf
commit 8a5b915002
86 changed files with 1160 additions and 2867 deletions
@@ -119,10 +119,10 @@ export function FileTree({
index >= dragOverRange.startIndex &&
index < dragOverRange.endIndex;
const baseClass =
"flex w-full min-w-0 max-w-full select-none items-center gap-2 rounded-md px-2 py-1.5 text-sm text-gray-700 hover:bg-[#f5f7fb]";
"group flex items-center gap-2 px-3 py-[2px] min-h-[27px] w-full select-none cursor-pointer rounded-[3px] text-wolai-text-primary text-[14px] font-medium hover:bg-wolai-bg-hover transition-colors duration-150 data-[active=true]:bg-wolai-bg-active";
const activeClass =
row.kind === "index"
? "text-[#2563eb] font-medium"
? "text-[#2563eb]"
: row.kind === "doc"
? "text-[#2563eb]"
: "";
@@ -135,9 +135,10 @@ export function FileTree({
className={cn(
baseClass,
active && activeClass,
selected && "bg-[#e8f2ff] text-[#2563eb]",
selected && "bg-wolai-bg-active text-[#2563eb]",
inDropFeedback && "bg-gray-200/70",
)}
data-active={active}
style={{ paddingLeft }}
onClick={(event) => onRowClick(row, event)}
onDoubleClick={(event) => onRowDoubleClick(row, event)}
@@ -228,19 +229,19 @@ export function FileTree({
event.stopPropagation();
onToggleExpand(row.docId);
}}
className="flex h-5 w-5 items-center justify-center rounded-md hover:bg-gray-100"
className="flex h-5 w-5 items-center justify-center rounded-md hover:bg-gray-100 shrink-0"
>
<ChevronRight
className={cn(
"h-3.5 w-3.5 transition-transform",
"w-4 h-4 text-wolai-text-secondary transition-transform group-hover:text-wolai-text-primary",
row.isExpanded && "rotate-90",
)}
/>
</button>
) : (
<span className="h-5 w-5" />
<span className="w-5 h-5 shrink-0" />
)}
<Folder className="h-4 w-4 text-[#2563eb]" />
<Folder className="w-4 h-4 text-[#2563eb] shrink-0" />
<span className="min-w-0 flex-1 truncate text-left">{label}</span>
<button
type="button"
@@ -249,15 +250,15 @@ export function FileTree({
event.stopPropagation();
onCreateChild(row.docId);
}}
className="rounded-md p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-700"
className="rounded-md p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-700 shrink-0"
>
<Plus className="h-3.5 w-3.5" />
<Plus className="w-4 h-4" />
</button>
</>
) : row.kind === "index" ? (
<>
<span className="h-4 w-4" />
<FileText className="h-4 w-4 text-gray-500" />
<span className="w-4 h-4 shrink-0" />
<FileText className="w-4 h-4 text-wolai-text-secondary shrink-0" />
<span className="min-w-0 flex-1 truncate text-left">{label}</span>
</>
) : row.kind === "asset-folder" ? (
@@ -270,25 +271,25 @@ export function FileTree({
event.stopPropagation();
onToggleAssetFolderExpand?.(row.asset.id);
}}
className="flex h-5 w-5 items-center justify-center rounded-md hover:bg-gray-100"
className="flex h-5 w-5 items-center justify-center rounded-md hover:bg-gray-100 shrink-0"
>
<ChevronRight
className={cn(
"h-3.5 w-3.5 transition-transform",
"w-4 h-4 text-wolai-text-secondary transition-transform group-hover:text-wolai-text-primary",
row.isExpanded && "rotate-90",
)}
/>
</button>
) : (
<span className="h-5 w-5" />
<span className="w-5 h-5 shrink-0" />
)}
<Folder className="h-4 w-4 text-[#2563eb]" />
<Folder className="w-4 h-4 text-[#2563eb] shrink-0" />
<span className="min-w-0 flex-1 truncate text-left">{label}</span>
</>
) : (
<>
<span className="h-4 w-4" />
<Paperclip className="h-4 w-4 text-gray-500" />
<span className="w-4 h-4 shrink-0" />
<Paperclip className="w-4 h-4 text-wolai-text-secondary shrink-0" />
<span className="min-w-0 flex-1 truncate text-left">{label}</span>
</>
)}
@@ -198,36 +198,37 @@ function SortableTreeRow({
ref={setNodeRef}
style={style}
className={cn(
"flex h-full items-center gap-1 border-b border-transparent px-2 text-sm text-gray-700 hover:bg-[#f5f7fb]",
activeId === node.id && "bg-[#e8f2ff] text-[#2563eb]",
"group flex items-center gap-2 px-3 py-[2px] min-h-[27px] w-full cursor-pointer rounded-[3px] text-wolai-text-primary text-[14px] font-medium hover:bg-wolai-bg-hover transition-colors duration-150",
activeId === node.id && "bg-wolai-bg-active text-[#2563eb]",
isDragging && "opacity-60",
)}
data-active={activeId === node.id}
onContextMenu={onContextMenu}
>
<button
type="button"
aria-label="拖拽排序"
className="flex h-6 w-6 items-center justify-center rounded-md text-gray-400 hover:text-gray-600"
className="flex h-6 w-6 items-center justify-center rounded-md text-wolai-text-secondary hover:text-wolai-text-primary shrink-0"
{...listeners}
{...attributes}
>
<GripVertical className="h-4 w-4" />
<GripVertical className="w-4 h-4" />
</button>
<div
className="flex flex-1 items-center gap-2 rounded-md px-1 py-1"
style={{ paddingLeft: depth * 14 }}
className="flex flex-1 items-center gap-2"
style={{ paddingLeft: depth * 12 }}
>
{hasChildren ? (
<button
type="button"
aria-label="展开或折叠"
onClick={onToggleExpand}
className="flex h-5 w-5 items-center justify-center rounded-md hover:bg-gray-100"
className="flex h-5 w-5 items-center justify-center rounded-md hover:bg-gray-100 shrink-0"
>
<ChevronRight className={cn("h-3.5 w-3.5 transition-transform", expanded && "rotate-90")} />
<ChevronRight className={cn("w-4 h-4 text-wolai-text-secondary transition-transform group-hover:text-wolai-text-primary", expanded && "rotate-90")} />
</button>
) : (
<span className="h-5 w-5" />
<span className="w-5 h-5 shrink-0" />
)}
<Link href={`/documents/${node.id}`} className="flex-1 truncate text-left">
{node.title || "无标题"}
@@ -237,9 +238,9 @@ function SortableTreeRow({
type="button"
aria-label="新建子页面"
onClick={onCreateChild}
className="rounded-md p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-700"
className="rounded-md p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-700 shrink-0"
>
<Plus className="h-3.5 w-3.5" />
<Plus className="w-4 h-4" />
</button>
<button
type="button"
@@ -248,9 +249,9 @@ function SortableTreeRow({
event.preventDefault();
onContextMenu(event);
}}
className="rounded-md p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-700"
className="rounded-md p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-700 shrink-0"
>
<MoreHorizontal className="h-4 w-4" />
<MoreHorizontal className="w-4 h-4" />
</button>
</div>
);
@@ -35,9 +35,9 @@ import { buildDocumentTree } from "@/lib/documents";
import { useSidebarStore } from "@/store/sidebar";
import type { SidebarInitialData, SidebarSectionId } from "@/components/sidebar/types";
import { useSidebarData } from "@/hooks/use-sidebar-data";
import { useConvexSidebarData } from "@/hooks/use-convex-sidebar-data";
import { PrivateTree } from "@/components/sidebar/private-tree";
import { buildSidebarSectionsFromTree, flattenDocumentTree } from "@/lib/sidebar-tree";
import { getSupabaseBrowserClient } from "@/lib/supabase/client";
import { useSearchPaletteStore } from "@/store/search-palette";
import { useEditorBridgeStore } from "@/store/editor-bridge";
import { FileTree } from "@/components/sidebar/file-tree";
@@ -126,26 +126,56 @@ interface ContextMenuState {
y: number;
}
// 主 Sidebar 组件:根据模式路由到不同的子组件
export function Sidebar({ initialData }: SidebarProps) {
const useConvex = useMemo(() => Boolean(getMnoteRuntimeConfig().useConvex), []);
// 根据模式渲染不同的子组件,确保 Supabase 模式完全不调用 Convex hooks
if (useConvex) {
return <SidebarConvex initialData={initialData} />;
}
return <SidebarSupabase initialData={initialData} />;
}
// Convex 模式专用组件 - 只调用 Convex hooks
function SidebarConvex({ initialData }: SidebarProps) {
const convexData = useConvexSidebarData(initialData.activeWorkspaceId);
return <SidebarContent initialData={initialData} sidebarQuery={convexData} />;
}
// Supabase 模式专用组件 - 只调用 Supabase hooks
function SidebarSupabase({ initialData }: SidebarProps) {
const supabaseData = useSidebarData(initialData);
return <SidebarContent initialData={initialData} sidebarQuery={supabaseData} />;
}
// 共享的 UI 内容组件 - 包含所有现有的 Sidebar 逻辑
interface SidebarContentProps {
initialData: SidebarInitialData;
sidebarQuery: {
data: SidebarInitialData | null | undefined;
isLoading: boolean;
refetch: () => Promise<unknown>;
};
}
function SidebarContent({ initialData, sidebarQuery }: SidebarContentProps) {
const { open, setOpen, width, setWidth, collapsedSections, toggleSection, setSectionCollapsed, trashConfirm, setTrashConfirm } =
useSidebarStore();
const useConvex = useMemo(() => Boolean(getMnoteRuntimeConfig().useConvex), []);
const supabaseBrowser = useMemo(() => {
if (useConvex) return null;
try {
return getSupabaseBrowserClient();
} catch {
return null;
}
}, [useConvex]);
const sectionsTrayOpen = useSidebarStore((state) => state.sectionsTrayOpen);
const toggleSectionsTray = useSidebarStore((state) => state.toggleSectionsTray);
const setSectionsTrayOpen = useSidebarStore((state) => state.setSectionsTrayOpen);
const viewMode = useSidebarStore((state) => state.viewMode);
const setViewMode = useSidebarStore((state) => state.setViewMode);
const openSearchPalette = useSearchPaletteStore((state) => state.openSearch);
const sidebarQuery = useSidebarData(initialData);
const sidebarData = sidebarQuery.data ?? initialData;
const openSearchPalette = useSearchPaletteStore((state) => state.openSearch);
// 处理数据
const sidebarData = useMemo(() => {
return sidebarQuery.data ?? initialData;
}, [sidebarQuery, initialData]);
// isLoading 判断
const isLoading = sidebarQuery.isLoading;
const segments = useSelectedLayoutSegments();
const router = useRouter();
const activeId = segments?.[1] ?? "";
@@ -253,45 +283,6 @@ export function Sidebar({ initialData }: SidebarProps) {
await sidebarQuery.refetch();
}, [sidebarQuery]);
useEffect(() => {
if (!supabaseBrowser) return;
const channel = supabaseBrowser
.channel("documents-feed")
.on(
"postgres_changes",
{ event: "*", schema: "public", table: "documents" },
() => {
void refreshTree();
},
)
.subscribe();
return () => {
supabaseBrowser.removeChannel(channel);
};
}, [refreshTree, supabaseBrowser]);
useEffect(() => {
if (!supabaseBrowser) return;
const channel = supabaseBrowser
.channel("media-assets-feed")
.on(
"postgres_changes",
{
event: "*",
schema: "public",
table: "media_assets",
filter: `workspace_id=eq.${sidebarData.activeWorkspaceId}`,
},
() => {
void sidebarQuery.refetch();
},
)
.subscribe();
return () => {
supabaseBrowser.removeChannel(channel);
};
}, [sidebarData.activeWorkspaceId, sidebarQuery, supabaseBrowser]);
const sections = useMemo(() => buildSidebarSectionsFromTree(tree), [tree]);
const starredNodes = useMemo(() => sections.find((section) => section.id === "starred")?.nodes ?? [], [sections]);
const publicNodes = useMemo(() => sections.find((section) => section.id === "public")?.nodes ?? [], [sections]);
@@ -498,29 +489,7 @@ export function Sidebar({ initialData }: SidebarProps) {
const handleEmbedPrompt = useCallback(async (node: DocumentNode) => {
openMoveEmbedPicker(node, "embed");
return;
if (sidebarData.activeWorkspaceId) {
openMoveEmbedPicker(node, "embed");
return;
}
if (typeof window === "undefined") {
return;
}
const target = window.prompt("输入希望嵌入到的页面 ID");
if (!target) {
return;
}
const response = await fetch("/api/documents/embed", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ sourceId: node.id, targetId: target.trim() }),
});
if (!response.ok) {
window.alert("嵌入失败,请检查目标页面 ID");
return;
}
window.alert("已在目标页面末尾插入引用块");
}, []);
}, [openMoveEmbedPicker]);
const toggleExpand = useCallback((id: string) => {
setExpanded((prev) => {
@@ -1492,22 +1461,8 @@ export function Sidebar({ initialData }: SidebarProps) {
const handleMovePrompt = useCallback(
async (node: DocumentNode) => {
openMoveEmbedPicker(node, "move");
return;
if (sidebarData.activeWorkspaceId) {
openMoveEmbedPicker(node, "move");
return;
}
if (typeof window === "undefined") {
return;
}
const target = window.prompt("输入目标父页面 ID(留空表示移动到根目录)", node.parent_id ?? "");
if (target === null) {
return;
}
const trimmed = target.trim();
await handleMove(node.id, trimmed.length > 0 ? trimmed : null, 0);
},
[handleMove],
[openMoveEmbedPicker],
);
const handleDelete = useCallback(
@@ -2035,8 +1990,8 @@ export function Sidebar({ initialData }: SidebarProps) {
return (
<>
<aside className="hidden h-full min-w-0 overflow-hidden md:flex" style={{ width }}>
<div className="flex h-full min-w-0 flex-1 flex-col overflow-hidden border-r border-[#e9edf5] bg-white">
<aside className="hidden h-full min-w-0 overflow-hidden md:flex shrink-0" style={{ width }}>
<div className="flex h-full min-w-0 flex-1 flex-col overflow-hidden border-r border-wolai-border bg-wolai-bg-sidebar">
{sidebarBody}
</div>
<div className="w-1 cursor-col-resize bg-transparent" onMouseDown={handleResizeStart} />