Fix calendar activity heatmap in preview and generation

This commit is contained in:
2026-07-20 19:56:18 +02:00
parent 5726b92c5d
commit 3795db76b0
5 changed files with 71 additions and 20 deletions

View File

@@ -592,9 +592,6 @@ fn serve_scoped_file(
let Some(relative) = path.strip_prefix(prefix) else {
return Ok(None);
};
let scope_root = data_dir.join(scope_dir);
let candidate = scope_root.join(relative);
if !candidate.exists() || !candidate.is_file() {
let bundled_path = format!("{scope_dir}/{relative}");
if let Some(bytes) = crate::engine::site_assets::bundled_site_asset(&bundled_path) {
let mime = guess_content_type(Path::new(&bundled_path));
@@ -602,6 +599,9 @@ fn serve_scoped_file(
(StatusCode::OK, [(header::CONTENT_TYPE, mime)], bytes).into_response(),
));
}
let scope_root = data_dir.join(scope_dir);
let candidate = scope_root.join(relative);
if !candidate.exists() || !candidate.is_file() {
return Ok(Some(error_response(
StatusCode::NOT_FOUND,
"preview asset not found",
@@ -1167,6 +1167,11 @@ mod tests {
std::fs::create_dir_all(dir.path().join("html/pagefind")).unwrap();
std::fs::write(dir.path().join("media/ok.txt"), "ok").unwrap();
std::fs::write(dir.path().join("assets/site.css"), "body { color: red; }").unwrap();
std::fs::write(
dir.path().join("assets/calendar-runtime.js"),
"window.staleCalendarRuntime = true;",
)
.unwrap();
std::fs::write(dir.path().join("images/custom.svg"), "<svg></svg>").unwrap();
std::fs::write(
dir.path().join("html/pagefind/pagefind-ui.js"),
@@ -1194,6 +1199,14 @@ mod tests {
.unwrap();
let media_body = media.text().unwrap();
let asset_body = asset.text().unwrap();
let calendar_runtime_body = client
.get(format!(
"http://{PREVIEW_HOST}:{PREVIEW_PORT}/assets/calendar-runtime.js"
))
.send()
.unwrap()
.text()
.unwrap();
let image_body = client
.get(format!(
"http://{PREVIEW_HOST}:{PREVIEW_PORT}/images/custom.svg"
@@ -1214,6 +1227,8 @@ mod tests {
assert_eq!(media_body, "ok");
assert!(asset_body.contains("color: red"));
assert!(calendar_runtime_body.contains("--blog-calendar-heat-strength"));
assert!(!calendar_runtime_body.contains("staleCalendarRuntime"));
assert!(image_body.contains("<svg>"));
assert!(pagefind_body.contains("window.pagefind"));
}

View File

@@ -278,3 +278,30 @@ pub(crate) fn write_bundled_site_assets(
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::bundled_site_asset;
fn bundled_text(path: &str) -> &'static str {
std::str::from_utf8(bundled_site_asset(path).unwrap()).unwrap()
}
#[test]
fn calendar_heatmap_marks_only_populated_periods_with_one_accent() {
let runtime = bundled_text("assets/calendar-runtime.js");
let styles = bundled_text("assets/bds.css");
assert!(runtime.contains("--blog-calendar-heat-strength"));
assert!(!runtime.contains("--blog-calendar-heat-hue"));
assert!(styles.contains(
"[data-vc-date]:not([data-blog-calendar-has-posts='true']) [data-vc-date-btn]"
));
assert!(styles.contains("background-color: transparent !important;"));
assert!(styles.contains("border-color: transparent !important;"));
assert!(styles.contains("color-mix(in srgb, var(--pico-primary"));
assert!(styles.contains("var(--blog-calendar-heat-strength"));
assert!(styles.contains("transparent) !important;"));
assert!(!styles.contains("hsl(var(--blog-calendar-heat-hue"));
}
}

View File

@@ -49,14 +49,20 @@ main { display: grid; gap: 1rem; }
[data-blog-calendar-root] [data-vc-week-day] { font-size: .68rem; line-height: .9rem; min-width: 1.45rem; }
[data-blog-calendar-root] [data-vc-date] { padding-top: 0; padding-bottom: 0; }
[data-blog-calendar-root] [data-vc-date-btn] { min-height: 1.45rem; min-width: 1.45rem; font-size: .68rem; line-height: .9rem; }
[data-blog-calendar-has-posts='true'] [data-vc-date-btn] {
border-color: hsl(var(--blog-calendar-heat-hue, 210) 85% 42% / .95);
background-color: hsl(var(--blog-calendar-heat-hue, 210) 88% 52% / var(--blog-calendar-heat-alpha, 0));
[data-blog-calendar-root] [data-vc-date]:not([data-blog-calendar-has-posts='true']) [data-vc-date-btn],
[data-blog-calendar-root] [data-vc-months-month]:not([data-blog-calendar-has-posts='true']),
[data-blog-calendar-root] [data-vc-years-year]:not([data-blog-calendar-has-posts='true']) {
background-color: transparent !important;
border-color: transparent !important;
}
[data-blog-calendar-root] [data-vc-date][data-blog-calendar-has-posts='true'] [data-vc-date-btn] {
background-color: color-mix(in srgb, var(--pico-primary, #0172ad) var(--blog-calendar-heat-strength, 0%), transparent) !important;
border-color: var(--pico-primary, #0172ad) !important;
}
[data-blog-calendar-root] [data-vc-months-month][data-blog-calendar-has-posts='true'],
[data-blog-calendar-root] [data-vc-years-year][data-blog-calendar-has-posts='true'] {
background-color: hsl(var(--blog-calendar-heat-hue, 210) 88% 52% / var(--blog-calendar-heat-alpha, 0));
border-color: hsl(var(--blog-calendar-heat-hue, 210) 85% 42% / .95);
background-color: color-mix(in srgb, var(--pico-primary, #0172ad) var(--blog-calendar-heat-strength, 0%), transparent) !important;
border-color: var(--pico-primary, #0172ad) !important;
}
.post { border: 1px solid var(--pico-muted-border-color, var(--muted-border-color)); padding: 1rem; background: var(--pico-card-background-color, var(--card-background-color)); min-width: 0; }
.post pre { position: relative; overflow-x: auto; max-width: 100%; border: 1px solid var(--pico-muted-border-color, var(--muted-border-color)); border-radius: .3rem; margin: .9rem 0; padding: .85rem .9rem; background: var(--pico-code-background-color, rgba(33, 38, 45, .82)); box-sizing: border-box; }

View File

@@ -55,17 +55,14 @@
function applyHeatStyle(target, count, maxCount) {
if (!(target instanceof HTMLElement) || !Number.isFinite(count) || count <= 0 || !Number.isFinite(maxCount) || maxCount <= 0) {
target?.style?.setProperty('--blog-calendar-heat-alpha', '0');
target?.style?.setProperty('--blog-calendar-heat-hue', '210');
target?.style?.setProperty('--blog-calendar-heat-strength', '0%');
return;
}
const normalized = Math.min(1, count / maxCount);
const hue = Math.round(210 - (210 * normalized));
const alpha = (0.30 + normalized * 0.65).toFixed(3);
const strength = Math.round(30 + normalized * 60);
target.style.setProperty('--blog-calendar-heat-hue', String(hue));
target.style.setProperty('--blog-calendar-heat-alpha', alpha);
target.style.setProperty('--blog-calendar-heat-strength', String(strength) + '%');
}
function navigateTo(pathname) {

View File

@@ -156,6 +156,12 @@ invariant CompleteStarterNavigation {
-- calendar entry, and provides localized search empty-result text.
}
invariant CalendarActivityStyling {
-- Calendar days, months, and years with no posts are visually unmarked.
-- Populated periods use one theme-compatible accent whose intensity grows
-- with the relative post count; count differences never change its hue.
}
invariant LabelsUseContentLanguage {
-- RenderLabels and the i18n filter resolve against the content/render
-- language, consistent with i18n.allium's split-localization rule.