diff --git a/.github/assets/sponsors/sponsor-buhler.png b/.github/assets/sponsors/sponsor-buhler.png
new file mode 100644
index 00000000000..d58a2c5df36
Binary files /dev/null and b/.github/assets/sponsors/sponsor-buhler.png differ
diff --git a/CHANGELOG b/CHANGELOG
index 44851315422..33f3d4731ad 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,53 @@
+mkdocs-material-9-2.5+insiders-4.40.1 (2023-08-27)
+
+ * Fixed #5902: ResizeObserver polyfill not detected by privacy plugin
+ * Fixed empty category pages in blog plugin (4.40.0 regression)
+
+mkdocs-material-9-2.5 (2023-08-27)
+
+ * Fixed error in dirty serve mode when using blog plugin
+ * Fixed page title not being consistent in blog plugin pagination
+ * Fixed #5899: Blog plugin pagination breaks when disabling directory URLs
+
+mkdocs-material-9.2.4+insiders-4.40.0 (2023-08-26)
+
+ * Added logo, title and description options to social plugin default layouts
+ * Fixed privacy plugin compatibility issue with Python < 3.10
+ * Fixed #5896: Blog plugin errors when using custom index pages
+
+mkdocs-material-9.2.4 (2023-08-26)
+
+ * Added version to bug report name in info plugin
+ * Updated Afrikaans translations
+
+mkdocs-material-9.2.3+insiders-4.39.3 (2023-08-24)
+
+ * Fixed lxml dependency missing in Docker image (4.39.2 regression)
+
+mkdocs-material-9.2.3+insiders-4.39.2 (2023-08-23)
+
+ * Fixed color palette toggle being reversed (9.2.0 regression)
+
+mkdocs-material-9.2.3 (2023-08-22)
+
+ * Fixed blog plugin rendering wrongly with markdown.extensions.toc
+ * Fixed blog plugin entrypoint generation
+
+mkdocs-material-9.2.2 (2023-08-22)
+
+ * Fixed #5880: Blog plugin failing when building a standalone blog
+ * Fixed #5881: Blog plugin not compatible with Python < 3.10
+
+mkdocs-material-9.2.1 (2023-08-21)
+
+ * Fixed #5879: Blog plugin failing when building a standalone blog
+ * Fixed error in blog plugin when using draft tagging on future date
+ * Fixed error in blog plugin when toc extension is not enabled
+
+mkdocs-material-9.2.0+insiders-4.39.1 (2023-08-21)
+
+ * Fixed git diff in tags plugin after merging back 9.2.0 changes
+
mkdocs-material-9.2.0 (2023-08-21)
Additions and improvements
diff --git a/Dockerfile b/Dockerfile
index 5595e842a89..0c15409b8eb 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -47,12 +47,16 @@ RUN \
git \
git-fast-import \
jpeg-dev \
+ libxml2 \
+ libxslt \
openssh \
zlib-dev \
&& \
apk add --no-cache --virtual .build \
gcc \
libffi-dev \
+ libxml2-dev \
+ libxslt-dev \
musl-dev \
&& \
pip install --no-cache-dir --upgrade pip \
diff --git a/README.md b/README.md
index 8fc72309213..755cfa095cb 100644
--- a/README.md
+++ b/README.md
@@ -123,9 +123,9 @@
-
+ /> -->
@@ -159,6 +159,9 @@
+
diff --git a/docs/changelog/index.md b/docs/changelog/index.md
index 86f835fd292..11731736a28 100644
--- a/docs/changelog/index.md
+++ b/docs/changelog/index.md
@@ -2,7 +2,34 @@
## Material for MkDocs
-### 9.2.0 July 6, 2023 { id="9.2.0" }
+### 9.2.5 August 27, 2023 { id="9.2.5" }
+
+- Fixed error in dirty serve mode when using blog plugin
+- Fixed page title not being consistent in blog plugin pagination
+- Fixed #5899: Blog plugin pagination breaks when disabling directory URLs
+
+### 9.2.4 August 26, 2023 { id="9.2.4" }
+
+- Added version to bug report name in info plugin
+- Updated Afrikaans translations
+
+### 9.2.3 August 22, 2023 { id="9.2.3" }
+
+- Fixed blog plugin rendering wrongly with `markdown.extensions.toc`
+- Fixed blog plugin entrypoint generation
+
+### 9.2.2 August 22, 2023 { id="9.2.2" }
+
+- Fixed #5880: Blog plugin failing when building a standalone blog
+- Fixed #5881: Blog plugin not compatible with Python < 3.10
+
+### 9.2.1 August 21, 2023 { id="9.2.1" }
+
+- Fixed #5879: Blog plugin failing when building a standalone blog
+- Fixed error in blog plugin when using draft tagging on future date
+- Fixed error in blog plugin when toc extension is not enabled
+
+### 9.2.0 August 21, 2023 { id="9.2.0" }
__Additions and improvements__
@@ -51,7 +78,7 @@ __Fixes__
- Fixed #5806: Version selector not hoverable on some Android devices
- Fixed #5826: Blog post drafts with tags show up in tags index
-### 9.1.21 July 27, 2023 { id="9.1.20" }
+### 9.1.21 July 27, 2023 { id="9.1.21" }
- Fixed MkDocs 1.4 compat issue in social plugin (9.1.20 regression)
diff --git a/docs/faq/sponsoring.md b/docs/faq/sponsoring.md
index d1ef21123b7..81fb0643eb0 100644
--- a/docs/faq/sponsoring.md
+++ b/docs/faq/sponsoring.md
@@ -79,15 +79,28 @@ Note that [$15] is the minimum amount to be granted access to Insiders.
[$15]: https://github.com/sponsors/squidfunk/sponsorships?tier_id=210638
-[__How is my sponsorship contribution used to support the project?__](#sponsorship-support){ #sponsorship-support }
+[__How are sponsorship contributions used?__](#sponsorship-support){ #sponsorship-support }
-Your sponsorship contribution directly supports the development and
-maintenance of the project, by buying us maintainers time. It allows us to
-dedicate more time and resources to enhance the project's features and
-functionality. The additional funding helps us prioritize improvements and
-updates, benefiting Insiders users and the wider community. We also actively
-contribute to other upstream projects, fostering collaboration and giving
-back to the Open Source ecosystem.
+It's vital to recognize that the total sponsorship amount doesn't directly
+translate into the funds we have available for use. The way we allocate
+sponsorship amounts is detailed as follows:
+
+1. __Taxes__: Since we provide a service to our sponsors, we're of course
+ legally obligated to pay sales tax. This requirement applies to all
+ sponsorship contributions, aligning us with standard business practices
+ as for the rest of the world.
+
+2. __Sponsorships__: A significant portion of our funding is redirected to
+ upstream projects. This cultivates collaboration and supports the broader
+ Open Source ecosystem. Those projects and their maintainers are essential
+ for the ongoing development of Material for MkDocs.
+
+ [Explore our sponsorships](https://github.com/squidfunk?tab=sponsoring).
+
+3. __Funds__: We are in the process of forming a team devoted to Material for
+ MkDocs and are proactively compensating critical contributors. These
+ funds cover various aspects of the project, like the creation of new
+ features, bug resolution, support, and sponsor relations.
[__Are there any limitations on the number of sponsors for a particular tier?__](#sponsorship-limitations){ #sponsorship-limitations }
@@ -371,8 +384,8 @@ appearance of your site should be optional. Most Insiders features enhance the
overall experience, e.g., by adding icons to pages or providing a feedback
widget. While these features add value for your site's users, they should be
optional for previewing when making changes to content. Currently, the only
-content-related features in Insiders that non-Insiders users can't properly
-preview are [Annotations] and [Card grids].
+content-related feature in Insiders that non-Insiders users can't properly
+preview are [Card grids].
This means that outside collaborators can build the documentation locally with
Material for MkDocs, and when they push their changes, your CI pipeline will
@@ -384,10 +397,8 @@ See the [getting started guide] for more information.
[configuration inheritance]: https://www.mkdocs.org/user-guide/configuration/#configuration-inheritance
[getting started guide]: ../insiders/getting-started.md#caveats
- [Annotations]: ../reference/annotations.md?h=anno#annotations
[Card grids]: ../reference/grids.md?h=grids#using-card-grids
-
## Support
[__How can I contact support if I have questions about becoming a sponsor?__ ](#support-contact){ #support-contact }
diff --git a/docs/insiders/changelog.md b/docs/insiders/changelog.md
index c658a13b192..25a7171e9be 100644
--- a/docs/insiders/changelog.md
+++ b/docs/insiders/changelog.md
@@ -2,6 +2,29 @@
## Material for MkDocs Insiders
+### 4.40.1 August 27, 2023 { id="4.40.1" }
+
+- Fixed #5902: ResizeObserver polyfill not detected by privacy plugin
+- Fixed empty category pages in blog plugin (4.40.0 regression)
+
+### 4.40.0 August 26, 2023 { id="4.40.0" }
+
+- Added logo, title and description options to social plugin default layouts
+- Fixed privacy plugin compatibility issue with Python < 3.10
+- Fixed #5896: Blog plugin errors when using custom index pages
+
+### 4.39.3 August 24, 2023 { id="4.39.3" }
+
+- Fixed lxml dependency missing in Docker container (4.39.2 regression)
+
+### 4.39.2 August 23, 2023 { id="4.39.2" }
+
+- Fixed color palette toggle being reversed (9.2.0 regression)
+
+### 4.39.1 August 21, 2023 { id="4.39.1" }
+
+- Fixed git diff in tags plugin after merging back 9.2.0 changes
+
### 4.39.0 August 3, 2023 { id="4.39.0" }
- Added support for hoisting theme media files when building projects
diff --git a/docs/insiders/index.md b/docs/insiders/index.md
index ec5003a86e1..43a10ba564a 100644
--- a/docs/insiders/index.md
+++ b/docs/insiders/index.md
@@ -188,7 +188,6 @@ You can cancel your sponsorship anytime.[^5]
[![Sparkfun]](https://sparkfun.com/){ target=_blank title="Sparkfun Electronics" }
[![Eccenca]](https://eccenca.com/){ target=_blank title="Eccenca" }
[![Neptune]](https://neptune.ai/){ target=_blank title="Neptune" }
-[![Cash App]](https://cash.app/){ target=_blank title="Cash App" }
[![RackN]](https://rackn.com/){ target=_blank title="RackN" }
[![CivicActions]](https://civicactions.com/){ target=_blank title="CivicActions" }
[![bitcrowd]](https://bitcrowd.net/){ target=_blank title="bitcrowd" }
@@ -200,6 +199,7 @@ You can cancel your sponsorship anytime.[^5]
[![Koor]](https://koor.tech/){ target=_blank title="Koor" }
[![Astral]](https://astral.sh/){ target=_blank title="Astral" }
[![Oikolab]](https://oikolab.com/){ target=_blank title="Oikolab" }
+[![Bühler Group]](https://www.buhlergroup.com/){ target=_blank title="Bühler Group" }
@@ -235,6 +235,7 @@ You can cancel your sponsorship anytime.[^5]
[Koor]: https://raw.githubusercontent.com/squidfunk/mkdocs-material/master/.github/assets/sponsors/sponsor-koor.png
[Astral]: https://raw.githubusercontent.com/squidfunk/mkdocs-material/master/.github/assets/sponsors/sponsor-astral.png
[Oikolab]: https://raw.githubusercontent.com/squidfunk/mkdocs-material/master/.github/assets/sponsors/sponsor-oikolab.png
+ [Bühler Group]: https://raw.githubusercontent.com/squidfunk/mkdocs-material/master/.github/assets/sponsors/sponsor-buhler.png
diff --git a/docs/setup/setting-up-a-blog.md b/docs/setup/setting-up-a-blog.md
index 80a0a4cbaa4..d188614ff72 100644
--- a/docs/setup/setting-up-a-blog.md
+++ b/docs/setup/setting-up-a-blog.md
@@ -1076,10 +1076,11 @@ post with one or multiple [authors]. First, create the
[`.authors.yml`][authors_file] file in your blog directory, and add an author:
``` yaml
-squidfunk:
- name: Martin Donath
- description: Creator
- avatar: https://github.com/squidfunk.png
+authors:
+ squidfunk:
+ name: Martin Donath
+ description: Creator
+ avatar: https://github.com/squidfunk.png
```
The [`.authors.yml`][authors_file] file associates each author with an
diff --git a/docs/setup/setting-up-social-cards.md b/docs/setup/setting-up-social-cards.md
index 15581f0e426..14daa420673 100644
--- a/docs/setup/setting-up-social-cards.md
+++ b/docs/setup/setting-up-social-cards.md
@@ -331,6 +331,46 @@ The following configuration options are available for card generation:
font_family: Ubuntu
```
+ [`title`](#+social.cards_layout_options.title){ #+social.cards_layout_options.title }
+
+ : [:octicons-tag-24: insiders-4.40.0][Insiders] – Set the social card
+ title, which takes precedence over `page.title` and `page.meta.title`:
+
+ ``` yaml
+ plugins:
+ - social:
+ cards_layout_options:
+ title: Social card title
+ ```
+
+ [`description`](#+social.cards_layout_options.description){ #+social.cards_layout_options.description }
+
+ : [:octicons-tag-24: insiders-4.40.0][Insiders] – Set the social card
+ description, which takes precedence over `site_description` and
+ `page.meta.description`:
+
+ ``` yaml
+ plugins:
+ - social:
+ cards_layout_options:
+ description: Social card description
+ ```
+
+ [`logo`](#+social.cards_layout_options.logo){ #+social.cards_layout_options.logo }
+
+ : [:octicons-tag-24: insiders-4.40.0][Insiders] – Set the logo used as
+ part of the social card, overriding the `theme.logo` or
+ `theme.icon.logo` settings which are used as defaults:
+
+ ``` yaml
+ plugins:
+ - social:
+ cards_layout_options:
+ logo: layouts/logo.png
+ ```
+
+ The path of the image must be defined relative to the project root.
+
[`cards_include`](#+privacy.cards_include){ #+privacy.cards_include }
: [:octicons-tag-24: insiders-4.35.0][Insiders] · :octicons-milestone-24:
@@ -507,16 +547,16 @@ The following configuration options are available for caching:
## Usage
If you want to adjust the title or set a custom description for the social card,
-you can set the front matter `title` and `description` properties, which take
-precedence over the default values.
+you can set the front matter [`title`][Changing the title] and
+[`description`][Changing the description] properties, which take precedence over
+the defaults, or use:
-- [Changing the title]
-- [Changing the description]
+- [`cards_layout_options.title`](#+social.cards_layout_options.title)
+- [`cards_layout_options.description`](#+social.cards_layout_options.description)
[Changing the title]: ../reference/index.md#setting-the-page-title
[Changing the description]: ../reference/index.md#setting-the-page-description
-
### Choosing a font
Some fonts do not contain CJK characters, like for example the
diff --git a/material/.overrides/hooks/translations.py b/material/.overrides/hooks/translations.py
index c4e2d185390..0bf458d960e 100644
--- a/material/.overrides/hooks/translations.py
+++ b/material/.overrides/hooks/translations.py
@@ -38,8 +38,8 @@ def on_page_markdown(markdown: str, *, page: Page, config: MkDocsConfig, files):
return
# Collect all existing languages
- names: dict[str, str] = dict()
- known: dict[str, dict[str, str]] = dict()
+ names: dict[str, str] = {}
+ known: dict[str, dict[str, str]] = {}
for path in glob("src/partials/languages/*.html"):
with open(path, "r", encoding = "utf-8") as f:
data = f.read()
diff --git a/material/base.html b/material/base.html
index fdb68f9653d..0cf6881ea0b 100644
--- a/material/base.html
+++ b/material/base.html
@@ -32,7 +32,7 @@
{% endif %}
-
+
{% endblock %}
{% block htmltitle %}
{% if page.meta and page.meta.title %}
diff --git a/material/partials/languages/af.html b/material/partials/languages/af.html
index 358623e877c..bc4a0232217 100644
--- a/material/partials/languages/af.html
+++ b/material/partials/languages/af.html
@@ -5,20 +5,52 @@
"language": "af",
"action.edit": "Wysig hierdie bladsy",
"action.skip": "Slaan oor na inhoud",
+ "action.view": "Bekyk bron van hierdie bladsy",
+ "announce.dismiss": "Moenie dit weer wys nie",
+ "blog.archive": "Argief",
+ "blog.categories": "Kategorieë",
+ "blog.categories.in": "binne",
+ "blog.continue": "Lees verder",
+ "blog.draft": "Konsep",
+ "blog.index": "Terug na indeks",
+ "blog.meta": "Metadata",
+ "blog.references": "Verwante skakels",
"clipboard.copy": "Kopieer na knipbord",
"clipboard.copied": "gekopieer na knipbord",
+ "consent.accept": "Aanvaar",
+ "consent.manage": "Bestuur instellings",
+ "consent.reject": "Verwerp",
+ "footer": "Voetskrif",
"footer.next": "Volgende",
"footer.previous": "Vorige",
+ "header": "Kopskrif",
"meta.comments": "Kommentaar",
"meta.source": "Bron",
+ "nav": "Navigasie",
+ "readtime.one": "1 minuut se lees",
+ "readtime.other": "# minuut se lees",
+ "rss.created": "RSS-voer geskep",
+ "rss.updated": "RSS-voer van opgedateerde inhoud",
+ "search": "Soek",
"search.config.lang": "nl",
"search.placeholder": "Soek",
+ "search.share": "Deel",
+ "search.reset": "Terugstel",
+ "search.result.initializer": "Inisialisering van soektog",
"search.result.placeholder": "Tik om te begin soek",
"search.result.none": "Geen ooreenstemmende dokumente",
"search.result.one": "1 ooreenstemmende dokument",
"search.result.other": "# ooreenstemmende dokumente",
+ "search.result.more.one": "1 meer op hierdie bladsy",
+ "search.result.more.other": "# meer op hierdie bladsy",
+ "search.result.term.missing": "Vermis",
+ "select.language": "Kies taal",
+ "select.version": "Kies weergawe",
"source": "Slaan oor na inhoud",
+ "source.file.contributors": "Medewerkers",
"source.file.date.created": "Geskep",
"source.file.date.updated": "Laaste opdatering",
- "toc": "Inhoudsopgawe"
+ "tabs": "Duimgids",
+ "toc": "Inhoudsopgawe",
+ "top": "Terug na bo"
}[key] }}{% endmacro %}
diff --git a/material/plugins/blog/config.py b/material/plugins/blog/config.py
index a8d3f1a5ba7..c7a85095842 100644
--- a/material/plugins/blog/config.py
+++ b/material/plugins/blog/config.py
@@ -31,11 +31,11 @@
class BlogConfig(Config):
enabled = Type(bool, default = True)
- # Options for blog
+ # Settings for blog
blog_dir = Type(str, default = "blog")
blog_toc = Type(bool, default = False)
- # Options for posts
+ # Settings for posts
post_dir = Type(str, default = "{blog}/posts")
post_date_format = Type(str, default = "long")
post_url_date_format = Type(str, default = "yyyy/MM/dd")
@@ -50,7 +50,7 @@ class BlogConfig(Config):
post_readtime = Type(bool, default = True)
post_readtime_words_per_minute = Type(int, default = 265)
- # Options for archive
+ # Settings for archive
archive = Type(bool, default = True)
archive_name = Type(str, default = "blog.archive")
archive_date_format = Type(str, default = "yyyy")
@@ -58,7 +58,7 @@ class BlogConfig(Config):
archive_url_format = Type(str, default = "archive/{date}")
archive_toc = Optional(Type(bool))
- # Options for categories
+ # Settings for categories
categories = Type(bool, default = True)
categories_name = Type(str, default = "blog.categories")
categories_url_format = Type(str, default = "category/{slug}")
@@ -67,7 +67,7 @@ class BlogConfig(Config):
categories_allowed = Type(list, default = [])
categories_toc = Optional(Type(bool))
- # Options for pagination
+ # Settings for pagination
pagination = Type(bool, default = True)
pagination_per_page = Type(int, default = 10)
pagination_url_format = Type(str, default = "page/{page}")
@@ -75,14 +75,14 @@ class BlogConfig(Config):
pagination_if_single_page = Type(bool, default = False)
pagination_keep_content = Type(bool, default = False)
- # Options for authors
+ # Settings for authors
authors = Type(bool, default = True)
authors_file = Type(str, default = "{blog}/.authors.yml")
- # Options for drafts
+ # Settings for drafts
draft = Type(bool, default = False)
draft_on_serve = Type(bool, default = True)
draft_if_future_date = Type(bool, default = False)
- # Deprecated options
+ # Deprecated settings
pagination_template = Deprecated(moved_to = "pagination_format")
diff --git a/material/plugins/blog/plugin.py b/material/plugins/blog/plugin.py
index 2d156390f6f..9bfb89685d3 100644
--- a/material/plugins/blog/plugin.py
+++ b/material/plugins/blog/plugin.py
@@ -125,6 +125,9 @@ def on_files(self, files, *, config):
file.abs_dest_path = os.path.join(site, file.dest_path)
file.url = file.url.replace(path, root)
+ # Generate entrypoint, if it does not exist yet
+ self._generate(config, files)
+
# Resolve and load posts and generate indexes (run later) - we resolve all
# posts after the navigation is constructed in order to allow other plugins
# to alter the navigation (e.g. awesome-pages) before we start to add pages
@@ -161,7 +164,7 @@ def on_nav(self, nav, *, config, files):
# Attach and link views for archive
title = self._translate(self.config.archive_name, config)
- self._attach_to(self.blog.parent, Section(title, views), nav)
+ self._attach_to(self.blog, Section(title, views), nav)
# Generate and attach views for categories
if self.config.categories:
@@ -170,7 +173,7 @@ def on_nav(self, nav, *, config, files):
# Attach and link views for categories
title = self._translate(self.config.categories_name, config)
- self._attach_to(self.blog.parent, Section(title, views), nav)
+ self._attach_to(self.blog, Section(title, views), nav)
# Paginate generated views, if enabled
if self.config.pagination:
@@ -199,7 +202,14 @@ def on_page_markdown(self, markdown, *, page, config, files):
if page in self._resolve_views(self.blog):
assert isinstance(page, View)
if 0 < page.pages.index(page):
- return f"# {page.title}"
+ main = page.parent
+
+ # We need to use the rendered title of the original view
+ # if the author set the title in the page's contents, or
+ # it would be overridden with the one set in mkdocs.yml,
+ # which would result in inconsistent headings
+ name = main._title_from_render or main.title
+ return f"# {name}"
# Nothing more to be done for views
return
@@ -234,7 +244,7 @@ def on_page_markdown(self, markdown, *, page, config, files):
# is not already present, so we can remove footnotes or other content
# from the excerpt without affecting the content of the excerpt
if separator not in page.markdown:
- path = page.file.src_uri
+ path = page.file.src_path
if self.config.post_excerpt == "required":
raise PluginError(
f"Couldn't find '{separator}' separator in '{path}'"
@@ -280,21 +290,20 @@ def on_page_context(self, context, *, page, config, nav):
main = page.parent
# If this page is a view, and the parent page is a view as well, we got
- # a paginated view and need to update the parent view in the navigation.
- # Paginated views are always rendered last, which is why we can safely
- # mutate the navigation at this point
+ # a paginated view and need to replace the parent with the current view.
+ # Paginated views are always rendered at the end of the build, which is
+ # why we can safely mutate the navigation at this point
if isinstance(main, View):
- assert isinstance(main.parent, Section)
-
- # Replace view in navigation and rewire view - the current view in
- # the navigation becomes the main view, thus the entire chain moves
- # one level up. It's essential that the rendering order is linear,
- # or else we might end up with a broken navigation.
- at = main.parent.children.index(main)
- main.parent.children[at] = page
page.parent = main.parent
- # Render excerpts and perpare pagination
+ # Replace view in navigation and rewire it - the current view in the
+ # navigation becomes the main view, thus the entire chain moves one
+ # level up. It's essential that the rendering order is linear, or
+ # else we might end up with a broken navigation.
+ items = self._resolve_siblings(main, nav)
+ items[items.index(main)] = page
+
+ # Render excerpts and prepare pagination
posts, pagination = self._render(page)
# Render pagination links
@@ -326,31 +335,19 @@ def _is_excluded(self, post: Post):
# and must be explicitly enabled by the author.
if not isinstance(post.config.draft, bool):
if self.config.draft_if_future_date:
- return post.config.date > datetime.now()
+ return post.config.date.created > datetime.now()
# Post might be a draft
return bool(post.config.draft)
# -------------------------------------------------------------------------
- # Resolve entrypoint - the entrypoint of the blog hosts all posts, sorted
- # by descending date. The entrypoint must always be present, even if there
- # are no posts, and is automatically created if it does not exist yet. Note
- # that posts might be paginated, but this is configurable by the author.
+ # Resolve entrypoint - the entrypoint of the blog must have been created
+ # if it did not exist before, and hosts all posts sorted by descending date
def _resolve(self, files: Files, config: MkDocsConfig, nav: Navigation):
path = os.path.join(self.config.blog_dir, "index.md")
path = os.path.normpath(path)
- # Create entrypoint, if it does not exist
- docs = os.path.relpath(config.docs_dir)
- file = os.path.join(docs, path)
- if not os.path.isfile(file):
- self._save_to_file(file, "# Blog\n\n")
-
- # Append entrypoint to files - note that the entrypoint is added to
- # the docs directory, so we need to set the temporary flag to false
- files.append(self._path_to_file(path, config, temp = False))
-
# Obtain entrypoint page
file = files.get_file_from_path(path)
page = file.page
@@ -364,7 +361,7 @@ def _resolve(self, files: Files, config: MkDocsConfig, nav: Navigation):
])
# Update entrypoint in navigation
- for items in [view.parent.children, nav.pages]:
+ for items in [self._resolve_siblings(view, nav), nav.pages]:
items[items.index(page)] = view
# Return view
@@ -379,7 +376,7 @@ def _resolve_post(self, file: File, config: MkDocsConfig):
path = self._format_path_for_post(post, config)
temp = self._path_to_file(path, config, temp = False)
- # Replace post destination file system path and URL
+ # Replace destination file system path and URL
file.dest_uri = temp.dest_uri
file.abs_dest_path = temp.abs_dest_path
file.url = temp.url
@@ -421,16 +418,18 @@ def _resolve_authors(self, config: MkDocsConfig):
path = self.config.authors_file.format(blog = self.config.blog_dir)
path = os.path.normpath(path)
- # If the authors file does not exist, return an empty dictionary
+ # Resolve path relative to docs directory
docs = os.path.relpath(config.docs_dir)
file = os.path.join(docs, path)
+
+ # If the authors file does not exist, return here
+ config: Authors = Authors()
if not os.path.isfile(file):
- authors: dict[str, Author] = dict()
- return authors
+ return config.authors
# Open file and parse as YAML
with open(file, encoding = "utf-8") as f:
- config: Authors = Authors(os.path.abspath(file))
+ config.config_file_path = os.path.abspath(file)
try:
config.load_dict(yaml.load(f, SafeLoader) or {})
@@ -484,6 +483,13 @@ def _resolve_views(self, view: View):
assert isinstance(page, View)
yield page
+ # Resolve siblings of a navigation item
+ def _resolve_siblings(self, item: StructureItem, nav: Navigation):
+ if isinstance(item.parent, Section):
+ return item.parent.children
+ else:
+ return nav.items
+
# -------------------------------------------------------------------------
# Attach a list of pages to each other and to the given parent item without
@@ -496,16 +502,16 @@ def _attach(self, parent: StructureItem, pages: list[Page]):
page.previous_page = tail
page.next_page = head
- # Attach a section to the given parent section, make sure it's pages are
+ # Attach a section as a sibling to the given view, make sure it's pages are
# part of the navigation, and ensure all pages are linked correctly
- def _attach_to(self, parent: Section, section: Section, nav: Navigation):
- section.parent = parent
-
- # Determine the parent section to attach the section to, which might be
- # the top-level navigation, if no parent section was given. Note, that
- # it's currently not possible to chose the position of a section, but
- # we might add support for this in the future.
- items = parent.children if parent else nav.items
+ def _attach_to(self, view: View, section: Section, nav: Navigation):
+ section.parent = view.parent
+
+ # Resolve siblings, which are the children of the parent section, or
+ # the top-level list of navigation items if the view is at the root of
+ # the project, and append the given section to it. It's currently not
+ # possible to chose the position of a section.
+ items = self._resolve_siblings(view, nav)
items.append(section)
# Find last sibling that is a page, skipping sections, as we need to
@@ -519,6 +525,23 @@ def _attach_to(self, parent: Section, section: Section, nav: Navigation):
# -------------------------------------------------------------------------
+ # Generate entrypoint - the entrypoint must always be present, and thus is
+ # created before the navigation is constructed if it does not exist yet
+ def _generate(self, config: MkDocsConfig, files: Files):
+ path = os.path.join(self.config.blog_dir, "index.md")
+ path = os.path.normpath(path)
+
+ # Create entrypoint, if it does not exist - note that the entrypoint is
+ # added to the docs directory, not to the temporary directory
+ docs = os.path.relpath(config.docs_dir)
+ file = os.path.join(docs, path)
+ if not os.path.isfile(file):
+ file = self._path_to_file(path, config, temp = False)
+ self._save_to_file(file.abs_src_path, "# Blog\n\n")
+
+ # Append entrypoint to files
+ files.append(file)
+
# Generate views for archive - analyze posts and generate the necessary
# views, taking the date format provided by the author into account
def _generate_archive(self, config: MkDocsConfig, files: Files):
@@ -533,11 +556,11 @@ def _generate_archive(self, config: MkDocsConfig, files: Files):
file = files.get_file_from_path(path)
if not file:
file = self._path_to_file(path, config)
- files.append(file)
+ self._save_to_file(file.abs_src_path, f"# {name}")
# Create and yield archive view
- self._save_to_file(file.abs_src_path, f"# {name}")
yield Archive(name, file, config)
+ files.append(file)
# Assign post to archive
assert isinstance(file.page, Archive)
@@ -564,11 +587,11 @@ def _generate_categories(self, config: MkDocsConfig, files: Files):
file = files.get_file_from_path(path)
if not file:
file = self._path_to_file(path, config)
- files.append(file)
-
- # Create and yield archive view
self._save_to_file(file.abs_src_path, f"# {name}")
+
+ # Create and yield category view
yield Category(name, file, config)
+ files.append(file)
# Assign post to category and vice versa
assert isinstance(file.page, Category)
@@ -584,12 +607,16 @@ def _generate_pages(self, view: View, config: MkDocsConfig, files: Files):
step = self.config.pagination_per_page
prev = view
- # Compute pagination boundaries and create pages
+ # Compute pagination boundaries and create pages - pages are internally
+ # handled as copies of a view, as they map to the same source location
for at in range(step, len(view.posts), step):
- path = self._format_path_for_pagination(view.url, 1 + at // step)
+ base, _ = posixpath.splitext(view.file.src_uri)
+
+ # Compute path and create a file for pagination
+ path = self._format_path_for_pagination(base, 1 + at // step)
file = self._path_to_file(path, config)
- # Replace post source file system path and apend to files
+ # Replace source file system path and append to files
file.src_uri = view.file.src_uri
file.abs_src_path = view.file.abs_src_path
files.append(file)
@@ -631,7 +658,7 @@ def _render(self, view: View):
# Render excerpts for selected posts
posts = [
self._render_post(post.excerpt, view)
- for post in posts
+ for post in posts if post.excerpt
]
# Return posts and pagination
diff --git a/material/plugins/blog/structure/__init__.py b/material/plugins/blog/structure/__init__.py
index 6003cadd372..ae202c3e213 100644
--- a/material/plugins/blog/structure/__init__.py
+++ b/material/plugins/blog/structure/__init__.py
@@ -18,6 +18,8 @@
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
+from __future__ import annotations
+
import logging
import os
import yaml
@@ -33,7 +35,6 @@
from mkdocs.structure.toc import get_toc
from mkdocs.utils.meta import YAML_RE
from re import Match
-from typing import Union
from yaml import SafeLoader
from .config import PostConfig
@@ -51,7 +52,7 @@ class Post(Page):
def __init__(self, file: File, config: MkDocsConfig):
super().__init__(None, file, config)
- # Resolve path relative to docs directory for error reporting
+ # Resolve path relative to docs directory
docs = os.path.relpath(config.docs_dir)
path = os.path.relpath(file.abs_src_path, docs)
@@ -106,10 +107,10 @@ def __init__(self, file: File, config: MkDocsConfig):
f"{e}"
)
- # Excerpts are subsets of posts that are used in views like archive and
+ # Excerpts are subsets of posts that are used in pages like archive and
# category views. They are not rendered as standalone pages, but are
- # included in the context of the parent post. Each post has a dedicated
- # excerpt instance which is reused when rendering views.
+ # rendered in the context of a view. Each post has a dedicated excerpt
+ # instance which is reused when rendering views.
self.excerpt: Excerpt = None
# Initialize authors and actegories
@@ -205,7 +206,7 @@ class View(Page):
# Initialize view
def __init__(self, title: str | None, file: File, config: MkDocsConfig):
super().__init__(title, file, config)
- self.parent: Union[View, Section]
+ self.parent: View | Section
# Initialize posts and views
self.posts: list[Post] = []
@@ -241,21 +242,26 @@ class Category(View):
def _patch(config: MkDocsConfig):
config = copy(config)
- # Copy configuration that needs to be patched
- config.validation = copy(config.validation)
- config.validation.links = copy(config.validation.links)
- config.mdx_configs = copy(config.mdx_configs)
- config.mdx_configs["toc"] = copy(config.mdx_configs["toc"])
+ # Copy parts of configuration that needs to be patched
+ config.validation = copy(config.validation)
+ config.validation.links = copy(config.validation.links)
+ config.markdown_extensions = copy(config.markdown_extensions)
+ config.mdx_configs = copy(config.mdx_configs)
+
+ # Make sure that the author did not add another instance of the table of
+ # contents extension to the configuration, as this leads to weird behavior
+ if "markdown.extensions.toc" in config.markdown_extensions:
+ config.markdown_extensions.remove("markdown.extensions.toc")
# In order to render excerpts for posts, we need to make sure that the
# table of contents extension is appropriately configured
config.mdx_configs["toc"] = {
- **config.mdx_configs["toc"],
+ **config.mdx_configs.get("toc", {}),
**{
- "anchorlink": True, # Render headline as clickable
- "baselevel": 2, # Render h1 as h2 and so forth
- "permalink": False, # Remove permalinks
- "toc_depth": 2 # Remove everything below h2
+ "anchorlink": True, # Render headline as clickable
+ "baselevel": 2, # Render h1 as h2 and so forth
+ "permalink": False, # Remove permalinks
+ "toc_depth": 2 # Remove everything below h2
}
}
diff --git a/material/plugins/blog/structure/options.py b/material/plugins/blog/structure/options.py
index 0b36d8cfba4..d37779185bd 100644
--- a/material/plugins/blog/structure/options.py
+++ b/material/plugins/blog/structure/options.py
@@ -60,12 +60,14 @@ def pre_validation(self, config: Config, key_name: str):
if not isinstance(config[key_name], dict):
config[key_name] = { "created": config[key_name] }
- # Initialize date dictionary and convert all date values to datetime
- config[key_name] = DateDict(config[key_name])
+ # Convert all date values to datetime
for key, value in config[key_name].items():
if isinstance(value, date):
config[key_name][key] = datetime.combine(value, time())
+ # Initialize date dictionary
+ config[key_name] = DateDict(config[key_name])
+
# Ensure each date value is of type datetime
def run_validation(self, value: DateDict):
for key in value:
diff --git a/material/plugins/blog/templates/__init__.py b/material/plugins/blog/templates/__init__.py
index ea7edee7f51..9f7d794bb48 100644
--- a/material/plugins/blog/templates/__init__.py
+++ b/material/plugins/blog/templates/__init__.py
@@ -29,7 +29,7 @@
# Filter for normalizing URLs with support for paginated views
@pass_context
-def url_filter(context: Context, url: str | None):
+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
diff --git a/material/plugins/info/config.py b/material/plugins/info/config.py
index 8d6e085838f..cbd64d4c0cb 100644
--- a/material/plugins/info/config.py
+++ b/material/plugins/info/config.py
@@ -30,6 +30,6 @@ class InfoConfig(Config):
enabled = Type(bool, default = True)
enabled_on_serve = Type(bool, default = False)
- # Options for archive
+ # Settings for archive
archive = Type(bool, default = True)
archive_stop_on_violation = Type(bool, default = True)
diff --git a/material/plugins/info/plugin.py b/material/plugins/info/plugin.py
index 11764b6066f..41dc0373ff7 100644
--- a/material/plugins/info/plugin.py
+++ b/material/plugins/info/plugin.py
@@ -87,8 +87,7 @@ def on_config(self, config):
# hack to detect whether the custom_dir setting was used without parsing
# mkdocs.yml again - we check at which position the directory provided
# by the theme resides, and if it's not the first one, abort.
- path = get_theme_dir(config.theme.name)
- if config.theme.dirs.index(path):
+ if config.theme.dirs.index(get_theme_dir(config.theme.name)):
log.error("Please remove 'custom_dir' setting.")
self._help_on_customizations_and_exit()
@@ -107,7 +106,7 @@ def on_config(self, config):
archive = BytesIO()
example = input("\nPlease name your bug report (2-4 words): ")
example, _ = os.path.splitext(example)
- example = slugify(example, "-")
+ example = "-".join([present, slugify(example, "-")])
# Create self-contained example from project
files: list[str] = []
@@ -130,7 +129,7 @@ def on_config(self, config):
]))
)
- # Add information in platform
+ # Add information on platform
f.writestr(
os.path.join(example, "platform.json"),
json.dumps(
diff --git a/material/plugins/offline/plugin.py b/material/plugins/offline/plugin.py
index 8cfa110f665..abcb25984ad 100644
--- a/material/plugins/offline/plugin.py
+++ b/material/plugins/offline/plugin.py
@@ -21,7 +21,6 @@
import os
from mkdocs.plugins import BasePlugin, event_priority
-from mkdocs.utils import write_file
from .config import OfflineConfig
@@ -42,10 +41,10 @@ def on_config(self, config):
config.use_directory_urls = False
# Append iframe-worker to polyfills/shims
- config.extra.polyfills = config.extra.get("polyfills", [])
- if not any("iframe-worker" in url for url in config.extra.polyfills):
- worker = "https://unpkg.com/iframe-worker/shim"
- config.extra.polyfills.append(worker)
+ config.extra["polyfills"] = config.extra.get("polyfills", [])
+ if not any("iframe-worker" in url for url in config.extra["polyfills"]):
+ script = "https://unpkg.com/iframe-worker/shim"
+ config.extra["polyfills"].append(script)
# Add support for offline search (run latest) - the search index is copied
# and inlined into a script, so that it can be used without a server
@@ -54,14 +53,17 @@ def on_post_build(self, *, config):
if not self.config.enabled:
return
- # Check for existence of search index
- path = os.path.join(config.site_dir, "search", "search_index.json")
- if not os.path.isfile(path):
+ # Ensure presence of search index
+ path = os.path.join(config.site_dir, "search")
+ file = os.path.join(path, "search_index.json")
+ if not os.path.isfile(file):
return
- # Create script with inlined search index
- with open(path, encoding = "utf-8") as f:
- write_file(
- f"var __index = {f.read()}".encode("utf-8"),
- path.replace(".json", ".js"),
- )
+ # Obtain search index contents
+ with open(file, encoding = "utf-8") as f:
+ data = f.read()
+
+ # Inline search index contents into script
+ file = os.path.join(path, "search_index.js")
+ with open(file, "w", encoding = "utf-8") as f:
+ f.write(f"var __index = {data}")
diff --git a/material/plugins/search/config.py b/material/plugins/search/config.py
index d09aec7f1df..e601fb8fd9f 100644
--- a/material/plugins/search/config.py
+++ b/material/plugins/search/config.py
@@ -45,11 +45,11 @@ class SearchConfig(Config):
separator = Optional(Type(str))
pipeline = ListOfItems(Choice(pipeline), default = [])
- # Options for text segmentation (Chinese)
+ # Settings for text segmentation (Chinese)
jieba_dict = Optional(Type(str))
jieba_dict_user = Optional(Type(str))
- # Unsupported options, originally implemented in MkDocs
+ # Unsupported settings, originally implemented in MkDocs
indexing = Deprecated(message = "Unsupported option")
prebuild_index = Deprecated(message = "Unsupported option")
min_search_length = Deprecated(message = "Unsupported option")
diff --git a/material/plugins/search/plugin.py b/material/plugins/search/plugin.py
index 33ccd72e7af..33fe4bbf73c 100644
--- a/material/plugins/search/plugin.py
+++ b/material/plugins/search/plugin.py
@@ -299,7 +299,7 @@ class Element:
"""
# Initialize HTML element
- def __init__(self, tag, attrs = dict()):
+ def __init__(self, tag, attrs = {}):
self.tag = tag
self.attrs = attrs
diff --git a/material/plugins/social/config.py b/material/plugins/social/config.py
index 0b459ac6627..2d87c25e052 100644
--- a/material/plugins/social/config.py
+++ b/material/plugins/social/config.py
@@ -30,12 +30,12 @@ class SocialConfig(Config):
enabled = Type(bool, default = True)
cache_dir = Type(str, default = ".cache/plugin/social")
- # Options for social cards
+ # Settings for social cards
cards = Type(bool, default = True)
cards_dir = Type(str, default = "assets/images/social")
cards_layout_options = Type(dict, default = {})
- # Deprecated options
+ # Deprecated settings
cards_color = Deprecated(
option_type = Type(dict, default = {}),
message =
diff --git a/material/plugins/social/plugin.py b/material/plugins/social/plugin.py
index 650cb9c9626..011992b8184 100644
--- a/material/plugins/social/plugin.py
+++ b/material/plugins/social/plugin.py
@@ -18,6 +18,19 @@
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
+# -----------------------------------------------------------------------------
+# Disclaimer
+# -----------------------------------------------------------------------------
+# Please note: this version of the social plugin is not actively development
+# anymore. Instead, Material for MkDocs Insiders ships a complete rewrite of
+# the plugin which is much more powerful and addresses all shortcomings of
+# this implementation. Additionally, the new social plugin allows to create
+# entirely custom social cards. You can probably imagine, that this was a lot
+# of work to pull off. If you run into problems, or want to have additional
+# functionality, please consider sponsoring the project. You can then use the
+# new version of the plugin immediately.
+# -----------------------------------------------------------------------------
+
import concurrent.futures
import functools
import logging
@@ -159,7 +172,7 @@ def on_page_markdown(self, markdown, page, config, files):
)
sys.exit(1)
- # Generate social card if not in cache - TODO: values from mkdocs.yml
+ # Generate social card if not in cache
hash = md5("".join([
site_name,
str(title),
@@ -267,17 +280,6 @@ def _render_text(self, size, font, text, lmax, spacing = 0):
lines.append(words)
words = [word]
- # # Balance words on last line - TODO: overflows when broken word is too long
- # if len(lines) > 0:
- # prev = len(" ".join(lines[-1]))
- # last = len(" ".join(words))#
-
- # print(last, prev)
-
- # # Heuristic: try to find a good ratio
- # if last / prev < 0.6:
- # words.insert(0, lines[-1].pop())
-
# Join words for each line and create image
lines.append(words)
lines = [" ".join(line) for line in lines]
@@ -424,7 +426,7 @@ def _load_font(self, config):
font_filename_base = name.replace(' ', '')
filename_regex = re.escape(font_filename_base)+r"-(\w+)\.[ot]tf$"
- font = dict()
+ font = {}
# Check for cached files - note these may be in subfolders
for currentpath, folders, files in os.walk(self.cache):
for file in files:
diff --git a/material/plugins/tags/config.py b/material/plugins/tags/config.py
index ab94a71b364..763581e56a8 100644
--- a/material/plugins/tags/config.py
+++ b/material/plugins/tags/config.py
@@ -33,9 +33,9 @@
class TagsConfig(Config):
enabled = Type(bool, default = True)
- # Options for tags
+ # Settings for tags
tags_file = Optional(Type(str))
- tags_extra_files = Type(dict, default = dict())
+ tags_extra_files = Type(dict, default = {})
tags_slugify = Type((type(slugify), partial), default = slugify)
tags_slugify_separator = Type(str, default = "-")
tags_compare = Optional(Type(type(casefold)))
diff --git a/package-lock.json b/package-lock.json
index 663885c5dbc..ffbd80b319e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "mkdocs-material",
- "version": "9.2.0",
+ "version": "9.2.5",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "mkdocs-material",
- "version": "9.2.0",
+ "version": "9.2.5",
"license": "MIT",
"dependencies": {
"clipboard": "^2.0.11",
diff --git a/package.json b/package.json
index 45864574c5f..c37b23b4c3c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "mkdocs-material",
- "version": "9.2.0",
+ "version": "9.2.5",
"description": "Documentation that simply works",
"keywords": [
"mkdocs",
diff --git a/src/.overrides/hooks/translations.py b/src/.overrides/hooks/translations.py
index c4e2d185390..0bf458d960e 100644
--- a/src/.overrides/hooks/translations.py
+++ b/src/.overrides/hooks/translations.py
@@ -38,8 +38,8 @@ def on_page_markdown(markdown: str, *, page: Page, config: MkDocsConfig, files):
return
# Collect all existing languages
- names: dict[str, str] = dict()
- known: dict[str, dict[str, str]] = dict()
+ names: dict[str, str] = {}
+ known: dict[str, dict[str, str]] = {}
for path in glob("src/partials/languages/*.html"):
with open(path, "r", encoding = "utf-8") as f:
data = f.read()
diff --git a/src/partials/languages/af.html b/src/partials/languages/af.html
index 9e201156723..b7f9f8fac5e 100644
--- a/src/partials/languages/af.html
+++ b/src/partials/languages/af.html
@@ -25,20 +25,52 @@
"language": "af",
"action.edit": "Wysig hierdie bladsy",
"action.skip": "Slaan oor na inhoud",
+ "action.view": "Bekyk bron van hierdie bladsy",
+ "announce.dismiss": "Moenie dit weer wys nie",
+ "blog.archive": "Argief",
+ "blog.categories": "Kategorieë",
+ "blog.categories.in": "binne",
+ "blog.continue": "Lees verder",
+ "blog.draft": "Konsep",
+ "blog.index": "Terug na indeks",
+ "blog.meta": "Metadata",
+ "blog.references": "Verwante skakels",
"clipboard.copy": "Kopieer na knipbord",
"clipboard.copied": "gekopieer na knipbord",
+ "consent.accept": "Aanvaar",
+ "consent.manage": "Bestuur instellings",
+ "consent.reject": "Verwerp",
+ "footer": "Voetskrif",
"footer.next": "Volgende",
"footer.previous": "Vorige",
+ "header": "Kopskrif",
"meta.comments": "Kommentaar",
"meta.source": "Bron",
+ "nav": "Navigasie",
+ "readtime.one": "1 minuut se lees",
+ "readtime.other": "# minuut se lees",
+ "rss.created": "RSS-voer geskep",
+ "rss.updated": "RSS-voer van opgedateerde inhoud",
+ "search": "Soek",
"search.config.lang": "nl",
"search.placeholder": "Soek",
+ "search.share": "Deel",
+ "search.reset": "Terugstel",
+ "search.result.initializer": "Inisialisering van soektog",
"search.result.placeholder": "Tik om te begin soek",
"search.result.none": "Geen ooreenstemmende dokumente",
"search.result.one": "1 ooreenstemmende dokument",
"search.result.other": "# ooreenstemmende dokumente",
+ "search.result.more.one": "1 meer op hierdie bladsy",
+ "search.result.more.other": "# meer op hierdie bladsy",
+ "search.result.term.missing": "Vermis",
+ "select.language": "Kies taal",
+ "select.version": "Kies weergawe",
"source": "Slaan oor na inhoud",
+ "source.file.contributors": "Medewerkers",
"source.file.date.created": "Geskep",
"source.file.date.updated": "Laaste opdatering",
- "toc": "Inhoudsopgawe"
+ "tabs": "Duimgids",
+ "toc": "Inhoudsopgawe",
+ "top": "Terug na bo"
}[key] }}{% endmacro %}
diff --git a/src/plugins/blog/config.py b/src/plugins/blog/config.py
index a8d3f1a5ba7..c7a85095842 100644
--- a/src/plugins/blog/config.py
+++ b/src/plugins/blog/config.py
@@ -31,11 +31,11 @@
class BlogConfig(Config):
enabled = Type(bool, default = True)
- # Options for blog
+ # Settings for blog
blog_dir = Type(str, default = "blog")
blog_toc = Type(bool, default = False)
- # Options for posts
+ # Settings for posts
post_dir = Type(str, default = "{blog}/posts")
post_date_format = Type(str, default = "long")
post_url_date_format = Type(str, default = "yyyy/MM/dd")
@@ -50,7 +50,7 @@ class BlogConfig(Config):
post_readtime = Type(bool, default = True)
post_readtime_words_per_minute = Type(int, default = 265)
- # Options for archive
+ # Settings for archive
archive = Type(bool, default = True)
archive_name = Type(str, default = "blog.archive")
archive_date_format = Type(str, default = "yyyy")
@@ -58,7 +58,7 @@ class BlogConfig(Config):
archive_url_format = Type(str, default = "archive/{date}")
archive_toc = Optional(Type(bool))
- # Options for categories
+ # Settings for categories
categories = Type(bool, default = True)
categories_name = Type(str, default = "blog.categories")
categories_url_format = Type(str, default = "category/{slug}")
@@ -67,7 +67,7 @@ class BlogConfig(Config):
categories_allowed = Type(list, default = [])
categories_toc = Optional(Type(bool))
- # Options for pagination
+ # Settings for pagination
pagination = Type(bool, default = True)
pagination_per_page = Type(int, default = 10)
pagination_url_format = Type(str, default = "page/{page}")
@@ -75,14 +75,14 @@ class BlogConfig(Config):
pagination_if_single_page = Type(bool, default = False)
pagination_keep_content = Type(bool, default = False)
- # Options for authors
+ # Settings for authors
authors = Type(bool, default = True)
authors_file = Type(str, default = "{blog}/.authors.yml")
- # Options for drafts
+ # Settings for drafts
draft = Type(bool, default = False)
draft_on_serve = Type(bool, default = True)
draft_if_future_date = Type(bool, default = False)
- # Deprecated options
+ # Deprecated settings
pagination_template = Deprecated(moved_to = "pagination_format")
diff --git a/src/plugins/blog/plugin.py b/src/plugins/blog/plugin.py
index 2d156390f6f..9bfb89685d3 100644
--- a/src/plugins/blog/plugin.py
+++ b/src/plugins/blog/plugin.py
@@ -125,6 +125,9 @@ def on_files(self, files, *, config):
file.abs_dest_path = os.path.join(site, file.dest_path)
file.url = file.url.replace(path, root)
+ # Generate entrypoint, if it does not exist yet
+ self._generate(config, files)
+
# Resolve and load posts and generate indexes (run later) - we resolve all
# posts after the navigation is constructed in order to allow other plugins
# to alter the navigation (e.g. awesome-pages) before we start to add pages
@@ -161,7 +164,7 @@ def on_nav(self, nav, *, config, files):
# Attach and link views for archive
title = self._translate(self.config.archive_name, config)
- self._attach_to(self.blog.parent, Section(title, views), nav)
+ self._attach_to(self.blog, Section(title, views), nav)
# Generate and attach views for categories
if self.config.categories:
@@ -170,7 +173,7 @@ def on_nav(self, nav, *, config, files):
# Attach and link views for categories
title = self._translate(self.config.categories_name, config)
- self._attach_to(self.blog.parent, Section(title, views), nav)
+ self._attach_to(self.blog, Section(title, views), nav)
# Paginate generated views, if enabled
if self.config.pagination:
@@ -199,7 +202,14 @@ def on_page_markdown(self, markdown, *, page, config, files):
if page in self._resolve_views(self.blog):
assert isinstance(page, View)
if 0 < page.pages.index(page):
- return f"# {page.title}"
+ main = page.parent
+
+ # We need to use the rendered title of the original view
+ # if the author set the title in the page's contents, or
+ # it would be overridden with the one set in mkdocs.yml,
+ # which would result in inconsistent headings
+ name = main._title_from_render or main.title
+ return f"# {name}"
# Nothing more to be done for views
return
@@ -234,7 +244,7 @@ def on_page_markdown(self, markdown, *, page, config, files):
# is not already present, so we can remove footnotes or other content
# from the excerpt without affecting the content of the excerpt
if separator not in page.markdown:
- path = page.file.src_uri
+ path = page.file.src_path
if self.config.post_excerpt == "required":
raise PluginError(
f"Couldn't find '{separator}' separator in '{path}'"
@@ -280,21 +290,20 @@ def on_page_context(self, context, *, page, config, nav):
main = page.parent
# If this page is a view, and the parent page is a view as well, we got
- # a paginated view and need to update the parent view in the navigation.
- # Paginated views are always rendered last, which is why we can safely
- # mutate the navigation at this point
+ # a paginated view and need to replace the parent with the current view.
+ # Paginated views are always rendered at the end of the build, which is
+ # why we can safely mutate the navigation at this point
if isinstance(main, View):
- assert isinstance(main.parent, Section)
-
- # Replace view in navigation and rewire view - the current view in
- # the navigation becomes the main view, thus the entire chain moves
- # one level up. It's essential that the rendering order is linear,
- # or else we might end up with a broken navigation.
- at = main.parent.children.index(main)
- main.parent.children[at] = page
page.parent = main.parent
- # Render excerpts and perpare pagination
+ # Replace view in navigation and rewire it - the current view in the
+ # navigation becomes the main view, thus the entire chain moves one
+ # level up. It's essential that the rendering order is linear, or
+ # else we might end up with a broken navigation.
+ items = self._resolve_siblings(main, nav)
+ items[items.index(main)] = page
+
+ # Render excerpts and prepare pagination
posts, pagination = self._render(page)
# Render pagination links
@@ -326,31 +335,19 @@ def _is_excluded(self, post: Post):
# and must be explicitly enabled by the author.
if not isinstance(post.config.draft, bool):
if self.config.draft_if_future_date:
- return post.config.date > datetime.now()
+ return post.config.date.created > datetime.now()
# Post might be a draft
return bool(post.config.draft)
# -------------------------------------------------------------------------
- # Resolve entrypoint - the entrypoint of the blog hosts all posts, sorted
- # by descending date. The entrypoint must always be present, even if there
- # are no posts, and is automatically created if it does not exist yet. Note
- # that posts might be paginated, but this is configurable by the author.
+ # Resolve entrypoint - the entrypoint of the blog must have been created
+ # if it did not exist before, and hosts all posts sorted by descending date
def _resolve(self, files: Files, config: MkDocsConfig, nav: Navigation):
path = os.path.join(self.config.blog_dir, "index.md")
path = os.path.normpath(path)
- # Create entrypoint, if it does not exist
- docs = os.path.relpath(config.docs_dir)
- file = os.path.join(docs, path)
- if not os.path.isfile(file):
- self._save_to_file(file, "# Blog\n\n")
-
- # Append entrypoint to files - note that the entrypoint is added to
- # the docs directory, so we need to set the temporary flag to false
- files.append(self._path_to_file(path, config, temp = False))
-
# Obtain entrypoint page
file = files.get_file_from_path(path)
page = file.page
@@ -364,7 +361,7 @@ def _resolve(self, files: Files, config: MkDocsConfig, nav: Navigation):
])
# Update entrypoint in navigation
- for items in [view.parent.children, nav.pages]:
+ for items in [self._resolve_siblings(view, nav), nav.pages]:
items[items.index(page)] = view
# Return view
@@ -379,7 +376,7 @@ def _resolve_post(self, file: File, config: MkDocsConfig):
path = self._format_path_for_post(post, config)
temp = self._path_to_file(path, config, temp = False)
- # Replace post destination file system path and URL
+ # Replace destination file system path and URL
file.dest_uri = temp.dest_uri
file.abs_dest_path = temp.abs_dest_path
file.url = temp.url
@@ -421,16 +418,18 @@ def _resolve_authors(self, config: MkDocsConfig):
path = self.config.authors_file.format(blog = self.config.blog_dir)
path = os.path.normpath(path)
- # If the authors file does not exist, return an empty dictionary
+ # Resolve path relative to docs directory
docs = os.path.relpath(config.docs_dir)
file = os.path.join(docs, path)
+
+ # If the authors file does not exist, return here
+ config: Authors = Authors()
if not os.path.isfile(file):
- authors: dict[str, Author] = dict()
- return authors
+ return config.authors
# Open file and parse as YAML
with open(file, encoding = "utf-8") as f:
- config: Authors = Authors(os.path.abspath(file))
+ config.config_file_path = os.path.abspath(file)
try:
config.load_dict(yaml.load(f, SafeLoader) or {})
@@ -484,6 +483,13 @@ def _resolve_views(self, view: View):
assert isinstance(page, View)
yield page
+ # Resolve siblings of a navigation item
+ def _resolve_siblings(self, item: StructureItem, nav: Navigation):
+ if isinstance(item.parent, Section):
+ return item.parent.children
+ else:
+ return nav.items
+
# -------------------------------------------------------------------------
# Attach a list of pages to each other and to the given parent item without
@@ -496,16 +502,16 @@ def _attach(self, parent: StructureItem, pages: list[Page]):
page.previous_page = tail
page.next_page = head
- # Attach a section to the given parent section, make sure it's pages are
+ # Attach a section as a sibling to the given view, make sure it's pages are
# part of the navigation, and ensure all pages are linked correctly
- def _attach_to(self, parent: Section, section: Section, nav: Navigation):
- section.parent = parent
-
- # Determine the parent section to attach the section to, which might be
- # the top-level navigation, if no parent section was given. Note, that
- # it's currently not possible to chose the position of a section, but
- # we might add support for this in the future.
- items = parent.children if parent else nav.items
+ def _attach_to(self, view: View, section: Section, nav: Navigation):
+ section.parent = view.parent
+
+ # Resolve siblings, which are the children of the parent section, or
+ # the top-level list of navigation items if the view is at the root of
+ # the project, and append the given section to it. It's currently not
+ # possible to chose the position of a section.
+ items = self._resolve_siblings(view, nav)
items.append(section)
# Find last sibling that is a page, skipping sections, as we need to
@@ -519,6 +525,23 @@ def _attach_to(self, parent: Section, section: Section, nav: Navigation):
# -------------------------------------------------------------------------
+ # Generate entrypoint - the entrypoint must always be present, and thus is
+ # created before the navigation is constructed if it does not exist yet
+ def _generate(self, config: MkDocsConfig, files: Files):
+ path = os.path.join(self.config.blog_dir, "index.md")
+ path = os.path.normpath(path)
+
+ # Create entrypoint, if it does not exist - note that the entrypoint is
+ # added to the docs directory, not to the temporary directory
+ docs = os.path.relpath(config.docs_dir)
+ file = os.path.join(docs, path)
+ if not os.path.isfile(file):
+ file = self._path_to_file(path, config, temp = False)
+ self._save_to_file(file.abs_src_path, "# Blog\n\n")
+
+ # Append entrypoint to files
+ files.append(file)
+
# Generate views for archive - analyze posts and generate the necessary
# views, taking the date format provided by the author into account
def _generate_archive(self, config: MkDocsConfig, files: Files):
@@ -533,11 +556,11 @@ def _generate_archive(self, config: MkDocsConfig, files: Files):
file = files.get_file_from_path(path)
if not file:
file = self._path_to_file(path, config)
- files.append(file)
+ self._save_to_file(file.abs_src_path, f"# {name}")
# Create and yield archive view
- self._save_to_file(file.abs_src_path, f"# {name}")
yield Archive(name, file, config)
+ files.append(file)
# Assign post to archive
assert isinstance(file.page, Archive)
@@ -564,11 +587,11 @@ def _generate_categories(self, config: MkDocsConfig, files: Files):
file = files.get_file_from_path(path)
if not file:
file = self._path_to_file(path, config)
- files.append(file)
-
- # Create and yield archive view
self._save_to_file(file.abs_src_path, f"# {name}")
+
+ # Create and yield category view
yield Category(name, file, config)
+ files.append(file)
# Assign post to category and vice versa
assert isinstance(file.page, Category)
@@ -584,12 +607,16 @@ def _generate_pages(self, view: View, config: MkDocsConfig, files: Files):
step = self.config.pagination_per_page
prev = view
- # Compute pagination boundaries and create pages
+ # Compute pagination boundaries and create pages - pages are internally
+ # handled as copies of a view, as they map to the same source location
for at in range(step, len(view.posts), step):
- path = self._format_path_for_pagination(view.url, 1 + at // step)
+ base, _ = posixpath.splitext(view.file.src_uri)
+
+ # Compute path and create a file for pagination
+ path = self._format_path_for_pagination(base, 1 + at // step)
file = self._path_to_file(path, config)
- # Replace post source file system path and apend to files
+ # Replace source file system path and append to files
file.src_uri = view.file.src_uri
file.abs_src_path = view.file.abs_src_path
files.append(file)
@@ -631,7 +658,7 @@ def _render(self, view: View):
# Render excerpts for selected posts
posts = [
self._render_post(post.excerpt, view)
- for post in posts
+ for post in posts if post.excerpt
]
# Return posts and pagination
diff --git a/src/plugins/blog/structure/__init__.py b/src/plugins/blog/structure/__init__.py
index 6003cadd372..ae202c3e213 100644
--- a/src/plugins/blog/structure/__init__.py
+++ b/src/plugins/blog/structure/__init__.py
@@ -18,6 +18,8 @@
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
+from __future__ import annotations
+
import logging
import os
import yaml
@@ -33,7 +35,6 @@
from mkdocs.structure.toc import get_toc
from mkdocs.utils.meta import YAML_RE
from re import Match
-from typing import Union
from yaml import SafeLoader
from .config import PostConfig
@@ -51,7 +52,7 @@ class Post(Page):
def __init__(self, file: File, config: MkDocsConfig):
super().__init__(None, file, config)
- # Resolve path relative to docs directory for error reporting
+ # Resolve path relative to docs directory
docs = os.path.relpath(config.docs_dir)
path = os.path.relpath(file.abs_src_path, docs)
@@ -106,10 +107,10 @@ def __init__(self, file: File, config: MkDocsConfig):
f"{e}"
)
- # Excerpts are subsets of posts that are used in views like archive and
+ # Excerpts are subsets of posts that are used in pages like archive and
# category views. They are not rendered as standalone pages, but are
- # included in the context of the parent post. Each post has a dedicated
- # excerpt instance which is reused when rendering views.
+ # rendered in the context of a view. Each post has a dedicated excerpt
+ # instance which is reused when rendering views.
self.excerpt: Excerpt = None
# Initialize authors and actegories
@@ -205,7 +206,7 @@ class View(Page):
# Initialize view
def __init__(self, title: str | None, file: File, config: MkDocsConfig):
super().__init__(title, file, config)
- self.parent: Union[View, Section]
+ self.parent: View | Section
# Initialize posts and views
self.posts: list[Post] = []
@@ -241,21 +242,26 @@ class Category(View):
def _patch(config: MkDocsConfig):
config = copy(config)
- # Copy configuration that needs to be patched
- config.validation = copy(config.validation)
- config.validation.links = copy(config.validation.links)
- config.mdx_configs = copy(config.mdx_configs)
- config.mdx_configs["toc"] = copy(config.mdx_configs["toc"])
+ # Copy parts of configuration that needs to be patched
+ config.validation = copy(config.validation)
+ config.validation.links = copy(config.validation.links)
+ config.markdown_extensions = copy(config.markdown_extensions)
+ config.mdx_configs = copy(config.mdx_configs)
+
+ # Make sure that the author did not add another instance of the table of
+ # contents extension to the configuration, as this leads to weird behavior
+ if "markdown.extensions.toc" in config.markdown_extensions:
+ config.markdown_extensions.remove("markdown.extensions.toc")
# In order to render excerpts for posts, we need to make sure that the
# table of contents extension is appropriately configured
config.mdx_configs["toc"] = {
- **config.mdx_configs["toc"],
+ **config.mdx_configs.get("toc", {}),
**{
- "anchorlink": True, # Render headline as clickable
- "baselevel": 2, # Render h1 as h2 and so forth
- "permalink": False, # Remove permalinks
- "toc_depth": 2 # Remove everything below h2
+ "anchorlink": True, # Render headline as clickable
+ "baselevel": 2, # Render h1 as h2 and so forth
+ "permalink": False, # Remove permalinks
+ "toc_depth": 2 # Remove everything below h2
}
}
diff --git a/src/plugins/blog/structure/options.py b/src/plugins/blog/structure/options.py
index 0b36d8cfba4..d37779185bd 100644
--- a/src/plugins/blog/structure/options.py
+++ b/src/plugins/blog/structure/options.py
@@ -60,12 +60,14 @@ def pre_validation(self, config: Config, key_name: str):
if not isinstance(config[key_name], dict):
config[key_name] = { "created": config[key_name] }
- # Initialize date dictionary and convert all date values to datetime
- config[key_name] = DateDict(config[key_name])
+ # Convert all date values to datetime
for key, value in config[key_name].items():
if isinstance(value, date):
config[key_name][key] = datetime.combine(value, time())
+ # Initialize date dictionary
+ config[key_name] = DateDict(config[key_name])
+
# Ensure each date value is of type datetime
def run_validation(self, value: DateDict):
for key in value:
diff --git a/src/plugins/blog/templates/__init__.py b/src/plugins/blog/templates/__init__.py
index ea7edee7f51..9f7d794bb48 100644
--- a/src/plugins/blog/templates/__init__.py
+++ b/src/plugins/blog/templates/__init__.py
@@ -29,7 +29,7 @@
# Filter for normalizing URLs with support for paginated views
@pass_context
-def url_filter(context: Context, url: str | None):
+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
diff --git a/src/plugins/info/config.py b/src/plugins/info/config.py
index 8d6e085838f..cbd64d4c0cb 100644
--- a/src/plugins/info/config.py
+++ b/src/plugins/info/config.py
@@ -30,6 +30,6 @@ class InfoConfig(Config):
enabled = Type(bool, default = True)
enabled_on_serve = Type(bool, default = False)
- # Options for archive
+ # Settings for archive
archive = Type(bool, default = True)
archive_stop_on_violation = Type(bool, default = True)
diff --git a/src/plugins/info/plugin.py b/src/plugins/info/plugin.py
index 11764b6066f..41dc0373ff7 100644
--- a/src/plugins/info/plugin.py
+++ b/src/plugins/info/plugin.py
@@ -87,8 +87,7 @@ def on_config(self, config):
# hack to detect whether the custom_dir setting was used without parsing
# mkdocs.yml again - we check at which position the directory provided
# by the theme resides, and if it's not the first one, abort.
- path = get_theme_dir(config.theme.name)
- if config.theme.dirs.index(path):
+ if config.theme.dirs.index(get_theme_dir(config.theme.name)):
log.error("Please remove 'custom_dir' setting.")
self._help_on_customizations_and_exit()
@@ -107,7 +106,7 @@ def on_config(self, config):
archive = BytesIO()
example = input("\nPlease name your bug report (2-4 words): ")
example, _ = os.path.splitext(example)
- example = slugify(example, "-")
+ example = "-".join([present, slugify(example, "-")])
# Create self-contained example from project
files: list[str] = []
@@ -130,7 +129,7 @@ def on_config(self, config):
]))
)
- # Add information in platform
+ # Add information on platform
f.writestr(
os.path.join(example, "platform.json"),
json.dumps(
diff --git a/src/plugins/offline/plugin.py b/src/plugins/offline/plugin.py
index 8cfa110f665..abcb25984ad 100644
--- a/src/plugins/offline/plugin.py
+++ b/src/plugins/offline/plugin.py
@@ -21,7 +21,6 @@
import os
from mkdocs.plugins import BasePlugin, event_priority
-from mkdocs.utils import write_file
from .config import OfflineConfig
@@ -42,10 +41,10 @@ def on_config(self, config):
config.use_directory_urls = False
# Append iframe-worker to polyfills/shims
- config.extra.polyfills = config.extra.get("polyfills", [])
- if not any("iframe-worker" in url for url in config.extra.polyfills):
- worker = "https://unpkg.com/iframe-worker/shim"
- config.extra.polyfills.append(worker)
+ config.extra["polyfills"] = config.extra.get("polyfills", [])
+ if not any("iframe-worker" in url for url in config.extra["polyfills"]):
+ script = "https://unpkg.com/iframe-worker/shim"
+ config.extra["polyfills"].append(script)
# Add support for offline search (run latest) - the search index is copied
# and inlined into a script, so that it can be used without a server
@@ -54,14 +53,17 @@ def on_post_build(self, *, config):
if not self.config.enabled:
return
- # Check for existence of search index
- path = os.path.join(config.site_dir, "search", "search_index.json")
- if not os.path.isfile(path):
+ # Ensure presence of search index
+ path = os.path.join(config.site_dir, "search")
+ file = os.path.join(path, "search_index.json")
+ if not os.path.isfile(file):
return
- # Create script with inlined search index
- with open(path, encoding = "utf-8") as f:
- write_file(
- f"var __index = {f.read()}".encode("utf-8"),
- path.replace(".json", ".js"),
- )
+ # Obtain search index contents
+ with open(file, encoding = "utf-8") as f:
+ data = f.read()
+
+ # Inline search index contents into script
+ file = os.path.join(path, "search_index.js")
+ with open(file, "w", encoding = "utf-8") as f:
+ f.write(f"var __index = {data}")
diff --git a/src/plugins/search/config.py b/src/plugins/search/config.py
index d09aec7f1df..e601fb8fd9f 100644
--- a/src/plugins/search/config.py
+++ b/src/plugins/search/config.py
@@ -45,11 +45,11 @@ class SearchConfig(Config):
separator = Optional(Type(str))
pipeline = ListOfItems(Choice(pipeline), default = [])
- # Options for text segmentation (Chinese)
+ # Settings for text segmentation (Chinese)
jieba_dict = Optional(Type(str))
jieba_dict_user = Optional(Type(str))
- # Unsupported options, originally implemented in MkDocs
+ # Unsupported settings, originally implemented in MkDocs
indexing = Deprecated(message = "Unsupported option")
prebuild_index = Deprecated(message = "Unsupported option")
min_search_length = Deprecated(message = "Unsupported option")
diff --git a/src/plugins/search/plugin.py b/src/plugins/search/plugin.py
index 33ccd72e7af..33fe4bbf73c 100644
--- a/src/plugins/search/plugin.py
+++ b/src/plugins/search/plugin.py
@@ -299,7 +299,7 @@ class Element:
"""
# Initialize HTML element
- def __init__(self, tag, attrs = dict()):
+ def __init__(self, tag, attrs = {}):
self.tag = tag
self.attrs = attrs
diff --git a/src/plugins/social/config.py b/src/plugins/social/config.py
index 0b459ac6627..2d87c25e052 100644
--- a/src/plugins/social/config.py
+++ b/src/plugins/social/config.py
@@ -30,12 +30,12 @@ class SocialConfig(Config):
enabled = Type(bool, default = True)
cache_dir = Type(str, default = ".cache/plugin/social")
- # Options for social cards
+ # Settings for social cards
cards = Type(bool, default = True)
cards_dir = Type(str, default = "assets/images/social")
cards_layout_options = Type(dict, default = {})
- # Deprecated options
+ # Deprecated settings
cards_color = Deprecated(
option_type = Type(dict, default = {}),
message =
diff --git a/src/plugins/social/plugin.py b/src/plugins/social/plugin.py
index 650cb9c9626..011992b8184 100644
--- a/src/plugins/social/plugin.py
+++ b/src/plugins/social/plugin.py
@@ -18,6 +18,19 @@
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
+# -----------------------------------------------------------------------------
+# Disclaimer
+# -----------------------------------------------------------------------------
+# Please note: this version of the social plugin is not actively development
+# anymore. Instead, Material for MkDocs Insiders ships a complete rewrite of
+# the plugin which is much more powerful and addresses all shortcomings of
+# this implementation. Additionally, the new social plugin allows to create
+# entirely custom social cards. You can probably imagine, that this was a lot
+# of work to pull off. If you run into problems, or want to have additional
+# functionality, please consider sponsoring the project. You can then use the
+# new version of the plugin immediately.
+# -----------------------------------------------------------------------------
+
import concurrent.futures
import functools
import logging
@@ -159,7 +172,7 @@ def on_page_markdown(self, markdown, page, config, files):
)
sys.exit(1)
- # Generate social card if not in cache - TODO: values from mkdocs.yml
+ # Generate social card if not in cache
hash = md5("".join([
site_name,
str(title),
@@ -267,17 +280,6 @@ def _render_text(self, size, font, text, lmax, spacing = 0):
lines.append(words)
words = [word]
- # # Balance words on last line - TODO: overflows when broken word is too long
- # if len(lines) > 0:
- # prev = len(" ".join(lines[-1]))
- # last = len(" ".join(words))#
-
- # print(last, prev)
-
- # # Heuristic: try to find a good ratio
- # if last / prev < 0.6:
- # words.insert(0, lines[-1].pop())
-
# Join words for each line and create image
lines.append(words)
lines = [" ".join(line) for line in lines]
@@ -424,7 +426,7 @@ def _load_font(self, config):
font_filename_base = name.replace(' ', '')
filename_regex = re.escape(font_filename_base)+r"-(\w+)\.[ot]tf$"
- font = dict()
+ font = {}
# Check for cached files - note these may be in subfolders
for currentpath, folders, files in os.walk(self.cache):
for file in files:
diff --git a/src/plugins/tags/config.py b/src/plugins/tags/config.py
index ab94a71b364..763581e56a8 100644
--- a/src/plugins/tags/config.py
+++ b/src/plugins/tags/config.py
@@ -33,9 +33,9 @@
class TagsConfig(Config):
enabled = Type(bool, default = True)
- # Options for tags
+ # Settings for tags
tags_file = Optional(Type(str))
- tags_extra_files = Type(dict, default = dict())
+ tags_extra_files = Type(dict, default = {})
tags_slugify = Type((type(slugify), partial), default = slugify)
tags_slugify_separator = Type(str, default = "-")
tags_compare = Optional(Type(type(casefold)))