优化模版
This commit is contained in:
parent
c17182527d
commit
a3c021561c
@ -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
|
@ -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>
|
||||
)
|
||||
}
|
@ -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' })
|
||||
})
|
||||
])
|
||||
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
|
@ -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'
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user