diff --git a/.gitignore b/.gitignore index d4f0d5c..836deed 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ # dependencies /node_modules +/release /.pnp .pnp.* .yarn/* diff --git a/release/noteai-fixes-2026-03-04-v2.zip b/release/noteai-fixes-2026-03-04-v2.zip deleted file mode 100644 index ddd28e1..0000000 Binary files a/release/noteai-fixes-2026-03-04-v2.zip and /dev/null differ diff --git a/release/noteai-fixes-2026-03-04-v3-flat.zip b/release/noteai-fixes-2026-03-04-v3-flat.zip deleted file mode 100644 index 48fd162..0000000 Binary files a/release/noteai-fixes-2026-03-04-v3-flat.zip and /dev/null differ diff --git a/release/noteai-fixes-2026-03-04.zip b/release/noteai-fixes-2026-03-04.zip deleted file mode 100644 index c7e6d9e..0000000 Binary files a/release/noteai-fixes-2026-03-04.zip and /dev/null differ diff --git a/release/noteai-full-source-with-db-2026-03-04-flat.zip b/release/noteai-full-source-with-db-2026-03-04-flat.zip deleted file mode 100644 index 2b84c3d..0000000 Binary files a/release/noteai-full-source-with-db-2026-03-04-flat.zip and /dev/null differ diff --git a/release/noteai-full-source-with-db-2026-03-04.zip b/release/noteai-full-source-with-db-2026-03-04.zip deleted file mode 100644 index c09a4ba..0000000 Binary files a/release/noteai-full-source-with-db-2026-03-04.zip and /dev/null differ diff --git a/src/app/globals.css b/src/app/globals.css index a88b24f..706e59d 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -179,10 +179,10 @@ padding-bottom: 120px; } - /* 移动端侧边栏文档树:增大字号和触摸目标 */ + /* 移动端侧边栏文档树:适中字号与触摸目标 */ .sidebar-tree-item { font-size: 0.9375rem !important; - min-height: 2.5rem !important; + min-height: 2.3rem !important; line-height: 1.2; } @@ -292,7 +292,7 @@ /* Table Styles - Ensure content is compact */ .ProseMirror table p { margin: 0; - line-height: var(--table-line-height, 1.2); + line-height: var(--table-line-height, 1.5); } /* 利用 TipTap 表格扩展的 .tableWrapper 包裹层实现横向滚动 */ @@ -315,7 +315,7 @@ border-color: hsl(var(--border)); box-sizing: border-box; min-width: 1em; - padding: 2px 4px; + padding: 6px 8px; position: relative; vertical-align: top; } @@ -393,13 +393,3 @@ ul[data-type="taskList"], white-space: nowrap; line-height: 1.2; } - -@media (min-width: 768px) { - .sidebar-title { - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 2; - white-space: normal; - max-height: calc(1.25em * 2); - } -} diff --git a/src/app/page.tsx b/src/app/page.tsx index 24b91ac..553c93e 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -6,8 +6,7 @@ import { useEditorStore } from "@/lib/store"; import { Hash, X, Plus, ChevronLeft, ChevronRight, ChevronDown, Sparkles, Lock, Unlock, FolderOpen, History, RotateCcw } from "lucide-react"; import { useSettingsStore } from "@/lib/settings-store"; import { exportPageAsMarkdown } from "@/lib/export"; -import { useMemo, useState } from "react"; -import { useEffect } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import { cn, getTagColor } from "@/lib/utils"; import { useSearchStore } from "@/lib/search-store"; import { type Editor as TiptapEditor } from "@tiptap/react"; @@ -39,6 +38,9 @@ export default function Home() { const [isIconPickerOpen, setIsIconPickerOpen] = useState(false); const [isHistoryOpen, setIsHistoryOpen] = useState(false); const [selectedHistoryIndex, setSelectedHistoryIndex] = useState(0); + const [isMobileSidebarOpen, setIsMobileSidebarOpen] = useState(() => !activePageId); + const edgeSwipeStartRef = useRef<{ x: number; y: number } | null>(null); + const drawerSwipeStartRef = useRef<{ x: number; y: number } | null>(null); const breadcrumbs = useMemo(() => { if (!activePage) return []; @@ -123,25 +125,85 @@ export default function Home() { }; }, [flushPendingSyncs]); + const handleEdgeSwipeStart = (e: React.TouchEvent) => { + if (isMobileSidebarOpen) return; + const touch = e.touches[0]; + if (!touch || touch.clientX > 24) return; + edgeSwipeStartRef.current = { x: touch.clientX, y: touch.clientY }; + }; + + const handleEdgeSwipeEnd = (e: React.TouchEvent) => { + const start = edgeSwipeStartRef.current; + edgeSwipeStartRef.current = null; + if (!start || isMobileSidebarOpen) return; + const touch = e.changedTouches[0]; + if (!touch) return; + + const deltaX = touch.clientX - start.x; + const deltaY = Math.abs(touch.clientY - start.y); + if (deltaX > 56 && deltaY < 42) { + setIsMobileSidebarOpen(true); + } + }; + + const handleDrawerSwipeStart = (e: React.TouchEvent) => { + const touch = e.touches[0]; + if (!touch) return; + drawerSwipeStartRef.current = { x: touch.clientX, y: touch.clientY }; + }; + + const handleDrawerSwipeEnd = (e: React.TouchEvent) => { + const start = drawerSwipeStartRef.current; + drawerSwipeStartRef.current = null; + if (!start) return; + const touch = e.changedTouches[0]; + if (!touch) return; + + const deltaX = touch.clientX - start.x; + const deltaY = Math.abs(touch.clientY - start.y); + if (deltaX < -56 && deltaY < 42) { + setIsMobileSidebarOpen(false); + } + }; + return ( -
+
-
- +
+
+ setIsMobileSidebarOpen(false)} /> +
-
+
{activePage ? (
)} - style={{ width: isCollapsed ? 0 : width }} - > - +
+ setIsCollapsed((v) => !v)} /> +
-
- -
- - {!isCollapsed && ( -
- )} - + {!isCollapsed && ( +
+ )} + + ); } -export function SidebarContent({ onCloseMobile }: { onCloseMobile?: () => void }) { +export function SidebarContent({ + onCloseMobile, + onToggleSidebarCollapse, +}: { + onCloseMobile?: () => void; + onToggleSidebarCollapse?: () => void; +}) { const { pages, activePageId, fetchPages, addPage, isLoading, movePage, reorderPages } = useEditorStore(); const { setOpen, openSearchWithTag } = useSearchStore(); const { triggerImport, isImporting } = useImport(); @@ -197,6 +204,12 @@ export function SidebarContent({ onCloseMobile }: { onCloseMobile?: () => void } }, []); const allTags = Array.from(new Set(pages.flatMap((p) => p.tags || []))).sort((a, b) => a.localeCompare(b)); + const activePage = pages.find((p) => p.id === activePageId) || null; + const contextualImportParentId = activePage + ? activePage.type === "folder" + ? activePage.id + : activePage.parentId + : null; const handleLogout = async () => { try { @@ -273,6 +286,9 @@ export function SidebarContent({ onCloseMobile }: { onCloseMobile?: () => void } if (onCloseMobile) onCloseMobile(); }; + const canCollapseSidebar = Boolean(onToggleSidebarCollapse); + const canCloseMobile = Boolean(onCloseMobile); + return (
@@ -285,46 +301,105 @@ export function SidebarContent({ onCloseMobile }: { onCloseMobile?: () => void }
- - - - {onCloseMobile && ( + {(canCollapseSidebar || canCloseMobile) && ( )} + + + + + + + { + addPage(null, "file"); + handleItemClick(); + }} + > + + 新建文件 + + { + addPage(null, "folder"); + handleItemClick(); + }} + > + + 新建文件夹 + + { + setOpen(true); + handleItemClick(); + }} + > + + 搜索文档 + + + { + triggerImport(contextualImportParentId); + handleItemClick(); + }} + > + + {isImporting ? "导入中..." : "导入到当前层级"} + + { + triggerImport(null); + handleItemClick(); + }} + > + + {isImporting ? "导入中..." : "导入到根目录"} + + + { + router.push("/settings"); + handleItemClick(); + }} + > + + 设置 + + setTheme(theme === "dark" ? "light" : "dark")} + > + {mounted ? (theme === "dark" ? : ) : } + {mounted ? (theme === "dark" ? "切换到明亮模式" : "切换到暗黑模式") : "切换主题"} + + + + 退出登录 + + + +
@@ -378,13 +453,13 @@ export function SidebarContent({ onCloseMobile }: { onCloseMobile?: () => void } )}
-
+
- - - - - -
-
-
); } diff --git a/src/components/sidebar/tree-view.tsx b/src/components/sidebar/tree-view.tsx index dbfaead..6aeb5c8 100644 --- a/src/components/sidebar/tree-view.tsx +++ b/src/components/sidebar/tree-view.tsx @@ -97,7 +97,7 @@ function TreeNode({ }); const style = { - paddingLeft: `${level * 12 + 6}px`, + paddingLeft: `${level * 6}px`, transform: CSS.Translate.toString(transform), transition, opacity: isDragging ? 0.5 : 1, @@ -107,7 +107,7 @@ function TreeNode({
{isActive && } -
+