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
|
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)
|
||||||
# ──────────────────────────────────
|
# ──────────────────────────────────
|
||||||
|
|
|
||||||
122
app/index.html
122
app/index.html
|
|
@ -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
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
|
#!/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}"})
|
||||||
|
|
|
||||||
|
|
@ -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" }
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue