import type {AstroIntegration, ContentEntryType, HookParameters} from "astro" import {fileURLToPath} from "url" import {parse as parseYaml} from "yaml" export interface GemtextConfig {} type SetupHookParams = HookParameters<"astro:config:setup"> & { // `addPageExtension` and `contentEntryType` are not a public APIs // Add type defs here addPageExtension: (extension: string) => void addContentEntryType: (contentEntryType: ContentEntryType) => void } export default function gemtext(config: GemtextConfig = {}): AstroIntegration { return { name: "astro-gemtext", hooks: { "astro:config:setup": async (_params) => { const params = _params as SetupHookParams params.addPageExtension(".gmi") params.addContentEntryType({ extensions: [".gmi"], getRenderFunction: async (config) => { return async (entry) => ({ html: entry.body ?? "", frontmatter: entry.data, }) }, getEntryInfo: async ({fileUrl, contents}) => { const lines = contents.split("\n") let data: Record = {} let frontmatter = "" let bodyStartLineIndex = 0 if (lines.at(0) === "---" && lines.lastIndexOf("---") > 0) { bodyStartLineIndex = lines.indexOf("---", 1) + 1 frontmatter = lines .slice(1, bodyStartLineIndex - 1) .join("\n") try { data = { ...data, ...parseYaml(frontmatter, {schema: "yaml-1.1"}), } } catch (e) { console.debug( "Failed to parse YAML frontmatter in gemtext:", e, ) } } const body = lines.slice(bodyStartLineIndex).join("\n") const filepath = fileURLToPath(fileUrl) const filename = filepath.slice(filepath.lastIndexOf("/") + 1) const slug = typeof data["slug"] === "string" ? data["slug"] : filename data["title"] = data["title"] ?? lines .slice(bodyStartLineIndex) .find((line) => line.startsWith("# ")) ?.slice(2) data["pubDate"] = data["pubDate"] ?? validDateOrUndefined( lines .slice(bodyStartLineIndex) .find((line) => line.startsWith("Published on: ")) ?.slice(14), ) ?? validDateOrUndefined(filename.replace(/\.[^.]\+$/, "")) ?? validDateOrUndefined(slug.replace(/\.[^.]\+$/, "")) data["updatedDate"] = data["updatedDate"] ?? validDateOrUndefined( lines .slice(bodyStartLineIndex) .find((line) => line.startsWith("Updated on: ")) ?.slice(14), ) return { data, rawData: frontmatter, body, slug, } }, }) }, }, } } const validDateOrUndefined = (x: unknown) => { const d = new Date(x as string) if (isNaN(d.valueOf())) { return undefined } return d }