From 2c459f9978691a87f1d638faec57c03c5abc8efb Mon Sep 17 00:00:00 2001
From: Chris Sewell
Date: Wed, 3 Jul 2024 15:56:47 +0200
Subject: [PATCH 01/12] [rst] Add `collabsible` option to admonition directives
---
.ruff.toml | 5 +-
sphinx/application.py | 1 +
sphinx/directives/__init__.py | 37 ++++++----
sphinx/directives/admonitions.py | 120 +++++++++++++++++++++++++++++++
sphinx/directives/other.py | 10 ---
sphinx/writers/html5.py | 20 +++++-
6 files changed, 166 insertions(+), 27 deletions(-)
create mode 100644 sphinx/directives/admonitions.py
diff --git a/.ruff.toml b/.ruff.toml
index 4451e8eb538..08ee43e0304 100644
--- a/.ruff.toml
+++ b/.ruff.toml
@@ -445,7 +445,10 @@ exclude = [
"sphinx/builders/*",
"sphinx/cmd/*",
"sphinx/config.py",
- "sphinx/directives/*",
+ "sphinx/directives/__init__.py ",
+ "sphinx/directives/code.py",
+ "sphinx/directives/other.py",
+ "sphinx/directives/patches.py",
"sphinx/domains/*",
"sphinx/environment/*",
"sphinx/ext/autodoc/__init__.py",
diff --git a/sphinx/application.py b/sphinx/application.py
index bf828fb8983..095f0b4f9f1 100644
--- a/sphinx/application.py
+++ b/sphinx/application.py
@@ -81,6 +81,7 @@
'sphinx.domains.rst',
'sphinx.domains.std',
'sphinx.directives',
+ 'sphinx.directives.admonitions',
'sphinx.directives.code',
'sphinx.directives.other',
'sphinx.directives.patches',
diff --git a/sphinx/directives/__init__.py b/sphinx/directives/__init__.py
index a9699f168ad..46a672c76bc 100644
--- a/sphinx/directives/__init__.py
+++ b/sphinx/directives/__init__.py
@@ -224,18 +224,21 @@ def run(self) -> list[Node]:
'no-index' in self.options
# xref RemovedInSphinx90Warning
# deprecate noindex in Sphinx 9.0
- or 'noindex' in self.options)
+ or 'noindex' in self.options
+ )
node['no-index-entry'] = node['noindexentry'] = (
'no-index-entry' in self.options
# xref RemovedInSphinx90Warning
# deprecate noindexentry in Sphinx 9.0
- or 'noindexentry' in self.options)
+ or 'noindexentry' in self.options
+ )
node['no-contents-entry'] = node['nocontentsentry'] = (
'no-contents-entry' in self.options
# xref RemovedInSphinx90Warning
# deprecate nocontentsentry in Sphinx 9.0
- or 'nocontentsentry' in self.options)
- node['no-typesetting'] = ('no-typesetting' in self.options)
+ or 'nocontentsentry' in self.options
+ )
+ node['no-typesetting'] = 'no-typesetting' in self.options
if self.domain:
node['classes'].append(self.domain)
node['classes'].append(node['objtype'])
@@ -282,8 +285,9 @@ def run(self) -> list[Node]:
content_node = addnodes.desc_content('', *content_children)
node.append(content_node)
self.transform_content(content_node)
- self.env.app.emit('object-description-transform',
- self.domain, self.objtype, content_node)
+ self.env.app.emit(
+ 'object-description-transform', self.domain, self.objtype, content_node
+ )
DocFieldTransformer(self).transform_all(content_node)
self.env.temp_data['object'] = None
self.after_content()
@@ -294,8 +298,11 @@ def run(self) -> list[Node]:
# If ``:no-index:`` is set, or there are no ids on the node
# or any of its children, then just return the index node,
# as Docutils expects a target node to have at least one id.
- if node_ids := [node_id for el in node.findall(nodes.Element) # type: ignore[var-annotated]
- for node_id in el.get('ids', ())]:
+ if node_ids := [
+ node_id
+ for el in node.findall(nodes.Element) # type: ignore[var-annotated]
+ for node_id in el.get('ids', ())
+ ]:
target_node = nodes.target(ids=node_ids)
self.set_source_info(target_node)
return [self.indexnode, target_node]
@@ -316,16 +323,20 @@ def run(self) -> list[Node]:
docutils.unregister_role('')
return []
role_name = self.arguments[0]
- role, messages = roles.role(role_name, self.state_machine.language,
- self.lineno, self.state.reporter)
+ role, messages = roles.role(
+ role_name, self.state_machine.language, self.lineno, self.state.reporter
+ )
if role:
docutils.register_role('', role) # type: ignore[arg-type]
self.env.temp_data['default_role'] = role_name
else:
literal_block = nodes.literal_block(self.block_text, self.block_text)
reporter = self.state.reporter
- error = reporter.error('Unknown interpreted text role "%s".' % role_name,
- literal_block, line=self.lineno)
+ error = reporter.error(
+ 'Unknown interpreted text role "%s".' % role_name,
+ literal_block,
+ line=self.lineno,
+ )
messages += [error]
return cast(list[nodes.Node], messages)
@@ -355,7 +366,7 @@ def run(self) -> list[Node]:
def setup(app: Sphinx) -> ExtensionMetadata:
- app.add_config_value("strip_signature_backslash", False, 'env')
+ app.add_config_value('strip_signature_backslash', False, 'env')
directives.register_directive('default-role', DefaultRole)
directives.register_directive('default-domain', DefaultDomain)
directives.register_directive('describe', ObjectDescription)
diff --git a/sphinx/directives/admonitions.py b/sphinx/directives/admonitions.py
new file mode 100644
index 00000000000..d44de33f724
--- /dev/null
+++ b/sphinx/directives/admonitions.py
@@ -0,0 +1,120 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, ClassVar
+
+from docutils import nodes
+from docutils.parsers.rst import directives
+from docutils.parsers.rst.roles import set_classes
+
+from sphinx import addnodes
+from sphinx.util.docutils import SphinxDirective
+
+if TYPE_CHECKING:
+ from docutils.nodes import Element, Node
+
+ from sphinx.application import Sphinx
+ from sphinx.util.typing import ExtensionMetadata, OptionSpec
+
+
+class BaseAdmonition(SphinxDirective):
+ final_argument_whitespace = True
+ option_spec: ClassVar[OptionSpec] = {
+ 'class': directives.class_option,
+ 'name': directives.unchanged,
+ 'collapsible': directives.flag,
+ 'open': directives.flag,
+ }
+ has_content = True
+
+ node_class: ClassVar[type[Element]] = nodes.admonition
+ """Subclasses must set this to the appropriate admonition node class."""
+
+ def run(self) -> list[Node]:
+ set_classes(self.options)
+ self.assert_has_content()
+ if 'collapsible' in self.options:
+ self.options['collapsible'] = True
+ if 'open' in self.options:
+ self.options['open'] = True
+ admonition_node = self.node_class('\n'.join(self.content), **self.options)
+ self.add_name(admonition_node)
+ if self.node_class is nodes.admonition:
+ title_text = self.arguments[0]
+ textnodes, messages = self.parse_inline(title_text, lineno=self.lineno)
+ title = nodes.title(title_text, '', *textnodes)
+ self.set_source_info(title)
+ admonition_node += title
+ admonition_node += messages
+ if 'classes' not in self.options:
+ admonition_node['classes'] += ['admonition-' + nodes.make_id(title_text)]
+ admonition_node.extend(self.parse_content_to_nodes())
+ return [admonition_node]
+
+
+class Admonition(BaseAdmonition):
+ required_arguments = 1
+ node_class = nodes.admonition
+
+
+class Attention(BaseAdmonition):
+ node_class = nodes.attention
+
+
+class Caution(BaseAdmonition):
+ node_class = nodes.caution
+
+
+class Danger(BaseAdmonition):
+ node_class = nodes.danger
+
+
+class Error(BaseAdmonition):
+ node_class = nodes.error
+
+
+class Hint(BaseAdmonition):
+ node_class = nodes.hint
+
+
+class Important(BaseAdmonition):
+ node_class = nodes.important
+
+
+class Note(BaseAdmonition):
+ node_class = nodes.note
+
+
+class Tip(BaseAdmonition):
+ node_class = nodes.tip
+
+
+class Warning(BaseAdmonition):
+ node_class = nodes.warning
+
+
+class SeeAlso(BaseAdmonition):
+ """
+ An admonition mentioning things to look at as reference.
+ """
+
+ node_class = addnodes.seealso
+
+
+def setup(app: Sphinx) -> ExtensionMetadata:
+ directives.register_directive('admonition', Admonition)
+ directives.register_directive('attention', Attention)
+ directives.register_directive('caution', Caution)
+ directives.register_directive('danger', Danger)
+ directives.register_directive('error', Error)
+ directives.register_directive('hint', Hint)
+ directives.register_directive('important', Important)
+ directives.register_directive('note', Note)
+ directives.register_directive('tip', Tip)
+ directives.register_directive('warning', Warning)
+ directives.register_directive('seealso', SeeAlso)
+
+ return {
+ 'version': 'builtin',
+ 'parallel_read_safe': True,
+ 'parallel_write_safe': True,
+ }
diff --git a/sphinx/directives/other.py b/sphinx/directives/other.py
index 7fa12f74ead..fa160c3b3e7 100644
--- a/sphinx/directives/other.py
+++ b/sphinx/directives/other.py
@@ -7,7 +7,6 @@
from docutils import nodes
from docutils.parsers.rst import directives
-from docutils.parsers.rst.directives.admonitions import BaseAdmonition
from docutils.parsers.rst.directives.misc import Class
from docutils.parsers.rst.directives.misc import Include as BaseInclude
from docutils.statemachine import StateMachine
@@ -206,14 +205,6 @@ def run(self) -> list[Node]:
return ret
-class SeeAlso(BaseAdmonition):
- """
- An admonition mentioning things to look at as reference.
- """
-
- node_class = addnodes.seealso
-
-
class TabularColumns(SphinxDirective):
"""
Directive to give an explicit tabulary column definition to LaTeX.
@@ -426,7 +417,6 @@ def setup(app: Sphinx) -> ExtensionMetadata:
directives.register_directive('sectionauthor', Author)
directives.register_directive('moduleauthor', Author)
directives.register_directive('codeauthor', Author)
- directives.register_directive('seealso', SeeAlso)
directives.register_directive('tabularcolumns', TabularColumns)
directives.register_directive('centered', Centered)
directives.register_directive('acks', Acks)
diff --git a/sphinx/writers/html5.py b/sphinx/writers/html5.py
index 1ebea36058a..2d24280e188 100644
--- a/sphinx/writers/html5.py
+++ b/sphinx/writers/html5.py
@@ -345,13 +345,24 @@ def visit_comment(self, node: Element) -> None:
# overwritten
def visit_admonition(self, node: Element, name: str = '') -> None:
- self.body.append(self.starttag(
- node, 'div', CLASS=('admonition ' + name)))
+ if node.get('collapsible'):
+ if node.get('open'):
+ self.body.append(
+ self.starttag(node, 'details', CLASS=('admonition ' + name), open='open')
+ )
+ else:
+ self.body.append(self.starttag(node, 'details', CLASS=('admonition ' + name)))
+ else:
+ self.body.append(self.starttag(
+ node, 'div', CLASS=('admonition ' + name)))
if name:
node.insert(0, nodes.title(name, admonitionlabels[name]))
def depart_admonition(self, node: Element | None = None) -> None:
- self.body.append('\n')
+ if node and node.get('collapsible'):
+ self.body.append('\n')
+ else:
+ self.body.append('\n')
def visit_seealso(self, node: Element) -> None:
self.visit_admonition(node, 'seealso')
@@ -471,6 +482,9 @@ def visit_title(self, node: Element) -> None:
self.body.append(self.starttag(node, 'p', '', CLASS='caption', ROLE='heading'))
self.body.append('')
self.context.append('
\n')
+ elif isinstance(node.parent, nodes.Admonition) and node.parent.get("collapsible"): # type: ignore[attr-defined]
+ self.body.append(self.starttag(node, 'summary', '', CLASS='admonition-title'))
+ self.context.append('\n')
else:
super().visit_title(node)
self.add_secnumber(node)
From 2c5d76658c29a84a24d2ea4c4be45457adad7eca Mon Sep 17 00:00:00 2001
From: Chris Sewell
Date: Wed, 3 Jul 2024 16:03:11 +0200
Subject: [PATCH 02/12] revert format
---
.ruff.toml | 2 +-
sphinx/directives/__init__.py | 37 ++++++++++++-----------------------
2 files changed, 14 insertions(+), 25 deletions(-)
diff --git a/.ruff.toml b/.ruff.toml
index 08ee43e0304..035fc4bd3a7 100644
--- a/.ruff.toml
+++ b/.ruff.toml
@@ -445,7 +445,7 @@ exclude = [
"sphinx/builders/*",
"sphinx/cmd/*",
"sphinx/config.py",
- "sphinx/directives/__init__.py ",
+ "sphinx/directives/__init__.py",
"sphinx/directives/code.py",
"sphinx/directives/other.py",
"sphinx/directives/patches.py",
diff --git a/sphinx/directives/__init__.py b/sphinx/directives/__init__.py
index 46a672c76bc..a9699f168ad 100644
--- a/sphinx/directives/__init__.py
+++ b/sphinx/directives/__init__.py
@@ -224,21 +224,18 @@ def run(self) -> list[Node]:
'no-index' in self.options
# xref RemovedInSphinx90Warning
# deprecate noindex in Sphinx 9.0
- or 'noindex' in self.options
- )
+ or 'noindex' in self.options)
node['no-index-entry'] = node['noindexentry'] = (
'no-index-entry' in self.options
# xref RemovedInSphinx90Warning
# deprecate noindexentry in Sphinx 9.0
- or 'noindexentry' in self.options
- )
+ or 'noindexentry' in self.options)
node['no-contents-entry'] = node['nocontentsentry'] = (
'no-contents-entry' in self.options
# xref RemovedInSphinx90Warning
# deprecate nocontentsentry in Sphinx 9.0
- or 'nocontentsentry' in self.options
- )
- node['no-typesetting'] = 'no-typesetting' in self.options
+ or 'nocontentsentry' in self.options)
+ node['no-typesetting'] = ('no-typesetting' in self.options)
if self.domain:
node['classes'].append(self.domain)
node['classes'].append(node['objtype'])
@@ -285,9 +282,8 @@ def run(self) -> list[Node]:
content_node = addnodes.desc_content('', *content_children)
node.append(content_node)
self.transform_content(content_node)
- self.env.app.emit(
- 'object-description-transform', self.domain, self.objtype, content_node
- )
+ self.env.app.emit('object-description-transform',
+ self.domain, self.objtype, content_node)
DocFieldTransformer(self).transform_all(content_node)
self.env.temp_data['object'] = None
self.after_content()
@@ -298,11 +294,8 @@ def run(self) -> list[Node]:
# If ``:no-index:`` is set, or there are no ids on the node
# or any of its children, then just return the index node,
# as Docutils expects a target node to have at least one id.
- if node_ids := [
- node_id
- for el in node.findall(nodes.Element) # type: ignore[var-annotated]
- for node_id in el.get('ids', ())
- ]:
+ if node_ids := [node_id for el in node.findall(nodes.Element) # type: ignore[var-annotated]
+ for node_id in el.get('ids', ())]:
target_node = nodes.target(ids=node_ids)
self.set_source_info(target_node)
return [self.indexnode, target_node]
@@ -323,20 +316,16 @@ def run(self) -> list[Node]:
docutils.unregister_role('')
return []
role_name = self.arguments[0]
- role, messages = roles.role(
- role_name, self.state_machine.language, self.lineno, self.state.reporter
- )
+ role, messages = roles.role(role_name, self.state_machine.language,
+ self.lineno, self.state.reporter)
if role:
docutils.register_role('', role) # type: ignore[arg-type]
self.env.temp_data['default_role'] = role_name
else:
literal_block = nodes.literal_block(self.block_text, self.block_text)
reporter = self.state.reporter
- error = reporter.error(
- 'Unknown interpreted text role "%s".' % role_name,
- literal_block,
- line=self.lineno,
- )
+ error = reporter.error('Unknown interpreted text role "%s".' % role_name,
+ literal_block, line=self.lineno)
messages += [error]
return cast(list[nodes.Node], messages)
@@ -366,7 +355,7 @@ def run(self) -> list[Node]:
def setup(app: Sphinx) -> ExtensionMetadata:
- app.add_config_value('strip_signature_backslash', False, 'env')
+ app.add_config_value("strip_signature_backslash", False, 'env')
directives.register_directive('default-role', DefaultRole)
directives.register_directive('default-domain', DefaultDomain)
directives.register_directive('describe', ObjectDescription)
From ff8e7d572afd1c60af5edbf4ffe98b16143548bd Mon Sep 17 00:00:00 2001
From: Chris Sewell
Date: Wed, 3 Jul 2024 17:25:38 +0200
Subject: [PATCH 03/12] Add CSS for internal docs theme, and example in
documentation
---
doc/_themes/sphinx13/static/sphinx13.css | 105 +++++++++++++++++-----
doc/usage/restructuredtext/directives.rst | 26 ++++++
2 files changed, 108 insertions(+), 23 deletions(-)
diff --git a/doc/_themes/sphinx13/static/sphinx13.css b/doc/_themes/sphinx13/static/sphinx13.css
index ecc952d0e8e..57afd6d15df 100644
--- a/doc/_themes/sphinx13/static/sphinx13.css
+++ b/doc/_themes/sphinx13/static/sphinx13.css
@@ -31,6 +31,9 @@
--icon-warning: url('data:image/svg+xml;charset=utf-8,');
--icon-failure: url('data:image/svg+xml;charset=utf-8,');
--icon-spark: url('data:image/svg+xml;charset=utf-8,');
+
+ /* icons used for details summaries */
+ --icon-details-open: url('data:image/svg+xml;charset=utf-8,');
}
body {
@@ -394,7 +397,7 @@ table td, table th {
padding: 0.2em 0.5em 0.2em 0.5em;
}
-div.admonition, div.warning {
+div.admonition, div.warning, details.admonition {
font-size: 0.9em;
margin: 1em 0 1em 0;
border: 1px solid #86989B;
@@ -403,16 +406,16 @@ div.admonition, div.warning {
padding: 1rem;
}
-div.admonition > p, div.warning > p {
+div.admonition > p, div.warning > p, details.admonition > p {
margin: 0;
padding: 0;
}
-div.admonition > pre, div.warning > pre {
+div.admonition > pre, div.warning > pre, details.admonition > pre {
margin: 0.4em 1em 0.4em 1em;
}
-div.admonition > p.admonition-title {
+div.admonition > p.admonition-title, details.admonition > summary.admonition-title {
position: relative;
font-weight: 500;
background-color: var(--color-admonition-bg);
@@ -421,33 +424,77 @@ div.admonition > p.admonition-title {
border-radius: var(--admonition-radius) var(--admonition-radius) 0 0;
}
+details.admonition:not([open]) {
+ padding-bottom: 0;
+}
+details.admonition > summary.admonition-title {
+ list-style: none;
+ cursor: pointer;
+}
+details.admonition > summary.admonition-title::after {
+ background-color: currentcolor;
+ content: "";
+ height: 1.2rem;
+ width: 1.2rem;
+ -webkit-mask-image: var(--icon-details-open);
+ mask-image: var(--icon-details-open);
+ -webkit-mask-position: center;
+ mask-position: center;
+ -webkit-mask-repeat: no-repeat;
+ mask-repeat: no-repeat;
+ -webkit-mask-size: contain;
+ mask-size: contain;
+ transform: rotate(0deg);
+ transition: transform .25s;
+ float: right;
+}
+details.admonition[open] > summary.admonition-title::after {
+ transform: rotate(90deg);
+}
+details.admonition:not([open]) > summary.admonition-title {
+ margin-bottom: 0;
+ border-radius: var(--admonition-radius);
+}
+
div.attention > p.admonition-title,
div.danger > p.admonition-title,
-div.error > p.admonition-title {
+div.error > p.admonition-title,
+details.attention > summary.admonition-title,
+details.danger > summary.admonition-title,
+details.error > summary.admonition-title {
background-color: var(--colour-error-bg);
}
div.important > p.admonition-title,
div.caution > p.admonition-title,
-div.warning > p.admonition-title {
+div.warning > p.admonition-title,
+details.important > summary.admonition-title,
+details.caution > summary.admonition-title,
+details.warning > summary.admonition-title {
background-color: var(--colour-warning-bg);
}
-div.note > p.admonition-title {
+div.note > p.admonition-title,
+details.note > summary.admonition-title {
background-color: var(--colour-note-bg);
}
div.hint > p.admonition-title,
div.tip > p.admonition-title,
-div.seealso > p.admonition-title {
+div.seealso > p.admonition-title,
+details.hint > summary.admonition-title,
+details.tip > summary.admonition-title,
+details.seealso > summary.admonition-title {
background-color: var(--colour-success-bg);
}
-div.admonition-todo > p.admonition-title {
+div.admonition-todo > p.admonition-title,
+details.admonition-todo > summary.admonition-title {
background-color: var(--colour-todo-bg);
}
-p.admonition-title::before {
+p.admonition-title::before,
+summary.admonition-title::before {
content: "";
height: 1rem;
left: .5rem;
@@ -456,68 +503,80 @@ p.admonition-title::before {
background-color: #5f5f5f;
}
-div.admonition > p.admonition-title::before {
+div.admonition > p.admonition-title::before,
+details.admonition > summary.admonition-title::before {
background-color: var(--color-admonition-fg);
-webkit-mask-image: var(--icon-abstract);
mask-image: var(--icon-abstract);
}
-div.attention > p.admonition-title::before {
+div.attention > p.admonition-title::before,
+details.attention > summary.admonition-title::before {
background-color: var(--colour-error-fg);
-webkit-mask-image: var(--icon-warning);
mask-image: var(--icon-warning);
}
-div.caution > p.admonition-title::before {
+div.caution > p.admonition-title::before,
+details.caution > summary.admonition-title::before {
background-color: var(--colour-warning-fg);
-webkit-mask-image: var(--icon-spark);
mask-image: var(--icon-spark);
}
-div.danger > p.admonition-title::before {
+div.danger > p.admonition-title::before,
+details.danger > summary.admonition-title::before {
background-color: var(--colour-error-fg);
-webkit-mask-image: var(--icon-spark);
mask-image: var(--icon-spark);
}
-div.error > p.admonition-title::before {
+div.error > p.admonition-title::before,
+details.error > summary.admonition-title::before {
background-color: var(--colour-error-fg);
-webkit-mask-image: var(--icon-failure);
mask-image: var(--icon-failure);
}
-div.hint > p.admonition-title::before {
+div.hint > p.admonition-title::before,
+details.hint > summary.admonition-title::before {
background-color: var(--colour-success-fg);
-webkit-mask-image: var(--icon-question);
mask-image: var(--icon-question);
}
-div.important > p.admonition-title::before {
+div.important > p.admonition-title::before,
+details.important > summary.admonition-title::before {
background-color: var(--colour-warning-fg);
-webkit-mask-image: var(--icon-flame);
mask-image: var(--icon-flame);
}
-div.note > p.admonition-title::before {
+div.note > p.admonition-title::before,
+details.note > summary.admonition-title::before {
background-color: var(--colour-note-fg);
-webkit-mask-image: var(--icon-pencil);
mask-image: var(--icon-pencil);
}
-div.seealso > p.admonition-title::before {
+div.seealso > p.admonition-title::before,
+details.seealso > summary.admonition-title::before {
background-color: var(--colour-success-fg);
-webkit-mask-image: var(--icon-info);
mask-image: var(--icon-info);
}
-div.tip > p.admonition-title::before {
+div.tip > p.admonition-title::before,
+details.tip > summary.admonition-title::before {
background-color: var(--colour-success-fg);
-webkit-mask-image: var(--icon-info);
mask-image: var(--icon-info);
}
-div.admonition-todo > p.admonition-title::before {
+div.admonition-todo > p.admonition-title::before,
+details.admonition-todo > summary.admonition-title::before {
background-color: var(--colour-todo-fg);
-webkit-mask-image: var(--icon-pencil);
mask-image: var(--icon-pencil);
}
-div.warning > p.admonition-title::before {
+div.warning > p.admonition-title::before,
+details.warning > summary.admonition-title::before {
background-color: var(--colour-warning-fg);
-webkit-mask-image: var(--icon-warning);
mask-image: var(--icon-warning);
}
-div.warning {
+div.warning, details.warning {
border: 1px solid #940000;
}
diff --git a/doc/usage/restructuredtext/directives.rst b/doc/usage/restructuredtext/directives.rst
index ff425246fa0..19114c94b9a 100644
--- a/doc/usage/restructuredtext/directives.rst
+++ b/doc/usage/restructuredtext/directives.rst
@@ -284,6 +284,32 @@ units as well as normal text.
This function is not suitable for sending spam e-mails.
+ Add a ``:collapsible:`` option to make the note collapsible.
+ This is useful for long notes that are not always relevant.
+ Example::
+
+ .. note::
+ :collapsible:
+
+ This note is collapsed.
+
+ .. note::
+ :collapsible:
+ :open:
+
+ This note is collapsible, but initially open.
+
+ .. note::
+ :collapsible:
+
+ This note is collapsed.
+
+ .. note::
+ :collapsible:
+ :open:
+
+ This note is collapsible, but initially open.
+
.. rst:directive:: .. warning::
An important bit of information about an API that a user should be very aware
From 42533e529514a62cb9c43f911664a655e0cc3529 Mon Sep 17 00:00:00 2001
From: Chris Sewell
Date: Wed, 3 Jul 2024 17:29:55 +0200
Subject: [PATCH 04/12] Update directives.rst
---
doc/usage/restructuredtext/directives.rst | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/doc/usage/restructuredtext/directives.rst b/doc/usage/restructuredtext/directives.rst
index 19114c94b9a..d9febf902f7 100644
--- a/doc/usage/restructuredtext/directives.rst
+++ b/doc/usage/restructuredtext/directives.rst
@@ -284,6 +284,10 @@ units as well as normal text.
This function is not suitable for sending spam e-mails.
+ .. note::
+
+ This function is not suitable for sending spam e-mails.
+
Add a ``:collapsible:`` option to make the note collapsible.
This is useful for long notes that are not always relevant.
Example::
From 1b96eff0006107d4d1b4ce1b272016ab0f1b44f5 Mon Sep 17 00:00:00 2001
From: Chris Sewell
Date: Wed, 3 Jul 2024 17:35:52 +0200
Subject: [PATCH 05/12] Update sphinx13.css
---
doc/_themes/sphinx13/static/sphinx13.css | 1 +
1 file changed, 1 insertion(+)
diff --git a/doc/_themes/sphinx13/static/sphinx13.css b/doc/_themes/sphinx13/static/sphinx13.css
index 57afd6d15df..65e289145d4 100644
--- a/doc/_themes/sphinx13/static/sphinx13.css
+++ b/doc/_themes/sphinx13/static/sphinx13.css
@@ -430,6 +430,7 @@ details.admonition:not([open]) {
details.admonition > summary.admonition-title {
list-style: none;
cursor: pointer;
+ padding-right: .5rem;
}
details.admonition > summary.admonition-title::after {
background-color: currentcolor;
From 0c44f1d24002dac7d62bf49ebb55bdeb62d2a430 Mon Sep 17 00:00:00 2001
From: Adam Turner <9087854+aa-turner@users.noreply.github.com>
Date: Sun, 26 Jan 2025 07:24:31 +0000
Subject: [PATCH 06/12] fmt
---
sphinx/directives/admonitions.py | 12 +++++++-----
sphinx/writers/html5.py | 19 +++++++++++++------
2 files changed, 20 insertions(+), 11 deletions(-)
diff --git a/sphinx/directives/admonitions.py b/sphinx/directives/admonitions.py
index d44de33f724..ffa079ed0fe 100644
--- a/sphinx/directives/admonitions.py
+++ b/sphinx/directives/admonitions.py
@@ -1,6 +1,6 @@
from __future__ import annotations
-from typing import TYPE_CHECKING, ClassVar
+from typing import TYPE_CHECKING
from docutils import nodes
from docutils.parsers.rst import directives
@@ -10,6 +10,8 @@
from sphinx.util.docutils import SphinxDirective
if TYPE_CHECKING:
+ from typing import ClassVar
+
from docutils.nodes import Element, Node
from sphinx.application import Sphinx
@@ -46,7 +48,9 @@ def run(self) -> list[Node]:
admonition_node += title
admonition_node += messages
if 'classes' not in self.options:
- admonition_node['classes'] += ['admonition-' + nodes.make_id(title_text)]
+ admonition_node['classes'] += [
+ 'admonition-' + nodes.make_id(title_text)
+ ]
admonition_node.extend(self.parse_content_to_nodes())
return [admonition_node]
@@ -93,9 +97,7 @@ class Warning(BaseAdmonition):
class SeeAlso(BaseAdmonition):
- """
- An admonition mentioning things to look at as reference.
- """
+ """An admonition mentioning things to look at as reference."""
node_class = addnodes.seealso
diff --git a/sphinx/writers/html5.py b/sphinx/writers/html5.py
index fe2fac43536..8f68ef83151 100644
--- a/sphinx/writers/html5.py
+++ b/sphinx/writers/html5.py
@@ -370,13 +370,16 @@ def visit_admonition(self, node: Element, name: str = '') -> None:
if node.get('collapsible'):
if node.get('open'):
self.body.append(
- self.starttag(node, 'details', CLASS=('admonition ' + name), open='open')
+ self.starttag(
+ node, 'details', CLASS=('admonition ' + name), open='open'
+ )
)
else:
- self.body.append(self.starttag(node, 'details', CLASS=('admonition ' + name)))
+ self.body.append(
+ self.starttag(node, 'details', CLASS=('admonition ' + name))
+ )
else:
- self.body.append(self.starttag(
- node, 'div', CLASS=('admonition ' + name)))
+ self.body.append(self.starttag(node, 'div', CLASS=('admonition ' + name)))
if name:
node.insert(0, nodes.title(name, admonitionlabels[name]))
@@ -512,8 +515,12 @@ def visit_title(self, node: Element) -> None:
)
self.body.append('')
self.context.append('\n')
- elif isinstance(node.parent, nodes.Admonition) and node.parent.get("collapsible"): # type: ignore[attr-defined]
- self.body.append(self.starttag(node, 'summary', '', CLASS='admonition-title'))
+ elif isinstance(node.parent, nodes.Admonition) and node.parent.get(
+ 'collapsible'
+ ): # type: ignore[attr-defined]
+ self.body.append(
+ self.starttag(node, 'summary', '', CLASS='admonition-title')
+ )
self.context.append('\n')
else:
super().visit_title(node)
From d5d3109e06676f220b4e097481a6489dc646656b Mon Sep 17 00:00:00 2001
From: Adam Turner <9087854+aa-turner@users.noreply.github.com>
Date: Sun, 26 Jan 2025 08:08:48 +0000
Subject: [PATCH 07/12] Updates & tweaks
---
doc/usage/restructuredtext/directives.rst | 78 +++++++++++--------
sphinx/directives/admonitions.py | 93 ++++++++++-------------
sphinx/ext/todo.py | 31 +++-----
sphinx/writers/html5.py | 36 ++++-----
4 files changed, 113 insertions(+), 125 deletions(-)
diff --git a/doc/usage/restructuredtext/directives.rst b/doc/usage/restructuredtext/directives.rst
index 87e760c6fcc..413a9591618 100644
--- a/doc/usage/restructuredtext/directives.rst
+++ b/doc/usage/restructuredtext/directives.rst
@@ -423,36 +423,6 @@ and the generic :rst:dir:`admonition` directive.
Remember your sun cream!
- .. note::
-
- This function is not suitable for sending spam e-mails.
-
- Add a ``:collapsible:`` option to make the note collapsible.
- This is useful for long notes that are not always relevant.
- Example::
-
- .. note::
- :collapsible:
-
- This note is collapsed.
-
- .. note::
- :collapsible:
- :open:
-
- This note is collapsible, but initially open.
-
- .. note::
- :collapsible:
-
- This note is collapsed.
-
- .. note::
- :collapsible:
- :open:
-
- This note is collapsible, but initially open.
-
.. rst:directive:: .. warning::
An important bit of information that the reader should be very aware of.
@@ -510,6 +480,54 @@ and the generic :rst:dir:`admonition` directive.
Documentation for tar archive files, including GNU tar extensions.
+.. _collapsible-admonitions:
+
+.. rubric:: Collapsible text
+
+Each admonition directive supports a ``:collapsible:`` option,
+to make the content of the admonition collapsible
+(where supported by the output format).
+This can be useful for content that is not always relevant.
+By default, collapsible admonitions are initially open,
+but this can be controlled with the ``open`` and ``closed`` arguments
+to the ``:collapsible:`` option, which change the default state.
+In output formats that don't support collapsible content,
+the text is always included.
+For example:
+
+.. code-block:: rst
+
+ .. note::
+ :collapsible:
+
+ This note is collapsible, and initially open by default.
+
+ .. admonition:: Example
+ :collapsible: open
+
+ This example is collapsible, and initially open.
+
+ .. hint::
+ :collapsible: closed
+
+ This hint is collapsible, but initially closed.
+
+.. note::
+ :collapsible:
+
+ This note is collapsible, and initially open by default.
+
+.. admonition:: Example
+ :collapsible: open
+
+ This example is collapsible, and initially open.
+
+.. hint::
+ :collapsible: closed
+
+ This hint is collapsible, but initially closed.
+
+
Describing changes between versions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/sphinx/directives/admonitions.py b/sphinx/directives/admonitions.py
index ffa079ed0fe..ddd7a9eca73 100644
--- a/sphinx/directives/admonitions.py
+++ b/sphinx/directives/admonitions.py
@@ -3,8 +3,7 @@
from typing import TYPE_CHECKING
from docutils import nodes
-from docutils.parsers.rst import directives
-from docutils.parsers.rst.roles import set_classes
+from docutils.parsers.rst.directives.admonitions import BaseAdmonition
from sphinx import addnodes
from sphinx.util.docutils import SphinxDirective
@@ -12,108 +11,94 @@
if TYPE_CHECKING:
from typing import ClassVar
- from docutils.nodes import Element, Node
+ from docutils.nodes import Node
from sphinx.application import Sphinx
from sphinx.util.typing import ExtensionMetadata, OptionSpec
-class BaseAdmonition(SphinxDirective):
- final_argument_whitespace = True
- option_spec: ClassVar[OptionSpec] = {
- 'class': directives.class_option,
- 'name': directives.unchanged,
- 'collapsible': directives.flag,
- 'open': directives.flag,
+def _collapsible_arg(argument: str | None) -> str:
+ if argument is None:
+ return 'open'
+ if (value := argument.lower().strip()) in {'open', 'closed'}:
+ return value
+ msg = f'"{argument}" unknown; choose from "open" or "closed".'
+ raise ValueError(msg)
+
+
+class SphinxAdmonition(BaseAdmonition, SphinxDirective):
+ option_spec: ClassVar[OptionSpec] = BaseAdmonition.option_spec.copy() # type: ignore[union-attr]
+ option_spec |= {
+ 'collapsible': _collapsible_arg,
}
- has_content = True
- node_class: ClassVar[type[Element]] = nodes.admonition
+ node_class: type[nodes.Admonition] = nodes.admonition
"""Subclasses must set this to the appropriate admonition node class."""
def run(self) -> list[Node]:
- set_classes(self.options)
- self.assert_has_content()
- if 'collapsible' in self.options:
- self.options['collapsible'] = True
- if 'open' in self.options:
- self.options['open'] = True
- admonition_node = self.node_class('\n'.join(self.content), **self.options)
- self.add_name(admonition_node)
- if self.node_class is nodes.admonition:
- title_text = self.arguments[0]
- textnodes, messages = self.parse_inline(title_text, lineno=self.lineno)
- title = nodes.title(title_text, '', *textnodes)
- self.set_source_info(title)
- admonition_node += title
- admonition_node += messages
- if 'classes' not in self.options:
- admonition_node['classes'] += [
- 'admonition-' + nodes.make_id(title_text)
- ]
- admonition_node.extend(self.parse_content_to_nodes())
+ (admonition_node,) = super().run()
return [admonition_node]
-class Admonition(BaseAdmonition):
+class Admonition(SphinxAdmonition):
required_arguments = 1
node_class = nodes.admonition
-class Attention(BaseAdmonition):
+class Attention(SphinxAdmonition):
node_class = nodes.attention
-class Caution(BaseAdmonition):
+class Caution(SphinxAdmonition):
node_class = nodes.caution
-class Danger(BaseAdmonition):
+class Danger(SphinxAdmonition):
node_class = nodes.danger
-class Error(BaseAdmonition):
+class Error(SphinxAdmonition):
node_class = nodes.error
-class Hint(BaseAdmonition):
+class Hint(SphinxAdmonition):
node_class = nodes.hint
-class Important(BaseAdmonition):
+class Important(SphinxAdmonition):
node_class = nodes.important
-class Note(BaseAdmonition):
+class Note(SphinxAdmonition):
node_class = nodes.note
-class Tip(BaseAdmonition):
+class Tip(SphinxAdmonition):
node_class = nodes.tip
-class Warning(BaseAdmonition):
+class Warning(SphinxAdmonition):
node_class = nodes.warning
-class SeeAlso(BaseAdmonition):
+class SeeAlso(SphinxAdmonition):
"""An admonition mentioning things to look at as reference."""
node_class = addnodes.seealso
def setup(app: Sphinx) -> ExtensionMetadata:
- directives.register_directive('admonition', Admonition)
- directives.register_directive('attention', Attention)
- directives.register_directive('caution', Caution)
- directives.register_directive('danger', Danger)
- directives.register_directive('error', Error)
- directives.register_directive('hint', Hint)
- directives.register_directive('important', Important)
- directives.register_directive('note', Note)
- directives.register_directive('tip', Tip)
- directives.register_directive('warning', Warning)
- directives.register_directive('seealso', SeeAlso)
+ app.add_directive('admonition', Admonition, override=True)
+ app.add_directive('attention', Attention, override=True)
+ app.add_directive('caution', Caution, override=True)
+ app.add_directive('danger', Danger, override=True)
+ app.add_directive('error', Error, override=True)
+ app.add_directive('hint', Hint, override=True)
+ app.add_directive('important', Important, override=True)
+ app.add_directive('note', Note, override=True)
+ app.add_directive('tip', Tip, override=True)
+ app.add_directive('warning', Warning, override=True)
+ app.add_directive('seealso', SeeAlso, override=True)
return {
'version': 'builtin',
diff --git a/sphinx/ext/todo.py b/sphinx/ext/todo.py
index 0e192dbdadf..ab23921a6c9 100644
--- a/sphinx/ext/todo.py
+++ b/sphinx/ext/todo.py
@@ -12,11 +12,10 @@
from typing import TYPE_CHECKING, cast
from docutils import nodes
-from docutils.parsers.rst import directives
-from docutils.parsers.rst.directives.admonitions import BaseAdmonition
import sphinx
from sphinx import addnodes
+from sphinx.directives.admonitions import SphinxAdmonition
from sphinx.domains import Domain
from sphinx.errors import NoUri
from sphinx.locale import _, __
@@ -46,35 +45,25 @@ class todolist(nodes.General, nodes.Element):
pass
-class Todo(BaseAdmonition, SphinxDirective):
+class Todo(SphinxAdmonition):
"""A todo entry, displayed (if configured) in the form of an admonition."""
node_class = todo_node
- has_content = True
- required_arguments = 0
- optional_arguments = 0
- final_argument_whitespace = False
- option_spec: ClassVar[OptionSpec] = {
- 'class': directives.class_option,
- 'name': directives.unchanged,
- }
def run(self) -> list[Node]:
if not self.options.get('class'):
self.options['class'] = ['admonition-todo']
(todo,) = super().run()
- if isinstance(todo, nodes.system_message):
+ if not isinstance(todo, todo_node):
return [todo]
- elif isinstance(todo, todo_node):
- todo.insert(0, nodes.title(text=_('Todo')))
- todo['docname'] = self.env.docname
- self.add_name(todo)
- self.set_source_info(todo)
- self.state.document.note_explicit_target(todo)
- return [todo]
- else:
- raise TypeError # never reached here
+
+ todo.insert(0, nodes.title(text=_('Todo')))
+ todo['docname'] = self.env.docname
+ self.add_name(todo)
+ self.set_source_info(todo)
+ self.state.document.note_explicit_target(todo)
+ return [todo]
class TodoDomain(Domain):
diff --git a/sphinx/writers/html5.py b/sphinx/writers/html5.py
index 8f68ef83151..c497d6120af 100644
--- a/sphinx/writers/html5.py
+++ b/sphinx/writers/html5.py
@@ -367,27 +367,21 @@ def visit_comment(self, node: Element) -> None:
# overwritten
def visit_admonition(self, node: Element, name: str = '') -> None:
- if node.get('collapsible'):
- if node.get('open'):
- self.body.append(
- self.starttag(
- node, 'details', CLASS=('admonition ' + name), open='open'
- )
- )
- else:
- self.body.append(
- self.starttag(node, 'details', CLASS=('admonition ' + name))
- )
- else:
- self.body.append(self.starttag(node, 'div', CLASS=('admonition ' + name)))
+ attributes = {}
+ tag_name = 'div'
+ if collapsible := node.get('collapsible'):
+ tag_name = 'details'
+ if collapsible == 'open':
+ attributes['open'] = 'open'
+ self.body.append(
+ self.starttag(node, tag_name, CLASS=f'admonition {name}', **attributes)
+ )
+ self.context.append(f'{tag_name}>\n')
if name:
node.insert(0, nodes.title(name, admonitionlabels[name]))
def depart_admonition(self, node: Element | None = None) -> None:
- if node and node.get('collapsible'):
- self.body.append('\n')
- else:
- self.body.append('\n')
+ self.body.append(self.context.pop())
def visit_seealso(self, node: Element) -> None:
self.visit_admonition(node, 'seealso')
@@ -515,9 +509,11 @@ def visit_title(self, node: Element) -> None:
)
self.body.append('')
self.context.append('\n')
- elif isinstance(node.parent, nodes.Admonition) and node.parent.get(
- 'collapsible'
- ): # type: ignore[attr-defined]
+ elif (
+ isinstance(node.parent, nodes.Admonition)
+ and isinstance(node.parent, nodes.Element)
+ and 'collapsible' in node.parent
+ ):
self.body.append(
self.starttag(node, 'summary', '', CLASS='admonition-title')
)
From 48a729780ecaf81cc9f6e965c70d8adb42be20c1 Mon Sep 17 00:00:00 2001
From: Chris Sewell
Date: Wed, 29 Jan 2025 11:35:03 +0100
Subject: [PATCH 08/12] add changes entry
---
CHANGES.rst | 2 ++
doc/usage/restructuredtext/directives.rst | 2 ++
2 files changed, 4 insertions(+)
diff --git a/CHANGES.rst b/CHANGES.rst
index da1f123ed66..19d308c5b4c 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -70,6 +70,8 @@ Features added
which defaults to ``True`` for backwards compatibility.
The default will change to ``False`` in Sphinx 10.
Patch by Adam Turner.
+* #12507: Add the :ref:`collapsible ` option to admonition directives.
+ Patch by Chris Sewell.
Bugs fixed
----------
diff --git a/doc/usage/restructuredtext/directives.rst b/doc/usage/restructuredtext/directives.rst
index 413a9591618..0472b0ca476 100644
--- a/doc/usage/restructuredtext/directives.rst
+++ b/doc/usage/restructuredtext/directives.rst
@@ -484,6 +484,8 @@ and the generic :rst:dir:`admonition` directive.
.. rubric:: Collapsible text
+.. versionadded:: 8.2.0
+
Each admonition directive supports a ``:collapsible:`` option,
to make the content of the admonition collapsible
(where supported by the output format).
From 093779d6f309d3c8d50f04d3434e4916e65ef244 Mon Sep 17 00:00:00 2001
From: Chris Sewell
Date: Wed, 29 Jan 2025 12:11:18 +0100
Subject: [PATCH 09/12] Add html test
---
.../conf.py | 0
.../index.rst | 17 +++++++++
tests/test_builders/test_build_html.py | 38 +++++++++++++++++++
3 files changed, 55 insertions(+)
create mode 100644 tests/roots/test-directives-admonition-collapse/conf.py
create mode 100644 tests/roots/test-directives-admonition-collapse/index.rst
diff --git a/tests/roots/test-directives-admonition-collapse/conf.py b/tests/roots/test-directives-admonition-collapse/conf.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/tests/roots/test-directives-admonition-collapse/index.rst b/tests/roots/test-directives-admonition-collapse/index.rst
new file mode 100644
index 00000000000..62387b361c2
--- /dev/null
+++ b/tests/roots/test-directives-admonition-collapse/index.rst
@@ -0,0 +1,17 @@
+test-directives-admonition-collapse
+===================================
+
+.. note::
+ :collapsible:
+
+ This note is collapsible, and initially open by default.
+
+.. admonition:: Example
+ :collapsible: open
+
+ This example is collapsible, and initially open.
+
+.. hint::
+ :collapsible: closed
+
+ This hint is collapsible, but initially closed.
diff --git a/tests/test_builders/test_build_html.py b/tests/test_builders/test_build_html.py
index 035ee90baa1..1377e1cef7a 100644
--- a/tests/test_builders/test_build_html.py
+++ b/tests/test_builders/test_build_html.py
@@ -715,3 +715,41 @@ def __call__(self, nodes):
r'.//dt[@id="MyList"][1]',
chk('class MyList[\nT\n](list[T])'),
)
+
+
+@pytest.mark.sphinx('html', testroot='directives-admonition-collapse')
+def test_html_admonition_collapse(app):
+ app.build()
+ fname = app.outdir / 'index.html'
+ etree = etree_parse(fname)
+
+ def _create_check(text: str, open: bool): # type: ignore[no-untyped-def]
+ def _check(els):
+ assert len(els) == 1
+ el = els[0]
+ if open:
+ assert el.attrib['open'] == 'open'
+ else:
+ assert 'open' not in el.attrib
+ assert el.find('p').text == text
+
+ return _check
+
+ check_xpath(
+ etree,
+ fname,
+ r'.//details[@class="admonition note"]',
+ _create_check('This note is collapsible, and initially open by default.', True),
+ )
+ check_xpath(
+ etree,
+ fname,
+ r'.//details[@class="admonition-example admonition"]',
+ _create_check('This example is collapsible, and initially open.', True),
+ )
+ check_xpath(
+ etree,
+ fname,
+ r'.//details[@class="admonition hint"]',
+ _create_check('This hint is collapsible, but initially closed.', False),
+ )
From 6f124c9a620de04ff0be0374222a64b0a49b7c25 Mon Sep 17 00:00:00 2001
From: Chris Sewell
Date: Wed, 29 Jan 2025 12:17:00 +0100
Subject: [PATCH 10/12] Update CHANGES.rst
---
CHANGES.rst | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/CHANGES.rst b/CHANGES.rst
index 3780db8571c..1261d44b6fe 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -88,7 +88,8 @@ Features added
* #13271: Support the ``:abstract:`` option for
classes, methods, and properties in the Python domain.
Patch by Adam Turner.
-* #12507: Add the :ref:`collapsible ` option to admonition directives.
+* #12507: Add the :ref:`collapsible ` option
+ to admonition directives.
Patch by Chris Sewell.
Bugs fixed
From 9c126fa020ffed3ec7c3e5276f78020ab074c4de Mon Sep 17 00:00:00 2001
From: Chris Sewell
Date: Wed, 29 Jan 2025 12:25:29 +0100
Subject: [PATCH 11/12] update test
---
tests/roots/test-directives-admonition-collapse/index.rst | 5 +++++
tests/test_builders/test_build_html.py | 6 ++++++
2 files changed, 11 insertions(+)
diff --git a/tests/roots/test-directives-admonition-collapse/index.rst b/tests/roots/test-directives-admonition-collapse/index.rst
index 62387b361c2..11023d2f62e 100644
--- a/tests/roots/test-directives-admonition-collapse/index.rst
+++ b/tests/roots/test-directives-admonition-collapse/index.rst
@@ -1,6 +1,11 @@
test-directives-admonition-collapse
===================================
+.. note::
+ :class: standard
+
+ This is a standard note.
+
.. note::
:collapsible:
diff --git a/tests/test_builders/test_build_html.py b/tests/test_builders/test_build_html.py
index 1377e1cef7a..c3f8ca3279e 100644
--- a/tests/test_builders/test_build_html.py
+++ b/tests/test_builders/test_build_html.py
@@ -735,6 +735,12 @@ def _check(els):
return _check
+ check_xpath(
+ etree,
+ fname,
+ r'.//div[@class="standard admonition note"]//p',
+ 'This is a standard note.',
+ )
check_xpath(
etree,
fname,
From cab019ac6b9ababffd87df754d3be56f390f5b58 Mon Sep 17 00:00:00 2001
From: Adam Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Wed, 29 Jan 2025 14:39:04 +0000
Subject: [PATCH 12/12] Update doc/usage/restructuredtext/directives.rst
---
doc/usage/restructuredtext/directives.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/doc/usage/restructuredtext/directives.rst b/doc/usage/restructuredtext/directives.rst
index 0472b0ca476..ee085788e1d 100644
--- a/doc/usage/restructuredtext/directives.rst
+++ b/doc/usage/restructuredtext/directives.rst
@@ -484,7 +484,7 @@ and the generic :rst:dir:`admonition` directive.
.. rubric:: Collapsible text
-.. versionadded:: 8.2.0
+.. versionadded:: 8.2
Each admonition directive supports a ``:collapsible:`` option,
to make the content of the admonition collapsible