diff --git a/docs/_static/extra_css.css b/docs/_static/extra_css.css index 68db199bf..bdce350a5 100644 --- a/docs/_static/extra_css.css +++ b/docs/_static/extra_css.css @@ -36,15 +36,19 @@ mask-image: var(--md-admonition-icon--pied-piper); } -/* *********************** sphinx-design tab-set style overrides ********************* */ +/* ************************ custom-tab-set-style **************************** */ -.sd-tab-set>input:checked+label { - border-color: var(--md-primary-fg-color); - color: var(--md-primary-fg-color); +.custom-tab-set-style { + border: solid 2px var(--md-default-fg-color); + padding: 0 5px; } -.sd-tab-set>input:not(:checked)+label:hover { - color: var(--md-primary-fg-color); +/* *********************** custom-tab-item-style **************************** */ + +.custom-tab-item-style { + border: solid 2px var(--md-default-fg-color); + padding: 0 5px; + margin-top: 3px; } /* ************************* inline icon stuff ****************************** */ diff --git a/docs/admonitions.rst b/docs/admonitions.rst index 36e335653..b96c24a9a 100644 --- a/docs/admonitions.rst +++ b/docs/admonitions.rst @@ -33,7 +33,7 @@ usage in other sphinx-based themes. They are: Admonitions from mkdocs-material ******************************** -Some addtional admonitions are supported via the source code from the mkdocs-material theme. +Some additional admonitions are supported via the source code from the mkdocs-material theme. These admonitions can still be used, but the syntax is a little different because it relies on the generic admonition defined in the reStructuredText specifications. @@ -42,7 +42,7 @@ shown inside the demonstrated admonition. .. important:: The ``:class:`` options below (in the rST code blocks) must use lower case letters for the - styling to work. Otherwise, the admonition will look like a ``note`` (as that is the + styling to work. Otherwise, the admonition will look like a `note` (as that is the default fallback style). ``todo``, ``info`` @@ -120,67 +120,48 @@ shown inside the demonstrated admonition. .. admonition:: Quote :class: quote -Collapsable dropdown +Collapsible dropdown ********************* -For collapsable dropdown admonitions, the mkdocs-material theme relies on a markdown syntax -extension that cannot be used with sphinx. Instead, this sphinx-immaterial theme relies on -other sphinx extensions to get similar (and more customizable) results. - -.. dropdown:: We endorse the sphinx-design extension! - :icon: package-dependents - :animate: fade-in-slide-down - :class-title: sd-text-primary sd-outline-primary - :class-container: sd-outline-danger +.. _sphinxcontrib-details-directive extension: https://pypi.org/project/sphinxcontrib-details-directive - .. card:: You can do some pretty cool stuff with the :bdg-info-line:`sphinx-design extension`. - :class-title: sd-text-center - :margin: auto - - .. grid:: +For collapsible dropdown admonitions, the mkdocs-material theme relies on a markdown syntax +extension that cannot be used with sphinx. Instead, this sphinx-immaterial theme relies on +the `sphinxcontrib-details-directive extension`_ +to get similar results. - .. grid-item:: - :columns: auto - :margin: auto +The `sphinxcontrib-details-directive extension`_ should be added to conf.py's extension list. - .. button-ref:: buttons - :color: success +.. code-block:: python - .. grid-item:: - :columns: auto - :margin: auto + extensions = ["sphinx_immaterial", "sphinxcontrib.details.directive"] - .. button-ref:: tabs - :color: success - .. grid-item:: - :columns: auto - :margin: auto +If the ``:class:`` option is not supplied to the ``details`` directive then the admonition +style falls back to a `note` admonition style. - .. button-ref:: grids - :color: success +.. details:: Open by default + :class: example + :open: - .. grid-item:: - :columns: auto - :margin: auto + .. code-block:: rst - .. button-ref:: cards - :color: success + .. details:: Open by default + :class: example + :open: - .. grid-item:: - :columns: auto - :margin: auto +.. details:: Closed by default + :class: help - .. button-ref:: dropdowns - :color: success + .. code-block:: rst - Not to mention inline octicon :octicon:`infinity;1.5rem;sd-text-info` and fontawesome - :fab:`font-awesome-flag` icons and :bdg-ref-info:`badges`. + .. details:: Closed by default + :class: help Removing the title ****************** -Since the mkdocs-material theme relies on a mardown extension that also allows removing the title +Since the mkdocs-material theme relies on a markdown extension that also allows removing the title from an admonition, this theme has an added directive to do just that: ``md-admonition``. The admonition's title can be removed if the ``md-admonition`` directive is not provided @@ -222,10 +203,9 @@ If you want to add a custom admonition type, all you need is a color and an \*.s Copy the icon's code from the `.icons `_ folder and add the new CSS to an additional style sheet. -.. tab-set:: +.. md-tab-set:: - .. tab-item:: rST code - :class-label: sd-font-weight-light + .. md-tab-item:: rST code .. code-block:: rst @@ -234,8 +214,7 @@ folder and add the new CSS to an additional style sheet. Don't tell him you use spaces instead of tabs... - .. tab-item:: CSS code - :class-label: sd-font-weight-light + .. md-tab-item:: CSS code .. code-block:: css :caption: docs/_static/extra_css.css @@ -256,37 +235,15 @@ folder and add the new CSS to an additional style sheet. mask-image: var(--md-admonition-icon--pied-piper); } - .. tab-item:: conf.py code - :class-label: sd-font-weight-light + .. md-tab-item:: conf.py code - .. code-block:: python + .. code-block:: python - html_static_path = ["_static"] - html_css_files = ["extra_css.css"] + html_static_path = ["_static"] + html_css_files = ["extra_css.css"] .. admonition:: Pied Piper :class: pied-piper Don't tell him you use spaces instead of tabs... - -.. _tabbed_locks: - -.. md-admonition:: - :class: todo - - The use of tabbed blocks (as seen above) are provided from `sphinx-design extension`_. - We added some custom CSS to make the tabs' labels conform to this theme's color palete. - - .. code-block:: css - - .sd-tab-set>input:checked+label { - border-color: var(--md-primary-fg-color); - color: var(--md-primary-fg-color); - } - - .sd-tab-set>input:not(:checked)+label:hover { - color: var(--md-primary-fg-color); - } - -.. _sphinx-design extension: ` `_ diff --git a/docs/conf.py b/docs/conf.py index b215a937f..212e58cc9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -34,13 +34,12 @@ "sphinx.ext.todo", "sphinx.ext.mathjax", "sphinx.ext.viewcode", - "sphinx_design", + "sphinxcontrib.details.directive", ] intersphinx_mapping = { "python": ("https://docs.python.org/3", None), "sphinx_docs": ("https://www.sphinx-doc.org/en/master", None), - "sphinx-design": ("https://sphinx-design.readthedocs.io/en/furo-theme", None), } # The reST default role (used for this markup: `text`) to use for all diff --git a/docs/content_tabs.rst b/docs/content_tabs.rst new file mode 100644 index 000000000..85a2cbd66 --- /dev/null +++ b/docs/content_tabs.rst @@ -0,0 +1,143 @@ +.. _sphinx-design tabs: https://sphinx-design.readthedocs.io/en/furo-theme/tabs.html + +Content tabs +============ + +.. note:: + This document discusses content tabs, not navigation tabs. + +Use of `content tabs in the mkdocs-material `_ +theme relies on a markdown extension that isn't used in the world of Sphinx. Instead, +the sphinx-immaterial theme provides its own directives to make use of content tabs. + +.. admonition:: Linked Tabs + :class: missing + + The `linked content tabs `_ + feature seen in mkdocs-material is not supported until that feature transitions from the mkdocs-material theme's insider + releases to its public releases. + + You can use other sphinx extensions (like `sphinx-design tabs`_) to achieve this functionality. + However, other extensions will require some custom CSS styling to match the mkdocs-material + theme's styling for content tabs. + +.. rst:directive:: md-tab-set + + Each set of tabs on a page must begin with a `md-tab-set` directive. This directive + only accepts children that are `md-tab-item` directives. + + .. rst:directive:option:: class + :type: string + + A space delimited list of qualified names that get used as the HTMl element's + ``class`` attribute. + + .. rst:directive:option:: name + :type: string + + A qualified name that get used as the HTML element's ``id`` attribute. + + Use the `ref` role to reference the element by name. + + This directive supports ``:class:`` and ``:name:`` options to use custom CSS classes + and reference links (respectively). + + .. code-block:: rst + + .. md-tab-set:: + :class: custom-tab-set-style + :name: ref_this_tab_set + + .. md-tab-item:: Local Ref + + A reference to this tab set renders like so: + `tab set description `. + + This syntax can only be used on the same page as the tab set. + + .. md-tab-item:: Cross-page Ref + + To cross-reference this tab set from a different page, use + :ref:`tab set description ` + + Clearly, this also works on the same page. + + .. md-tab-item:: Custom CSS + + .. literalinclude:: _static/extra_css.css + :language: css + :start-at: /* ************************ custom-tab-set-style + :end-before: /* *********************** custom-tab-item-style + + .. md-tab-set:: + :class: custom-tab-set-style + :name: ref_this_tab_set + + .. md-tab-item:: Local Ref + + A reference to this tab set renders like so: + `tab set description `. + + This syntax can only be used on the same page as the tab set. + + .. md-tab-item:: Cross-page Ref + + To cross-reference this tab set from a different page, use + :ref:`tab set description ` + + Clearly, this also works on the same page as the tab set. + + .. md-tab-item:: Custom CSS + + .. literalinclude:: _static/extra_css.css + :language: css + :start-at: /* ************************ custom-tab-set-style + :end-before: /* *********************** custom-tab-item-style + +.. rst:directive:: md-tab-item + + This directive is used to create a tab within a set of content tabs. It requires a + label as it's argument. + + .. rst:directive:option:: class + :type: string + + A space delimited list of qualified names that get used as the HTMl element's + ``class`` attribute. + + Use the ``:class:`` option to optionally provide custom CSS classes to the tab's content + (not the tab's label). + + .. code-block:: rst + + .. md-tab-set:: + + .. md-tab-item:: Customized content + :class: custom-tab-item-style + + This content could be styled differently from other page content. + + .. md-tab-item:: Custom CSS + + .. literalinclude:: _static/extra_css.css + :language: css + :start-at: /* *********************** custom-tab-item-style + :end-before: /* ************************* inline icon stuff + + .. md-tab-set:: + + .. md-tab-item:: Customized content + :class: custom-tab-item-style + + This content could be styled differently from other page content. + + .. md-tab-item:: Custom CSS + + .. literalinclude:: _static/extra_css.css + :language: css + :start-at: /* *********************** custom-tab-item-style + :end-before: /* ************************* inline icon stuff + +Typical examples are seen in this documentations' +`Custom admonitions `_ and +:ref:`Version Information Structure ` sections. \ No newline at end of file diff --git a/docs/customization.rst b/docs/customization.rst index 3d5e3422c..b93102a5a 100644 --- a/docs/customization.rst +++ b/docs/customization.rst @@ -264,7 +264,7 @@ Configuration Options .. confval:: palette - The theme's color pallete **must be** a single `dict` or a `list` of `dict`\ s. + The theme's color pallette **must be** a single `dict` or a `list` of `dict`\ s. Each `dict` can optionally specify a ``scheme``, ``primary``, ``accent``, and ``media`` fields. @@ -427,7 +427,7 @@ The standard structure of the site (relative to the base domain) is usually /1.0 /2.0 -Whereas Sphinx must be executed seperately for each version of the documentation you are +Whereas Sphinx must be executed separately for each version of the documentation you are building. .. code-block:: text @@ -531,10 +531,10 @@ aliases. Other required fields include ``version`` and ``title``. ``aliases`` do not apply when using an external URL (as in not relative to the same webserver) in the ``verion`` field. -.. tab-set:: +.. md-tab-set:: + :name: version_info_example - .. tab-item:: Using ``version_info`` in conf.py - :class-label: sd-font-weight-light + .. md-tab-item:: Using ``version_info`` in conf.py .. code-block:: python @@ -546,8 +546,7 @@ aliases. Other required fields include ``version`` and ``title``. ], } - .. tab-item:: Using a JSON file - :class-label: sd-font-weight-light + .. md-tab-item:: Using a JSON file .. hint:: Remember to set ``"version_dropdown": True`` in the conf.py file's `html_theme_options` `dict`. diff --git a/docs/index.rst b/docs/index.rst index b7a40c184..1230d213b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -69,6 +69,8 @@ or ``theme.conf`` for more details. customization admonitions + content_tabs + mermaid_diagrams .. toctree:: :caption: Examples and Uses diff --git a/docs/mermaid_diagrams.rst b/docs/mermaid_diagrams.rst new file mode 100644 index 000000000..59328cf65 --- /dev/null +++ b/docs/mermaid_diagrams.rst @@ -0,0 +1,220 @@ +.. _mermaid.js: https://mermaid-js.github.io/mermaid/ + +Mermaid diagrams +================ + +.. note:: + Use of this feature has no affect or affiliation with sphinx's graphviz implementation. + +The mkdocs-material theme is equipped to make use of diagrams generated (during page load time) +with `mermaid.js`_. Although, its implementation relies on a markdown extension that does not get +used by this sphinx-immaterial theme. Thus, the sphinx-immaterial theme provides an optional +directive that exposes the underlying implementation in mkdocs-material theme. + +.. rst:directive:: md-mermaid + + .. rst:directive:option:: class + :type: string + + A space delimited list of qualified names that get used as the HTML element's + ``class`` attribute. + + .. rst:directive:option:: name + :type: string + + A qualified name that get used as the HTML element's ``id`` attribute. + + Use the `ref` role to reference the element by name. + + The `md-mermaid` directive's ``:class:`` and ``:name:`` options can be used + as respective class and id specifiers in custom CSS. + + This theme comes with CSS styling that conforms to the chosen `primary` & `accent` colors + (based on the selected `scheme`). + + .. md-admonition:: + :class: missing + + While all `mermaid.js`_ features should work out-of-the-box, this theme will currently only + adjust the fonts and colors for `flowcharts`_, `sequence diagrams `, + `class diagrams `, `state diagrams `, and + `entity-relationship diagrams `. + +Using flowcharts +---------------- + +.. code-block:: rst + + .. md-mermaid:: + :name: flowcharts + + graph LR + A[Start] --> B{Error?}; + B -->|Yes| C[Hmm...]; + C --> D[Debug]; + D --> B; + B ---->|No| E[Yay!]; + +.. md-mermaid:: + :name: flowcharts + + graph LR + A[Start] --> B{Error?}; + B -->|Yes| C[Hmm...]; + C --> D[Debug]; + D --> B; + B ---->|No| E[Yay!]; + +Using sequence diagrams +----------------------- + +.. code-block:: rst + + .. md-mermaid:: + :name: sequence-diagrams + + sequenceDiagram + Alice->>John: Hello John, how are you? + loop Healthcheck + John->>John: Fight against hypochondria + end + Note right of John: Rational thoughts! + John-->>Alice: Great! + John->>Bob: How about you? + Bob-->>John: Jolly good! + +.. md-mermaid:: + :name: sequence-diagrams + + sequenceDiagram + Alice->>John: Hello John, how are you? + loop Healthcheck + John->>John: Fight against hypochondria + end + Note right of John: Rational thoughts! + John-->>Alice: Great! + John->>Bob: How about you? + Bob-->>John: Jolly good! + +Using state diagrams +-------------------- + +.. code-block:: rst + + .. md-mermaid:: + :name: state-diagrams + + stateDiagram-v2 + state fork_state <> + [*] --> fork_state + fork_state --> State2 + fork_state --> State3 + + state join_state <> + State2 --> join_state + State3 --> join_state + join_state --> State4 + State4 --> [*] + +.. md-mermaid:: + :name: state-diagrams + + stateDiagram-v2 + state fork_state <> + [*] --> fork_state + fork_state --> State2 + fork_state --> State3 + + state join_state <> + State2 --> join_state + State3 --> join_state + join_state --> State4 + State4 --> [*] + +Using class diagrams +-------------------- + + +.. code-block:: rst + + .. md-mermaid:: + :name: class-diagrams + + classDiagram + Person <|-- Student + Person <|-- Professor + Person : +String name + Person : +String phoneNumber + Person : +String emailAddress + Person: +purchaseParkingPass() + Address "1" <-- "0..1" Person:lives at + class Student{ + +int studentNumber + +int averageMark + +isEligibleToEnrol() + +getSeminarsTaken() + } + class Professor{ + +int salary + } + class Address{ + +String street + +String city + +String state + +int postalCode + +String country + -validate() + +outputAsLabel() + } + +.. md-mermaid:: + :name: class-diagrams + + classDiagram + Person <|-- Student + Person <|-- Professor + Person : +String name + Person : +String phoneNumber + Person : +String emailAddress + Person: +purchaseParkingPass() + Address "1" <-- "0..1" Person:lives at + class Student{ + +int studentNumber + +int averageMark + +isEligibleToEnrol() + +getSeminarsTaken() + } + class Professor{ + +int salary + } + class Address{ + +String street + +String city + +String state + +int postalCode + +String country + -validate() + +outputAsLabel() + } + +Using entity-relationship diagrams +---------------------------------- + + +.. code-block:: rst + + .. md-mermaid:: + :name: entity-relationship-diagrams + + erDiagram + CUSTOMER ||--o{ ORDER : places + ORDER ||--|{ LINE-ITEM : contains + CUSTOMER }|..|{ DELIVERY-ADDRESS : uses + +.. md-mermaid:: + :name: entity-relationship-diagrams + + erDiagram + CUSTOMER ||--o{ ORDER : places + ORDER ||--|{ LINE-ITEM : contains + CUSTOMER }|..|{ DELIVERY-ADDRESS : uses diff --git a/docs/requirements.txt b/docs/requirements.txt index fa0dda8e3..4f1766b22 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,3 +1,3 @@ # The sphinx docs theme to use sphinx_immaterial -sphinx-design +sphinxcontrib-details-directive diff --git a/package-lock.json b/package-lock.json index 80dc7d9fd..38ecbdef4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,16 +26,16 @@ "@types/css-modules": "^1.0.2", "@types/escape-html": "1.0.1", "@types/html-minifier": "^4.0.2", - "@types/node": "^17.0.18", + "@types/node": "^17.0.21", "@types/resize-observer-browser": "^0.1.7", "@types/sass": "^1.43.1", - "@typescript-eslint/eslint-plugin": "^5.12.0", - "@typescript-eslint/parser": "^5.12.0", + "@typescript-eslint/eslint-plugin": "^5.12.1", + "@typescript-eslint/parser": "^5.12.1", "autoprefixer": "^10.4.2", "chokidar": "^3.5.3", "cssnano": "^5.0.17", "esbuild": "^0.14.23", - "eslint": "^8.9.0", + "eslint": "^8.10.0", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-import": "^2.25.4", "eslint-plugin-jsdoc": "^37.9.4", @@ -48,14 +48,14 @@ "material-shadows": "^3.0.1", "npm-check-updates": "^12.4.0", "npm-run-all": "^4.1.5", - "postcss": "^8.4.6", + "postcss": "^8.4.7", "postcss-dir-pseudo-class": "^6.0.4", "postcss-inline-svg": "^5.0.0", "postcss-logical": "^5.0.4", "preact": "^10.6.6", "rimraf": "^3.0.2", - "sass": "^1.49.8", - "stylelint": "^14.5.1", + "sass": "^1.49.9", + "stylelint": "^14.5.3", "stylelint-config-rational-order": "^0.1.2", "stylelint-config-recommended": "^7.0.0", "stylelint-config-standard-scss": "^3.0.0", @@ -428,16 +428,16 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.1.0.tgz", - "integrity": "sha512-C1DfL7XX4nPqGd6jcP01W9pVM1HYCuUkFk1432D7F0v3JSlUIeOYn9oCoi3eoLZ+iwBSb29BMFxxny0YrrEZqg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz", + "integrity": "sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.3.1", "globals": "^13.9.0", - "ignore": "^4.0.6", + "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.0.4", @@ -453,15 +453,6 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, "node_modules/@eslint/eslintrc/node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -3449,12 +3440,12 @@ } }, "node_modules/eslint": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.9.0.tgz", - "integrity": "sha512-PB09IGwv4F4b0/atrbcMFboF/giawbBLVC7fyDamk5Wtey4Jh2K+rYaBhCAbUyEI4QzB1ly09Uglc9iCtFaG2Q==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.11.0.tgz", + "integrity": "sha512-/KRpd9mIRg2raGxHRGwW9ZywYNAClZrHjdueHcrVDuO3a6bj83eoTirCCk0M0yPwOjWYKHwRVRid+xK4F/GHgA==", "dev": true, "dependencies": { - "@eslint/eslintrc": "^1.1.0", + "@eslint/eslintrc": "^1.2.1", "@humanwhocodes/config-array": "^0.9.2", "ajv": "^6.10.0", "chalk": "^4.0.0", @@ -4666,9 +4657,9 @@ } }, "node_modules/globals": { - "version": "13.12.1", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz", - "integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==", + "version": "13.13.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz", + "integrity": "sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -8145,21 +8136,27 @@ } }, "node_modules/postcss": { - "version": "8.4.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.6.tgz", - "integrity": "sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==", + "version": "8.4.12", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.12.tgz", + "integrity": "sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], "dependencies": { - "nanoid": "^3.2.0", + "nanoid": "^3.3.1", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, "engines": { "node": "^10 || ^12 || >=14" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" } }, "node_modules/postcss-calc": { @@ -13020,16 +13017,16 @@ } }, "@eslint/eslintrc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.1.0.tgz", - "integrity": "sha512-C1DfL7XX4nPqGd6jcP01W9pVM1HYCuUkFk1432D7F0v3JSlUIeOYn9oCoi3eoLZ+iwBSb29BMFxxny0YrrEZqg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz", + "integrity": "sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.3.1", "globals": "^13.9.0", - "ignore": "^4.0.6", + "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.0.4", @@ -13042,12 +13039,6 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, "import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -15216,12 +15207,12 @@ "dev": true }, "eslint": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.9.0.tgz", - "integrity": "sha512-PB09IGwv4F4b0/atrbcMFboF/giawbBLVC7fyDamk5Wtey4Jh2K+rYaBhCAbUyEI4QzB1ly09Uglc9iCtFaG2Q==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.11.0.tgz", + "integrity": "sha512-/KRpd9mIRg2raGxHRGwW9ZywYNAClZrHjdueHcrVDuO3a6bj83eoTirCCk0M0yPwOjWYKHwRVRid+xK4F/GHgA==", "dev": true, "requires": { - "@eslint/eslintrc": "^1.1.0", + "@eslint/eslintrc": "^1.2.1", "@humanwhocodes/config-array": "^0.9.2", "ajv": "^6.10.0", "chalk": "^4.0.0", @@ -16157,9 +16148,9 @@ } }, "globals": { - "version": "13.12.1", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz", - "integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==", + "version": "13.13.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz", + "integrity": "sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -18784,12 +18775,12 @@ "dev": true }, "postcss": { - "version": "8.4.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.6.tgz", - "integrity": "sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==", + "version": "8.4.12", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.12.tgz", + "integrity": "sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg==", "dev": true, "requires": { - "nanoid": "^3.2.0", + "nanoid": "^3.3.1", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } diff --git a/setup.py b/setup.py index e2df1e3c7..605f0306e 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -import setuptools +import setuptools # pylint: disable=wrong-import-order import atexit import distutils.command.build @@ -30,7 +30,7 @@ import setuptools.command.install import setuptools.command.sdist -with open("requirements.txt") as reqs: +with open("requirements.txt", encoding="utf-8") as reqs: REQUIREMENTS = [reqs.readlines()] root_dir = os.path.dirname(os.path.abspath(__file__)) diff --git a/sphinx_immaterial/__init__.py b/sphinx_immaterial/__init__.py index 60cb2b4fc..b7c6ad1b9 100644 --- a/sphinx_immaterial/__init__.py +++ b/sphinx_immaterial/__init__.py @@ -20,6 +20,7 @@ from . import object_toc from . import postprocess_html from . import search_adapt +from .details_patch import monkey_patch_details_run logger = sphinx.util.logging.getLogger(__name__) @@ -260,6 +261,8 @@ def _config_inited( """Merge defaults into theme options.""" if config["language"] is None: config["language"] = "en" # default to English language + # make code-blocks' line numbers be a separate column of a 1-row table + config["html_codeblock_linenos_style"] = "table" # default is "inline" config["html_theme_options"] = dict_merge( DEFAULT_THEME_OPTIONS, config["html_theme_options"] ) @@ -282,8 +285,12 @@ def setup(app): app.add_builder(_get_html_builder(app.registry.builders["html"]), override=True) app.add_html_theme("sphinx_immaterial", os.path.abspath(os.path.dirname(__file__))) - # register our custom adminition directive + # register our custom adminition directive that are tied to the theme's CSS app.setup_extension("sphinx_immaterial.md_admonition") + app.setup_extension("sphinx_immaterial.content_tabs") + app.setup_extension("sphinx_immaterial.mermaid_diagrams") + # patch the details directive's run method + monkey_patch_details_run() return { "parallel_read_safe": True, diff --git a/sphinx_immaterial/content_tabs.py b/sphinx_immaterial/content_tabs.py new file mode 100644 index 000000000..15a809ad3 --- /dev/null +++ b/sphinx_immaterial/content_tabs.py @@ -0,0 +1,179 @@ +""" +A special theme-specific extension to support "content tabs" from mkdocs-material. +""" +from typing import List +from docutils import nodes +from docutils.parsers.rst import directives +from sphinx.util.docutils import SphinxDirective +from sphinx.application import Sphinx +from sphinx.util.logging import getLogger +from sphinx.writers.html import HTMLTranslator + +LOGGER = getLogger(__name__) + + +def is_md_tab_type(node: nodes.Node, name: str): + """Check if a node is a certain tabbed component.""" + try: + return node.get("type") == name + except AttributeError: + return False + + +class content_tab_set(nodes.container): + pass + + +class MaterialTabSetDirective(SphinxDirective): + """A container for a set of tab items.""" + + has_content = True + option_spec = { + "name": directives.unchanged, + "class": directives.class_option, + } + + def run(self) -> List[nodes.Node]: + """Run the directive.""" + self.assert_has_content() + tab_set = content_tab_set( + "", + is_div=True, + type="md-tab-set", + classes=["tabbed-set", "tabbed-alternate"] + self.options.get("class", []), + ) + self.set_source_info(tab_set) + if self.options.get("name", ""): + self.add_name(tab_set) + self.state.nested_parse(self.content, self.content_offset, tab_set) + for item in tab_set.children: + if not is_md_tab_type(item, "md-tab-item"): + LOGGER.warning( + "All children of a 'md-tab-set' should be 'md-tab-item'", + location=item, + ) + break + return [tab_set] + + +class MaterialTabItemDirective(SphinxDirective): + """A single tab item in a tab set. + Note: This directive generates a single container, + for the label and content:: + + + ...title nodes + + ...content nodes + This allows for a default rendering in non-HTML outputs. + The ``MaterialTabSetHtmlTransform`` then transforms this container + into the HTML specific structure. + """ + + required_arguments = 1 # the tab label is the first argument + final_argument_whitespace = True + has_content = True + option_spec = { + "class": directives.class_option, + } + + def run(self) -> List[nodes.Node]: + """Run the directive.""" + self.assert_has_content() + if not is_md_tab_type(self.state_machine.node, "md-tab-set"): + LOGGER.warning( + "The parent of a 'md-tab-item' should be a 'md-tab-set'", + location=(self.env.docname, self.lineno), + ) + tab_item = nodes.container( + "", is_div=True, type="md-tab-item", classes=["tabbed-block"] + ) + + # add tab label + textnodes, _ = self.state.inline_text(self.arguments[0], self.lineno) + tab_label = nodes.rubric( + self.arguments[0], *textnodes, classes=["tabbed-label"] + ) + self.add_name(tab_label) + tab_item += tab_label + + # add tab content + tab_content = nodes.container( + "", + is_div=True, + type="", + classes=["tabbed-block"] + self.options.get("class", []), + ) + self.state.nested_parse(self.content, self.content_offset, tab_content) + tab_item += tab_content + + return [tab_item] + + +class content_tab_label(nodes.TextElement, nodes.General): + pass + + +def visit_tab_label(self, node): + attributes = {"for": node["input_id"]} + self.body.append(self.starttag(node, "label", **attributes)) + + +def depart_tab_label(self, node): + self.body.append("") + + +def visit_tab_set(self: HTMLTranslator, node: content_tab_set): + # increment tab set counter + self.tab_set_count = getattr(self, "tab_set_count", 0) + 1 + + # configure tab set's div attributes + tab_set_identity = f"__tabbed_{self.tab_set_count}" + attributes = {"data-tabs": f"{self.tab_set_count}:{len(node.children)}"} + self.body.append(self.starttag(node, "div", **attributes)) + + # walkabout the children + tab_label_div = nodes.container("", is_div=True, classes=["tabbed-labels"]) + tab_content_div = nodes.container("", is_div=True, classes=["tabbed-content"]) + for tab_count, tab_item in enumerate(node.children): + try: + tab_label, tab_block = tab_item.children + except ValueError as exc: + raise ValueError(f"md-tab-item has no children:\n{repr(tab_item)}") from exc + tab_item_identity = tab_set_identity + f"_{tab_count + 1}" + + # create: + self.body.append( + "' + ) + + # create: + label_node = content_tab_label( + "", + *tab_label.children, + input_id=tab_item_identity, + classes=tab_label["classes"], + ) + label_node.source, label_node.line = tab_item.source, tab_item.line + tab_label_div += label_node + + # add content + tab_content_div += tab_block + + tab_label_div.walkabout(self) + tab_content_div.walkabout(self) + raise nodes.SkipNode() + + +def depart_tab_set(self, node): + self.body.append("") + + +def setup(app: Sphinx) -> None: + app.add_directive("md-tab-set", MaterialTabSetDirective) + app.add_directive("md-tab-item", MaterialTabItemDirective) + app.add_node(content_tab_label, html=(visit_tab_label, depart_tab_label)) + app.add_node(content_tab_set, html=(visit_tab_set, depart_tab_set)) diff --git a/sphinx_immaterial/details_patch.py b/sphinx_immaterial/details_patch.py new file mode 100644 index 000000000..da5dde10b --- /dev/null +++ b/sphinx_immaterial/details_patch.py @@ -0,0 +1,32 @@ +from typing import List +from docutils import nodes + +try: + from sphinxcontrib.details.directive import DetailsDirective +except ImportError: + DetailsDirective = None + + +def monkey_patch_details_run(): + """Patch the details directive to respect the class option. + This solution is a temporary fix pending response from + https://github.com/tk0miya/sphinxcontrib-details-directive/issues/4 + """ + if DetailsDirective is None: + return + + def run(self) -> List[nodes.container]: + admonition = nodes.container( + "", + classes=self.options.get("class", []) + self.options.get("classes", []), + opened="open" in self.options, + type="details", + ) + textnodes, messages = self.state.inline_text(self.arguments[0], self.lineno) + admonition += nodes.paragraph(self.arguments[0], "", *textnodes) + admonition += messages + self.state.nested_parse(self.content, self.content_offset, admonition) + self.add_name(admonition) + return [admonition] + + DetailsDirective.run = run diff --git a/sphinx_immaterial/mermaid_diagrams.py b/sphinx_immaterial/mermaid_diagrams.py new file mode 100644 index 000000000..530d39907 --- /dev/null +++ b/sphinx_immaterial/mermaid_diagrams.py @@ -0,0 +1,63 @@ +"""A custom directive that allows using mermaid diagrams""" +from typing import List +from docutils import nodes +from docutils.parsers.rst import directives +from sphinx.util.docutils import SphinxDirective +from sphinx.application import Sphinx + + +class mermaid_node(nodes.General, nodes.Element): + pass + + +class MermaidDirective(SphinxDirective): + """a special directive""" + + has_content = True + option_spec = { + "name": directives.unchanged, + "class": directives.class_option, + } + + def run(self) -> List[nodes.Node]: + """Run the directive.""" + self.assert_has_content() + content = "\n".join(self.content) + diagram = mermaid_node("", classes=["mermaid"], content=content) + diagram += nodes.literal("", content, format="html") + diagram_div = nodes.container( + "", + is_div=True, + classes=["mermaid-diagram"] + self.options.get("class", []), + ) + if self.options.get("name", ""): + self.add_name(diagram_div) + diagram_div += diagram + self.set_source_info(diagram_div) + return [diagram_div] + + +def visit_mermaid_node_html(self, node: mermaid_node): + attributes = {"class": "mermaid"} + self.body.append(self.starttag(node, "pre", **attributes)) + + +def depart_mermaid_node_html(self, node: mermaid_node): + self.body.append("") + + +def visit_mermaid_node_latex(self, node: mermaid_node): + self.body.append("\n\\begin{sphinxVerbatim}[commandchars=\\\\\\{\\}]\n") + + +def depart_mermaid_node_latex(self, node: mermaid_node): + self.body.append("\n\\end{sphinxVerbatim}\n") + + +def setup(app: Sphinx): + app.add_directive("md-mermaid", MermaidDirective) + app.add_node( + mermaid_node, + html=(visit_mermaid_node_html, depart_mermaid_node_html), + latex=(visit_mermaid_node_latex, depart_mermaid_node_latex), + ) diff --git a/src/assets/stylesheets/main.scss b/src/assets/stylesheets/main.scss index dcabfd15b..7a4ead995 100644 --- a/src/assets/stylesheets/main.scss +++ b/src/assets/stylesheets/main.scss @@ -67,7 +67,7 @@ @import "main/extensions/pymdownx/arithmatex"; @import "main/extensions/pymdownx/critic"; -/// @import "main/extensions/pymdownx/details"; +@import "main/extensions/pymdownx/details"; @import "main/extensions/pymdownx/emoji"; @import "main/extensions/pymdownx/highlight"; @import "main/extensions/pymdownx/keys";