优化模版
This commit is contained in:
		
							parent
							
								
									52ee91c20b
								
							
						
					
					
						commit
						57ebc424c4
					
				| @ -685,3 +685,13 @@ | ||||
| .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 textarea = event.currentTarget | ||||
|       const previewContainer = editorRef.current.parentElement?.querySelector('.preview-container') | ||||
|       if (!previewContainer) return | ||||
| 
 | ||||
|         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 scrollPercentage = textarea.scrollTop / (textarea.scrollHeight - textarea.clientHeight) | ||||
|       const previewScrollTop = scrollPercentage * (previewContainer.scrollHeight - previewContainer.clientHeight) | ||||
|        | ||||
|         const targetScrollTop = percentage * (editorElement.scrollHeight - editorElement.clientHeight) | ||||
|         editorElement.scrollTop = targetScrollTop | ||||
|       } | ||||
|       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
	 tianyaxiang
						tianyaxiang