def test_get_relative_url(self): to_files = [ 'index.md', 'foo/index.md', 'foo/bar/index.md', 'foo/bar/baz/index.md', 'foo.md', 'foo/bar.md', 'foo/bar/baz.md' ] to_file_urls = [ 'index.html', 'foo/index.html', 'foo/bar/index.html', 'foo/bar/baz/index.html', 'foo.html', 'foo/bar.html', 'foo/bar/baz.html' ] from_file = File('img.jpg', '/path/to/docs', '/path/to/site', use_directory_urls=False) expected = [ 'img.jpg', # img.jpg relative to . '../img.jpg', # img.jpg relative to foo/ '../../img.jpg', # img.jpg relative to foo/bar/ '../../../img.jpg', # img.jpg relative to foo/bar/baz/ 'img.jpg', # img.jpg relative to foo.html '../img.jpg', # img.jpg relative to foo/bar.html '../../img.jpg' # img.jpg relative to foo/bar/baz.html ] for i, filename in enumerate(to_files): file = File(filename, '/path/to/docs', '/path/to/site', use_directory_urls=False) self.assertEqual(from_file.url, 'img.jpg') self.assertEqual(file.url, to_file_urls[i]) self.assertEqual(from_file.url_relative_to(file.url), expected[i]) self.assertEqual(from_file.url_relative_to(file), expected[i]) from_file = File('foo/img.jpg', '/path/to/docs', '/path/to/site', use_directory_urls=False) expected = [ 'foo/img.jpg', # foo/img.jpg relative to . 'img.jpg', # foo/img.jpg relative to foo/ '../img.jpg', # foo/img.jpg relative to foo/bar/ '../../img.jpg', # foo/img.jpg relative to foo/bar/baz/ 'foo/img.jpg', # foo/img.jpg relative to foo.html 'img.jpg', # foo/img.jpg relative to foo/bar.html '../img.jpg' # foo/img.jpg relative to foo/bar/baz.html ] for i, filename in enumerate(to_files): file = File(filename, '/path/to/docs', '/path/to/site', use_directory_urls=False) self.assertEqual(from_file.url, 'foo/img.jpg') self.assertEqual(file.url, to_file_urls[i]) self.assertEqual(from_file.url_relative_to(file.url), expected[i]) self.assertEqual(from_file.url_relative_to(file), expected[i]) from_file = File('index.html', '/path/to/docs', '/path/to/site', use_directory_urls=False) expected = [ 'index.html', # index.html relative to . '../index.html', # index.html relative to foo/ '../../index.html', # index.html relative to foo/bar/ '../../../index.html', # index.html relative to foo/bar/baz/ 'index.html', # index.html relative to foo.html '../index.html', # index.html relative to foo/bar.html '../../index.html' # index.html relative to foo/bar/baz.html ] for i, filename in enumerate(to_files): file = File(filename, '/path/to/docs', '/path/to/site', use_directory_urls=False) self.assertEqual(from_file.url, 'index.html') self.assertEqual(file.url, to_file_urls[i]) self.assertEqual(from_file.url_relative_to(file.url), expected[i]) self.assertEqual(from_file.url_relative_to(file), expected[i]) from_file = File('file.html', '/path/to/docs', '/path/to/site', use_directory_urls=False) expected = [ 'file.html', # file.html relative to . '../file.html', # file.html relative to foo/ '../../file.html', # file.html relative to foo/bar/ '../../../file.html', # file.html relative to foo/bar/baz/ 'file.html', # file.html relative to foo.html '../file.html', # file.html relative to foo/bar.html '../../file.html' # file.html relative to foo/bar/baz.html ] for i, filename in enumerate(to_files): file = File(filename, '/path/to/docs', '/path/to/site', use_directory_urls=False) self.assertEqual(from_file.url, 'file.html') self.assertEqual(file.url, to_file_urls[i]) self.assertEqual(from_file.url_relative_to(file.url), expected[i]) self.assertEqual(from_file.url_relative_to(file), expected[i])
class PrintSitePlugin(BasePlugin): """ MkDocs Plugin class for combining all site pages into a single page. """ config_scheme = ( ("add_to_navigation", config_options.Type(bool, default=False)), ("print_page_title", config_options.Type(str, default="Print Site")), ("add_table_of_contents", config_options.Type(bool, default=True)), ("toc_title", config_options.Type(str, default="Table of Contents")), ("toc_depth", config_options.Type(int, default=3)), ("add_full_urls", config_options.Type(bool, default=False)), ("enumerate_headings", config_options.Type(bool, default=True)), ("enumerate_headings_depth", config_options.Type(int, default=6)), ("enumerate_figures", config_options.Type(bool, default=True)), ("add_cover_page", config_options.Type(bool, default=False)), ("cover_page_template", config_options.Type(str, default="")), ("add_print_site_banner", config_options.Type(bool, default=False)), ("print_site_banner_template", config_options.Type(str, default="")), ("path_to_pdf", config_options.Type(str, default="")), ("include_css", config_options.Type(bool, default=True)), ("enabled", config_options.Type(bool, default=True)), ("exclude", config_options.Type(list, default=[])), ) def on_config(self, config, **kwargs): """ Event trigger on config. See https://www.mkdocs.org/user-guide/plugins/#on_config. """ if not self.config.get("enabled"): return config # Check valid table of contents depth assert self.config.get("toc_depth") >= 1 assert self.config.get("toc_depth") <= 6 assert self.config.get("enumerate_headings_depth") >= 1 assert self.config.get("enumerate_headings_depth") <= 6 # Because other plugins can alter the navigation # (and thus which pages should be in the print page) # it is important 'print-site' is defined last in the 'plugins' plugins = config.get("plugins") print_site_position = [*dict(plugins)].index("print-site") if print_site_position != len(plugins) - 1: msg = "[mkdocs-print-site] 'print-site' should be defined as the *last* plugin," msg += "to ensure the print page has any changes other plugins make." msg += "Please update the 'plugins:' section in your mkdocs.yml" logger.warning(msg) if "--dirtyreload" in sys.argv: msg = ( "[mkdocs-print-site] Note the 'print-site' page does render all pages " ) msg += "when using the --dirtyreload option." logger.warning(msg) # Get abs path to cover_page_template self.cover_page_template_path = "" if self.config.get("add_cover_page"): if self.config.get("cover_page_template") == "": self.cover_page_template_path = os.path.join( HERE, "templates", "cover_page.tpl") else: self.cover_page_template_path = os.path.join( os.path.dirname(config.get("config_file_path")), self.config.get("cover_page_template"), ) if not os.path.exists(self.cover_page_template_path): msg = "[print-site-plugin]: Path specified in 'cover_page_template' not found." msg += "\nMake sure to use the URL relative to your mkdocs.yml file." logger.warning(msg) raise FileNotFoundError("File not found: %s" % self.cover_page_template_path) # Get abs path to print_site_banner_template self.banner_template_path = "" if self.config.get("add_print_site_banner"): if self.config.get("print_site_banner_template") == "": self.banner_template_path = os.path.join( HERE, "templates", "print_site_banner.tpl") else: self.banner_template_path = os.path.join( os.path.dirname(config.get("config_file_path")), self.config.get("print_site_banner_template"), ) if not os.path.exists(self.banner_template_path): msg = "[print-site-plugin]: Path specified in 'print_site_banner_template' not found." msg += "\nMake sure to use the URL relative to your mkdocs.yml file." logger.warning(msg) raise FileNotFoundError("File not found: %s" % self.banner_template_path) # Add pointer to print-site javascript config["extra_javascript"] = ["js/print-site.js" ] + config["extra_javascript"] # Add pointer to theme specific css files if self.config.get("include_css"): file = "print-site-%s.css" % get_theme_name(config) if file in os.listdir(os.path.join(HERE, "css")): config["extra_css"] = ["css/%s" % file] + config["extra_css"] else: msg = f"[mkdocs-print-site] Theme '{get_theme_name(config)}' not yet supported\n" msg += "which means print margins and page breaks might be off. Feel free to open an issue!" logger.warning(msg) # Add pointer to print-site css files config["extra_css"] = ["css/print-site.css"] + config["extra_css"] # Enumeration CSS files self.enum_css_files = [] if self.config.get('enumerate_headings'): self.enum_css_files += ["css/print-site-enum-headings1.css"] if self.config.get('enumerate_headings_depth') >= 2: self.enum_css_files += ["css/print-site-enum-headings2.css"] if self.config.get('enumerate_headings_depth') >= 3: self.enum_css_files += ["css/print-site-enum-headings3.css"] if self.config.get('enumerate_headings_depth') >= 4: self.enum_css_files += ["css/print-site-enum-headings4.css"] if self.config.get('enumerate_headings_depth') >= 5: self.enum_css_files += ["css/print-site-enum-headings5.css"] if self.config.get('enumerate_headings_depth') >= 6: self.enum_css_files += ["css/print-site-enum-headings6.css"] config["extra_css"] = self.enum_css_files + config["extra_css"] # Create MkDocs Page and File instances self.print_file = File( path="print_page.md", src_dir="", dest_dir=config["site_dir"], use_directory_urls=config.get("use_directory_urls"), ) self.print_page = Page( title=self.config.get("print_page_title"), file=self.print_file, config=config, ) self.print_page.edit_url = None # Save instance of the print page renderer self.renderer = Renderer( plugin_config=self.config, mkdocs_config=config, cover_page_template_path=self.cover_page_template_path, banner_template_path=self.banner_template_path, print_page=self.print_page, ) # Tracker # to see if context has been extracted from # template context self.context = {} return config def on_nav(self, nav, config, files, **kwargs): """ The nav event is called after the site navigation is created. Can be used to alter the site navigation. See https://www.mkdocs.org/user-guide/plugins/#on_nav. """ if not self.config.get("enabled"): return nav # Save the (order of) pages and sections in the navigation before adding the print page self.renderer.items = nav.items self.all_pages_in_nav = flatten_nav(nav.items) # Optionally add the print page to the site navigation if self.config.get("add_to_navigation"): nav.items.append(self.print_page) nav.pages.append(self.print_page) return nav def on_page_content(self, html, page, config, files, **kwargs): """ The page_content event is called after the Markdown text is rendered to HTML. (but before being passed to a template) and can be used to alter the HTML body of the page. See https://www.mkdocs.org/user-guide/plugins/#on_page_content. """ if not self.config.get("enabled"): return html # Save each page HTML *before* a template is applied inside the page class 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") != "": page.url_to_pdf = get_relative_url(self.config.get("path_to_pdf"), page.file.url) return html def on_page_context(self, context, page, config, nav, **kwargs): """ The page_context event is called after the context for a page is created. It can be used to alter the context for that specific page only. See https://www.mkdocs.org/user-guide/plugins/#on_page_context. """ if not self.config.get("enabled"): return # Save relative link to print page # This can be used to customize a theme and add a print button to each page page.url_to_print_page = self.print_file.url_relative_to(page.file) def on_template_context(self, context, template_name, config, **kwargs): """ The template_context event is called immediately after the context is created for the subject template and can be used to alter the context for that specific template only. See https://www.mkdocs.org/dev-guide/plugins/#on_template_context """ if not self.config.get("enabled"): return # Save the page context # We'll use the same context of the last rendered page # And apply it to the print page as well (in on_post_build event) # Note a theme can have multiple templates # Found a bug where in the mkdocs theme, # the "sitemap.xml" static template # has incorrect 'extra_css' and 'extra_js' paths # leading to breaking the print page # at random (when sitemap.xml was rendered last) # we're assuming here all templates have a 404.html template # print(f"\nName: {template_name}\nContext: {context.get('extra_css')}") if template_name == "404.html": self.context = context # Make sure paths are OK if config.get('extra_css'): self.context['extra_css'] = [ get_relative_url(f, self.print_page.file.url) for f in config.get('extra_css') ] if config.get('extra_javascript'): self.context['extra_javascript'] = [ get_relative_url(f, self.print_page.file.url) for f in config.get('extra_javascript') ] def on_post_build(self, config, **kwargs): """ The post_build event does not alter any variables. Use this event to call post-build scripts. See https://www.mkdocs.org/user-guide/plugins/#on_post_build. """ if not self.config.get("enabled"): return if len(self.context) == 0: msg = "Could not find a template context.\n" msg += "Report an issue at https://github.com/timvink/mkdocs-print-site-plugin\n" msg += f"And mention the template you're using: {get_theme_name(config)}" raise PluginError(msg) # Add print-site.js js_output_base_path = os.path.join(config["site_dir"], "js") js_file_path = os.path.join(js_output_base_path, "print-site.js") copy_file(os.path.join(os.path.join(HERE, "js"), "print-site.js"), js_file_path) if self.config.get("include_css"): # Add print-site.css css_output_base_path = os.path.join(config["site_dir"], "css") css_file_path = os.path.join(css_output_base_path, "print-site.css") copy_file( os.path.join(os.path.join(HERE, "css"), "print-site.css"), css_file_path) # Add enumeration css for f in self.enum_css_files: f = f.replace("/", os.sep) css_file_path = os.path.join(config["site_dir"], f) copy_file(os.path.join(HERE, f), css_file_path) # Add theme CSS file css_file = "print-site-%s.css" % get_theme_name(config) if css_file in os.listdir(os.path.join(HERE, "css")): css_file_path = os.path.join(css_output_base_path, css_file) copy_file(os.path.join(os.path.join(HERE, "css"), css_file), css_file_path) # Combine the HTML of all pages present in the navigation self.print_page.content = self.renderer.write_combined() # Generate a TOC sidebar for HTML version of print page self.print_page.toc = self.renderer.get_toc_sidebar() # Get the info for MkDocs to be able to apply a theme template on our print page env = config["theme"].get_env() # env.list_templates() template = env.get_template("main.html") self.context["page"] = self.print_page # Render the theme template for the print page html = template.render(self.context) # Remove lazy loading attributes from images # https://regex101.com/r/HVpKPs/1 html = re.sub(r"(\<img.+)(loading=\"lazy\")", r"\1", html) # Compatiblity with mkdocs-chart-plugin # As this plugin adds some javascript to every page # It should be included in the print site also if config.get("plugins", {}).get("charts"): html = (config.get("plugins", {}).get("charts").add_javascript_variables( html, self.print_page, config)) # Compatibility with https://github.com/g-provost/lightgallery-markdown # This plugin insert link hrefs with double dashes, f.e. # <link href="//assets/css/somecss.css"> # Details https://github.com/timvink/mkdocs-print-site-plugin/issues/68 htmls = html.split("</head>") base_url = "../" if config.get("use_directory_urls") else "" htmls[0] = htmls[0].replace("href=\"//", f"href=\"{base_url}") htmls[0] = htmls[0].replace("src=\"//", f"src=\"{base_url}") html = "</head>".join(htmls) # Determine calls to required javascript functions js_calls = "remove_material_navigation();" js_calls += "remove_mkdocs_theme_navigation();" if self.config.get("add_table_of_contents"): js_calls += "generate_toc();" # Inject JS into print page print_site_js = (""" <script type="text/javascript"> document.addEventListener('DOMContentLoaded', function () { %s }) </script> """ % js_calls) html = html.replace("</head>", print_site_js + "</head>") # Write the print_page file to the output folder write_file( html.encode("utf-8", errors="xmlcharrefreplace"), self.print_page.file.abs_dest_path, )
class PrintSitePlugin(BasePlugin): """ MkDocs Plugin class for combining all site pages into a single page. """ config_scheme = ( ("add_to_navigation", config_options.Type(bool, default=True)), ("print_page_title", config_options.Type(str, default="Print Site")), ("add_table_of_contents", config_options.Type(bool, default=True)), ("toc_title", config_options.Type(str, default="Table of Contents")), ("toc_depth", config_options.Type(int, default=6)), ("add_full_urls", config_options.Type(bool, default=False)), ("enumerate_headings", config_options.Type(bool, default=False)), ("enumerate_figures", config_options.Type(bool, default=False)), ("add_cover_page", config_options.Type(bool, default=False)), ("cover_page_template", config_options.Type(str, default="")), ("add_print_site_banner", config_options.Type(bool, default=True)), ("print_site_banner_template", config_options.Type(str, default="")), ("path_to_pdf", config_options.Type(str, default="")), ("exclude", config_options.Type(list, default=[])), ) def on_config(self, config, **kwargs): """ Event trigger on config. See https://www.mkdocs.org/user-guide/plugins/#on_config. """ # Check valid table of contents depth assert self.config.get("toc_depth") >= 1 assert self.config.get("toc_depth") <= 6 # Because other plugins can alter the navigation # (and thus which pages should be in the print page) # it is important 'print-site' is defined last in the 'plugins' plugins = config.get("plugins") print_site_position = [*dict(plugins)].index("print-site") if print_site_position != len(plugins) - 1: msg = "[mkdocs-print-site] 'print-site' should be defined as the *last* plugin," msg += "to ensure the print page has any changes other plugins make." msg += "Please update the 'plugins:' section in your mkdocs.yml" logger.warning(msg) # Get abs path to cover_page_template self.cover_page_template_path = "" if self.config.get("add_cover_page"): if self.config.get("cover_page_template") == "": self.cover_page_template_path = os.path.join( HERE, "templates", "cover_page.tpl") else: self.cover_page_template_path = os.path.join( os.path.dirname(config.get("config_file_path")), self.config.get("cover_page_template"), ) if not os.path.exists(self.cover_page_template_path): msg = "[print-site-plugin]: Path specified in 'cover_page_template' not found." msg += "\nMake sure to use the URL relative to your mkdocs.yml file." logger.warning(msg) raise FileNotFoundError("File not found: %s" % self.cover_page_template_path) # Get abs path to print_site_banner_template self.banner_template_path = "" if self.config.get("add_print_site_banner"): if self.config.get("print_site_banner_template") == "": self.banner_template_path = os.path.join( HERE, "templates", "print_site_banner.tpl") else: self.banner_template_path = os.path.join( os.path.dirname(config.get("config_file_path")), self.config.get("print_site_banner_template"), ) if not os.path.exists(self.banner_template_path): msg = "[print-site-plugin]: Path specified in 'print_site_banner_template' not found." msg += "\nMake sure to use the URL relative to your mkdocs.yml file." logger.warning(msg) raise FileNotFoundError("File not found: %s" % self.banner_template_path) # Add pointer to print-site javascript config["extra_javascript"] = ["js/print-site.js" ] + config["extra_javascript"] config["extra_javascript"] = ["js/print-site-instant-loading.js" ] + config["extra_javascript"] # Add pointer to theme specific css files file = "print-site-%s.css" % get_theme_name(config) if file in os.listdir(os.path.join(HERE, "css")): config["extra_css"] = ["css/%s" % file] + config["extra_css"] else: msg = f"[mkdocs-print-site] Theme '{get_theme_name(config)}' not yet supported\n" msg += "which means print margins and page breaks might be off. Feel free to open an issue!" logger.warning(msg) # Add pointer to print-site css files config["extra_css"] = ["css/print-site.css"] + config["extra_css"] # Create MkDocs Page and File instances self.print_file = File( path="print_page.md", src_dir="", dest_dir=config["site_dir"], use_directory_urls=config.get("use_directory_urls"), ) self.print_page = Page( title=self.config.get("print_page_title"), file=self.print_file, config=config, ) self.print_page.edit_url = None # Save instance of the print page renderer self.renderer = Renderer( plugin_config=self.config, mkdocs_config=config, cover_page_template_path=self.cover_page_template_path, banner_template_path=self.banner_template_path, print_page=self.print_page, ) return config def on_nav(self, nav, config, files, **kwargs): """ The nav event is called after the site navigation is created. Can be used to alter the site navigation. See https://www.mkdocs.org/user-guide/plugins/#on_nav. """ # Save the (order of) pages and sections in the navigation before adding the print page self.renderer.items = nav.items # Optionally add the print page to the site navigation if self.config.get("add_to_navigation"): nav.items.append(self.print_page) nav.pages.append(self.print_page) return nav def on_page_content(self, html, page, config, files, **kwargs): """ The page_content event is called after the Markdown text is rendered to HTML. (but before being passed to a template) and can be used to alter the HTML body of the page. See https://www.mkdocs.org/user-guide/plugins/#on_page_content. """ # Save each page HTML *before* a template is applied inside the page class if page != self.print_page: page.html = html # Link to the PDF version of the entire site on a page. if self.config.get("path_to_pdf") != "": page.url_to_pdf = get_relative_url(self.config.get("path_to_pdf"), page.file.url) return html def on_page_context(self, context, page, config, nav): """ The page_context event is called after the context for a page is created. It can be used to alter the context for that specific page only. See https://www.mkdocs.org/user-guide/plugins/#on_page_context. """ # Save the page context # We'll use the same context of the last rendered page # And apply it to the print page as well (in on_post_build event) self.context = context # Save relative link to print page # This can be used to customize a theme and add a print button to each page page.url_to_print_page = self.print_file.url_relative_to(page.file) def on_post_build(self, config): """ The post_build event does not alter any variables. Use this event to call post-build scripts. See https://www.mkdocs.org/user-guide/plugins/#on_post_build. """ # Add print-site.js js_output_base_path = os.path.join(config["site_dir"], "js") js_file_path = os.path.join(js_output_base_path, "print-site.js") copy_file(os.path.join(os.path.join(HERE, "js"), "print-site.js"), js_file_path) # Add print-site.css css_output_base_path = os.path.join(config["site_dir"], "css") css_file_path = os.path.join(css_output_base_path, "print-site.css") copy_file(os.path.join(os.path.join(HERE, "css"), "print-site.css"), css_file_path) # Add theme CSS file css_file = "print-site-%s.css" % get_theme_name(config) if css_file in os.listdir(os.path.join(HERE, "css")): css_file_path = os.path.join(css_output_base_path, css_file) copy_file(os.path.join(os.path.join(HERE, "css"), css_file), css_file_path) # Determine calls to required javascript functions js_calls = "" if self.config.get("add_table_of_contents"): js_calls += "generate_toc();" # Add JS file for compatibility for mkdocs-material instant loading # note this is inserted to all mkdocs pages, # because each page can be the start of instant loading session js_instant_loading = (""" // Subscribe functions for compatibility // with mkdocs-material's instant loading feature if ( typeof app !== "undefined" && typeof app.document$ !== "undefined" ) { app.document$.subscribe(function() { if ( document.querySelector("#print-site-page") !== null ) { %s } }) } """ % js_calls) write_file( js_instant_loading.encode("utf-8", errors="xmlcharrefreplace"), os.path.join(js_output_base_path, "print-site-instant-loading.js"), ) # Combine the HTML of all pages present in the navigation self.print_page.content = self.renderer.write_combined() # Get the info for MkDocs to be able to apply a theme template on our print page env = config["theme"].get_env() template = env.get_template("main.html") self.context["page"] = self.print_page # Render the theme template for the print page html = template.render(self.context) # Inject JS into print page print_site_js = (""" <script type="text/javascript"> window.addEventListener('load', function () { %s }) </script> """ % js_calls) html = html.replace("</body>", print_site_js + "</body>") # Write the print_page file to the output folder write_file( html.encode("utf-8", errors="xmlcharrefreplace"), self.print_page.file.abs_dest_path, )
class PrintSitePlugin(BasePlugin): config_scheme = ( ("add_to_navigation", config_options.Type(bool, default=True)), ("print_page_title", config_options.Type(str, default="Print Site")), ("add_table_of_contents", config_options.Type(bool, default=True)), ("add_full_urls", config_options.Type(bool, default=False)), ("enumerate_headings", config_options.Type(bool, default=False)), ("enumerate_figures", config_options.Type(bool, default=False)), ("add_cover_page", config_options.Type(bool, default=False)), ("cover_page_template", config_options.Type(str, default="")), ("path_to_pdf", config_options.Type(str, default="")), ) def on_config(self, config, **kwargs): # Because other plugins can alter the navigation # (and thus which pages should be in the print page) # it is important 'print-site' is defined last in the 'plugins' plugins = config.get("plugins") print_site_position = [*dict(plugins)].index("print-site") if print_site_position != len(plugins) - 1: logger.warning( "[mkdocs-print-site] 'print-site' should be defined as the *last* plugin, to ensure the print page has any changes other plugins make. Please update the 'plugins:' section in your mkdocs.yml" ) # Get abs path to cover_page_template self.cover_page_template_path = "" if self.config.get("add_cover_page"): if self.config.get("cover_page_template") == "": self.cover_page_template_path = os.path.join( HERE, "templates", "cover_page.tpl") else: self.cover_page_template_path = os.path.join( os.path.dirname(config.get("config_file_path")), self.config.get("cover_page_template"), ) if not os.path.exists(self.cover_page_template_path): logger.warning( "[print-site-plugin]: Path specified in 'cover_page_template' not found. Make sure to use the URL relative to your mkdocs.yml file." ) raise FileNotFoundError("File not found: %s" % self.cover_page_template_path) # Create the (empty) print page file in temp directory tmp_dir = tempfile.gettempdir() tmp_path = os.path.join(tmp_dir, "print_page.md") f = open(tmp_path, "w") f.write("") f.close() assert os.path.exists(tmp_path) # Add pointer to print-site javascript config["extra_javascript"] = ["js/print-site.js" ] + config["extra_javascript"] config["extra_javascript"] = ["js/print-site-instant-loading.js" ] + config["extra_javascript"] # Add pointer to print-site css files config["extra_css"] = ["css/print-site.css"] + config["extra_css"] # Add pointer to theme specific css files file = "print-site-%s.css" % config.get("theme").name if file in os.listdir(os.path.join(HERE, "css")): config["extra_css"] = ["css/%s" % file] + config["extra_css"] else: logger.warning( "[mkdocs-print-site] Theme '%s' not yet supported, which means print margins and page breaks might be off. Feel free to open an issue!" % config.get("theme").name) # Create MkDocs Page and File instances self.print_file = File( path="print_page.md", src_dir=tmp_dir, dest_dir=config["site_dir"], use_directory_urls=config.get("use_directory_urls"), ) self.print_page = Page( title=self.config.get("print_page_title"), file=self.print_file, config=config, ) self.print_page.edit_url = None # Save instance of the print page renderer self.renderer = Renderer( plugin_config=self.config, mkdocs_config=config, cover_page_template_path=self.cover_page_template_path, print_page=self.print_page, ) return config def on_nav(self, nav, config, files, **kwargs): # Save the (order of) pages in the navigation before adding the print page self.renderer.pages = nav.pages.copy() # nav_pages # Optionally add the print page to the site navigation if self.config.get("add_to_navigation"): nav.items.append(self.print_page) nav.pages.append(self.print_page) return nav def on_page_content(self, html, page, config, files, **kwargs): # Save each page HTML *before* a template is applied inside the page class if page != self.print_page: page.html = html if self.config.get("path_to_pdf") != "": page.url_to_pdf = get_relative_url(self.config.get("path_to_pdf"), page.file.url) return html def on_page_context(self, context, page, config, nav): # Save the page context # We'll use the same context of the last rendered page # And apply it to the print page as well (in on_post_build event) self.context = context # Save relative link to print page # This can be used to customize a theme and add a print button to each page page.url_to_print_page = self.print_file.url_relative_to(page.file) def on_post_build(self, config): # Add print-site.js js_output_base_path = os.path.join(config["site_dir"], "js") js_file_path = os.path.join(js_output_base_path, "print-site.js") copy_file(os.path.join(os.path.join(HERE, "js"), "print-site.js"), js_file_path) # Add print-site.css css_output_base_path = os.path.join(config["site_dir"], "css") css_file_path = os.path.join(css_output_base_path, "print-site.css") copy_file(os.path.join(os.path.join(HERE, "css"), "print-site.css"), css_file_path) # Add theme CSS file css_file = "print-site-%s.css" % config.get("theme").name if css_file in os.listdir(os.path.join(HERE, "css")): css_file_path = os.path.join(css_output_base_path, css_file) copy_file(os.path.join(os.path.join(HERE, "css"), css_file), css_file_path) # Determine calls to required javascript functions js_calls = "" if config.get("theme").name == "material": js_calls += "change_material_theme('default');" if self.config.get("add_table_of_contents"): js_calls += "generate_toc();" # Add JS file for compatibility for mkdocs-material instant loading # note this is inserted to all mkdocs pages, # because each page can be the start of instant loading session js_instant_loading = (""" // Subscribe functions for compatibility // with mkdocs-material's instant loading feature body = document.getElementsByTagName('body')[0]; mkdocs_material_site_color_theme = body.getAttribute('data-md-color-scheme'); if ( typeof app !== "undefined" && typeof app.document$ !== "undefined" ) { app.document$.subscribe(function() { if ( document.querySelector("#print-site-page") !== null ) { %s } else { // Make sure to change the color theme back! if ( mkdocs_material_site_color_theme !== null ) { change_material_theme(mkdocs_material_site_color_theme); } } }) } """ % js_calls) write_file( js_instant_loading.encode("utf-8", errors="xmlcharrefreplace"), os.path.join(js_output_base_path, "print-site-instant-loading.js"), ) # Combine the HTML of all pages present in the navigation self.print_page.content = self.renderer.write_combined() # Get the info for MkDocs to be able to apply a theme template on our print page env = config["theme"].get_env() template = env.get_template("main.html") self.context["page"] = self.print_page # Render the theme template for the print page html = template.render(self.context) # Inject JS into print page print_site_js = (""" <script type="text/javascript"> window.addEventListener('load', function () { %s }) </script> """ % js_calls) html = html.replace("</body>", print_site_js + "</body>") # Write the print_page file to the output folder write_file( html.encode("utf-8", errors="xmlcharrefreplace"), self.print_page.file.abs_dest_path, )