ソースを参照

improve behavior on missing language with markdown code blocks (#4750)

* improve behavior on missing language with markdown code blocks

* special case on literal var

* fix tests

* missing f

* remove extra throw
Khaleel Al-Adhami 3 ヶ月 前
コミット
9d23271c14

+ 36 - 10
reflex/components/datadisplay/code.py

@@ -3,6 +3,7 @@
 from __future__ import annotations
 from __future__ import annotations
 
 
 import dataclasses
 import dataclasses
+import typing
 from typing import ClassVar, Dict, Literal, Optional, Union
 from typing import ClassVar, Dict, Literal, Optional, Union
 
 
 from reflex.components.component import Component, ComponentNamespace
 from reflex.components.component import Component, ComponentNamespace
@@ -503,7 +504,7 @@ class CodeBlock(Component, MarkdownComponentMap):
         return ["can_copy", "copy_button"]
         return ["can_copy", "copy_button"]
 
 
     @classmethod
     @classmethod
-    def _get_language_registration_hook(cls, language_var: Var = _LANGUAGE) -> str:
+    def _get_language_registration_hook(cls, language_var: Var = _LANGUAGE) -> Var:
         """Get the hook to register the language.
         """Get the hook to register the language.
 
 
         Args:
         Args:
@@ -514,21 +515,46 @@ class CodeBlock(Component, MarkdownComponentMap):
         Returns:
         Returns:
             The hook to register the language.
             The hook to register the language.
         """
         """
-        return f"""
- if ({language_var!s}) {{
-    (async () => {{
-      try {{
+        language_in_there = Var.create(typing.get_args(LiteralCodeLanguage)).contains(
+            language_var
+        )
+        async_load = f"""
+(async () => {{
+    try {{
         const module = await import(`react-syntax-highlighter/dist/cjs/languages/prism/${{{language_var!s}}}`);
         const module = await import(`react-syntax-highlighter/dist/cjs/languages/prism/${{{language_var!s}}}`);
         SyntaxHighlighter.registerLanguage({language_var!s}, module.default);
         SyntaxHighlighter.registerLanguage({language_var!s}, module.default);
-      }} catch (error) {{
-        console.error(`Error importing language module for ${{{language_var!s}}}:`, error);
-      }}
-    }})();
+    }} catch (error) {{
+        console.error(`Language ${{{language_var!s}}} is not supported for code blocks inside of markdown: `, error);
+    }}
+}})();
+"""
+        return Var(
+            f"""
+ if ({language_var!s}) {{
+    if (!{language_in_there!s}) {{
+        console.warn(`Language \\`${{{language_var!s}}}\\` is not supported for code blocks inside of markdown.`);
+        {language_var!s} = '';
+    }} else {{
+        {async_load!s}
+    }}
   }}
   }}
 """
 """
+            if not isinstance(language_var, LiteralVar)
+            else f"""
+if ({language_var!s}) {{
+    {async_load!s}
+}}""",
+            _var_data=VarData(
+                imports={
+                    cls.__fields__["library"].default: [
+                        ImportVar(tag="PrismAsyncLight", alias="SyntaxHighlighter")
+                    ]
+                },
+            ),
+        )
 
 
     @classmethod
     @classmethod
-    def get_component_map_custom_code(cls) -> str:
+    def get_component_map_custom_code(cls) -> Var:
         """Get the custom code for the component.
         """Get the custom code for the component.
 
 
         Returns:
         Returns:

+ 1 - 1
reflex/components/datadisplay/code.pyi

@@ -984,7 +984,7 @@ class CodeBlock(Component, MarkdownComponentMap):
 
 
     def add_style(self): ...
     def add_style(self): ...
     @classmethod
     @classmethod
-    def get_component_map_custom_code(cls) -> str: ...
+    def get_component_map_custom_code(cls) -> Var: ...
     def add_hooks(self) -> list[str | Var]: ...
     def add_hooks(self) -> list[str | Var]: ...
 
 
 class CodeblockNamespace(ComponentNamespace):
 class CodeblockNamespace(ComponentNamespace):

+ 28 - 7
reflex/components/markdown/markdown.py

@@ -12,7 +12,7 @@ from reflex.components.component import BaseComponent, Component, CustomComponen
 from reflex.components.tags.tag import Tag
 from reflex.components.tags.tag import Tag
 from reflex.utils import types
 from reflex.utils import types
 from reflex.utils.imports import ImportDict, ImportVar
 from reflex.utils.imports import ImportDict, ImportVar
-from reflex.vars.base import LiteralVar, Var
+from reflex.vars.base import LiteralVar, Var, VarData
 from reflex.vars.function import ARRAY_ISARRAY, ArgsFunctionOperation, DestructuredArg
 from reflex.vars.function import ARRAY_ISARRAY, ArgsFunctionOperation, DestructuredArg
 from reflex.vars.number import ternary_operation
 from reflex.vars.number import ternary_operation
 
 
@@ -83,13 +83,13 @@ class MarkdownComponentMap:
     _explicit_return: bool = dataclasses.field(default=False)
     _explicit_return: bool = dataclasses.field(default=False)
 
 
     @classmethod
     @classmethod
-    def get_component_map_custom_code(cls) -> str:
+    def get_component_map_custom_code(cls) -> Var:
         """Get the custom code for the component map.
         """Get the custom code for the component map.
 
 
         Returns:
         Returns:
             The custom code for the component map.
             The custom code for the component map.
         """
         """
-        return ""
+        return Var("")
 
 
     @classmethod
     @classmethod
     def create_map_fn_var(
     def create_map_fn_var(
@@ -97,6 +97,7 @@ class MarkdownComponentMap:
         fn_body: Var | None = None,
         fn_body: Var | None = None,
         fn_args: Sequence[str] | None = None,
         fn_args: Sequence[str] | None = None,
         explicit_return: bool | None = None,
         explicit_return: bool | None = None,
+        var_data: VarData | None = None,
     ) -> Var:
     ) -> Var:
         """Create a function Var for the component map.
         """Create a function Var for the component map.
 
 
@@ -104,6 +105,7 @@ class MarkdownComponentMap:
             fn_body: The formatted component as a string.
             fn_body: The formatted component as a string.
             fn_args: The function arguments.
             fn_args: The function arguments.
             explicit_return: Whether to use explicit return syntax.
             explicit_return: Whether to use explicit return syntax.
+            var_data: The var data for the function.
 
 
         Returns:
         Returns:
             The function Var for the component map.
             The function Var for the component map.
@@ -116,6 +118,7 @@ class MarkdownComponentMap:
             args_names=(DestructuredArg(fields=tuple(fn_args)),),
             args_names=(DestructuredArg(fields=tuple(fn_args)),),
             return_expr=fn_body,
             return_expr=fn_body,
             explicit_return=explicit_return,
             explicit_return=explicit_return,
+            _var_data=var_data,
         )
         )
 
 
     @classmethod
     @classmethod
@@ -239,6 +242,15 @@ class Markdown(Component):
                 component(_MOCK_ARG)._get_all_imports()
                 component(_MOCK_ARG)._get_all_imports()
                 for component in self.component_map.values()
                 for component in self.component_map.values()
             ],
             ],
+            *(
+                [inline_code_var_data.old_school_imports()]
+                if (
+                    inline_code_var_data
+                    := self._get_inline_code_fn_var()._get_all_var_data()
+                )
+                is not None
+                else []
+            ),
         ]
         ]
 
 
     def _get_tag_map_fn_var(self, tag: str) -> Var:
     def _get_tag_map_fn_var(self, tag: str) -> Var:
@@ -278,12 +290,20 @@ class Markdown(Component):
             self._get_map_fn_custom_code_from_children(self.get_component("code"))
             self._get_map_fn_custom_code_from_children(self.get_component("code"))
         )
         )
 
 
-        codeblock_custom_code = "\n".join(custom_code_list)
+        var_data = VarData.merge(
+            *[
+                code._get_all_var_data()
+                for code in custom_code_list
+                if isinstance(code, Var)
+            ]
+        )
+
+        codeblock_custom_code = "\n".join(map(str, custom_code_list))
 
 
         # Format the code to handle inline and block code.
         # Format the code to handle inline and block code.
         formatted_code = f"""
         formatted_code = f"""
 const match = (className || '').match(/language-(?<lang>.*)/);
 const match = (className || '').match(/language-(?<lang>.*)/);
-const {_LANGUAGE!s} = match ? match[1] : '';
+let {_LANGUAGE!s} = match ? match[1] : '';
 {codeblock_custom_code};
 {codeblock_custom_code};
             return inline ? (
             return inline ? (
                 {self.format_component("code")}
                 {self.format_component("code")}
@@ -302,6 +322,7 @@ const {_LANGUAGE!s} = match ? match[1] : '';
             ),
             ),
             fn_body=Var(_js_expr=formatted_code),
             fn_body=Var(_js_expr=formatted_code),
             explicit_return=True,
             explicit_return=True,
+            var_data=var_data,
         )
         )
 
 
     def get_component(self, tag: str, **props) -> Component:
     def get_component(self, tag: str, **props) -> Component:
@@ -381,7 +402,7 @@ const {_LANGUAGE!s} = match ? match[1] : '';
 
 
     def _get_map_fn_custom_code_from_children(
     def _get_map_fn_custom_code_from_children(
         self, component: BaseComponent
         self, component: BaseComponent
-    ) -> list[str]:
+    ) -> list[str | Var]:
         """Recursively get markdown custom code from children components.
         """Recursively get markdown custom code from children components.
 
 
         Args:
         Args:
@@ -390,7 +411,7 @@ const {_LANGUAGE!s} = match ? match[1] : '';
         Returns:
         Returns:
             A list of markdown custom code strings.
             A list of markdown custom code strings.
         """
         """
-        custom_code_list = []
+        custom_code_list: list[str | Var] = []
         if isinstance(component, MarkdownComponentMap):
         if isinstance(component, MarkdownComponentMap):
             custom_code_list.append(component.get_component_map_custom_code())
             custom_code_list.append(component.get_component_map_custom_code())
 
 

+ 3 - 2
reflex/components/markdown/markdown.pyi

@@ -11,7 +11,7 @@ from reflex.components.component import Component
 from reflex.event import EventType
 from reflex.event import EventType
 from reflex.style import Style
 from reflex.style import Style
 from reflex.utils.imports import ImportDict
 from reflex.utils.imports import ImportDict
-from reflex.vars.base import LiteralVar, Var
+from reflex.vars.base import LiteralVar, Var, VarData
 
 
 _CHILDREN = Var(_js_expr="children", _var_type=str)
 _CHILDREN = Var(_js_expr="children", _var_type=str)
 _PROPS = Var(_js_expr="...props")
 _PROPS = Var(_js_expr="...props")
@@ -32,13 +32,14 @@ def get_base_component_map() -> dict[str, Callable]: ...
 @dataclasses.dataclass()
 @dataclasses.dataclass()
 class MarkdownComponentMap:
 class MarkdownComponentMap:
     @classmethod
     @classmethod
-    def get_component_map_custom_code(cls) -> str: ...
+    def get_component_map_custom_code(cls) -> Var: ...
     @classmethod
     @classmethod
     def create_map_fn_var(
     def create_map_fn_var(
         cls,
         cls,
         fn_body: Var | None = None,
         fn_body: Var | None = None,
         fn_args: Sequence[str] | None = None,
         fn_args: Sequence[str] | None = None,
         explicit_return: bool | None = None,
         explicit_return: bool | None = None,
+        var_data: VarData | None = None,
     ) -> Var: ...
     ) -> Var: ...
     @classmethod
     @classmethod
     def get_fn_args(cls) -> Sequence[str]: ...
     def get_fn_args(cls) -> Sequence[str]: ...

ファイルの差分が大きいため隠しています
+ 0 - 1
tests/units/components/markdown/test_markdown.py


この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません