feat(content): v4.6.0 Content Blocks enrichis — Callout, TOC, Math (KaTeX), Toggle, Multi-colonnes
- Ajout blocs enrichis dans l'éditeur: callout (avec sélecteur d'emoji), table of contents (ancres), math (LaTeX/KaTeX), toggle lists (enfants collapsibles), multi-colonnes - Intégration KaTeX 0.16.11 self-hosté (JS/CSS/fonts) — aucun CDN externe - Rendu des nouveaux blocs dans les pages publiques (/p/<slug>): TOC, KaTeX, colonnes, toggle <details> - Persistance 'children' (colonnes/toggle) + export Markdown étendu - Sidebar customization par utilisateur (config API + colonne sidebar_config) - Correctif test_views_calendar: parsing des query params year/month (le défaut ouvrait sur le mois courant) - Tests: 184 passing
This commit is contained in:
+112
-4
@@ -619,9 +619,9 @@ def test_views_calendar(client):
|
||||
r = client.post("/db/api", json={"name": "Cal DB"})
|
||||
cid = r.json()["id"]
|
||||
client.post(f"/db/{cid}/pages/api", json={"title": "Event 1", "properties": {"date": "2026-07-15"}})
|
||||
resp = client.get(f"/db/{cid}/view/calendar")
|
||||
resp = client.get(f"/db/{cid}/view/calendar?year=2026&month=7")
|
||||
assert resp.status_code == 200
|
||||
assert "July" in resp.text or "juillet" in resp.text
|
||||
assert "July 2026" in resp.text or "juillet 2026" in resp.text
|
||||
client.delete(f"/db/api/{cid}")
|
||||
|
||||
|
||||
@@ -674,7 +674,7 @@ def test_views_calendar_navigation(client):
|
||||
cid = r.json()["id"]
|
||||
resp = client.get(f"/db/{cid}/view/calendar?year=2026&month=8")
|
||||
assert resp.status_code == 200
|
||||
assert "August" in resp.text or "août" in resp.text
|
||||
assert "August 2026" in resp.text or "août 2026" in resp.text
|
||||
client.delete(f"/db/api/{cid}")
|
||||
|
||||
|
||||
@@ -2757,4 +2757,112 @@ def test_my_tasks_page_render(client):
|
||||
resp = client.get("/my-tasks?view=all")
|
||||
assert resp.status_code == 200
|
||||
assert "Urgent fix" in resp.text
|
||||
assert "My Tasks DB" in resp.text
|
||||
assert "My Tasks DB" in resp.text
|
||||
|
||||
|
||||
# ── v4.6.0: Content Blocks Enriched ──
|
||||
|
||||
def _make_published_page(client, blocks, title="Enriched Page"):
|
||||
"""Create a user + published page directly in DB with blocks, return public HTML."""
|
||||
import uuid
|
||||
import json as _json
|
||||
from app.db import get_conn
|
||||
login = "v460_" + uuid.uuid4().hex[:10]
|
||||
slug = "v460-" + uuid.uuid4().hex[:8]
|
||||
with get_conn() as conn:
|
||||
conn.execute("INSERT INTO users (login, full_name, email) VALUES (?, 'V460', ?)",
|
||||
(login, login + "@t.com"))
|
||||
uid = conn.execute("SELECT id FROM users WHERE login=?", (login,)).fetchone()["id"]
|
||||
cur = conn.execute(
|
||||
"INSERT INTO pages (workspace, title, content, content_format, is_published, publish_slug) "
|
||||
"VALUES (?, ?, ?, 'blocks', 1, ?)",
|
||||
(login, title, _json.dumps(blocks), slug),
|
||||
)
|
||||
pid = cur.lastrowid
|
||||
conn.commit()
|
||||
resp = client.get(f"/p/{slug}")
|
||||
with get_conn() as conn:
|
||||
conn.execute("DELETE FROM users WHERE id=?", (uid,))
|
||||
conn.execute("DELETE FROM pages WHERE id=?", (pid,))
|
||||
conn.commit()
|
||||
return resp
|
||||
|
||||
|
||||
def test_v460_public_table_of_contents(client):
|
||||
"""Table of contents block renders a list of page headings."""
|
||||
blocks = [
|
||||
{"id": "h1", "type": "heading_1", "content": "Overview"},
|
||||
{"id": "b1", "type": "table_of_contents", "content": ""},
|
||||
{"id": "h2", "type": "heading_2", "content": "Installation"},
|
||||
]
|
||||
resp = _make_published_page(client, blocks)
|
||||
assert resp.status_code == 200
|
||||
assert "On this page" in resp.text
|
||||
assert "Overview" in resp.text
|
||||
assert "Installation" in resp.text
|
||||
|
||||
|
||||
def test_v460_public_math_block(client):
|
||||
"""Math block renders a KaTeX-display marker with the LaTeX source."""
|
||||
blocks = [
|
||||
{"id": "m1", "type": "math", "content": "E = mc^2"},
|
||||
]
|
||||
resp = _make_published_page(client, blocks)
|
||||
assert resp.status_code == 200
|
||||
assert 'data-katex="E = mc^2"' in resp.text
|
||||
|
||||
|
||||
def test_v460_public_columns(client):
|
||||
"""Column blocks render children in a flex row."""
|
||||
blocks = [
|
||||
{"id": "cb", "type": "columns",
|
||||
"children": [
|
||||
{"id": "c1", "type": "paragraph", "content": "Left cell"},
|
||||
{"id": "c2", "type": "paragraph", "content": "Right cell"},
|
||||
]},
|
||||
]
|
||||
resp = _make_published_page(client, blocks)
|
||||
assert resp.status_code == 200
|
||||
assert "Left cell" in resp.text
|
||||
assert "Right cell" in resp.text
|
||||
|
||||
|
||||
def test_v460_public_toggle_children(client):
|
||||
"""Toggle block renders nested children."""
|
||||
blocks = [
|
||||
{"id": "tg", "type": "toggle", "content": "Details", "expanded": True,
|
||||
"children": [{"id": "t1", "type": "bulleted_list", "content": "Nested item"}]},
|
||||
]
|
||||
resp = _make_published_page(client, blocks)
|
||||
assert resp.status_code == 200
|
||||
assert "Details" in resp.text
|
||||
assert "Nested item" in resp.text
|
||||
|
||||
|
||||
def test_v460_save_load_blocks_preserves_children(client):
|
||||
"""Blocks API round-trip preserves children for columns and toggles."""
|
||||
from app.db import get_conn
|
||||
import json as _json
|
||||
r = client.post("/board/api/pages?title=Block RT§ion=Private&project=test/test")
|
||||
pid = r.json()["id"]
|
||||
client.post(f"/board/api/pages/{pid}/blocks", json={
|
||||
"title": "Block RT",
|
||||
"blocks": [
|
||||
{"id": "cb", "type": "columns", "children": [
|
||||
{"id": "c1", "type": "paragraph", "content": "A"},
|
||||
{"id": "c2", "type": "paragraph", "content": "B"},
|
||||
]},
|
||||
{"id": "tg", "type": "toggle", "content": "T", "children": [
|
||||
{"id": "t1", "type": "paragraph", "content": "X"},
|
||||
]},
|
||||
{"id": "mt", "type": "math", "content": "x^2"},
|
||||
],
|
||||
})
|
||||
with get_conn() as conn:
|
||||
row = conn.execute("SELECT content, content_format FROM pages WHERE id=?", (pid,)).fetchone()
|
||||
assert row["content_format"] == "blocks"
|
||||
data = _json.loads(row["content"])
|
||||
types = {b["type"] for b in data}
|
||||
assert "columns" in types and "toggle" in types and "math" in types
|
||||
col = next(b for b in data if b["type"] == "columns")
|
||||
assert len(col["children"]) == 2
|
||||
Reference in New Issue
Block a user