Перейти к основному содержимому

Упоминания и теги

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);

Добавление командного меню в стиле slash

С помощью action можно создать командное меню в стиле slash (как / в Notion или Slack). Храните название команды в поле id каждого элемента, её параметры — в пользовательском поле config, и позвольте колбэку выполнять её через api.exec:

// каждый элемент хранит название действия api.exec в `id`, а его параметры — в `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" } // конфигурация отсутствует → применяется `|| {}`
];

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

Связанный пример: RichText. Slash-команды