You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现contenteditable/Markdown与EJS混合的半可视化模板编辑?

哈哈,这个需求我前段时间刚折腾过!要平衡可视化编辑和EJS模板的可编程性,确实不能直接用原生contenteditable或普通Markdown编辑器,给你几个亲测有效的解决方案:

1. 拆分编辑区:可视化内容 + 独立代码块

这是最稳妥的方案,完全避免两种内容互相污染。核心思路是把编辑器分成两个独立区域:

  • 可视化内容区:用成熟的Markdown编辑器(比如SimpleMDE)让用户写富文本内容,不用管HTML结构;
  • EJS代码区:用代码编辑器(比如CodeMirror)让用户写逻辑片段,支持语法高亮;

给用户加一个「插入EJS片段」的按钮,用户可以在Markdown内容里插入自定义占位符(比如{{ejs-1}}),最后后台把占位符替换成对应的EJS代码块,生成完整的模板。

举个例子:
用户在Markdown区写:

# 欢迎{{ejs-username}}
{{ejs-admin-note}}

在代码区写两个片段:
ejs-username:<%= user.name %>
ejs-admin-note:

<% if(user.isAdmin) { %>
> 管理员专属提示:请定期清理数据
<% } %>

后台合并后就是标准EJS:

<h1>欢迎<%= user.name %></h1>
<% if(user.isAdmin) { %>
<blockquote>管理员专属提示:请定期清理数据</blockquote>
<% } %>

2. 增强版contenteditable:实时过滤非法标签

如果一定要用单一的contenteditable编辑器,那必须监听输入事件,实时清理破坏EJS结构的自动生成标签(比如<div>)。

核心步骤:

  1. 让用户用特定标记包裹EJS代码(比如<% ... %>,这个是EJS原生语法,用户容易理解);
  2. 监听编辑器的input事件,每次输入后遍历DOM,把EJS代码段外面的冗余标签去掉;
  3. 处理光标位置,避免用户输入时光标跳走(这个是坑,一定要做)。

示例代码(简化版):

const editor = document.getElementById('template-editor');

// 保存光标位置的工具函数
function saveCursorPosition(el) {
  const selection = window.getSelection();
  const range = selection.getRangeAt(0);
  const preSelectionRange = range.cloneRange();
  preSelectionRange.selectNodeContents(el);
  preSelectionRange.setEnd(range.startContainer, range.startOffset);
  return preSelectionRange.toString().length;
}

// 恢复光标位置的工具函数
function restoreCursorPosition(el, pos) {
  const selection = window.getSelection();
  const range = document.createRange();
  range.setStart(el.firstChild, pos);
  range.collapse(true);
  selection.removeAllRanges();
  selection.addRange(range);
}

editor.addEventListener('input', () => {
  const cursorPos = saveCursorPosition(editor);
  const parser = new DOMParser();
  const doc = parser.parseFromString(editor.innerHTML, 'text/html');
  
  // 找出所有包含EJS代码的节点
  const ejsMatches = Array.from(doc.body.childNodes).reduce((acc, node) => {
    if (node.nodeType === Node.TEXT_NODE && /<%.*%>/.test(node.textContent)) {
      acc.push(node);
    } else if (node.nodeType === Node.ELEMENT_NODE) {
      const text = node.textContent;
      if (/<%.*%>/.test(text)) {
        acc.push(node);
      }
    }
    return acc;
  }, []);

  // 清理EJS代码外的冗余标签
  ejsMatches.forEach(node => {
    if (node.tagName === 'DIV' && /<%.*%>/.test(node.textContent)) {
      const textNode = document.createTextNode(node.textContent);
      node.replaceWith(textNode);
    }
  });

  editor.innerHTML = doc.body.innerHTML;
  restoreCursorPosition(editor, cursorPos);
});

3. 自定义简化语法 + 编译转换

如果不想让用户直接接触EJS的语法,可以设计一套更简单的可视化友好语法,用户用Markdown编辑器写内容时插入这些简化标签,最后后台编译成标准EJS。

比如自定义语法规则:

  • [[var]] → <%= var %>
  • [[if condition]] ... [[/if]] → <% if(condition) { %> ... <% } %>
  • [[each item in list]] ... [[/each]] → <% list.forEach(item => { %> ... <% }) %>

用户输入的内容:

# 欢迎[[username]]
[[if isAdmin]]
> 管理员你好,当前用户数:[[userCount]]
[[/if]]

后台用正则编译成EJS:

<h1>欢迎<%= username %></h1>
<% if(isAdmin) { %>
<blockquote>管理员你好,当前用户数:<%= userCount %></blockquote>
<% } %>

这个方案对用户门槛最低,完全不用懂EJS,而且不会被Markdown或contenteditable的标签干扰。

4. 用可定制的富文本编辑器:自定义节点类型

如果想要更流畅的混合编辑体验,可以用Slate.js这种高度可定制的富文本编辑器,自定义一个「EJS代码块」节点类型,让这个节点的内容不会被富文本的格式化逻辑影响。

具体做法:

  • 定义一个EjsBlock节点,渲染成一个带语法高亮的代码输入框;
  • 配置编辑器,当用户输入<%时自动切换到EJS代码节点;
  • 导出内容时,直接把EJS节点的内容和其他富文本内容拼接成标准EJS模板。

这种方案体验最好,但需要一定的前端开发成本,适合对编辑器体验要求高的场景。


总结一下:

  • 追求简单稳妥:选拆分编辑区方案;
  • 追求用户零门槛:选自定义简化语法方案;
  • 必须单一编辑器:选增强版contenteditable方案;
  • 追求极致体验:选Slate自定义节点方案。

内容的提问来源于stack exchange,提问作者Merc

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 07:29:44