## Дизайн ### 1. Зависимость ```bash /opt/vesti/.venv/bin/pip install "Markdown>=3.6" echo "Markdown>=3.6" >> /opt/vesti/requirements.txt ``` ### 2. Фильтр в web/app.py Рядом с существующим `tpl.filters["from_json"]` (строка 29) добавить: ```python import markdown as md_lib from markupsafe import Markup def md_filter(text: str) -> Markup: if not text: return Markup("") # 1) экранируем HTML (защита от XSS), 2) рендерим markdown, 3) переносы строк import markupsafe safe = markupsafe.escape(text) html = md_lib.markdown(safe, extensions=["nl2br", "sane_lists"]) return Markup(html) tpl.filters["markdown"] = md_filter ``` (В Jinja2 по умолчанию `Markup` не экранируется повторно; `markupsafe` уже идёт с Jinja2.) ### 3. Шаблон candidates.html Заменить строку 41: ```jinja
{{ (p.text or '')[:500] }}
``` на: ```jinja
{{ (p.text or '')[:2000] | markdown }}
``` И в `base.html` для `.post-text` оставить `white-space: pre-wrap` (после nl2br переносы строк уже есть, но pre-wrap не помешает) — или сменить на `line-height: 1.5`. ### 4. Безопасность - `markupsafe.escape` до markdown-парсера — ссылки `[x](javascript:...)` должны быть заблокированы. Python-Markdown сам экранирует опасные протоколы в ссылках, но предварительное экранирование — обязательный слой. - Не использовать `|safe` без `Markup`. ### 5. Проверка - Пост с текстом `**жирный** [ссылка](https://x) - пункт` рендерится жирным/ссылкой/списком. - В HTML нет сырых `**`, `[`, `](` символов разметки (кроме намеренных). - Ввод `` отображается как текст, не исполняется. - `published.html` — если там есть `p.text`/`p.summary`, применить тот же фильтр (grep).