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 渲染功能。

相关推荐
__zRainy__3 小时前
Node系列 · ORM:Sequelize 简介
数据库·后端·node.js
水獭比特4 小时前
工具都批准了,为什么还不能执行?给 Agent 补上第二道校验
javascript·人工智能·node.js
Simon_He4 小时前
一个渲染内核,五个框架包:我的流式 Markdown 渲染库是怎么长成“全家桶”的
前端·vue.js·markdown
__zRainy__8 小时前
Node系列 · ORM:mysql 驱动程序
数据库·后端·mysql·node.js·orm
__zRainy__9 小时前
Node系列 · ORM:Sequelize 模型
数据库·后端·node.js
秋秋小事10 小时前
提示词工程
node.js
__zRainy__11 小时前
Node系列 · 数据库:MySQL 安装
数据库·后端·mysql·node.js
EricStone12 小时前
Agent开发学习一:Hello-Agents TypeScript 全栈实现
typescript·node.js·agent
嘟嘟071712 小时前
一个微信公众号 Markdown 渲染组件,如何用 SDD 把需求、架构、任务一次写清
react.js·markdown
必须会一定会13 小时前
Agent Handoff M5 发布验收:`CHANGES.md`、`npm pack`、`release:check` 与干净环境安装验证
前端·人工智能·npm·node.js·ai编程