优化模版

This commit is contained in:
tianyaxiang 2025-02-01 16:45:08 +08:00
parent 52ee91c20b
commit 57ebc424c4
4 changed files with 86 additions and 59 deletions

View File

@ -684,4 +684,14 @@
/* 暗黑模式下的加载动画 */
.dark .animate-spin {
color: hsl(var(--primary));
}
@layer utilities {
.scrollbar-none {
scrollbar-width: none;
-ms-overflow-style: none;
}
.scrollbar-none::-webkit-scrollbar {
display: none;
}
}

View File

@ -393,8 +393,10 @@ export default function WechatEditor() {
setReadingTime(calculateReadingTime(plainText))
}, [previewContent])
const isScrolling = useRef<boolean>(false)
return (
<div className="h-full flex flex-col relative">
<div className="h-screen flex flex-col overflow-hidden">
<div className="hidden sm:block">
<EditorToolbar
value={value}
@ -441,7 +443,7 @@ export default function WechatEditor() {
<div
ref={editorRef}
className={cn(
"h-full overflow-y-auto",
"h-full",
selectedTemplate && templates.find(t => t.id === selectedTemplate)?.styles
)}
>
@ -450,9 +452,26 @@ export default function WechatEditor() {
value={value}
onChange={handleInput}
onKeyDown={handleKeyDown}
className="w-full h-full resize-none outline-none p-4 font-mono text-base leading-relaxed"
className="w-full h-full resize-none outline-none p-4 font-mono text-base leading-relaxed overflow-y-scroll scrollbar-none"
placeholder="开始写作..."
spellCheck={false}
onScroll={(e) => {
if (isScrolling.current) return
isScrolling.current = true
try {
const textarea = e.currentTarget
const previewContainer = document.querySelector('.preview-container .overflow-y-auto')
if (!previewContainer) return
const scrollPercentage = textarea.scrollTop / (textarea.scrollHeight - textarea.clientHeight)
const previewScrollTop = scrollPercentage * (previewContainer.scrollHeight - previewContainer.clientHeight)
previewContainer.scrollTop = previewScrollTop
} finally {
requestAnimationFrame(() => {
isScrolling.current = false
})
}
}}
/>
</div>
</TabsContent>
@ -476,7 +495,7 @@ export default function WechatEditor() {
<div
ref={editorRef}
className={cn(
"editor-container bg-background transition-all duration-300 ease-in-out flex flex-col h-[calc(100vh-theme(spacing.16)-theme(spacing.10))] min-h-[600px]",
"editor-container bg-background transition-all duration-300 ease-in-out flex flex-col h-full",
showPreview
? "w-1/2 border-r"
: "w-full",
@ -484,15 +503,32 @@ export default function WechatEditor() {
)}
>
<MarkdownToolbar onInsert={handleToolbarInsert} />
<div className="flex-1">
<div className="flex-1 overflow-hidden">
<textarea
ref={textareaRef}
value={value}
onChange={handleInput}
onKeyDown={handleKeyDown}
className="w-full h-full resize-none outline-none p-4 font-mono text-base leading-relaxed"
className="w-full h-full resize-none outline-none p-4 font-mono text-base leading-relaxed overflow-y-scroll scrollbar-none"
placeholder="开始写作..."
spellCheck={false}
onScroll={(e) => {
if (isScrolling.current) return
isScrolling.current = true
try {
const textarea = e.currentTarget
const previewContainer = document.querySelector('.preview-container .overflow-y-auto')
if (!previewContainer) return
const scrollPercentage = textarea.scrollTop / (textarea.scrollHeight - textarea.clientHeight)
const previewScrollTop = scrollPercentage * (previewContainer.scrollHeight - previewContainer.clientHeight)
previewContainer.scrollTop = previewScrollTop
} finally {
requestAnimationFrame(() => {
isScrolling.current = false
})
}
}}
/>
</div>
</div>

View File

@ -2,7 +2,7 @@ import { cn } from '@/lib/utils'
import { PREVIEW_SIZES, type PreviewSize } from '../constants'
import { Loader2, ZoomIn, ZoomOut, Maximize2, Minimize2 } from 'lucide-react'
import { templates } from '@/config/wechat-templates'
import { useState } from 'react'
import { useState, useRef } from 'react'
interface EditorPreviewProps {
previewRef: React.RefObject<HTMLDivElement>
@ -23,6 +23,7 @@ export function EditorPreview({
}: EditorPreviewProps) {
const [zoom, setZoom] = useState(100)
const [isFullscreen, setIsFullscreen] = useState(false)
const isScrolling = useRef<boolean>(false)
const handleZoomIn = () => {
setZoom(prev => Math.min(prev + 10, 200))
@ -47,12 +48,12 @@ export function EditorPreview({
ref={previewRef}
className={cn(
"preview-container bg-background transition-all duration-300 ease-in-out flex flex-col",
"h-[50%] sm:h-full sm:w-1/2",
"markdown-body",
"h-full sm:w-1/2",
"markdown-body relative",
selectedTemplate && templates.find(t => t.id === selectedTemplate)?.styles
)}
>
<div className="bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 border-b flex items-center justify-between z-10 sticky top-0">
<div className="bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 border-b flex items-center justify-between z-10 sticky top-0 left-0 right-0">
<div className="text-sm text-muted-foreground px-2 py-1"></div>
<div className="flex items-center gap-4 px-2 py-1">
<div className="flex items-center gap-2">
@ -96,8 +97,23 @@ export function EditorPreview({
</div>
</div>
<div className="flex-1 overflow-y-auto">
<div className="min-h-full py-8 px-4">
<div className="flex-1 overflow-y-auto" onScroll={(e) => {
const container = e.currentTarget
const textarea = document.querySelector('.editor-container textarea')
if (!textarea || isScrolling.current) return
isScrolling.current = true
try {
const scrollPercentage = container.scrollTop / (container.scrollHeight - container.clientHeight)
const textareaScrollTop = scrollPercentage * (textarea.scrollHeight - textarea.clientHeight)
textarea.scrollTop = textareaScrollTop
} finally {
requestAnimationFrame(() => {
isScrolling.current = false
})
}
}}>
<div className="h-full py-8 px-4">
<div
className={cn(
"bg-background mx-auto rounded-lg transition-all duration-300",

View File

@ -1,43 +1,23 @@
import { useCallback, useEffect, useRef } from 'react'
import { useCallback, useRef } from 'react'
export function useEditorSync(editorRef: React.RefObject<HTMLDivElement>) {
// 防止滚动事件循环的标志
const isScrolling = useRef(false)
// 同步滚动处理
const handleScroll = useCallback((event: Event) => {
const source = event.target
// 检查是否是编辑器滚动
const isEditor = source instanceof Element && source.closest('.editor-container')
if (!editorRef.current) return
const editorElement = editorRef.current.querySelector('.bytemd-editor')
if (!editorElement) return
// 防止滚动事件循环
if (isScrolling.current) return
const handleScroll = useCallback((event: React.UIEvent<HTMLTextAreaElement>) => {
if (!editorRef.current || isScrolling.current) return
isScrolling.current = true
try {
if (isEditor) {
const sourceScrollTop = (source as Element).scrollTop
const sourceMaxScroll = (source as Element).scrollHeight - (source as Element).clientHeight
const percentage = sourceScrollTop / sourceMaxScroll
const windowMaxScroll = document.documentElement.scrollHeight - window.innerHeight
window.scrollTo({
top: percentage * windowMaxScroll,
behavior: 'auto'
})
} else {
const windowScrollTop = window.scrollY
const windowMaxScroll = document.documentElement.scrollHeight - window.innerHeight
const percentage = windowScrollTop / windowMaxScroll
const targetScrollTop = percentage * (editorElement.scrollHeight - editorElement.clientHeight)
editorElement.scrollTop = targetScrollTop
}
const textarea = event.currentTarget
const previewContainer = editorRef.current.parentElement?.querySelector('.preview-container')
if (!previewContainer) return
const scrollPercentage = textarea.scrollTop / (textarea.scrollHeight - textarea.clientHeight)
const previewScrollTop = scrollPercentage * (previewContainer.scrollHeight - previewContainer.clientHeight)
previewContainer.scrollTop = previewScrollTop
} finally {
// 确保在下一帧重置标志
requestAnimationFrame(() => {
@ -46,20 +26,5 @@ export function useEditorSync(editorRef: React.RefObject<HTMLDivElement>) {
}
}, [editorRef])
// 添加滚动事件监听
useEffect(() => {
const editorElement = editorRef.current?.querySelector('.bytemd-editor')
if (editorElement) {
editorElement.addEventListener('scroll', handleScroll, { passive: true })
window.addEventListener('scroll', handleScroll, { passive: true })
return () => {
editorElement.removeEventListener('scroll', handleScroll)
window.removeEventListener('scroll', handleScroll)
}
}
}, [handleScroll])
return { handleScroll }
}