fix: colonnes rendues, tables IA sans pipe de tête, copier/couper/coller multi-blocs (v7.68.0)
Colonnes / équation / TOC - renderBlock() n'appelait JAMAIS renderColumnsBlock()/renderMathBlock()/renderTOC() (3 renderers morts) → un bloc colonnes (slash « Column list », Turn into 2/3/4/5 colonnes) tombait dans le rendu générique = paragraphe vide. Dispatch ajouté ; la × de suppression de colonne (opacity:0 sans survol) devient visible au survol. Tables rendues par l'IA - _tblIsData/_tblIsSep exigeaient un | de tête → le GFM sans pipe (A | B / --- | ---) devenait des paragraphes. Pipe de tête optionnel, séparateur gardé à ≥2 cellules (« texte » + « --- » reste un séparateur). md2b et paste2b partagent les helpers. Copier / couper / coller - Sélection de texte MULTI-BLOCS : _clipState()/_clipMarkdown() (début partiel → blocs entiers via blocksToMarkdown → fin partielle), clipCopy()/clipCut() (coupure = tête du 1er bloc + queue du dernier), listeners copy/cut (Ctrl+C/X via les événements natifs), groupe Clipboard (Cut/Copy/Paste) dans le menu du clic droit avec la sélection figée à l'ouverture (la recherche prend le focus), clic droit sur un bloc sélectionné = sélection multi-blocs conservée. - Coller un seul bloc structuré (table/code/titre) = vraie insertion (avant : texte brut) ; pasteBlocks colle après le dernier bloc sans focus. - « Copy link to block » réparé (data-bm-act link → copy-link). Tests: pytest tests/ -q -n auto → 1394 passed / 0 failed ; ruff OK ; e2e v768_columns_clipboard (4 : slash+Turn into colonnes, table IA sans pipe, copier/couper/coller, clic droit Clipboard) + v762/v764/v766/v7641/v763/ regression_editor_mount → verts sur :8081 (image flowdeck:v7.68.0).
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
"""7.68.0 — colonnes/math/TOC rendus + copier-couper-coller multi-blocs."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
SRC = (ROOT / "static" / "js" / "page_editor_scripts.js").read_text(encoding="utf-8")
|
||||
CSS = (ROOT / "static" / "css" / "app.css").read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def _fn(name: str, span: int = 1500) -> str:
|
||||
i = SRC.index(name + "(")
|
||||
return SRC[i:i + span]
|
||||
|
||||
|
||||
def test_renderers_are_actually_dispatched():
|
||||
# les trois renderers existaient mais n'étaient JAMAIS appelés →
|
||||
# colonnes (slash + Turn into), équation et TOC rendaient un paragraphe vide
|
||||
dispatch = _fn("renderBlock")
|
||||
for call in ("renderColumnsBlock(b)", "renderMathBlock(b)", "renderTOC(b)"):
|
||||
assert call in dispatch, call
|
||||
assert SRC.count("function " + call.split("(")[0] + "(") == 1
|
||||
# la suppression d'une colonne n'est plus un bouton invisible
|
||||
assert ".block-column-wrapper:hover .block-actions {" in CSS
|
||||
|
||||
|
||||
def test_gfm_tables_without_leading_pipe():
|
||||
# l'IA rend souvent « A | B » / « --- | --- » — le pipe de tête est optionnel
|
||||
assert "s.indexOf('|')>=0&&_tblSplitLine(s).length>=2" in SRC
|
||||
# une ligne de séparation doit rester ≥2 cellules (sinon « texte » + « --- »)
|
||||
assert "if(cells.length<2)return false;return cells.every" in SRC
|
||||
# md2b ET paste2b passent par les mêmes helpers (alignés)
|
||||
assert SRC.count("_tblIsSep(lines[i+1])") == 2
|
||||
|
||||
|
||||
def test_clipboard_multi_block_selection():
|
||||
# état : sélection vivante traversant ≥2 blocs ; sinon null (natif)
|
||||
for tok in ("_clipState(){", "si>=ei)return null;", "_srcOffset(sblk,r.startContainer,r.startOffset)",
|
||||
"_srcOffset(eblk,r.endContainer,r.endOffset)"):
|
||||
assert tok in SRC, tok
|
||||
# markdown : début partiel → blocs entiers → fin partielle
|
||||
assert "_clipMarkdown(st){" in SRC
|
||||
assert "blocksToMarkdown(b)" in SRC
|
||||
# coupure : premier/trois restent (têtes/queues), milieu supprimé
|
||||
assert "this.blocks.slice(0,st.si+1).concat([last],this.blocks.slice(st.ei+1))" in SRC
|
||||
# Ctrl+C / Ctrl+X : listeners copy/cut (le clavier natif les déclenche)
|
||||
assert "ct.addEventListener('copy'," in SRC
|
||||
assert "ct.addEventListener('cut'," in SRC
|
||||
assert "ev.clipboardData.setData('text/plain',md)" in SRC
|
||||
# menu clic droit : groupe Clipboard Cut/Copy/Paste
|
||||
assert "'<div class=\"bm-group\">Clipboard</div>'" in SRC
|
||||
for act in ("clip-cut", "clip-copy", "clip-paste"):
|
||||
assert f"data-bm-act=\"{act}\"" in SRC or f"'{act}'" in SRC, act
|
||||
# « Copy link to block » : l'act du menu doit être celle du handler
|
||||
assert "_blockMenuItem('copy-link'," in SRC
|
||||
assert "act==='copy-link'" in SRC
|
||||
|
||||
|
||||
def test_paste_single_structured_block_is_inserted():
|
||||
# un seul bloc structuré collé (table, code, titre) ne retombe plus en texte brut
|
||||
assert "if(parsed.length===1&&parsed[0].type==='paragraph')return fallback();" in SRC
|
||||
Reference in New Issue
Block a user