_includes/config.ts (187 lines of code) (raw):
import {
CollectionApi,
getResourceMap,
RegisterIncludesProps,
} from "../src/registration";
import { Resource, ResourceMap } from "../src/ResourceModels";
import { Author } from "./resources/author/AuthorModels";
import { Topic } from "./resources/topic/TopicModels";
import { Tip } from "./resources/tip/TipModels";
import { Tutorial } from "./resources/tutorial/TutorialModels";
import { TutorialStep } from "./resources/tutorial/TutorialStepModels";
import { Playlist } from "./resources/playlist/PlaylistModels";
import { dumpSchemas } from "../src/schemas";
import * as fs from "fs";
import MarkdownIt from "markdown-it";
import { bundledLanguages, getHighlighter } from "shiki";
import {
getResource,
getResources,
QueryFilter,
RESOURCE_MODELS_BY_TYPE,
} from "./queries";
import { Page } from "./resources/page/PageModels";
import { Article } from "./resources/article/ArticleModels";
import { Channel } from "./resources/channel/ChannelModels";
import { RESOURCE_TYPES } from "../src/resourceType";
import { Link } from "./resources/link/LinkModels";
import path from "upath";
import { darkTheme } from "jetbrains-ide-themes";
import { jsxToString } from "jsx-async-runtime";
export type ResourceMapType = {
channel: Channel;
page: Page;
playlist: Playlist;
tip: Tip;
tutorial: Tutorial;
tutorialstep: TutorialStep;
article: Article;
author: Author;
topic: Topic;
link: Link;
};
// noinspection TypeScriptRedundantGenericType
export const resourceClasses: Record<
RESOURCE_TYPES,
new (...args: any[]) => Resource<RESOURCE_TYPES>
> = {
channel: Channel,
page: Page,
playlist: Playlist,
tip: Tip,
tutorial: Tutorial,
tutorialstep: TutorialStep,
article: Article,
author: Author,
topic: Topic,
link: Link,
resource: Resource,
} as const;
export async function registerIncludes(
{ eleventyConfig }: RegisterIncludesProps,
sitePath: string,
) {
let resourceMap: ResourceMap;
eleventyConfig.addExtension(["11ty.jsx", "11ty.ts", "11ty.tsx"], {
key: "11ty.js",
compile: function () {
return async function (data: any) {
// @ts-ignore
const content: JSX.Element = await this.defaultRenderer(data);
return jsxToString(content);
};
},
});
eleventyConfig.addTemplateFormats("11ty.ts,11ty.tsx");
eleventyConfig.addTemplateFormats("11ty.jsx, 11ty.tsx");
eleventyConfig.addTemplateFormats("11ty.jsx, 11ty.tsx");
// jsx doesn't let you add <!DOCTYPE html> as an element
// this hacks the rendering to force the content in at transform time
eleventyConfig.addTransform("doctype-jsx", function (content: any) {
// @ts-ignore
if ((this.page.outputPath || "").endsWith(".html")) {
return `<!DOCTYPE html>${content}`;
}
// If not an HTML output, return content as-is
return content;
});
let resources: Resource[];
eleventyConfig.addCollection(
`resourceMap`,
async function (collectionApi: CollectionApi) {
// Make the resource map of resolved resources
resourceMap = getResourceMap({ collectionApi, resourceClasses });
resources = Array.from(resourceMap.values());
// Generate JSON Schemas
const schemas = Object.entries(resourceClasses).reduce(
(acc, [key, resourceClass]: [string, any]) => {
return { ...acc, [key]: resourceClass.frontmatterSchema };
},
{},
);
const schemasOutputPath = path.join(
"docs",
"schemas",
path.basename(sitePath),
);
fs.mkdirSync(schemasOutputPath, { recursive: true });
await dumpSchemas(schemas, resourceMap, schemasOutputPath);
return resourceMap;
},
);
// Query helpers
eleventyConfig.addJavaScriptFunction(
"getResources",
(filter: QueryFilter): RESOURCE_MODELS_BY_TYPE | null =>
getResources(resources, filter),
);
eleventyConfig.addJavaScriptFunction(
"getResource",
(url: string): Resource => getResource(resources, url),
);
// centralize Markdown configuration
const md = new MarkdownIt("commonmark", {
html: true,
breaks: false,
linkify: true,
}).enable("table");
// custom markdown renderer
eleventyConfig.setLibrary("md", md);
eleventyConfig.addJavaScriptFunction(
"renderMarkdown",
(content: string): string => {
return md.render(content);
},
);
eleventyConfig.on("eleventy.before", async () => {
const highlighter = await getHighlighter({
themes: [darkTheme],
langs: Object.keys(bundledLanguages),
});
eleventyConfig.amendLibrary("md", (mdLib: any) =>
mdLib.set({
highlight: (code: string, lang: string) =>
highlighter.codeToHtml(code, { lang, theme: "Jetbrains Dark Theme" }),
}),
);
});
// This is a hack to let eleventy know that we touch that library
eleventyConfig.amendLibrary("md", () => {});
// register any short codes that can be used in content or layouts
addShortcodes(eleventyConfig);
}
function addShortcodes(eleventyConfig: any) {
// short code
eleventyConfig.addShortcode("cta", async function (msgOverride: any) {
// @ts-ignore
const callToAction = this.ctx.environments.callToAction;
if (callToAction) {
let { title, message, action, url, image } = callToAction;
if (msgOverride) {
message = msgOverride;
}
if (image) {
image = `<figure class="media-left">
<p class="image is-64x64">
<img alt='call to action image' src='${image}'>
</p>
</figure>`;
}
let button = "";
if (action && url) {
const isUrlRelativeToBase = !url.includes("http");
const formattedUrl = isUrlRelativeToBase
? url.endsWith("/")
? url
: `${url}/`
: url;
button = `<p><a href="${formattedUrl}" class="mt-2 button is-info is-rounded" target="_blank">${action}</a></p>`;
}
return `
<article class="message is-info">
<div class="message-header">
<p>${title}</p>
</div>
<div class="message-body">
<div class="media">
${image}
<div class="media-content">
<div class="content">
${message}
${button}
</div>
</div>
</div>
</div>
</article>
`;
} else {
// @ts-ignore
console.error(`missing call to action in: ${this.page.inputPath}`);
}
});
}