Files
vesti/openspec/changes/archive/2026-09-13-web-render-markdown/proposal.md
T

2.4 KiB
Raw Blame History

Why

В списке кандидатов (web/templates/candidates.html:41) текст поста выводится как есть:

<div class="post-text mt-2">{{ (p.text or '')[:500] }}</div>

Jinja2 экранирует HTML-сущности ({{ }} — автоэскейп), но не парсит markdown: жирный текст, ссылки, списки, заголовки в исходных постах (телеграм-посты с markdown-разметкой) отображаются сырыми символами **, [text](url), - item. Пользователь видит «сырой markdown, а не красивый».

What Changes

  • Рендерить текст поста из markdown в HTML перед выводом в списке кандидатов.
  • Добавить Jinja2-фильтр markdown (или md): {{ (p.text or '')[:2000] | markdown }}.
  • Использовать локальную Python-библиотеку (не JS/CDN!): markdown (Python-Markdown) — уже покрывает жирный/курсив/ссылки/списки/заголовки. Безопасный вывод: экранирование HTML-тегов в исходном тексте (вход — непроверенный текст из TG), nl2br/pre-обёртка для переносов строк.
  • Только серверный рендер, без внешних JS-библиотек (в духе deexternalize-web-assets).
  • Превратить .post-text в блок с классом post-text и white-space нормальным (не pre-wrap над сырым md) или оставить, но уже с HTML.

Impact

  • Файлы: web/app.py (зарегистрировать фильтр), web/templates/candidates.html (заменить вывод), возможно published.html (если там тоже текст).
  • Зависимость: добавить Markdown>=3.6 в requirements.txt (pip, локально).
  • Безопасность: важно экранировать HTML до передачи в markdown-парсер (иначе XSS из telegram-постов).
  • Минимальная правка; поведение страниц не меняется, кроме вида текста.
  • Rollback: вернуть {{ (p.text or '')[:500] }}, убрать фильтр.