feat: aperçu markdown formaté + metadata dans tooltip normal
All checks were successful
CI / lint (push) Successful in 13s
CI / security (push) Successful in 8s
CI / test (push) Successful in 16s
CI / build (push) Successful in 9s

This commit is contained in:
Bruno Charest 2026-05-29 23:45:26 -04:00
parent 2a74503bed
commit 4a916e80db

View File

@ -523,18 +523,34 @@ export const GraphViewManager = {
_showTooltip(node, screenX, screenY, isPreview) { _showTooltip(node, screenX, screenY, isPreview) {
if (!this._tooltipEl) return; if (!this._tooltipEl) return;
if (isPreview) return; // Preview tooltip handled by _showPreviewTooltip if (isPreview) return; // Preview tooltip handled by _showPreviewTooltip
const tags = (node.tags || []).slice(0, 5).join(', '); const cacheKey = `${this._vault}:${node.path}`;
const cached = this._previewCache[cacheKey];
const meta = cached?.meta;
// Build metadata summary if available
let metaHtml = '';
if (meta?.frontmatter && Object.keys(meta.frontmatter).length > 0) {
const fm = meta.frontmatter;
const parts = [];
if (fm.auteur) parts.push(`✍️ ${fm.auteur}`);
if (fm.date || fm.creation_date) parts.push(`📅 ${fm.date || fm.creation_date}`);
if (fm.statut) parts.push(`📌 ${fm.statut}`);
if (fm.publish) parts.push('✅ publié');
if (fm.favoris) parts.push('⭐ favoris');
if (parts.length > 0) metaHtml = `<br><span style="color:${COLORS.muted};font-size:0.65rem">${parts.join(' · ')}</span>`;
}
const tags = (meta?.tags || node.tags || []).slice(0, 5).join(', ');
const inc = node.incoming_count || 0; const inc = node.incoming_count || 0;
const out = node.outgoing_count || 0; const out = node.outgoing_count || 0;
this._tooltipEl.innerHTML = ` this._tooltipEl.innerHTML = `
<strong>${node.name}</strong> <strong>${meta?.title || node.name}</strong>
${node.type === 'file' ? `<br><span style="color:${COLORS.muted};font-size:0.7rem">${node.path}</span>` : ''} ${node.type === 'file' ? `<br><span style="color:${COLORS.muted};font-size:0.7rem">${node.path}</span>` : ''}
${metaHtml}
${tags ? `<br>🏷️ ${tags}` : ''} ${tags ? `<br>🏷️ ${tags}` : ''}
${inc + out > 0 ? `<br>🔗 ${out} sortants · ${inc} entrants` : ''} ${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> ${node.type === 'file' ? `<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.display = 'block';
this._tooltipEl.style.maxWidth = '250px'; this._tooltipEl.style.maxWidth = '280px';
this._tooltipEl.style.left = (screenX + 15) + 'px'; this._tooltipEl.style.left = (screenX + 15) + 'px';
this._tooltipEl.style.top = (screenY - 10) + 'px'; this._tooltipEl.style.top = (screenY - 10) + 'px';
}, },
@ -647,35 +663,64 @@ export const GraphViewManager = {
async _fetchPreview(node, screenX, screenY) { async _fetchPreview(node, screenX, screenY) {
const cacheKey = `${this._vault}:${node.path}`; const cacheKey = `${this._vault}:${node.path}`;
if (this._previewCache[cacheKey] !== undefined) { if (this._previewCache[cacheKey] !== undefined) {
this._showPreviewTooltip(node, this._previewCache[cacheKey], screenX, screenY); const cached = this._previewCache[cacheKey];
this._showPreviewTooltip(node, cached.preview, screenX, screenY);
return; return;
} }
// Show loading indicator // Show loading indicator
this._showPreviewTooltip(node, 'Chargement...', screenX, screenY); this._showPreviewTooltip(node, '<em>Chargement...</em>', screenX, screenY);
try { try {
const data = await api(`/api/file/${encodeURIComponent(this._vault)}/raw?path=${encodeURIComponent(node.path)}`); const data = await api(`/api/file/${encodeURIComponent(this._vault)}?path=${encodeURIComponent(node.path)}`);
const content = (data.raw || '').substring(0, 400).replace(/\n/g, ' '); // Strip HTML tags, keep line breaks, limit to 600 chars
this._previewCache[cacheKey] = content; const text = (data.html || '').replace(/<[^>]*>/g, '').replace(/\n+/g, '\n').trim();
this._showPreviewTooltip(node, content, screenX, screenY); const preview = text.substring(0, 600) + (text.length > 600 ? '…' : '');
// Cache both preview text and full metadata for tooltips
this._previewCache[cacheKey] = {
preview,
meta: {
title: data.title,
frontmatter: data.frontmatter || {},
tags: data.tags || [],
path: node.path,
}
};
this._showPreviewTooltip(node, preview, screenX, screenY);
} catch { } catch {
this._previewCache[cacheKey] = '(impossible de charger le contenu)'; this._previewCache[cacheKey] = { preview: '(impossible de charger)', meta: null };
this._showPreviewTooltip(node, '(impossible de charger le contenu)', screenX, screenY); this._showPreviewTooltip(node, '(impossible de charger le contenu)', screenX, screenY);
} }
}, },
_showPreviewTooltip(node, preview, screenX, screenY) { _showPreviewTooltip(node, preview, screenX, screenY) {
if (!this._tooltipEl || this._ctrlHoverNode !== node) return; if (!this._tooltipEl || this._ctrlHoverNode !== node) return;
const tags = (node.tags || []).slice(0, 3).join(', '); const cacheKey = `${this._vault}:${node.path}`;
const cached = this._previewCache[cacheKey];
const meta = cached?.meta;
// Format frontmatter summary
let fmSummary = '';
if (meta?.frontmatter && Object.keys(meta.frontmatter).length > 0) {
const fm = meta.frontmatter;
const items = [];
if (fm.auteur) items.push(`✍️ ${fm.auteur}`);
if (fm.date || fm.creation_date) items.push(`📅 ${fm.date || fm.creation_date}`);
if (fm.statut) items.push(`📌 ${fm.statut}`);
if (fm.catégorie || fm.categorie) items.push(`📂 ${fm.catégorie || fm.categorie}`);
if (fm.publish) items.push('✅ publié');
if (fm.favoris) items.push('⭐ favoris');
if (items.length > 0) fmSummary = `<div style="font-size:0.65rem;color:${COLORS.muted};margin:3px 0">${items.join(' · ')}</div>`;
}
const tags = (meta?.tags || node.tags || []).slice(0, 5).join(', ');
this._tooltipEl.innerHTML = ` this._tooltipEl.innerHTML = `
<strong>${node.name}</strong> <strong>${meta?.title || node.name}</strong>
<span style="color:${COLORS.muted};font-size:0.65rem;display:block;margin:2px 0">${node.path}</span> <span style="color:${COLORS.muted};font-size:0.65rem;display:block;margin:2px 0">${node.path}</span>
${fmSummary}
${tags ? `<span style="color:${COLORS.accent};font-size:0.65rem">🏷️ ${tags}</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> <div style="margin-top:4px;padding-top:4px;border-top:1px solid ${COLORS.border};font-size:0.75rem;color:${COLORS.text};line-height:1.5;max-height:150px;overflow-y:auto;white-space:pre-wrap;word-break:break-word">${preview}</div>
`; `;
this._tooltipEl.style.display = 'block'; this._tooltipEl.style.display = 'block';
this._tooltipEl.style.maxWidth = '350px'; this._tooltipEl.style.maxWidth = '420px';
this._tooltipEl.style.left = Math.min(screenX + 15, window.innerWidth - 370) + 'px'; this._tooltipEl.style.left = Math.min(screenX + 15, window.innerWidth - 440) + 'px';
this._tooltipEl.style.top = Math.min(screenY - 10, window.innerHeight - 180) + 'px'; this._tooltipEl.style.top = Math.min(screenY - 10, window.innerHeight - 220) + 'px';
}, },
_onMouseUp(e) { _onMouseUp(e) {