mirror of
https://github.com/kbenestad/mdcms.git
synced 2026-06-18 15:24:32 +00:00
v0.4 Phase 4: callout tags
- Extend renderer.code to match `mdcms <type>` 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
This commit is contained in:
parent
8a39bc31e9
commit
690965df7d
3 changed files with 192 additions and 51 deletions
|
|
@ -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.
|
||||
|
|
|
|||
133
app/index.html
133
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 '<div class="mdcms-tag" data-config="' + encoded + '"></div>';
|
||||
}
|
||||
|
|
@ -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'));
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue