rx.markdown custom styles for tags (#1416)

This commit is contained in:
Nikhil Rao 2023-07-25 13:28:02 -07:00 committed by GitHub
parent 5eeb560238
commit d0fc965c7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 128 additions and 67 deletions

View File

@ -67,46 +67,48 @@ class Tag(Base):
Raises: Raises:
TypeError: If the prop is not a valid type. TypeError: If the prop is not a valid type.
""" """
# Handle var props. try:
if isinstance(prop, Var): # Handle var props.
if not prop.is_local or prop.is_string: if isinstance(prop, Var):
return str(prop) if not prop.is_local or prop.is_string:
if types._issubclass(prop.type_, str): return str(prop)
return format.json_dumps(prop.full_name) if types._issubclass(prop.type_, str):
prop = prop.full_name 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: else:
# All other events. # Dump the prop as JSON.
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:
prop = format.json_dumps(prop) prop = format.json_dumps(prop)
except TypeError as e: except TypeError as e:
raise TypeError( raise TypeError(
f"Could not format prop: {prop} of type {type(prop)}" f"Could not format prop: {prop} of type {type(prop)}"
) from e ) from e
# Wrap the variable in braces. # Wrap the variable in braces.
assert isinstance(prop, str), "The prop must be a string." assert isinstance(prop, str), "The prop must be a string."

View File

@ -9,5 +9,8 @@ class Heading(ChakraComponent):
tag = "Heading" tag = "Heading"
# Override the tag. The default tag is `<h2>`.
as_: Var[str]
# "4xl" | "3xl" | "2xl" | "xl" | "lg" | "md" | "sm" | "xs" # "4xl" | "3xl" | "2xl" | "xl" | "lg" | "md" | "sm" | "xs"
size: Var[str] size: Var[str]

View File

@ -1,12 +1,33 @@
"""Markdown component.""" """Markdown component."""
import textwrap 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.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.utils import types
from reflex.vars import BaseVar, ImportVar, Var 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): class Markdown(Component):
"""A markdown component.""" """A markdown component."""
@ -17,6 +38,37 @@ class Markdown(Component):
is_default = True 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 @classmethod
def create(cls, *children, **props) -> Component: def create(cls, *children, **props) -> Component:
"""Create a markdown component. """Create a markdown component.
@ -39,47 +91,45 @@ class Markdown(Component):
return super().create(src, **props) return super().create(src, **props)
def _get_imports(self): def _get_imports(self):
from reflex.components.datadisplay.code import Code, CodeBlock
imports = super()._get_imports() imports = super()._get_imports()
imports["@chakra-ui/react"] = {
ImportVar(tag="Heading"), # Special markdown imports.
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)}
imports["remark-math"] = {ImportVar(tag="remarkMath", is_default=True)} imports["remark-math"] = {ImportVar(tag="remarkMath", is_default=True)}
imports["remark-gfm"] = {ImportVar(tag="remarkGfm", is_default=True)} imports["remark-gfm"] = {ImportVar(tag="remarkGfm", is_default=True)}
imports["rehype-katex"] = {ImportVar(tag="rehypeKatex", is_default=True)} imports["rehype-katex"] = {ImportVar(tag="rehypeKatex", is_default=True)}
imports["rehype-raw"] = {ImportVar(tag="rehypeRaw", is_default=True)} imports["rehype-raw"] = {ImportVar(tag="rehypeRaw", is_default=True)}
imports[""] = {ImportVar(tag="katex/dist/katex.min.css")} 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 return imports
def _render(self): def _render(self):
return ( from reflex.components.tags.tag import Tag
super()
._render() components = {
.add_props( tag: f"{{({{node, ...props}}) => <{(component().tag)} {{...props}} {''.join(Tag(name='', props=Style(self.custom_styles.get(tag, {}))).format_props())} />}}"
components={ for tag, component in components_by_tag.items()
"h1": "{({node, ...props}) => <Heading size='2xl' paddingY='0.5em' {...props} />}", }
"h2": "{({node, ...props}) => <Heading size='xl' paddingY='0.5em' {...props} />}", components[
"h3": "{({node, ...props}) => <Heading size='lg' paddingY='0.5em' {...props} />}", "code"
"h4": "{({node, ...props}) => <Heading size='sm' paddingY='0.5em' {...props} />}", ] = """{({node, inline, className, children, ...props}) =>
"h5": "{({node, ...props}) => <Heading size='xs' paddingY='0.5em' {...props} />}",
"ul": "{UnorderedList}",
"ol": "{OrderedList}",
"li": "{ListItem}",
"p": "{({node, ...props}) => <Text paddingY='0.5em' {...props} />}",
"a": "{Link}",
"code": """{({node, inline, className, children, ...props}) =>
{ {
const match = (className || '').match(/language-(?<lang>.*)/); const match = (className || '').match(/language-(?<lang>.*)/);
return !inline ? ( return !inline ? (
<Prism <Prism
children={String(children).replace(/\n$/, '')} children={String(children).replace(/\n$/, '')}
language={match ? match[1] : ''} language={match ? match[1] : ''}
theme={light}
{...props} {...props}
/> />
) : ( ) : (
@ -88,12 +138,18 @@ class Markdown(Component):
</Code> </Code>
); );
}}""".replace( }}""".replace(
"\n", " " "\n", " "
), )
},
return (
super()
._render()
.add_props(
components=components,
remark_plugins=BaseVar(name="[remarkMath, remarkGfm]", type_=List[str]), remark_plugins=BaseVar(name="[remarkMath, remarkGfm]", type_=List[str]),
rehype_plugins=BaseVar( rehype_plugins=BaseVar(
name="[rehypeKatex, rehypeRaw]", type_=List[str] name="[rehypeKatex, rehypeRaw]", type_=List[str]
), ),
) )
.remove_props("custom_components")
) )