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.

422 lines (419 loc) 11.1 kB
import { jsx, jsxs, Fragment } from 'react/jsx-runtime'; import { assertNever, sanitizeUrl } from '@liveblocks/core'; import { Slot } from '@radix-ui/react-slot'; import { Lexer } from 'marked'; import { forwardRef, useMemo, memo } from 'react'; const defaultComponents = { Paragraph: ({ children }) => { return /* @__PURE__ */ jsx("p", { children }); }, Inline: ({ type, children }) => { switch (type) { case "strong": return /* @__PURE__ */ jsx("strong", { children }); case "em": return /* @__PURE__ */ jsx("em", { children }); case "code": return /* @__PURE__ */ jsx("code", { children }); case "del": return /* @__PURE__ */ jsx("del", { children }); default: assertNever(type, "Unknown inline type"); } }, 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 }); }, Table: ({ headings, rows }) => { return /* @__PURE__ */ jsxs("table", { children: [ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: headings.map((heading, index) => { return /* @__PURE__ */ jsx("th", { align: heading.align, children: heading.children }, index); }) }) }), /* @__PURE__ */ jsx("tbody", { children: rows.map((row, index) => { return /* @__PURE__ */ jsx("tr", { children: row.map((cell, index2) => { return /* @__PURE__ */ jsx("td", { align: cell.align, children: cell.children }, index2); }) }, index); }) }) ] }); }, List: ({ type, items, start }) => { const List = type === "ordered" ? "ol" : "ul"; return /* @__PURE__ */ jsx(List, { start: start === 1 ? void 0 : start, children: items.map((item, index) => /* @__PURE__ */ jsxs("li", { children: [ item.checked !== void 0 && /* @__PURE__ */ jsxs(Fragment, { children: [ /* @__PURE__ */ jsx("input", { type: "checkbox", disabled: true, checked: item.checked }), " " ] }), item.children ] }, index)) }); }, Separator: () => { return /* @__PURE__ */ jsx("hr", {}); } }; 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(MemoizedMarkdownToken, { token, components }, index); }) }); } ); const MemoizedMarkdownToken = memo( ({ token, components }) => { return /* @__PURE__ */ jsx(MarkdownToken, { token, components }); }, (previousProps, nextProps) => { const previousToken = previousProps.token; const nextToken = nextProps.token; if (previousToken.raw.length !== nextToken.raw.length) { return false; } if (previousToken.type !== nextToken.type) { return false; } return previousToken.raw === nextToken.raw; } ); function MarkdownToken({ token, components }) { if (!isMarkedToken(token)) { return null; } switch (token.type) { case "escape": { return token.text; } case "space": { return null; } case "text": { if (token.tokens !== void 0) { return /* @__PURE__ */ jsx(MarkdownTokens, { tokens: token.tokens, components }); } else { return parseHtmlEntities(token.text); } } case "br": { return /* @__PURE__ */ jsx("br", {}); } case "paragraph": { const Paragraph = components?.Paragraph ?? defaultComponents.Paragraph; return /* @__PURE__ */ jsx(Paragraph, { children: /* @__PURE__ */ jsx(MarkdownTokens, { tokens: token.tokens, components }) }); } case "heading": { const Heading = components?.Heading ?? defaultComponents.Heading; return /* @__PURE__ */ jsx(Heading, { level: clampHeadingLevel(token.depth), children: /* @__PURE__ */ jsx(MarkdownTokens, { tokens: token.tokens, components }) }); } case "strong": { const Inline = components?.Inline ?? defaultComponents.Inline; return /* @__PURE__ */ jsx(Inline, { type: "strong", children: /* @__PURE__ */ jsx(MarkdownTokens, { tokens: token.tokens, components }) }); } case "em": { const Inline = components?.Inline ?? defaultComponents.Inline; return /* @__PURE__ */ jsx(Inline, { type: "em", children: /* @__PURE__ */ jsx(MarkdownTokens, { tokens: token.tokens, components }) }); } case "codespan": { const Inline = components?.Inline ?? defaultComponents.Inline; return /* @__PURE__ */ jsx(Inline, { type: "code", children: parseHtmlEntities(token.text) }); } case "del": { const Inline = components?.Inline ?? defaultComponents.Inline; return /* @__PURE__ */ jsx(Inline, { type: "del", children: /* @__PURE__ */ jsx(MarkdownTokens, { tokens: token.tokens, components }) }); } case "link": { const href = sanitizeUrl(token.href); if (href === null) { return /* @__PURE__ */ jsx(MarkdownTokens, { tokens: token.tokens, components }); } const Link = components?.Link ?? defaultComponents.Link; return /* @__PURE__ */ jsx(Link, { href, title: token.title ?? void 0, children: /* @__PURE__ */ jsx(MarkdownTokens, { tokens: token.tokens, components }) }); } 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 Blockquote = components?.Blockquote ?? defaultComponents.Blockquote; return /* @__PURE__ */ jsx(Blockquote, { children: /* @__PURE__ */ jsx(MarkdownTokens, { tokens: token.tokens, components, normalizeToBlockTokens: true }) }); } case "list": { const List = components?.List ?? defaultComponents.List; const items = token.items.map((item) => { return { checked: item.task ? item.checked : void 0, children: /* @__PURE__ */ jsx(MarkdownTokens, { tokens: item.tokens, components, normalizeToBlockTokens: item.loose }) }; }); const props = token.ordered ? { type: "ordered", items, start: token.start || 1 } : { type: "unordered", items }; return /* @__PURE__ */ jsx(List, { ...props }); } case "table": { const Table = components?.Table ?? defaultComponents.Table; const headings = token.header.map( (cell) => ({ align: cell.align ?? void 0, children: /* @__PURE__ */ jsx(MarkdownTokens, { tokens: cell.tokens, components }) }) ); const rows = token.rows.map( (row) => row.map((cell) => ({ align: cell.align ?? void 0, children: /* @__PURE__ */ jsx(MarkdownTokens, { tokens: cell.tokens, components }) })) ); return /* @__PURE__ */ jsx(Table, { headings, rows }); } 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 "hr": { const Separator = components?.Separator ?? defaultComponents.Separator; return /* @__PURE__ */ jsx(Separator, {}); } case "html": default: { return null; } } } function MarkdownTokens({ tokens, components, normalizeToBlockTokens = false }) { const normalizedTokens = []; if (normalizeToBlockTokens) { for (let i = 0; i < tokens.length; i++) { const token = tokens[i]; switch (token.type) { case "text": { const texts = [token]; while (i + 1 < tokens.length && tokens[i + 1].type === "text") { i++; texts.push(tokens[i]); } normalizedTokens.push({ type: "paragraph", tokens: texts, raw: texts.map((text) => text.raw).join(""), text: texts.map((text) => text.text).join("") }); break; } default: { normalizedTokens.push(token); } } } } return tokens.map((token, index) => /* @__PURE__ */ jsx(MarkdownToken, { token, components }, index)); } const markedTokenTypes = [ "blockquote", "br", "code", "codespan", "def", "del", "em", "escape", "heading", "hr", "html", "image", "link", "list", "list_item", "paragraph", "space", "strong", "table", "text" ]; function isMarkedToken(token) { return typeof token === "object" && token !== null && "type" in token && markedTokenTypes.includes(token.type); } 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, MarkdownToken }; //# sourceMappingURL=Markdown.js.map