优化模版
This commit is contained in:
parent
52ee91c20b
commit
57ebc424c4
@ -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;
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -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",
|
||||
|
@ -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 }
|
||||
}
|
Loading…
Reference in New Issue
Block a user