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

无人机视觉导航:DAMO-YOLO TinyNAS实时障碍物检测

无人机视觉导航:DAMO-YOLO TinyNAS实时障碍物检测 1. 引言 无人机在自主飞行时最头疼的问题是什么?不是续航,不是信号,而是那些突然出现的障碍物。树木、电线、建筑物,甚至是其他飞行器,都可能让一次完美的飞行任务瞬间变成"炸机现场"。 传统的避障方案要么依赖昂贵的激光雷达,要么使用计算复杂的视觉算法,在资源有限的无人机平台上往往力不从心。直到我们遇到了DAMO-YOLO TinyNAS——这个专门为边缘设备优化的目标检测模型,让无人机真正拥有了"火眼金睛"。 我们在Jetson Xavier平台上进行了实测,这套方案能够稳定达到30FPS的检测速度,完全满足实时避障的需求。更重要的是,它的功耗控制得相当不错,不会让无人机的续航时间大打折扣。 2. DAMO-YOLO TinyNAS技术解析 2.1 什么是TinyNAS技术 TinyNAS不是简单的模型压缩或剪枝,而是一种从网络结构源头优化的神经网络架构搜索技术。简单来说,它就像是为你的硬件量身定制衣服,而不是买现成的成衣再去修改。 传统的做法是先训练一个大模型,然后通过各种技巧把它变小。而TinyNA

VLA机器人革命:解析当下10篇最关键的视觉-语言-动作模型论文

VLA机器人革命:解析当下10篇最关键的视觉-语言-动作模型论文

VLA机器人革命:解析当下10篇最关键的视觉-语言-动作模型论文 概览 2024-2026年,机器人领域正经历一场范式转换:从传统的任务特定编程转向视觉-语言-动作(Vision-Language-Action, VLA)模型。这些模型将视觉感知、自然语言理解和动作执行统一在单一框架中,让机器人能够像人类一样理解指令、推理场景并执行复杂操作。 本文精选5篇最fundamental的基础性论文和5篇热度最高的前沿论文,深入剖析VLA领域的核心思想、技术演进和未来方向。这些论文代表了从Google DeepMind、NVIDIA、斯坦福、Physical Intelligence等顶尖机构的最新突破,涵盖了从单臂操作到双臂人形机器人、从模拟环境到真实家庭场景的全方位进展。 Part I: 五篇Fundamental基础性论文 这些论文奠定了VLA领域的理论基础和技术范式,是理解整个领域发展脉络的关键。 1. RT-2: New Model Translates Vision and Language into Action 发表机构:Google DeepMind 时间:

一、FPGA到底是什么???(一篇文章让你明明白白)

一句话概括 FPGA(现场可编程门阵列) 是一块可以通过编程来“变成”特定功能数字电路的芯片。它不像CPU或GPU那样有固定的硬件结构,而是可以根据你的需求,被配置成处理器、通信接口、控制器,甚至是整个片上系统。 一个生动的比喻:乐高积木 vs. 成品玩具 * CPU(中央处理器):就像一个工厂里生产好的玩具机器人。它的功能是固定的,你只能通过软件(比如按不同的按钮)来指挥它做预设好的动作(走路、跳舞),但你无法改变它的机械结构。 * ASIC(专用集成电路):就像一个为某个特定任务(比如只会翻跟头)而专门设计和铸造的金属模型。性能极好,成本低(量产时),但一旦制造出来,功能就永远无法改变。 * FPGA:就像一盒万能乐高积木。它提供了大量基本的逻辑单元(逻辑门、触发器)、连线和接口模块。你可以通过“编程”(相当于按照图纸搭建乐高)将这些基本模块连接起来,构建出你想要的任何数字系统——可以今天搭成一个CPU,明天拆了重新搭成一个音乐播放器。 “现场可编程”