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:
@@ -8,6 +8,7 @@ from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
from app.markdown_utils import render_markdown
|
||||
from app.models.journal import JournalMood
|
||||
from app.routers.pages import _current_user_or_redirect, templates
|
||||
from app.schemas.journal import JournalCategoryCreate, JournalEntryCreate, JournalEntryUpdate
|
||||
@@ -33,6 +34,7 @@ templates.env.globals["journal_mood_emoji"] = {m.value: emoji for m, emoji, _ in
|
||||
# Jinja2 내장 |tojson 필터가 이 policy를 읽어서 json.dumps에 넘긴다 — 기본값(ensure_ascii=True)이면
|
||||
# 한글이 \uXXXX로 이스케이프돼 응답 본문에서 읽기 힘들어진다.
|
||||
templates.env.policies["json.dumps_kwargs"] = {"ensure_ascii": False}
|
||||
templates.env.filters["markdown"] = render_markdown
|
||||
|
||||
|
||||
def _parse_moods(raw: str) -> list[JournalMood]:
|
||||
@@ -133,6 +135,17 @@ def journal_day_detail(request: Request, entry_date: date, db: Session = Depends
|
||||
return _render_day_detail(request, db, current.id, entry_date)
|
||||
|
||||
|
||||
@router.post("/journal/preview")
|
||||
def preview_entry_content(request: Request, content: str = Form(""), db: Session = Depends(get_db)):
|
||||
current = _current_user_or_redirect(request, db)
|
||||
if isinstance(current, RedirectResponse):
|
||||
return current
|
||||
|
||||
if not content.strip():
|
||||
return HTMLResponse('<span class="empty-state">미리보기가 여기에 표시돼요</span>')
|
||||
return HTMLResponse(render_markdown(content))
|
||||
|
||||
|
||||
@router.post("/journal/new")
|
||||
def create_entry_page(
|
||||
request: Request,
|
||||
@@ -149,6 +162,9 @@ def create_entry_page(
|
||||
if isinstance(current, RedirectResponse):
|
||||
return current
|
||||
|
||||
if journal_service.get_category(db, category_id, current.id) is None:
|
||||
return HTMLResponse("카테고리를 찾을 수 없어요")
|
||||
|
||||
try:
|
||||
parsed_date = date.fromisoformat(entry_date)
|
||||
data = JournalEntryCreate(
|
||||
@@ -202,6 +218,16 @@ def edit_entry_page(
|
||||
|
||||
original_date = entry.entry_date
|
||||
|
||||
if journal_service.get_category(db, category_id, current.id) is None:
|
||||
return _render_day_detail(
|
||||
request,
|
||||
db,
|
||||
current.id,
|
||||
original_date,
|
||||
edit_error="카테고리를 찾을 수 없어요",
|
||||
editing_entry_id=entry_id,
|
||||
)
|
||||
|
||||
try:
|
||||
parsed_date = date.fromisoformat(entry_date)
|
||||
data = JournalEntryUpdate(
|
||||
|
||||
Reference in New Issue
Block a user