Use Mermaid API directly to fix nested diagram inside `ui.dialog` inside `ui.markdown` (#4722)
This PR is extremely similar in nature to #4692, in that it fixes:
- broken text as in
https://github.com/zauberzeug/nicegui/pull/4170#issuecomment-2865321544
- showing the mermaid diagrams source code for about half a second
whenever a page is refreshed containing the markdown component using
mermaid, as in
https://github.com/zauberzeug/nicegui/pull/4170#issue-2767685163
by, again invoking the Mermaid API exactly as prescribed at
https://mermaid.js.org/config/usage.html#api-usage
Notable differences compared to past attempt at #4170:
- Uses `mermaid.render` not `mermaid.run`, more easy API integration
- `this.mermaid.initialize({ startOnLoad: false });` should not be
awaited, according to the Mermaid docs.
- Since the SVG is added by me because I control the API usage, I always
make the text hidden no-matter-what in the CSS, and add the SVG in
another class so that it shows up. No need stuff like
`.mermaid[data-processed="true"]``
Notable differences compared to #4692:
- Stores the last rendered Mermaid diagram, since the markdown is
somehow updated when the `ui.dialog` finishes the animation, and
otherwise we'd render twice and there'd be Layout Shift.
There are still tasks to do:
- [x] Address multiple-mermaid situation (I'd imagine this
implementation breaking, especially with the caching of rendered
diagrams).
But the mermaid integration with markdown isn't perfect. There are still
some tasks on my wishlist. I hope to address some of them some day soon:
- Cannot influence the mermaid initialize parameters so as to enable
click events like
https://nicegui.io/documentation/mermaid#handle_click_events
- Cannot listen for mermaid error messages like
https://nicegui.io/documentation/mermaid#handle_errors
---
Test Script (updated to include multi-mermaid scenario):
<details>
<summary> It's a bit long but it works </summary>
```py
from nicegui import ui
import random
def generate_random_graph():
nodes = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J']
two_random_nodes = random.sample(nodes, 2)
# Generate a random graph with 1 edge
edges = [f"{two_random_nodes[0]} --> {two_random_nodes[1]}"]
return f"graph TD; {' '.join(edges)}"
@ui.page('/mermaid_inside_markdown')
def mermaid_inside_markdown():
md = ui.markdown('''
- Mermaid inside markdown
```mermaid
graph TD;
A-->B;
A-->C;
B-->D;
C-->D;
```
''', extras=['mermaid'])
# mermaid inside markdown inside ui.dialog
@ui.page('/mermaid_inside_markdown_dialog')
def mermaid_inside_markdown_dialog():
with ui.dialog(value=True).props('transition-duration=1500'), ui.card():
my_markdown = ui.markdown('''
- Mermaid inside markdown inside dialog
```mermaid
graph TD;
A-->B;
A-->C;
B-->D;
C-->D;
```
''', extras=['mermaid'])
def change_to_random_markdown_with_mermaid():
my_markdown.set_content(f'''
- Mermaid inside markdown inside dialog
```mermaid
{generate_random_graph()}
```
''')
def change_to_markdown_with_errorneous_mermaid():
my_markdown.set_content('''
- Mermaid inside markdown inside dialog
```mermaid
graph TD;
A-->B;
A->C;
```
''')
def change_to_markdown_with_many_mermaid():
my_markdown.set_content(f'''
- Mermaid inside markdown inside dialog
```mermaid
{generate_random_graph()}
```
```mermaid
{generate_random_graph()}
```
```mermaid
{generate_random_graph()}
```
''')
ui.button('Change to random graph', on_click=change_to_random_markdown_with_mermaid)
ui.button('Change to erroneous graph', on_click=change_to_markdown_with_errorneous_mermaid)
ui.button('Change to many graphs', on_click=change_to_markdown_with_many_mermaid)
# many meriaid inside markdown inside dialog
@ui.page('/mermaid_inside_markdown_dialog_many')
def mermaid_inside_markdown_dialog_many():
with ui.dialog(value=True).props('transition-duration=1500'), ui.card():
my_markdown = ui.markdown(f'''
- Mermaid inside markdown inside dialog
```mermaid
{generate_random_graph()}
```
```mermaid
{generate_random_graph()}
```
```mermaid
{generate_random_graph()}
```
''', extras=['mermaid'])
ui.link('mermaid inside markdown', '/mermaid_inside_markdown')
ui.link('mermaid inside markdown dialog', '/mermaid_inside_markdown_dialog')
ui.link('mermaid inside markdown dialog many', '/mermaid_inside_markdown_dialog_many')
ui.run(show=False, port=9191)
```
</details>
<img width="253" alt="{7C71FC62-1331-43CC-900D-5682586111C3}"
src="https://github.com/user-attachments/assets/3a2191f9-fe0c-4439-a279-12e6a2562792"
/>
<img width="271" alt="{28A96891-BF42-43B3-8027-D0C7E6CDD3C2}"
src="https://github.com/user-attachments/assets/34c3afd5-528b-4177-bd1b-30a54c612736"
/>
---------
Co-authored-by: Falko Schindler <falko@zauberzeug.com>