mirror of
https://github.com/kbenestad/mdcms.git
synced 2026-06-18 15:24:32 +00:00
Merge pull request #13 from kbenestad/claude/linux-install-docs-cwQsb
Claude/linux install docs cw qsb
This commit is contained in:
commit
56c22f075d
5 changed files with 173 additions and 38 deletions
|
|
@ -22,6 +22,7 @@
|
|||
# ──────────────────────────────────
|
||||
sitename: MD-CMS New Site
|
||||
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
|
||||
|
||||
|
|
@ -30,20 +31,13 @@ navigation: topbar # sidebar | topbar
|
|||
# favicon: favicon.png
|
||||
# footer: "© 2026 Your Name"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Typography (optional)
|
||||
# ──────────────────────────────────
|
||||
# font-title: "Inter:700"
|
||||
# font-body: Inter
|
||||
# font-code: JetBrains Mono
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Layout (optional)
|
||||
# ──────────────────────────────────
|
||||
# main-width: 80em
|
||||
# nav-width: 20em
|
||||
# nav-position: left # left | right (sidebar mode)
|
||||
|
||||
# Typography and colours are configured in theme.yml, not here.
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Features (optional)
|
||||
# ──────────────────────────────────
|
||||
|
|
|
|||
122
app/index.html
122
app/index.html
|
|
@ -117,6 +117,11 @@
|
|||
--font-body-weight: 400;
|
||||
--main-width: 80em;
|
||||
--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; }
|
||||
|
|
@ -126,7 +131,7 @@ body {
|
|||
font-weight: var(--font-body-weight);
|
||||
color: var(--font-colour);
|
||||
background: var(--bg-main);
|
||||
line-height: 1.7;
|
||||
line-height: var(--line-height-body);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
transition: background-color 0.2s ease, color 0.2s ease;
|
||||
|
|
@ -1152,6 +1157,53 @@ body {
|
|||
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() {
|
||||
const root = document.documentElement;
|
||||
['light', 'dark'].forEach(mode => {
|
||||
|
|
@ -1211,35 +1263,48 @@ body {
|
|||
}
|
||||
|
||||
// ─── Fonts ────────────────────────────────────────────────
|
||||
function loadFonts() {
|
||||
const fonts = {};
|
||||
['font-title', 'font-body', 'font-code'].forEach(key => {
|
||||
const val = config[key];
|
||||
if (!val) return;
|
||||
const [name, weight] = val.split(':');
|
||||
fonts[key] = { name: name.trim(), weight: weight ? weight.trim() : '400' };
|
||||
});
|
||||
const googleFonts = [];
|
||||
Object.entries(fonts).forEach(([, { name, weight }]) => {
|
||||
googleFonts.push(`${name.replace(/ /g, '+')}:wght@${weight}`);
|
||||
});
|
||||
function loadFonts(tc) {
|
||||
function parseFont(spec) {
|
||||
if (!spec) return null;
|
||||
const parts = spec.split(':');
|
||||
if (parts.length >= 3) return { provider: parts[0].trim(), name: parts.slice(1, -1).join(':').trim(), weight: parts[parts.length - 1].trim() };
|
||||
if (parts.length === 2) return { provider: 'google', name: parts[0].trim(), weight: parts[1].trim() };
|
||||
return { provider: 'google', name: parts[0].trim(), weight: '400' };
|
||||
}
|
||||
|
||||
const src = tc || {};
|
||||
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) {
|
||||
const link = document.createElement('link');
|
||||
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);
|
||||
}
|
||||
|
||||
const root = document.documentElement;
|
||||
if (fonts['font-title']) {
|
||||
root.style.setProperty('--font-title', `"${fonts['font-title'].name}", system-ui, sans-serif`);
|
||||
root.style.setProperty('--font-title-weight', fonts['font-title'].weight);
|
||||
if (headingFont) {
|
||||
root.style.setProperty('--font-title', `"${headingFont.name}", system-ui, sans-serif`);
|
||||
root.style.setProperty('--font-title-weight', headingFont.weight);
|
||||
}
|
||||
if (fonts['font-body']) {
|
||||
root.style.setProperty('--font-body', `"${fonts['font-body'].name}", system-ui, sans-serif`);
|
||||
root.style.setProperty('--font-body-weight', fonts['font-body'].weight);
|
||||
if (bodyFont) {
|
||||
root.style.setProperty('--font-body', `"${bodyFont.name}", system-ui, sans-serif`);
|
||||
root.style.setProperty('--font-body-weight', bodyFont.weight);
|
||||
}
|
||||
if (fonts['font-code']) {
|
||||
root.style.setProperty('--font-code', `"${fonts['font-code'].name}", monospace`);
|
||||
if (codeFont) {
|
||||
root.style.setProperty('--font-code', `"${codeFont.name}", monospace`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2418,14 +2483,23 @@ function fmtDatetime(dtStr) {
|
|||
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();
|
||||
|
||||
const navMode = config.navigation || 'sidebar';
|
||||
if (navMode === 'topbar') buildTopbar();
|
||||
else buildSidebar();
|
||||
|
||||
applyConfigTheme();
|
||||
if (config.theme) applyThemeYml(themeConfig);
|
||||
else applyConfigTheme();
|
||||
applyTheme(getInitialTheme());
|
||||
|
||||
// nav.yml — phase 2 expects `sections:` + `pages:` blocks; phase 1 flat
|
||||
|
|
|
|||
66
app/theme.yml
Normal file
66
app/theme.yml
Normal 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
|
||||
9
mdcms.py
9
mdcms.py
|
|
@ -1,16 +1,17 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# mdcms v0.3.2 — CLI companion
|
||||
# mdcms v0.3.3 — CLI companion
|
||||
#
|
||||
# Copyright 2026 Kristian Benestad
|
||||
# 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 os
|
||||
import re
|
||||
import ssl
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
|
@ -20,7 +21,7 @@ import certifi
|
|||
import click
|
||||
import yaml
|
||||
|
||||
CLI_VERSION = "0.3.2"
|
||||
CLI_VERSION = "0.3.3"
|
||||
CLI_RELEASE_DATE = "16 May 2026"
|
||||
MIN_SUPPORTED_VERSION = "0.3"
|
||||
|
||||
|
|
@ -470,7 +471,7 @@ def _version_callback(ctx, param, value):
|
|||
if not value or ctx.resilient_parsing:
|
||||
return
|
||||
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:
|
||||
ssl_ctx = ssl.create_default_context(cafile=certifi.where())
|
||||
req = urllib.request.Request(url, headers={"User-Agent": f"mdcms/{CLI_VERSION}"})
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||
|
||||
[project]
|
||||
name = "mdcms"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
description = "MD-CMS — Markdown-based CMS companion CLI"
|
||||
readme = "README.md"
|
||||
license = { text = "Apache-2.0" }
|
||||
|
|
|
|||
Loading…
Reference in a new issue