refactor: remove hidden files indexing configuration and convert to UI-only display preference, eliminating includeHidden and hiddenWhitelist from vault config and indexing logic while adding hideHiddenFiles client-side filtering

This commit is contained in:
Bruno Charest 2026-03-26 19:53:40 -04:00
parent 34f4e41419
commit 2b69c49ed1
7 changed files with 98 additions and 340 deletions

View File

@ -4,8 +4,6 @@ from pathlib import Path
from typing import Dict, List, Optional, Set from typing import Dict, List, Optional, Set
import threading import threading
from backend.utils import should_include_path
logger = logging.getLogger("obsigate.attachment_indexer") logger = logging.getLogger("obsigate.attachment_indexer")
# Image file extensions to index # Image file extensions to index
@ -40,12 +38,12 @@ def _scan_vault_attachments(vault_name: str, vault_path: str, vault_cfg: dict =
"""Synchronously scan a vault directory for image attachments. """Synchronously scan a vault directory for image attachments.
Walks the vault tree and builds a filename -> absolute path mapping Walks the vault tree and builds a filename -> absolute path mapping
for all image files. for all image files. All files are scanned, including hidden files (starting with '.').
Args: Args:
vault_name: Display name of the vault. vault_name: Display name of the vault.
vault_path: Absolute filesystem path to the vault root. vault_path: Absolute filesystem path to the vault root.
vault_cfg: Optional vault configuration dict with hidden files settings. vault_cfg: Optional vault configuration dict (unused for indexing, kept for compatibility).
Returns: Returns:
Dict mapping lowercase filenames to lists of absolute paths. Dict mapping lowercase filenames to lists of absolute paths.
@ -53,10 +51,6 @@ def _scan_vault_attachments(vault_name: str, vault_path: str, vault_cfg: dict =
vault_root = Path(vault_path) vault_root = Path(vault_path)
index: Dict[str, List[Path]] = {} index: Dict[str, List[Path]] = {}
# Default config if not provided
if vault_cfg is None:
vault_cfg = {"includeHidden": False, "hiddenWhitelist": []}
if not vault_root.exists(): if not vault_root.exists():
logger.warning(f"Vault path does not exist for attachment scan: {vault_path}") logger.warning(f"Vault path does not exist for attachment scan: {vault_path}")
return index return index
@ -65,11 +59,6 @@ def _scan_vault_attachments(vault_name: str, vault_path: str, vault_cfg: dict =
try: try:
for fpath in vault_root.rglob("*"): for fpath in vault_root.rglob("*"):
# Check if path should be included based on hidden files configuration
rel_parts = fpath.relative_to(vault_root).parts
if not should_include_path(rel_parts, vault_cfg):
continue
# Only process files # Only process files
if not fpath.is_file(): if not fpath.is_file():
continue continue

View File

@ -9,8 +9,6 @@ from typing import Dict, List, Optional, Any
import frontmatter import frontmatter
from backend.utils import should_include_path
logger = logging.getLogger("obsigate.indexer") logger = logging.getLogger("obsigate.indexer")
# Global in-memory index # Global in-memory index
@ -61,16 +59,12 @@ def load_vault_config() -> Dict[str, Dict[str, Any]]:
Also reads optional configuration: Also reads optional configuration:
- VAULT_N_ATTACHMENTS_PATH: relative path to attachments folder - VAULT_N_ATTACHMENTS_PATH: relative path to attachments folder
- VAULT_N_SCAN_ATTACHMENTS: "true"/"false" to enable/disable scanning - VAULT_N_SCAN_ATTACHMENTS: "true"/"false" to enable/disable scanning
- VAULT_N_INCLUDE_HIDDEN: "true"/"false" to include all hidden files/folders
- VAULT_N_HIDDEN_WHITELIST: comma-separated list of hidden paths to include (e.g., ".obsidian,.github")
Returns: Returns:
Dict mapping vault names to configuration dicts with keys: Dict mapping vault names to configuration dicts with keys:
- path: filesystem path (required) - path: filesystem path (required)
- attachmentsPath: relative attachments folder (optional) - attachmentsPath: relative attachments folder (optional)
- scanAttachmentsOnStartup: boolean (default True) - scanAttachmentsOnStartup: boolean (default True)
- includeHidden: boolean (default False) - include all hidden files/folders
- hiddenWhitelist: list of hidden paths to include even if includeHidden is False
- type: "VAULT" or "DIR" - type: "VAULT" or "DIR"
""" """
vaults: Dict[str, Dict[str, Any]] = {} vaults: Dict[str, Dict[str, Any]] = {}
@ -84,16 +78,11 @@ def load_vault_config() -> Dict[str, Dict[str, Any]]:
# Optional configuration # Optional configuration
attachments_path = os.environ.get(f"VAULT_{n}_ATTACHMENTS_PATH") attachments_path = os.environ.get(f"VAULT_{n}_ATTACHMENTS_PATH")
scan_attachments = os.environ.get(f"VAULT_{n}_SCAN_ATTACHMENTS", "true").lower() == "true" scan_attachments = os.environ.get(f"VAULT_{n}_SCAN_ATTACHMENTS", "true").lower() == "true"
include_hidden = os.environ.get(f"VAULT_{n}_INCLUDE_HIDDEN", "false").lower() == "true"
hidden_whitelist_str = os.environ.get(f"VAULT_{n}_HIDDEN_WHITELIST", "")
hidden_whitelist = [item.strip() for item in hidden_whitelist_str.split(",") if item.strip()]
vaults[name] = { vaults[name] = {
"path": path, "path": path,
"attachmentsPath": attachments_path, "attachmentsPath": attachments_path,
"scanAttachmentsOnStartup": scan_attachments, "scanAttachmentsOnStartup": scan_attachments,
"includeHidden": include_hidden,
"hiddenWhitelist": hidden_whitelist,
"type": "VAULT" "type": "VAULT"
} }
n += 1 n += 1
@ -105,16 +94,10 @@ def load_vault_config() -> Dict[str, Dict[str, Any]]:
if not name or not path: if not name or not path:
break break
include_hidden = os.environ.get(f"DIR_{n}_INCLUDE_HIDDEN", "false").lower() == "true"
hidden_whitelist_str = os.environ.get(f"DIR_{n}_HIDDEN_WHITELIST", "")
hidden_whitelist = [item.strip() for item in hidden_whitelist_str.split(",") if item.strip()]
vaults[name] = { vaults[name] = {
"path": path, "path": path,
"attachmentsPath": None, "attachmentsPath": None,
"scanAttachmentsOnStartup": False, "scanAttachmentsOnStartup": False,
"includeHidden": include_hidden,
"hiddenWhitelist": hidden_whitelist,
"type": "DIR" "type": "DIR"
} }
n += 1 n += 1
@ -219,11 +202,13 @@ def _scan_vault(vault_name: str, vault_path: str, vault_cfg: Optional[Dict[str,
Walks the vault tree, reads supported files, extracts metadata Walks the vault tree, reads supported files, extracts metadata
(tags, title, content preview) and stores a capped content snapshot (tags, title, content preview) and stores a capped content snapshot
for in-memory full-text search. for in-memory full-text search.
All files and directories are indexed, including hidden files (starting with '.').
Args: Args:
vault_name: Display name of the vault. vault_name: Display name of the vault.
vault_path: Absolute filesystem path to the vault root. vault_path: Absolute filesystem path to the vault root.
vault_cfg: Optional vault configuration dict with hidden files settings. vault_cfg: Optional vault configuration dict (unused for indexing, kept for compatibility).
Returns: Returns:
Dict with keys ``files`` (list), ``tags`` (counter dict), ``path`` (str), ``paths`` (list). Dict with keys ``files`` (list), ``tags`` (counter dict), ``path`` (str), ``paths`` (list).
@ -232,21 +217,12 @@ def _scan_vault(vault_name: str, vault_path: str, vault_cfg: Optional[Dict[str,
files: List[Dict[str, Any]] = [] files: List[Dict[str, Any]] = []
tag_counts: Dict[str, int] = {} tag_counts: Dict[str, int] = {}
paths: List[Dict[str, str]] = [] paths: List[Dict[str, str]] = []
# Default config if not provided
if vault_cfg is None:
vault_cfg = {"includeHidden": False, "hiddenWhitelist": []}
if not vault_root.exists(): if not vault_root.exists():
logger.warning(f"Vault path does not exist: {vault_path}") logger.warning(f"Vault path does not exist: {vault_path}")
return {"files": [], "tags": {}, "path": vault_path, "paths": []} return {"files": [], "tags": {}, "path": vault_path, "paths": []}
for fpath in vault_root.rglob("*"): for fpath in vault_root.rglob("*"):
# Check if path should be included based on hidden files configuration
rel_parts = fpath.relative_to(vault_root).parts
if not should_include_path(rel_parts, vault_cfg):
continue
rel_path_str = str(fpath.relative_to(vault_root)).replace("\\", "/") rel_path_str = str(fpath.relative_to(vault_root)).replace("\\", "/")
# Add all paths (files and directories) to path index # Add all paths (files and directories) to path index
@ -329,17 +305,8 @@ async def build_index(progress_callback=None) -> None:
vault_config.clear() vault_config.clear()
vault_config.update(load_vault_config()) vault_config.update(load_vault_config())
# Merge vault_settings (from UI) with vault_config (from env vars) # Note: vault_settings are now only used for UI display preferences (hideHiddenFiles)
from backend.vault_settings import get_all_vault_settings # Indexing always includes all files regardless of settings
saved_settings = get_all_vault_settings()
for vault_name, config in vault_config.items():
if vault_name in saved_settings:
settings = saved_settings[vault_name]
# Override with saved settings if present
if "includeHidden" in settings:
config["includeHidden"] = settings["includeHidden"]
if "hiddenWhitelist" in settings:
config["hiddenWhitelist"] = settings["hiddenWhitelist"]
global _index_generation global _index_generation
with _index_lock: with _index_lock:
@ -443,22 +410,11 @@ async def reload_single_vault(vault_name: str) -> Dict[str, Any]:
# Reload vault config from env vars # Reload vault config from env vars
vault_config.update(load_vault_config()) vault_config.update(load_vault_config())
# Merge with saved settings
from backend.vault_settings import get_vault_setting
saved_settings = get_vault_setting(vault_name)
if vault_name not in vault_config: if vault_name not in vault_config:
raise ValueError(f"Vault '{vault_name}' not found in configuration") raise ValueError(f"Vault '{vault_name}' not found in configuration")
config = vault_config[vault_name] config = vault_config[vault_name]
# Override with saved settings if present
if saved_settings:
if "includeHidden" in saved_settings:
config["includeHidden"] = saved_settings["includeHidden"]
if "hiddenWhitelist" in saved_settings:
config["hiddenWhitelist"] = saved_settings["hiddenWhitelist"]
# Remove old vault data from index structures # Remove old vault data from index structures
await remove_vault_from_index(vault_name) await remove_vault_from_index(vault_name)
@ -520,12 +476,14 @@ def _get_async_lock() -> asyncio.Lock:
def _index_single_file_sync(vault_name: str, vault_path: str, file_path: str, vault_cfg: Optional[Dict[str, Any]] = None) -> Optional[Dict[str, Any]]: def _index_single_file_sync(vault_name: str, vault_path: str, file_path: str, vault_cfg: Optional[Dict[str, Any]] = None) -> Optional[Dict[str, Any]]:
"""Synchronously read and parse a single file for indexing. """Synchronously read and parse a single file for indexing.
All files are indexed, including hidden files (starting with '.').
Args: Args:
vault_name: Name of the vault. vault_name: Name of the vault.
vault_path: Absolute path to vault root. vault_path: Absolute path to vault root.
file_path: Absolute path to the file. file_path: Absolute path to the file.
vault_cfg: Optional vault configuration dict with hidden files settings. vault_cfg: Optional vault configuration dict (unused for indexing, kept for compatibility).
Returns: Returns:
File info dict or None if the file cannot be read. File info dict or None if the file cannot be read.
@ -538,13 +496,6 @@ def _index_single_file_sync(vault_name: str, vault_path: str, file_path: str, va
return None return None
relative = fpath.relative_to(vault_root) relative = fpath.relative_to(vault_root)
rel_parts = relative.parts
# Check if path should be included based on hidden files configuration
if vault_cfg is None:
vault_cfg = {"includeHidden": False, "hiddenWhitelist": []}
if not should_include_path(rel_parts, vault_cfg):
return None
ext = fpath.suffix.lower() ext = fpath.suffix.lower()
basename_lower = fpath.name.lower() basename_lower = fpath.name.lower()
@ -815,8 +766,6 @@ async def add_vault_to_index(vault_name: str, vault_path: str) -> Dict[str, Any]
"path": vault_path, "path": vault_path,
"attachmentsPath": None, "attachmentsPath": None,
"scanAttachmentsOnStartup": True, "scanAttachmentsOnStartup": True,
"includeHidden": False,
"hiddenWhitelist": [],
} }
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()

View File

@ -1414,32 +1414,28 @@ async def api_attachment_stats(vault: Optional[str] = Query(None, description="V
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Vault Settings API — Hidden files configuration # Vault Settings API — Display preferences
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@app.get("/api/vaults/{vault_name}/settings") @app.get("/api/vaults/{vault_name}/settings")
async def api_get_vault_settings(vault_name: str, current_user=Depends(require_auth)): async def api_get_vault_settings(vault_name: str, current_user=Depends(require_auth)):
"""Get settings for a specific vault including hidden files configuration. """Get UI display settings for a specific vault.
Args: Args:
vault_name: Name of the vault. vault_name: Name of the vault.
Returns: Returns:
Dict with vault settings including includeHidden and hiddenWhitelist. Dict with vault settings including hideHiddenFiles.
""" """
if vault_name not in index: if vault_name not in index:
raise HTTPException(status_code=404, detail=f"Vault '{vault_name}' not found") raise HTTPException(status_code=404, detail=f"Vault '{vault_name}' not found")
# Get current vault config from environment
vault_cfg = vault_config.get(vault_name, {})
# Get persisted settings # Get persisted settings
persisted = get_vault_setting(vault_name) or {} persisted = get_vault_setting(vault_name) or {}
# Merge with defaults # Default settings
settings = { settings = {
"includeHidden": vault_cfg.get("includeHidden", False), "hideHiddenFiles": False,
"hiddenWhitelist": vault_cfg.get("hiddenWhitelist", []),
} }
settings.update(persisted) settings.update(persisted)
@ -1448,11 +1444,11 @@ async def api_get_vault_settings(vault_name: str, current_user=Depends(require_a
@app.post("/api/vaults/{vault_name}/settings") @app.post("/api/vaults/{vault_name}/settings")
async def api_update_vault_settings(vault_name: str, body: dict = Body(...), current_user=Depends(require_admin)): async def api_update_vault_settings(vault_name: str, body: dict = Body(...), current_user=Depends(require_admin)):
"""Update settings for a specific vault. """Update UI display settings for a specific vault.
Args: Args:
vault_name: Name of the vault. vault_name: Name of the vault.
body: Dict with settings to update (includeHidden, hiddenWhitelist). body: Dict with settings to update (hideHiddenFiles).
Returns: Returns:
Updated settings dict. Updated settings dict.
@ -1463,18 +1459,10 @@ async def api_update_vault_settings(vault_name: str, body: dict = Body(...), cur
# Validate settings # Validate settings
settings_to_update = {} settings_to_update = {}
if "includeHidden" in body: if "hideHiddenFiles" in body:
if not isinstance(body["includeHidden"], bool): if not isinstance(body["hideHiddenFiles"], bool):
raise HTTPException(status_code=400, detail="includeHidden must be a boolean") raise HTTPException(status_code=400, detail="hideHiddenFiles must be a boolean")
settings_to_update["includeHidden"] = body["includeHidden"] settings_to_update["hideHiddenFiles"] = body["hideHiddenFiles"]
if "hiddenWhitelist" in body:
if not isinstance(body["hiddenWhitelist"], list):
raise HTTPException(status_code=400, detail="hiddenWhitelist must be a list")
# Validate each item is a string
if not all(isinstance(item, str) for item in body["hiddenWhitelist"]):
raise HTTPException(status_code=400, detail="All hiddenWhitelist items must be strings")
settings_to_update["hiddenWhitelist"] = body["hiddenWhitelist"]
# Update persisted settings # Update persisted settings
try: try:
@ -1492,10 +1480,6 @@ async def api_update_vault_settings(vault_name: str, body: dict = Body(...), cur
detail=f"Failed to save settings: {str(e)}" detail=f"Failed to save settings: {str(e)}"
) )
# Update in-memory vault config
if vault_name in vault_config:
vault_config[vault_name].update(settings_to_update)
logger.info(f"Updated settings for vault '{vault_name}': {settings_to_update}") logger.info(f"Updated settings for vault '{vault_name}': {settings_to_update}")
return updated return updated
@ -1503,7 +1487,7 @@ async def api_update_vault_settings(vault_name: str, body: dict = Body(...), cur
@app.get("/api/vaults/settings/all") @app.get("/api/vaults/settings/all")
async def api_get_all_vault_settings(current_user=Depends(require_auth)): async def api_get_all_vault_settings(current_user=Depends(require_auth)):
"""Get settings for all vaults. """Get UI display settings for all vaults.
Returns: Returns:
Dict mapping vault names to their settings. Dict mapping vault names to their settings.
@ -1511,12 +1495,10 @@ async def api_get_all_vault_settings(current_user=Depends(require_auth)):
all_settings = {} all_settings = {}
for vault_name in index.keys(): for vault_name in index.keys():
vault_cfg = vault_config.get(vault_name, {})
persisted = get_vault_setting(vault_name) or {} persisted = get_vault_setting(vault_name) or {}
settings = { settings = {
"includeHidden": vault_cfg.get("includeHidden", False), "hideHiddenFiles": False,
"hiddenWhitelist": vault_cfg.get("hiddenWhitelist", []),
} }
settings.update(persisted) settings.update(persisted)
all_settings[vault_name] = settings all_settings[vault_name] = settings

View File

@ -1,42 +1,2 @@
"""Utility functions shared across backend modules.""" """Utility functions shared across backend modules."""
from typing import Dict, Any, Tuple from typing import Dict, Any, Tuple
def should_include_path(rel_parts: Tuple[str, ...], vault_config: Dict[str, Any]) -> bool:
"""Check if a path should be included based on hidden files configuration.
Simplified logic:
- If includeHidden=True: include ALL hidden files/folders recursively
- If includeHidden=False: check whitelist
- If a whitelisted folder is in the path, include ALL sub-hidden files/folders
- Otherwise, exclude the path
Args:
rel_parts: Tuple of path parts relative to vault root
vault_config: Vault configuration dict with includeHidden and hiddenWhitelist
Returns:
True if the path should be included, False otherwise
"""
include_hidden = vault_config.get("includeHidden", False)
hidden_whitelist = vault_config.get("hiddenWhitelist", [])
# Check if any part of the path starts with a dot (hidden)
hidden_parts = [part for part in rel_parts if part.startswith(".")]
if not hidden_parts:
# No hidden parts, always include
return True
if include_hidden:
# Include all hidden files/folders recursively
return True
# Check if the FIRST hidden part is in the whitelist
# If yes, include all sub-hidden files/folders automatically
first_hidden = hidden_parts[0]
if first_hidden in hidden_whitelist:
return True
# First hidden part not in whitelist, exclude
return False

View File

@ -1,7 +1,11 @@
"""Vault-specific settings management. """Vault-specific settings management.
Provides persistent storage for per-vault configuration like hidden files settings. Provides persistent storage for per-vault UI display preferences.
Settings are stored in /app/data/vault_settings.json Settings are stored in /app/data/vault_settings.json
Current settings:
- hideHiddenFiles (bool): Whether to hide files/folders starting with '.' in the UI
Note: All files are always indexed regardless of this setting. This only affects display.
""" """
import json import json

View File

@ -24,6 +24,9 @@
let _iconDebounceTimer = null; let _iconDebounceTimer = null;
let activeSidebarTab = "vaults"; let activeSidebarTab = "vaults";
let filterDebounce = null; let filterDebounce = null;
// Vault settings cache for hideHiddenFiles
let vaultSettings = {};
// Advanced search state // Advanced search state
let advancedSearchOffset = 0; let advancedSearchOffset = 0;
@ -1866,6 +1869,39 @@
renderTagCloud(filteredTags); renderTagCloud(filteredTags);
} }
// ---------------------------------------------------------------------------
// Helper: Check if path should be displayed based on hideHiddenFiles setting
// ---------------------------------------------------------------------------
function shouldDisplayPath(path, vaultName) {
// Get hideHiddenFiles setting for this vault (default: false = show all)
const settings = vaultSettings[vaultName] || { hideHiddenFiles: false };
if (!settings.hideHiddenFiles) {
// Show all files
return true;
}
// Check if any segment of the path starts with a dot (hidden)
const segments = path.split('/').filter(Boolean);
for (const segment of segments) {
if (segment.startsWith('.')) {
return false; // Hide this path
}
}
return true; // Show this path
}
async function loadVaultSettings() {
try {
const settings = await api("/api/vaults/settings/all");
vaultSettings = settings;
} catch (err) {
console.error("Failed to load vault settings:", err);
vaultSettings = {};
}
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Sidebar — Vault tree // Sidebar — Vault tree
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -2013,6 +2049,11 @@
const fragment = document.createDocumentFragment(); const fragment = document.createDocumentFragment();
data.items.forEach((item) => { data.items.forEach((item) => {
// Apply client-side filtering for hidden files
if (!shouldDisplayPath(item.path, vaultName)) {
return; // Skip this item
}
if (item.type === "directory") { if (item.type === "directory") {
const dirItem = el("div", { class: "tree-item", "data-vault": vaultName, "data-path": item.path }, [ const dirItem = el("div", { class: "tree-item", "data-vault": vaultName, "data-path": item.path }, [
icon("chevron-right", 14), icon("chevron-right", 14),
@ -3013,9 +3054,6 @@
// Hidden files configuration // Hidden files configuration
const saveHiddenBtn = document.getElementById("cfg-save-hidden-files"); const saveHiddenBtn = document.getElementById("cfg-save-hidden-files");
if (saveHiddenBtn) saveHiddenBtn.addEventListener("click", saveHiddenFilesSettings); if (saveHiddenBtn) saveHiddenBtn.addEventListener("click", saveHiddenFilesSettings);
const reindexHiddenBtn = document.getElementById("cfg-reindex-hidden");
if (reindexHiddenBtn) reindexHiddenBtn.addEventListener("click", reindexWithHiddenFiles);
document.addEventListener("keydown", (e) => { document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && modal.classList.contains("active")) { if (e.key === "Escape" && modal.classList.contains("active")) {
@ -3231,32 +3269,6 @@
// --- Hidden Files Configuration --- // --- Hidden Files Configuration ---
// Track which vaults have been modified
let modifiedVaults = new Set();
function updateHiddenFilesButtonStates() {
const saveBtn = document.getElementById("cfg-save-hidden-files");
const reindexBtn = document.getElementById("cfg-reindex-hidden");
if (modifiedVaults.size > 0) {
if (saveBtn) {
saveBtn.textContent = `💾 Sauvegarder (${modifiedVaults.size} modifié${modifiedVaults.size > 1 ? 's' : ''})`;
saveBtn.classList.add("config-btn-highlight");
}
if (reindexBtn) {
reindexBtn.textContent = "🔄 Sauvegarder et réindexer";
}
} else {
if (saveBtn) {
saveBtn.textContent = "💾 Sauvegarder";
saveBtn.classList.remove("config-btn-highlight");
}
if (reindexBtn) {
reindexBtn.textContent = "🔄 Réindexer";
}
}
}
async function loadHiddenFilesSettings() { async function loadHiddenFilesSettings() {
const container = document.getElementById("hidden-files-vault-list"); const container = document.getElementById("hidden-files-vault-list");
if (!container) return; if (!container) return;
@ -3281,7 +3293,7 @@
} }
allVaults.forEach(vault => { allVaults.forEach(vault => {
const settings = allSettings[vault.name] || { includeHidden: false, hiddenWhitelist: [] }; const settings = allSettings[vault.name] || { hideHiddenFiles: false };
const vaultCard = el("div", { class: "hidden-files-vault-card", "data-vault": vault.name }); const vaultCard = el("div", { class: "hidden-files-vault-card", "data-vault": vault.name });
@ -3291,144 +3303,32 @@
el("span", { class: "hidden-files-vault-type" }, [document.createTextNode(vault.type || "VAULT")]) el("span", { class: "hidden-files-vault-type" }, [document.createTextNode(vault.type || "VAULT")])
]); ]);
// Include all hidden toggle // Hide hidden files toggle
const toggleRow = el("div", { class: "config-row" }, [ const toggleRow = el("div", { class: "config-row" }, [
el("label", { class: "config-label", for: `hidden-include-${vault.name}` }, [ el("label", { class: "config-label", for: `hide-hidden-${vault.name}` }, [
document.createTextNode("Afficher tous les fichiers cachés") document.createTextNode("Masquer les fichiers/dossiers cachés")
]), ]),
el("label", { class: "config-toggle" }, [ el("label", { class: "config-toggle" }, [
el("input", { el("input", {
type: "checkbox", type: "checkbox",
id: `hidden-include-${vault.name}`, id: `hide-hidden-${vault.name}`,
"data-vault": vault.name, "data-vault": vault.name,
checked: settings.includeHidden || false checked: settings.hideHiddenFiles || false
}), }),
el("span", { class: "config-toggle-slider" }) el("span", { class: "config-toggle-slider" })
]), ]),
el("span", { class: "config-hint" }, [ el("span", { class: "config-hint" }, [
document.createTextNode("Indexer tous les fichiers/dossiers commençant par un point") document.createTextNode("Masquer les fichiers/dossiers commençant par un point dans l'interface (ils restent indexés et cherchables)")
]) ])
]); ]);
// Whitelist section
const whitelistSection = el("div", { class: "hidden-files-whitelist" });
const whitelistLabel = el("label", { class: "config-label" }, [
document.createTextNode("Liste blanche")
]);
const whitelistHint = el("span", { class: "config-hint", style: "display:block;margin-bottom:8px" }, [
document.createTextNode("Dossiers cachés spécifiques à afficher (tous les sous-dossiers cachés seront inclus)")
]);
const whitelistItems = el("div", { class: "hidden-files-whitelist-items", id: `whitelist-items-${vault.name}` });
// Render existing whitelist items
(settings.hiddenWhitelist || []).forEach(item => {
const itemEl = createWhitelistItem(vault.name, item);
whitelistItems.appendChild(itemEl);
});
// Add new item input
const addRow = el("div", { class: "hidden-files-add-row" }, [
el("input", {
type: "text",
class: "config-input",
id: `whitelist-input-${vault.name}`,
placeholder: ".obsidian"
}),
el("button", {
class: "config-btn-add",
type: "button",
"data-vault": vault.name
}, [document.createTextNode(" Ajouter")])
]);
whitelistSection.appendChild(whitelistLabel);
whitelistSection.appendChild(whitelistHint);
whitelistSection.appendChild(whitelistItems);
whitelistSection.appendChild(addRow);
vaultCard.appendChild(header); vaultCard.appendChild(header);
vaultCard.appendChild(toggleRow); vaultCard.appendChild(toggleRow);
vaultCard.appendChild(whitelistSection);
container.appendChild(vaultCard); container.appendChild(vaultCard);
// Event listeners
const addBtn = addRow.querySelector("button");
const input = addRow.querySelector("input");
const checkbox = toggleRow.querySelector("input[type='checkbox']");
addBtn.addEventListener("click", () => {
addWhitelistItem(vault.name);
modifiedVaults.add(vault.name);
updateHiddenFilesButtonStates();
});
input.addEventListener("keypress", (e) => {
if (e.key === "Enter") {
addWhitelistItem(vault.name);
modifiedVaults.add(vault.name);
updateHiddenFilesButtonStates();
}
});
checkbox.addEventListener("change", () => {
modifiedVaults.add(vault.name);
updateHiddenFilesButtonStates();
});
}); });
} }
function createWhitelistItem(vaultName, itemValue) {
const item = el("div", { class: "hidden-files-whitelist-item" }, [
el("span", { class: "whitelist-item-text" }, [document.createTextNode(itemValue)]),
el("button", {
class: "whitelist-item-remove",
type: "button",
title: "Supprimer",
"data-vault": vaultName,
"data-item": itemValue
}, [document.createTextNode("×")])
]);
const removeBtn = item.querySelector("button");
removeBtn.addEventListener("click", () => {
item.remove();
modifiedVaults.add(vaultName);
updateHiddenFilesButtonStates();
});
return item;
}
function addWhitelistItem(vaultName) {
const input = document.getElementById(`whitelist-input-${vaultName}`);
const container = document.getElementById(`whitelist-items-${vaultName}`);
const includeHiddenCheckbox = document.getElementById(`hidden-include-${vaultName}`);
if (!input || !container) return;
const value = input.value.trim();
if (!value) return;
// Auto-add dot if missing
const normalizedValue = value.startsWith(".") ? value : `.${value}`;
// Check for duplicates
const existing = Array.from(container.querySelectorAll(".whitelist-item-text"))
.map(el => el.textContent);
if (existing.includes(normalizedValue)) {
showToast("Cet élément existe déjà", "error");
return;
}
// Auto-uncheck "Afficher tous les fichiers cachés" when adding to whitelist
if (includeHiddenCheckbox && includeHiddenCheckbox.checked) {
includeHiddenCheckbox.checked = false;
}
const item = createWhitelistItem(vaultName, normalizedValue);
container.appendChild(item);
input.value = "";
}
async function saveHiddenFilesSettings() { async function saveHiddenFilesSettings() {
const btn = document.getElementById("cfg-save-hidden-files"); const btn = document.getElementById("cfg-save-hidden-files");
@ -3440,15 +3340,10 @@
vaultCards.forEach(card => { vaultCards.forEach(card => {
const vaultName = card.dataset.vault; const vaultName = card.dataset.vault;
const includeHidden = document.getElementById(`hidden-include-${vaultName}`)?.checked || false; const hideHiddenFiles = document.getElementById(`hide-hidden-${vaultName}`)?.checked || false;
const whitelistItems = Array.from(
document.querySelectorAll(`#whitelist-items-${vaultName} .whitelist-item-text`)
).map(el => el.textContent);
const settings = { const settings = {
includeHidden, hideHiddenFiles
hiddenWhitelist: whitelistItems
}; };
promises.push( promises.push(
@ -3467,9 +3362,14 @@
}); });
await Promise.all(promises); await Promise.all(promises);
modifiedVaults.clear();
updateHiddenFilesButtonStates(); // Reload vault settings to update the cache
await loadVaultSettings();
showToast("✓ Paramètres sauvegardés", "success"); showToast("✓ Paramètres sauvegardés", "success");
// Refresh the UI to apply the filter
await Promise.all([loadVaults(), refreshTagsForContext()]);
} catch (err) { } catch (err) {
console.error("Failed to save hidden files settings:", err); console.error("Failed to save hidden files settings:", err);
const errorMsg = err.message || "Erreur inconnue"; const errorMsg = err.message || "Erreur inconnue";
@ -3479,41 +3379,6 @@
} }
} }
async function reindexWithHiddenFiles() {
const btn = document.getElementById("cfg-reindex-hidden");
if (btn) { btn.disabled = true; btn.textContent = "💾 Sauvegarde..."; }
try {
// First save the settings to ensure they are persisted
await saveHiddenFilesSettings();
// Update button text to show reindexing phase
if (btn) { btn.textContent = "⏳ Réindexation..."; }
// Reindex only modified vaults if any, otherwise reindex all
if (modifiedVaults.size > 0) {
const vaultsToReindex = Array.from(modifiedVaults);
for (const vaultName of vaultsToReindex) {
await api(`/api/index/reload/${encodeURIComponent(vaultName)}`);
}
showToast(`✓ Réindexation terminée (${vaultsToReindex.length} vault${vaultsToReindex.length > 1 ? 's' : ''})`, "success");
} else {
await api("/api/index/reload");
showToast("✓ Réindexation terminée", "success");
}
modifiedVaults.clear();
updateHiddenFilesButtonStates();
loadDiagnostics();
await Promise.all([loadVaults(), loadTags()]);
} catch (err) {
console.error("Reindex with hidden files error:", err);
const errorMsg = err.message || "Erreur inconnue";
showToast(`Erreur: ${errorMsg}`, "error");
} finally {
if (btn) { btn.disabled = false; btn.textContent = "🔄 Réindexer"; }
}
}
function renderConfigFilters() { function renderConfigFilters() {
const config = TagFilterService.getConfig(); const config = TagFilterService.getConfig();
@ -3826,6 +3691,11 @@
} }
const container = el("div", { class: "search-results" }); const container = el("div", { class: "search-results" });
data.results.forEach((r) => { data.results.forEach((r) => {
// Apply client-side filtering for hidden files
if (!shouldDisplayPath(r.path, r.vault)) {
return; // Skip this result
}
const titleDiv = el("div", { class: "search-result-title" }); const titleDiv = el("div", { class: "search-result-title" });
if (query && query.trim()) { if (query && query.trim()) {
highlightSearchText(titleDiv, r.title, query, searchCaseSensitive); highlightSearchText(titleDiv, r.title, query, searchCaseSensitive);
@ -3986,6 +3856,11 @@
// Results list // Results list
const container = el("div", { class: "search-results" }); const container = el("div", { class: "search-results" });
data.results.forEach((r) => { data.results.forEach((r) => {
// Apply client-side filtering for hidden files
if (!shouldDisplayPath(r.path, r.vault)) {
return; // Skip this result
}
const titleDiv = el("div", { class: "search-result-title" }); const titleDiv = el("div", { class: "search-result-title" });
if (freeText) { if (freeText) {
highlightSearchText(titleDiv, r.title, freeText, searchCaseSensitive); highlightSearchText(titleDiv, r.title, freeText, searchCaseSensitive);
@ -5544,7 +5419,7 @@
if (authOk) { if (authOk) {
try { try {
await Promise.all([loadVaults(), loadTags()]); await Promise.all([loadVaultSettings(), loadVaults(), loadTags()]);
// Check for popup mode query parameter // Check for popup mode query parameter
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);

View File

@ -551,9 +551,9 @@
<!-- Hidden Files/Folders Configuration --> <!-- Hidden Files/Folders Configuration -->
<section class="config-section"> <section class="config-section">
<h2>🗂️ Fichiers cachés</h2> <h2>🗂️ Fichiers cachés</h2>
<p class="config-description">Gérez l'affichage des fichiers/dossiers cachés (commençant par <code>.</code>) par vault.</p> <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;"> <p class="config-hint" style="margin-bottom: 12px; padding: 8px; background: var(--background-secondary); border-radius: 4px;">
⚠️ <strong>Important :</strong> Après avoir modifié les paramètres, cliquez sur <strong>"Sauvegarder"</strong> puis sur <strong>"Réindexer"</strong> pour appliquer les changements. <strong>Note :</strong> Tous les fichiers sont toujours indexés et cherchables. Ce paramètre contrôle uniquement leur visibilité dans l'interface.
</p> </p>
<div id="hidden-files-vault-list"> <div id="hidden-files-vault-list">
@ -562,7 +562,6 @@
<div class="config-actions-row" style="margin-top: 16px;"> <div class="config-actions-row" style="margin-top: 16px;">
<button class="config-btn-save" id="cfg-save-hidden-files">💾 Sauvegarder</button> <button class="config-btn-save" id="cfg-save-hidden-files">💾 Sauvegarder</button>
<button class="config-btn-secondary" id="cfg-reindex-hidden">🔄 Réindexer</button>
</div> </div>
</section> </section>