UNPKG

@telefonica/markdown-confluence-sync

Version:

Creates/updates/deletes Confluence pages based on markdown files in a directory. Supports Mermaid diagrams and per-page configuration using frontmatter metadata. Works great with Docusaurus

138 lines (137 loc) 4.51 kB
// SPDX-FileCopyrightText: 2024 Telefónica Innovación Digital // SPDX-License-Identifier: Apache-2.0 import rehypeParse from "rehype-parse"; import rehypeStringify from "rehype-stringify"; import { remark } from "remark"; import remarkRehype from "remark-rehype"; import { replace } from "../../../../../lib/support/unist/unist-util-replace.js"; import { InvalidDetailsTagMissingSummaryError } from "../../errors/InvalidDetailsTagMissingSummaryError.js"; /** * UnifiedPlugin to replace \<details\> HastElements from tree. * * @example * <details> * <summary>Greetings</summary> * <p>Hi</p> * </details> * // becomes * <ac:structured-macro ac:name="expand"> * <ac:parameter ac:name="title">Greetings</ac:parameter> * <ac:rich-text-body><p>Hi</p></ac:rich-text-body> * </ac:structured-macro> * @throws {InvalidDetailsTagMissingSummaryError} if \<details\> tag does not have a \<summary\> tag * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details} */ const rehypeReplaceDetails = function rehypeReplaceDetails() { return function (tree) { // FIXME: typescript error inferring the types of replace function <t:CROSS-1484> // Getting Typescript following error when running `check:types:test:unit: // TS2589: Type instantiation is excessively deep and possibly infinite. // @ts-expect-error TS2589 replace(tree, { type: "element", tagName: "details" }, replaceDetailsTag); }; }; function replaceDetailsTag(node) { const detailTitle = node.children.find((child) => child.type === "element" && child.tagName === "summary"); if (detailTitle === undefined) { throw new InvalidDetailsTagMissingSummaryError(); } const childrenCopy = [...node.children]; childrenCopy.splice(childrenCopy.indexOf(detailTitle), 1); const childrenProcessed = processDetailsTagChildren(childrenCopy); return { type: "element", tagName: "ac:structured-macro", properties: { "ac:name": "expand", }, children: [ { type: "element", tagName: "ac:parameter", properties: { "ac:name": "title", }, children: [...detailTitle.children], }, { type: "element", tagName: "ac:rich-text-body", children: childrenProcessed, }, ], }; } /** Parse a string and return a hast HastElement of type body having as children the parsed content * @param file - The file to parse * @returns The hast HastElement of type body * @example * parseStringToElement("<h1>Hello, world!</h1>Bye, world!") * // returns * { * type: 'element', * tagName: 'body', * properties: {}, * children: [ * { * type: 'element', * tagName: 'h1', * properties: {}, * children: [ * { * type: 'text', * value: 'Hello, world!' * } * ] * }, * { * type: 'text', * value: 'Bye, world!' * } * ] * } */ function parseStringToElement(file) { return remark().use(rehypeParse).parse(file).children[0] .children[1]; } /** Convert a hast node to a string * @param node - The hast node to convert * @returns The string representation of the node * @example * convertHastNodeToString({ type: 'element', tagName: 'h1', properties: {}, children: [{ type: 'text', value: 'Hello, world!'}]}) * // returns * '<h1>Hello, world!</h1>' */ function convertHastNodeToString(node) { return remark() .use(rehypeStringify, { allowDangerousHtml: true, closeSelfClosing: true, tightSelfClosing: true, }) .stringify(node); } function processDetailsTagChildren(children) { return children .map((child) => { if (child.type === "element" && child.tagName === "details") { return convertHastNodeToString(replaceDetailsTag(child)); } if (child.type === "text") { return remark() .use(remarkRehype) .use(rehypeStringify, { allowDangerousHtml: true, closeSelfClosing: true, tightSelfClosing: true, }) .processSync(child.value); } return convertHastNodeToString(child); }) .map(parseStringToElement) .map((child) => child.children) .flat(); } export default rehypeReplaceDetails;