mirror of
https://github.com/squidfunk/mkdocs-material.git
synced 2024-06-14 11:52:32 +03:00
Refactored blog plugin (+ other plugins)
This commit is contained in:
parent
1fa81d05f4
commit
38b7282b20
@ -1,4 +1,5 @@
|
||||
squidfunk:
|
||||
authors:
|
||||
squidfunk:
|
||||
name: Martin Donath
|
||||
description: Creator
|
||||
avatar: https://avatars.githubusercontent.com/u/932156
|
||||
|
@ -0,0 +1,19 @@
|
||||
# Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
|
||||
|
||||
# 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.
|
@ -52,14 +52,14 @@
|
||||
{% include "partials/icons.html" %}
|
||||
{% endblock %}
|
||||
{% block libs %}
|
||||
{% for path in config.extra.polyfills %}
|
||||
<script src="{{ path | url }}"></script>
|
||||
{% for script in config.extra.polyfills %}
|
||||
{{ script | script_tag }}
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
{% block fonts %}
|
||||
{% if config.theme.font != false %}
|
||||
{% set text = config.theme.font.text | d("Roboto", true) %}
|
||||
{% set code = config.theme.font.code | d("Roboto Mono", true) %}
|
||||
{% set text = config.theme.font.get("text", "Roboto") %}
|
||||
{% set code = config.theme.font.get("code", "Roboto Mono") %}
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family={{
|
||||
text | replace(' ', '+') + ':300,300i,400,400i,700,700i%7C' +
|
||||
@ -252,11 +252,7 @@
|
||||
{% block scripts %}
|
||||
<script src="{{ 'assets/javascripts/bundle.9e673e97.min.js' | url }}"></script>
|
||||
{% for script in config.extra_javascript %}
|
||||
{% if script.path %}
|
||||
{{ script | script_tag }}
|
||||
{% else %}
|
||||
<script src="{{ script | url }}"></script>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
</body>
|
||||
|
@ -1,16 +0,0 @@
|
||||
{#-
|
||||
This file was automatically generated - do not edit
|
||||
-#}
|
||||
{% extends "main.html" %}
|
||||
{% block container %}
|
||||
<div class="md-content" data-md-component="content">
|
||||
<div class="md-content__inner">
|
||||
<header class="md-typeset">
|
||||
{{ page.content }}
|
||||
</header>
|
||||
{% for post in posts %}
|
||||
{% include "partials/post.html" %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,16 +0,0 @@
|
||||
{#-
|
||||
This file was automatically generated - do not edit
|
||||
-#}
|
||||
{% extends "main.html" %}
|
||||
{% block container %}
|
||||
<div class="md-content" data-md-component="content">
|
||||
<div class="md-content__inner">
|
||||
<header class="md-typeset">
|
||||
{{ page.content }}
|
||||
</header>
|
||||
{% for post in posts %}
|
||||
{% include "partials/post.html" %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -45,17 +45,17 @@
|
||||
<li class="md-nav__item">
|
||||
<div class="md-nav__link">
|
||||
{% include ".icons/material/calendar.svg" %}
|
||||
<time datetime="{{ page.meta.date }}" class="md-ellipsis">
|
||||
{{- page.meta.date_format -}}
|
||||
<time datetime="{{ page.config.date.created }}" class="md-ellipsis">
|
||||
{{- page.config.date.created | date -}}
|
||||
</time>
|
||||
</div>
|
||||
</li>
|
||||
{% if page.meta.date_updated %}
|
||||
{% if page.config.date.updated %}
|
||||
<li class="md-nav__item">
|
||||
<div class="md-nav__link">
|
||||
{% include ".icons/material/calendar-clock.svg" %}
|
||||
<time datetime="{{ page.meta.date_updated }}" class="md-ellipsis">
|
||||
{{- page.meta.date_updated_format -}}
|
||||
<time datetime="{{ page.config.date.updated }}" class="md-ellipsis">
|
||||
{{- page.config.date.updated | date -}}
|
||||
</time>
|
||||
</div>
|
||||
</li>
|
||||
@ -76,8 +76,8 @@
|
||||
</div>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if page.meta.readtime %}
|
||||
{% set time = page.meta.readtime %}
|
||||
{% if page.config.readtime %}
|
||||
{% set time = page.config.readtime %}
|
||||
<li class="md-nav__item">
|
||||
<div class="md-nav__link">
|
||||
{% include ".icons/material/clock-outline.svg" %}
|
||||
|
@ -11,9 +11,11 @@
|
||||
{% for post in posts %}
|
||||
{% include "partials/post.html" %}
|
||||
{% endfor %}
|
||||
{% if pagination %}
|
||||
{% block pagination %}
|
||||
{% include "partials/pagination.html" %}
|
||||
{% endblock %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -15,8 +15,8 @@
|
||||
<div class="md-post__meta md-meta">
|
||||
<ul class="md-meta__list">
|
||||
<li class="md-meta__item">
|
||||
<time datetime="{{ post.meta.date }}">
|
||||
{{- post.meta.date_format -}}
|
||||
<time datetime="{{ post.config.date.created }}">
|
||||
{{- post.config.date.created | date -}}
|
||||
</time>
|
||||
{#- Collapse whitespace -#}
|
||||
</li>
|
||||
@ -31,8 +31,8 @@
|
||||
{% endfor -%}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if post.meta.readtime %}
|
||||
{% set time = post.meta.readtime %}
|
||||
{% if post.config.readtime %}
|
||||
{% set time = post.config.readtime %}
|
||||
<li class="md-meta__item">
|
||||
{% if time == 1 %}
|
||||
{{ lang.t("readtime.one") }}
|
||||
@ -42,7 +42,7 @@
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% if post.meta.draft %}
|
||||
{% if post.config.draft %}
|
||||
<span class="md-draft">
|
||||
{{ lang.t("blog.draft") }}
|
||||
</span>
|
||||
|
@ -0,0 +1,19 @@
|
||||
# Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
|
||||
|
||||
# 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.
|
@ -0,0 +1,19 @@
|
||||
# Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
|
||||
|
||||
# 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.
|
38
material/plugins/blog/author.py
Normal file
38
material/plugins/blog/author.py
Normal file
@ -0,0 +1,38 @@
|
||||
# Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
|
||||
|
||||
# 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 mkdocs.config.base import Config
|
||||
from mkdocs.config.config_options import DictOfItems, SubConfig, Type
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Author
|
||||
class Author(Config):
|
||||
name = Type(str)
|
||||
description = Type(str)
|
||||
avatar = Type(str)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Authors
|
||||
class Authors(Config):
|
||||
authors = DictOfItems(SubConfig(Author), default = {})
|
@ -24,10 +24,10 @@ from mkdocs.config.config_options import Choice, Deprecated, Optional, Type
|
||||
from mkdocs.config.base import Config
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Class
|
||||
# Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Blog plugin configuration scheme
|
||||
# Blog plugin configuration
|
||||
class BlogConfig(Config):
|
||||
enabled = Type(bool, default = True)
|
||||
|
||||
@ -36,6 +36,7 @@ class BlogConfig(Config):
|
||||
blog_toc = Type(bool, default = False)
|
||||
|
||||
# Options 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")
|
||||
post_url_format = Type(str, default = "{date}/{slug}")
|
||||
@ -70,7 +71,9 @@ class BlogConfig(Config):
|
||||
pagination = Type(bool, default = True)
|
||||
pagination_per_page = Type(int, default = 10)
|
||||
pagination_url_format = Type(str, default = "page/{page}")
|
||||
pagination_template = Type(str, default = "~2~")
|
||||
pagination_format = Type(str, default = "~2~")
|
||||
pagination_if_single_page = Type(bool, default = False)
|
||||
pagination_keep_content = Type(bool, default = False)
|
||||
|
||||
# Options for authors
|
||||
authors = Type(bool, default = True)
|
||||
@ -80,3 +83,6 @@ class BlogConfig(Config):
|
||||
draft = Type(bool, default = False)
|
||||
draft_on_serve = Type(bool, default = True)
|
||||
draft_if_future_date = Type(bool, default = False)
|
||||
|
||||
# Deprecated options
|
||||
pagination_template = Deprecated(moved_to = "pagination_format")
|
||||
|
File diff suppressed because it is too large
Load Diff
277
material/plugins/blog/structure/__init__.py
Normal file
277
material/plugins/blog/structure/__init__.py
Normal file
@ -0,0 +1,277 @@
|
||||
# Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
|
||||
|
||||
# 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.
|
||||
|
||||
import logging
|
||||
import os
|
||||
import yaml
|
||||
|
||||
from copy import copy
|
||||
from markdown import Markdown
|
||||
from material.plugins.blog.author import Author
|
||||
from mkdocs.config.defaults import MkDocsConfig
|
||||
from mkdocs.exceptions import PluginError
|
||||
from mkdocs.structure.files import File, Files
|
||||
from mkdocs.structure.nav import Section
|
||||
from mkdocs.structure.pages import Page, _RelativePathTreeprocessor
|
||||
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
|
||||
from .markdown import ExcerptTreeprocessor
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Post
|
||||
class Post(Page):
|
||||
|
||||
# Initialize post - posts are never listed in the navigation, which is why
|
||||
# they will never include a title that was manually set, so we can omit it
|
||||
def __init__(self, file: File, config: MkDocsConfig):
|
||||
super().__init__(None, file, config)
|
||||
|
||||
# Resolve path relative to docs directory for error reporting
|
||||
docs = os.path.relpath(config.docs_dir)
|
||||
path = os.path.relpath(file.abs_src_path, docs)
|
||||
|
||||
# Read contents and metadata immediately
|
||||
with open(file.abs_src_path, encoding = "utf-8") as f:
|
||||
self.markdown = f.read()
|
||||
|
||||
# Sadly, MkDocs swallows any exceptions that occur during parsing.
|
||||
# As we want to provide the best possible authoring experience, we
|
||||
# need to catch errors early and display them nicely. We decided to
|
||||
# drop support for MkDocs' MultiMarkdown syntax, because it is not
|
||||
# correctly implemented anyway. When using MultiMarkdown syntax, all
|
||||
# date formats are returned as strings and list are not properly
|
||||
# supported. Thus, we just use the relevants parts of `get_data`.
|
||||
match: Match = YAML_RE.match(self.markdown)
|
||||
if not match:
|
||||
raise PluginError(
|
||||
f"Error reading metadata of post '{path}' in '{docs}':\n"
|
||||
f"Expected metadata to be defined but found nothing"
|
||||
)
|
||||
|
||||
# Extract metadata and parse as YAML
|
||||
try:
|
||||
self.meta = yaml.load(match.group(1), SafeLoader) or {}
|
||||
self.markdown = self.markdown[match.end():].lstrip("\n")
|
||||
|
||||
# The post's metadata could not be parsed because of a syntax error,
|
||||
# which we display to the user with a nice error message
|
||||
except Exception as e:
|
||||
raise PluginError(
|
||||
f"Error reading metadata of post '{path}' in '{docs}':\n"
|
||||
f"{e}"
|
||||
)
|
||||
|
||||
# Initialize post configuration, but remove all keys that this plugin
|
||||
# doesn't care about, or they will be reported as invalid configuration
|
||||
self.config: PostConfig = PostConfig(file.abs_src_path)
|
||||
self.config.load_dict({
|
||||
key: self.meta[key] for key in (
|
||||
set(self.meta.keys()) &
|
||||
set(self.config.keys())
|
||||
)
|
||||
})
|
||||
|
||||
# Validate configuration and throw if errors occurred
|
||||
errors, warnings = self.config.validate()
|
||||
for _, w in warnings:
|
||||
log.warning(w)
|
||||
for k, e in errors:
|
||||
raise PluginError(
|
||||
f"Error reading metadata '{k}' of post '{path}' in '{docs}':\n"
|
||||
f"{e}"
|
||||
)
|
||||
|
||||
# Excerpts are subsets of posts that are used in views 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.
|
||||
self.excerpt: Excerpt = None
|
||||
|
||||
# Initialize authors and actegories
|
||||
self.authors: list[Author] = []
|
||||
self.categories: list[Category] = []
|
||||
|
||||
# Ensure template is set or use default
|
||||
self.meta.setdefault("template", "blog-post.html")
|
||||
|
||||
# Ensure template hides navigation
|
||||
self.meta["hide"] = self.meta.get("hide", [])
|
||||
if "navigation" not in self.meta["hide"]:
|
||||
self.meta["hide"].append("navigation")
|
||||
|
||||
# The contents and metadata were already read in the constructor (and not
|
||||
# in `read_source` as for pages), so this function must be set to a no-op
|
||||
def read_source(self, config: MkDocsConfig):
|
||||
pass
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Excerpt
|
||||
class Excerpt(Page):
|
||||
|
||||
# Initialize an excerpt for the given post - we create the Markdown parser
|
||||
# when intitializing the excerpt in order to improve rendering performance
|
||||
# for excerpts, as they are reused across several different views, because
|
||||
# posts might be referenced from multiple different locations
|
||||
def __init__(self, post: Post, config: MkDocsConfig, files: Files):
|
||||
self.file = copy(post.file)
|
||||
self.post = post
|
||||
|
||||
# Initialize configuration, contents and metadata
|
||||
self.config = post.config
|
||||
self.markdown = post.markdown
|
||||
self.meta = post.meta
|
||||
|
||||
# Initialize authors and categories - note that views usually contain
|
||||
# subsets of those lists, which is why we need to manage them here
|
||||
self.authors: list[Author] = []
|
||||
self.categories: list[Category] = []
|
||||
|
||||
# Initialize parser - note that we need to patch the configuration,
|
||||
# more specifically the table of contents extension
|
||||
config = _patch(config)
|
||||
self.md = Markdown(
|
||||
extensions = config.markdown_extensions,
|
||||
extension_configs = config.mdx_configs,
|
||||
)
|
||||
|
||||
# Register excerpt tree processor - this processor resolves anchors to
|
||||
# posts from within views, so they point to the correct location
|
||||
self.md.treeprocessors.register(
|
||||
ExcerptTreeprocessor(post),
|
||||
"excerpt",
|
||||
0
|
||||
)
|
||||
|
||||
# Register relative path tree processor - this processor resolves links
|
||||
# to other pages and assets, and is used by MkDocs itself
|
||||
self.md.treeprocessors.register(
|
||||
_RelativePathTreeprocessor(self.file, files, config),
|
||||
"relpath",
|
||||
1
|
||||
)
|
||||
|
||||
# Render an excerpt of the post on the given page - note that this is not
|
||||
# thread-safe because excerpts are shared across views, as it cuts down on
|
||||
# the cost of initialization. However, if in the future, we decide to render
|
||||
# posts and views concurrently, we must change this behavior.
|
||||
def render(self, page: Page, separator: str):
|
||||
self.file.url = page.url
|
||||
|
||||
# Retrieve excerpt tree processor and set page as base
|
||||
at = self.md.treeprocessors.get_index_for_name("excerpt")
|
||||
processor: ExcerptTreeprocessor = self.md.treeprocessors[at]
|
||||
processor.base = page
|
||||
|
||||
# Convert Markdown to HTML and extract excerpt
|
||||
self.content = self.md.convert(self.markdown)
|
||||
self.content, *_ = self.content.split(separator, 1)
|
||||
|
||||
# Extract table of contents and reset post URL - if we wouldn't reset
|
||||
# the excerpt URL, linking to the excerpt from the view would not work
|
||||
self.toc = get_toc(getattr(self.md, "toc_tokens", []))
|
||||
self.file.url = self.post.url
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# View
|
||||
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]
|
||||
|
||||
# Initialize posts and views
|
||||
self.posts: list[Post] = []
|
||||
self.views: list[View] = []
|
||||
|
||||
# Initialize pages for pagination
|
||||
self.pages: list[View] = []
|
||||
|
||||
# Set necessary metadata
|
||||
def read_source(self, config: MkDocsConfig):
|
||||
super().read_source(config)
|
||||
|
||||
# Ensure template is set or use default
|
||||
self.meta.setdefault("template", "blog.html")
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Archive view
|
||||
class Archive(View):
|
||||
pass
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Category view
|
||||
class Category(View):
|
||||
pass
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Helper functions
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Patch configuration
|
||||
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"])
|
||||
|
||||
# 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"],
|
||||
**{
|
||||
"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
|
||||
}
|
||||
}
|
||||
|
||||
# Additionally, we disable link validation when rendering excerpts, because
|
||||
# invalid links have already been reported when rendering the page
|
||||
links = config.validation.links
|
||||
links.not_found = logging.DEBUG
|
||||
links.absolute_links = logging.DEBUG
|
||||
links.unrecognized_links = logging.DEBUG
|
||||
|
||||
# Return patched configuration
|
||||
return config
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Data
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Set up logging
|
||||
log = logging.getLogger("mkdocs.material.blog")
|
37
material/plugins/blog/structure/config.py
Normal file
37
material/plugins/blog/structure/config.py
Normal file
@ -0,0 +1,37 @@
|
||||
# Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
|
||||
|
||||
# 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 mkdocs.config.base import Config
|
||||
from mkdocs.config.config_options import ListOfItems, Optional, Type
|
||||
|
||||
from .options import PostDate
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Post configuration
|
||||
class PostConfig(Config):
|
||||
authors = ListOfItems(Type(str), default = [])
|
||||
categories = ListOfItems(Type(str), default = [])
|
||||
date = PostDate()
|
||||
draft = Optional(Type(bool))
|
||||
readtime = Optional(Type(int))
|
||||
slug = Optional(Type(str))
|
58
material/plugins/blog/structure/markdown.py
Normal file
58
material/plugins/blog/structure/markdown.py
Normal file
@ -0,0 +1,58 @@
|
||||
# Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
|
||||
|
||||
# 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 markdown.treeprocessors import Treeprocessor
|
||||
from mkdocs.structure.pages import Page
|
||||
from mkdocs.utils import get_relative_url
|
||||
from xml.etree.ElementTree import Element
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Excerpt tree processor
|
||||
class ExcerptTreeprocessor(Treeprocessor):
|
||||
|
||||
# Initialize excerpt tree processor
|
||||
def __init__(self, page: Page, base: Page = None):
|
||||
self.page = page
|
||||
self.base = base
|
||||
|
||||
# Transform HTML after Markdown processing
|
||||
def run(self, root: Element):
|
||||
main = True
|
||||
|
||||
# We're only interested in anchors, which is why we continue when the
|
||||
# link does not start with an anchor tag
|
||||
for el in root.iter("a"):
|
||||
anchor = el.get("href")
|
||||
if not anchor.startswith("#"):
|
||||
continue
|
||||
|
||||
# The main headline should link to the post page, not to a specific
|
||||
# anchor, which is why we remove the anchor in that case
|
||||
path = get_relative_url(self.page.url, self.base.url)
|
||||
if main:
|
||||
el.set("href", path)
|
||||
else:
|
||||
el.set("href", path + anchor)
|
||||
|
||||
# Main headline has been seen
|
||||
main = False
|
85
material/plugins/blog/structure/options.py
Normal file
85
material/plugins/blog/structure/options.py
Normal file
@ -0,0 +1,85 @@
|
||||
# Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
|
||||
|
||||
# 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 collections import UserDict
|
||||
from datetime import date, datetime, time
|
||||
from mkdocs.config.base import BaseConfigOption, Config, ValidationError
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Date dictionary
|
||||
class DateDict(UserDict[str, datetime]):
|
||||
|
||||
# Initialize date dictionary
|
||||
def __init__(self, data: dict):
|
||||
super().__init__(data)
|
||||
|
||||
# Initialize date of creation
|
||||
if "created" in data:
|
||||
self.created: datetime = data["created"]
|
||||
|
||||
def __getattr__(self, name: str):
|
||||
if name in self.data:
|
||||
return self.data[name]
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Post date option
|
||||
class PostDate(BaseConfigOption[DateDict]):
|
||||
|
||||
# Initialize post dates
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Normalize the supported types for post dates to datetime
|
||||
def pre_validation(self, config: Config, key_name: str):
|
||||
|
||||
# If the date points to a scalar value, convert it to a dictionary,
|
||||
# since we want to allow the user to specify custom and arbitrary date
|
||||
# values for posts. Currently, only the `created` date is mandatory,
|
||||
# because it's needed to sort posts for views.
|
||||
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])
|
||||
for key, value in config[key_name].items():
|
||||
if isinstance(value, date):
|
||||
config[key_name][key] = datetime.combine(value, time())
|
||||
|
||||
# Ensure each date value is of type datetime
|
||||
def run_validation(self, value: DateDict):
|
||||
for key in value:
|
||||
if not isinstance(value[key], datetime):
|
||||
raise ValidationError(
|
||||
f"Expected type: {date} or {datetime} "
|
||||
f"but received: {type(value[key])}"
|
||||
)
|
||||
|
||||
# Ensure presence of `date.created`
|
||||
if not value.created:
|
||||
raise ValidationError(
|
||||
"Expected 'created' date when using dictionary syntax"
|
||||
)
|
||||
|
||||
# Return date dictionary
|
||||
return value
|
42
material/plugins/blog/templates/__init__.py
Normal file
42
material/plugins/blog/templates/__init__.py
Normal file
@ -0,0 +1,42 @@
|
||||
# Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
|
||||
|
||||
# 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 | 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):
|
||||
if page.url == url:
|
||||
url = page.pages[0].url
|
||||
|
||||
# Forward to original template filter
|
||||
return _url_filter(context, url)
|
@ -0,0 +1,19 @@
|
||||
# Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
|
||||
|
||||
# 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.
|
@ -22,10 +22,10 @@ from mkdocs.config.config_options import Type
|
||||
from mkdocs.config.base import Config
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Class
|
||||
# Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Info plugin configuration scheme
|
||||
# Info plugin configuration
|
||||
class InfoConfig(Config):
|
||||
enabled = Type(bool, default = True)
|
||||
enabled_on_serve = Type(bool, default = False)
|
||||
|
@ -29,15 +29,15 @@ from colorama import Fore, Style
|
||||
from importlib.metadata import distributions, version
|
||||
from io import BytesIO
|
||||
from markdown.extensions.toc import slugify
|
||||
from mkdocs import utils
|
||||
from mkdocs.plugins import BasePlugin, event_priority
|
||||
from mkdocs.structure.files import get_files
|
||||
from mkdocs.utils import get_theme_dir
|
||||
from zipfile import ZipFile, ZIP_DEFLATED
|
||||
|
||||
from material.plugins.info.config import InfoConfig
|
||||
from .config import InfoConfig
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Class
|
||||
# Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Info plugin
|
||||
@ -87,8 +87,8 @@ class InfoPlugin(BasePlugin[InfoConfig]):
|
||||
# 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.
|
||||
base = utils.get_theme_dir(config.theme.name)
|
||||
if config.theme.dirs.index(base):
|
||||
path = get_theme_dir(config.theme.name)
|
||||
if config.theme.dirs.index(path):
|
||||
log.error("Please remove 'custom_dir' setting.")
|
||||
self._help_on_customizations_and_exit()
|
||||
|
||||
|
@ -0,0 +1,19 @@
|
||||
# Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
|
||||
|
||||
# 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.
|
@ -22,9 +22,9 @@ from mkdocs.config.config_options import Type
|
||||
from mkdocs.config.base import Config
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Class
|
||||
# Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Offline plugin configuration scheme
|
||||
# Offline plugin configuration
|
||||
class OfflineConfig(Config):
|
||||
enabled = Type(bool, default = True)
|
||||
|
@ -20,13 +20,13 @@
|
||||
|
||||
import os
|
||||
|
||||
from mkdocs import utils
|
||||
from mkdocs.plugins import BasePlugin, event_priority
|
||||
from mkdocs.utils import write_file
|
||||
|
||||
from material.plugins.offline.config import OfflineConfig
|
||||
from .config import OfflineConfig
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Class
|
||||
# Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Offline plugin
|
||||
@ -55,14 +55,13 @@ class OfflinePlugin(BasePlugin[OfflineConfig]):
|
||||
return
|
||||
|
||||
# Check for existence of search index
|
||||
base = os.path.join(config.site_dir, "search")
|
||||
path = os.path.join(base, "search_index.json")
|
||||
path = os.path.join(config.site_dir, "search", "search_index.json")
|
||||
if not os.path.isfile(path):
|
||||
return
|
||||
|
||||
# Create script with inlined search index
|
||||
with open(path, "r") as f:
|
||||
utils.write_file(
|
||||
with open(path, encoding = "utf-8") as f:
|
||||
write_file(
|
||||
f"var __index = {f.read()}".encode("utf-8"),
|
||||
path.replace(".json", ".js"),
|
||||
)
|
||||
|
@ -0,0 +1,19 @@
|
||||
# Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
|
||||
|
||||
# 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.
|
@ -29,13 +29,17 @@ from mkdocs.config.base import Config
|
||||
from mkdocs.contrib.search import LangOption
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Class
|
||||
# Options
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Search pipeline functions
|
||||
# Options for search pipeline
|
||||
pipeline = ("stemmer", "stopWordFilter", "trimmer")
|
||||
|
||||
# Search plugin configuration scheme
|
||||
# -----------------------------------------------------------------------------
|
||||
# Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Search plugin configuration
|
||||
class SearchConfig(Config):
|
||||
lang = Optional(LangOption())
|
||||
separator = Optional(Type(str))
|
||||
|
@ -28,7 +28,7 @@ from html.parser import HTMLParser
|
||||
from mkdocs import utils
|
||||
from mkdocs.plugins import BasePlugin
|
||||
|
||||
from material.plugins.search.config import SearchConfig
|
||||
from .config import SearchConfig
|
||||
|
||||
try:
|
||||
import jieba
|
||||
@ -36,7 +36,7 @@ except ImportError:
|
||||
jieba = None
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Class
|
||||
# Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Search plugin
|
||||
@ -46,13 +46,13 @@ class SearchPlugin(BasePlugin[SearchConfig]):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Initialize variables for incremental builds
|
||||
# Initialize incremental builds
|
||||
self.is_dirtyreload = False
|
||||
|
||||
# Initialize search index cache
|
||||
self.search_index_prev = None
|
||||
|
||||
# Determine whether we're serving
|
||||
# Determine whether we're serving the site
|
||||
def on_startup(self, *, command, dirty):
|
||||
self.is_dirty = dirty
|
||||
|
||||
@ -81,7 +81,7 @@ class SearchPlugin(BasePlugin[SearchConfig]):
|
||||
# Set jieba dictionary, if given
|
||||
if self.config.jieba_dict:
|
||||
path = os.path.normpath(self.config.jieba_dict)
|
||||
if os.path.exists(path):
|
||||
if os.path.isfile(path):
|
||||
jieba.set_dictionary(path)
|
||||
log.debug(f"Loading jieba dictionary: {path}")
|
||||
else:
|
||||
@ -93,7 +93,7 @@ class SearchPlugin(BasePlugin[SearchConfig]):
|
||||
# Set jieba user dictionary, if given
|
||||
if self.config.jieba_dict_user:
|
||||
path = os.path.normpath(self.config.jieba_dict_user)
|
||||
if os.path.exists(path):
|
||||
if os.path.isfile(path):
|
||||
jieba.load_userdict(path)
|
||||
log.debug(f"Loading jieba user dictionary: {path}")
|
||||
else:
|
||||
|
@ -21,6 +21,11 @@
|
||||
import logging
|
||||
import sys
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Checks
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Check for pillow and cairosvg
|
||||
try:
|
||||
import cairosvg as _
|
||||
import PIL as _
|
||||
|
@ -22,10 +22,10 @@ from mkdocs.config.base import Config
|
||||
from mkdocs.config.config_options import Deprecated, Type
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Class
|
||||
# Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Social plugin configuration scheme
|
||||
# Social plugin configuration
|
||||
class SocialConfig(Config):
|
||||
enabled = Type(bool, default = True)
|
||||
cache_dir = Type(str, default = ".cache/plugin/social")
|
||||
|
@ -25,6 +25,7 @@ import os
|
||||
import posixpath
|
||||
import re
|
||||
import requests
|
||||
import sys
|
||||
|
||||
from cairosvg import svg2png
|
||||
from collections import defaultdict
|
||||
@ -37,10 +38,10 @@ from shutil import copyfile
|
||||
from tempfile import TemporaryFile
|
||||
from zipfile import ZipFile
|
||||
|
||||
from material.plugins.social.config import SocialConfig
|
||||
from .config import SocialConfig
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Class
|
||||
# Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Social plugin
|
||||
|
@ -23,13 +23,13 @@ from markdown.extensions.toc import slugify
|
||||
from mkdocs.config.config_options import Optional, Type
|
||||
from mkdocs.config.base import Config
|
||||
|
||||
from material.plugins.tags import casefold
|
||||
from . import casefold
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Class
|
||||
# Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Tags plugin configuration scheme
|
||||
# Tags plugin configuration
|
||||
class TagsConfig(Config):
|
||||
enabled = Type(bool, default = True)
|
||||
|
||||
|
@ -28,11 +28,11 @@ from mkdocs.plugins import BasePlugin
|
||||
|
||||
# deprecated, but kept for downward compatibility. Use 'material.plugins.tags'
|
||||
# as an import source instead. This import is removed in the next major version.
|
||||
from material.plugins.tags import casefold
|
||||
from material.plugins.tags.config import TagsConfig
|
||||
from . import casefold
|
||||
from .config import TagsConfig
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Class
|
||||
# Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Tags plugin
|
||||
|
@ -31,6 +31,6 @@ babel>=2.10.3
|
||||
colorama>=0.4
|
||||
lxml>=4.6
|
||||
paginate>=0.5.6
|
||||
regex>=2022.4.24
|
||||
readtime>=2.0
|
||||
regex>=2022.4.24
|
||||
requests>=2.26
|
||||
|
@ -0,0 +1,19 @@
|
||||
# Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
|
||||
|
||||
# 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.
|
@ -116,8 +116,8 @@
|
||||
|
||||
<!-- JavaScript libraries -->
|
||||
{% block libs %}
|
||||
{% for path in config.extra.polyfills %}
|
||||
<script src="{{ path | url }}"></script>
|
||||
{% for script in config.extra.polyfills %}
|
||||
{{ script | script_tag }}
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
@ -126,8 +126,8 @@
|
||||
|
||||
<!-- Load fonts from Google -->
|
||||
{% if config.theme.font != false %}
|
||||
{% set text = config.theme.font.text | d("Roboto", true) %}
|
||||
{% set code = config.theme.font.code | d("Roboto Mono", true) %}
|
||||
{% set text = config.theme.font.get("text", "Roboto") %}
|
||||
{% set code = config.theme.font.get("code", "Roboto Mono") %}
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
@ -442,13 +442,7 @@
|
||||
|
||||
<!-- Custom JavaScript -->
|
||||
{% for script in config.extra_javascript %}
|
||||
|
||||
<!-- Detected MkDocs 1.5+ which has `script.path` and `script_tag` -->
|
||||
{% if script.path %}
|
||||
{{ script | script_tag }}
|
||||
{% else %}
|
||||
<script src="{{ script | url }}"></script>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
</body>
|
||||
|
@ -1,41 +0,0 @@
|
||||
<!--
|
||||
Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
{% extends "main.html" %}
|
||||
|
||||
<!-- Page content -->
|
||||
{% block container %}
|
||||
<div class="md-content" data-md-component="content">
|
||||
<div class="md-content__inner">
|
||||
|
||||
<!-- Header -->
|
||||
<header class="md-typeset">
|
||||
{{ page.content }}
|
||||
</header>
|
||||
|
||||
<!-- Posts -->
|
||||
{% for post in posts %}
|
||||
{% include "partials/post.html" %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,41 +0,0 @@
|
||||
<!--
|
||||
Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
{% extends "main.html" %}
|
||||
|
||||
<!-- Page content -->
|
||||
{% block container %}
|
||||
<div class="md-content" data-md-component="content">
|
||||
<div class="md-content__inner">
|
||||
|
||||
<!-- Header -->
|
||||
<header class="md-typeset">
|
||||
{{ page.content }}
|
||||
</header>
|
||||
|
||||
<!-- Posts -->
|
||||
{% for post in posts %}
|
||||
{% include "partials/post.html" %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -81,19 +81,19 @@
|
||||
<li class="md-nav__item">
|
||||
<div class="md-nav__link">
|
||||
{% include ".icons/material/calendar.svg" %}
|
||||
<time datetime="{{ page.meta.date }}" class="md-ellipsis">
|
||||
{{- page.meta.date_format -}}
|
||||
<time datetime="{{ page.config.date.created }}" class="md-ellipsis">
|
||||
{{- page.config.date.created | date -}}
|
||||
</time>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<!-- Page date updated -->
|
||||
{% if page.meta.date_updated %}
|
||||
{% if page.config.date.updated %}
|
||||
<li class="md-nav__item">
|
||||
<div class="md-nav__link">
|
||||
{% include ".icons/material/calendar-clock.svg" %}
|
||||
<time datetime="{{ page.meta.date_updated }}" class="md-ellipsis">
|
||||
{{- page.meta.date_updated_format -}}
|
||||
<time datetime="{{ page.config.date.updated }}" class="md-ellipsis">
|
||||
{{- page.config.date.updated | date -}}
|
||||
</time>
|
||||
</div>
|
||||
</li>
|
||||
@ -118,8 +118,8 @@
|
||||
{% endif %}
|
||||
|
||||
<!-- Page readtime -->
|
||||
{% if page.meta.readtime %}
|
||||
{% set time = page.meta.readtime %}
|
||||
{% if page.config.readtime %}
|
||||
{% set time = page.config.readtime %}
|
||||
<li class="md-nav__item">
|
||||
<div class="md-nav__link">
|
||||
{% include ".icons/material/clock-outline.svg" %}
|
||||
|
@ -38,9 +38,11 @@
|
||||
{% endfor %}
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if pagination %}
|
||||
{% block pagination %}
|
||||
{% include "partials/pagination.html" %}
|
||||
{% endblock %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -41,8 +41,8 @@
|
||||
|
||||
<!-- Post date -->
|
||||
<li class="md-meta__item">
|
||||
<time datetime="{{ post.meta.date }}">
|
||||
{{- post.meta.date_format -}}
|
||||
<time datetime="{{ post.config.date.created }}">
|
||||
{{- post.config.date.created | date -}}
|
||||
</time>
|
||||
{#- Collapse whitespace -#}
|
||||
</li>
|
||||
@ -64,8 +64,8 @@
|
||||
{% endif %}
|
||||
|
||||
<!-- Post readtime -->
|
||||
{% if post.meta.readtime %}
|
||||
{% set time = post.meta.readtime %}
|
||||
{% if post.config.readtime %}
|
||||
{% set time = post.config.readtime %}
|
||||
<li class="md-meta__item">
|
||||
{% if time == 1 %}
|
||||
{{ lang.t("readtime.one") }}
|
||||
@ -77,7 +77,7 @@
|
||||
</ul>
|
||||
|
||||
<!-- Draft marker -->
|
||||
{% if post.meta.draft %}
|
||||
{% if post.config.draft %}
|
||||
<span class="md-draft">
|
||||
{{ lang.t("blog.draft") }}
|
||||
</span>
|
||||
|
@ -0,0 +1,19 @@
|
||||
# Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
|
||||
|
||||
# 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.
|
@ -0,0 +1,19 @@
|
||||
# Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
|
||||
|
||||
# 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.
|
38
src/plugins/blog/author.py
Normal file
38
src/plugins/blog/author.py
Normal file
@ -0,0 +1,38 @@
|
||||
# Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
|
||||
|
||||
# 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 mkdocs.config.base import Config
|
||||
from mkdocs.config.config_options import DictOfItems, SubConfig, Type
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Author
|
||||
class Author(Config):
|
||||
name = Type(str)
|
||||
description = Type(str)
|
||||
avatar = Type(str)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Authors
|
||||
class Authors(Config):
|
||||
authors = DictOfItems(SubConfig(Author), default = {})
|
@ -24,10 +24,10 @@ from mkdocs.config.config_options import Choice, Deprecated, Optional, Type
|
||||
from mkdocs.config.base import Config
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Class
|
||||
# Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Blog plugin configuration scheme
|
||||
# Blog plugin configuration
|
||||
class BlogConfig(Config):
|
||||
enabled = Type(bool, default = True)
|
||||
|
||||
@ -36,6 +36,7 @@ class BlogConfig(Config):
|
||||
blog_toc = Type(bool, default = False)
|
||||
|
||||
# Options 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")
|
||||
post_url_format = Type(str, default = "{date}/{slug}")
|
||||
@ -70,7 +71,9 @@ class BlogConfig(Config):
|
||||
pagination = Type(bool, default = True)
|
||||
pagination_per_page = Type(int, default = 10)
|
||||
pagination_url_format = Type(str, default = "page/{page}")
|
||||
pagination_template = Type(str, default = "~2~")
|
||||
pagination_format = Type(str, default = "~2~")
|
||||
pagination_if_single_page = Type(bool, default = False)
|
||||
pagination_keep_content = Type(bool, default = False)
|
||||
|
||||
# Options for authors
|
||||
authors = Type(bool, default = True)
|
||||
@ -80,3 +83,6 @@ class BlogConfig(Config):
|
||||
draft = Type(bool, default = False)
|
||||
draft_on_serve = Type(bool, default = True)
|
||||
draft_if_future_date = Type(bool, default = False)
|
||||
|
||||
# Deprecated options
|
||||
pagination_template = Deprecated(moved_to = "pagination_format")
|
||||
|
File diff suppressed because it is too large
Load Diff
277
src/plugins/blog/structure/__init__.py
Normal file
277
src/plugins/blog/structure/__init__.py
Normal file
@ -0,0 +1,277 @@
|
||||
# Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
|
||||
|
||||
# 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.
|
||||
|
||||
import logging
|
||||
import os
|
||||
import yaml
|
||||
|
||||
from copy import copy
|
||||
from markdown import Markdown
|
||||
from material.plugins.blog.author import Author
|
||||
from mkdocs.config.defaults import MkDocsConfig
|
||||
from mkdocs.exceptions import PluginError
|
||||
from mkdocs.structure.files import File, Files
|
||||
from mkdocs.structure.nav import Section
|
||||
from mkdocs.structure.pages import Page, _RelativePathTreeprocessor
|
||||
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
|
||||
from .markdown import ExcerptTreeprocessor
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Post
|
||||
class Post(Page):
|
||||
|
||||
# Initialize post - posts are never listed in the navigation, which is why
|
||||
# they will never include a title that was manually set, so we can omit it
|
||||
def __init__(self, file: File, config: MkDocsConfig):
|
||||
super().__init__(None, file, config)
|
||||
|
||||
# Resolve path relative to docs directory for error reporting
|
||||
docs = os.path.relpath(config.docs_dir)
|
||||
path = os.path.relpath(file.abs_src_path, docs)
|
||||
|
||||
# Read contents and metadata immediately
|
||||
with open(file.abs_src_path, encoding = "utf-8") as f:
|
||||
self.markdown = f.read()
|
||||
|
||||
# Sadly, MkDocs swallows any exceptions that occur during parsing.
|
||||
# As we want to provide the best possible authoring experience, we
|
||||
# need to catch errors early and display them nicely. We decided to
|
||||
# drop support for MkDocs' MultiMarkdown syntax, because it is not
|
||||
# correctly implemented anyway. When using MultiMarkdown syntax, all
|
||||
# date formats are returned as strings and list are not properly
|
||||
# supported. Thus, we just use the relevants parts of `get_data`.
|
||||
match: Match = YAML_RE.match(self.markdown)
|
||||
if not match:
|
||||
raise PluginError(
|
||||
f"Error reading metadata of post '{path}' in '{docs}':\n"
|
||||
f"Expected metadata to be defined but found nothing"
|
||||
)
|
||||
|
||||
# Extract metadata and parse as YAML
|
||||
try:
|
||||
self.meta = yaml.load(match.group(1), SafeLoader) or {}
|
||||
self.markdown = self.markdown[match.end():].lstrip("\n")
|
||||
|
||||
# The post's metadata could not be parsed because of a syntax error,
|
||||
# which we display to the user with a nice error message
|
||||
except Exception as e:
|
||||
raise PluginError(
|
||||
f"Error reading metadata of post '{path}' in '{docs}':\n"
|
||||
f"{e}"
|
||||
)
|
||||
|
||||
# Initialize post configuration, but remove all keys that this plugin
|
||||
# doesn't care about, or they will be reported as invalid configuration
|
||||
self.config: PostConfig = PostConfig(file.abs_src_path)
|
||||
self.config.load_dict({
|
||||
key: self.meta[key] for key in (
|
||||
set(self.meta.keys()) &
|
||||
set(self.config.keys())
|
||||
)
|
||||
})
|
||||
|
||||
# Validate configuration and throw if errors occurred
|
||||
errors, warnings = self.config.validate()
|
||||
for _, w in warnings:
|
||||
log.warning(w)
|
||||
for k, e in errors:
|
||||
raise PluginError(
|
||||
f"Error reading metadata '{k}' of post '{path}' in '{docs}':\n"
|
||||
f"{e}"
|
||||
)
|
||||
|
||||
# Excerpts are subsets of posts that are used in views 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.
|
||||
self.excerpt: Excerpt = None
|
||||
|
||||
# Initialize authors and actegories
|
||||
self.authors: list[Author] = []
|
||||
self.categories: list[Category] = []
|
||||
|
||||
# Ensure template is set or use default
|
||||
self.meta.setdefault("template", "blog-post.html")
|
||||
|
||||
# Ensure template hides navigation
|
||||
self.meta["hide"] = self.meta.get("hide", [])
|
||||
if "navigation" not in self.meta["hide"]:
|
||||
self.meta["hide"].append("navigation")
|
||||
|
||||
# The contents and metadata were already read in the constructor (and not
|
||||
# in `read_source` as for pages), so this function must be set to a no-op
|
||||
def read_source(self, config: MkDocsConfig):
|
||||
pass
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Excerpt
|
||||
class Excerpt(Page):
|
||||
|
||||
# Initialize an excerpt for the given post - we create the Markdown parser
|
||||
# when intitializing the excerpt in order to improve rendering performance
|
||||
# for excerpts, as they are reused across several different views, because
|
||||
# posts might be referenced from multiple different locations
|
||||
def __init__(self, post: Post, config: MkDocsConfig, files: Files):
|
||||
self.file = copy(post.file)
|
||||
self.post = post
|
||||
|
||||
# Initialize configuration, contents and metadata
|
||||
self.config = post.config
|
||||
self.markdown = post.markdown
|
||||
self.meta = post.meta
|
||||
|
||||
# Initialize authors and categories - note that views usually contain
|
||||
# subsets of those lists, which is why we need to manage them here
|
||||
self.authors: list[Author] = []
|
||||
self.categories: list[Category] = []
|
||||
|
||||
# Initialize parser - note that we need to patch the configuration,
|
||||
# more specifically the table of contents extension
|
||||
config = _patch(config)
|
||||
self.md = Markdown(
|
||||
extensions = config.markdown_extensions,
|
||||
extension_configs = config.mdx_configs,
|
||||
)
|
||||
|
||||
# Register excerpt tree processor - this processor resolves anchors to
|
||||
# posts from within views, so they point to the correct location
|
||||
self.md.treeprocessors.register(
|
||||
ExcerptTreeprocessor(post),
|
||||
"excerpt",
|
||||
0
|
||||
)
|
||||
|
||||
# Register relative path tree processor - this processor resolves links
|
||||
# to other pages and assets, and is used by MkDocs itself
|
||||
self.md.treeprocessors.register(
|
||||
_RelativePathTreeprocessor(self.file, files, config),
|
||||
"relpath",
|
||||
1
|
||||
)
|
||||
|
||||
# Render an excerpt of the post on the given page - note that this is not
|
||||
# thread-safe because excerpts are shared across views, as it cuts down on
|
||||
# the cost of initialization. However, if in the future, we decide to render
|
||||
# posts and views concurrently, we must change this behavior.
|
||||
def render(self, page: Page, separator: str):
|
||||
self.file.url = page.url
|
||||
|
||||
# Retrieve excerpt tree processor and set page as base
|
||||
at = self.md.treeprocessors.get_index_for_name("excerpt")
|
||||
processor: ExcerptTreeprocessor = self.md.treeprocessors[at]
|
||||
processor.base = page
|
||||
|
||||
# Convert Markdown to HTML and extract excerpt
|
||||
self.content = self.md.convert(self.markdown)
|
||||
self.content, *_ = self.content.split(separator, 1)
|
||||
|
||||
# Extract table of contents and reset post URL - if we wouldn't reset
|
||||
# the excerpt URL, linking to the excerpt from the view would not work
|
||||
self.toc = get_toc(getattr(self.md, "toc_tokens", []))
|
||||
self.file.url = self.post.url
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# View
|
||||
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]
|
||||
|
||||
# Initialize posts and views
|
||||
self.posts: list[Post] = []
|
||||
self.views: list[View] = []
|
||||
|
||||
# Initialize pages for pagination
|
||||
self.pages: list[View] = []
|
||||
|
||||
# Set necessary metadata
|
||||
def read_source(self, config: MkDocsConfig):
|
||||
super().read_source(config)
|
||||
|
||||
# Ensure template is set or use default
|
||||
self.meta.setdefault("template", "blog.html")
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Archive view
|
||||
class Archive(View):
|
||||
pass
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Category view
|
||||
class Category(View):
|
||||
pass
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Helper functions
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Patch configuration
|
||||
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"])
|
||||
|
||||
# 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"],
|
||||
**{
|
||||
"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
|
||||
}
|
||||
}
|
||||
|
||||
# Additionally, we disable link validation when rendering excerpts, because
|
||||
# invalid links have already been reported when rendering the page
|
||||
links = config.validation.links
|
||||
links.not_found = logging.DEBUG
|
||||
links.absolute_links = logging.DEBUG
|
||||
links.unrecognized_links = logging.DEBUG
|
||||
|
||||
# Return patched configuration
|
||||
return config
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Data
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Set up logging
|
||||
log = logging.getLogger("mkdocs.material.blog")
|
37
src/plugins/blog/structure/config.py
Normal file
37
src/plugins/blog/structure/config.py
Normal file
@ -0,0 +1,37 @@
|
||||
# Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
|
||||
|
||||
# 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 mkdocs.config.base import Config
|
||||
from mkdocs.config.config_options import ListOfItems, Optional, Type
|
||||
|
||||
from .options import PostDate
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Post configuration
|
||||
class PostConfig(Config):
|
||||
authors = ListOfItems(Type(str), default = [])
|
||||
categories = ListOfItems(Type(str), default = [])
|
||||
date = PostDate()
|
||||
draft = Optional(Type(bool))
|
||||
readtime = Optional(Type(int))
|
||||
slug = Optional(Type(str))
|
58
src/plugins/blog/structure/markdown.py
Normal file
58
src/plugins/blog/structure/markdown.py
Normal file
@ -0,0 +1,58 @@
|
||||
# Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
|
||||
|
||||
# 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 markdown.treeprocessors import Treeprocessor
|
||||
from mkdocs.structure.pages import Page
|
||||
from mkdocs.utils import get_relative_url
|
||||
from xml.etree.ElementTree import Element
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Excerpt tree processor
|
||||
class ExcerptTreeprocessor(Treeprocessor):
|
||||
|
||||
# Initialize excerpt tree processor
|
||||
def __init__(self, page: Page, base: Page = None):
|
||||
self.page = page
|
||||
self.base = base
|
||||
|
||||
# Transform HTML after Markdown processing
|
||||
def run(self, root: Element):
|
||||
main = True
|
||||
|
||||
# We're only interested in anchors, which is why we continue when the
|
||||
# link does not start with an anchor tag
|
||||
for el in root.iter("a"):
|
||||
anchor = el.get("href")
|
||||
if not anchor.startswith("#"):
|
||||
continue
|
||||
|
||||
# The main headline should link to the post page, not to a specific
|
||||
# anchor, which is why we remove the anchor in that case
|
||||
path = get_relative_url(self.page.url, self.base.url)
|
||||
if main:
|
||||
el.set("href", path)
|
||||
else:
|
||||
el.set("href", path + anchor)
|
||||
|
||||
# Main headline has been seen
|
||||
main = False
|
85
src/plugins/blog/structure/options.py
Normal file
85
src/plugins/blog/structure/options.py
Normal file
@ -0,0 +1,85 @@
|
||||
# Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
|
||||
|
||||
# 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 collections import UserDict
|
||||
from datetime import date, datetime, time
|
||||
from mkdocs.config.base import BaseConfigOption, Config, ValidationError
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Date dictionary
|
||||
class DateDict(UserDict[str, datetime]):
|
||||
|
||||
# Initialize date dictionary
|
||||
def __init__(self, data: dict):
|
||||
super().__init__(data)
|
||||
|
||||
# Initialize date of creation
|
||||
if "created" in data:
|
||||
self.created: datetime = data["created"]
|
||||
|
||||
def __getattr__(self, name: str):
|
||||
if name in self.data:
|
||||
return self.data[name]
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Post date option
|
||||
class PostDate(BaseConfigOption[DateDict]):
|
||||
|
||||
# Initialize post dates
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Normalize the supported types for post dates to datetime
|
||||
def pre_validation(self, config: Config, key_name: str):
|
||||
|
||||
# If the date points to a scalar value, convert it to a dictionary,
|
||||
# since we want to allow the user to specify custom and arbitrary date
|
||||
# values for posts. Currently, only the `created` date is mandatory,
|
||||
# because it's needed to sort posts for views.
|
||||
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])
|
||||
for key, value in config[key_name].items():
|
||||
if isinstance(value, date):
|
||||
config[key_name][key] = datetime.combine(value, time())
|
||||
|
||||
# Ensure each date value is of type datetime
|
||||
def run_validation(self, value: DateDict):
|
||||
for key in value:
|
||||
if not isinstance(value[key], datetime):
|
||||
raise ValidationError(
|
||||
f"Expected type: {date} or {datetime} "
|
||||
f"but received: {type(value[key])}"
|
||||
)
|
||||
|
||||
# Ensure presence of `date.created`
|
||||
if not value.created:
|
||||
raise ValidationError(
|
||||
"Expected 'created' date when using dictionary syntax"
|
||||
)
|
||||
|
||||
# Return date dictionary
|
||||
return value
|
42
src/plugins/blog/templates/__init__.py
Normal file
42
src/plugins/blog/templates/__init__.py
Normal file
@ -0,0 +1,42 @@
|
||||
# Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
|
||||
|
||||
# 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 | 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):
|
||||
if page.url == url:
|
||||
url = page.pages[0].url
|
||||
|
||||
# Forward to original template filter
|
||||
return _url_filter(context, url)
|
@ -0,0 +1,19 @@
|
||||
# Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
|
||||
|
||||
# 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.
|
@ -22,10 +22,10 @@ from mkdocs.config.config_options import Type
|
||||
from mkdocs.config.base import Config
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Class
|
||||
# Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Info plugin configuration scheme
|
||||
# Info plugin configuration
|
||||
class InfoConfig(Config):
|
||||
enabled = Type(bool, default = True)
|
||||
enabled_on_serve = Type(bool, default = False)
|
||||
|
@ -29,15 +29,15 @@ from colorama import Fore, Style
|
||||
from importlib.metadata import distributions, version
|
||||
from io import BytesIO
|
||||
from markdown.extensions.toc import slugify
|
||||
from mkdocs import utils
|
||||
from mkdocs.plugins import BasePlugin, event_priority
|
||||
from mkdocs.structure.files import get_files
|
||||
from mkdocs.utils import get_theme_dir
|
||||
from zipfile import ZipFile, ZIP_DEFLATED
|
||||
|
||||
from material.plugins.info.config import InfoConfig
|
||||
from .config import InfoConfig
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Class
|
||||
# Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Info plugin
|
||||
@ -87,8 +87,8 @@ class InfoPlugin(BasePlugin[InfoConfig]):
|
||||
# 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.
|
||||
base = utils.get_theme_dir(config.theme.name)
|
||||
if config.theme.dirs.index(base):
|
||||
path = get_theme_dir(config.theme.name)
|
||||
if config.theme.dirs.index(path):
|
||||
log.error("Please remove 'custom_dir' setting.")
|
||||
self._help_on_customizations_and_exit()
|
||||
|
||||
|
@ -0,0 +1,19 @@
|
||||
# Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
|
||||
|
||||
# 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.
|
@ -22,9 +22,9 @@ from mkdocs.config.config_options import Type
|
||||
from mkdocs.config.base import Config
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Class
|
||||
# Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Offline plugin configuration scheme
|
||||
# Offline plugin configuration
|
||||
class OfflineConfig(Config):
|
||||
enabled = Type(bool, default = True)
|
||||
|
@ -20,13 +20,13 @@
|
||||
|
||||
import os
|
||||
|
||||
from mkdocs import utils
|
||||
from mkdocs.plugins import BasePlugin, event_priority
|
||||
from mkdocs.utils import write_file
|
||||
|
||||
from material.plugins.offline.config import OfflineConfig
|
||||
from .config import OfflineConfig
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Class
|
||||
# Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Offline plugin
|
||||
@ -55,14 +55,13 @@ class OfflinePlugin(BasePlugin[OfflineConfig]):
|
||||
return
|
||||
|
||||
# Check for existence of search index
|
||||
base = os.path.join(config.site_dir, "search")
|
||||
path = os.path.join(base, "search_index.json")
|
||||
path = os.path.join(config.site_dir, "search", "search_index.json")
|
||||
if not os.path.isfile(path):
|
||||
return
|
||||
|
||||
# Create script with inlined search index
|
||||
with open(path, "r") as f:
|
||||
utils.write_file(
|
||||
with open(path, encoding = "utf-8") as f:
|
||||
write_file(
|
||||
f"var __index = {f.read()}".encode("utf-8"),
|
||||
path.replace(".json", ".js"),
|
||||
)
|
||||
|
@ -0,0 +1,19 @@
|
||||
# Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>
|
||||
|
||||
# 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.
|
@ -29,13 +29,17 @@ from mkdocs.config.base import Config
|
||||
from mkdocs.contrib.search import LangOption
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Class
|
||||
# Options
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Search pipeline functions
|
||||
# Options for search pipeline
|
||||
pipeline = ("stemmer", "stopWordFilter", "trimmer")
|
||||
|
||||
# Search plugin configuration scheme
|
||||
# -----------------------------------------------------------------------------
|
||||
# Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Search plugin configuration
|
||||
class SearchConfig(Config):
|
||||
lang = Optional(LangOption())
|
||||
separator = Optional(Type(str))
|
||||
|
@ -28,7 +28,7 @@ from html.parser import HTMLParser
|
||||
from mkdocs import utils
|
||||
from mkdocs.plugins import BasePlugin
|
||||
|
||||
from material.plugins.search.config import SearchConfig
|
||||
from .config import SearchConfig
|
||||
|
||||
try:
|
||||
import jieba
|
||||
@ -36,7 +36,7 @@ except ImportError:
|
||||
jieba = None
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Class
|
||||
# Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Search plugin
|
||||
@ -46,13 +46,13 @@ class SearchPlugin(BasePlugin[SearchConfig]):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Initialize variables for incremental builds
|
||||
# Initialize incremental builds
|
||||
self.is_dirtyreload = False
|
||||
|
||||
# Initialize search index cache
|
||||
self.search_index_prev = None
|
||||
|
||||
# Determine whether we're serving
|
||||
# Determine whether we're serving the site
|
||||
def on_startup(self, *, command, dirty):
|
||||
self.is_dirty = dirty
|
||||
|
||||
@ -81,7 +81,7 @@ class SearchPlugin(BasePlugin[SearchConfig]):
|
||||
# Set jieba dictionary, if given
|
||||
if self.config.jieba_dict:
|
||||
path = os.path.normpath(self.config.jieba_dict)
|
||||
if os.path.exists(path):
|
||||
if os.path.isfile(path):
|
||||
jieba.set_dictionary(path)
|
||||
log.debug(f"Loading jieba dictionary: {path}")
|
||||
else:
|
||||
@ -93,7 +93,7 @@ class SearchPlugin(BasePlugin[SearchConfig]):
|
||||
# Set jieba user dictionary, if given
|
||||
if self.config.jieba_dict_user:
|
||||
path = os.path.normpath(self.config.jieba_dict_user)
|
||||
if os.path.exists(path):
|
||||
if os.path.isfile(path):
|
||||
jieba.load_userdict(path)
|
||||
log.debug(f"Loading jieba user dictionary: {path}")
|
||||
else:
|
||||
|
@ -21,6 +21,11 @@
|
||||
import logging
|
||||
import sys
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Checks
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Check for pillow and cairosvg
|
||||
try:
|
||||
import cairosvg as _
|
||||
import PIL as _
|
||||
|
@ -22,10 +22,10 @@ from mkdocs.config.base import Config
|
||||
from mkdocs.config.config_options import Deprecated, Type
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Class
|
||||
# Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Social plugin configuration scheme
|
||||
# Social plugin configuration
|
||||
class SocialConfig(Config):
|
||||
enabled = Type(bool, default = True)
|
||||
cache_dir = Type(str, default = ".cache/plugin/social")
|
||||
|
@ -25,6 +25,7 @@ import os
|
||||
import posixpath
|
||||
import re
|
||||
import requests
|
||||
import sys
|
||||
|
||||
from cairosvg import svg2png
|
||||
from collections import defaultdict
|
||||
@ -37,10 +38,10 @@ from shutil import copyfile
|
||||
from tempfile import TemporaryFile
|
||||
from zipfile import ZipFile
|
||||
|
||||
from material.plugins.social.config import SocialConfig
|
||||
from .config import SocialConfig
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Class
|
||||
# Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Social plugin
|
||||
|
@ -23,13 +23,13 @@ from markdown.extensions.toc import slugify
|
||||
from mkdocs.config.config_options import Optional, Type
|
||||
from mkdocs.config.base import Config
|
||||
|
||||
from material.plugins.tags import casefold
|
||||
from . import casefold
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Class
|
||||
# Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Tags plugin configuration scheme
|
||||
# Tags plugin configuration
|
||||
class TagsConfig(Config):
|
||||
enabled = Type(bool, default = True)
|
||||
|
||||
|
@ -28,11 +28,11 @@ from mkdocs.plugins import BasePlugin
|
||||
|
||||
# deprecated, but kept for downward compatibility. Use 'material.plugins.tags'
|
||||
# as an import source instead. This import is removed in the next major version.
|
||||
from material.plugins.tags import casefold
|
||||
from material.plugins.tags.config import TagsConfig
|
||||
from . import casefold
|
||||
from .config import TagsConfig
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Class
|
||||
# Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Tags plugin
|
||||
|
Loading…
Reference in New Issue
Block a user