Forcing inputStyle to contenteditable (previous commit) didn't fix it on
iPhone, confirming this is CodeMirror 5's own long-standing weakness with
CJK IME composition on iOS WebKit, not just the iPad desktop-UA detection
issue. There's no reliable fix short of dropping CodeMirror for the journal
content field, so EasyMDE is removed entirely and the textarea goes back to
a native, uncontrolled <textarea> — nothing intercepts/re-renders it during
composition, so iOS's IME just works.
The markdown toolbar buttons (bold/italic/heading/quote/lists/code/link)
now manipulate the textarea's selection directly via a small JournalEditor
JS API instead of calling EasyMDE/CodeMirror commands, and image paste
tracks its upload placeholder by a unique text marker instead of a
CodeMirror bookmark. Also drops the vendored easymde.min.js/css (no longer
referenced) and bumps the service worker CACHE_NAME so stale cached copies
of the old vendor files get evicted.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
iPadOS 13+ sends a desktop Safari UA by default, which fools CodeMirror's mobile detection into using the more fragile textarea input mode. That, combined with CodeMirror repainting the line mid-composition, breaks Korean IME composition and leaves jamo unmerged. inputStyle can't be changed after the editor is created, so it must be set in the EasyMDE constructor options.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- 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>
- 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>