merge: fix article .md lookup + lighter editor
All checks were successful
CI / test (push) Successful in 51s
CI / tag (push) Successful in 3s

This commit is contained in:
Deeman
2026-03-02 11:47:13 +01:00
2 changed files with 43 additions and 17 deletions

View File

@@ -2121,6 +2121,27 @@ _ARTICLES_DIR = Path(__file__).parent.parent.parent.parent.parent / "data" / "co
_FRONTMATTER_RE = re.compile(r"^---\s*\n(.*?)\n---\s*\n", re.DOTALL) _FRONTMATTER_RE = re.compile(r"^---\s*\n(.*?)\n---\s*\n", re.DOTALL)
def _find_article_md(slug: str) -> Path | None:
"""Return the Path of the .md file whose frontmatter slug matches, or None.
Tries the exact name first ({slug}.md), then scans _ARTICLES_DIR for any
file whose YAML frontmatter contains 'slug: <slug>'. This handles the
common pattern where files are named {slug}-{lang}.md but the frontmatter
slug omits the language suffix.
"""
if not _ARTICLES_DIR.is_dir():
return None
exact = _ARTICLES_DIR / f"{slug}.md"
if exact.exists():
return exact
for path in _ARTICLES_DIR.glob("*.md"):
raw = path.read_text(encoding="utf-8")
m = _FRONTMATTER_RE.match(raw)
if m and f"slug: {slug}" in m.group(1):
return path
return None
async def _sync_static_articles() -> None: async def _sync_static_articles() -> None:
"""Upsert static .md articles from data/content/articles/ into the DB. """Upsert static .md articles from data/content/articles/ into the DB.
@@ -2544,11 +2565,12 @@ async def article_edit(article_id: int):
# Load markdown source if available (manual or generated) # Load markdown source if available (manual or generated)
from ..content.routes import BUILD_DIR as CONTENT_BUILD_DIR from ..content.routes import BUILD_DIR as CONTENT_BUILD_DIR
md_path = _ARTICLES_DIR / f"{article['slug']}.md" md_path = _find_article_md(article["slug"])
if not md_path.exists(): if md_path is None:
lang = article["language"] or "en" lang = article["language"] or "en"
md_path = CONTENT_BUILD_DIR / lang / "md" / f"{article['slug']}.md" fallback = CONTENT_BUILD_DIR / lang / "md" / f"{article['slug']}.md"
raw = md_path.read_text() if md_path.exists() else "" md_path = fallback if fallback.exists() else None
raw = md_path.read_text() if md_path else ""
# Strip YAML frontmatter so only the prose body appears in the editor # Strip YAML frontmatter so only the prose body appears in the editor
m = _FRONTMATTER_RE.match(raw) m = _FRONTMATTER_RE.match(raw)
body = raw[m.end():].lstrip("\n") if m else raw body = raw[m.end():].lstrip("\n") if m else raw

View File

@@ -156,8 +156,8 @@
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 0.375rem 0.875rem; padding: 0.375rem 0.875rem;
background: #1E293B; background: #F8FAFC;
border-bottom: 1px solid #0F172A; border-bottom: 1px solid #E2E8F0;
flex-shrink: 0; flex-shrink: 0;
} }
.ae-pane--preview .ae-pane__header { .ae-pane--preview .ae-pane__header {
@@ -171,13 +171,11 @@
letter-spacing: 0.09em; letter-spacing: 0.09em;
color: #94A3B8; color: #94A3B8;
} }
.ae-pane--editor .ae-pane__label { color: #475569; }
.ae-pane__hint { .ae-pane__hint {
font-size: 0.625rem; font-size: 0.625rem;
font-family: var(--font-mono); font-family: var(--font-mono);
color: #475569; color: #94A3B8;
} }
.ae-pane--preview .ae-pane__hint { color: #94A3B8; }
/* The markdown textarea */ /* The markdown textarea */
.ae-editor { .ae-editor {
@@ -185,16 +183,16 @@
resize: none; resize: none;
border: none; border: none;
outline: none; outline: none;
padding: 1.125rem 1.25rem; padding: 1.5rem 2rem;
font-family: var(--font-mono); font-family: var(--font-mono);
font-size: 0.8125rem; font-size: 0.875rem;
line-height: 1.75; line-height: 1.8;
background: #0F172A; background: #FEFDFB;
color: #CBD5E1; color: #1E293B;
caret-color: #3B82F6; caret-color: #1D4ED8;
tab-size: 2; tab-size: 2;
} }
.ae-editor::placeholder { color: #334155; } .ae-editor::placeholder { color: #CBD5E1; }
.ae-editor:focus { outline: none; } .ae-editor:focus { outline: none; }
/* Preview pane */ /* Preview pane */
@@ -283,9 +281,15 @@
color: #94A3B8; color: #94A3B8;
font-family: var(--font-mono); font-family: var(--font-mono);
opacity: 0; opacity: 0;
transition: opacity 0.15s; transition: opacity 0.2s;
} }
.ae-loading.htmx-request { opacity: 1; } .ae-loading.htmx-request { opacity: 1; }
/* Responsive: stack on narrow screens */
@media (max-width: 900px) {
.ae-split { grid-template-columns: 1fr; }
.ae-pane--preview { display: none; }
}
</style> </style>
{% endblock %} {% endblock %}