feat: points 11a + 11b — Indicateur AI Actif & Page d'accueil voute
Point 11a — Indicateur AI Actif:
- Badge sync flashe en blanc pendant traitement AI (animation CSS)
- Événements ai-processing-start/end dans ai.js
- Section statut IA dans panneau sync (configurée/non configurée/en cours)
- Endpoint GET /api/ai/status vérifiant les clés API configurées
Point 11b — Page d'accueil de voute:
- Endpoint GET /api/vault/{vault}/files?dir=... (trié par mtime)
- Module vaulthome.js: affichage style résultats de recherche
- Breadcrumb navigable, mise à jour auto sur clic dossier dans tree
- Déclenché auto quand un vault spécifique est sélectionné
This commit is contained in:
+5
-1
@@ -347,6 +347,8 @@ export async function createAIToolbar(container, getEditorView) {
|
||||
return;
|
||||
}
|
||||
showToast('⏳ AI: traitement en cours...', 'info');
|
||||
// Dispatch AI processing start event
|
||||
document.dispatchEvent(new CustomEvent('ai-processing-start', { detail: { endpoint, mode } }));
|
||||
try {
|
||||
const result = await aiAction(endpoint, text, extra);
|
||||
replaceSelection(v, result, mode);
|
||||
@@ -360,9 +362,11 @@ export async function createAIToolbar(container, getEditorView) {
|
||||
} else {
|
||||
showToast('AI: ' + msg, 'error');
|
||||
}
|
||||
} finally {
|
||||
// Dispatch AI processing end event
|
||||
document.dispatchEvent(new CustomEvent('ai-processing-end'));
|
||||
}
|
||||
}
|
||||
|
||||
// ── Keyboard shortcut Ctrl+J for inline completion ──
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.ctrlKey && e.key === 'j') {
|
||||
|
||||
@@ -43,6 +43,10 @@ async function setSelectedVaultContext(vaultName, options) {
|
||||
}
|
||||
|
||||
showWelcome();
|
||||
// If a specific vault is selected, switch to vault home page
|
||||
if (vaultName !== "all") {
|
||||
import('./vaulthome.js').then(m => m.showVaultHome(vaultName, ''));
|
||||
}
|
||||
if (options && options.focusVault && vaultName !== "all") {
|
||||
await focusVaultInSidebar(vaultName);
|
||||
}
|
||||
|
||||
@@ -246,6 +246,9 @@ function initSyncStatus() {
|
||||
});
|
||||
|
||||
IndexUpdateManager.connect();
|
||||
|
||||
// Initialize AI status tracking
|
||||
_initAIStatus();
|
||||
}
|
||||
|
||||
function toggleSyncPanel() {
|
||||
@@ -323,6 +326,118 @@ function _renderSyncPanel(panel) {
|
||||
}
|
||||
|
||||
panel.innerHTML = html;
|
||||
|
||||
// ── AI Status section ──
|
||||
_renderAIStatusSection(panel);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AI Status Indicator (inside sync panel + header badge animation)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
let _aiProcessing = false;
|
||||
let _aiStatusChecked = false;
|
||||
let _aiConfigured = null; // null = unknown, true/false
|
||||
|
||||
/** Check if AI is configured by calling the status endpoint. */
|
||||
async function _checkAIStatus() {
|
||||
if (_aiStatusChecked) return;
|
||||
try {
|
||||
const resp = await api('/api/ai/status');
|
||||
_aiConfigured = !!(resp && resp.configured);
|
||||
} catch (e) {
|
||||
_aiConfigured = false;
|
||||
}
|
||||
_aiStatusChecked = true;
|
||||
}
|
||||
|
||||
function _renderAIStatusSection(panel) {
|
||||
const section = document.createElement('div');
|
||||
section.className = 'sync-panel__ai-section';
|
||||
section.id = 'sync-panel-ai';
|
||||
|
||||
let statusIcon, statusText, statusClass;
|
||||
if (_aiProcessing) {
|
||||
statusIcon = 'loader';
|
||||
statusText = 'Traitement en cours…';
|
||||
statusClass = 'sync-panel__ai--processing';
|
||||
} else if (_aiConfigured === true) {
|
||||
statusIcon = 'check-circle';
|
||||
statusText = 'IA configurée et active';
|
||||
statusClass = 'sync-panel__ai--active';
|
||||
} else if (_aiConfigured === false) {
|
||||
statusIcon = 'x-circle';
|
||||
statusText = 'IA non configurée';
|
||||
statusClass = 'sync-panel__ai--inactive';
|
||||
} else {
|
||||
statusIcon = 'help-circle';
|
||||
statusText = 'Statut IA inconnu';
|
||||
statusClass = 'sync-panel__ai--unknown';
|
||||
}
|
||||
|
||||
section.innerHTML = `
|
||||
<div class="sync-panel__ai-header">
|
||||
<span class="sync-panel__ai-title">Intelligence Artificielle</span>
|
||||
<span class="sync-panel__ai-state ${statusClass}" data-lucide-icon="${statusIcon}">
|
||||
<i data-lucide="${statusIcon}" style="width:14px;height:14px"></i>
|
||||
${statusText}
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
panel.appendChild(section);
|
||||
// Trigger icon refresh via global
|
||||
if (window._obsigateRefreshIcons) window._obsigateRefreshIcons();
|
||||
}
|
||||
|
||||
/** Update the AI section in the open sync panel. */
|
||||
function _updateAIStatusInPanel() {
|
||||
const section = document.getElementById('sync-panel-ai');
|
||||
if (!section) return;
|
||||
section.innerHTML = '';
|
||||
const panel = document.getElementById('sync-panel');
|
||||
if (panel) {
|
||||
// Re-render just the AI section
|
||||
const oldSection = panel.querySelector('.sync-panel__ai-section');
|
||||
if (oldSection) oldSection.remove();
|
||||
_renderAIStatusSection(panel);
|
||||
}
|
||||
}
|
||||
|
||||
/** Flash the sync badge white during AI processing. */
|
||||
function _setBadgeAIProcessing(active) {
|
||||
const badge = document.getElementById('sync-badge');
|
||||
if (!badge) return;
|
||||
if (active) {
|
||||
badge.classList.add('sync-badge--ai-processing');
|
||||
} else {
|
||||
badge.classList.remove('sync-badge--ai-processing');
|
||||
}
|
||||
}
|
||||
|
||||
/** Initialize AI status tracking. */
|
||||
function _initAIStatus() {
|
||||
// Listen for AI processing events from ai.js
|
||||
document.addEventListener('ai-processing-start', () => {
|
||||
_aiProcessing = true;
|
||||
_setBadgeAIProcessing(true);
|
||||
_updateAIStatusInPanel();
|
||||
});
|
||||
|
||||
document.addEventListener('ai-processing-end', () => {
|
||||
_aiProcessing = false;
|
||||
_setBadgeAIProcessing(false);
|
||||
_updateAIStatusInPanel();
|
||||
});
|
||||
|
||||
// Check AI status on first sync panel open
|
||||
const badge = document.getElementById('sync-badge');
|
||||
if (badge) {
|
||||
const origClick = badge.onclick;
|
||||
badge.addEventListener('click', () => {
|
||||
_checkAIStatus().then(() => _updateAIStatusInPanel());
|
||||
}, { once: false });
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,284 @@
|
||||
/**
|
||||
* Vault Home Page module for ObsiGate.
|
||||
*
|
||||
* When a specific vault is selected (not "All"), replaces the dashboard
|
||||
* with a file listing sorted by modification time (newest first).
|
||||
* The listing updates automatically when browsing into subdirectories.
|
||||
*
|
||||
* The visual style mirrors the search results (.search-result-item) for
|
||||
* consistency.
|
||||
*
|
||||
* Usage:
|
||||
* import { showVaultHome, updateVaultHomeDir, isVaultHomeActive } from './vaulthome.js';
|
||||
*/
|
||||
|
||||
import { api } from './auth.js';
|
||||
import { el, safeCreateIcons, getFileIcon } from './viewer.js';
|
||||
import { TabManager } from './ui.js';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// State
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const state = {
|
||||
active: false,
|
||||
currentVault: null,
|
||||
currentDir: '', // empty = vault root
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public API
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Check if vault home is currently displayed. */
|
||||
export function isVaultHomeActive() {
|
||||
return state.active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the vault home page for a vault directory.
|
||||
* @param {string} vaultName - Vault name
|
||||
* @param {string} dirPath - Directory path (empty for root)
|
||||
*/
|
||||
export async function showVaultHome(vaultName, dirPath = '') {
|
||||
if (vaultName === 'all' || !vaultName) {
|
||||
state.active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
state.active = true;
|
||||
state.currentVault = vaultName;
|
||||
state.currentDir = dirPath || '';
|
||||
|
||||
const area = document.getElementById('content-area');
|
||||
if (!area) return;
|
||||
|
||||
// Remove any existing dashboard
|
||||
const home = document.getElementById('dashboard-home');
|
||||
if (home) home.remove();
|
||||
|
||||
// Build vault home container
|
||||
area.innerHTML = '';
|
||||
|
||||
const container = el('div', { class: 'vault-home', id: 'vault-home' });
|
||||
area.appendChild(container);
|
||||
|
||||
// Breadcrumb
|
||||
renderBreadcrumb(container, vaultName, state.currentDir);
|
||||
|
||||
// Results container
|
||||
const resultsArea = el('div', { class: 'vault-home-results', id: 'vault-home-results' });
|
||||
container.appendChild(resultsArea);
|
||||
|
||||
// Show loading
|
||||
resultsArea.innerHTML = `
|
||||
<div class="tree-loading" style="padding:2rem;text-align:center">
|
||||
<div class="loading-spinner" style="width:24px;height:24px;border-width:3px;margin:0 auto"></div>
|
||||
<p style="margin-top:0.75rem;color:var(--text-muted);font-size:0.85rem">Chargement des fichiers...</p>
|
||||
</div>`;
|
||||
|
||||
await loadAndRender(vaultName, state.currentDir, resultsArea);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the vault home directory without rebuilding the whole page.
|
||||
* Called when navigating into a subdirectory.
|
||||
* @param {string} vaultName
|
||||
* @param {string} dirPath
|
||||
*/
|
||||
export async function updateVaultHomeDir(vaultName, dirPath) {
|
||||
if (!state.active || state.currentVault !== vaultName) return;
|
||||
|
||||
state.currentDir = dirPath || '';
|
||||
|
||||
const container = document.getElementById('vault-home');
|
||||
if (!container) return;
|
||||
|
||||
// Update breadcrumb
|
||||
const bc = container.querySelector('.vault-home-breadcrumb');
|
||||
if (bc) {
|
||||
bc.innerHTML = '';
|
||||
buildBreadcrumbHTML(bc, vaultName, state.currentDir);
|
||||
safeCreateIcons();
|
||||
}
|
||||
|
||||
// Reload results
|
||||
const resultsArea = document.getElementById('vault-home-results');
|
||||
if (resultsArea) {
|
||||
resultsArea.innerHTML = `
|
||||
<div class="tree-loading" style="padding:2rem;text-align:center">
|
||||
<div class="loading-spinner" style="width:20px;height:20px;border-width:2px;margin:0 auto"></div>
|
||||
</div>`;
|
||||
await loadAndRender(vaultName, state.currentDir, resultsArea);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tree click delegation — update vault home on directory clicks
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function _initTreeDelegation() {
|
||||
const tree = document.getElementById('vault-tree');
|
||||
if (!tree) return;
|
||||
|
||||
tree.addEventListener('click', (e) => {
|
||||
if (!state.active) return;
|
||||
|
||||
// Find the closest tree-item that is a directory
|
||||
const item = e.target.closest('.tree-item');
|
||||
if (!item || !item.dataset.vault || item.dataset.path === undefined) return;
|
||||
|
||||
const vault = item.dataset.vault;
|
||||
const path = item.dataset.path;
|
||||
|
||||
// Only react to directory clicks (items that have a child container)
|
||||
const hasChildContainer = item.nextElementSibling
|
||||
&& item.nextElementSibling.classList.contains('tree-children');
|
||||
if (!hasChildContainer) return;
|
||||
|
||||
// Update vault home dir
|
||||
updateVaultHomeDir(vault, path);
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize delegation when module loads
|
||||
_initTreeDelegation();
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Rendering
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function renderBreadcrumb(container, vaultName, dirPath) {
|
||||
const bc = el('div', { class: 'vault-home-breadcrumb' });
|
||||
buildBreadcrumbHTML(bc, vaultName, dirPath);
|
||||
container.appendChild(bc);
|
||||
}
|
||||
|
||||
function buildBreadcrumbHTML(parentEl, vaultName, dirPath) {
|
||||
// Vault icon + name (root)
|
||||
const vaultLink = el('span', { class: 'vault-home-breadcrumb-item vault-home-breadcrumb-root' }, [
|
||||
el('i', { 'data-lucide': 'database', style: 'width:14px;height:14px' }),
|
||||
el('span', {}, [document.createTextNode(vaultName)]),
|
||||
]);
|
||||
vaultLink.addEventListener('click', () => updateVaultHomeDir(vaultName, ''));
|
||||
vaultLink.style.cursor = 'pointer';
|
||||
parentEl.appendChild(vaultLink);
|
||||
|
||||
if (!dirPath) return;
|
||||
|
||||
const segments = dirPath.split('/').filter(Boolean);
|
||||
let cumulative = '';
|
||||
for (const seg of segments) {
|
||||
cumulative = cumulative ? cumulative + '/' + seg : seg;
|
||||
const sep = el('span', { class: 'vault-home-breadcrumb-sep' }, [document.createTextNode(' / ')]);
|
||||
parentEl.appendChild(sep);
|
||||
const segLink = el('span', { class: 'vault-home-breadcrumb-item' }, [
|
||||
el('i', { 'data-lucide': 'folder', style: 'width:14px;height:14px' }),
|
||||
el('span', {}, [document.createTextNode(seg)]),
|
||||
]);
|
||||
segLink.addEventListener('click', () => updateVaultHomeDir(vaultName, cumulative));
|
||||
segLink.style.cursor = 'pointer';
|
||||
parentEl.appendChild(segLink);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadAndRender(vaultName, dirPath, container) {
|
||||
try {
|
||||
const url = `/api/vault/${encodeURIComponent(vaultName)}/files?dir=${encodeURIComponent(dirPath)}&limit=200`;
|
||||
const data = await api(url);
|
||||
|
||||
if (!data.files || data.files.length === 0) {
|
||||
container.innerHTML = `
|
||||
<div class="vault-home-empty">
|
||||
<i data-lucide="folder-open" style="width:32px;height:32px;color:var(--text-muted)"></i>
|
||||
<p>Aucun fichier dans ce répertoire</p>
|
||||
</div>`;
|
||||
safeCreateIcons();
|
||||
return;
|
||||
}
|
||||
|
||||
// Header
|
||||
const header = el('div', { class: 'vault-home-header' });
|
||||
const count = el('span', { class: 'vault-home-count' }, [
|
||||
document.createTextNode(`${data.files.length} fichier${data.files.length > 1 ? 's' : ''}`),
|
||||
]);
|
||||
header.appendChild(count);
|
||||
|
||||
// Sort dropdown (already sorted by mtime from backend)
|
||||
const dirLabel = dirPath ? `/${dirPath}` : ' (racine)';
|
||||
const dirInfo = el('span', { class: 'vault-home-dir-info' }, [
|
||||
document.createTextNode(dirLabel),
|
||||
]);
|
||||
header.appendChild(dirInfo);
|
||||
|
||||
container.innerHTML = '';
|
||||
container.appendChild(header);
|
||||
|
||||
// File list — using search-result-item style
|
||||
const list = el('div', { class: 'search-results' });
|
||||
|
||||
data.files.forEach((f) => {
|
||||
const fileIconName = getFileIcon ? getFileIcon(f.name) : 'file';
|
||||
const displayName = f.name.match(/\.md$/i) ? f.name.replace(/\.md$/i, '') : f.name;
|
||||
const ext = f.extension || '';
|
||||
|
||||
const titleDiv = el('div', { class: 'search-result-title' }, [
|
||||
document.createTextNode(displayName),
|
||||
]);
|
||||
|
||||
// Format date
|
||||
const modDate = new Date(f.modified * 1000);
|
||||
const dateStr = modDate.toLocaleDateString('fr-CA', {
|
||||
year: 'numeric', month: 'short', day: 'numeric',
|
||||
hour: '2-digit', minute: '2-digit',
|
||||
});
|
||||
|
||||
const metaDiv = el('div', { class: 'search-result-vault' }, [
|
||||
document.createTextNode(`${dateStr} · ${formatSize(f.size)}`),
|
||||
]);
|
||||
|
||||
const item = el('div', {
|
||||
class: 'search-result-item vault-home-item',
|
||||
'data-vault': f.vault,
|
||||
'data-path': f.path,
|
||||
}, [
|
||||
el('span', { class: 'search-result-ext' }, [document.createTextNode(ext)]),
|
||||
el('i', { 'data-lucide': fileIconName, style: 'width:14px;height:14px;margin-right:4px;opacity:0.6' }),
|
||||
titleDiv,
|
||||
metaDiv,
|
||||
]);
|
||||
|
||||
item.addEventListener('click', () => TabManager.openPreview(f.vault, f.path));
|
||||
item.addEventListener('dblclick', (e) => {
|
||||
e.preventDefault();
|
||||
TabManager.openPersistent(f.vault, f.path);
|
||||
});
|
||||
|
||||
list.appendChild(item);
|
||||
});
|
||||
|
||||
container.appendChild(list);
|
||||
safeCreateIcons();
|
||||
} catch (err) {
|
||||
container.innerHTML = `
|
||||
<div class="vault-home-error">
|
||||
<p>Erreur de chargement: ${escapeHtml(err?.detail || err?.message || 'Inconnue')}</p>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Utility
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function formatSize(bytes) {
|
||||
if (bytes < 1024) return bytes + ' o';
|
||||
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' Ko';
|
||||
return (bytes / (1024 * 1024)).toFixed(1) + ' Mo';
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = str;
|
||||
return div.innerHTML;
|
||||
}
|
||||
@@ -3872,6 +3872,184 @@ body.resizing-v {
|
||||
}
|
||||
}
|
||||
|
||||
/* AI processing — white flash on the sync badge */
|
||||
.sync-badge--ai-processing {
|
||||
background: #ffffff !important;
|
||||
box-shadow: 0 0 6px 3px rgba(255, 255, 255, 0.6) !important;
|
||||
animation: ai-flash 0.6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes ai-flash {
|
||||
0%, 100% { opacity: 1; box-shadow: 0 0 6px 3px rgba(255,255,255,0.6); }
|
||||
50% { opacity: 0.3; box-shadow: 0 0 12px 6px rgba(255,255,255,0.9); }
|
||||
}
|
||||
|
||||
/* ── AI Status section inside sync panel ── */
|
||||
|
||||
.sync-panel__ai-section {
|
||||
border-top: 1px solid var(--border);
|
||||
padding: 10px 14px;
|
||||
}
|
||||
|
||||
.sync-panel__ai-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.sync-panel__ai-title {
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.sync-panel__ai-state {
|
||||
font-size: 0.75rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.sync-panel__ai-state i {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sync-panel__ai--active {
|
||||
color: #22c55e;
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
}
|
||||
|
||||
.sync-panel__ai--inactive {
|
||||
color: #f59e0b;
|
||||
background: rgba(245, 158, 11, 0.1);
|
||||
}
|
||||
|
||||
.sync-panel__ai--processing {
|
||||
color: #ffffff;
|
||||
background: rgba(59, 130, 246, 0.2);
|
||||
animation: ai-status-pulse 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.sync-panel__ai--unknown {
|
||||
color: var(--text-muted);
|
||||
background: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
@keyframes ai-status-pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.6; }
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
Vault Home Page
|
||||
--------------------------------------------------------------------------- */
|
||||
|
||||
.vault-home {
|
||||
padding: 16px 24px;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.vault-home-breadcrumb {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 2px;
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 16px;
|
||||
padding: 8px 12px;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.vault-home-breadcrumb-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
color: var(--text-secondary);
|
||||
transition: background 0.15s, color 0.15s;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.vault-home-breadcrumb-item:hover {
|
||||
background: color-mix(in srgb, var(--accent) 10%, transparent);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.vault-home-breadcrumb-root {
|
||||
font-weight: 600;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.vault-home-breadcrumb-sep {
|
||||
color: var(--text-muted);
|
||||
user-select: none;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.vault-home-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.vault-home-count {
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-secondary);
|
||||
padding: 3px 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.vault-home-dir-info {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-muted);
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.vault-home-results {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.vault-home-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem;
|
||||
color: var(--text-muted);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.vault-home-empty p {
|
||||
font-size: 0.85rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.vault-home-error {
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
color: var(--danger, #ef4444);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.vault-home-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.vault-home-item:hover {
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
Sync Panel — event log overlay
|
||||
--------------------------------------------------------------------------- */
|
||||
|
||||
Reference in New Issue
Block a user