<% // @license MIT // @copyright 2026 Mickaël Canouil // @author Mickaël Canouil

// ============================================================================= // Talks listing — year-grouped speaking history. Items declared inline in // talks.qmd; no individual talk pages. Each row surfaces the metadata a // visitor actually wants when scanning a speaker’s record: when, where, // what, and which materials are still available. // =============================================================================

// item.date is already formatted by Quarto’s date-format, so Date(…) is // Invalid; pull the four-digit year via regex. function getYear(item) { const m = String(item.date || ““).match(/()/); return m ? m[1] : null; }

function renderCategoryChips(prefix, categories) { if (!categories || !categories.length) return ““; return categories .map((c) => <span class="${prefix}-category" onclick="window.quartoListingCategory('${utils.b64encode(c)}'); return false;">${c}</span>) .join(”“); }

const grouped = {}; for (const it of items) { const y = getYear(it) || “Undated”; if (!grouped[y]) grouped[y] = []; grouped[y].push(it); } const years = Object.keys(grouped).sort((a, b) => { if (a === “Undated”) return 1; if (b === “Undated”) return -1; return b.localeCompare(a); });

// Bootstrap icons render in any HTML context (unlike iconify shortcodes which // are not re-processed inside listing EJS output). const ACTIONS = [ { key: “slides”, icon: “bi-easel2”, label: “Slides” }, { key: “code”, icon: “bi-github”, label: “Code” }, { key: “video”, icon: “bi-play-circle-fill”, label: “Video” }, { key: “blog”, icon: “bi-newspaper”, label: “Blog” }, { key: “meetup”, icon: “bi-people-fill”, label: “Meetup” },]; %>

<% years.forEach(year => { const list = grouped[year]; %>

<%= year %> <%= list.length %> talk<%= list.length === 1 ? "" : "s" %>

    <% list.forEach(item => { const actions = ACTIONS.filter(a => item[a.key]); %>
  • >
    <% if (item.venue) { %>

    <%= item.venue %>

    <% } %>

    <%= item.title %>

    <% if (item.description) { %>

    <%= item.description %>

    <% } %> <% if (actions.length) { %>
    <% actions.forEach(a => { %> <%= a.label %> <% }); %>
    <% } %> <% if (item.categories && item.categories.length) { %>
    <%= renderCategoryChips("talks", item.categories) %>
    <% } %>
  • <% }); %>
<% }); %>
Back to top