monaco-editor
: https://github.com/microsoft/monaco-editor
微软开源的代码编辑器,,支持多种编程语言的语法高亮,智能提示,代码补全,错误提示等功能。。他是Visual Studio Code 编辑器的核心组件,非常强大灵活
"use client"
import {useEffect, useRef, useState} from "react";
import * as monaco from 'monaco-editor';
import dynamic from "next/dynamic";
import {message} from "antd";
// nextjs中ssr渲染,没有window对象
if (typeof window !== "undefined"){
(window as any).MonacoEnvironment = {
getWorker: function (_: string, label: string) {
switch (label) {
case 'json':
return new Worker(new URL('monaco-editor/esm/vs/language/json/json.worker', import.meta.url))
case 'css':
case 'scss':
case 'less':
return new Worker(new URL('monaco-editor/esm/vs/language/css/css.worker', import.meta.url))
case 'html':
case 'handlebars':
case 'razor':
return new Worker(new URL('monaco-editor/esm/vs/language/html/html.worker', import.meta.url))
case 'typescript':
case 'javascript':
return new Worker(new URL('monaco-editor/esm/vs/language/typescript/ts.worker', import.meta.url))
default:
return new Worker(new URL('monaco-editor/esm/vs/editor/editor.worker', import.meta.url))
}
}
}
}
function MonacoEditor({language="javascript",value,onChange}){
var editorRef = useRef(null);
// 创建的编辑器
var monacoEditorRef = useRef(null);
// 初始高度 30px
const [editorHeight, setEditorHeight] = useState(30)
useEffect(() => {
if (editorRef.current){
// 初始化 Monaco 编辑器
const editor = monaco.editor.create(editorRef.current, {
value: value || '',
language: language,
theme: 'vs-dark', // 你可以根据需求设置不同的主题
automaticLayout: true,
scrollBeyondLastLine:false,
scrollbar:{
vertical:"hidden",
horizontal:"hidden"
},
minimap:{
enabled:false
}
});
// 创建的编辑器
monacoEditorRef.current = editor
// 监听编辑器的内容变化
editor.onDidChangeModelContent((event) => {
onChange(editor.getValue());
// 根据内容高度调整编辑器容器的高度
var contentHeight = editor.getContentHeight();
console.log(contentHeight,"height")
setEditorHeight(contentHeight)
});
// 根据内容高度调整编辑器容器的高度
var contentHeight = editor.getContentHeight();
console.log(contentHeight,"height")
setEditorHeight(contentHeight)
return () => {
editor.dispose();
};
}
}, []);
const handleCopy = ()=>{
// editorRef.current
const text = monacoEditorRef.current?.getValue() || ""
console.log(text,"text")
navigator.clipboard.writeText(text).then(()=>{
message.success("拷贝成功")
})
}
return (
<div className="relative">
<div ref={editorRef} style={{height: editorHeight, minHeight: "30px", width: '100%'}}/>
{/* 复制按钮 */}
<button
onClick={handleCopy}
style={{
position: 'absolute',
top: 8,
right: 8,
padding: '4px 8px',
fontSize: 12,
background: '#333',
color: '#fff',
border: 'none',
borderRadius: 4,
cursor: 'pointer',
zIndex: 10,
}}
>
复制代码
</button>
</div>
)
}
export default MonacoEditor
marked解析markdown
https://github.com/markedjs/marked
文档:https://marked.js.org/using_advanced#highlight
https://marked.js.org/using_pro
marked
是一个非常流行的markdown解析库,能够将markdown转换为html,,,
npm install marked
里面有一个配置,可以设置renderer属性:
属性为code是代码片段的配置,,将code片段替换成monaco-editor
因为nextjs中SSR没有window对象,引入monaco-editor会报错:
// 动态导入 MonacoEditor,禁用 SSR
const MonacoEditor = dynamic(() => import("@/components/MonacoEditor"), {
ssr: false, // 仅在客户端渲染
});
"use client"
import { marked } from 'marked';
import content from "./content"
import "github-markdown-css";
import "highlight.js/styles/github-dark.css";
import {useEffect, useState} from "react";
import dynamic from "next/dynamic"; // 你可以换成其他主题
// 动态导入 MonacoEditor,禁用 SSR
const MonacoEditor = dynamic(() => import("@/components/MonacoEditor"), {
ssr: false, // 仅在客户端渲染
});
function ArticleItem(){
// 创建一个 marked 实例的 Parser
const parser = new marked.Parser();
// Override function
// const renderer = {
// heading({ tokens, depth }) {
// const text = this.parser.parseInline(tokens);
// const escapedText = text.toLowerCase().replace(/[^\w]+/g, '-');
//
// return `
// <h${depth}>
// <a name="${escapedText}" class="anchor" href="#${escapedText}">
// <span class="header-link"></span>
// </a>
// ${text}
// </h${depth}>`;
// },
// code({ text, lang }) {
// console.log(text,lang,"1231")
// // 确保 lang 是合法的语言
// const validLang = hljs.getLanguage(lang) ? lang : 'plaintext';
// const highlightedCode = hljs.highlight(text.trim(), { language: validLang }).value;
//
// return `
// <pre><code class="hljs ${validLang}">
// ${highlightedCode.trim()}
// </code></pre>`;
// },
// };
const renderer = new marked.Renderer()
renderer.heading = (data) => {
const { raw, text, depth } = data;
console.log(text, depth);
console.log()
// 将一级标题转换为h1标签
if (depth === 1) {
return `<h1 class="hClass"> ${text}</h1>`;
} else if (depth === 2) {
return `<h2 class="hClass">${text}</h2>`;
} else if (depth === 3) {
return `<h3 class="hClass">${text}</h3>`;
} else if (depth === 4) {
return `<h4 class="hClass">${text}</h4>`;
} else if (depth === 5) {
return `<h5 class="hClass">${text}</h5>`;
} else if (depth === 6) {
return `<h6 class="hClass">${text}</h6>`;
}
};
renderer.html = (html) => {
console.log(html);
const { text } = html;
return `<div class="htmlClass">${text}</div>`;
};
// renderer.code = ({ text, lang })=> {
// console.log(text,lang,"1231")
// // 确保 lang 是合法的语言
// const validLang = hljs.getLanguage(lang) ? lang : 'plaintext';
// const highlightedCode = hljs.highlight(text.trim(), { language: validLang }).value;
//
// // return `
// // <pre><code class="hljs ${validLang}">
// // ${highlightedCode.trim()}
// // </code></pre>`;
//
//
// return `<MonacoEditor
// language="javascript"
// value=${text}
// onChange={(newCode) => setCode(newCode)}
// />`
// }
marked.use({
renderer,
breaks:true
})
// let str = marked.parse(content)
// console.log(str,"str")
// const htmlContent = marked(content).replace(/(\s{2,})/g, ' ') // 将多余的空格替换为一个空格
// .replace(/\n+/g, '\n') // 保留换行符,将多个换行符替换为一个
// .trim(); // 去掉开头和结尾的空格;
const [parsedContent, setParsedContent] = useState([])
useEffect(() => {
// 解析出来的每个标签
var tokens = marked.lexer(content);
console.log(tokens,"tokens")
const newParsedContent = tokens.map((token,index)=>{
if (token.type === "code"){
return {
type:"code",
content:token.text,
lang:token.lang || "plaintext",
index
}
}
return {
type:"html",
content: marked.parser([token]),
index
}
})
console.log(newParsedContent,"parse-content")
setParsedContent(newParsedContent)
}, []);
// const htmlContent = marked(content).trim()
const [isClient, setIsClient] = useState(false)
useEffect(() => {
setIsClient(typeof window !=="undefined")
}, []);
if (!isClient) return null
return (
<div>
{parsedContent.map((block,index)=>{
let html = block.type==="code"?(
<div key={index}>
{block.content && <MonacoEditor key={index} language={block.lang} value={block.content} />}
</div>
): (
<div key={index}>
<div key={index} dangerouslySetInnerHTML={{__html: block.content}}/>
</div>
)
return html
})}
{/*<div dangerouslySetInnerHTML={{__html:htmlContent}} ></div>*/}
</div>
)
}
export default ArticleItem
引用:https://blog.csdn.net/Qxn530/article/details/140997694
https://github.com/microsoft/monaco-editor/blob/main/docs/integrate-esm.md
https://juejin.cn/post/7255557296951738429
bytemd
https://github.com/pd4d10/bytemd
https://github.com/hinesboy/mavonEditor
https://markdown-it.github.io/markdown-it/