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

开发一个基于 Node.js 和 mermaid.js 的 Web 服务器程序,实现在线编辑 Markdown 文档并支持 mermaid.js 流程图渲染的功能。我会为你搭建一个完整、可直接运行的服务端程序,包含核心的 Web 服务、Markdown 解析和 Mermaid 渲染能力。

实现思路

  1. 使用 Express 作为基础 Web 框架搭建服务器
  2. 使用 marked 解析 Markdown 内容,并配置 Mermaid 扩展
  3. 使用 mermaid 实现流程图的渲染
  4. 提供一个简洁的前端编辑界面,支持实时预览
  5. 实现基本的文件保存/加载功能(本地文件存储)

完整实现代码

1. 项目初始化与依赖安装

首先创建项目并安装所需依赖:

创建项目目录

mkdir md-edit-app

cd md-edit-app

初始化 npm 项目

npm init -y

安装核心依赖

npm install express@4 marked@12

npm install cors body-parser fs-extra ejs

我用的 node.js 版本: 18.20 , 搭配的版本是

npm install express@4.18.3 marked@12.0.2

npm install cors@2.8.5 body-parser@1.20.2 fs-extra@11.2.0 ejs@3.1.9

2. 服务器主程序 (app.js)
javascript 复制代码
const express = require('express');
const marked = require('marked');
// 移除 const mermaid = require('mermaid');
const cors = require('cors');
const bodyParser = require('body-parser');
const fs = require('fs-extra');
const path = require('path');

// 初始化 Express 应用
const app = express();

// 移除 mermaid 初始化代码 ↓
// mermaid.initialize({
//   startOnLoad: false,
//   theme: 'default',
//   securityLevel: 'loose' // 允许更多的渲染选项
// });

// 配置 Marked 解析器,添加 Mermaid 支持
const renderer = new marked.Renderer();

// 重写代码块渲染逻辑,识别 mermaid 代码块
renderer.code = (code, language) => {
  if (language === 'mermaid') {
    // 为 Mermaid 代码块生成容器(仅保留代码,渲染交给前端)
    return `<div class="mermaid">${code}</div>`;
  }
  // 其他代码块使用默认渲染
  return `<pre><code class="language-${language}">${code}</code></pre>`;
};

// 配置 marked
marked.setOptions({
  renderer: renderer,
  highlight: true,
  breaks: true,
  gfm: true // 启用 GitHub 风格的 Markdown
});

// 中间件配置
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'));

// 确保保存文件的目录存在
const DOCS_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 为 HTML
    const 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 = 8000;
app.listen(PORT, () => {
  console.log(`服务器运行在: http://localhost:${PORT}`);
  console.log(`文档保存目录: ${DOCS_DIR}`);
});
3. 前端编辑器页面 (views/editor.ejs)
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title><%= title %></title>
  <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.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;
    }
    .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;
    }
    pre {
      background: #f4f4f4;
      padding: 1rem;
      border-radius: 4px;
      overflow-x: auto;
    }
    code {
      font-family: 'Consolas', 'Monaco', monospace;
    }
  </style>
</head>
<body>
  <div class="header">
    <h1>Markdown 在线编辑器 (支持 Mermaid 流程图)</h1>
  </div>
  
  <div class="controls">
    <input type="text" id="filename" placeholder="输入文件名(无需.md)" value="demo">
    <button id="saveBtn">保存文档</button>
    <button id="loadBtn">加载文档</button>
    <button id="refreshBtn">刷新预览</button>
  </div>
  
  <div class="container">
    <div class="editor-container">
      <textarea id="editor" placeholder="请输入 Markdown 内容...">
## Markdown 编辑器 (支持 Mermaid)

### 功能说明
- 支持标准 Markdown 语法
- 支持 Mermaid 流程图渲染
- 支持文档保存/加载

### Mermaid 示例
```mermaid
graph TD
    A[开始] --> B{选择操作}
    B -->|编辑| C[Markdown 编辑]
    B -->|预览| D[实时预览]
    C --> E[保存文档]
    D --> E
    E --> F[结束]

普通代码示例

javascript 复制代码
  console.log('Hello, Markdown!');
复制代码
  </textarea>
</div>
<div class="preview-container">
  <div id="preview" class="preview"></div>
</div>

```

运行与使用说明

  1. 创建目录结构

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

  2. 启动服务器

bash 复制代码
 node app.js
  1. 访问编辑器

    打开浏览器访问 http://localhost:8000 即可看到 Markdown 编辑器界面

  2. 功能使用

    • 在左侧编辑框输入 Markdown 内容(包含 Mermaid 代码块)
    • 点击「刷新预览」查看右侧渲染效果
    • 输入文件名后,点击「保存文档」可将内容保存到服务器
    • 输入已保存的文件名,点击「加载文档」可加载保存的内容

总结

  1. 核心功能:该程序基于 Express 搭建 Web 服务器,使用 marked 解析 Markdown,集成 mermaid.js 实现流程图渲染,提供了完整的在线编辑、预览、保存、加载功能。
  2. 技术栈:Node.js + Express + marked (Markdown解析) + mermaid.js (流程图渲染) + EJS (模板引擎)。
  3. 扩展建议:可进一步添加用户认证、多人协作、云存储、更多 Markdown 扩展(如表格、公式)等功能,也可优化前端界面使其更美观易用。

这个程序开箱即用,你可以直接复制代码运行,体验完整的 Markdown 在线编辑和 Mermaid 渲染功能。

相关推荐
前端双越老师9 小时前
Skills 是什么?如何用于 Agent 开发?
人工智能·node.js·agent
San301 天前
AI 时代的“USB-C”接口:MCP 核心原理与实战
langchain·node.js·mcp
helloweilei2 天前
javascript 结构化克隆
javascript·node.js
小蜜蜂dry3 天前
nestjs学习 - 控制器、提供者、模块
前端·node.js·nestjs
San303 天前
手写 Mini Cursor:基于 Node.js 与 LangChain 的开发实战
langchain·node.js·agent
子玖3 天前
让你的文章里俏皮的添加emoji
程序员·markdown·visual studio code
前端付豪3 天前
Nest 项目小实践之图书增删改查
前端·node.js·nestjs
sunny_4 天前
面试踩大坑!同一段 Node.js 代码,CJS 和 ESM 的执行顺序居然是反的?!99% 的人都答错了
前端·面试·node.js
用户962377954484 天前
🚀 docx2md-picgo:Word 文档图片一键上传图床工具
python·markdown
Qinana4 天前
150行代码搞定私有知识库!Node.js + LangChain 打造最小化 RAG 系统全流程
人工智能·程序员·node.js