Docsme 为了能够让使用者可以开箱即用,在插件中包含了默认的页面模板。但这种方式可能造成与网站的页面样式不兼容,比如顶部菜单栏无法复用。所以在最新的版本中(1.0.0-alpha.14),我们重构了默认主题的模板结构,可以让主题开发者更加方便地为文档页面提供模板。
复用模板
在 1.0.0-alpha.14 中,我们提供了一些专门用于复用的模板,以减少主题的开发量。在下面的文档中,我们会先列出所有可复用的模板,再讲解如何在自己开发的主题中使用。
模板列表
modules/doc.html
文档页面,包含最基本的文档结构。
点击查看模板源码
<th:block th:fragment="doc (footer)">
<div class="dm-layout">
<th:block th:replace="~{plugin:plugin-docsme:modules/sidebar}" />
<main class="dm-main">
<div class="dm-content">
<th:block th:replace="~{plugin:plugin-docsme:modules/content-header}" />
<article
data-toc-content
class="dm-content__body prose-content line-numbers"
th:utext="${docInfo.content.content}"
></article>
<div
th:if="${haloCommentEnabled}"
id="comment"
class="dm-content__comment"
>
<halo:comment
group="doc.halo.run"
kind="DocTree"
th:attr="name=${docTree.metadata.name}"
/>
</div>
<th:block th:replace="~{plugin:plugin-docsme:modules/navigation}" />
<th:block th:if="${footer != null}">
<th:block th:replace="${footer}" />
</th:block>
</div>
<aside
class="dm-toc"
data-toc-container
dm-data
dm-bind:class="{'dm-toc--show' : $store.toc.open}"
>
<div class="dm-toc__header">
<h2>目录</h2>
</div>
<div class="dm-toc__body" data-toc-list></div>
</aside>
<div
class="dm-toc-backdrop"
dm-show="$store.toc.open"
dm-on:click="$store.toc.open = false"
></div>
</main>
</div>
</th:block>
modules/doc-catalog.html
文档目录页面。
点击查看模板源码
<th:block th:fragment="doc-catalog (footer)">
<div class="dm-layout">
<th:block th:replace="~{plugin:plugin-docsme:modules/sidebar}" />
<main class="dm-main">
<div class="dm-content">
<th:block th:replace="~{plugin:plugin-docsme:modules/content-header}" />
<article class="dm-content__body">
<div class="dm-directory-grid">
<a
th:each="node : ${sonNodes}"
th:href="${node.status.permalink}"
class="dm-directory-card"
>
<div
class="dm-directory-card__icon"
th:text="${node.spec.type == 'TREE' ? '📁' : '📝'}"
>
{{ item.icon }}
</div>
<h3 class="dm-directory-card__title" th:text="${node.spec.title}">
{{ item.name }}
</h3>
</a>
</div>
</article>
<th:block th:replace="~{plugin:plugin-docsme:modules/navigation}" />
<th:block th:if="${footer != null}">
<th:block th:replace="${footer}" />
</th:block>
</div>
</main>
</div>
</th:block>
modules/docs.html
文档项目列表页面。
点击查看模板源码
<th:block th:fragment="docs (footer,showHeader,showThemeSwitcher)">
<header th:if="${showHeader}" class="dm-header">
<div class="dm-header__content">
<div class="dm-header__logo">
<img th:unless="${#strings.isEmpty(site.logo)}" th:src="${site.logo}" />
<a th:href="${site.url}" th:text="${site.title}"></a>
</div>
<nav class="dm-header__nav">
<button
th:if="${showThemeSwitcher}"
class="dm-header__button"
data-theme-switcher
>
<span
class="theme-icon theme-icon--light icon-[mingcute--brightness-line]"
></span>
<span
class="theme-icon theme-icon--dark icon-[mingcute--moon-line]"
></span>
</button>
</nav>
</div>
</header>
<main class="dm-main-content">
<div class="dm-projects-header">
<h1 class="dm-projects-header__title">文档</h1>
<p class="dm-projects-header__description">所有文档项目</p>
</div>
<div class="dm-projects-grid">
<a
th:each="project : ${projects}"
th:href="${project.status.permalink}"
class="dm-project-card"
>
<div class="dm-project-card__banner">
<div
th:unless="${#strings.isEmpty(project.spec.icon)}"
class="dm-project-card__icon"
>
<img
th:src="${project.spec.icon}"
th:alt="${project.spec.displayName}"
/>
</div>
<div
th:if="${#strings.isEmpty(project.spec.icon)}"
class="dm-project-card__icon"
>
📚
</div>
</div>
<div class="dm-project-card__content">
<h3
class="dm-project-card__title"
th:text="${project.spec.displayName}"
></h3>
<ul class="dm-project-card__info">
<li th:text="|共 ${project.status.totalDocs ?: 0} 篇文档|"></li>
<li th:text="${project.status.permalink}"></li>
</ul>
<div class="dm-project-card__actions">
<span class="dm-project-card__button">访问项目</span>
</div>
</div>
</a>
</div>
<th:block th:if="${footer != null}">
<th:block th:replace="${footer}" />
</th:block>
</main>
</th:block>
modules/content-header.html
文档内容顶部模块,包含面包屑和在移动端展开文档树和目录的按钮。
点击查看模板源码
<header class="dm-content__header">
<nav class="dm-content__breadcrumb">
<a th:href="${project.status.permalink}">首页</a>
<a th:each="crumb : ${crumbs}" th:href="${crumb.status.permalink}" th:text="${crumb.spec.title}"></a>
</nav>
<div class="dm-content__controls">
<button dm-data dm-on:click="$store.sidebar.open = !$store.sidebar.open">
<span class="icon-[mingcute--menu-line]"></span>
菜单
</button>
<button dm-data th:if="${docTree.spec.type == 'DOC'}" dm-on:click="$store.toc.open = !$store.toc.open">
<span class="icon-[mingcute--list-ordered-line]"></span>
本页目录
</button>
</div>
</header>
modules/doc-tree.html
左侧文档树模块。
点击查看模板源码
<details
th:attr="open=${#arrays.contains(crumbs, parentDocTree) || currentDocTree.metadata.name == docTree.metadata.name}"
th:id="${parentDocTree.metadata.name}"
th:fragment="next (parentDocTree,docTrees)"
>
<summary class="dm-nav-tree__toggle">
<a
th:href="${parentDocTree.status.permalink}"
th:text="${parentDocTree.spec.title}"
class="dm-nav-tree__link"
th:classappend="${currentDocTree.metadata.name == docTree.metadata.name ? 'dm-nav-tree__link--active' : ''}"
></a>
</summary>
<ul class="dm-nav-tree dm-nav-tree__nested">
<li class="dm-nav-tree__item" th:fragment="single (docTrees)" th:each="currentDocTree : ${docTrees}">
<a
class="dm-nav-tree__link"
th:if="${currentDocTree.spec.type == 'DOC'}"
th:href="@{${currentDocTree.status.permalink}}"
th:title="${currentDocTree.spec.title}"
th:classappend="${currentDocTree.metadata.name == docTree.metadata.name ? 'dm-nav-tree__link--active' : ''}"
th:text="${currentDocTree.spec.title}"
>
</a>
<th:block th:if="${currentDocTree.spec.type == 'TREE'}">
<th:block
th:replace="~{plugin:plugin-docsme:modules/doc-tree :: next (parentDocTree=${currentDocTree},docTrees=${currentDocTree.children})}"
></th:block>
</th:block>
</li>
</ul>
</details>
modules/header.html
顶部菜单栏模块,包含文档 Logo、名称、文档版本切换按钮、语言切换按钮、明暗主题切换按钮。
点击查看模板源码
<th:block th:fragment="header(showThemeSwitcher)">
<header class="dm-header">
<div class="dm-header__content">
<div class="dm-header__logo">
<img
th:unless="${#strings.isEmpty(project.spec.icon)}"
th:src="${project.spec.icon}"
/>
<a
th:href="${project.status.permalink}"
th:text="${project.spec.displayName}"
></a>
</div>
<nav class="dm-header__nav">
<div
th:if="${#lists.size(versions) gt 1}"
dm-data="{
open: false,
toggle() {
if (this.open) {
return this.close()
}
this.$refs.button.focus()
this.open = true
},
close(focusAfter) {
if (! this.open) return
this.open = false
focusAfter && focusAfter.focus()
}
}"
dm-on:keydown.escape.prevent.stop="close($refs.button)"
dm-on:focusin.window="$refs.panel && ! $refs.panel.contains($event.target) && close()"
dm-id="['version-switcher']"
class="dm-header__switcher"
>
<!-- Button -->
<div
dm-ref="button"
dm-on:click="toggle()"
:aria-expanded="open"
:aria-controls="$id('version-switcher')"
class="dm-header__switcher-button"
>
<span th:text="${currentVersion.spec.slug}"></span>
<span
class="dm-header__switcher-icon icon-[mingcute--down-line]"
></span>
</div>
<div
dm-ref="panel"
dm-show="open"
dm-transition.origin.top.left
dm-on:click.outside="close($refs.button)"
:id="$id('version-switcher')"
dm-cloak
th:attr="dm-data=|{ currentVersionLink : '${currentVersion.status.permalink}' }|"
class="dm-header__switcher-panel"
>
<a
th:attr="dm-data=|{ versionLink : '${version.status.permalink}' }|"
th:each="version : ${versions}"
dm-bind:href="location.pathname.replace(currentVersionLink, versionLink)"
th:text="${version.spec.slug}"
>
</a>
</div>
</div>
<div
th:if="${#lists.size(languages) gt 1}"
dm-data="{
open: false,
toggle() {
if (this.open) {
return this.close()
}
this.$refs.button.focus()
this.open = true
},
close(focusAfter) {
if (! this.open) return
this.open = false
focusAfter && focusAfter.focus()
}
}"
dm-on:keydown.escape.prevent.stop="close($refs.button)"
dm-on:focusin.window="$refs.panel && ! $refs.panel.contains($event.target) && close()"
dm-id="['language-switcher']"
class="dm-header__switcher"
>
<!-- Button -->
<div
dm-ref="button"
dm-on:click="toggle()"
:aria-expanded="open"
:aria-controls="$id('language-switcher')"
class="dm-header__switcher-button"
>
<span
th:if="${currentLanguage != null}"
th:text="${currentLanguage.label}"
></span>
<span
th:if="${currentLanguage == null}"
class="icon-[mingcute--translate-2-line]"
></span>
<span
class="dm-header__switcher-icon icon-[mingcute--down-line]"
></span>
</div>
<div
dm-ref="panel"
dm-show="open"
dm-transition.origin.top.left
dm-on:click.outside="close($refs.button)"
:id="$id('language-switcher')"
dm-cloak
th:attr="dm-data=|{ currentLanguageLink : '${currentLanguage.link}' }|"
class="dm-header__switcher-panel"
>
<a
th:attr="dm-data=|{ languageLink : '${language.link}' }|"
th:each="language : ${languages}"
dm-bind:href="location.pathname.replace(currentLanguageLink,languageLink)"
>
<span th:text="${language.language}"></span>
<span th:text="${language.label}"></span>
</a>
</div>
</div>
<button
th:if="${pluginFinder.available('PluginSearchWidget')}"
id="btn-search"
class="dm-header__button"
title="搜索"
>
<span class="icon-[mingcute--search-line]"></span>
</button>
<button
th:if="${showThemeSwitcher}"
class="dm-header__button"
data-theme-switcher
>
<span
class="theme-icon theme-icon--light icon-[mingcute--brightness-line]"
></span>
<span
class="theme-icon theme-icon--dark icon-[mingcute--moon-line]"
></span>
</button>
</nav>
</div>
</header>
</th:block>
modules/navigation.html
文档底部上一篇/下一篇按钮。
点击查看模板源码
<nav th:if="${linkNavigation}" class="dm-doc-nav">
<a
th:if="${linkNavigation.hasPrevious}"
th:href="${linkNavigation.previous.link}"
class="dm-doc-nav__item dm-doc-nav__item--prev"
>
<span class="dm-doc-nav__icon icon-[mingcute--right-line]"></span>
<div class="dm-doc-nav__content">
<div class="dm-doc-nav__label">上一篇</div>
<h3 class="dm-doc-nav__title" th:text="${linkNavigation.previous.title}"></h3>
</div>
</a>
<a
th:if="${linkNavigation.hasNext}"
th:href="${linkNavigation.next.link}"
class="dm-doc-nav__item dm-doc-nav__item--next"
>
<span class="dm-doc-nav__icon icon-[mingcute--right-line]"></span>
<div class="dm-doc-nav__content">
<div class="dm-doc-nav__label">下一篇</div>
<h3 class="dm-doc-nav__title" th:text="${linkNavigation.next.title}"></h3>
</div>
</a>
</nav>
modules/plugin-scripts.html
点击查看模板源码
<script th:if="${pluginFinder.available('PluginSearchWidget')}" th:inline="javascript">
document.addEventListener("DOMContentLoaded", () => {
const btnSearch = document.getElementById("btn-search");
if (btnSearch) {
btnSearch.addEventListener("click", () => {
const categoryNames = {
project: "[(${project.metadata.name})]",
version: "[(${currentVersion.metadata.name})]",
language: "[(${currentLanguage.language})]",
};
const options = {
includeCategoryNames: Object.entries(categoryNames)
.map(([key, value]) => {
if (value) {
return `${key}:${value}`;
}
})
.filter(Boolean),
};
SearchWidget.open(options);
});
}
});
</script>
<th:block th:if="${pluginFinder.available('text-diagram')} and ${docTree.spec.type == 'DOC'}">
<script defer src="/plugins/text-diagram/assets/static/mermaid.min.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function () {
const isDark = document.documentElement.dataset.theme === "dark";
mermaid.initialize({
startOnLoad: false,
theme: isDark ? "dark" : "default",
});
mermaid.run({
querySelector: "#content text-diagram[data-type=mermaid]",
});
});
</script>
</th:block>
<th:block th:if="${pluginFinder.available('plugin-katex')} and ${docTree.spec.type == 'DOC'}">
<link rel="stylesheet" href="/plugins/plugin-katex/assets/static/katex.min.css" />
<script defer src="/plugins/plugin-katex/assets/static/katex.min.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function () {
const body = document.getElementById("content");
const renderMath = (selector, displayMode) => {
const els = body.querySelectorAll(selector);
els.forEach((el) => {
katex.render(el.innerText, el, { displayMode });
});
};
if (body) {
renderMath("[math-inline]", false);
renderMath("[math-display]", true);
}
});
</script>
</th:block>
modules/prism.html
默认代码高亮插件的样式和脚本文件,如果你需要使用其他代码高亮插件,可以不引入。
点击查看模板源码
<link
rel="stylesheet"
th:href="@{/plugins/plugin-docsme/assets/static/libs/prism/prism.css?v={version}(version=${plugin.version})}"
/>
<script
th:src="@{/plugins/plugin-docsme/assets/static/libs/prism/prism.js?v={version}(version=${plugin.version})}"
></script>
modules/script.html
基本的文档页面交互脚本,包括明暗主题切换、文档目录生成。
点击查看模板源码
<script
th:src="@{/plugins/plugin-docsme/assets/static/dist/main.iife.js?v={version}(version=${plugin.version})}"
></script>
modules/sidebar.html
文档页面侧边栏,主要包含文档目录树。
点击查看模板源码
<aside
class="dm-sidebar"
dm-data
dm-bind:class="{'dm-sidebar--show' : $store.sidebar.open}"
>
<div class="dm-sidebar__content">
<ul class="dm-nav-tree">
<li
th:replace="~{plugin:plugin-docsme:modules/doc-tree :: single(docTrees=${docTrees})}"
/>
</ul>
</div>
</aside>
<div
class="dm-sidebar-backdrop"
dm-show="$store.sidebar.open"
dm-on:click="$store.sidebar.open = false"
></div>
modules/style.html
基本的文档样式文件,如果你需要完全定制文档页面的样式,可以根据页面的 dom 结构自行编写样式。
点击查看模板源码
<link
rel="stylesheet"
th:href="@{/plugins/plugin-docsme/assets/static/dist/main.css?v={version}(version=${plugin.version})}"
/>
主题模板编写
文档项目列表页面
在主题中创建一个名为 docs.html
的模板,最基础的示例如下:
<th:block th:replace="~{plugin:plugin-docsme:modules/style}" />
<th:block th:replace="~{plugin:plugin-docsme:modules/script}" />
<div class="dm-container">
<th:block th:replace="~{plugin:plugin-docsme:modules/docs :: docs (footer = null, showHeader = true)}" />
</div>
文档页面
在主题中创建一个名为 doc.html
的模板,最基础的示例如下:
<th:block th:replace="~{plugin:plugin-docsme:modules/style}" />
<th:block th:replace="~{plugin:plugin-docsme:modules/script}" />
<th:block th:replace="~{plugin:plugin-docsme:modules/prism}" />
<th:block th:replace="~{plugin:plugin-docsme:modules/plugin-scripts}" />
<div class="dm-container">
<th:block th:replace="~{plugin:plugin-docsme:modules/header :: header (showThemeSwitcher = false)}" />
<th:block th:replace="~{plugin:plugin-docsme:modules/doc :: doc(footer = null)}" />
</div>
文档目录页面
在主题中创建一个名为 doc-catalog.html
的模板,最基础的示例如下:
<th:block th:replace="~{plugin:plugin-docsme:modules/style}" />
<th:block th:replace="~{plugin:plugin-docsme:modules/script}" />
<th:block th:replace="~{plugin:plugin-docsme:modules/plugin-scripts}" />
<div class="dm-container">
<th:block th:replace="~{plugin:plugin-docsme:modules/header :: header (showThemeSwitcher = false)}" />
<th:block th:replace="~{plugin:plugin-docsme:modules/doc-catalog :: doc-catalog(footer = null)}" />
</div>
以上只是最基本的模板结构,如果你要与主题布局相结合,那么需要自行进行调整,比如在各个模板外层引入 layout.html
布局模板,假设你的 layout.html
模板为:
<!DOCTYPE html>
<html lang="en" xmlns:th="https://www.thymeleaf.org" th:fragment="html (head,content)">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2" />
<title th:text="${site.title}"></title>
<link rel="stylesheet" th:href="@{/assets/dist/style.css}" />
<script th:src="@{/assets/dist/main.iife.js}"></script>
<th:block th:if="${head != null}">
<th:block th:replace="${head}" />
</th:block>
</head>
<body>
<section>
<th:block th:replace="${content}" />
</section>
</body>
</html>
那么你的 doc.html
模板就可以这样写:
<!DOCTYPE html>
<html
xmlns:th="https://www.thymeleaf.org"
th:replace="~{modules/layout :: html(head = ~{::head},content = ~{::content})}"
>
<th:block th:fragment="head">
<th:block th:replace="~{plugin:plugin-docsme:modules/style}" />
<th:block th:replace="~{plugin:plugin-docsme:modules/script}" />
<th:block th:replace="~{plugin:plugin-docsme:modules/prism}" />
<th:block th:replace="~{plugin:plugin-docsme:modules/plugin-scripts}" />
</th:block>
<th:block th:fragment="content">
<div class="dm-container">
<th:block
th:replace="~{plugin:plugin-docsme:modules/header :: header (showThemeSwitcher = false)}"
/>
<th:block
th:replace="~{plugin:plugin-docsme:modules/doc :: doc(footer = null)}"
/>
</div>
</th:block>
</html>