Упоминания и теги
RichText поддерживает пользовательские символы-триггеры, которые открывают выпадающий список подсказок внутри документа. Когда пользователь выбирает элемент, RichText вставляет в документ неизменяемый токен. Типичные варианты использования:
@— упомянуть человека#— применить тег/— вставить команду или шаблон$— вставить финансовый тикер или переменную:— вставить эмодзи
Настройте поведение через свойство triggers. Каждая запись связывает один символ с источником данных.
Настройка триггеров
Каждый триггер — это объект { trigger, data, showTrigger?, action? } внутри массива triggers. Поле 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. Упоминания, теги и асинхронный поиск
Отображение токена
Когда пользователь выбирает элемент из выпадающего списка, RichText вставляет его как элемент <a> с двумя атрибутами данных:
<a
data-token="@"
data-token-id="alice"
href="mailto:alice@example.com">@Alice</a>
Токен является единым неизменяемым узлом. Клавиша Backspace удаляет его за один шаг. RichText сохраняет поле url в атрибуте href, поэтому Ctrl+Click по токену открывает ссылку.
Вы можете стилизовать токены с помощью селектора data-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— закрытие списка без вставки
Подписка на события подсказок
Три события предоставляют доступ к жизненному циклу выпадающего списка через шину событий:
insert-token— срабатывает, когда пользователь выбирает элементshow-suggest— срабатывает при открытии выпадающего спискаhide-suggest— срабатывает при закрытии выпадающего списка
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 каждого элемента. Чтобы отрисовать пользовательские подсказки (например, аватар, имя и email), передайте шаблон через свойство 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. Пользовательский шаблон выпадающего списка для каждого триггера
Пользовательское действие при выборе
По умолчанию выбор элемента вставляет его в документ как токен. Чтобы выполнить собственный код вместо этого, добавьте колбэк action к триггеру. RichText удаляет введённый символ триггера и вызывает action(item) с выбранным элементом — токен не вставляется, и вы сами решаете, что добавить.
action имеет приоритет над showTrigger. Если задан action, значение showTrigger игнорируется.
Добавление эмодзи
Триггер : может вставлять эмодзи, где каждый элемент содержит пользовательское поле code. Используйте action вместе с triggerTemplate, чтобы выпадающий список отображал эмодзи, а не просто его метку:
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. Автодополнение эмодзи
Группировка эмодзи по категориям
Когда параметр 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);