Files
ObsiGate/frontend/index.html
T
bruno 69e86c239b
CI / lint (push) Successful in 14s
CI / security (push) Successful in 8s
CI / test (push) Failing after 22s
CI / build (push) Has been skipped
fix: CSP add cdn.jsdelivr.net, fix regex syntax, fix manifest PNG->SVG
2026-06-04 23:50:10 -04:00

4410 lines
208 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="fr" data-theme="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>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"
/>
<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
/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/umd/lucide.min.js"></script>
<script 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]"
}
}
</script>
<!-- CodeMirror 6 -->
<script type="module">
import {
EditorView,
keymap,
lineNumbers,
highlightActiveLineGutter,
highlightSpecialChars,
drawSelection,
dropCursor,
rectangularSelection,
crosshairCursor,
highlightActiveLine,
} from "https://esm.sh/@codemirror/[email protected]?external=@codemirror/state";
import { EditorState } from "@codemirror/state";
import {
defaultHighlightStyle,
syntaxHighlighting,
indentOnInput,
bracketMatching,
foldGutter,
foldKeymap,
} from "https://esm.sh/@codemirror/[email protected]?external=@codemirror/state";
import {
defaultKeymap,
history,
historyKeymap,
} 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,
};
</script>
</head>
<body>
<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">ObsiGate</span>
</div>
<p class="login-subtitle">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">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">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>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">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"
placeholder="Rechercher ..."
autocomplete="off"
/>
<div class="search-actions">
<button
class="search-btn tog"
id="search-case-btn"
type="button"
title="Respecter la casse (Alt-C)"
>
Aa
</button>
<button
class="search-btn tog"
id="search-word-btn"
type="button"
title="Mot entier (Alt-W)"
>
wd
</button>
<button
class="search-btn tog"
id="search-regex-btn"
type="button"
title="Expression régulière (Alt-R)"
>
.*
</button>
<span class="search-sep"></span>
<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>
<span class="search-sep"></span>
<span
class="search-count"
id="search-match-count"
>0/0</span
>
<button
class="search-btn icon-only"
id="search-prev-btn"
type="button"
title="Résultat précédent"
>
&#8593;
</button>
<button
class="search-btn icon-only"
id="search-next-btn"
type="button"
title="Résultat suivant"
>
&#8595;
</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"
placeholder="Inclure: **/*.md, notes/**"
/>
<input
type="text"
class="search-filter-input"
id="search-exclude-input"
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>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>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>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">
<div
class="vault-context-indicator"
id="vault-context-indicator"
title="Contexte vault actif"
>
<i
data-lucide="database"
style="width: 14px; height: 14px"
></i>
<span id="vault-context-text">All</span>
</div>
<!-- User menu (visible when logged in) -->
<div class="user-menu" id="user-menu"></div>
<div class="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"
>
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"
>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"
>Administration</span
>
<span class="menu-list-subtitle"
>Gérer les utilisateurs</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"
>Configurations</span
>
<span class="menu-list-subtitle"
>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"
>Guide d'utilisation</span
>
<span class="menu-list-subtitle"
>Ouvrir l'aide</span
>
</span>
</button>
</div>
</div>
</div>
</div>
</header>
<!-- Main -->
<div class="main-body">
<!-- Mobile overlay -->
<div class="sidebar-overlay" id="sidebar-overlay"></div>
<!-- Sidebar -->
<aside
class="sidebar"
id="sidebar"
role="navigation"
aria-label="Navigation des vaults"
>
<!-- Sidebar filter -->
<div class="sidebar-filter">
<div class="sidebar-filter-input-wrapper">
<i
data-lucide="filter"
class="sidebar-filter-icon"
style="width: 14px; height: 14px"
></i>
<input
type="text"
id="sidebar-filter-input"
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>
</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"
>
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="">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>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 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 sauvegardée</span>
</div>
</div>
</aside>
<!-- Sidebar resize handle -->
<div
class="sidebar-resize-handle"
id="sidebar-resize-handle"
></div>
<!-- Content Wrapper (tab bar + content area) -->
<div class="vault-context-bar" id="vault-context-bar">
<span class="vault-context-pill" id="vault-context-pill">📂 Toutes les vaults</span>
</div>
<div class="content-wrapper">
<!-- Tab bar -->
<div class="tab-bar" id="tab-bar" hidden>
<div class="tab-list" id="tab-list"></div>
</div>
<!-- Content -->
<main
class="content-area"
id="content-area"
aria-label="Contenu principal"
>
<div
id="dashboard-home"
class="dashboard-home"
role="region"
aria-label="Tableau de bord"
>
<!-- Dashboard Tabs -->
<div class="dashboard-tabs">
<button
class="dashboard-tab active"
data-tab="stats"
>
<i
data-lucide="bar-chart-3"
style="width: 14px; height: 14px"
></i>
Statistiques
</button>
<button
class="dashboard-tab"
data-tab="bookmarks"
>
<i
data-lucide="bookmark"
style="width: 14px; height: 14px"
></i>
Bookmarks
</button>
<button class="dashboard-tab" data-tab="recent">
<i
data-lucide="clock"
style="width: 14px; height: 14px"
></i>
Récents
</button>
<button class="dashboard-tab" data-tab="shared">
<i
data-lucide="share-2"
style="width: 14px; height: 14px"
></i>
Partagés
</button>
</div>
<!-- Stats Panel -->
<div
id="dashboard-panel-stats"
class="dashboard-panel active"
>
<div
id="dashboard-stats-grid"
class="dashboard-stats-grid"
>
<div class="dashboard-stats-loading">
Chargement...
</div>
</div>
<div
id="dashboard-conflicts-container"
style="margin-top: 16px"
></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>
É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>Aucun fichier récent</span>
<p>
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>Aucun document partagé</span>
<p>
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"
id="right-sidebar"
role="complementary"
aria-label="Table des matières"
>
<div class="right-sidebar-header">
<h2 class="right-sidebar-title">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>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 -->
<div class="editor-modal" id="editor-modal">
<div class="editor-container">
<div class="editor-header">
<div class="editor-title" id="editor-title">Édition</div>
<div class="editor-actions">
<button
class="editor-btn danger"
id="editor-delete"
title="Supprimer"
aria-label="Supprimer"
>
<i
data-lucide="trash-2"
style="width: 16px; height: 16px"
></i>
</button>
<button
class="editor-btn"
id="editor-cancel"
title="Annuler"
aria-label="Annuler"
>
<i
data-lucide="x"
style="width: 16px; height: 16px"
></i>
</button>
<button
class="editor-btn primary"
id="editor-save"
title="Sauvegarder"
aria-label="Sauvegarder"
>
<i
data-lucide="check"
style="width: 16px; height: 16px"
></i>
</button>
</div>
</div>
<div class="editor-body" id="editor-body">
<div id="ai-toolbar-container"></div>
</div>
</div>
</div>
<!-- Configurations Modal -->
<div class="editor-modal" id="config-modal">
<div class="editor-container">
<div class="editor-header">
<div class="editor-title">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" id="config-body">
<div class="config-content">
<!-- Performance Settings - Frontend -->
<section class="config-section">
<h2>Paramètres de recherche</h2>
<p class="config-description">
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>
<!-- Historique récent -->
<section class="config-section">
<h2>
📋 Historique récent
<span class="config-badge-restart"
>Redémarrage non requis</span
>
</h2>
<div class="config-row">
<label
class="config-label"
for="cfg-recent-limit"
>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"
>Entre 5 et 100 fichiers (sauvegarde sur le
serveur)</span
>
</div>
<div class="config-row">
<label
class="config-label"
for="cfg-max-backups"
>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"
>Nombre maximum de versions backup conservees par fichier (1-50)</span
>
</div>
</section>
<!-- Performance Settings - Backend -->
<section class="config-section">
<h2>
Paramètres backend
<span class="config-badge-restart"
>Redémarrage requis</span
>
</h2>
<p class="config-description">
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"
>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"
>Threads dédiés à la recherche (1-8)</span
>
</div>
<div class="config-row">
<label
class="config-label"
for="cfg-max-content"
>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"
>Contenu indexé par fichier (10K-1M).
Réindexation requise.</span
>
</div>
<div class="config-row">
<label
class="config-label"
for="cfg-title-boost"
>Boost titre</label
>
<input
type="number"
id="cfg-title-boost"
class="config-input config-input--num"
min="0"
max="10"
step="0.5"
value="3.0"
/>
<span class="config-hint"
>Multiplicateur de pertinence pour les
correspondances dans le titre</span
>
</div>
<div class="config-row">
<label class="config-label" for="cfg-tag-boost"
>Boost tags</label
>
<input
type="number"
id="cfg-tag-boost"
class="config-input config-input--num"
min="0"
max="10"
step="0.5"
value="2.0"
/>
<span class="config-hint"
>Multiplicateur de pertinence pour les
correspondances dans les tags</span
>
</div>
<div class="config-row">
<label class="config-label" for="cfg-prefix-exp"
>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"
>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"
>
Sauvegarder
</button>
<button
class="config-btn-secondary"
id="cfg-reindex"
>
Forcer réindexation
</button>
<button
class="config-btn-secondary"
id="cfg-reset-defaults"
>
Réinitialiser
</button>
</div>
</section>
<!-- Tag Filtering (existing) -->
<section class="config-section">
<h2>Filtrage de tags</h2>
<p class="config-description">
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"
>
Ajouter
</button>
</div>
<div
class="config-regex-preview"
id="config-regex-preview"
style="display: none"
>
<small
>Regex : <code id="config-regex-code"></code
></small>
</div>
</section>
<!-- Watcher / Synchronisation -->
<section class="config-section">
<h2>Synchronisation automatique</h2>
<p class="config-description">
Surveillance des fichiers en temps réel via
watchdog. Les modifications sont détectées
automatiquement et l'index est mis à jour sans
redémarrage.
</p>
<div class="config-row">
<label
class="config-label"
for="cfg-watcher-enabled"
>Activer la surveillance</label
>
<label class="config-toggle">
<input
type="checkbox"
id="cfg-watcher-enabled"
checked
/>
<span class="config-toggle-slider"></span>
</label>
<span class="config-hint"
>Activer/désactiver la surveillance
automatique des fichiers</span
>
</div>
<div class="config-row">
<label
class="config-label"
for="cfg-watcher-polling"
>Mode polling (fallback)</label
>
<label class="config-toggle">
<input
type="checkbox"
id="cfg-watcher-polling"
/>
<span class="config-toggle-slider"></span>
</label>
<span class="config-hint"
>Forcer le mode polling au lieu de inotify
natif (utile si le mode natif ne fonctionne
pas)</span
>
</div>
<div class="config-row">
<label
class="config-label"
for="cfg-watcher-interval"
>Intervalle polling (s)</label
>
<input
type="number"
id="cfg-watcher-interval"
class="config-input config-input--num"
min="1"
max="30"
step="1"
value="5"
/>
<span class="config-hint"
>Intervalle de scrutation en mode polling
(1-30 secondes)</span
>
</div>
<div class="config-row">
<label
class="config-label"
for="cfg-watcher-debounce"
>Debounce (s)</label
>
<input
type="number"
id="cfg-watcher-debounce"
class="config-input config-input--num"
min="0.5"
max="10"
step="0.5"
value="2"
/>
<span class="config-hint"
>Délai avant traitement des changements
groupés (0.5-10 secondes)</span
>
</div>
</section>
<!-- Hidden Files/Folders Configuration -->
<section class="config-section">
<h2>🗂️ 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>Note :</strong> Tous les fichiers sont
toujours indexés et cherchables. Ce paramètre
contrôle uniquement leur visibilité dans
l'interface.
</p>
<div id="hidden-files-vault-list">
<!-- Vault-specific settings will be injected here -->
</div>
<div
class="config-actions-row"
style="margin-top: 16px"
>
<button
class="config-btn-save"
id="cfg-save-hidden-files"
>
💾 Sauvegarder
</button>
</div>
</section>
<!-- Diagnostics -->
<section class="config-section">
<h2>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">
Chargement...
</div>
</div>
<button
class="config-btn-secondary"
id="cfg-refresh-diag"
style="margin-top: 8px"
>
Rafraîchir
</button>
</section>
<!-- À propos -->
<section class="config-section">
<h2>📦 À propos</h2>
<div id="config-about" class="config-diagnostics">
<div class="config-diag-loading">
Chargement...
</div>
</div>
</section>
<!-- Webhooks -->
<section class="config-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"
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"
>
Ajouter
</button>
</div>
</section>
<!-- Partages publics -->
<section class="config-section">
<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>
</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">
Guide d'utilisation ObsiGate
</div>
<div class="editor-actions">
<button
class="editor-btn"
id="help-close"
title="Fermer l'aide"
aria-label="Fermer l'aide"
>
<i
data-lucide="x"
style="width: 16px; height: 16px"
></i>
</button>
</div>
</div>
<div class="editor-body help-body" id="help-body">
<nav class="help-nav" id="help-nav">
<div class="help-nav-header">
<div class="help-nav-title">Sommaire</div>
</div>
<div class="help-nav-search-wrap">
<input
type="text"
class="help-nav-search"
id="help-nav-search"
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"
>📘 Introduction</a
>
</li>
<li>
<a href="#help-interface" class="help-nav-link"
>🧭 Interface</a
>
</li>
<li>
<a href="#help-navigation" class="help-nav-link"
>🗺️ Navigation</a
>
</li>
<li>
<a href="#help-tabs" class="help-nav-link"
>📑 Onglets</a
>
</li>
<li>
<a href="#help-recherche" class="help-nav-link"
>🔍 Recherche</a
>
</li>
<li>
<a href="#help-tags" class="help-nav-link"
>🏷️ Tags</a
>
</li>
<li>
<a href="#help-fichiers" class="help-nav-link"
>📁 Fichiers</a
>
</li>
<li>
<a href="#help-edition" class="help-nav-link"
>✏️ Édition</a
>
</li>
<li>
<a href="#help-graphe" class="help-nav-link"
>🗺️ Graphe</a
>
</li>
<li>
<a href="#help-ia" class="help-nav-link"
>🤖 IA Éditeur</a
>
</li>
<li>
<a href="#help-palette" class="help-nav-link"
>⌨️ Palette</a
>
</li>
<li>
<a href="#help-raccourcis" class="help-nav-link"
>⌨️ Raccourcis</a
>
</li>
<li>
<a href="#help-partage" class="help-nav-link"
>🔗 Partage</a
>
</li>
<li>
<a href="#help-webhooks" class="help-nav-link"
>🪝 Webhooks</a
>
</li>
<li>
<a
href="#help-sauvegardes"
class="help-nav-link"
>💾 Sauvegardes</a
>
</li>
<li>
<a href="#help-securite" class="help-nav-link"
>🛡️ Sécurité</a
>
</li>
<li>
<a
href="#help-personnalisation"
class="help-nav-link"
>🎨 Personnalisation</a
>
</li>
<li>
<a href="#help-astuces" class="help-nav-link"
>💡 Astuces</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">Guide officiel</div>
<h1>📘 Bienvenue dans ObsiGate</h1>
<p class="help-lead">
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"
>⚡ Navigation rapide</span
>
<span class="help-badge"
>🗂️ Multi-vault</span
>
<span class="help-badge"
>🏷️ Tags intelligents</span
>
<span class="help-badge"
>📝 Lecture et édition</span
>
<span class="help-badge"
>🔍 Recherche TF-IDF</span
>
</div>
</div>
<div class="help-hero-visual" aria-hidden="true">
<div class="help-stat-card">
<span class="help-stat-label"
>Expérience</span
>
<strong>Simple</strong>
</div>
<div class="help-stat-card">
<span class="help-stat-label"
>Recherche</span
>
<strong>Instantanée</strong>
</div>
<div class="help-stat-card">
<span class="help-stat-label">Accès</span>
<strong>Multi-vault</strong>
</div>
</div>
</section>
<section class="help-section" id="help-interface">
<h2>🧭 Interface utilisateur</h2>
<h3>En-tête</h3>
<p>
La barre supérieure contient les éléments
essentiels :
</p>
<ul>
<li>
<strong>Bouton menu hamburger</strong>
(mobile) : Affiche/masque la sidebar
</li>
<li>
<strong>Bouton toggle sidebar</strong>
(desktop) : Masque la sidebar pour plus
d'espace
</li>
<li>
<strong>Logo ObsiGate</strong> : Retour à
l'accueil
</li>
<li>
<strong>Barre de recherche</strong> :
Recherche globale avec autocomplétion
</li>
<li>
<strong>Indicateur de vault</strong> :
Affiche le contexte actif (All, vault1,
etc.)
</li>
<li>
<strong>Menu Options</strong> : Accès aux
paramètres, thème, aide
</li>
</ul>
<h3>Sidebar (barre latérale)</h3>
<p>La sidebar est divisée en deux onglets :</p>
<ul>
<li>
<strong>Vaults</strong> : Arborescence des
fichiers et dossiers
</li>
<li>
<strong>Tags</strong> : Nuage de tags
cliquables
</li>
</ul>
<p>Fonctionnalités de la sidebar :</p>
<ul>
<li>
<strong>Filtrage</strong> : Barre de
recherche pour filtrer fichiers/tags
</li>
<li>
<strong>Sélection rapide</strong> : Dropdown
pour changer de vault
</li>
<li>
<strong>Redimensionnement</strong> : Glissez
le bord droit pour ajuster la largeur
</li>
<li>
<strong>Masquage</strong> : Cliquez sur
l'icône sidebar dans le header
</li>
</ul>
<h3>Zone de contenu</h3>
<p>La zone centrale affiche :</p>
<ul>
<li>
<strong>Breadcrumb</strong> : Chemin du
fichier (cliquable pour repositionner la
sidebar)
</li>
<li>
<strong>Titre et tags</strong> : Métadonnées
du fichier
</li>
<li>
<strong>Boutons d'action</strong> : Copier,
Source, Télécharger, Éditer, Ouvrir dans
nouvelle fenêtre
</li>
<li>
<strong>Contenu rendu</strong> : Markdown
avec wikilinks et images
</li>
</ul>
</section>
<section class="help-section" id="help-navigation">
<h2>🗺️ Navigation</h2>
<h3>Arborescence des vaults</h3>
<p>
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>Développer/Réduire</strong> :
Cliquez sur un dossier
</li>
<li>
<strong>Ouvrir un fichier</strong> : Cliquez
sur le nom du fichier
</li>
<li>
<strong>Icônes</strong> : Chaque type de
fichier a son icône (markdown, code, etc.)
</li>
</ul>
<h3>Filtrage de la sidebar</h3>
<p>
Utilisez la barre de filtrage en haut de la
sidebar pour rechercher rapidement :
</p>
<ul>
<li>
<strong>Recherche instantanée</strong> :
Tapez pour filtrer fichiers et dossiers
</li>
<li>
<strong>Case-sensitive</strong> : Bouton
"Aa" pour respecter la casse
</li>
<li>
<strong>Effacer</strong> : Bouton "×" pour
réinitialiser le filtre
</li>
<li>
<strong>Résultats groupés</strong> :
Affichage par vault avec chemins complets
</li>
</ul>
<h3>Breadcrumb et repositionnement</h3>
<p>
Le chemin affiché au-dessus du contenu est
interactif :
</p>
<ul>
<li>
<strong>Cliquez sur le vault</strong> :
Focus sur le vault dans la sidebar
</li>
<li>
<strong>Cliquez sur un segment</strong> :
Navigation vers ce dossier
</li>
<li>
<strong>Auto-scroll</strong> : La sidebar se
positionne automatiquement
</li>
</ul>
<h3>Sélection de vault</h3>
<p>
Changez de contexte pour filtrer l'affichage :
</p>
<ul>
<li>
<strong>Menu header</strong> : Dropdown
"Vault" dans le menu Options
</li>
<li>
<strong>Sidebar dropdown</strong> :
Sélection rapide en haut de l'onglet Vaults
</li>
<li>
<strong>Tous les vaults</strong> : Vue
globale de tous vos contenus
</li>
<li>
<strong>Vault spécifique</strong> : Focus
sur un seul vault
</li>
</ul>
</section>
<section class="help-section" id="help-tabs">
<h2>📑 Gestion des onglets</h2>
<p>
ObsiGate supporte l'ouverture de plusieurs
fichiers simultanément dans des onglets.
</p>
<h3>Ouverture d'onglets</h3>
<ul>
<li>
<strong>Simple clic</strong> : Ouvre le
fichier dans un onglet temporaire (aperçu,
italique)
</li>
<li>
<strong>Double clic</strong> : Ouvre le
fichier dans un onglet persistant
</li>
<li>
<strong>Navigation récente</strong> :
L'onglet "Récent" dans la sidebar liste vos
derniers fichiers
</li>
</ul>
<h3>Navigation entre onglets</h3>
<ul>
<li>
<strong>Clic sur un onglet</strong> : Active
l'onglet
</li>
<li>
<strong>Ctrl+Tab</strong> : Onglet suivant
</li>
<li>
<strong>Ctrl+Shift+Tab</strong> : Onglet
précédent
</li>
<li>
<strong>Ctrl+W</strong> : Fermer l'onglet
actif
</li>
<li>
<strong>Bouton ✕</strong> : Ferme l'onglet
(sauf s'il est seul)
</li>
</ul>
<h3>Fonctionnalités</h3>
<ul>
<li>
<strong>Barre d'onglets</strong> : Affichage
horizontal avec le nom du fichier et son
icône
</li>
<li>
<strong>Onglet actif</strong> : Surligné en
couleur d'accentuation
</li>
<li>
<strong>Prévisualisation</strong> : Un
onglet temporaire (italique) est remplacé
par le prochain clic
</li>
<li>
<strong>Onglets persistants</strong> :
Restent ouverts jusqu'à fermeture manuelle
</li>
</ul>
</section>
<section class="help-section" id="help-recherche">
<h2>🔍 Recherche</h2>
<h3>Recherche simple</h3>
<p>
Tapez dans la barre de recherche en haut pour
lancer une recherche fulltext :
</p>
<ul>
<li>
<strong>Recherche dans tout</strong> :
Titre, contenu, tags, chemins
</li>
<li>
<strong>Scoring intelligent</strong> :
Résultats classés par pertinence TF-IDF
</li>
<li>
<strong>Snippets surlignés</strong> :
Extraits avec termes en surbrillance
</li>
<li>
<strong>Pagination</strong> : Navigation par
pages de 50 résultats
</li>
</ul>
<h3>Opérateurs avancés</h3>
<p>
Utilisez des opérateurs pour affiner votre
recherche :
</p>
<ul>
<li>
<code>tag:docker</code> ou
<code>#docker</code> : Filtrer par tag
</li>
<li>
<code>vault:IT</code> : Rechercher dans un
vault spécifique
</li>
<li>
<code>title:kubernetes</code> : Chercher
dans les titres uniquement
</li>
<li>
<code>path:recettes/soupes</code> : Filtrer
par chemin
</li>
<li>
<code>ext:md</code> : Filtrer par type de
fichier
</li>
<li>
<code>"phrase exacte"</code> : Recherche de
phrase entre guillemets
</li>
</ul>
<p>
<strong>Exemples</strong> :
<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>
<p>
<strong>Exemple combiné</strong> :
<code
>tag:linux vault:IT ext:md serveur web</code
>
recherche "serveur web" dans les fichiers
Markdown du vault IT avec le tag linux.
</p>
<h3>Autocomplétion</h3>
<p>
L'autocomplétion vous aide à trouver rapidement
:
</p>
<ul>
<li>
<strong>Historique</strong> : Les 50
dernières recherches (localStorage)
</li>
<li>
<strong>Fichiers</strong> : Suggestions de
titres de fichiers
</li>
<li>
<strong>Tags</strong> : Suggestions de tags
avec compteurs
</li>
<li>
<strong>Navigation clavier</strong> : ↑/↓
pour naviguer, Enter pour sélectionner
</li>
</ul>
<h3>Chips de filtres actifs</h3>
<p>
Les opérateurs utilisés s'affichent sous forme
de chips colorés :
</p>
<ul>
<li>
<strong>Visualisation</strong> : Voir d'un
coup d'œil les filtres actifs
</li>
<li>
<strong>Suppression</strong> : Cliquez sur
"×" pour retirer un filtre
</li>
<li>
<strong>Couleurs</strong> : Chaque type
d'opérateur a sa couleur
</li>
</ul>
<h3>Facettes et tri</h3>
<p>
Les résultats incluent des facettes pour affiner
:
</p>
<ul>
<li>
<strong>Facettes tags</strong> : Compteurs
par tag dans les résultats
</li>
<li>
<strong>Facettes vaults</strong> : Compteurs
par vault
</li>
<li>
<strong>Tri par pertinence</strong> : Score
TF-IDF (défaut)
</li>
<li>
<strong>Tri par date</strong> : Dernière
modification
</li>
</ul>
</section>
<section class="help-section" id="help-tags">
<h2>🏷️ Tags</h2>
<h3>Tag cloud</h3>
<p>
L'onglet Tags de la sidebar affiche un nuage de
tags :
</p>
<ul>
<li>
<strong>Tous les tags</strong> : Extraits
des frontmatters YAML et inline (#tag)
</li>
<li>
<strong>Cliquable</strong> : Cliquez pour
filtrer les résultats
</li>
<li>
<strong>Filtrage</strong> : Utilisez la
barre de filtrage pour chercher un tag
</li>
<li>
<strong>Contexte vault</strong> : Les tags
s'adaptent au vault sélectionné
</li>
</ul>
<h3>Tags inline vs frontmatter</h3>
<p>ObsiGate supporte deux types de tags :</p>
<ul>
<li>
<strong>Frontmatter YAML</strong> :
<code>tags: [docker, linux]</code> ou
<code>tags: docker, linux</code>
</li>
<li>
<strong>Inline</strong> :
<code>#docker</code> dans le contenu
markdown
</li>
<li>
<strong>Fusion automatique</strong> : Les
deux types sont combinés
</li>
</ul>
<h3>Filtrage de tags template</h3>
<p>
Dans les Configurations, vous pouvez masquer les
tags de template :
</p>
<ul>
<li>
<strong>Patterns wildcards</strong> : Ex:
<code>#&lt;% ... %&gt;</code> ou
<code>#{{ ... }}</code>
</li>
<li>
<strong>Regex générée</strong> : Aperçu de
la regex appliquée
</li>
<li>
<strong>Ajout/Suppression</strong> : Gérez
vos patterns de filtrage
</li>
</ul>
</section>
<section class="help-section" id="help-fichiers">
<h2>📄 Fichiers</h2>
<h3>Visualisation</h3>
<p>Les fichiers markdown sont rendus avec :</p>
<ul>
<li>
<strong>Wikilinks cliquables</strong> :
<code>[[lien]]</code> et
<code>[[lien|texte]]</code>
</li>
<li>
<strong>Images Obsidian</strong> : Support
de <code>![[image.png]]</code>
</li>
<li>
<strong>Syntax highlighting</strong> :
Coloration des blocs de code
</li>
<li>
<strong>Tables, listes, quotes</strong> :
Rendu complet du markdown
</li>
</ul>
<h3>Actions sur fichiers</h3>
<p>Boutons disponibles au-dessus du contenu :</p>
<ul>
<li>
<strong>Copier</strong> : Copie le contenu
source dans le presse-papiers
</li>
<li>
<strong>Source</strong> : Affiche/masque le
markdown brut
</li>
<li>
<strong>Télécharger</strong> : Télécharge le
fichier
</li>
<li>
<strong>Éditer</strong> : Ouvre l'éditeur
CodeMirror
</li>
<li>
<strong
>Ouvrir dans nouvelle fenêtre</strong
>
: Ouvre le fichier dans un nouvel onglet
</li>
</ul>
<h3>Édition avec CodeMirror 6</h3>
<p>L'éditeur intégré offre :</p>
<ul>
<li>
<strong>Coloration syntaxique</strong> :
Support de nombreux langages
</li>
<li>
<strong>Numéros de ligne</strong> :
Navigation facile
</li>
<li>
<strong>Autocomplétion</strong> :
Suggestions contextuelles
</li>
<li>
<strong>Recherche</strong> : Ctrl+F pour
chercher dans le fichier
</li>
<li>
<strong>Sauvegarde</strong> : Bouton ✓ pour
enregistrer
</li>
<li>
<strong>Suppression</strong> : Bouton
corbeille pour supprimer
</li>
</ul>
<h3>Types de fichiers supportés</h3>
<p>ObsiGate indexe et affiche :</p>
<ul>
<li>
<strong>Markdown</strong> : .md (rendu
complet)
</li>
<li>
<strong>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>Autres</strong> : .txt, .log, .sql,
.csv
</li>
</ul>
</section>
<!-- ✏️ Édition -->
<section class="help-section" id="help-edition">
<h2>✏️ Édition avancée</h2>
<h3>Éditeur CodeMirror 6</h3>
<p>
L'éditeur intégré offre une expérience complète
d'édition :
</p>
<ul>
<li>
<strong>Coloration syntaxique</strong> :
Python, JS, HTML, CSS, Java, C++, Rust, Go
et bien d'autres
</li>
<li>
<strong>Numéros de ligne</strong> :
Navigation facilitée dans les fichiers longs
</li>
<li>
<strong>Find in page</strong> : Ctrl+F pour
chercher dans le fichier (casse, regex, mot
entier)
</li>
<li>
<strong>Sauvegarde automatique</strong> :
Auto-save silencieux après 2s d'inactivité,
flash vert de confirmation
</li>
<li>
<strong>Bouton ✓</strong> : Sauvegarde
manuelle
</li>
<li>
<strong>Bouton 🗑️</strong> : Suppression du
fichier
</li>
<li>
<strong>Barre d'outils AI</strong> :
Complétion, réécriture, traduction (voir
section <a href="#help-ia">IA</a>)
</li>
</ul>
</section>
<!-- 🗺️ Graphe -->
<section class="help-section" id="help-graphe">
<h2>🗺️ Vue Graphe</h2>
<p>
La vue graphe offre une visualisation
interactive des connexions entre vos notes via
les wikilinks.
</p>
<h3>Ouverture</h3>
<ul>
<li>
<strong>Menu contextuel</strong> : Clic
droit sur un vault ou dossier → Vue
Graphique
</li>
<li>
<strong>Bouton dédié</strong> : Dans la
barre d'outils (le cas échéant)
</li>
</ul>
<h3>Interaction</h3>
<ul>
<li>
<strong>Zoom/Pan</strong> : Molette pour
zoomer, cliquer-glisser pour se déplacer
</li>
<li>
<strong>Slider de profondeur</strong> :
Contrôle la profondeur d'exploration (0-3)
</li>
<li>
<strong>Filtre par tag</strong> : N'affiche
que les nœuds avec un tag spécifique
</li>
<li>
<strong>Filtre par type</strong> :
Inclut/exclut fichiers, dossiers, markdown,
autres
</li>
<li>
<strong>Recherche visuelle</strong> : Barre
de recherche pour surligner des nœuds
</li>
<li>
<strong>Mode focus</strong> : Centre la vue
sur un nœud spécifique
</li>
<li>
<strong>Navigation ← → ↑</strong> :
Historique de navigation entre les nœuds
</li>
</ul>
<h3>Affichage</h3>
<ul>
<li>
<strong>Nœuds</strong> : Fichiers (carrés)
et dossiers (cercles) avec icônes
</li>
<li>
<strong>Arêtes</strong> : Wikilinks sortants
(bleu) et backlinks entrants (vert)
</li>
<li>
<strong>Panneau d'info</strong> :
Métadonnées du nœud (tags, liens, chemin)
</li>
<li>
<strong>Ctrl+survol</strong> : Aperçu du
contenu de la note sans naviguer
</li>
<li>
<strong>Plein écran</strong> : Bouton pour
agrandir la vue
</li>
<li>
<strong>Export PNG</strong> : Sauvegardez le
graphe en image
</li>
</ul>
<h3>Performance</h3>
<p>
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>🎨 Personnalisation</h2>
<h3>Thème clair/sombre</h3>
<p>Basculez entre les thèmes :</p>
<ul>
<li>
<strong>Menu Options</strong> : Cliquez sur
"Thème" dans le menu
</li>
<li>
<strong>Raccourci</strong> :
<code>Ctrl+T</code>
</li>
<li>
<strong>Persistance</strong> : Votre choix
est sauvegardé en localStorage
</li>
<li>
<strong>Highlight.js</strong> : Les blocs de
code s'adaptent au thème
</li>
</ul>
<h3>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>Glisser-déposer</strong> : Cliquez
et glissez pour ajuster
</li>
<li>
<strong>Limites</strong> : Min 200px, Max
500px
</li>
<li>
<strong>Persistance</strong> : La largeur
est sauvegardée
</li>
</ul>
<h3>Configuration avancée</h3>
<p>
Le modal Configurations (menu Options) permet de
:
</p>
<ul>
<li>
<strong>Paramètres de recherche</strong> :
Délai debounce, résultats par page, timeout
</li>
<li>
<strong>Paramètres backend</strong> :
Workers, boosts TF-IDF, expansions préfixe
</li>
<li>
<strong>Filtrage de tags</strong> : Patterns
pour masquer les tags template
</li>
<li>
<strong>Diagnostics</strong> : Stats index,
mémoire, moteur de recherche
</li>
<li>
<strong>Réindexation</strong> : Forcer un
rescan des vaults
</li>
</ul>
</section>
<!-- ⌨️ Palette de commandes -->
<section class="help-section" id="help-palette">
<h2>⌨️ Palette de commandes</h2>
<p>
La palette de commandes permet d'accéder
rapidement aux fichiers et aux actions sans
utiliser la souris.
</p>
<h3>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>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>Mode commandes</h3>
<p>
Tapez <code>&gt;</code> (ou Ctrl+Alt+Espace)
pour voir la liste des actions disponibles :
</p>
<ul>
<li>
🏠 <strong>Accueil</strong> : Retour à
l'accueil
</li>
<li>
✏️ <strong>É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>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>Créer répertoire</strong> : Crée
un nouveau dossier
</li>
<li>
🗑️ <strong>Supprimer répertoire</strong> :
Supprime un dossier et son contenu
</li>
<li>
🌓 <strong>Changer le thème</strong> :
Bascule clair/sombre
</li>
<li>
🔄 <strong>Réindexer</strong> : Force la
réindexation complète
</li>
<li>
❓ <strong>Aide</strong> : Ouvre ce guide
</li>
<li>
⚙️ <strong>Configuration</strong> : Ouvre
les paramètres
</li>
</ul>
<h3>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>⌨️ Raccourcis clavier</h2>
<h3>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>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>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>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>É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>🤖 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>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>
Ouvrez un fichier en mode Édition (bouton ✏️
Éditer)
</li>
<li>
La barre d'outils AI apparaît sous le titre
de l'éditeur
</li>
</ol>
<h3>Barre d'outils AI</h3>
<p>
La barre d'outils contient les boutons suivants
:
</p>
<ul>
<li>
<strong>✦ AI</strong> — Complétion inline
(<kbd>Ctrl+J</kbd>). Complète
automatiquement le texte là où se trouve le
curseur.
</li>
<li>
<strong>Éditer ▾</strong> — Menu de
modification du texte sélectionné :
<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> — Traduit le
texte sélectionné vers une autre langue :
<ul>
<li>
🇬🇧 English · 🇨🇳 Chinese · 🇯🇵
Japanese · 🇩🇪 German · 🇫🇷 French ·
🇪🇸 Spanish
</li>
</ul>
</li>
<li>
<strong>Générer ▾</strong> — Génère du
contenu à partir de la sélection :
<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>💬 Réécrire</strong> — Réécrit le
texte selon vos instructions personnalisées.
Une boîte de dialogue vous demande votre
instruction.
</li>
<li>
<strong>🧰 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>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>AI: texte traité ✓</strong> confirme
le succès
</li>
<li>
En cas d'erreur de clé API :
<strong>AI: clé API invalide</strong>
</li>
</ul>
<h3>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>Providers supportés</h3>
<table>
<tr>
<th>Provider</th>
<th>Variable .env</th>
<th>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>
</section>
<!-- 🔗 Partage -->
<section class="help-section" id="help-partage">
<h2>🔗 Partage et Webhooks</h2>
<h3>Publication publique</h3>
<p>
ObsiGate permet de partager des documents via un
lien public temporaire.
</p>
<ul>
<li>
<strong>Bouton Partager</strong> : Dans les
actions du fichier, génère un lien unique
</li>
<li>
<strong>Token unique</strong> : 64
caractères hexadécimaux, impossible à
deviner
</li>
<li>
<strong>Expiration configurable</strong> :
Durée de vie en heures
</li>
<li>
<strong>Lecture seule</strong> : La vue
publique n'affiche que le contenu sans
édition
</li>
<li>
<strong>Gestion des liens</strong> : Liste
et révocation dans les Configurations
</li>
<li>
<strong>Export PDF</strong> : Téléchargement
du document partagé en PDF via
<code>/s/{token}/pdf</code>
</li>
</ul>
</section>
<!-- 🪝 Webhooks -->
<section class="help-section" id="help-webhooks">
<h2>🪝 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>Cas d'usage</h3>
<ul>
<li>
<strong>Automatisation CI/CD</strong> :
Déclencher un build quand une note de
procédure est mise à jour
</li>
<li>
<strong>Synchronisation</strong> : Copier
les nouveaux fichiers vers un autre système
(SharePoint, Google Drive)
</li>
<li>
<strong>Notifications</strong> : Alerter un
canal Slack/Discord quand un document est
créé
</li>
<li>
<strong>Audit externe</strong> : Logger
toutes les modifications dans une base de
données externe
</li>
<li>
<strong>Déploiement</strong> : Publier un
article de blog quand un fichier est marqué
<code>publish: true</code>
</li>
</ul>
<h3>É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);
"
>
Événement
</th>
<th
style="
padding: 8px 12px;
text-align: left;
border-bottom: 1px solid
var(--border);
"
>
Déclencheur
</th>
<th
style="
padding: 8px 12px;
text-align: left;
border-bottom: 1px solid
var(--border);
"
>
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);
"
>
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);
"
>
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);
"
>
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>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>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>Configuration</h3>
<p>
Les webhooks se gèrent dans le modal
<strong>Configurations</strong> (⚙️ menu
Options), section Webhooks. Réservé aux
administrateurs.
</p>
<ol>
<li>
Allez dans ⚙️ Configurations → onglet
Webhooks
</li>
<li>
Remplissez :
<strong>Nom</strong> (descriptif),
<strong>URL</strong> (endpoint cible),
<strong>Secret</strong> (optionnel)
</li>
<li>Cochez les événements à écouter</li>
<li>Cliquez <strong>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>Exemples concrets</h3>
<h4>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>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>Sélectionnez les événements souhaités</li>
<li>
Le workflow se déclenche automatiquement à
chaque événement
</li>
</ol>
<h4>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>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>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>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>Pas de retry automatique en cas d'échec</li>
<li>
Pas de filtrage par vault — un webhook
écoute toutes les vaults
</li>
</ul>
</section>
<section class="help-section" id="help-sauvegardes">
<h2>💾 Sauvegardes et Audits</h2>
<h3>Backup automatique</h3>
<p>
ObsiGate crée automatiquement des sauvegardes
avant chaque modification destructive.
</p>
<ul>
<li>
<strong>Avant écriture</strong> : Sauvegarde
du contenu original dans
<code>.obsigate-backup/</code>
</li>
<li>
<strong>Timestamp</strong> : Chaque backup
est horodaté
</li>
<li>
<strong>Répertoire dédié</strong> :
<code>.obsigate-backup/</code> dans le
répertoire de la vault
</li>
</ul>
<h3>Audit log</h3>
<p>
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>Événements tracés</strong> :
Écritures, suppressions, changements de
configuration
</li>
<li>
<strong>Rotation</strong> : Rotation
automatique à 10 Mo
</li>
</ul>
</section>
<!-- 🛡️ Sécurité -->
<section class="help-section" id="help-securite">
<h2>🛡️ Sécurité</h2>
<h3>Authentification</h3>
<ul>
<li>
<strong>JWT + Argon2id</strong> : Tokens JWT
avec mots de passe hachés Argon2id
</li>
<li>
<strong>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>Rate limiting</strong> : 10
tentatives / 15 min par IP + verrouillage
compte
</li>
<li>
<strong>Compte admin</strong> : Créé
automatiquement au premier démarrage
</li>
</ul>
<h3>Protection</h3>
<ul>
<li>
<strong>Secret redactor</strong> : Masque
JWT, clés API, tokens GitHub dans les
aperçus
</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>Atomic writes</strong> : Écriture
via fichier temporaire + renommage atomique
</li>
<li>
<strong>Non-root Docker</strong> : Conteneur
tourne avec UID 1000
</li>
</ul>
</section>
<section class="help-section" id="help-astuces">
<h2>💡 Astuces et bonnes pratiques</h2>
<h3>Workflow recommandé</h3>
<ol>
<li>
<strong>Sélectionnez un contexte</strong> :
Choisissez un vault pour réduire le bruit
</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> :
Combinez opérateurs et texte libre
</li>
<li>
<strong>Profitez de l'historique</strong> :
Réutilisez vos recherches fréquentes
</li>
</ol>
<h3>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>Favorisez les opérateurs</strong> :
<code>tag:</code> et
<code>vault:</code> accélèrent la recherche
</li>
<li>
<strong>Breadcrumb cliquable</strong> :
Repositionnez rapidement la sidebar
</li>
</ul>
<h3>Trucs et astuces</h3>
<ul>
<li>
<strong>Navigation visuelle</strong> : Le
breadcrumb repositionne automatiquement la
sidebar
</li>
<li>
<strong>Gestion de l'espace</strong> :
Réduisez les sections Vaults/Tags selon vos
besoins
</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>Wikilinks</strong> : Naviguez entre
notes comme dans Obsidian
</li>
<li>
<strong>Images</strong> : Support complet
des syntaxes Obsidian
</li>
</ul>
<h3>Cas d'usage avancés</h3>
<ul>
<li>
<strong>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>Édition rapide</strong> : Ouvrez
l'éditeur, modifiez, sauvegardez
</li>
<li>
<strong>Export</strong> : Téléchargez des
fichiers pour backup ou partage
</li>
<li>
<strong>Multi-fenêtres</strong> : Ouvrez
plusieurs notes dans des onglets séparés
</li>
</ul>
</section>
<div class="help-footer">
<p>
<strong>ObsiGate v1.7.0</strong> - Porte
d'entrée web pour vos vaults Obsidian
</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"
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 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">Explorer</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">Recherche</span>
</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">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">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>
</body>
</html>