Files
ObsiGate/frontend/index.html
T
bruno 9fb094f505
CI / lint (push) Successful in 2m1s
CI / security (push) Successful in 1m26s
CI / test (push) Successful in 4m50s
CI / build (push) Successful in 1m22s
CI / e2e (push) Successful in 15m34s
feat: refonte mobile de la section Configurations #114
2026-09-24 08:43:18 -04:00

5785 lines
322 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, viewport-fit=cover" />
<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>
<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>
<div
class="menu-list-row menu-list-version"
role="none"
>
<span
class="menu-list-icon"
aria-hidden="true"
>
<i
data-lucide="tag"
style="width: 17px; height: 17px"
></i>
</span>
<span class="menu-list-content">
<span class="menu-list-title" data-i18n="header.menu_version"
>Version</span
>
<span class="menu-list-subtitle"
>ObsiGate
<span
class="version-badge"
id="version-badge"
title="Version ObsiGate"
></span
></span>
</span>
</div>
</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>
<!-- User section (bottom of the sidebar, #112) -->
<div class="sidebar-user" id="sidebar-user" hidden>
<button
class="sidebar-user-profile"
id="sidebar-user-profile"
type="button"
title="Profil"
>
<span
class="sidebar-user-avatar"
id="sidebar-user-avatar"
aria-hidden="true"
></span>
<span class="sidebar-user-meta">
<span
class="sidebar-user-name user-display-name"
id="sidebar-user-name"
></span>
<span
class="sidebar-user-role"
id="sidebar-user-role"
></span>
</span>
</button>
<button
class="sidebar-user-logout"
id="sidebar-user-logout"
type="button"
title="Déconnexion"
>
<i
data-lucide="log-out"
style="width: 16px; height: 16px"
></i>
</button>
</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"
id="editor-fullscreen"
title="Plein écran"
aria-label="Plein écran"
data-i18n-attr="title:editor.fullscreen"
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/></svg>
</button>
<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">
<button
class="help-hamburger"
id="config-hamburger"
data-i18n-attr="title:config.toc_toggle;aria-label:config.toc_toggle"
title="Afficher le sommaire"
aria-label="Afficher le sommaire"
>
<i
data-lucide="menu"
style="width: 18px; height: 18px"
></i>
</button>
<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>
<button
class="help-toc-close"
id="config-toc-close"
data-i18n-attr="aria-label:config.toc_close"
aria-label="Fermer le sommaire"
>
<i
data-lucide="x"
style="width: 16px; height: 16px"
></i>
</button>
</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-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-themes" class="help-nav-link" data-i18n="settings.themes"></a></li>
<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-tags" class="help-nav-link" data-i18n="config.section_tags"></a></li>
<li><a href="#cfg-hidden-files" class="help-nav-link" data-i18n="config.section_hidden"></a></li>
<li><a href="#cfg-sync" class="help-nav-link" data-i18n="settings.sync"></a></li>
<li><a href="#cfg-backend-settings" class="help-nav-link" data-i18n="config.section_backend"></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-sources" class="help-nav-link" data-i18n="config.section_sources">Sources connectées</a></li>
<li><a href="#cfg-tokens" class="help-nav-link" data-i18n="config.nav_tokens">🔑 Clés API & MCP</a></li>
<li><a href="#cfg-push" class="help-nav-link" data-i18n="config.section_push">Notifications push</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>
<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>
</ul>
</nav>
<div class="help-content" id="config-scroll">
<div class="config-content">
<!-- 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 profile-avatar-field" id="profile-avatar-field">
<span class="profile-avatar-label" id="profile-avatar-label" data-i18n="config.avatar_label">Photo de profil</span>
<div class="profile-avatar-row">
<div
class="profile-avatar-preview"
id="profile-avatar-preview"
data-i18n-attr="title:config.avatar_choose;aria-label:config.avatar_choose"
title="Choisir une image"
aria-label="Choisir une image"
>
<img id="profile-avatar-img" alt="" hidden />
<span class="profile-avatar-initials" id="profile-avatar-initials" aria-hidden="true"></span>
<button
type="button"
class="profile-avatar-overlay"
id="profile-avatar-overlay"
data-i18n-attr="title:config.avatar_choose;aria-label:config.avatar_choose"
title="Choisir une image"
aria-label="Choisir une image"
>
<i data-lucide="camera" style="width: 18px; height: 18px"></i>
</button>
</div>
<div class="profile-avatar-actions">
<button type="button" class="config-btn-secondary" id="profile-avatar-choose" data-i18n="config.avatar_choose">Choisir une image</button>
<button type="button" class="config-btn-secondary profile-avatar-remove-btn" id="profile-avatar-remove" data-i18n="config.avatar_remove" hidden>Supprimer la photo</button>
<input
type="file"
id="profile-avatar-input"
accept="image/png,image/jpeg,image/webp"
hidden
/>
<span class="profile-hint" data-i18n="config.avatar_hint">PNG, JPG ou WEBP — image carrée recadrée et réduite à 256 px.</span>
</div>
</div>
<p class="profile-avatar-error hidden" id="profile-avatar-error" role="alert"></p>
</div>
<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>
<!-- 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>
<!-- 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>
<!-- Tag Filtering (existing) -->
<section class="config-section help-section" id="cfg-tags">
<h2 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>
<!-- 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>
<!-- 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>
<!-- 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>
<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>
<!-- 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 (#104 — accordéon fournisseurs) -->
<section
class="config-section help-section"
id="cfg-ai"
>
<div class="ai-keys-header">
<div class="ai-keys-header-text">
<h2 data-i18n="config.section_cles-api-intelligence-artificielle">🤖 Cles API Intelligence Artificielle</h2>
<p class="config-description" data-i18n="config.ai_header_desc">
Configurez vos cles API fournisseur par fournisseur. Dépliez une carte pour saisir une
clé, puis cliquez sur <strong data-i18n="config.test">Tester</strong> pour charger les
modeles.
</p>
</div>
<div class="ai-keys-search">
<i data-lucide="search" class="icon"></i>
<input
type="search"
id="cfg-ai-search"
data-i18n-placeholder="config.ai_search_placeholder"
placeholder="Rechercher un fournisseur…"
autocomplete="off"
/>
</div>
</div>
<div class="ai-default-card">
<div class="ai-card-title" data-i18n="config.ai_default_section">Configuration par defaut</div>
<div class="ai-default-grid">
<div class="ai-field">
<label
class="ai-field-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"
>
<option value="">--</option>
</select>
</div>
<div class="ai-field">
<label
class="ai-field-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"
>
<option value="">--</option>
</select>
</div>
</div>
<div class="ai-field">
<span
class="ai-field-label"
data-i18n="config.ai_capabilities"
>Capacites du modele</span
>
<div
id="cfg-ai-default-model-caps"
class="ai-caps-badges"
></div>
</div>
</div>
<div class="ai-providers-header">
<h3
class="ai-card-title"
data-i18n="config.ai_providers_title"
>
Fournisseurs d'API
</h3>
</div>
<div id="cfg-ai-providers" class="ai-providers-list"></div>
<div
id="cfg-ai-providers-empty"
class="ai-providers-empty hidden"
data-i18n="config.ai_providers_empty"
>
Aucun fournisseur ne correspond
</div>
<div class="ai-keys-footer">
<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"
class="ai-keys-status"
></span>
</div>
</section>
<!-- Connected sources & keyed web search (#103) -->
<section
class="config-section help-section"
id="cfg-sources"
>
<h2 data-i18n="config.section_sources"
>Sources connectées & recherche web</h2
>
<p
class="config-description"
data-i18n="config.sources_desc"
>
Clés utilisées par les outils de l'Assistant IA
(recherche web à clé, Gitea, GitHub). Elles sont
stockées localement et priment sur les variables
d'environnement.
</p>
<div class="config-row">
<label
class="config-label"
for="cfg-tavily-key"
>Tavily API Key</label
>
<input
type="password"
id="cfg-tavily-key"
class="config-input"
placeholder="tvly-..."
autocomplete="off"
/>
</div>
<div class="config-row">
<label
class="config-label"
for="cfg-brave-key"
>Brave Search API Key</label
>
<input
type="password"
id="cfg-brave-key"
class="config-input"
placeholder="BSA..."
autocomplete="off"
/>
</div>
<div class="config-row">
<label
class="config-label"
for="cfg-serpapi-key"
>SerpAPI Key</label
>
<input
type="password"
id="cfg-serpapi-key"
class="config-input"
placeholder="..."
autocomplete="off"
/>
</div>
<div class="config-row">
<label
class="config-label"
for="cfg-exa-key"
>Exa API Key</label
>
<input
type="password"
id="cfg-exa-key"
class="config-input"
placeholder="..."
autocomplete="off"
/>
</div>
<div class="config-row">
<label
class="config-label"
for="cfg-gitea-url"
data-i18n="config.gitea_url"
>URL Gitea</label
>
<input
type="text"
id="cfg-gitea-url"
class="config-input"
placeholder="https://git.example.net"
autocomplete="off"
/>
</div>
<div class="config-row">
<label
class="config-label"
for="cfg-gitea-token"
>Gitea Token</label
>
<input
type="password"
id="cfg-gitea-token"
class="config-input"
placeholder="token personnel..."
autocomplete="off"
/>
</div>
<div class="config-row">
<label
class="config-label"
for="cfg-github-token"
>GitHub Token</label
>
<input
type="password"
id="cfg-github-token"
class="config-input"
placeholder="ghp_..."
autocomplete="off"
/>
</div>
<div
class="config-actions-row"
style="margin-top: 16px"
>
<button class="config-btn-save"
id="cfg-save-tool-keys" data-i18n="help.shortcut_save">
Sauvegarder
</button>
</div>
</section>
<!-- Clés API / MCP (#107) -->
<section id="cfg-tokens" class="config-section help-section">
<h2 data-i18n="config.section_tokens">🔑 Clés API &amp; MCP</h2>
<p class="config-description" data-i18n="config.tokens_desc">
Jetons longue durée pour l'API REST et le serveur MCP.
La même clé fonctionne pour les deux (en-tête Authorization: Bearer).
</p>
<div id="tokens-list"></div>
<div class="config-add-row" id="tokens-create-row">
<input
type="text"
id="token-name-input"
data-i18n-placeholder="config.token_name_placeholder"
placeholder="Nom (ex: Claude Desktop)"
class="config-input"
style="width: 180px"
/>
<select id="token-expiry-select" class="config-input" style="width: 140px">
<option value="1d" data-i18n="config.token_expiry_1d">1 jour</option>
<option value="30d" selected data-i18n="config.token_expiry_30d">1 mois</option>
<option value="180d" data-i18n="config.token_expiry_180d">6 mois</option>
<option value="365d" data-i18n="config.token_expiry_365d">1 an</option>
<option value="never" data-i18n="config.token_expiry_never">Sans fin</option>
</select>
<button class="config-btn-add" id="token-create-btn" data-i18n="config.token_create">Créer une clé</button>
</div>
<div id="token-secret-area" class="token-secret-area hidden">
<p class="config-description" data-i18n="config.token_secret_warning">Copiez cette clé maintenant — elle ne sera plus jamais affichée.</p>
<textarea id="token-secret-value" class="config-input token-secret-text" rows="3" readonly></textarea>
<div class="config-add-row">
<button class="config-btn-add" id="token-copy-btn" data-i18n="config.token_copy">Copier</button>
<button class="config-btn-add" id="token-dismiss-btn" data-i18n="config.token_done">Terminé</button>
</div>
</div>
<div class="qh-tip" style="margin-top:10px">
<strong data-i18n="config.token_usage">Utilisation</strong>
<span data-i18n="config.token_usage_detail">: en-tête `Authorization: Bearer ` sur l'API, et config MCP (`claude_desktop_config.json`) : `{"mcpServers":{"obsigate":{"url":"<base>/mcp","headers":{"Authorization":"Bearer "}}}}`.</span>
</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>
<!-- 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" id="cfg-partages-publics">
<h2>📤 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>
<!-- 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>
</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>
</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-download-md"
title="Télécharger ce guide en Markdown"
data-i18n-attr="title:guide105.dl_md_title"
>
<i
data-lucide="file-code-2"
style="width: 16px; height: 16px"
></i>
</button>
<button
class="editor-btn"
id="help-download-pdf"
title="Télécharger ce guide en PDF"
data-i18n-attr="title:guide105.dl_pdf_title"
>
<i
data-lucide="file-down"
style="width: 16px; height: 16px"
></i>
</button>
<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" 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-architecture" class="help-nav-link" data-i18n="guide105.nav_architecture">🏗️ Architecture</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-diagrams" class="help-nav-link" data-i18n="guide105.nav_diagrams">📊 Diagrammes</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-library" class="help-nav-link" data-i18n="guide105.nav_library">⭐ Bibliothèque</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-offline" class="help-nav-link" data-i18n="guide105.nav_offline">📴 Hors-ligne</a>
</li>
<li>
<a href="#help-collab" class="help-nav-link" data-i18n="guide105.nav_collab">👥 Collaboration</a>
</li>
<li>
<a href="#help-desktop" class="help-nav-link" data-i18n="guide105.nav_desktop">🖥️ Desktop</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-api" class="help-nav-link" data-i18n="guide105.nav_api">🔌 API</a>
</li>
<li>
<a href="#help-languages" class="help-nav-link" data-i18n="guide105.nav_languages">🌍 Multilingue</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-architecture">
<h2 data-i18n="guide105.nav_architecture">🏗️ Architecture</h2>
<p data-i18n-html="guide105.arch_intro">ObsiGate est une application web complète construite en couches indépendantes, sans base de données externe : les notes vivent dans vos dossiers Obsidian, l'état applicatif dans des fichiers JSON de <code>data/</code>, l'index de recherche en mémoire.</p>
<pre class="mermaid-code"><code class="language-mermaid">flowchart TB
subgraph client["Clients"]
UI["SPA vanilla JS\n(frontend/js)"]
PWA["PWA hors-ligne\n(service worker + IndexedDB)"]
DESK["App desktop Tauri\n(fenêtre native)"]
end
subgraph server["Serveur FastAPI (Python 3.11)"]
API["REST /api\nJWT + Argon2id"]
IDX["Index recherche\nTF-IDF + embeddings"]
FS["Accès fichiers\nwatchdog + safe paths"]
PDF["Rendu markdown\nmistune + WeasyPrint"]
AI["Assistant IA\nproviders + outils"]
MCP["Serveur MCP\n/mcp (HTTP)"]
WS["WebSocket\ncollab Yjs + SSE"]
WH["Webhooks\nHMAC-SHA256"]
end
subgraph data["Données"]
V1["Vault 1 (dossier)"]
V2["Vault 2 (dossier)"]
CFG["data/*.json\nconfig, users, audit"]
BK[".obsigate-backup/\nbackups horodatés"]
end
UI -- HTTP --> API
PWA -- "cache + queue" --> API
DESK -- embarqué --> API
API --> IDX
API --> FS
API --> PDF
API --> AI
MCP --> AI
WS --> FS
FS --> V1
FS --> V2
IDX --> V1
IDX --> V2
BK --> V1
API --> CFG
API -- événements --> WH</code></pre>
<p data-i18n="guide105.arch_diagram_note">Le diagramme est interactif dans l'application : zoom, plein écran, copie SVG ou code.</p>
<h3 data-i18n="guide105.arch_h3_layers">Les grandes composantes</h3>
<ul>
<li>
<strong data-i18n="guide105.arch_lbl_fe">Frontend</strong><span data-i18n-html="guide105.arch_fe"> — SPA en JavaScript vanilla (modules ES), sans framework ni build npm : <code>frontend/js/</code> (~30 modules). Le CSS utilise des variables pour les thèmes.</span>
</li>
<li>
<strong data-i18n="guide105.arch_lbl_be">Backend</strong><span data-i18n="guide105.arch_be"> — serveur FastAPI (Python 3.11) : rendu markdown (mistune + wikilinks), recherche TF-IDF stemmisée (index inversé en mémoire), watchers watchdog, JWT + Argon2id, webhooks HMAC, export PDF (WeasyPrint).</span>
</li>
<li>
<strong data-i18n="guide105.arch_lbl_realtime">Temps réel & MCP</strong><span data-i18n-html="guide105.arch_rt"> — passerelle WebSocket (collaboration Yjs, notifications SSE/push) et serveur MCP (Streamable HTTP, <code>/mcp</code>) pour les clients externes.</span>
</li>
<li>
<strong data-i18n="guide105.arch_lbl_ai">Couche IA</strong><span data-i18n="guide105.arch_ai"> — assistants d'édition et BooksLM multi-providers (DeepSeek, OpenRouter, Gemini, Mistral…), bibliothèque d'outils (function calling, recherche web, crawl, lecture de documents) et embeddings optionnels pour la recherche sémantique.</span>
</li>
<li>
<strong data-i18n="guide105.arch_lbl_data">Données</strong><span data-i18n-html="guide105.arch_data"> — les vaults Obsidian sur disque (source de vérité), la configuration en JSON (<code>data/</code>), les backups horodatés (<code>.obsigate-backup/</code>), l'audit en JSON lines, les clés API chiffrées dans <code>data/api_keys.json</code>.</span>
</li>
<li>
<strong data-i18n="guide105.arch_lbl_deploy">Déploiement</strong><span data-i18n="guide105.arch_deploy"> — application desktop Tauri (Rust) embarquant le backend Python, conteneur Docker, ou PWA installable dans le navigateur (mode hors-ligne).</span>
</li>
</ul>
<h3 data-i18n="guide105.arch_h3_flux">Flux typique</h3>
<p data-i18n-html="guide105.arch_flux"> Un clic sur un fichier émet <code>GET /api/file/...</code> ; le backend résout le chemin en sécurité, parse le frontmatter, rend le markdown et renvoie le HTML ; le frontend enrichit l'affichage (Mermaid, coloration, wikilinks cliquables). Chaque écriture crée un backup avant application.</p>
</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>
<li>
<strong data-i18n="help.sidebar_user">Compte</strong> <span data-i18n="help.desc_sidebar_user">: Avatar, nom,
rôle et déconnexion en bas de la sidebar</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>
<h3 data-i18n="guide105.h3_push">Notifications web (push)</h3>
<p data-i18n="guide105.push_p1">Autorisez les notifications (bouton 🔔 de l'en-tête) pour être averti des fins de synchronisation hors-ligne et des événements importants. La gestion des abonnements est dans les Configurations.</p>
<p data-i18n="guide105.push_p2">Basée sur la Web Push API (clés VAPID) ; fonctionne sur desktop et PWA mobile, sans service tiers : le serveur émet directement vers les endpoints push des navigateurs.</p>
</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-html="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-html="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>
<h3 data-i18n="guide105.h3_semantic">Recherche sémantique (hybride)</h3>
<p data-i18n="guide105.sem_p1">Activez le bouton « S » de la barre de recherche (ou Alt-S) pour combiner TF-IDF et similarité vectorielle (fusion RRF) : les concepts approchants (« velours » trouve « tissu doux ») remontent mieux.</p>
<p data-i18n="guide105.sem_p2">Le moteur d'embeddings (modèle multilingue) est optionnel : sans lui, un repli par hash conserve une recherche hybride fonctionnelle. Les vecteurs sont recalculés à chaque indexation du vault.</p>
</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-html="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-html="help.desc_two_tag_types">ObsiGate supporte deux types de tags :</p>
<ul>
<li>
<strong>Frontmatter YAML</strong><span data-i18n-html="help.desc_frontmatter">:
<code>tags: [docker, linux]</code> ou
<code>tags: docker, linux</code></span>
</li>
<li>
<strong>Inline</strong><span data-i18n-html="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-html="help.desc_template_intro">
Dans les Configurations, vous pouvez masquer les
tags de template :
</p>
<ul>
<li>
<strong>Patterns wildcards</strong><span data-i18n-html="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-html="help.desc_wikilinks">:
<code>[[lien]]</code> et
<code>[[lien|texte]]</code></span>
</li>
<li>
<strong>Images Obsidian</strong><span data-i18n-html="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>
<h3 data-i18n="guide105.h3_pdf">Export PDF</h3>
<p data-i18n-html="guide105.pdf_p">Le bouton « PDF » d'un document le rend avec le même moteur que la vue (WeasyPrint) : titres, tableaux, listes et code sont conservés. Depuis un lien public, la route <code>/s/{token}/pdf</code> produit le même PDF.</p>
<h3 data-i18n="guide105.h3_exports">Export HTML / ePub / ZIP</h3>
<p data-i18n="guide105.exp_p">Le menu « Exporter » propose trois formats : HTML autonome (fichier unique, images incluses), ePub pour les liseuses et, pour un dossier, un bundle Markdown en ZIP — liens et ressources résolus pendant l'export.</p>
<h3 data-i18n="guide105.h3_dupe">Anti-doublons à l'upload</h3>
<p data-i18n="guide105.dupe_p">L'upload en masse (glisser-déposer un dossier sur la sidebar) compare chaque fichier au contenu existant : un fichier déjà présent est ignoré plutôt que dupliqué avec un suffixe « (1) ». Utile pour restaurer un vault sans créer de doublons.</p>
</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-diagrams">
<h2 data-i18n="guide105.nav_diagrams">📊 Diagrammes</h2>
<p data-i18n-html="guide105.dia_intro">Les blocs <code>```mermaid</code> de vos notes sont rendus en diagrammes interactifs (Mermaid v11, chargé depuis un CDN).</p>
<ul>
<li data-i18n="guide105.dia_zoom">Zoom : boutons + / − dans la barre d'outils du diagramme.</li>
<li data-i18n="guide105.dia_fs">Plein écran : idéal pour les grandes matrices.</li>
<li data-i18n="guide105.dia_copy">Copie : exportez le SVG ou le code source (boutons dédiés).</li>
<li data-i18n="guide105.dia_toggle">Bascule Aperçu / Code pour éditer la source sans quitter la vue.</li>
<li data-i18n="guide105.dia_theme">Thème : le diagramme suit le thème clair/sombre de l'application.</li>
</ul>
<p data-i18n="guide105.dia_types">Types supportés : flowchart, sequence, class, state, ER, gantt, pie, journey, quadrant, radar, mindmap, timeline, C4, xychart, sankey — plus un préprocesseur qui comprend la syntaxe Obsidian.</p>
<p data-i18n-html="guide105.dia_excalidraw_ref">Les dessins à main levée (<code>.excalidraw</code>, <code>.excalidraw.md</code>) sont couverts dans la section 🎨 Excalidraw.</p>
</section>
<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-ai" data-i18n="settings.ai">🤖 IA</a>)
</li>
</ul>
</section>
<!-- 🗺️ Graphe -->
<section class="help-section" id="help-mobile-editor">
<h2 data-i18n="help.mobile_editor_title">📱 Édition mobile</h2>
<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>
<section class="help-section" id="help-library">
<h2 data-i18n="guide105.nav_library">⭐ Bibliothèque</h2>
<h3 data-i18n="guide105.lib_h3_bookmarks">Signets & récents</h3>
<p data-i18n="guide105.lib_bookmarks">Marquez un fichier d'un ★ (bouton Signet de la barre d'actions) : il rejoint la liste des signets du dashboard. Les fichiers récemment ouverts sont listés automatiquement dans l'onglet « Récents » de la sidebar, avec un filtre de recherche dédié.</p>
<h3 data-i18n="guide105.lib_h3_saved">Recherches sauvegardées</h3>
<p data-i18n="guide105.lib_saved">Enregistrez une recherche depuis la page de résultats pour la relancer en un clic depuis la sidebar : chaque recherche sauvegardée conserve ses opérateurs et filtres.</p>
<h3 data-i18n="guide105.lib_h3_backlinks">Backlinks & graphe</h3>
<p data-i18n="guide105.lib_backlinks">Le panneau Backlinks liste toutes les notes qui pointent vers le fichier ouvert. La vue Graphe (bouton 🕸️) affiche les liens entre fichiers : glissez les nœuds, zoomez à la molette, double-cliquez pour ouvrir une note.</p>
<h3 data-i18n="guide105.lib_h3_conflicts">Conflits de synchronisation</h3>
<p data-i18n="guide105.lib_conflicts">Si vous synchronisez le vault avec Syncthing, ObsiGate détecte les fichiers de conflit (copies « sync-conflict ») et propose de les comparer puis résoudre depuis la page dédiée du menu Options.</p>
<h3 data-i18n="guide105.lib_h3_attach">Fichiers joints & médias</h3>
<p data-i18n-html="guide105.lib_attach">Les images <code>![[image.png]]</code>, pièces jointes et médias (audio, vidéo, PDF intégrés) dans les notes sont rendus dans le viewer et indexés pour la recherche ; le bouton « Rescan attachments » de la configuration recrée l'index des pièces jointes.</p>
</section>
<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>
<h3 data-i18n="guide105.h3_panes">Vue multi-panneaux (split view)</h3>
<p data-i18n="guide105.panes_p">Le bouton « Diviser » de la barre d'actions ouvre le document dans un panneau jumeau ; empilez plusieurs panneaux pour comparer deux notes ou lire et éditer en parallèle. Les largeurs se règlent au bord des panneaux et sont mémorisées.</p>
</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_insert">
Le bouton « Ajouter » (au survol d'une réponse) insère la
réponse dans le document ouvert dans l'éditeur (Editer ou
Forge) ; chaque bloc de code propose « Ajouter la section »
pour n'insérer que ce bloc.
</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-offline">
<h2 data-i18n="guide105.nav_offline">📴 Hors-ligne</h2>
<ul>
<li data-i18n="guide105.off_pwa">ObsiGate est une PWA : installez-la (icône d'installation de la barre d'adresse) pour l'ouvrir comme une application. Le service worker met en cache l'interface et vos derniers documents consultés.</li>
<li data-i18n="guide105.off_edit">Hors-ligne, vous pouvez lire les documents en cache et même les éditer : les modifications sont mises en file d'attente dans IndexedDB.</li>
<li data-i18n="guide105.off_sync">Au retour en ligne, la file se rejoue automatiquement (badge de synchronisation dans l'en-tête). Si la version serveur a divergé entre-temps, le fichier est marqué en conflit et la version serveur est préservée en backup.</li>
<li data-i18n="guide105.off_watch">Les modifications externes (Obsidian sur disque) sont détectées par le watcher : la vue se recharge sans perte de position, ou signale « modifié en externe » pendant une édition.</li>
</ul>
</section>
<section class="help-section" id="help-collab">
<h2 data-i18n="guide105.nav_collab">👥 Collaboration</h2>
<ul>
<li data-i18n="guide105.col_intro">Ouvrez un document en mode Édition : plusieurs personnes peuvent travailler simultanément sur le même fichier via un WebSocket Yjs (CRDT). Les modifications fusionnent sans verrou.</li>
<li data-i18n="guide105.col_cursors">Les curseurs et sélections des collaborateurs apparaissent avec une couleur et un nom par personne (awareness).</li>
<li data-i18n="guide105.col_save">La fusion est persistée côté serveur après 2 s d'inactivité ; chaque écriture crée un backup horodaté avant application.</li>
<li data-i18n="guide105.col_perm">Accès limité aux utilisateurs authentifiés disposant de la permission sur la vault.</li>
</ul>
</section>
<section class="help-section" id="help-desktop">
<h2 data-i18n="guide105.nav_desktop">🖥️ Desktop</h2>
<ul>
<li data-i18n="guide105.des_get">L'application desktop ObsiGate (Tauri) embarque le serveur Python : aucune installation de Docker nécessaire. Elle se télécharge sur la page des Releases du dépôt et se met à jour automatiquement (updater signé).</li>
<li data-i18n="guide105.des_wizard">Au premier lancement, un assistant demande le dossier de vos vaults (ou crée un vault de démonstration). Chaque document peut être détaché en fenêtre native séparée.</li>
<li data-i18n="guide105.des_data">Les données desktop restent dans le répertoire applicatif ; les vaults pointent sur vos dossiers existants. Toutes les fonctionnalités web (recherche, IA, partage) sont disponibles.</li>
<li data-i18n="guide105.des_native">Menu système natif, raccourci global optionnel pour afficher/masquer la fenêtre et jumplist des vaults récents.</li>
</ul>
</section>
<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>
<h3 data-i18n="guide105.h3_mfa">MFA : TOTP, WebAuthn, codes de secours</h3>
<p data-i18n="guide105.mfa_p">Activez la double authentification dans Réglages → Profil : applications TOTP (Authy, Aegis…), clés de sécurité et passkeys (WebAuthn, y compris Windows Hello) et 10 codes de secours à conserver hors ligne. Chaque méthode s'active et se désactive indépendamment.</p>
<h3 data-i18n="guide105.h3_admin">Tableau de bord administrateur</h3>
<p data-i18n-html="guide105.admin_p">Le rôle admin ouvre une page dédiée <code>/admin.html</code> (bouton du menu Options) : statut du serveur en direct, utilisateurs, vaults, sessions actives et journal d'audit. Le CRUD utilisateurs est aussi disponible dans les Configurations.</p>
</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-api">
<h2 data-i18n="guide105.nav_api">🔌 API</h2>
<p data-i18n="guide105.api_intro">ObsiGate expose une API REST couvrant toute l'application (vaults, fichiers, recherche, backups, export, IA, partage, admin), documentée en OpenAPI 3.1 :</p>
<ul>
<li data-i18n-html="guide105.api_docs_url"><code>/docs</code> — interface Swagger UI pour essayer les requêtes en direct.</li>
<li data-i18n-html="guide105.api_redoc"><code>/redoc</code> — référence alternative plus compacte.</li>
<li data-i18n-html="guide105.api_landing"><code>/api</code> — page de garde regroupant les endpoints par catégorie.</li>
<li data-i18n-html="guide105.api_schema"><code>/openapi.json</code> — le schéma machine, à importer dans Postman ou Insomnia.</li>
</ul>
<h3 data-i18n="guide105.api_h3_auth">Authentification</h3>
<p data-i18n-html="guide105.api_auth">Connectez-vous via <code>POST /api/auth/login</code> pour obtenir un token Bearer (le même jeton est accepté en cookie HttpOnly, ce qui permet aux clients navigateur d'utiliser <code>credentials: "include"</code>). Toutes les routes <code>/api/*</code> exigent ce jeton sauf mention contraire.</p>
<h3 data-i18n="guide105.api_h3_mcp">Serveur MCP</h3>
<p data-i18n-html="guide105.api_mcp">Les outils de l'assistant IA (lire, lister, chercher, ouvrir, écrire…) sont exposés à tout client MCP (Claude Desktop, Cursor, Cline…) sur <code>https://votre-instance/mcp</code> avec un token d'API. Configuration et exemples : <code>docs/MCP_GUIDE.md</code>.</p>
<h3 data-i18n="guide105.api_h3_autom">Automatisation</h3>
<p data-i18n-html="guide105.api_autom">Pour automatiser depuis l'extérieur : <code>GET /api/search?q=…</code> et <code>GET /api/file/{vault}?path=…</code> permettent d'indexer ou relire vos notes dans un autre outil ; les webhooks sortants (section 🪝) évitent le polling.</p>
</section>
<section class="help-section" id="help-languages">
<h2 data-i18n="guide105.nav_languages">🌍 Multilingue</h2>
<ul>
<li data-i18n="guide105.lng_how">L'interface est intégralement bilingue français / anglais. Réglages → Profil → Langue : le choix est enregistré sur votre compte et vous suit sur tous les appareils.</li>
<li data-i18n="guide105.lng_scope">Tout est traduit : menus, messages, notifications, et le présent guide. Les réponses de l'assistant IA suivent la langue de vos documents.</li>
<li data-i18n="guide105.lng_export">Les boutons Markdown / PDF de ce guide téléchargent la version dans votre langue.</li>
</ul>
</section>
<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" data-i18n="settings.explorer">Explorateur</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>