Add callout as a seperate type

This commit is contained in:
Peaceultime 2024-12-04 17:59:28 +01:00
parent be5f48feba
commit cccfccc299
1 changed files with 50 additions and 81 deletions

View File

@ -4,14 +4,27 @@ import { toHast } from "mdast-util-to-hast"
import { toHtml } from "hast-util-to-html" import { toHtml } from "hast-util-to-html"
import { toString } from 'mdast-util-to-string'; import { toString } from 'mdast-util-to-string';
import { FindAndReplaceList, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace" import { FindAndReplaceList, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace"
import type { Callout, Root, Tag } from "mdast";
declare module 'mdast' declare module 'mdast'
{ {
interface Tag extends Literal { interface Tag extends Literal
{
type: "tag"; type: "tag";
data?: TagData | undefined; data?: TagData | undefined;
} }
interface TagData extends Data {} interface TagData extends Data {}
interface Callout extends Parent
{
type: "callout";
data?: CalloutData | undefined;
}
interface CalloutData extends Data
{
type: string;
title?: string;
fold?: boolean;
}
interface PhrasingContentMap interface PhrasingContentMap
{ {
tag: Tag; tag: Tag;
@ -19,6 +32,7 @@ declare module 'mdast'
interface RootContentMap interface RootContentMap
{ {
tag: Tag; tag: Tag;
callout: Callout;
} }
} }
@ -139,9 +153,9 @@ function pathToRoot(slug: string) {
} }
export default function ofm() { export default function ofm() {
return function (tree: any, file: any) { return function (tree: Root, file: any) {
const base = pathToRoot(file?.data?.slug ?? ''); const base = pathToRoot(file?.data?.slug ?? '');
const replacements: FindAndReplaceList = [] const replacements: FindAndReplaceList = [];
replacements.push([ replacements.push([
wikilinkRegex, wikilinkRegex,
@ -164,7 +178,7 @@ export default function ofm() {
], ],
} }
}, },
]) ]);
replacements.push([ replacements.push([
highlightRegex, highlightRegex,
@ -175,7 +189,7 @@ export default function ofm() {
value: `<span class="text-highlight">${inner}</span>`, value: `<span class="text-highlight">${inner}</span>`,
} }
}, },
]) ]);
replacements.push([ replacements.push([
commentRegex, commentRegex,
@ -186,7 +200,7 @@ export default function ofm() {
value: `<span class="text-comment">${inner}</span>`, value: `<span class="text-comment">${inner}</span>`,
} }
}, },
]) ]);
replacements.push([ replacements.push([
tagRegex, tagRegex,
@ -210,11 +224,11 @@ export default function ofm() {
{ type: 'text', value: tag } { type: 'text', value: tag }
] ]
} }
} } as Tag;
}, },
]) ]);
visit(tree, "html", (node) => { /*visit(tree, "html", (node) => {
for (const [regex, replace] of replacements) { for (const [regex, replace] of replacements) {
if (typeof replace === "string") { if (typeof replace === "string") {
node.value = node.value.replace(regex, replace) node.value = node.value.replace(regex, replace)
@ -233,93 +247,48 @@ export default function ofm() {
}) })
} }
} }
}) })*/
mdastFindReplace(tree, replacements); mdastFindReplace(tree, replacements);
visit(tree, "blockquote", (node) => { visit(tree, "blockquote", (node, index, parent) => {
if (node.children.length === 0) { if (node.children.length === 0 || parent === null || index === null) {
return return;
} }
// find first line // find first line
const firstChild = node.children[0] const firstChild = node.children[0];
if (firstChild.type !== "paragraph" || firstChild.children[0]?.type !== "text") { if (firstChild.type !== "paragraph" || firstChild.children[0]?.type !== "text") {
return return;
} }
const text = firstChild.children[0].value const text = firstChild.children[0].value;
const restChildren = firstChild.children.slice(1) const [firstLine, ...remainingLines] = text.split("\n");
const [firstLine, ...remainingLines] = text.split("\n")
const remainingText = remainingLines.join("\n")
const match = firstLine.match(/^\[\!(\w+)\]([+-]?)/); const match = firstLine.match(/^\[\!(\w+)\]([+-]?)/);
if (match && match.input) { if (match && match.input) {
const [calloutDirective, typeString, collapseChar] = match const [calloutTitle, typeString, foldChar] = match;
const calloutType = canonicalizeCallout( const type = canonicalizeCallout(
typeString.toLowerCase() as keyof typeof calloutMapping, typeString.toLowerCase() as keyof typeof calloutMapping,
) );
const collapse = collapseChar === "+" || collapseChar === "-" const fold = foldChar === "+" || foldChar === "-" ? foldChar === "-" : undefined;
const defaultState = collapseChar === "-" ? "collapsed" : "expanded" const title = match.input.slice(calloutTitle.length).trim() || capitalize(type);
const titleContent =
match.input.slice(calloutDirective.length).trim() || capitalize(calloutType)
const titleNode = {
type: "paragraph",
children: [{ type: "text", value: titleContent + " " }, ...restChildren],
}
const title = mdastToHtml(titleNode)
const toggleIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="fold"> firstChild.children[0].value = remainingLines.join('\n');
<polyline points="6 9 12 15 18 9"></polyline> parent.children[index] = {
</svg>` type: 'callout',
data: {
const titleHtml = { type,
type: "html", fold,
value: `<div title,
class="callout-title" hName: 'callout',
> hProperties: {
<div class="callout-icon">${callouts[calloutType]}</div> type,
<div class="callout-title-inner">${title}</div> fold,
${collapse ? toggleIcon : ""} title,
</div>`, },
}
const blockquoteContent: any[] = [titleHtml]
if (remainingText.length > 0) {
blockquoteContent.push({
type: "paragraph",
hProperties: { className: "callout-content" },
children: [
{
type: "text",
value: remainingText,
},
],
})
}
// replace first line of blockquote with title and rest of the paragraph text
node.children.splice(0, 1, ...blockquoteContent)
// add properties to base blockquote
node.data = {
hProperties: {
...(node.data?.hProperties ?? {}),
className: `callout ${collapse ? "is-collapsible" : ""} ${defaultState === "collapsed" ? "is-collapsed" : ""
}`,
"data-callout": calloutType,
"data-callout-fold": collapse,
},
}
}
})
visit(tree, "code", (node) => {
if (node.lang === "mermaid") {
node.data = {
hProperties: {
className: ["mermaid"],
}, },
children: firstChild.children,
} }
} }
}) })