diff --git a/extras/typography/general.css b/extras/typography/general.css index 4f50a27..aab40b4 100644 --- a/extras/typography/general.css +++ b/extras/typography/general.css @@ -86,6 +86,9 @@ p { a { font-family: inherit; color: var(--theme-text-link); + display: inline-flex; + align-items: center; + column-gap: 0.2rem; } a:active, diff --git a/src/build-files/helpers.ts b/src/build-files/helpers.ts index 5aabead..f35695b 100644 --- a/src/build-files/helpers.ts +++ b/src/build-files/helpers.ts @@ -10,6 +10,7 @@ import { render_markdown_to_html, render_markdown_to_html_inline_sync } from '@d import { RSSEntry } from './rss'; import { DateTime } from 'luxon'; import { EventEntry } from './icalendar'; +import { as_context_time, as_html_time, from_iso } from '../time'; export interface OutFileURL { base_url: string; @@ -82,6 +83,22 @@ export async function build_partials(state: BuildState) { } export function mustache_context(state: BuildState, page_url: string, frontmatter?: FrontMatter) : Context { + let event: Context['event'] = { + start: null, + end: null, + zone: null, + }; + + if (frontmatter?.event) { + const start = from_iso(frontmatter.event.start, frontmatter.event.zone); + event.start = as_context_time(start); + + const end = from_iso(frontmatter.event.end, frontmatter.event.zone); + event.end = as_context_time(end); + + event.zone = frontmatter.event.zone; + } + return { env: state.env, page: frontmatter, @@ -89,6 +106,7 @@ export function mustache_context(state: BuildState, page_url: string, frontmatte page_url: page_url, site_title: state.conf.title, author: get_author(state, frontmatter), + event: event, build_time: state.build_time, icons: icons, rss_feeds: state.conf.rss || [ ], @@ -173,8 +191,12 @@ function handle_page_side_effects(state: BuildState, in_file: string, out_file: } function handle_rss(state: BuildState, rss_conf: RSSConfig, entries: RSSEntry[], in_file: string, out_url: OutFileURL, text: string, frontmatter: FrontMatter) { + if (frontmatter?.rss?.skip) { + return; + } + const author_or_authors = get_author(state, frontmatter); - const author = Array.isArray(author_or_authors) ? author_or_authors[0] : author_or_authors; + const authors = author_or_authors && (Array.isArray(author_or_authors) ? author_or_authors : [ author_or_authors ]); entries.push({ url: out_url.abs_url, @@ -182,12 +204,16 @@ function handle_rss(state: BuildState, rss_conf: RSSConfig, entries: RSSEntry[], html_content: text, title: frontmatter?.title, description: frontmatter?.description, - author_name: author?.name, + authors: authors, tags: frontmatter?.tags, }); } function handle_sitemap(state: BuildState, out_url: OutFileURL, frontmatter: FrontMatter) { + if (frontmatter?.sitemap?.skip) { + return; + } + state.sitemap.push({ url: out_url.abs_url, lastmod: state.build_time.iso, @@ -211,9 +237,9 @@ function handle_event(state: BuildState, in_file: string, out_url: OutFileURL, f description: frontmatter.description, author_name: author?.name, author_email: author?.email, - start_time: frontmatter.event?.start_time, - end_time: frontmatter.event?.end_time, - time_zone: frontmatter.event?.time_zone, + start_time: frontmatter.event?.start, + end_time: frontmatter.event?.end, + time_zone: frontmatter.event?.zone, }); } @@ -228,9 +254,9 @@ function handle_calendar(state: BuildState, cal_conf: CalendarConfig, entries: E description: frontmatter.description, author_name: author?.name, author_email: author?.email, - start_time: frontmatter.event?.start_time, - end_time: frontmatter.event?.end_time, - time_zone: frontmatter.event?.time_zone, + start_time: frontmatter.event?.start, + end_time: frontmatter.event?.end, + time_zone: frontmatter.event?.zone, }); } diff --git a/src/build-files/icalendar.ts b/src/build-files/icalendar.ts index 7a1f4b6..89e630d 100644 --- a/src/build-files/icalendar.ts +++ b/src/build-files/icalendar.ts @@ -33,7 +33,7 @@ export async function write_events_and_calendars_if_needed(state: BuildState) { }; const calendar = create_icalendar(cal_data, event); - const out_file = await map_input_file_to_output_file(state, entry.in_file, [ '.html', '.md', '.markdown' ], '.isc'); + const out_file = await map_input_file_to_output_file(state, entry.in_file, [ '.html', '.md', '.markdown' ], '.ics'); await write_text(out_file, calendar); } } diff --git a/src/build-files/index.ts b/src/build-files/index.ts index dea76a1..b44d1c4 100644 --- a/src/build-files/index.ts +++ b/src/build-files/index.ts @@ -16,6 +16,7 @@ import { render_json_schema_files } from './jsonschema'; import { write_sitemap_if_needed } from './sitemap'; import { write_rss_if_needed } from './rss'; import { write_events_and_calendars_if_needed } from './icalendar'; +import { as_context_time, as_html_time } from '../time'; export { BuildState, ThemeGroups } from './state'; @@ -61,10 +62,7 @@ export async function build_docs_project(conf: Config) { sitemap: [ ], events: [ ], calendars: [ ], - build_time: { - iso: now.toISO(), - rfc2822: now.toRFC2822(), - }, + build_time: as_context_time(now), }; for (const theme of Object.values(themes)) { diff --git a/src/build-files/rss.ts b/src/build-files/rss.ts index a5c2c97..324acba 100644 --- a/src/build-files/rss.ts +++ b/src/build-files/rss.ts @@ -3,7 +3,7 @@ import type { XMLBuilder } from 'xmlbuilder2/lib/interfaces'; import { create as create_xml } from 'xmlbuilder2'; import { BuildState } from './state'; import { write_text } from '../fs'; -import { app_version } from '../conf'; +import { AuthorConfig, app_version } from '../conf'; import { map_output_file_to_url } from './helpers'; import { DateTime } from 'luxon'; @@ -12,7 +12,7 @@ export interface RSSEntry { in_file: string; title?: string; description?: string; - author_name?: string; + authors?: AuthorConfig[]; html_content?: string; tags?: string[]; } @@ -100,8 +100,10 @@ export async function write_rss_if_needed(state: BuildState) { item.ele('description').txt(entry.description); } - if (entry.author_name) { - item.ele('dc:creator').ele({ $: entry.author_name }); + if (entry.authors) { + for (const author of entry.authors) { + item.ele('dc:creator').ele({ $: author.name || author.url || author.email }); + } } if (entry.tags) { diff --git a/src/build-files/state.ts b/src/build-files/state.ts index 130c4ff..576a335 100644 --- a/src/build-files/state.ts +++ b/src/build-files/state.ts @@ -5,6 +5,7 @@ import type { ColorTheme } from '@doc-utils/color-themes'; import type { SitemapEntry } from './sitemap'; import { RSSEntry } from './rss'; import { EventEntry } from './icalendar'; +import { ContextTime } from '../template'; export interface BuildState { conf: Config; @@ -22,10 +23,7 @@ export interface BuildState { sitemap: SitemapEntry[]; events: EventEntry[]; calendars: EventEntry[][]; - build_time: { - iso: string; - rfc2822: string; - }; + build_time: ContextTime; } export interface ThemeGroups { diff --git a/src/template.ts b/src/template.ts index 5fe1f99..c95ed97 100644 --- a/src/template.ts +++ b/src/template.ts @@ -8,6 +8,7 @@ import { load_from_dir } from './fs'; import { ColorTheme } from '@doc-utils/color-themes'; import { ThemeGroups } from './build-files'; import { ChangeFreq } from './build-files/sitemap'; +import { DateTime } from 'luxon'; export interface Context { env?: Record; @@ -16,20 +17,28 @@ export interface Context { page_url: string; site_title: string; author: AuthorConfig | AuthorConfig[]; + event?: { + start: ContextTime; + end: ContextTime; + zone: `${string}/${string}`; + }; icons: Record; themes: ColorTheme[]; theme_groups: ThemeGroups; rss_feeds: RSSConfig[]; calendars: CalendarConfig[]; - build_time: { - iso: string; - rfc2822: string; - }; + build_time: ContextTime; markdown: { render_inline(): MustacheRenderer; } } +export interface ContextTime { + iso: string; + rfc2822: string; + html: string; +} + export interface FrontMatter { skip?: boolean; layout?: string; @@ -44,18 +53,19 @@ export interface FrontMatter { } interface SitemapFrontmatter { + skip?: boolean; change_freq?: ChangeFreq; priority?: number; } interface EventFrontmatter { - start_time?: string; - end_time?: string; - time_zone?: `${string}/${string}`; + start?: string; + end?: string; + zone?: `${string}/${string}`; } interface RSSFrontmatter { - // + skip?: boolean; } export function render_template(template: string, context: Context, layout?: string, partials: Record = { }, tags?: [ string, string ]) { diff --git a/src/time.ts b/src/time.ts index ca50f45..4b425a0 100644 --- a/src/time.ts +++ b/src/time.ts @@ -13,42 +13,20 @@ export function from_iso(time: string, zone?: string) { : DateTime.fromISO(time); } -// function date_formatters(lang: string, time_zone: string) { -// return { -// date() { -// return (text, render) => { -// return format_with_config(render(text), { -// dateStyle: 'short', -// timeZone: time_zone, -// }); -// }; -// }, -// time() { -// return (text, render) => { -// return format_with_config(render(text), { -// timeStyle: 'long', -// timeZone: time_zone, -// }); -// }; -// }, -// datetime() { -// return (text, render) => { -// return format_with_config(render(text), { -// dateStyle: 'short', -// timeStyle: 'long', -// timeZone: time_zone, -// }); -// }; -// }, -// }; - -// function format_with_config(text: string, config: Intl.DateTimeFormatOptions) { -// const date = new Date(text.trim()); -// const formatter = new Intl.DateTimeFormat(lang, config); -// return formatter.format(date); -// } -// } - - +export function as_html_time(time: DateTime, lang?: string, config?: Intl.DateTimeFormatOptions) { + if (lang && config) { + const formatter = new Intl.DateTimeFormat(lang, config); + const formatted = formatter.format(new Date(time.toISO())); + return ``; + } + return ``; +} +export function as_context_time(time: DateTime, lang?: string, config?: Intl.DateTimeFormatOptions) { + return { + iso: time.toISO(), + rfc2822: time.toRFC2822(), + html: as_html_time(time, lang, config), + }; +}