v5.4.1 Partage : correctif permissions + autocomplete membres (inclut v5.4.0 Interactions de bloc)
FlowDeck CI / test (push) Failing after 18s
FlowDeck CI / docker (push) Skipped

- Fix "Invalid permission" : le client envoyait editor/commenter/viewer alors que
  l'API attend view/comment/edit (invitePermission -> 'edit', menu participants aligné)
- PUT /api/pages/{page_id}/share/{share_id} : mise à jour de permission persistée
- Autocomplete des membres existants dans le champ d'invitation (search users + debounce,
  navigation clavier, envoi user_id pour lier le partage au compte)
- Upsert anti-doublon dans le partage + trim email backend
- 12 tests tests/test_sharing.py ; suite 319 verte (+3 PDF pre-existants)

Sans oublier v5.4.0 (non publie jusque-la) :
- Undo/Redo (Ctrl+Z/Ctrl+Shift+Z/Ctrl+Y), duplicate (Ctrl+D), menu de bloc (turn
  into, couleurs, lien #fdblk-, move to, delete), drag&drop multi-selection,
  slash "Actions", en-tetes de tableau (has_header/first_col_header) + exports,
  sync realtime immédiat apres mutation ; 9 tests test_block_interactions.py
This commit is contained in:
2026-09-08 09:34:41 -04:00
parent f84ff201ea
commit b3c90efa73
12 changed files with 1055 additions and 119 deletions
+37 -19
View File
@@ -264,24 +264,28 @@ def _table_to_markdown(b: dict) -> str:
align = b.get("align") or []
width = max(len(r) for r in rows)
align = (align + ["left"] * width)[:width]
has_header = b.get("has_header", True)
out: list[str] = []
def rowline(r):
cells = list(r) + [""] * (width - len(r))
return "| " + " | ".join(cells) + " |"
for ri, r in enumerate(rows):
out.append(rowline(r))
if b.get("has_header") and ri == 0:
seps = []
for a in align:
if a == "center":
seps.append(":---:")
elif a == "right":
seps.append("---:")
else:
seps.append(":---")
out.append("| " + " | ".join(seps) + " |")
start = 0
if has_header:
out.append(rowline(rows[0]))
seps = []
for a in align:
if a == "center":
seps.append(":---:")
elif a == "right":
seps.append("---:")
else:
seps.append(":---")
out.append("| " + " | ".join(seps) + " |")
start = 1
for ri in range(start, len(rows)):
out.append(rowline(rows[ri]))
return "\n".join(out)
@@ -297,16 +301,30 @@ def _table_to_html(b: dict) -> str:
style = f' style="text-align:{a};"' if a and a != "left" else ""
return f"<{tag}{style}>{_text(text)}</{tag}>"
def row_html(r, tag_default):
cells = list(r) + [""] * (width - len(r))
return "<tr>" + "".join(cell_html(tag_default, c, align[ci]) for ci, c in enumerate(cells)) + "</tr>"
header_rows = 1 if b.get("has_header") else 0
has_header = b.get("has_header", True)
first_col = b.get("first_col_header", False)
header_rows = 1 if has_header else 0
head = ""
if header_rows:
head = "<thead>" + "".join(row_html(r, "th") for r in rows[:header_rows]) + "</thead>"
head_rows = []
hr = rows[0]
cells = list(hr) + [""] * (width - len(hr))
head_cells = []
for ci, c in enumerate(cells):
tag = "th" if first_col and ci == 0 else "th"
head_cells.append(cell_html(tag, c, align[ci]))
head_rows.append("<tr>" + "".join(head_cells) + "</tr>")
head = "<thead>" + "".join(head_rows) + "</thead>"
tbody_rows = rows[header_rows:]
body = "<tbody>" + "".join(row_html(r, "td") for r in tbody_rows) + "</tbody>"
body_rows = []
for r in tbody_rows:
cells = list(r) + [""] * (width - len(r))
row_cells = []
for ci, c in enumerate(cells):
tag = "th" if first_col and ci == 0 else "td"
row_cells.append(cell_html(tag, c, align[ci]))
body_rows.append("<tr>" + "".join(row_cells) + "</tr>")
body = "<tbody>" + "".join(body_rows) + "</tbody>"
return f'<table class="ftable">{head}{body}</table>'