Skip to content

Commit

Permalink
[ch-markdown-viewer] Add support for showIndicator property in th…
Browse files Browse the repository at this point in the history
…e markdown-viewer when rendering `rawHTML` (#436)

* Add support to showIndicator property in the markdown-viewer when rendering rawHTML

* Update markdown.showcase.tsx

* Fix showcase
  • Loading branch information
ncamera authored Oct 11, 2024
1 parent 33015d6 commit cf45db1
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 17 deletions.
3 changes: 2 additions & 1 deletion src/components/markdown-viewer/markdown-viewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ export class ChMarkdownViewer {
allowDangerousHtml: true, // Allow dangerous in this version
codeRender: this.renderCode,
lastNestedChildClass: LAST_NESTED_CHILD_CLASS,
rawHTML: this.rawHtml
rawHTML: this.rawHtml,
showIndicator: this.showIndicator
});
}

Expand Down
15 changes: 10 additions & 5 deletions src/components/markdown-viewer/parsers/markdown-to-jsx.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { h } from "@stencil/core";
import { AlignType, Code, Root, Table } from "mdast";
import { AlignType, Code, Html, Root, Table } from "mdast";
import { markdownToMdAST } from "@genexus/markdown-parser";

import {
Expand All @@ -26,9 +26,9 @@ export const LAST_NESTED_CHILD_CLASS = "last-nested-child";

// Lazy load the code parser implementation
let HTMLToJSX: typeof rawHTMLToJSX;
let lastNestedChild: Root | ElementsWithChildren | Code;
let lastNestedChild: Root | ElementsWithChildren | Code | Html;

const isLastNestedChildClass = (element: ElementsWithChildren | Code) =>
const isLastNestedChildClass = (element: ElementsWithChildren | Code | Html) =>
element === lastNestedChild;

const checkAndGetLastNestedChildClass = (
Expand Down Expand Up @@ -226,7 +226,12 @@ export const renderDictionary: {
}

return metadata.rawHTML
? HTMLToJSX(element.value, metadata.allowDangerousHtml)
? HTMLToJSX(
element.value,
metadata.allowDangerousHtml,
// TODO: Add unit tests for these cases
metadata.showIndicator && isLastNestedChildClass(element)
)
: element.value;
},

Expand Down Expand Up @@ -347,7 +352,7 @@ const findLastNestedChild = (
return findLastNestedChild(lastChild as ElementsWithChildren);
}

if (lastChild.type === "code") {
if (lastChild.type === "code" || lastChild.type === "html") {
return lastChild;
}

Expand Down
50 changes: 43 additions & 7 deletions src/components/markdown-viewer/parsers/raw-html-to-jsx.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { h } from "@stencil/core";
import { fromHTMLStringToHast } from "@genexus/markdown-parser/dist/parse-html.js";
// import { Root, RootContent, RootContentMap } from "hast";
import { Element as HElement, Root, RootContentMap } from "hast";
import { LAST_NESTED_CHILD_CLASS } from "./markdown-to-jsx";

const tagsToSanitize = new Set([
"base",
Expand Down Expand Up @@ -45,10 +46,14 @@ const getStyleObjectFromString = (
};

const renderDictionary: {
[key in keyof RootContentMap]: (element: RootContentMap[key]) => any;
[key in keyof RootContentMap]: (
element: RootContentMap[key],
showIndicator: boolean,
lastElementChild: HElement | undefined
) => any;
} = {
comment: () => "",
element: element => {
element: (element, showIndicator, lastElementChild) => {
const properties = element.properties;

// Minimal sanitization
Expand Down Expand Up @@ -84,28 +89,59 @@ const renderDictionary: {
delete properties.htmlFor;
}

// Indicator for streaming
const isLastElementChild = showIndicator && element === lastElementChild;
if (isLastElementChild) {
properties.class = properties.class
? `${properties.class} ${LAST_NESTED_CHILD_CLASS}`
: LAST_NESTED_CHILD_CLASS;
}

return (
<element.tagName {...properties}>
{renderChildren(element)}
{renderChildren(element, showIndicator, lastElementChild)}
</element.tagName>
);
},
text: element => element.value,
doctype: () => ""
};

function renderChildren(element: Root | HElement) {
return element.children.map(child => renderDictionary[child.type](child));
function renderChildren(
element: Root | HElement,
showIndicator: boolean,
lastElementChild: HElement | undefined
) {
return element.children.map(child =>
renderDictionary[child.type](child, showIndicator, lastElementChild)
);
}

const findLastNestedChild = (elementWithChildren: HElement) => {
const lastChild = elementWithChildren.children.at(-1) as HElement;

// The last element have children. We must check its sub children
if (lastChild.children?.length > 0) {
return findLastNestedChild(lastChild);
}

return elementWithChildren;
};

export const rawHTMLToJSX = (
htmlString: string,
allowDangerousHtml: boolean
allowDangerousHtml: boolean,
showIndicator: boolean
) => {
const hast: Root = fromHTMLStringToHast(htmlString, allowDangerousHtml, {
fragment: true
});

const lastElementChild =
hast.children.at(-1).type === "element"
? findLastNestedChild(hast.children.at(-1) as HElement)
: undefined;

// Render the hast as JSX
return renderChildren(hast);
return renderChildren(hast, showIndicator, lastElementChild);
};
1 change: 1 addition & 0 deletions src/components/markdown-viewer/parsers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export type MarkdownViewerToJSXCommonMetadata = {
codeRender: MarkdownViewerCodeRender;
lastNestedChildClass: string;
rawHTML: boolean;
showIndicator: boolean;
};

export type MarkdownViewerCodeRender = (
Expand Down
20 changes: 16 additions & 4 deletions src/showcase/assets/components/markdown/markdown.showcase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import { markdownReadmeModel } from "./models";

let initialMarkdown = markdownReadmeModel;
let rawHTMLEnabled = "false";
let showIndicator = "false";

let checkboxRef: HTMLChCheckboxElement;
let checkboxRawHTMLRef: HTMLChCheckboxElement;
let checkboxIndicatorRef: HTMLChCheckboxElement;
let textareaRef: HTMLTextAreaElement;

const handleValueChange = () => {
Expand All @@ -22,12 +24,13 @@ const handleValueChange = () => {
};

const handleCheckboxValueChange = () => {
rawHTMLEnabled = checkboxRef.value;
rawHTMLEnabled = checkboxRawHTMLRef.value;
showIndicator = checkboxIndicatorRef.value;

// TODO: Until we support external slots in the ch-flexible-layout-render,
// this is a hack to update the render of the widget and thus re-render the
// combo-box updating the displayed items
const showcaseRef = checkboxRef.closest("ch-showcase");
const showcaseRef = checkboxRawHTMLRef.closest("ch-showcase");

if (showcaseRef) {
forceUpdate(showcaseRef);
Expand All @@ -42,7 +45,15 @@ const render: ShowcaseRender = designSystem => (
class="checkbox"
checkedValue="true"
onInput={handleCheckboxValueChange}
ref={el => (checkboxRef = el)}
ref={el => (checkboxRawHTMLRef = el)}
></ch-checkbox>

<ch-checkbox
caption="Show indicator"
class="checkbox"
checkedValue="true"
onInput={handleCheckboxValueChange}
ref={el => (checkboxIndicatorRef = el)}
></ch-checkbox>
</div>

Expand All @@ -63,6 +74,7 @@ const render: ShowcaseRender = designSystem => (
}
value={initialMarkdown}
rawHtml={rawHTMLEnabled === "true"}
showIndicator={showIndicator === "true"}
></ch-markdown-viewer>
</div>
);
Expand Down

0 comments on commit cf45db1

Please sign in to comment.