diff --git a/docs/dev-guide/themes.md b/docs/dev-guide/themes.md index ce6a5a6a..b5ea0fc2 100644 --- a/docs/dev-guide/themes.md +++ b/docs/dev-guide/themes.md @@ -354,6 +354,11 @@ All `page` objects contain the following attributes: show_root_full_path: false heading_level: 5 +::: properdocs.structure.pages.Page.content_title + options: + show_root_full_path: false + heading_level: 5 + ::: properdocs.structure.pages.Page.content options: show_root_full_path: false @@ -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 }} +{% if not page.is_homepage %}{{ page.title }} - {% endif %}{{ site_name }} ``` ::: properdocs.structure.pages.Page.previous_page diff --git a/properdocs/structure/pages.py b/properdocs/structure/pages.py index b03bacfb..afd76034 100644 --- a/properdocs/structure/pages.py +++ b/properdocs/structure/pages.py @@ -52,9 +52,6 @@ def __init__(self, title: str | None, file: File, config: ProperDocsConfig) -> N ) # 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 = {} @@ -71,14 +68,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`][properdocs.structure.toc.AnchorLink].""" @@ -227,15 +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()`. Checks these in order and uses the first that returns a valid title: - - value provided on init (passed in from config) + - value specified in the `nav` config in properdocs.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: @@ -260,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 ProperDocs 1.7.** + + 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 `` tag. To apply the preferred behavior but keep compatibility with older versions, you can use: + + ```jinja + <title>{{ page.content_title or page.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: ProperDocsConfig, files: Files) -> None: """Convert the Markdown source file to HTML as per the config.""" if self.markdown is None: diff --git a/properdocs/tests/structure/page_tests.py b/properdocs/tests/structure/page_tests.py index 851ad0b0..cd363c9c 100644 --- a/properdocs/tests/structure/page_tests.py +++ b/properdocs/tests/structure/page_tests.py @@ -318,8 +318,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 ProperDocs') + self.assertRaises(RuntimeError, lambda: pg.content_title) pg.render(cfg, Files([fl])) self.assertEqual(pg.title, 'Welcome to ProperDocs') + self.assertEqual(pg.content_title, 'Welcome to ProperDocs') def _test_extract_title(self, content, expected, extensions={}): md = markdown.Markdown(extensions=list(extensions.keys()), extension_configs=extensions) @@ -437,10 +439,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) @@ -463,9 +471,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)