From 690965df7dad1664b54989966553f08ca8ca5ee2 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 17 May 2026 17:43:27 +0000 Subject: [PATCH] v0.4 Phase 4: callout tags - Extend renderer.code to match `mdcms ` fence syntax (e.g. ```mdcms callout-info) - Extend parseMdcmsTag to capture body lines after the key-value block - Add renderCalloutTag: icon + title row, markdown body, colour CSS vars - Add hexToRgba helper for low-opacity background colour - Make themeConfig module-level so callout renderer can read callout defaults - Add callout CSS: left border, title row flex layout, icon fill - Add reusable message: key support with category-aware language resolution - Add aitranslation callout message to config.yml for test - Update home.md with full Phase 4 test cases https://claude.ai/code/session_01UP8Wo2CKPNhvvTkzX48CWF --- app/config.yml | 10 ++++ app/index.html | 135 +++++++++++++++++++++++++++++++++++++++++++--- app/pages/home.md | 98 ++++++++++++++++++--------------- 3 files changed, 192 insertions(+), 51 deletions(-) diff --git a/app/config.yml b/app/config.yml index 0da7820..d4f01c7 100644 --- a/app/config.yml +++ b/app/config.yml @@ -43,3 +43,13 @@ theme: theme.yml # presentational config — edit theme.yml to custo # ────────────────────────────────── # search: true # default-theme: system # light | dark | system + +# ────────────────────────────────── +# Reusable callout messages (optional) +# ────────────────────────────────── +callouts: + aitranslation: + type: warning + en: + title: "PLEASE NOTE:" + text: This page has been translated with artificial intelligence. It has not been reviewed by staff yet. diff --git a/app/index.html b/app/index.html index 9576974..30035d6 100644 --- a/app/index.html +++ b/app/index.html @@ -862,6 +862,32 @@ body { } .post-load-more:hover { background: var(--nav-hover-bg); } +/* ── Callout tags ──────────────────────────────────────── */ +.mdcms-callout { + border-left: 4px solid var(--callout-primary, var(--accent)); + background: var(--callout-bg, transparent); + border-radius: 0 0.4rem 0.4rem 0; + padding: 0.75rem 1rem; + margin: 1rem 0; +} +.mdcms-callout-title { + display: flex; + align-items: center; + gap: 0.45rem; + font-weight: 700; + color: var(--callout-primary, var(--accent)); + margin-bottom: 0.4rem; +} +.mdcms-callout-title .mdcms-icon svg { + fill: var(--callout-primary, var(--accent)); + width: 1.2em; + height: 1.2em; + display: block; +} +.mdcms-callout-body { margin: 0; } +.mdcms-callout-body > :first-child { margin-top: 0; } +.mdcms-callout-body > :last-child { margin-bottom: 0; } + @media print { .sidebar, .topbar, .scroll-top, .hamburger, .mobile-header, .theme-toggle, .search-container { display: none !important; } @@ -893,6 +919,7 @@ body { let searchIndex = []; let fuseInstance = null; let currentPage = null; + let themeConfig = {}; // Category state (phase 3) let categoriesUse = false; @@ -1367,8 +1394,11 @@ body { } else { codeText = code; codeLang = lang; } - if (codeLang === 'mdcms') { - const tag = parseMdcmsTag(codeText); + // Match both ```mdcms (type in content) and ```mdcms callout-info (type in fence) + if (codeLang && (codeLang === 'mdcms' || codeLang.startsWith('mdcms '))) { + const fenceType = codeLang === 'mdcms' ? '' : codeLang.slice('mdcms '.length).trim(); + const fullText = fenceType ? (fenceType + '\n' + (codeText || '')) : (codeText || ''); + const tag = parseMdcmsTag(fullText); const encoded = JSON.stringify(tag).replace(/&/g, '&').replace(/"/g, '"'); return '
'; } @@ -1487,11 +1517,18 @@ function fmtDatetime(dtStr) { var lines = text.trim().split('\n'); var tagName = lines[0].trim(); var options = {}; + var bodyStart = lines.length; for (var i = 1; i < lines.length; i++) { - var m = lines[i].match(/^\s*([a-z\-]+)\s*:\s*(.+)$/i); - if (m) options[m[1].toLowerCase()] = m[2].trim(); + var m = lines[i].match(/^\s*([a-z\-]+)\s*:\s*(.*)$/i); + if (m) { + options[m[1].toLowerCase()] = m[2].trim(); + } else { + bodyStart = i; + break; + } } - return { tagName: tagName, options: options }; + var body = lines.slice(bodyStart).join('\n').trim(); + return { tagName: tagName, options: options, body: body }; } function parsePostTagName(name) { @@ -1769,11 +1806,96 @@ function fmtDatetime(dtStr) { renderYear(); } + // Callout type defaults (fallback when theme.yml has no callouts block) + const CALLOUT_DEFAULTS = { + info: { icon: 'info', colour: '#2563EB' }, + warning: { icon: 'warning', colour: '#D97706' }, + success: { icon: 'success', colour: '#16A34A' }, + error: { icon: 'error', colour: '#DC2626' }, + }; + + function renderCalloutTag(container, tag) { + var typeMatch = tag.tagName.match(/^callout-(info|warning|success|error)$/); + var calloutType = typeMatch ? typeMatch[1] : 'info'; + + var opts = tag.options; + var msgKey = opts.message || null; + var title = opts.title || null; + var iconName = opts.icon || null; + var bodyMd = tag.body || ''; + + // Resolve message: key — config.yml callouts block + if (msgKey) { + var msgDefs = config.callouts || {}; + var msgDef = msgDefs[msgKey]; + if (msgDef) { + // Override callout type from message definition + if (msgDef.type) calloutType = msgDef.type; + // Language resolution: activeCategory → defaultCategoryCode → first key + var lang = activeCategory || defaultCategoryCode; + var langEntry = (lang && msgDef[lang]) || msgDef[defaultCategoryCode]; + if (!langEntry) { + var keys = Object.keys(msgDef).filter(function(k) { return k !== 'type'; }); + langEntry = msgDef[keys[0]]; + } + if (langEntry) { + title = langEntry.title || null; + bodyMd = langEntry.text || ''; + } + if (opts.title || tag.body) { + console.warn('[mdcms] callout: message: key takes precedence; inline title/body ignored.'); + } + } + } + + // Get callout colours/icon from theme.yml callouts block, then fallback + var themeCallouts = (themeConfig.callouts || {})[calloutType] || {}; + var fallback = CALLOUT_DEFAULTS[calloutType] || CALLOUT_DEFAULTS.info; + var primaryColour = themeCallouts['primary-colour'] || fallback.colour; + var bgColour = themeCallouts['background-colour'] || fallback.colour; + if (!iconName) iconName = themeCallouts.icon || fallback.icon; + + // Build element + container.className = 'mdcms-callout mdcms-callout-' + calloutType; + container.style.setProperty('--callout-primary', primaryColour); + container.style.setProperty('--callout-bg', hexToRgba(bgColour, 0.08)); + + if (title) { + var titleRow = document.createElement('div'); + titleRow.className = 'mdcms-callout-title'; + titleRow.appendChild(iconEl(iconName)); + var titleText = document.createElement('span'); + titleText.textContent = title; + titleRow.appendChild(titleText); + container.appendChild(titleRow); + } + + if (bodyMd) { + var bodyEl = document.createElement('div'); + bodyEl.className = 'mdcms-callout-body'; + bodyEl.innerHTML = marked.parse(bodyMd); + container.appendChild(bodyEl); + } + } + + function hexToRgba(hex, alpha) { + var h = hex.replace('#', ''); + if (h.length === 3) h = h[0]+h[0]+h[1]+h[1]+h[2]+h[2]; + var r = parseInt(h.substring(0,2), 16); + var g = parseInt(h.substring(2,4), 16); + var b = parseInt(h.substring(4,6), 16); + return 'rgba(' + r + ',' + g + ',' + b + ',' + alpha + ')'; + } + function hydrateMdcmsTags() { document.querySelectorAll('.mdcms-tag').forEach(function(tagEl) { try { var cfg = JSON.parse(tagEl.getAttribute('data-config')); - renderPostTag(tagEl, cfg); + if (/^callout-(info|warning|success|error)$/.test(cfg.tagName)) { + renderCalloutTag(tagEl, cfg); + } else { + renderPostTag(tagEl, cfg); + } } catch (e) { tagEl.textContent = 'Error rendering tag.'; } @@ -2508,7 +2630,6 @@ function fmtDatetime(dtStr) { if (link) link.href = `assets/images/${config.logo}`; } - let themeConfig = {}; if (config.theme) { try { const themeResp = await fetch(config.theme); diff --git a/app/pages/home.md b/app/pages/home.md index ccf41dc..d076ea1 100644 --- a/app/pages/home.md +++ b/app/pages/home.md @@ -3,65 +3,75 @@ title: Home sort: 100 --- -# MD-CMS +# Phase 4 — Callout Tags -This is the default startpage for MD-CMS. +Check each callout below. Each should show a coloured left border, an icon, a bold title in the accent colour, and a rendered body. -## Testing MD-CMS +--- -If you want to test `MD-CMS` you can grab `samplesite` from the repo and place the content in your website root. This page (`pages/home.md`) won't be replaced. +## Basic types -**Post listing tests** below contains various custom tags to display posts. There are no posts now, but if you download the `samplesite` it will fetch the posts in +```mdcms callout-info +title: Information +This is an **info** callout. Supports *italic*, `code`, and lists: -## Post listing tests - -## Reverse chronological (newest first) - -```mdcms -posts-created-reversechronological -limit: 3 -paginate: no +- Item one +- Item two ``` -## Chronological (oldest first) - -```mdcms -posts-created-chronological -limit: all -paginate: none +```mdcms callout-warning +title: Warning +Something needs your attention. This is a **warning** callout. ``` -## By year (reverse chrono) - -```mdcms -posts-created-reversechronological-byyear -limit: all -defaultyear: current -selectyear: yes -paginate: none +```mdcms callout-success +title: Success +The operation completed successfully. This is a **success** callout. ``` -## By year+month (chrono) - -```mdcms -posts-created-chronological-byyearmonth -limit: all -defaultyear: 2024 -selectyear: yes +```mdcms callout-error +title: Error +Something went wrong. This is an **error** callout. ``` -## Last 30 days +--- -```mdcms -posts-created-reversechronological-lastmonth -limit: all -paginate: none +## No title + +```mdcms callout-info +No title key here. The title row should not appear at all — just the body. ``` -## Paginated (2 per page) +--- -```mdcms -posts-created-reversechronological -limit: 2 -paginate: yes +## Markdown body + +```mdcms callout-warning +title: Rich body +- List item one +- List item two + +A paragraph with `inline code` and a [link](https://example.com). ``` + +--- + +## Custom icon override + +```mdcms callout-info +title: Info with warning icon +icon: warning +This info callout uses the warning icon instead of the default info icon. +``` + +--- + +## Config-defined message (message: key) + +```mdcms callout-warning +message: aitranslation +``` + +--- + +Toggle dark mode and check all four callout types still look correct.