UNPKG

@liveblocks/react-ui

Version:

A set of React pre-built components for the Liveblocks products. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.

490 lines (487 loc) 14.5 kB
import { jsx, jsxs } from 'react/jsx-runtime'; import { sanitizeUrl } from '@liveblocks/core'; import { Slot } from '@radix-ui/react-slot'; import { Lexer } from 'marked'; import { forwardRef, useMemo, memo } from 'react'; const defaultComponents = { CodeBlock: ({ language, code }) => { return /* @__PURE__ */ jsx("pre", { "data-language": language ?? void 0, children: /* @__PURE__ */ jsx("code", { children: code }) }); }, Link: ({ href, title, children }) => { return /* @__PURE__ */ jsx("a", { href, title, target: "_blank", rel: "noopener noreferrer", children }); }, Heading: ({ level, children }) => { const Heading = `h${level}`; return /* @__PURE__ */ jsx(Heading, { children }); }, Image: ({ src, alt, title }) => { return /* @__PURE__ */ jsx("img", { src, alt, title }); }, Blockquote: ({ children }) => { return /* @__PURE__ */ jsx("blockquote", { children }); } }; const Markdown = forwardRef( ({ content, components, asChild, ...props }, forwardedRef) => { const Component = asChild ? Slot : "div"; const tokens = useMemo(() => { return new Lexer().lex(content); }, [content]); return /* @__PURE__ */ jsx(Component, { ...props, ref: forwardedRef, children: tokens.map((token, index) => { return /* @__PURE__ */ jsx(MemoizedMarkdownBlockToken, { token, components }, index); }) }); } ); const MemoizedMarkdownBlockToken = memo( ({ token, components }) => { return /* @__PURE__ */ jsx(MarkdownBlockToken, { token, components }); }, (prevProps, nextProps) => { const prevToken = prevProps.token; const nextToken = nextProps.token; if (prevToken.raw.length !== nextToken.raw.length) { return false; } if (prevToken.type !== nextToken.type) { return false; } return prevToken.raw === nextToken.raw; } ); function MarkdownBlockToken({ token, components }) { switch (token.type) { case "space": { return null; } case "code": { let language = void 0; if (token.lang !== void 0) { language = token.lang.match(/^\S*/)?.[0] ?? void 0; } const CodeBlock = components?.CodeBlock ?? defaultComponents.CodeBlock; return /* @__PURE__ */ jsx(CodeBlock, { language, code: token.text }); } case "blockquote": { const tokens = []; for (let i = 0; i < token.tokens.length; i++) { switch (token.tokens[i].type) { case "space": case "code": case "blockquote": case "html": case "heading": case "hr": case "list": case "paragraph": case "table": { tokens.push(token.tokens[i]); break; } case "text": { const texts = [token.tokens[i]]; while (i + 1 < token.tokens.length && token.tokens[i + 1].type === "text") { i++; texts.push(token.tokens[i]); } tokens.push({ type: "paragraph", tokens: texts, raw: texts.map((text) => text.raw).join(""), text: texts.map((text) => text.text).join("") }); break; } default: { continue; } } } const Blockquote = components?.Blockquote ?? defaultComponents.Blockquote; return /* @__PURE__ */ jsx(Blockquote, { children: tokens.map((token2, index) => { return /* @__PURE__ */ jsx(MarkdownBlockToken, { token: token2, components }, index); }) }); } case "html": { return token.text; } case "heading": { const Heading = components?.Heading ?? defaultComponents.Heading; return /* @__PURE__ */ jsx(Heading, { level: clampHeadingLevel(token.depth), children: token.tokens.map((token2, index) => /* @__PURE__ */ jsx(MarkdownInlineToken, { token: token2, components }, index)) }); } case "hr": { return /* @__PURE__ */ jsx("hr", {}); } case "list": { const ListTag = token.ordered ? "ol" : "ul"; return /* @__PURE__ */ jsx(ListTag, { children: token.items.map((item, index) => { if (item.loose) { if (item.task) { const tokens = [...item.tokens]; if (tokens[0]?.type === "paragraph") { const token2 = tokens[0]; token2.tokens.unshift( { type: "checkbox", checked: item.checked, raw: "" }, { type: "text", text: " ", raw: " ", escaped: false } ); } else { tokens.unshift( { type: "checkbox", checked: item.checked, raw: "" }, { type: "text", text: " ", raw: " ", escaped: false } ); } const items = []; for (let i = 0; i < tokens.length; i++) { switch (tokens[i].type) { case "space": case "code": case "blockquote": case "html": case "heading": case "hr": case "list": case "paragraph": case "table": { items.push(tokens[i]); break; } case "text": case "checkbox": { const texts = [ tokens[i] ]; while (i + 1 < tokens.length && tokens[i + 1].type === "text") { i++; texts.push(tokens[i]); } items.push({ type: "paragraph", tokens: texts, raw: texts.map((text) => text.raw).join(""), text: texts.map((text) => text.text).join("") }); break; } default: { continue; } } } return /* @__PURE__ */ jsx("li", { children: items.map((token2, index2) => { return /* @__PURE__ */ jsx(MarkdownBlockToken, { token: token2, components }, index2); }) }, index); } else { const tokens = []; for (let i = 0; i < item.tokens.length; i++) { switch (item.tokens[i].type) { case "space": case "code": case "blockquote": case "html": case "heading": case "hr": case "list": case "paragraph": case "table": { tokens.push(item.tokens[i]); break; } case "text": { const texts = [ item.tokens[i] ]; while (i + 1 < item.tokens.length && item.tokens[i + 1].type === "text") { i++; texts.push(item.tokens[i]); } tokens.push({ type: "paragraph", tokens: texts, raw: texts.map((text) => text.raw).join(""), text: texts.map((text) => text.text).join("") }); break; } default: { continue; } } } return /* @__PURE__ */ jsx("li", { children: tokens.map((token2, index2) => { return /* @__PURE__ */ jsx(MarkdownBlockToken, { token: token2, components }, index2); }) }, index); } } else { const Items = item.tokens.map((token2, index2) => { switch (token2.type) { case "space": case "code": case "blockquote": case "html": case "heading": case "hr": case "list": case "paragraph": case "table": { return /* @__PURE__ */ jsx(MarkdownBlockToken, { token: token2, components }, index2); } case "text": { return /* @__PURE__ */ jsx(MarkdownInlineToken, { token: token2, components }, index2); } default: { return null; } } }); if (item.task) { return /* @__PURE__ */ jsxs("li", { children: [ /* @__PURE__ */ jsx("input", { type: "checkbox", disabled: true, checked: item.checked }), " ", Items ] }, index); } else { return /* @__PURE__ */ jsx("li", { children: Items }, index); } } }) }); } case "paragraph": { return /* @__PURE__ */ jsx("p", { children: token.tokens.map((token2, index) => /* @__PURE__ */ jsx(MarkdownInlineToken, { token: token2, components }, index)) }); } case "table": { return /* @__PURE__ */ jsxs("table", { children: [ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: token.header.map((cell, index) => { return /* @__PURE__ */ jsx("th", { align: cell.align ?? void 0, children: cell.tokens.map((token2, index2) => /* @__PURE__ */ jsx(MarkdownInlineToken, { token: token2, components }, index2)) }, index); }) }) }), /* @__PURE__ */ jsx("tbody", { children: token.rows.map((row, index) => { return /* @__PURE__ */ jsx("tr", { children: row.map((cell, index2) => { return /* @__PURE__ */ jsx("td", { align: cell.align ?? void 0, children: cell.tokens.map((token2, index3) => /* @__PURE__ */ jsx(MarkdownInlineToken, { token: token2, components }, index3)) }, index2); }) }, index); }) }) ] }); } } } function MarkdownInlineToken({ token, components }) { switch (token.type) { case "strong": { return /* @__PURE__ */ jsx("strong", { children: token.tokens.map((token2, index) => /* @__PURE__ */ jsx(MarkdownInlineToken, { token: token2, components }, index)) }); } case "em": { return /* @__PURE__ */ jsx("em", { children: token.tokens.map((token2, index) => /* @__PURE__ */ jsx(MarkdownInlineToken, { token: token2, components }, index)) }); } case "codespan": { return /* @__PURE__ */ jsx("code", { children: parseHtmlEntities(token.text) }); } case "br": { return /* @__PURE__ */ jsx("br", {}); } case "del": { return /* @__PURE__ */ jsx("del", { children: token.tokens.map((token2, index) => /* @__PURE__ */ jsx(MarkdownInlineToken, { token: token2, components }, index)) }); } case "link": { const href = sanitizeUrl(token.href); if (href === null) { return token.tokens.map((token2, index) => /* @__PURE__ */ jsx(MarkdownInlineToken, { token: token2, components }, index)); } const Link = components?.Link ?? defaultComponents.Link; return /* @__PURE__ */ jsx(Link, { href, title: token.title ?? void 0, children: token.tokens.map((token2, index) => /* @__PURE__ */ jsx(MarkdownInlineToken, { token: token2, components }, index)) }); } case "image": { const href = sanitizeUrl(token.href); if (href === null) { return token.text; } const Image = components?.Image ?? defaultComponents.Image; return /* @__PURE__ */ jsx(Image, { src: href, alt: token.text, title: token.title ?? void 0 }); } case "text": { if (token.tokens !== void 0) { return token.tokens.map((token2, index) => /* @__PURE__ */ jsx(MarkdownInlineToken, { token: token2, components }, index)); } else { return parseHtmlEntities(token.text); } } case "escape": { return token.text; } case "checkbox": { return /* @__PURE__ */ jsx("input", { type: "checkbox", disabled: true, checked: token.checked }); } default: { return null; } } } function parseHtmlEntities(input) { const document = new DOMParser().parseFromString( `<!doctype html><body>${input}`, "text/html" ); return document.body.textContent; } function clampHeadingLevel(level) { return Math.max(1, Math.min(6, level)); } export { Markdown, MarkdownBlockToken }; //# sourceMappingURL=Markdown.js.map