Merge pull request #13 from kbenestad/claude/linux-install-docs-cwQsb

Claude/linux install docs cw qsb
This commit is contained in:
Kristian Benestad 2026-05-16 22:46:38 +07:00 committed by GitHub
commit 56c22f075d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 173 additions and 38 deletions

View file

@ -22,6 +22,7 @@
# ────────────────────────────────── # ──────────────────────────────────
sitename: MD-CMS New Site sitename: MD-CMS New Site
navigation: topbar # sidebar | topbar navigation: topbar # sidebar | topbar
theme: theme.yml # presentational config — edit theme.yml to customise colours, fonts, and layout
# homepage: pages/home.md # override the default landing page # homepage: pages/home.md # override the default landing page
@ -30,20 +31,13 @@ navigation: topbar # sidebar | topbar
# favicon: favicon.png # favicon: favicon.png
# footer: "© 2026 Your Name" # footer: "© 2026 Your Name"
# ──────────────────────────────────
# Typography (optional)
# ──────────────────────────────────
# font-title: "Inter:700"
# font-body: Inter
# font-code: JetBrains Mono
# ────────────────────────────────── # ──────────────────────────────────
# Layout (optional) # Layout (optional)
# ────────────────────────────────── # ──────────────────────────────────
# main-width: 80em
# nav-width: 20em
# nav-position: left # left | right (sidebar mode) # nav-position: left # left | right (sidebar mode)
# Typography and colours are configured in theme.yml, not here.
# ────────────────────────────────── # ──────────────────────────────────
# Features (optional) # Features (optional)
# ────────────────────────────────── # ──────────────────────────────────

View file

@ -117,6 +117,11 @@
--font-body-weight: 400; --font-body-weight: 400;
--main-width: 80em; --main-width: 80em;
--nav-width: 20em; --nav-width: 20em;
--line-height-body: 1.7;
--colour-info: #2563EB;
--colour-warning: #D97706;
--colour-success: #16A34A;
--colour-error: #DC2626;
} }
html { font-size: 16px; scroll-behavior: smooth; } html { font-size: 16px; scroll-behavior: smooth; }
@ -126,7 +131,7 @@ body {
font-weight: var(--font-body-weight); font-weight: var(--font-body-weight);
color: var(--font-colour); color: var(--font-colour);
background: var(--bg-main); background: var(--bg-main);
line-height: 1.7; line-height: var(--line-height-body);
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
transition: background-color 0.2s ease, color 0.2s ease; transition: background-color 0.2s ease, color 0.2s ease;
@ -1152,6 +1157,53 @@ body {
applyTheme(current === 'dark' ? 'light' : 'dark'); applyTheme(current === 'dark' ? 'light' : 'dark');
} }
function applyThemeYml(tc) {
if (!tc) return;
const root = document.documentElement;
const getOrCreateStyle = id => {
let s = document.getElementById(id);
if (!s) { s = document.createElement('style'); s.id = id; document.head.appendChild(s); }
return s;
};
let modeCss = '';
['light', 'dark'].forEach(mode => {
const m = tc[mode];
if (!m) return;
const vars = [];
if (m.accent) {
const rgb = hexToRgb(m.accent);
vars.push(`--accent: ${m.accent}`);
vars.push(`--accent-rgb: ${rgb}`);
vars.push(`--nav-active-bg: rgba(${rgb}, 0.10)`);
vars.push(`--nav-hover-bg: rgba(${rgb}, 0.05)`);
vars.push(`--table-header-bg: rgba(${rgb}, 0.08)`);
vars.push(`--link-colour: ${m.accent}`);
}
if (m.background) { vars.push(`--bg-main: ${m.background}`); vars.push(`--search-bg: ${m.background}`); }
if (m['nav-background']) vars.push(`--bg-nav: ${m['nav-background']}`);
if (m.text) { vars.push(`--font-colour: ${m.text}`); vars.push(`--code-font: ${m.text}`); }
if (m['text-muted']) vars.push(`--font-colour-muted: ${m['text-muted']}`);
if (vars.length) modeCss += `:root[data-theme="${mode}"] { ${vars.join('; ')}; }\n`;
});
if (modeCss) getOrCreateStyle('theme-overrides').textContent = modeCss;
if (tc['colours-semantic']) {
const sem = tc['colours-semantic'];
const semVars = [];
if (sem.info) semVars.push(`--colour-info: ${sem.info}`);
if (sem.warning) semVars.push(`--colour-warning: ${sem.warning}`);
if (sem.success) semVars.push(`--colour-success: ${sem.success}`);
if (sem.error) semVars.push(`--colour-error: ${sem.error}`);
if (semVars.length) getOrCreateStyle('theme-semantic').textContent = `:root { ${semVars.join('; ')}; }`;
}
if (tc['main-width']) root.style.setProperty('--main-width', tc['main-width']);
if (tc['nav-width']) root.style.setProperty('--nav-width', tc['nav-width']);
if (tc['line-height']) root.style.setProperty('--line-height-body', String(tc['line-height']));
if (tc['font-size']) document.documentElement.style.fontSize = `${tc['font-size'] * 16}px`;
}
function applyConfigTheme() { function applyConfigTheme() {
const root = document.documentElement; const root = document.documentElement;
['light', 'dark'].forEach(mode => { ['light', 'dark'].forEach(mode => {
@ -1211,35 +1263,48 @@ body {
} }
// ─── Fonts ──────────────────────────────────────────────── // ─── Fonts ────────────────────────────────────────────────
function loadFonts() { function loadFonts(tc) {
const fonts = {}; function parseFont(spec) {
['font-title', 'font-body', 'font-code'].forEach(key => { if (!spec) return null;
const val = config[key]; const parts = spec.split(':');
if (!val) return; if (parts.length >= 3) return { provider: parts[0].trim(), name: parts.slice(1, -1).join(':').trim(), weight: parts[parts.length - 1].trim() };
const [name, weight] = val.split(':'); if (parts.length === 2) return { provider: 'google', name: parts[0].trim(), weight: parts[1].trim() };
fonts[key] = { name: name.trim(), weight: weight ? weight.trim() : '400' }; return { provider: 'google', name: parts[0].trim(), weight: '400' };
}); }
const googleFonts = [];
Object.entries(fonts).forEach(([, { name, weight }]) => { const src = tc || {};
googleFonts.push(`${name.replace(/ /g, '+')}:wght@${weight}`); const bodyFont = parseFont(src['font-body'] || config['font-body']);
}); const headingFont = parseFont(src['font-heading'] || src['font-title'] || config['font-title']);
const codeFont = parseFont(src['font-code'] || config['font-code']);
const allFonts = [bodyFont, headingFont, codeFont].filter(Boolean);
const bunnyFonts = allFonts.filter(f => f.provider === 'bunny');
const googleFonts = allFonts.filter(f => f.provider === 'google');
if (bunnyFonts.length) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = `https://fonts.bunny.net/css?family=${bunnyFonts.map(f => `${f.name.replace(/ /g, '+')}:${f.weight}`).join('&family=')}`;
document.head.appendChild(link);
}
if (googleFonts.length) { if (googleFonts.length) {
const link = document.createElement('link'); const link = document.createElement('link');
link.rel = 'stylesheet'; link.rel = 'stylesheet';
link.href = `https://fonts.googleapis.com/css2?${googleFonts.map(f => `family=${f}`).join('&')}&display=swap`; link.href = `https://fonts.googleapis.com/css2?${googleFonts.map(f => `family=${f.name.replace(/ /g, '+')}:wght@${f.weight}`).join('&')}&display=swap`;
document.head.appendChild(link); document.head.appendChild(link);
} }
const root = document.documentElement; const root = document.documentElement;
if (fonts['font-title']) { if (headingFont) {
root.style.setProperty('--font-title', `"${fonts['font-title'].name}", system-ui, sans-serif`); root.style.setProperty('--font-title', `"${headingFont.name}", system-ui, sans-serif`);
root.style.setProperty('--font-title-weight', fonts['font-title'].weight); root.style.setProperty('--font-title-weight', headingFont.weight);
} }
if (fonts['font-body']) { if (bodyFont) {
root.style.setProperty('--font-body', `"${fonts['font-body'].name}", system-ui, sans-serif`); root.style.setProperty('--font-body', `"${bodyFont.name}", system-ui, sans-serif`);
root.style.setProperty('--font-body-weight', fonts['font-body'].weight); root.style.setProperty('--font-body-weight', bodyFont.weight);
} }
if (fonts['font-code']) { if (codeFont) {
root.style.setProperty('--font-code', `"${fonts['font-code'].name}", monospace`); root.style.setProperty('--font-code', `"${codeFont.name}", monospace`);
} }
} }
@ -2418,14 +2483,23 @@ function fmtDatetime(dtStr) {
if (link) link.href = `assets/images/${config.logo}`; if (link) link.href = `assets/images/${config.logo}`;
} }
loadFonts(); let themeConfig = {};
if (config.theme) {
try {
const themeResp = await fetch(config.theme);
if (themeResp.ok) themeConfig = jsyaml.load(await themeResp.text()) || {};
} catch (e) { /* fall back to hardcoded CSS defaults */ }
}
loadFonts(themeConfig);
initCategories(); initCategories();
const navMode = config.navigation || 'sidebar'; const navMode = config.navigation || 'sidebar';
if (navMode === 'topbar') buildTopbar(); if (navMode === 'topbar') buildTopbar();
else buildSidebar(); else buildSidebar();
applyConfigTheme(); if (config.theme) applyThemeYml(themeConfig);
else applyConfigTheme();
applyTheme(getInitialTheme()); applyTheme(getInitialTheme());
// nav.yml — phase 2 expects `sections:` + `pages:` blocks; phase 1 flat // nav.yml — phase 2 expects `sections:` + `pages:` blocks; phase 1 flat

66
app/theme.yml Normal file
View file

@ -0,0 +1,66 @@
# mdcms theme — default
# Edit colours, fonts, and layout here. See docs for full reference.
# ──────────────────────────────────
# Colours
# ──────────────────────────────────
light:
accent: "#2563EB"
background: "#FFFFFF"
nav-background: "#F8FAFC"
text: "#1E293B"
text-muted: "#64748B"
dark:
accent: "#60A5FA"
background: "#0F172A"
nav-background: "#1E293B"
text: "#F1F5F9"
text-muted: "#94A3B8"
# ──────────────────────────────────
# Semantic colours
# Used by callout tags (info, warning, success, error).
# Choose values that work on both light and dark backgrounds.
# ──────────────────────────────────
colours-semantic:
info: "#2563EB"
warning: "#D97706"
success: "#16A34A"
error: "#DC2626"
# ──────────────────────────────────
# Callout defaults
# ──────────────────────────────────
callouts:
info:
icon: info
primary-colour: "#2563EB"
background-colour: "#2563EB"
warning:
icon: warning
primary-colour: "#D97706"
background-colour: "#D97706"
success:
icon: success
primary-colour: "#16A34A"
background-colour: "#16A34A"
error:
icon: error
primary-colour: "#DC2626"
background-colour: "#DC2626"
# ──────────────────────────────────
# Typography
# Format: "provider:Font Name:weight" (provider: bunny | google)
# ──────────────────────────────────
font-body: "bunny:Noto Sans:400"
font-heading: "bunny:Noto Sans:700"
font-size: 1.0 # unitless multiplier (1.0 = 16px base)
line-height: 1.7 # unitless multiplier
# ──────────────────────────────────
# Layout
# ──────────────────────────────────
main-width: 80em
nav-width: 20em

View file

@ -1,16 +1,17 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# #
# mdcms v0.3.2 — CLI companion # mdcms v0.3.3 — CLI companion
# #
# Copyright 2026 Kristian Benestad # Copyright 2026 Kristian Benestad
# Apache License, Version 2.0 — https://www.apache.org/licenses/LICENSE-2.0 # Apache License, Version 2.0 — https://www.apache.org/licenses/LICENSE-2.0
"""MD-CMS v0.3.2 — CLI tool for managing and building MD-CMS sites.""" """MD-CMS v0.3.3 — CLI tool for managing and building MD-CMS sites."""
import json import json
import os import os
import re import re
import ssl import ssl
import time
import urllib.error import urllib.error
import urllib.request import urllib.request
from pathlib import Path from pathlib import Path
@ -20,7 +21,7 @@ import certifi
import click import click
import yaml import yaml
CLI_VERSION = "0.3.2" CLI_VERSION = "0.3.3"
CLI_RELEASE_DATE = "16 May 2026" CLI_RELEASE_DATE = "16 May 2026"
MIN_SUPPORTED_VERSION = "0.3" MIN_SUPPORTED_VERSION = "0.3"
@ -470,7 +471,7 @@ def _version_callback(ctx, param, value):
if not value or ctx.resilient_parsing: if not value or ctx.resilient_parsing:
return return
click.echo(f"mdcms v{CLI_VERSION} (released {CLI_RELEASE_DATE})") click.echo(f"mdcms v{CLI_VERSION} (released {CLI_RELEASE_DATE})")
url = f"https://raw.githubusercontent.com/kbenestad/mdcms/refs/heads/main/docs/banner/v{CLI_VERSION}.txt" url = f"https://raw.githubusercontent.com/kbenestad/mdcms/refs/heads/main/docs/banner/v{CLI_VERSION}.txt?t={int(time.time())}"
try: try:
ssl_ctx = ssl.create_default_context(cafile=certifi.where()) ssl_ctx = ssl.create_default_context(cafile=certifi.where())
req = urllib.request.Request(url, headers={"User-Agent": f"mdcms/{CLI_VERSION}"}) req = urllib.request.Request(url, headers={"User-Agent": f"mdcms/{CLI_VERSION}"})

View file

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "mdcms" name = "mdcms"
version = "0.3.2" version = "0.3.3"
description = "MD-CMS — Markdown-based CMS companion CLI" description = "MD-CMS — Markdown-based CMS companion CLI"
readme = "README.md" readme = "README.md"
license = { text = "Apache-2.0" } license = { text = "Apache-2.0" }