From f53205bc45696e1cd622a6ca6a30b9ea9b206584 Mon Sep 17 00:00:00 2001 From: Bruno Charest Date: Tue, 6 Oct 2026 15:15:01 -0400 Subject: [PATCH] fix: peek des bases hydrate FlowDeckDB + Open de ligne, tableau pleine largeur (v7.50.1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - page_editor_embed.html incluait seulement _page_editor_scripts.html : window.FlowDeckDB etait undefined dans l'iframe -> une base ouverte en side peek restait a « Loading database... ». Include _database_table_scripts.html ajoute (meme traitement que page_editor.html). - Le panneau #db-side-peek n'existait que dans _database_table.html : hors page dediee, openPageInSidePeek etait un no-op. Desormais cree a la demande par ensureDbPeek (database_table.js) ; markup unique retire du partial. - .database-table-container : plafond 1100px retire (regle heritee de Library) + max-width 100% pour .db-first et database-page-title : le tableau prend la largeur du document, plus de barre de scroll parasite sous le tableau. - Tests: test_embed_editor_can_render_inline_databases. Suite 1265/0 (-n auto) + ruff 0. Bump VERSION/main.py 7.50.1, CHANGELOG, WORKLOAD, OpenAPI. --- CHANGELOG.md | 27 ++++ VERSION | 2 +- WORKLOAD.md | 2 +- app/main.py | 2 +- app/templates/_database_table.html | 20 +-- app/templates/page_editor_embed.html | 5 + docs/openapi-v2.json | 2 +- static/css/app.css | 13 +- static/js/database_table.js | 140 +++++++++++---------- tests/test_my_tasks_workspace_coherence.py | 16 +++ 10 files changed, 137 insertions(+), 92 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e71779..5f5fcb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,32 @@ # Changelog - FlowDeck +## v7.50.1 (2026-10-06) — Peek des bases : le contenu se rend enfin + tableau pleine largeur + +### Fixed + +- **Une base ouverte en side peek restait à « Loading database… »** — + `page_editor_embed.html` (le peek de `/pages/{id}?embed=1`) n'incluait pas + `database_table.js` : `window.FlowDeckDB` était undefined dans l'iframe et + le bloc `embed/collection` ne s'hydrat jamais. Inclus désormais (même + traitement que `page_editor.html`). +- **L'icône « Open » de fin de ligne d'un tableau de base ne faisait rien + hors page dédiée** — le panneau `#db-side-peek` n'existait que dans le + markup de `_database_table.html` ; sans lui, `openPageInSidePeek` était un + no-op. Le panneau est créé à la demande par `database_table.js` + (`ensureDbPeek`, comme `mt-peek` de My Tasks) : markup unique, le partial + ne contient plus le panneau statique. +- **Barre de scroll sous le tableau des bases** — `.database-table-container` + plafonnait à 1100 px (règle héritée de Library) : le tableau ne prenait + pas la largeur du document. Plafond retiré ; `max-width: 100%` aussi pour + les conteneurs `.db-first` (base embarquée en premier bloc) et le titre + des pages de base — les colonnes dictent seules le scroll horizontal. + +### Tests + +- `test_embed_editor_can_render_inline_databases` (include peek + panneau + créé à la demande, source unique de markup). Suite 1265 passed / 0 failed + (-n auto) + ruff 0. + ## v7.50.0 (2026-10-06) — Side peek : le document seul (mode édition), sans sidebar ni barre ### Fixed diff --git a/VERSION b/VERSION index cf89fc8..1e6c3f0 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.50.0 +7.50.1 diff --git a/WORKLOAD.md b/WORKLOAD.md index c48de54..ad55298 100644 --- a/WORKLOAD.md +++ b/WORKLOAD.md @@ -1,6 +1,6 @@ # WORKLOAD — FlowDeck Notion Clone -> **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 🔄 +> **Début**: 2026-07-08 | **Version**: v7.50.1 (peek des bases : hydratation FlowDeckDB + Open de ligne, tableau pleine largeur) | **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` ## Avancement Global diff --git a/app/main.py b/app/main.py index 56f27a4..ee0e4ff 100644 --- a/app/main.py +++ b/app/main.py @@ -185,7 +185,7 @@ async def lifespan(_app: FastAPI): app = FastAPI( title="FlowDeck", - version="7.50.0", + version="7.50.1", docs_url="/docs", redoc_url="/redoc", lifespan=lifespan, diff --git a/app/templates/_database_table.html b/app/templates/_database_table.html index bbfb4a5..18a1792 100644 --- a/app/templates/_database_table.html +++ b/app/templates/_database_table.html @@ -125,21 +125,7 @@
→┤ Insert right
- -
-
-
- -
- -
-
-
Chargement…
- -
-
+ diff --git a/app/templates/page_editor_embed.html b/app/templates/page_editor_embed.html index 7b149dd..4035816 100644 --- a/app/templates/page_editor_embed.html +++ b/app/templates/page_editor_embed.html @@ -8,4 +8,9 @@ {% endblock %} {% block scripts %} {% include '_page_editor_scripts.html' %} +{# database_table.js = FlowDeckDB + le panneau de peek des bases : sans cet + include, une base embarquée dans un document restait bloquée à + « Loading database… » dans le peek et l'icône Open de fin de ligne ne + faisait rien. #} +{% include '_database_table_scripts.html' %} {% endblock %} diff --git a/docs/openapi-v2.json b/docs/openapi-v2.json index 751d44a..91ec9e2 100644 --- a/docs/openapi-v2.json +++ b/docs/openapi-v2.json @@ -2,7 +2,7 @@ "openapi": "3.1.0", "info": { "title": "FlowDeck", - "version": "7.50.0" + "version": "7.50.1" }, "paths": { "/auth/register": { diff --git a/static/css/app.css b/static/css/app.css index 77acd5a..e02ae14 100644 --- a/static/css/app.css +++ b/static/css/app.css @@ -2246,13 +2246,15 @@ button.breadcrumb-current { max-width: 260px; } /* Page de base (premier bloc = embed collection) : le titre et le tableau s'alignent sur la marge gauche de la topbar (16px) au lieu d'être centrés - dans la colonne 900px — le tableau démarre au ras du sidebar. */ + dans la colonne 900px — le tableau démarre au ras du sidebar et prend la + LARGEUR DU DOCUMENT (les colonnes dictent le scroll horizontal). */ .page-editor-wrapper.db-first .page-cover-area, .page-editor-wrapper.db-first .page-title-block, .page-editor-wrapper.db-first .blocks-container { - max-width: 1100px; + max-width: 100%; margin: 0; padding-left: var(--space-lg); + padding-right: var(--space-lg); } /* Une base embarquée ne doit jamais déborder de sa colonne de blocs : @@ -2262,7 +2264,7 @@ button.breadcrumb-current { max-width: 260px; } /* Titre des pages de base dédiées (page_editor_collection.html) — même liseré gauche que .database-table-container. */ .database-page-title { - max-width: 1100px; + max-width: 100%; margin: 0; padding: 28px var(--space-lg) 0; font-size: 30px; @@ -3899,11 +3901,10 @@ button.breadcrumb-current { max-width: 260px; } ═══════════════════════════════════════════════════════════ */ /* Base = viewport du contenu ; les vues tabulaires larges (bases de - données) s'alignent à gauche, au ras de la marge, à la même largeur - que Library (1100px). */ + données) prennent la LARGEUR DU DOCUMENT (les colonnes dictent le scroll + horizontal), alignées à gauche au ras de la marge. */ .database-table-container { width: 100%; - max-width: 1100px; margin: 0; /* var(--space-lg) = padding de .topbar : le bord gauche du tableau s'aligne sur le titre du fil d'ariane, au ras de la marge. */ diff --git a/static/js/database_table.js b/static/js/database_table.js index 3c9783d..d089030 100644 --- a/static/js/database_table.js +++ b/static/js/database_table.js @@ -1283,76 +1283,86 @@ const DB_CONFIG=(()=>{try{const el=document.getElementById('db-config');const v= } // ── Side peek : panneau latéral des lignes de base (vanilla JS) ── - // Le panneau vit dans _database_table.html (page de base ET bases - // embarquées inline). Il doit être câblé SUR le document, jamais via - // container.__x : le conteneur n'a pas de x-data (les bindings Alpine - // du panneau restaient inertes — clic sur une ligne = rien). - var peekEl = document.getElementById('db-side-peek'); - // Sans panneau (collections embarquées inline dans l'éditeur), le clic ne - // doit pas lever de ReferenceError : repli discret, comme l'ancien code. - if (!peekEl) window.openPageInSidePeek = function() {}; - if (peekEl) { + // Le panneau vit dans _database_table.html (page de base). Une base + // EMBARQUÉE dans un document n'a pas ce markup : on le crée à la demande + // (comme mt-peek de My Tasks), sinon l'icône Open de fin de ligne était + // un no-op silencieux. Il doit être câblé SUR le document, jamais via + // container.__x : le conteneur n'a pas de x-data. + var PEEK_HTML = + '
' + + '
' + + '' + + '
' + + '' + + '
' + + '
' + + '
Chargement…
' + + '' + + '
'; + function ensureDbPeek() { + var p = document.getElementById('db-side-peek'); + if (p) return p; + p = document.createElement('div'); + p.id = 'db-side-peek'; + p.className = 'db-side-peek'; + p.innerHTML = PEEK_HTML; + document.body.appendChild(p); + document.getElementById('db-side-peek-close').addEventListener('click', closeDbPeek); + document.getElementById('db-side-peek-full').addEventListener('click', function () { + var f = document.getElementById('db-side-peek-iframe'); + // `dataset.page` et non `iframe.src` : le src porte `?embed=1`, + // la pleine page doit garder sa sidebar et sa barre. + if (f && f.dataset.page) window.location.href = '/pages/' + f.dataset.page; + }); + if (window.fdWirePeekResize) window.fdWirePeekResize(p, p.querySelector('.peek-resize-handle'), closeDbPeek); + return p; + } + function closeDbPeek() { + var p = document.getElementById('db-side-peek'); + if (!p) return; + p.classList.remove('open'); + document.getElementById('db-side-peek-iframe').src='about:blank'; + var t = document.getElementById('db-side-peek-title'); + if (t) t.textContent = ''; + } + window.openPageInSidePeek = function(pageId) { + if (!pageId) return; + var peekEl = ensureDbPeek(); var peekFrame = document.getElementById('db-side-peek-iframe'); var peekTitle = document.getElementById('db-side-peek-title'); var peekLoading = document.getElementById('db-side-peek-loading'); - function closeDbPeek() { - peekEl.classList.remove('open'); - document.getElementById('db-side-peek-iframe').src='about:blank'; - if (peekTitle) { peekTitle.textContent = ''; } - } - window.openPageInSidePeek = function(pageId) { - if (!pageId) return; - peekEl.classList.add('open'); - if (peekLoading) peekLoading.style.display = ''; - var iframe = document.getElementById('db-side-peek-iframe'); - if (iframe) iframe.src='about:blank'; - if (peekTitle) peekTitle.textContent = ''; - resolveRowContentPage(pageId).then(function(pid){ - if (!peekEl.classList.contains('open')) return; - if (peekTitle) peekTitle.textContent = ''; - if (peekLoading) peekLoading.style.display = 'none'; - // `?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(){ - // L'échec est rendu DANS le panneau (srcdoc) : sous le build - // CSP d'Alpine les toasts ne s'affichent pas partout. - if (iframe) { - iframe.srcdoc = '

Impossible d’ouvrir le contenu de cette ligne

'; - } - peekEl.classList.add('open'); - toast('Impossible d’ouvrir le contenu de cette ligne', 'error'); - }); - }; - var pc = document.getElementById('db-side-peek-close'); - if (pc) pc.addEventListener('click', closeDbPeek); - var pf = document.getElementById('db-side-peek-full'); - if (pf) pf.addEventListener('click', function() { - // `dataset.page` et non `iframe.src` : le src porte `?embed=1`, - // 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) { - if (e.key === 'Escape' && peekEl.classList.contains('open')) closeDbPeek(); - }); - document.addEventListener('pointerdown', function(e) { + peekEl.classList.add('open'); + if (peekLoading) peekLoading.style.display = ''; + if (peekFrame) peekFrame.src = 'about:blank'; + if (peekTitle) peekTitle.textContent = ''; + resolveRowContentPage(pageId).then(function(pid){ if (!peekEl.classList.contains('open')) return; - if (peekEl.contains(e.target)) return; - if (e.target.closest('#db-table-container, .database-table-container, [id^="inline-db-"]')) return; - closeDbPeek(); - }); - function wireDbPeekResize() { - if (window.fdWirePeekResize) { - window.fdWirePeekResize(peekEl, peekEl.querySelector('.peek-resize-handle'), closeDbPeek); - } else if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', wireDbPeekResize); + if (peekLoading) peekLoading.style.display = 'none'; + // `?embed=1` : uniquement le document edite, pas l'habillage + // (sidebar + barre) — la pleine page reste via le bouton « full ». + if (peekFrame) { peekFrame.dataset.page = pid; peekFrame.src = '/pages/'+pid+'?embed=1'; } + }) + .catch(function(){ + // L'échec est rendu DANS le panneau (srcdoc) : sous le build + // CSP d'Alpine les toasts ne s'affichent pas partout. + if (peekFrame) { + peekFrame.srcdoc = '

Impossible d’ouvrir le contenu de cette ligne

'; } - } - wireDbPeekResize(); - } + peekEl.classList.add('open'); + toast('Impossible d’ouvrir le contenu de cette ligne', 'error'); + }); + }; + document.addEventListener('keydown', function(e) { + var p = document.getElementById('db-side-peek'); + if (e.key === 'Escape' && p && p.classList.contains('open')) closeDbPeek(); + }); + document.addEventListener('pointerdown', function(e) { + var p = document.getElementById('db-side-peek'); + if (!p || !p.classList.contains('open')) return; + if (p.contains(e.target)) return; + if (e.target.closest('#db-table-container, .database-table-container, [id^="inline-db-"]')) return; + closeDbPeek(); + }); function resolveRowContentPage(rowId){ return fetch('/db/pages/'+rowId+'/open/api') diff --git a/tests/test_my_tasks_workspace_coherence.py b/tests/test_my_tasks_workspace_coherence.py index 62ee676..a719b1b 100644 --- a/tests/test_my_tasks_workspace_coherence.py +++ b/tests/test_my_tasks_workspace_coherence.py @@ -1485,6 +1485,22 @@ def test_base_page_collection_renders_the_table_when_embedded(): assert "not embed" not in branch.split("template")[0] +def test_embed_editor_can_render_inline_databases(): + """Le peek (`page_editor_embed.html`) doit charger database_table.js : + sans lui, une base embarquée restait bloquée à « Loading database… » et + l'icône Open de fin de ligne ne faisait rien. Le panneau #db-side-peek + est créé à la demande par database_table.js (markup unique).""" + from pathlib import Path + + html = Path("app/templates/page_editor_embed.html").read_text(encoding="utf-8") + assert "_database_table_scripts.html" in html + js = Path("static/js/database_table.js").read_text(encoding="utf-8") + assert "ensureDbPeek" in js + # Pas de second markup statique du panneau (source unique). + tbl = Path("app/templates/_database_table.html").read_text(encoding="utf-8") + assert 'id="db-side-peek"' not in tbl + + def test_open_button_carries_its_title_as_attribute(): """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`."""