From ef3f54a4fa0dbe0d1f5c7f31f0f204185826ce57 Mon Sep 17 00:00:00 2001 From: tianyaxiang Date: Sat, 1 Feb 2025 15:30:13 +0800 Subject: [PATCH] add logo --- src/app/layout.tsx | 2 + src/components/editor/WechatEditor.tsx | 150 ++++++++++-------- .../editor/components/EditorToolbar.tsx | 69 +++++++- 3 files changed, 154 insertions(+), 67 deletions(-) diff --git a/src/app/layout.tsx b/src/app/layout.tsx index b054228..2636e40 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -2,6 +2,7 @@ import './globals.css' import { ThemeProvider } from '@/components/theme/ThemeProvider' import { cn } from '@/lib/utils' import { Inter } from 'next/font/google' +import { Toaster } from '@/components/ui/toaster' const inter = Inter({ subsets: ['latin'] }) @@ -49,6 +50,7 @@ export default function RootLayout({ disableTransitionOnChange > {children} + diff --git a/src/components/editor/WechatEditor.tsx b/src/components/editor/WechatEditor.tsx index d7a2f69..b798310 100644 --- a/src/components/editor/WechatEditor.tsx +++ b/src/components/editor/WechatEditor.tsx @@ -15,7 +15,20 @@ import { MarkdownToolbar } from './components/MarkdownToolbar' import { type PreviewSize } from './constants' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { WechatStylePicker } from '@/components/template/WechatStylePicker' -import { Copy } from 'lucide-react' +import { Copy, Clock, Type } from 'lucide-react' + +// 计算阅读时间(假设每分钟阅读300字) +const calculateReadingTime = (text: string): string => { + const words = text.trim().length + const minutes = Math.ceil(words / 300) + return `${minutes} 分钟` +} + +// 计算字数 +const calculateWordCount = (text: string): string => { + const count = text.trim().length + return count.toLocaleString() +} export default function WechatEditor() { const { toast } = useToast() @@ -32,6 +45,10 @@ export default function WechatEditor() { const [previewContent, setPreviewContent] = useState('') const [cursorPosition, setCursorPosition] = useState<{ start: number; end: number }>({ start: 0, end: 0 }) + // 添加字数和阅读时间状态 + const [wordCount, setWordCount] = useState('0') + const [readingTime, setReadingTime] = useState('1 分钟') + // 使用自定义 hooks const { handleScroll } = useEditorSync(editorRef) const { handleEditorChange } = useAutoSave(value, setIsDraft) @@ -150,6 +167,49 @@ export default function WechatEditor() { } }, [value, selectedTemplate, styleOptions]) + // 处理复制 + const handleCopy = useCallback(async () => { + try { + const htmlContent = getPreviewContent() + const tempDiv = document.createElement('div') + tempDiv.innerHTML = htmlContent + const plainText = tempDiv.textContent || tempDiv.innerText + + await navigator.clipboard.write([ + new ClipboardItem({ + 'text/html': new Blob([htmlContent], { type: 'text/html' }), + 'text/plain': new Blob([plainText], { type: 'text/plain' }) + }) + ]) + + toast({ + title: "复制成功", + description: "已复制预览内容", + duration: 2000 + }) + return true + } catch (err) { + console.error('Copy error:', err) + try { + await navigator.clipboard.writeText(previewContent) + toast({ + title: "复制成功", + description: "已复制预览内容(仅文本)", + duration: 2000 + }) + return true + } catch (fallbackErr) { + toast({ + variant: "destructive", + title: "复制失败", + description: "无法访问剪贴板,请检查浏览器权限", + action: 重试, + }) + return false + } + } + }, [previewContent, toast, getPreviewContent]) + // 手动保存 const handleSave = useCallback(() => { try { @@ -228,45 +288,6 @@ export default function WechatEditor() { ) }, [getPreviewContent]) - const handleCopy = useCallback(async () => { - try { - const htmlContent = getPreviewContent() - const tempDiv = document.createElement('div') - tempDiv.innerHTML = htmlContent - const plainText = tempDiv.textContent || tempDiv.innerText - - await navigator.clipboard.write([ - new ClipboardItem({ - 'text/html': new Blob([htmlContent], { type: 'text/html' }), - 'text/plain': new Blob([plainText], { type: 'text/plain' }) - }) - ]) - - toast({ - title: "复制成功", - description: "已复制预览内容", - duration: 2000 - }) - } catch (err) { - console.error('Copy error:', err) - try { - await navigator.clipboard.writeText(previewContent) - toast({ - title: "复制成功", - description: "已复制预览内容(仅文本)", - duration: 2000 - }) - } catch (fallbackErr) { - toast({ - variant: "destructive", - title: "复制失败", - description: "无法访问剪贴板,请检查浏览器权限", - action: 重试, - }) - } - } - }, [previewRef, toast, getPreviewContent]) - // 检测是否为移动设备 const isMobile = useCallback(() => { if (typeof window === 'undefined') return false @@ -365,8 +386,15 @@ export default function WechatEditor() { setStyleOptions({}) }, []) + // 更新字数和阅读时间 + useEffect(() => { + const plainText = previewContent.replace(/<[^>]+>/g, '') + setWordCount(calculateWordCount(plainText)) + setReadingTime(calculateReadingTime(plainText)) + }, [previewContent]) + return ( -
+
+ + {/* 底部工具栏 */} +
+
+ + {wordCount} 字 +
+
+ + 约 {readingTime} +
+
+ + {/* 为底部工具栏添加间距 */} +
) } \ No newline at end of file diff --git a/src/components/editor/components/EditorToolbar.tsx b/src/components/editor/components/EditorToolbar.tsx index 6d0aaf4..30be352 100644 --- a/src/components/editor/components/EditorToolbar.tsx +++ b/src/components/editor/components/EditorToolbar.tsx @@ -9,6 +9,9 @@ import { type RendererOptions } from '@/lib/markdown' import { ThemeToggle } from '@/components/theme/ThemeToggle' import { Logo } from '@/components/icons/Logo' import Link from 'next/link' +import { Button } from '@/components/ui/button' +import { useToast } from '@/components/ui/use-toast' +import { ToastAction } from '@/components/ui/toast' interface EditorToolbarProps { value: string @@ -16,8 +19,8 @@ interface EditorToolbarProps { showPreview: boolean selectedTemplate: string onSave: () => void - onCopy: () => void - onCopyPreview: () => void + onCopy: () => Promise + onCopyPreview: () => Promise onNewArticle: () => void onArticleSelect: (article: Article) => void onTemplateSelect: (template: string) => void @@ -43,6 +46,62 @@ export function EditorToolbar({ onPreviewToggle, styleOptions }: EditorToolbarProps) { + const { toast } = useToast() + + const handleCopy = async () => { + try { + const result = await onCopy() + if (result) { + toast({ + title: "复制成功", + description: "已复制源码内容", + duration: 2000 + }) + } else { + toast({ + variant: "destructive", + title: "复制失败", + description: "无法访问剪贴板,请检查浏览器权限", + action: 重试, + }) + } + } catch (error) { + toast({ + variant: "destructive", + title: "复制失败", + description: "发生错误,请重试", + action: 重试, + }) + } + } + + const handleCopyPreview = async () => { + try { + const result = await onCopyPreview() + if (result) { + toast({ + title: "复制成功", + description: "已复制预览内容", + duration: 2000 + }) + } else { + toast({ + variant: "destructive", + title: "复制失败", + description: "无法访问剪贴板,请检查浏览器权限", + action: 重试, + }) + } + } catch (error) { + toast({ + variant: "destructive", + title: "复制失败", + description: "发生错误,请重试", + action: 重试, + }) + } + } + return (
@@ -104,14 +163,14 @@ export function EditorToolbar({ 保存