Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7e068baed |
+27
-1
@@ -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é
|
||||
|
||||
+3
-3
@@ -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.
|
||||
|
||||
[]()
|
||||
[]()
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://www.docker.com/)
|
||||
[](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*
|
||||
|
||||
@@ -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.
|
||||
|
||||
[]()
|
||||
[]()
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://www.docker.com/)
|
||||
[](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*
|
||||
|
||||
Generated
+1
-1
@@ -2626,7 +2626,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "obsigate-desktop"
|
||||
version = "2.11.3"
|
||||
version = "2.11.4"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"env_logger",
|
||||
|
||||
+1
-1
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 `<embed type="application/pdf">` à `<iframe>` (autorisée par `frame-src 'self'`, le stream étant same-origin). Tests : `tests/frontend/pdf-viewer.test.mjs` (+6) et `tests/e2e/pdf-viewer.spec.js` (fixture `test_vault/sample-pdf.pdf`) | Cause : la CSP durcie en BUG-034 pose `object-src 'none'`, directive qui gouverne `<embed>`/`<object>` → le lecteur PDF natif était bloqué (barre d'outils rendue, corps vide). Le test E2E échoue bien avec l'ancien `<embed>`. `object-src 'none'` conservé (le correctif ne désarme pas la CSP) |
|
||||
| *BUG-061* | [🟡 IMPORTANT] Assistant IA : le bouton « Plein écran » n'agrandit plus le panneau | 🟢 corrigé | P2 | 📱 frontend | IA | `frontend/style.css`, `tests/frontend/ai.test.mjs` | Ouvrir l'assistant, redimensionner le panneau, puis cliquer « Plein écran » | La largeur du panneau est écrite en ligne par la poignée de redimensionnement / la largeur persistée (`localStorage`) ; l'inline l'emportait sur `.bookslm-panel.fullscreen { width: 100vw }`. Ajout de `!important` sur la règle plein écran. Tests : `ai.test.mjs` (+1 : classe basculée + règle CSS). Vérifié Playwright : 640 px → 1400 px (viewport) |
|
||||
| *BUG-062* | [🟡 IMPORTANT] Viewer PDF : le document ne prend pas toute la largeur quand la navigation est masquée | 🟢 corrigé | P2 | 📱 frontend | IA | `frontend/style.css`, `tests/frontend/pdf-viewer.test.mjs`, `tests/e2e/pdf-viewer.spec.js` | Ouvrir un PDF puis masquer la barre de navigation gauche | La règle `.sidebar.hidden ~ .content-wrapper .content-area { max-width: 1200px }` (colonne de lecture centrée) s'appliquait aussi aux viewers plein cadre. Ajout de `.content-area:has(.pdf-viewer-container)` (et `.image-viewer-container`) avec `max-width: none; margin: 0`. Test E2E : `max-width` calculé = `none`, conteneur = largeur du contenu |
|
||||
| *BUG-063* | [🟡 IMPORTANT] Viewer PDF : la table des matières s'affiche mais ne navigue pas | 🟢 corrigé | P1 | 📱 frontend | IA | `frontend/js/viewer.js`, `tests/frontend/pdf-viewer.test.mjs`, `tests/e2e/pdf-viewer.spec.js` (fixture `test_vault/sample-pdf-toc.pdf`) | Ouvrir un PDF avec signets, puis cliquer une entrée de la TOC | Les liens faisaient `contentWindow.location.hash='page=N'` : le lecteur PDF natif vit dans une fenêtre `about:blank`, l'affectation n'atteint jamais le document. Nouveau `navigatePdfToPage()` (liens `data-page` + listeners, plus d'`onclick` inline) qui recharge l'iframe avec `#page=N`. Test E2E : `src` se termine par `#page=3` | Le hash du `contentWindow` restait sur `about:blank#page=N` sans effet |
|
||||
| | | | | | | | | | | |
|
||||
|
||||
### TODOs techniques (améliorations / nouvelles tâches)
|
||||
@@ -233,6 +236,7 @@ Avant de corriger quoi que ce soit, un agent IA doit :
|
||||
| 2026-09-17 | BUG-059 | Correction | `frontend/js/bookslm.js`, `tests/frontend/ai.test.mjs`, `CHANGELOG.md`, `docs/ISSUES_TODOLIST.md` | **BUG-059** : dans une conversation ouverte (post ancré en haut), **tout clic** dans la fenêtre de messages — lien de fichier, étapes, sélection de texte — faisait sauter toute la conversation au bas de la fenêtre. Cause : le gestionnaire `mousedown` de dépintage (prévu pour la molette/tactile/poignée de scroll) se déclenchait aussi sur un simple clic, et le retrait du padding d'ancre (`paddingBottom`) bornait le `scrollTop` à la nouvelle hauteur max → saut au bas. Correctif : helper pur `isScrollbarPress(target, clientX, container)` — un appui ne dépine que s'il vise la **poignée de scroll** (cible = conteneur + zone de gouttière droite) ; molette et tactile conservent leur comportement. Vérifié : `ai.test.mjs` 92/92 (+1), unit 9/9, validate-imports 38 modules, pytest / ruff / mypy inchangés côté backend. | 🟢 corrigé (en attente vérif utilisateur) |
|
||||
| 2026-09-17 | BUG-060 | Correction | `frontend/js/viewer.js`, `.gitea/workflows/ci.yml`, `tests/frontend/pdf-viewer.test.mjs` (nouveau), `tests/e2e/pdf-viewer.spec.js` (nouveau), `test_vault/sample-pdf.pdf` (nouveau), `CHANGELOG.md`, `docs/ISSUES_TODOLIST.md` | **BUG-060** : l'ouverture d'un PDF n'affichait aucune page (barre d'outils « PDF — N pages » présente, corps vide). Cause : la CSP durcie en BUG-034 pose `object-src 'none'` — directive qui gouverne `<embed>`/`<object>` — alors que le viewer rendait le PDF via `<embed type="application/pdf">` : le lecteur natif était bloqué. Correctif : rendu dans une `<iframe>` (autorisée par `frame-src 'self'`, le stream `/api/file/{vault}/pdf/stream` étant same-origin) ; `object-src 'none'` conservé. Tests : `pdf-viewer.test.mjs` 6/6 (statique : pas d'`<embed>`, CSP `frame-src 'self'`, iframe pleine hauteur), `pdf-viewer.spec.js` (E2E : iframe + stream `application/pdf` 200/206 + zéro violation CSP ; échoue bien avec l'ancien `<embed>`). Vérifié : pytest 1184 passed / 6 skipped, frontend 14 suites JSDOM vertes, validate-imports 38 modules, ruff/mypy 0. | 🟢 corrigé (en attente vérif utilisateur) |
|
||||
| 2026-09-17 | BUG-035, BUG-036, BUG-037, BUG-038, BUG-039, BUG-040 | Correction | `backend/secret_redactor.py`, `backend/collab.py`, `backend/auth/{middleware,password,router}.py`, `backend/indexer.py`, `backend/main.py`, `tests/test_api_main.py`, `tests/test_auth.py`, `tests/test_auth_api.py`, `tests/test_collab.py`, `tests/test_pdf.py`, `CHANGELOG.md`, `docs/ISSUES_TODOLIST.md` | **Lot de 6 bugs mineurs (P2/P3)** : BUG-035 masquage hex conditionné au contexte (git/SHA épargnés) ; BUG-036 jeton WebSocket cookie-only (plus de `?token=`) + plafond de trame 16 Mio ; BUG-037 garde-fou au démarrage (refus d'un bind public sans auth sauf `OBSIGATE_ALLOW_INSECURE=true`) ; BUG-038 Argon2 recalibré 19 Mio/t=2/p=1 ; BUG-039 login uniforme 401 (fini 429/403 distinctifs) ; BUG-040 extraction PDF différée via `enrich_pdf_texts()`. Vérifié : pytest 1204 passed / 6 skipped, ruff 0, mypy 0 (77 fichiers), frontend validate-imports 38 modules + unit 9/9. | 🟢 corrigé (en attente vérif utilisateur) |
|
||||
| 2026-09-17 | BUG-061, BUG-062, BUG-063 | Correction | `frontend/style.css`, `frontend/js/viewer.js`, `tests/frontend/ai.test.mjs`, `tests/frontend/pdf-viewer.test.mjs`, `tests/e2e/pdf-viewer.spec.js`, `test_vault/sample-pdf-toc.pdf` (nouveau), `CHANGELOG.md`, `docs/ISSUES_TODOLIST.md` | **Viewer PDF & assistant IA** : BUG-061 le bouton plein écran du panneau assistant l'emportait mal sur la largeur inline (redimensionnement/persistée) → `width: 100vw !important` ; BUG-062 le plafond de lecture 1200 px s'appliquait au PDF quand la navigation était masquée → `:has(.pdf-viewer-container)` en `max-width:none` ; BUG-063 la TOC PDF ne naviguait pas (`contentWindow` = `about:blank`) → `navigatePdfToPage()` recharge l'iframe avec `#page=N`, liens `data-page` sans `onclick` inline. Vérifié : `ai.test.mjs` 93/93, `pdf-viewer.test.mjs` 8/8, validate-imports 38 modules (311 exports), unit 9/9, E2E `pdf-viewer.spec.js` 3/3, et Playwright sur l'instance de test (plein écran 640→1400 px, `src` → `#page=3`, `max-width:none`). | 🟢 corrigé (en attente vérif utilisateur) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# ObsiGate — Roadmap
|
||||
|
||||
> **Version :** 2.11.3 | **Dernière mise à jour :** 2026-09-17
|
||||
> **Version :** 2.11.4 | **Dernière mise à jour :** 2026-09-17
|
||||
> **Ce fichier ne contient que le travail à venir** (🔵 En cours + ⚪ Backlog) et un index compact
|
||||
> vers les fonctionnalités livrées.
|
||||
> - **Méthode de livraison à appliquer pour toute tâche : [DELIVERY_WORKFLOW.md](./DELIVERY_WORKFLOW.md)**
|
||||
|
||||
+27
-2
@@ -518,6 +518,25 @@ function applyPrettyHighlight(codeEl, lang, text) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Jump the inline PDF viewer to a specific page.
|
||||
*
|
||||
* The browser's built-in PDF viewer lives in an ``about:blank`` content window,
|
||||
* so assigning ``contentWindow.location.hash`` never reaches the document (the
|
||||
* TOC links used to do exactly that and did nothing). Reloading the iframe with
|
||||
* the ``#page=N`` fragment is the supported way to target a page.
|
||||
*
|
||||
* @param {HTMLElement} area - Content area containing the ``.pdf-iframe``.
|
||||
* @param {string|number} page - 1-based page number from the PDF outline.
|
||||
*/
|
||||
export function navigatePdfToPage(area, page) {
|
||||
const iframe = area && area.querySelector('.pdf-iframe');
|
||||
if (!iframe || page === null || page === undefined || page === '') return;
|
||||
const base = iframe.getAttribute('data-pdf-url') || iframe.src.split('#')[0];
|
||||
iframe.setAttribute('data-pdf-url', base);
|
||||
iframe.src = `${base}#page=${page}`;
|
||||
}
|
||||
|
||||
export function renderFile(data) {
|
||||
// #93 — An inline edition session (#editor-container mounted in the content
|
||||
// area) is destroyed by this very re-render: release it first so the editor
|
||||
@@ -537,7 +556,7 @@ export function renderFile(data) {
|
||||
tocHtml = '<div class="pdf-toc"><h3>Table des matières</h3><ul>';
|
||||
for (const item of toc) {
|
||||
const indent = (item.level - 1) * 16;
|
||||
tocHtml += `<li style="padding-left:${indent}px"><a href="#" onclick="document.querySelector('.pdf-iframe').contentWindow.location.hash='page=${item.page}';return false">${escapeHtml(item.title)}</a> <span class="toc-page">p.${item.page}</span></li>`;
|
||||
tocHtml += `<li style="padding-left:${indent}px"><a href="#" data-page="${item.page}">${escapeHtml(item.title)}</a> <span class="toc-page">p.${item.page}</span></li>`;
|
||||
}
|
||||
tocHtml += '</ul></div>';
|
||||
}
|
||||
@@ -555,9 +574,15 @@ export function renderFile(data) {
|
||||
</div>
|
||||
<div class="pdf-body">
|
||||
${tocHtml}
|
||||
<iframe src="${pdfUrl}" class="pdf-iframe" title="${escapeHtml(data.title)}"></iframe>
|
||||
<iframe src="${pdfUrl}" data-pdf-url="${pdfUrl}" class="pdf-iframe" title="${escapeHtml(data.title)}"></iframe>
|
||||
</div>
|
||||
</div>`;
|
||||
area.querySelectorAll('.pdf-toc a[data-page]').forEach((link) => {
|
||||
link.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
navigatePdfToPage(area, link.getAttribute('data-page'));
|
||||
});
|
||||
});
|
||||
lucide.createIcons();
|
||||
return;
|
||||
}
|
||||
|
||||
+11
-1
@@ -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; }
|
||||
|
||||
+1
-1
@@ -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": {
|
||||
|
||||
@@ -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=/&<K"<EU]9[SSm*P9LuAr1A`Y./=ub]S+JZn3-Xqn.)>^t)3an^%I4h`&g_Y!<hT~>endstream
|
||||
endobj
|
||||
14 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 124
|
||||
>>
|
||||
stream
|
||||
GapQh0E=F,0U\H3T\pNYT^QKk?tc>IP,;W#U1^23ihPEM_?CW4KISi<![7`#OB_qus.nXJpV`4oKb/`HKs]']P1$(("^Qh6`:R"4,>ElR/;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_?CW4KISi<![7`#OB_qus.nXJpV`4oKb/`HKs]']P1$(("^Qh6`:R"4@nhZ=/;4WeODY4S!3TQDK)~>endstream
|
||||
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
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
|
||||
@@ -39,12 +39,12 @@ test("viewer.js — PDF branch renders the stream in an <iframe class=pdf-iframe
|
||||
assert.ok(block, "PDF render block not found");
|
||||
assert.match(
|
||||
block[1],
|
||||
/<iframe src="\$\{pdfUrl\}" class="pdf-iframe"/,
|
||||
/<iframe src="\$\{pdfUrl\}" data-pdf-url="\$\{pdfUrl\}" class="pdf-iframe"/,
|
||||
"PDF must use <iframe>, not <embed>/<object> (CSP object-src 'none' otherwise blocks it)",
|
||||
);
|
||||
assert.match(
|
||||
block[1],
|
||||
/\.pdf-iframe[\s\S]*?src="\$\{pdfUrl\}"/,
|
||||
/<iframe src="\$\{pdfUrl\}"/,
|
||||
"iframe src must come from the /pdf/stream URL",
|
||||
);
|
||||
});
|
||||
@@ -54,12 +54,37 @@ test("viewer.js — no <embed>/<object> left in the source", () => {
|
||||
assert.doesNotMatch(viewer, /<object\b/i, "<object> is blocked by CSP object-src 'none'");
|
||||
});
|
||||
|
||||
test("viewer.js — TOC still targets the pdf-iframe via contentWindow", () => {
|
||||
test("viewer.js — TOC links carry a data-page and never poke contentWindow", () => {
|
||||
assert.match(
|
||||
viewer,
|
||||
/document\.querySelector\('\.pdf-iframe'\)\.contentWindow\.location\.hash='page=\$\{item\.page\}'/,
|
||||
"TOC links must keep navigating the iframe",
|
||||
/<a href="#" data-page="\$\{item\.page\}">/,
|
||||
"TOC links must expose the target page via data-page",
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
viewer,
|
||||
/contentWindow\.location\.hash\s*=/,
|
||||
"contentWindow is about:blank in the native PDF viewer: hash navigation never reaches the document",
|
||||
);
|
||||
});
|
||||
|
||||
test("viewer.js — navigatePdfToPage reloads the iframe with the #page fragment", () => {
|
||||
const fn = viewer.match(/export function navigatePdfToPage\(area, page\) \{([\s\S]*?)\n\}/);
|
||||
assert.ok(fn, "navigatePdfToPage helper not found");
|
||||
assert.match(fn[1], /data-pdf-url/, "the base URL must be preserved without the fragment");
|
||||
assert.match(fn[1], /iframe\.src = `\$\{base\}#page=\$\{page\}`/, "the iframe must be reloaded with #page=N");
|
||||
});
|
||||
|
||||
test("style.css — full-bleed viewers ignore the centered reading width", () => {
|
||||
assert.match(
|
||||
css,
|
||||
/\.sidebar\.hidden ~ \.content-wrapper \.content-area:has\(\.pdf-viewer-container\)/,
|
||||
"PDF viewer must fill the width when the navigation sidebar is hidden",
|
||||
);
|
||||
const rule = css.match(
|
||||
/\.content-area:has\(\.pdf-viewer-container\)[\s\S]*?\{([^}]*)\}/,
|
||||
);
|
||||
assert.ok(rule, "full-bleed rule not found");
|
||||
assert.match(rule[1], /max-width:\s*none/, "the 1200px reading cap must be lifted");
|
||||
});
|
||||
|
||||
// ── backend : la CSP autorise le cadre same-origin ─────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user