import os import re from functools import lru_cache from typing import List import markdown2 from pygments.formatters import HtmlFormatter from ..dependencies import register_component from .mixins.content_element import ContentElement register_component('markdown', __file__, 'markdown.js', ['lib/mermaid.min.js']) class Markdown(ContentElement): def __init__(self, content: str = '', *, extras: List[str] = ['fenced-code-blocks', 'tables']) -> None: """Markdown Element Renders Markdown onto the page. :param content: the Markdown content to be displayed :param extras: list of `markdown2 extensions `_ (default: `['fenced-code-blocks', 'tables']`) """ self.extras = extras super().__init__(tag='markdown', content=content) self._props['codehilite_css'] = HtmlFormatter(nobackground=True).get_style_defs('.codehilite') 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.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(remove_indentation(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 = { '': '

', r'': '

', ' str: """Remove indentation from a multi-line string based on the indentation of the first non-empty line.""" lines = text.splitlines() while lines and not lines[0].strip(): lines.pop(0) if not lines: return '' indentation = len(lines[0]) - len(lines[0].lstrip()) return '\n'.join(line[indentation:] for line in lines)