From 7ff88affdb5da4e3ac2cff925add4487f54294d4 Mon Sep 17 00:00:00 2001 From: hugo Date: Sun, 22 Feb 2026 15:00:49 +0100 Subject: [PATCH] feat: calendar context sensitive now --- src/main/engine/PageRenderer.ts | 10 +++++ src/main/engine/assets/calendarRuntime.ts | 39 ++++++++++++++++++- .../templates/partials/menu-items.liquid | 2 + .../engine/templates/partials/menu.liquid | 4 +- src/main/engine/templates/post-list.liquid | 2 +- src/main/engine/templates/single-post.liquid | 2 +- tests/engine/BlogGenerationEngine.test.ts | 18 +++++++++ 7 files changed, 71 insertions(+), 6 deletions(-) diff --git a/src/main/engine/PageRenderer.ts b/src/main/engine/PageRenderer.ts index 8cd7881..464c9a0 100644 --- a/src/main/engine/PageRenderer.ts +++ b/src/main/engine/PageRenderer.ts @@ -68,6 +68,8 @@ export interface PostListTemplateContext { } | null; min_date: { day: number; month: number; year: number } | null; max_date: { day: number; month: number; year: number } | null; + calendar_initial_year: number | null; + calendar_initial_month: number | null; is_list_page: boolean; is_first_page: boolean; is_last_page: boolean; @@ -90,6 +92,8 @@ export interface SinglePostTemplateContext { post_categories: string[]; post_tags: string[]; tag_color_by_name: Record; + calendar_initial_year: number | null; + calendar_initial_month: number | null; canonical_post_path_by_slug: Record; canonical_media_path_by_source_path: Record; } @@ -1005,6 +1009,8 @@ export class PageRenderer { let minDateParts: { day: number; month: number; year: number } | null = null; let maxDateParts: { day: number; month: number; year: number } | null = null; + const calendarInitialDate = posts.length > 0 ? posts[0].createdAt : null; + const calendarInitialParts = calendarInitialDate ? toDateParts(calendarInitialDate) : null; const hasRangeHeading = Boolean( !isFirstPage @@ -1060,6 +1066,8 @@ export class PageRenderer { : null, min_date: minDateParts, max_date: maxDateParts, + calendar_initial_year: calendarInitialParts?.year ?? null, + calendar_initial_month: calendarInitialParts?.month ?? null, is_list_page: isListPage, is_first_page: isFirstPage, is_last_page: isLastPage, @@ -1146,6 +1154,8 @@ export class PageRenderer { post_categories: postCategories, post_tags: postTags, tag_color_by_name: pageContext.tag_color_by_name ?? {}, + calendar_initial_year: renderablePost.createdAt.getFullYear(), + calendar_initial_month: renderablePost.createdAt.getMonth() + 1, canonical_post_path_by_slug: mapToRecord(rewriteContext.canonicalPostPathBySlug), canonical_media_path_by_source_path: mapToRecord(rewriteContext.canonicalMediaPathBySourcePath), }; diff --git a/src/main/engine/assets/calendarRuntime.ts b/src/main/engine/assets/calendarRuntime.ts index 8b478a4..f43dff2 100644 --- a/src/main/engine/assets/calendarRuntime.ts +++ b/src/main/engine/assets/calendarRuntime.ts @@ -73,6 +73,36 @@ export const CALENDAR_RUNTIME_JS = String.raw`(() => { window.location.assign(pathname); } + function parseInitialYearMonth() { + const initialYearRaw = button.getAttribute('data-blog-calendar-year'); + const initialMonthRaw = button.getAttribute('data-blog-calendar-month'); + + const initialYear = Number(initialYearRaw); + const initialMonth = Number(initialMonthRaw); + + let selectedYear = Number.isInteger(initialYear) && initialYear > 0 ? initialYear : null; + let selectedMonth = Number.isInteger(initialMonth) && initialMonth >= 1 && initialMonth <= 12 + ? (initialMonth - 1) + : null; + + if (!Number.isInteger(selectedYear) || !Number.isInteger(selectedMonth)) { + const pathname = window.location.pathname || ''; + const parts = pathname.split('/').filter(Boolean); + const pathYear = Number(parts[0]); + const pathMonth = Number(parts[1]); + + if (!Number.isInteger(selectedYear) && Number.isInteger(pathYear) && pathYear > 0 && String(parts[0]).length === 4) { + selectedYear = pathYear; + } + + if (!Number.isInteger(selectedMonth) && Number.isInteger(pathMonth) && pathMonth >= 1 && pathMonth <= 12) { + selectedMonth = pathMonth - 1; + } + } + + return { selectedYear, selectedMonth }; + } + async function loadCalendarData() { const response = await fetch('/calendar.json', { cache: 'no-store' }); if (!response.ok) { @@ -116,7 +146,10 @@ export const CALENDAR_RUNTIME_JS = String.raw`(() => { throw new Error('Vanilla Calendar Pro is unavailable'); } - const calendar = new Calendar('[data-blog-calendar-root]', { + const initialYearMonth = parseInitialYearMonth(); + const calendarOptions = { + ...(Number.isInteger(initialYearMonth.selectedYear) ? { selectedYear: initialYearMonth.selectedYear } : {}), + ...(Number.isInteger(initialYearMonth.selectedMonth) ? { selectedMonth: initialYearMonth.selectedMonth } : {}), onCreateDateEls(_self, dateEl) { const dateKey = dateEl.dataset.vcDate || ''; const count = Number(days[dateKey] || 0); @@ -224,7 +257,9 @@ export const CALENDAR_RUNTIME_JS = String.raw`(() => { navigateTo('/' + String(selectedYear) + '/'); }, - }); + }; + + const calendar = new Calendar('[data-blog-calendar-root]', calendarOptions); calendar.init(); status.textContent = ''; diff --git a/src/main/engine/templates/partials/menu-items.liquid b/src/main/engine/templates/partials/menu-items.liquid index db564df..eb2aa1f 100644 --- a/src/main/engine/templates/partials/menu-items.liquid +++ b/src/main/engine/templates/partials/menu-items.liquid @@ -20,6 +20,8 @@ type="button" class="blog-menu-calendar-button" data-blog-calendar-toggle + {% if calendar_initial_year %}data-blog-calendar-year="{{ calendar_initial_year }}"{% endif %} + {% if calendar_initial_month %}data-blog-calendar-month="{{ calendar_initial_month }}"{% endif %} aria-label="{{ 'render.calendar.open' | i18n: language }}" title="{{ 'render.calendar.open' | i18n: language }}" > diff --git a/src/main/engine/templates/partials/menu.liquid b/src/main/engine/templates/partials/menu.liquid index 33ffd69..a959e08 100644 --- a/src/main/engine/templates/partials/menu.liquid +++ b/src/main/engine/templates/partials/menu.liquid @@ -1,7 +1,7 @@ diff --git a/src/main/engine/templates/post-list.liquid b/src/main/engine/templates/post-list.liquid index 669805f..9b1ce11 100644 --- a/src/main/engine/templates/post-list.liquid +++ b/src/main/engine/templates/post-list.liquid @@ -27,7 +27,7 @@ {% endif %} {% endif %} - {% render 'partials/menu', menu_items: menu_items, language: language %} + {% render 'partials/menu', menu_items: menu_items, language: language, calendar_initial_year: calendar_initial_year, calendar_initial_month: calendar_initial_month %}
{% for day_block in day_blocks %} diff --git a/src/main/engine/templates/single-post.liquid b/src/main/engine/templates/single-post.liquid index 5d043a1..69b53a9 100644 --- a/src/main/engine/templates/single-post.liquid +++ b/src/main/engine/templates/single-post.liquid @@ -4,7 +4,7 @@

{{ post.title }}

- {% render 'partials/menu', menu_items: menu_items, language: language %} + {% render 'partials/menu', menu_items: menu_items, language: language, calendar_initial_year: calendar_initial_year, calendar_initial_month: calendar_initial_month %} {% if post_categories.size > 0 or post_tags.size > 0 %}
{% for category in post_categories %} diff --git a/tests/engine/BlogGenerationEngine.test.ts b/tests/engine/BlogGenerationEngine.test.ts index f670be6..62842f8 100644 --- a/tests/engine/BlogGenerationEngine.test.ts +++ b/tests/engine/BlogGenerationEngine.test.ts @@ -355,6 +355,7 @@ describe('BlogGenerationEngine', () => { slug: 'one', title: 'One', categories: ['news'], + tags: ['updates'], createdAt: new Date('2025-03-15T10:00:00Z'), }), makePost({ @@ -362,6 +363,7 @@ describe('BlogGenerationEngine', () => { slug: 'two', title: 'Two', categories: ['news'], + tags: ['updates'], createdAt: new Date('2025-03-15T12:00:00Z'), }), makePost({ @@ -369,6 +371,7 @@ describe('BlogGenerationEngine', () => { slug: 'three', title: 'Three', categories: ['news'], + tags: ['updates'], createdAt: new Date('2025-04-01T10:00:00Z'), }), ]; @@ -396,16 +399,31 @@ describe('BlogGenerationEngine', () => { const indexHtml = await readFile(path.join(tempDir, 'html', 'index.html'), 'utf-8'); expect(indexHtml).toContain('class="blog-menu-calendar-button"'); + expect(indexHtml).toContain('data-blog-calendar-year="2025"'); + expect(indexHtml).toContain('data-blog-calendar-month="4"'); expect(indexHtml).toContain('id="blog-calendar"'); expect(indexHtml).toContain('href="/assets/vanilla-calendar.min.css"'); expect(indexHtml).toContain('src="/assets/vanilla-calendar.min.js"'); expect(indexHtml).toContain('src="/assets/calendar-runtime.js"'); + const singleHtml = await readFile(path.join(tempDir, 'html', '2025', '03', '15', 'one', 'index.html'), 'utf-8'); + expect(singleHtml).toContain('data-blog-calendar-year="2025"'); + expect(singleHtml).toContain('data-blog-calendar-month="3"'); + + const tagArchiveHtml = await readFile(path.join(tempDir, 'html', 'tag', 'updates', 'index.html'), 'utf-8'); + expect(tagArchiveHtml).toContain('data-blog-calendar-year="2025"'); + expect(tagArchiveHtml).toContain('data-blog-calendar-month="4"'); + const calendarRuntime = await readFile(path.join(tempDir, 'html', 'assets', 'calendar-runtime.js'), 'utf-8'); expect(calendarRuntime).toContain('--blog-calendar-heat-hue'); expect(calendarRuntime).toContain('--blog-calendar-heat-alpha'); expect(calendarRuntime).toContain('onCreateMonthEls'); expect(calendarRuntime).toContain('onCreateYearEls'); + expect(calendarRuntime).toContain('data-blog-calendar-year'); + expect(calendarRuntime).toContain('data-blog-calendar-month'); + expect(calendarRuntime).toContain('window.location.pathname'); + expect(calendarRuntime).toContain('selectedYear'); + expect(calendarRuntime).toContain('selectedMonth'); expect(calendarRuntime).not.toContain('blog-calendar-post-count'); });