fix: side peek = document seul en edition + bouton Open calendrier/Kanban (v7.50.0)
- Les 4 peeks (library.js, local_workspace.js, my_tasks.js, database_table.js)
chargent /pages/{id}?embed=1 : le panneau ne montre plus que le document en
mode edition, sans sidebar ni barre (regression v7.49.0). Le cadre du
panneau (close/full/resize partages fdWirePeekResize) est inchange.
- Calendrier My Tasks : le handler testait e.target.dataset.openRow mais les
boutons .mt-cal-open ne contiennent que leur <svg> -> clic sur l'icone =
e.target = <svg>, rien ne partait. cause racine corrigee par
t.closest('[data-open-row]') : un seul chemin tableau/calendrier/Kanban.
- Kanban My Tasks : meme bouton Open ajoute sur les tuiles (openButton(t),
overlay absolu revele au survol de la carte .mt-card).
- Menu contextuel global « Open in side peek » : etait un toast d'erreur ;
route maintenant vers le peek de la page courante (_libData.openPeekById /
_wsData.openSidePeek), repli nouvel onglet ailleurs.
- /pages/{id}?embed=1 sur une page de base (content_format=collection)
rendait un editeur de blocs vide : le template collection est garde en
embed (body.embed-mode retire sidebar+barre, le tableau reste). Bouton
pleine page des peeks de bases : navigue via dataset.page (URL propre)
au lieu du src ?embed=1.
- Tests : garde de coherence inversee (?embed=1 exige sur les 4 fichiers),
+3 nouveaux (closest, tuiles Kanban, collection embed). Fix d'un test
pre-existant casse (assertion panel.style.display, reecriture vanilla JS
v7.49.0). Suite 1264 passed / 0 failed (-n auto) + ruff 0.
- Bump VERSION/main.py 7.50.0, CHANGELOG, WORKLOAD, OpenAPI regenere.
This commit is contained in:
@@ -1,5 +1,50 @@
|
|||||||
# Changelog - FlowDeck
|
# Changelog - FlowDeck
|
||||||
|
|
||||||
|
## v7.50.0 (2026-10-06) — Side peek : le document seul (mode édition), sans sidebar ni barre
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **Tous les boutons « Open » ouvraient l'habillage pleine page dans le panneau
|
||||||
|
latéral** — `library.js` (OPEN de hover), `local_workspace.js` (↢ Open +
|
||||||
|
« Open in side peek » du menu contextuel) et `my_tasks.js` (peek des tâches)
|
||||||
|
chargeaient `/pages/{id}` SANS `?embed=1` : le peek affichait sidebar +
|
||||||
|
barre de navigation + éditeur (régression v7.49.0). Tous repassent en
|
||||||
|
`/pages/{id}?embed=1` = le document en mode édition SEUL ; le cadre du
|
||||||
|
panneau (garde/close/resize partagés `fdWirePeekResize`) reste identique.
|
||||||
|
- **Le bouton « Open » du calendrier My Tasks ne faisait rien** — cause
|
||||||
|
racine : le handler de clic testait `e.target.dataset.openRow`, mais les
|
||||||
|
boutons `.mt-cal-open` du calendrier (et du Kanban) ne contiennent que leur
|
||||||
|
`<svg>` : cliquer l'icône donnait `e.target` = `<svg>` sans dataset. Le
|
||||||
|
handler résout désormais `t.closest('[data-open-row]')` — un seul chemin
|
||||||
|
pour tableau, calendrier (5 vues) et Kanban.
|
||||||
|
- **Menu contextuel global « Open in side peek »** — le clic ne faisait
|
||||||
|
qu'afficher « Side peek not available in browser » : il route maintenant
|
||||||
|
vers le peek de la page courante (`_libData.openPeekById` en Library,
|
||||||
|
`_wsData.openSidePeek` en workspace local), repli nouvel onglet ailleurs.
|
||||||
|
- **Peek d'une page de base (`content_format='collection'`)** — `?embed=1`
|
||||||
|
forçait le template éditeur : une base ouverte dans le peek montrait un
|
||||||
|
éditeur de blocs vide au lieu du tableau. Le template collection est gardé
|
||||||
|
en mode embed (body.embed-mode retire sidebar + barre, le tableau reste).
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Bouton « Open » sur les tuiles du Kanban My Tasks** — le même que les
|
||||||
|
lignes du tableau (`openButton(t)`, `.mt-hover-only` révélé au survol de la
|
||||||
|
carte, overlay absolu pour ne pas déformer la tuile) ; ouvre le contenu de
|
||||||
|
la tâche dans le panneau latéral.
|
||||||
|
- **Bouton « pleine page » des peeks de bases** — navigue vers
|
||||||
|
`/pages/{pageId}` (pleine page, sidebar + barre conservées) au lieu de
|
||||||
|
l'URL `?embed=1` de l'iframe ; `dataset.page` porte l'id résolu.
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
- `test_my_tasks_workspace_coherence.py` : les 4 peek JS (`library`,
|
||||||
|
`my_tasks`, `database_table`, `local_workspace`) doivent porter
|
||||||
|
`?embed=1` (gate de cohérence inversé — l'ancien test imposait l'URL nue) ;
|
||||||
|
nouveaux : résolution `closest('[data-open-row]')`, bouton Open sur tuiles
|
||||||
|
Kanban, collection embed = tableau. Suite 1264 passed / 0 failed
|
||||||
|
(-n auto) + ruff 0.
|
||||||
|
|
||||||
## v7.49.2 (2026-10-05) — Pages de base : titre au-dessus, tableau à gauche
|
## v7.49.2 (2026-10-05) — Pages de base : titre au-dessus, tableau à gauche
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# WORKLOAD — FlowDeck Notion Clone
|
# WORKLOAD — FlowDeck Notion Clone
|
||||||
|
|
||||||
> **Début**: 2026-07-08 | **Version**: v7.49.2 (pages de base : titre au-dessus du tableau, tableau calé à gauche au ras du sidebar) | **Statut**: EN COURS 🔄
|
> **Début**: 2026-07-08 | **Version**: v7.50.0 (side peek : document seul en mode édition, bouton Open calendrier/Kanban My Tasks) | **Statut**: EN COURS 🔄
|
||||||
> **Cible**: parité Notion + intégration forge · **Follow-ups v7.3 livrés**: sidebar teamspaces, notif `page.updated`, charts `number` + dashboards multi-DB, unfurl forge, UI Settings → Audit — voir `ROADMAP.md § v7.3.0`
|
> **Cible**: parité Notion + intégration forge · **Follow-ups v7.3 livrés**: sidebar teamspaces, notif `page.updated`, charts `number` + dashboards multi-DB, unfurl forge, UI Settings → Audit — voir `ROADMAP.md § v7.3.0`
|
||||||
|
|
||||||
## Avancement Global
|
## Avancement Global
|
||||||
|
|||||||
+1
-1
@@ -185,7 +185,7 @@ async def lifespan(_app: FastAPI):
|
|||||||
|
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title="FlowDeck",
|
title="FlowDeck",
|
||||||
version="7.49.2",
|
version="7.50.0",
|
||||||
docs_url="/docs",
|
docs_url="/docs",
|
||||||
redoc_url="/redoc",
|
redoc_url="/redoc",
|
||||||
lifespan=lifespan,
|
lifespan=lifespan,
|
||||||
|
|||||||
@@ -217,8 +217,10 @@ def view_page_root(request: Request, page_id: int):
|
|||||||
"{% from '_icons.html' import fd_icon %}{{ fd_icon('paperclip', 14) }}"
|
"{% from '_icons.html' import fd_icon %}{{ fd_icon('paperclip', 14) }}"
|
||||||
).render(),
|
).render(),
|
||||||
)
|
)
|
||||||
# Select template: collection pages use database table view
|
# Select template: collection pages use database table view (aussi en
|
||||||
if page.get("content_format") == "collection" and not embed:
|
# mode embed : `page_editor_collection.html` + body.embed-mode = le
|
||||||
|
# tableau SANS sidebar ni barre — le peek d'une base reste le tableau).
|
||||||
|
if page.get("content_format") == "collection":
|
||||||
template = env.get_template("page_editor_collection.html")
|
template = env.get_template("page_editor_collection.html")
|
||||||
else:
|
else:
|
||||||
template = env.get_template("page_editor_embed.html" if embed else "page_editor.html")
|
template = env.get_template("page_editor_embed.html" if embed else "page_editor.html")
|
||||||
|
|||||||
@@ -2093,9 +2093,16 @@
|
|||||||
case 'openTab':
|
case 'openTab':
|
||||||
window.open('/' + pageId, '_blank');
|
window.open('/' + pageId, '_blank');
|
||||||
break;
|
break;
|
||||||
case 'openPeek':
|
case 'openPeek': {
|
||||||
this.toast('Side peek not available in browser', 'info');
|
// Route vers le panneau de peek DE LA PAGE courante (même
|
||||||
|
// éditeur allégé `?embed=1` que le bouton « ↢ Open »), jamais une
|
||||||
|
// navigation. En dehors de ces pages : repli sur un nouvel onglet.
|
||||||
|
var num = pageId.replace('page/', '');
|
||||||
|
if (window._libData && window._libData.openPeekById) window._libData.openPeekById(parseInt(num));
|
||||||
|
else if (window._wsData && window._wsData.openSidePeek) window._wsData.openSidePeek(parseInt(num));
|
||||||
|
else window.open('/pages/' + num, '_blank');
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.contextMenu.visible = false;
|
this.contextMenu.visible = false;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -450,8 +450,8 @@
|
|||||||
<!-- Le corps et l'iframe sont HORS du <template x-if> : ils existent donc en
|
<!-- Le corps et l'iframe sont HORS du <template x-if> : ils existent donc en
|
||||||
permanence, ce qui permet de piloter `src` en JS (le build CSP
|
permanence, ce qui permet de piloter `src` en JS (le build CSP
|
||||||
d'Alpine n'evalue pas le binding `:src` — l'iframe restait donc vide).
|
d'Alpine n'evalue pas le binding `:src` — l'iframe restait donc vide).
|
||||||
Charger `/pages/{id}` sans `?embed=1` : c'est exactement l'editeur
|
Charger `/pages/{id}?embed=1` : le peek affiche uniquement le
|
||||||
plein ecran, le peek n'est plus une variante allegee. -->
|
document en mode edition, sans sidebar ni barre de navigation. -->
|
||||||
<div class="peek-body" style="flex:1;display:flex;flex-direction:column;min-height:0;">
|
<div class="peek-body" style="flex:1;display:flex;flex-direction:column;min-height:0;">
|
||||||
<div class="peek-loading" id="lib-peek-loading">Chargement…</div>
|
<div class="peek-loading" id="lib-peek-loading">Chargement…</div>
|
||||||
<iframe id="lib-peek-iframe" src="about:blank" title="Document" style="width:100%;height:100%;border:none;flex:1;min-height:0;"></iframe>
|
<iframe id="lib-peek-iframe" src="about:blank" title="Document" style="width:100%;height:100%;border:none;flex:1;min-height:0;"></iframe>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"openapi": "3.1.0",
|
"openapi": "3.1.0",
|
||||||
"info": {
|
"info": {
|
||||||
"title": "FlowDeck",
|
"title": "FlowDeck",
|
||||||
"version": "7.49.2"
|
"version": "7.50.0"
|
||||||
},
|
},
|
||||||
"paths": {
|
"paths": {
|
||||||
"/auth/register": {
|
"/auth/register": {
|
||||||
|
|||||||
@@ -207,6 +207,14 @@
|
|||||||
}
|
}
|
||||||
.mt-card:active { cursor: grabbing; opacity: .7; }
|
.mt-card:active { cursor: grabbing; opacity: .7; }
|
||||||
.mt-card-title { font-weight: 500; }
|
.mt-card-title { font-weight: 500; }
|
||||||
|
/* Bouton « Open » en overlay au survol de la tuile (la carte est une
|
||||||
|
colonne flex : une place en flux déformerait la tuile). */
|
||||||
|
.mt-card { position: relative; }
|
||||||
|
.mt-card .mt-open-btn {
|
||||||
|
position: absolute; top: 6px; right: 6px; margin-left: 0;
|
||||||
|
}
|
||||||
|
.mt-card:hover .mt-hover-only,
|
||||||
|
.mt-card:focus-within .mt-hover-only { opacity: 1; }
|
||||||
.mt-card-due,
|
.mt-card-due,
|
||||||
.mt-card-src { font-size: 11px; color: var(--text-dim, #999); }
|
.mt-card-src { font-size: 11px; color: var(--text-dim, #999); }
|
||||||
.mt-card-src { opacity: .8; }
|
.mt-card-src { opacity: .8; }
|
||||||
|
|||||||
@@ -1311,7 +1311,9 @@ const DB_CONFIG=(()=>{try{const el=document.getElementById('db-config');const v=
|
|||||||
if (!peekEl.classList.contains('open')) return;
|
if (!peekEl.classList.contains('open')) return;
|
||||||
if (peekTitle) peekTitle.textContent = '';
|
if (peekTitle) peekTitle.textContent = '';
|
||||||
if (peekLoading) peekLoading.style.display = 'none';
|
if (peekLoading) peekLoading.style.display = 'none';
|
||||||
if (iframe) iframe.src = '/pages/'+pid;
|
// `?embed=1` : uniquement le document edite, pas l'habillage
|
||||||
|
// (sidebar + barre) — la pleine page reste via le bouton « full ».
|
||||||
|
if (iframe) { iframe.dataset.page = pid; iframe.src = '/pages/'+pid+'?embed=1'; }
|
||||||
})
|
})
|
||||||
.catch(function(){
|
.catch(function(){
|
||||||
// L'échec est rendu DANS le panneau (srcdoc) : sous le build
|
// L'échec est rendu DANS le panneau (srcdoc) : sous le build
|
||||||
@@ -1327,8 +1329,10 @@ const DB_CONFIG=(()=>{try{const el=document.getElementById('db-config');const v=
|
|||||||
if (pc) pc.addEventListener('click', closeDbPeek);
|
if (pc) pc.addEventListener('click', closeDbPeek);
|
||||||
var pf = document.getElementById('db-side-peek-full');
|
var pf = document.getElementById('db-side-peek-full');
|
||||||
if (pf) pf.addEventListener('click', function() {
|
if (pf) pf.addEventListener('click', function() {
|
||||||
if (peekFrame && peekFrame.src && peekFrame.src !== 'about:blank') {
|
// `dataset.page` et non `iframe.src` : le src porte `?embed=1`,
|
||||||
window.location.href = peekFrame.src;
|
// la pleine page doit garder sa sidebar et sa barre.
|
||||||
|
if (peekFrame && peekFrame.dataset.page) {
|
||||||
|
window.location.href = '/pages/' + peekFrame.dataset.page;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
document.addEventListener('keydown', function(e) {
|
document.addEventListener('keydown', function(e) {
|
||||||
|
|||||||
@@ -979,9 +979,9 @@ _renderIcon(item) {
|
|||||||
if (loading) loading.style.display = '';
|
if (loading) loading.style.display = '';
|
||||||
frame.onload = function () { if (loading) loading.style.display = 'none'; };
|
frame.onload = function () { if (loading) loading.style.display = 'none'; };
|
||||||
frame.src = 'about:blank';
|
frame.src = 'about:blank';
|
||||||
// `/pages/{id}` sans `?embed=1` : le peek affiche le MÊME éditeur que la
|
// `?embed=1` : le peek affiche SEULEMENT le document en mode édition
|
||||||
// page pleine (sidebar, barre, actions compris), pas une variante.
|
// (ni sidebar ni barre de navigation) — le cadre du panneau suffit.
|
||||||
frame.src = '/pages/' + item.id;
|
frame.src = '/pages/' + item.id + '?embed=1';
|
||||||
},
|
},
|
||||||
|
|
||||||
closePeek() {
|
closePeek() {
|
||||||
|
|||||||
@@ -395,12 +395,12 @@ const _wsInitData = (function() { // lexical : NON propriété globalThis →
|
|||||||
window.showToast('Failed to copy', 'error');
|
window.showToast('Failed to copy', 'error');
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
// Load the full-screen editor in the iframe (no `?embed=1`: the peek
|
// Load ONLY the editable document in the iframe (`?embed=1`: no
|
||||||
// shows the same editor as the page itself). `about:blank` first, so
|
// sidebar, no navbar). `about:blank` first, so opening another
|
||||||
// opening another document really reloads the frame instead of keeping
|
// document really reloads the frame instead of keeping the previous
|
||||||
// the previous one when the URL happens to be identical.
|
// one when the URL happens to be identical.
|
||||||
var iframe = document.getElementById('ws-preview-iframe');
|
var iframe = document.getElementById('ws-preview-iframe');
|
||||||
if (iframe) { iframe.src = 'about:blank'; iframe.src = '/pages/' + node.id; }
|
if (iframe) { iframe.src = 'about:blank'; iframe.src = '/pages/' + node.id + '?embed=1'; }
|
||||||
// Show panel
|
// Show panel
|
||||||
panel.style.display = 'flex';
|
panel.style.display = 'flex';
|
||||||
requestAnimationFrame(function() {
|
requestAnimationFrame(function() {
|
||||||
|
|||||||
+12
-6
@@ -152,9 +152,10 @@ function flowDeckMyTasks() {
|
|||||||
f.style.display = '';
|
f.style.display = '';
|
||||||
// `about:blank` d'abord : ouvrir une autre tâche doit REcharger
|
// `about:blank` d'abord : ouvrir une autre tâche doit REcharger
|
||||||
// l'éditeur, sinon l'URL identique laisse le document précédent
|
// l'éditeur, sinon l'URL identique laisse le document précédent
|
||||||
// affiché. `/pages/{id}` sans `?embed=1` = l'éditeur plein écran.
|
// affiché. `?embed=1` = le document en édition seule, sans
|
||||||
|
// sidebar ni barre de navigation (le cadre du panneau suffit).
|
||||||
f.src = 'about:blank';
|
f.src = 'about:blank';
|
||||||
f.src = '/pages/' + pid;
|
f.src = '/pages/' + pid + '?embed=1';
|
||||||
}).catch(function (e) {
|
}).catch(function (e) {
|
||||||
if (loading) {
|
if (loading) {
|
||||||
loading.textContent = 'Impossible d’ouvrir le contenu : ' + e.message;
|
loading.textContent = 'Impossible d’ouvrir le contenu : ' + e.message;
|
||||||
@@ -389,6 +390,7 @@ function flowDeckMyTasks() {
|
|||||||
h += '<div class="mt-card" draggable="true" data-id="' + t.id + '">' +
|
h += '<div class="mt-card" draggable="true" data-id="' + t.id + '">' +
|
||||||
'<span class="mt-icon">' + esc(rowIcon(t.icon)) + '</span>' +
|
'<span class="mt-icon">' + esc(rowIcon(t.icon)) + '</span>' +
|
||||||
'<span class="mt-card-title">' + esc(t.title) + '</span>' +
|
'<span class="mt-card-title">' + esc(t.title) + '</span>' +
|
||||||
|
openButton(t) +
|
||||||
(t.due ? '<span class="mt-card-due mt-due-' + esc(t.due_state) + '">📅 ' +
|
(t.due ? '<span class="mt-card-due mt-due-' + esc(t.due_state) + '">📅 ' +
|
||||||
esc(t.due) + '</span>' : '') +
|
esc(t.due) + '</span>' : '') +
|
||||||
'<span class="mt-card-src">' + esc(t.collection_icon) + ' ' +
|
'<span class="mt-card-src">' + esc(t.collection_icon) + ' ' +
|
||||||
@@ -878,17 +880,21 @@ function flowDeckMyTasks() {
|
|||||||
var t = e.target;
|
var t = e.target;
|
||||||
|
|
||||||
if (t.id === 'mt-new-btn') { openNewModal(); return; }
|
if (t.id === 'mt-new-btn') { openNewModal(); return; }
|
||||||
if (t.dataset && t.dataset.openRow) {
|
var opener = t.closest ? t.closest('[data-open-row]') : null;
|
||||||
|
if (opener) {
|
||||||
|
// `closest` : dans le calendrier le bouton ne contient que son
|
||||||
|
// SVG — un clic sur l'icone donnait `e.target` = <svg> sans
|
||||||
|
// dataset, et le bouton ne faisait rien.
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
// Le titre vient de l'attribut : il est disponible aussi bien dans
|
// Le titre vient de l'attribut : il est disponible aussi bien dans
|
||||||
// le tableau que dans le calendrier ou la vue Jour.
|
// le tableau que dans le calendrier ou la vue Jour.
|
||||||
var label = t.dataset.openTitle;
|
var label = opener.dataset.openTitle;
|
||||||
if (!label) {
|
if (!label) {
|
||||||
var row = t.closest('.mt-row');
|
var row = opener.closest('.mt-row');
|
||||||
var cell = row && row.querySelector('.mt-title-text');
|
var cell = row && row.querySelector('.mt-title-text');
|
||||||
label = cell ? cell.textContent : '';
|
label = cell ? cell.textContent : '';
|
||||||
}
|
}
|
||||||
openRowContent(Number(t.dataset.openRow), label);
|
openRowContent(Number(opener.dataset.openRow), label);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (t.dataset && t.dataset.fixSource) {
|
if (t.dataset && t.dataset.fixSource) {
|
||||||
|
|||||||
@@ -1388,13 +1388,23 @@ def test_library_peek_iframe_is_not_inside_the_x_if_template():
|
|||||||
assert at > html.index("</template>"), "l'iframe doit être hors du x-if"
|
assert at > html.index("</template>"), "l'iframe doit être hors du x-if"
|
||||||
|
|
||||||
|
|
||||||
def test_library_peek_loads_the_full_screen_editor():
|
def test_library_peek_loads_the_embed_editor_only():
|
||||||
"""Le peek charge `/pages/{id}` SANS `?embed=1` : exactement l'éditeur
|
"""Le peek charge `/pages/{id}?embed=1` : le document en mode édition
|
||||||
de la page entière (le mode embed masquait sidebar, barre et actions)."""
|
SEUL — le cadre du panneau suffit, ni sidebar ni barre de navigation."""
|
||||||
body = _js_func(_library_js(), "openPeek(item) {")
|
body = _js_func(_library_js(), "openPeek(item) {")
|
||||||
|
|
||||||
assert "frame.src = '/pages/' + item.id" in body
|
assert "frame.src = '/pages/' + item.id + '?embed=1'" in body
|
||||||
assert "embed=1" not in _strip_js_comments(body)
|
|
||||||
|
|
||||||
|
def test_every_side_peek_loads_the_embed_editor_only():
|
||||||
|
"""Library, My Tasks, bases et workspace local : le peek ne montre QUE le
|
||||||
|
document éditable (`?embed=1`), jamais l'habillage pleine page."""
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
for name in ("library.js", "my_tasks.js", "database_table.js",
|
||||||
|
"local_workspace.js"):
|
||||||
|
src = _strip_js_comments((Path("static/js") / name).read_text(encoding="utf-8"))
|
||||||
|
assert "?embed=1" in src, name
|
||||||
|
|
||||||
|
|
||||||
def test_library_peek_reloads_when_switching_documents():
|
def test_library_peek_reloads_when_switching_documents():
|
||||||
@@ -1419,16 +1429,6 @@ def test_close_peek_unloads_the_editor_iframe():
|
|||||||
assert "frame.onload = null" in body
|
assert "frame.onload = null" in body
|
||||||
|
|
||||||
|
|
||||||
def test_every_side_peek_loads_the_plain_page_url():
|
|
||||||
"""Library, My Tasks, bases et workspace local : même éditeur partout."""
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
for name in ("library.js", "my_tasks.js", "database_table.js",
|
|
||||||
"local_workspace.js"):
|
|
||||||
src = (Path("static/js") / name).read_text(encoding="utf-8")
|
|
||||||
assert "?embed=1" not in _strip_js_comments(src), name
|
|
||||||
|
|
||||||
|
|
||||||
def test_calendar_offers_five_views():
|
def test_calendar_offers_five_views():
|
||||||
"""Mois / Année / Semaine / Jour / Planning."""
|
"""Mois / Année / Semaine / Jour / Planning."""
|
||||||
js = _my_tasks_js()
|
js = _my_tasks_js()
|
||||||
@@ -1451,6 +1451,40 @@ def test_calendar_events_have_a_side_peek_icon():
|
|||||||
assert "data-open-title=" in body
|
assert "data-open-title=" in body
|
||||||
|
|
||||||
|
|
||||||
|
def test_open_buttons_resolve_with_closest_not_target():
|
||||||
|
"""Le gestionnaire de clic lit `data-open-row` via `closest` : les boutons
|
||||||
|
du calendrier et du Kanban ne contiennent que leur SVG — un clic sur
|
||||||
|
l'icone (e.target = <svg>, sans dataset) doit ouvrir le peek aussi."""
|
||||||
|
js = _my_tasks_js()
|
||||||
|
i = js.index("app.addEventListener('click'")
|
||||||
|
block = js[i:i + 900]
|
||||||
|
assert "closest('[data-open-row]')" in block
|
||||||
|
|
||||||
|
|
||||||
|
def test_kanban_cards_have_the_open_button():
|
||||||
|
"""Les tuiles du Kanban portent le même bouton « Open » que les lignes
|
||||||
|
du tableau (parité avec la page d'origine : même peek latéral)."""
|
||||||
|
body = _js_func(_my_tasks_js(), "function viewBoard(")
|
||||||
|
|
||||||
|
assert "openButton(t)" in body
|
||||||
|
# Le bouton est révélé au survol de la tuile (overlay, la carte est une
|
||||||
|
# colonne flex — pas de place en flux).
|
||||||
|
css = _my_tasks_css()
|
||||||
|
assert ".mt-card:hover .mt-hover-only" in css
|
||||||
|
|
||||||
|
|
||||||
|
def test_base_page_collection_renders_the_table_when_embedded():
|
||||||
|
"""`/pages/{id}?embed=1` sur une page de base garde le TABLEAU (template
|
||||||
|
collection) — le peek d'une base ouvre le tableau sans sidebar ni barre,
|
||||||
|
pas l'éditeur de blocs vide."""
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
src = Path("app/routers/dashboard/pages_html.py").read_text(encoding="utf-8")
|
||||||
|
i = src.index('content_format") == "collection"')
|
||||||
|
branch = src[i:i + 220]
|
||||||
|
assert "not embed" not in branch.split("template")[0]
|
||||||
|
|
||||||
|
|
||||||
def test_open_button_carries_its_title_as_attribute():
|
def test_open_button_carries_its_title_as_attribute():
|
||||||
"""Le titre est lu depuis `dataset.openTitle` : disponible aussi dans le
|
"""Le titre est lu depuis `dataset.openTitle` : disponible aussi dans le
|
||||||
calendrier et la vue Jour, où il n'y a pas de ligne `.mt-row`."""
|
calendrier et la vue Jour, où il n'y a pas de ligne `.mt-row`."""
|
||||||
@@ -1520,4 +1554,6 @@ def test_db_side_peek_reports_resolution_failure_in_the_panel():
|
|||||||
|
|
||||||
assert "srcdoc" in block, "erreur rendue dans le panneau"
|
assert "srcdoc" in block, "erreur rendue dans le panneau"
|
||||||
assert "Impossible d’ouvrir le contenu" in block
|
assert "Impossible d’ouvrir le contenu" in block
|
||||||
assert block.index("srcdoc") < block.index("panel.style.display = 'flex'")
|
# Le panneau (révélé par `classList.add('open')` depuis la réécriture
|
||||||
|
# vanilla JS de v7.49.0) doit s'afficher APRÈS l'injection du message.
|
||||||
|
assert block.index("srcdoc") < block.index("classList.add('open')")
|
||||||
|
|||||||
Reference in New Issue
Block a user