技术栈:react18,ant design,marked-highlight,highlight.js,marked。
highlight主要负责样式高亮,marked负责格式转换。效果预览:
转化对比:
markdown预览
html预览
首先编写通用的样式代码。新建文件markdown.css:
/* Markdown通用样式 */
/* 设置全局字体样式 */
body {
font-family: Arial, sans-serif;
font-size: 16px;
line-height: 1.6;
color: #333;
}
/* 设置标题样式 */
h1,
h2,
h3,
h4,
h5,
h6 {
margin-top: 1.3em;
margin-bottom: 0.6em;
font-weight: bold;
}
h1 {
font-size: 2.2em;
}
h2 {
font-size: 1.8em;
}
h3 {
font-size: 1.6em;
}
h4 {
font-size: 1.4em;
}
h5 {
font-size: 1.2em;
}
h6 {
font-size: 1em;
}
/* 设置段落样式 */
p {
margin-bottom: 1.3em;
}
/* 设置链接样式 */
a {
color: #337ab7;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
/* 设置列表样式 */
ul,
ol {
margin-top: 0;
margin-bottom: 1.3em;
padding-left: 2em;
}
/* 设置代码块样式 */
pre {
background-color: #f7f7f7;
padding: 1em;
border-radius: 4px;
overflow: auto;
}
code {
font-family: Consolas, Monaco, Courier, monospace;
font-size: 0.9em;
background-color: #f7f7f7;
padding: 0.2em 0.4em;
border-radius: 4px;
}
/* 设置引用样式 */
blockquote {
margin: 0;
padding-left: 1em;
border-left: 4px solid #ddd;
color: #777;
}
/* 设置表格样式 */
table {
border-collapse: collapse;
width: 100%;
margin-bottom: 1.3em;
}
table th,
table td {
padding: 0.5em;
border: 1px solid #ccc;
}
/* 添加一些额外的样式,如图片居中显示 */
img {
display: block;
margin: 0 auto;
max-width: 100%;
height: auto;
}
/* 设置代码行号样式 */
pre code .line-numbers {
display: inline-block;
width: 2em;
padding-right: 1em;
color: #999;
text-align: right;
user-select: none;
pointer-events: none;
border-right: 1px solid #ddd;
margin-right: 0.5em;
}
/* 设置代码行样式 */
pre code .line {
display: block;
padding-left: 1.5em;
}
/* 设置代码高亮样式 */
pre code .line.highlighted {
background-color: #f7f7f7;
}
/* 添加一些响应式样式,适应移动设备 */
@media only screen and (max-width: 768px) {
body {
font-size: 14px;
line-height: 1.5;
}
h1 {
font-size: 1.8em;
}
h2 {
font-size: 1.5em;
}
h3 {
font-size: 1.3em;
}
h4 {
font-size: 1.1em;
}
h5 {
font-size: 1em;
}
h6 {
font-size: 0.9em;
}
table {
font-size: 14px;
}
}
准备工作
-
安装所需依赖:
marked
、highlight.js
、ant design
,marked-highlight
-
然后编写tsx文件。新建
MarkdownTransfer.tsx
文件,引入react
核心方法,引入ui组件库ant design
的一些组件,引入marked
、highlight.js
、marked-highlight
,引入刚才编写好的markdown.css
文件。
import { useState } from 'react';
import { Col, Row, Card, Button, Input, Modal, Space, message } from 'antd';
import { markedHighlight } from "marked-highlight"
import 'highlight.js/styles/atom-one-dark.css'; // 引入合适的样式
import {Marked} from 'marked';
import hljs from 'highlight.js'
import './markdown.css'
思路分析:
- 预览markdown:基本的思路是左边的输入框改变时,拿到输入框内的值,用marked转换为html格式,动态修改右侧预览区域的html。
- 下载markdown:拿到左侧输入框的值,创建一个具有download属性的a标签,模拟点击下载。
- 下载html:和上面类似,只不过下载的字符串变为转化后的html字符串。注意下载时要拼接上markdown.css中的css,才能正确显示样式。
代码实现
- 由于没有复杂的交互,这次没有划分子组件,直接在一个组件里完成了所有的功能。下面是组件中的完整代码。配合同级目录下的在前文创建的
markdown.css
,即可实现所有功能。可以无痛移植到其他现有的react
项目中。
import { useState } from 'react';
import { Col, Row, Card, Button, Input, Modal, Space, message } from 'antd';
import { markedHighlight } from "marked-highlight"
import 'highlight.js/styles/atom-one-dark.css'; // 引入合适的样式
import {Marked} from 'marked';
import hljs from 'highlight.js'
import './markdown.css'
let cssStr = ''
import('./markdown.css').then((res) => {
console.log('cssStr',res)
cssStr = res.default
})
const { TextArea } = Input;
const marked=new Marked(
markedHighlight({
langPrefix: 'hljs language-',
highlight(code, lang) {
const language = hljs.getLanguage(lang) ? lang : 'shell'
return hljs.highlight(code, { language}).value
}
})
)
const transfer = function () {
let cardStyle = {
width: 'auto',
height: '100%',
}
let resultStyle = {
width: 'auto',
height: '550px',
overflowY: 'scroll',
backgroundColor: 'rgb(255,255,255,0.5)',
borderRadius: '6px',
}
let [markdown, setMarkdown] = useState('')
let [myHtml, setMyHtml] = useState('')
let [isMarkdownOpen, setIsMarkdownOpen] = useState(false)
const [isModalOpen, setIsModalOpen] = useState(false);
const [messageApi, contextHolder] = message.useMessage();
/**
* 文本域变化回调,动态修改预览区html
* @param e
*/
const handleChange = (e: any) => {
let md = e.target.value
setMarkdown(md)
let htmlStr = marked.parse(md) as string
setMyHtml(htmlStr)
}
const preview = () => {
setIsModalOpen(true)
}
const handleCancel = () => {
setIsModalOpen(false)
}
const showMarkdown = () => {
setIsMarkdownOpen(true)
}
/**
* @param name 下载的文件名
* @param text 下载的文本字符串
* @param type 下载的类型
* @returns void
*/
const downText = (text: string, type: string = 'text/plain', name: string = 'down.txt') => {
if (!text) {
messageApi.warning('内容为空,无法下载')
return
}
const link = document.createElement('a')
link.download = name
let href = URL.createObjectURL(new Blob([text], { type }))
link.href = href
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
const downMarkdown = () => {
downText(markdown, 'text/plain', 'markdown.md')
}
const downHtml = () => {
let wholeHtml = myHtml + `<style>${cssStr}</style>`//完整的html需要加上css部分。
console.log('wholeHtml', wholeHtml)
downText(wholeHtml, 'text/html', 'myHtml.html')
}
return (
<>
<Row gutter={6}>
{contextHolder}
<Col span={12}>
<Card title="markdown编写" extra={
<Space>
<Button onClick={showMarkdown} type="primary">查看markdown</Button>
<Button onClick={preview} type="primary">预览Html</Button>
</Space>
}
style={cardStyle}>
<TextArea rows={23} placeholder="最多输入9999字" maxLength={9999}
onChange={handleChange} />
</Card>
</Col>
<Col span={12}>
<Card title="markdown预览" extra={<a href="#">More</a>} style={cardStyle}>
<div id="resultContainer" style={resultStyle}>
<div dangerouslySetInnerHTML={{ __html: myHtml }} ></div>
</div>
</Card>
</Col>
</Row>
<Modal title="HTML预览" width={1000} bodyStyle={{ height: '500px', overflowY: 'scroll' }} open={isModalOpen}
footer={<Button type="primary" onClick={downHtml}>下载Html至本地</Button>} onCancel={handleCancel}>
<div dangerouslySetInnerHTML={{ __html: myHtml }} ></div>
</Modal>
<Modal title="markdown预览" width={1000} open={isMarkdownOpen}
footer={<Button type="primary" onClick={downMarkdown}>下载Markdown至本地</Button>} onCancel={markdown => setIsMarkdownOpen(false)}>
<div>
<TextArea rows={28} placeholder="最多输入9999字" maxLength={9999} disabled value={markdown}/>
</div>
</Modal>
</>
)
}
export default transfer