0.3.1 UI修复
This commit is contained in:
@@ -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} />
|
||||
|
||||
Reference in New Issue
Block a user