Skip to content

Commit

Permalink
i herd u like comments
Browse files Browse the repository at this point in the history
  • Loading branch information
jennspencer committed Jun 17, 2024
1 parent 023c2e7 commit f5cb19f
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 10 deletions.
4 changes: 3 additions & 1 deletion processor/compile/html-block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { reformatHTML, getHProps } from '../utils'
const htmlBlock = (node: HTMLBlock) => {
const { runScripts, html } = getHProps<HTMLBlock['data']['hProperties']>(node);

return `<HTMLBlock${runScripts != null ? ' runScripts="' + runScripts + '"' : ''}>{\`\n${ reformatHTML(html) }\n\`}</HTMLBlock>`;
return `<HTMLBlock${runScripts != null ? ` runScripts="${runScripts}"` : ''}>{\`
${ reformatHTML(html) }
\`}</HTMLBlock>`;
}

export default htmlBlock;
90 changes: 81 additions & 9 deletions processor/utils.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,52 @@
import { Node } from 'mdast';
import { MdxJsxFlowElement, MdxJsxTextElement, MdxFlowExpression } from 'mdast-util-mdx';

export const formatHProps = <T>(node: Node) => {
/**
* Formats the hProperties of a node as a string, so they can be compiled back into JSX/MDX.
* This currently sets all the values to a string since we process/compile the MDX on the fly
* through the editor, and it'll throw errors over malformed JSX. TODO: fix this.
*
* @param {Node} node
* @returns {string} formatted hProperties as JSX attributes
*/
export const formatHProps = (node: Node): string => {
const hProps = getHProps(node);
const hPropKeys = getHPropKeys(node) as string[];
return hPropKeys.map(key => `${key}="${hProps[key]}"`).join(' ') as T;
return hPropKeys.map(key => `${key}="${hProps[key]}"`).join(' ');
}

export const getHProps = <T>(node: Node) => {
/**
* Returns the hProperties of a node.
*
* @template T
* @param {Node} node
* @returns {T} hProperties
*/
export const getHProps = <T>(node: Node): T => {
const hProps = node.data?.hProperties || {};
return hProps as T;
}

export const getHPropKeys = <T>(node: Node) => {
/**
* Returns array of hProperty keys.
*
* @template T
* @param {Node} node
* @returns {Array} array of hProperty keys
*/
export const getHPropKeys = <T>(node: Node): any => {
const hProps = getHProps(node);
return Object.keys(hProps) || [] as T;
}

export const getAttrs = <T>(jsx: MdxJsxFlowElement | MdxJsxTextElement) =>
/**
* Gets the attributes of an MDX element and returns them as an object of hProperties.
*
* @template T
* @param {(MdxJsxFlowElement | MdxJsxTextElement)} jsx
* @returns {T} object of hProperties
*/
export const getAttrs = <T>(jsx: MdxJsxFlowElement | MdxJsxTextElement): any =>
jsx.attributes.reduce((memo, attr) => {
if ('name' in attr) {
memo[attr.name] = attr.value;
Expand All @@ -26,35 +55,78 @@ export const getAttrs = <T>(jsx: MdxJsxFlowElement | MdxJsxTextElement) =>
return memo;
}, {} as T);

export const getChildren = <T>(jsx: MdxJsxFlowElement | MdxJsxTextElement) =>
/**
* Gets the children of an MDX element and returns them as an array of Text nodes.
* Currently only being used by the HTML Block component, which only expects a single text node.
*
* @template T
* @param {(MdxJsxFlowElement | MdxJsxTextElement)} jsx
* @returns {Array} array of child text nodes
*/
export const getChildren = <T>(jsx: MdxJsxFlowElement | MdxJsxTextElement): any =>
jsx.children.reduce((memo, child: MdxFlowExpression, i) => {
memo[i] = {
// TODO: infer type
type: 'text',
value: child.value,
position: child.position,
};
return memo;
}, [] as T);

/**
* Tests if a node is an MDX element.
* TODO: Make this more extensible to all types of nodes. isElement(node, 'type' or ['type1', 'type2']), say
*
* @param {Node} node
* @returns {(node is MdxJsxFlowElement | MdxJsxTextElement)}
*/
export const isMDXElement = (node: Node): node is MdxJsxFlowElement | MdxJsxTextElement => {
return ['mdxJsxFlowElement', 'mdxJsxTextElement'].includes(node.type);
}

/**
* Takes an HTML string and formats it for display in the editor. Removes leading/trailing newlines
* and unindents the HTML.
*
* @param {string} html
* @returns {string} formatted HTML
*/
export const formatHTML = (html: string): string => {
// Remove leading/trailing backticks if present, since they're used to keep the HTML
// from being parsed prematurely
if (html.startsWith('`') && html.endsWith('`')) {
html = html.slice(1, -1);
}
// Removes the leading/trailing newlinesl
const cleaned = html.replace(/^\s*\n|\n\s*$/g, '');

// Get the number of spaces in the first line to determine the tab size
const tab = cleaned.match(/^\s*/)[0].length;

// Remove the first indentation level from each line
const tabRegex = new RegExp(`^\\s{${tab}}`, 'gm');
const unindented = cleaned.replace(tabRegex, '');

return unindented;
}

export const reformatHTML = (html: string, indent = 2) => {
/**
* Reformat HTML for the markdown/mdx by adding an indentation to each line. This assures that the
* HTML is indentend properly within the HTMLBlock component when rendered in the markdown/mdx.
*
* @param {string} html
* @param {number} [indent=2]
* @returns {string} re-formatted HTML
*/
export const reformatHTML = (html: string, indent: number = 2): string => {
// Remove leading/trailing newlines
const cleaned = html.replace(/^\s*\n|\n\s*$/g, '');

// Create a tab/indent with the specified number of spaces
const tab = ' '.repeat(indent);

// Indent each line of the HTML (converts to an array, indents each line, then joins back)
const indented = cleaned.split('\n').map((line: string) => `${tab}${line}`).join('\n');

return indented;
}
}

0 comments on commit f5cb19f

Please sign in to comment.