import { Box, Mark } from "@mantine/core" import { Constants } from "app/constants" import { calculatePlaceholderSize } from "app/utils" import { BasicHtmlStyles } from "components/content/BasicHtmlStyles" import { ImageWithPlaceholderWhileLoading } from "components/ImageWithPlaceholderWhileLoading" import escapeStringRegexp from "escape-string-regexp" import { type ChildrenNode, Interweave, Matcher, type MatchResponse, type Node, type TransformCallback } from "interweave" import React from "react" import { tss } from "tss" export interface ContentProps { content: string highlight?: string } const useStyles = tss.create(() => ({ content: { // break long links or long words overflowWrap: "anywhere", "& a": { color: "inherit", textDecoration: "underline", }, "& iframe": { maxWidth: "100%", }, "& pre, & code": { whiteSpace: "pre-wrap", }, }, })) const transform: TransformCallback = node => { if (node.tagName === "IMG") { // show placeholders for loading img tags, this allows the entry to have its final height immediately const src = node.getAttribute("src") ?? undefined if (!src) return undefined const alt = node.getAttribute("alt") ?? "image" const title = node.getAttribute("title") ?? undefined const nodeWidth = node.getAttribute("width") const nodeHeight = node.getAttribute("height") const width = nodeWidth ? parseInt(nodeWidth, 10) : undefined const height = nodeHeight ? parseInt(nodeHeight, 10) : undefined const placeholderSize = calculatePlaceholderSize({ width, height, maxWidth: Constants.layout.entryMaxWidth, }) return ( ) } return undefined } class HighlightMatcher extends Matcher { private readonly search: string constructor(search: string) { super("highlight") this.search = escapeStringRegexp(search) } match(string: string): MatchResponse | null { const pattern = this.search.split(" ").join("|") return this.doMatch(string, new RegExp(pattern, "i"), () => ({})) } replaceWith(children: ChildrenNode): Node { return {children} } asTag(): string { return "span" } } // memoize component because Interweave is costly const Content = React.memo((props: ContentProps) => { const { classes } = useStyles() const matchers = props.highlight ? [new HighlightMatcher(props.highlight)] : [] return ( ) }) Content.displayName = "Content" export { Content }