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

小白也能用!Hunyuan-MT-7B-WEBUI零基础翻译部署教程

小白也能用!Hunyuan-MT-7B-WEBUI零基础翻译部署教程 你是不是也遇到过这些情况: 想把一篇维吾尔语的政策文件快速转成中文,却卡在模型下载失败; 看到别人用AI翻译出流畅自然的西语新闻,自己照着GitHub文档配环境配了三天还报错“CUDA out of memory”; 听说有个叫“混元MT”的翻译模型很强,点开项目页第一行就是“需熟悉PyTorch、HuggingFace、Docker”,默默关掉了网页…… 别急——这次真不用懂代码,不用装依赖,不用查报错。 Hunyuan-MT-7B-WEBUI 镜像,就是专为“不会部署”的人设计的。 它把腾讯开源的最强民汉翻译模型(支持日法西葡维吾尔等38种语言互译),打包成一个“点开即用”的网页工具。你只需要三步:启动镜像 → 点个脚本 → 打开浏览器,就能开始翻译。 本文不讲原理、不列公式、不堆参数,只说你真正需要的操作步骤。全程用大白话,配真实截图逻辑(文字描述版),连Linux命令都给你写全了。哪怕你第一次听说“GPU”“Docker”“端口”

Mac上运行DeepSeek-OCR的完整方案|基于DeepSeek-OCR-WEBUI镜像轻松部署

Mac上运行DeepSeek-OCR的完整方案|基于DeepSeek-OCR-WEBUI镜像轻松部署 你是不是也遇到过这种情况:看到 DeepSeek-OCR 这个强大的开源OCR模型火了,想在自己的Mac上试试,结果发现官方只提供了基于CUDA和Linux的推理脚本?一通折腾后才发现根本跑不起来。别急,这不是你的问题,而是当前很多大模型默认“为NVIDIA显卡而生”的现实写照。 但好消息是——现在你完全可以在Mac上本地运行 DeepSeek-OCR,而且不需要懂太多技术细节。本文将带你通过 DeepSeek-OCR-WEBUI 镜像,实现一键部署、开箱即用的OCR体验。无论你是M1/M2/M3芯片的Apple Silicon用户,还是Intel处理器的老款Mac,都能顺利运行。 整个过程只需三步:拉取镜像 → 启动服务 → 浏览器访问。无需手动配置环境、不用修改代码、不碰命令行难题。尤其适合希望快速验证效果、保护数据隐私、或用于文档数字化、票据识别等实际场景的用户。 1. 为什么要在Mac上运行DeepSeek-OCR? 1.1 OCR的实际价值不容忽视 光学字符识别

Nunchaku-FLUX.1-devWebUI高级功能:图像重绘/局部重绘/图生图扩展能力

Nunchaku-FLUX.1-dev WebUI高级功能:图像重绘/局部重绘/图生图扩展能力 1. 从文生图到创意编辑:解锁WebUI的进阶玩法 如果你已经用Nunchaku-FLUX.1-dev玩过基础的文生图,看着那些根据文字描述生成的精美图片,可能会想:能不能在现有图片上做点修改?比如给照片换个背景、给人物换个发型,或者只修改图片的某个局部? 好消息是,Nunchaku-FLUX.1-dev的WebUI不只是个简单的文生图工具。它内置了强大的图像编辑能力,让你能像专业设计师一样,对图片进行各种创意修改。今天我就带你深入探索这些高级功能,看看如何用它们解决实际创作中的难题。 想象一下这些场景: * 你生成了一张不错的风景图,但天空部分不太满意,想换成晚霞 * 电商产品图需要换个背景,让商品更突出 * 人物肖像的某个细节需要调整,比如眼睛颜色或衣服款式 * 想把一张普通照片转换成特定艺术风格 这些需求,用传统的图片编辑软件可能需要复杂的操作,但用Nunchaku-FLUX.1-dev的WebUI,几个简单的步骤就能搞定。下面我就带你一步步掌握这些进阶技巧。

前端SSE(Server-Sent Events)实现详解:从原理到前端AI对话应用

一、什么是SSE? SSE(Server-Sent Events)是一种服务器向客户端推送数据的技术,它允许服务器主动向客户端发送数据,而不需要客户端频繁轮询。SSE特别适合实时通信场景,比如AI聊天的流式输出、实时通知、股票行情更新等。 SSE的核心特点: * 单向通信 :服务器向客户端单向推送数据 * 基于HTTP :使用标准的HTTP协议,不需要特殊的服务器支持 * 自动重连 :连接断开时会自动尝试重连 * 文本格式 :使用简单的文本格式传输数据 * 轻量级 :实现简单,开销小 二、SSE的工作原理 1. 连接建立 客户端通过向服务器发送一个HTTP请求来建立SSE连接。服务器返回一个特殊的响应,设置 Content-Type: text/event-stream 头,告诉客户端这是一个SSE流。 2. 数据传输 服务器以流的形式持续发送数据,每个数据块都是一个SSE格式的消息。SSE消息格式如下: data: 消息内容\n\n 其中: * data: 是固定前缀 * 消息内容可以是任意文本,