nodejs: 能在线编辑 Markdown 文档的 Web 服务程序,更多扩展功能

承上一篇:nodejs: 能在线编辑 Markdown 文档的 Web 服务程序

如果需要更多 Markdown 扩展(如表格、数学公式)等功能,怎样编写?

已经采用了移除服务端 mermaid 依赖的方案,现在想要为这个 Markdown 编辑器扩展表格、数学公式等功能,继续完善代码,添加这些常用的 Markdown 扩展能力,同时保持代码的简洁和可维护性。

实现思路

  1. 表格支持marked 本身已内置 GitHub 风格的表格解析,只需确保启用相关配置
  2. 数学公式支持:集成 katexmathjax 来渲染 LaTeX 格式的数学公式
  3. 代码高亮:添加 highlight.js 增强代码块的语法高亮效果
  4. 其他扩展:支持任务列表、脚注等常用 Markdown 扩展

完整修改后的代码

1. 安装新增依赖
安装代码高亮和数学公式渲染相关依赖

npm install marked-highlight highlight.js

2. 编写 app2.js(服务端增强 Markdown 解析)
// Web Server 程序const express =require('express');const marked =require('marked');const markedHighlight =require('marked-highlight');const hljs =require('highlight.js');const cors =require('cors');const bodyParser =require('body-parser');const fs =require('fs-extra');const path =require('path');// 初始化 Express 应用const app =express();// 配置静态文件目录 app.use(express.static(path.join(__dirname,'public')));// 配置 Marked 解析器,添加高亮、表格、公式等支持// 配置代码高亮const highlightOptions ={langPrefix:'hljs language-',highlight(code, lang){if(lang && hljs.getLanguage(lang)){try{return hljs.highlight(code,{language: lang }).value;}catch(err){ console.error(`高亮 ${lang} 代码失败:`, err);}}// 自动检测语言return hljs.highlightAuto(code).value;}};// 创建带高亮的 rendererconst renderer =newmarked.Renderer();// 重写代码块渲染逻辑,识别 mermaid 代码块 renderer.code=(code, language)=>{if(language ==='mermaid'){// 为 Mermaid 代码块生成容器(仅保留代码,渲染交给前端)return`<div>${code}</div>`;}// 其他代码块使用高亮渲染return`<pre><codetoken interpolation">${language}">${ highlightOptions.highlight(code, language)}</code></pre>`;};// 配置 marked 核心选项(启用所有扩展) marked.setOptions({renderer: renderer,extensions:[// 启用表格扩展(GFM 已包含,但显式声明更清晰){name:'table',level:'block',start:(src)=> src.match(/^\|/)?.index },// 启用任务列表{name:'tasklist',level:'block',start:(src)=> src.match(/^\s*-\s\[\s?x?\]\s/)?.index }],gfm:true,// 启用 GitHub 风格 Markdownbreaks:true,// 支持换行符tables:true,// 启用表格tasklists:true,// 启用任务列表footnotes:true,// 启用脚注smartypants:true// 启用智能标点});// 中间件配置 app.use(cors()); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({extended:true})); app.use(express.static(path.join(__dirname,'public')));// 设置模板引擎 app.set('view engine','ejs'); app.set('views', path.join(__dirname,'views'));// 确保保存文件的目录存在constDOCS_DIR= path.join(__dirname,'docs'); fs.ensureDirSync(DOCS_DIR);// 路由配置// 首页 - 编辑器界面 app.get('/',(req, res)=>{ res.render('editor',{title:'Markdown 在线编辑器 (支持表格/公式/Mermaid)'});});// 解析 Markdown 为 HTML (API) app.post('/api/parse',(req, res)=>{try{const{ markdown }= req.body;if(!markdown){return res.status(400).json({error:'Markdown 内容不能为空'});}// 解析 Markdown 为 HTMLconst html = marked.parse(markdown); res.json({ html });}catch(error){ res.status(500).json({error:'解析 Markdown 失败: '+ error.message });}});// 保存文档 (API) app.post('/api/save',(req, res)=>{try{const{ filename, content }= req.body;if(!filename ||!content){return res.status(400).json({error:'文件名和内容不能为空'});}// 拼接文件路径const filePath = path.join(DOCS_DIR,`${filename}.md`);// 写入文件 fs.writeFileSync(filePath, content,'utf8'); res.json({success:true,message:'文件保存成功', filePath });}catch(error){ res.status(500).json({error:'保存文件失败: '+ error.message });}});// 加载文档 (API) app.get('/api/load/:filename',(req, res)=>{try{const{ filename }= req.params;const filePath = path.join(DOCS_DIR,`${filename}.md`);// 检查文件是否存在if(!fs.existsSync(filePath)){return res.status(404).json({error:'文件不存在'});}// 读取文件内容const content = fs.readFileSync(filePath,'utf8'); res.json({success:true, content });}catch(error){ res.status(500).json({error:'加载文件失败: '+ error.message });}});// 获取文档列表 (API) app.get('/api/docs',(req, res)=>{try{// 读取目录下所有 .md 文件const files = fs.readdirSync(DOCS_DIR).filter(file=> path.extname(file)==='.md').map(file=>({name: path.basename(file,'.md'),path: file })); res.json({success:true,docs: files });}catch(error){ res.status(500).json({error:'获取文档列表失败: '+ error.message });}});// 启动服务器//const PORT = process.env.PORT || 8000;constPORT=8000; app.listen(PORT,()=>{ console.log(`服务器运行在: http://localhost:${PORT}`); console.log(`文档保存目录: ${DOCS_DIR}`);});
3. 编辑 views/editor.ejs(前端添加公式渲染和样式)
<!DOCTYPEhtml><htmllang="zh-CN"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title><%= title %></title><!-- 1. 引入 Mermaid --><scriptsrc="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script><!-- 2. 引入 highlight.js 核心脚本(关键修复) --><scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js"></script><!-- 3. 引入代码高亮样式 --><linkrel="stylesheet"href="https://cdn.jsdelivr.net/npm/highlight.js/styles/github-dark.min.css"><!-- 4. 引入 KaTeX 用于数学公式渲染 --><linkrel="stylesheet"href="https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.css"><scriptsrc="https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.js"></script><scriptsrc="https://cdn.jsdelivr.net/npm/[email protected]/dist/contrib/auto-render.min.js"></script><style>*{box-sizing: border-box;margin: 0;padding: 0;}body{font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;display: flex;flex-direction: column;height: 100vh;}.header{background: #2c3e50;color: white;padding: 1rem;text-align: center;}.container{display: flex;flex: 1;overflow: hidden;}.editor-container, .preview-container{flex: 1;padding: 1rem;overflow: hidden;border: 1px solid #ddd;}textarea{width: 100%;height: 100%;padding: 1rem;font-family:'Consolas','Monaco', monospace;font-size: 14px;border: none;outline: none;resize: none;}.preview{width: 100%;height: 100%;overflow-y: auto;padding: 1rem;background: #f9f9f9;line-height: 1.6;}.controls{padding: 1rem;background: #f1f1f1;display: flex;gap: 1rem;}button{padding: 0.5rem 1rem;background: #3498db;color: white;border: none;border-radius: 4px;cursor: pointer;}button:hover{background: #2980b9;}.file-input{padding: 0.5rem;}/* 表格样式优化 */table{border-collapse: collapse;width: 100%;margin: 1rem 0;}th, td{border: 1px solid #ddd;padding: 8px 12px;text-align: left;}th{background-color: #f2f2f2;font-weight: bold;}tr:nth-child(even){background-color: #f9f9f9;}/* 任务列表样式 */.task-list-item{list-style-type: none;margin: 0.5rem 0;}/* 脚注样式 */.footnote-ref{vertical-align: super;font-size: 0.8em;}.footnotes{margin-top: 2rem;border-top: 1px solid #ddd;padding-top: 1rem;font-size: 0.9em;}/* 代码块样式优化 */pre{padding: 1rem;border-radius: 6px;margin: 1rem 0;overflow-x: auto;}code{font-family:'Consolas','Monaco', monospace;}/* 公式样式 */.katex{font-size: 1.1em !important;}</style></head><body><divclass="header"><h1>Markdown 在线编辑器 (支持表格/公式/Mermaid)</h1></div><divclass="controls"><inputtype="text"id="filename"placeholder="输入文件名(无需.md)"value="demo"><buttonid="saveBtn">保存文档</button><buttonid="loadBtn">加载文档</button><buttonid="refreshBtn">刷新预览</button></div><divclass="container"><divclass="editor-container"><textareaid="editor"placeholder="请输入 Markdown 内容..."></textarea></div><divclass="preview-container"><divid="preview"class="preview"></div></div></div><script>// 初始化 Mermaid mermaid.initialize({startOnLoad:false,theme:'default'});// 获取 DOM 元素const editor = document.getElementById('editor');const preview = document.getElementById('preview');const saveBtn = document.getElementById('saveBtn');const loadBtn = document.getElementById('loadBtn');const refreshBtn = document.getElementById('refreshBtn');const filenameInput = document.getElementById('filename');// 解析 Markdown 并更新预览asyncfunctionupdatePreview(){try{const markdown = editor.value;const response =awaitfetch('/api/parse',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({ markdown })});const result =await response.json();if(result.error){ preview.innerHTML =`<div>解析错误: ${result.error}</div>`;return;}// 更新预览内容 preview.innerHTML = result.html;// 渲染 Mermaid 图表 mermaid.run({querySelector:'.mermaid',node: preview });// 渲染数学公式renderMathInElement(preview,{delimiters:[{left:'$$',right:'$$',display:true},// 块级公式{left:'$',right:'$',display:false},// 行内公式{left:'\\(',right:'\\)',display:false},// LaTeX 行内{left:'\\[',right:'\\]',display:true}// LaTeX 块级],throwOnError:false,strict:false});// 高亮所有代码块(兜底) document.querySelectorAll('pre code').forEach((block)=>{ hljs.highlightElement(block);});}catch(error){ preview.innerHTML =`<div>预览更新失败: ${error.message}</div>`;}}// 保存文档asyncfunctionsaveDocument(){const filename = filenameInput.value.trim();const content = editor.value;if(!filename){alert('请输入文件名');return;}try{const response =awaitfetch('/api/save',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({ filename, content })});const result =await response.json();if(result.error){alert('保存失败: '+ result.error);}else{alert('保存成功!');}}catch(error){alert('保存失败: '+ error.message);}}// 加载文档asyncfunctionloadDocument(){const filename = filenameInput.value.trim();if(!filename){alert('请输入要加载的文件名');return;}try{const response =awaitfetch(`/api/load/${filename}`);const result =await response.json();if(result.error){alert('加载失败: '+ result.error);}else{ editor.value = result.content;updatePreview();alert('加载成功!');}}catch(error){alert('加载失败: '+ error.message);}}// 绑定事件 refreshBtn.addEventListener('click', updatePreview); saveBtn.addEventListener('click', saveDocument); loadBtn.addEventListener('click', loadDocument);// 初始加载预览 window.addEventListener('load', updatePreview);// 可选:实时预览(取消注释启用)// editor.addEventListener('input', debounce(updatePreview, 300));// 防抖函数(实时预览时避免频繁请求)functiondebounce(func, wait){let timeout;returnfunction(...args){clearTimeout(timeout); timeout =setTimeout(()=>func.apply(this, args), wait);};}</script></body></html>
4. 编写 docs/demo.md
# Markdown 编辑器 (增强版) ## 1. 表格示例 | 姓名 | 年龄 | 职业 | |------|------|------| | 张三 | 25 | 工程师 | | 李四 | 30 | 设计师 | | 王五 | 28 | 产品经理 | ## 2. 数学公式示例 ### 行内公式 欧拉公式:$e^{i\pi} + 1 = 0$ ### 块级公式 $$ \int_{0}^{\infty} e^{-x^2} dx = \frac{\sqrt{\pi}}{2} $$ 二次方程求根公式: $$ x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a} $$ ## 3. 任务列表示例 - [x] 完成 Markdown 解析 - [x] 集成表格支持 - [ ] 优化公式渲染 - [ ] 测试兼容性 ## 4. 代码高亮示例 ```javascript // JavaScript 代码示例 function calculateArea(radius) { const PI = Math.PI; return PI * radius * radius; } console.log(calculateArea(5)); // 78.53981633974483 
# Python 代码示例deffibonacci(n): a, b =0,1for _ inrange(n):yield a a, b = b, a + b print(list(fibonacci(10)))

5. Mermaid 流程图示例

编辑

预览

开始

选择功能

Markdown 编辑

实时预览

支持表格/公式/代码高亮

保存文档

结束

## 6. 脚注示例 这是一个带脚注的文本[^1],还有另一个脚注[^2]。 [^1]: 第一个脚注的内容 [^2]: 第二个脚注的内容,支持多行 这是第二行内容 这是第三行(缩进显示) 

运行与使用说明

  1. 创建目录结构
 md-edit-app/ ├── app.js 和 app2.js // 服务器主程序 ├── views/ // 模板目录 │ └── edit.ejs 和 editor.ejs // 编辑器页面 ├── public/ // 静态资源目录 │ └── css 和 js // 静态子目录 └── docs/ // 文档保存目录 (自动创建) 

所有的 *.min.js , 可用 curl -O https://… 下载,都放在 public/js/
所有的 *.css , 可用 curl -O https://… 下载,都放在 public/css/

  1. 重启服务器:修改代码后,重启 Node.js 服务使配置生效
    node app2.js
  2. 功能测试
    • 表格:直接使用 GitHub 风格的表格语法即可渲染
    • 数学公式:使用 $公式$(行内)或 $$公式$$(块级)包裹 LaTeX 公式
    • 任务列表:使用 - [x](完成)或 - [ ](未完成)格式
    • 代码高亮:自动识别代码语言并高亮,支持上百种编程语言
    • 脚注:使用 [^标记] 定义脚注,文末添加 [^标记]: 内容

扩展更多功能的建议

  1. 图片上传:可以集成 multer 实现本地图片上传,或对接第三方图库
  2. 目录生成:解析 Markdown 标题,自动生成导航目录
  3. 导出功能:添加导出为 HTML/PDF 的功能(可使用 puppeteer
  4. 自定义样式:允许用户切换不同的预览主题(如浅色/深色模式)
  5. 自动保存:添加 localStorage 或定时保存功能,防止内容丢失

总结

  1. 核心扩展:通过启用 marked 的内置配置支持表格、任务列表、脚注;集成 katex 实现数学公式渲染;使用 highlight.js 增强代码高亮。
  2. 实现方式:服务端负责 Markdown 解析和扩展支持,前端负责公式、Mermaid 的渲染和样式美化。
  3. 使用要点:数学公式使用 LaTeX 语法,表格使用 GitHub 风格语法,所有扩展功能无需额外配置即可直接使用。

现在你的编辑器已经具备了完整的 Markdown 扩展能力,涵盖表格、数学公式、代码高亮、任务列表、脚注等常用功能,完全满足日常编辑需求。

Read more

Spring Boot 数据导入导出与报表生成

Spring Boot 数据导入导出与报表生成

Spring Boot 数据导入导出与报表生成 24.1 学习目标与重点提示 学习目标:掌握Spring Boot数据导入导出与报表生成的核心概念与使用方法,包括数据导入导出的定义与特点、Spring Boot与数据导入导出的集成、Spring Boot与数据导入导出的配置、Spring Boot与报表生成的基本方法、Spring Boot的实际应用场景,学会在实际开发中处理数据导入导出与报表生成问题。 重点:数据导入导出的定义与特点、Spring Boot与数据导入导出的集成、Spring Boot与数据导入导出的配置、Spring Boot与报表生成的基本方法、Spring Boot的实际应用场景。 24.2 数据导入导出概述 数据导入导出是Java开发中的重要组件。 24.2.1 数据导入导出的定义 定义:数据导入导出是指将数据从一个系统导入到另一个系统,或从一个系统导出到另一个系统的过程。 作用: * 实现数据的迁移。 * 实现数据的备份。 * 实现数据的共享。 常见的数据导入导出格式: * CSV:Comma-Separated Values,逗号分

By Ne0inhk
Django REST framework企业级API架构实战

Django REST framework企业级API架构实战

目录 摘要 1. 🎯 开篇:从踩坑到架构 2. 🏗️ 核心原理深度解析 2.1 DRF架构设计哲学 2.2 视图集:CRUD的终极抽象 2.3 序列化器:不只是数据转换 3. 🔧 实战:完整API实现 3.1 用户管理API 3.2 分页、过滤、排序 3.3 节流与限流 4. 🔥 高级实战:企业级API 4.1 缓存优化策略 4.2 性能监控中间件 4.3 API版本管理 5. 🚀 性能优化指南 5.1 数据库优化 5.

By Ne0inhk
Flutter for OpenHarmony: Flutter 三方库 redux_thunk 解决鸿蒙应用状态管理中的复杂异步副作用(异步架构神器)

Flutter for OpenHarmony: Flutter 三方库 redux_thunk 解决鸿蒙应用状态管理中的复杂异步副作用(异步架构神器)

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 在 OpenHarmony 应用架构设计中,状态管理(State Management)是业务的核心。如果你选择了经典的 Redux 模式,你会发现它天生是“同步”的:Action 发出,Reducer 改变 State。但在真实项目中,我们需要处理网络请求、数据库读写、文件 IO 等延时操作。如何在纯净的 Redux 链条中插入这些破坏性的“副作用”? redux_thunk 提供了一个简单而精妙的方案。它通过扩展 Redux 的中间件机制,允许你 Dispatch(派发)一个 函数 而不仅仅是对象。这为鸿蒙应用处理复杂的业务流提供了极大灵活性。 一、异步 Action

By Ne0inhk
【MYSQL】MYSQL学习的一大重点:MYSQL库的操作

【MYSQL】MYSQL学习的一大重点:MYSQL库的操作

🎬 个人主页:艾莉丝努力练剑 ❄专栏传送门:《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录》 《Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享》 ⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平 🎬 艾莉丝的简介: 文章目录 * 0 ~> 实际场景:创建和删除数据库 * 0.1 创建方式1 * 0.2 创建方式2 * 0.3 创建方式3 * 1 ~> 数据库的编码集 * 1.1 目前整个数据库支持的字符集 * 1.2 目前整个数据库支持的字符集 * 1.3 UTF-8需要设置配置文件 * 1.4 MySQL 中与字符集排序规则(

By Ne0inhk