Files
ObsiGate/frontend/index.html
T
bruno c9e3240ae2
CI / lint (push) Successful in 1m28s
CI / security (push) Successful in 58s
CI / test (push) Successful in 2m9s
CI / build (push) Successful in 1m3s
CI / e2e (push) Successful in 11m12s
feat(assistant): #94-#97 historique permanent, sidebar IA, panneau + et bouton rond
2026-09-16 14:56:45 -04:00

5406 lines
291 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="fr" data-theme="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title data-i18n="header.logo">ObsiGate</title>
<!-- PWA Meta Tags -->
<meta
name="description"
content="Porte d'entrée web pour vos vaults Obsidian - Accédez, naviguez et recherchez dans toutes vos notes"
/>
<meta name="theme-color" content="#2563eb" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta
name="apple-mobile-web-app-status-bar-style"
content="black-translucent"
/>
<meta name="apple-mobile-web-app-title" content="ObsiGate" />
<!-- PWA Manifest -->
<link rel="manifest" href="/static/manifest.json" />
<!-- Apple Touch Icons -->
<link
rel="apple-touch-icon"
sizes="152x152"
href="/static/icons/icon-152x152.svg"
/>
<link
rel="apple-touch-icon"
sizes="192x192"
href="/static/icons/icon-192x192.svg"
/>
<!-- Favicon -->
<link
rel="icon"
type="image/svg+xml"
href="/static/icons/icon-72x72.svg"
/>
<link
rel="icon"
type="image/svg+xml"
sizes="192x192"
href="/static/icons/icon-192x192.svg"
/>
<!-- Boot splash CSS — inline so it applies BEFORE /static/style.css is
available (desktop: embedded page served from tauri:// has no /static) -->
<style id="boot-splash-css">
body:not(.app-booted) > *:not(#boot-splash) { display: none !important; }
#boot-splash {
position: fixed; inset: 0; z-index: 2147483647;
display: flex; align-items: center; justify-content: center;
background: radial-gradient(1200px 600px at 50% -10%, #1b2030 0%, #0e1117 60%);
color: #e6e8ee; font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
}
body.app-booted #boot-splash { display: none; }
.bs-card { display: flex; flex-direction: column; align-items: center; gap: 14px; }
.bs-logo {
width: 72px; height: 72px; display: flex; align-items: center; justify-content: center;
font-size: 38px; background: #171c26; border: 1px solid #2a3242; border-radius: 18px;
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.45);
}
.bs-title { font-size: 22px; font-weight: 600; letter-spacing: 0.3px; }
.bs-spinner {
width: 34px; height: 34px; border-radius: 50%;
border: 3px solid rgba(124, 58, 237, 0.25); border-top-color: #7c3aed;
animation: bs-spin 0.9s linear infinite;
}
@keyframes bs-spin { to { transform: rotate(360deg); } }
.bs-status { font-size: 13px; color: #9aa3b2; min-height: 18px; }
@media (prefers-reduced-motion: reduce) { .bs-spinner { animation-duration: 1.8s; } }
</style>
<link rel="stylesheet" href="/static/style.css" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css"
id="hljs-theme-dark"
/>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css"
id="hljs-theme-light"
disabled
/>
<!-- defer: ne bloque pas le premier paint (le splash inline s'affiche immédiatement) -->
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<!-- Langues highlight.js hors bundle "common" (enregistrement auto apres le core) -->
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/dockerfile.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/powershell.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/dos.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/cmake.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/dart.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/elixir.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/haskell.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/nginx.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/http.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/plaintext.min.js"></script>
<script defer src="https://unpkg.com/[email protected]/dist/umd/lucide.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
<script type="importmap">
{
"imports": {
"@codemirror/state": "https://esm.sh/@codemirror/[email protected]",
"yjs": "https://esm.sh/[email protected]"
}
}
</script>
<!-- CodeMirror 6 -->
<script type="module">
import {
EditorView,
keymap,
lineNumbers,
highlightActiveLineGutter,
highlightSpecialChars,
drawSelection,
dropCursor,
rectangularSelection,
crosshairCursor,
highlightActiveLine,
Decoration,
ViewPlugin,
WidgetType,
} from "https://esm.sh/@codemirror/[email protected]?external=@codemirror/state";
import { EditorState, StateEffect } from "@codemirror/state";
import {
defaultHighlightStyle,
syntaxHighlighting,
indentOnInput,
bracketMatching,
foldGutter,
foldKeymap,
} from "https://esm.sh/@codemirror/[email protected]?external=@codemirror/state";
import {
defaultKeymap,
history,
historyKeymap,
undo,
redo,
} from "https://esm.sh/@codemirror/[email protected]?external=@codemirror/state";
import {
searchKeymap,
highlightSelectionMatches,
} from "https://esm.sh/@codemirror/[email protected]?external=@codemirror/state";
import {
autocompletion,
completionKeymap,
closeBrackets,
closeBracketsKeymap,
} from "https://esm.sh/@codemirror/[email protected]?external=@codemirror/state";
import { markdown } from "https://esm.sh/@codemirror/[email protected]?external=@codemirror/state";
import { python } from "https://esm.sh/@codemirror/[email protected]?external=@codemirror/state";
import { javascript } from "https://esm.sh/@codemirror/[email protected]?external=@codemirror/state";
import { html } from "https://esm.sh/@codemirror/[email protected]?external=@codemirror/state";
import { css } from "https://esm.sh/@codemirror/[email protected]?external=@codemirror/state";
import { json } from "https://esm.sh/@codemirror/[email protected]?external=@codemirror/state";
import { xml } from "https://esm.sh/@codemirror/[email protected]?external=@codemirror/state";
import { sql } from "https://esm.sh/@codemirror/[email protected]?external=@codemirror/state";
import { php } from "https://esm.sh/@codemirror/[email protected]?external=@codemirror/state";
import { cpp } from "https://esm.sh/@codemirror/[email protected]?external=@codemirror/state";
import { java } from "https://esm.sh/@codemirror/[email protected]?external=@codemirror/state";
import { rust } from "https://esm.sh/@codemirror/[email protected]?external=@codemirror/state";
import { oneDark } from "https://esm.sh/@codemirror/[email protected]?external=@codemirror/state";
const basicSetup = [
lineNumbers(),
highlightActiveLineGutter(),
highlightSpecialChars(),
history(),
foldGutter(),
drawSelection(),
dropCursor(),
EditorState.allowMultipleSelections.of(true),
indentOnInput(),
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
bracketMatching(),
closeBrackets(),
autocompletion(),
rectangularSelection(),
crosshairCursor(),
highlightActiveLine(),
highlightSelectionMatches(),
keymap.of([
...closeBracketsKeymap,
...defaultKeymap,
...searchKeymap,
...historyKeymap,
...foldKeymap,
...completionKeymap,
]),
];
window.CodeMirror = {
EditorView,
EditorState,
basicSetup,
markdown,
python,
javascript,
html,
css,
json,
xml,
sql,
php,
cpp,
java,
rust,
oneDark,
keymap,
undo,
redo,
Decoration,
ViewPlugin,
WidgetType,
StateEffect,
};
</script>
</head>
<body>
<!-- Boot splash : visible tant que l'app n'a pas booté avec son CSS.
Sur desktop, la page embarquée (tauri://) n'a pas /static/style.css →
sans ce splash, le squelette HTML brut serait visible pendant le
démarrage du backend (FOUC 3-10 s). Styles 100% inline. -->
<div id="boot-splash" role="status" aria-live="polite">
<div class="bs-card">
<div class="bs-logo">📖</div>
<div class="bs-title">ObsiGate</div>
<div class="bs-spinner" aria-hidden="true"></div>
<div class="bs-status" id="boot-splash-status">Démarrage…</div>
</div>
</div>
<script>
(function () {
var done = false;
function bootReady() {
if (done) return;
done = true;
document.body.classList.add("app-booted");
var s = document.getElementById("boot-splash");
if (s && s.parentNode) s.parentNode.removeChild(s);
}
window.__obsigateBootReady = bootReady;
window.__setBootStatus = function (txt) {
var el = document.getElementById("boot-splash-status");
if (el) el.textContent = txt;
};
var poll = setInterval(function () {
if (window.__OBSIGATE_BOOTED) { clearInterval(poll); bootReady(); }
}, 80);
// Filet de sécurité : si le CSS principal a chargé (origine backend réelle)
// mais que le JS a échoué, révéler l'app après 10 s au lieu d'un splash infini.
setTimeout(function () {
var link = document.querySelector('link[href*="style.css"]');
if (link && link.sheet) { clearInterval(poll); bootReady(); }
}, 10000);
})();
</script>
<div class="search-progress-bar" id="search-progress-bar">
<div class="search-progress-bar__fill"></div>
</div>
<!-- Login Screen (hidden by default, shown when auth is required) -->
<div id="login-screen" class="login-screen hidden">
<div class="login-card">
<div class="login-header">
<div class="login-logo">
<span class="login-logo-icon">📖</span>
<span class="login-logo-text" data-i18n="header.logo">ObsiGate</span>
</div>
<p class="login-subtitle" data-i18n="auth.login_title">Accès sécurisé à vos vaults</p>
</div>
<form id="login-form" class="login-form" autocomplete="on">
<div class="form-group">
<label for="login-username" data-i18n="auth.username">Nom d'utilisateur</label>
<input
type="text"
id="login-username"
name="username"
autocomplete="username"
placeholder="utilisateur"
required
autofocus
/>
</div>
<div class="form-group">
<label for="login-password" data-i18n="auth.password">Mot de passe</label>
<div class="password-input-wrapper">
<input
type="password"
id="login-password"
name="password"
autocomplete="current-password"
placeholder="••••••••"
required
/>
<button
type="button"
id="toggle-password"
class="password-toggle"
aria-label="Afficher le mot de passe"
>
👁
</button>
</div>
</div>
<div class="form-group form-group--inline">
<label class="checkbox-label">
<input
type="checkbox"
id="remember-me"
name="remember_me"
/>
<span data-i18n="auth.remember_me">Se souvenir de moi (30 jours)</span>
</label>
</div>
<div id="login-error" class="login-error hidden"></div>
<button type="submit" id="login-btn" class="btn-login">
<span class="btn-text" data-i18n="auth.login_button">Se connecter</span>
<span class="btn-spinner hidden">⏳</span>
</button>
</form>
</div>
</div>
<!-- Main Application (wrapped for show/hide) -->
<div id="app" class="app-container">
<!-- Header -->
<header class="header">
<div class="header-left">
<button
class="hamburger-btn"
id="hamburger-btn"
title="Menu"
>
<i
data-lucide="menu"
style="width: 20px; height: 20px"
></i>
</button>
<button
class="sidebar-toggle-btn"
id="sidebar-toggle-btn"
title="Afficher/Masquer la sidebar"
>
<i
data-lucide="sidebar"
style="width: 20px; height: 20px"
></i>
</button>
<div class="header-logo" id="header-logo">
<i
data-lucide="book-open"
style="width: 20px; height: 20px"
></i>
ObsiGate
</div>
<span
class="sync-badge sync-badge--disconnected"
id="sync-badge"
title="Synchronisation"
></span>
</div>
<div class="header-center">
<div class="search-wrapper">
<i
data-lucide="search"
class="search-icon"
style="width: 16px; height: 16px"
></i>
<div class="search-input-wrapper">
<input
type="text"
id="search-input"
data-i18n-placeholder="header.search_placeholder"
placeholder="Rechercher ..."
autocomplete="off"
/>
<div class="search-actions">
<button
class="search-btn icon-only"
id="search-filter-btn"
type="button"
title="Filtres de chemin (Alt-F)"
>
<i
data-lucide="filter"
style="width: 14px; height: 14px"
></i>
</button>
<button
class="search-btn icon-only"
id="search-clear-btn"
type="button"
title="Effacer"
aria-label="Effacer la recherche"
>
<i
data-lucide="x"
style="width: 14px; height: 14px"
></i>
</button>
</div>
</div>
<!-- Path filter row -->
<div
class="search-filter-row"
id="search-filter-row"
style="display: none"
>
<input
type="text"
class="search-filter-input"
id="search-include-input"
data-i18n-placeholder="search.include_placeholder" placeholder="Inclure: **/*.md, notes/**"
/>
<input
type="text"
class="search-filter-input"
id="search-exclude-input"
data-i18n-placeholder="search.exclude_placeholder" placeholder="Exclure: vendor/*, *.lock"
/>
</div>
<!-- Advanced search autocomplete dropdown -->
<div
class="search-dropdown"
id="search-dropdown"
role="listbox"
aria-label="Suggestions de recherche"
hidden
>
<div
class="search-dropdown__section search-dropdown__section--history"
id="search-dropdown-history"
>
<div class="search-dropdown__section-header">
<span data-i18n="search.dropdown_history">Historique</span>
<button
class="search-dropdown__clear-btn"
id="search-dropdown-clear-history"
type="button"
title="Effacer l'historique"
>
<i
data-lucide="trash-2"
style="width: 12px; height: 12px"
></i>
</button>
</div>
<ul
class="search-dropdown__list"
id="search-dropdown-history-list"
></ul>
</div>
<div
class="search-dropdown__section search-dropdown__section--titles"
id="search-dropdown-titles"
>
<div class="search-dropdown__section-header">
<span data-i18n="palette.mode_files">Fichiers</span>
</div>
<ul
class="search-dropdown__list"
id="search-dropdown-titles-list"
></ul>
</div>
<div
class="search-dropdown__section search-dropdown__section--tags"
id="search-dropdown-tags"
>
<div class="search-dropdown__section-header">
<span data-i18n="sidebar.tab_tags">Tags</span>
</div>
<ul
class="search-dropdown__list"
id="search-dropdown-tags-list"
></ul>
</div>
<div
class="search-dropdown__empty"
id="search-dropdown-empty"
hidden
>
Aucune suggestion
</div>
</div>
<!-- Active search filter chips -->
<div
class="search-chips"
id="search-chips"
hidden
></div>
</div>
</div>
<div class="header-right">
<!-- Offline indicator (hidden by default) -->
<div
class="header-icon offline-indicator"
id="offline-badge"
style="display:none"
title="Mode hors-ligne"
></div>
<div
class="vault-context-indicator"
id="vault-context-indicator"
title="Contexte vault actif"
>
<i
data-lucide="database"
style="width: 14px; height: 14px"
></i>
<span id="vault-context-text">All</span>
</div>
<!-- User menu (visible when logged in) -->
<div class="user-menu" id="user-menu"></div>
<div class="version-badge" id="version-badge" title="Version ObsiGate"></div>
<div class="header-menu">
<button
class="header-menu-btn"
id="header-menu-btn"
title="Options"
>
<i
data-lucide="settings"
style="width: 18px; height: 18px"
></i>
</button>
<div
class="header-menu-dropdown"
id="header-menu-dropdown"
>
<div
class="menu-list"
role="menu"
aria-label="Options"
>
<label
class="menu-list-row menu-list-select-row hidden"
for="vault-filter"
>
<span
class="menu-list-icon"
aria-hidden="true"
>
<i
data-lucide="folders"
style="width: 17px; height: 17px"
></i>
</span>
<span class="menu-list-content">
<span class="menu-list-title"
>Vault</span
>
<div
class="custom-dropdown"
id="vault-filter-dropdown"
>
<button
class="custom-dropdown-trigger"
type="button"
aria-haspopup="listbox"
aria-expanded="false"
>
<span
class="custom-dropdown-selected"
>Tous les vaults</span
>
<i
data-lucide="chevron-down"
style="
width: 14px;
height: 14px;
"
></i>
</button>
<ul
class="custom-dropdown-menu"
role="listbox"
id="vault-filter-options"
>
<li role="option"
data-value="all"
class="custom-dropdown-option selected" data-i18n="help.all_vaults">
Tous les vaults
</li>
</ul>
<input
type="hidden"
id="vault-filter"
value="all"
/>
</div>
</span>
</label>
<button
class="menu-list-row menu-list-button"
id="theme-toggle"
type="button"
role="menuitem"
>
<span
class="menu-list-icon"
aria-hidden="true"
>
<i
data-lucide="sun"
style="width: 17px; height: 17px"
></i>
</span>
<span class="menu-list-content">
<span class="menu-list-title" data-i18n="header.menu_theme"
>Thème</span
>
<span
class="menu-list-subtitle"
id="theme-label"
>Clair</span
>
</span>
</button>
<button
class="menu-list-row menu-list-button hidden"
id="admin-menu-row"
type="button"
role="menuitem"
>
<span
class="menu-list-icon"
aria-hidden="true"
>
<i
data-lucide="shield"
style="width: 17px; height: 17px"
></i>
</span>
<span class="menu-list-content">
<span class="menu-list-title" data-i18n="header.menu_admin"
>Administration</span
>
<span class="menu-list-subtitle" data-i18n="header.menu_admin_desc"
>Gérer les utilisateurs</span
>
</span>
</button>
<button
class="menu-list-row menu-list-button"
id="api-docs-menu-row"
type="button"
role="menuitem"
>
<span
class="menu-list-icon"
aria-hidden="true"
>
<i
data-lucide="code-2"
style="width: 17px; height: 17px"
></i>
</span>
<span class="menu-list-content">
<span class="menu-list-title" data-i18n="header.menu_api"
>API</span
>
<span class="menu-list-subtitle" data-i18n="header.menu_api_desc"
>Documentation OpenAPI</span
>
</span>
</button>
<button
class="menu-list-row menu-list-button"
id="config-open-btn"
type="button"
role="menuitem"
>
<span
class="menu-list-icon"
aria-hidden="true"
>
<i
data-lucide="settings"
style="width: 17px; height: 17px"
></i>
</span>
<span class="menu-list-content">
<span class="menu-list-title" data-i18n="header.menu_config"
>Configurations</span
>
<span class="menu-list-subtitle" data-i18n="header.menu_config_desc"
>Paramètres de l'application</span
>
</span>
</button>
<button
class="menu-list-row menu-list-button"
id="help-open-btn"
type="button"
role="menuitem"
>
<span
class="menu-list-icon"
aria-hidden="true"
>
<i
data-lucide="book-open"
style="width: 17px; height: 17px"
></i>
</span>
<span class="menu-list-content">
<span class="menu-list-title" data-i18n="header.menu_help"
>Guide d'utilisation</span
>
<span class="menu-list-subtitle" data-i18n="header.menu_help_desc"
>Ouvrir l'aide</span
>
</span>
</button>
<button
class="menu-list-row menu-list-button"
id="about-open-btn"
type="button"
role="menuitem"
>
<span
class="menu-list-icon"
aria-hidden="true"
>
<i
data-lucide="sparkles"
style="width: 17px; height: 17px"
></i>
</span>
<span class="menu-list-content">
<span class="menu-list-title" data-i18n="header.menu_about"
>A propos</span
>
<span class="menu-list-subtitle" data-i18n="header.menu_about_desc"
>Version, credits &amp; info</span
>
</span>
</button>
<button
class="menu-list-row menu-list-button"
id="profile-open-btn"
type="button"
role="menuitem"
>
<span
class="menu-list-icon"
aria-hidden="true"
>
<i
data-lucide="user"
style="width: 17px; height: 17px"
></i>
</span>
<span class="menu-list-content">
<span class="menu-list-title" data-i18n="header.menu_profile"
>Profil</span
>
<span class="menu-list-subtitle" data-i18n="header.menu_profile_desc"
>Langue &amp; preferences</span
>
</span>
</button>
<script>function doLogoutFallback(){var x=new XMLHttpRequest();x.open('POST','/api/auth/logout');x.withCredentials=true;x.onload=function(){var a=document.getElementById('app');var l=document.getElementById('login-screen');if(a)a.classList.add('hidden');if(l)l.classList.remove('hidden');try{localStorage.removeItem('obsigate-token')}catch(e){}};x.send()}</script>
<button
class="menu-list-row menu-list-button"
id="logout-btn"
type="button"
role="menuitem"
onclick="if(window.handleLogout)window.handleLogout();else{doLogoutFallback()}"
>
<span
class="menu-list-icon"
aria-hidden="true"
>
<i
data-lucide="log-out"
style="width: 17px; height: 17px; color: var(--danger)"
></i>
</span>
<span class="menu-list-content">
<span class="menu-list-title" data-i18n="header.menu_logout"
style="color:var(--danger)"
>Deconnexion</span
>
<span class="menu-list-subtitle" data-i18n="header.menu_logout_desc"
>Quitter la session</span
>
</span>
</button>
</div>
</div>
</div>
</div>
</header>
<!-- Main -->
<div class="main-body">
<!-- Mobile overlay -->
<div class="sidebar-overlay" id="sidebar-overlay"></div>
<!-- Sidebar -->
<aside
class="sidebar"
id="sidebar"
role="navigation"
aria-label="Navigation des vaults"
>
<!-- Sidebar filter -->
<div class="sidebar-filter">
<div class="sidebar-filter-input-wrapper">
<i
data-lucide="filter"
class="sidebar-filter-icon"
style="width: 14px; height: 14px"
></i>
<input
type="text"
id="sidebar-filter-input"
data-i18n-placeholder="sidebar.filter_placeholder" placeholder="Filtrer fichiers..."
autocomplete="off"
/>
<div class="sidebar-filter-actions">
<button
class="sidebar-filter-btn"
id="sidebar-filter-case-btn"
type="button"
title="Respecter la casse"
aria-label="Respecter la casse"
>
<span>Aa</span>
</button>
<button
class="sidebar-filter-btn"
id="sidebar-filter-clear-btn"
type="button"
title="Effacer le filtrage"
aria-label="Effacer le filtrage"
>
<i
data-lucide="x"
style="width: 14px; height: 14px"
></i>
</button>
</div>
</div>
</div>
<!-- Tab bar -->
<div
class="sidebar-tabs"
role="tablist"
aria-label="Sections sidebar"
>
<button
class="sidebar-tab active"
id="sidebar-tab-vaults"
role="tab"
aria-selected="true"
aria-controls="sidebar-panel-vaults"
data-tab="vaults"
title="Vaults"
>
<i
data-lucide="folder-tree"
style="width: 18px; height: 18px"
></i>
</button>
<button
class="sidebar-tab"
id="sidebar-tab-tags"
role="tab"
aria-selected="false"
aria-controls="sidebar-panel-tags"
data-tab="tags"
title="Tags"
>
<i
data-lucide="tag"
style="width: 18px; height: 18px"
></i>
</button>
<button
class="sidebar-tab"
id="sidebar-tab-recent"
role="tab"
aria-selected="false"
aria-controls="sidebar-panel-recent"
data-tab="recent"
title="Récent"
>
<i
data-lucide="clock"
style="width: 18px; height: 18px"
></i>
</button>
<button
class="sidebar-tab"
id="sidebar-tab-saved"
role="tab"
aria-selected="false"
aria-controls="sidebar-panel-saved"
data-tab="saved"
title="Sauvegardes"
>
<i
data-lucide="bookmark"
style="width: 18px; height: 18px"
></i>
</button>
<button
class="sidebar-tab"
id="sidebar-tab-ai"
role="tab"
aria-selected="false"
aria-controls="sidebar-panel-ai"
data-tab="ai"
title="Historique IA"
>
<i
data-lucide="messages-square"
style="width: 18px; height: 18px"
></i>
</button>
</div>
<!-- Vaults panel -->
<div
class="sidebar-tab-panel active"
id="sidebar-panel-vaults"
role="tabpanel"
aria-labelledby="sidebar-tab-vaults"
>
<div
class="custom-dropdown sidebar-dropdown"
id="vault-quick-select-dropdown"
>
<button
class="custom-dropdown-trigger"
type="button"
aria-haspopup="listbox"
aria-expanded="false"
>
<span class="custom-dropdown-selected"
>Tous les vaults</span
>
<i
data-lucide="chevron-down"
style="width: 14px; height: 14px"
></i>
</button>
<ul
class="custom-dropdown-menu"
role="listbox"
id="vault-quick-select-options"
>
<li role="option"
data-value="all"
class="custom-dropdown-option selected" data-i18n="help.all_vaults">
Tous les vaults
</li>
</ul>
<input
type="hidden"
id="vault-quick-select"
value="all"
/>
</div>
<div
id="vault-tree"
role="tree"
aria-label="Arborescence des fichiers"
></div>
</div>
<!-- Tags panel -->
<div
class="sidebar-tab-panel"
id="sidebar-panel-tags"
role="tabpanel"
aria-labelledby="sidebar-tab-tags"
>
<div class="tag-cloud" id="tag-cloud"></div>
</div>
<!-- Recent panel -->
<div
class="sidebar-tab-panel"
id="sidebar-panel-recent"
role="tabpanel"
aria-labelledby="sidebar-tab-recent"
>
<div class="recent-filter-bar">
<select id="recent-vault-filter">
<option value="" data-i18n="help.all_vaults">Tous les vaults</option>
</select>
</div>
<div id="recent-list" class="recent-list"></div>
<div id="recent-empty" class="recent-empty hidden">
<i
data-lucide="inbox"
style="width: 32px; height: 32px"
></i>
<span data-i18n="dashboard.no_recent">Aucun fichier récent</span>
</div>
</div>
<!-- Saved Searches panel -->
<div
class="sidebar-tab-panel"
id="sidebar-panel-saved"
role="tabpanel"
aria-labelledby="sidebar-tab-saved"
>
<div class="saved-filter-bar" id="saved-filter-bar">
<button class="saved-filter-pill active" data-filter="all" data-i18n="common.all">Tous</button>
<button class="saved-filter-pill" data-filter="search" data-i18n="sidebar.tab_saved">Recherches</button>
<button class="saved-filter-pill" data-filter="directory">Repertoires</button>
</div>
<div id="saved-searches-list" class="recent-list"></div>
<div id="saved-searches-empty" class="recent-empty">
<i
data-lucide="bookmark"
style="width: 32px; height: 32px"
></i>
<span>Aucune recherche sauvegardee</span>
</div>
</div>
<!-- AI history panel -->
<div
class="sidebar-tab-panel"
id="sidebar-panel-ai"
role="tabpanel"
aria-labelledby="sidebar-tab-ai"
>
<div id="ai-history-list" class="recent-list"></div>
<div id="ai-history-empty" class="recent-empty hidden">
<i
data-lucide="messages-square"
style="width: 32px; height: 32px"
></i>
<span>Aucune conversation</span>
</div>
</div>
</aside>
<!-- Sidebar resize handle -->
<div
class="sidebar-resize-handle"
id="sidebar-resize-handle"
></div>
<!-- Content Wrapper (tab bar + content area) -->
<div class="vault-context-bar" id="vault-context-bar">
<span class="vault-context-pill" id="vault-context-pill"
>📂 Toutes les vaults</span
>
</div>
<div class="content-wrapper">
<!-- Tab bar -->
<div class="tab-bar" id="tab-bar" hidden>
<div class="tab-list" id="tab-list"></div>
</div>
<!-- Content -->
<main
class="content-area"
id="content-area"
aria-label="Contenu principal"
>
<div
id="dashboard-home"
class="dashboard-home"
role="region"
aria-label="Tableau de bord"
>
<!-- Dashboard Tabs -->
<div class="dashboard-tabs">
<button
class="dashboard-tab active"
data-tab="stats"
>
<i
data-lucide="bar-chart-3"
style="width: 14px; height: 14px"
></i>
Statistiques
</button>
<button
class="dashboard-tab"
data-tab="bookmarks"
>
<i
data-lucide="bookmark"
style="width: 14px; height: 14px"
></i>
Bookmarks
</button>
<button class="dashboard-tab" data-tab="recent">
<i
data-lucide="clock"
style="width: 14px; height: 14px"
></i>
Récents
</button>
<button class="dashboard-tab" data-tab="shared">
<i
data-lucide="share-2"
style="width: 14px; height: 14px"
></i>
Partagés
</button>
</div>
<!-- Stats Panel -->
<div
id="dashboard-panel-stats"
class="dashboard-panel active"
>
<div
id="dashboard-stats-grid"
class="dashboard-stats-grid"
>
<div class="dashboard-stats-loading" data-i18n="common.loading">
Chargement...
</div>
</div>
<div
id="dashboard-conflicts-container"
style="margin-top: 16px"
></div>
<!-- Quick Help -->
<div class="quick-help" id="quick-help">
<div class="qh-header">
<i
data-lucide="keyboard"
style="width: 16px; height: 16px"
></i>
<span data-i18n="help.shortcuts_title">Raccourcis &amp; Astuces</span>
<button
class="qh-toggle"
id="qh-toggle"
title="Masquer"
aria-label="Masquer l'aide"
>
&minus;
</button>
</div>
<div class="qh-body" id="qh-body">
<div class="qh-row">
<span class="qh-kbd"
>Ctrl+Shift+Space</span
>
<span class="qh-desc"
>Palette de commandes</span
>
</div>
<div class="qh-row">
<span class="qh-kbd"
>Ctrl+Alt+Space</span
>
<span class="qh-desc"
>Palette de fichiers (nav.
rapide)</span
>
</div>
<div class="qh-row">
<span class="qh-kbd">Ctrl+K</span>
<span class="qh-desc"
>Focus barre de recherche</span
>
</div>
<div class="qh-row">
<span class="qh-kbd">/</span>
<span class="qh-desc"
>Recherche rapide (hors champ
texte)</span
>
</div>
<div class="qh-row">
<span class="qh-kbd">Ctrl+S</span>
<span class="qh-desc"
>Sauvegarder le fichier
ouvert</span
>
</div>
<div class="qh-row">
<span class="qh-kbd">Ctrl+F</span>
<span class="qh-desc"
>Rechercher dans le
document</span
>
</div>
<div class="qh-row">
<span class="qh-kbd">Esc</span>
<span class="qh-desc"
>Fermer l'editeur / modale</span
>
</div>
<div class="qh-row">
<span class="qh-kbd">Ctrl+J</span>
<span class="qh-desc"
>Completion IA inline (dans
l'editeur)</span
>
</div>
<div class="qh-row">
<span class="qh-kbd">Alt+I</span>
<span class="qh-desc"
>Insertion rapide (editeur
Forge)</span
>
</div>
<div class="qh-row">
<span class="qh-kbd">F1</span>
<span class="qh-desc"
>Aide de l'editeur Forge</span
>
</div>
<div class="qh-divider"></div>
<div class="qh-tip">
<i
data-lucide="lightbulb"
style="
width: 14px;
height: 14px;
"
></i>
<span
>Survolez un document avec
<strong>Ctrl</strong> pour un
apercu rapide. Cliquez sur
<strong>Forge</strong> dans la
barre d'outils pour l'editeur
avance. <strong>Editer</strong> et
<strong>Forge</strong> remplacent
le document en mode lecture
(retour par ✓, ✕ ou
<strong>Echap</strong>) : le
panneau de l'assistant IA reste
accessible a cote.</span
>
</div>
</div>
</div>
</div>
<!-- Bookmarks Panel -->
<div
id="dashboard-panel-bookmarks"
class="dashboard-panel"
>
<div
id="dashboard-bookmarks-grid"
class="dashboard-recent-grid"
></div>
<div
id="dashboard-bookmarks-empty"
class="dashboard-recent-empty"
>
<i data-lucide="pin"></i>
<span>Aucun bookmark</span>
<p data-i18n="auto.c089d968">
Épinglez des fichiers pour les retrouver
ici.
</p>
</div>
</div>
<!-- Recent Panel -->
<div
id="dashboard-panel-recent"
class="dashboard-panel"
>
<div class="dashboard-header">
<div class="dashboard-title-row">
<span
id="dashboard-count"
class="dashboard-badge"
></span>
</div>
</div>
<div
id="dashboard-recent-grid"
class="dashboard-recent-grid"
></div>
<div
id="dashboard-loading"
class="dashboard-loading"
>
<div class="skeleton-card"></div>
<div class="skeleton-card"></div>
<div class="skeleton-card"></div>
<div class="skeleton-card"></div>
<div class="skeleton-card"></div>
<div class="skeleton-card"></div>
</div>
<div
id="dashboard-recent-empty"
class="dashboard-recent-empty hidden"
>
<i data-lucide="inbox"></i>
<span data-i18n="dashboard.no_recent">Aucun fichier récent</span>
<p data-i18n="auto.753d30a8">
Ouvrez un fichier pour le voir
apparaître ici
</p>
</div>
</div>
<!-- Shared Panel -->
<div
id="dashboard-panel-shared"
class="dashboard-panel"
>
<div
id="dashboard-shared-grid"
class="dashboard-recent-grid"
></div>
<div
id="dashboard-shared-empty"
class="dashboard-recent-empty"
>
<i data-lucide="share-2"></i>
<span data-i18n="dashboard.no_shares">Aucun document partagé</span>
<p data-i18n="auto.e61598c1">
Partagez un document pour le voir
apparaître ici
</p>
</div>
</div>
</div>
</main>
</div>
<!-- /content-wrapper -->
<!-- Right sidebar resize handle -->
<div
class="right-sidebar-resize-handle"
id="right-sidebar-resize-handle"
></div>
<!-- Right Sidebar (Outline/TOC) -->
<aside
class="right-sidebar hidden"
id="right-sidebar"
role="complementary"
aria-label="Table des matières"
>
<div class="right-sidebar-header">
<h2 class="right-sidebar-title" data-i18n="viewer.on_this_page">SUR CETTE PAGE</h2>
<button
class="right-sidebar-toggle-btn"
id="right-sidebar-toggle-btn"
type="button"
title="Masquer le panneau"
aria-label="Masquer le panneau"
>
<i
data-lucide="chevron-right"
style="width: 16px; height: 16px"
></i>
</button>
</div>
<div class="outline-panel" id="outline-panel">
<nav
class="outline-list"
id="outline-list"
role="navigation"
aria-label="Navigation dans le document"
>
<!-- Outline items will be injected here -->
</nav>
<div class="outline-empty" id="outline-empty" hidden>
<i
data-lucide="list"
style="width: 24px; height: 24px; opacity: 0.3"
></i>
<span data-i18n="viewer.no_headings">Aucun titre dans ce document</span>
</div>
</div>
<div class="reading-progress" id="reading-progress">
<div class="reading-progress-bar">
<div
class="reading-progress-fill"
id="reading-progress-fill"
></div>
</div>
<div
class="reading-progress-text"
id="reading-progress-text"
>
0%
</div>
</div>
</aside>
</div>
</div>
<!-- Editor Modal — the .editor-container is moved into the document
content area (inline mode, #93) when a file is being edited, so the
editor replaces the read view instead of overlaying it. -->
<div class="editor-modal" id="editor-modal">
<div class="editor-container" id="editor-container">
<div class="editor-header">
<span class="editor-brand" data-i18n="header.logo">ObsiGate</span>
<input
class="editor-title-input"
id="editor-title-input"
data-i18n-placeholder="forge.title_placeholder" placeholder="Titre..."
spellcheck="false"
title="Titre (sync avec # Heading)"
/>
<span class="editor-sep">/</span>
<input
class="editor-filename-input"
id="editor-filename-input"
placeholder="nom.md"
spellcheck="false"
title="Renommer le fichier"
/>
<div class="editor-spacer"></div>
<div class="collab-presence" id="collab-presence" style="display: none"
title="Collaboration"></div>
<span class="editor-save-dot ok" id="editor-save-dot"
><span class="dot"></span
><span id="editor-save-label">Saved</span></span
>
<button
class="editor-btn danger"
id="editor-delete"
title="Supprimer"
aria-label="Supprimer"
>
&#128465;
</button>
<button
class="editor-btn"
id="editor-cancel"
title="Annuler"
aria-label="Annuler"
>
&#10005;
</button>
<button
class="editor-btn primary"
id="editor-save"
title="Sauvegarder"
aria-label="Sauvegarder"
>
&#10003;
</button>
</div>
<div class="editor-body editor-body-cm" id="editor-body">
<div id="ai-toolbar-container"></div>
</div>
<div class="editor-footer">
<span class="editor-stat-file" id="editor-stat-file"></span>
<span class="editor-stat-words" id="editor-stat-words"
>0 words</span
>
</div>
</div>
</div>
<!-- Configurations Modal -->
<div class="editor-modal" id="config-modal">
<div class="editor-container">
<div class="editor-header">
<div class="editor-title" data-i18n="header.menu_config">Configurations</div>
<div class="editor-actions">
<button
class="editor-btn"
id="config-close"
title="Fermer"
aria-label="Fermer"
>
<i
data-lucide="x"
style="width: 16px; height: 16px"
></i>
</button>
</div>
</div>
<div class="editor-body help-body" id="config-body">
<nav class="help-nav" id="config-nav">
<div class="help-nav-header">
<div class="help-nav-title" data-i18n="config.title">Configuration</div>
</div>
<div class="help-nav-search-wrap">
<input
type="text"
class="help-nav-search"
id="config-nav-search"
data-i18n-placeholder="settings.search_placeholder" placeholder="Rechercher..."
autocomplete="off"
spellcheck="false"
/>
<button
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
</button>
</div>
<ul class="help-nav-list">
<li><a href="#cfg-search" class="help-nav-link" data-i18n="help.nav_search"></a></li>
<li><a href="#cfg-recent" class="help-nav-link" data-i18n="config.section_recent"></a></li>
<li><a href="#cfg-backend-settings" class="help-nav-link" data-i18n="config.section_backend"></a></li>
<li><a href="#cfg-tags" class="help-nav-link" data-i18n="config.section_tags"></a></li>
<li><a href="#cfg-sync" class="help-nav-link" data-i18n="settings.sync"></a></li>
<li><a href="#cfg-hidden-files" class="help-nav-link" data-i18n="config.section_hidden"></a></li>
<li><a href="#cfg-diags" class="help-nav-link" data-i18n="settings.diagnostics"></a></li>
<li><a href="#cfg-ai" class="help-nav-link" data-i18n="settings.ai"></a></li>
<li><a href="#cfg-themes" class="help-nav-link" data-i18n="settings.themes"></a></li>
<li><a href="#cfg-profile" class="help-nav-link" data-i18n="settings.profile"></a></li>
<li><a href="#cfg-security" class="help-nav-link" data-i18n="settings.security"></a></li>
<li><a href="#cfg-push" class="help-nav-link" data-i18n="config.section_push">Notifications push</a></li>
<li><a href="#cfg-plugins" class="help-nav-link" data-i18n="config.section_plugins">🧩 Plugins</a></li>
<li><a href="#cfg-about" class="help-nav-link" data-i18n="settings.about"></a></li>
<li><a href="#cfg-webhooks" class="help-nav-link" data-i18n="config.section_webhooks"></a></li>
<li><a href="#cfg-partages-publics" class="help-nav-link" data-i18n="config.section_shares"></a></li>
</ul>
</nav>
<div class="help-content" id="config-scroll">
<div class="config-content">
<!-- Performance Settings - Frontend -->
<section
class="config-section help-section"
id="cfg-search"
>
<h2 data-i18n="config.section_search">Paramètres de recherche</h2>
<p class="config-description" data-i18n="auto.7e13c97a">
Ces paramètres s'appliquent immédiatement
côté client.
</p>
<div class="config-row">
<label
class="config-label"
for="cfg-debounce"
>Délai debounce (ms)</label
>
<input
type="number"
id="cfg-debounce"
class="config-input config-input--num"
min="100"
max="2000"
step="50"
value="300"
/>
<span class="config-hint"
>Délai avant exécution de la recherche
(100-2000)</span
>
</div>
<div class="config-row">
<label
class="config-label"
for="cfg-results-per-page"
>Résultats par page</label
>
<input
type="number"
id="cfg-results-per-page"
class="config-input config-input--num"
min="10"
max="200"
step="10"
value="50"
/>
<span class="config-hint"
>Nombre de résultats affichés par page
(10-200)</span
>
</div>
<div class="config-row">
<label
class="config-label"
for="cfg-min-query"
>Longueur min. requête</label
>
<input
type="number"
id="cfg-min-query"
class="config-input config-input--num"
min="1"
max="5"
step="1"
value="2"
/>
<span class="config-hint"
>Nombre minimum de caractères avant
recherche (1-5)</span
>
</div>
<div class="config-row">
<label
class="config-label"
for="cfg-timeout"
>Timeout recherche (ms)</label
>
<input
type="number"
id="cfg-timeout"
class="config-input config-input--num"
min="5000"
max="120000"
step="5000"
value="30000"
/>
<span class="config-hint"
>Annuler la recherche après ce délai
(5000-120000)</span
>
</div>
</section>
<!-- Recent History -->
<section id="cfg-recent" class="config-section help-section">
<h2>
📋 <span data-i18n="settings.history_section">Historique récent</span>
<span class="config-badge-restart"
data-i18n="settings.no_restart_badge">Redémarrage non requis</span
>
</h2>
<div class="config-row">
<label
class="config-label"
for="cfg-recent-limit"
data-i18n="settings.history_count_label"
>Nombre de fichiers dans l'historique</label
>
<input
type="number"
id="cfg-recent-limit"
class="config-input config-input--num"
min="5"
max="100"
step="5"
value="20"
/>
<span class="config-hint"
data-i18n="settings.history_count_desc"
>Entre 5 et 100 fichiers (sauvegarde sur le serveur)</span
>
</div>
<div class="config-row">
<label
class="config-label"
for="cfg-max-backups"
data-i18n="config.max_backups_label"
>Max backups par fichier</label
>
<input
type="number"
id="cfg-max-backups"
class="config-input config-input--num"
min="1"
max="50"
step="1"
value="10"
/>
<span class="config-hint"
data-i18n="config.max_backups_desc"
>Nombre maximum de versions backup conservées par fichier (1-50)</span
>
</div>
</section>
<!-- Performance Settings - Backend -->
<section id="cfg-backend-settings" class="config-section help-section">
<h2 data-i18n="config.section_backend">
Paramètres backend
<span class="config-badge-restart"
data-i18n="config.restart_required">Redémarrage requis</span
>
</h2>
<p class="config-description" data-i18n="auto.b114ed1c">
Ces paramètres sont sauvegardés sur le
serveur. Certains nécessitent un redémarrage
ou une réindexation.
</p>
<div class="config-row">
<label
class="config-label"
for="cfg-workers"
data-i18n="config.search_workers_label"
>Workers de recherche</label
>
<input
type="number"
id="cfg-workers"
class="config-input config-input--num"
min="1"
max="8"
step="1"
value="2"
/>
<span class="config-hint"
data-i18n="config.search_workers_hint"
>Threads dédiés à la recherche (1-8)</span
>
</div>
<div class="config-row">
<label
class="config-label"
for="cfg-max-content"
data-i18n="config.max_content_label"
>Taille max contenu (octets)</label
>
<input
type="number"
id="cfg-max-content"
class="config-input config-input--num"
min="10000"
max="1000000"
step="10000"
value="100000"
/>
<span class="config-hint"
data-i18n="config.max_content_hint"
>Contenu indexé par fichier (10K-1M). Réindexation requise.</span
>
</div>
<div class="config-row">
<label
class="config-label"
for="cfg-title-boost"
data-i18n="config.title_boost_label"
>Boost titre</label
>
<input
type="number"
id="cfg-title-boost"
class="config-input config-input--num"
min="0.1"
max="10"
step="0.5"
value="3"
/>
<span class="config-hint"
data-i18n="config.title_boost_hint"
>Multiplicateur de pertinence pour les correspondances dans le titre</span
>
</div>
<div class="config-row">
<label
class="config-label"
for="cfg-tag-boost"
data-i18n="config.tag_boost_label"
>Boost tags</label
>
<input
type="number"
id="cfg-tag-boost"
class="config-input config-input--num"
min="0.1"
max="10"
step="0.5"
value="2"
/>
<span class="config-hint"
data-i18n="config.tag_boost_hint"
>Multiplicateur de pertinence pour les correspondances dans les tags</span
>
</div>
<div class="config-row">
<label
class="config-label"
for="cfg-prefix-exp"
data-i18n="config.prefix_expansions_label"
>Expansions préfixe max</label
>
<input
type="number"
id="cfg-prefix-exp"
class="config-input config-input--num"
min="10"
max="200"
step="10"
value="50"
/>
<span class="config-hint"
data-i18n="config.prefix_expansions_hint"
>Nombre max de tokens élargis par préfixe (10-200)</span
>
</div>
</section>
<div class="config-actions-row">
<button class="config-btn-save"
id="cfg-save-backend" data-i18n="help.shortcut_save">
Sauvegarder
</button>
<button class="config-btn-secondary"
id="cfg-reindex" data-i18n="settings.reindex">
Forcer réindexation
</button>
<button class="config-btn-secondary"
id="cfg-reset-defaults" data-i18n="config.reset">
Réinitialiser
</button>
</div>
</section>
<!-- Tag Filtering (existing) -->
<section class="config-section help-section">
<h2 id="cfg-tags" data-i18n="config.tag_filtering_label">Filtrage de tags</h2>
<p class="config-description" data-i18n="auto.e0b8a606">
Définissez les patterns de tags à masquer
dans la sidebar. Vous pouvez utiliser des
wildcards pour cibler les tags de template.
</p>
<div
class="config-filters-list"
id="config-filters-list"
></div>
<div class="config-add-pattern">
<input
type="text"
id="config-pattern-input"
placeholder="Ex: #&lt;% ... %&gt; ou #{{ ... }}"
class="config-input"
/>
<button id="config-add-btn"
class="config-btn-add" data-i18n="config.add_webhook">
Ajouter
</button>
</div>
<div
class="config-regex-preview"
id="config-regex-preview"
style="display: none"
>
<small
>Regex :
<code id="config-regex-code"></code
></small>
</div>
</section>
<!-- Watcher / Synchronisation -->
<section
class="config-section help-section"
id="cfg-sync"
>
<h2 data-i18n="config.section_watcher">Synchronisation automatique</h2>
<p class="config-description" data-i18n="auto.d33601dc">
Surveillance des fichiers en temps réel via
watchdog. Les modifications sont détectées
automatiquement et l'index est mis à jour
sans redémarrage.
</p>
<div class="config-row">
<label
class="config-label"
for="cfg-watcher-enabled"
>Activer la surveillance</label
>
<label class="config-toggle">
<input
type="checkbox"
id="cfg-watcher-enabled"
checked
/>
<span
class="config-toggle-slider"
></span>
</label>
<span class="config-hint"
>Activer/désactiver la surveillance
automatique des fichiers</span
>
</div>
<div class="config-row">
<label
class="config-label"
for="cfg-watcher-polling"
>Mode polling (fallback)</label
>
<label class="config-toggle">
<input
type="checkbox"
id="cfg-watcher-polling"
/>
<span
class="config-toggle-slider"
></span>
</label>
<span class="config-hint"
>Forcer le mode polling au lieu de
inotify natif (utile si le mode natif ne
fonctionne pas)</span
>
</div>
<div class="config-row">
<label
class="config-label"
for="cfg-watcher-interval"
>Intervalle polling (s)</label
>
<input
type="number"
id="cfg-watcher-interval"
class="config-input config-input--num"
min="1"
max="30"
step="1"
value="5"
/>
<span class="config-hint"
>Intervalle de scrutation en mode
polling (1-30 secondes)</span
>
</div>
<div class="config-row">
<label
class="config-label"
for="cfg-watcher-debounce"
>Debounce (s)</label
>
<input
type="number"
id="cfg-watcher-debounce"
class="config-input config-input--num"
min="0.5"
max="10"
step="0.5"
value="2"
/>
<span class="config-hint"
>Délai avant traitement des changements
groupés (0.5-10 secondes)</span
>
</div>
</section>
<!-- Hidden Files/Folders Configuration -->
<section id="cfg-hidden-files" class="config-section help-section">
<h2 data-i18n="auto.f8ba6127">🗂️ Fichiers cachés</h2>
<p class="config-description">
Contrôlez l'affichage des fichiers/dossiers
cachés (commençant par <code>.</code>) par
vault.
</p>
<p
class="config-hint"
style="
margin-bottom: 12px;
padding: 8px;
background: var(--background-secondary);
border-radius: 4px;
"
>
i️ <strong data-i18n="config.note">Note :</strong> Tous les fichiers
sont toujours indexés et cherchables. Ce
paramètre contrôle uniquement leur
visibilité dans l'interface.
</p>
<div id="hidden-files-vault-list">
<!-- Vault-specific settings will be injected here -->
</div>
<div
class="config-actions-row"
style="margin-top: 16px"
>
<button
class="config-btn-save"
id="cfg-save-hidden-files"
>
💾 Sauvegarder
</button>
</div>
</section>
<!-- Diagnostics -->
<section
class="config-section help-section"
id="cfg-diags"
>
<h2 data-i18n="config.diagnostics">Diagnostics</h2>
<p class="config-description">
Statistiques de l'index et du moteur de
recherche.
</p>
<div
id="config-diagnostics"
class="config-diagnostics"
>
<div class="config-diag-loading" data-i18n="common.loading">
Chargement...
</div>
</div>
<button class="config-btn-secondary"
id="cfg-refresh-diag"
style="margin-top: 8px" data-i18n="header.refresh">
Rafraîchir
</button>
</section>
<!-- Cles API AI -->
<section
class="config-section help-section"
id="cfg-ai"
>
<h2>🤖 Cles API Intelligence Artificielle</h2>
<p class="config-description">
Configurez vos cles API. Cliquez sur
<strong data-i18n="config.test">Tester</strong> pour charger les
modeles.
</p>
<div class="config-row">
<label
class="config-label"
for="cfg-ai-default-provider"
data-i18n="config.ai_default_provider"
>Fournisseur par defaut</label
>
<select
id="cfg-ai-default-provider"
class="config-select"
style="width: 200px"
>
<option value="">--</option>
</select>
</div>
<div class="config-row">
<label
class="config-label"
for="cfg-ai-default-model"
data-i18n="config.ai_default_model"
>Modele par defaut</label
>
<select
id="cfg-ai-default-model"
class="config-select"
style="width: 220px"
>
<option value="">--</option>
</select>
</div>
<div class="config-row" style="align-items: flex-start">
<label
class="config-label"
data-i18n="config.ai_capabilities"
>Capacites du modele</label
>
<div
id="cfg-ai-default-model-caps"
class="ai-picker-caps"
style="flex: 1"
></div>
</div>
<div class="config-row">
<label
class="config-label"
for="cfg-deepseek-key"
>DeepSeek API Key</label
>
<input
type="password"
id="cfg-deepseek-key"
class="config-input"
placeholder="sk-..."
autocomplete="off"
/>
<select
id="cfg-deepseek-model"
class="config-select"
style="width: 200px"
>
<option value="">-- Modele --</option>
</select>
</div>
<div class="config-row">
<label
class="config-label"
for="cfg-openrouter-key"
>OpenRouter API Key</label
>
<input
type="password"
id="cfg-openrouter-key"
class="config-input"
placeholder="sk-or-..."
autocomplete="off"
/>
<select
id="cfg-openrouter-model"
class="config-select"
style="width: 200px"
>
<option value="">-- Modele --</option>
</select>
</div>
<div class="config-row">
<label
class="config-label"
for="cfg-gemini-key"
>Gemini API Key</label
>
<input
type="password"
id="cfg-gemini-key"
class="config-input"
placeholder="AIza..."
autocomplete="off"
/>
<select
id="cfg-gemini-model"
class="config-select"
style="width: 200px"
>
<option value="">-- Modele --</option>
</select>
</div>
<div class="config-row">
<label
class="config-label"
for="cfg-nvidia-key"
>NVIDIA API Key</label
>
<input
type="password"
id="cfg-nvidia-key"
class="config-input"
placeholder="nvapi-..."
autocomplete="off"
/>
<select
id="cfg-nvidia-model"
class="config-select"
style="width: 200px"
>
<option value="">-- Modele --</option>
</select>
</div>
<div class="config-row">
<label
class="config-label"
for="cfg-qwencloud-key"
>QwenCloud API Key</label
>
<input
type="password"
id="cfg-qwencloud-key"
class="config-input"
placeholder="sk-..."
autocomplete="off"
/>
<select
id="cfg-qwencloud-model"
class="config-select"
style="width: 200px"
>
<option value="">-- Modele --</option>
</select>
</div>
<div class="config-row">
<label
class="config-label"
for="cfg-xiaomi-key"
>Xiaomi API Key</label
>
<input
type="password"
id="cfg-xiaomi-key"
class="config-input"
placeholder="xm-..."
autocomplete="off"
/>
<select
id="cfg-xiaomi-model"
class="config-select"
style="width: 200px"
>
<option value="">-- Modele --</option>
</select>
</div>
<div class="config-row">
<label
class="config-label"
for="cfg-mistral-key"
>Mistral API Key</label
>
<input
type="password"
id="cfg-mistral-key"
class="config-input"
placeholder="sk-..."
autocomplete="off"
/>
<select
id="cfg-mistral-model"
class="config-select"
style="width: 200px"
>
<option value="">-- Modele --</option>
</select>
</div>
<div
class="config-actions-row"
style="margin-top: 16px"
>
<button class="config-btn-save"
id="cfg-save-ai-keys" data-i18n="help.shortcut_save">
Sauvegarder
</button>
<button class="config-btn-secondary"
id="cfg-test-ai-keys" data-i18n="config.test">
Tester
</button>
<span
id="cfg-ai-status"
style="
font-size: 12px;
color: var(--text-muted);
margin-left: 12px;
"
></span>
</div>
</section>
<!-- Themes -->
<section
class="config-section help-section"
id="cfg-themes"
>
<h2 data-i18n="auto.c14b5603">🎨 Thèmes</h2>
<p class="config-description" data-i18n="auto.caac40b9">
Choisissez un thème visuel. Chaque thème
offre un mode sombre, clair, contraste élevé et sépia.
</p>
<div class="theme-grid" id="theme-grid">
<div class="config-diag-loading" data-i18n="common.loading">Chargement...</div>
</div>
</section>
<!-- Profil -->
<section
class="config-section help-section"
id="cfg-profile"
>
<h2 data-i18n="config.profile">👤 Profil utilisateur</h2>
<p class="config-description" data-i18n="config.profile_desc">
Préférences personnelles pour personnaliser
l'expérience ObsiGate.
</p>
<div class="profile-form" id="profile-form">
<div class="profile-field">
<label for="profile-lang" data-i18n="config.lang_label">Langue préférée</label>
<select id="profile-lang" class="config-select">
<option value="fr">Francais</option>
<option value="en">English</option>
<option value="es">Espanol</option>
<option value="de">Deutsch</option>
</select>
<span class="profile-hint" data-i18n="config.lang_hint">Utilisée par l'autocomplétion et le ghost text.</span>
</div>
<div class="profile-field">
<label for="profile-name" data-i18n="config.display_name">Nom d'affichage</label>
<input type="text" id="profile-name" class="config-input" placeholder="Votre nom" maxlength="60">
</div>
<button class="config-save-btn" id="profile-save" data-i18n="config.save">Enregistrer</button>
<button class="config-save-btn" id="profile-logout" style="background:var(--danger-bg);color:var(--danger);border-color:var(--danger);margin-left:8px" data-i18n="config.logout" onclick="if(window.handleLogout)window.handleLogout();else{doLogoutFallback()}">Déconnexion</button>
<span class="profile-saved" id="profile-saved" style="display:none" data-i18n="config.saved">✓ Sauvegardé</span>
</div>
</section>
<!-- Sécurité du compte (MFA) -->
<section
class="config-section help-section"
id="cfg-security"
>
<h2 data-i18n="settings.security">🔒 Sécurité du compte</h2>
<p class="config-description" data-i18n="settings.security_desc">
Activez l'authentification à deux facteurs (2FA) pour renforcer la sécurité de votre compte.
</p>
<div id="mfa-settings">
<div id="mfa-status" class="mfa-status-section">
<div class="mfa-status-row">
<span class="mfa-status-label" data-i18n="mfa.status_label">Authentification 2FA</span>
<span id="mfa-status-badge" class="mfa-badge mfa-badge-off" data-i18n="mfa.disabled">Désactivée</span>
</div>
<div id="mfa-setup-area"></div>
</div>
</div>
</section>
<!-- Notifications push -->
<section
class="config-section help-section"
id="cfg-push"
>
<h2 data-i18n="config.section_push">🔔 Notifications push</h2>
<p class="config-description">
Recevez des notifications web push
lorsque des fichiers changent dans vos
vaults, même si ObsiGate n'est pas ouvert.
</p>
<div id="push-settings">
<div class="config-diag-loading" data-i18n="common.loading">
Chargement...
</div>
</div>
</section>
<!-- Plugins -->
<section
class="config-section help-section"
id="cfg-plugins"
>
<h2 data-i18n="config.section_plugins">🧩 Plugins</h2>
<p class="config-description" data-i18n="plugins.description">
Extend ObsiGate with custom renderers, search filters, and editor actions.
</p>
<div id="plugins-settings-container"></div>
</section>
<!-- À propos -->
<section
class="config-section help-section"
id="cfg-about"
>
<h2 data-i18n="auto.a3319169">📦 À propos</h2>
<div
id="config-about"
class="config-diagnostics"
>
<div class="config-diag-loading" data-i18n="common.loading">
Chargement...
</div>
</div>
</section>
<!-- Webhooks -->
<section id="cfg-webhooks" class="config-section help-section">
<h2>🔔 Webhooks</h2>
<p class="config-description">
Notifications HTTP vers des services
externes lors des changements de fichiers.
</p>
<div id="webhooks-list"></div>
<div class="config-add-row">
<input
type="text"
id="webhook-name-input"
data-i18n-placeholder="config.webhook_name_placeholder" placeholder="Nom"
class="config-input"
style="width: 100px"
/>
<input
type="text"
id="webhook-url-input"
placeholder="https://..."
class="config-input"
style="flex: 1"
/>
<button id="webhook-add-btn"
class="config-btn config-btn-add" data-i18n="config.add_webhook">
Ajouter
</button>
</div>
</section>
<!-- Partages publics -->
<section class="config-section help-section">
<h2 id="cfg-partages-publics">📤 Partages publics</h2>
<p class="config-description">
Liens de partage publics pour des documents
(lecture seule, sans authentification).
</p>
<div id="shares-list"></div>
</section>
</div>
</div>
</div>
</div>
</div>
<!-- Graph View Modal -->
<div class="editor-modal" id="graph-modal">
<div
class="editor-container"
style="
max-width: 95vw;
width: 1000px;
height: 85vh;
display: flex;
flex-direction: column;
"
>
<!-- Header: 2 lignes — nav+titre+contrôles | recherche -->
<div
class="editor-header"
style="
display: flex;
flex-direction: column;
gap: 6px;
padding: 8px 12px;
border-bottom: 1px solid var(--border-color);
"
>
<!-- Line 1: nav + title + controls -->
<div
style="
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
"
>
<button
class="editor-btn"
id="graph-nav-back"
title="Retour"
style="
font-size: 0.75rem;
padding: 2px 4px;
opacity: 0.3;
flex-shrink: 0;
min-width: 22px;
"
disabled
>
←
</button>
<button
class="editor-btn"
id="graph-nav-fwd"
title="Avancer"
style="
font-size: 0.75rem;
padding: 2px 4px;
opacity: 0.3;
flex-shrink: 0;
min-width: 22px;
"
disabled
>
→
</button>
<button
class="editor-btn"
id="graph-nav-up"
title="Dossier parent"
style="
font-size: 0.65rem;
padding: 2px 5px;
flex-shrink: 0;
display: none;
"
>
↑
</button>
<div
class="editor-title"
id="graph-title"
style="
font-weight: 600;
font-size: 0.85rem;
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
"
>
Vue Graphique
</div>
<button
class="editor-btn"
id="graph-full-vault"
title="Vue complète du vault"
style="
font-size: 0.65rem;
padding: 3px 6px;
flex-shrink: 0;
"
>
🌐 Tout
</button>
<span
style="
font-size: 0.6rem;
color: var(--text-muted);
flex-shrink: 0;
"
>Prof.</span
>
<input
type="range"
id="graph-depth"
min="0"
max="3"
value="1"
style="width: 40px; flex-shrink: 0"
title="Profondeur"
/>
<button
class="editor-btn"
id="graph-zoom-out"
title="Zoom arrière"
style="flex-shrink: 0"
>
<i
data-lucide="zoom-out"
style="width: 14px; height: 14px"
></i>
</button>
<button
class="editor-btn"
id="graph-zoom-in"
title="Zoom avant"
style="flex-shrink: 0"
>
<i
data-lucide="zoom-in"
style="width: 14px; height: 14px"
></i>
</button>
<button
class="editor-btn"
id="graph-reset"
title="Réinitialiser"
style="flex-shrink: 0"
>
<i
data-lucide="maximize"
style="width: 14px; height: 14px"
></i>
</button>
<button
class="editor-btn"
id="graph-export"
title="Exporter PNG"
style="flex-shrink: 0"
>
<i
data-lucide="download"
style="width: 14px; height: 14px"
></i>
</button>
<button
class="editor-btn"
id="graph-fullscreen"
title="Plein écran"
style="flex-shrink: 0"
>
<i
data-lucide="expand"
style="width: 14px; height: 14px"
></i>
</button>
<button
class="editor-btn"
id="graph-close"
title="Fermer"
style="flex-shrink: 0"
>
<i
data-lucide="x"
style="width: 15px; height: 15px"
></i>
</button>
</div>
<!-- Line 2: centered search -->
<div style="display: flex; justify-content: center">
<div
style="
position: relative;
width: 100%;
max-width: 450px;
"
>
<i
data-lucide="search"
style="
position: absolute;
left: 10px;
top: 50%;
transform: translateY(-50%);
width: 14px;
height: 14px;
color: var(--text-muted);
pointer-events: none;
"
></i>
<input
type="text"
id="graph-search"
placeholder="Rechercher ou #tag..."
style="
width: 100%;
font-size: 0.78rem;
padding: 5px 12px 5px 30px;
border-radius: 8px;
border: none;
background: var(--bg-secondary);
color: var(--text-primary);
outline: none;
box-shadow: inset 0 1px 3px
rgba(0, 0, 0, 0.2);
"
/>
<span
style="
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
font-size: 0.6rem;
color: var(--text-muted);
opacity: 0.4;
pointer-events: none;
"
>Ctrl+survol</span
>
</div>
</div>
</div>
<div
class="editor-body"
style="
flex: 1;
overflow: hidden;
position: relative;
padding: 0;
"
>
<canvas
id="graph-canvas"
style="width: 100%; height: 100%; cursor: grab"
></canvas>
<!-- Info panel: top-right, shows on hover, stays until closed -->
<div
id="graph-info-panel"
style="
display: none;
position: absolute;
top: 10px;
left: 10px;
max-width: 320px;
background: var(--bg-primary);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 8px;
padding: 12px 14px;
font-size: 0.78rem;
color: var(--text-primary);
z-index: 10;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
"
>
<button
id="graph-info-close"
style="
position: absolute;
top: 6px;
right: 8px;
background: none;
border: none;
color: var(--text-muted);
cursor: pointer;
font-size: 1rem;
line-height: 1;
padding: 2px;
"
title="Fermer"
>
✕
</button>
<div id="graph-info-content"></div>
</div>
<!-- Preview panel: right sidebar, full height, shows on Ctrl+hover, stays until closed -->
<div
id="graph-preview-panel"
style="
display: none;
position: absolute;
top: 0;
right: 0;
width: 380px;
height: 100%;
background: var(--bg-primary);
border-left: 1px solid var(--border-color);
z-index: 9;
overflow-y: auto;
font-size: 0.8rem;
color: var(--text-primary);
box-shadow: -4px 0 16px rgba(0, 0, 0, 0.3);
"
>
<button
id="graph-preview-close"
style="
position: sticky;
top: 0;
float: right;
background: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: 4px;
color: var(--text-muted);
cursor: pointer;
font-size: 0.9rem;
padding: 2px 8px;
z-index: 1;
margin-bottom: -24px;
"
title="Fermer"
>
✕ Fermer
</button>
<div
id="graph-preview-content"
style="padding: 16px; padding-top: 8px"
></div>
</div>
<!-- Status bar + legend -->
<div
style="
position: absolute;
bottom: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 4px 12px;
background: var(--bg-primary);
border-top: 1px solid var(--border-color);
font-size: 0.7rem;
color: var(--text-muted);
"
>
<span id="graph-status">—</span>
<div
style="
display: flex;
align-items: center;
gap: 10px;
"
>
<label
style="
cursor: pointer;
display: flex;
align-items: center;
gap: 3px;
"
><input
type="checkbox"
id="graph-filter-dir"
checked
/>
🟦 Dossier</label
>
<label
style="
cursor: pointer;
display: flex;
align-items: center;
gap: 3px;
"
><input
type="checkbox"
id="graph-filter-md"
checked
/>
🟩 .md</label
>
<label
style="
cursor: pointer;
display: flex;
align-items: center;
gap: 3px;
"
><input
type="checkbox"
id="graph-filter-other"
checked
/>
⬜ Autre</label
>
<span style="opacity: 0.5">│</span>
<span>── Parent</span>
<span style="color: var(--accent-color, #2563eb)"
>┅┅ Wikilink</span
>
<span style="color: #e74c3c">← Backlink</span>
</div>
</div>
</div>
</div>
</div>
<!-- Help Modal -->
<div class="editor-modal" id="help-modal">
<div class="editor-container help-container">
<div class="editor-header">
<button
class="help-hamburger"
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';
}
"
>
<i
data-lucide="menu"
style="width: 18px; height: 18px"
></i>
</button>
<div class="editor-title" id="help-title" data-i18n="help.modal_title">
Guide d'utilisation ObsiGate
</div>
<div class="editor-actions">
<button
class="editor-btn"
id="help-close"
title="Fermer l'aide"
aria-label="Fermer l'aide"
>
<i
data-lucide="x"
style="width: 16px; height: 16px"
></i>
</button>
</div>
</div>
<div class="editor-body help-body" id="help-body">
<nav class="help-nav" id="help-nav">
<div class="help-nav-header">
<div class="help-nav-title" data-i18n="help.toc_title">Sommaire</div>
</div>
<div class="help-nav-search-wrap">
<input
type="text"
class="help-nav-search"
id="help-nav-search"
data-i18n-placeholder="help.search_placeholder" data-i18n-placeholder="help.search_placeholder" placeholder="Rechercher dans l'aide..."
autocomplete="off"
spellcheck="false"
/>
<button
class="help-search-clear"
id="help-search-clear"
title="Effacer la recherche"
aria-label="Effacer"
onclick="
var s =
document.getElementById(
'help-nav-search',
);
if (s) {
s.value = '';
s.dispatchEvent(new Event('input'));
s.focus();
}
"
>
✕
</button>
</div>
<ul class="help-nav-list" id="help-nav-list">
<li>
<a href="#help-intro" class="help-nav-link"
data-i18n="help.nav_intro">📘 Introduction</a
>
</li>
<li>
<a href="#help-interface" class="help-nav-link"
data-i18n="help.nav_interface">🧭 Interface</a
>
</li>
<li>
<a href="#help-navigation" class="help-nav-link"
data-i18n="help.nav_navigation">🗺️ Navigation</a
>
</li>
<li>
<a href="#help-tabs" class="help-nav-link"
data-i18n="help.nav_tabs">📑 Onglets</a
>
</li>
<li>
<a href="#help-recherche" class="help-nav-link"
data-i18n="help.nav_search">🔍 Recherche</a
>
</li>
<li>
<a href="#help-tags" class="help-nav-link"
data-i18n="help.nav_tags">🏷️ Tags</a
>
</li>
<li>
<a href="#help-fichiers" class="help-nav-link"
data-i18n="help.nav_files">📁 Fichiers</a
>
</li>
<li>
<a href="#help-excalidraw" class="help-nav-link"
data-i18n="help.nav_excalidraw">🎨 Excalidraw</a
>
</li>
<li>
<a href="#help-edition" class="help-nav-link"
data-i18n="help.nav_editing">✏️ Édition</a
>
</li>
<li>
<a href="#help-mobile-editor" class="help-nav-link"
data-i18n="help.nav_mobile_editor">📱 Mobile</a
>
</li>
<li>
<a href="#help-graphe" class="help-nav-link"
data-i18n="help.nav_graph">🗺️ Graphe</a
>
</li>
<li>
<a href="#help-ai" class="help-nav-link"
data-i18n="help.nav_ai">🤖 IA Éditeur</a
>
</li>
<li>
<a href="#help-palette" class="help-nav-link"
data-i18n="help.nav_palette">⌨️ Palette</a
>
</li>
<li>
<a href="#help-raccourcis" class="help-nav-link"
data-i18n="help.nav_shortcuts">⌨️ Raccourcis</a
>
</li>
<li>
<a href="#help-partage" class="help-nav-link"
data-i18n="help.nav_sharing">🔗 Partage</a
>
</li>
<li>
<a href="#help-webhooks" class="help-nav-link"
data-i18n="help.nav_webhooks">🪝 Webhooks</a
>
</li>
<li>
<a
href="#help-sauvegardes"
class="help-nav-link"
data-i18n="help.nav_backups">💾 Sauvegardes</a
>
</li>
<li>
<a href="#help-securite" class="help-nav-link"
data-i18n="help.nav_security">🛡️ Sécurité</a
>
</li>
<li>
<a
href="#help-personnalisation"
class="help-nav-link"
data-i18n="help.nav_customization">🎨 Personnalisation</a
>
</li>
<li>
<a href="#help-astuces" class="help-nav-link"
data-i18n="help.nav_tips">💡 Astuces</a
>
</li>
<li>
<a href="#help-plugins" class="help-nav-link"
data-i18n="help.nav_plugins">🧩 Plugins</a
>
</li>
</ul>
</nav>
<div class="help-content">
<section class="help-section help-hero" id="help-intro">
<div class="help-hero-copy">
<div class="help-kicker" data-i18n="help.guide_official">Guide officiel</div>
<h1 data-i18n="help.welcome_title">📘 Bienvenue dans ObsiGate</h1>
<p class="help-lead" data-i18n="auto.6c166d35">
ObsiGate est votre porte d'entrée web vers
vos vaults Obsidian. Accédez, naviguez et
recherchez dans toutes vos notes depuis
n'importe quel appareil via une interface
moderne et responsive.
</p>
<div class="help-badges">
<span class="help-badge" data-i18n="help.welcome_tag1"
>⚡ Navigation rapide</span
>
<span class="help-badge" data-i18n="help.welcome_tag2"
>🗂️ Multi-vault</span
>
<span class="help-badge" data-i18n="help.welcome_tag3"
>🏷️ Tags intelligents</span
>
<span class="help-badge" data-i18n="help.welcome_tag4"
>📝 Lecture et édition</span
>
<span class="help-badge" data-i18n="help.welcome_tag5"
>🔍 Recherche TF-IDF</span
>
</div>
</div>
<div class="help-hero-visual" aria-hidden="true">
<div class="help-stat-card">
<span class="help-stat-label" data-i18n="help.experience"
>Expérience</span
>
<strong data-i18n="help.simple">Simple</strong>
</div>
<div class="help-stat-card">
<span class="help-stat-label" data-i18n="help.search_bar"
>Recherche</span
>
<strong data-i18n="help.instant">Instantanée</strong>
</div>
<div class="help-stat-card">
<span class="help-stat-label" data-i18n="help.access"
>Accès</span
>
<strong data-i18n="help.multi_vault">Multi-vault</strong>
</div>
</div>
</section>
<section class="help-section" id="help-interface">
<h2 data-i18n="help.nav_interface">🧭 Interface utilisateur</h2>
<h3 data-i18n="help.header_section">En-tête</h3>
<p data-i18n="auto.209c5490">
La barre supérieure contient les éléments
essentiels :
</p>
<ul>
<li>
<strong data-i18n="help.hamburger_btn">Bouton menu hamburger</strong>
: <span data-i18n="help.desc_hamburger">obile) : Affiche/masque la sidebar</span>
</li>
<li>
<strong data-i18n="help.sidebar_toggle_btn">Bouton toggle sidebar</strong> <span data-i18n="help.desc_sidebar_toggle">(desktop) : Masque la sidebar pour plus
d'espace</span>
</li>
<li>
<strong data-i18n="help.logo">Logo ObsiGate</strong> <span data-i18n="help.desc_logo">: Retour à
l'accueil</span>
</li>
<li>
<strong data-i18n="help.search_bar">Barre de recherche</strong> <span data-i18n="help.desc_search_bar">:
Recherche globale avec autocomplétion</span>
</li>
<li>
<strong data-i18n="help.vault_indicator">Indicateur de vault</strong> <span data-i18n="help.desc_vault_indicator">:
Affiche le contexte actif (All, vault1,
etc.)</span>
</li>
<li>
<strong data-i18n="help.menu_options">Menu Options</strong> <span data-i18n="help.desc_menu_options">: Accès aux
paramètres, thème, aide</span>
</li>
</ul>
<h3 data-i18n="help.sidebar_section">Sidebar (barre latérale)</h3>
<p data-i18n="help.sidebar_tabs">La sidebar est divisée en deux onglets :</p>
<ul>
<li>
<strong data-i18n="sidebar.tab_vaults">Vaults</strong> <span data-i18n="help.desc_vaults_tab">: Arborescence des
fichiers et dossiers</span>
</li>
<li>
<strong data-i18n="sidebar.tab_tags">Tags</strong> <span data-i18n="help.desc_tags_tab">: Nuage de tags
cliquables</span>
</li>
</ul>
<p data-i18n="help.sidebar_features">Fonctionnalités de la sidebar :</p>
<ul>
<li>
<strong data-i18n="help.filtering">Filtrage</strong> <span data-i18n="help.desc_filtering">: Barre de
recherche pour filtrer fichiers/tags</span>
</li>
<li>
<strong data-i18n="help.quick_select">Sélection rapide</strong> <span data-i18n="help.desc_quick_select">: Dropdown
pour changer de vault</span>
</li>
<li>
<strong data-i18n="help.resize">Redimensionnement</strong> <span data-i18n="help.desc_resize">: Glissez
le bord droit pour ajuster la largeur</span>
</li>
<li>
<strong data-i18n="help.hide">Masquage</strong> <span data-i18n="help.desc_hide">: Cliquez sur
l'icône sidebar dans le header</span>
</li>
</ul>
<h3 data-i18n="help.content_area">Zone de contenu</h3>
<p data-i18n="help.content_shows">La zone centrale affiche :</p>
<ul>
<li>
<strong data-i18n="help.breadcrumb">Breadcrumb</strong> <span data-i18n="help.desc_breadcrumb">: Chemin du
fichier (cliquable pour repositionner la
sidebar)</span>
</li>
<li>
<strong data-i18n="help.title_tags">Titre et tags</strong> <span data-i18n="help.desc_title_tags">: Métadonnées
du fichier</span>
</li>
<li>
<strong data-i18n="help.action_buttons">Boutons d'action</strong> <span data-i18n="help.desc_action_buttons">: Copier,
Source, Télécharger, Éditer, Ouvrir dans
nouvelle fenêtre</span>
</li>
<li>
<strong data-i18n="help.rendered_content">Contenu rendu</strong> <span data-i18n="help.desc_rendered_content">: Markdown
avec wikilinks et images</span>
</li>
</ul>
</section>
<section class="help-section" id="help-navigation">
<h2 data-i18n="help.733f0559">🗺️ Navigation</h2>
<h3 data-i18n="help.tree_section">Arborescence des vaults</h3>
<p data-i18n="auto.4c908ade">
Cliquez sur un vault pour l'ouvrir et voir son
contenu. Les dossiers affichent un chevron et un
badge avec le nombre d'éléments.
</p>
<ul>
<li>
<strong data-i18n="sidebar.expand_collapse">Développer/Réduire</strong><span data-i18n="help.desc_expand">:
Cliquez sur un dossier</span>
</li>
<li>
<strong data-i18n="sidebar.open_file">Ouvrir un fichier</strong><span data-i18n="help.desc_open_file">: Cliquez
sur le nom du fichier</span>
</li>
<li>
<strong data-i18n="sidebar.file_icons">Icônes</strong><span data-i18n="help.desc_fdeaf803"> : Chaque type de
fichier a son icône (markdown, code, etc.)</span></li>
</ul>
<h3 data-i18n="help.sidebar_filtering">Filtrage de la sidebar</h3>
<p data-i18n="help.desc_filter_usage">
Utilisez la barre de filtrage en haut de la
sidebar pour rechercher rapidement :
</p>
<ul>
<li>
<strong data-i18n="sidebar.filter_instant">Recherche instantanée</strong><span data-i18n="help.desc_instant_filter">:
Tapez pour filtrer fichiers et dossiers</span>
</li>
<li>
<strong data-i18n="help.case_sensitive">Case-sensitive</strong><span data-i18n="help.desc_case_sensitive">: Bouton
"Aa" pour respecter la casse</span>
</li>
<li>
<strong data-i18n="help.clear">Effacer</strong><span data-i18n="help.desc_a4a4e21b"> : Bouton "×" pour
réinitialiser le filtre</span></li>
<li>
<strong data-i18n="help.results_grouped">Résultats groupés</strong><span data-i18n="help.desc_grouped_results">:
Affichage par vault avec chemins complets</span>
</li>
</ul>
<h3 data-i18n="help.breadcrumb_reposition">Breadcrumb et repositionnement</h3>
<p data-i18n="auto.1abf6661">
Le chemin affiché au-dessus du contenu est
interactif :
</p>
<ul>
<li>
<strong data-i18n="help.click_vault">Cliquez sur le vault</strong><span data-i18n="help.desc_click_vault">:
Focus sur le vault dans la sidebar</span>
</li>
<li>
<strong data-i18n="help.click_segment">Cliquez sur un segment</strong><span data-i18n="help.desc_click_segment">:
Navigation vers ce dossier</span>
</li>
<li>
<strong data-i18n="help.auto_scroll">Auto-scroll</strong><span data-i18n="help.desc_autoscroll">: La sidebar se
positionne automatiquement</span>
</li>
</ul>
<h3 data-i18n="help.vault_selector">Sélection de vault</h3>
<p data-i18n="help.desc_vault_context">
Changez de contexte pour filtrer l'affichage :
</p>
<ul>
<li>
<strong data-i18n="help.menu_header">Menu header</strong><span data-i18n="help.desc_menu_header">: Dropdown
"Vault" dans le menu Options</span>
</li>
<li>
<strong data-i18n="help.sidebar_dropdown">Sidebar dropdown</strong><span data-i18n="help.desc_085ec31f"> :
Sélection rapide en haut de l'onglet Vaults</span></li>
<li>
<strong data-i18n="help.all_vaults">Tous les vaults</strong><span data-i18n="help.desc_all_vaults">: Vue
globale de tous vos contenus</span>
</li>
<li>
<strong data-i18n="help.vault_specific">Vault spécifique</strong><span data-i18n="help.desc_specific_vault">: Focus
sur un seul vault</span>
</li>
</ul>
</section>
<section class="help-section" id="help-tabs">
<h2 data-i18n="help.8c5341e0">📑 Gestion des onglets</h2>
<p data-i18n="auto.27a2f7c1">
ObsiGate supporte l'ouverture de plusieurs
fichiers simultanément dans des onglets.
</p>
<h3 data-i18n="help.tabs_section">Ouverture d'onglets</h3>
<ul>
<li>
<strong data-i18n="help.single_click">Simple clic</strong><span data-i18n="help.desc_e3fce7a3"> : Ouvre le
fichier dans un onglet temporaire (aperçu,
italique)</span></li>
<li>
<strong data-i18n="help.double_click">Double clic</strong><span data-i18n="help.desc_double_click">: Ouvre le
fichier dans un onglet persistant</span>
</li>
<li>
<strong data-i18n="help.recent_nav">Navigation récente</strong><span data-i18n="help.desc_d186a19e"> :
L'onglet "Récent" dans la sidebar liste vos
derniers fichiers</span></li>
</ul>
<h3 data-i18n="help.tab_nav">Navigation entre onglets</h3>
<ul>
<li>
<strong data-i18n="help.click_tab">Clic sur un onglet</strong><span data-i18n="help.desc_click_tab">: Active
l'onglet</span>
</li>
<li>
<strong>Ctrl+Tab</strong><span data-i18n="help.desc_ctrl_tab">: Onglet suivant</span>
</li>
<li>
<strong>Ctrl+Shift+Tab</strong><span data-i18n="help.desc_e5246734"> : Onglet
précédent</span></li>
<li>
<strong>Ctrl+W</strong><span data-i18n="help.desc_ctrl_w">: Fermer l'onglet
actif</span>
</li>
<li>
<strong data-i18n="help.close_tab_x">Bouton ✕</strong><span data-i18n="help.desc_close_btn">: Ferme l'onglet
(sauf s'il est seul)</span>
</li>
</ul>
<h3 data-i18n="help.tab_features">Fonctionnalités</h3>
<ul>
<li>
<strong data-i18n="help.tab_bar">Barre d'onglets</strong><span data-i18n="help.desc_170a6982"> : Affichage
horizontal avec le nom du fichier et son
icône</span></li>
<li>
<strong data-i18n="help.active_tab">Onglet actif</strong><span data-i18n="help.desc_6b649b1e"> : Surligné en
couleur d'accentuation</span></li>
<li>
<strong data-i18n="help.preview_tab">Prévisualisation</strong><span data-i18n="help.desc_ef1531ce"> : Un
onglet temporaire (italique) est remplacé
par le prochain clic</span></li>
<li>
<strong data-i18n="help.persistent_tabs">Onglets persistants</strong><span data-i18n="help.desc_5ab8c78d"> :
Restent ouverts jusqu'à fermeture manuelle</span></li>
</ul>
</section>
<section class="help-section" id="help-recherche">
<h2 data-i18n="help.adf30d78">🔍 Recherche</h2>
<h3 data-i18n="help.simple_search">Recherche simple</h3>
<p data-i18n="help.desc_search_intro">
Tapez dans la barre de recherche en haut pour
lancer une recherche fulltext :
</p>
<ul>
<li>
<strong data-i18n="search.full_text">Recherche dans tout</strong><span data-i18n="help.desc_search_all">:
Titre, contenu, tags, chemins</span>
</li>
<li>
<strong data-i18n="help.smart_scoring">Scoring intelligent</strong><span data-i18n="help.desc_00b9572b"> :
Résultats classés par pertinence TF-IDF</span></li>
<li>
<strong data-i18n="help.highlighted_snippets">Snippets surlignés</strong><span data-i18n="help.desc_snippets">:
Extraits avec termes en surbrillance</span>
</li>
<li>
<strong data-i18n="help.pagination">Pagination</strong><span data-i18n="help.desc_53bfaeca"> : Navigation par
pages de 50 résultats</span></li>
</ul>
<h3 data-i18n="help.advanced_operators">Opérateurs avancés</h3>
<p data-i18n="auto.4bc1f00a">
Utilisez des opérateurs pour affiner votre
recherche :
</p>
<ul>
<li>
<span data-i18n="help.desc_op_tag"><code>tag:docker</code> ou
<code>#docker</code> : Filtrer par tag</span>
</li>
<li>
<span data-i18n="help.desc_op_vault"><code>vault:IT</code> : Rechercher dans un
vault spécifique</span>
</li>
<li>
<span data-i18n="help.desc_op_title"><code>title:kubernetes</code> : Chercher
dans les titres uniquement</span>
</li>
<li>
<span data-i18n="help.desc_op_path"><code>path:recettes/soupes</code> : Filtrer
par chemin</span>
</li>
<li>
<span data-i18n="help.desc_op_ext"><code>ext:md</code> : Filtrer par type de
fichier</span>
</li>
<li>
<span data-i18n="help.desc_op_phrase"><code>"phrase exacte"</code> : Recherche de
phrase entre guillemets</span>
</li>
</ul>
<p>
<strong>Exemples</strong><span data-i18n="help.desc_examples">:
<code>ext:sh</code> recherche dans les scripts
bash, <code>ext:py</code> dans les scripts
Python, <code>ext:md</code> dans les fichiers
Markdown.
</p></span>
<p>
<strong data-i18n="auto.1a54bd69">Exemple combiné</strong> :
<code
>tag:linux vault:IT ext:md serveur web</code
>
<span data-i18n="help.desc_combined">recherche "serveur web" dans les fichiers
Markdown du vault IT avec le tag linux.</span>
</p>
<h3 data-i18n="help.autocomplete">Autocomplétion</h3>
<p data-i18n="auto.404c8f1c">
L'autocomplétion vous aide à trouver rapidement
:
</p>
<ul>
<li>
<strong data-i18n="search.dropdown_history">Historique</strong><span data-i18n="help.desc_4de7e318"> : Les 50
dernières recherches (localStorage)</span></li>
<li>
<strong data-i18n="palette.mode_files">Fichiers</strong><span data-i18n="help.desc_file_suggest">: Suggestions de
titres de fichiers</span>
</li>
<li>
<strong data-i18n="sidebar.tab_tags">Tags</strong><span data-i18n="help.desc_tag_suggest">: Suggestions de tags
avec compteurs</span>
</li>
<li>
<strong>Navigation clavier</strong><span data-i18n="help.desc_6b8fc728"> : ↑/↓
pour naviguer, Enter pour sélectionner</span></li>
</ul>
<h3 data-i18n="help.active_filter_chips">Chips de filtres actifs</h3>
<p data-i18n="auto.03a6e5b1">
Les opérateurs utilisés s'affichent sous forme
de chips colorés :
</p>
<ul>
<li>
<strong>Visualisation</strong><span data-i18n="help.desc_chip_view">: Voir d'un
coup d'œil les filtres actifs</span>
</li>
<li>
<strong>Suppression</strong><span data-i18n="help.desc_chip_remove">: Cliquez sur
"×" pour retirer un filtre</span>
</li>
<li>
<strong>Couleurs</strong><span data-i18n="help.desc_6ebebdfd"> : Chaque type
d'opérateur a sa couleur</span></li>
</ul>
<h3 data-i18n="help.a648c49e">Facettes et tri</h3>
<p data-i18n="auto.cd7332c8">
Les résultats incluent des facettes pour affiner
:
</p>
<ul>
<li>
<strong>Facettes tags</strong><span data-i18n="help.desc_cf5ccd40"> : Compteurs
par tag dans les résultats</span></li>
<li>
<strong>Facettes vaults</strong><span data-i18n="help.desc_facet_vaults">: Compteurs
par vault</span>
</li>
<li>
<strong>Tri par pertinence</strong><span data-i18n="help.desc_9fec424d"> : Score
TF-IDF (défaut)</span></li>
<li>
<strong>Tri par date</strong><span data-i18n="help.desc_42a81347"> : Dernière
modification</span></li>
</ul>
</section>
<section class="help-section" id="help-tags">
<h2 data-i18n="help.2507242f">🏷️ Tags</h2>
<h3 data-i18n="help.5f87ea79">Tag cloud</h3>
<p data-i18n="help.desc_tag_cloud_intro">
L'onglet Tags de la sidebar affiche un nuage de
tags :
</p>
<ul>
<li>
<strong>Tous les tags</strong><span data-i18n="help.desc_all_tags">: Extraits
des frontmatters YAML et inline (#tag)</span>
</li>
<li>
<strong data-i18n="help.clickable">Cliquable</strong><span data-i18n="help.desc_6caffeed"> : Cliquez pour
filtrer les résultats</span></li>
<li>
<strong data-i18n="help.filtering">Filtrage</strong><span data-i18n="help.desc_tag_filter_use">: Utilisez la
barre de filtrage pour chercher un tag</span>
</li>
<li>
<strong>Contexte vault</strong><span data-i18n="help.desc_6a5ddef2"> : Les tags
s'adaptent au vault sélectionné</span></li>
</ul>
<h3 data-i18n="help.2c03a7ba">Tags inline vs frontmatter</h3>
<p data-i18n="help.desc_two_tag_types">ObsiGate supporte deux types de tags :</p>
<ul>
<li>
<strong>Frontmatter YAML</strong><span data-i18n="help.desc_frontmatter">:
<code>tags: [docker, linux]</code> ou
<code>tags: docker, linux</code></span>
</li>
<li>
<strong>Inline</strong><span data-i18n="help.desc_inline_tags">:
<code>#docker</code> dans le contenu
markdown</span>
</li>
<li>
<strong>Fusion automatique</strong><span data-i18n="help.desc_828db5da"> : Les
deux types sont combinés</span></li>
</ul>
<h3 data-i18n="help.63f024d4">Filtrage de tags template</h3>
<p data-i18n="help.desc_template_intro">
Dans les Configurations, vous pouvez masquer les
tags de template :
</p>
<ul>
<li>
<strong>Patterns wildcards</strong><span data-i18n="help.desc_patterns">: Ex:
<code>#&lt;% ... %&gt;</code> ou
<code>#{{ ... }}</code></span>
</li>
<li>
<strong data-i18n="auto.5d936af6">Regex générée</strong><span data-i18n="help.desc_b9c94994"> : Aperçu de
la regex appliquée</span></li>
<li>
<strong data-i18n="help.add_remove">Ajout/Suppression</strong><span data-i18n="help.desc_ef3f8158"> : Gérez
vos patterns de filtrage</span></li>
</ul>
</section>
<section class="help-section" id="help-fichiers">
<h2 data-i18n="help.c017944b">📄 Fichiers</h2>
<h3 data-i18n="help.cf8615b1">Visualisation</h3>
<p data-i18n="help.desc_md_render">Les fichiers markdown sont rendus avec :</p>
<ul>
<li>
<strong>Wikilinks cliquables</strong><span data-i18n="help.desc_wikilinks">:
<code>[[lien]]</code> et
<code>[[lien|texte]]</code></span>
</li>
<li>
<strong>Images Obsidian</strong><span data-i18n="help.desc_images">: Support
de <code>![[image.png]]</code></span>
</li>
<li>
<strong>Syntax highlighting</strong><span data-i18n="help.desc_syntax_hl">:
Coloration des blocs de code</span>
</li>
<li>
<strong>Tables, listes, quotes</strong><span data-i18n="help.desc_markdown_all">:
Rendu complet du markdown</span>
</li>
</ul>
<h3 data-i18n="help.context_actions">Actions sur fichiers</h3>
<p data-i18n="help.actions_detail">Boutons disponibles au-dessus du contenu :</p>
<ul>
<li>
<strong data-i18n="viewer.copy">Copier</strong><span data-i18n="help.desc_copy_btn">: Copie le contenu
source dans le presse-papiers</span>
</li>
<li>
<strong data-i18n="viewer.source">Source</strong><span data-i18n="help.desc_source_btn">: Affiche/masque le
markdown brut</span>
</li>
<li>
<strong data-i18n="viewer.download">Télécharger</strong><span data-i18n="help.desc_a52fc3b1"> : Télécharge le
fichier</span></li>
<li>
<strong data-i18n="viewer.edit">Éditer</strong><span data-i18n="help.desc_995a711f"> : Ouvre l'éditeur
CodeMirror</span></li>
<li>
<strong
>Ouvrir dans nouvelle fenêtre</strong
><span data-i18n="help.desc_open_new">: Ouvre le fichier dans un nouvel onglet</span>
</li>
</ul>
<h3 data-i18n="auto.1efbbc9a">Édition avec CodeMirror 6</h3>
<p data-i18n="auto.edf89626">L'éditeur intégré offre :</p>
<ul>
<li>
<strong>Coloration syntaxique</strong><span data-i18n="help.desc_cm_syntax">:
Support de nombreux langages</span>
</li>
<li>
<strong data-i18n="auto.1d46301c">Numéros de ligne</strong><span data-i18n="help.desc_cm_lines">:
Navigation facile</span>
</li>
<li>
<strong data-i18n="help.autocomplete">Autocomplétion</strong><span data-i18n="help.desc_cm_autocomplete">:
Suggestions contextuelles</span>
</li>
<li>
<strong data-i18n="settings.search">Recherche</strong><span data-i18n="help.desc_cm_search">: Ctrl+F pour
chercher dans le fichier</span>
</li>
<li>
<strong>Sauvegarde</strong><span data-i18n="help.desc_save_btn_new">: Bouton ✓ pour
enregistrer</span>
</li>
<li>
<strong>Suppression</strong><span data-i18n="help.desc_delete_btn_new">: Bouton
corbeille pour supprimer</span>
</li>
</ul>
<h3 data-i18n="auto.ec7260fa">Types de fichiers supportés</h3>
<p data-i18n="help.indexes">ObsiGate indexe et affiche :</p>
<ul>
<li>
<strong data-i18n="viewer.markdown">Markdown</strong> : .md (rendu
complet)
</li>
<li>
<strong data-i18n="mermaid.code">Code</strong> : .py, .js, .ts, .jsx,
.tsx, .java, .c, .cpp, .rs, .go, etc.
</li>
<li>
<strong>Config</strong> : .json, .yaml,
.yml, .toml, .xml, .ini, .env
</li>
<li>
<strong>Scripts</strong> : .sh, .bash, .ps1,
.bat
</li>
<li>
<strong>Web</strong> : .html, .css, .scss
</li>
<li>
<strong>PDF</strong> : .pdf (visualiseur natif, texte indexé)
</li>
<li>
<strong>Excalidraw</strong> : .excalidraw,
.excalidraw.md
</li>
<li>
<strong data-i18n="help.other">Autres</strong> : .txt, .log, .sql,
.csv
</li>
</ul>
</section>
<!-- 🎨 Excalidraw -->
<section class="help-section" id="help-excalidraw">
<h2 data-i18n="help.excalidraw_title">🎨 Diagrammes Excalidraw</h2>
<p data-i18n-html="help.excalidraw_intro">
Les fichiers <code>.excalidraw</code> et
<code>.excalidraw.md</code> s'ouvrent dans un éditeur visuel
Excalidraw complet, intégré dans une iframe sandboxée.
</p>
<h3 data-i18n="help.excalidraw_open_title">Ouverture</h3>
<ul>
<li data-i18n-html="help.excalidraw_open">
Cliquez sur un fichier <code>.excalidraw</code> dans
l'arborescence : l'éditeur s'ouvre à la place du viewer markdown.
</li>
<li data-i18n="help.excalidraw_edit">
Dessinez, ajoutez des formes, du texte, des flèches et des
images — l'expérience Excalidraw complète.
</li>
</ul>
<h3 data-i18n="help.excalidraw_save_title">Sauvegarde</h3>
<ul>
<li data-i18n-html="help.excalidraw_save">
Les modifications sont sauvegardées automatiquement (2 s) ou
avec <strong>Ctrl+S</strong>. L'éditeur suit le thème
clair/sombre.
</li>
</ul>
<h3 data-i18n="help.excalidraw_search_title">Recherche</h3>
<ul>
<li data-i18n="help.excalidraw_search">
Le texte des éléments du diagramme est extrait à l'indexation :
il est donc recherchable via la recherche full-text.
</li>
<li data-i18n-html="help.excalidraw_compat">
Les fichiers créés avec le plugin Obsidian Excalidraw (y
compris le format <code>.excalidraw.md</code> compressé) sont
compatibles.
</li>
</ul>
</section>
<!-- ✏️ Édition -->
<section class="help-section" id="help-edition">
<h2 data-i18n="auto.f346076a">✏️ Édition avancée</h2>
<h3 data-i18n="auto.695d9e47">Éditeur CodeMirror 6</h3>
<p data-i18n="auto.ef8c5f4d">
L'éditeur intégré offre une expérience complète
d'édition :
</p>
<ul>
<li>
<strong>Coloration syntaxique</strong><span data-i18n="help.desc_adv_syntax">:
Python, JS, HTML, CSS, Java, C++, Rust, Go
et bien d'autres</span>
</li>
<li>
<strong data-i18n="auto.1d46301c">Numéros de ligne</strong><span data-i18n="help.desc_9f78b3ec"> :
Navigation facilitée dans les fichiers longs</span></li>
<li>
<strong>Find in page</strong><span data-i18n="help.desc_find_in_page">: Ctrl+F pour
chercher dans le fichier (casse, regex, mot
entier)</span>
</li>
<li>
<strong data-i18n="help.auto_save">Sauvegarde automatique</strong><span data-i18n="help.desc_065f53ee"> :
Auto-save silencieux après 2s d'inactivité,
flash vert de confirmation</span></li>
<li>
<strong>Bouton ✓</strong><span data-i18n="help.desc_check_btn">: Sauvegarde
manuelle</span>
</li>
<li>
<strong>Bouton 🗑️</strong><span data-i18n="help.desc_trash_btn">: Suppression du
fichier</span>
</li>
<li>
<strong data-i18n="help.toolbar_section">Barre d'outils AI</strong><span data-i18n="help.desc_c8532869"> :
Complétion, réécriture, traduction (voir
section</span><a href="#help-ia" data-i18n="settings.ai">🤖 IA</a>)
</li>
</ul>
<h3 data-i18n="help.mobile_editor_title">📱 Édition mobile</h3>
<p data-i18n="help.mobile_editor_intro">
Sur téléphone et tablette, l'édition s'adapte au
tactile : barre d'outils flottante, gestes et mode
lecture plein écran.
</p>
<ul>
<li>
<strong data-i18n="help.mobile_editor_toolbar_label">Barre d'outils flottante</strong><span data-i18n="help.mobile_editor_toolbar"> :
gras, italique, code, liste à puces et lien.
Les boutons A−/A+ ajustent la taille de la
police.</span>
</li>
<li>
<strong data-i18n="help.mobile_editor_paste_label">Presse-papiers</strong><span data-i18n="help.mobile_editor_paste"> :
bouton « Coller » persistant (contourne la
restriction iOS).</span>
</li>
<li>
<strong data-i18n="help.mobile_editor_gestures_label">Gestes tactiles</strong><span data-i18n="help.mobile_editor_gestures"> :
balayage vers la gauche → liens entrants,
vers la droite → table des matières.</span>
</li>
<li>
<strong data-i18n="help.mobile_editor_reading_label">Mode lecture</strong><span data-i18n="help.mobile_editor_reading"> :
plein écran sans en-tête ni barres latérales ;
balayage horizontal pour passer au fichier
suivant ou précédent.</span>
</li>
<li>
<strong data-i18n="help.mobile_editor_zoom_label">Zoom &amp; hauteur</strong><span data-i18n="help.mobile_editor_zoom"> :
pincement à deux doigts pour zoomer, poignée
pour redimensionner la zone d'édition.</span>
</li>
</ul>
</section>
<!-- 🗺️ Graphe -->
<section class="help-section" id="help-graphe">
<h2 data-i18n="help.9c7a0a8d">🗺️ Vue Graphe</h2>
<p data-i18n="help.desc_graph_intro">
La vue graphe offre une visualisation
interactive des connexions entre vos notes via
les wikilinks.
</p>
<h3 data-i18n="help.b6619ac9">Ouverture</h3>
<ul>
<li>
<strong data-i18n="help.graph_context">Menu contextuel</strong><span data-i18n="help.desc_context_menu">: Clic
droit sur un vault ou dossier → Vue
Graphique</span>
</li>
<li>
<strong data-i18n="help.popout_button">Bouton dédié</strong><span data-i18n="help.desc_e23c94dd"> : Dans la
barre d'outils (le cas échéant)</span></li>
</ul>
<h3 data-i18n="help.61ee5a22">Interaction</h3>
<ul>
<li>
<strong data-i18n="help.graph_zoom">Zoom/Pan</strong><span data-i18n="help.desc_f0adf67a"> : Molette pour
zoomer, cliquer-glisser pour se déplacer</span></li>
<li>
<strong data-i18n="help.graph_depth">Slider de profondeur</strong><span data-i18n="help.desc_9edeaa11"> :
Contrôle la profondeur d'exploration (0-3)</span></li>
<li>
<strong data-i18n="help.graph_tag_filter">Filtre par tag</strong><span data-i18n="help.desc_89bd3c4c"> : N'affiche
que les nœuds avec un tag spécifique</span></li>
<li>
<strong data-i18n="help.graph_type_filter">Filtre par type</strong><span data-i18n="help.desc_filter_type">:
Inclut/exclut fichiers, dossiers, markdown,
autres</span>
</li>
<li>
<strong data-i18n="help.graph_visual">Recherche visuelle</strong><span data-i18n="help.desc_visual_search">: Barre
de recherche pour surligner des nœuds</span>
</li>
<li>
<strong data-i18n="help.graph_focus">Mode focus</strong><span data-i18n="help.desc_a9db82b5"> : Centre la vue
sur un nœud spécifique</span></li>
<li>
<strong data-i18n="help.graph_nav_arrows">Navigation ← → ↑</strong><span data-i18n="help.desc_graph_nav">:
Historique de navigation entre les nœuds</span>
</li>
</ul>
<h3 data-i18n="help.8caed987">Affichage</h3>
<ul>
<li>
<strong data-i18n="help.graph_nodes">Nœuds</strong><span data-i18n="help.desc_ab2c4150"> : Fichiers (carrés)
et dossiers (cercles) avec icônes</span></li>
<li>
<strong data-i18n="help.nodes_edges">Arêtes</strong><span data-i18n="help.desc_edges">: Wikilinks sortants
(bleu) et backlinks entrants (vert)</span>
</li>
<li>
<strong data-i18n="help.graph_info">Panneau d'info</strong><span data-i18n="help.desc_8717768b"> :
Métadonnées du nœud (tags, liens, chemin)</span></li>
<li>
<strong data-i18n="help.graph_preview">Ctrl+survol</strong><span data-i18n="help.desc_a579cf4d"> : Aperçu du
contenu de la note sans naviguer</span></li>
<li>
<strong data-i18n="graph.fullscreen">Plein écran</strong><span data-i18n="help.desc_fullscreen">: Bouton pour
agrandir la vue</span>
</li>
<li>
<strong data-i18n="help.graph_export">Export PNG</strong><span data-i18n="help.desc_export_png">: Sauvegardez le
graphe en image</span>
</li>
</ul>
<h3 id="cfg-performance" data-i18n="config.section_perf">Performance</h3>
<p data-i18n="auto.aa8ca265">
La vue utilise l'algorithme Barnes-Hut pour
calculer la disposition en O(n log n),
garantissant des performances fluides même avec
des centaines de nœuds.
</p>
</section>
<section
class="help-section"
id="help-personnalisation"
>
<h2 data-i18n="help.86e3825d">🎨 Personnalisation</h2>
<h3 data-i18n="auto.337d8626">Thème clair/sombre</h3>
<p data-i18n="help.theme_toggle">Basculez entre les thèmes :</p>
<ul>
<li>
<strong data-i18n="help.menu_options">Menu Options</strong><span data-i18n="help.desc_beed5686"> : Cliquez sur
"Thème" dans le menu</span></li>
<li>
<strong>Raccourci</strong> :
<code>Ctrl+T</code>
</li>
<li>
<strong>Persistance</strong><span data-i18n="help.desc_e5b67e3d"> : Votre choix
est sauvegardé en localStorage</span></li>
<li>
<strong>Highlight.js</strong><span data-i18n="help.desc_d5c977ac"> : Les blocs de
code s'adaptent au thème</span></li>
</ul>
<h3 data-i18n="help.7bcbb2e3">Redimensionnement de la sidebar</h3>
<p>
Sur desktop, ajustez la largeur de la sidebar :
</p>
<ul>
<li>
<strong>Handle de redimensionnement</strong>
: Bordure droite de la sidebar
</li>
<li>
<strong data-i18n="auto.1a8a8118">Glisser-déposer</strong> : Cliquez
et glissez pour ajuster
</li>
<li>
<strong>Limites</strong> : Min 200px, Max
500px
</li>
<li>
<strong>Persistance</strong><span data-i18n="help.desc_6a80241e"> : La largeur
est sauvegardée</span></li>
</ul>
<h3 data-i18n="auto.9122fd5c">Configuration avancée</h3>
<p>
Le modal Configurations (menu Options) permet de
:
</p>
<ul>
<li>
<strong data-i18n="config.section_search">Paramètres de recherche</strong><span data-i18n="help.desc_6b88a375"> :
Délai debounce, résultats par page, timeout</span></li>
<li>
<strong data-i18n="settings.backend_section">Paramètres backend</strong><span data-i18n="help.desc_5d0fbb81"> :
Workers, boosts TF-IDF, expansions préfixe</span></li>
<li>
<strong data-i18n="config.tag_filtering_label">Filtrage de tags</strong> : Patterns
pour masquer les tags template
</li>
<li>
<strong data-i18n="config.diagnostics">Diagnostics</strong><span data-i18n="help.desc_6ae5342c"> : Stats index,
mémoire, moteur de recherche</span></li>
<li>
<strong data-i18n="auto.83a0efdf">Réindexation</strong> : Forcer un
rescan des vaults
</li>
</ul>
</section>
<!-- ⌨️ Palette de commandes -->
<section class="help-section" id="help-palette">
<h2 data-i18n="help.62308f96">⌨️ Palette de commandes</h2>
<p data-i18n="auto.774dbd63">
La palette de commandes permet d'accéder
rapidement aux fichiers et aux actions sans
utiliser la souris.
</p>
<h3 data-i18n="help.b6619ac9">Ouverture</h3>
<ul>
<li>
<strong>Ctrl+Shift+Espace</strong> : Palette
de fichiers (recherche par titre)
</li>
<li>
<strong>Ctrl+Alt+Espace</strong> : Palette
de commandes (liste d'actions)
</li>
<li>
<strong>Tab</strong> : Bascule entre mode
fichiers et mode commandes
</li>
</ul>
<h3 data-i18n="help.6d547397">Mode fichiers</h3>
<p>
Tapez le nom d'un fichier pour le trouver
rapidement. La recherche utilise l'API
<code>/api/suggest</code> pour des suggestions
instantanées. Appuyez sur <kbd>Enter</kbd> pour
ouvrir le fichier sélectionné.
</p>
<h3 data-i18n="help.3cf370b1">Mode commandes</h3>
<p>
Tapez <code>&gt;</code> (ou Ctrl+Alt+Espace)
pour voir la liste des actions disponibles :
</p>
<ul>
<li>
🏠 <strong data-i18n="viewer.breadcrumb_home">Accueil</strong> : Retour à
l'accueil
</li>
<li>
✏️ <strong data-i18n="auto.b893650a">Éditer fichier courant</strong> :
Ouvre l'éditeur sur l'onglet actif
</li>
<li>
🪟 <strong>Pop-out</strong> : Ouvre le
document dans une fenêtre détachée
</li>
<li>
📄 <strong data-i18n="auto.aedff644">Créer fichier markdown</strong> :
Crée un nouveau fichier .md et l'ouvre dans
l'éditeur
</li>
<li>
🗑️
<strong>Supprimer fichier courant</strong> :
Supprime le fichier de l'onglet actif
</li>
<li>
📁 <strong data-i18n="auto.195a8e44">Créer répertoire</strong> : Crée
un nouveau dossier
</li>
<li>
🗑️ <strong data-i18n="auto.5a486420">Supprimer répertoire</strong> :
Supprime un dossier et son contenu
</li>
<li>
🌓 <strong data-i18n="theme.change">Changer le thème</strong> :
Bascule clair/sombre
</li>
<li>
🔄 <strong data-i18n="config.reindex">Réindexer</strong> : Force la
réindexation complète
</li>
<li>
❓ <strong data-i18n="header.help">Aide</strong> : Ouvre ce guide
</li>
<li>
⚙️ <strong data-i18n="config.title">Configuration</strong> : Ouvre
les paramètres
</li>
</ul>
<h3 data-i18n="help.nav_navigation">Navigation</h3>
<ul>
<li>
<kbd>↑</kbd> <kbd>↓</kbd> : Se déplacer dans
la liste
</li>
<li><kbd>Enter</kbd> : Ouvrir/sélectionner</li>
<li><kbd>Esc</kbd> : Fermer la palette</li>
<li>
<kbd>Tab</kbd> : Basculer entre fichiers et
commandes
</li>
</ul>
</section>
<section class="help-section" id="help-raccourcis">
<h2 data-i18n="help.2a2e5b0f">⌨️ Raccourcis clavier</h2>
<h3 data-i18n="help.nav_navigation">Navigation</h3>
<ul>
<li>
<code>Ctrl+K</code> ou <code>Cmd+K</code> :
Focaliser la barre de recherche
</li>
<li>
<code>/</code> : Focaliser la recherche
(hors champ texte)
</li>
<li>
<code>Escape</code> : Fermer les suggestions
/ quitter la recherche
</li>
</ul>
<h3 data-i18n="auto.13c3ee2c">Recherche et autocomplétion</h3>
<ul>
<li>
<code>↑</code> / <code>↓</code> : Naviguer
dans les suggestions
</li>
<li>
<code>Enter</code> : Sélectionner la
suggestion active ou lancer la recherche
</li>
<li>
<code>Escape</code> : Fermer le dropdown de
suggestions
</li>
</ul>
<h3 data-i18n="help.nav_interface">Interface</h3>
<ul>
<li>
<code>Ctrl+T</code> : Basculer le thème
clair/sombre
</li>
<li>
<code>Ctrl+Shift+Espace</code> : Palette de
fichiers
</li>
<li>
<code>Ctrl+Alt+Espace</code> : Palette de
commandes
</li>
<li>
<code>Escape</code> : Fermer la palette /
les suggestions
</li>
</ul>
<h3 data-i18n="help.nav_tabs">Onglets</h3>
<ul>
<li>
<code>Ctrl+W</code> : Fermer l'onglet actif
</li>
<li><code>Ctrl+Tab</code> : Onglet suivant</li>
<li>
<code>Ctrl+Shift+Tab</code> : Onglet
précédent
</li>
</ul>
<h3 data-i18n="auto.a131dfab">Éditeur CodeMirror</h3>
<ul>
<li>
<code>Ctrl+F</code> : Rechercher dans le
fichier
</li>
<li><code>Ctrl+Z</code> : Annuler</li>
<li><code>Ctrl+Y</code> : Refaire</li>
<li>
<code>Ctrl+S</code> : Sauvegarder (si
configuré)
</li>
</ul>
</section>
<section class="help-section" id="help-ai">
<h2 data-i18n="auto.c4922a99">🤖 Intelligence Artificielle dans l'Éditeur</h2>
<p>
ObsiGate intègre un assistant IA directement
dans l'éditeur de texte. Pour l'activer,
configurez au moins un provider AI dans le
fichier <code>.env</code> (DeepSeek, OpenRouter
ou Gemini).
</p>
<h3 data-i18n="help.activation">Activation</h3>
<ol>
<li>
Ajoutez votre clé API dans
<code>.env</code> :
<code>DEEPSEEK_API_KEY=sk-...</code>
</li>
<li>
Redémarrez le conteneur :
<code
>docker compose down && docker compose
up -d --build</code
>
</li>
<li data-i18n="auto.5d4a2516">
Ouvrez un fichier en mode Édition (bouton ✏️
Éditer)
</li>
<li data-i18n="auto.396d0dd8">
La barre d'outils AI apparaît sous le titre
de l'éditeur
</li>
</ol>
<h3 data-i18n="help.toolbar_section">Barre d'outils AI</h3>
<p>
La barre d'outils contient les boutons suivants
:
</p>
<ul>
<li>
<strong>✦ AI</strong><span data-i18n="help.desc_3ace259e"> — Complétion inline
(</span><kbd>Ctrl+J</kbd>). Complète
automatiquement le texte là où se trouve le
curseur.
</li>
<li>
<strong data-i18n="auto.a105a2d9">Éditer ▾</strong><span data-i18n="help.desc_a0ec890a"> — Menu de
modification du texte sélectionné :</span><ul>
<li>
🪄
<strong>Improve writing</strong> :
Améliore la qualité, la clarté et le
flux du texte
</li>
<li>
🔤
<strong
>Fix spelling & grammar</strong
>
: Corrige les fautes d'orthographe
et de grammaire
</li>
<li>
📏 <strong>Make shorter</strong> :
Rend le texte plus concis tout en
préservant l'information
</li>
<li>
📐 <strong>Make longer</strong> :
Ajoute des détails, exemples ou
explications
</li>
<li>
📋
<strong>Simplify language</strong> :
Simplifie le langage, évite le
jargon
</li>
</ul>
</li>
<li>
<strong>Ton ▾</strong> — Change le ton du
texte :
<ul>
<li>
💼 <strong>Professional</strong> :
Ton formel et professionnel
</li>
<li>
💬 <strong>Casual</strong> : Ton
décontracté et conversationnel
</li>
</ul>
</li>
<li>
<strong>Traduire ▾</strong><span data-i18n="help.desc_1c130d89"> — Traduit le
texte sélectionné vers une autre langue :</span><ul>
<li>
🇬🇧 English · 🇨🇳 Chinese · 🇯🇵
Japanese · 🇩🇪 German · 🇫🇷 French ·
🇪🇸 Spanish
</li>
</ul>
</li>
<li>
<strong data-i18n="auto.ce4eb802">Générer ▾</strong><span data-i18n="help.desc_991307d6"> — Génère du
contenu à partir de la sélection :</span><ul>
<li>
ℹ️ <strong>Explain this</strong> :
Explique le texte sélectionné de
façon claire
</li>
<li>
📝 <strong>Summarize</strong> :
Résume le texte de façon concise
</li>
<li>
✏️
<strong>Continue writing</strong> :
Continue l'écriture à partir du
texte
</li>
</ul>
</li>
<li>
<strong data-i18n="auto.62c3d50d">💬 Réécrire</strong><span data-i18n="help.desc_c63f02cc"> — Réécrit le
texte selon vos instructions personnalisées.
Une boîte de dialogue vous demande votre
instruction.</span></li>
<li>
<strong data-i18n="auto.0257e77f">🧰 Boîte ▾</strong> — Outils de
conversion :
<ul>
<li>
📋
<strong>Convert to list</strong> :
Transforme le texte en liste
Markdown
</li>
<li>
📊
<strong>Convert to table</strong> :
Transforme en tableau Markdown
</li>
<li>
⚙️
<strong
>Generate frontmatter</strong
>
: Génère un bloc YAML frontmatter
</li>
<li>
🔷
<strong>Convert to canvas</strong> :
Convertit en diagramme Mermaid.js
</li>
</ul>
</li>
</ul>
<h3 data-i18n="help.096367af">Indicateurs visuels</h3>
<ul>
<li>
Pendant une requête AI, un toast
<strong
>⏳ AI: traitement en cours...</strong
>
s'affiche
</li>
<li>
Une fois terminé,
<strong data-i18n="auto.4d281c6c">AI: texte traité ✓</strong> confirme
le succès
</li>
<li>
En cas d'erreur de clé API :
<strong data-i18n="auto.13eb7a51">AI: clé API invalide</strong>
</li>
</ul>
<h3 data-i18n="help.2dad2bfd">Sauvegarde automatique</h3>
<p>
L'éditeur sauvegarde automatiquement après 2
secondes d'inactivité. Le bouton de sauvegarde
<strong>flashe en vert</strong> brièvement pour
confirmer.
</p>
<h3 data-i18n="auto.272dd29a">Providers supportés</h3>
<table>
<tr>
<th>Provider</th>
<th>Variable .env</th>
<thdata-i18n="auto.033a9ddf">Modèle par défaut</th>
</tr>
<tr>
<td>DeepSeek</td>
<td><code>DEEPSEEK_API_KEY</code></td>
<td><code>deepseek-chat</code></td>
</tr>
<tr>
<td>OpenRouter</td>
<td><code>OPENROUTER_API_KEY</code></td>
<td><code>openai/gpt-4o-mini</code></td>
</tr>
<tr>
<td>Gemini</td>
<td><code>GEMINI_API_KEY</code></td>
<td><code>gemini-2.0-flash</code></td>
</tr>
</table>
<p>
Pour changer de provider par défaut :
<code>AI_DEFAULT_PROVIDER=openrouter</code>
</p>
<h3 data-i18n="help.assistant_panel">🧠 Panneau Assistant (BooksLM)</h3>
<p data-i18n="help.assistant_panel_desc">
L'assistant latéral (bouton flottant ou menu contextuel d'un
dossier) répond en Markdown formaté et contextualise vos
répertoires ou documents.
</p>
<ul>
<li data-i18n="help.assistant_markdown">
Réponses formatées : titres, listes, tableaux, citations et
blocs de code.
</li>
<li data-i18n="help.assistant_links">
Les fichiers et chemins cités sont des liens : cliquez sur un
fichier pour l'ouvrir, sur un dossier pour le révéler dans
l'arborescence.
</li>
<li data-i18n="help.assistant_sessions">
L'icône historique de l'en-tête liste les sessions passées
(recharger ou supprimer) ; « + » démarre une nouvelle
conversation.
</li>
<li data-i18n="help.assistant_agent">
Le bouton « mode agent » active les outils (lire, lister,
chercher) ; les actions de modification demandent une
confirmation avec aperçu des changements.
</li>
<li data-i18n="help.assistant_resize">
Le bord gauche du panneau est redimensionnable ; la largeur est
mémorisée.
</li>
<li data-i18n="help.assistant_at">
Tapez <code>@</code> pour joindre un fichier ou un répertoire au
contexte, ou pour attacher une image d'un répertoire.
</li>
<li data-i18n="help.assistant_slash">
Tapez <code>/</code> pour lancer un skill (recherche, résumé,
correction, plan…) ou une commande admin
(<code>/help</code>, <code>/providers</code>,
<code>/model</code>, <code>/keys</code>).
</li>
<li data-i18n="help.assistant_images">
Collez une image dans la zone de saisie (ou joignez-la) pour
l'analyser avec un modèle compatible vision. Le bouton ⓘ affiche
les capacités du modèle sélectionné, et la liste des modèles est
filtrable par recherche.
</li>
</ul>
</section>
<!-- 🔗 Partage -->
<section class="help-section" id="help-partage">
<h2 data-i18n="help.8c65b2f2">🔗 Partage et Webhooks</h2>
<h3 data-i18n="help.public_shares">Publication publique</h3>
<p>
ObsiGate permet de partager des documents via un
lien public temporaire.
</p>
<ul>
<li>
<strong data-i18n="help.share_button">Bouton Partager</strong><span data-i18n="help.desc_d66e911c"> : Dans les
actions du fichier, génère un lien unique</span></li>
<li>
<strong>Token unique</strong><span data-i18n="help.desc_04493800"> : 64
caractères hexadécimaux, impossible à
deviner</span></li>
<li>
<strong>Expiration configurable</strong><span data-i18n="help.desc_858681a1"> :
Durée de vie en heures</span></li>
<li>
<strong>Lecture seule</strong><span data-i18n="help.desc_e47d2328"> : La vue
publique n'affiche que le contenu sans
édition</span></li>
<li>
<strong>Gestion des liens</strong><span data-i18n="help.desc_5e9a7fd9"> : Liste
et révocation dans les Configurations</span></li>
<li>
<strong data-i18n="help.pdf_export">Export PDF</strong><span data-i18n="help.desc_e93517a5"> : Téléchargement
du document partagé en PDF via</span><code>/s/{token}/pdf</code>
</li>
</ul>
</section>
<!-- 🪝 Webhooks -->
<section class="help-section" id="help-webhooks">
<h2 data-i18n="help.29afa01d">🪝 Webhooks</h2>
<p>
Les webhooks permettent à ObsiGate de
<strong
>notifier automatiquement des services
externes</strong
>
lorsque des fichiers ou dossiers sont modifiés.
Chaque événement déclenche un
<code>POST</code> HTTP vers l'URL de votre
choix, avec un payload JSON détaillé.
</p>
<h3 data-i18n="help.use_cases">Cas d'usage</h3>
<ul>
<li>
<strong data-i18n="help.ci_cd">Automatisation CI/CD</strong><span data-i18n="help.desc_1ba39cc4"> :
Déclencher un build quand une note de
procédure est mise à jour</span></li>
<li>
<strong data-i18n="header.sync_status">Synchronisation</strong><span data-i18n="help.desc_d9cdaa89"> : Copier
les nouveaux fichiers vers un autre système
(SharePoint, Google Drive)</span></li>
<li>
<strong>Notifications</strong><span data-i18n="help.desc_6f7c5889"> : Alerter un
canal Slack/Discord quand un document est
créé</span></li>
<li>
<strong data-i18n="help.external_audit">Audit externe</strong><span data-i18n="help.desc_599f3242"> : Logger
toutes les modifications dans une base de
données externe</span></li>
<li>
<strong data-i18n="auto.a232672b">Déploiement</strong><span data-i18n="help.desc_2f2c16b5"> : Publier un
article de blog quand un fichier est marqué</span><code>publish: true</code>
</li>
</ul>
<h3 data-i18n="auto.ba768f01">Événements supportés</h3>
<div style="overflow-x: auto">
<table
style="
width: 100%;
border-collapse: collapse;
font-size: 0.85rem;
margin: 8px 0 16px;
"
>
<tr style="background: var(--bg-secondary)">
<th
style="
padding: 8px 12px;
text-align: left;
border-bottom: 1px solid
var(--border);
"
data-i18n="auto.f5a8c116">
Événement
</th>
<th
style="
padding: 8px 12px;
text-align: left;
border-bottom: 1px solid
var(--border);
"
data-i18n="auto.d4d28717">
Déclencheur
</th>
<th
style="
padding: 8px 12px;
text-align: left;
border-bottom: 1px solid
var(--border);
"
data-i18n="auto.460b8298">
Données
</th>
</tr>
<tr>
<td
style="
padding: 8px 12px;
border-bottom: 1px solid
var(--border);
"
>
<code>file_created</code>
</td>
<td
style="
padding: 8px 12px;
border-bottom: 1px solid
var(--border);
"
data-i18n="auto.a05514cb">
Création via l'API ou la palette
</td>
<td
style="
padding: 8px 12px;
border-bottom: 1px solid
var(--border);
"
>
<code>vault</code>,
<code>path</code>
</td>
</tr>
<tr style="background: var(--bg-secondary)">
<td
style="
padding: 8px 12px;
border-bottom: 1px solid
var(--border);
"
>
<code>file_deleted</code>
</td>
<td
style="
padding: 8px 12px;
border-bottom: 1px solid
var(--border);
"
data-i18n="auto.fc694490">
Suppression via l'API ou l'éditeur
</td>
<td
style="
padding: 8px 12px;
border-bottom: 1px solid
var(--border);
"
>
<code>vault</code>,
<code>path</code>
</td>
</tr>
<tr>
<td
style="
padding: 8px 12px;
border-bottom: 1px solid
var(--border);
"
>
<code>file_renamed</code>
</td>
<td
style="
padding: 8px 12px;
border-bottom: 1px solid
var(--border);
"
>
Renommage via l'API
</td>
<td
style="
padding: 8px 12px;
border-bottom: 1px solid
var(--border);
"
>
<code>vault</code>,
<code>old_path</code>,
<code>new_path</code>
</td>
</tr>
<tr style="background: var(--bg-secondary)">
<td
style="
padding: 8px 12px;
border-bottom: 1px solid
var(--border);
"
>
<code>directory_created</code>
</td>
<td
style="
padding: 8px 12px;
border-bottom: 1px solid
var(--border);
"
data-i18n="auto.2bc66bab">
Création de dossier via l'API
</td>
<td
style="
padding: 8px 12px;
border-bottom: 1px solid
var(--border);
"
>
<code>vault</code>,
<code>path</code>
</td>
</tr>
<tr>
<td
style="
padding: 8px 12px;
border-bottom: 1px solid
var(--border);
"
>
<code>directory_deleted</code>
</td>
<td
style="
padding: 8px 12px;
border-bottom: 1px solid
var(--border);
"
>
Suppression de dossier via l'API
</td>
<td
style="
padding: 8px 12px;
border-bottom: 1px solid
var(--border);
"
>
<code>vault</code>,
<code>path</code>
</td>
</tr>
<tr style="background: var(--bg-secondary)">
<td
style="
padding: 8px 12px;
border-bottom: 1px solid
var(--border);
"
>
<code>directory_renamed</code>
</td>
<td
style="
padding: 8px 12px;
border-bottom: 1px solid
var(--border);
"
>
Renommage de dossier via l'API
</td>
<td
style="
padding: 8px 12px;
border-bottom: 1px solid
var(--border);
"
>
<code>vault</code>,
<code>old_path</code>,
<code>new_path</code>
</td>
</tr>
</table>
</div>
<p>
<em
>Note : l'événement
<code>file_modified</code> est réservé pour
usage futur. Les modifications de contenu
via l'éditeur ne déclenchent pas encore de
webhook.</em
>
</p>
<h3 data-i18n="help.77f4f06e">Format du payload</h3>
<p>
Chaque webhook reçoit un <code>POST</code> avec
<code>Content-Type: application/json</code> :
</p>
<pre
style="
background: var(--bg-secondary);
padding: 12px;
border-radius: 8px;
overflow-x: auto;
font-size: 0.78rem;
"
><code>{
"event": "file_created",
"timestamp": "2026-06-03T14:30:00.123456+00:00",
"data": {
"vault": "IT",
"path": "procedures/deploiement.md"
}
}</code></pre>
<h3 data-i18n="auto.28bf6aab">Sécurité : signature HMAC-SHA256</h3>
<p>
Si vous configurez un secret, chaque requête
inclut un header
<code>X-ObsiGate-Signature</code> :
</p>
<pre
style="
background: var(--bg-secondary);
padding: 12px;
border-radius: 8px;
overflow-x: auto;
font-size: 0.78rem;
"
><code>X-ObsiGate-Signature: sha256=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08</code></pre>
<p>
Pour vérifier côté récepteur : calculez
<code>HMAC-SHA256(corps_brut, secret)</code> et
comparez. Le header
<code>X-ObsiGate-Event</code> contient aussi le
type d'événement pour un routage rapide.
</p>
<h3 data-i18n="config.title">Configuration</h3>
<p>
Les webhooks se gèrent dans le modal
<strong data-i18n="header.menu_config">Configurations</strong> (⚙️ menu
Options), section Webhooks. Réservé aux
administrateurs.
</p>
<ol>
<li>
Allez dans ⚙️ Configurations → onglet
Webhooks
</li>
<li>
Remplissez :
<strong data-i18n="config.webhook_name_placeholder">Nom</strong> (descriptif),
<strong data-i18n="config.webhook_url_placeholder">URL</strong> (endpoint cible),
<strong>Secret</strong> (optionnel)
</li>
<li data-i18n="auto.7c9cdc3c">Cochez les événements à écouter</li>
<li>Cliquez <strong data-i18n="config.add_webhook">Ajouter</strong></li>
</ol>
<p>
Les webhooks peuvent être activés/désactivés,
modifiés ou supprimés à tout moment. Le
<code>last_fired_at</code> indique la dernière
exécution.
</p>
<h3 data-i18n="help.550b0240">Exemples concrets</h3>
<h4 data-i18n="auto.b419c2c2">Récepteur Python (Flask)</h4>
<pre
style="
background: var(--bg-secondary);
padding: 12px;
border-radius: 8px;
overflow-x: auto;
font-size: 0.78rem;
"
><code>from flask import Flask, request\nimport hmac, hashlib\n\napp = Flask(__name__)\nWEBHOOK_SECRET = "mon-secret" # même que dans ObsiGate\n\[email protected]("/webhook", methods=["POST"])\ndef webhook():\n # Vérifier la signature\n signature = request.headers.get("X-ObsiGate-Signature", "")\n body = request.get_data()\n expected = "sha256=" + hmac.new(\n WEBHOOK_SECRET.encode(), body, hashlib.sha256\n ).hexdigest()\n if not hmac.compare_digest(signature, expected):\n return "Invalid signature", 403\n\n payload = request.get_json()\n print(f"Événement: {payload['event']}")\n print(f"Fichier: {payload['data']['vault']}/{payload['data']['path']}")\n return "OK", 200\n\napp.run(port=5000)</code></pre>
<h4 data-i18n="help.d1ea1dc6">n8n / Make (no-code)</h4>
<ol>
<li>
Créez un
<strong>Webhook Trigger</strong> dans n8n ou
Make
</li>
<li>
Copiez l'URL générée (ex:
<code
>https://n8n.monserveur.com/webhook/abc123</code
>)
</li>
<li>
Dans ObsiGate, ajoutez un webhook pointant
vers cette URL
</li>
<li data-i18n="auto.14c75ac6">Sélectionnez les événements souhaités</li>
<li data-i18n="auto.2a5af19b">
Le workflow se déclenche automatiquement à
chaque événement
</li>
</ol>
<h4 data-i18n="help.229fd3e2">Notification Discord</h4>
<p>
Utilisez un webhook relais (n8n, Make, ou un
script) pour transformer le payload ObsiGate en
message Discord :
</p>
<pre
style="
background: var(--bg-secondary);
padding: 12px;
border-radius: 8px;
overflow-x: auto;
font-size: 0.78rem;
"
><code># Exemple avec curl + jq dans un script bash
WEBHOOK_URL="https://discord.com/api/webhooks/..."
OBSIGATE_SECRET="mon-secret"
# Lire le payload depuis stdin
read -r payload
event=$(echo "$payload" | jq -r '.event')
path=$(echo "$payload" | jq -r '.data.path')
curl -H "Content-Type: application/json" -d "{
\"content\": \"📝 **$event** — $path\"
}" "$WEBHOOK_URL"</code></pre>
<h4 data-i18n="help.40dd7e38">Tester avec curl</h4>
<pre
style="
background: var(--bg-secondary);
padding: 12px;
border-radius: 8px;
overflow-x: auto;
font-size: 0.78rem;
"
><code># Simuler ce qu'ObsiGate envoie (pour tester votre endpoint)
curl -X POST https://votre-serveur.com/webhook \
-H "Content-Type: application/json" \
-H "X-ObsiGate-Event: file_created" \
-H "X-ObsiGate-Signature: sha256=$(echo -n '{"event":"file_created",...}' | openssl dgst -sha256 -hmac 'mon-secret' | cut -d' ' -f2)" \
-d '{"event":"file_created","timestamp":"2026-06-03T14:30:00Z","data":{"vault":"IT","path":"notes/test.md"}}'</code></pre>
<h3 data-i18n="help.381abec7">Limitations actuelles</h3>
<ul>
<li>
Timeout de <strong>5 secondes</strong> par
requête — les webhooks sont "fire and
forget" (pas de retry)
</li>
<li>
Les modifications détectées par le watcher
filesystem (watchdog)
<strong data-i18n="auto.98c547ea">ne déclenchent pas</strong> de
webhooks — seules les opérations via l'API
REST sont prises en compte
</li>
<li>
<code>file_modified</code> n'est pas encore
dispatché (réservé pour version future)
</li>
<li data-i18n="auto.1d3330cc">Pas de retry automatique en cas d'échec</li>
<li data-i18n="auto.4dfa6a25">
Pas de filtrage par vault — un webhook
écoute toutes les vaults
</li>
</ul>
</section>
<section class="help-section" id="help-sauvegardes">
<h2 data-i18n="help.62549eca">💾 Sauvegardes et Audits</h2>
<h3 data-i18n="help.backup_auto">Backup automatique</h3>
<p data-i18n="auto.ab5e6155">
ObsiGate crée automatiquement des sauvegardes
avant chaque modification destructive.
</p>
<ul>
<li>
<strong data-i18n="help.before_write">Avant écriture</strong> : Sauvegarde
du contenu original dans
<code>.obsigate-backup/</code>
</li>
<li>
<strong>Timestamp</strong><span data-i18n="help.desc_31e7253b"> : Chaque backup
est horodaté</span></li>
<li>
<strong data-i18n="auto.41d7c6af">Répertoire dédié</strong> :
<code>.obsigate-backup/</code> dans le
répertoire de la vault
</li>
</ul>
<h3 data-i18n="help.audit_log">Audit log</h3>
<p data-i18n="auto.2bc24d2e">
Toutes les opérations sensibles sont tracées
dans un journal.
</p>
<ul>
<li>
<strong>Journal</strong> : Fichier
<code>data/audit.log</code> au format JSON
lines
</li>
<li>
<strong data-i18n="auto.598a0ca7">Événements tracés</strong><span data-i18n="help.desc_9f321d1e"> :
Écritures, suppressions, changements de
configuration</span></li>
<li>
<strong>Rotation</strong><span data-i18n="help.desc_19cf5730"> : Rotation
automatique à 10 Mo</span></li>
</ul>
</section>
<!-- 🛡️ Sécurité -->
<section class="help-section" id="help-securite">
<h2 data-i18n="auto.37561472">🛡️ Sécurité</h2>
<h3 data-i18n="help.c48fb18a">Authentification</h3>
<ul>
<li>
<strong>JWT + Argon2id</strong><span data-i18n="help.desc_8f0bffe3"> : Tokens JWT
avec mots de passe hachés Argon2id</span></li>
<li>
<strong data-i18n="auto.72e515fa">Contrôle d'accès par vault</strong>
: Chaque utilisateur voit uniquement ses
vaults
</li>
<li>
<strong>Refresh tokens</strong> : Cookies
HttpOnly avec rotation automatique
</li>
<li>
<strong data-i18n="help.rate_limiting">Rate limiting</strong> : 10
tentatives / 15 min par IP + verrouillage
compte
</li>
<li>
<strong>Compte admin</strong><span data-i18n="help.desc_5fc28b7d"> : Créé
automatiquement au premier démarrage</span></li>
</ul>
<h3 data-i18n="help.83f1d9ca">Protection</h3>
<ul>
<li>
<strong>Secret redactor</strong><span data-i18n="help.desc_9846fc07"> : Masque
JWT, clés API, tokens GitHub dans les
aperçus</span></li>
<li>
<strong>Path traversal</strong> : Validation
des chemins contre les attaques
<code>../</code>
</li>
<li>
<strong>Headers CSP</strong> :
Content-Security-Policy, X-Frame-Options,
XSS-Protection
</li>
<li>
<strong data-i18n="help.atomic_writes">Atomic writes</strong><span data-i18n="help.desc_752789ab"> : Écriture
via fichier temporaire + renommage atomique</span></li>
<li>
<strong>Non-root Docker</strong> : Conteneur
tourne avec UID 1000
</li>
</ul>
</section>
<section class="help-section" id="help-astuces">
<h2 data-i18n="help.686f8313">💡 Astuces et bonnes pratiques</h2>
<h3 data-i18n="auto.559ef1da">Workflow recommandé</h3>
<ol>
<li>
<strong data-i18n="auto.678711b5">Sélectionnez un contexte</strong><span data-i18n="help.desc_39979e71"> :
Choisissez un vault pour réduire le bruit</span></li>
<li>
<strong>Explorez l'arborescence</strong> :
Naviguez dans les dossiers
</li>
<li>
<strong>Utilisez les tags</strong> : Filtrez
par tags pour affiner
</li>
<li>
<strong>Recherchez intelligemment</strong><span data-i18n="help.desc_ae213f54"> :
Combinez opérateurs et texte libre</span></li>
<li>
<strong>Profitez de l'historique</strong><span data-i18n="help.desc_71eeb16a"> :
Réutilisez vos recherches fréquentes</span></li>
</ol>
<h3 data-i18n="help.fe388b67">Optimisations</h3>
<ul>
<li>
<strong>Masquez la sidebar</strong> : Plus
d'espace pour le contenu
</li>
<li>
<strong
>Utilisez le filtrage sidebar</strong
>
: Plus rapide que la recherche globale pour
trouver un fichier
</li>
<li>
<strong data-i18n="auto.3be698f6">Favorisez les opérateurs</strong> :
<code>tag:</code> et
<code>vault:</code> accélèrent la recherche
</li>
<li>
<strong data-i18n="help.breadcrumb_clickable">Breadcrumb cliquable</strong> :
Repositionnez rapidement la sidebar
</li>
</ul>
<h3 data-i18n="help.2a92203f">Trucs et astuces</h3>
<ul>
<li>
<strong>Navigation visuelle</strong> : Le
breadcrumb repositionne automatiquement la
sidebar
</li>
<li>
<strong>Gestion de l'espace</strong><span data-i18n="help.desc_287be69c"> :
Réduisez les sections Vaults/Tags selon vos
besoins</span></li>
<li>
<strong>Confort de lecture</strong> :
Ajustez la largeur de la sidebar
</li>
<li>
<strong>Mobile-friendly</strong> : Utilisez
le bouton hamburger sur mobile
</li>
<li>
<strong data-i18n="help.wikilinks">Wikilinks</strong> : Naviguez entre
notes comme dans Obsidian
</li>
<li>
<strong>Images</strong> : Support complet
des syntaxes Obsidian
</li>
</ul>
<h3 data-i18n="help.advanced_use_cases">Cas d'usage avancés</h3>
<ul>
<li>
<strong data-i18n="auto.b86ba5ea">Recherche multi-critères</strong> :
<code
>tag:docker tag:linux vault:IT
nginx</code
>
</li>
<li>
<strong>Exploration par tags</strong> :
Cliquez sur un tag dans une note pour voir
toutes les notes similaires
</li>
<li>
<strong data-i18n="auto.4fa5d7c2">Édition rapide</strong><span data-i18n="help.desc_cb6f0bfd"> : Ouvrez
l'éditeur, modifiez, sauvegardez</span></li>
<li>
<strong data-i18n="help.export">Export</strong><span data-i18n="help.desc_f9083ce5"> : Téléchargez des
fichiers pour backup ou partage</span></li>
<li>
<strong data-i18n="auto.79f6e1dd">Multi-fenêtres</strong><span data-i18n="help.desc_9cb5974c"> : Ouvrez
plusieurs notes dans des onglets séparés</span></li>
</ul>
</section>
<!-- 🧩 Plugins -->
<section class="help-section" id="help-plugins">
<h2 data-i18n="help.plugins_title">🧩 Plugins</h2>
<p data-i18n="help.plugins_intro">
Les plugins étendent ObsiGate : affichage personnalisé des
fichiers, filtres de recherche, actions d'édition, widgets
et intégrations. Ils s'exécutent dans des Web Workers
sandboxés — isolés de la page pour la sécurité.
</p>
<h3 data-i18n="help.plugins_install">Installation</h3>
<ol>
<li data-i18n="help.plugins_install_1">Ouvrez Réglages → 🧩 Plugins.</li>
<li data-i18n="help.plugins_install_2">Cliquez « Installer un plugin ».</li>
<li data-i18n="help.plugins_install_3">Sélectionnez un fichier <code>.zip</code> contenant le manifest
<code>plugin.json</code> + le code du plugin.</li>
<li data-i18n="help.plugins_install_4">Le manifest est validé (nom, version semver, hooks, permissions)
avant installation.</li>
</ol>
<h3 data-i18n="help.plugins_manage">Gestion</h3>
<ul>
<li data-i18n="help.plugins_manage_1"><strong>Activer / Désactiver</strong> : boutons sur chaque plugin — les
hooks ne tournent que si le plugin est activé.</li>
<li data-i18n="help.plugins_manage_2"><strong>Désinstaller</strong> : supprime définitivement le plugin du vault.</li>
<li data-i18n="help.plugins_manage_3"><strong>Code</strong> : visualisez le code source du plugin installé.</li>
</ul>
<h3 data-i18n="help.plugins_dev">Développer un plugin</h3>
<p data-i18n="help.plugins_dev_intro">
Téléchargez le modèle depuis Réglages → Plugins → « Obtenir le
modèle ». Un plugin se compose d'un manifest
<code>plugin.json</code> et d'un fichier principal (ex.
<code>index.js</code>).
</p>
<h3 data-i18n="help.plugins_hooks">Hooks disponibles</h3>
<ul>
<li><strong data-i18n="help.plugins_hook_render">onFileRender</strong> :
<span data-i18n="help.plugins_hook_render_desc">transforme le contenu affiché d'un fichier.</span></li>
<li><strong data-i18n="help.plugins_hook_search">onSearchFilter</strong> :
<span data-i18n="help.plugins_hook_search_desc">filtre les résultats de recherche.</span></li>
<li><strong data-i18n="help.plugins_hook_editor">onEditorAction</strong> :
<span data-i18n="help.plugins_hook_editor_desc">intercepte les actions de l'éditeur.</span></li>
<li><strong data-i18n="help.plugins_hook_sidebar">onSidebarItem</strong> :
<span data-i18n="help.plugins_hook_sidebar_desc">ajoute des éléments à la barre latérale.</span></li>
<li><strong data-i18n="help.plugins_hook_file">onFileCreate / onFileDelete</strong> :
<span data-i18n="help.plugins_hook_file_desc">réagit à la création / suppression de fichiers.</span></li>
<li><strong data-i18n="help.plugins_hook_vault">onVaultMount</strong> :
<span data-i18n="help.plugins_hook_vault_desc">exécuté au chargement d'un vault.</span></li>
</ul>
<h3 data-i18n="help.plugins_security">Sécurité</h3>
<ul>
<li data-i18n="help.plugins_security_1"><strong>Sandbox Web Worker</strong> : le code plugin tourne isolé, sans
accès direct au DOM.</li>
<li data-i18n="help.plugins_security_2"><strong>Permissions</strong> : chaque hook déclaré doit être satisfait
par les permissions du manifest.</li>
<li data-i18n="help.plugins_security_3"><strong>Empaquetage sécurisé</strong> : le .zip est vérifié contre le
path traversal à l'installation.</li>
<li data-i18n="help.plugins_security_4"><strong>Admin uniquement</strong> : l'installation et la suppression
requièrent le rôle admin.</li>
</ul>
</section>
<div class="help-footer">
<p>
<strong>ObsiGate <span id="help-version"></span></strong>
<span data-i18n="help.footer_tagline">- Porte d'entrée web pour vos vaults Obsidian</span>
</p>
<p>
Pour plus d'informations, consultez le
<a
href="https://git.dracodev.net/Projets/ObsiGate"
target="_blank"
>dépôt du projet</a
>.
</p>
</div>
</div>
</div>
</div>
</div>
<!-- Find in Page Bar -->
<div class="find-in-page-bar" id="find-in-page-bar" hidden>
<div class="find-in-page-content">
<i data-lucide="search" class="find-icon"></i>
<input
type="text"
id="find-input"
data-i18n-placeholder="settings.search_placeholder" placeholder="Rechercher..."
autocomplete="off"
aria-label="Rechercher dans la page"
/>
<span class="find-counter" id="find-counter" aria-live="polite"
>0 occurrence</span
>
<div class="find-nav-buttons">
<button
class="find-btn"
id="find-prev"
title="Précédent (Shift+Enter)"
aria-label="Résultat précédent"
disabled
>
<i
data-lucide="chevron-up"
style="width: 14px; height: 14px"
></i>
</button>
<button
class="find-btn"
id="find-next"
title="Suivant (Enter)"
aria-label="Résultat suivant"
disabled
>
<i
data-lucide="chevron-down"
style="width: 14px; height: 14px"
></i>
</button>
</div>
<div class="find-options">
<button
class="find-btn find-option-btn"
id="find-case-sensitive"
title="Respecter la casse"
aria-label="Respecter la casse"
aria-pressed="false"
>
<span class="find-option-text">Aa</span>
</button>
<button
class="find-btn find-option-btn"
id="find-whole-word"
title="Mot entier"
aria-label="Mot entier"
aria-pressed="false"
>
<span class="find-option-text">abc</span>
</button>
<button
class="find-btn find-option-btn"
id="find-regex"
title="Expression régulière"
aria-label="Expression régulière"
aria-pressed="false"
>
<span class="find-option-text">.*</span>
</button>
</div>
<button
class="find-btn find-close"
id="find-close"
title="Fermer (Escape)"
aria-label="Fermer la recherche"
>
<i data-lucide="x" style="width: 16px; height: 16px"></i>
</button>
</div>
<div class="find-error" id="find-error" hidden></div>
</div>
<script>
// One-time migration for devices stuck on the legacy service worker
// (cache-first with a fixed cache name). Deletes the old caches and
// triggers an update check; the new network-first SW then takes over.
// Keyed in localStorage so it never loops or re-runs on every session.
(function () {
if (!('serviceWorker' in navigator) || !('caches' in window)) return;
var MIGRATION_KEY = 'obsigate-sw-migration';
try {
if (localStorage.getItem(MIGRATION_KEY) === 'v3') return;
localStorage.setItem(MIGRATION_KEY, 'v3');
} catch (e) { return; }
caches.keys().then(function (names) {
return Promise.all(names.map(function (name) {
return /^obsigate-/.test(name) ? caches.delete(name) : null;
}));
}).then(function () {
return navigator.serviceWorker.getRegistration();
}).then(function (reg) {
if (reg) reg.update();
}).catch(function () { /* best effort */ });
})();
</script>
<script type="module" src="/static/js/app.js"></script>
<!-- ── Mobile Bottom Toolbar ── -->
<nav
class="mobile-toolbar"
id="mobile-toolbar"
aria-label="Barre d'outils mobile"
>
<button
class="mt-btn"
id="mt-explorer"
title="Explorateur"
aria-label="Explorateur de vault"
>
<i
data-lucide="folder-open"
style="width: 20px; height: 20px"
></i>
<span class="mt-label">settings.search</span>
</button>
<button
class="mt-btn"
id="mt-search"
title="Rechercher un fichier"
aria-label="Rechercher un fichier"
>
<i data-lucide="search" style="width: 20px; height: 20px"></i>
<span class="mt-label" data-i18n="settings.search">Recherche</span>
</button>
<button
class="mt-btn mt-btn-ai"
id="mt-ai"
title="Assistant AI"
aria-label="Ouvrir l'assistant AI"
>
<i data-lucide="bot" style="width: 24px; height: 24px"></i>
</button>
<button
class="mt-btn mt-btn-primary"
id="mt-palette"
title="Commandes"
aria-label="Palette de commandes"
>
<i data-lucide="terminal" style="width: 20px; height: 20px"></i>
<span class="mt-label" data-i18n="palette.mode_commands">Commandes</span>
</button>
<button
class="mt-btn"
id="mt-tabs"
title="Onglets"
aria-label="Onglets ouverts"
>
<i data-lucide="files" style="width: 20px; height: 20px"></i>
<span class="mt-badge" id="mt-tab-count" style="display: none"
>0</span
>
<span class="mt-label" data-i18n="settings.tabs">Onglets</span>
</button>
</nav>
<!-- ── Mobile Tab Switcher (bottom sheet) ── -->
<div class="mt-tab-switcher" id="mt-tab-switcher">
<div class="mt-sheet-handle"></div>
<div class="mt-sheet-header">
<span class="mt-sheet-title">Fichiers ouverts</span>
<div class="mt-sheet-actions">
<button
class="mt-sheet-close-all"
id="mt-close-all"
title="Fermer tout"
aria-label="Fermer tous les fichiers"
>
Fermer tout
</button>
<button
class="mt-sheet-close"
id="mt-sheet-close"
aria-label="Fermer"
>
✕
</button>
</div>
</div>
<div class="mt-sheet-list" id="mt-tab-list"></div>
</div>
<div class="mt-overlay" id="mt-overlay"></div>
<!-- Floating results bar (outside content-area, survives innerHTML) -->
<div class="results-bar" id="results-bar" style="display:none">
<span class="results-bar-count" id="rb-count">0/0</span>
<button class="results-bar-btn" id="rb-prev" title="Resultat precedent" aria-label="Precedent">&#8593;</button>
<button class="results-bar-btn" id="rb-next" title="Resultat suivant" aria-label="Suivant">&#8595;</button>
<span class="results-bar-sep"></span>
<button class="results-bar-btn tog" id="rb-case" title="Respecter la casse (Alt-C)">Aa</button>
<button class="results-bar-btn tog" id="rb-word" title="Mot entier (Alt-W)">wd</button>
<button class="results-bar-btn tog" id="rb-regex" title="Expression reguliere (Alt-R)">.*</button>
<button class="results-bar-btn tog" id="rb-semantic" title="Recherche sémantique (Alt-S)">~</button>
</div>
<!-- Offline conflict resolution modal -->
<div class="editor-modal" id="offline-conflict-modal">
<div class="editor-container" style="max-width:520px">
<div class="editor-header">
<span class="editor-brand">Conflit hors-ligne</span>
<div class="editor-spacer"></div>
<button class="editor-btn" id="offline-conflict-close" title="Fermer" aria-label="Fermer">&#10005;</button>
</div>
<div class="editor-body" style="padding:20px;overflow:auto">
<p style="color:var(--text-secondary);margin-bottom:12px" id="offline-conflict-msg"></p>
<div style="display:flex;gap:8px">
<button class="editor-btn primary" id="offline-conflict-keep-local">Garder ma version</button>
<button class="editor-btn" id="offline-conflict-keep-server" data-i18n="offline.keep_server">Garder version serveur</button>
</div>
</div>
</div>
</div>
<!-- About Modal -->
<div class="about-overlay" id="about-overlay">
<div class="about-modal">
<button class="about-close" id="about-close" aria-label="Fermer">&times;</button>
<div class="about-hero">
<div class="about-logo">
<svg width="56" height="56" viewBox="0 0 56 56" fill="none">
<rect width="56" height="56" rx="14" fill="var(--accent)" opacity="0.15"/>
<rect x="12" y="8" width="14" height="18" rx="3" fill="var(--accent)" opacity="0.9"/>
<rect x="29" y="8" width="14" height="18" rx="3" fill="var(--accent)" opacity="0.5"/>
<rect x="12" y="30" width="14" height="18" rx="3" fill="var(--accent)" opacity="0.7"/>
<rect x="29" y="30" width="14" height="18" rx="3" fill="var(--accent)" opacity="0.35"/>
</svg>
</div>
<h1 class="about-title" data-i18n="header.logo">ObsiGate</h1>
<p class="about-tagline">Vos notes Obsidian,<br>simplement accessibles.</p>
<span class="about-version" id="about-version">—</span>
</div>
<div class="about-body">
<div class="about-stats">
<div class="about-stat">
<span class="about-stat-value" id="about-stat-files">—</span>
<span class="about-stat-label" data-i18n="palette.mode_files">Fichiers</span>
</div>
<div class="about-stat">
<span class="about-stat-value" id="about-stat-vaults">—</span>
<span class="about-stat-label" data-i18n="sidebar.tab_vaults">Vaults</span>
</div>
<div class="about-stat">
<span class="about-stat-value">285</span>
<span class="about-stat-label">Tests</span>
</div>
</div>
<div class="about-stack">
<h3>Construit avec</h3>
<div class="about-stack-grid">
<span class="about-stack-badge">🐍 FastAPI</span>
<span class="about-stack-badge">⚡ vanilla JS</span>
<span class="about-stack-badge">🎨 CSS custom</span>
<span class="about-stack-badge">🐳 Docker</span>
<span class="about-stack-badge">🔍 TF-IDF</span>
<span class="about-stack-badge">📝 CodeMirror 6</span>
<span class="about-stack-badge">🧜 Mermaid.js</span>
<span class="about-stack-badge">🧠 Argon2id</span>
<span class="about-stack-badge">🔄 Watchdog</span>
<span class="about-stack-badge">📊 WeasyPrint</span>
<span class="about-stack-badge">🧪 Playwright</span>
<span class="about-stack-badge">🤖 AI multi-provider</span>
</div>
</div>
<div class="about-footer">
<div class="about-meta">
<span>Derniere livraison&nbsp;: <strong id="about-build-date">Juin 2026</strong></span>
<span>Git&nbsp;: <strong id="about-commit">—</strong></span>
</div>
<p class="about-joke" id="about-joke"></p>
<p class="about-copy">&copy; 2026 — Fait avec ☕, 🎧 et quelques <code>git push --force</code> accidentels.</p>
</div>
</div>
</div>
</div>
</body>
</html>