From d0fc965c7f0c9f7c90ca18b9978fddaedc5fa683 Mon Sep 17 00:00:00 2001 From: Nikhil Rao Date: Tue, 25 Jul 2023 13:28:02 -0700 Subject: [PATCH] rx.markdown custom styles for tags (#1416) --- reflex/components/tags/tag.py | 76 +++++++-------- reflex/components/typography/heading.py | 3 + reflex/components/typography/markdown.py | 116 +++++++++++++++++------ 3 files changed, 128 insertions(+), 67 deletions(-) diff --git a/reflex/components/tags/tag.py b/reflex/components/tags/tag.py index 335038697..85525d118 100644 --- a/reflex/components/tags/tag.py +++ b/reflex/components/tags/tag.py @@ -67,46 +67,48 @@ class Tag(Base): Raises: TypeError: If the prop is not a valid type. """ - # Handle var props. - if isinstance(prop, Var): - if not prop.is_local or prop.is_string: - return str(prop) - if types._issubclass(prop.type_, str): - return format.json_dumps(prop.full_name) - prop = prop.full_name + try: + # Handle var props. + if isinstance(prop, Var): + if not prop.is_local or prop.is_string: + return str(prop) + if types._issubclass(prop.type_, str): + return format.json_dumps(prop.full_name) + prop = prop.full_name + + # Handle event props. + elif isinstance(prop, EventChain): + if prop.full_control: + # Full control component events. + event = format.format_full_control_event(prop) + else: + # All other events. + chain = ",".join( + [format.format_event(event) for event in prop.events] + ) + event = f"Event([{chain}], {EVENT_ARG})" + prop = f"{EVENT_ARG} => {event}" + + # Handle other types. + elif isinstance(prop, str): + if format.is_wrapped(prop, "{"): + return prop + return format.json_dumps(prop) + + elif isinstance(prop, Figure): + prop = json.loads(to_json(prop))["data"] # type: ignore + + # For dictionaries, convert any properties to strings. + elif isinstance(prop, dict): + prop = format.format_dict(prop) - # Handle event props. - elif isinstance(prop, EventChain): - if prop.full_control: - # Full control component events. - event = format.format_full_control_event(prop) else: - # All other events. - chain = ",".join([format.format_event(event) for event in prop.events]) - event = f"Event([{chain}], {EVENT_ARG})" - prop = f"{EVENT_ARG} => {event}" - - # Handle other types. - elif isinstance(prop, str): - if format.is_wrapped(prop, "{"): - return prop - return format.json_dumps(prop) - - elif isinstance(prop, Figure): - prop = json.loads(to_json(prop))["data"] # type: ignore - - # For dictionaries, convert any properties to strings. - elif isinstance(prop, dict): - prop = format.format_dict(prop) - - else: - # Dump the prop as JSON. - try: + # Dump the prop as JSON. prop = format.json_dumps(prop) - except TypeError as e: - raise TypeError( - f"Could not format prop: {prop} of type {type(prop)}" - ) from e + except TypeError as e: + raise TypeError( + f"Could not format prop: {prop} of type {type(prop)}" + ) from e # Wrap the variable in braces. assert isinstance(prop, str), "The prop must be a string." diff --git a/reflex/components/typography/heading.py b/reflex/components/typography/heading.py index b4b8d5d37..dd7daeb57 100644 --- a/reflex/components/typography/heading.py +++ b/reflex/components/typography/heading.py @@ -9,5 +9,8 @@ class Heading(ChakraComponent): tag = "Heading" + # Override the tag. The default tag is `

`. + as_: Var[str] + # "4xl" | "3xl" | "2xl" | "xl" | "lg" | "md" | "sm" | "xs" size: Var[str] diff --git a/reflex/components/typography/markdown.py b/reflex/components/typography/markdown.py index 7e50233d9..743e46789 100644 --- a/reflex/components/typography/markdown.py +++ b/reflex/components/typography/markdown.py @@ -1,12 +1,33 @@ """Markdown component.""" import textwrap -from typing import List, Union +from typing import Callable, Dict, List, Union +from reflex.compiler import utils from reflex.components.component import Component +from reflex.components.datadisplay.list import ListItem, OrderedList, UnorderedList +from reflex.components.navigation import Link +from reflex.components.typography.heading import Heading +from reflex.components.typography.text import Text +from reflex.style import Style from reflex.utils import types from reflex.vars import BaseVar, ImportVar, Var +# Mapping from markdown tags to components. +components_by_tag: Dict[str, Callable] = { + "h1": Heading, + "h2": Heading, + "h3": Heading, + "h4": Heading, + "h5": Heading, + "h6": Heading, + "p": Text, + "ul": UnorderedList, + "ol": OrderedList, + "li": ListItem, + "a": Link, +} + class Markdown(Component): """A markdown component.""" @@ -17,6 +38,37 @@ class Markdown(Component): is_default = True + # Custom defined styles for the markdown elements. + custom_styles: Dict[str, Style] = { + k: Style(v) + for k, v in { + "h1": { + "as_": "h1", + "size": "2xl", + }, + "h2": { + "as_": "h2", + "size": "xl", + }, + "h3": { + "as_": "h3", + "size": "lg", + }, + "h4": { + "as_": "h4", + "size": "md", + }, + "h5": { + "as_": "h5", + "size": "sm", + }, + "h6": { + "as_": "h6", + "size": "xs", + }, + }.items() + } + @classmethod def create(cls, *children, **props) -> Component: """Create a markdown component. @@ -39,47 +91,45 @@ class Markdown(Component): return super().create(src, **props) def _get_imports(self): + from reflex.components.datadisplay.code import Code, CodeBlock + imports = super()._get_imports() - imports["@chakra-ui/react"] = { - ImportVar(tag="Heading"), - ImportVar(tag="Code"), - ImportVar(tag="Text"), - ImportVar(tag="Link"), - ImportVar(tag="UnorderedList"), - ImportVar(tag="OrderedList"), - ImportVar(tag="ListItem"), - } - imports["react-syntax-highlighter"] = {ImportVar(tag="Prism", is_default=True)} + + # Special markdown imports. imports["remark-math"] = {ImportVar(tag="remarkMath", is_default=True)} imports["remark-gfm"] = {ImportVar(tag="remarkGfm", is_default=True)} imports["rehype-katex"] = {ImportVar(tag="rehypeKatex", is_default=True)} imports["rehype-raw"] = {ImportVar(tag="rehypeRaw", is_default=True)} imports[""] = {ImportVar(tag="katex/dist/katex.min.css")} + + # Get the imports for each component. + for component in components_by_tag.values(): + imports = utils.merge_imports(imports, component()._get_imports()) + + # Get the imports for the code components. + imports = utils.merge_imports( + imports, CodeBlock.create(theme="light")._get_imports() + ) + imports = utils.merge_imports(imports, Code.create()._get_imports()) return imports def _render(self): - return ( - super() - ._render() - .add_props( - components={ - "h1": "{({node, ...props}) => }", - "h2": "{({node, ...props}) => }", - "h3": "{({node, ...props}) => }", - "h4": "{({node, ...props}) => }", - "h5": "{({node, ...props}) => }", - "ul": "{UnorderedList}", - "ol": "{OrderedList}", - "li": "{ListItem}", - "p": "{({node, ...props}) => }", - "a": "{Link}", - "code": """{({node, inline, className, children, ...props}) => + from reflex.components.tags.tag import Tag + + components = { + tag: f"{{({{node, ...props}}) => <{(component().tag)} {{...props}} {''.join(Tag(name='', props=Style(self.custom_styles.get(tag, {}))).format_props())} />}}" + for tag, component in components_by_tag.items() + } + components[ + "code" + ] = """{({node, inline, className, children, ...props}) => { const match = (className || '').match(/language-(?.*)/); return !inline ? ( ) : ( @@ -88,12 +138,18 @@ class Markdown(Component): ); }}""".replace( - "\n", " " - ), - }, + "\n", " " + ) + + return ( + super() + ._render() + .add_props( + components=components, remark_plugins=BaseVar(name="[remarkMath, remarkGfm]", type_=List[str]), rehype_plugins=BaseVar( name="[rehypeKatex, rehypeRaw]", type_=List[str] ), ) + .remove_props("custom_components") )