Files
vesti/openspec/changes/web-render-markdown/design.md
T

2.3 KiB
Raw Blame History

Дизайн

1. Зависимость

/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) добавить:

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:

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

на:

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

И в 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 нет сырых **, [, ]( символов разметки (кроме намеренных).
  • Ввод <script>alert(1)</script> отображается как текст, не исполняется.
  • published.html — если там есть p.text/p.summary, применить тот же фильтр (grep).