Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e0e419210 |
+20
-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).
|
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
|
> **En cours de développement** : les changements à venir sont listés dans la section
|
||||||
> [Unreleased](#unreleased). La dernière version livrée est **2.46.0**.
|
> [Unreleased](#unreleased). La dernière version livrée est **2.47.0**.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -14,6 +14,25 @@ et [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## [2.47.0] — 2026-10-01
|
||||||
|
|
||||||
|
### Ajouté
|
||||||
|
|
||||||
|
- **#157 — panneau de facettes repliable.** Le bloc **Vaults · Tags · Extensions** des résultats
|
||||||
|
de recherche se masque/restaure d'un clic sur un bouton discret (chevron), état mémorisé
|
||||||
|
dans `localStorage` et conservé d'une recherche à l'autre.
|
||||||
|
|
||||||
|
### Corrigé
|
||||||
|
|
||||||
|
- **#157 — facette « Extensions » absente de l'API.** `SearchFacets` (`backend/schemas.py`)
|
||||||
|
ne déclarait que `tags` et `vaults` : la clé `extensions` ajoutée par `backend/search.py`
|
||||||
|
était supprimée par le `response_model` avant d'atteindre le navigateur. Champ ajouté,
|
||||||
|
plus les tests d'advanced-search pointaient la route inexistante `/api/advanced-search`
|
||||||
|
et sautaient en silence — ils ciblent désormais `/api/search/advanced`, avec une assertion
|
||||||
|
stricte sur `facets.extensions`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [2.46.0] — 2026-10-01
|
## [2.46.0] — 2026-10-01
|
||||||
|
|
||||||
### Ajouté
|
### Ajouté
|
||||||
|
|||||||
+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.
|
**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://opensource.org/licenses/MIT)
|
||||||
[](https://www.docker.com/)
|
[](https://www.docker.com/)
|
||||||
[](https://www.python.org/)
|
[](https://www.python.org/)
|
||||||
@@ -976,8 +976,8 @@ Ce projet est sous licence **MIT** — voir le fichier [LICENSE](LICENSE) pour l
|
|||||||
|
|
||||||
## 📝 Changelog
|
## 📝 Changelog
|
||||||
|
|
||||||
Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.46.0).
|
Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.47.0).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*Projet : ObsiGate | Version : 2.46.0 | Dernière mise à jour : Septembre 2026*
|
*Projet : ObsiGate | Version : 2.47.0 | Dernière mise à jour : Septembre 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.
|
**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://opensource.org/licenses/MIT)
|
||||||
[](https://www.docker.com/)
|
[](https://www.docker.com/)
|
||||||
[](https://www.python.org/)
|
[](https://www.python.org/)
|
||||||
@@ -1151,8 +1151,8 @@ This project is licensed under the **MIT License** - see the [LICENSE](LICENSE)
|
|||||||
|
|
||||||
## 📝 Changelog
|
## 📝 Changelog
|
||||||
|
|
||||||
See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.46.0).
|
See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.47.0).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*Project: ObsiGate | Version: 2.46.0 | Last updated: September 2026*
|
*Project: ObsiGate | Version: 2.47.0 | Last updated: September 2026*
|
||||||
|
|||||||
+2
-1
@@ -646,6 +646,7 @@ class SearchFacets(BaseModel):
|
|||||||
|
|
||||||
tags: dict[str, int] = Field(default_factory=dict)
|
tags: dict[str, int] = Field(default_factory=dict)
|
||||||
vaults: dict[str, int] = Field(default_factory=dict)
|
vaults: dict[str, int] = Field(default_factory=dict)
|
||||||
|
extensions: dict[str, int] = Field(default_factory=dict, description="Counts per file extension, dotless and lowercase (query-ready for the ext: operator)")
|
||||||
|
|
||||||
|
|
||||||
class AdvancedSearchResponse(BaseModel):
|
class AdvancedSearchResponse(BaseModel):
|
||||||
@@ -655,7 +656,7 @@ class AdvancedSearchResponse(BaseModel):
|
|||||||
total: int = Field(description="Total number of matching results")
|
total: int = Field(description="Total number of matching results")
|
||||||
offset: int = Field(description="Current pagination offset")
|
offset: int = Field(description="Current pagination offset")
|
||||||
limit: int = Field(description="Page size")
|
limit: int = Field(description="Page size")
|
||||||
facets: SearchFacets = Field(description="Faceted counts by tag and vault")
|
facets: SearchFacets = Field(description="Faceted counts by tag, vault and file extension")
|
||||||
query_time_ms: float = Field(default=0, description="Server-side query time in milliseconds")
|
query_time_ms: float = Field(default=0, description="Server-side query time in milliseconds")
|
||||||
semantic_available: bool = Field(default=False, description="True when the semantic (embedding) index is ready")
|
semantic_available: bool = Field(default=False, description="True when the semantic (embedding) index is ready")
|
||||||
|
|
||||||
|
|||||||
Generated
+1
-1
@@ -2626,7 +2626,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "obsigate-desktop"
|
name = "obsigate-desktop"
|
||||||
version = "2.46.0"
|
version = "2.47.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "obsigate-desktop"
|
name = "obsigate-desktop"
|
||||||
version = "2.46.0"
|
version = "2.47.0"
|
||||||
description = "ObsiGate Desktop — Porte d'entrée native pour vos vaults Obsidian"
|
description = "ObsiGate Desktop — Porte d'entrée native pour vos vaults Obsidian"
|
||||||
authors = ["Bruno Charest"]
|
authors = ["Bruno Charest"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://raw.githubusercontent.com/nicedoc/obsigate/main/desktop/tauri.conf.schema.json",
|
"$schema": "https://raw.githubusercontent.com/nicedoc/obsigate/main/desktop/tauri.conf.schema.json",
|
||||||
"productName": "ObsiGate",
|
"productName": "ObsiGate",
|
||||||
"version": "2.46.0",
|
"version": "2.47.0",
|
||||||
"identifier": "com.obsigate.desktop",
|
"identifier": "com.obsigate.desktop",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../frontend",
|
"frontendDist": "../frontend",
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ avec :
|
|||||||
- **Normalisation des accents** — `resume` trouve `résumé`, `elephant` trouve `éléphant`.
|
- **Normalisation des accents** — `resume` trouve `résumé`, `elephant` trouve `éléphant`.
|
||||||
- **Stemming français** — les variantes des mots sont rapprochées.
|
- **Stemming français** — les variantes des mots sont rapprochées.
|
||||||
- **Snippets surlignés** — les termes trouvés sont mis en `<mark>` dans l'extrait.
|
- **Snippets surlignés** — les termes trouvés sont mis en `<mark>` dans l'extrait.
|
||||||
- **Facettes** — compteurs par vault, par tag et par extension sur les résultats.
|
- **Facettes** — compteurs par vault, par tag et par extension sur les résultats, panneau
|
||||||
|
repliable d'un clic (chevron, état mémorisé).
|
||||||
- **Pagination** — 50 résultats par page.
|
- **Pagination** — 50 résultats par page.
|
||||||
- **Tri** — par pertinence (TF-IDF) ou par date de modification.
|
- **Tri** — par pertinence (TF-IDF) ou par date de modification.
|
||||||
- **Chips de filtres** — les filtres actifs apparaissent sous forme de puces retirables.
|
- **Chips de filtres** — les filtres actifs apparaissent sous forme de puces retirables.
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
# ObsiGate — Roadmap
|
# ObsiGate — Roadmap
|
||||||
|
|
||||||
> **Version :** 2.46.0 | **Dernière mise à jour :** 2026-10-01
|
> **Version :** 2.47.0 | **Dernière mise à jour :** 2026-10-01
|
||||||
> **Ce fichier ne contient que le travail à venir** (🔵 En cours + ⚪ Backlog) et un index compact
|
> **Ce fichier ne contient que le travail à venir** (🔵 En cours + ⚪ Backlog) et un index compact
|
||||||
> vers les fonctionnalités livrées.
|
> vers les fonctionnalités livrées.
|
||||||
> - **Méthode de livraison à appliquer pour toute tâche : [DELIVERY_WORKFLOW.md](./DELIVERY_WORKFLOW.md)**
|
> - **Méthode de livraison à appliquer pour toute tâche : [DELIVERY_WORKFLOW.md](./DELIVERY_WORKFLOW.md)**
|
||||||
@@ -304,7 +304,7 @@
|
|||||||
| 115 | Viewer — barre d'outils de lecture épinglée au défilement | 2.25.0 | [features/viewer-toolbar-highlight-avatars.md](./features/viewer-toolbar-highlight-avatars.md) |
|
| 115 | Viewer — barre d'outils de lecture épinglée au défilement | 2.25.0 | [features/viewer-toolbar-highlight-avatars.md](./features/viewer-toolbar-highlight-avatars.md) |
|
||||||
| 117 | Configuration — avatars prédéfinis dans le profil utilisateur (12 images) | 2.25.0 | [features/viewer-toolbar-highlight-avatars.md](./features/viewer-toolbar-highlight-avatars.md) |
|
| 117 | Configuration — avatars prédéfinis dans le profil utilisateur (12 images) | 2.25.0 | [features/viewer-toolbar-highlight-avatars.md](./features/viewer-toolbar-highlight-avatars.md) |
|
||||||
| 85 | Refonte architecturale — découpage du monolithe (14 routers, `main.py` 4 827 → ~750 lignes), stores JSON verrouillés, rate-limit SQLite optionnel | 2.27.2→2.27.13 | [features/archi-refonte-85.md](./features/archi-refonte-85.md) |
|
| 85 | Refonte architecturale — découpage du monolithe (14 routers, `main.py` 4 827 → ~750 lignes), stores JSON verrouillés, rate-limit SQLite optionnel | 2.27.2→2.27.13 | [features/archi-refonte-85.md](./features/archi-refonte-85.md) |
|
||||||
| 157 | Recherche — facette « Extensions » dans les résultats (3ᵉ filtre avec Vaults et Tags) | 2.46.0 | [archive](./archive/COMPLETED_v1-v2.md) |
|
| 157 | Recherche — facette « Extensions » dans les résultats (3ᵉ filtre avec Vaults et Tags) + panneau repliable | 2.46.0→2.47.0 | [archive](./archive/COMPLETED_v1-v2.md) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -425,13 +425,15 @@ Le panneau de facettes de la page de résultats de recherche affiche désormais
|
|||||||
**Vaults**, **Tags** et **Extensions**. Le troisième liste les extensions de fichiers présents
|
**Vaults**, **Tags** et **Extensions**. Le troisième liste les extensions de fichiers présents
|
||||||
dans le résultat courant (`.md`, `.xlsx`, `.pdf`, …) avec leur compteur ; un clic ajoute
|
dans le résultat courant (`.md`, `.xlsx`, `.pdf`, …) avec leur compteur ; un clic ajoute
|
||||||
l'opérateur `ext:<type>` à la requête (un `ext:` déjà actif est remplacé, jamais dupliqué).
|
l'opérateur `ext:<type>` à la requête (un `ext:` déjà actif est remplacé, jamais dupliqué).
|
||||||
|
L'ensemble du panneau se replie/rouvre d'un clic sur un bouton discret (chevron), état
|
||||||
|
mémorisé dans `localStorage`.
|
||||||
|
|
||||||
| Aspect | Détail |
|
| Aspect | Détail |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Backend | `backend/search.py::advanced_search` — `facets.extensions` : compteurs par extension normalisée (sans point, en minuscules), triés par fréquence |
|
| Backend | `backend/search.py::advanced_search` — `facets.extensions` : compteurs par extension normalisée (sans point, en minuscules), triés par fréquence · **`backend/schemas.py::SearchFacets`** expose le champ (sinon le `response_model` le supprimait — corrigé après livraison de 2.46.0) |
|
||||||
| Frontend | `frontend/js/search.js::renderAdvancedSearchResults` — groupe `[data-facet="extensions"]`, items montés en `textContent` (les suffixes viennent de noms de fichiers non fiables) |
|
| Frontend | `frontend/js/search.js::renderAdvancedSearchResults` — groupe `[data-facet="extensions"]`, items montés en `textContent` (les suffixes viennent de noms de fichiers non fiables) ; panneau repliable via `.search-facets__toggle` + `data-collapsed` (état `obsigate_facets_collapsed`) |
|
||||||
| i18n / aide | `help.desc_facet_extensions` (FR/EN), guide `docs/GUIDES/RECHERCHE_PDF_EXCALIDRAW.md` |
|
| i18n / aide | `help.desc_facet_extensions`, `search.facets_collapse`/`_expand` (FR/EN), guide `docs/GUIDES/RECHERCHE_PDF_EXCALIDRAW.md` |
|
||||||
| Tests | `tests/test_search.py::test_facets` (clé `extensions`), `tests/frontend/search-facets.test.mjs` (4 tests JSDOM, ajoutés au CI) |
|
| Tests | `tests/test_search.py::test_facets` (route réelle `/api/search/advanced`, assertion stricte `facets.extensions`), `tests/frontend/search-facets.test.mjs` (5 tests JSDOM, ajoutés au CI), e2e `obsigate.spec.ts` (facette visible + repli/rouverture) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+24
-4
@@ -1035,7 +1035,26 @@ export function renderAdvancedSearchResults(data, query, tagFilter) {
|
|||||||
|
|
||||||
// Facets panel
|
// Facets panel
|
||||||
if (data.facets && (Object.keys(data.facets.tags || {}).length > 0 || Object.keys(data.facets.vaults || {}).length > 0 || Object.keys(data.facets.extensions || {}).length > 0)) {
|
if (data.facets && (Object.keys(data.facets.tags || {}).length > 0 || Object.keys(data.facets.vaults || {}).length > 0 || Object.keys(data.facets.extensions || {}).length > 0)) {
|
||||||
const facetsDiv = el("div", { class: "search-facets" });
|
const collapsed = localStorage.getItem("obsigate_facets_collapsed") === "1";
|
||||||
|
const facetsDiv = el("div", { class: "search-facets", "data-collapsed": collapsed ? "true" : "false" });
|
||||||
|
|
||||||
|
// Discreet collapse toggle for the whole panel (vaults + tags + extensions)
|
||||||
|
const toggleBtn = el("button", {
|
||||||
|
class: "search-facets__toggle",
|
||||||
|
type: "button",
|
||||||
|
"aria-expanded": String(!collapsed),
|
||||||
|
title: collapsed ? t("search.facets_expand") : t("search.facets_collapse"),
|
||||||
|
});
|
||||||
|
toggleBtn.innerHTML = '<i data-lucide="chevron-down" style="width:12px;height:12px"></i>';
|
||||||
|
toggleBtn.addEventListener("click", () => {
|
||||||
|
const nowCollapsed = facetsDiv.getAttribute("data-collapsed") !== "true";
|
||||||
|
facetsDiv.setAttribute("data-collapsed", nowCollapsed ? "true" : "false");
|
||||||
|
toggleBtn.setAttribute("aria-expanded", String(!nowCollapsed));
|
||||||
|
toggleBtn.title = nowCollapsed ? t("search.facets_expand") : t("search.facets_collapse");
|
||||||
|
localStorage.setItem("obsigate_facets_collapsed", nowCollapsed ? "1" : "0");
|
||||||
|
});
|
||||||
|
facetsDiv.appendChild(toggleBtn);
|
||||||
|
const facetsBody = el("div", { class: "search-facets__body" });
|
||||||
|
|
||||||
// Vault facets
|
// Vault facets
|
||||||
const vaultFacets = data.facets.vaults || {};
|
const vaultFacets = data.facets.vaults || {};
|
||||||
@@ -1056,7 +1075,7 @@ export function renderAdvancedSearchResults(data, query, tagFilter) {
|
|||||||
});
|
});
|
||||||
group.appendChild(item);
|
group.appendChild(item);
|
||||||
}
|
}
|
||||||
facetsDiv.appendChild(group);
|
facetsBody.appendChild(group);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tag facets
|
// Tag facets
|
||||||
@@ -1075,7 +1094,7 @@ export function renderAdvancedSearchResults(data, query, tagFilter) {
|
|||||||
});
|
});
|
||||||
group.appendChild(item);
|
group.appendChild(item);
|
||||||
}
|
}
|
||||||
facetsDiv.appendChild(group);
|
facetsBody.appendChild(group);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extension facets
|
// Extension facets
|
||||||
@@ -1101,9 +1120,10 @@ export function renderAdvancedSearchResults(data, query, tagFilter) {
|
|||||||
});
|
});
|
||||||
group.appendChild(item);
|
group.appendChild(item);
|
||||||
}
|
}
|
||||||
facetsDiv.appendChild(group);
|
facetsBody.appendChild(group);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
facetsDiv.appendChild(facetsBody);
|
||||||
area.appendChild(facetsDiv);
|
area.appendChild(facetsDiv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1609,6 +1609,8 @@
|
|||||||
"search.exclude_placeholder": "Exclude: vendor/*, *.lock",
|
"search.exclude_placeholder": "Exclude: vendor/*, *.lock",
|
||||||
"search.facet_tags": "Tags",
|
"search.facet_tags": "Tags",
|
||||||
"search.facet_vaults": "Vaults",
|
"search.facet_vaults": "Vaults",
|
||||||
|
"search.facets_collapse": "Collapse facets",
|
||||||
|
"search.facets_expand": "Expand facets",
|
||||||
"search.files_filtered": "{count} file(s) with filters",
|
"search.files_filtered": "{count} file(s) with filters",
|
||||||
"search.filter_path_title": "Path filters (Alt-F)",
|
"search.filter_path_title": "Path filters (Alt-F)",
|
||||||
"search.filters": "Filters",
|
"search.filters": "Filters",
|
||||||
|
|||||||
@@ -1609,6 +1609,8 @@
|
|||||||
"search.exclude_placeholder": "Exclure: vendor/*, *.lock",
|
"search.exclude_placeholder": "Exclure: vendor/*, *.lock",
|
||||||
"search.facet_tags": "Tags",
|
"search.facet_tags": "Tags",
|
||||||
"search.facet_vaults": "Vaults",
|
"search.facet_vaults": "Vaults",
|
||||||
|
"search.facets_collapse": "Réduire les facettes",
|
||||||
|
"search.facets_expand": "Afficher les facettes",
|
||||||
"search.files_filtered": "{count} fichier(s) avec filtres",
|
"search.files_filtered": "{count} fichier(s) avec filtres",
|
||||||
"search.filter_path_title": "Filtres de chemin (Alt-F)",
|
"search.filter_path_title": "Filtres de chemin (Alt-F)",
|
||||||
"search.filters": "Filtres",
|
"search.filters": "Filtres",
|
||||||
|
|||||||
@@ -5476,6 +5476,38 @@ body.resizing-v {
|
|||||||
font-size: 0.68rem;
|
font-size: 0.68rem;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
}
|
}
|
||||||
|
.search-facets__toggle {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
color: var(--text-muted);
|
||||||
|
cursor: pointer;
|
||||||
|
transition:
|
||||||
|
transform 120ms,
|
||||||
|
border-color 120ms,
|
||||||
|
color 120ms;
|
||||||
|
}
|
||||||
|
.search-facets__toggle:hover {
|
||||||
|
border-color: var(--accent);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
.search-facets[data-collapsed="true"] .search-facets__toggle {
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
.search-facets__body {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.search-facets[data-collapsed="true"] .search-facets__body {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------------------------------------------------------------------------
|
/* ---------------------------------------------------------------------------
|
||||||
Advanced Search — Pagination
|
Advanced Search — Pagination
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "obsigate",
|
"name": "obsigate",
|
||||||
"version": "2.46.0",
|
"version": "2.47.0",
|
||||||
"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.",
|
"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",
|
"main": "patch.js",
|
||||||
"directories": {
|
"directories": {
|
||||||
|
|||||||
@@ -143,6 +143,19 @@ test.describe('Full-Text Search', () => {
|
|||||||
// Not always present — depends on vault content
|
// Not always present — depends on vault content
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should show the extensions facet and collapse the facets panel (#157)', async ({ page }) => {
|
||||||
|
await goHome(page);
|
||||||
|
await searchFor(page, 'test');
|
||||||
|
const extFacet = page.locator('.search-facets__group[data-facet="extensions"]');
|
||||||
|
await expect(extFacet).toBeVisible();
|
||||||
|
await expect(extFacet.locator('.search-facets__item').first()).toBeVisible();
|
||||||
|
// discreet collapse/expand of the whole vaults+tags+extensions section
|
||||||
|
await page.locator('.search-facets__toggle').click();
|
||||||
|
await expect(page.locator('.search-facets__body')).toBeHidden();
|
||||||
|
await page.locator('.search-facets__toggle').click();
|
||||||
|
await expect(page.locator('.search-facets__body')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
test('should navigate results with floating bar arrows', async ({ page }) => {
|
test('should navigate results with floating bar arrows', async ({ page }) => {
|
||||||
await goHome(page);
|
await goHome(page);
|
||||||
await searchFor(page, 'test');
|
await searchFor(page, 'test');
|
||||||
|
|||||||
@@ -143,6 +143,35 @@ await test("clicking an extension adds the ext: operator", async () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await test("facets panel collapses and restores via the toggle", async () => {
|
||||||
|
localStorage.removeItem("obsigate_facets_collapsed");
|
||||||
|
renderAdvancedSearchResults(PAYLOAD, "python", null);
|
||||||
|
const panel = document.querySelector(".search-facets");
|
||||||
|
const toggle = panel.querySelector(".search-facets__toggle");
|
||||||
|
assert.ok(toggle, "collapse toggle rendered");
|
||||||
|
assert.equal(panel.getAttribute("data-collapsed"), "false", "expanded by default");
|
||||||
|
assert.ok(panel.querySelector(".search-facets__body"), "groups live in the body");
|
||||||
|
|
||||||
|
toggle.click();
|
||||||
|
assert.equal(panel.getAttribute("data-collapsed"), "true", "click collapses");
|
||||||
|
assert.equal(
|
||||||
|
localStorage.getItem("obsigate_facets_collapsed"),
|
||||||
|
"1",
|
||||||
|
"collapsed state persisted",
|
||||||
|
);
|
||||||
|
|
||||||
|
renderAdvancedSearchResults(PAYLOAD, "python", null);
|
||||||
|
const panel2 = document.querySelector(".search-facets");
|
||||||
|
assert.equal(
|
||||||
|
panel2.getAttribute("data-collapsed"),
|
||||||
|
"true",
|
||||||
|
"collapsed state survives a re-render",
|
||||||
|
);
|
||||||
|
panel2.querySelector(".search-facets__toggle").click();
|
||||||
|
assert.equal(panel2.getAttribute("data-collapsed"), "false", "toggle restores");
|
||||||
|
localStorage.removeItem("obsigate_facets_collapsed");
|
||||||
|
});
|
||||||
|
|
||||||
// ── Summary ──
|
// ── Summary ──
|
||||||
console.log(`\n${passCount}/${testCount} tests passed`);
|
console.log(`\n${passCount}/${testCount} tests passed`);
|
||||||
if (passCount !== testCount) {
|
if (passCount !== testCount) {
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ class TestAdvancedSearchAPI:
|
|||||||
pytest.skip(f"Advanced search non-JSON response: {resp.text[:200]}")
|
pytest.skip(f"Advanced search non-JSON response: {resp.text[:200]}")
|
||||||
|
|
||||||
def test_basic(self, client):
|
def test_basic(self, client):
|
||||||
resp = client.get("/api/advanced-search?q=python&vault=all")
|
resp = client.get("/api/search/advanced?q=python&vault=all")
|
||||||
data = self._check(resp)
|
data = self._check(resp)
|
||||||
assert data["total"] >= 1
|
assert data["total"] >= 1
|
||||||
assert len(data["results"]) > 0
|
assert len(data["results"]) > 0
|
||||||
@@ -217,16 +217,18 @@ class TestAdvancedSearchAPI:
|
|||||||
assert "path" in r
|
assert "path" in r
|
||||||
|
|
||||||
def test_pagination(self, client):
|
def test_pagination(self, client):
|
||||||
resp = client.get("/api/advanced-search?q=python&limit=1&offset=0")
|
resp = client.get("/api/search/advanced?q=python&limit=1&offset=0")
|
||||||
data = self._check(resp)
|
data = self._check(resp)
|
||||||
assert len(data["results"]) <= 1
|
assert len(data["results"]) <= 1
|
||||||
|
|
||||||
def test_facets(self, client):
|
def test_facets(self, client):
|
||||||
resp = client.get("/api/advanced-search?q=python&vault=all")
|
resp = client.get("/api/search/advanced?q=python&vault=all")
|
||||||
data = self._check(resp)
|
# Hard assertion: the facet contract must hold through the response_model
|
||||||
|
# (regression #157: `extensions` used to be stripped by SearchFacets).
|
||||||
|
assert resp.status_code == 200, resp.text[:200]
|
||||||
|
data = resp.json()
|
||||||
assert "facets" in data
|
assert "facets" in data
|
||||||
# #157 — extension facet: keys are query-ready (no leading dot) and,
|
assert "extensions" in data["facets"]
|
||||||
# when there are results, at least one extension is counted.
|
|
||||||
exts = data["facets"]["extensions"]
|
exts = data["facets"]["extensions"]
|
||||||
assert isinstance(exts, dict)
|
assert isinstance(exts, dict)
|
||||||
assert all(not e.startswith(".") for e in exts)
|
assert all(not e.startswith(".") for e in exts)
|
||||||
@@ -235,7 +237,7 @@ class TestAdvancedSearchAPI:
|
|||||||
assert sum(exts.values()) <= data["total"]
|
assert sum(exts.values()) <= data["total"]
|
||||||
|
|
||||||
def test_empty_query(self, client):
|
def test_empty_query(self, client):
|
||||||
resp = client.get("/api/advanced-search?q=")
|
resp = client.get("/api/search/advanced?q=")
|
||||||
data = self._check(resp)
|
data = self._check(resp)
|
||||||
# Empty query should return 0 results
|
# Empty query should return 0 results
|
||||||
assert data["total"] == 0
|
assert data["total"] == 0
|
||||||
|
|||||||
Reference in New Issue
Block a user