journal: real markdown editor (EasyMDE) with live preview toggle, fix default template

- Replace the plain textarea with EasyMDE (vendored locally, no CDN) for
  markdown authoring: syntax highlighting, smart list continuation, and a
  custom text-based toolbar (built-in EasyMDE toolbar icons require Font
  Awesome from a CDN, which this app doesn't use). unorderedListStyle is set
  to "-" to match the app's own template convention.
- Add a preview/edit toggle button that swaps the editor for the exact same
  server-rendered markdown (via /journal/preview) shown after saving, instead
  of always showing both.
- Fix create/edit entry routes to verify the submitted category_id actually
  belongs to the current user before inserting -- every other write path in
  this app already checked ownership; this one didn't (found while manually
  testing the new editor with a typo'd category id that happened to belong to
  someone else's category, which surfaced as an IntegrityError 500 instead of
  a clean 404-equivalent).
- Fix the default "일상" category template: bare "-" bullet lines don't parse
  as list items in the markdown renderer (they need a trailing space), and
  the content_template validator was silently stripping that trailing space
  off on every save. Backfill migration updates any category still holding
  the old, broken template text.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-05 12:24:18 +09:00
co-authored by Claude Sonnet 5
parent c466cc6639
commit bdf9d0bae7
18 changed files with 564 additions and 30 deletions
+18 -1
View File
@@ -41,13 +41,30 @@ def _upload(filename: str, content_type: str, data: bytes) -> UploadFile:
def test_ensure_default_category_creates_once(db_session, test_user):
first = journal_service.ensure_default_category(db_session, test_user.id)
assert first.name == journal_service.DEFAULT_CATEGORY_NAME
assert first.content_template == journal_service.DEFAULT_CATEGORY_TEMPLATE.strip()
assert first.content_template == journal_service.DEFAULT_CATEGORY_TEMPLATE
second = journal_service.ensure_default_category(db_session, test_user.id)
assert second.id == first.id
assert len(journal_service.list_categories(db_session, test_user.id)) == 1
def test_default_category_template_renders_as_headers_and_lists_not_bare_dashes(db_session, test_user):
from app.markdown_utils import render_markdown
category = journal_service.ensure_default_category(db_session, test_user.id)
html = render_markdown(category.content_template)
# "-"에 뒤 공백이 없으면 markdown이 목록으로 안 잡고 그냥 "<p>-</p>"로 렌더링해버리는
# 회귀가 있었다 — 다섯 항목 전부 실제 <ul><li> 목록이어야 한다.
assert html.count("<ul>") == 5
assert html.count("<li>") == 5
# 제목 줄 바로 다음에 "-"가 오면(빈 줄 없이) markdown이 그걸 제목 밑줄로 오인해서
# 굵은 글씨가 아니라 <h1>/<h2> 제목으로 바뀌어버리는 회귀도 있었다.
assert "<h1" not in html
assert "<h2" not in html
assert html.count("<strong>") == 6
def test_category_content_template_persists_and_updates(db_session, test_user):
category = journal_service.create_category(
db_session, test_user.id, JournalCategoryCreate(name="회고", content_template="질문 1\n\n질문 2")