优化模版

This commit is contained in:
tianyaxiang 2025-01-30 17:23:23 +08:00
parent c17182527d
commit a3c021561c
6 changed files with 22 additions and 305 deletions

View File

@ -1,79 +0,0 @@
'use client'
import { useEditor, EditorContent } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
import Link from '@tiptap/extension-link'
import Image from '@tiptap/extension-image'
import { useState } from 'react'
import { TemplateSelector } from '../template/TemplateSelector'
import { cn } from '@/lib/utils'
const templates = [
{
id: 'wechat',
transform: (html: string) => html, // Add your transform logic here
styles: 'wechat-specific-styles'
}
// Add more templates as needed
]
const Editor = () => {
const [selectedTemplate, setSelectedTemplate] = useState<string>('')
const [preview, setPreview] = useState(false)
const editor = useEditor({
extensions: [
StarterKit,
Link.configure({
openOnClick: false,
}),
Image,
],
content: '<p>开始编辑...</p>',
editorProps: {
attributes: {
class: 'prose prose-sm sm:prose lg:prose-lg xl:prose-2xl mx-auto focus:outline-none min-h-[500px]',
},
},
})
const handleTemplateSelect = (templateId: string) => {
setSelectedTemplate(templateId)
}
const getPreviewContent = () => {
if (!editor || !selectedTemplate) return ''
const template = templates.find(t => t.id === selectedTemplate)
if (!template) return editor.getHTML()
return template.transform(editor.getHTML())
}
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<TemplateSelector onSelect={handleTemplateSelect} />
<button
onClick={() => setPreview(!preview)}
className="px-4 py-2 rounded-md bg-primary text-primary-foreground hover:bg-primary/90"
>
{preview ? '编辑' : '预览'}
</button>
</div>
<div className="border rounded-lg p-4">
{preview ? (
<div className={cn(
"prose max-w-none",
selectedTemplate && templates.find(t => t.id === selectedTemplate)?.styles
)}>
<div dangerouslySetInnerHTML={{ __html: getPreviewContent() }} />
</div>
) : (
<EditorContent editor={editor} />
)}
</div>
</div>
)
}
export default Editor

View File

@ -1,132 +0,0 @@
'use client'
import { Editor } from '@tiptap/react'
import {
Bold,
Italic,
List,
ListOrdered,
Quote,
Heading1,
Heading2,
Heading3,
Minus,
Link,
Image,
} from 'lucide-react'
import { Toggle } from '@/components/ui/toggle'
import { Separator } from '@/components/ui/separator'
import { cn } from '@/lib/utils'
interface EditorToolbarProps {
editor: Editor | null
}
export function EditorToolbar({ editor }: EditorToolbarProps) {
if (!editor) return null
return (
<div className="border-b">
<div className="flex flex-wrap gap-1 p-1">
<Toggle
size="sm"
pressed={editor.isActive('heading', { level: 1 })}
onPressedChange={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
>
<Heading1 className="h-4 w-4" />
</Toggle>
<Toggle
size="sm"
pressed={editor.isActive('heading', { level: 2 })}
onPressedChange={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
>
<Heading2 className="h-4 w-4" />
</Toggle>
<Toggle
size="sm"
pressed={editor.isActive('heading', { level: 3 })}
onPressedChange={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}
>
<Heading3 className="h-4 w-4" />
</Toggle>
<Separator orientation="vertical" className="mx-1 h-6" />
<Toggle
size="sm"
pressed={editor.isActive('bold')}
onPressedChange={() => editor.chain().focus().toggleBold().run()}
>
<Bold className="h-4 w-4" />
</Toggle>
<Toggle
size="sm"
pressed={editor.isActive('italic')}
onPressedChange={() => editor.chain().focus().toggleItalic().run()}
>
<Italic className="h-4 w-4" />
</Toggle>
<Separator orientation="vertical" className="mx-1 h-6" />
<Toggle
size="sm"
pressed={editor.isActive('bulletList')}
onPressedChange={() => editor.chain().focus().toggleBulletList().run()}
>
<List className="h-4 w-4" />
</Toggle>
<Toggle
size="sm"
pressed={editor.isActive('orderedList')}
onPressedChange={() => editor.chain().focus().toggleOrderedList().run()}
>
<ListOrdered className="h-4 w-4" />
</Toggle>
<Separator orientation="vertical" className="mx-1 h-6" />
<Toggle
size="sm"
pressed={editor.isActive('blockquote')}
onPressedChange={() => editor.chain().focus().toggleBlockquote().run()}
>
<Quote className="h-4 w-4" />
</Toggle>
<Toggle
size="sm"
pressed={editor.isActive('horizontalRule')}
onPressedChange={() => editor.chain().focus().setHorizontalRule().run()}
>
<Minus className="h-4 w-4" />
</Toggle>
<Separator orientation="vertical" className="mx-1 h-6" />
<Toggle
size="sm"
pressed={editor.isActive('link')}
onPressedChange={() => {
const url = window.prompt('URL')
if (url) {
editor.chain().focus().setLink({ href: url }).run()
}
}}
>
<Link className="h-4 w-4" />
</Toggle>
<Toggle
size="sm"
onPressedChange={() => {
const url = window.prompt('Image URL')
if (url) {
editor.chain().focus().setImage({ src: url }).run()
}
}}
>
<Image className="h-4 w-4" />
</Toggle>
</div>
</div>
)
}

View File

@ -219,7 +219,18 @@ export default function WechatEditor() {
// 替换所有元素中的 CSS 变量
const replaceVariables = (element: HTMLElement) => {
const style = window.getComputedStyle(element)
const properties = ['color', 'background-color', 'border-color', 'border-left-color']
const properties = ['color', 'background-color', 'border-color', 'border-left-color', 'font-size']
// 获取元素的计算样式
const computedStyle = window.getComputedStyle(element)
// 保留原始样式
const originalStyle = element.getAttribute('style') || ''
// 如果是段落元素,确保应用字体大小
if (element.tagName.toLowerCase() === 'p') {
// alert(element.style.fontSize )
element.style.fontSize = '15px'
}
properties.forEach(prop => {
const value = style.getPropertyValue(prop)
@ -229,6 +240,9 @@ export default function WechatEditor() {
finalValue = finalValue.replace(`var(${variable})`, replacement)
})
element.style[prop as any] = finalValue
} else if (prop === 'font-size' && !element.style.fontSize) {
// 如果没有字体大小,从计算样式中获取
element.style.fontSize = computedStyle.fontSize
}
})
@ -250,8 +264,8 @@ export default function WechatEditor() {
// 创建并写入剪贴板
await navigator.clipboard.write([
new ClipboardItem({
'text/html': new Blob([tempDiv.innerHTML], { type: 'text/html' }),
'text/plain': new Blob([tempDiv.innerText], { type: 'text/plain' })
'text/html': new Blob([previewContent.innerHTML], { type: 'text/html' }),
'text/plain': new Blob([previewContent.innerText], { type: 'text/plain' })
})
])

View File

@ -1,86 +0,0 @@
'use client'
import { Check, ChevronDown } from "lucide-react"
import * as SelectPrimitive from '@radix-ui/react-select'
import { cn } from "@/lib/utils"
import { templates } from '@/config/templates'
import { useState } from 'react'
export function TemplateSelector({
onSelect
}: {
onSelect: (template: string, subTemplate?: string) => void
}) {
const [selectedTemplate, setSelectedTemplate] = useState<string>('')
return (
<div className="flex gap-2">
<SelectPrimitive.Root
onValueChange={(value) => {
setSelectedTemplate(value)
onSelect(value)
}}
>
<SelectPrimitive.Trigger className="inline-flex items-center justify-between rounded-md px-3 py-2 text-sm font-medium border border-input hover:bg-accent hover:text-accent-foreground min-w-[120px]">
<SelectPrimitive.Value placeholder="选择模板..." />
<SelectPrimitive.Icon>
<ChevronDown className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
<SelectPrimitive.Portal>
<SelectPrimitive.Content className="overflow-hidden bg-popover rounded-md border shadow-md">
<SelectPrimitive.Viewport className="p-1">
{templates.map((template) => (
<SelectPrimitive.Item
key={template.id}
value={template.id}
className={cn(
"relative flex items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none hover:bg-accent hover:text-accent-foreground cursor-pointer"
)}
>
<SelectPrimitive.ItemText>{template.name}</SelectPrimitive.ItemText>
<SelectPrimitive.ItemIndicator className="absolute left-2 inline-flex items-center">
<Check className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</SelectPrimitive.Item>
))}
</SelectPrimitive.Viewport>
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
</SelectPrimitive.Root>
{selectedTemplate && templates.find(t => t.id === selectedTemplate)?.subTemplates && (
<SelectPrimitive.Root onValueChange={(value) => onSelect(selectedTemplate, value)}>
<SelectPrimitive.Trigger className="inline-flex items-center justify-between rounded-md px-3 py-2 text-sm font-medium border border-input hover:bg-accent hover:text-accent-foreground min-w-[120px]">
<SelectPrimitive.Value placeholder="选择样式..." />
<SelectPrimitive.Icon>
<ChevronDown className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
<SelectPrimitive.Portal>
<SelectPrimitive.Content className="overflow-hidden bg-popover rounded-md border shadow-md">
<SelectPrimitive.Viewport className="p-1">
{templates
.find(t => t.id === selectedTemplate)
?.subTemplates?.map((subTemplate) => (
<SelectPrimitive.Item
key={subTemplate.id}
value={subTemplate.id}
className={cn(
"relative flex items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none hover:bg-accent hover:text-accent-foreground cursor-pointer"
)}
>
<SelectPrimitive.ItemText>{subTemplate.name}</SelectPrimitive.ItemText>
<SelectPrimitive.ItemIndicator className="absolute left-2 inline-flex items-center">
<Check className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</SelectPrimitive.Item>
))}
</SelectPrimitive.Viewport>
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
</SelectPrimitive.Root>
)}
</div>
)
}

View File

@ -53,16 +53,16 @@ export function WechatStylePicker({ value, onSelect }: WechatStylePickerProps) {
<h1 style={{
...template.options?.block?.h1,
fontSize: template.options?.block?.h1?.fontSize || '1.2em'
}}>
} as React.CSSProperties}>
</h1>
<p style={{
...template.options?.block?.p,
fontSize: template.options?.block?.p?.fontSize || '1em'
}}>
} as React.CSSProperties}>
</p>
<blockquote style={template.options?.block?.blockquote}>
<blockquote style={template.options?.block?.blockquote as React.CSSProperties}>
</blockquote>
</div>

View File

@ -65,7 +65,7 @@ export const templates: Template[] = [
borderRadius: '6px',
color: 'rgba(0,0,0,0.5)',
background: 'var(--blockquote-background)',
marginBottom: '1em'
margin: '0 0 1em 0'
},
code_pre: {
fontSize: '14px',
@ -201,7 +201,7 @@ export const templates: Template[] = [
fontSize: '15px',
color: '#4a5568',
margin: '20px 0',
lineHeight: 1.6,
lineHeight: 1.75,
borderLeft: '3px solid #4299e1',
paddingLeft: '1em'
},