Merged in feature/SW-3245-move-jsontohtml (pull request #2661)
Feature/SW-3245 move jsontohtml * wip * Move JsonToHtml -> design-system * Fix semantic issues within Stories * replace imports of 'storybook/react-vite' with 'storybook/nextjs-vite' * merge Approved-by: Anton Gunnarsson
This commit is contained in:
184
packages/design-system/lib/components/JsonToHtml/utils.tsx
Normal file
184
packages/design-system/lib/components/JsonToHtml/utils.tsx
Normal file
@@ -0,0 +1,184 @@
|
||||
import React, { cloneElement } from 'react'
|
||||
|
||||
import { renderOptions } from './renderOptions'
|
||||
|
||||
import styles from './jsontohtml.module.css'
|
||||
|
||||
import type { Node, Embeds } from './JsonToHtml'
|
||||
|
||||
import {
|
||||
AvailableParagraphFormatEnum,
|
||||
AvailableULFormatEnum,
|
||||
RTETypeEnum,
|
||||
} from './types/rte/enums'
|
||||
import {
|
||||
RTEMarkType,
|
||||
type RTENode,
|
||||
type RTERenderMark,
|
||||
type RTERenderOptionComponent,
|
||||
type RTETextNode,
|
||||
} from './types/rte/node'
|
||||
import type { RenderOptions } from './types/rte/option'
|
||||
import { EmbedByUid } from './JsonToHtml'
|
||||
|
||||
export function groupEmbedsByUid(embedsArray: Node<Embeds>[]) {
|
||||
const embedsByUid = embedsArray.reduce<EmbedByUid>((acc, embed) => {
|
||||
if ('system' in embed.node && embed.node.system?.uid) {
|
||||
acc[embed.node.system.uid] = embed
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
return embedsByUid
|
||||
}
|
||||
|
||||
export function nodeChildrenToHtml(
|
||||
nodes: RTENode[],
|
||||
embeds: EmbedByUid,
|
||||
fullRenderOptions: RenderOptions
|
||||
// TODO: Change this to an actual return
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
): any {
|
||||
return nodes
|
||||
.map((node, i) => {
|
||||
// This function either returns a JSX element or null
|
||||
const element = nodeToHtml(node, embeds, fullRenderOptions)
|
||||
if (!element) {
|
||||
return null
|
||||
}
|
||||
|
||||
return cloneElement(element, {
|
||||
key: `child-rte-${i}`,
|
||||
})
|
||||
})
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
export function textNodeToHtml(
|
||||
node: RTETextNode,
|
||||
fullRenderOptions: RenderOptions
|
||||
) {
|
||||
let text = <>{node.text}</>
|
||||
|
||||
if (node.classname || node.id) {
|
||||
text = (fullRenderOptions[RTEMarkType.classnameOrId] as RTERenderMark)(
|
||||
text,
|
||||
node.classname,
|
||||
node.id
|
||||
)
|
||||
}
|
||||
if (node.break) {
|
||||
text = (fullRenderOptions[RTEMarkType.break] as RTERenderMark)(text)
|
||||
}
|
||||
if (node.superscript) {
|
||||
text = (fullRenderOptions[RTEMarkType.superscript] as RTERenderMark)(text)
|
||||
}
|
||||
if (node.subscript) {
|
||||
text = (fullRenderOptions[RTEMarkType.subscript] as RTERenderMark)(text)
|
||||
}
|
||||
if (node.inlineCode) {
|
||||
text = (fullRenderOptions[RTEMarkType.inlineCode] as RTERenderMark)(text)
|
||||
}
|
||||
if (node.strikethrough) {
|
||||
text = (fullRenderOptions[RTEMarkType.strikethrough] as RTERenderMark)(text)
|
||||
}
|
||||
if (node.underline) {
|
||||
text = (fullRenderOptions[RTEMarkType.underline] as RTERenderMark)(text)
|
||||
}
|
||||
if (node.italic) {
|
||||
text = (fullRenderOptions[RTEMarkType.italic] as RTERenderMark)(text)
|
||||
}
|
||||
if (node.bold) {
|
||||
text = (fullRenderOptions[RTEMarkType.bold] as RTERenderMark)(text)
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
function next(
|
||||
nodes: RTENode[],
|
||||
embeds: EmbedByUid,
|
||||
fullRenderOptions: RenderOptions
|
||||
) {
|
||||
return nodeChildrenToHtml(nodes, embeds, fullRenderOptions)
|
||||
}
|
||||
|
||||
export function hasAvailableParagraphFormat(className?: string) {
|
||||
if (!className) {
|
||||
return false
|
||||
}
|
||||
return Object.keys(AvailableParagraphFormatEnum).includes(className)
|
||||
}
|
||||
|
||||
export function hasAvailableULFormat(className?: string) {
|
||||
if (!className) {
|
||||
return false
|
||||
}
|
||||
return Object.keys(AvailableULFormatEnum).includes(className)
|
||||
}
|
||||
|
||||
export function nodeToHtml(
|
||||
node: RTENode,
|
||||
embeds: EmbedByUid,
|
||||
fullRenderOptions: RenderOptions
|
||||
) {
|
||||
if ('type' in node === false) {
|
||||
return textNodeToHtml(node, fullRenderOptions)
|
||||
} else {
|
||||
const renderer = fullRenderOptions[node.type] as RTERenderOptionComponent
|
||||
if (renderer) {
|
||||
if (node.type === RTETypeEnum.doc) {
|
||||
return null
|
||||
}
|
||||
|
||||
return renderer(node, embeds, next, fullRenderOptions)
|
||||
} else {
|
||||
return next(node.children, embeds, fullRenderOptions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getUniqueId(node: RTENode) {
|
||||
if ('uid' in node) {
|
||||
return node.uid
|
||||
}
|
||||
return node.id
|
||||
}
|
||||
|
||||
export function nodesToHtml(
|
||||
nodes: RTENode[],
|
||||
embedsArray: Node<Embeds>[],
|
||||
overrideRenderOptions: RenderOptions
|
||||
) {
|
||||
const embeds = groupEmbedsByUid(embedsArray)
|
||||
const fullRenderOptions: RenderOptions = {
|
||||
...renderOptions,
|
||||
...overrideRenderOptions,
|
||||
}
|
||||
|
||||
return nodes.map((node, index) => {
|
||||
const nodeHtml = nodeToHtml(node, embeds, fullRenderOptions)
|
||||
|
||||
return (
|
||||
<React.Fragment key={getUniqueId(node) ?? `node-${index}`}>
|
||||
{nodeHtml}
|
||||
</React.Fragment>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export function makeCssModuleCompatibleClassName(
|
||||
className: string | undefined,
|
||||
formatType: 'ul'
|
||||
): string {
|
||||
if (!className) return ''
|
||||
|
||||
if (formatType === 'ul' && hasAvailableULFormat(className)) {
|
||||
// TODO: REMOVE
|
||||
// @ats-expect-error: 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
|
||||
return styles[className] || className
|
||||
}
|
||||
|
||||
return className
|
||||
}
|
||||
Reference in New Issue
Block a user