Skip to content

Commit 6b3fc19

Browse files
authored
👌 Improve: Non-directive colon-fences (#713)
At present, colon fences without a directive specified (e.g. `:::{note}`) are treated as code bocks ``` :::name Some text ::: ``` ```xml <code language=name> Some text ``` In keeping with the purpose of these block, as containers for nested MyST content, they are now converted to: ```xml <div class=name> <paragraph> Some text ``` This is also in keeping with the original pandoc/djot inspiration <https://htmlpreview.github.io/?https://github.com/jgm/djot/blob/master/doc/syntax.html#div>
1 parent ac111ea commit 6b3fc19

File tree

8 files changed

+104
-11
lines changed

8 files changed

+104
-11
lines changed

docs/syntax/optional.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ However, since Jinja2 substitutions allow for Python methods to be used, you can
403403
## Code fences using colons
404404

405405
By adding `"colon_fence"` to `myst_enable_extensions` (in the {{ confpy }}),
406-
you can also use `:::` delimiters to denote code fences, instead of ```` ``` ````.
406+
you can also use `:::` delimiters to denote directives, instead of ```` ``` ````.
407407

408408
Using colons instead of back-ticks has the benefit of allowing the content to be rendered correctly, when you are working in any standard Markdown editor.
409409
It is ideal for admonition type directives (as documented in [Directives](syntax/directives)) or tables with titles, for example:
@@ -477,6 +477,7 @@ This text is **standard** _Markdown_
477477
This text is **standard** _Markdown_
478478
:::
479479

480+
480481
(syntax/admonitions)=
481482

482483
## Admonition directives

myst_parser/mdit_to_docutils/base.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1479,15 +1479,27 @@ def render_myst_role(self, token: SyntaxTreeNode) -> None:
14791479
def render_colon_fence(self, token: SyntaxTreeNode) -> None:
14801480
"""Render a code fence with ``:`` colon delimiters."""
14811481

1482-
if token.content.startswith(":::"):
1483-
# the content starts with a nested fence block,
1484-
# but must distinguish between ``:options:``, so we add a new line
1485-
assert token.token is not None, '"colon_fence" must have a `token`'
1486-
linear_token = token.token.copy()
1487-
linear_token.content = "\n" + linear_token.content
1488-
token.token = linear_token
1489-
1490-
return self.render_fence(token)
1482+
info = token.info.strip() if token.info else token.info
1483+
name = info.split()[0] if info else ""
1484+
1485+
if name.startswith("{") and name.endswith("}"):
1486+
if token.content.startswith(":::"):
1487+
# the content starts with a nested fence block,
1488+
# but must distinguish between ``:options:``, so we add a new line
1489+
assert token.token is not None, '"colon_fence" must have a `token`'
1490+
linear_token = token.token.copy()
1491+
linear_token.content = "\n" + linear_token.content
1492+
token.token = linear_token
1493+
return self.render_directive(token)
1494+
1495+
container = nodes.container(is_div=True)
1496+
self.add_line_and_source_path(container, token)
1497+
self.copy_attributes(token, container, ("class", "id"))
1498+
if name:
1499+
# note, as per djot, the name is added to the end of the classes
1500+
container["classes"].append(name)
1501+
self.current_node.append(container)
1502+
self.nested_render_text(token.content, token_line(token, 0))
14911503

14921504
def render_dl(self, token: SyntaxTreeNode) -> None:
14931505
"""Render a definition list."""

myst_parser/parsers/docutils_.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,8 @@ def parse(self, inputstring: str, document: nodes.document) -> None:
261261

262262
HTMLTranslator.visit_rubric = visit_rubric_html
263263
HTMLTranslator.depart_rubric = depart_rubric_html
264+
HTMLTranslator.visit_container = visit_container_html
265+
HTMLTranslator.depart_container = depart_container_html
264266

265267
self.setup_parse(inputstring, document)
266268

@@ -426,3 +428,27 @@ def depart_rubric_html(self, node):
426428
self.body.append(f'</h{node["level"]}>\n')
427429
else:
428430
self.body.append("</p>\n")
431+
432+
433+
def visit_container_html(self, node: nodes.Node):
434+
"""Override the default HTML visit method for container nodes.
435+
436+
to remove the "container" class for divs
437+
this avoids CSS clashes with the bootstrap theme
438+
"""
439+
classes = "docutils container"
440+
attrs = {}
441+
if node.get("is_div", False):
442+
# we don't want the CSS for container for these nodes
443+
classes = "docutils"
444+
if "style" in node:
445+
attrs["style"] = node["style"]
446+
self.body.append(self.starttag(node, "div", CLASS=classes, **attrs))
447+
448+
449+
def depart_container_html(self, node: nodes.Node):
450+
"""Override the default HTML depart method for container nodes.
451+
452+
See explanation in `visit_container_html`
453+
"""
454+
self.body.append("</div>\n")

myst_parser/sphinx_ext/main.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44
from docutils import nodes
55
from sphinx.application import Sphinx
66

7-
from myst_parser.parsers.docutils_ import depart_rubric_html, visit_rubric_html
7+
from myst_parser.parsers.docutils_ import (
8+
depart_container_html,
9+
depart_rubric_html,
10+
visit_container_html,
11+
visit_rubric_html,
12+
)
813
from myst_parser.warnings_ import MystWarnings
914

1015

@@ -31,9 +36,18 @@ def setup_sphinx(app: Sphinx, load_parser=False):
3136
app.add_post_transform(MystReferenceResolver)
3237

3338
# override only the html writer visit methods for rubric, to use the "level" attribute
39+
# this allows for nested headers to be correctly rendered
3440
app.add_node(
3541
nodes.rubric, override=True, html=(visit_rubric_html, depart_rubric_html)
3642
)
43+
# override only the html writer visit methods for container,
44+
# to remove the "container" class for divs
45+
# this avoids CSS clashes with the bootstrap theme
46+
app.add_node(
47+
nodes.container,
48+
override=True,
49+
html=(visit_container_html, depart_container_html),
50+
)
3751

3852
for name, default, field in MdParserConfig().as_triple():
3953
if "sphinx" not in field.metadata.get("omit", []):

tests/test_renderers/fixtures/containers.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,31 @@ Admonition with options:
2929
<emphasis>
3030
hallo
3131
.
32+
33+
empty name:
34+
.
35+
:::
36+
This is **content**
37+
:::
38+
.
39+
<document source="<src>/index.md">
40+
<container is_div="True">
41+
<paragraph>
42+
This is
43+
<strong>
44+
content
45+
.
46+
47+
has name:
48+
.
49+
:::name
50+
This is **content**
51+
:::
52+
.
53+
<document source="<src>/index.md">
54+
<container classes="name" is_div="True">
55+
<paragraph>
56+
This is
57+
<strong>
58+
content
59+
.

tests/test_sphinx/sourcedirs/extended_syntaxes/index.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ This is a caption in **Markdown**
5555
This is a caption in **Markdown**
5656
:::
5757

58+
:::other
59+
Hallo *there*
60+
:::
61+
5862
linkify URL: www.example.com
5963

6064
- [ ] hallo

tests/test_sphinx/test_sphinx_builds/test_extended_syntaxes.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,11 @@ <h1>
131131
</p>
132132
</figcaption>
133133
</figure>
134+
<div class="other docutils">
135+
</div>
136+
<p>
137+
Hallo *there*
138+
</p>
134139
<p>
135140
linkify URL:
136141
<a class="reference external" href="http://www.example.com">

tests/test_sphinx/test_sphinx_builds/test_extended_syntaxes.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@
6969
<image alt="fishy" candidates="{'*': 'fun-fish.png'}" classes="bg-primary mb-1" uri="fun-fish.png" width="200px">
7070
<caption>
7171
This is a caption in **Markdown**
72+
<container classes="other" is_div="True">
73+
<paragraph>
74+
Hallo *there*
7275
<paragraph>
7376
linkify URL:
7477
<reference refuri="http://www.example.com">

0 commit comments

Comments
 (0)