diff --git a/core/package.json b/core/package.json index d4780e8..d1ccc5b 100644 --- a/core/package.json +++ b/core/package.json @@ -6,6 +6,9 @@ ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" + }, + "./elements": { + "import": "./dist/elements.js" } }, "repository": { diff --git a/core/rslib.config.ts b/core/rslib.config.ts index 1492d60..e08fe0d 100644 --- a/core/rslib.config.ts +++ b/core/rslib.config.ts @@ -16,5 +16,26 @@ export default defineConfig({ externals: ["vega", "vega-lite", "vega-embed"], }, }, + // Self-contained browser bundle (`./elements` export). Loading it registers + // the `` custom element, so a docs page needs only a single + // + * + * ``` + * + * Attributes: `preset` (unified preset name, default the molplot preset), + * `theme` (`auto` | `light` | `dark`, default `auto`), and `spec` (inline JSON, + * a one-line alternative to the script block). + */ + +/** + * Parse the Vega-Lite spec an author embedded in a ``. Reads the + * first child ` + + +``` + +
+ + + +
+ +Attributes mirror the fence options (`preset`, `theme`), plus a `spec` attribute +holding inline JSON as a one-line alternative to the script block. The element +renders through the same [`RawChart`](web.md) the library exposes, so a spec that +carries its own `config` is respected verbatim. + +## Enabling it + +Two small additions to `zensical.toml` — already configured in this repo: + +1. **Load the component** once for the whole site: + + ```toml + extra_javascript = [ + "https://cdn.jsdelivr.net/npm/@molcrafts/molplot@0.1/dist/elements.js", + ] + ``` + + Loading it registers `` cheaply; the ~350 KB Vega runtime is a + lazy chunk that downloads only when a chart actually mounts. + +2. **Register the fence** as a `pymdownx.superfences` custom fence pointing at the + formatter shipped with the Python package (`molplot.mdx`): + + ```toml + [[project.markdown_extensions.pymdownx.superfences.custom_fences]] + name = "molplot" + class = "molplot" + format = "molplot.mdx.molplot_fence" + validator = "molplot.mdx.molplot_validator" + ``` + + The formatter runs at build time and is a pure text transform: it turns the + fenced Vega-Lite spec into the `` element above — it does not + draw the chart. Because declaring `markdown_extensions` replaces Zensical's + defaults, this repo's `zensical.toml` re-lists the full default set alongside + this fence. The `molcrafts-molplot` package must be importable at build time + (it is in the `doc` dependency group). diff --git a/docs/getting-started/python.md b/docs/getting-started/python.md index ab5a9e3..805dfa7 100644 --- a/docs/getting-started/python.md +++ b/docs/getting-started/python.md @@ -20,7 +20,7 @@ molplot.use("molplot-paper") # serif, high-DPI, 'nature' base with molplot.style("molplot", mode="dark"): plt.plot(x, y) # scoped; restores on exit -molplot.palette()[0] # '#1f77b4' — same colours as the web +molplot.palette()[0] # '#0c5da5' — same colours as the web ``` `plt.style.use("molplot")` also works directly (the `.mplstyle` files register diff --git a/presets/molplot-paper.json b/presets/molplot-paper.json index 404bf9b..73df3fb 100644 --- a/presets/molplot-paper.json +++ b/presets/molplot-paper.json @@ -2,7 +2,7 @@ "$schema": "./preset.schema.json", "name": "molplot-paper", "label": "MolPlot Paper", - "description": "Publication variant of the MolPlot preset — serif type, tighter figure, high DPI, layered on scienceplots' 'nature' base. Same categorical palette as the default so web and paper figures stay colour-consistent.", + "description": "Publication variant layered on scienceplots' 'science' + 'nature' bases — the same standard seven-colour cycle as the default, thin 1.0 pt lines and serif type, but a tighter single-column figure and higher DPI. Web and paper figures stay colour-consistent.", "sciencePlotsBase": ["science", "nature", "no-latex"], "typography": { "family": "Times New Roman, Times, Nimbus Roman, serif", @@ -18,34 +18,21 @@ }, "palette": { "categorical": [ - "#1f77b4", - "#ff7f0e", - "#2ca02c", - "#d62728", - "#9467bd", - "#8c564b", - "#e377c2", - "#7f7f7f", - "#bcbd22", - "#17becf", - "#aec7e8", - "#ffbb78", - "#98df8a", - "#ff9896", - "#c5b0d5", - "#c49c94", - "#f7b6d2", - "#c7c7c7", - "#dbdb8d", - "#9edae5" + "#0c5da5", + "#00b945", + "#ff9500", + "#ff2c00", + "#845b97", + "#474747", + "#9e9e9e" ], - "defaultColor": "#1f77b4", + "defaultColor": "#0c5da5", "sequential": "viridis", "diverging": "RdBu" }, "geometry": { "lineWidth": 1, - "markerSize": 4, + "markerSize": 3, "barGap": 0.2, "figSize": [3.3, 2.5], "dpi": 600, diff --git a/presets/molplot.json b/presets/molplot.json index 78d2685..bc6e6e9 100644 --- a/presets/molplot.json +++ b/presets/molplot.json @@ -2,10 +2,10 @@ "$schema": "./preset.schema.json", "name": "molplot", "label": "MolPlot", - "description": "MolCrafts unified scientific charting preset — the single source of truth shared by the Observable Plot + D3 web renderer and the scienceplots/matplotlib Python renderer.", + "description": "MolCrafts unified scientific charting preset, faithful to scienceplots' 'science' base — its standard seven-colour cycle, thin 1.0 pt lines, serif type, and black-on-white axes — so a browser chart and a matplotlib figure share the same scienceplots look.", "sciencePlotsBase": ["science", "no-latex"], "typography": { - "family": "Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif", + "family": "Times New Roman, Times, Nimbus Roman, serif", "familyMono": "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace", "familySerif": "Times New Roman, Times, Nimbus Roman, serif", "size": { @@ -18,33 +18,20 @@ }, "palette": { "categorical": [ - "#1f77b4", - "#ff7f0e", - "#2ca02c", - "#d62728", - "#9467bd", - "#8c564b", - "#e377c2", - "#7f7f7f", - "#bcbd22", - "#17becf", - "#aec7e8", - "#ffbb78", - "#98df8a", - "#ff9896", - "#c5b0d5", - "#c49c94", - "#f7b6d2", - "#c7c7c7", - "#dbdb8d", - "#9edae5" + "#0c5da5", + "#00b945", + "#ff9500", + "#ff2c00", + "#845b97", + "#474747", + "#9e9e9e" ], - "defaultColor": "#60a5fa", + "defaultColor": "#0c5da5", "sequential": "viridis", "diverging": "RdBu" }, "geometry": { - "lineWidth": 2, + "lineWidth": 1, "markerSize": 6, "barGap": 0.2, "figSize": [3.5, 2.625], @@ -55,19 +42,19 @@ "light": { "background": "transparent", "figureFace": "#ffffff", - "foreground": "#52525b", - "gridColor": "rgba(120,120,120,0.18)", - "gridColorSolid": "#d4d4d8", - "tickColor": "#a1a1aa", + "foreground": "#000000", + "gridColor": "rgba(0,0,0,0.10)", + "gridColorSolid": "#cccccc", + "tickColor": "#000000", "highlightRing": "#111827" }, "dark": { "background": "transparent", "figureFace": "#111113", - "foreground": "#d4d4d8", - "gridColor": "rgba(220,220,220,0.12)", - "gridColorSolid": "#3f3f46", - "tickColor": "#71717a", + "foreground": "#e5e5e5", + "gridColor": "rgba(255,255,255,0.10)", + "gridColorSolid": "#444444", + "tickColor": "#e5e5e5", "highlightRing": "#f9fafb" } } diff --git a/python/README.md b/python/README.md index 50ccb1e..079c262 100644 --- a/python/README.md +++ b/python/README.md @@ -33,7 +33,7 @@ molplot.use("molplot-paper") with molplot.style("molplot", mode="dark"): plt.plot(x, y) # scoped; restores rcParams on exit -molplot.palette()[0] # '#1f77b4' — same categorical colours as the web +molplot.palette()[0] # '#0c5da5' — same categorical colours as the web ``` `plt.style.use("molplot")` also works directly (the `.mplstyle` files are diff --git a/python/pyproject.toml b/python/pyproject.toml index 44507fe..49ec413 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -29,11 +29,15 @@ dependencies = [ [project.optional-dependencies] convert = ["vl-convert-python>=1.6"] +# YAML authoring for the `molplot` Markdown fence formatter (molplot.mdx). +mdx = ["pyyaml>=6.0"] dev = [ "build>=1.0", "hatchling", "pytest>=7.0", "pytest-cov", + # mdx's runtime dep, so test_mdx.py can exercise the YAML path. + "pyyaml>=6.0", ] # Docs are built with Zensical from the single site config at ../zensical.toml @@ -45,6 +49,11 @@ doc = [ "zensical>=0.0.45", # The molcrafts docs theme (zensical.toml sets `theme.name = "molcrafts"`). "molcrafts-zensical-theme>=0.1.0", + # This package itself, so the `molplot` custom-fence formatter + # (molplot.mdx.molplot_fence) is importable while the site compiles. + "molcrafts-molplot", + # YAML authoring for the `molplot` fenced block (molplot.mdx). + "pyyaml>=6.0", ] [project.urls] diff --git a/python/src/molplot/mdx.py b/python/src/molplot/mdx.py new file mode 100644 index 0000000..e4027f1 --- /dev/null +++ b/python/src/molplot/mdx.py @@ -0,0 +1,144 @@ +"""Markdown authoring sugar — a ``molplot`` fenced block for docs. + +Lets a documentation author embed a chart by writing a Vega-Lite spec directly +in a fenced code block instead of raw HTML:: + + ```molplot + mark: line + data: + values: + - {step: 0, energy: 1} + - {step: 1, energy: 2} + encoding: + x: {field: step, type: quantitative} + y: {field: energy, type: quantitative} + ``` + +This is a `pymdownx.superfences` *custom fence* formatter — a build-time +**text → text** transform that runs while the site is compiled. It does **not** +draw the chart: it parses the fenced body (a plain Vega-Lite spec, YAML or JSON) +and returns a ```` custom-element string carrying that spec in a +nested ``' + f"" + f"" + ) + + +def molplot_validator( + language: str, + inputs: dict[str, str], + options: dict[str, Any], + attrs: dict[str, Any], + md: Any, +) -> bool: + """`pymdownx.superfences` custom-fence validator. + + The default validator rejects any fence-header options, so ``preset=…`` / + ``theme=…`` / ``type=…`` would fall back to a plain code block. This accepts + exactly those keys and forwards them to the formatter via ``options``; + anything else fails validation (so a typo surfaces rather than silently + vanishing). + """ + for key, value in inputs.items(): + if key not in _ALLOWED_OPTIONS: + return False + options[key] = value + return True + + +def molplot_fence( + source: str, + language: str, + css_class: str, + options: dict[str, Any], + md: Any, + **kwargs: Any, +) -> str: + """`pymdownx.superfences` custom-fence formatter (see the module docstring). + + Signature follows the superfences ``format`` contract; ``options`` holds the + validated ``key=value`` pairs from the fence header (e.g. + ``preset=molplot-paper``) that :func:`molplot_validator` allowed through. + """ + return render_element( + source, + preset=options.get("preset"), + theme=options.get("theme"), + ) diff --git a/python/src/molplot/presets/_generated.py b/python/src/molplot/presets/_generated.py index 02aee41..0ed42b7 100644 --- a/python/src/molplot/presets/_generated.py +++ b/python/src/molplot/presets/_generated.py @@ -9,13 +9,13 @@ "molplot": { "name": "molplot", "label": "MolPlot", - "description": "MolCrafts unified scientific charting preset — the single source of truth shared by the Observable Plot + D3 web renderer and the scienceplots/matplotlib Python renderer.", + "description": "MolCrafts unified scientific charting preset, faithful to scienceplots' 'science' base — its standard seven-colour cycle, thin 1.0 pt lines, serif type, and black-on-white axes — so a browser chart and a matplotlib figure share the same scienceplots look.", "sciencePlotsBase": [ "science", "no-latex", ], "typography": { - "family": "Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif", + "family": "Times New Roman, Times, Nimbus Roman, serif", "familyMono": "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace", "familySerif": "Times New Roman, Times, Nimbus Roman, serif", "size": { @@ -28,33 +28,20 @@ }, "palette": { "categorical": [ - "#1f77b4", - "#ff7f0e", - "#2ca02c", - "#d62728", - "#9467bd", - "#8c564b", - "#e377c2", - "#7f7f7f", - "#bcbd22", - "#17becf", - "#aec7e8", - "#ffbb78", - "#98df8a", - "#ff9896", - "#c5b0d5", - "#c49c94", - "#f7b6d2", - "#c7c7c7", - "#dbdb8d", - "#9edae5", + "#0c5da5", + "#00b945", + "#ff9500", + "#ff2c00", + "#845b97", + "#474747", + "#9e9e9e", ], - "defaultColor": "#60a5fa", + "defaultColor": "#0c5da5", "sequential": "viridis", "diverging": "RdBu", }, "geometry": { - "lineWidth": 2, + "lineWidth": 1, "markerSize": 6, "barGap": 0.2, "figSize": [ @@ -73,19 +60,19 @@ "light": { "background": "transparent", "figureFace": "#ffffff", - "foreground": "#52525b", - "gridColor": "rgba(120,120,120,0.18)", - "gridColorSolid": "#d4d4d8", - "tickColor": "#a1a1aa", + "foreground": "#000000", + "gridColor": "rgba(0,0,0,0.10)", + "gridColorSolid": "#cccccc", + "tickColor": "#000000", "highlightRing": "#111827", }, "dark": { "background": "transparent", "figureFace": "#111113", - "foreground": "#d4d4d8", - "gridColor": "rgba(220,220,220,0.12)", - "gridColorSolid": "#3f3f46", - "tickColor": "#71717a", + "foreground": "#e5e5e5", + "gridColor": "rgba(255,255,255,0.10)", + "gridColorSolid": "#444444", + "tickColor": "#e5e5e5", "highlightRing": "#f9fafb", }, }, @@ -93,7 +80,7 @@ "molplot-paper": { "name": "molplot-paper", "label": "MolPlot Paper", - "description": "Publication variant of the MolPlot preset — serif type, tighter figure, high DPI, layered on scienceplots' 'nature' base. Same categorical palette as the default so web and paper figures stay colour-consistent.", + "description": "Publication variant layered on scienceplots' 'science' + 'nature' bases — the same standard seven-colour cycle as the default, thin 1.0 pt lines and serif type, but a tighter single-column figure and higher DPI. Web and paper figures stay colour-consistent.", "sciencePlotsBase": [ "science", "nature", @@ -113,34 +100,21 @@ }, "palette": { "categorical": [ - "#1f77b4", - "#ff7f0e", - "#2ca02c", - "#d62728", - "#9467bd", - "#8c564b", - "#e377c2", - "#7f7f7f", - "#bcbd22", - "#17becf", - "#aec7e8", - "#ffbb78", - "#98df8a", - "#ff9896", - "#c5b0d5", - "#c49c94", - "#f7b6d2", - "#c7c7c7", - "#dbdb8d", - "#9edae5", + "#0c5da5", + "#00b945", + "#ff9500", + "#ff2c00", + "#845b97", + "#474747", + "#9e9e9e", ], - "defaultColor": "#1f77b4", + "defaultColor": "#0c5da5", "sequential": "viridis", "diverging": "RdBu", }, "geometry": { "lineWidth": 1, - "markerSize": 4, + "markerSize": 3, "barGap": 0.2, "figSize": [ 3.3, diff --git a/python/src/molplot/presets/molplot-dark.mplstyle b/python/src/molplot/presets/molplot-dark.mplstyle index 6044532..c4f1095 100644 --- a/python/src/molplot/presets/molplot-dark.mplstyle +++ b/python/src/molplot/presets/molplot-dark.mplstyle @@ -9,8 +9,8 @@ figure.facecolor: 111113 axes.facecolor: 111113 savefig.facecolor: 111113 -font.family: sans-serif -font.sans-serif: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif +font.family: serif +font.serif: Times New Roman, Times, Nimbus Roman, serif font.size: 10 axes.titlesize: 12 axes.labelsize: 10 @@ -18,18 +18,18 @@ xtick.labelsize: 9 ytick.labelsize: 9 legend.fontsize: 9 -axes.prop_cycle: cycler('color', ['1f77b4', 'ff7f0e', '2ca02c', 'd62728', '9467bd', '8c564b', 'e377c2', '7f7f7f', 'bcbd22', '17becf', 'aec7e8', 'ffbb78', '98df8a', 'ff9896', 'c5b0d5', 'c49c94', 'f7b6d2', 'c7c7c7', 'dbdb8d', '9edae5']) -lines.linewidth: 2 +axes.prop_cycle: cycler('color', ['0c5da5', '00b945', 'ff9500', 'ff2c00', '845b97', '474747', '9e9e9e']) +lines.linewidth: 1 lines.markersize: 6 -text.color: d4d4d8 -axes.edgecolor: d4d4d8 -axes.labelcolor: d4d4d8 -xtick.color: d4d4d8 -ytick.color: d4d4d8 +text.color: e5e5e5 +axes.edgecolor: e5e5e5 +axes.labelcolor: e5e5e5 +xtick.color: e5e5e5 +ytick.color: e5e5e5 axes.grid: True -grid.color: 3f3f46 +grid.color: 444444 grid.linewidth: 0.5 grid.alpha: 1.0 axes.axisbelow: True diff --git a/python/src/molplot/presets/molplot-paper-dark.mplstyle b/python/src/molplot/presets/molplot-paper-dark.mplstyle index 15634a5..e70903b 100644 --- a/python/src/molplot/presets/molplot-paper-dark.mplstyle +++ b/python/src/molplot/presets/molplot-paper-dark.mplstyle @@ -18,9 +18,9 @@ xtick.labelsize: 8 ytick.labelsize: 8 legend.fontsize: 8 -axes.prop_cycle: cycler('color', ['1f77b4', 'ff7f0e', '2ca02c', 'd62728', '9467bd', '8c564b', 'e377c2', '7f7f7f', 'bcbd22', '17becf', 'aec7e8', 'ffbb78', '98df8a', 'ff9896', 'c5b0d5', 'c49c94', 'f7b6d2', 'c7c7c7', 'dbdb8d', '9edae5']) +axes.prop_cycle: cycler('color', ['0c5da5', '00b945', 'ff9500', 'ff2c00', '845b97', '474747', '9e9e9e']) lines.linewidth: 1 -lines.markersize: 4 +lines.markersize: 3 text.color: e5e5e5 axes.edgecolor: e5e5e5 diff --git a/python/src/molplot/presets/molplot-paper.mplstyle b/python/src/molplot/presets/molplot-paper.mplstyle index 67d6565..97d4367 100644 --- a/python/src/molplot/presets/molplot-paper.mplstyle +++ b/python/src/molplot/presets/molplot-paper.mplstyle @@ -18,9 +18,9 @@ xtick.labelsize: 8 ytick.labelsize: 8 legend.fontsize: 8 -axes.prop_cycle: cycler('color', ['1f77b4', 'ff7f0e', '2ca02c', 'd62728', '9467bd', '8c564b', 'e377c2', '7f7f7f', 'bcbd22', '17becf', 'aec7e8', 'ffbb78', '98df8a', 'ff9896', 'c5b0d5', 'c49c94', 'f7b6d2', 'c7c7c7', 'dbdb8d', '9edae5']) +axes.prop_cycle: cycler('color', ['0c5da5', '00b945', 'ff9500', 'ff2c00', '845b97', '474747', '9e9e9e']) lines.linewidth: 1 -lines.markersize: 4 +lines.markersize: 3 text.color: 000000 axes.edgecolor: 000000 diff --git a/python/src/molplot/presets/molplot.mplstyle b/python/src/molplot/presets/molplot.mplstyle index 443d2b6..eda89e1 100644 --- a/python/src/molplot/presets/molplot.mplstyle +++ b/python/src/molplot/presets/molplot.mplstyle @@ -9,8 +9,8 @@ figure.facecolor: ffffff axes.facecolor: ffffff savefig.facecolor: ffffff -font.family: sans-serif -font.sans-serif: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif +font.family: serif +font.serif: Times New Roman, Times, Nimbus Roman, serif font.size: 10 axes.titlesize: 12 axes.labelsize: 10 @@ -18,18 +18,18 @@ xtick.labelsize: 9 ytick.labelsize: 9 legend.fontsize: 9 -axes.prop_cycle: cycler('color', ['1f77b4', 'ff7f0e', '2ca02c', 'd62728', '9467bd', '8c564b', 'e377c2', '7f7f7f', 'bcbd22', '17becf', 'aec7e8', 'ffbb78', '98df8a', 'ff9896', 'c5b0d5', 'c49c94', 'f7b6d2', 'c7c7c7', 'dbdb8d', '9edae5']) -lines.linewidth: 2 +axes.prop_cycle: cycler('color', ['0c5da5', '00b945', 'ff9500', 'ff2c00', '845b97', '474747', '9e9e9e']) +lines.linewidth: 1 lines.markersize: 6 -text.color: 52525b -axes.edgecolor: 52525b -axes.labelcolor: 52525b -xtick.color: 52525b -ytick.color: 52525b +text.color: 000000 +axes.edgecolor: 000000 +axes.labelcolor: 000000 +xtick.color: 000000 +ytick.color: 000000 axes.grid: True -grid.color: d4d4d8 +grid.color: cccccc grid.linewidth: 0.5 grid.alpha: 1.0 axes.axisbelow: True diff --git a/python/tests/test_mdx.py b/python/tests/test_mdx.py new file mode 100644 index 0000000..5e34ad6 --- /dev/null +++ b/python/tests/test_mdx.py @@ -0,0 +1,74 @@ +import json + +from molplot.mdx import molplot_fence, molplot_validator, render_element + + +def _extract_spec(el: str) -> dict: + start = el.index(">", el.index("") + return json.loads(el[start:end]) + + +def test_render_element_wraps_yaml_spec_in_a_block_div(): + el = render_element( + "mark: line\n" + "data:\n" + " values:\n" + " - {x: 0, y: 1}\n" + " - {x: 1, y: 2}\n" + "encoding:\n" + " x: {field: x, type: quantitative}\n" + ) + # A block-level
wrapper keeps Markdown/md_in_html from wrapping the + # element in

or reprocessing the JSON. + assert el.startswith('

') + assert el.endswith("
") + assert "" in el + spec = _extract_spec(el) + assert spec["mark"] == "line" + assert spec["data"]["values"] == [{"x": 0, "y": 1}, {"x": 1, "y": 2}] + + +def test_render_element_accepts_json_spec(): + spec = _extract_spec(render_element('{"mark": "bar", "data": {"values": []}}')) + assert spec == {"mark": "bar", "data": {"values": []}} + + +def test_render_element_forwards_preset_and_theme(): + el = render_element("mark: point", preset="molplot-paper", theme="dark") + assert 'preset="molplot-paper"' in el + assert 'theme="dark"' in el + + +def test_render_element_reports_parse_error_inline(): + el = render_element("mark: [unterminated") + assert "molplot-error" in el + assert "` Web Component (the `elements` +# bundle of @molcrafts/molplot). It registers the custom element cheaply; the +# vega runtime is a lazy chunk that downloads only when a chart mounts. This is +# what makes the `` element and the ```molplot fence render live. +extra_javascript = [ + "https://cdn.jsdelivr.net/npm/@molcrafts/molplot@0.1/dist/elements.js", +] nav = [ { "Home" = "index.md" }, @@ -21,10 +23,51 @@ nav = [ "getting-started/preset.md", { "Web (Vega-Lite)" = "getting-started/web.md" }, { "Python (scienceplots)" = "getting-started/python.md" }, + { "Charts in Markdown" = "getting-started/markdown.md" }, ] }, { "API Reference" = "api/index.md" }, ] +# Markdown extensions. Declaring this section REPLACES Zensical's built-in +# default set (it is not merged), so the full default set is reproduced verbatim +# below — the only addition is the `molplot` custom fence (last custom_fence +# entry), which compiles a ```molplot block to a element via +# molplot.mdx.molplot_fence (the Python package must be importable at build +# time; see the `doc` group in python/pyproject.toml). Keep this in sync with +# Zensical's defaults on upgrade. +[project.markdown_extensions] +abbr = {} +admonition = {} +attr_list = {} +def_list = {} +footnotes = {} +md_in_html = {} + +[project.markdown_extensions.toc] +permalink = true + +[project.markdown_extensions.pymdownx] +betterem = {} +caret = {} +details = {} +inlinehilite = {} +keys = {} +magiclink = {} +mark = {} +smartsymbols = {} +tilde = {} +arithmatex = { generic = true } +emoji = { emoji_generator = "zensical.extensions.emoji.to_svg", emoji_index = "zensical.extensions.emoji.twemoji" } +highlight = { anchor_linenums = true, line_spans = "__span", pygments_lang_class = true } +tabbed = { alternate_style = true, combine_header_slug = true } +tasklist = { custom_checkbox = true } + +[project.markdown_extensions.pymdownx.superfences] +custom_fences = [ + { name = "mermaid", class = "mermaid" }, + { name = "molplot", class = "molplot", format = "molplot.mdx.molplot_fence", validator = "molplot.mdx.molplot_validator" }, +] + # Shared MolCrafts theme (github.com/MolCrafts/molcrafts-zensical-theme). Provides # the brand palette, light/dark schemes, navigation features, and the home-page # hero/manual-home component system used by docs/index.md. The package must be