0.4.0 convex及界面修改

This commit is contained in:
liaibo
2026-02-01 08:47:40 +08:00
parent d1f055f51a
commit af92c4b149
636 changed files with 7522 additions and 1815 deletions
@@ -1,11 +1,12 @@
"use client";
import { useState, type ComponentType } from "react";
import { BookOpenCheck, Focus, ListOrdered, ListTree, Maximize2, ShieldCheck, Type } from "lucide-react";
import { BookOpenCheck, Focus, ListOrdered, ListTree, Maximize2, ShieldCheck, Type, MessageSquare } from "lucide-react";
import { cn } from "@/lib/utils";
import type { DocumentStats, PageOptionsState } from "@/types/page-options";
import type { BooleanPageOptionKey, DocumentStats, PageFont, PageLayoutDensity, PageOptionsState } from "@/types/page-options";
import { DocumentTaskPanel } from "@/components/document-task-panel";
import { Button } from "@/components/ui/button";
import { useAppPreferencesStore, type ThemeMode } from "@/store/app-preferences";
type TabId = "page" | "custom" | "global";
@@ -16,7 +17,7 @@ const TABS: Array<{ id: TabId; label: string }> = [
];
const OPTION_META: Record<
keyof PageOptionsState,
BooleanPageOptionKey,
{ label: string; description: string; icon: ComponentType<{ className?: string }> }
> = {
wideLayout: {
@@ -39,11 +40,6 @@ const OPTION_META: Record<
description: "在右侧展示目录导航",
icon: ListTree,
},
showStructure: {
label: "块结构线框",
description: "显示块级元素的结构边界",
icon: Focus,
},
protectEditing: {
label: "编辑保护",
description: "保护内容避免误触修改",
@@ -54,19 +50,53 @@ const OPTION_META: Record<
description: "实时展示字数和块统计",
icon: BookOpenCheck,
},
collapseBacklinks: {
label: "折叠反向引用",
description: "默认折叠页面底部的反向引用列表",
icon: Focus,
},
hideChildPages: {
label: "隐藏子页面",
description: "隐藏通过 /ym 创建的子页面块(不会删除内容)",
icon: Focus,
},
showBlockRefCount: {
label: "显示块引用数字",
description: "显示块被引用次数(当前为占位,后续补齐)",
icon: Focus,
},
};
const CUSTOM_LAYOUT_OPTIONS: (keyof PageOptionsState)[] = ["wideLayout", "smallText"];
const CUSTOM_STRUCTURE_OPTIONS: (keyof PageOptionsState)[] = ["showHeadingNumbers", "showToc"];
const GLOBAL_OPTIONS: (keyof PageOptionsState)[] = ["showStructure", "protectEditing", "showWordCount"];
const PAGE_OPTIONS: BooleanPageOptionKey[] = [
"wideLayout",
"smallText",
"showHeadingNumbers",
"showToc",
"protectEditing",
"showWordCount",
];
const CUSTOM_PAGE_OPTIONS: BooleanPageOptionKey[] = ["collapseBacklinks", "hideChildPages", "showBlockRefCount"];
interface PageOptionsSidebarProps {
documentId: string;
options: PageOptionsState;
stats?: DocumentStats;
onToggle: (key: keyof PageOptionsState) => void;
onToggle: (key: BooleanPageOptionKey) => void;
onSetPageFont?: (font: PageFont) => void;
onSetLayoutDensity?: (density: PageLayoutDensity) => void;
onSetEmbedDefaultToCursor?: () => void;
onClearEmbedDefault?: () => void;
onExport: () => void;
onOpenHistory: () => void;
onOpenComments?: () => void;
onUndo?: () => void;
onRedo?: () => void;
onDeletePage?: () => void;
onOpenMoveEmbedPicker?: () => void;
onCopyPageLink?: (includeTitle: boolean) => void;
onCopyPageReference?: (mode: "inline" | "embed") => void;
onAddToTemplates?: () => void;
}
export function PageOptionsSidebar({
@@ -74,10 +104,30 @@ export function PageOptionsSidebar({
options,
stats,
onToggle,
onSetPageFont,
onSetLayoutDensity,
onSetEmbedDefaultToCursor,
onClearEmbedDefault,
onExport,
onOpenHistory,
onOpenComments,
onUndo,
onRedo,
onDeletePage,
onOpenMoveEmbedPicker,
onCopyPageLink,
onCopyPageReference,
onAddToTemplates,
}: PageOptionsSidebarProps) {
const [activeTab, setActiveTab] = useState<TabId>("page");
const theme = useAppPreferencesStore((s) => s.theme);
const showStructure = useAppPreferencesStore((s) => s.showStructure);
const spellCheck = useAppPreferencesStore((s) => s.spellCheck);
const flightMode = useAppPreferencesStore((s) => s.flightMode);
const setTheme = useAppPreferencesStore((s) => s.setTheme);
const setShowStructure = useAppPreferencesStore((s) => s.setShowStructure);
const setSpellCheck = useAppPreferencesStore((s) => s.setSpellCheck);
const setFlightMode = useAppPreferencesStore((s) => s.setFlightMode);
return (
<aside className="flex h-full w-80 shrink-0 flex-col border-l border-[#f0f0f0] bg-white/95">
@@ -107,8 +157,13 @@ export function PageOptionsSidebar({
<StatsCell label="字符" value={stats.characterCount} />
<StatsCell label="块数" value={stats.blockCount} />
</div>
<div className="mt-3 grid grid-cols-2 gap-2 text-center text-xs text-gray-500">
<StatsCell label="待办总数" value={stats.todoTotal} />
<StatsCell label="已完成" value={stats.todoDone} />
</div>
</section>
)}
<OptionToggleGroup title="页面选项" optionKeys={PAGE_OPTIONS} options={options} onToggle={onToggle} />
<section className="rounded-2xl border border-[#eef1f6] p-4 text-sm text-gray-600">
<div className="flex items-center justify-between">
<span className="font-semibold text-gray-800"></span>
@@ -119,34 +174,197 @@ export function PageOptionsSidebar({
<Button type="button" variant="outline" size="sm" onClick={onOpenHistory}>
</Button>
<Button
type="button"
variant="outline"
size="sm"
onClick={onOpenComments}
disabled={!onOpenComments}
title={onOpenComments ? "打开评论" : "评论功能未启用"}
>
<MessageSquare className="mr-1 h-4 w-4" />
</Button>
</div>
</div>
<p className="mt-2 text-xs text-gray-400"></p>
</section>
<section className="rounded-2xl border border-[#eef1f6] p-4 text-sm text-gray-600">
<div className="text-sm font-semibold text-gray-800"></div>
<div className="mt-3 flex flex-wrap gap-2">
<Button type="button" size="sm" variant="outline" onClick={onUndo} disabled={!onUndo}>
</Button>
<Button type="button" size="sm" variant="outline" onClick={onRedo} disabled={!onRedo}>
</Button>
<Button
type="button"
size="sm"
variant="outline"
onClick={onOpenMoveEmbedPicker}
disabled={!onOpenMoveEmbedPicker}
>
/...
</Button>
<Button type="button" size="sm" variant="outline" onClick={onAddToTemplates} disabled={!onAddToTemplates}>
</Button>
<Button type="button" size="sm" variant="outline" onClick={() => onCopyPageLink?.(false)} disabled={!onCopyPageLink}>
</Button>
<Button type="button" size="sm" variant="outline" onClick={() => onCopyPageLink?.(true)} disabled={!onCopyPageLink}>
</Button>
<Button type="button" size="sm" variant="outline" onClick={() => onCopyPageReference?.("inline")} disabled={!onCopyPageReference}>
</Button>
<Button type="button" size="sm" variant="outline" onClick={() => onCopyPageReference?.("embed")} disabled={!onCopyPageReference}>
</Button>
<Button type="button" size="sm" variant="destructive" onClick={onDeletePage} disabled={!onDeletePage}>
</Button>
</div>
<p className="mt-2 text-xs text-gray-400">
<span className="font-mono">Ctrl/Cmd + Shift + L</span>
</p>
</section>
<DocumentTaskPanel documentId={documentId} />
</div>
)}
{activeTab === "custom" && (
<div className="space-y-5">
<OptionToggleGroup
title="布局与排版"
optionKeys={CUSTOM_LAYOUT_OPTIONS}
options={options}
onToggle={onToggle}
/>
<OptionToggleGroup
title="结构与目录"
optionKeys={CUSTOM_STRUCTURE_OPTIONS}
options={options}
onToggle={onToggle}
/>
<section className="rounded-2xl border border-[#eef1f6] p-4">
<div className="text-sm font-semibold text-gray-800"></div>
<p className="mt-1 text-xs text-gray-400"></p>
<div className="mt-3 flex flex-wrap gap-2">
{([
{ id: "default", label: "默认" },
{ id: "song", label: "宋体" },
{ id: "kai", label: "楷体" },
] as Array<{ id: PageFont; label: string }>).map((item) => (
<Button
key={item.id}
type="button"
size="sm"
variant={options.pageFont === item.id ? "default" : "outline"}
onClick={() => onSetPageFont?.(item.id)}
disabled={!onSetPageFont}
>
{item.label}
</Button>
))}
</div>
</section>
<section className="rounded-2xl border border-[#eef1f6] p-4">
<div className="text-sm font-semibold text-gray-800"></div>
<p className="mt-1 text-xs text-gray-400"></p>
<div className="mt-3 flex flex-wrap gap-2">
{([
{ id: "compact", label: "紧凑" },
{ id: "normal", label: "默认" },
{ id: "spacious", label: "宽容" },
] as Array<{ id: PageLayoutDensity; label: string }>).map((item) => (
<Button
key={item.id}
type="button"
size="sm"
variant={options.layoutDensity === item.id ? "default" : "outline"}
onClick={() => onSetLayoutDensity?.(item.id)}
disabled={!onSetLayoutDensity}
>
{item.label}
</Button>
))}
</div>
</section>
<OptionToggleGroup title="反向链接" optionKeys={CUSTOM_PAGE_OPTIONS} options={options} onToggle={onToggle} />
<section className="rounded-2xl border border-[#eef1f6] p-4">
<div className="text-sm font-semibold text-gray-800"></div>
<p className="mt-1 text-xs text-gray-400">
/...
</p>
<div className="mt-3 rounded-xl bg-[#f9fafc] px-3 py-2 text-xs text-gray-600">
{options.embedDefaultBlockId ? options.embedDefaultBlockId : "未设置"}
</div>
<div className="mt-3 flex gap-2">
<Button
type="button"
size="sm"
variant="outline"
onClick={onSetEmbedDefaultToCursor}
disabled={!onSetEmbedDefaultToCursor}
>
使
</Button>
<Button
type="button"
size="sm"
variant="outline"
onClick={onClearEmbedDefault}
disabled={!onClearEmbedDefault || !options.embedDefaultBlockId}
>
</Button>
</div>
</section>
<section className="rounded-2xl border border-[#eef1f6] p-4 text-xs text-gray-500">
<div className="text-sm font-semibold text-gray-800"></div>
<ul className="mt-2 list-disc space-y-1 pl-4">
<li></li>
</ul>
</section>
</div>
)}
{activeTab === "global" && (
<div className="space-y-5">
<OptionToggleGroup title="全局偏好" optionKeys={GLOBAL_OPTIONS} options={options} onToggle={onToggle} />
<section className="rounded-2xl border border-dashed border-[#e3e3e3] p-4 text-xs text-gray-400">
Good Night
<section className="rounded-2xl border border-[#eef1f6] p-4">
<div className="text-sm font-semibold text-gray-800"></div>
<PreferenceRow
title="显示块结构"
description="显示块级元素的结构边界(虚线框)。快捷键:Ctrl/Cmd + Shift + U。"
enabled={showStructure}
onToggle={() => setShowStructure(!showStructure)}
/>
<PreferenceRow
title="拼写检查"
description="控制编辑器的浏览器拼写检查(spellcheck)。"
enabled={spellCheck}
onToggle={() => setSpellCheck(!spellCheck)}
/>
</section>
<section className="rounded-2xl border border-[#eef1f6] p-4">
<div className="text-sm font-semibold text-gray-800"></div>
<p className="mt-1 text-xs text-gray-400">Good NightCtrl/Cmd + Alt/Opt + G</p>
<div className="mt-3 flex gap-2">
{(["system", "light", "dark"] as ThemeMode[]).map((mode) => (
<Button
key={mode}
type="button"
size="sm"
variant={theme === mode ? "default" : "outline"}
onClick={() => setTheme(mode)}
>
{mode === "system" ? "跟随系统" : mode === "dark" ? "深色" : "浅色"}
</Button>
))}
</div>
</section>
<section className="rounded-2xl border border-[#eef1f6] p-4">
<div className="text-sm font-semibold text-gray-800"></div>
<PreferenceRow
title="飞行模式"
description="开启后默认关闭 AI 面板的“联网”开关(避免误触外部网络)。"
enabled={flightMode}
onToggle={() => setFlightMode(!flightMode)}
/>
</section>
</div>
)}
@@ -162,9 +380,9 @@ function OptionToggleGroup({
onToggle,
}: {
title: string;
optionKeys: (keyof PageOptionsState)[];
optionKeys: BooleanPageOptionKey[];
options: PageOptionsState;
onToggle: (key: keyof PageOptionsState) => void;
onToggle: (key: BooleanPageOptionKey) => void;
}) {
return (
<section>
@@ -183,9 +401,9 @@ function OptionToggle({
options,
onToggle,
}: {
optionKey: keyof PageOptionsState;
optionKey: BooleanPageOptionKey;
options: PageOptionsState;
onToggle: (key: keyof PageOptionsState) => void;
onToggle: (key: BooleanPageOptionKey) => void;
}) {
const meta = OPTION_META[optionKey];
const Icon = meta.icon;
@@ -226,3 +444,27 @@ function StatsCell({ label, value }: { label: string; value: number }) {
</div>
);
}
function PreferenceRow({
title,
description,
enabled,
onToggle,
}: {
title: string;
description: string;
enabled: boolean;
onToggle: () => void;
}) {
return (
<div className="mt-3 flex items-start justify-between gap-3 rounded-xl bg-[#f9fafc] px-3 py-2">
<div className="min-w-0">
<div className="text-sm font-medium text-gray-900">{title}</div>
<div className="text-xs text-gray-400">{description}</div>
</div>
<Button type="button" size="sm" variant={enabled ? "default" : "outline"} onClick={onToggle}>
{enabled ? "已开启" : "已关闭"}
</Button>
</div>
);
}