Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add collapsible option to admonition directives #12507

Merged
merged 14 commits into from
Jan 29, 2025
5 changes: 4 additions & 1 deletion .ruff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
106 changes: 83 additions & 23 deletions doc/_themes/sphinx13/static/sphinx13.css
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
--icon-warning: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 14h-2v-4h2m0 8h-2v-2h2M1 21h22L12 2 1 21z"/></svg>');
--icon-failure: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 2c5.53 0 10 4.47 10 10s-4.47 10-10 10S2 17.53 2 12 6.47 2 12 2m3.59 5L12 10.59 8.41 7 7 8.41 10.59 12 7 15.59 8.41 17 12 13.41 15.59 17 17 15.59 13.41 12 17 8.41 15.59 7z"/></svg>');
--icon-spark: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M11.5 20l4.86-9.73H13V4l-5 9.73h3.5V20M12 2c2.75 0 5.1 1 7.05 2.95C21 6.9 22 9.25 22 12s-1 5.1-2.95 7.05C17.1 21 14.75 22 12 22s-5.1-1-7.05-2.95C3 17.1 2 14.75 2 12s1-5.1 2.95-7.05C6.9 3 9.25 2 12 2z"/></svg>');

/* icons used for details summaries */
--icon-details-open: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M8.59 16.58 13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.42Z"/></svg>');
}

body {
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -421,33 +424,78 @@ 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;
padding-right: .5rem;
}
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;
Expand All @@ -456,68 +504,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;
}

Expand Down
30 changes: 30 additions & 0 deletions doc/usage/restructuredtext/directives.rst
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,36 @@ 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::

.. 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
Expand Down
1 change: 1 addition & 0 deletions sphinx/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
'sphinx.domains.rst',
'sphinx.domains.std',
'sphinx.directives',
'sphinx.directives.admonitions',
'sphinx.directives.code',
'sphinx.directives.other',
'sphinx.directives.patches',
Expand Down
120 changes: 120 additions & 0 deletions sphinx/directives/admonitions.py
Original file line number Diff line number Diff line change
@@ -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,
}
Loading