diff --git a/frontend/app.js b/frontend/app.js index b5cee4a..57e29d0 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -2183,20 +2183,10 @@ window.open(popoutUrl, `popout_${data.vault}_${data.path.replace(/[^a-zA-Z0-9]/g, '_')}`, 'width=1000,height=700,menubar=no,toolbar=no,location=no,status=no,resizable=yes,scrollbars=no'); }); - // Frontmatter + // Frontmatter — Accent Card let fmSection = null; if (data.frontmatter && Object.keys(data.frontmatter).length > 0) { - const fmToggle = el("div", { class: "frontmatter-toggle" }, [ - document.createTextNode("▶ Frontmatter"), - ]); - const fmContent = el("div", { class: "frontmatter-content" }, [ - document.createTextNode(JSON.stringify(data.frontmatter, null, 2)), - ]); - fmToggle.addEventListener("click", () => { - fmContent.classList.toggle("open"); - fmToggle.textContent = fmContent.classList.contains("open") ? "▼ Frontmatter" : "▶ Frontmatter"; - }); - fmSection = el("div", {}, [fmToggle, fmContent]); + fmSection = buildFrontmatterCard(data.frontmatter); } // Content container (rendered HTML) @@ -3419,6 +3409,186 @@ }); } + // --------------------------------------------------------------------------- + // Frontmatter Accent Card Builder + // --------------------------------------------------------------------------- + + function buildFrontmatterCard(frontmatter) { + // Helper: format date + function formatDate(iso) { + if (!iso) return '—'; + const d = new Date(iso); + const date = d.toISOString().slice(0, 10); + const time = d.toTimeString().slice(0, 5); + return `${date} · ${time}`; + } + + // Extract boolean flags + const booleanFlags = ['publish', 'favoris', 'template', 'task', 'archive', 'draft', 'private'] + .map(key => ({ key, value: !!frontmatter[key] })); + + // Toggle state + let isOpen = true; + + // Build header with chevron + const chevron = el("span", { class: "fm-chevron open" }); + chevron.innerHTML = ''; + + const fmHeader = el("div", { class: "fm-header" }, [ + chevron, + document.createTextNode("Frontmatter") + ]); + + // ZONE 1: Top strip + const topBadges = []; + + // Title badge + const title = frontmatter.titre || frontmatter.title || ''; + if (title) { + topBadges.push(el("span", { class: "ac-title" }, [document.createTextNode(`"${title}"`)])); + } + + // Status badge + if (frontmatter.statut) { + const statusBadge = el("span", { class: "ac-badge green" }, [ + el("span", { class: "ac-dot" }), + document.createTextNode(frontmatter.statut) + ]); + topBadges.push(statusBadge); + } + + // Category badge + if (frontmatter.catégorie || frontmatter.categorie) { + const cat = frontmatter.catégorie || frontmatter.categorie; + const catBadge = el("span", { class: "ac-badge blue" }, [ + document.createTextNode(cat) + ]); + topBadges.push(catBadge); + } + + // Publish badge + if (frontmatter.publish) { + topBadges.push(el("span", { class: "ac-badge purple" }, [ + document.createTextNode("publié") + ])); + } + + // Favoris badge + if (frontmatter.favoris) { + topBadges.push(el("span", { class: "ac-badge purple" }, [ + document.createTextNode("favori") + ])); + } + + const acTop = el("div", { class: "ac-top" }, topBadges); + + // ZONE 2: Body 2 columns + const leftCol = el("div", { class: "ac-col" }, [ + el("div", { class: "ac-row" }, [ + el("span", { class: "ac-k" }, [document.createTextNode("auteur")]), + el("span", { class: "ac-v" }, [document.createTextNode(frontmatter.auteur || '—')]) + ]), + el("div", { class: "ac-row" }, [ + el("span", { class: "ac-k" }, [document.createTextNode("catégorie")]), + el("span", { class: "ac-v" }, [document.createTextNode(frontmatter.catégorie || frontmatter.categorie || '—')]) + ]), + el("div", { class: "ac-row" }, [ + el("span", { class: "ac-k" }, [document.createTextNode("statut")]), + el("span", { class: "ac-v" }, [document.createTextNode(frontmatter.statut || '—')]) + ]), + el("div", { class: "ac-row" }, [ + el("span", { class: "ac-k" }, [document.createTextNode("aliases")]), + el("span", { class: "ac-v muted" }, [ + document.createTextNode( + frontmatter.aliases && frontmatter.aliases.length > 0 + ? frontmatter.aliases.join(', ') + : '[]' + ) + ]) + ]) + ]); + + const rightCol = el("div", { class: "ac-col" }, [ + el("div", { class: "ac-row" }, [ + el("span", { class: "ac-k" }, [document.createTextNode("creation_date")]), + el("span", { class: "ac-v mono" }, [document.createTextNode(formatDate(frontmatter.creation_date))]) + ]), + el("div", { class: "ac-row" }, [ + el("span", { class: "ac-k" }, [document.createTextNode("modification_date")]), + el("span", { class: "ac-v mono" }, [document.createTextNode(formatDate(frontmatter.modification_date))]) + ]), + el("div", { class: "ac-row" }, [ + el("span", { class: "ac-k" }, [document.createTextNode("publish")]), + el("span", { class: "ac-v" }, [document.createTextNode(String(frontmatter.publish || false))]) + ]), + el("div", { class: "ac-row" }, [ + el("span", { class: "ac-k" }, [document.createTextNode("favoris")]), + el("span", { class: "ac-v" }, [document.createTextNode(String(frontmatter.favoris || false))]) + ]) + ]); + + const acBody = el("div", { class: "ac-body" }, [leftCol, rightCol]); + + // ZONE 3: Tags row + const tagPills = []; + if (frontmatter.tags && frontmatter.tags.length > 0) { + frontmatter.tags.forEach(tag => { + tagPills.push(el("span", { class: "ac-tag" }, [document.createTextNode(tag)])); + }); + } + + const acTagsRow = el("div", { class: "ac-tags-row" }, [ + el("span", { class: "ac-tags-k" }, [document.createTextNode("tags")]), + el("div", { class: "ac-tags-wrap" }, tagPills) + ]); + + // ZONE 4: Flags row + const flagChips = []; + booleanFlags.forEach(flag => { + const chipClass = flag.value ? "flag-chip on" : "flag-chip off"; + flagChips.push(el("span", { class: chipClass }, [ + el("span", { class: "flag-dot" }), + document.createTextNode(flag.key) + ])); + }); + + const acFlagsRow = el("div", { class: "ac-flags-row" }, [ + el("span", { class: "ac-flags-k" }, [document.createTextNode("flags")]), + ...flagChips + ]); + + // Assemble the card + const acCard = el("div", { class: "ac-card" }, [ + acTop, + acBody, + acTagsRow, + acFlagsRow + ]); + + // Toggle functionality + fmHeader.addEventListener("click", () => { + isOpen = !isOpen; + if (isOpen) { + acCard.style.display = "block"; + chevron.classList.remove("closed"); + chevron.classList.add("open"); + } else { + acCard.style.display = "none"; + chevron.classList.remove("open"); + chevron.classList.add("closed"); + } + safeCreateIcons(); + }); + + // Wrap in section + const fmSection = el("div", { class: "fm-section" }, [ + fmHeader, + acCard + ]); + + return fmSection; + } + // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- diff --git a/frontend/popout.html b/frontend/popout.html index 9a6e907..2c9571c 100644 --- a/frontend/popout.html +++ b/frontend/popout.html @@ -135,23 +135,9 @@ header.appendChild(actionsDiv); area.appendChild(header); - // Frontmatter + // Frontmatter — Accent Card if (data.frontmatter && Object.keys(data.frontmatter).length > 0) { - const fmSection = document.createElement("div"); - const fmToggle = document.createElement("div"); - fmToggle.className = "frontmatter-toggle"; - fmToggle.textContent = "▶ Frontmatter"; - const fmContent = document.createElement("div"); - fmContent.className = "frontmatter-content"; - fmContent.textContent = JSON.stringify(data.frontmatter, null, 2); - - fmToggle.addEventListener("click", () => { - fmContent.classList.toggle("open"); - fmToggle.textContent = fmContent.classList.contains("open") ? "▼ Frontmatter" : "▶ Frontmatter"; - }); - - fmSection.appendChild(fmToggle); - fmSection.appendChild(fmContent); + const fmSection = buildFrontmatterCard(data.frontmatter); area.appendChild(fmSection); } @@ -197,6 +183,186 @@ return item; } + function buildFrontmatterCard(frontmatter) { + // Helper: format date + function formatDate(iso) { + if (!iso) return '—'; + const d = new Date(iso); + const date = d.toISOString().slice(0, 10); + const time = d.toTimeString().slice(0, 5); + return `${date} · ${time}`; + } + + // Helper: create element + function el(tag, className, children) { + const e = document.createElement(tag); + if (className) e.className = className; + if (children) { + children.forEach(c => { if (c) e.appendChild(c); }); + } + return e; + } + + // Extract boolean flags + const booleanFlags = ['publish', 'favoris', 'template', 'task', 'archive', 'draft', 'private'] + .map(key => ({ key, value: !!frontmatter[key] })); + + // Toggle state + let isOpen = true; + + // Build header with chevron + const chevron = el("span", "fm-chevron open"); + chevron.innerHTML = ''; + + const fmHeader = el("div", "fm-header"); + fmHeader.appendChild(chevron); + fmHeader.appendChild(document.createTextNode("Frontmatter")); + + // ZONE 1: Top strip + const acTop = el("div", "ac-top"); + + // Title badge + const title = frontmatter.titre || frontmatter.title || ''; + if (title) { + const titleSpan = el("span", "ac-title"); + titleSpan.textContent = `"${title}"`; + acTop.appendChild(titleSpan); + } + + // Status badge + if (frontmatter.statut) { + const statusBadge = el("span", "ac-badge green"); + const dot = el("span", "ac-dot"); + statusBadge.appendChild(dot); + statusBadge.appendChild(document.createTextNode(frontmatter.statut)); + acTop.appendChild(statusBadge); + } + + // Category badge + if (frontmatter.catégorie || frontmatter.categorie) { + const cat = frontmatter.catégorie || frontmatter.categorie; + const catBadge = el("span", "ac-badge blue"); + catBadge.textContent = cat; + acTop.appendChild(catBadge); + } + + // Publish badge + if (frontmatter.publish) { + const pubBadge = el("span", "ac-badge purple"); + pubBadge.textContent = "publié"; + acTop.appendChild(pubBadge); + } + + // Favoris badge + if (frontmatter.favoris) { + const favBadge = el("span", "ac-badge purple"); + favBadge.textContent = "favori"; + acTop.appendChild(favBadge); + } + + // ZONE 2: Body 2 columns + const leftCol = el("div", "ac-col"); + + const row1 = el("div", "ac-row"); + row1.innerHTML = 'auteur' + (frontmatter.auteur || '—') + ''; + leftCol.appendChild(row1); + + const row2 = el("div", "ac-row"); + row2.innerHTML = 'catégorie' + (frontmatter.catégorie || frontmatter.categorie || '—') + ''; + leftCol.appendChild(row2); + + const row3 = el("div", "ac-row"); + row3.innerHTML = 'statut' + (frontmatter.statut || '—') + ''; + leftCol.appendChild(row3); + + const row4 = el("div", "ac-row"); + const aliases = frontmatter.aliases && frontmatter.aliases.length > 0 ? frontmatter.aliases.join(', ') : '[]'; + row4.innerHTML = 'aliases' + aliases + ''; + leftCol.appendChild(row4); + + const rightCol = el("div", "ac-col"); + + const row5 = el("div", "ac-row"); + row5.innerHTML = 'creation_date' + formatDate(frontmatter.creation_date) + ''; + rightCol.appendChild(row5); + + const row6 = el("div", "ac-row"); + row6.innerHTML = 'modification_date' + formatDate(frontmatter.modification_date) + ''; + rightCol.appendChild(row6); + + const row7 = el("div", "ac-row"); + row7.innerHTML = 'publish' + String(frontmatter.publish || false) + ''; + rightCol.appendChild(row7); + + const row8 = el("div", "ac-row"); + row8.innerHTML = 'favoris' + String(frontmatter.favoris || false) + ''; + rightCol.appendChild(row8); + + const acBody = el("div", "ac-body"); + acBody.appendChild(leftCol); + acBody.appendChild(rightCol); + + // ZONE 3: Tags row + const acTagsRow = el("div", "ac-tags-row"); + const tagsLabel = el("span", "ac-tags-k"); + tagsLabel.textContent = "tags"; + acTagsRow.appendChild(tagsLabel); + + const tagsWrap = el("div", "ac-tags-wrap"); + if (frontmatter.tags && frontmatter.tags.length > 0) { + frontmatter.tags.forEach(tag => { + const tagSpan = el("span", "ac-tag"); + tagSpan.textContent = tag; + tagsWrap.appendChild(tagSpan); + }); + } + acTagsRow.appendChild(tagsWrap); + + // ZONE 4: Flags row + const acFlagsRow = el("div", "ac-flags-row"); + const flagsLabel = el("span", "ac-flags-k"); + flagsLabel.textContent = "flags"; + acFlagsRow.appendChild(flagsLabel); + + booleanFlags.forEach(flag => { + const chipClass = flag.value ? "flag-chip on" : "flag-chip off"; + const chip = el("span", chipClass); + const dot = el("span", "flag-dot"); + chip.appendChild(dot); + chip.appendChild(document.createTextNode(flag.key)); + acFlagsRow.appendChild(chip); + }); + + // Assemble the card + const acCard = el("div", "ac-card"); + acCard.appendChild(acTop); + acCard.appendChild(acBody); + acCard.appendChild(acTagsRow); + acCard.appendChild(acFlagsRow); + + // Toggle functionality + fmHeader.addEventListener("click", () => { + isOpen = !isOpen; + if (isOpen) { + acCard.style.display = "block"; + chevron.classList.remove("closed"); + chevron.classList.add("open"); + } else { + acCard.style.display = "none"; + chevron.classList.remove("open"); + chevron.classList.add("closed"); + } + if (window.lucide) window.lucide.createIcons(); + }); + + // Wrap in section + const fmSection = el("div", "fm-section"); + fmSection.appendChild(fmHeader); + fmSection.appendChild(acCard); + + return fmSection; + } + window.onload = initPopout; diff --git a/frontend/style.css b/frontend/style.css index 862e396..e208722 100644 --- a/frontend/style.css +++ b/frontend/style.css @@ -30,6 +30,25 @@ --danger-bg: #3d1a18; --success: #3fb950; --success-bg: #1a3d1f; + + /* Accent Card variables */ + --accent-card: #58a6ff; + --accent-bg: #1f2a3a; + --accent-text: #79c0ff; + --green: #3fb950; + --green-bg: #1a3d1f; + --purple: #a371f7; + --purple-bg: #2d1b4e; + --surface: #161b22; + --surface2: #0f1117; + --surface3: #0d0f14; + --border-md: #30363d; + --text: #e6edf3; + --text-2: #8b949e; + --text-3: #484f58; + --mono: 'JetBrains Mono', monospace; + --radius: 6px; + --radius-lg: 10px; } /* ===== THEME — LIGHT ===== */ @@ -55,6 +74,25 @@ --danger-bg: #ffebe9; --success: #1a7f37; --success-bg: #dafbe1; + + /* Accent Card variables */ + --accent-card: #4f6ef7; + --accent-bg: #eef1fe; + --accent-text: #2540c9; + --green: #1a7f50; + --green-bg: #eaf5ee; + --purple: #6b40c4; + --purple-bg: #f1ecfd; + --surface: #ffffff; + --surface2: #f2f1ef; + --surface3: #ebe9e6; + --border-md: rgba(0,0,0,0.13); + --text: #1a1917; + --text-2: #6b6a67; + --text-3: #a09e9b; + --mono: 'JetBrains Mono', monospace; + --radius: 6px; + --radius-lg: 10px; } /* ===== BASE ===== */ @@ -1061,35 +1099,214 @@ select { background: var(--tag-bg); } -/* Frontmatter collapsible */ -.frontmatter-toggle { +/* ===== FRONTMATTER — ACCENT CARD ===== */ + +/* Frontmatter section wrapper */ +.fm-section { + margin-bottom: 20px; +} + +/* Header toggle */ +.fm-header { font-family: 'JetBrains Mono', monospace; font-size: 0.78rem; color: var(--text-muted); cursor: pointer; display: inline-flex; align-items: center; - gap: 4px; + gap: 6px; margin-bottom: 12px; user-select: none; + transition: color 150ms ease; } -.frontmatter-toggle:hover { +.fm-header:hover { color: var(--text-secondary); } -.frontmatter-content { - background: var(--code-bg); - border: 1px solid var(--border); - border-radius: 8px; + +/* Chevron icon */ +.fm-chevron { + transition: transform 0.2s; + display: inline-flex; + align-items: center; + justify-content: center; +} +.fm-chevron.open { + transform: rotate(0deg); +} +.fm-chevron.closed { + transform: rotate(-90deg); +} + +/* Accent Card main container */ +.ac-card { + border: 1px solid var(--border-md); + border-left: 3px solid var(--accent-card); + border-radius: 0 var(--radius-lg) var(--radius-lg) 0; + background: var(--surface); + overflow: hidden; +} + +/* ZONE 1 — Top strip */ +.ac-top { + padding: 12px 16px 10px; + border-bottom: 1px solid var(--border); + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; +} +.ac-title { + font-size: 14px; + font-weight: 600; + color: var(--text); + font-family: var(--mono); + letter-spacing: -0.01em; + flex-shrink: 0; +} +.ac-badge { + padding: 2px 9px; + border-radius: 99px; + font-size: 11px; + font-weight: 500; + display: inline-flex; + align-items: center; + gap: 4px; +} +.ac-badge.green { + background: var(--green-bg); + color: var(--green); +} +.ac-badge.blue { + background: var(--accent-bg); + color: var(--accent-text); +} +.ac-badge.purple { + background: var(--purple-bg); + color: var(--purple); +} +.ac-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: currentColor; + opacity: 0.7; +} + +/* ZONE 2 — Body 2 columns */ +.ac-body { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0; +} +.ac-col { padding: 12px 16px; - margin-bottom: 20px; - font-family: 'JetBrains Mono', monospace; - font-size: 0.8rem; - color: var(--text-secondary); - white-space: pre-wrap; - display: none; } -.frontmatter-content.open { - display: block; +.ac-col + .ac-col { + border-left: 1px solid var(--border); +} +.ac-row { + display: flex; + align-items: baseline; + gap: 6px; + padding: 4px 0; + font-size: 12.5px; +} +.ac-row + .ac-row { + border-top: 1px solid var(--border); +} +.ac-k { + color: var(--text-3); + font-size: 11px; + font-family: var(--mono); + min-width: 110px; + flex-shrink: 0; +} +.ac-v { + color: var(--text); + font-weight: 500; + font-size: 12.5px; +} +.ac-v.mono { + font-family: var(--mono); + font-weight: 400; + color: var(--text-2); + font-size: 11px; +} +.ac-v.muted { + color: var(--text-3); + font-weight: 400; +} + +/* ZONE 3 — Tags row */ +.ac-tags-row { + padding: 10px 16px; + border-top: 1px solid var(--border); + display: flex; + align-items: center; + gap: 10px; +} +.ac-tags-k { + font-size: 11px; + font-family: var(--mono); + color: var(--text-3); + flex-shrink: 0; +} +.ac-tags-wrap { + display: flex; + flex-wrap: wrap; + gap: 4px; +} +.ac-tag { + padding: 2px 9px; + border-radius: 99px; + font-size: 11px; + font-weight: 500; + background: var(--accent-bg); + color: var(--accent-text); + border: 1px solid rgba(79, 110, 247, 0.18); +} + +/* ZONE 4 — Flags row */ +.ac-flags-row { + padding: 9px 16px; + border-top: 1px solid var(--border); + background: var(--surface2); + display: flex; + flex-wrap: wrap; + gap: 6px; + align-items: center; +} +.ac-flags-k { + font-size: 11px; + font-family: var(--mono); + color: var(--text-3); + margin-right: 2px; +} +.flag-chip { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 8px; + border-radius: var(--radius); + font-size: 11px; + font-weight: 500; + border: 1px solid var(--border-md); +} +.flag-chip.on { + background: var(--green-bg); + color: var(--green); + border-color: rgba(26, 127, 80, 0.2); +} +.flag-chip.off { + background: var(--surface3); + color: var(--text-3); +} +.flag-dot { + width: 5px; + height: 5px; + border-radius: 50%; + background: currentColor; + opacity: 0.7; } /* --- Raw source view --- */