diff --git a/package.json b/package.json
index cd6acde..49bd30a 100644
--- a/package.json
+++ b/package.json
@@ -20,8 +20,8 @@
"@radix-ui/react-toast": "^1.2.5",
"@radix-ui/react-toggle": "^1.0.3",
"@radix-ui/react-tooltip": "^1.1.7",
- "@tiptap/extension-image": "^2.2.4",
- "@tiptap/extension-link": "^2.2.4",
+ "@tiptap/extension-image": "^2.11.3",
+ "@tiptap/extension-link": "^2.11.3",
"@tiptap/pm": "^2.2.4",
"@tiptap/react": "^2.2.4",
"@tiptap/starter-kit": "^2.2.4",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 5d86d4a..bf1a238 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -42,10 +42,10 @@ importers:
specifier: ^1.1.7
version: 1.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@tiptap/extension-image':
- specifier: ^2.2.4
+ specifier: ^2.11.3
version: 2.11.3(@tiptap/core@2.11.3(@tiptap/pm@2.11.3))
'@tiptap/extension-link':
- specifier: ^2.2.4
+ specifier: ^2.11.3
version: 2.11.3(@tiptap/core@2.11.3(@tiptap/pm@2.11.3))(@tiptap/pm@2.11.3)
'@tiptap/pm':
specifier: ^2.2.4
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index d0f217f..5b6ebf9 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,11 +1,9 @@
import type { Metadata, Viewport } from "next";
-import { Inter } from "next/font/google";
import "./globals.css";
import { cn } from "@/lib/utils";
import { Toaster } from "@/components/ui/toaster";
import { ThemeProvider } from "@/components/theme/ThemeProvider";
-const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "NeuraPress",
@@ -38,7 +36,6 @@ export default function RootLayout({
{
const [preview, setPreview] = useState(false)
const editor = useEditor({
- extensions: [StarterKit],
+ extensions: [
+ StarterKit,
+ Link.configure({
+ openOnClick: false,
+ }),
+ Image,
+ ],
content: '开始编辑...
',
editorProps: {
attributes: {
diff --git a/src/components/editor/StyleConfigDialog.tsx b/src/components/editor/StyleConfigDialog.tsx
index bce915f..bb5697d 100644
--- a/src/components/editor/StyleConfigDialog.tsx
+++ b/src/components/editor/StyleConfigDialog.tsx
@@ -13,50 +13,44 @@ const stylePresets = {
default: {
name: '默认样式',
options: {
- fontSize: {
- h1: '24px',
- h2: '20px',
- h3: '18px',
- paragraph: '15px',
- code: '14px'
+ base: {
+ primaryColor: '#333333',
+ textAlign: 'left',
+ lineHeight: '1.75'
},
- colors: {
- text: '#333333',
- heading: '#1a1a1a',
- link: '#576b95',
- code: '#333333',
- quote: '#666666'
+ block: {
+ h1: { fontSize: '24px', color: '#1a1a1a' },
+ h2: { fontSize: '20px', color: '#1a1a1a' },
+ h3: { fontSize: '18px', color: '#1a1a1a' },
+ p: { fontSize: '15px', color: '#333333' },
+ code_pre: { fontSize: '14px', color: '#333333' }
},
- spacing: {
- paragraph: '20px',
- heading: '30px',
- list: '20px',
- quote: '20px'
+ inline: {
+ link: { color: '#576b95' },
+ codespan: { color: '#333333' },
+ em: { color: '#666666' }
}
}
},
modern: {
name: '现代简约',
options: {
- fontSize: {
- h1: '28px',
- h2: '24px',
- h3: '20px',
- paragraph: '16px',
- code: '15px'
+ base: {
+ primaryColor: '#2d3748',
+ textAlign: 'left',
+ lineHeight: '1.8'
},
- colors: {
- text: '#2d3748',
- heading: '#1a202c',
- link: '#4299e1',
- code: '#2d3748',
- quote: '#718096'
+ block: {
+ h1: { fontSize: '28px', color: '#1a202c' },
+ h2: { fontSize: '24px', color: '#1a202c' },
+ h3: { fontSize: '20px', color: '#1a202c' },
+ p: { fontSize: '16px', color: '#2d3748' },
+ code_pre: { fontSize: '15px', color: '#2d3748' }
},
- spacing: {
- paragraph: '24px',
- heading: '36px',
- list: '24px',
- quote: '24px'
+ inline: {
+ link: { color: '#4299e1' },
+ codespan: { color: '#2d3748' },
+ em: { color: '#718096' }
}
}
}
@@ -64,16 +58,16 @@ const stylePresets = {
interface StyleConfigDialogProps {
value: RendererOptions
- onChange: (options: RendererOptions) => void
+ onChangeAction: (options: RendererOptions) => void
}
-export function StyleConfigDialog({ value, onChange }: StyleConfigDialogProps) {
+export function StyleConfigDialog({ value, onChangeAction }: StyleConfigDialogProps) {
const [currentOptions, setCurrentOptions] = useState(value)
const handlePresetChange = (preset: keyof typeof stylePresets) => {
const newOptions = stylePresets[preset].options
setCurrentOptions(newOptions)
- onChange(newOptions)
+ onChangeAction(newOptions)
}
const handleOptionChange = (
@@ -89,7 +83,7 @@ export function StyleConfigDialog({ value, onChange }: StyleConfigDialogProps) {
}
}
setCurrentOptions(newOptions)
- onChange(newOptions)
+ onChangeAction(newOptions)
}
return (
@@ -107,9 +101,9 @@ export function StyleConfigDialog({ value, onChange }: StyleConfigDialogProps) {
预设样式
- 字体
- 颜色
- 间距
+ 基础
+ 块级元素
+ 行内元素
@@ -122,10 +116,10 @@ export function StyleConfigDialog({ value, onChange }: StyleConfigDialogProps) {
>
{preset.name}
@@ -134,50 +128,42 @@ export function StyleConfigDialog({ value, onChange }: StyleConfigDialogProps) {
-
+
- {Object.entries(currentOptions.fontSize || {}).map(([key, value]) => (
+ {currentOptions.base && Object.entries(currentOptions.base).map(([key, value]) => (
handleOptionChange('fontSize', key, e.target.value)}
+ onChange={(e) => handleOptionChange('base', key, e.target.value)}
/>
))}
-
+
- {Object.entries(currentOptions.colors || {}).map(([key, value]) => (
+ {currentOptions.block && Object.entries(currentOptions.block).map(([key, value]) => (
))}
-
+
- {Object.entries(currentOptions.spacing || {}).map(([key, value]) => (
+ {currentOptions.inline && Object.entries(currentOptions.inline).map(([key, value]) => (
handleOptionChange('spacing', key, e.target.value)}
+ value={JSON.stringify(value)}
+ onChange={(e) => handleOptionChange('inline', key, e.target.value)}
/>
))}
diff --git a/src/components/editor/components/EditorToolbar.tsx b/src/components/editor/components/EditorToolbar.tsx
index 2e27c7f..1527365 100644
--- a/src/components/editor/components/EditorToolbar.tsx
+++ b/src/components/editor/components/EditorToolbar.tsx
@@ -67,7 +67,7 @@ export function EditorToolbar({
diff --git a/src/components/theme/ThemeProvider.tsx b/src/components/theme/ThemeProvider.tsx
index 220a1f8..51c643f 100644
--- a/src/components/theme/ThemeProvider.tsx
+++ b/src/components/theme/ThemeProvider.tsx
@@ -1,8 +1,7 @@
"use client"
import * as React from "react"
-import { ThemeProvider as NextThemesProvider } from "next-themes"
-import { type ThemeProviderProps } from "next-themes/dist/types"
+import { ThemeProvider as NextThemesProvider, type ThemeProviderProps } from "next-themes"
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return {children}
diff --git a/src/lib/markdown.ts b/src/lib/markdown.ts
index e38aa1d..6330b11 100644
--- a/src/lib/markdown.ts
+++ b/src/lib/markdown.ts
@@ -4,9 +4,7 @@ import type { CSSProperties } from 'react'
// 配置 marked 选项
marked.setOptions({
gfm: true,
- breaks: true,
- headerIds: false,
- mangle: false,
+ breaks: true
})
// 将 React CSSProperties 转换为 CSS 字符串
@@ -48,12 +46,11 @@ function baseStylesToString(base: RendererOptions['base'] = {}): string {
export function convertToWechat(markdown: string, options: RendererOptions = {}): string {
const renderer = new marked.Renderer()
// 标题渲染
- renderer.heading = ({ tokens, depth }: { tokens: Tokens.Generic[]; depth: number }) => {
- const style = options.block?.[`h${depth}` as keyof RendererOptions['block']]
+ renderer.heading = ({ text, depth }: Tokens.Heading) => {
+ const style = options.block?.[`h${depth}` as keyof typeof options.block]
const styleStr = cssPropertiesToString(style)
- const content = tokens.map(token => token.text).join('')
- return `${content}`
- }
+ return `${text}`
+ }
// 段落渲染
renderer.paragraph = (text) => {
@@ -102,17 +99,18 @@ export function convertToWechat(markdown: string, options: RendererOptions = {})
}
// 链接渲染
- renderer.link = (href, title, text) => {
+ renderer.link = ({ href, title, tokens }: Tokens.Link) => {
const style = options.inline?.link
const styleStr = cssPropertiesToString(style)
+ const text = tokens?.map(t => 'text' in t ? t.text : '').join('') || ''
return `${text}`
}
// 图片渲染
- renderer.image = (href, title, text) => {
+ renderer.image = ({ href, title, text }: Tokens.Image) => {
const style = options.block?.image
const styleStr = cssPropertiesToString(style)
- return `
`
+ return `
`
}
// 列表渲染
@@ -145,7 +143,7 @@ export function convertToXiaohongshu(markdown: string): string {
// 配置小红书特定的样式
const xiaohongshuRenderer = new marked.Renderer()
- xiaohongshuRenderer.heading = (text, level) => {
+ xiaohongshuRenderer.heading = ({ text, depth }: Tokens.Heading) => {
const fontSize = {
1: '20px',
2: '18px',
@@ -153,9 +151,9 @@ export function convertToXiaohongshu(markdown: string): string {
4: '15px',
5: '14px',
6: '14px'
- }[level]
+ }[depth]
- return `${text}`
+ return `${text}`
}
xiaohongshuRenderer.paragraph = (text) => {