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

Expose a page.content_title attribute #3533

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 6 additions & 1 deletion docs/dev-guide/themes.md
Expand Up @@ -354,6 +354,11 @@ All `page` objects contain the following attributes:
show_root_full_path: false
heading_level: 5

::: mkdocs.structure.pages.Page.content_title
options:
show_root_full_path: false
heading_level: 5

::: mkdocs.structure.pages.Page.content
options:
show_root_full_path: false
Expand Down Expand Up @@ -453,7 +458,7 @@ object to alter the behavior. For example, to display a different title
on the homepage:

```django
{% if not page.is_homepage %}{{ page.title }} - {% endif %}{{ site_name }}
<title>{% if not page.is_homepage %}{{ page.title }} - {% endif %}{{ site_name }}</title>
```

::: mkdocs.structure.pages.Page.previous_page
Expand Down
51 changes: 42 additions & 9 deletions mkdocs/structure/pages.py
Expand Up @@ -53,9 +53,6 @@ def __init__(self, title: str | None, file: File, config: MkDocsConfig) -> None:
)

# Placeholders to be filled in later in the build process.
self.markdown = None
self._title_from_render: str | None = None
self.content = None
self.toc = [] # type: ignore
self.meta = {}

Expand All @@ -72,14 +69,16 @@ def __repr__(self):
url = self.abs_url or self.file.url
return f"{name}(title={title}, url={url!r})"

markdown: str | None
markdown: str | None = None
"""The original Markdown content from the file."""

content: str | None
content: str | None = None
"""The rendered Markdown as HTML, this is the contents of the documentation.

Populated after `.render()`."""

_title_from_render: str | None = None

toc: TableOfContents
"""An iterable object representing the Table of contents for a page. Each item in
the `toc` is an [`AnchorLink`][mkdocs.structure.toc.AnchorLink]."""
Expand Down Expand Up @@ -227,14 +226,15 @@ def _set_title(self) -> None:
@weak_property
def title(self) -> str | None: # type: ignore[override]
"""
Returns the title for the current page.
Returns the title for the page, in the context of the nav.

Before calling `read_source()`, this value is empty. It can also be updated by `render()`.

Check these in order and use the first that returns a valid title:
- value provided on init (passed in from config)
Checks these in order and uses the first that returns a valid title:

- value specified in the `nav` config in mkdocs.yml (or the value that was passed when creating the `Page` programmatically)
- value of metadata 'title'
- content of the first H1 in Markdown content
- content of the first H1 heading in Markdown content
- convert filename to title
"""
if self.markdown is None:
Expand All @@ -259,6 +259,39 @@ def title(self) -> str | None: # type: ignore[override]
title = title.capitalize()
return title

@property
def content_title(self) -> str:
"""
Returns the title for the page, in the context of the current page's content.

NEW: **New in MkDocs 1.6.**

Similar to `title` but prioritizes the title from the document itself over the title
specified in the `nav` config.

For themes, this should be preferred within the `<title>` tag. To apply the preferred behavior but keep compatibility with older versions, you can use:

```jinja
<title>{{ page.content_title or page.title }}</title>
```

Checks these in order and uses the first that returns a valid title:

- value of metadata 'title'
- content of the first H1 heading in Markdown content
- value specified in the `nav` config in mkdocs.yml (or the value that was passed when creating the `Page` programmatically)
- convert filename to title

When using this property outside of themes, do not access it before `render()` was called on the content, or it will raise.
"""
if self.content is None:
raise RuntimeError("`content` field hasn't been set (via `render`)")
if 'title' in self.meta:
return self.meta['title']
if self._title_from_render:
return self._title_from_render
return self.title

def render(self, config: MkDocsConfig, files: Files) -> None:
"""Convert the Markdown source file to HTML as per the config."""
if self.markdown is None:
Expand Down
15 changes: 15 additions & 0 deletions mkdocs/tests/structure/page_tests.py
Expand Up @@ -317,8 +317,10 @@ def test_page_title_from_markdown(self):
self.assertEqual(pg.parent, None)
self.assertEqual(pg.previous_page, None)
self.assertEqual(pg.title, 'Welcome to MkDocs')
self.assertRaises(RuntimeError, lambda: pg.content_title)
pg.render(cfg, Files([fl]))
self.assertEqual(pg.title, 'Welcome to MkDocs')
self.assertEqual(pg.content_title, 'Welcome to MkDocs')

def _test_extract_title(self, content, expected, extensions={}):
md = markdown.Markdown(extensions=list(extensions.keys()), extension_configs=extensions)
Expand Down Expand Up @@ -436,10 +438,16 @@ def test_page_title_from_meta(self):
self.assertEqual(pg.next_page, None)
self.assertEqual(pg.parent, None)
self.assertEqual(pg.previous_page, None)
self.assertRaises(RuntimeError, lambda: pg.content_title)
self.assertEqual(pg.title, 'A Page Title')
self.assertEqual(pg.toc, [])
pg.render(cfg, Files([fl]))
self.assertEqual(pg.title, 'A Page Title')
self.assertEqual(pg.content_title, 'A Page Title')
# Test setting the title directly (e.g. through a plugin):
pg.title = 'New title'
self.assertEqual(pg.title, 'New title')
self.assertEqual(pg.content_title, 'A Page Title')

def test_page_title_from_filename(self):
cfg = load_config(docs_dir=DOCS_DIR)
Expand All @@ -462,9 +470,16 @@ def test_page_title_from_filename(self):
self.assertEqual(pg.next_page, None)
self.assertEqual(pg.parent, None)
self.assertEqual(pg.previous_page, None)
self.assertRaises(RuntimeError, lambda: pg.content_title)
self.assertEqual(pg.title, 'Page title')
pg.render(cfg, Files([fl]))
self.assertEqual(pg.title, 'Page title')
self.assertEqual(pg.content_title, 'Page title')
# Test setting the title directly (e.g. through a plugin):
pg.title = 'New title'
self.assertEqual(pg.title, 'New title')
# Content title is missing, title still takes precedence.
self.assertEqual(pg.content_title, 'New title')

def test_page_title_from_capitalized_filename(self):
cfg = load_config(docs_dir=DOCS_DIR)
Expand Down