1
0
Эх сурвалжийг харах

Merge branch 'main' into update_queue

Rodja Trappe 2 жил өмнө
parent
commit
790591f7d0

+ 19 - 13
nicegui/element.py

@@ -1,6 +1,7 @@
 from __future__ import annotations
 from __future__ import annotations
 
 
-import shlex
+import json
+import re
 from abc import ABC
 from abc import ABC
 from copy import deepcopy
 from copy import deepcopy
 from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, Union
 from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, Union
@@ -13,6 +14,8 @@ from .slot import Slot
 if TYPE_CHECKING:
 if TYPE_CHECKING:
     from .client import Client
     from .client import Client
 
 
+PROPS_PATTERN = re.compile(r'([\w\-]+)(?:=(?:("[^"\\]*(?:\\.[^"\\]*)*")|([\w\-.%:\/]+)))?(?:$|\s)')
+
 
 
 class Element(ABC, Visibility):
 class Element(ABC, Visibility):
 
 
@@ -94,7 +97,13 @@ class Element(ABC, Visibility):
 
 
     @staticmethod
     @staticmethod
     def _parse_style(text: Optional[str]) -> Dict[str, str]:
     def _parse_style(text: Optional[str]) -> Dict[str, str]:
-        return dict(_split(part, ':') for part in text.strip('; ').split(';')) if text else {}
+        result = {}
+        for word in (text or '').split(';'):
+            word = word.strip()
+            if word:
+                key, value = word.split(':', 1)
+                result[key.strip()] = value.strip()
+        return result
 
 
     def style(self, add: Optional[str] = None, *, remove: Optional[str] = None, replace: Optional[str] = None):
     def style(self, add: Optional[str] = None, *, remove: Optional[str] = None, replace: Optional[str] = None):
         '''CSS style sheet definitions to modify the look of the element.
         '''CSS style sheet definitions to modify the look of the element.
@@ -115,12 +124,14 @@ class Element(ABC, Visibility):
 
 
     @staticmethod
     @staticmethod
     def _parse_props(text: Optional[str]) -> Dict[str, Any]:
     def _parse_props(text: Optional[str]) -> Dict[str, Any]:
-        if not text:
-            return {}
-        lexer = shlex.shlex(text, posix=True)
-        lexer.whitespace = ' '
-        lexer.wordchars += '=-.%:/'
-        return dict(_split(word, '=') if '=' in word else (word, True) for word in lexer)
+        dictionary = {}
+        for match in PROPS_PATTERN.finditer(text or ''):
+            key = match.group(1)
+            value = match.group(2) or match.group(3)
+            if value and value.startswith('"') and value.endswith('"'):
+                value = json.loads(value)
+            dictionary[key] = value or True
+        return dictionary
 
 
     def props(self, add: Optional[str] = None, *, remove: Optional[str] = None):
     def props(self, add: Optional[str] = None, *, remove: Optional[str] = None):
         '''Quasar props https://quasar.dev/vue-components/button#design to modify the look of the element.
         '''Quasar props https://quasar.dev/vue-components/button#design to modify the look of the element.
@@ -201,8 +212,3 @@ class Element(ABC, Visibility):
 
 
         Can be overridden to perform cleanup.
         Can be overridden to perform cleanup.
         """
         """
-
-
-def _split(text: str, separator: str) -> Tuple[str, str]:
-    words = text.split(separator, 1)
-    return words[0].strip(), words[1].strip()

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
nicegui/elements/lib/mermaid.min.js


+ 21 - 0
nicegui/elements/markdown.js

@@ -0,0 +1,21 @@
+export default {
+  template: `<div></div>`,
+  mounted() {
+    this.update(this.$el.innerHTML);
+  },
+  methods: {
+    update(content) {
+      this.$el.innerHTML = content;
+      this.$el.querySelectorAll(".mermaid-pre").forEach((pre, i) => {
+        const code = decodeHtml(pre.children[0].innerHTML);
+        mermaid.render(`mermaid_${this.$el.id}_${i}`, code, (svg) => (pre.innerHTML = svg));
+      });
+    },
+  },
+};
+
+function decodeHtml(html) {
+  const txt = document.createElement("textarea");
+  txt.innerHTML = html;
+  return txt.value;
+}

+ 21 - 18
nicegui/elements/markdown.py

@@ -5,24 +5,10 @@ from typing import List
 
 
 import markdown2
 import markdown2
 
 
+from ..dependencies import register_component
 from .mixins.content_element import ContentElement
 from .mixins.content_element import ContentElement
 
 
-
-def apply_tailwind(html: str) -> str:
-    rep = {
-        '<h1': '<h1 class="text-5xl mb-4 mt-6"',
-        '<h2': '<h2 class="text-4xl mb-3 mt-5"',
-        '<h3': '<h3 class="text-3xl mb-2 mt-4"',
-        '<h4': '<h4 class="text-2xl mb-1 mt-3"',
-        '<h5': '<h5 class="text-1xl mb-0.5 mt-2"',
-        '<a': '<a class="underline text-blue-600 hover:text-blue-800 visited:text-purple-600"',
-        '<ul': '<ul class="list-disc ml-6"',
-        '<p>': '<p class="mb-2">',
-        '<div\ class="codehilite">': '<div class="codehilite mb-2 p-2">',
-        '<code': '<code style="background-color: transparent"',
-    }
-    pattern = re.compile('|'.join(rep.keys()))
-    return pattern.sub(lambda m: rep[re.escape(m.group(0))], html)
+register_component('markdown', __file__, 'markdown.js', ['lib/mermaid.min.js'])
 
 
 
 
 class Markdown(ContentElement):
 class Markdown(ContentElement):
@@ -36,16 +22,33 @@ class Markdown(ContentElement):
         :param extras: list of `markdown2 extensions <https://github.com/trentm/python-markdown2/wiki/Extras#implemented-extras>`_ (default: `['fenced-code-blocks', 'tables']`)
         :param extras: list of `markdown2 extensions <https://github.com/trentm/python-markdown2/wiki/Extras#implemented-extras>`_ (default: `['fenced-code-blocks', 'tables']`)
         """
         """
         self.extras = extras
         self.extras = extras
-        super().__init__(tag='div', content=content)
+        super().__init__(tag='markdown', content=content)
 
 
     def on_content_change(self, content: str) -> None:
     def on_content_change(self, content: str) -> None:
         html = prepare_content(content, extras=' '.join(self.extras))
         html = prepare_content(content, extras=' '.join(self.extras))
         if self._props.get('innerHTML') != html:
         if self._props.get('innerHTML') != html:
             self._props['innerHTML'] = html
             self._props['innerHTML'] = html
-            self.update()
+            self.run_method('update', html)
 
 
 
 
 @lru_cache(maxsize=int(os.environ.get('MARKDOWN_CONTENT_CACHE_SIZE', '1000')))
 @lru_cache(maxsize=int(os.environ.get('MARKDOWN_CONTENT_CACHE_SIZE', '1000')))
 def prepare_content(content: str, extras: str) -> str:
 def prepare_content(content: str, extras: str) -> str:
     html = markdown2.markdown(content, extras=extras.split())
     html = markdown2.markdown(content, extras=extras.split())
     return apply_tailwind(html)  # we need explicit markdown styling because tailwind CSS removes all default styles
     return apply_tailwind(html)  # we need explicit markdown styling because tailwind CSS removes all default styles
+
+
+def apply_tailwind(html: str) -> str:
+    rep = {
+        '<h1': '<h1 class="text-5xl mb-4 mt-6"',
+        '<h2': '<h2 class="text-4xl mb-3 mt-5"',
+        '<h3': '<h3 class="text-3xl mb-2 mt-4"',
+        '<h4': '<h4 class="text-2xl mb-1 mt-3"',
+        '<h5': '<h5 class="text-1xl mb-0.5 mt-2"',
+        '<a': '<a class="underline text-blue-600 hover:text-blue-800 visited:text-purple-600"',
+        '<ul': '<ul class="list-disc ml-6"',
+        '<p>': '<p class="mb-2">',
+        '<div\ class="codehilite">': '<div class="codehilite mb-2 p-2">',
+        '<code': '<code style="background-color: transparent"',
+    }
+    pattern = re.compile('|'.join(rep.keys()))
+    return pattern.sub(lambda m: rep[re.escape(m.group(0))], html)

+ 11 - 0
nicegui/elements/mermaid.js

@@ -0,0 +1,11 @@
+export default {
+  template: `<div></div>`,
+  mounted() {
+    this.update(this.$el.innerText);
+  },
+  methods: {
+    update(content) {
+      mermaid.render("mermaid" + this.$el.id, content, (svg) => (this.$el.innerHTML = svg));
+    },
+  },
+};

+ 20 - 0
nicegui/elements/mermaid.py

@@ -0,0 +1,20 @@
+from ..dependencies import register_component
+from .mixins.content_element import ContentElement
+
+register_component('mermaid', __file__, 'mermaid.js', ['lib/mermaid.min.js'])
+
+
+class Mermaid(ContentElement):
+
+    def __init__(self, content: str) -> None:
+        '''Mermaid Diagrams
+
+        Renders diagrams and charts written in the Markdown-inspired `Mermaid <https://mermaid.js.org/>`_ language.
+
+        :param content: the Mermaid content to be displayed
+        '''
+        super().__init__(tag='mermaid', content=content)
+
+    def on_content_change(self, content: str) -> None:
+        self._props['innerHTML'] = content
+        self.run_method('update', content)

+ 1 - 1
nicegui/run.py

@@ -47,7 +47,7 @@ def run(*,
     :param uvicorn_reload_includes: string with comma-separated list of glob-patterns which trigger reload on modification (default: `'.py'`)
     :param uvicorn_reload_includes: string with comma-separated list of glob-patterns which trigger reload on modification (default: `'.py'`)
     :param uvicorn_reload_excludes: string with comma-separated list of glob-patterns which should be ignored for reload (default: `'.*, .py[cod], .sw.*, ~*'`)
     :param uvicorn_reload_excludes: string with comma-separated list of glob-patterns which should be ignored for reload (default: `'.*, .py[cod], .sw.*, ~*'`)
     :param exclude: comma-separated string to exclude elements (with corresponding JavaScript libraries) to save bandwidth
     :param exclude: comma-separated string to exclude elements (with corresponding JavaScript libraries) to save bandwidth
-      (possible entries: audio, chart, colors, interactive_image, joystick, keyboard, log, scene, table, video)
+      (possible entries: audio, chart, colors, interactive_image, joystick, keyboard, log, mermaid, scene, table, video)
     :param tailwind: whether to use Tailwind (experimental, default: `True`)
     :param tailwind: whether to use Tailwind (experimental, default: `True`)
     '''
     '''
     globals.ui_run_has_been_called = True
     globals.ui_run_has_been_called = True

+ 1 - 0
nicegui/ui.py

@@ -30,6 +30,7 @@ from .elements.log import Log as log
 from .elements.markdown import Markdown as markdown
 from .elements.markdown import Markdown as markdown
 from .elements.menu import Menu as menu
 from .elements.menu import Menu as menu
 from .elements.menu import MenuItem as menu_item
 from .elements.menu import MenuItem as menu_item
+from .elements.mermaid import Mermaid as mermaid
 from .elements.number import Number as number
 from .elements.number import Number as number
 from .elements.progress import CircularProgress as circular_progress
 from .elements.progress import CircularProgress as circular_progress
 from .elements.progress import LinearProgress as linear_progress
 from .elements.progress import LinearProgress as linear_progress

+ 5 - 13
poetry.lock

@@ -437,7 +437,6 @@ files = [
     {file = "debugpy-1.6.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b5d1b13d7c7bf5d7cf700e33c0b8ddb7baf030fcf502f76fc061ddd9405d16c"},
     {file = "debugpy-1.6.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b5d1b13d7c7bf5d7cf700e33c0b8ddb7baf030fcf502f76fc061ddd9405d16c"},
     {file = "debugpy-1.6.6-cp38-cp38-win32.whl", hash = "sha256:70ab53918fd907a3ade01909b3ed783287ede362c80c75f41e79596d5ccacd32"},
     {file = "debugpy-1.6.6-cp38-cp38-win32.whl", hash = "sha256:70ab53918fd907a3ade01909b3ed783287ede362c80c75f41e79596d5ccacd32"},
     {file = "debugpy-1.6.6-cp38-cp38-win_amd64.whl", hash = "sha256:c05349890804d846eca32ce0623ab66c06f8800db881af7a876dc073ac1c2225"},
     {file = "debugpy-1.6.6-cp38-cp38-win_amd64.whl", hash = "sha256:c05349890804d846eca32ce0623ab66c06f8800db881af7a876dc073ac1c2225"},
-    {file = "debugpy-1.6.6-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:11a0f3a106f69901e4a9a5683ce943a7a5605696024134b522aa1bfda25b5fec"},
     {file = "debugpy-1.6.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a771739902b1ae22a120dbbb6bd91b2cae6696c0e318b5007c5348519a4211c6"},
     {file = "debugpy-1.6.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a771739902b1ae22a120dbbb6bd91b2cae6696c0e318b5007c5348519a4211c6"},
     {file = "debugpy-1.6.6-cp39-cp39-win32.whl", hash = "sha256:549ae0cb2d34fc09d1675f9b01942499751d174381b6082279cf19cdb3c47cbe"},
     {file = "debugpy-1.6.6-cp39-cp39-win32.whl", hash = "sha256:549ae0cb2d34fc09d1675f9b01942499751d174381b6082279cf19cdb3c47cbe"},
     {file = "debugpy-1.6.6-cp39-cp39-win_amd64.whl", hash = "sha256:de4a045fbf388e120bb6ec66501458d3134f4729faed26ff95de52a754abddb1"},
     {file = "debugpy-1.6.6-cp39-cp39-win_amd64.whl", hash = "sha256:de4a045fbf388e120bb6ec66501458d3134f4729faed26ff95de52a754abddb1"},
@@ -688,19 +687,19 @@ files = [
 
 
 [[package]]
 [[package]]
 name = "isort"
 name = "isort"
-version = "5.11.4"
+version = "5.11.5"
 description = "A Python utility / library to sort Python imports."
 description = "A Python utility / library to sort Python imports."
 category = "dev"
 category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.7.0"
 python-versions = ">=3.7.0"
 files = [
 files = [
-    {file = "isort-5.11.4-py3-none-any.whl", hash = "sha256:c033fd0edb91000a7f09527fe5c75321878f98322a77ddcc81adbd83724afb7b"},
-    {file = "isort-5.11.4.tar.gz", hash = "sha256:6db30c5ded9815d813932c04c2f85a360bcdd35fed496f4d8f35495ef0a261b6"},
+    {file = "isort-5.11.5-py3-none-any.whl", hash = "sha256:ba1d72fb2595a01c7895a5128f9585a5cc4b6d395f1c8d514989b9a7eb2a8746"},
+    {file = "isort-5.11.5.tar.gz", hash = "sha256:6be1f76a507cb2ecf16c7cf14a37e41609ca082330be4e3436a18ef74add55db"},
 ]
 ]
 
 
 [package.extras]
 [package.extras]
 colors = ["colorama (>=0.4.3,<0.5.0)"]
 colors = ["colorama (>=0.4.3,<0.5.0)"]
-pipfile-deprecated-finder = ["pipreqs", "requirementslib"]
+pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"]
 plugins = ["setuptools"]
 plugins = ["setuptools"]
 requirements-deprecated-finder = ["pip-api", "pipreqs"]
 requirements-deprecated-finder = ["pip-api", "pipreqs"]
 
 
@@ -1127,13 +1126,6 @@ files = [
     {file = "Pillow-9.4.0-1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b8c2f6eb0df979ee99433d8b3f6d193d9590f735cf12274c108bd954e30ca858"},
     {file = "Pillow-9.4.0-1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b8c2f6eb0df979ee99433d8b3f6d193d9590f735cf12274c108bd954e30ca858"},
     {file = "Pillow-9.4.0-1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b70756ec9417c34e097f987b4d8c510975216ad26ba6e57ccb53bc758f490dab"},
     {file = "Pillow-9.4.0-1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b70756ec9417c34e097f987b4d8c510975216ad26ba6e57ccb53bc758f490dab"},
     {file = "Pillow-9.4.0-1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:43521ce2c4b865d385e78579a082b6ad1166ebed2b1a2293c3be1d68dd7ca3b9"},
     {file = "Pillow-9.4.0-1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:43521ce2c4b865d385e78579a082b6ad1166ebed2b1a2293c3be1d68dd7ca3b9"},
-    {file = "Pillow-9.4.0-2-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:9d9a62576b68cd90f7075876f4e8444487db5eeea0e4df3ba298ee38a8d067b0"},
-    {file = "Pillow-9.4.0-2-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:87708d78a14d56a990fbf4f9cb350b7d89ee8988705e58e39bdf4d82c149210f"},
-    {file = "Pillow-9.4.0-2-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:8a2b5874d17e72dfb80d917213abd55d7e1ed2479f38f001f264f7ce7bae757c"},
-    {file = "Pillow-9.4.0-2-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:83125753a60cfc8c412de5896d10a0a405e0bd88d0470ad82e0869ddf0cb3848"},
-    {file = "Pillow-9.4.0-2-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:9e5f94742033898bfe84c93c831a6f552bb629448d4072dd312306bab3bd96f1"},
-    {file = "Pillow-9.4.0-2-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:013016af6b3a12a2f40b704677f8b51f72cb007dac785a9933d5c86a72a7fe33"},
-    {file = "Pillow-9.4.0-2-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:99d92d148dd03fd19d16175b6d355cc1b01faf80dae93c6c3eb4163709edc0a9"},
     {file = "Pillow-9.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:2968c58feca624bb6c8502f9564dd187d0e1389964898f5e9e1fbc8533169157"},
     {file = "Pillow-9.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:2968c58feca624bb6c8502f9564dd187d0e1389964898f5e9e1fbc8533169157"},
     {file = "Pillow-9.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c5c1362c14aee73f50143d74389b2c158707b4abce2cb055b7ad37ce60738d47"},
     {file = "Pillow-9.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c5c1362c14aee73f50143d74389b2c158707b4abce2cb055b7ad37ce60738d47"},
     {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd752c5ff1b4a870b7661234694f24b1d2b9076b8bf337321a814c612665f343"},
     {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd752c5ff1b4a870b7661234694f24b1d2b9076b8bf337321a814c612665f343"},
@@ -2060,4 +2052,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools"
 [metadata]
 [metadata]
 lock-version = "2.0"
 lock-version = "2.0"
 python-versions = "^3.7"
 python-versions = "^3.7"
-content-hash = "99911c0418f3637a5aa69bb4f1411cb4704e5d24583afa69269b311fca710a7d"
+content-hash = "d295b6331955c188539cab3df96edbc39a6c5329b5ca37d73733378473367126"

+ 1 - 1
pyproject.toml

@@ -11,7 +11,7 @@ keywords = ["gui", "ui", "web", "interface", "live"]
 [tool.poetry.dependencies]
 [tool.poetry.dependencies]
 python = "^3.7"
 python = "^3.7"
 typing-extensions = ">=3.10.0"
 typing-extensions = ">=3.10.0"
-markdown2 = "^2.4.3"
+markdown2 = "^2.4.7"
 Pygments = "^2.9.0"
 Pygments = "^2.9.0"
 docutils = "^0.17.1"
 docutils = "^0.17.1"
 uvicorn = {extras = ["standard"], version = "^0.20.0"}
 uvicorn = {extras = ["standard"], version = "^0.20.0"}

+ 4 - 0
tests/test_element.py

@@ -34,6 +34,7 @@ def test_classes(screen: Screen):
 
 
 
 
 def test_style_parsing():
 def test_style_parsing():
+    assert Element._parse_style(None) == {}
     assert Element._parse_style('color: red; background-color: green') == {'color': 'red', 'background-color': 'green'}
     assert Element._parse_style('color: red; background-color: green') == {'color': 'red', 'background-color': 'green'}
     assert Element._parse_style('width:12em;height:34.5em') == {'width': '12em', 'height': '34.5em'}
     assert Element._parse_style('width:12em;height:34.5em') == {'width': '12em', 'height': '34.5em'}
     assert Element._parse_style('transform: translate(120.0px, 50%)') == {'transform': 'translate(120.0px, 50%)'}
     assert Element._parse_style('transform: translate(120.0px, 50%)') == {'transform': 'translate(120.0px, 50%)'}
@@ -41,10 +42,13 @@ def test_style_parsing():
 
 
 
 
 def test_props_parsing():
 def test_props_parsing():
+    assert Element._parse_props(None) == {}
     assert Element._parse_props('one two=1 three="abc def"') == {'one': True, 'two': '1', 'three': 'abc def'}
     assert Element._parse_props('one two=1 three="abc def"') == {'one': True, 'two': '1', 'three': 'abc def'}
     assert Element._parse_props('loading percentage=12.5') == {'loading': True, 'percentage': '12.5'}
     assert Element._parse_props('loading percentage=12.5') == {'loading': True, 'percentage': '12.5'}
     assert Element._parse_props('size=50%') == {'size': '50%'}
     assert Element._parse_props('size=50%') == {'size': '50%'}
     assert Element._parse_props('href=http://192.168.42.100/') == {'href': 'http://192.168.42.100/'}
     assert Element._parse_props('href=http://192.168.42.100/') == {'href': 'http://192.168.42.100/'}
+    assert Element._parse_props('hint="Your \\"given\\" name"') == {'hint': 'Your "given" name'}
+    assert Element._parse_props('input-style="{ color: #ff0000 }"') == {'input-style': '{ color: #ff0000 }'}
 
 
 
 
 def test_style(screen: Screen):
 def test_style(screen: Screen):

+ 45 - 0
tests/test_markdown.py

@@ -0,0 +1,45 @@
+from nicegui import ui
+
+from .screen import Screen
+
+
+def test_markdown(screen: Screen):
+    m = ui.markdown('This is **markdown**')
+
+    screen.open('/')
+    element = screen.find('This is')
+    assert element.text == 'This is markdown'
+    assert element.get_attribute('innerHTML') == 'This is <strong>markdown</strong>'
+
+    m.set_content('New **content**')
+    element = screen.find('New')
+    assert element.text == 'New content'
+    assert element.get_attribute('innerHTML') == 'New <strong>content</strong>'
+
+
+def test_markdown_with_mermaid(screen: Screen):
+    m = ui.markdown('''
+Mermaid:
+
+```mermaid
+graph TD;
+    Node_A --> Node_B;
+```
+''', extras=['mermaid', 'fenced-code-blocks'])
+
+    screen.open('/')
+    screen.should_contain('Mermaid')
+    assert screen.find_by_tag('svg').get_attribute('id') == f'mermaid_{m.id}_0'
+    assert screen.find('Node_A').get_attribute('class') == 'nodeLabel'
+
+    m.set_content('''
+New:
+    
+```mermaid
+graph TD;
+    Node_C --> Node_D;
+```
+''')
+    screen.should_contain('New')
+    assert screen.find('Node_C').get_attribute('class') == 'nodeLabel'
+    screen.should_not_contain('Node_A')

+ 20 - 0
tests/test_mermaid.py

@@ -0,0 +1,20 @@
+from nicegui import ui
+
+from .screen import Screen
+
+
+def test_mermaid(screen: Screen):
+    m = ui.mermaid('''
+graph TD;
+    Node_A --> Node_B;
+''')
+
+    screen.open('/')
+    assert screen.find('Node_A').get_attribute('class') == 'nodeLabel'
+
+    m.set_content('''
+graph TD;
+    Node_C --> Node_D;
+''')
+    assert screen.find('Node_C').get_attribute('class') == 'nodeLabel'
+    screen.should_not_contain('Node_A')

+ 8 - 0
website/reference.py

@@ -168,6 +168,14 @@ def create_full() -> None:
     def markdown_example():
     def markdown_example():
         ui.markdown('''This is **Markdown**.''')
         ui.markdown('''This is **Markdown**.''')
 
 
+    @example(ui.mermaid)
+    def mermaid_example():
+        ui.mermaid('''
+        graph LR;
+            A --> B;
+            A --> C;
+        ''')
+
     @example(ui.html)
     @example(ui.html)
     def html_example():
     def html_example():
         ui.html('This is <strong>HTML</strong>.')
         ui.html('This is <strong>HTML</strong>.')

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно