Skip to content

Commit 764178b

Browse files
committed
Merge Insiders features
1 parent 9853cc3 commit 764178b

File tree

240 files changed

+6100
-22158
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

240 files changed

+6100
-22158
lines changed

Dockerfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ WORKDIR /tmp
3333
# Copy files necessary for build
3434
COPY material material
3535
COPY package.json package.json
36+
COPY pyproject.toml pyproject.toml
3637
COPY README.md README.md
3738
COPY *requirements.txt ./
38-
COPY pyproject.toml pyproject.toml
3939

4040
# Perform build and cleanup artifacts and caches
4141
RUN \
@@ -48,6 +48,7 @@ RUN \
4848
git-fast-import \
4949
jpeg-dev \
5050
openssh \
51+
pngquant \
5152
tini \
5253
zlib-dev \
5354
&& \
@@ -64,6 +65,7 @@ RUN \
6465
if [ "${WITH_PLUGINS}" = "true" ]; then \
6566
pip install --no-cache-dir \
6667
mkdocs-material[recommended] \
68+
mkdocs-material[git] \
6769
mkdocs-material[imaging]; \
6870
fi \
6971
&& \

giscus.json

Lines changed: 0 additions & 3 deletions
This file was deleted.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ Issues = "https://github.com/squidfunk/mkdocs-material/issues"
7979
"material/info" = "material.plugins.info.plugin:InfoPlugin"
8080
"material/meta" = "material.plugins.meta.plugin:MetaPlugin"
8181
"material/offline" = "material.plugins.offline.plugin:OfflinePlugin"
82+
"material/optimize" = "material.plugins.optimize.plugin:OptimizePlugin"
8283
"material/privacy" = "material.plugins.privacy.plugin:PrivacyPlugin"
8384
"material/search" = "material.plugins.search.plugin:SearchPlugin"
8485
"material/social" = "material.plugins.social.plugin:SocialPlugin"

src/extensions/preview.py

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
# Copyright (c) 2016-2025 Martin Donath <martin.donath@squidfunk.com>
2+
3+
# Permission is hereby granted, free of charge, to any person obtaining a copy
4+
# of this software and associated documentation files (the "Software"), to
5+
# deal in the Software without restriction, including without limitation the
6+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7+
# sell copies of the Software, and to permit persons to whom the Software is
8+
# furnished to do so, subject to the following conditions:
9+
10+
# The above copyright notice and this permission notice shall be included in
11+
# all copies or substantial portions of the Software.
12+
13+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
16+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
19+
# IN THE SOFTWARE.
20+
21+
from __future__ import annotations
22+
23+
import logging
24+
25+
from material.utilities.filter import FileFilter, FilterConfig
26+
from mkdocs.structure.pages import _RelativePathTreeprocessor
27+
from markdown import Extension, Markdown
28+
from markdown.treeprocessors import Treeprocessor
29+
from mkdocs.exceptions import ConfigurationError
30+
from urllib.parse import urlparse
31+
from xml.etree.ElementTree import Element
32+
33+
# -----------------------------------------------------------------------------
34+
# Classes
35+
# -----------------------------------------------------------------------------
36+
37+
class PreviewProcessor(Treeprocessor):
38+
"""
39+
A Markdown treeprocessor to enable instant previews on links.
40+
41+
Note that this treeprocessor is dependent on the `relpath` treeprocessor
42+
registered programmatically by MkDocs before rendering a page.
43+
"""
44+
45+
def __init__(self, md: Markdown, config: dict):
46+
"""
47+
Initialize the treeprocessor.
48+
49+
Arguments:
50+
md: The Markdown instance.
51+
config: The configuration.
52+
"""
53+
super().__init__(md)
54+
self.config = config
55+
56+
def run(self, root: Element):
57+
"""
58+
Run the treeprocessor.
59+
60+
Arguments:
61+
root: The root element of the parsed Markdown document.
62+
"""
63+
at = self.md.treeprocessors.get_index_for_name("relpath")
64+
65+
# Hack: Python Markdown has no notion of where it is, i.e., which file
66+
# is being processed. This seems to be a deliberate design decision, as
67+
# it is not possible to access the file path of the current page, but
68+
# it might also be an oversight that is now impossible to fix. However,
69+
# since this extension is only useful in the context of Material for
70+
# MkDocs, we can assume that the _RelativePathTreeprocessor is always
71+
# present, telling us the file path of the current page. If that ever
72+
# changes, we would need to wrap this extension in a plugin, but for
73+
# the time being we are sneaky and will probably get away with it.
74+
processor = self.md.treeprocessors[at]
75+
if not isinstance(processor, _RelativePathTreeprocessor):
76+
raise TypeError("Relative path processor not registered")
77+
78+
# Normalize configurations
79+
configurations = self.config["configurations"]
80+
configurations.append({
81+
"sources": self.config.get("sources"),
82+
"targets": self.config.get("targets")
83+
})
84+
85+
# Walk through all configurations - @todo refactor so that we don't
86+
# iterate multiple times over the same elements
87+
for configuration in configurations:
88+
89+
# Skip, if the configuration defines nothing – we could also fix
90+
# this in the file filter, but we first fix it here and check if
91+
# it generalizes well enough to other inclusion/exclusion sites,
92+
# because here, it would hinder the ability to automaticaly
93+
# include all sources, while excluding specific targets.
94+
if (
95+
not configuration.get("sources") and
96+
not configuration.get("targets")
97+
):
98+
continue
99+
100+
# Skip if page should not be considered
101+
filter = get_filter(configuration, "sources")
102+
if not filter(processor.file):
103+
continue
104+
105+
# Walk through all links and add preview attributes
106+
filter = get_filter(configuration, "targets")
107+
for el in root.iter("a"):
108+
href = el.get("href")
109+
if not href:
110+
continue
111+
112+
# Skip footnotes
113+
if "footnote-ref" in el.get("class", ""):
114+
continue
115+
116+
# Skip external links
117+
url = urlparse(href)
118+
if url.scheme or url.netloc:
119+
continue
120+
121+
# Add preview attribute to internal links
122+
for path in processor._possible_target_uris(
123+
processor.file, url.path,
124+
processor.config.use_directory_urls
125+
):
126+
target = processor.files.get_file_from_path(path)
127+
if not target:
128+
continue
129+
130+
# Include, if filter matches
131+
if filter(target):
132+
el.set("data-preview", "")
133+
134+
# -----------------------------------------------------------------------------
135+
136+
class PreviewExtension(Extension):
137+
"""
138+
A Markdown extension to enable instant previews on links.
139+
140+
This extensions allows to automatically add the `data-preview` attribute to
141+
internal links matching specific criteria, so Material for MkDocs renders a
142+
nice preview on hover as part of a tooltip. It is the recommended way to
143+
add previews to links in a programmatic way.
144+
"""
145+
146+
def __init__(self, *args, **kwargs):
147+
"""
148+
"""
149+
self.config = {
150+
"configurations": [[], "Filter configurations"],
151+
"sources": [{}, "Link sources"],
152+
"targets": [{}, "Link targets"]
153+
}
154+
super().__init__(*args, **kwargs)
155+
156+
def extendMarkdown(self, md: Markdown):
157+
"""
158+
Register Markdown extension.
159+
160+
Arguments:
161+
md: The Markdown instance.
162+
"""
163+
md.registerExtension(self)
164+
165+
# Create and register treeprocessor - we use the same priority as the
166+
# `relpath` treeprocessor, the latter of which is guaranteed to run
167+
# after our treeprocessor, so we can check the original Markdown URIs
168+
# before they are resolved to URLs.
169+
processor = PreviewProcessor(md, self.getConfigs())
170+
md.treeprocessors.register(processor, "preview", 0)
171+
172+
# -----------------------------------------------------------------------------
173+
# Functions
174+
# -----------------------------------------------------------------------------
175+
176+
def get_filter(settings: dict, key: str):
177+
"""
178+
Get file filter from settings.
179+
180+
Arguments:
181+
settings: The settings.
182+
key: The key in the settings.
183+
184+
Returns:
185+
The file filter.
186+
"""
187+
config = FilterConfig()
188+
config.load_dict(settings.get(key) or {})
189+
190+
# Validate filter configuration
191+
errors, warnings = config.validate()
192+
for _, w in warnings:
193+
log.warning(
194+
f"Error reading filter configuration in '{key}':\n"
195+
f"{w}"
196+
)
197+
for _, e in errors:
198+
raise ConfigurationError(
199+
f"Error reading filter configuration in '{key}':\n"
200+
f"{e}"
201+
)
202+
203+
# Return file filter
204+
return FileFilter(config = config) # type: ignore
205+
206+
def makeExtension(**kwargs):
207+
"""
208+
Register Markdown extension.
209+
210+
Arguments:
211+
**kwargs: Configuration options.
212+
213+
Returns:
214+
The Markdown extension.
215+
"""
216+
return PreviewExtension(**kwargs)
217+
218+
# -----------------------------------------------------------------------------
219+
# Data
220+
# -----------------------------------------------------------------------------
221+
222+
# Set up logging
223+
log = logging.getLogger("mkdocs.material.extensions.preview")

src/plugins/blog/__init__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,17 @@
1717
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
1818
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
1919
# IN THE SOFTWARE.
20+
21+
from .structure import View
22+
23+
# -----------------------------------------------------------------------------
24+
# Functions
25+
# -----------------------------------------------------------------------------
26+
27+
# Sort views by name
28+
def view_name(view: View):
29+
return view.name
30+
31+
# Sort views by post count
32+
def view_post_count(view: View):
33+
return len(view.posts)

src/plugins/blog/config.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
from mkdocs.config.base import Config
2424
from pymdownx.slugs import slugify
2525

26+
from . import view_name
27+
2628
# -----------------------------------------------------------------------------
2729
# Classes
2830
# -----------------------------------------------------------------------------
@@ -56,6 +58,8 @@ class BlogConfig(Config):
5658
archive_date_format = Type(str, default = "yyyy")
5759
archive_url_date_format = Type(str, default = "yyyy")
5860
archive_url_format = Type(str, default = "archive/{date}")
61+
archive_pagination = Optional(Type(bool))
62+
archive_pagination_per_page = Optional(Type(int))
5963
archive_toc = Optional(Type(bool))
6064

6165
# Settings for categories
@@ -64,12 +68,22 @@ class BlogConfig(Config):
6468
categories_url_format = Type(str, default = "category/{slug}")
6569
categories_slugify = Type(Callable, default = slugify(case = "lower"))
6670
categories_slugify_separator = Type(str, default = "-")
71+
categories_sort_by = Type(Callable, default = view_name)
72+
categories_sort_reverse = Type(bool, default = False)
6773
categories_allowed = Type(list, default = [])
74+
categories_pagination = Optional(Type(bool))
75+
categories_pagination_per_page = Optional(Type(int))
6876
categories_toc = Optional(Type(bool))
6977

7078
# Settings for authors
7179
authors = Type(bool, default = True)
7280
authors_file = Type(str, default = "{blog}/.authors.yml")
81+
authors_profiles = Type(bool, default = False)
82+
authors_profiles_name = Type(str, default = "blog.authors")
83+
authors_profiles_url_format = Type(str, default = "author/{slug}")
84+
authors_profiles_pagination = Optional(Type(bool))
85+
authors_profiles_pagination_per_page = Optional(Type(int))
86+
authors_profiles_toc = Optional(Type(bool))
7387

7488
# Settings for pagination
7589
pagination = Type(bool, default = True)

0 commit comments

Comments
 (0)