Improved markdown rendering, allow hiding components
This commit is contained in:
parent
d200d48903
commit
a9a979c5f8
10 changed files with 598 additions and 100 deletions
|
@ -1,14 +1,47 @@
|
|||
import path from "node:path";
|
||||
import { type ParsedComponent, parsePlaybillComponent } from "define";
|
||||
import slugify from "slugify";
|
||||
import YAML, { YAMLParseError } from "yaml";
|
||||
import { ZodError } from "zod";
|
||||
import { loadFileOrFail } from "#util";
|
||||
import { MalformedResourceFileError } from "./errors";
|
||||
import { extractFrontmatter, extractMarkdown } from "./markdown";
|
||||
import { toSlug } from "./slug";
|
||||
|
||||
type FileFormat = "yaml" | "markdown";
|
||||
|
||||
const FRONTMATTER_REGEX = /^---[\s\S]*?---/gm;
|
||||
|
||||
/**
|
||||
* Attempt to parse YAML frontmatter from a mixed yaml/md doc
|
||||
* @param content The raw markdown content
|
||||
* @returns Any successfully parsed frontmatter
|
||||
*/
|
||||
function extractFrontmatter(content: string) {
|
||||
// If it does not start with `---`, it is invalid for frontmatter
|
||||
if (content.trim().indexOf("---") !== 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (FRONTMATTER_REGEX.test(content)) {
|
||||
const frontmatterString = content.match(FRONTMATTER_REGEX)?.[0] ?? "";
|
||||
const cleanFrontmatter = frontmatterString.replaceAll("---", "").trim();
|
||||
return YAML.parse(cleanFrontmatter);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a string of a markdown document, extract the markdown content
|
||||
* @param content The raw markdown content
|
||||
* @returns The markdown content without frontmatter
|
||||
*/
|
||||
function extractMarkdown(content: string): string {
|
||||
if (content.trim().indexOf("---") !== 0) {
|
||||
return content;
|
||||
}
|
||||
return content.replace(FRONTMATTER_REGEX, "").trim();
|
||||
}
|
||||
|
||||
function parseYAMLResourceFile(
|
||||
filePath: string,
|
||||
text: string,
|
||||
|
@ -33,7 +66,10 @@ function parseYAMLResourceFile(
|
|||
|
||||
for (const doc of parsedDocs) {
|
||||
const raw = doc.toJS();
|
||||
collection.push(parsePlaybillComponent(raw));
|
||||
const parsedComponent = parsePlaybillComponent(raw);
|
||||
if (parsedComponent !== null) {
|
||||
collection.push(parsedComponent);
|
||||
}
|
||||
}
|
||||
|
||||
return collection;
|
||||
|
@ -45,7 +81,7 @@ function parseMarkdownResourceFile(
|
|||
): ParsedComponent[] {
|
||||
try {
|
||||
const defaultName = path.basename(filePath, ".md");
|
||||
const defaultId = slugify(defaultName, { lower: true });
|
||||
const defaultId = toSlug(defaultName);
|
||||
const frontmatter = extractFrontmatter(text);
|
||||
const markdown = extractMarkdown(text);
|
||||
|
||||
|
@ -57,6 +93,11 @@ function parseMarkdownResourceFile(
|
|||
description: markdown,
|
||||
});
|
||||
|
||||
// Null means hidden
|
||||
if (together === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [together];
|
||||
} catch (e) {
|
||||
if (e instanceof YAMLParseError) {
|
||||
|
@ -84,11 +125,13 @@ export class ComponentFile {
|
|||
private _raw = "";
|
||||
private _format: FileFormat;
|
||||
private _components: ParsedComponent[] = [];
|
||||
private _basename: string;
|
||||
|
||||
constructor(filePath: string) {
|
||||
this._filePath = path.resolve(filePath);
|
||||
|
||||
const extension = path.extname(filePath).slice(1).toLowerCase();
|
||||
this._basename = path.basename(filePath, `.${extension}`);
|
||||
|
||||
switch (extension) {
|
||||
case "yaml":
|
||||
|
@ -126,4 +169,8 @@ export class ComponentFile {
|
|||
get path() {
|
||||
return this._filePath;
|
||||
}
|
||||
|
||||
get baseName() {
|
||||
return this._basename;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export * from "./errors";
|
||||
export * from "./markdown";
|
||||
export * from "./pfm";
|
||||
export * from "./component-file";
|
||||
export * from "./binding";
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
import type { MarkedExtension } from "marked";
|
||||
import { marked } from "marked";
|
||||
import YAML from "yaml";
|
||||
|
||||
const plugin: MarkedExtension = {
|
||||
renderer: {
|
||||
heading({ tokens, depth }) {
|
||||
const text = this.parser.parseInline(tokens);
|
||||
const level = Math.max(depth, 3);
|
||||
return `<h${level}>${text}</h${level}>`;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
marked.use(plugin);
|
||||
|
||||
/**
|
||||
* Given a string of markdown, convert it to HTML
|
||||
*
|
||||
* @param markdown The markdown to convert
|
||||
* @returns A promise resolving to the HTML representation of the markdown
|
||||
*/
|
||||
export async function renderMarkdown(markdown: string): Promise<string> {
|
||||
return await marked.parse(markdown, {
|
||||
async: true,
|
||||
gfm: true,
|
||||
});
|
||||
}
|
||||
const FRONTMATTER_REGEX = /^---[\s\S]*?---/gm;
|
||||
|
||||
/**
|
||||
* Attempt to parse YAML frontmatter from a mixed yaml/md doc
|
||||
* @param content The raw markdown content
|
||||
* @returns Any successfully parsed frontmatter
|
||||
*/
|
||||
export function extractFrontmatter(content: string) {
|
||||
// If it does not start with `---`, it is invalid for frontmatter
|
||||
if (content.trim().indexOf("---") !== 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (FRONTMATTER_REGEX.test(content)) {
|
||||
const frontmatterString = content.match(FRONTMATTER_REGEX)?.[0] ?? "";
|
||||
const cleanFrontmatter = frontmatterString.replaceAll("---", "").trim();
|
||||
return YAML.parse(cleanFrontmatter);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a string of a markdown document, extract the markdown content
|
||||
* @param content The raw markdown content
|
||||
* @returns The markdown content without frontmatter
|
||||
*/
|
||||
export function extractMarkdown(content: string): string {
|
||||
if (content.trim().indexOf("---") !== 0) {
|
||||
return content;
|
||||
}
|
||||
return content.replace(FRONTMATTER_REGEX, "").trim();
|
||||
}
|
47
src/lib/pfm.ts
Normal file
47
src/lib/pfm.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
import rehypeStringify from "rehype-stringify";
|
||||
import remarkParse from "remark-parse";
|
||||
import remarkRehype from "remark-rehype";
|
||||
import { unified } from "unified";
|
||||
import type { BoundPlaybill } from "./binding";
|
||||
import remarkWikiLink from "remark-wiki-link";
|
||||
import { toSlug } from "./slug";
|
||||
import rehypeRaw from "rehype-raw";
|
||||
import rehypeSanitize from "rehype-sanitize";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import rehypeShiftHeading from "rehype-shift-heading";
|
||||
|
||||
export type MarkdownParserFunction = (input: string) => Promise<string>;
|
||||
|
||||
export function createMarkdownRenderer(
|
||||
binding: BoundPlaybill,
|
||||
): MarkdownParserFunction {
|
||||
// TODO: Allow specifying links to other files via file paths
|
||||
// In this scenario, assume its a markdown-defined file and find the ID, use that for the link
|
||||
// to allow for more natural Obsidian-like linking
|
||||
// For now, this will mostly work, but if you need to specify a unique ID, it wont work in Obsidian
|
||||
// despite being valid for Muse
|
||||
|
||||
const parser = unified()
|
||||
.use(remarkParse)
|
||||
.use(remarkGfm)
|
||||
.use(remarkWikiLink, {
|
||||
aliasDivider: "|",
|
||||
permalinks: binding.playbill
|
||||
.allComponents()
|
||||
.map((c) => `${c.component.id}`),
|
||||
pageResolver: (permalink: string) => {
|
||||
return [toSlug(permalink)];
|
||||
},
|
||||
hrefTemplate: (permalink: string) => `#component/${permalink}`,
|
||||
})
|
||||
.use(remarkRehype, { allowDangerousHtml: true })
|
||||
.use(rehypeRaw)
|
||||
.use(rehypeShiftHeading, { shift: 1 })
|
||||
.use(rehypeSanitize)
|
||||
.use(rehypeStringify);
|
||||
|
||||
return async (input: string): Promise<string> => {
|
||||
const parsed = await parser.process(input);
|
||||
return String(parsed);
|
||||
};
|
||||
}
|
5
src/lib/slug.ts
Normal file
5
src/lib/slug.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import slugify from "slugify";
|
||||
|
||||
export function toSlug(str: string): string {
|
||||
return slugify(str, { lower: true });
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue