abstract the map var logic
This commit is contained in:
parent
0b6988ba15
commit
827a171980
@ -8,14 +8,17 @@ from typing import ClassVar, Dict, Literal, Optional, Union
|
|||||||
from reflex.components.component import Component, ComponentNamespace
|
from reflex.components.component import Component, ComponentNamespace
|
||||||
from reflex.components.core.cond import color_mode_cond
|
from reflex.components.core.cond import color_mode_cond
|
||||||
from reflex.components.lucide.icon import Icon
|
from reflex.components.lucide.icon import Icon
|
||||||
|
from reflex.components.markdown.markdown import (
|
||||||
|
_LANGUAGE,
|
||||||
|
MarkdownComponentMap,
|
||||||
|
)
|
||||||
from reflex.components.radix.themes.components.button import Button
|
from reflex.components.radix.themes.components.button import Button
|
||||||
from reflex.components.radix.themes.layout.box import Box
|
from reflex.components.radix.themes.layout.box import Box
|
||||||
from reflex.components.markdown.markdown import MarkdownComponentMapMixin, _PROPS, _CHILDREN
|
|
||||||
from reflex.constants.colors import Color
|
from reflex.constants.colors import Color
|
||||||
from reflex.event import set_clipboard
|
from reflex.event import set_clipboard
|
||||||
from reflex.style import Style
|
from reflex.style import Style
|
||||||
from reflex.utils import console, format
|
from reflex.utils import console, format
|
||||||
from reflex.utils.imports import ImportDict, ImportVar
|
from reflex.utils.imports import ImportVar
|
||||||
from reflex.vars.base import LiteralVar, Var, VarData
|
from reflex.vars.base import LiteralVar, Var, VarData
|
||||||
|
|
||||||
LiteralCodeLanguage = Literal[
|
LiteralCodeLanguage = Literal[
|
||||||
@ -379,9 +382,7 @@ for theme_name in dir(Theme):
|
|||||||
setattr(Theme, theme_name, getattr(Theme, theme_name)._replace(_var_type=Theme))
|
setattr(Theme, theme_name, getattr(Theme, theme_name)._replace(_var_type=Theme))
|
||||||
|
|
||||||
|
|
||||||
LANGUAGE_VAR = Var(_js_expr="__language")
|
class CodeBlock(Component, MarkdownComponentMap):
|
||||||
|
|
||||||
class CodeBlock(Component, MarkdownComponentMapMixin):
|
|
||||||
"""A code block."""
|
"""A code block."""
|
||||||
|
|
||||||
library = "react-syntax-highlighter@15.6.1"
|
library = "react-syntax-highlighter@15.6.1"
|
||||||
@ -520,9 +521,8 @@ class CodeBlock(Component, MarkdownComponentMapMixin):
|
|||||||
|
|
||||||
theme = self.theme
|
theme = self.theme
|
||||||
|
|
||||||
|
|
||||||
out.add_props(style=theme).remove_props("theme", "code", "language").add_props(
|
out.add_props(style=theme).remove_props("theme", "code", "language").add_props(
|
||||||
children=self.code, language=LANGUAGE_VAR
|
children=self.code, language=_LANGUAGE
|
||||||
)
|
)
|
||||||
|
|
||||||
return out
|
return out
|
||||||
@ -530,6 +530,22 @@ class CodeBlock(Component, MarkdownComponentMapMixin):
|
|||||||
def _exclude_props(self) -> list[str]:
|
def _exclude_props(self) -> list[str]:
|
||||||
return ["can_copy", "copy_button"]
|
return ["can_copy", "copy_button"]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _get_language_registration_hook(cls) -> str:
|
||||||
|
"""Get the hook to register the language."""
|
||||||
|
return f"""
|
||||||
|
if ({str(_LANGUAGE)}) {{
|
||||||
|
(async () => {{
|
||||||
|
try {{
|
||||||
|
const module = await import(`react-syntax-highlighter/dist/cjs/languages/prism/${{{str(_LANGUAGE)}}}`);
|
||||||
|
SyntaxHighlighter.registerLanguage({str(_LANGUAGE)}, module.default);
|
||||||
|
}} catch (error) {{
|
||||||
|
console.error(`Error importing language module for ${{{str(_LANGUAGE)}}}:`, error);
|
||||||
|
}}
|
||||||
|
}})();
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_component_map_custom_code(cls) -> str:
|
def get_component_map_custom_code(cls) -> str:
|
||||||
"""Get the custom code for the component.
|
"""Get the custom code for the component.
|
||||||
@ -537,21 +553,11 @@ class CodeBlock(Component, MarkdownComponentMapMixin):
|
|||||||
Returns:
|
Returns:
|
||||||
The custom code for the component.
|
The custom code for the component.
|
||||||
"""
|
"""
|
||||||
return """
|
return f"""
|
||||||
const match = (className || '').match(/language-(?<lang>.*)/);
|
const match = (className || '').match(/language-(?<lang>.*)/);
|
||||||
const language = match ? match[1] : '';
|
const {str(_LANGUAGE)} = match ? match[1] : '';
|
||||||
if (language) {
|
{cls._get_language_registration_hook()}
|
||||||
(async () => {
|
"""
|
||||||
try {
|
|
||||||
const module = await import(`react-syntax-highlighter/dist/cjs/languages/prism/${{language}}`);
|
|
||||||
SyntaxHighlighter.registerLanguage(language, module.default);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error importing language module for ${language}:`, error);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def add_hooks(self) -> list[str | Var]:
|
def add_hooks(self) -> list[str | Var]:
|
||||||
"""Add hooks for the component.
|
"""Add hooks for the component.
|
||||||
@ -560,19 +566,8 @@ const match = (className || '').match(/language-(?<lang>.*)/);
|
|||||||
The hooks for the component.
|
The hooks for the component.
|
||||||
"""
|
"""
|
||||||
return [
|
return [
|
||||||
f"const {str(LANGUAGE_VAR)} = {str(self.language)}",
|
f"const {str(_LANGUAGE)} = {str(self.language)}",
|
||||||
f"""
|
self._get_language_registration_hook(),
|
||||||
if ({str(LANGUAGE_VAR)}) {{
|
|
||||||
(async () => {{
|
|
||||||
try {{
|
|
||||||
const module = await import(`react-syntax-highlighter/dist/cjs/languages/prism/${{{str(LANGUAGE_VAR)}}}`);
|
|
||||||
SyntaxHighlighter.registerLanguage({str(LANGUAGE_VAR)}, module.default);
|
|
||||||
}} catch (error) {{
|
|
||||||
console.error(`Error importing language module for ${{{str(LANGUAGE_VAR)}}}:`, error);
|
|
||||||
}}
|
|
||||||
}})();
|
|
||||||
}}
|
|
||||||
"""
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,10 +7,10 @@ import dataclasses
|
|||||||
from typing import Any, ClassVar, Dict, Literal, Optional, Union, overload
|
from typing import Any, ClassVar, Dict, Literal, Optional, Union, overload
|
||||||
|
|
||||||
from reflex.components.component import Component, ComponentNamespace
|
from reflex.components.component import Component, ComponentNamespace
|
||||||
|
from reflex.components.markdown.markdown import MarkdownComponentMap
|
||||||
from reflex.constants.colors import Color
|
from reflex.constants.colors import Color
|
||||||
from reflex.event import BASE_STATE, EventType
|
from reflex.event import BASE_STATE, EventType
|
||||||
from reflex.style import Style
|
from reflex.style import Style
|
||||||
from reflex.utils.imports import ImportDict
|
|
||||||
from reflex.vars.base import Var
|
from reflex.vars.base import Var
|
||||||
|
|
||||||
LiteralCodeLanguage = Literal[
|
LiteralCodeLanguage = Literal[
|
||||||
@ -349,8 +349,7 @@ for theme_name in dir(Theme):
|
|||||||
continue
|
continue
|
||||||
setattr(Theme, theme_name, getattr(Theme, theme_name)._replace(_var_type=Theme))
|
setattr(Theme, theme_name, getattr(Theme, theme_name)._replace(_var_type=Theme))
|
||||||
|
|
||||||
class CodeBlock(Component):
|
class CodeBlock(Component, MarkdownComponentMap):
|
||||||
def add_imports(self) -> ImportDict: ...
|
|
||||||
@overload
|
@overload
|
||||||
@classmethod
|
@classmethod
|
||||||
def create( # type: ignore
|
def create( # type: ignore
|
||||||
@ -984,6 +983,9 @@ class CodeBlock(Component):
|
|||||||
...
|
...
|
||||||
|
|
||||||
def add_style(self): ...
|
def add_style(self): ...
|
||||||
|
@classmethod
|
||||||
|
def get_component_map_custom_code(cls) -> str: ...
|
||||||
|
def add_hooks(self) -> list[str | Var]: ...
|
||||||
|
|
||||||
class CodeblockNamespace(ComponentNamespace):
|
class CodeblockNamespace(ComponentNamespace):
|
||||||
themes = Theme
|
themes = Theme
|
||||||
|
@ -28,6 +28,7 @@ _CHILDREN = Var(_js_expr="children", _var_type=str)
|
|||||||
_PROPS = Var(_js_expr="...props")
|
_PROPS = Var(_js_expr="...props")
|
||||||
_PROPS_IN_TAG = Var(_js_expr="{...props}")
|
_PROPS_IN_TAG = Var(_js_expr="{...props}")
|
||||||
_MOCK_ARG = Var(_js_expr="", _var_type=str)
|
_MOCK_ARG = Var(_js_expr="", _var_type=str)
|
||||||
|
_LANGUAGE = Var(_js_expr="_language", _var_type=str)
|
||||||
|
|
||||||
# Special remark plugins.
|
# Special remark plugins.
|
||||||
_REMARK_MATH = Var(_js_expr="remarkMath")
|
_REMARK_MATH = Var(_js_expr="remarkMath")
|
||||||
@ -74,10 +75,55 @@ def get_base_component_map() -> dict[str, Callable]:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class MarkdownComponentMapMixin:
|
class MarkdownComponentMap:
|
||||||
def get_component_map_custom_code(self) -> str:
|
"""Mixin class for handling custom component maps in Markdown components."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_component_map_custom_code(cls) -> str:
|
||||||
|
"""Get the custom code for the component map.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The custom code for the component map.
|
||||||
|
"""
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_map_fn_var(
|
||||||
|
cls, fn_body: str | None = None, fn_args: list | None = None
|
||||||
|
) -> Var:
|
||||||
|
"""Create a function Var for the component map.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
fn_body: The formatted component as a string.
|
||||||
|
fn_args: The function arguments.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The function Var for the component map.
|
||||||
|
"""
|
||||||
|
fn_args = fn_args or cls.get_fn_args()
|
||||||
|
fn_body = fn_body or cls.get_fn_body()
|
||||||
|
fn_args_str = ", ".join(fn_args)
|
||||||
|
|
||||||
|
return Var(_js_expr=f"(({{{fn_args_str}}}) => {fn_body})")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_fn_args(cls) -> list[str]:
|
||||||
|
"""Get the function arguments for the component map.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The function arguments as a list of strings.
|
||||||
|
"""
|
||||||
|
return ["node", _CHILDREN._js_expr, _PROPS._js_expr]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_fn_body(cls) -> str:
|
||||||
|
"""Get the function body for the component map.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The function body as a string.
|
||||||
|
"""
|
||||||
|
return "()"
|
||||||
|
|
||||||
|
|
||||||
class Markdown(Component):
|
class Markdown(Component):
|
||||||
"""A markdown component."""
|
"""A markdown component."""
|
||||||
@ -132,7 +178,7 @@ class Markdown(Component):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _get_all_custom_components(
|
def _get_all_custom_components(
|
||||||
self, seen: set[str] | None = None
|
self, seen: set[str] | None = None
|
||||||
) -> set[CustomComponent]:
|
) -> set[CustomComponent]:
|
||||||
"""Get all the custom components used by the component.
|
"""Get all the custom components used by the component.
|
||||||
|
|
||||||
@ -158,9 +204,6 @@ class Markdown(Component):
|
|||||||
Returns:
|
Returns:
|
||||||
The imports for the markdown component.
|
The imports for the markdown component.
|
||||||
"""
|
"""
|
||||||
from reflex.components.datadisplay.code import CodeBlock, Theme
|
|
||||||
from reflex.components.radix.themes.typography.code import Code
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"": "katex/dist/katex.min.css",
|
"": "katex/dist/katex.min.css",
|
||||||
@ -184,10 +227,67 @@ class Markdown(Component):
|
|||||||
component(_MOCK_ARG)._get_all_imports() # type: ignore
|
component(_MOCK_ARG)._get_all_imports() # type: ignore
|
||||||
for component in self.component_map.values()
|
for component in self.component_map.values()
|
||||||
],
|
],
|
||||||
# CodeBlock.create(theme=Theme.light)._get_imports(),
|
|
||||||
# Code.create()._get_imports(),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def _get_tag_map_fn_var(self, tag: str) -> Var:
|
||||||
|
return self._get_map_fn_var_from_children(self.get_component(tag), tag)
|
||||||
|
|
||||||
|
def format_component_map(self) -> dict[str, Var]:
|
||||||
|
"""Format the component map for rendering.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The formatted component map.
|
||||||
|
"""
|
||||||
|
components = {
|
||||||
|
tag: self._get_tag_map_fn_var(tag)
|
||||||
|
for tag in self.component_map
|
||||||
|
if tag not in ("code", "codeblock")
|
||||||
|
}
|
||||||
|
|
||||||
|
# Separate out inline code and code blocks.
|
||||||
|
components["code"] = self._get_inline_code_fn_var()
|
||||||
|
|
||||||
|
return components
|
||||||
|
|
||||||
|
def _get_inline_code_fn_var(self) -> Var:
|
||||||
|
"""Get the function variable for inline code.
|
||||||
|
|
||||||
|
This function creates a Var that represents a function to handle
|
||||||
|
both inline code and code blocks in markdown.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The Var for inline code.
|
||||||
|
"""
|
||||||
|
# Get any custom code from the codeblock and code components.
|
||||||
|
custom_code_list = self._get_map_fn_custom_code_from_children(
|
||||||
|
self.get_component("codeblock")
|
||||||
|
)
|
||||||
|
custom_code_list.extend(
|
||||||
|
self._get_map_fn_custom_code_from_children(self.get_component("code"))
|
||||||
|
)
|
||||||
|
|
||||||
|
codeblock_custom_code = "\n".join(custom_code_list)
|
||||||
|
|
||||||
|
# Format the code to handle inline and block code.
|
||||||
|
formatted_code = f"""{{{codeblock_custom_code};
|
||||||
|
return inline ? (
|
||||||
|
{self.format_component("code")}
|
||||||
|
) : (
|
||||||
|
{self.format_component("codeblock", language=_LANGUAGE)}
|
||||||
|
);
|
||||||
|
}}""".replace("\n", " ")
|
||||||
|
|
||||||
|
return MarkdownComponentMap.create_map_fn_var(
|
||||||
|
fn_args=[
|
||||||
|
"node",
|
||||||
|
"inline",
|
||||||
|
"className",
|
||||||
|
_CHILDREN._js_expr,
|
||||||
|
_PROPS._js_expr,
|
||||||
|
],
|
||||||
|
fn_body=formatted_code,
|
||||||
|
)
|
||||||
|
|
||||||
def get_component(self, tag: str, **props) -> Component:
|
def get_component(self, tag: str, **props) -> Component:
|
||||||
"""Get the component for a tag and props.
|
"""Get the component for a tag and props.
|
||||||
|
|
||||||
@ -244,36 +344,23 @@ class Markdown(Component):
|
|||||||
"""
|
"""
|
||||||
return str(self.get_component(tag, **props)).replace("\n", "")
|
return str(self.get_component(tag, **props)).replace("\n", "")
|
||||||
|
|
||||||
def format_component_map(self) -> dict[str, Var]:
|
def _get_map_fn_var_from_children(self, component: Component, tag: str) -> Var:
|
||||||
"""Format the component map for rendering.
|
"""Create a function Var for the component map for the specified tag.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
component: The component to check for custom code.
|
||||||
|
tag: The tag of the component.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The formatted component map.
|
The function Var for the component map.
|
||||||
"""
|
"""
|
||||||
components = {
|
if isinstance(component, MarkdownComponentMap):
|
||||||
tag: Var(
|
return component.create_map_fn_var(f"({self.format_component(tag)})")
|
||||||
_js_expr=f"(({{node, {_CHILDREN._js_expr}, {_PROPS._js_expr}}}) => ({self.format_component(tag)}))"
|
|
||||||
)
|
|
||||||
for tag in self.component_map
|
|
||||||
}
|
|
||||||
codeblock_component = self.get_component("codeblock")
|
|
||||||
custom_code_list = self._get_custom_code_from_children(codeblock_component)
|
|
||||||
codeblock_custom_code = "\n".join(custom_code_list)
|
|
||||||
# Separate out inline code and code blocks.
|
|
||||||
components["code"] = Var(
|
|
||||||
_js_expr=f"""(({{node, inline, className, {_CHILDREN._js_expr}, {_PROPS._js_expr}}}) => {{
|
|
||||||
{codeblock_custom_code};
|
|
||||||
return inline ? (
|
|
||||||
{self.format_component("code")}
|
|
||||||
) : (
|
|
||||||
{self.format_component("codeblock", language=Var(_js_expr="language", _var_type=str))}
|
|
||||||
);
|
|
||||||
}})""".replace("\n", " ")
|
|
||||||
)
|
|
||||||
|
|
||||||
return components
|
# fallback to the default fn Var creation if the component is not a MarkdownComponentMap.
|
||||||
|
return MarkdownComponentMap.create_map_fn_var(f"({self.format_component(tag)})")
|
||||||
|
|
||||||
def _get_custom_code_from_children(self, component) -> list[str]:
|
def _get_map_fn_custom_code_from_children(self, component) -> list[str]:
|
||||||
"""Recursively get markdown custom code from children components.
|
"""Recursively get markdown custom code from children components.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -283,16 +370,25 @@ class Markdown(Component):
|
|||||||
A list of markdown custom code strings.
|
A list of markdown custom code strings.
|
||||||
"""
|
"""
|
||||||
custom_code_list = []
|
custom_code_list = []
|
||||||
if hasattr(component, "get_component_map_custom_code"):
|
if isinstance(component, MarkdownComponentMap):
|
||||||
custom_code_list.append(component.get_component_map_custom_code())
|
custom_code_list.append(component.get_component_map_custom_code())
|
||||||
|
|
||||||
|
# If the component is a custom component(rx.memo), obtain the underlining
|
||||||
|
# component and get the custom code from the children.
|
||||||
if isinstance(component, CustomComponent):
|
if isinstance(component, CustomComponent):
|
||||||
custom_code_list.extend(self._get_custom_code_from_children(component.component_fn(*component.get_prop_vars())))
|
custom_code_list.extend(
|
||||||
|
self._get_map_fn_custom_code_from_children(
|
||||||
|
component.component_fn(*component.get_prop_vars())
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
for child in component.children:
|
for child in component.children:
|
||||||
custom_code_list.extend(self._get_custom_code_from_children(child))
|
custom_code_list.extend(
|
||||||
|
self._get_map_fn_custom_code_from_children(child)
|
||||||
|
)
|
||||||
|
|
||||||
return custom_code_list
|
return custom_code_list
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _component_map_hash(component_map) -> str:
|
def _component_map_hash(component_map) -> str:
|
||||||
inp = str(
|
inp = str(
|
||||||
|
@ -16,6 +16,7 @@ _CHILDREN = Var(_js_expr="children", _var_type=str)
|
|||||||
_PROPS = Var(_js_expr="...props")
|
_PROPS = Var(_js_expr="...props")
|
||||||
_PROPS_IN_TAG = Var(_js_expr="{...props}")
|
_PROPS_IN_TAG = Var(_js_expr="{...props}")
|
||||||
_MOCK_ARG = Var(_js_expr="", _var_type=str)
|
_MOCK_ARG = Var(_js_expr="", _var_type=str)
|
||||||
|
_LANGUAGE = Var(_js_expr="_language", _var_type=str)
|
||||||
_REMARK_MATH = Var(_js_expr="remarkMath")
|
_REMARK_MATH = Var(_js_expr="remarkMath")
|
||||||
_REMARK_GFM = Var(_js_expr="remarkGfm")
|
_REMARK_GFM = Var(_js_expr="remarkGfm")
|
||||||
_REMARK_UNWRAP_IMAGES = Var(_js_expr="remarkUnwrapImages")
|
_REMARK_UNWRAP_IMAGES = Var(_js_expr="remarkUnwrapImages")
|
||||||
@ -28,6 +29,18 @@ NO_PROPS_TAGS = ("ul", "ol", "li")
|
|||||||
@lru_cache
|
@lru_cache
|
||||||
def get_base_component_map() -> dict[str, Callable]: ...
|
def get_base_component_map() -> dict[str, Callable]: ...
|
||||||
|
|
||||||
|
class MarkdownComponentMap:
|
||||||
|
@classmethod
|
||||||
|
def get_component_map_custom_code(cls) -> str: ...
|
||||||
|
@classmethod
|
||||||
|
def create_map_fn_var(
|
||||||
|
cls, fn_body: str | None = None, fn_args: list | None = None
|
||||||
|
) -> Var: ...
|
||||||
|
@classmethod
|
||||||
|
def get_fn_args(cls) -> list[str]: ...
|
||||||
|
@classmethod
|
||||||
|
def get_fn_body(cls) -> str: ...
|
||||||
|
|
||||||
class Markdown(Component):
|
class Markdown(Component):
|
||||||
@overload
|
@overload
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -82,6 +95,6 @@ class Markdown(Component):
|
|||||||
...
|
...
|
||||||
|
|
||||||
def add_imports(self) -> ImportDict | list[ImportDict]: ...
|
def add_imports(self) -> ImportDict | list[ImportDict]: ...
|
||||||
|
def format_component_map(self) -> dict[str, Var]: ...
|
||||||
def get_component(self, tag: str, **props) -> Component: ...
|
def get_component(self, tag: str, **props) -> Component: ...
|
||||||
def format_component(self, tag: str, **props) -> str: ...
|
def format_component(self, tag: str, **props) -> str: ...
|
||||||
def format_component_map(self) -> dict[str, Var]: ...
|
|
||||||
|
Loading…
Reference in New Issue
Block a user