From f7e068baed4c0a95f7021e8211dd6a9c8ddc15fc Mon Sep 17 00:00:00 2001 From: Bruno Charest Date: Thu, 17 Sep 2026 20:45:31 -0400 Subject: [PATCH] fix: viewer PDF (TOC, largeur) et plein ecran assistant (BUG-061 a BUG-063) --- CHANGELOG.md | 28 ++++++- README.fr.md | 6 +- README.md | 6 +- VERSION | 2 +- desktop/Cargo.lock | 2 +- desktop/Cargo.toml | 2 +- desktop/tauri.conf.json | 2 +- docs/ISSUES_TODOLIST.md | 4 + docs/ROADMAP.md | 2 +- frontend/js/viewer.js | 29 ++++++- frontend/style.css | 12 ++- package.json | 2 +- test_vault/sample-pdf-toc.pdf | 130 +++++++++++++++++++++++++++++ tests/e2e/pdf-viewer.spec.js | 45 ++++++++++ tests/frontend/ai.test.mjs | 23 +++++ tests/frontend/pdf-viewer.test.mjs | 35 ++++++-- 16 files changed, 309 insertions(+), 21 deletions(-) create mode 100644 test_vault/sample-pdf-toc.pdf diff --git a/CHANGELOG.md b/CHANGELOG.md index e4889e1..3710fff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ Format basé sur [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/), et [Semantic Versioning](https://semver.org/spec/v2.0.0.html). > **En cours de développement** : les changements à venir sont listés dans la section -> [Unreleased](#unreleased). La dernière version livrée est **2.11.3**. +> [Unreleased](#unreleased). La dernière version livrée est **2.11.4**. --- @@ -14,6 +14,32 @@ et [Semantic Versioning](https://semver.org/spec/v2.0.0.html). --- +## [2.11.4] — 2026-09-17 + +### Corrigé + +- **BUG-061 — Assistant IA : le bouton « Plein écran » n'agrandissait plus le panneau** : la + largeur du panneau est écrite en style inline par la poignée de redimensionnement (et par la + largeur persistée en `localStorage`) ; cet inline l'emportait sur la règle + `.bookslm-panel.fullscreen { width: 100vw }`, donc le panneau restait à sa largeur courante. + La règle plein écran est désormais prioritaire (`!important`). Fichier : `frontend/style.css`. +- **BUG-062 — Viewer PDF : largeur incomplète quand la navigation est masquée** : la règle de + colonne de lecture centrée (`.sidebar.hidden … { max-width: 1200px }`) s'appliquait aussi aux + viewers plein cadre. Les conteneurs PDF et image sont maintenant exemptés + (`:has(.pdf-viewer-container)` / `:has(.image-viewer-container)` → `max-width: none`). Fichier : + `frontend/style.css`. +- **BUG-063 — Viewer PDF : la table des matières ne naviguait pas** : les liens faisaient + `contentWindow.location.hash = 'page=N'`, mais le lecteur PDF natif vit dans une fenêtre + `about:blank` et l'affectation n'atteignait jamais le document. Nouveau helper + `navigatePdfToPage()` qui recharge l'iframe avec le fragment `#page=N` ; les entrées portent un + `data-page` et sont câblées par des écouteurs (plus d'`onclick` inline). Fichiers : + `frontend/js/viewer.js`, `frontend/style.css`. +- **Tests** : `tests/frontend/ai.test.mjs` (+1), `tests/frontend/pdf-viewer.test.mjs` (TOC, plein + largeur), `tests/e2e/pdf-viewer.spec.js` (TOC `#page=N`, largeur, fixture + `test_vault/sample-pdf-toc.pdf`). + +--- + ## [2.11.3] — 2026-09-17 ### Corrigé diff --git a/README.fr.md b/README.fr.md index c7b772c..c11e312 100644 --- a/README.fr.md +++ b/README.fr.md @@ -4,7 +4,7 @@ **Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive. -[![Version](https://img.shields.io/badge/Version-2.11.3-blue.svg)]() +[![Version](https://img.shields.io/badge/Version-2.11.4-blue.svg)]() [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/) [![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/) @@ -926,8 +926,8 @@ Ce projet est sous licence **MIT** — voir le fichier [LICENSE](LICENSE) pour l ## 📝 Changelog -Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.11.3). +Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.11.4). --- -*Projet : ObsiGate | Version : 2.11.3 | Dernière mise à jour : Juin 2026* +*Projet : ObsiGate | Version : 2.11.4 | Dernière mise à jour : Juin 2026* diff --git a/README.md b/README.md index 3e7ebe9..abc80a5 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ **Ultra-light web gateway for your Obsidian vaults** — Access, browse, and search all your Obsidian notes from any device via a modern, responsive web interface. -[![Version](https://img.shields.io/badge/Version-2.11.3-blue.svg)]() +[![Version](https://img.shields.io/badge/Version-2.11.4-blue.svg)]() [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/) [![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/) @@ -1095,8 +1095,8 @@ This project is licensed under the **MIT License** - see the [LICENSE](LICENSE) ## 📝 Changelog -See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.11.3). +See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.11.4). --- -*Project: ObsiGate | Version: 2.11.3 | Last updated: May 2026* +*Project: ObsiGate | Version: 2.11.4 | Last updated: May 2026* diff --git a/VERSION b/VERSION index 22e3b6b..7cd5929 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.11.3 +2.11.4 diff --git a/desktop/Cargo.lock b/desktop/Cargo.lock index f973dec..6a781e1 100644 --- a/desktop/Cargo.lock +++ b/desktop/Cargo.lock @@ -2626,7 +2626,7 @@ dependencies = [ [[package]] name = "obsigate-desktop" -version = "2.11.3" +version = "2.11.4" dependencies = [ "chrono", "env_logger", diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index 475941c..e8e2d70 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "obsigate-desktop" -version = "2.11.3" +version = "2.11.4" description = "ObsiGate Desktop — Porte d'entrée native pour vos vaults Obsidian" authors = ["Bruno Charest"] edition = "2021" diff --git a/desktop/tauri.conf.json b/desktop/tauri.conf.json index 99129ee..bad00df 100644 --- a/desktop/tauri.conf.json +++ b/desktop/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://raw.githubusercontent.com/nicedoc/obsigate/main/desktop/tauri.conf.schema.json", "productName": "ObsiGate", - "version": "2.11.3", + "version": "2.11.4", "identifier": "com.obsigate.desktop", "build": { "frontendDist": "../frontend", diff --git a/docs/ISSUES_TODOLIST.md b/docs/ISSUES_TODOLIST.md index 7ee8a8a..20689cd 100644 --- a/docs/ISSUES_TODOLIST.md +++ b/docs/ISSUES_TODOLIST.md @@ -169,6 +169,9 @@ Avant de corriger quoi que ce soit, un agent IA doit : | *BUG-057* | [🟡 IMPORTANT] Assistant IA : le bouton « Ajouter » est inopérant dans l'éditeur Forge (fonctionne seulement dans « Editer ») | 🟢 corrigé | P1 | 📱 frontend | IA | `frontend/js/bookslm.js`, `frontend/editor-poc.html` | Ouvrir un document dans Forge, demander une réponse à l'assistant puis cliquer « Ajouter » | `_insertIntoEditor()` cible Forge (`#forge-iframe`) : `postMessage({ type: 'parent-insert', text })` ; `editor-poc.html` insère au curseur (`insertAtCursor`) et marque le tampon modifié. Repli textarea inclus. Tests : `tests/frontend/ai.test.mjs` (+3), `tests/frontend/editor-inline.test.mjs` (+1) | `state.editorView` (CodeMirror) est nul en Forge : le clic affichait « Aucun document ouvert dans l'éditeur » | | *BUG-058* | [🔵 MINEUR] Éditeur « Editer » : la barre de numérotation de ligne ne suit pas la couleur du thème (gutter clair `#f5f5f5` en thème sombre) | 🟢 corrigé | P2 | 📱 frontend | IA | `frontend/style.css` | Ouvrir un document → Editer en thème sombre : la colonne des numéros de ligne reste gris clair alors que le fond de l'éditeur est sombre | Thème du gutter CodeMirror via les variables CSS (`color-mix(var(--text-primary) …)` pour le fond, `--text-secondary` pour les numéros, `--border` pour la séparation, `--text-primary` pour la ligne active) au lieu des valeurs codées en dur de CodeMirror ; test de non-régression dans `tests/frontend/editor-inline.test.mjs`. Vérifié Playwright (instance de test) : sombre `color(srgb 0.90 0.93 0.95 / 0.05)` + bordure `#21262d`, clair `color(srgb 0.12 0.14 0.16 / 0.05)` + bordure `#d0d7de` | CodeMirror applique `background:#f5f5f5` par défaut, indépendamment du thème ObsiGate ; en mode sombre le fond de l'éditeur suit `--bg-secondary` mais pas le gutter | | *BUG-060* | [🟡 IMPORTANT] Viewer PDF : l'affichage des pages ne fonctionne pas — seule la barre d'outils « PDF — N pages » s'affiche, le contenu reste vide | 🟢 corrigé | P1 | 📱 frontend | IA | `frontend/js/viewer.js`, `tests/frontend/pdf-viewer.test.mjs` (nouveau), `tests/e2e/pdf-viewer.spec.js` (nouveau) | Cliquer un fichier `.pdf` dans l'arborescence | `frontend/js/viewer.js` : le rendu PDF passe de `` à ` + `; + area.querySelectorAll('.pdf-toc a[data-page]').forEach((link) => { + link.addEventListener('click', (e) => { + e.preventDefault(); + navigatePdfToPage(area, link.getAttribute('data-page')); + }); + }); lucide.createIcons(); return; } diff --git a/frontend/style.css b/frontend/style.css index 4ed33e3..7599197 100644 --- a/frontend/style.css +++ b/frontend/style.css @@ -1473,6 +1473,14 @@ select { margin: 0 auto; max-width: 1200px; } + +/* Full-bleed viewers (PDF, images) must use the whole width when the + navigation sidebar is hidden instead of the centered reading column. */ +.sidebar.hidden ~ .content-wrapper .content-area:has(.pdf-viewer-container), +.sidebar.hidden ~ .content-wrapper .content-area:has(.image-viewer-container) { + margin: 0; + max-width: none; +} .content-area::-webkit-scrollbar { width: 8px; } @@ -9492,7 +9500,9 @@ body.popup-mode .content-area { .bookslm-toolbar .ai-picker { margin-left: 0; padding-left: 0; border-left: none; flex-wrap: wrap; row-gap: 4px; } .bookslm-toolbar .ai-picker select { max-width: 220px; } -.bookslm-panel.fullscreen { width: 100vw; } +/* `!important` is required: the panel width is also written inline by the + resize handle / persisted width, and an inline style would otherwise win. */ +.bookslm-panel.fullscreen { width: 100vw !important; } .bookslm-status { padding: 6px 16px; font-size: 12px; color: var(--text-secondary); border-bottom: 1px solid var(--border); display: flex; gap: 12px; align-items: center; } .bookslm-status .bookslm-status-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } diff --git a/package.json b/package.json index 5c1ef4c..7aa0749 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsigate", - "version": "2.11.3", + "version": "2.11.4", "description": "**Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive.", "main": "patch.js", "directories": { diff --git a/test_vault/sample-pdf-toc.pdf b/test_vault/sample-pdf-toc.pdf new file mode 100644 index 0000000..eca236d --- /dev/null +++ b/test_vault/sample-pdf-toc.pdf @@ -0,0 +1,130 @@ +%PDF-1.3 +%“Œ‹ž ReportLab Generated PDF document (opensource) +1 0 obj +<< +/F1 2 0 R +>> +endobj +2 0 obj +<< +/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font +>> +endobj +3 0 obj +<< +/Contents 13 0 R /MediaBox [ 0 0 612 792 ] /Parent 12 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +4 0 obj +<< +/Contents 14 0 R /MediaBox [ 0 0 612 792 ] /Parent 12 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +5 0 obj +<< +/Contents 15 0 R /MediaBox [ 0 0 612 792 ] /Parent 12 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +6 0 obj +<< +/Outlines 8 0 R /PageMode /UseNone /Pages 12 0 R /Type /Catalog +>> +endobj +7 0 obj +<< +/Author (anonymous) /CreationDate (D:20260917204040-04'00') /Creator (anonymous) /Keywords () /ModDate (D:20260917204040-04'00') /Producer (ReportLab PDF Library - \(opensource\)) + /Subject (unspecified) /Title (untitled) /Trapped /False +>> +endobj +8 0 obj +<< +/Count 3 /First 9 0 R /Last 11 0 R /Type /Outlines +>> +endobj +9 0 obj +<< +/Dest [ 3 0 R /Fit ] /Next 10 0 R /Parent 8 0 R /Title (Page One) +>> +endobj +10 0 obj +<< +/Dest [ 4 0 R /Fit ] /Next 11 0 R /Parent 8 0 R /Prev 9 0 R /Title (Page Two) +>> +endobj +11 0 obj +<< +/Dest [ 5 0 R /Fit ] /Parent 8 0 R /Prev 10 0 R /Title (Page Three) +>> +endobj +12 0 obj +<< +/Count 3 /Kids [ 3 0 R 4 0 R 5 0 R ] /Type /Pages +>> +endobj +13 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 122 +>> +stream +Gap@Db6gL2'Lh3!@LZ0U8'7>U;'tH2cm;<+UO9dKg:K5pXY%ILno7bT=/&^t)3an^%I4h`&g_Y!endstream +endobj +14 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 124 +>> +stream +GapQh0E=F,0U\H3T\pNYT^QKk?tc>IP,;W#U1^23ihPEM_?CW4KISiElR/;4WeODY4S!3T'6Jc~>endstream +endobj +15 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 124 +>> +stream +GapQh0E=F,0U\H3T\pNYT^QKk?tc>IP,;W#U1^23ihPEM_?CW4KISiendstream +endobj +xref +0 16 +0000000000 65535 f +0000000061 00000 n +0000000092 00000 n +0000000199 00000 n +0000000394 00000 n +0000000589 00000 n +0000000784 00000 n +0000000869 00000 n +0000001130 00000 n +0000001202 00000 n +0000001289 00000 n +0000001389 00000 n +0000001479 00000 n +0000001551 00000 n +0000001764 00000 n +0000001979 00000 n +trailer +<< +/ID +[<6132df6a3beacba675b566627d60fb2e><6132df6a3beacba675b566627d60fb2e>] +% ReportLab generated PDF document -- digest (opensource) + +/Info 7 0 R +/Root 6 0 R +/Size 16 +>> +startxref +2194 +%%EOF diff --git a/tests/e2e/pdf-viewer.spec.js b/tests/e2e/pdf-viewer.spec.js index 1b4a595..719669d 100644 --- a/tests/e2e/pdf-viewer.spec.js +++ b/tests/e2e/pdf-viewer.spec.js @@ -89,4 +89,49 @@ test.describe('PDF viewer — affichage inline (BUG-060)', () => { // Aucune violation CSP liée à object-src pendant l'ouverture expect(cspViolations).toEqual([]); }); +}); + +test.describe('PDF viewer — TOC & plein largeur', () => { + + test('les entrées de la TOC rechargent l\'iframe sur la page ciblée (#page=N)', async ({ page }) => { + await login(page); + await openFile(page, 'TestVault', 'sample-pdf-toc.pdf'); + + const tocLinks = page.locator('#content-area .pdf-toc a[data-page]'); + await expect(tocLinks).toHaveCount(3, { timeout: 10000 }); + + await page.locator('#content-area .pdf-toc a[data-page="3"]').click(); + await expect(page.locator('#content-area .pdf-iframe')).toHaveAttribute( + 'src', + /\/pdf\/stream\?path=.*#page=3$/, + { timeout: 5000 }, + ); + }); + + test('le PDF occupe toute la largeur quand la navigation est masquée', async ({ page }) => { + await login(page); + await openFile(page, 'TestVault', 'sample-pdf-toc.pdf'); + await expect(page.locator('#content-area .pdf-viewer-container')).toBeVisible({ timeout: 10000 }); + + // Masquer la barre de navigation (bouton réel). + await page.locator('#sidebar-toggle-btn').click(); + await expect(page.locator('#sidebar')).toHaveClass(/hidden/); + + const widths = await page.evaluate(() => { + const area = document.getElementById('content-area'); + const cs = getComputedStyle(area); + const container = document.querySelector('.pdf-viewer-container'); + const contentWidth = + area.clientWidth - parseFloat(cs.paddingLeft) - parseFloat(cs.paddingRight); + return { + container: container.getBoundingClientRect().width, + contentWidth, + maxWidth: cs.maxWidth, + }; + }); + + // Le plafond de lecture (1200px) ne doit plus s'appliquer au viewer PDF. + expect(widths.maxWidth).toBe('none'); + expect(Math.abs(widths.container - widths.contentWidth)).toBeLessThan(2); + }); }); \ No newline at end of file diff --git a/tests/frontend/ai.test.mjs b/tests/frontend/ai.test.mjs index 14059e5..6e5089f 100644 --- a/tests/frontend/ai.test.mjs +++ b/tests/frontend/ai.test.mjs @@ -786,6 +786,29 @@ async function main() { panel.remove(); }); + // ── 10b. Fullscreen toggle must beat the persisted inline width ── + await test("fullscreen button toggles the panel and CSS lifts the inline width", async () => { + const { readFileSync } = await import("node:fs"); + const css = readFileSync(path.resolve(JS_DIR, "..", "style.css"), "utf-8"); + assert.match( + css, + /\.bookslm-panel\.fullscreen\s*\{\s*width:\s*100vw\s*!important;/, + "the fullscreen width must override the inline width written by the resize handle", + ); + + const b = new BooksLM(); + const panel = b._render(); + document.body.appendChild(panel); + const btn = panel.querySelector(".bookslm-btn-fullscreen"); + btn.click(); + assert.ok(panel.classList.contains("fullscreen"), "fullscreen class added on first click"); + assert.equal(b._isFullscreen, true); + btn.click(); + assert.ok(!panel.classList.contains("fullscreen"), "fullscreen class removed on second click"); + assert.equal(b._isFullscreen, false); + panel.remove(); + }); + // ── 11. Formatted markdown rendering ── await test("_renderMarkdown renders headings, lists, code and tables", () => { const b = new BooksLM(); diff --git a/tests/frontend/pdf-viewer.test.mjs b/tests/frontend/pdf-viewer.test.mjs index eb6ffcf..bebc6b3 100644 --- a/tests/frontend/pdf-viewer.test.mjs +++ b/tests/frontend/pdf-viewer.test.mjs @@ -39,12 +39,12 @@ test("viewer.js — PDF branch renders the stream in an