diff --git a/frontend/src/components/calendar/ConferenceCalendar.tsx b/frontend/src/components/calendar/ConferenceCalendar.tsx index 5504826..d761e87 100644 --- a/frontend/src/components/calendar/ConferenceCalendar.tsx +++ b/frontend/src/components/calendar/ConferenceCalendar.tsx @@ -98,6 +98,57 @@ function renderEventContent(arg: EventContentArg) { /** «г.» — сноска Intl.DateTimeFormat('ru-RU', ...) после года при формате year+month(+day); в подписи периода не нужна. */ const YEAR_SUFFIX_RE = /\s*г\.(?=\s|$)/g +/** + * Подписи дней в самой сетке, из которых надо убрать «г.» (см. + * `useStripYearSuffix`): месячный вид — номер дня в ячейке, недельный — + * подпись колонки дня. + */ +const DAY_LABEL_SELECTOR = '.fc-daygrid-day-number, .fc-col-header-cell-cushion' + +/** Убирает «г.» из `aria-label`/`title` элемента; ничего не пишет, если менять нечего. */ +function stripYearSuffixAttrs(el: Element) { + for (const attr of ['aria-label', 'title']) { + const value = el.getAttribute(attr) + if (!value) continue + const cleaned = value.replace(YEAR_SUFFIX_RE, '') + if (cleaned !== value) el.setAttribute(attr, cleaned) + } +} + +/** + * Убирает «г.» из служебных подписей дней внутри сетки FullCalendar. + * + * Кроме подписи периода (её формат мы задаём сами, см. `buildTitleFormatter`), + * дату дня FullCalendar подставляет ещё и в подпись ячейки — она видна в + * accessibility-дереве и как всплывающая подсказка. Формат этой подписи + * библиотека наружу НЕ отдаёт: `buildNavLinkAttrs` (@fullcalendar/core) + * форматирует дату жёстко зашитым `{year,month:'long',day:'numeric'}` и кладёт + * результат в `aria-label` (в `title` — когда включены navLinks), а с локалью + * `ru` Intl всегда даёт «20 июля 2026 г.». Настройкой это не убирается, + * поэтому правим уже отрендеренный атрибут. + * + * Почему MutationObserver, а не хуки `dayCellDidMount`/`dayHeaderDidMount`: + * при переключении недельного вида на месячный FullCalendar ПЕРЕИСПОЛЬЗУЕТ + * уже смонтированные ячейки — `didMount` для них повторно не зовётся, а + * подпись внутри перерисовывается заново, и «г.» возвращается (поймали на + * живой проверке: после переключения вида ровно строка переиспользованных + * ячеек оставалась с «г.»). Наблюдатель же ловит любую перерисовку. + * Зацикливания нет: `stripYearSuffixAttrs` пишет атрибут, только если он + * реально меняется, поэтому вызванная нашей же правкой итерация ничего не + * пишет и цепочка обрывается. + */ +function useStripYearSuffix(rootRef: React.RefObject) { + useEffect(() => { + const root = rootRef.current + if (!root) return + const clean = () => root.querySelectorAll(DAY_LABEL_SELECTOR).forEach(stripYearSuffixAttrs) + clean() + const observer = new MutationObserver(clean) + observer.observe(root, { subtree: true, childList: true, attributeFilter: ['aria-label', 'title'] }) + return () => observer.disconnect() + }, [rootRef]) +} + /** * Форма аргумента, который FullCalendar передаёт функции-`titleFormat` * (`VerboseFormattingArg` из @fullcalendar/core/internal-common — не @@ -152,6 +203,9 @@ export function ConferenceCalendar({ }: ConferenceCalendarProps) { const calendarRef = useRef(null) const titleRef = useRef(null) + const gridRef = useRef(null) + + useStripYearSuffix(gridRef) // См. комментарий у `latestOnEditClick` в начале файла — синхронизируем // переменную вместо того, чтобы замыкать пропс напрямую в renderEventContent. @@ -269,7 +323,7 @@ export function ConferenceCalendar({ -
+