Initial commit

This commit is contained in:
2026-03-08 01:34:54 +08:00
commit 1f104f73c8
441 changed files with 64911 additions and 0 deletions

4
src/hooks/ui/index.ts Normal file
View File

@@ -0,0 +1,4 @@
export { useSidebarState, type UseSidebarStateReturn } from './useSidebarState'
export { useSidebarResize } from './useSidebarResize'
export { useTOCState, type UseTOCStateReturn } from './useTOCState'
export { useKeyboardShortcuts, type UseKeyboardShortcutsParams } from './useKeyboardShortcuts'

View File

@@ -0,0 +1,54 @@
import { useEffect } from 'react'
import type { FileItem } from '@/lib/api'
export interface UseKeyboardShortcutsParams {
selectedFile: FileItem | null
isSpecialTab: (file: FileItem | null) => boolean
isAsyncImportProcessing: boolean
isCurrentTabEditing: boolean
hasCurrentTabUnsavedChanges: boolean
handleSave: () => Promise<void>
toggleTabEdit: (path: string) => void
}
export function useKeyboardShortcuts({
selectedFile,
isSpecialTab,
isAsyncImportProcessing,
isCurrentTabEditing,
hasCurrentTabUnsavedChanges,
handleSave,
toggleTabEdit
}: UseKeyboardShortcutsParams): void {
useEffect(() => {
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
if (isCurrentTabEditing && hasCurrentTabUnsavedChanges) {
e.preventDefault()
e.returnValue = ''
}
}
window.addEventListener('beforeunload', handleBeforeUnload)
return () => window.removeEventListener('beforeunload', handleBeforeUnload)
}, [isCurrentTabEditing, hasCurrentTabUnsavedChanges])
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (isSpecialTab(selectedFile) || isAsyncImportProcessing) return
if (e.ctrlKey && e.key === 's') {
e.preventDefault()
if (isCurrentTabEditing) {
handleSave()
}
} else if (e.ctrlKey && e.key === 'e') {
e.preventDefault()
if (selectedFile) {
toggleTabEdit(selectedFile.path)
}
}
}
window.addEventListener('keydown', handleKeyDown)
return () => window.removeEventListener('keydown', handleKeyDown)
}, [selectedFile, isSpecialTab, isAsyncImportProcessing, isCurrentTabEditing, handleSave, toggleTabEdit])
}

View File

@@ -0,0 +1,33 @@
import { useCallback, useEffect, useState } from 'react'
export const useSidebarResize = (initialWidth: number = 250) => {
const [sidebarWidth, setSidebarWidth] = useState(initialWidth)
const [isResizing, setIsResizing] = useState(false)
const startResizing = useCallback((e: React.MouseEvent) => {
e.preventDefault()
setIsResizing(true)
}, [])
useEffect(() => {
if (!isResizing) return
const stopResizing = () => setIsResizing(false)
const resize = (e: MouseEvent) => {
const newWidth = e.clientX
if (newWidth > 150 && newWidth < 600) {
setSidebarWidth(newWidth)
}
}
window.addEventListener('mousemove', resize)
window.addEventListener('mouseup', stopResizing)
return () => {
window.removeEventListener('mousemove', resize)
window.removeEventListener('mouseup', stopResizing)
}
}, [isResizing])
return { sidebarWidth, startResizing }
}

View File

@@ -0,0 +1,22 @@
import { useState, useCallback } from 'react'
export interface UseSidebarStateReturn {
sidebarOpen: boolean
setSidebarOpen: React.Dispatch<React.SetStateAction<boolean>>
refreshKey: number
bumpRefresh: () => void
}
export function useSidebarState(): UseSidebarStateReturn {
const [sidebarOpen, setSidebarOpen] = useState(true)
const [refreshKey, setRefreshKey] = useState(0)
const bumpRefresh = useCallback(() => setRefreshKey((prev) => prev + 1), [])
return {
sidebarOpen,
setSidebarOpen,
refreshKey,
bumpRefresh
}
}

View File

@@ -0,0 +1,60 @@
import { useState, useCallback } from 'react'
import type { FileItem } from '@/lib/api'
import type { TOCItem } from '@/lib/utils'
import { scrollToElementInTab } from '@/components/tabs'
export interface UseTOCStateReturn {
showTOC: boolean
tocItems: TOCItem[]
showTOCButton: boolean
handleTOCClick: () => void
handleTOCClose: () => void
handleTOCItemClick: (id: string) => void
handleTocUpdated: (filePath: string, items: TOCItem[]) => void
}
export function useTOCState(selectedFile: FileItem | null): UseTOCStateReturn {
const [showTOC, setShowTOC] = useState(false)
const [tocItems, setTocItems] = useState<TOCItem[]>([])
const [showTOCButton, setShowTOCButton] = useState(true)
const handleTocUpdated = useCallback((filePath: string, items: TOCItem[]) => {
if (selectedFile?.path === filePath) {
setTocItems(items)
}
}, [selectedFile])
const handleTOCClick = useCallback(() => {
const newState = !showTOC
setShowTOC(newState)
if (newState) {
setTimeout(() => {
setShowTOCButton(false)
}, 300)
} else {
setShowTOCButton(true)
}
}, [showTOC])
const handleTOCClose = useCallback(() => {
setShowTOC(false)
setShowTOCButton(true)
}, [])
const handleTOCItemClick = useCallback((id: string) => {
if (selectedFile) {
scrollToElementInTab(id, selectedFile.path)
}
}, [selectedFile])
return {
showTOC,
tocItems,
showTOCButton,
handleTOCClick,
handleTOCClose,
handleTOCItemClick,
handleTocUpdated
}
}