Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ jobs:
# Install specific Sphinx and docutils versions, according to test matrix slots.
uv pip install 'sphinx==${{ matrix.sphinx-version }}'
uv pip install 'docutils==${{ matrix.docutils-version }}'
uv pip install 'types-docutils==${{ matrix.docutils-version }}'

- name: Sphinx and docutils versions
run: |
Expand Down
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Unreleased
- Dependencies: Added compatibility with docutils 0.19 - 0.22
- Dependencies: Added compatibility with sphinx 7 - 9
- Add "link tree" element, using directive `linktree`

## v0.4.0 - 2024-06-27
- Dependencies: Update to sphinx-design 0.6.0
Expand Down
9 changes: 9 additions & 0 deletions docs/_templates/linktree-demo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<h3>Classic toctree</h3>
<div class="sidebar-tree">
{{ sde_linktree_primary }}
</div>

<h3>Custom linktree</h3>
<div class="sidebar-tree">
{{ demo_synthetic_linktree }}
</div>
10 changes: 10 additions & 0 deletions docs/_templates/page.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{%- extends "!page.html" %}

{% block content %}
{{ super() }}

{% if pagename == "linktree" %}
{% include "linktree-demo.html" %}
{% endif %}

{% endblock %}
38 changes: 38 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
"""Configuration file for the Sphinx documentation builder."""

import os
import traceback
import typing as t

from sphinx.application import Sphinx

from sphinx_design_elements.navigation import default_tree, demo_tree

project = "Sphinx Design Elements"
copyright = "2023-2025, Panodata Developers"
Expand All @@ -23,6 +29,9 @@
# html_logo = "_static/logo_wide.svg"
# html_favicon = "_static/logo_square.svg"

# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]

# if html_theme not in ("sphinx_book_theme", "pydata_sphinx_theme"):
# html_css_files = [
# "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css"
Expand All @@ -33,6 +42,9 @@
"sidebar_hide_name": False,
}

# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True

exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
myst_enable_extensions = [
"attrs_block",
Expand Down Expand Up @@ -64,3 +76,29 @@
}

todo_include_todos = True


def setup(app: Sphinx) -> None:
"""Set up the sphinx extension."""
app.require_sphinx("3.0")
app.connect("html-page-context", _html_page_context)


def _html_page_context(
app: Sphinx,
pagename: str,
templatename: str,
context: t.Dict[str, t.Any],
doctree: t.Any,
) -> None:
"""
Sphinx HTML page context provider.
"""

# Initialize link tree navigation component.
try:
context["sde_linktree_primary"] = default_tree(builder=app.builder, context=context).render()
context["demo_synthetic_linktree"] = demo_tree(builder=app.builder, context=context).render()
except Exception as ex:
traceback.print_exception(ex)
raise
1 change: 1 addition & 0 deletions docs/get_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ provided by this collection, and how to use them in your documentation markup.

- [](#gridtable-directive)
- [](#infocard-directive)
- [](#linktree-directive)
- [](#tag-role)

Both [reStructuredText] and [Markedly Structured Text] syntax are supported equally well.
Expand Down
8 changes: 8 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ get_started
gridtable
infocard
shield
linktree
```

```{toctree}
Expand Down Expand Up @@ -134,6 +135,13 @@ Badge generator for Shields\.io, with optional target linking.
A versatile hyperlink generator.
:::

:::{grid-item-card} {octicon}`workflow` Link tree
:link: linktree
:link-type: doc

A programmable toctree component.
:::

:::{grid-item-card} {octicon}`tag` Special badges
:link: tag
:link-type: doc
Expand Down
128 changes: 128 additions & 0 deletions docs/linktree.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
(linktree-directive)=

# Link Tree


## About

Similar but different from a Toc Tree.

```{attention}
This component is a work in progress. Breaking changes should be expected until a
1.0 release, so version pinning is recommended.
```

### Problem

So much work went into the toctree mechanics, it is sad that it is not a reusable
component for building any kinds of navigation structures, and to be able to define
its contents more freely.

### Solution

This component implements a programmable toc tree component, the link tree.


## Details

The link tree component builds upon the Sphinx [toc] and [toctree] subsystem. It provides
both a rendered primary navigation within the `sde_linktree_primary` context variable
for use from HTML templates, and a Sphinx directive, `linktree`, for rendering
navigation trees into pages, similar but different from the [toctree directive]. The
user interface mechanics and styles are based on [Furo]'s primary sidebar component.


## Customizing

Link trees can be customized by creating them programmatically, similar to how
the `sde_linktree_primary` context variable is populated with the default Sphinx
toc tree.

The section hidden behind the dropdown outlines how the "custom linktree" is
defined, which is displayed at the bottom of the page in a rendered variant.
:::{dropdown} Custom linktree example code

```python
import typing as t

from sphinx.application import Sphinx
from sphinx_design_elements.lib.linktree import LinkTree


def demo_tree(app: Sphinx, context: t.Dict[str, t.Any], docname: str = None) -> LinkTree:
"""
The demo link tree showcases some features what can be done.

It uses regular page links to documents in the current project, a few
intersphinx references, and a few plain, regular, URL-based links.
"""
linktree = LinkTree.from_context(app=app, context=context)
doc = linktree.api.doc
ref = linktree.api.ref
link = linktree.api.link

linktree \
.title("Project-local page links") \
.add(
doc(name="gridtable"),
doc(name="infocard"),
)

linktree \
.title("Intersphinx links") \
.add(
ref("sd:index"),
ref("sd:badges", label="sphinx{design} badges"),
ref("myst:syntax/images_and_figures", "MyST » Images and figures"),
ref("myst:syntax/referencing", "MyST » Cross references"),
)

linktree \
.title("URL links") \
.add(
link(uri="https://example.com"),
link(uri="https://example.com", label="A link to example.com, using a custom label ⚽."),
)

return linktree
```
:::

```{todo}
- Use the `linktree` directive to define custom link trees.
- Link to other examples of custom link trees.
- Maybe use `:link:` and `:link-type:` directive options of `grid-item-card` directive.
```


## Directive examples

### Example 1

The link tree of the `index` page, using a defined maximum depth, and a custom title.
```{linktree}
:docname: index
:maxdepth: 1
:title: Custom title
```
Comment on lines +102 to +107
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs to be completed by using a custom MyST markup to define the content items of a linktree element.



## Appendix

Here, at the bottom of the page, different global template variables are presented,
which contain representations of navigation trees, rendered to HTML.

- `sde_linktree_primary`: The classic toctree, like it will usually be rendered
into the primary sidebar.
- `demo_synthetic_linktree`: A customized link tree composed of links to project-local
pages, intersphinx links, and URLs, for demonstration purposes.

```{hint}
The corresponding template, `linktree-demo.html` will exclusively be rendered
here, and not on other pages.
```

[Furo]: https://pradyunsg.me/furo/
[toctree directive]: https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-toctree
[toc]: https://www.sphinx-doc.org/en/master/development/templating.html#toc
[toctree]: https://www.sphinx-doc.org/en/master/development/templating.html#toctree
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ dynamic = [
dependencies = [
"beautifulsoup4",
"docutils<0.23",
"furo",
"myst-parser",
"sphinx<10",
"sphinx-design==0.6.1",
Expand All @@ -98,7 +99,6 @@ optional-dependencies.develop = [
"validate-pyproject<1",
]
optional-dependencies.docs = [
"furo",
"myst-parser[linkify]>=0.18",
"sphinx-autobuild",
"sphinx-copybutton",
Expand Down Expand Up @@ -215,7 +215,7 @@ warn_unused_ignores = false
warn_redundant_casts = true

[[tool.mypy.overrides]]
module = [ "docutils.*" ]
module = [ "docutils.*", "furo.*" ]
ignore_missing_imports = true

[tool.versioningit.vcs]
Expand Down
7 changes: 7 additions & 0 deletions sphinx_design_elements/compiled/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,10 @@ hr.docutils {
.bottom-margin-generous {
margin-bottom: 2em !important;
}

/**
* Fix appearance of page-rendered link tree.
**/
article .sidebar-tree p.caption {
text-align: unset;
}
2 changes: 2 additions & 0 deletions sphinx_design_elements/extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from .gridtable import setup_gridtable
from .hyper import setup_hyper
from .infocard import setup_infocard
from .linktree import setup_linktree
from .shield import setup_shield
from .tag import setup_tags

Expand All @@ -32,6 +33,7 @@ def setup_extension(app: Sphinx) -> None:
setup_gridtable(app)
setup_hyper(app)
setup_infocard(app)
setup_linktree(app)
setup_shield(app)
setup_tags(app)

Expand Down
10 changes: 5 additions & 5 deletions sphinx_design_elements/hyper.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import yaml
from docutils import nodes
from docutils.nodes import Node, system_message, unescape
from docutils.nodes import Node, system_message, unescape # type: ignore[attr-defined]
from docutils.parsers.rst.states import Inliner
from myst_parser.mocking import MockInliner
from sphinx.application import Sphinx
Expand Down Expand Up @@ -141,8 +141,8 @@ def __call__(

else:
error = ValueError("Unable to resolve reference")
msg = inliner.reporter.warning(error)
prb = inliner.problematic(rawtext, rawtext, msg)
msg = inliner.reporter.warning(error) # type: ignore[union-attr]
prb = inliner.problematic(rawtext, rawtext, msg) # type: ignore[union-attr]
return [prb], [msg]

return SphinxRole.__call__(self, name, rawtext, text, lineno, inliner, options, content) # type: ignore[arg-type]
Expand All @@ -158,7 +158,7 @@ def resolve_page_title(self) -> str:
except Exception:
return self.target
elif self.srh.is_traditional_intersphinx_reference():
document = self.inliner.document
document = self.inliner.document # type: ignore[attr-defined]
ref = resolve_reference(env=self.app.env, document=document, target=self.target)
elif self.srh.is_myst_reference():
link = f"[]({self.target})"
Expand Down Expand Up @@ -308,7 +308,7 @@ def render_snippet(self, snippet: str) -> Tuple[List[nodes.Node], List[nodes.sys
Render a MyST snippet.
"""
directive_nodes, _ = self.inliner.parse_block( # type: ignore[attr-defined]
text=snippet, lineno=self.lineno, memo=self, parent=self.inliner.parent, with_container=self.with_container
text=snippet, lineno=self.lineno, memo=self, parent=self.inliner.parent, with_container=self.with_container # type: ignore[attr-defined]
)
if not directive_nodes:
return [], self.system_messages
Expand Down
Empty file.
Loading