自定义主题

创建和分发自定义主题的指南。


!!! Note “注意:”

  1. 如果你只是寻找第三方主题,请转到MkDocsGitHub上的[wiki页面](https://github.com/mkdocs/mkdocs/wiki/MkDocs-Themes)。如果你想共享自己的创作的主题,也可以在该页面上提交。

创建新主题时,您可以按照本指南中的步骤从头开始创建一个,也可以下载mkdocs-basic-theme,在此基础开始创作。该主题是一个基本的,但是包含了一个主题所必须的所有文件。 您可以在GitHub上找到此基本主题。它包含代码中的详细注释,用于描述不同的功能及其用法。

创建自定义主题

自定义主题至少包含一个main.htmlJinja2 template文件,要求该文件存放于一个不是docs_dir子目录的目录中。 在mkdocs.yml中,将theme.custom_dir选项设置为包含main.html的目录的路径。 路径应该相对于配置文件。 例如,给定此示例项目布局:

  1. mkdocs.yml
  2. docs/
  3. index.md
  4. about.md
  5. custom_theme/
  6. main.html
  7. ...

您将在mkdocs.yml中包含以下设置以使用自定义主题目录:

  1. theme:
  2. name: null
  3. custom_dir: 'custom_theme/'

!!! Note “注意:”

  1. 通常,在构建自己的自定义主题时,[theme.name]配置设置将设置为`null` 但是,如果theme.[custom_dir]配置值与现有主题结合使用,则theme.[custom_dir]可用于仅替换内置主题的特定部分。 例如,使用上面的布局,如果你设置`name:"mkdocs"`那么主题中的`main.html`文件。[custom_dir]将替换`mkdocs`主题中的同名文件,否则 `mkdocs`主题将保持不变。如果要对现有主题进行小幅调整,这将非常有用。
  2. 有关更具体的信息,请参阅[样式化您的文档]。

基本主题

最简单的main.html文件如下:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>{% if page.title %}{{ page.title }} - {% endif %}{{ config.site_name }}</title>
  5. </head>
  6. <body>
  7. {{ page.content }}
  8. </body>
  9. </html>

使用{{page.content}}标签指代每个页面的正文内容。 样式表和脚本可以像普通HTML文件一样进入此主题。 导航栏和目录也可以分别通过navtoc对象自动生成和包含。 如果您希望编写自己的主题,建议您从[内置主题]之一开始,并相应地进行修改。

!!! Note “注意:”

  1. 由于MkDocs使用[Jinja]作为其模板引擎,因此您可以访问Jinja的所有功能,包括[模板继承]。您可能会注意到MkDocs中包含的主题广泛使用模板继承和块,允许用户轻松地从主题[custom_dir]覆盖模板的一小部分。 因此,内置主题在`base.html`文件中实现,`main.html`扩展。虽然不是必需的,但鼓励第三方模板作者遵循类似的模式,并且可能希望定义与内置主题中使用的相同[块]以保持一致性。

模板变量

主题中的每个模板都使用模板上下文构建。 这些是主题可用的变量。 上下文取决于正在构建的模板。目前,模板是使用全局上下文或特定于页面的上下文构建的。全局上下文用于不代表单个Markdown文档的HTML页面,例如404.html页面或search.html。

全局上下文

以下变量可在任何模板上全局使用。

config

config变量是从mkdocs.yml配置文件生成的MkDocs配置对象的实例。 虽然您可以使用任何配置选项,但一些常用选项包括:

nav

nav变量用于创建文档的导航。 nav对象是由nav配置设置定义的导航对象的可迭代对象。

除了navigation objects的可迭代之外,nav对象还包含以下属性:

nav.homepage

网站主页的页面对象。

nav.pages

导航中包含的所有页面对象的平面列表。此列表不一定是所有网站页面的完整列表,因为它不包含导航中未包含的页面。此列表与用于所有“下一页”和“上一页”链接的页面列表和顺序相匹配。有关所有页面的列表,请使用pages模板变量。

Nav变量示例

以下是将第一级和第二级导航输出为嵌套列表的基本用法示例。

  1. {% if nav|length>1 %}
  2. <ul>
  3. {% for nav_item in nav %}
  4. {% if nav_item.children %}
  5. <li>{{ nav_item.title }}
  6. <ul>
  7. {% for nav_item in nav_item.children %}
  8. <li class="{% if nav_item.active%}current{% endif %}">
  9. <a href="{{ nav_item.url|url }}">{{ nav_item.title }}</a>
  10. </li>
  11. {% endfor %}
  12. </ul>
  13. </li>
  14. {% else %}
  15. <li class="{% if nav_item.active%}current{% endif %}">
  16. <a href="{{ nav_item.url|url }}">{{ nav_item.title }}</a>
  17. </li>
  18. {% endif %}
  19. {% endfor %}
  20. </ul>
  21. {% endif %}

base_url

base_url提供了MkDocs项目根目录的相对路径。虽然可以通过将其预先添加到本地相对URL来直接使用它,但最好使用url模板过滤器,它更灵活地应用base_url

mkdocs_version

包含当前的MkDocs版本。

build_date_utc

一个Python日期时间对象,表示以UTC格式构建文档的日期和时间。 这对于显示文档最近的更新非常有用。

pages

包含项目中所有页面的page对象列表。该列表是一个平面列表,所有页面按字母顺序按目录和文件名排序。请注意,索引页面排在目录中的顶部。 此列表可以包含未包含在全局导航中的页面,并且可能与该导航中的页面顺序不匹配。

page

在未从Markdown源文件呈现的模板中,page变量为None。 在从Markdown源文件呈现的模板中,page变量包含page对象。 相同的page对象在全局navigationpages模板变量中用作page 导航对象

所有page对象都包含以下属性:

page.title

包含当前页面的标题。

page.content

呈现Markdown为HTML,这是文档的内容。

page.toc

一个可迭代的对象,表示页面的目录。 toc中的每个项目都是AnchorLink,它包含以下属性:

  • AnchorLink.title: 该项目的文本。
  • AnchorLink.url: 指向该项的URL中的以#开头的片段。
  • AnchorLink.level: 项目的从零开始的级别。
  • AnchorLink.children: 任何子项的可迭代。

以下示例将显示页面的目录的前两个级别。

  1. <ul>
  2. {% for toc_item in page.toc %}
  3. <li><a href="{{ toc_item.url }}">{{ toc_item.title }}</a></li>
  4. {% for toc_item in toc_item.children %}
  5. <li><a href="{{ toc_item.url }}">{{ toc_item.title }}</a></li>
  6. {% endfor %}
  7. {% endfor %}
  8. </ul>
page.meta

包含在markdown页面顶部的元数据的映射。 在这个例子中,我们在页面标题上面定义了一个source属性。

  1. source: generics.py
  2. mixins.py
  3. # Page title
  4. Content...

模板可以使用meta.source变量访问页面的元数据。 然后,可以使用它链接到与文档页面相关的源文件。

  1. {% for filename in page.meta.source %}
  2. <a class="github" href="https://github.com/.../{{ filename }}">
  3. <span class="label label-info">{{ filename }}</span>
  4. </a>
  5. {% endfor %}
page.url

页面的URL相对于MkDocssite_dir。 预计这将与url过滤器一起使用,以确保URL相对于当前页面。

  1. <a href="{{ page.url|url }}">{{ page.title }}</a>
page.abs_url

来自服务器根目录的页面的绝对URL,由分配给site_url配置设置的值确定。 该值包括site_url中包含的任何子目录,但不包括域。 base_url不应与此变量一起使用。

例如,如果site_url:https://example.com/,那么页面foo.mdpage.abs_url的值将是/foo/。但是,如果site_url:https://example.com/bar/,那么页面foo.mdpage.abs_url的值将是/bar/foo/

page.canonical_url

通过分配给site_url配置设置的值确定的当前页面的完整,规范URL。 该值包括域和“site_url”中包含的任何子目录。 base_url不应与此变量一起使用。

page.edit_url

源存储库中源页面的完整URL。 通常用于提供编辑源页面的链接。 base_url不应与此变量一起使用。

page.is_homepage

对网站的主页评估为True,对所有其他页面评估为False。 这可以与page对象的其他属性一起使用来改变行为。 例如,要在主页上显示不同的标题:

  1. {% if not page.is_homepage %}{{ page.title }} - {% endif %}{{ site_name }}
page.previous_page

上一页的页面对象或“无”。如果当前页面是站点导航中的第一项,或者当前页面根本不包含在导航中,则该值将为“无”。 当值是页面对象时,用法与“page”的用法相同。

page.next_page

下一页的页面对象或“无”。如果当前页面是站点导航中的最后一项,或者当前页面根本不包含在导航中,则该值将为“无”。 当值是页面对象时,用法与“page”的用法相同。

page.parent

网站导航中页面的直接父级。 如果页面位于顶层,则为None

page.children

页面不包含子项,属性始终为None

page.active

True时,表示该页面是当前查看的页面。 默认为False

page.is_section

表示导航对象是“section”对象。 页面对象始终为False

page.is_page

表示导航对象是“页面”对象。 页面对象始终为True

page.is_link

表示导航对象是“链接”对象。 页面对象始终为False

导航对象

nav模板变量中包含的导航对象可以是section对象,page对象和link对象之一。 虽然节点对象可能包含嵌套的导航对象,但页面和链接却不包含。

页面对象是用于当前[页面](#页面)的完整页面对象,具有所有可用的相同属性。Section和Link对象包含以下定义的那些属性的子集:

Section

section导航对象在导航中定义命名部分,并包含子导航对象的列表。请注意,这些部分不包含URL,也不是任何类型的链接。但是,默认情况下,如果主题选择这样做,MkDocs会将索引页面排序到顶部,并且第一个子节点可能会用作节点的URL。

section对象提供以下属性:

section.title

节点的标题。

section.parent

如果该节点位于顶层,则该节点的直接父级或None

section.children

所有子导航对象的可迭代。 子级可能包括嵌套的部分,页面和链接。

section.active

True时,表示此节点的子页面是当前页面,可用于将该部分突出显示为当前查看的节点。 默认为False

section.is_section

表示导航对象是”节点”对象。 节点对象始终为True

section.is_page

表示导航对象是“页面”对象。 对于节点对象,始终为False

section.is_link

表示导航对象是“链接”对象。 对于节点对象,始终为False

Link

link导航对象包含一个不指向内部MkDocs页面的链接。 link对象提供以下属性:

link.title

链接的标题。 这通常用作链接的标签。

link.url

链接指向的URL。 URL应该始终是绝对URL,并且不需要预先设置base_url

link.parent

链接的直接父级。 如果链接位于顶层,则为None

link.children

链接不包含子项,属性始终为None

link.active

外部链接不能“活动”,属性始终为False

link.is_section

表示导航对象是“section”对象。 链接对象始终为False

link.is_page

表示导航对象是“页面”对象。 链接对象始终为False

link.is_link

表示导航对象是“链接”对象。 链接对象始终为True

额外的上下文

可以使用extra配置选项将其他变量传递给模板。 这是一组键值对,可以使自定义模板更加灵活。

例如,这可以用于包括所有页面的项目版本以及与项目相关的链接列表。 这可以通过以下extra配置来实现:

  1. extra:
  2. version: 0.13.0
  3. links:
  4. - https://github.com/mkdocs
  5. - https://docs.readthedocs.org/en/latest/builds.html#mkdocs
  6. - https://www.mkdocs.org/

然后在自定义主题中显示此HTML。

  1. {{ config.extra.version }}
  2. {% if config.extra.links %}
  3. <ul>
  4. {% for link in config.extra.links %}
  5. <li>{{ link }}</li>
  6. {% endfor %}
  7. </ul>
  8. {% endif %}

模板过滤器

除了Jinja的默认过滤器之外,还可以在MkDocs模板中使用以下自定义过滤器:

url

规范化URL。 绝对URL通过不变的方式传递。如果URL是相对的并且模板上下文包括页面对象,则相对于页面对象返回URL。否则,返回的URL前缀为base_url

  1. <a href="{{ page.url|url }}">{{ page.title }}</a>

tojson

Safety convert a Python object to a value in a JavaScript script. 用于JavaScript中,安全地转换python对象的值。

  1. <script>
  2. var mkdocs_page_name = {{ page.title|tojson|safe }};
  3. </script>

主题中的搜索

截至MkDocs版本 0.17 客户端搜索支持已通过search插件添加到MkDocs。主题需要为插件提供一些与主题一起使用的东西。

虽然默认情况下会激活search插件,但用户可以禁用该插件,主题应该考虑到这一点。建议主题模板使用对插件的检查来包装搜索特定标记:

  1. {% if 'search' in config['plugins'] %}
  2. search stuff here...
  3. {% endif %}

在其最基本的功能上,搜索插件将只提供一个索引文件,该文件不过是包含所有页面内容的JSON文件。 主题需要在客户端实现自己的搜索功能。 但是,通过一些设置和必要的模板,插件可以提供基于lunr.js的完整功能的客户端搜索工具。

需要将以下HTML添加到主题中,以便提供的JavaScript能够正确加载搜索脚本并从当前页面进行搜索结果的相对链接。

  1. <script>var base_url = '{{ base_url }}';</script>

使用正确配置的设置,模板中的以下HTML将为您的主题添加完整的搜索实现。

  1. <h1 id="search">Search Results</h1>
  2. <form action="search.html">
  3. <input name="q" id="mkdocs-search-query" type="text" >
  4. </form>
  5. <div id="mkdocs-search-results">
  6. Sorry, page not found.
  7. </div>

插件中的JavaScript通过查找上述HTML中使用的特定ID来工作。 用户键入搜索查询的表单输入必须使用id =“mkdocs-search-query”来标识,并且必须使用id =“mkdocs-search-results”来识别将放置结果的div。

该插件支持在[theme的配置文件]mkdocs_theme.yml中设置以下选项:

include_search_page

确定搜索插件是否期望主题通过位于search / search.html的模板提供专用搜索页面。

include_search_page设置为true时,搜索模板将在search / search.html上构建并可用。 这个方法被readthedocs主题使用。

include_search_page设置为false或未定义时,预期主题提供一些其他显示搜索结果的机制。 例如,mkdocs主题通过模态在任何页面上显示结果。

search_index_only

确定搜索插件是仅生成搜索索引还是生成完整的搜索解决方案。

search_index_only设置为false时,搜索插件通过添加自己的templates目录(优先级低于主题)来修改Jinja环境,并将其脚本添加到extra_javascript配置设置中。

search_index_only设置为true或未定义时,搜索插件不会对Jinja环境进行任何修改。使用提供的索引文件的完整解决方案是主题的责任。

搜索索引将写入site_dir中的search / search_index.jsonJSON文件。该文件中包含的JSON对象最多可包含三个对象。

  1. {
  2. config: {...},
  3. data: [...],
  4. index: {...}
  5. }

如果存在,config对象包含在plugings.search下用户的mkdocs.yml配置文件中为插件定义的配置选项的键/值对。 config对象是MkDocs版本 1.0 中的新对象。

data对象包含文档对象列表。 每个文档对象由“位置”(URL),“标题”和“文本”组成,可用于创建搜索索引和/或显示搜索结果。

如果存在,index对象包含一个预构建的索引,它为较大的站点提供性能改进。 请注意,仅当用户明确启用prebuild_index配置选项时,才会创建预构建索引。 主题应该期望索引不存在,但可以选择在可用时使用索引。 index对象是MkDocs版本 1.0 中的新对象。

打包主题

MkDocs利用Python packaging来分发主题。 这有一些要求。

要查看包含一个主题的包的示例,请参阅MkDocs Bootstrap主题;查看包含许多主题的包,请参阅MkDocs Bootswatch主题

!!! Note “注意:”

  1. 打包主题并不是绝对必要的,因为整个主题可以包含在`custom_dir`中。如果你创造了一个“一次性主题”,那就足够了。 但是,如果您打算将主题分发给其他人使用,则打包主题具有一些优势。通过打包您的主题,您的用户可以更轻松地安装它,然后他们可以利用[custom_dir]对您的主题进行调整,以更好地满足他们的需求。

包布局

建议主题使用以下布局。 顶级目录中的两个文件名为MANIFEST.in和主题目录旁边的setup.py,其中包含一个空的__init __。py文件,一个主题配置文件(mkdocs-theme.yml),以及模板和媒体文件。

  1. .
  2. |-- MANIFEST.in
  3. |-- theme_name
  4. | |-- __init__.py
  5. | |-- mkdocs-theme.yml
  6. | |-- main.html
  7. | |-- styles.css
  8. `-- setup.py

MANIFEST.in文件应包含以下内容,但更新了theme_name,并在include中添加了任何额外的文件扩展名。

  1. recursive-include theme_name *.ico *.js *.css *.png *.html *.eot *.svg *.ttf *.woff
  2. recursive-exclude * __pycache__
  3. recursive-exclude * *.py[co]

setup.py应该包含以下文本以及下面描述的修改。

  1. from setuptools import setup, find_packages
  2. VERSION = '0.0.1'
  3. setup(
  4. name="mkdocs-themename",
  5. version=VERSION,
  6. url='',
  7. license='',
  8. description='',
  9. author='',
  10. author_email='',
  11. packages=find_packages(),
  12. include_package_data=True,
  13. entry_points={
  14. 'mkdocs.themes': [
  15. 'themename = theme_name',
  16. ]
  17. },
  18. zip_safe=False
  19. )

填写URL,许可证,说明,作者和作者电子邮件地址。

该名称应遵循惯例mkdocs-themename(如mkdocs-bootstrapmkdocs-bootswatch),从MkDocs开始,使用连字符分隔单词并包括主题名称。

该文件的大部分内容可以未经编辑。 我们需要更改的最后一部分是entry_points。 这就是MkDocs如何找到您在包中包含的主题。左侧的名称是用户将在其mkdocs.yml中使用的名称,右侧的名称是包含主题文件的目录。

您在本节开头使用main.html文件创建的目录应包含所有其他主题文件。 最低要求是它包含主题的“main.html”。 它必须还包括一个应该为空的__init __。py文件,这个文件告诉Python该目录是一个包。

主题配置

打包的主题需要包含名为mkdocs_theme.yml的配置文件,该文件放在模板文件的根目录中。该文件应包含主题的默认配置选项。 但是,如果主题不提供配置选项,则仍需要该文件,并且可以留空。

主题作者可以自由定义任何认为必要的任意选项,并且这些选项将在模板中提供以控制行为。例如,主题可能希望侧边栏可选,并在mkdocs_theme.yml文件中包含以下内容:

  1. show_sidebar: true

然后在模板中,可以引用该配置选项:

  1. {% if config.theme.show_sidebar %}
  2. <div id="sidebar">...</div>
  3. {% endif %}

用户可以覆盖项目的mkdocs.yml配置文件中的默认值:

  1. theme:
  2. name: themename
  3. show_sidebar: false

除了主题定义的任意选项外,MkDoc还定义了一些改变其行为的特殊选项:

!!! block “”

  1. #### static_templates
  2. 此选项镜像同名的[theme]配置选项,并允许主题设置一些默认值。 请注意,虽然用户可以向此列表添加模板,但用户无法删除主题配置中包含的模板。
  3. #### extends
  4. 定义此主题继承的父主题。 该值应该是父主题的字符串名称。 正常的Jinja继承规则适用。

插件还可以定义一些选项,允许主题通知插件它期望的插件选项集。 请参阅文档,了解您可能希望在主题中支持的任何插件。

分发主题

通过上述更改,您的主题现在应该可以安装了。 如果你仍然和setup.py在同一目录下,可以使用pip install。来完成这个。

大多数Python包,包括MkDocs,都是在PyPI上发布的。 为此,您应该运行以下命令。

  1. python setup.py register

如果您没有设置帐户,系统会提示您创建帐户。

有关更详细的指南,请参阅[打包和分发项目]的官方Python打包文档。