- Paste-to-embed: pasting an image into the markdown editor uploads it and
inserts  at the cursor. Unlike gallery attachments these aren't
tied to a journal_entry (the entry may not exist yet while composing), so
they're stored per-user under app/media/journal/{user_id}/pasted/ with no
DB row, served through an ownership-scoped route, and never cleaned up
automatically when an entry is deleted -- an accepted tradeoff at this
app's personal scale.
- The markdown sanitizer was stripping all <img> tags (not on the bleach
allowlist), which would have silently deleted every pasted image on save;
added img/src/alt/title while keeping event-handler attributes blocked.
- Cap embedded image width in both the editor pane and the rendered preview
so a large pasted photo can't overflow its card.
- Fix real data loss risk found while testing this: docker-compose.yml had
no volume for app/media, so every container recreate during a deploy wiped
uploaded photos, and deploy_sftp.py was syncing app/media/ (runtime user
data, not source) into the remote build context. Added the volume mount
and excluded media/ from the sync script. Recovered and relocated the
real attachments that had already landed in the wrong place on the NAS
during earlier deploys this session.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
24 lines
1.1 KiB
Python
24 lines
1.1 KiB
Python
import bleach
|
|
import markdown
|
|
from markupsafe import Markup
|
|
|
|
# nl2br: 빈 줄 없이 그냥 엔터만 쳐도 줄바꿈되게 한다 — 지금까지 백엔드가 순수 텍스트를
|
|
# white-space: pre-wrap으로 보여주던 것과 체감이 최대한 비슷하도록.
|
|
_MARKDOWN_EXTENSIONS = ["nl2br", "sane_lists"]
|
|
|
|
_ALLOWED_TAGS = [
|
|
"p", "br", "strong", "em", "del",
|
|
"h1", "h2", "h3", "h4",
|
|
"ul", "ol", "li",
|
|
"blockquote", "code", "pre", "hr", "a", "img",
|
|
]
|
|
_ALLOWED_ATTRS = {"a": ["href", "title"], "img": ["src", "alt", "title"]}
|
|
|
|
|
|
def render_markdown(text: str) -> Markup:
|
|
"""저널 기록 내용을 마크다운 HTML로 렌더링한다. markdown 라이브러리는 기본적으로 원본 HTML을
|
|
그대로 통과시키므로(<script> 등 포함) bleach로 허용 태그만 남기고 나머지는 전부 지운다 —
|
|
이 함수가 반환하는 Markup만 템플릿에서 이스케이프 없이(그대로 안전하게) 렌더링해야 한다."""
|
|
html = markdown.markdown(text, extensions=_MARKDOWN_EXTENSIONS)
|
|
return Markup(bleach.clean(html, tags=_ALLOWED_TAGS, attributes=_ALLOWED_ATTRS, strip=True))
|