diff --git a/src/index.ts b/src/index.ts index d7c3556..a2d2f5c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,127 +5,127 @@ import type { LeafDirective } from "mdast-util-directive"; import type { Plugin } from "unified"; import { visit } from "unist-util-visit"; import { - isContainerDirective, - isImage, - isLink, - isParagraph, - isText, + isContainerDirective, + isImage, + isLink, + isParagraph, + isText, } from "./utils.js"; export interface Config { - customHTMLTags: { - enabled: boolean; - }; - imageContainerClass: string; - contentContainerClass: string; + customHTMLTags: { + enabled: boolean; + }; + imageContainerClass: string; + contentContainerClass: string; } export const defaultConfig: Config = { - customHTMLTags: { - enabled: false, - }, - imageContainerClass: "image-container", - contentContainerClass: "content-container", + customHTMLTags: { + enabled: false, + }, + imageContainerClass: "image-container", + contentContainerClass: "content-container", }; const remarkCard: Plugin<[Config?], Root> = (config = defaultConfig) => { - return (tree) => { - visit(tree, isContainerDirective, (node) => { - if (node.name !== "card-grid") return; - if (node.children.length === 0) return; - - node.data = { - ...node.data, - hName: config.customHTMLTags.enabled ? "card-grid" : "div", - hProperties: { - ...node.attributes, - }, - }; - }); - - visit(tree, isContainerDirective, (node) => { - if (node.name !== "card") return; - if (node.children.length === 0) return; - - const [firstNode, secondNode, ..._restNodes] = node.children; - if (!isParagraph(firstNode)) return; - if (firstNode.children.length === 0) return; - - let cardImageOrLink: PhrasingContent; - let cardContent: PhrasingContent[]; - let cardLabel = ""; - - const imageContainer: LeafDirective = { - type: "leafDirective", - name: "image-container", - data: { - hName: "div", - hProperties: { - className: config.imageContainerClass, - }, - }, - children: [], - }; - - const contentContainer: LeafDirective = { - type: "leafDirective", - name: "content-container", - data: { - hName: "div", - hProperties: { - className: config.contentContainerClass, - }, - }, - children: [], - }; - - if (firstNode.data?.directiveLabel === true) { - if (!isText(firstNode.children[0])) return; - - cardLabel = firstNode.children[0].value; - - if (!isParagraph(secondNode)) return; - const [imageOrLink, ...restContent] = secondNode.children; - cardImageOrLink = imageOrLink; - cardContent = restContent; - } else { - const [imageOrLink, ...restContent] = firstNode.children; - cardImageOrLink = imageOrLink; - cardContent = restContent; - } - - if (isImage(cardImageOrLink)) { - cardImageOrLink.alt = cardImageOrLink.alt || cardLabel; - } else if (isLink(cardImageOrLink)) { - const cardImage = cardImageOrLink.children[0]; - if (!isImage(cardImage)) return; - - cardImage.alt = cardImage.alt || cardLabel; - } else { - return; - } - - imageContainer.children.push(cardImageOrLink); - - for (const contentElem of cardContent) { - contentContainer.children.push(contentElem); - } - - node.data = { - ...node.data, - hName: config.customHTMLTags.enabled ? "card" : "div", - hProperties: { - ...node.attributes, - }, - }; - node.children.splice( - 0, - Number.POSITIVE_INFINITY, - imageContainer, - contentContainer - ); - }); - }; + return (tree) => { + visit(tree, isContainerDirective, (node) => { + if (node.name !== "card-grid") return; + if (node.children.length === 0) return; + + node.data = { + ...node.data, + hName: config.customHTMLTags.enabled ? "card-grid" : "div", + hProperties: { + ...node.attributes, + }, + }; + }); + + visit(tree, isContainerDirective, (node) => { + if (node.name !== "card") return; + if (node.children.length === 0) return; + + const [firstNode, secondNode, ..._restNodes] = node.children; + if (!isParagraph(firstNode)) return; + if (firstNode.children.length === 0) return; + + let cardImageOrLink: PhrasingContent; + let cardContent: PhrasingContent[]; + let cardLabel = ""; + + const imageContainer: LeafDirective = { + type: "leafDirective", + name: "image-container", + data: { + hName: "div", + hProperties: { + className: config.imageContainerClass, + }, + }, + children: [], + }; + + const contentContainer: LeafDirective = { + type: "leafDirective", + name: "content-container", + data: { + hName: "div", + hProperties: { + className: config.contentContainerClass, + }, + }, + children: [], + }; + + if (firstNode.data?.directiveLabel === true) { + if (!isText(firstNode.children[0])) return; + + cardLabel = firstNode.children[0].value; + + if (!isParagraph(secondNode)) return; + const [imageOrLink, ...restContent] = secondNode.children; + cardImageOrLink = imageOrLink; + cardContent = restContent; + } else { + const [imageOrLink, ...restContent] = firstNode.children; + cardImageOrLink = imageOrLink; + cardContent = restContent; + } + + if (isImage(cardImageOrLink)) { + cardImageOrLink.alt = cardImageOrLink.alt || cardLabel; + } else if (isLink(cardImageOrLink)) { + const cardImage = cardImageOrLink.children[0]; + if (!isImage(cardImage)) return; + + cardImage.alt = cardImage.alt || cardLabel; + } else { + return; + } + + imageContainer.children.push(cardImageOrLink); + + for (const contentElem of cardContent) { + contentContainer.children.push(contentElem); + } + + node.data = { + ...node.data, + hName: config.customHTMLTags.enabled ? "card" : "div", + hProperties: { + ...node.attributes, + }, + }; + node.children.splice( + 0, + Number.POSITIVE_INFINITY, + imageContainer, + contentContainer, + ); + }); + }; }; export default remarkCard; diff --git a/test/index.test.ts b/test/index.test.ts index 3da31d6..0386ef0 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -9,49 +9,49 @@ import { unified } from "unified"; import remarkCard, { type Config, defaultConfig } from "../src/index.js"; const normalizeHtml = (html: string) => { - return html.replace(/[\n\s]*(<)|>([\n\s]*)/g, (_match, p1, _p2) => - p1 ? "<" : ">" - ); + return html.replace(/[\n\s]*(<)|>([\n\s]*)/g, (_match, p1, _p2) => + p1 ? "<" : ">", + ); }; const parseMarkdown = mock( - async (markdown: string, options: Config = defaultConfig, debug = false) => { - const remarkProcessor = unified() - .use(remarkParse) - .use(remarkDirective) - .use(remarkCard, options) - .use(remarkRehype) - .use(rehypeStringify); - - if (debug) { - const remarkOutput = await remarkProcessor.run( - remarkProcessor.parse(markdown) - ); - console.log("Remark output:", JSON.stringify(remarkOutput, null, 2)); - } - - const output = String(await remarkProcessor.process(markdown)); - - if (debug) { - console.log( - `HTML output: - ${normalizeHtml(output)}` - ); - } - - return output; - } + async (markdown: string, options: Config = defaultConfig, debug = false) => { + const remarkProcessor = unified() + .use(remarkParse) + .use(remarkDirective) + .use(remarkCard, options) + .use(remarkRehype) + .use(rehypeStringify); + + if (debug) { + const remarkOutput = await remarkProcessor.run( + remarkProcessor.parse(markdown), + ); + console.log("Remark output:", JSON.stringify(remarkOutput, null, 2)); + } + + const output = String(await remarkProcessor.process(markdown)); + + if (debug) { + console.log( + `HTML output: + ${normalizeHtml(output)}`, + ); + } + + return output; + }, ); describe("Test the basic usage of card", () => { - test("Card with single-line text & image", async () => { - const input = ` + test("Card with single-line text & image", async () => { + const input = ` :::card ![image alt](https://xxxxx.xxx/yyy.jpg) Single-line text ::: `; - const output = ` + const output = `
image alt @@ -60,19 +60,19 @@ describe("Test the basic usage of card", () => {
`; - const html = await parseMarkdown(input); + const html = await parseMarkdown(input); - expect(normalizeHtml(html)).toBe(normalizeHtml(output)); - }); + expect(normalizeHtml(html)).toBe(normalizeHtml(output)); + }); - test("Card with class attributes and single-line text & image", async () => { - const input = ` + test("Card with class attributes and single-line text & image", async () => { + const input = ` :::card{.card.solid-border} ![image alt](https://xxxxx.xxx/yyy.jpg) Single-line text ::: `; - const output = ` + const output = `
image alt @@ -81,19 +81,19 @@ describe("Test the basic usage of card", () => {
`; - const html = await parseMarkdown(input); + const html = await parseMarkdown(input); - expect(normalizeHtml(html)).toBe(normalizeHtml(output)); - }); + expect(normalizeHtml(html)).toBe(normalizeHtml(output)); + }); - test("Card with a directive label and single-line text & image with no alt text", async () => { - const input = ` + test("Card with a directive label and single-line text & image with no alt text", async () => { + const input = ` :::card[card alt] ![](https://xxxxx.xxx/yyy.jpg) Single-line text ::: `; - const output = ` + const output = `
card alt @@ -102,19 +102,19 @@ describe("Test the basic usage of card", () => {
`; - const html = await parseMarkdown(input); + const html = await parseMarkdown(input); - expect(normalizeHtml(html)).toBe(normalizeHtml(output)); - }); + expect(normalizeHtml(html)).toBe(normalizeHtml(output)); + }); - test("Card with a directive label and single-line text & image", async () => { - const input = ` + test("Card with a directive label and single-line text & image", async () => { + const input = ` :::card[card alt] ![image alt](https://xxxxx.xxx/yyy.jpg) Single-line text ::: `; - const output = ` + const output = `
image alt @@ -123,13 +123,13 @@ describe("Test the basic usage of card", () => {
`; - const html = await parseMarkdown(input); + const html = await parseMarkdown(input); - expect(normalizeHtml(html)).toBe(normalizeHtml(output)); - }); + expect(normalizeHtml(html)).toBe(normalizeHtml(output)); + }); - test("Card with multiple-line text & image", async () => { - const input = ` + test("Card with multiple-line text & image", async () => { + const input = ` :::card ![image alt](https://xxxxx.xxx/yyy.jpg) Multiple @@ -137,7 +137,7 @@ describe("Test the basic usage of card", () => { Text ::: `; - const output = ` + const output = `
image alt @@ -147,19 +147,19 @@ describe("Test the basic usage of card", () => {
`; - const html = await parseMarkdown(input); + const html = await parseMarkdown(input); - expect(normalizeHtml(html)).toBe(normalizeHtml(output)); - }); + expect(normalizeHtml(html)).toBe(normalizeHtml(output)); + }); - test("Card with strong text & image", async () => { - const input = ` + test("Card with strong text & image", async () => { + const input = ` :::card ![image alt](https://xxxxx.xxx/yyy.jpg) **Strong text** ::: `; - const output = ` + const output = `
image alt @@ -170,19 +170,19 @@ describe("Test the basic usage of card", () => {
`; - const html = await parseMarkdown(input); + const html = await parseMarkdown(input); - expect(normalizeHtml(html)).toBe(normalizeHtml(output)); - }); + expect(normalizeHtml(html)).toBe(normalizeHtml(output)); + }); - test("Card with link text & image", async () => { - const input = ` + test("Card with link text & image", async () => { + const input = ` :::card ![image alt](https://xxxxx.xxx/yyy.jpg) [Link text](https://xxxxx.xxx/) ::: `; - const output = ` + const output = `
image alt @@ -193,19 +193,19 @@ describe("Test the basic usage of card", () => {
`; - const html = await parseMarkdown(input); + const html = await parseMarkdown(input); - expect(normalizeHtml(html)).toBe(normalizeHtml(output)); - }); + expect(normalizeHtml(html)).toBe(normalizeHtml(output)); + }); - test("Card with emphasized text & image", async () => { - const input = ` + test("Card with emphasized text & image", async () => { + const input = ` :::card ![image alt](https://xxxxx.xxx/yyy.jpg) _Emphasized text_ ::: `; - const output = ` + const output = `
image alt @@ -216,19 +216,19 @@ describe("Test the basic usage of card", () => {
`; - const html = await parseMarkdown(input); + const html = await parseMarkdown(input); - expect(normalizeHtml(html)).toBe(normalizeHtml(output)); - }); + expect(normalizeHtml(html)).toBe(normalizeHtml(output)); + }); - test("Card with image & image", async () => { - const input = ` + test("Card with image & image", async () => { + const input = ` :::card ![image alt](https://xxxxx.xxx/yyy.jpg) ![image alt 2](https://xxxxx.xxx/zzz.jpg) ::: `; - const output = ` + const output = `
image alt @@ -239,19 +239,19 @@ describe("Test the basic usage of card", () => {
`; - const html = await parseMarkdown(input); + const html = await parseMarkdown(input); - expect(normalizeHtml(html)).toBe(normalizeHtml(output)); - }); + expect(normalizeHtml(html)).toBe(normalizeHtml(output)); + }); - test("Card with single-line text & image link", async () => { - const input = ` + test("Card with single-line text & image link", async () => { + const input = ` :::card [![image alt](https://xxxxx.xxx/yyy.jpg)](https://xxxxx.xxx/) Single-line text ::: `; - const output = ` + const output = `
`; - const html = await parseMarkdown(input); - - expect(normalizeHtml(html)).toBe(normalizeHtml(output)); - }); - - // test("Nested cards", async () => { - // const input = ` - // :::::card{.parent} - // ![parent card](https://xxxxx.xxx/xxx.jpg) - // Parent - // ::::card{.child} - // ![child card](https://xxxxx.xxx/yyy.jpg) - // Child - // :::card{.grandchild} - // ![grandchild card](https://xxxxx.xxx/zzz.jpg) - // Grandchild - // ::: - // :::: - // ::::: - // `; - // const output = ` - //
- //
- // parent card - //
- //
- // Parent - //
- //
- // child card - //
- //
- // Child - //
- //
- // grandchild card - //
- //
- // Grandchild - //
- //
- //
- //
- //
- //
- // `; - - // const html = await parseMarkdown(input); - - // expect(normalizeHtml(html)).toBe(normalizeHtml(output)); - // }); - - test("Card with no content", async () => { - const input = ` + const html = await parseMarkdown(input); + + expect(normalizeHtml(html)).toBe(normalizeHtml(output)); + }); + + // test("Nested cards", async () => { + // const input = ` + // :::::card{.parent} + // ![parent card](https://xxxxx.xxx/xxx.jpg) + // Parent + // ::::card{.child} + // ![child card](https://xxxxx.xxx/yyy.jpg) + // Child + // :::card{.grandchild} + // ![grandchild card](https://xxxxx.xxx/zzz.jpg) + // Grandchild + // ::: + // :::: + // ::::: + // `; + // const output = ` + //
+ //
+ // parent card + //
+ //
+ // Parent + //
+ //
+ // child card + //
+ //
+ // Child + //
+ //
+ // grandchild card + //
+ //
+ // Grandchild + //
+ //
+ //
+ //
+ //
+ //
+ // `; + + // const html = await parseMarkdown(input); + + // expect(normalizeHtml(html)).toBe(normalizeHtml(output)); + // }); + + test("Card with no content", async () => { + const input = ` :::card ::: `; - const output = ` + const output = `
`; - const html = await parseMarkdown(input); + const html = await parseMarkdown(input); - expect(normalizeHtml(html)).toBe(normalizeHtml(output)); - }); + expect(normalizeHtml(html)).toBe(normalizeHtml(output)); + }); - test("Custom Tag Card with single-line text & image", async () => { - const input = ` + test("Custom Tag Card with single-line text & image", async () => { + const input = ` :::card ![image alt](https://xxxxx.xxx/yyy.jpg) Single-line text ::: `; - const output = ` + const output = `
image alt @@ -344,19 +344,19 @@ describe("Test the basic usage of card", () => { `; - const html = await parseMarkdown(input, { - customHTMLTags: { enabled: true }, - imageContainerClass: "image-container", - contentContainerClass: "content-container", - }); + const html = await parseMarkdown(input, { + customHTMLTags: { enabled: true }, + imageContainerClass: "image-container", + contentContainerClass: "content-container", + }); - expect(normalizeHtml(html)).toBe(normalizeHtml(output)); - }); + expect(normalizeHtml(html)).toBe(normalizeHtml(output)); + }); }); describe("Test the basic usage of card-grid", () => { - test("Card grid & some cards", async () => { - const input = ` + test("Card grid & some cards", async () => { + const input = ` ::::card-grid :::card{.card-1} ![card 1](https://xxxxx.xxx/yyy.jpg) @@ -372,7 +372,7 @@ describe("Test the basic usage of card-grid", () => { ::: :::: `; - const output = ` + const output = `
@@ -395,13 +395,13 @@ describe("Test the basic usage of card-grid", () => {
`; - const html = await parseMarkdown(input); + const html = await parseMarkdown(input); - expect(normalizeHtml(html)).toBe(normalizeHtml(output)); - }); + expect(normalizeHtml(html)).toBe(normalizeHtml(output)); + }); - test("Card grid with class attributes & some cards", async () => { - const input = ` + test("Card grid with class attributes & some cards", async () => { + const input = ` ::::card-grid{.card-grid} :::card{.card-1} ![card 1](https://xxxxx.xxx/yyy.jpg) @@ -417,7 +417,7 @@ describe("Test the basic usage of card-grid", () => { ::: :::: `; - const output = ` + const output = `
@@ -440,13 +440,13 @@ describe("Test the basic usage of card-grid", () => {
`; - const html = await parseMarkdown(input); + const html = await parseMarkdown(input); - expect(normalizeHtml(html)).toBe(normalizeHtml(output)); - }); + expect(normalizeHtml(html)).toBe(normalizeHtml(output)); + }); - test("Card grid with a directive label & some cards", async () => { - const input = ` + test("Card grid with a directive label & some cards", async () => { + const input = ` ::::card-grid[Card grid label] :::card{.card-1} ![card 1](https://xxxxx.xxx/yyy.jpg) @@ -462,7 +462,7 @@ describe("Test the basic usage of card-grid", () => { ::: :::: `; - const output = ` + const output = `

Card grid label

@@ -486,27 +486,27 @@ describe("Test the basic usage of card-grid", () => {
`; - const html = await parseMarkdown(input); + const html = await parseMarkdown(input); - expect(normalizeHtml(html)).toBe(normalizeHtml(output)); - }); + expect(normalizeHtml(html)).toBe(normalizeHtml(output)); + }); - test("Card grid & no card", async () => { - const input = ` + test("Card grid & no card", async () => { + const input = ` ::::card-grid :::: `; - const output = ` + const output = `
`; - const html = await parseMarkdown(input); + const html = await parseMarkdown(input); - expect(normalizeHtml(html)).toBe(normalizeHtml(output)); - }); + expect(normalizeHtml(html)).toBe(normalizeHtml(output)); + }); - test("Custom Tag Card grid & some cards", async () => { - const input = ` + test("Custom Tag Card grid & some cards", async () => { + const input = ` ::::card-grid :::card{.card-1} ![card 1](https://xxxxx.xxx/yyy.jpg) @@ -522,7 +522,7 @@ describe("Test the basic usage of card-grid", () => { ::: :::: `; - const output = ` + const output = `
@@ -545,12 +545,12 @@ describe("Test the basic usage of card-grid", () => { `; - const html = await parseMarkdown(input, { - customHTMLTags: { enabled: true }, - imageContainerClass: "image-container", - contentContainerClass: "content-container", - }); + const html = await parseMarkdown(input, { + customHTMLTags: { enabled: true }, + imageContainerClass: "image-container", + contentContainerClass: "content-container", + }); - expect(normalizeHtml(html)).toBe(normalizeHtml(output)); - }); + expect(normalizeHtml(html)).toBe(normalizeHtml(output)); + }); });