feat: header graph repensé + filtre tag + Ctrl+survol aperçu contenu
CI / lint (push) Successful in 13s
CI / security (push) Successful in 8s
CI / test (push) Successful in 14s
CI / build (push) Successful in 2s

This commit is contained in:
2026-05-29 22:58:22 -04:00
parent 5a76a48e28
commit 3de990cf7d
2 changed files with 87 additions and 13 deletions
+73 -5
View File
@@ -50,8 +50,12 @@ export const GraphViewManager = {
_scope: 'directory',
_depth: 1,
_searchTerm: '',
_tagFilter: '',
_hoveredNode: null,
_tooltipEl: null,
_previewCache: {},
_previewPending: null,
_ctrlHoverNode: null,
async open(vault, path, type) {
this._vault = vault;
@@ -85,7 +89,7 @@ export const GraphViewManager = {
_cacheKey: '',
_getCacheKey() {
return `${this._vault}|${this._path}|${this._depth}|${this._scope}|${this._searchTerm}`;
return `${this._vault}|${this._path}|${this._depth}|${this._scope}|${this._searchTerm}|${this._tagFilter}`;
},
async _fetchAndRender() {
@@ -111,7 +115,7 @@ export const GraphViewManager = {
if (this._path) params.set('path', this._path);
params.set('depth', String(this._depth));
params.set('scope', this._scope);
if (this._searchTerm) params.set('tag', this._searchTerm);
if (this._tagFilter) params.set('tag', this._tagFilter);
try {
const data = await api(
@@ -167,6 +171,11 @@ export const GraphViewManager = {
}
},
setTagFilter(tag) {
this._tagFilter = tag;
this.reload();
},
close() {
const modal = document.getElementById('graph-modal');
if (modal) modal.classList.remove('active');
@@ -511,8 +520,9 @@ export const GraphViewManager = {
return null;
},
_showTooltip(node, screenX, screenY) {
_showTooltip(node, screenX, screenY, isPreview) {
if (!this._tooltipEl) return;
if (isPreview) return; // Preview tooltip handled by _showPreviewTooltip
const tags = (node.tags || []).slice(0, 5).join(', ');
const inc = node.incoming_count || 0;
const out = node.outgoing_count || 0;
@@ -521,8 +531,10 @@ export const GraphViewManager = {
${node.type === 'file' ? `<br><span style="color:${COLORS.muted};font-size:0.7rem">${node.path}</span>` : ''}
${tags ? `<br>🏷️ ${tags}` : ''}
${inc + out > 0 ? `<br>🔗 ${out} sortants · ${inc} entrants` : ''}
<br><span style="color:${COLORS.muted};font-size:0.6rem;opacity:0.7">Ctrl+survol pour aperçu</span>
`;
this._tooltipEl.style.display = 'block';
this._tooltipEl.style.maxWidth = '250px';
this._tooltipEl.style.left = (screenX + 15) + 'px';
this._tooltipEl.style.top = (screenY - 10) + 'px';
},
@@ -609,17 +621,63 @@ export const GraphViewManager = {
if (hit) {
this._canvas.style.cursor = 'pointer';
this._canvas.title = hit.node.type === 'file'
? `📄 ${hit.node.name} (cliquer pour ouvrir)`
? `📄 ${hit.node.name} (cliquer pour ouvrir${e.ctrlKey ? ', Ctrl+survol = aperçu' : ''})`
: `📁 ${hit.node.name} (cliquer pour explorer)`;
this._showTooltip(hit.node, e.clientX, e.clientY);
// Ctrl+hover: fetch content preview for file nodes
if (e.ctrlKey && hit.node.type === 'file') {
if (this._ctrlHoverNode !== hit.node) {
this._ctrlHoverNode = hit.node;
this._fetchPreview(hit.node, e.clientX, e.clientY);
}
this._showTooltip(hit.node, e.clientX, e.clientY, true);
} else {
this._ctrlHoverNode = null;
this._showTooltip(hit.node, e.clientX, e.clientY, false);
}
} else {
this._canvas.style.cursor = 'grab';
this._canvas.title = '';
this._ctrlHoverNode = null;
this._hideTooltip();
}
}
},
async _fetchPreview(node, screenX, screenY) {
const cacheKey = `${this._vault}:${node.path}`;
if (this._previewCache[cacheKey] !== undefined) {
this._showPreviewTooltip(node, this._previewCache[cacheKey], screenX, screenY);
return;
}
// Show loading indicator
this._showPreviewTooltip(node, 'Chargement...', screenX, screenY);
try {
const data = await api(`/api/file/${encodeURIComponent(this._vault)}?path=${encodeURIComponent(node.path)}`);
const content = (data.content || '').substring(0, 400).replace(/\\n/g, ' ');
this._previewCache[cacheKey] = content;
this._showPreviewTooltip(node, content, screenX, screenY);
} catch {
this._previewCache[cacheKey] = '(impossible de charger le contenu)';
this._showPreviewTooltip(node, '(impossible de charger le contenu)', screenX, screenY);
}
},
_showPreviewTooltip(node, preview, screenX, screenY) {
if (!this._tooltipEl || this._ctrlHoverNode !== node) return;
const tags = (node.tags || []).slice(0, 3).join(', ');
this._tooltipEl.innerHTML = `
<strong>${node.name}</strong>
<span style="color:${COLORS.muted};font-size:0.65rem;display:block;margin:2px 0">${node.path}</span>
${tags ? `<span style="color:${COLORS.accent};font-size:0.65rem">🏷️ ${tags}</span>` : ''}
<div style="margin-top:4px;padding-top:4px;border-top:1px solid ${COLORS.border};font-size:0.7rem;color:${COLORS.text};line-height:1.4;max-height:120px;overflow:hidden">${preview}</div>
`;
this._tooltipEl.style.display = 'block';
this._tooltipEl.style.maxWidth = '350px';
this._tooltipEl.style.left = Math.min(screenX + 15, window.innerWidth - 370) + 'px';
this._tooltipEl.style.top = Math.min(screenY - 10, window.innerHeight - 180) + 'px';
},
_onMouseUp(e) {
if (this._dragging && this._dragNode) {
const node = this._dragNode.node;
@@ -698,6 +756,16 @@ export function initGraphView() {
});
}
// Tag filter input
const tagFilterInput = document.getElementById('graph-tag-filter');
if (tagFilterInput) {
let tagDebounce;
tagFilterInput.addEventListener('input', () => {
clearTimeout(tagDebounce);
tagDebounce = setTimeout(() => gm.setTagFilter(tagFilterInput.value.trim()), 400);
});
}
// Export PNG
const exportBtn = document.getElementById('graph-export');
if (exportBtn) exportBtn.addEventListener('click', () => gm.exportPNG());