support for rss/calendar
This commit is contained in:
@@ -1,12 +1,15 @@
|
||||
|
||||
import { dirname, join as path_join } from 'path';
|
||||
import { mkdirp, write_text } from '../fs';
|
||||
import { icons } from '../icons';
|
||||
import { BuildState } from './state';
|
||||
import { load_partials, FrontMatter, Context, load_layout, render_template } from '../template';
|
||||
import { render_theme_css_properties } from '../themes';
|
||||
import { render_markdown_to_html, render_markdown_to_html_inline_sync } from '@doc-utils/markdown2html';
|
||||
import { mkdirp, write_text } from '../fs';
|
||||
import { CalendarConfig, RSSConfig } from '../conf';
|
||||
import { render_theme_css_properties } from '../themes';
|
||||
import { load_partials, FrontMatter, Context, load_layout, render_template } from '../template';
|
||||
import { render_markdown_to_html, render_markdown_to_html_inline_sync } from '@doc-utils/markdown2html';
|
||||
import { RSSEntry } from './rss';
|
||||
import { DateTime } from 'luxon';
|
||||
import { EventEntry } from './icalendar';
|
||||
|
||||
export interface OutFileURL {
|
||||
base_url: string;
|
||||
@@ -84,8 +87,12 @@ export function mustache_context(state: BuildState, page_url: string, frontmatte
|
||||
page: frontmatter,
|
||||
base_url: state.conf.base_url,
|
||||
page_url: page_url,
|
||||
site_title: state.conf.title,
|
||||
author: get_author(state, frontmatter),
|
||||
build_time: state.build_time,
|
||||
icons: icons,
|
||||
rss_feeds: state.conf.rss || [ ],
|
||||
calendars: state.conf.calendars || [ ],
|
||||
themes: Object.values(state.themes),
|
||||
theme_groups: structuredClone(state.theme_groups),
|
||||
markdown: {
|
||||
@@ -102,7 +109,7 @@ export function mustache_context(state: BuildState, page_url: string, frontmatte
|
||||
};
|
||||
}
|
||||
|
||||
export async function render_page(state: BuildState, out_file: string, out_url: OutFileURL, text: string, render_as_markdown: boolean, frontmatter?: any) {
|
||||
export async function render_page(state: BuildState, in_file: string, out_file: string, out_url: OutFileURL, text: string, render_as_markdown: boolean, frontmatter?: any) {
|
||||
if (render_as_markdown) {
|
||||
const opts = Object.assign({ }, state.conf.markdown, {
|
||||
base_url: out_url.abs_url
|
||||
@@ -126,70 +133,108 @@ export async function render_page(state: BuildState, out_file: string, out_url:
|
||||
const context = mustache_context(state, out_url.abs_url, frontmatter);
|
||||
const rendered = render_template(text, context, layout, structuredClone(state.partials), tags);
|
||||
await write_text(out_file, rendered);
|
||||
|
||||
handle_page_side_effects(state, out_file, out_url, frontmatter);
|
||||
|
||||
handle_page_side_effects(state, in_file, out_file, out_url, text, frontmatter);
|
||||
}
|
||||
|
||||
function handle_page_side_effects(state: BuildState, out_file: string, out_url: OutFileURL, frontmatter?: any) {
|
||||
function handle_page_side_effects(state: BuildState, in_file: string, out_file: string, out_url: OutFileURL, text: string, frontmatter?: any) {
|
||||
// Only actual HTML webpages are registered with things like the RSS feed; This is
|
||||
// to prevent things like CSS files showing up, which may be templated (and therefore
|
||||
// pass through this function), but are not really "pages"
|
||||
if (frontmatter && typeof frontmatter === 'object' && out_file.endsWith('.html')) {
|
||||
if (state.conf.rss) {
|
||||
if (Array.isArray(state.conf.rss)) {
|
||||
for (const rss_conf of state.conf.rss) {
|
||||
handle_rss(state, rss_conf, out_url, frontmatter);
|
||||
for (const [index, rss_conf] of Object.entries(state.conf.rss)) {
|
||||
if (in_file.startsWith(rss_conf.in_dir + '/')) {
|
||||
const entries = (state.rss[index] = state.rss[index] || [ ]);
|
||||
handle_rss(state, rss_conf, entries, in_file, out_url, text, frontmatter);
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
handle_rss(state, { }, out_url, frontmatter);
|
||||
}
|
||||
}
|
||||
|
||||
handle_sitemap(state, out_url, frontmatter);
|
||||
handle_event(state, out_url, frontmatter);
|
||||
if (state.conf.sitemap) {
|
||||
handle_sitemap(state, out_url, frontmatter);
|
||||
}
|
||||
|
||||
if (state.conf.calendars) {
|
||||
for (const cal_conf of state.conf.calendars) {
|
||||
handle_calendar(state, cal_conf, out_url, frontmatter);
|
||||
if (frontmatter?.event) {
|
||||
if (state.conf.events) {
|
||||
handle_event(state, in_file, out_url, frontmatter);
|
||||
}
|
||||
|
||||
if (state.conf.calendars) {
|
||||
for (const [index, cal_conf] of Object.entries(state.conf.calendars)) {
|
||||
if (in_file.startsWith(cal_conf.in_dir + '/')) {
|
||||
const entries = (state.rss[index] = state.rss[index] || [ ]);
|
||||
handle_calendar(state, cal_conf, entries, in_file, out_url, frontmatter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handle_rss(state: BuildState, rss_conf: RSSConfig, out_url: OutFileURL, frontmatter: any) {
|
||||
// const field = rss_conf
|
||||
// const rss_frontmatter = state.
|
||||
// //
|
||||
}
|
||||
function handle_rss(state: BuildState, rss_conf: RSSConfig, entries: RSSEntry[], in_file: string, out_url: OutFileURL, text: string, frontmatter: FrontMatter) {
|
||||
const author_or_authors = get_author(state, frontmatter);
|
||||
const author = Array.isArray(author_or_authors) ? author_or_authors[0] : author_or_authors;
|
||||
|
||||
function handle_sitemap(state: BuildState, out_url: OutFileURL, frontmatter: any) {
|
||||
if (! state.conf.sitemap) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sitemap_frontmatter = state.conf.sitemap.front_matter_field
|
||||
? frontmatter?.[state.conf.sitemap.front_matter_field] || { }
|
||||
: frontmatter?.sitemap || { };
|
||||
|
||||
state.sitemap.push({
|
||||
entries.push({
|
||||
url: out_url.abs_url,
|
||||
lastmod: state.build_time.iso,
|
||||
change_freq: sitemap_frontmatter?.change_freq,
|
||||
priority: sitemap_frontmatter?.priority,
|
||||
in_file: in_file,
|
||||
html_content: text,
|
||||
title: frontmatter?.title,
|
||||
description: frontmatter?.description,
|
||||
author_name: author?.name,
|
||||
tags: frontmatter?.tags,
|
||||
});
|
||||
}
|
||||
|
||||
function handle_event(state: BuildState, out_url: OutFileURL, frontmatter: any) {
|
||||
//
|
||||
function handle_sitemap(state: BuildState, out_url: OutFileURL, frontmatter: FrontMatter) {
|
||||
state.sitemap.push({
|
||||
url: out_url.abs_url,
|
||||
lastmod: state.build_time.iso,
|
||||
change_freq: frontmatter?.sitemap?.change_freq,
|
||||
priority: frontmatter?.sitemap?.priority,
|
||||
});
|
||||
}
|
||||
|
||||
function handle_calendar(state: BuildState, cal_conf: CalendarConfig, out_url: OutFileURL, frontmatter: any) {
|
||||
//
|
||||
function handle_event(state: BuildState, in_file: string, out_url: OutFileURL, frontmatter: FrontMatter) {
|
||||
if (! state.conf.events) {
|
||||
return;
|
||||
}
|
||||
|
||||
const author_or_authors = get_author(state, frontmatter);
|
||||
const author = Array.isArray(author_or_authors) ? author_or_authors[0] : author_or_authors;
|
||||
|
||||
state.events.push({
|
||||
url: out_url.abs_url,
|
||||
in_file: in_file,
|
||||
title: frontmatter.title,
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
export function file_hash_matches(state: BuildState, in_file: string, new_hash: string) {
|
||||
function handle_calendar(state: BuildState, cal_conf: CalendarConfig, entries: EventEntry[], in_file: string, out_url: OutFileURL, frontmatter: FrontMatter) {
|
||||
const author_or_authors = get_author(state, frontmatter);
|
||||
const author = Array.isArray(author_or_authors) ? author_or_authors[0] : author_or_authors;
|
||||
|
||||
entries.push({
|
||||
url: out_url.abs_url,
|
||||
in_file: in_file,
|
||||
title: frontmatter.title,
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
export function config_hash_matches(state: BuildState) {
|
||||
const { old_metadata, new_metadata } = state;
|
||||
|
||||
if (! old_metadata.last_build?.config_hash) {
|
||||
@@ -199,7 +244,11 @@ export function file_hash_matches(state: BuildState, in_file: string, new_hash:
|
||||
if (old_metadata.last_build.config_hash !== new_metadata.last_build.config_hash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function file_hash_matches(state: BuildState, in_file: string, new_hash: string) {
|
||||
in_file = in_file.slice(state.conf.input.root.length);
|
||||
const old_hash = state.old_metadata.files[in_file]?.last_build_hash;
|
||||
|
||||
@@ -217,7 +266,12 @@ export function file_hash_matches(state: BuildState, in_file: string, new_hash:
|
||||
export function skip_file(state: BuildState, in_file: string, out_file: string, out_url: OutFileURL, frontmatter?: any) {
|
||||
in_file = in_file.slice(state.conf.input.root.length);
|
||||
state.new_metadata.files[in_file] = structuredClone(state.old_metadata?.files?.[in_file]);
|
||||
handle_page_side_effects(state, out_file, out_url, frontmatter);
|
||||
handle_page_side_effects(state, in_file, out_file, out_url, frontmatter);
|
||||
}
|
||||
|
||||
export function copy_metadata(state: BuildState, in_file: string) {
|
||||
in_file = in_file.slice(state.conf.input.root.length);
|
||||
state.new_metadata.files[in_file] = structuredClone(state.old_metadata?.files?.[in_file]);
|
||||
}
|
||||
|
||||
export function update_metadata(state: BuildState, in_file: string, hash: string) {
|
||||
@@ -228,3 +282,23 @@ export function update_metadata(state: BuildState, in_file: string, hash: string
|
||||
last_updated_time: state.build_time.iso,
|
||||
};
|
||||
}
|
||||
|
||||
export function get_author(state: BuildState, frontmatter?: FrontMatter) {
|
||||
if (! frontmatter?.author) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Array.isArray(frontmatter.author)) {
|
||||
const list = frontmatter.author
|
||||
.map((author) => state.conf.authors?.[author])
|
||||
.filter((author) => author);
|
||||
|
||||
if (! list.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
return state.conf.authors?.[frontmatter.author]
|
||||
}
|
||||
|
131
src/build-files/icalendar.ts
Normal file
131
src/build-files/icalendar.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
|
||||
import { write_text } from '../fs';
|
||||
import { BuildState } from './state';
|
||||
import { map_input_file_to_output_file } from './helpers';
|
||||
import { parseICS, CalendarComponent } from 'ical';
|
||||
import create_calendar, { ICalEventData, ICalCalendarData } from 'ical-generator';
|
||||
|
||||
export type { ICalEventData, ICalCalendarData, ICalAttendeeData, ICalAttendeeStatus } from 'ical-generator';
|
||||
|
||||
export interface EventEntry {
|
||||
url: string;
|
||||
in_file: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
author_name?: string;
|
||||
author_email?: string;
|
||||
start_time?: string;
|
||||
end_time?: string;
|
||||
time_zone?: `${string}/${string}`;
|
||||
}
|
||||
|
||||
export async function write_events_and_calendars_if_needed(state: BuildState) {
|
||||
if (state.conf.events) {
|
||||
for (const entry of state.events) {
|
||||
const event = icalendar_event(state, entry);
|
||||
const cal_data: ICalCalendarData = {
|
||||
prodId: {
|
||||
company: 'jbrumond.me',
|
||||
product: 'docs2website',
|
||||
language: 'EN',
|
||||
},
|
||||
// ...
|
||||
};
|
||||
|
||||
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');
|
||||
await write_text(out_file, calendar);
|
||||
}
|
||||
}
|
||||
|
||||
if (state.conf.calendars) {
|
||||
for (let index = 0; index < state.conf.calendars.length; index++) {
|
||||
const cal_conf = state.conf.calendars[index];
|
||||
const cal_entries = state.calendars[index];
|
||||
|
||||
for (const entry of cal_entries) {
|
||||
const event = icalendar_event(state, entry);
|
||||
const cal_data: ICalCalendarData = {
|
||||
name: cal_conf.title,
|
||||
prodId: {
|
||||
company: 'jbrumond.me',
|
||||
product: 'docs2website',
|
||||
language: 'EN',
|
||||
},
|
||||
// ...
|
||||
};
|
||||
|
||||
const calendar = create_icalendar(cal_data, event);
|
||||
await write_text(cal_conf.out_file, calendar);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function parse_icalendar(contents: string) {
|
||||
const parsed = parseICS(contents);
|
||||
const calendar: CalendarComponent[] = [ ];
|
||||
|
||||
for (const data of Object.values(parsed)) {
|
||||
calendar.push(data);
|
||||
}
|
||||
|
||||
return calendar;
|
||||
}
|
||||
|
||||
export function create_icalendar(cal: ICalCalendarData, events: ICalEventData | ICalEventData[]) {
|
||||
const calendar = create_calendar(cal);
|
||||
|
||||
if (Array.isArray(events)) {
|
||||
for (const event of events) {
|
||||
calendar.createEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
calendar.createEvent(events);
|
||||
}
|
||||
|
||||
return calendar.toString();
|
||||
}
|
||||
|
||||
export function icalendar_event(state: BuildState, entry: EventEntry) : ICalEventData {
|
||||
const in_file = entry.in_file.slice(state.conf.input.root.length);
|
||||
const metadata = state.new_metadata.files[in_file];
|
||||
|
||||
return {
|
||||
id: entry.url,
|
||||
summary: entry.title,
|
||||
description: entry.description || void 0,
|
||||
start: entry.start_time,
|
||||
end: entry.end_time,
|
||||
url: entry.url,
|
||||
timezone: entry.time_zone,
|
||||
created: metadata.first_seen_time,
|
||||
lastModified: metadata.last_updated_time,
|
||||
organizer: {
|
||||
name: entry.author_name || 'Unknown',
|
||||
email: entry.author_email,
|
||||
},
|
||||
// attendees: post.mentions.flatMap((mention) : ICalAttendeeData | ICalAttendeeData[] => {
|
||||
// if (mention.is_rsvp && mention.is_reply_to_this) {
|
||||
// const ext = mention.external as ExternalEntry;
|
||||
// const status = ext.rsvp_type === 'yes'
|
||||
// ? 'ACCEPTED' as const
|
||||
// : ext.rsvp_type === 'no'
|
||||
// ? 'DECLINED' as const
|
||||
// : ext.rsvp_type === 'maybe'
|
||||
// ? 'TENTATIVE' as const
|
||||
// : 'NEEDS-ACTION' as const;
|
||||
|
||||
// return {
|
||||
// name: mention.author_name,
|
||||
// rsvp: ext.rsvp_type === 'yes',
|
||||
// status: status as ICalAttendeeStatus
|
||||
// };
|
||||
// }
|
||||
|
||||
// return [ ];
|
||||
// }),
|
||||
};
|
||||
}
|
@@ -14,6 +14,8 @@ import { render_text_file_templates } from './mustache';
|
||||
import { render_markdown_files } from './markdown';
|
||||
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';
|
||||
|
||||
export { BuildState, ThemeGroups } from './state';
|
||||
|
||||
@@ -55,7 +57,10 @@ export async function build_docs_project(conf: Config) {
|
||||
},
|
||||
extras: await load_extras(),
|
||||
made_directories: new Set<string>(),
|
||||
rss: [ ],
|
||||
sitemap: [ ],
|
||||
events: [ ],
|
||||
calendars: [ ],
|
||||
build_time: {
|
||||
iso: now.toISO(),
|
||||
rfc2822: now.toRFC2822(),
|
||||
@@ -87,8 +92,8 @@ export async function build_docs_project(conf: Config) {
|
||||
// todo: other file types...
|
||||
|
||||
await write_sitemap_if_needed(state);
|
||||
// todo: rss
|
||||
// todo: events
|
||||
await write_rss_if_needed(state);
|
||||
await write_events_and_calendars_if_needed(state);
|
||||
|
||||
// Write the updated metadata file
|
||||
await write_json(conf.metadata, state.new_metadata, true);
|
||||
|
@@ -4,7 +4,7 @@ import { BuildState } from './state';
|
||||
import { read_json, write_text, read_yaml } from '../fs';
|
||||
import { build_markdown_from_json_schema } from '@doc-utils/jsonschema2markdown';
|
||||
import { stringify as to_yaml } from 'yaml';
|
||||
import { build_partials, file_hash_matches, map_input_file_to_output_file, map_output_file_to_url, render_page, skip_file, update_metadata } from './helpers';
|
||||
import { build_partials, copy_metadata, file_hash_matches, map_input_file_to_output_file, map_output_file_to_url, render_page, skip_file, update_metadata } from './helpers';
|
||||
|
||||
export async function render_json_schema_files(state: BuildState) {
|
||||
const promises: Promise<any>[] = [ ];
|
||||
@@ -113,10 +113,6 @@ export async function render_json_schema(state: BuildState, schema: unknown, in_
|
||||
|
||||
const out_url = map_output_file_to_url(state, out_file);
|
||||
|
||||
if (file_hash_matches(state, in_file, hash)) {
|
||||
return skip_file(state, in_file, out_file, out_url, frontmatter);
|
||||
}
|
||||
|
||||
const promises: Promise<any>[] = [ ];
|
||||
const markdown = build_markdown_from_json_schema(schema);
|
||||
|
||||
@@ -128,9 +124,14 @@ export async function render_json_schema(state: BuildState, schema: unknown, in_
|
||||
}
|
||||
|
||||
promises.push(
|
||||
render_page(state, out_file, out_url, markdown, true, frontmatter)
|
||||
render_page(state, in_file, out_file, out_url, markdown, true, frontmatter)
|
||||
);
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
if (file_hash_matches(state, in_file, hash)) {
|
||||
return copy_metadata(state, in_file);
|
||||
}
|
||||
|
||||
update_metadata(state, in_file, hash);
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
import { glob } from 'glob';
|
||||
import { read_text } from '../fs';
|
||||
import { BuildState } from './state';
|
||||
import { build_partials, file_hash_matches, map_input_file_to_output_file, map_output_file_to_url, render_page, skip_file, update_metadata } from './helpers';
|
||||
import { build_partials, copy_metadata, file_hash_matches, map_input_file_to_output_file, map_output_file_to_url, render_page, skip_file, update_metadata } from './helpers';
|
||||
|
||||
export async function render_markdown_files(state: BuildState) {
|
||||
const promises: Promise<any>[] = [ ];
|
||||
@@ -38,11 +38,12 @@ export async function render_markdown_file(state: BuildState, in_file: string) {
|
||||
if (frontmatter?.skip) {
|
||||
return;
|
||||
}
|
||||
|
||||
await render_page(state, in_file, out_file, out_url, text, true, frontmatter);
|
||||
|
||||
if (file_hash_matches(state, in_file, hash)) {
|
||||
return skip_file(state, in_file, out_file, out_url, frontmatter);
|
||||
return copy_metadata(state, in_file);
|
||||
}
|
||||
|
||||
await render_page(state, out_file, out_url, text, true, frontmatter);
|
||||
update_metadata(state, in_file, hash);
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
import { glob } from 'glob';
|
||||
import { read_text } from '../fs';
|
||||
import { BuildState } from './state';
|
||||
import { build_partials, file_hash_matches, map_input_file_to_output_file, map_output_file_to_url, render_page, skip_file, update_metadata } from './helpers';
|
||||
import { build_partials, copy_metadata, file_hash_matches, map_input_file_to_output_file, map_output_file_to_url, render_page, skip_file, update_metadata } from './helpers';
|
||||
|
||||
export async function render_text_file_templates(state: BuildState) {
|
||||
const promises: Promise<any>[] = [ ];
|
||||
@@ -39,10 +39,11 @@ export async function render_text_file_template(state: BuildState, in_file: stri
|
||||
return;
|
||||
}
|
||||
|
||||
await render_page(state, in_file, out_file, out_url, text, false, frontmatter);
|
||||
|
||||
if (file_hash_matches(state, in_file, hash)) {
|
||||
return skip_file(state, in_file, out_file, out_url, frontmatter);
|
||||
return copy_metadata(state, in_file);
|
||||
}
|
||||
|
||||
await render_page(state, out_file, out_url, text, false, frontmatter);
|
||||
update_metadata(state, in_file, hash);
|
||||
}
|
||||
|
131
src/build-files/rss.ts
Normal file
131
src/build-files/rss.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
|
||||
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 { map_output_file_to_url } from './helpers';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
export interface RSSEntry {
|
||||
url: string;
|
||||
in_file: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
author_name?: string;
|
||||
html_content?: string;
|
||||
tags?: string[];
|
||||
}
|
||||
|
||||
export async function write_rss_if_needed(state: BuildState) {
|
||||
if (! state.conf.rss) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let index = 0; index < state.conf.rss.length; index++) {
|
||||
const rss_conf = state.conf.rss[index];
|
||||
const doc = create_xml({ version: '1.0', encoding: 'UTF-8' });
|
||||
const { abs_url: self_url } = map_output_file_to_url(state, rss_conf.out_file);
|
||||
|
||||
if (rss_conf.xsl) {
|
||||
for (const url of rss_conf.xsl) {
|
||||
doc.ins('xml-stylesheet', `href="${url}" type="text/xsl"`);
|
||||
}
|
||||
}
|
||||
|
||||
const rss = doc.ele('rss', {
|
||||
version: '2.0',
|
||||
'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
|
||||
'xmlns:content': 'http://purl.org/rss/1.0/modules/content/',
|
||||
'xmlns:atom': 'http://www.w3.org/2005/Atom',
|
||||
// 'xmlns:docs2website': 'urn:uuid:7fc4e5d4-f68e-11ed-b0d0-00155ddef564',
|
||||
});
|
||||
|
||||
const channel = rss.ele('channel');
|
||||
channel.ele('generator').txt(`docs2website ${app_version}`);
|
||||
|
||||
if (rss_conf.title) {
|
||||
channel.ele('title').txt(rss_conf.title);
|
||||
}
|
||||
|
||||
if (rss_conf.link) {
|
||||
channel.ele('link').txt(rss_conf.link);
|
||||
}
|
||||
|
||||
link(channel, 'application/rss+xml', 'self', self_url);
|
||||
|
||||
if (rss_conf.alternates?.length) {
|
||||
for (const alt of rss_conf.alternates) {
|
||||
link(channel, alt.type, 'alternate', alt.href);
|
||||
}
|
||||
}
|
||||
|
||||
if (rss_conf.description) {
|
||||
channel.ele('description').txt(rss_conf.description);
|
||||
}
|
||||
|
||||
if (rss_conf.language) {
|
||||
channel.ele('language').txt(rss_conf.language);
|
||||
}
|
||||
|
||||
if (rss_conf.copyright) {
|
||||
channel.ele('copyright').txt(rss_conf.copyright);
|
||||
}
|
||||
|
||||
channel.ele('lastBuildDate').txt(state.build_time.rfc2822);
|
||||
|
||||
// channel.ele('rating').txt(''); // see: https://www.w3.org/PICS/
|
||||
// channel.ele('pubDate').txt(publish_date.toUTCString());
|
||||
// channel.ele('categories').txt('');
|
||||
// channel.ele('docs').txt('');
|
||||
// channel.ele('managingEditor').txt('james@jbrumond.me');
|
||||
// channel.ele('webMaster').txt('james@jbrumond.me');
|
||||
// channel.ele('ttl').txt('');
|
||||
// channel.ele('image').txt('');
|
||||
// channel.ele('skipHours').txt('');
|
||||
// channel.ele('skipDays').txt('');
|
||||
|
||||
for (const entry of state.rss[index] || [ ]) {
|
||||
const item = channel.ele('item');
|
||||
const in_file = entry.in_file.slice(state.conf.input.root.length);
|
||||
const metadata = state.new_metadata.files[in_file];
|
||||
|
||||
item.ele('link').txt(entry.url);
|
||||
|
||||
if (entry.title) {
|
||||
item.ele('title').txt(entry.title);
|
||||
}
|
||||
|
||||
if (entry.description) {
|
||||
item.ele('description').txt(entry.description);
|
||||
}
|
||||
|
||||
if (entry.author_name) {
|
||||
item.ele('dc:creator').ele({ $: entry.author_name });
|
||||
}
|
||||
|
||||
if (entry.tags) {
|
||||
entry.tags.forEach((tag) => item.ele('category').txt(tag));
|
||||
}
|
||||
|
||||
item.ele('guid').txt(entry.url);
|
||||
item.ele('pubDate').txt(DateTime.fromISO(metadata.first_seen_time).toRFC2822());
|
||||
|
||||
if (entry.html_content) {
|
||||
item.ele('content:encoded').ele({ $: entry.html_content });
|
||||
}
|
||||
}
|
||||
|
||||
const xml = doc.toString({
|
||||
indent: ' ',
|
||||
prettyPrint: true,
|
||||
});
|
||||
|
||||
await write_text(rss_conf.out_file, xml);
|
||||
}
|
||||
}
|
||||
|
||||
function link(channel: XMLBuilder, type: string, rel: string, href: string) {
|
||||
// channel.ele('link', { href, rel, type, });
|
||||
channel.ele('atom:link', { type, rel, href, });
|
||||
}
|
@@ -3,6 +3,8 @@ import type { Config } from '../conf';
|
||||
import type { Metadata } from '../metadata';
|
||||
import type { ColorTheme } from '@doc-utils/color-themes';
|
||||
import type { SitemapEntry } from './sitemap';
|
||||
import { RSSEntry } from './rss';
|
||||
import { EventEntry } from './icalendar';
|
||||
|
||||
export interface BuildState {
|
||||
conf: Config;
|
||||
@@ -16,12 +18,10 @@ export interface BuildState {
|
||||
made_directories: Set<string>;
|
||||
old_metadata: Metadata;
|
||||
new_metadata: Metadata;
|
||||
// rss: {
|
||||
// url: string;
|
||||
// last_updated: string;
|
||||
// }[];
|
||||
rss: RSSEntry[][];
|
||||
sitemap: SitemapEntry[];
|
||||
// events: EventEntry[];
|
||||
events: EventEntry[];
|
||||
calendars: EventEntry[][];
|
||||
build_time: {
|
||||
iso: string;
|
||||
rfc2822: string;
|
||||
|
Reference in New Issue
Block a user