import { removeMultipleSlashes } from "@scandic-hotels/common/utils/url" import Body from "@scandic-hotels/design-system/Body" import Caption from "@scandic-hotels/design-system/Caption" import { Divider } from "@scandic-hotels/design-system/Divider" import Footnote from "@scandic-hotels/design-system/Footnote" import Image from "@scandic-hotels/design-system/Image" import ImageContainer from "@scandic-hotels/design-system/ImageContainer" import Link from "@scandic-hotels/design-system/Link" import Subtitle from "@scandic-hotels/design-system/Subtitle" import Title from "@scandic-hotels/design-system/Title" import { AvailableParagraphFormatEnum, RTEItemTypeEnum, RTETypeEnum, } from "@scandic-hotels/trpc/types/RTEenums" import { insertResponseToImageVaultAsset } from "@scandic-hotels/trpc/utils/imageVault" import Table from "../TempDesignSystem/Table" import BiroScript from "../TempDesignSystem/Text/BiroScript" import { hasAvailableParagraphFormat, hasAvailableULFormat } from "./utils" import styles from "./jsontohtml.module.css" import type { ImageVaultAsset } from "@scandic-hotels/trpc/types/imageVault" import type { EmbedByUid } from "@/types/components/deprecatedjsontohtml" import { EmbedEnum } from "@/types/requests/utils/embeds" import type { Attributes, RTEImageVaultAttrs } from "@/types/rte/attrs" import { type RTEDefaultNode, type RTEImageNode, RTEMarkType, type RTENext, type RTENode, type RTERegularNode, type RTETextNode, } from "@/types/rte/node" import type { RenderOptions } from "@/types/rte/option" function extractPossibleAttributes(attrs: Attributes | undefined) { if (!attrs) return {} const props: Record = {} if (attrs.id) { props.id = attrs.id } if (attrs.class) { props.className = attrs.class } else if (attrs["class-name"]) { props.className = attrs["class-name"] } else if (attrs.classname) { props.className = attrs.classname } else if (attrs?.style?.["text-align"]) { props.style = { textAlign: attrs?.style?.["text-align"], } } return props } export const renderOptions: RenderOptions = { [RTETypeEnum.a]: ( node: RTERegularNode, embeds: EmbedByUid, next: RTENext, fullRenderOptions: RenderOptions ) => { if (node.attrs.url) { const props = extractPossibleAttributes(node.attrs) return ( {next(node.children, embeds, fullRenderOptions)} ) } return null }, [RTETypeEnum.blockquote]: ( node: RTEDefaultNode, embeds: EmbedByUid, next: RTENext, fullRenderOptions: RenderOptions ) => { const props = extractPossibleAttributes(node.attrs) return ( {next(node.children, embeds, fullRenderOptions)} ) }, [RTETypeEnum.code]: ( node: RTEDefaultNode, embeds: EmbedByUid, next: RTENext, fullRenderOptions: RenderOptions ) => { const props = extractPossibleAttributes(node.attrs) return ( {next(node.children, embeds, fullRenderOptions)} ) }, [RTETypeEnum.embed]: ( node: RTEDefaultNode, embeds: EmbedByUid, next: RTENext, fullRenderOptions: RenderOptions ) => { const props = extractPossibleAttributes(node.attrs) if (node.attrs.src) { props.src = node.attrs.src } if (node.attrs.url) { props.src = node.attrs.url } if (!props.src) { return null } return ( ) }, [RTETypeEnum.h1]: ( node: RTEDefaultNode, embeds: EmbedByUid, next: RTENext, fullRenderOptions: RenderOptions ) => { const props = extractPossibleAttributes(node.attrs) return ( {next(node.children, embeds, fullRenderOptions)} ) }, [RTETypeEnum.h2]: ( node: RTEDefaultNode, embeds: EmbedByUid, next: RTENext, fullRenderOptions: RenderOptions ) => { const props = extractPossibleAttributes(node.attrs) return ( {next(node.children, embeds, fullRenderOptions)} ) }, [RTETypeEnum.h3]: ( node: RTEDefaultNode, embeds: EmbedByUid, next: RTENext, fullRenderOptions: RenderOptions ) => { const props = extractPossibleAttributes(node.attrs) return ( {next(node.children, embeds, fullRenderOptions)} ) }, [RTETypeEnum.h4]: ( node: RTEDefaultNode, embeds: EmbedByUid, next: RTENext, fullRenderOptions: RenderOptions ) => { const props = extractPossibleAttributes(node.attrs) return ( {next(node.children, embeds, fullRenderOptions)} ) }, [RTETypeEnum.h5]: ( node: RTEDefaultNode, embeds: EmbedByUid, next: RTENext, fullRenderOptions: RenderOptions ) => { const props = extractPossibleAttributes(node.attrs) return ( {next(node.children, embeds, fullRenderOptions)} ) }, [RTETypeEnum.hr]: () => { return }, [RTETypeEnum.li]: ( node: RTEDefaultNode, embeds: EmbedByUid, next: RTENext, fullRenderOptions: RenderOptions ) => { const props = extractPossibleAttributes(node.attrs) return (
  • {next(node.children, embeds, fullRenderOptions)}
  • ) }, [RTETypeEnum.ol]: ( node: RTEDefaultNode, embeds: EmbedByUid, next: RTENext, fullRenderOptions: RenderOptions ) => { const props = extractPossibleAttributes(node.attrs) // Set the number of rows dynamically to create even rows for each column. We want the li:s // to flow with the column, so therefore this is needed. let numberOfRows: number | undefined if (node.children.length > 4) { const half = node.children.length / 2 numberOfRows = Math.ceil(half) } return (
      {next(node.children, embeds, fullRenderOptions)}
    ) }, [RTETypeEnum.p]: ( node: RTEDefaultNode, embeds: EmbedByUid, next: RTENext, fullRenderOptions: RenderOptions ) => { const props = extractPossibleAttributes(node.attrs) const hasFormat = node.children.some((item) => hasAvailableParagraphFormat((item as RTETextNode)?.classname) ) // If a child node has an available format as className, we wrap it in a // span and render the children with the correct component if (hasFormat) { return next(node.children, embeds, fullRenderOptions) } return ( {next(node.children, embeds, fullRenderOptions)} ) }, [RTETypeEnum.reference]: ( node: RTENode, embeds: EmbedByUid, next: RTENext, fullRenderOptions: RenderOptions ) => { if ("attrs" in node) { const type = node.attrs.type if (type === RTEItemTypeEnum.asset) { const image = embeds?.[node?.attrs?.["asset-uid"]] if (image?.node.__typename === EmbedEnum.SysAsset) { const alt = image?.node?.title ?? node.attrs.alt const props = extractPossibleAttributes(node.attrs) props.className = styles.image return ( {alt} ) } } else if (type === RTEItemTypeEnum.entry) { const entry = embeds?.[node?.attrs?.["entry-uid"]] if (entry?.node.__typename === EmbedEnum.ImageContainer) { if (entry.node.image_left && entry.node.image_right) { if ("dimensions" in entry.node.image_left) { // Only temp until all ImageVaultAssets are // handled in schema.transform() return ( ) } const leftImage = insertResponseToImageVaultAsset( entry.node.image_left ) const rightImage = insertResponseToImageVaultAsset( entry.node.image_right ) return ( ) } return null } else if ( entry?.node.__typename === EmbedEnum.LoyaltyPage || entry?.node.__typename === EmbedEnum.ContentPage || entry?.node.__typename === EmbedEnum.AccountPage ) { // If entry is not an ImageContainer, it is a page and we return it as a link const props = extractPossibleAttributes(node.attrs) let href = "" if (entry?.node.__typename === EmbedEnum.AccountPage) { href = removeMultipleSlashes( `/${entry.node.system.locale}${entry.node.url}` ) } else { href = entry.node?.web?.original_url || removeMultipleSlashes( `/${entry.node.system.locale}${entry.node.url}` ) } return ( {next(node.children, embeds, fullRenderOptions)} ) } } } return null }, [RTETypeEnum.ImageVault]: (node: RTEImageNode) => { if ("attrs" in node) { const type = node.type if (type === RTETypeEnum.ImageVault) { const attrs = node.attrs as RTEImageVaultAttrs let image = undefined if ("dimensions" in attrs) { image = attrs } else { image = insertResponseToImageVaultAsset(attrs) } const alt = image.meta.alt ?? image.title const width = attrs.width ? parseInt(attrs.width.replaceAll("px", "")) : image.dimensions.width const props = extractPossibleAttributes(attrs) return (
    {alt} {image.meta.caption}
    ) } } return null }, [RTETypeEnum.table]: ( node: RTEDefaultNode, embeds: EmbedByUid, next: RTENext, fullRenderOptions: RenderOptions ) => { const props = extractPossibleAttributes(node.attrs) return (
    {next(node.children, embeds, fullRenderOptions)}
    ) }, [RTETypeEnum.thead]: ( node: RTEDefaultNode, embeds: EmbedByUid, next: RTENext, fullRenderOptions: RenderOptions ) => { // Override the styling of p tags inside the thead tag const theadChildPRenderOptions = { ...fullRenderOptions, [RTETypeEnum.p]: ( node: RTEDefaultNode, embeds: EmbedByUid, next: RTENext, fullRenderOptions: RenderOptions ) => ( {next(node.children, embeds, fullRenderOptions)} ), } const props = extractPossibleAttributes(node.attrs) return ( {next(node.children, embeds, theadChildPRenderOptions)} ) }, [RTETypeEnum.tbody]: ( node: RTEDefaultNode, embeds: EmbedByUid, next: RTENext, fullRenderOptions: RenderOptions ) => { const props = extractPossibleAttributes(node.attrs) return ( {next(node.children, embeds, fullRenderOptions)} ) }, [RTETypeEnum.tfoot]: ( node: RTEDefaultNode, embeds: EmbedByUid, next: RTENext, fullRenderOptions: RenderOptions ) => { const props = extractPossibleAttributes(node.attrs) return ( {next(node.children, embeds, fullRenderOptions)} ) }, [RTETypeEnum.fragment]: ( node: RTEDefaultNode, embeds: EmbedByUid, next: RTENext, fullRenderOptions: RenderOptions ) => { return <>{next(node.children, embeds, fullRenderOptions)} }, [RTETypeEnum.tr]: ( node: RTEDefaultNode, embeds: EmbedByUid, next: RTENext, fullRenderOptions: RenderOptions ) => { const props = extractPossibleAttributes(node.attrs) return ( {next(node.children, embeds, fullRenderOptions)} ) }, [RTETypeEnum.th]: ( node: RTEDefaultNode, embeds: EmbedByUid, next: RTENext, fullRenderOptions: RenderOptions ) => { const props = extractPossibleAttributes(node.attrs) return ( {next(node.children, embeds, fullRenderOptions)} ) }, [RTETypeEnum.td]: ( node: RTEDefaultNode, embeds: EmbedByUid, next: RTENext, fullRenderOptions: RenderOptions ) => { const props = extractPossibleAttributes(node.attrs) return ( {next(node.children, embeds, fullRenderOptions)} ) }, [RTETypeEnum.ul]: ( node: RTEDefaultNode, embeds: EmbedByUid, next: RTENext, fullRenderOptions: RenderOptions ) => { const props = extractPossibleAttributes(node.attrs) // Set the number of rows dynamically to create even rows for each column. We want the li:s // to flow with the column, so therefore this is needed. let numberOfRows: number | undefined if (node.children.length > 4) { const half = node.children.length / 2 numberOfRows = Math.ceil(half) } return (
      {next(node.children, embeds, fullRenderOptions)}
    ) }, /** TextNode wrappers */ [RTEMarkType.bold]: (children: React.ReactNode) => { return {children} }, [RTEMarkType.italic]: (children: React.ReactNode) => { return {children} }, [RTEMarkType.underline]: (children: React.ReactNode) => { return {children} }, [RTEMarkType.strikethrough]: (children: React.ReactNode) => { return {children} }, [RTEMarkType.inlineCode]: (children: React.ReactNode) => { return {children} }, [RTEMarkType.subscript]: (children: React.ReactNode) => { return {children} }, [RTEMarkType.superscript]: (children: React.ReactNode) => { return {children} }, [RTEMarkType.break]: (children: React.ReactNode) => { return ( <>
    {children} ) }, [RTEMarkType.classnameOrId]: ( children: React.ReactNode, className?: string, id?: string ) => { let props = { className, id, } if (!className) { delete props.className } if (!id) { delete props.id } if (className) { if (hasAvailableULFormat(className)) { // @ts-ignore: We want to set css modules classNames even if it does not correspond // to an existing class in the module style sheet. Due to our css modules plugin for // typescript, we cannot do this without the ts-ignore props.className = styles[className] } } if (className === AvailableParagraphFormatEnum.footnote) { return ( {children} ) } if (className === AvailableParagraphFormatEnum.caption) { return ( {children} ) } if (className === AvailableParagraphFormatEnum["script-1"]) { return ( {children} ) } if (className === AvailableParagraphFormatEnum["script-2"]) { return ( {children} ) } if (className === AvailableParagraphFormatEnum["subtitle-1"]) { return ( {children} ) } if (className === AvailableParagraphFormatEnum["subtitle-2"]) { return ( {children} ) } return ( {children} ) }, /** * Contentstack can return something called `default` as seen here in their * own SDK (https://github.com/contentstack/contentstack-utils-javascript/blob/master/src/options/default-node-options.ts#L89) */ default: ( node: RTEDefaultNode, embeds: EmbedByUid, next: RTENext, fullRenderOptions: RenderOptions ) => { return next(node.children, embeds, fullRenderOptions) }, }