feat(client): сноски внизу страницы — рендер без сдвига (текст к левому полю) и уменьшенным шрифтом #420
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?
Проблема
Блок сносок внизу страницы (секция
FOOTNOTES) рендерится с двумя визуальными недостатками:N.живёт в отдельной узкой колонке слева, а текст (и все перенесённые строки) выровнены по правому краю этой колонки. Из-за этого сноски «уезжают» вправо относительно заголовкаFOOTNOTESи основного текста.--mantine-font-size-md, 16px), хотя это вспомогательный аппарат и должен быть мельче.Нужно: рендерить сноски без сдвига (номер и текст, включая перенесённые строки, прижаты к левому полю) и шрифтом меньше основного.
Причина (текущая реализация)
Рендер живёт в клиенте, в NodeView сноски-определения и его CSS-модуле:
.definition,.definitionMarker,.definitionContent..definitionMarkerрендерится отдельным<span>).Сейчас
.definition— это флекс-строка[content][marker][backlink], где маркер визуально выносится влево черезorder: -1, а его колонка задаёт отступ:Итого текст начинается на ~
1.5em + 0.4em ≈ 1.9emот левого поля, и перенесённые строки висят на том же отступе. Размер шрифта наследуется от.ProseMirror { font-size: var(--mantine-font-size-md) }— как у тела.Почему так сделано (важный контекст, ломать нельзя):
NodeViewContentобязан быть первым дочерним элементом в DOM — иначе браузерное hit-testing клика (posAtCoords) промахивается мимоcontentDOMи ставит каретку выше, в тело. Поэтому контент идёт первым, а маркер/бэклинк — после него и раскладываются влево/вправо через CSSorder. Инвариант проверяется тестом footnote-views.structure.test.tsx.↩ a b c— эту логику трогать не нужно, но шрифт/выравнивание должны остаться корректными.Желаемое поведение
N.и текст прижаты к тому же левому полю, что заголовокFOOTNOTESи основной текст.↩/↩ a b cостаются на месте и кликабельны.Предлагаемое решение (рекомендуется)
Убрать отдельную колонку-маркер и рендерить номер как инлайн-контент в начале первой строки текста через CSS
::before(тот же приём уже используется для маркера-ссылки:.reference::after { content: var(--footnote-number) }). Номер декоративный и берётся из CSS-переменной--footnote-number, которая уже проставляется на обёртке.definitionинлайн-стилем — то есть в модель документа он не попадает и на экспорт/маркдаун не влияет.Конкретно:
.definitionзадать меньший кегль:font-size: var(--mantine-font-size-sm)(14px против 16px тела — совпадает с кеглем.listHeading). Оставитьdisplay: flexтолько для пары[content][backlink], зазор уменьшить..definitionMarker(колонка-маркер больше не нужна)..definitionContentостаётсяflex: 1 1 autoво всю ширину (начинается от левого поля => сдвига нет). Правило.definitionContent > :first-child { margin-top: 0 }сохранить.<span className={classes.definitionMarker}>{number}.</span>(номер теперь из::before). Переменную--footnote-numberна обёртке.definitionоставить — из неё берётся значение.↩/↩ a b c(#168) не трогать.Почему так, а не иначе: генерируемый
::beforeвиден визуально перед контентом, при этом реальныйcontentDOMостаётся первым в DOM — инвариант #146 сохраняется автоматически, а костыль сorder: -1на маркере уходит совсем (структура даже упрощается).Более лёгкий fallback (если
::beforeвнутри редактируемого контента даст проблемы с кареткой)Оставить
<span>-маркер, но:font-size: var(--mantine-font-size-sm)на.definitionи.definitionMarker { min-width: 0 }+ минимальныйgap. Это уменьшает сдвиг, но не убирает hanging indent для многострочных сносок (переносы всё равно висят на ширине номера). Применять только если основной вариант ломает редактирование.Ограничения и краевые случаи (проверить при реализации)
::beforeпроверить, что клик в начало текста сноски и постановка каретки работают, ничего не «прыгает» в тело. Прогнать footnote-views.structure.test.tsx — инвариант «contentDOM первый, вся нередактируемая обвязка после него» должен сохраниться (бэклинк↩остаётсяcontenteditable=false, так что проверкаnonEditable.length > 0не падает).↩ a b c— проверить выравнивание и что мельче стал и текст, и буквенные бэклинки (.backLink/.backLinkArrowзаданы в относительныхem— унаследуют новый кегль).font-variant-numeric: tabular-nums.var(--mantine-color-dimmed)— уже корректен в light/dark.::beforeне входит в модель документа и в маркдаун-экспорт (номер и так вычисляемый, не хранится) — регрессий экспорта быть не должно; допустимо, что при выделении текста мышью в номерN.попадёт в буфер (косметика)..popoverBody) ужеsm— не трогаем, консистентно.Критерии приёмки (DoD)
FOOTNOTES); визуального сдвига текста вправо нет.sm).↩/↩ a b cработают и корректно выровнены; постановка каретки и редактирование текста сноски не сломаны (#146).