优化移动设备体验

This commit is contained in:
tianyaxiang 2025-01-29 23:34:10 +08:00
parent c4c9372c0a
commit 55d44228ec
2 changed files with 103 additions and 134 deletions

View File

@ -15,6 +15,8 @@ import { MobileToolbar } from './components/MobileToolbar'
import { MarkdownToolbar } from './components/MarkdownToolbar' import { MarkdownToolbar } from './components/MarkdownToolbar'
import { type PreviewSize } from './constants' import { type PreviewSize } from './constants'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { WechatStylePicker } from '@/components/template/WechatStylePicker'
import { Copy } from 'lucide-react'
export default function WechatEditor() { export default function WechatEditor() {
const { toast } = useToast() const { toast } = useToast()
@ -339,62 +341,78 @@ export default function WechatEditor() {
return ( return (
<div className="h-full flex flex-col"> <div className="h-full flex flex-col">
<EditorToolbar <div className="hidden sm:block">
value={value} <EditorToolbar
isDraft={isDraft} value={value}
showPreview={showPreview} isDraft={isDraft}
selectedTemplate={selectedTemplate} showPreview={showPreview}
onSave={handleSave} selectedTemplate={selectedTemplate}
onCopy={copyContent} onSave={handleSave}
onCopyPreview={handleCopy} onCopy={copyContent}
onNewArticle={handleNewArticle} onCopyPreview={handleCopy}
onArticleSelect={handleArticleSelect} onNewArticle={handleNewArticle}
onTemplateSelect={setSelectedTemplate} onArticleSelect={handleArticleSelect}
onTemplateChange={() => setValue(value)} onTemplateSelect={setSelectedTemplate}
onStyleOptionsChange={setStyleOptions} onTemplateChange={() => setValue(value)}
onPreviewToggle={() => setShowPreview(!showPreview)} onStyleOptionsChange={setStyleOptions}
styleOptions={styleOptions} onPreviewToggle={() => setShowPreview(!showPreview)}
/> styleOptions={styleOptions}
/>
</div>
<div className="flex-1 flex flex-col sm:flex-row overflow-hidden"> <div className="flex-1 flex flex-col sm:flex-row overflow-hidden">
{/* Mobile Tabs */} {/* Mobile View */}
<div className="sm:hidden flex-1 flex flex-col"> <div className="sm:hidden flex-1 flex flex-col">
<div className="flex items-center justify-between p-2 border-b bg-background">
<div className="flex-1 mr-2">
<WechatStylePicker
value={selectedTemplate}
onSelect={setSelectedTemplate}
/>
</div>
<button
onClick={handleCopy}
className="flex items-center justify-center gap-1 px-2 py-1 rounded-md text-xs text-primary hover:bg-muted transition-colors"
>
<Copy className="h-3.5 w-3.5" />
</button>
</div>
<Tabs defaultValue="editor" className="flex-1 flex flex-col"> <Tabs defaultValue="editor" className="flex-1 flex flex-col">
<TabsList className="grid w-full grid-cols-2"> <TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="editor"></TabsTrigger> <TabsTrigger value="editor"></TabsTrigger>
<TabsTrigger value="preview"></TabsTrigger> <TabsTrigger value="preview"></TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="editor" className="flex-1 flex flex-col data-[state=inactive]:hidden"> <TabsContent value="editor" className="flex-1 data-[state=inactive]:hidden">
<div <div
ref={editorRef} ref={editorRef}
className={cn( className={cn(
"editor-container bg-background flex-1 flex flex-col", "h-full overflow-y-auto",
selectedTemplate && templates.find(t => t.id === selectedTemplate)?.styles selectedTemplate && templates.find(t => t.id === selectedTemplate)?.styles
)} )}
> >
<MarkdownToolbar onInsert={handleToolbarInsert} /> <textarea
<div className="flex-1"> ref={textareaRef}
<textarea value={value}
ref={textareaRef} onChange={handleInput}
value={value} onKeyDown={handleKeyDown}
onChange={handleInput} className="w-full h-full resize-none outline-none p-4 font-mono text-base leading-relaxed"
onKeyDown={handleKeyDown} placeholder="开始写作..."
className="w-full h-full resize-none outline-none p-4 font-mono text-base leading-relaxed" spellCheck={false}
placeholder="开始写作..." />
spellCheck={false}
/>
</div>
</div> </div>
</TabsContent> </TabsContent>
<TabsContent value="preview" className="flex-1 flex flex-col data-[state=inactive]:hidden"> <TabsContent value="preview" className="flex-1 data-[state=inactive]:hidden">
<EditorPreview <div className="h-full overflow-y-auto">
previewRef={previewRef} <EditorPreview
selectedTemplate={selectedTemplate} previewRef={previewRef}
previewSize={previewSize} selectedTemplate={selectedTemplate}
isConverting={isConverting} previewSize={previewSize}
previewContent={previewContent} isConverting={isConverting}
onPreviewSizeChange={setPreviewSize} previewContent={previewContent}
/> onPreviewSizeChange={setPreviewSize}
/>
</div>
</TabsContent> </TabsContent>
</Tabs> </Tabs>
</div> </div>
@ -437,18 +455,6 @@ export default function WechatEditor() {
)} )}
</div> </div>
</div> </div>
{/* Only show mobile toolbar on desktop */}
<div className="hidden sm:block">
<MobileToolbar
showPreview={showPreview}
isDraft={isDraft}
onPreviewToggle={() => setShowPreview(!showPreview)}
onSave={handleSave}
onCopy={copyContent}
onCopyPreview={handleCopy}
/>
</div>
</div> </div>
) )
} }

View File

@ -1,7 +1,7 @@
import { Copy, Save, Settings, Image, Link } from 'lucide-react' import { Copy, Save, Settings } from 'lucide-react'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { useState } from 'react'
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet' import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet'
import { WechatStylePicker } from '../../template/WechatStylePicker'
interface MobileToolbarProps { interface MobileToolbarProps {
showPreview: boolean showPreview: boolean
@ -10,8 +10,8 @@ interface MobileToolbarProps {
onSave: () => void onSave: () => void
onCopy: () => void onCopy: () => void
onCopyPreview: () => void onCopyPreview: () => void
onImageUpload?: (file: File) => Promise<string> selectedTemplate: string
onLinkInsert?: (url: string) => void onTemplateSelect: (template: string) => void
} }
export function MobileToolbar({ export function MobileToolbar({
@ -21,98 +21,61 @@ export function MobileToolbar({
onSave, onSave,
onCopy, onCopy,
onCopyPreview, onCopyPreview,
onImageUpload, selectedTemplate,
onLinkInsert onTemplateSelect
}: MobileToolbarProps) { }: MobileToolbarProps) {
return ( return (
<div className="sm:hidden fixed bottom-0 left-0 right-0 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 border-t"> <div className="bg-background border-t">
<div className="flex items-center justify-between p-2"> <div className="flex flex-col">
<div className="flex items-center gap-2"> <div className="border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<button <div className="p-2">
onClick={onSave} <WechatStylePicker
className={cn( value={selectedTemplate}
"flex flex-col items-center gap-1 px-2 py-1 rounded-md text-xs transition-colors relative", onSelect={onTemplateSelect}
isDraft />
? "text-primary" </div>
: "text-muted-foreground" </div>
)} <div className="flex items-center justify-between p-2 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
>
<Save className="h-5 w-5" />
{isDraft && <span className="absolute -top-1 -right-1 w-2 h-2 bg-primary rounded-full" />}
</button>
<button <button
onClick={onCopyPreview} onClick={onCopyPreview}
className="flex flex-col items-center gap-1 px-2 py-1 rounded-md text-xs text-muted-foreground transition-colors" className="flex-1 flex items-center justify-center gap-2 px-3 py-2 rounded-md text-sm bg-primary text-primary-foreground hover:bg-primary/90 transition-colors mr-2"
> >
<Copy className="h-5 w-5" /> <Copy className="h-4 w-4" />
</button> </button>
</div>
<Sheet> <Sheet>
<SheetTrigger asChild> <SheetTrigger asChild>
<button className="p-1 rounded-md text-muted-foreground"> <button className="flex items-center justify-center p-2 rounded-md text-muted-foreground hover:bg-muted/80">
<Settings className="h-5 w-5" /> <Settings className="h-4 w-4" />
</button>
</SheetTrigger>
<SheetContent side="bottom" className="h-[40vh]">
<div className="grid grid-cols-4 gap-4 p-4">
<button
onClick={onCopy}
className="flex flex-col items-center gap-2 p-3 rounded-lg border hover:bg-muted/80"
>
<Copy className="h-6 w-6" />
<span className="text-xs"></span>
</button> </button>
<button </SheetTrigger>
onClick={onCopyPreview} <SheetContent side="bottom" className="h-[30vh]">
className="flex flex-col items-center gap-2 p-3 rounded-lg border hover:bg-muted/80" <div className="flex flex-col gap-4 p-4">
>
<Copy className="h-6 w-6" />
<span className="text-xs"></span>
</button>
{onImageUpload && (
<button <button
onClick={() => { onClick={onSave}
const input = document.createElement('input') className={cn(
input.type = 'file' "flex items-center justify-center gap-2 px-4 py-2 rounded-md text-sm transition-colors",
input.accept = 'image/*' isDraft
input.onchange = async (e) => { ? "bg-primary text-primary-foreground"
const file = (e.target as HTMLInputElement).files?.[0] : "bg-muted text-muted-foreground"
if (file && onImageUpload) { )}
try {
await onImageUpload(file)
} catch (error) {
console.error('Image upload failed:', error)
}
}
}
input.click()
}}
className="flex flex-col items-center gap-2 p-3 rounded-lg border hover:bg-muted/80"
> >
<Image className="h-6 w-6" /> <Save className="h-4 w-4" />
<span className="text-xs"></span>
{isDraft && <span className="w-2 h-2 bg-primary-foreground rounded-full" />}
</button> </button>
)}
{onLinkInsert && (
<button <button
onClick={() => { onClick={onCopy}
const url = window.prompt('请输入链接地址') className="flex items-center justify-center gap-2 px-4 py-2 rounded-md bg-muted text-muted-foreground text-sm"
if (url && onLinkInsert) {
onLinkInsert(url)
}
}}
className="flex flex-col items-center gap-2 p-3 rounded-lg border hover:bg-muted/80"
> >
<Link className="h-6 w-6" /> <Copy className="h-4 w-4" />
<span className="text-xs"></span>
</button> </button>
)} </div>
</div> </SheetContent>
</SheetContent> </Sheet>
</Sheet> </div>
</div> </div>
</div> </div>
) )