[feature][editor] Кнопка «Ударение» в bubble-меню: знак ударения (U+0301) на выделенную букву #270
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Добавить в всплывающее меню редактора (bubble menu) кнопку «Ударение»: пользователь выделяет гласную букву и одним кликом ставит над ней знак ударения. Повторный клик — снимает ударение (toggle).
Реализуется как вставка комбинируемого диакритического знака U+0301 (COMBINING ACUTE ACCENT) сразу после выделенной буквы. Это «честный» Unicode-символ, а НЕ кастомная TipTap-метка: он остаётся в тексте документа, в экспортируемых HTML/Markdown, в индексе полнотекстового поиска и в публичном шаре — без каких-либо изменений на сервере и в конвертерах.
Поиск по
stress|ударени|u0301|accent|combiningвapps/иpackages/пуст — функции сейчас нет.Где:
apps/client/src/features/editor/components/bubble-menu/bubble-menu.tsx— это тот самый toolbar со скриншота (Text → выравнивание → B/I/U/S → код → спойлер → очистка → цвет → ссылка → комментарий).Принятые решения
editor-ext, регистрация вcollaboration.util.ts, turndown-правила экспорта, CSS. Markdown/HTML round-trip «бесплатный» — это обычный текст.tr.insertTextприменяет$from.marks()), поэтому остаётся в одном текстовом узле с буквой и корректно рисуется поверх неё, даже если буква bold/italic/цветная.IconGrave— это надгробие, а не диакритика. Делаем маленький компонент с тем же API, что у Tabler-иконок ({ style, stroke }).readonly-bubble-menu.tsxкнопку не добавляем (это действие правки).Архитектура / реализация
Все изменения — клиентские: один файл редактора + переводы. Сервер и конвертеры не трогаем.
1.
bubble-menu.tsx— иконка, состояние, пункт менюКастомная иконка (локальный мини-компонент):
Расширить
useEditorStateselector — флаг наличия ударения у выделенной буквы (для подсветкиisActive):Новый пункт в массив
items(логично между «Spoiler» и «Clear formatting»):2. i18n
Ключ
"Stress": en-US →"Stress", ru-RU →"Ударение"(apps/client/public/locales/*/translation.json, рядом сBold/Italic/Spoiler). Остальные локали — по желанию, иначе фолбэк на английский ключ.Тонкости и edge-cases
Math.min(to+1, content.size)+textBetweenчерез границу блока вернёт""→ совпадения нет → просто вставка (безвредно)..focus()→ Ctrl+Z снимает за один шаг (ср. с багом поте��и фокуса #269 — здесь фокус не теряем).insertTextне подхватит марки:tr.replaceWith(to, to, state.schema.text(STRESS, state.doc.resolve(to).marks())).Definition of Done
isActiveподсвечивает состояние.Out of scope
inline-marks-group.tsx) — опциональный follow-up для консистентности.