Repository: timvink/mkdocs-print-site-plugin

Issue:
Use frontmatter `title` in place of level 1 heading for TOC and heading enumeration
This is more of a feature request than bug report.

I am trying to use this plugin but the majority of the docs do not have a level 1 heading, resulting in almost all of them triggering the following error:

> The page [page name] ([page.location]) does not start with a level 1 heading.This is required for print page Table of Contents and/or enumeration of headings.

To enhance the compatibility of this plugin regarding page title, I would like to propose for the plugin to mimic the [MkDocs behaviour of determining the page title](https://www.mkdocs.org/user-guide/writing-your-docs/#meta-data):

> MkDocs will attempt to determine the title of a document in the following ways, in order:
> 1. A title defined in the [nav](https://www.mkdocs.org/user-guide/configuration/#nav) configuration setting for a document.
> 2. A title defined in the title meta-data key of a document.
> 3. A level 1 Markdown header on the first line of the document body.
>     ([Setext-style](https://daringfireball.net/projects/markdown/syntax#header) headers are supported only since MkDocs 1.5.)
> 4. The filename of a document.

Discussion:
@sblausten: This would be great. It is very painful to have to go around adding h1s to docs to get them to work. @dtuite: Agreed. It's a pain to add these h1's after the fact.@timvink: This one is tricky. The most common 'mistake' I've seen is that people use a secondary level heading as the first heading on a page, like so:

```
## title

content
```

Then other pages have normal titles again like so:

```
# title

content
```

When you combine these pages, the level of the title is not identical, which really messes up the layout and the TOC (the small one on the right):

<img width="907" alt="image" src="https://github.com/timvink/mkdocs-print-site-plugin/assets/5570380/7e1ab20f-14d8-4725-964d-a4a53e4f1933">

Hence the logic to detect this and raise an error (also deals with the edge case that there are pages that have no title at all, because some users indeed it to be part of the previous page when the entire site is combined into a single page, or because they just didn't want a title).

https://github.com/timvink/mkdocs-print-site-plugin/blob/0a7300b251929d0c06d5f719a741525cf0ec26f8/mkdocs_print_site_plugin/plugin.py#L219-L230

You propose using the mkdocs behaviour of page titles, which is actually just using `page.title`. I went down the rabbit hole and I think I have a fix. The new behavior is to raise a warning instead of an error (which will become an error for users that use `mkdocs build --strict`)

![image](https://github.com/timvink/mkdocs-print-site-plugin/assets/5570380/ba476c68-a2cd-4d66-b198-4773d41da771)

The unit tests pass.. but before I release it I'd appreciate if someone could give it a spin for their use case. Maybe @sblausten or @dtuite ?  You can install it form the pull request branch: https://github.com/timvink/mkdocs-print-site-plugin/pull/104

```bash
pip install git+https://github.com/timvink/mkdocs-print-site-plugin.git@support_missing_h1
```

If it works I'll merge and release on pypi.
 




Solution patch:
diff --git a/docs/how-to/export-HTML.md b/docs/how-to/export-HTML.md
index ea2331c..9ddbce9 100644
--- a/docs/how-to/export-HTML.md
+++ b/docs/how-to/export-HTML.md
@@ -1,20 +1,20 @@
 # Export to HTML
 
-After enabling the `print-site` plugin in your `mkdocs.yml`, you will have your entire site combined into a single page. That allows you to create a standalone HTML page: a single self-contained file that has all images, styling and scripts embedded. This means you could send a site as an email attachment, a use case common within companies where deploying static sites might be more involved.
+After enabling the `print-site` plugin in your `mkdocs.yml`, you will have your entire site combined into a single page. 
 
-In order to create a self-contained, standalone HTML file from the print page, we will need to embed images, CSS and javascript using [data URLs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs). We can do this quite easily using the [htmlark](https://github.com/BitLooter/htmlark) python package:
+That allows you to create a standalone HTML page: a single self-contained file that has all images, styling and scripts embedded. This means you could send a site as an email attachment, a use case common within companies where deploying static sites might be more involved.
+This works because all the resources the page uses, such images, stylesheets (CSS) and interactive elements (javascript) are embedded into in a single HTML file using [data URLs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs).
 
+You can create a .html file export using your internet browser (f.e. save as > webpage, single page). You can also do this programmatically, for example using the [htmlark](https://github.com/BitLooter/htmlark) python package:
 
 ```shell
-pip install http html5lib requests
-pip install htmlark
+pip install htmlark[http,parsers]
 ```
 
 To create the export:
 
 ```shell
 mkdocs build
-
 cd site/
 
 # when mkdocs.yml has use_directory_urls: true (the default)
diff --git a/mkdocs_print_site_plugin/plugin.py b/mkdocs_print_site_plugin/plugin.py
index 1820828..51f7f1a 100644
--- a/mkdocs_print_site_plugin/plugin.py
+++ b/mkdocs_print_site_plugin/plugin.py
@@ -216,19 +216,6 @@ class PrintSitePlugin(BasePlugin):
         if page != self.print_page:
             page.html = html
 
-        # We need to validate that the first heading on each page is a h1
-        # This is required for the print page table of contents and enumeration logic
-        if self.config.get("add_table_of_contents") or self.config.get(
-            "enumerate_headings"
-        ):
-            if page in self.all_pages_in_nav:
-                match = re.search(r"\<h[0-6]", html)
-                if match:
-                    if not match.group() == "<h1":
-                        msg = f"The page {page.title} ({page.file.src_path}) does not start with a level 1 heading."
-                        msg += "This is required for print page Table of Contents and/or enumeration of headings."
-                        raise AssertionError(msg)
-
         # Link to the PDF version of the entire site on a page.
         if self.config.get("path_to_pdf") != "":
             pdf_url = self.config.get("path_to_pdf")
diff --git a/mkdocs_print_site_plugin/renderer.py b/mkdocs_print_site_plugin/renderer.py
index c6546ff..75d5940 100644
--- a/mkdocs_print_site_plugin/renderer.py
+++ b/mkdocs_print_site_plugin/renderer.py
@@ -76,7 +76,7 @@ class Renderer(object):
             """
             Get all the HTML from the pages.
             """
-            item_html = ""
+            items_html = ""
 
             for item in items:
                 if item.is_page:
@@ -94,13 +94,31 @@ class Renderer(object):
                                 % item.file.src_path
                             )
                             continue
+
+                        item_html = item.html
+                        
+                        # Add missing h1 tag if it doesn't exist
+                        if not item_html.startswith("<h1"):
+                            item_html = f"<h1 id=\"{to_snake_case(item.title)}\">{item.title}</h1>{item_html}"
+                            logger.warning(f"[mkdocs-print-site] '{item.file.src_path}' file is missing a leading h1 tag. Added to the print-page with title '{item.title}'")
+                        
+                        # Support mkdocs-material tags
+                        # See https://squidfunk.github.io/mkdocs-material/plugins/tags
+                        if "tags" in item.meta:
+                            tags = item.meta["tags"]
+                            tags_html = "<nav class='md-tags'>"
+                            for tag in tags:
+                                tags_html += f"<span class='md-tag'>{tag}</span>"
+                            tags_html += "</nav>"
+                            item_html = tags_html + item_html
+                        
                         # Update internal anchor links, image urls, etc
-                        item_html += fix_internal_links(
-                            item.html, item.url, directory_urls=dir_urls
+                        items_html += fix_internal_links(
+                            item_html, item.url, directory_urls=dir_urls
                         )
 
                 if item.is_section:
-                    item_html += """
+                    items_html += """
                         <h%s class='nav-section-title' id='section-%s'>
                             %s <a class='headerlink' href='#section-%s' title='Permanent link'>↵</a>
                         </h%s>
@@ -111,16 +129,16 @@ class Renderer(object):
                         to_snake_case(item.title),
                         min(6, section_depth + 1),
                     )
-                    item_html += get_html_from_items(
+                    items_html += get_html_from_items(
                         item.children, dir_urls, excluded_pages, section_depth + 1
                     )
                     # We also need to indicate the end of section page
                     # We do that using a h1 with a specific class
                     # In CSS we display:none, in JS we can use it for formatting the table of contents.
-                    item_html += (
+                    items_html += (
                         "<h1 class='nav-section-title-end'>Ended: %s</h1>" % item.title
                     )
-            return item_html
+            return items_html
 
         html += get_html_from_items(
             self._get_items(),
diff --git a/setup.py b/setup.py
index e562e3a..e64a683 100644
--- a/setup.py
+++ b/setup.py
@@ -9,7 +9,7 @@ with open("README.md", "r", encoding="UTF-8") as f:
 
 setup(
     name="mkdocs-print-site-plugin",
-    version="2.4.1",
+    version="2.5.0",
     description="MkDocs plugin that combines all pages into one, allowing for easy export to PDF and standalone HTML.",
     long_description=long_description,
     long_description_content_type="text/markdown",


Test patch:
diff --git a/tests/fixtures/projects/mkdocs_material_tags/docs/index.md b/tests/fixtures/projects/mkdocs_material_tags/docs/index.md
new file mode 100644
index 0000000..8903870
--- /dev/null
+++ b/tests/fixtures/projects/mkdocs_material_tags/docs/index.md
@@ -0,0 +1,10 @@
+---
+tags:
+  - HTML5
+  - JavaScript
+  - CSS
+---
+
+# Hello there
+
+Testing [tags](https://squidfunk.github.io/mkdocs-material/plugins/tags/?h=tags#usage) plugin.
\ No newline at end of file
diff --git a/tests/fixtures/projects/mkdocs_material_tags/mkdocs.yml b/tests/fixtures/projects/mkdocs_material_tags/mkdocs.yml
new file mode 100644
index 0000000..939d653
--- /dev/null
+++ b/tests/fixtures/projects/mkdocs_material_tags/mkdocs.yml
@@ -0,0 +1,12 @@
+site_name: Test
+
+theme:
+  name: material
+
+plugins:
+    - tags
+    - print-site:
+        add_to_navigation: true
+  
+markdown_extensions:
+    - attr_list
\ No newline at end of file
diff --git a/tests/test_building.py b/tests/test_building.py
index 10f6c94..4c1b7bf 100644
--- a/tests/test_building.py
+++ b/tests/test_building.py
@@ -269,10 +269,17 @@ def test_basic_build6(tmp_path):
 
 def test_basic_build7(tmp_path):
     """
-    Test error when page does not start with h1 heading.
+    Test when page does not start with h1 heading.
+
+    As of v2.5.0, this is allowed (the plugin will add a heading to the print page)
     """
-    check_build(tmp_path, "bad_headings/mkdocs.yml", exit_code=1)
+    check_build(tmp_path, "bad_headings/mkdocs.yml", exit_code=0)
 
+def test_build_with_material_tags(tmp_path):
+    """
+    Test support with tags.
+    """
+    check_build(tmp_path, "mkdocs_material_tags/mkdocs.yml", exit_code=0)
 
 def test_basic_disable_plugin(tmp_path):
     """