Skip to content

Commit

Permalink
Merge branch 'mermaid'
Browse files Browse the repository at this point in the history
  • Loading branch information
rodja committed Feb 6, 2023
2 parents 38d6e71 + 6e7decb commit e79597e
Show file tree
Hide file tree
Showing 12 changed files with 1,438 additions and 33 deletions.
1,284 changes: 1,284 additions & 0 deletions nicegui/elements/lib/mermaid.min.js

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions nicegui/elements/markdown.js
Original file line number Diff line number Diff line change
@@ -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;
}
39 changes: 21 additions & 18 deletions nicegui/elements/markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,10 @@

import markdown2

from ..dependencies import register_component
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):
Expand All @@ -36,16 +22,33 @@ def __init__(self, content: str = '', *, extras: List[str] = ['fenced-code-block
:param extras: list of `markdown2 extensions <https://github.com/trentm/python-markdown2/wiki/Extras#implemented-extras>`_ (default: `['fenced-code-blocks', 'tables']`)
"""
self.extras = extras
super().__init__(tag='div', content=content)
super().__init__(tag='markdown', content=content)

def on_content_change(self, content: str) -> None:
html = prepare_content(content, extras=' '.join(self.extras))
if self._props.get('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')))
def prepare_content(content: str, extras: str) -> str:
html = markdown2.markdown(content, extras=extras.split())
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 changes: 11 additions & 0 deletions nicegui/elements/mermaid.js
Original file line number Diff line number Diff line change
@@ -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 changes: 20 additions & 0 deletions nicegui/elements/mermaid.py
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 1 addition & 1 deletion nicegui/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -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_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
(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`)
'''
globals.ui_run_has_been_called = True
Expand Down
1 change: 1 addition & 0 deletions nicegui/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from .elements.markdown import Markdown as markdown
from .elements.menu import Menu as menu
from .elements.menu import MenuItem as menu_item
from .elements.mermaid import Mermaid as mermaid
from .elements.number import Number as number
from .elements.progress import CircularProgress as circular_progress
from .elements.progress import LinearProgress as linear_progress
Expand Down
18 changes: 5 additions & 13 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ keywords = ["gui", "ui", "web", "interface", "live"]
[tool.poetry.dependencies]
python = "^3.7"
typing-extensions = ">=3.10.0"
markdown2 = "^2.4.3"
markdown2 = "^2.4.7"
Pygments = "^2.9.0"
docutils = "^0.17.1"
uvicorn = {extras = ["standard"], version = "^0.20.0"}
Expand Down
45 changes: 45 additions & 0 deletions tests/test_markdown.py
Original file line number Diff line number Diff line change
@@ -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 changes: 20 additions & 0 deletions tests/test_mermaid.py
Original file line number Diff line number Diff line change
@@ -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 changes: 8 additions & 0 deletions website/reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,14 @@ def upload_example():
def markdown_example():
ui.markdown('''This is **Markdown**.''')

@example(ui.mermaid)
def mermaid_example():
ui.mermaid('''
graph LR;
A --> B;
A --> C;
''')

@example(ui.html)
def html_example():
ui.html('This is <strong>HTML</strong>.')
Expand Down

0 comments on commit e79597e

Please sign in to comment.