diff --git a/panel/_templates/autoload_panel_js.js b/panel/_templates/autoload_panel_js.js
index 01d9ade156..4fa3febf4f 100644
--- a/panel/_templates/autoload_panel_js.js
+++ b/panel/_templates/autoload_panel_js.js
@@ -182,7 +182,7 @@ calls it with the rendered model.
console.debug("Bokeh: injecting script tag for BokehJS library: ", url);
element.textContent = `
import ${name} from "${url}"
- window.${name} = ${name}
+ window.${name.replace('* as ', '')} = ${name.replace('* as ', '')}
window._bokeh_on_load()
`
document.head.appendChild(element);
diff --git a/panel/_templates/js_resources.html b/panel/_templates/js_resources.html
index b0912f0f7e..0ea2c3fb9a 100644
--- a/panel/_templates/js_resources.html
+++ b/panel/_templates/js_resources.html
@@ -28,7 +28,7 @@
{%- for name, file in js_module_exports.items() %}
{%- endfor %}
diff --git a/panel/config.py b/panel/config.py
index 5f7955d03f..7d7346a0a0 100644
--- a/panel/config.py
+++ b/panel/config.py
@@ -668,6 +668,7 @@ class panel_extension(_pyviz_extension):
'jsoneditor': 'panel.models.jsoneditor',
'katex': 'panel.models.katex',
'mathjax': 'panel.models.mathjax',
+ 'myst': 'panel.models.myst',
'perspective': 'panel.models.perspective',
'plotly': 'panel.models.plotly',
'tabulator': 'panel.models.tabulator',
@@ -689,6 +690,7 @@ class panel_extension(_pyviz_extension):
'gridstack': ['GridStack'],
'katex': ['katex'],
'mathjax': ['MathJax'],
+ 'myst': ['mystparser'],
'perspective': ["customElements.get('perspective-viewer')"],
'plotly': ['Plotly'],
'tabulator': ['Tabulator'],
diff --git a/panel/dist/css/markdown.css b/panel/dist/css/markdown.css
index f82f241016..033604dada 100644
--- a/panel/dist/css/markdown.css
+++ b/panel/dist/css/markdown.css
@@ -317,7 +317,8 @@ h2:hover a.header-anchor::before {
visibility: visible;
}
-.codehilite {
+.codehilite,
+pre > code.hljs {
padding: 1rem 1.25rem;
margin-top: 1rem;
margin-bottom: 1rem;
@@ -341,6 +342,7 @@ pre {
}
.codehilite > pre > code,
-.codehilite > code {
+.codehilite > code,
+pre > code.hljs {
white-space: break-spaces;
}
diff --git a/panel/models/html.ts b/panel/models/html.ts
index fe549519e2..dfeefcd7a9 100644
--- a/panel/models/html.ts
+++ b/panel/models/html.ts
@@ -181,7 +181,7 @@ export class HTMLView extends PanelMarkupView {
return processed_text.join("")
}
- private contains_tex(html: string): boolean {
+ contains_tex(html: string): boolean {
if (!this.provider.MathJax) {
return false
}
diff --git a/panel/models/index.ts b/panel/models/index.ts
index ba9cf6e1fd..f732a98742 100644
--- a/panel/models/index.ts
+++ b/panel/models/index.ts
@@ -27,6 +27,7 @@ export {JSONEditor} from "./jsoneditor"
export {KaTeX} from "./katex"
export {Location} from "./location"
export {MathJax} from "./mathjax"
+export {MyST} from "./myst"
export {PDF} from "./pdf"
export {Perspective} from "./perspective"
export {Player} from "./player"
diff --git a/panel/models/myst.py b/panel/models/myst.py
new file mode 100644
index 0000000000..12725e60fb
--- /dev/null
+++ b/panel/models/myst.py
@@ -0,0 +1,37 @@
+from ..config import config
+from ..io.resources import bundled_files
+from ..util import classproperty
+from .markup import HTML
+
+
+class MyST(HTML):
+ """
+ A bokeh model to render MyST markdown on the client side
+ """
+
+ __javascript_module_exports__ = ['* as mystparser', '* as myst2html']
+
+ __javascript_modules_raw__ = [
+ f"{config.npm_cdn}/myst-parser@1.5.3/+esm",
+ f"{config.npm_cdn}/myst-to-html@1.5.3/+esm"
+ ]
+
+ @classproperty
+ def __javascript_modules__(cls):
+ return bundled_files(cls, 'javascript_modules')
+
+ __javascript_raw__ = [
+ 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js'
+ ]
+
+ @classproperty
+ def __javascript__(cls):
+ return bundled_files(cls)
+
+ __css_raw__ = [
+ 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github-dark.min.css'
+ ]
+
+ @classproperty
+ def __css__(cls):
+ return bundled_files(cls, 'css')
diff --git a/panel/models/myst.ts b/panel/models/myst.ts
new file mode 100644
index 0000000000..7b49cb353f
--- /dev/null
+++ b/panel/models/myst.ts
@@ -0,0 +1,60 @@
+import type * as p from "@bokehjs/core/properties"
+import {HTMLView, HTML} from "./html"
+
+export class MySTView extends HTMLView {
+ declare model: MyST
+
+ override set_html(html: string | null): void {
+ super.set_html(html)
+ for (const el of this.container.querySelectorAll("pre code")) {
+ (window as any).hljs.highlightElement(el)
+ }
+ }
+
+ override process_tex(): string {
+ const parsed = (window as any).mystparser.mystParse(this.model.text)
+ const text = (window as any).myst2html.mystToHtml(parsed)
+ if (this.model.disable_math || !this.contains_tex(text)) {
+ return text
+ }
+
+ const tex_parts = this.provider.MathJax.find_tex(text)
+ const processed_text: string[] = []
+
+ let last_index: number | undefined = 0
+ for (const part of tex_parts) {
+ processed_text.push(text.slice(last_index, part.start.n))
+ processed_text.push(this.provider.MathJax.tex2svg(part.math, {display: part.display}).outerHTML)
+
+ last_index = part.end.n
+ }
+
+ if (last_index! < text.length) {
+ processed_text.push(text.slice(last_index))
+ }
+
+ return processed_text.join("")
+ }
+}
+
+export namespace MyST {
+ export type Attrs = p.AttrsOf
+ export type Props = HTML.Props
+}
+
+export interface MyST extends MyST.Attrs {}
+
+export class MyST extends HTML {
+ declare properties: MyST.Props
+
+ constructor(attrs?: Partial) {
+ super(attrs)
+ }
+
+ static override __module__ = "panel.models.myst"
+
+ static {
+ this.prototype.default_view = MySTView
+ this.define(({}) => ({}))
+ }
+}
diff --git a/panel/pane/markup.py b/panel/pane/markup.py
index 583903a932..065dfef2eb 100644
--- a/panel/pane/markup.py
+++ b/panel/pane/markup.py
@@ -14,9 +14,13 @@
import param # type: ignore
+from pyviz_comms import JupyterComm
+
from ..io.resources import CDN_DIST
from ..models.markup import HTML as _BkHTML, JSON as _BkJSON, HTMLStreamEvent
-from ..util import HTML_SANITIZER, escape, prefix_length
+from ..util import (
+ HTML_SANITIZER, escape, lazy_load, prefix_length,
+)
from .base import ModelPane
if TYPE_CHECKING:
@@ -361,7 +365,7 @@ class Markdown(HTMLBasePane):
Additional markdown-it-py plugins to use.""")
renderer = param.Selector(default='markdown-it', objects=[
- 'markdown-it', 'myst', 'markdown'], doc="""
+ 'markdown-it', 'myst', 'markdown', 'mystjs'], doc="""
Markdown renderer implementation.""")
renderer_options = param.Dict(default={}, nested_refs=True, doc="""
@@ -449,6 +453,8 @@ def hilite(token, langname, attrs):
return parser
def _transform_object(self, obj: Any) -> dict[str, Any]:
+ if self.renderer == 'mystjs':
+ return dict(object=obj)
import markdown
if obj is None:
obj = ''
@@ -482,6 +488,18 @@ def _process_param_change(self, params):
params['css_classes'] = ['markdown'] + params['css_classes']
return super()._process_param_change(params)
+ def _get_model(
+ self, doc: Document, root: Model | None = None,
+ parent: Model | None = None, comm: Comm | None = None
+ ) -> Model:
+ if self.renderer == 'mystjs':
+ self._bokeh_model = lazy_load(
+ 'panel.models.myst', 'MyST', isinstance(comm, JupyterComm), root
+ )
+ else:
+ self._bokeh_model = _BkHTML
+ model = super()._get_model(doc, root, parent, comm)
+ return model
class JSON(HTMLBasePane):