"""Journal entry body as Markdown. Media stays in media_assets; the body only references it. Canonical embed: ![caption](kansho-media:) Legacy [[media:|caption]] is still read and normalised on save. Tokens never go to the Privacy Gateway; captions may, as user wording. """ from __future__ import annotations import re UUID = r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}" UUID_RE = re.compile(rf"^{UUID}$") MD_MEDIA_RE = re.compile(rf"!\[([^\]]*)\]\(kansho-media:({UUID})\)") LEGACY_MEDIA_RE = re.compile(rf"\[\[media:({UUID})(?:\|([^\]]*))?\]\]") HEADING_PREFIX = re.compile(r"^#{1,6}\s+") def clean_title(raw: str) -> str: """First line only, without Markdown heading marks.""" line = (raw or "").strip().splitlines()[0].strip() if raw else "" return HEADING_PREFIX.sub("", line).strip() def media_token(media_id: str, caption: str = "") -> str: cap = (caption or "").replace("]", "").replace("\n", " ").strip() return f"![{cap}](kansho-media:{media_id})" def media_ids(body: str) -> list[str]: seen: list[str] = [] for match in MD_MEDIA_RE.finditer(body or ""): media_id = match.group(2) if media_id not in seen: seen.append(media_id) for match in LEGACY_MEDIA_RE.finditer(body or ""): media_id = match.group(1) if media_id not in seen: seen.append(media_id) return seen def to_markdown(body: str) -> str: """Legacy media tokens become Markdown image syntax. Already-Markdown is unchanged.""" def repl(match: re.Match) -> str: return media_token(match.group(1), match.group(2) or "") return LEGACY_MEDIA_RE.sub(repl, body or "") def plain_text(body: str) -> str: """User wording only: no media URLs, no asset IDs, no Markdown markup. Captions stay.""" text = to_markdown(body) def caption(match: re.Match) -> str: return (match.group(1) or "").strip() text = MD_MEDIA_RE.sub(caption, text) text = re.sub(r"(.*?)", r"\1", text, flags=re.I | re.S) text = re.sub(r"^#{1,6}\s+", "", text, flags=re.M) text = re.sub(r"\*\*(.+?)\*\*", r"\1", text) text = re.sub(r"__(.+?)__", r"\1", text) text = re.sub(r"(?