-
-
Notifications
You must be signed in to change notification settings - Fork 604
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Mermaid in markdown #278
Comments
I noticed that the workaround of adding the "mermaid" class ourselves won't work in general since we can't know which pre tag is Mermaid and which is not. But markdown2 already has a solution which only needs to get released to PyPI. So we'll wait until this fix is out. |
Ok, markdown2 has been updated and seams to be working. But the integration turns out to be a bit trickier than expected. When adding the body HTML via When changing the markdown content, the new graph is not rendered. So far I couldn't find a way to trigger the rendering again. @SebastienDorgan Do you have an idea how we could do that? I pushed my current implementation to the "mermaid" branch: https://github.com/zauberzeug/nicegui/tree/mermaid |
I did nothing special, here is the code that worked 2 days ago with nicegui master and markdown2 master. from typing import Any, Dict, List
from nicegui import ui
from shunter.model import ProcessSpec, SinkSpec, SourceSpec
from shunter.repository import Document
from shunter.service.application import ApplicationServiceApi
async def content(service: ApplicationServiceApi) -> None:
mermaid_header = """\
<style>
.mermaid-pre {
visibility: hidden;
}
</style>
"""
ui.add_head_html(mermaid_header)
mermaid_footer = """\
<script type="module" defer>
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@9/dist/mermaid.esm.min.mjs';
mermaid.initialize({
securityLevel: 'loose',
startOnLoad: true
});
let observer = new MutationObserver(mutations => {
for(let mutation of mutations) {
mutation.target.style.visibility = "visible";
}
});
document.querySelectorAll("pre.mermaid-pre div.mermaid").forEach(item => {
observer.observe(item, {
attributes: true,
attributeFilter: ['data-processed'] });
});
</script>
"""
ui.add_body_html(mermaid_footer)
with ui.left_drawer().style():
ui.tree(await load_appplication_tree(service), label_key="id", on_select=lambda e: ui.notify(e.value))
with ui.card().classes("rounded w-full").style("height: 91vh"):
ui.label("Streams")
content = """\
```mermaid
graph TD;
A-->B;
A-->C;
B-->D;
C-->D;
```
"""
graph = ui.markdown(content, extras=["fenced-code-blocks", "mermaid"])
print(graph)
async def load_appplication_tree(service: ApplicationServiceApi) -> List[Dict[str, Any]]:
def to_child(doc: Document[ProcessSpec] | Document[SourceSpec] | Document[SinkSpec]):
return {"id": f"{doc.content.name}:{doc.content.version}"}
process_children = list(map(to_child, service.get_all_processes()))
source_children = list(map(to_child, service.get_all_sources()))
sink_children = list(map(to_child, service.get_all_sinks()))
return [
{"id": "Processes", "children": process_children},
{"id": "Sources", "children": source_children},
{"id": "Sinks", "children": sink_children},
] Today, with nicegui master, markdown2 2.4.7 it doesn't work. Unfortunately I did not track the commit tags, I don't use this code anymore. |
Hi, I don't know where you are in your reflections but I came up with a solution that I think is generic enough but may seem overkill because I do server side rendering. Maybe server side rendering could be useful to implement other functionalities? The code for the web rendering: from lxml import etree
from selenium import webdriver
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
class Renderer:
def __init__(self) -> None:
chrome_options = Options()
chrome_options.add_argument("--disable-extensions")
chrome_options.add_argument("--disable-gpu")
chrome_options.add_argument("--no-sandbox") # linux only
chrome_options.add_argument("--headless")
# chrome_options.headless = True # also works
self.driver = webdriver.Chrome(options=chrome_options)
def render_to_div(self, web_content: str) -> str:
self.driver.get("data:text/html;charset=utf-8," + web_content)
full_page = self.driver.page_source
root = etree.XML(full_page, parser=None)
scripts = root.findall("./body/script")
body = root.find("body")
if body is not None:
for script in scripts:
body.remove(script)
body.tag = "div"
return etree.tostring(body).decode("utf-8")
raise ValueError("Invalid HTML content, body tag not found")
def render(self, web_content: str) -> str:
self.driver.get("data:text/html;charset=utf-8," + web_content)
return self.driver.page_source The working example (I replaced ``` by ~~~ for the formatting): from multiprocessing.connection import Client
from shunter.ui import web
from nicegui import ui, Client
import markdown2
web_renderer = web.Renderer()
html_header = """
<html>
<head>
<style>
.mermaid-pre {
visibility: hidden;
}
</style>
</head>
<body>"""
mermaid_footer = """
<script type="module" defer>
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@9/dist/mermaid.esm.min.mjs';
mermaid.initialize({
securityLevel: 'loose',
startOnLoad: true
});
let observer = new MutationObserver(mutations => {
for(let mutation of mutations) {
mutation.target.style.visibility = "visible";
}
});
document.querySelectorAll("pre.mermaid-pre div.mermaid").forEach(item => {
observer.observe(item, {
attributes: true,
attributeFilter: ['data-processed'] });
});
</script>
"""
content = """\
# Title
~~~mermaid
graph TD;
A-->B;
A-->C;
B-->D;
C-->D;
~~~
"""
@ui.page("/")
async def raw_html(client: Client):
with ui.column().classes("w-full 95vh"):
html = markdown2.markdown(html_header+content+mermaid_footer, extras=["fenced-code-blocks", "mermaid"])
div_html = web_renderer.render_to_div(html)
print(div_html)
ui.html(div_html)
ui.run(show=False) |
@SebastienDorgan Very interesting! I thought about server-side rendering as well, but started with a standalone |
The driver is slow to start, but once done the rendering is fast |
I managed to implement both a Mermaid: me = ui.mermaid('graph LR; A-->B;')
ui.button('Add', on_click=lambda: ui.mermaid('graph LR; X-->Y;'))
ui.button('Update', on_click=lambda: me.set_content('graph LR; C-->D;')) Markdown: ma = ui.markdown('''
Mermaid:
```mermaid
graph LR;
A-->B;
```
''', extras=['mermaid', 'fenced-code-blocks'])
ui.button('Add', on_click=lambda: ui.markdown('''
More Mermaid:
```mermaid
graph LR;
X-->Y;
```
''', extras=['mermaid', 'fenced-code-blocks']))
ui.button('Update', on_click=lambda: ma.set_content('''
This has changed:
```mermaid
graph LR;
C-->D;
```
''')) The mermaid branch is now ready for review, testing, merge, ... |
In my test Mermaid inside Markdown is still not rendering. Even after clearing the browser cache.... |
Ah, I had not updated markdown2. After running |
Discussed in #277
Originally posted by SebastienDorgan January 21, 2023
Hi,
I am am really new to nicegui and I try to figure out how to make mermaid work with ui.markdown elements.
Here is what I tried:
unfortunately the result is not what I expected to be:
Can you help me?
We identified a bug in markdown2, but found a workaround. So we could extend
ui.markdown
to support Mermaid.ui.markdown
insert additional head and body HTML blocks (only once per client) if "mermaid" extra is usedui.mermaid
elementThe text was updated successfully, but these errors were encountered: