Files
blog/packages/@zblog/toolchain/src/markdown-loader.ts

144 lines
4.1 KiB
TypeScript

import fs from "fs";
import {
copyFile,
readFileAsText,
writeTextAsFile,
} from "./toolchain-helpers.js";
import { toHtml } from "./markdown-parser.js";
import { IPluginBuilder, MenuManifest, Plugin } from "./typings.js";
import { path } from "@ffmpeg-installer/ffmpeg";
import ffmpeg from "fluent-ffmpeg";
type MarkdownLoaderManifestOptions = {
notesRootPath: string;
noteFileName: string;
assetsDirectoryName: string;
imageWidth?: number;
};
type MarkdownLoaderManifest = {
directoryName: string;
name: string;
markdown: string;
assetDirectoryPath: string;
publicAssetDirectoryPath: string;
assetFiles: { path: string; name: string }[];
}[];
const createMarkdownLoaderManifest = ({
notesRootPath,
assetsDirectoryName,
noteFileName,
}: MarkdownLoaderManifestOptions): MarkdownLoaderManifest => {
const directoryNames = fs.readdirSync(notesRootPath);
return directoryNames.map((noteDirectory) => {
const assetDirectoryPath = `${notesRootPath}/${noteDirectory}/${assetsDirectoryName}`;
return {
directoryName: noteDirectory,
name: noteDirectory,
markdown: fs.readFileSync(
`${notesRootPath}/${noteDirectory}/${noteFileName}`,
{ encoding: "utf-8" }
),
assetDirectoryPath,
publicAssetDirectoryPath: `/${noteDirectory}_`,
assetFiles: fs.existsSync(assetDirectoryPath)
? fs.readdirSync(assetDirectoryPath).map((name) => ({
path: `${assetDirectoryPath}/${name}`,
name,
}))
: [],
};
});
};
const copyAssetsToOutputDirectory = (
manifest: MarkdownLoaderManifest,
outputDirectory: string,
options: MarkdownLoaderOptions
) => {
ffmpeg.setFfmpegPath(path);
manifest.forEach((m) => {
return m.assetFiles.forEach((asset) => {
const isImage = asset.path.match(/\.(png|jpe?g|gif|webp)$/);
if (isImage) {
console.log("Processing image", asset.path);
ffmpeg(asset.path)
.output(`${outputDirectory}/${m.name}_${asset.name}`)
.size(`${options.imageWidth ?? 200}x?`)
.run();
} else {
return copyFile(
asset.path,
`${outputDirectory}/${m.name}_${asset.name}`
);
}
});
});
};
const writeMarkdownAsHtmlToOutputDirectory = (
markdownManifest: MarkdownLoaderManifest,
noteHtmlTemplate: string,
markdownHtmlReplacementTag: string,
outputDirectory: string
) => {
markdownManifest.forEach((m) => {
const markdownAsHtml = toHtml(m.markdown, m.publicAssetDirectoryPath);
const titleWithoutDashesOrNumbers = (m.name.slice(m.name.indexOf('-') + 1) ?? "").replaceAll('-', ' ');
const noteHtmlDocument = noteHtmlTemplate.replace(
markdownHtmlReplacementTag,
markdownAsHtml
).replace("{{title}}", titleWithoutDashesOrNumbers);
writeTextAsFile(
`${outputDirectory}/${m.directoryName}.html`,
noteHtmlDocument
);
});
};
type MarkdownLoaderOptions = {
rootPath: string;
assetsDirectoryName: string;
noteFileName: string;
noteHtmlTemplatePath: string;
markdownHtmlReplacementTag: string;
imageWidth?: number;
};
export class MarkdownLoader implements IPluginBuilder {
options: MarkdownLoaderOptions;
constructor(options: MarkdownLoaderOptions) {
this.options = options;
}
build(): Plugin {
return (builderContext) => {
const markdownManifest = createMarkdownLoaderManifest({
notesRootPath: this.options.rootPath,
assetsDirectoryName: this.options.assetsDirectoryName,
noteFileName: this.options.noteFileName,
});
builderContext.menuManifest.push(
...markdownManifest.map((m) => ({
name: m.name,
link: `/${m.directoryName}.html`,
}))
);
const noteHtmlTemplate = readFileAsText(
this.options.noteHtmlTemplatePath
);
writeMarkdownAsHtmlToOutputDirectory(
markdownManifest,
noteHtmlTemplate,
this.options.markdownHtmlReplacementTag,
builderContext.outputDirectory
);
copyAssetsToOutputDirectory(
markdownManifest,
builderContext.outputDirectory,
this.options
);
};
}
}