diff --git a/material/plugins/blog/plugin.py b/material/plugins/blog/plugin.py index 6ae2c2c84..081f190ba 100644 --- a/material/plugins/blog/plugin.py +++ b/material/plugins/blog/plugin.py @@ -27,6 +27,8 @@ import yaml from babel.dates import format_date from datetime import datetime +from jinja2 import pass_context +from jinja2.runtime import Context from mkdocs.config.defaults import MkDocsConfig from mkdocs.exceptions import PluginError from mkdocs.plugins import BasePlugin, event_priority @@ -35,6 +37,7 @@ from mkdocs.structure.files import File, Files, InclusionLevel from mkdocs.structure.nav import Navigation, Section from mkdocs.structure.pages import Page from mkdocs.utils import copy_file, get_relative_url +from mkdocs.utils.templates import url_filter from paginate import Page as Pagination from shutil import rmtree from tempfile import mkdtemp @@ -44,7 +47,6 @@ from .author import Authors from .config import BlogConfig from .readtime import readtime from .structure import Archive, Category, Excerpt, Post, View -from .templates import url_filter # ----------------------------------------------------------------------------- # Classes @@ -148,7 +150,6 @@ class BlogPlugin(BasePlugin[BlogConfig]): if self.config.pagination: for view in self._resolve_views(self.blog): for page in self._generate_pages(view, config, files): - page.file.inclusion = InclusionLevel.EXCLUDED view.pages.append(page) # Ensure that entrypoint is always included in navigation @@ -180,6 +181,7 @@ class BlogPlugin(BasePlugin[BlogConfig]): # Revert temporary exclusion of views from navigation for view in self._resolve_views(self.blog): + view.file.inclusion = self.blog.file.inclusion for page in view.pages: page.file.inclusion = self.blog.file.inclusion @@ -298,9 +300,25 @@ class BlogPlugin(BasePlugin[BlogConfig]): def date_filter(date: datetime): return self._format_date_for_post(date, config) + # Patch URL template filter to add support for paginated views, i.e., + # that paginated views never link to themselves but to the main view + @pass_context + def url_filter_with_pagination(context: Context, url: str | None): + page = context["page"] + + # If the current page is a view, check if the URL links to the page + # itself, and replace it with the URL of the main view + if isinstance(page, View): + view = self._resolve_original(page) + if page.url == url: + url = view.url + + # Forward to original template filter + return url_filter(context, url) + # Register custom template filters env.filters["date"] = date_filter - env.filters["url"] = url_filter + env.filters["url"] = url_filter_with_pagination # Prepare view for rendering (run latest) - views are rendered last, as we # need to mutate the navigation to account for pagination. The main problem @@ -527,7 +545,7 @@ class BlogPlugin(BasePlugin[BlogConfig]): # Resolve original page or view (e.g. for paginated views) def _resolve_original(self, page: Page): - if isinstance(page, View): + if isinstance(page, View) and page.pages: return page.pages[0] else: return page @@ -550,8 +568,10 @@ class BlogPlugin(BasePlugin[BlogConfig]): file = self._path_to_file(path, config) files.append(file) - # Create file in temporary directory + # Create file in temporary directory and temporarily remove + # from navigation, as we'll add it at a specific location self._save_to_file(file.abs_src_path, f"# {name}") + file.inclusion = InclusionLevel.EXCLUDED # Create and yield view - we don't explicitly set the title of # the view, so authors can override them in the page's content @@ -585,8 +605,10 @@ class BlogPlugin(BasePlugin[BlogConfig]): file = self._path_to_file(path, config) files.append(file) - # Create file in temporary directory + # Create file in temporary directory and temporarily remove + # from navigation, as we'll add it at a specific location self._save_to_file(file.abs_src_path, f"# {name}") + file.inclusion = InclusionLevel.EXCLUDED # Create and yield view - we don't explicitly set the title of # the view, so authors can override them in the page's content @@ -615,8 +637,10 @@ class BlogPlugin(BasePlugin[BlogConfig]): file = self._path_to_file(path, config) files.append(file) - # Copy file to temporary directory + # Copy file to temporary directory and temporarily remove + # from navigation, as we'll add it at a specific location copy_file(view.file.abs_src_path, file.abs_src_path) + file.inclusion = InclusionLevel.EXCLUDED # Create and yield view if not isinstance(file.page, View): diff --git a/material/plugins/blog/templates/__init__.py b/material/plugins/blog/templates/__init__.py deleted file mode 100644 index 9f7d794bb..000000000 --- a/material/plugins/blog/templates/__init__.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright (c) 2016-2023 Martin Donath - -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to -# deal in the Software without restriction, including without limitation the -# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -# sell copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. - -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -# IN THE SOFTWARE. - -from jinja2 import pass_context -from jinja2.runtime import Context -from material.plugins.blog.structure import View -from mkdocs.utils.templates import url_filter as _url_filter - -# ----------------------------------------------------------------------------- -# Functions -# ----------------------------------------------------------------------------- - -# Filter for normalizing URLs with support for paginated views -@pass_context -def url_filter(context: Context, url: str): - page = context["page"] - - # If the current page is a view, check if the URL links to the page - # itself, and replace it with the URL of the main view - if isinstance(page, View): - if page.url == url: - url = page.pages[0].url - - # Forward to original template filter - return _url_filter(context, url) diff --git a/src/plugins/blog/plugin.py b/src/plugins/blog/plugin.py index 6ae2c2c84..081f190ba 100644 --- a/src/plugins/blog/plugin.py +++ b/src/plugins/blog/plugin.py @@ -27,6 +27,8 @@ import yaml from babel.dates import format_date from datetime import datetime +from jinja2 import pass_context +from jinja2.runtime import Context from mkdocs.config.defaults import MkDocsConfig from mkdocs.exceptions import PluginError from mkdocs.plugins import BasePlugin, event_priority @@ -35,6 +37,7 @@ from mkdocs.structure.files import File, Files, InclusionLevel from mkdocs.structure.nav import Navigation, Section from mkdocs.structure.pages import Page from mkdocs.utils import copy_file, get_relative_url +from mkdocs.utils.templates import url_filter from paginate import Page as Pagination from shutil import rmtree from tempfile import mkdtemp @@ -44,7 +47,6 @@ from .author import Authors from .config import BlogConfig from .readtime import readtime from .structure import Archive, Category, Excerpt, Post, View -from .templates import url_filter # ----------------------------------------------------------------------------- # Classes @@ -148,7 +150,6 @@ class BlogPlugin(BasePlugin[BlogConfig]): if self.config.pagination: for view in self._resolve_views(self.blog): for page in self._generate_pages(view, config, files): - page.file.inclusion = InclusionLevel.EXCLUDED view.pages.append(page) # Ensure that entrypoint is always included in navigation @@ -180,6 +181,7 @@ class BlogPlugin(BasePlugin[BlogConfig]): # Revert temporary exclusion of views from navigation for view in self._resolve_views(self.blog): + view.file.inclusion = self.blog.file.inclusion for page in view.pages: page.file.inclusion = self.blog.file.inclusion @@ -298,9 +300,25 @@ class BlogPlugin(BasePlugin[BlogConfig]): def date_filter(date: datetime): return self._format_date_for_post(date, config) + # Patch URL template filter to add support for paginated views, i.e., + # that paginated views never link to themselves but to the main view + @pass_context + def url_filter_with_pagination(context: Context, url: str | None): + page = context["page"] + + # If the current page is a view, check if the URL links to the page + # itself, and replace it with the URL of the main view + if isinstance(page, View): + view = self._resolve_original(page) + if page.url == url: + url = view.url + + # Forward to original template filter + return url_filter(context, url) + # Register custom template filters env.filters["date"] = date_filter - env.filters["url"] = url_filter + env.filters["url"] = url_filter_with_pagination # Prepare view for rendering (run latest) - views are rendered last, as we # need to mutate the navigation to account for pagination. The main problem @@ -527,7 +545,7 @@ class BlogPlugin(BasePlugin[BlogConfig]): # Resolve original page or view (e.g. for paginated views) def _resolve_original(self, page: Page): - if isinstance(page, View): + if isinstance(page, View) and page.pages: return page.pages[0] else: return page @@ -550,8 +568,10 @@ class BlogPlugin(BasePlugin[BlogConfig]): file = self._path_to_file(path, config) files.append(file) - # Create file in temporary directory + # Create file in temporary directory and temporarily remove + # from navigation, as we'll add it at a specific location self._save_to_file(file.abs_src_path, f"# {name}") + file.inclusion = InclusionLevel.EXCLUDED # Create and yield view - we don't explicitly set the title of # the view, so authors can override them in the page's content @@ -585,8 +605,10 @@ class BlogPlugin(BasePlugin[BlogConfig]): file = self._path_to_file(path, config) files.append(file) - # Create file in temporary directory + # Create file in temporary directory and temporarily remove + # from navigation, as we'll add it at a specific location self._save_to_file(file.abs_src_path, f"# {name}") + file.inclusion = InclusionLevel.EXCLUDED # Create and yield view - we don't explicitly set the title of # the view, so authors can override them in the page's content @@ -615,8 +637,10 @@ class BlogPlugin(BasePlugin[BlogConfig]): file = self._path_to_file(path, config) files.append(file) - # Copy file to temporary directory + # Copy file to temporary directory and temporarily remove + # from navigation, as we'll add it at a specific location copy_file(view.file.abs_src_path, file.abs_src_path) + file.inclusion = InclusionLevel.EXCLUDED # Create and yield view if not isinstance(file.page, View): diff --git a/src/plugins/blog/templates/__init__.py b/src/plugins/blog/templates/__init__.py deleted file mode 100644 index 9f7d794bb..000000000 --- a/src/plugins/blog/templates/__init__.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright (c) 2016-2023 Martin Donath - -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to -# deal in the Software without restriction, including without limitation the -# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -# sell copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. - -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -# IN THE SOFTWARE. - -from jinja2 import pass_context -from jinja2.runtime import Context -from material.plugins.blog.structure import View -from mkdocs.utils.templates import url_filter as _url_filter - -# ----------------------------------------------------------------------------- -# Functions -# ----------------------------------------------------------------------------- - -# Filter for normalizing URLs with support for paginated views -@pass_context -def url_filter(context: Context, url: str): - page = context["page"] - - # If the current page is a view, check if the URL links to the page - # itself, and replace it with the URL of the main view - if isinstance(page, View): - if page.url == url: - url = page.pages[0].url - - # Forward to original template filter - return _url_filter(context, url)