markdown前端解析

发布于:2025-04-05 ⋅ 阅读:(48) ⋅ 点赞:(0)

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/


网站公告

今日签到

点亮在社区的每一天
去签到