diff --git a/mkdocs/structure/pages.py b/mkdocs/structure/pages.py index 74fe872a..e8eaf61c 100644 --- a/mkdocs/structure/pages.py +++ b/mkdocs/structure/pages.py @@ -96,6 +96,19 @@ def __repr__(self): meta: MutableMapping[str, Any] """A mapping of the metadata included at the top of the markdown page.""" + @property + def content_title(self) -> str | None: + """ + The title of the page extracted from the first `

` heading in the page content. + + This is `None` until the page has been rendered. Unlike `title`, + this does not fall back to any other source — it reflects exactly + the `

` text from the rendered Markdown, with HTML tags stripped + and entities unescaped. It is useful for plugins that need the raw + heading text as it appears in the page body. + """ + return self._title_from_render + @property def url(self) -> str: """The URL of the page relative to the MkDocs `site_dir`.""" diff --git a/mkdocs/tests/structure/page_tests.py b/mkdocs/tests/structure/page_tests.py index 53af9c75..29bcf5ee 100644 --- a/mkdocs/tests/structure/page_tests.py +++ b/mkdocs/tests/structure/page_tests.py @@ -329,6 +329,17 @@ def test_page_title_from_markdown(self): self.assertEqual(pg.title, "Welcome to MkDocs") pg.render(cfg, Files([fl])) self.assertEqual(pg.title, "Welcome to MkDocs") + self.assertEqual(pg.content_title, "Welcome to MkDocs") + + def test_content_title_property(self): + """content_title is None before rendering, set after.""" + cfg = load_config() + fl = File("testing.md", cfg.docs_dir, cfg.site_dir, cfg.use_directory_urls) + pg = Page(None, fl, cfg) + pg.read_source(cfg) + self.assertIsNone(pg.content_title) + pg.render(cfg, Files([fl])) + self.assertEqual(pg.content_title, "Welcome to MkDocs") def _test_extract_title(self, content, expected, extensions={}): md = markdown.Markdown(