跳到主要内容

提及与标签

RichText 支持用户自定义触发字符,在文档中输入后会弹出建议下拉菜单。用户从菜单中选择一项后,RichText 会将一个不可编辑的 token 插入文档。典型使用场景:

  • @ — 提及某人
  • # — 添加标签
  • / — 插入命令或模板
  • $ — 插入金融代码或变量
  • : — 插入表情符号

通过 triggers 属性配置相关行为。每个条目将一个字符绑定到一个数据源。

配置触发器

每个触发器是 triggers 数组中形如 { trigger, data, showTrigger?, action? } 的对象。data 字段支持三种形式:

  • 静态数组 — RichText 会自动按 label 进行过滤(不区分大小写,使用 startsWith):
{ trigger: "@", data: people }
  • 同步函数 — 由你自己处理结果过滤:
{
trigger: "#",
data: query => tags.filter(t =>
t.label.toLowerCase().startsWith(query.toLowerCase())
)
}
  • 异步函数 — 用于服务端搜索:
{
trigger: "+",
data: async query => {
const res = await fetch(`/api/users?q=${encodeURIComponent(query)}`);
const users = await res.json();
return users.map(u => ({
id: String(u.id),
label: u.name,
url: u.website
}));
}
}

相关示例: RichText. 提及、标签与异步查询

Token 渲染

用户从下拉菜单中选择一项后,RichText 会将其以带有两个 data 属性的 <a> 元素形式插入:

<a 
data-token="@"
data-token-id="alice"
href="mailto:alice@example.com">@Alice</a>

该 token 是一个不可编辑的单节点。按 Backspace 可一次性将其删除。RichText 将 url 字段存储在 href 中,因此 Ctrl+Click token 可跳转链接。

你可以使用 data-token 选择器为 token 设置样式:

.wx-editor-content a[data-token="@"][data-token-id="alice"] {
background: #fb8500;
color: #fff;
border-radius: 3px;
padding: 0 2px;
}

相关示例:

隐藏触发字符

在触发器上设置 showTrigger: false,可仅插入条目标签,而不显示触发符号:

{
trigger: "/",
data: commands,
showTrigger: false
}

键盘交互

在建议下拉菜单中可使用以下快捷键:

  • / — 在条目间移动
  • Enter — 插入当前选中的条目
  • Escape — 关闭下拉菜单且不插入内容

监听建议事件

三个事件通过 Event Bus 暴露下拉菜单的生命周期:

const editor = new richtext.Richtext("#root", {
triggers: [{ trigger: "@", data: people }]
});

editor.api.on("insert-token", ({ data, trigger, showTrigger }) => {
console.log(`Inserted ${trigger}${data.label} (id: ${data.id})`);
});

自定义下拉条目

默认情况下,下拉菜单显示每个条目的 label。如需渲染自定义建议项(例如头像、姓名和邮箱),可通过 triggerTemplate 属性传入模板。

示例

const { template } = richtext;

new richtext.Richtext("#root", {
triggers: [{ trigger: "@", data: people }],
triggerTemplate: template(({ data, trigger }) => `
<div className="user">
<div className="user-name">${trigger}${data.label}</div>
<div className="user-url">${data.url || ""}</div>
</div>
`)
});

相关示例: RichText. 每个触发器的自定义下拉模板

选择时执行自定义动作

默认情况下,选择条目会将其作为 token 插入文档。若要改为执行自己的代码,可在触发器中添加 action callback。RichText 会删除已输入的触发文本并以所选条目调用 action(item)——不插入 token,由你决定添加什么内容。

注意

action 优先于 showTrigger。设置 action 后,showTrigger 将被忽略。

添加表情符号

: 触发器可插入表情符号,每个条目包含自定义的 code 字段。将 actiontriggerTemplate 配合使用,使下拉菜单显示表情符号本身而非仅显示其标签:

const { template, Richtext } = richtext;

const emoji = [
{
id: "apple", label: "apple", code: "1F34E"
},
{
id: "blue_car", label: "blue_car", code: "1F699"
},
{
id: "computer", label: "computer", code: "1F4BB"
}
];

const editor = new Richtext("#root", {
triggers: [
{
trigger: ":",
data: emoji, // [{ id: "apple", label: "apple", code: "1F34E" }, ...]
action: item => editor.insertValue(`<span>${emojiFromCode(item.code)} </span>`)
}
],
// 在下拉菜单中渲染表情符号本身(而不仅仅是其标签)
triggerTemplate: template(({ data }) => `${emojiFromCode(data.code)} ${data.label}`)
});

function emojiFromCode(code) {
return String.fromCodePoint(parseInt(code, 16));
}

相关示例: RichText. Emoji 自动补全

按分类分组表情符号

data 参数为函数时,你不必局限于内置的 label 匹配逻辑,可以自行过滤并在下拉菜单中保留分类标题。添加包含 label 字段但不含 code 的标题条目。data 函数首先找到匹配查询的表情符号,然后将表情符号与仍有匹配项的分类标题一并返回:

const { template, Richtext } = richtext;

// 标题条目不含 `code` 字段;表情符号条目则包含
const emoji = [
{ id: "$smileys", label: "Smileys", category: 1 }, // 分类
{ id: "grinning", label: "grinning", code: "1F600", category: 1 },
{ id: "smile", label: "smile", code: "1F604", category: 1 },
{ id: "$animals", label: "Animals", category: 2 }, // 分类
{ id: "dog", label: "dog", code: "1F436", category: 2 },
{ id: "cat", label: "cat", code: "1F431", category: 2 }
];

const editor = new Richtext("#root", {
triggers: [
{
trigger: ":",
data: query => {
const matched = emoji.filter(item =>
item.code &&
item.label.toLowerCase().startsWith(query.toLowerCase().trim())
);
const categories = new Set(matched.map(item => item.category));
// 保留匹配的表情符号以及仍有匹配项的分类标题
return emoji.filter(item =>
item.code ? matched.includes(item) : categories.has(item.category)
);
},
action: item => editor.insertValue(`<span>${emojiFromCode(item.code)} </span>`)
}
],
// 正常渲染表情符号行,并以粗体渲染分类标题
triggerTemplate: template(({ data }) =>
data.code ? `${emojiFromCode(data.code)} ${data.label}` : `<b>${data.label}</b>`
)
});

function emojiFromCode(code) {
return String.fromCodePoint(parseInt(code, 16));
}

// 标题不含 `code` —— 忽略对它们的选择,使其永远不会被插入
editor.api.intercept("insert-token", ({ data }) => !!data.code);

添加斜杠命令菜单

你可以使用 action 构建斜杠命令菜单(类似 Notion 或 Slack 中的 /)。将命令名称存储在每个条目的 id 中,将其选项存储在自定义的 config 字段中,并让 callback 通过 api.exec 执行命令:

// 每个条目在 `id` 中存储一个 api.exec 操作名称,在 `config` 中存储其参数
const commands = [
{ id: "set-text-style", label: "Heading 1", config: { tag: "h1" } },
{ id: "insert-list", label: "Bulleted list", config: { type: "bulleted" } },
{ id: "insert-line", label: "Divider" } // 无 config → 应用 `|| {}`
];

const editor = new richtext.Richtext("#root", {
triggers: [
{
trigger: "/",
data: commands,
action: item => editor.api.exec(item.id, item.config || {})
}
]
});

相关示例: RichText. 斜杠命令