@wzyjs/components 0.2.55 → 0.2.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -3
- package/src/Markdown/index.tsx +135 -8
- package/src/Markdown/style.ts +106 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wzyjs/components",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.56",
|
|
4
4
|
"description": "description",
|
|
5
5
|
"author": "wzy",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -22,8 +22,11 @@
|
|
|
22
22
|
"react-json-view": "^1.21.3",
|
|
23
23
|
"react-markdown": "^10.1.0",
|
|
24
24
|
"react-syntax-highlighter": "^15.5.0",
|
|
25
|
+
"rehype-autolink-headings": "^7.1.0",
|
|
26
|
+
"rehype-slug": "^6.0.0",
|
|
25
27
|
"rehype-starry-night": "^2.2.0",
|
|
26
|
-
"remark-gfm": "^4.0.1"
|
|
28
|
+
"remark-gfm": "^4.0.1",
|
|
29
|
+
"remark-toc": "^9.0.0"
|
|
27
30
|
},
|
|
28
31
|
"peerDependencies": {
|
|
29
32
|
"@wzyjs/antd": "^0.2.37",
|
|
@@ -40,7 +43,7 @@
|
|
|
40
43
|
"@types/react-grid-layout": "^1.3.5",
|
|
41
44
|
"@types/react-syntax-highlighter": "^15.5.5"
|
|
42
45
|
},
|
|
43
|
-
"gitHead": "
|
|
46
|
+
"gitHead": "2a14d6fdc5a381dd93390472cdfbc409f12e65db",
|
|
44
47
|
"publishConfig": {
|
|
45
48
|
"access": "public"
|
|
46
49
|
}
|
package/src/Markdown/index.tsx
CHANGED
|
@@ -1,23 +1,150 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import { useMemo } from 'react'
|
|
4
|
+
import ReactMarkdown from 'react-markdown'
|
|
5
|
+
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
|
|
6
|
+
import { oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism'
|
|
5
7
|
import remarkGfm from 'remark-gfm'
|
|
6
|
-
import
|
|
8
|
+
import rehypeSlug from 'rehype-slug'
|
|
9
|
+
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
|
|
7
10
|
|
|
8
11
|
import 'github-markdown-css/github-markdown-light.css'
|
|
12
|
+
import { style } from './style'
|
|
9
13
|
|
|
10
14
|
interface MarkdownProps {
|
|
11
15
|
content: string
|
|
16
|
+
showToc?: boolean // 是否显示目录
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface TocItem {
|
|
20
|
+
id: string
|
|
21
|
+
text: string
|
|
22
|
+
level: number
|
|
12
23
|
}
|
|
13
24
|
|
|
14
25
|
export const Markdown = (props: MarkdownProps) => {
|
|
15
|
-
const { content } = props
|
|
26
|
+
const { content, showToc = true } = props
|
|
27
|
+
|
|
28
|
+
// 提取标题生成目录
|
|
29
|
+
const tocItems = useMemo(() => {
|
|
30
|
+
if (!showToc) {
|
|
31
|
+
return []
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const lines = content.split('\n')
|
|
35
|
+
const items: TocItem[] = []
|
|
36
|
+
|
|
37
|
+
lines.forEach((line) => {
|
|
38
|
+
const match = line.match(/^(#{2,4})\s+(.+)$/)
|
|
39
|
+
if (match) {
|
|
40
|
+
const level = match[1].length
|
|
41
|
+
const text = match[2].trim()
|
|
42
|
+
const id = text
|
|
43
|
+
.toLowerCase()
|
|
44
|
+
.replace(/[^\w\u4e00-\u9fa5\s-]/g, '') // 保留中文、英文、数字、空格和连字符
|
|
45
|
+
.replace(/\s+/g, '-') // 空格替换为连字符
|
|
46
|
+
|
|
47
|
+
items.push({ id, text, level })
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
return items
|
|
52
|
+
}, [content, showToc])
|
|
53
|
+
|
|
54
|
+
// 渲染目录
|
|
55
|
+
const renderToc = () => {
|
|
56
|
+
if (!showToc || tocItems.length === 0) {
|
|
57
|
+
return null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div
|
|
62
|
+
className='markdown-toc-container'
|
|
63
|
+
style={{
|
|
64
|
+
padding: '10px 20px 10px 0',
|
|
65
|
+
backgroundColor: '#f8f9fa',
|
|
66
|
+
borderLeft: '4px solid #1890ff',
|
|
67
|
+
borderRadius: '4px',
|
|
68
|
+
}}
|
|
69
|
+
>
|
|
70
|
+
<ul style={{ margin: 0, padding: 0 }}>
|
|
71
|
+
{tocItems.map((item, index) => (
|
|
72
|
+
<li
|
|
73
|
+
key={index}
|
|
74
|
+
style={{
|
|
75
|
+
marginBottom: '4px',
|
|
76
|
+
marginLeft: `${(item.level - 1) * 16}px`,
|
|
77
|
+
listStyle: 'none',
|
|
78
|
+
position: 'relative',
|
|
79
|
+
}}
|
|
80
|
+
>
|
|
81
|
+
<a
|
|
82
|
+
href={`#${item.id}`}
|
|
83
|
+
style={{
|
|
84
|
+
textDecoration: 'none',
|
|
85
|
+
color: '#1890ff',
|
|
86
|
+
fontSize: item.level <= 2 ? '14px' : '13px',
|
|
87
|
+
fontWeight: item.level === 1 ? 600 : 400,
|
|
88
|
+
}}
|
|
89
|
+
onClick={(e) => {
|
|
90
|
+
e.preventDefault()
|
|
91
|
+
const element = document.getElementById(item.id)
|
|
92
|
+
if (element) {
|
|
93
|
+
element.scrollIntoView({ behavior: 'smooth' })
|
|
94
|
+
}
|
|
95
|
+
}}
|
|
96
|
+
>
|
|
97
|
+
{item.text}
|
|
98
|
+
</a>
|
|
99
|
+
</li>
|
|
100
|
+
))}
|
|
101
|
+
</ul>
|
|
102
|
+
</div>
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
16
106
|
return (
|
|
17
|
-
<div
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
107
|
+
<div style={{ height: '100%' }}>
|
|
108
|
+
<style>{style}</style>
|
|
109
|
+
<div className='markdown-body markdown-toc' style={{ height: '100%', display: 'flex' }}>
|
|
110
|
+
<div style={{ marginRight: 14 }}>
|
|
111
|
+
{renderToc()}
|
|
112
|
+
</div>
|
|
113
|
+
<div style={{ overflow: 'auto', flex: 1 }}>
|
|
114
|
+
<ReactMarkdown
|
|
115
|
+
remarkPlugins={[remarkGfm]}
|
|
116
|
+
rehypePlugins={[
|
|
117
|
+
rehypeSlug,
|
|
118
|
+
[rehypeAutolinkHeadings, {
|
|
119
|
+
behavior: 'wrap',
|
|
120
|
+
properties: {
|
|
121
|
+
className: ['anchor'],
|
|
122
|
+
},
|
|
123
|
+
}],
|
|
124
|
+
]}
|
|
125
|
+
components={{
|
|
126
|
+
code(props) {
|
|
127
|
+
const { children, className } = props
|
|
128
|
+
const match = /language-(\w+)/.exec(className || '')
|
|
129
|
+
return match ? (
|
|
130
|
+
<SyntaxHighlighter
|
|
131
|
+
PreTag='div'
|
|
132
|
+
children={String(children).replace(/\n$/, '')}
|
|
133
|
+
language={match[1]}
|
|
134
|
+
style={oneLight}
|
|
135
|
+
/>
|
|
136
|
+
) : (
|
|
137
|
+
<code className={className}>
|
|
138
|
+
{children}
|
|
139
|
+
</code>
|
|
140
|
+
)
|
|
141
|
+
},
|
|
142
|
+
}}
|
|
143
|
+
>
|
|
144
|
+
{content}
|
|
145
|
+
</ReactMarkdown>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
21
148
|
</div>
|
|
22
149
|
)
|
|
23
150
|
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
export const style = `
|
|
2
|
+
code {
|
|
3
|
+
color: #c7254e;
|
|
4
|
+
background-color: #f9f2f4;
|
|
5
|
+
border-radius: 4px;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/* 目录容器样式 */
|
|
9
|
+
.markdown-toc .markdown-body {
|
|
10
|
+
position: relative;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/* 锚点链接样式 */
|
|
14
|
+
.markdown-toc .anchor {
|
|
15
|
+
text-decoration: none !important;
|
|
16
|
+
color: inherit !important;
|
|
17
|
+
display: contents !important;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.markdown-toc .anchor:hover {
|
|
21
|
+
text-decoration: underline !important;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* 标题悬浮效果 */
|
|
25
|
+
.markdown-toc h1:hover .anchor::after,
|
|
26
|
+
.markdown-toc h2:hover .anchor::after,
|
|
27
|
+
.markdown-toc h3:hover .anchor::after,
|
|
28
|
+
.markdown-toc h4:hover .anchor::after,
|
|
29
|
+
.markdown-toc h5:hover .anchor::after,
|
|
30
|
+
.markdown-toc h6:hover .anchor::after {
|
|
31
|
+
content: " 🔗";
|
|
32
|
+
opacity: 0.6;
|
|
33
|
+
font-size: 0.8em;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* 目录标题样式 */
|
|
37
|
+
// .markdown-toc h2:first-of-type {
|
|
38
|
+
// display: block !important;
|
|
39
|
+
// color: #24292f !important;
|
|
40
|
+
// font-size: 18px !important;
|
|
41
|
+
// font-weight: 600 !important;
|
|
42
|
+
// line-height: 1.4 !important;
|
|
43
|
+
// border-bottom: 1px solid #d1d9e0 !important;
|
|
44
|
+
// padding-bottom: 8px !important;
|
|
45
|
+
// margin: 0 0 16px 0 !important;
|
|
46
|
+
// }
|
|
47
|
+
|
|
48
|
+
/* 目录专用样式 - 只对第一个h2下的列表生效 */
|
|
49
|
+
.markdown-toc h2:first-of-type + ul {
|
|
50
|
+
background: #f8f9fa !important;
|
|
51
|
+
border: 1px solid #e1e4e8 !important;
|
|
52
|
+
border-radius: 6px !important;
|
|
53
|
+
padding: 16px !important;
|
|
54
|
+
margin: 16px 0 24px 0 !important;
|
|
55
|
+
list-style: none !important;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.markdown-toc h2:first-of-type + ul li {
|
|
59
|
+
margin: 8px 0 !important;
|
|
60
|
+
padding-left: 0 !important;
|
|
61
|
+
list-style: none !important;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.markdown-toc h2:first-of-type + ul li::before {
|
|
65
|
+
content: "📖 " !important;
|
|
66
|
+
margin-right: 8px !important;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.markdown-toc h2:first-of-type + ul li ul {
|
|
70
|
+
margin: 4px 0 0 0 !important;
|
|
71
|
+
padding-left: 20px !important;
|
|
72
|
+
list-style: none !important;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.markdown-toc h2:first-of-type + ul li ul li {
|
|
76
|
+
margin: 4px 0 !important;
|
|
77
|
+
padding-left: 0 !important;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.markdown-toc h2:first-of-type + ul li ul li::before {
|
|
81
|
+
content: "▸ " !important;
|
|
82
|
+
margin-right: 6px !important;
|
|
83
|
+
color: #666 !important;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.markdown-toc h2:first-of-type + ul li ul li ul {
|
|
87
|
+
padding-left: 16px !important;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.markdown-toc h2:first-of-type + ul li ul li ul li::before {
|
|
91
|
+
content: "◦ " !important;
|
|
92
|
+
color: #999 !important;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* 目录链接样式 */
|
|
96
|
+
.markdown-toc h2:first-of-type + ul a {
|
|
97
|
+
color: #0969da !important;
|
|
98
|
+
text-decoration: none !important;
|
|
99
|
+
font-weight: 500 !important;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.markdown-toc h2:first-of-type + ul a:hover {
|
|
103
|
+
text-decoration: underline !important;
|
|
104
|
+
color: #0550ae !important;
|
|
105
|
+
}
|
|
106
|
+
`
|