diff --git a/CHANGELOG.md b/CHANGELOG.md
index a8ea87d..633206a 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.28.5**.
+> [Unreleased](#unreleased). La dernière version livrée est **2.28.6**.
---
@@ -14,6 +14,10 @@ et [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
---
+## [2.28.6] — 2026-09-26
+
+---
+
## [2.28.5] — 2026-09-26
---
diff --git a/README.fr.md b/README.fr.md
index 8c0c9ac..ae27164 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.
-[]()
+[]()
[](https://opensource.org/licenses/MIT)
[](https://www.docker.com/)
[](https://www.python.org/)
@@ -976,8 +976,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.28.5).
+Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.28.6).
---
-*Projet : ObsiGate | Version : 2.28.5 | Dernière mise à jour : Septembre 2026*
+*Projet : ObsiGate | Version : 2.28.6 | Dernière mise à jour : Septembre 2026*
diff --git a/README.md b/README.md
index d112293..8847bc4 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.
-[]()
+[]()
[](https://opensource.org/licenses/MIT)
[](https://www.docker.com/)
[](https://www.python.org/)
@@ -1151,8 +1151,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.28.5).
+See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.28.6).
---
-*Project: ObsiGate | Version: 2.28.5 | Last updated: September 2026*
+*Project: ObsiGate | Version: 2.28.6 | Last updated: September 2026*
diff --git a/VERSION b/VERSION
index cb5e764..759bcf0 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.28.5
+2.28.6
diff --git a/desktop/Cargo.lock b/desktop/Cargo.lock
index 44e084f..35b5272 100644
--- a/desktop/Cargo.lock
+++ b/desktop/Cargo.lock
@@ -2626,7 +2626,7 @@ dependencies = [
[[package]]
name = "obsigate-desktop"
-version = "2.28.5"
+version = "2.28.6"
dependencies = [
"chrono",
"env_logger",
diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml
index 6d15abd..55e3feb 100644
--- a/desktop/Cargo.toml
+++ b/desktop/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "obsigate-desktop"
-version = "2.28.5"
+version = "2.28.6"
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 454e0ec..a498aa1 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.28.5",
+ "version": "2.28.6",
"identifier": "com.obsigate.desktop",
"build": {
"frontendDist": "../frontend",
diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md
index 9a9e354..ffb6032 100644
--- a/docs/ROADMAP.md
+++ b/docs/ROADMAP.md
@@ -1,6 +1,6 @@
# ObsiGate — Roadmap
-> **Version :** 2.28.5 | **Dernière mise à jour :** 2026-09-26
+> **Version :** 2.28.6 | **Dernière mise à jour :** 2026-09-26
> **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)**
diff --git a/frontend/index.html b/frontend/index.html
index d0b1558..95730a8 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -1576,17 +1576,6 @@
class="help-search-clear"
id="config-search-clear"
title="Effacer"
- onclick="
- var s =
- document.getElementById(
- 'config-nav-search',
- );
- if (s) {
- s.value = '';
- s.dispatchEvent(new Event('input'));
- s.focus();
- }
- "
>
X
@@ -1698,7 +1687,7 @@
-
+
@@ -3005,14 +2994,6 @@
id="help-hamburger"
title="Sommaire"
aria-label="Afficher le sommaire"
- onclick="
- var n = document.getElementById('help-nav');
- if (n) {
- var d = n.style.display;
- n.style.display =
- d === 'none' || d === '' ? 'flex' : 'none';
- }
- "
>
✕
diff --git a/frontend/js/auth.js b/frontend/js/auth.js
index d538e7c..2b841d1 100644
--- a/frontend/js/auth.js
+++ b/frontend/js/auth.js
@@ -1305,8 +1305,7 @@ async function _startMfaSetup() {
// secret when the backend has no QR generator available.
const qrImg = data.qr_data_url
? ``
+ src="${data.qr_data_url}">`
: "";
const fallbackStyle = data.qr_data_url ? "display:none" : "";
flowArea.innerHTML = `
@@ -1335,6 +1334,17 @@ async function _startMfaSetup() {
codeInput.value = codeInput.value.replace(/[^0-9]/g, "");
});
+ // QR fallback (#87, ex-onerror inline) : si l'image ne charge pas,
+ // afficher la saisie manuelle du secret.
+ const qrImgEl = document.getElementById("mfa-qr-img");
+ if (qrImgEl) {
+ qrImgEl.addEventListener("error", () => {
+ qrImgEl.style.display = "none";
+ const fallback = document.getElementById("mfa-qr-fallback");
+ if (fallback) fallback.style.display = "block";
+ });
+ }
+
document.getElementById("mfa-confirm-btn").addEventListener("click", async () => {
const code = codeInput.value.trim();
if (code.length !== 6) return;
diff --git a/frontend/js/config.js b/frontend/js/config.js
index b49a0cc..e1b5f85 100644
--- a/frontend/js/config.js
+++ b/frontend/js/config.js
@@ -363,6 +363,27 @@ function initHelpModal() {
}
});
+ // Help TOC hamburger + search clear (#87, ex-onclick inline in index.html).
+ var helpHamburger = document.getElementById("help-hamburger");
+ if (helpHamburger) {
+ helpHamburger.addEventListener("click", function() {
+ var n = document.getElementById("help-nav");
+ if (n) {
+ var d = n.style.display;
+ n.style.display = d === "none" || d === "" ? "flex" : "none";
+ }
+ });
+ }
+ var helpSearch = document.getElementById("help-nav-search");
+ var helpSearchClear = document.getElementById("help-search-clear");
+ if (helpSearchClear && helpSearch) {
+ helpSearchClear.addEventListener("click", function() {
+ helpSearch.value = "";
+ helpSearch.dispatchEvent(new Event("input"));
+ helpSearch.focus();
+ });
+ }
+
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && modal.classList.contains("active")) {
closeHelpModal();
@@ -883,7 +904,7 @@ function initConfigModal() {
});
}
- // Logout button — handled inline in index.html (onclick)
+ // Logout button — wired here with addEventListener (#87, no inline onclick)
// Config nav search
var cfgSearch = document.getElementById("config-nav-search");
@@ -901,6 +922,16 @@ function initConfigModal() {
});
}
+ // Config search clear button (#87, ex-onclick inline).
+ var cfgSearchClear = document.getElementById("config-search-clear");
+ if (cfgSearchClear && cfgSearch) {
+ cfgSearchClear.addEventListener("click", function() {
+ cfgSearch.value = "";
+ cfgSearch.dispatchEvent(new Event("input"));
+ cfgSearch.focus();
+ });
+ }
+
// BUG-071/#114: mobile table of contents. #config-nav shares the .help-nav
// rule that hides it below 768px, but — unlike the help modal — the config
// modal had no toggle to reveal it, leaving mobile users with no way to
@@ -1575,13 +1606,15 @@ export async function openShareDialog(vault, path) {
${escapeHtml(vault)}/${escapeHtml(path)}
${expiresInfo}${existingShare.access_count} vue(s)
- + `; + const shareUrlInput = div.querySelector(".share-url-input"); + if (shareUrlInput) shareUrlInput.addEventListener("click", function() { shareUrlInput.select(); }); div.querySelector(".share-copy-btn").addEventListener("click", async () => { try { await navigator.clipboard.writeText(url); @@ -2525,7 +2558,10 @@ function initProfile() { } catch(e) {} }); - // Logout button — handled inline in index.html (onclick) + // Logout button (#87, ex-onclick inline in index.html). + if (logoutBtn && window.handleLogout) { + logoutBtn.addEventListener('click', function() { window.handleLogout(); }); + } // ── Avatar (#113) ──────────────────────────────────────────────── var avatarField = document.getElementById('profile-avatar-field'); diff --git a/frontend/js/plugins.js b/frontend/js/plugins.js index a514e49..fdbc566 100644 --- a/frontend/js/plugins.js +++ b/frontend/js/plugins.js @@ -478,8 +478,8 @@ function openTemplateModal() { '' + '' + '' + ''; @@ -487,11 +487,11 @@ function openTemplateModal() { modal.querySelector('.modal-close')?.addEventListener('click', () => modal.remove()); modal.addEventListener('click', (e) => { if (e.target === modal) modal.remove(); }); - - window.copyTemplate = () => { + modal.querySelector('.btn-modal-close')?.addEventListener('click', () => modal.remove()); + modal.querySelector('.btn-copy-template')?.addEventListener('click', () => { navigator.clipboard.writeText(JSON.stringify(template, null, 2)); showToast('Template copied to clipboard', 'success'); - }; + }); }); } @@ -508,12 +508,13 @@ function openCodeModal(name, code) { '' + escapeHtml(code) + '' + '' + '' + ''; document.body.appendChild(modal); modal.querySelector('.modal-close')?.addEventListener('click', () => modal.remove()); + modal.querySelector('.btn-modal-close')?.addEventListener('click', () => modal.remove()); modal.addEventListener('click', (e) => { if (e.target === modal) modal.remove(); }); } diff --git a/frontend/js/sync.js b/frontend/js/sync.js index 5d53a1b..2df2a23 100644 --- a/frontend/js/sync.js +++ b/frontend/js/sync.js @@ -543,10 +543,16 @@ function showUpdateNotification() { message.innerHTML = `