初始化仓库

初始化仓库
This commit is contained in:
2026-09-09 13:19:09 +08:00
parent 484d3877aa
commit d36050cff7
3289 changed files with 499918 additions and 0 deletions
+279
View File
@@ -0,0 +1,279 @@
<!doctype html>
<html
lang="zh-CN"
xmlns:th="https://www.thymeleaf.org"
th:lang="${#locale.toLanguageTag}"
class="color-scheme-auto"
data-color-scheme="auto"
>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title th:text="|归档 - ${site.title}|">归档</title>
<script type="module" crossorigin src="/themes/theme-mdocs/assets/main-BAX9Eehy.js"></script>
<link rel="stylesheet" crossorigin href="/themes/theme-mdocs/assets/main-OuYKDL0n.css">
</head>
<body
th:style="'--radius:' + (${theme.config.global?.border_radius} ?: '12px') + ';--font-size:' + (${theme.config.global?.font_size} ?: '16px')"
th:attr="data-bg-light=${theme.config.global?.bg_color_light ?: '#ffffff'},data-bg-dark=${theme.config.global?.bg_color_dark ?: '#0d1117'},data-bg-image=${theme.config.global?.background_image},data-primary=${theme.config.global?.primary_color}"
th:with="siteLogo = ${not #strings.isEmpty(theme.config.global?.site_logo) ? theme.config.global?.site_logo : site.logo}, siteLogoDark = ${not #strings.isEmpty(theme.config.global?.site_logo_dark) ? theme.config.global?.site_logo_dark : siteLogo}, showSiteName = ${theme.config.global?.show_site_name != false}"
>
<header class="site-header">
<div
class="container header-inner"
th:with="primaryMenu = ${menuFinder.getPrimary()}"
>
<a class="site-brand" th:href="@{/}">
<img
th:if="${not #strings.isEmpty(siteLogo)}"
th:src="${siteLogo}"
th:attr="data-logo-light=${siteLogo},data-logo-dark=${siteLogoDark}"
class="brand-logo"
alt="logo"
/>
<span class="brand-name" th:if="${showSiteName}" th:text="${site.title}">源蝶工作室</span>
</a>
<nav class="primary-nav" th:if="${not #lists.isEmpty(theme.config.nav?.doc_nav_buttons)}">
<a
class="primary-nav-link"
th:each="btn : ${theme.config.nav?.doc_nav_buttons ?: T(java.util.Collections).emptyList()}"
th:href="@{${btn.url ?: '/'}}"
th:text="${btn.title}"
></a>
</nav>
<div
class="header-right"
>
<button
class="search-trigger"
id="search-trigger"
type="button"
th:if="${pluginFinder.available('PluginSearchWidget')}"
onclick="SearchWidget.open()"
aria-label="搜索"
>
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
<span class="search-trigger-text">搜索</span>
<kbd class="search-trigger-kbd">⌘K</kbd>
</button>
<button
class="mobile-menu-toggle"
id="mobile-menu-toggle"
type="button"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
aria-label="打开菜单"
aria-controls="header-menu-panel"
aria-expanded="false"
>
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</button>
<div
class="header-menu-wrap"
id="header-menu-panel"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
>
<th:block th:replace="~{header-menu :: menu}" />
</div>
<div
class="header-divider"
aria-hidden="true"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
></div>
<div class="header-actions">
<th:block
th:if="${not #lists.isEmpty(theme.config.nav?.user_buttons)}"
th:each="btn : ${theme.config.nav?.user_buttons ?: T(java.util.Collections).emptyList()}"
>
<a
class="header-action-btn header-icon-btn header-action-icon-btn"
th:if="${not #strings.isEmpty(btn.svg)}"
th:href="@{${btn.url ?: '#'}}"
th:title="${btn.label}"
th:style="${not #strings.isEmpty(btn.color)} ? '--icon-color:' + btn.color : ''"
>
<span class="header-action-icon" th:utext="${btn.svg}"></span>
</a>
</th:block>
<button
class="theme-toggle header-icon-btn"
id="theme-toggle"
aria-label="切换明暗主题"
>
<svg class="icon-sun" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
<svg class="icon-moon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
</svg>
</button>
<th:block th:replace="~{user-menu :: user-menu}" />
</div>
</div>
<div
class="mobile-menu-backdrop"
id="mobile-menu-backdrop"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
></div>
</div>
</header>
<main class="site-main">
<div class="container page-wrap">
<h1 class="page-title">归档</h1>
<th:block th:each="archive : ${archives.items}">
<div class="archive-year">
<h2 th:text="${archive.year}"></h2>
<th:block th:each="month : ${archive.months}">
<h3 th:text="|${archive.year}-${month.month}|"></h3>
<ul class="post-list">
<li class="post-item" th:each="post : ${month.posts}">
<h2 class="post-title">
<a th:href="@{${post.status.permalink}}" th:text="${post.spec.title}"></a>
</h2>
<p class="post-meta">
<time th:text="${#temporals.format(post.spec.publishTime, 'yyyy-MM-dd')}"></time>
</p>
</li>
</ul>
</th:block>
</div>
</th:block>
<nav class="pagination" th:if="${archives.hasPrevious() || archives.hasNext()}">
<a th:if="${archives.hasPrevious()}" th:href="@{${archives.prevUrl}}">上一页</a>
<span th:text="|${archives.page} / ${archives.totalPages}|"></span>
<a th:if="${archives.hasNext()}" th:href="@{${archives.nextUrl}}">下一页</a>
</nav>
</div>
</main>
<footer
class="site-footer"
th:with="separateFooterLogo = ${theme.config.footer?.footer_separate_logo == true}, footerLogo = ${separateFooterLogo ? (not #strings.isEmpty(theme.config.footer?.footer_logo) ? theme.config.footer?.footer_logo : siteLogo) : siteLogo}, footerLogoDark = ${separateFooterLogo ? (not #strings.isEmpty(theme.config.footer?.footer_logo_dark) ? theme.config.footer?.footer_logo_dark : (not #strings.isEmpty(theme.config.footer?.footer_logo) ? theme.config.footer?.footer_logo : siteLogoDark)) : siteLogoDark}, showFooterName = ${theme.config.footer?.show_footer_name != false}"
>
<div class="footer-container">
<th:block
th:if="${not #lists.isEmpty(theme.config.footer?.footer_menus)}"
>
<div class="footer-menu-columns">
<th:block
th:each="menuName : ${theme.config.footer?.footer_menus}"
>
<th:block
th:with="footerMenu = ${not #strings.isEmpty(menuName) ? menuFinder.getByName(menuName) : null}"
>
<div
class="footer-menu-col"
th:if="${footerMenu != null and not #lists.isEmpty(footerMenu.menuItems)}"
>
<h4 th:text="${footerMenu.spec.displayName}"></h4>
<ul>
<li th:each="menuItem : ${footerMenu.menuItems}">
<a
th:href="@{${menuItem.status.href}}"
th:text="${menuItem.status.displayName}"
th:target="${menuItem.spec.target?.value}"
th:rel="${menuItem.spec.target?.value == '_blank'} ? 'noopener' : null"
></a>
</li>
</ul>
</div>
</th:block>
</th:block>
</div>
</th:block>
<div class="footer-bottom">
<a
class="footer-bottom-brand"
th:href="@{/}"
th:if="${not #strings.isEmpty(footerLogo) or showFooterName}"
>
<img
th:if="${not #strings.isEmpty(footerLogo)}"
th:src="${footerLogo}"
th:attr="data-logo-light=${footerLogo},data-logo-dark=${footerLogoDark}"
class="footer-bottom-logo"
alt="logo"
/>
<span
class="footer-bottom-name"
th:if="${showFooterName}"
th:text="${site.title}"
></span>
</a>
<span
class="footer-copyright"
th:if="${not #strings.isEmpty(theme.config.footer?.copyright)}"
th:text="${theme.config.footer?.copyright}"
></span>
<span
class="footer-beian"
th:if="${not #strings.isEmpty(theme.config.footer?.icp) or not #strings.isEmpty(theme.config.footer?.gongan)}"
>
<a
th:if="${not #strings.isEmpty(theme.config.footer?.icp)}"
th:href="${theme.config.footer?.icp_link ?: 'https://beian.miit.gov.cn/'}"
target="_blank"
rel="noopener"
th:text="${theme.config.footer?.icp}"
></a>
<a
class="footer-gongan"
th:if="${not #strings.isEmpty(theme.config.footer?.gongan)}"
th:href="${theme.config.footer?.gongan_link ?: 'https://beian.mps.gov.cn/#/query/webSearch'}"
target="_blank"
rel="noopener"
>
<img
th:src="@{/assets/img/gongan.svg}"
class="footer-gongan-icon"
alt=""
/>
<span th:text="${theme.config.footer?.gongan}"></span>
</a>
</span>
</div>
</div>
<halo:footer />
</footer>
<script th:inline="javascript">
window.themePluginPage = {
docs: /*[[${theme.config.docs}]]*/ {},
docsme: /*[[${theme.config.docsme}]]*/ {},
minidocs: /*[[${theme.config.minidocs}]]*/ {},
};
</script>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 472 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 608 KiB

+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="_图层_2" data-name="图层 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120.58 93.33">
<g id="_图层_1-2" data-name="图层 1">
<g>
<g>
<path d="M64.96,27.24c-.39,.44-1.1,.09-.99-.49,.8-4.17,2-9.36,3.84-15.23C70.99,1.36,72.63,.21,73.86,.06c1.45-.18,4.48,.94,5.85,2.23,0,0,1.67,1.94,1.66,4.51,0,.84-.19,1.55-.44,2.12-.09,.21-.22,.42-.38,.6l-15.58,17.73Z"/>
<path d="M82.68,91.32l6.14-13.04c.67-1.83,.26-3.89-1.09-5.33l-9.01-9.53c-5.93-6.27-5.84-16.11,.2-22.28l12.93-13.19c.55-.56,1.3-.88,2.09-.88h3.22c1.32,0,2.1,1.48,1.36,2.57l-14.68,21.54c-1.19,1.74,.04,4.11,2.15,4.14l9.09,.13c2.73,.04,5.22-1.52,6.38-3.99L120.18,11.53c2.56-8.31-7.71-15.38-14.62-9.15l-36.6,32.98c-3.11,2.8-4.89,6.8-4.89,10.98v25.78c0,2.76,.95,5.44,2.69,7.58l10.04,12.35c1.62,2,4.78,1.61,5.87-.72Z"/>
</g>
<g>
<path d="M55.62,27.24c.39,.44,1.1,.09,.99-.49-.8-4.17-2-9.36-3.84-15.23C49.58,1.36,47.95,.21,46.72,.06c-1.45-.18-4.48,.94-5.85,2.23,0,0-1.67,1.94-1.66,4.51,0,.84,.19,1.55,.44,2.12,.09,.21,.22,.42,.38,.6l15.58,17.73Z"/>
<path d="M37.9,91.32l-6.14-13.04c-.67-1.83-.26-3.89,1.09-5.33l9.01-9.53c5.93-6.27,5.84-16.11-.2-22.28l-12.93-13.19c-.55-.56-1.3-.88-2.09-.88h-3.22c-1.32,0-2.1,1.48-1.36,2.57l14.68,21.54c1.19,1.74-.04,4.11-2.15,4.14l-9.09,.13c-2.73,.04-5.22-1.52-6.38-3.99L.4,11.53C-2.16,3.21,8.11-3.85,15.02,2.37L51.62,35.36c3.11,2.8,4.89,6.8,4.89,10.98v25.78c0,2.76-.95,5.44-2.69,7.58l-10.04,12.35c-1.62,2-4.78,1.61-5.87-.72Z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+5
View File
@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" aria-hidden="true">
<circle cx="10" cy="10" r="10" fill="#3b6fd6"/>
<path d="M10 2.9 14.7 5.1v4c0 3.1-1.8 5.7-4.7 7.1-2.9-1.4-4.7-4-4.7-7.1v-4Z" fill="#ffffff"/>
<path d="M10 5.7l.9 1.9 2.1.3-1.5 1.4.4 2.1-1.9-1-1.9 1 .4-2.1-1.5-1.4 2.1-.3Z" fill="#3b6fd6"/>
</svg>

After

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 KiB

+22
View File
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="_图层_2" data-name="图层 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120.58 93.33">
<defs>
<style>
.cls-1 {
fill: #fff;
}
</style>
</defs>
<g id="_图层_1-2" data-name="图层 1">
<g>
<g>
<path class="cls-1" d="M64.96,27.24c-.39,.44-1.1,.09-.99-.49,.8-4.17,2-9.36,3.84-15.23C70.99,1.36,72.63,.21,73.86,.06c1.45-.18,4.48,.94,5.85,2.23,0,0,1.67,1.94,1.66,4.51,0,.84-.19,1.55-.44,2.12-.09,.21-.22,.42-.38,.6l-15.58,17.73Z"/>
<path class="cls-1" d="M82.68,91.32l6.14-13.04c.67-1.83,.26-3.89-1.09-5.33l-9.01-9.53c-5.93-6.27-5.84-16.11,.2-22.28l12.93-13.19c.55-.56,1.3-.88,2.09-.88h3.22c1.32,0,2.1,1.48,1.36,2.57l-14.68,21.54c-1.19,1.74,.04,4.11,2.15,4.14l9.09,.13c2.73,.04,5.22-1.52,6.38-3.99L120.18,11.53c2.56-8.31-7.71-15.38-14.62-9.15l-36.6,32.98c-3.11,2.8-4.89,6.8-4.89,10.98v25.78c0,2.76,.95,5.44,2.69,7.58l10.04,12.35c1.62,2,4.78,1.61,5.87-.72Z"/>
</g>
<g>
<path class="cls-1" d="M55.62,27.24c.39,.44,1.1,.09,.99-.49-.8-4.17-2-9.36-3.84-15.23C49.58,1.36,47.95,.21,46.72,.06c-1.45-.18-4.48,.94-5.85,2.23,0,0-1.67,1.94-1.66,4.51,0,.84,.19,1.55,.44,2.12,.09,.21,.22,.42,.38,.6l15.58,17.73Z"/>
<path class="cls-1" d="M37.9,91.32l-6.14-13.04c-.67-1.83-.26-3.89,1.09-5.33l9.01-9.53c5.93-6.27,5.84-16.11-.2-22.28l-12.93-13.19c-.55-.56-1.3-.88-2.09-.88h-3.22c-1.32,0-2.1,1.48-1.36,2.57l14.68,21.54c1.19,1.74-.04,4.11-2.15,4.14l-9.09,.13c-2.73,.04-5.22-1.52-6.38-3.99L.4,11.53C-2.16,3.21,8.11-3.85,15.02,2.37L51.62,35.36c3.11,2.8,4.89,6.8,4.89,10.98v25.78c0,2.76-.95,5.44-2.69,7.58l-10.04,12.35c-1.62,2-4.78,1.61-5.87-.72Z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+266
View File
@@ -0,0 +1,266 @@
<!doctype html>
<html
lang="zh-CN"
xmlns:th="https://www.thymeleaf.org"
th:lang="${#locale.toLanguageTag}"
class="color-scheme-auto"
data-color-scheme="auto"
>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title th:text="|${author.spec.displayName} - ${site.title}|">作者</title>
<script type="module" crossorigin src="/themes/theme-mdocs/assets/main-BAX9Eehy.js"></script>
<link rel="stylesheet" crossorigin href="/themes/theme-mdocs/assets/main-OuYKDL0n.css">
</head>
<body
th:style="'--radius:' + (${theme.config.global?.border_radius} ?: '12px') + ';--font-size:' + (${theme.config.global?.font_size} ?: '16px')"
th:attr="data-bg-light=${theme.config.global?.bg_color_light ?: '#ffffff'},data-bg-dark=${theme.config.global?.bg_color_dark ?: '#0d1117'},data-bg-image=${theme.config.global?.background_image},data-primary=${theme.config.global?.primary_color}"
th:with="siteLogo = ${not #strings.isEmpty(theme.config.global?.site_logo) ? theme.config.global?.site_logo : site.logo}, siteLogoDark = ${not #strings.isEmpty(theme.config.global?.site_logo_dark) ? theme.config.global?.site_logo_dark : siteLogo}, showSiteName = ${theme.config.global?.show_site_name != false}"
>
<header class="site-header">
<div
class="container header-inner"
th:with="primaryMenu = ${menuFinder.getPrimary()}"
>
<a class="site-brand" th:href="@{/}">
<img
th:if="${not #strings.isEmpty(siteLogo)}"
th:src="${siteLogo}"
th:attr="data-logo-light=${siteLogo},data-logo-dark=${siteLogoDark}"
class="brand-logo"
alt="logo"
/>
<span class="brand-name" th:if="${showSiteName}" th:text="${site.title}">源蝶工作室</span>
</a>
<nav class="primary-nav" th:if="${not #lists.isEmpty(theme.config.nav?.doc_nav_buttons)}">
<a
class="primary-nav-link"
th:each="btn : ${theme.config.nav?.doc_nav_buttons ?: T(java.util.Collections).emptyList()}"
th:href="@{${btn.url ?: '/'}}"
th:text="${btn.title}"
></a>
</nav>
<div
class="header-right"
>
<button
class="search-trigger"
id="search-trigger"
type="button"
th:if="${pluginFinder.available('PluginSearchWidget')}"
onclick="SearchWidget.open()"
aria-label="搜索"
>
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
<span class="search-trigger-text">搜索</span>
<kbd class="search-trigger-kbd">⌘K</kbd>
</button>
<button
class="mobile-menu-toggle"
id="mobile-menu-toggle"
type="button"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
aria-label="打开菜单"
aria-controls="header-menu-panel"
aria-expanded="false"
>
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</button>
<div
class="header-menu-wrap"
id="header-menu-panel"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
>
<th:block th:replace="~{header-menu :: menu}" />
</div>
<div
class="header-divider"
aria-hidden="true"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
></div>
<div class="header-actions">
<th:block
th:if="${not #lists.isEmpty(theme.config.nav?.user_buttons)}"
th:each="btn : ${theme.config.nav?.user_buttons ?: T(java.util.Collections).emptyList()}"
>
<a
class="header-action-btn header-icon-btn header-action-icon-btn"
th:if="${not #strings.isEmpty(btn.svg)}"
th:href="@{${btn.url ?: '#'}}"
th:title="${btn.label}"
th:style="${not #strings.isEmpty(btn.color)} ? '--icon-color:' + btn.color : ''"
>
<span class="header-action-icon" th:utext="${btn.svg}"></span>
</a>
</th:block>
<button
class="theme-toggle header-icon-btn"
id="theme-toggle"
aria-label="切换明暗主题"
>
<svg class="icon-sun" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
<svg class="icon-moon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
</svg>
</button>
<th:block th:replace="~{user-menu :: user-menu}" />
</div>
</div>
<div
class="mobile-menu-backdrop"
id="mobile-menu-backdrop"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
></div>
</div>
</header>
<main class="site-main">
<div class="container page-wrap">
<h1 class="page-title" th:text="${author.spec.displayName}"></h1>
<p th:if="${not #strings.isEmpty(author.spec.bio)}" th:text="${author.spec.bio}"></p>
<ul class="post-list">
<!-- Partial error: Could not load post-card.html -->
</ul>
<nav class="pagination" th:if="${posts.hasPrevious() || posts.hasNext()}">
<a th:if="${posts.hasPrevious()}" th:href="@{${posts.prevUrl}}">上一页</a>
<span th:text="|${posts.page} / ${posts.totalPages}|"></span>
<a th:if="${posts.hasNext()}" th:href="@{${posts.nextUrl}}">下一页</a>
</nav>
</div>
</main>
<footer
class="site-footer"
th:with="separateFooterLogo = ${theme.config.footer?.footer_separate_logo == true}, footerLogo = ${separateFooterLogo ? (not #strings.isEmpty(theme.config.footer?.footer_logo) ? theme.config.footer?.footer_logo : siteLogo) : siteLogo}, footerLogoDark = ${separateFooterLogo ? (not #strings.isEmpty(theme.config.footer?.footer_logo_dark) ? theme.config.footer?.footer_logo_dark : (not #strings.isEmpty(theme.config.footer?.footer_logo) ? theme.config.footer?.footer_logo : siteLogoDark)) : siteLogoDark}, showFooterName = ${theme.config.footer?.show_footer_name != false}"
>
<div class="footer-container">
<th:block
th:if="${not #lists.isEmpty(theme.config.footer?.footer_menus)}"
>
<div class="footer-menu-columns">
<th:block
th:each="menuName : ${theme.config.footer?.footer_menus}"
>
<th:block
th:with="footerMenu = ${not #strings.isEmpty(menuName) ? menuFinder.getByName(menuName) : null}"
>
<div
class="footer-menu-col"
th:if="${footerMenu != null and not #lists.isEmpty(footerMenu.menuItems)}"
>
<h4 th:text="${footerMenu.spec.displayName}"></h4>
<ul>
<li th:each="menuItem : ${footerMenu.menuItems}">
<a
th:href="@{${menuItem.status.href}}"
th:text="${menuItem.status.displayName}"
th:target="${menuItem.spec.target?.value}"
th:rel="${menuItem.spec.target?.value == '_blank'} ? 'noopener' : null"
></a>
</li>
</ul>
</div>
</th:block>
</th:block>
</div>
</th:block>
<div class="footer-bottom">
<a
class="footer-bottom-brand"
th:href="@{/}"
th:if="${not #strings.isEmpty(footerLogo) or showFooterName}"
>
<img
th:if="${not #strings.isEmpty(footerLogo)}"
th:src="${footerLogo}"
th:attr="data-logo-light=${footerLogo},data-logo-dark=${footerLogoDark}"
class="footer-bottom-logo"
alt="logo"
/>
<span
class="footer-bottom-name"
th:if="${showFooterName}"
th:text="${site.title}"
></span>
</a>
<span
class="footer-copyright"
th:if="${not #strings.isEmpty(theme.config.footer?.copyright)}"
th:text="${theme.config.footer?.copyright}"
></span>
<span
class="footer-beian"
th:if="${not #strings.isEmpty(theme.config.footer?.icp) or not #strings.isEmpty(theme.config.footer?.gongan)}"
>
<a
th:if="${not #strings.isEmpty(theme.config.footer?.icp)}"
th:href="${theme.config.footer?.icp_link ?: 'https://beian.miit.gov.cn/'}"
target="_blank"
rel="noopener"
th:text="${theme.config.footer?.icp}"
></a>
<a
class="footer-gongan"
th:if="${not #strings.isEmpty(theme.config.footer?.gongan)}"
th:href="${theme.config.footer?.gongan_link ?: 'https://beian.mps.gov.cn/#/query/webSearch'}"
target="_blank"
rel="noopener"
>
<img
th:src="@{/assets/img/gongan.svg}"
class="footer-gongan-icon"
alt=""
/>
<span th:text="${theme.config.footer?.gongan}"></span>
</a>
</span>
</div>
</div>
<halo:footer />
</footer>
<script th:inline="javascript">
window.themePluginPage = {
docs: /*[[${theme.config.docs}]]*/ {},
docsme: /*[[${theme.config.docsme}]]*/ {},
minidocs: /*[[${theme.config.minidocs}]]*/ {},
};
</script>
</body>
</html>
+333
View File
@@ -0,0 +1,333 @@
<!doctype html>
<html
lang="zh-CN"
xmlns:th="https://www.thymeleaf.org"
th:lang="${#locale.toLanguageTag}"
class="color-scheme-auto"
data-color-scheme="auto"
>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title th:text="|分类 - ${site.title}|">分类</title>
<script type="module" crossorigin src="/themes/theme-mdocs/assets/main-BAX9Eehy.js"></script>
<link rel="stylesheet" crossorigin href="/themes/theme-mdocs/assets/main-OuYKDL0n.css">
</head>
<body
th:style="'--radius:' + (${theme.config.global?.border_radius} ?: '12px') + ';--font-size:' + (${theme.config.global?.font_size} ?: '16px')"
th:attr="data-bg-light=${theme.config.global?.bg_color_light ?: '#ffffff'},data-bg-dark=${theme.config.global?.bg_color_dark ?: '#0d1117'},data-bg-image=${theme.config.global?.background_image},data-primary=${theme.config.global?.primary_color}"
th:with="siteLogo = ${not #strings.isEmpty(theme.config.global?.site_logo) ? theme.config.global?.site_logo : site.logo}, siteLogoDark = ${not #strings.isEmpty(theme.config.global?.site_logo_dark) ? theme.config.global?.site_logo_dark : siteLogo}, showSiteName = ${theme.config.global?.show_site_name != false}"
>
<header class="site-header">
<div
class="container header-inner"
th:with="primaryMenu = ${menuFinder.getPrimary()}"
>
<a class="site-brand" th:href="@{/}">
<img
th:if="${not #strings.isEmpty(siteLogo)}"
th:src="${siteLogo}"
th:attr="data-logo-light=${siteLogo},data-logo-dark=${siteLogoDark}"
class="brand-logo"
alt="logo"
/>
<span class="brand-name" th:if="${showSiteName}" th:text="${site.title}">源蝶工作室</span>
</a>
<nav class="primary-nav" th:if="${not #lists.isEmpty(theme.config.nav?.doc_nav_buttons)}">
<a
class="primary-nav-link"
th:each="btn : ${theme.config.nav?.doc_nav_buttons ?: T(java.util.Collections).emptyList()}"
th:href="@{${btn.url ?: '/'}}"
th:text="${btn.title}"
></a>
</nav>
<div
class="header-right"
>
<button
class="search-trigger"
id="search-trigger"
type="button"
th:if="${pluginFinder.available('PluginSearchWidget')}"
onclick="SearchWidget.open()"
aria-label="搜索"
>
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
<span class="search-trigger-text">搜索</span>
<kbd class="search-trigger-kbd">⌘K</kbd>
</button>
<button
class="mobile-menu-toggle"
id="mobile-menu-toggle"
type="button"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
aria-label="打开菜单"
aria-controls="header-menu-panel"
aria-expanded="false"
>
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</button>
<div
class="header-menu-wrap"
id="header-menu-panel"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
>
<th:block th:replace="~{header-menu :: menu}" />
</div>
<div
class="header-divider"
aria-hidden="true"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
></div>
<div class="header-actions">
<th:block
th:if="${not #lists.isEmpty(theme.config.nav?.user_buttons)}"
th:each="btn : ${theme.config.nav?.user_buttons ?: T(java.util.Collections).emptyList()}"
>
<a
class="header-action-btn header-icon-btn header-action-icon-btn"
th:if="${not #strings.isEmpty(btn.svg)}"
th:href="@{${btn.url ?: '#'}}"
th:title="${btn.label}"
th:style="${not #strings.isEmpty(btn.color)} ? '--icon-color:' + btn.color : ''"
>
<span class="header-action-icon" th:utext="${btn.svg}"></span>
</a>
</th:block>
<button
class="theme-toggle header-icon-btn"
id="theme-toggle"
aria-label="切换明暗主题"
>
<svg class="icon-sun" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
<svg class="icon-moon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
</svg>
</button>
<th:block th:replace="~{user-menu :: user-menu}" />
</div>
</div>
<div
class="mobile-menu-backdrop"
id="mobile-menu-backdrop"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
></div>
</div>
</header>
<main class="site-main">
<!-- 文档组归档:主题设置手动添加的分类 -->
<div
class="mdocs-plugin-list mdocs-docgroup-archive"
th:if="${theme.config.docs?.doc_groups != null and not #lists.isEmpty(theme.config.docs?.doc_groups)}"
>
<div class="container">
<header class="mdocs-plugin-list-head">
<h1
th:text="${not #strings.isEmpty(theme.config.docs?.archive_title) ? theme.config.docs?.archive_title : '文档'}"
>文档</h1>
<p
th:text="${not #strings.isEmpty(theme.config.docs?.archive_description) ? theme.config.docs?.archive_description : '按分类整理的文档组'}"
>按分类整理的文档组</p>
</header>
<div class="mdocs-plugin-card-grid">
<th:block
th:each="groupName : ${theme.config.docs?.doc_groups}"
>
<th:block
th:with="group = ${categoryFinder.getByName(groupName)}"
>
<a
class="mdocs-plugin-card mdocs-category-card"
th:if="${group != null}"
th:href="@{${group.status.permalink}}"
>
<span
class="mdocs-plugin-card-banner"
th:if="${not #strings.isEmpty(group.spec.cover)}"
>
<img th:src="${group.spec.cover}" th:alt="${group.spec.displayName}" />
</span>
<span
class="mdocs-plugin-card-banner mdocs-plugin-card-fallback"
th:unless="${not #strings.isEmpty(group.spec.cover)}"
>
<span
th:text="${not #strings.isEmpty(group.spec.displayName) ? #strings.substring(group.spec.displayName, 0, 1) : '文'}"
></span>
</span>
<span class="mdocs-plugin-card-body">
<span class="mdocs-plugin-card-title" th:text="${group.spec.displayName}"></span>
<span
class="mdocs-plugin-card-desc"
th:if="${not #strings.isEmpty(group.spec.description)}"
th:text="${group.spec.description}"
></span>
<span class="mdocs-plugin-card-meta">
<span
th:text="|共 ${group.status != null and group.status.postCount != null ? group.status.postCount : 0} 篇文章|"
>共 0 篇文章</span>
<span class="mdocs-plugin-card-link" th:text="${group.status.permalink}"></span>
</span>
<span class="mdocs-plugin-card-action">访问文档 →</span>
</span>
</a>
</th:block>
</th:block>
</div>
</div>
</div>
<!-- 普通分类列表 -->
<div class="container page-wrap" th:unless="${theme.config.docs?.doc_groups != null and not #lists.isEmpty(theme.config.docs?.doc_groups)}">
<h1 class="page-title">分类</h1>
<ul class="category-list">
<li th:each="cat : ${categories.items}">
<a
th:href="@{${cat.status.permalink}}"
th:text="|${cat.spec.displayName} (${cat.postCount})|"
class="category"
></a>
</li>
</ul>
<nav class="pagination" th:if="${categories.hasPrevious() || categories.hasNext()}">
<a th:if="${categories.hasPrevious()}" th:href="@{${categories.prevUrl}}">上一页</a>
<span th:text="|${categories.page} / ${categories.totalPages}|"></span>
<a th:if="${categories.hasNext()}" th:href="@{${categories.nextUrl}}">下一页</a>
</nav>
</div>
</main>
<footer
class="site-footer"
th:with="separateFooterLogo = ${theme.config.footer?.footer_separate_logo == true}, footerLogo = ${separateFooterLogo ? (not #strings.isEmpty(theme.config.footer?.footer_logo) ? theme.config.footer?.footer_logo : siteLogo) : siteLogo}, footerLogoDark = ${separateFooterLogo ? (not #strings.isEmpty(theme.config.footer?.footer_logo_dark) ? theme.config.footer?.footer_logo_dark : (not #strings.isEmpty(theme.config.footer?.footer_logo) ? theme.config.footer?.footer_logo : siteLogoDark)) : siteLogoDark}, showFooterName = ${theme.config.footer?.show_footer_name != false}"
>
<div class="footer-container">
<th:block
th:if="${not #lists.isEmpty(theme.config.footer?.footer_menus)}"
>
<div class="footer-menu-columns">
<th:block
th:each="menuName : ${theme.config.footer?.footer_menus}"
>
<th:block
th:with="footerMenu = ${not #strings.isEmpty(menuName) ? menuFinder.getByName(menuName) : null}"
>
<div
class="footer-menu-col"
th:if="${footerMenu != null and not #lists.isEmpty(footerMenu.menuItems)}"
>
<h4 th:text="${footerMenu.spec.displayName}"></h4>
<ul>
<li th:each="menuItem : ${footerMenu.menuItems}">
<a
th:href="@{${menuItem.status.href}}"
th:text="${menuItem.status.displayName}"
th:target="${menuItem.spec.target?.value}"
th:rel="${menuItem.spec.target?.value == '_blank'} ? 'noopener' : null"
></a>
</li>
</ul>
</div>
</th:block>
</th:block>
</div>
</th:block>
<div class="footer-bottom">
<a
class="footer-bottom-brand"
th:href="@{/}"
th:if="${not #strings.isEmpty(footerLogo) or showFooterName}"
>
<img
th:if="${not #strings.isEmpty(footerLogo)}"
th:src="${footerLogo}"
th:attr="data-logo-light=${footerLogo},data-logo-dark=${footerLogoDark}"
class="footer-bottom-logo"
alt="logo"
/>
<span
class="footer-bottom-name"
th:if="${showFooterName}"
th:text="${site.title}"
></span>
</a>
<span
class="footer-copyright"
th:if="${not #strings.isEmpty(theme.config.footer?.copyright)}"
th:text="${theme.config.footer?.copyright}"
></span>
<span
class="footer-beian"
th:if="${not #strings.isEmpty(theme.config.footer?.icp) or not #strings.isEmpty(theme.config.footer?.gongan)}"
>
<a
th:if="${not #strings.isEmpty(theme.config.footer?.icp)}"
th:href="${theme.config.footer?.icp_link ?: 'https://beian.miit.gov.cn/'}"
target="_blank"
rel="noopener"
th:text="${theme.config.footer?.icp}"
></a>
<a
class="footer-gongan"
th:if="${not #strings.isEmpty(theme.config.footer?.gongan)}"
th:href="${theme.config.footer?.gongan_link ?: 'https://beian.mps.gov.cn/#/query/webSearch'}"
target="_blank"
rel="noopener"
>
<img
th:src="@{/assets/img/gongan.svg}"
class="footer-gongan-icon"
alt=""
/>
<span th:text="${theme.config.footer?.gongan}"></span>
</a>
</span>
</div>
</div>
<halo:footer />
</footer>
<script th:inline="javascript">
window.themePluginPage = {
docs: /*[[${theme.config.docs}]]*/ {},
docsme: /*[[${theme.config.docsme}]]*/ {},
minidocs: /*[[${theme.config.minidocs}]]*/ {},
};
</script>
</body>
</html>
+476
View File
@@ -0,0 +1,476 @@
<!doctype html>
<html
lang="zh-CN"
xmlns:th="https://www.thymeleaf.org"
th:lang="${#locale.toLanguageTag}"
class="color-scheme-auto"
data-color-scheme="auto"
>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title th:text="|${category.spec.displayName} - ${site.title}|">分类</title>
<script type="module" crossorigin src="/themes/theme-mdocs/assets/main-BAX9Eehy.js"></script>
<link rel="stylesheet" crossorigin href="/themes/theme-mdocs/assets/main-OuYKDL0n.css">
</head>
<body
th:style="'--radius:' + (${theme.config.global?.border_radius} ?: '12px') + ';--font-size:' + (${theme.config.global?.font_size} ?: '16px')"
th:attr="data-bg-light=${theme.config.global?.bg_color_light ?: '#ffffff'},data-bg-dark=${theme.config.global?.bg_color_dark ?: '#0d1117'},data-bg-image=${theme.config.global?.background_image},data-primary=${theme.config.global?.primary_color}"
th:with="siteLogo = ${not #strings.isEmpty(theme.config.global?.site_logo) ? theme.config.global?.site_logo : site.logo}, siteLogoDark = ${not #strings.isEmpty(theme.config.global?.site_logo_dark) ? theme.config.global?.site_logo_dark : siteLogo}, showSiteName = ${theme.config.global?.show_site_name != false}"
>
<header class="site-header">
<div
class="container header-inner"
th:with="primaryMenu = ${menuFinder.getPrimary()}"
>
<a class="site-brand" th:href="@{/}">
<img
th:if="${not #strings.isEmpty(siteLogo)}"
th:src="${siteLogo}"
th:attr="data-logo-light=${siteLogo},data-logo-dark=${siteLogoDark}"
class="brand-logo"
alt="logo"
/>
<span class="brand-name" th:if="${showSiteName}" th:text="${site.title}">源蝶工作室</span>
</a>
<nav class="primary-nav" th:if="${not #lists.isEmpty(theme.config.nav?.doc_nav_buttons)}">
<a
class="primary-nav-link"
th:each="btn : ${theme.config.nav?.doc_nav_buttons ?: T(java.util.Collections).emptyList()}"
th:href="@{${btn.url ?: '/'}}"
th:text="${btn.title}"
></a>
</nav>
<div
class="header-right"
>
<button
class="search-trigger"
id="search-trigger"
type="button"
th:if="${pluginFinder.available('PluginSearchWidget')}"
onclick="SearchWidget.open()"
aria-label="搜索"
>
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
<span class="search-trigger-text">搜索</span>
<kbd class="search-trigger-kbd">⌘K</kbd>
</button>
<button
class="mobile-menu-toggle"
id="mobile-menu-toggle"
type="button"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
aria-label="打开菜单"
aria-controls="header-menu-panel"
aria-expanded="false"
>
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</button>
<div
class="header-menu-wrap"
id="header-menu-panel"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
>
<th:block th:replace="~{header-menu :: menu}" />
</div>
<div
class="header-divider"
aria-hidden="true"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
></div>
<div class="header-actions">
<th:block
th:if="${not #lists.isEmpty(theme.config.nav?.user_buttons)}"
th:each="btn : ${theme.config.nav?.user_buttons ?: T(java.util.Collections).emptyList()}"
>
<a
class="header-action-btn header-icon-btn header-action-icon-btn"
th:if="${not #strings.isEmpty(btn.svg)}"
th:href="@{${btn.url ?: '#'}}"
th:title="${btn.label}"
th:style="${not #strings.isEmpty(btn.color)} ? '--icon-color:' + btn.color : ''"
>
<span class="header-action-icon" th:utext="${btn.svg}"></span>
</a>
</th:block>
<button
class="theme-toggle header-icon-btn"
id="theme-toggle"
aria-label="切换明暗主题"
>
<svg class="icon-sun" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
<svg class="icon-moon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
</svg>
</button>
<th:block th:replace="~{user-menu :: user-menu}" />
</div>
</div>
<div
class="mobile-menu-backdrop"
id="mobile-menu-backdrop"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
></div>
</div>
</header>
<main class="site-main">
<th:block
th:with="breadcrumbs = ${categoryFinder.getBreadcrumbs(category.metadata.name)},
docGroup = ${#lists.isEmpty(breadcrumbs) ? null : breadcrumbs[0]},
docEnabled = ${not #lists.isEmpty(theme.config.docs?.doc_groups) and docGroup != null and #lists.contains(theme.config.docs?.doc_groups, docGroup.metadata.name)}"
>
<th:block th:if="${docEnabled}">
<button class="sidebar-toggle" id="sidebar-toggle" aria-label="切换目录">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
<span>目录</span>
</button>
<div class="sidebar-backdrop" id="sidebar-backdrop"></div>
<div
class="doc-layout"
th:with="breadcrumbs = ${categoryFinder.getBreadcrumbs(category.metadata.name)}, docGroup = ${#lists.isEmpty(breadcrumbs) ? null : breadcrumbs[0]}"
th:attr="data-current-group=${docGroup != null ? docGroup.metadata.name : ''}, data-default-expanded=${theme.config.docs.sidebar_default_expanded}"
>
<aside class="doc-sidebar">
<div
class="doc-sidebar-header"
th:if="${not (theme.config.docs?.doc_groups != null and not #lists.isEmpty(theme.config.docs?.doc_groups) and docGroup != null and #lists.contains(theme.config.docs?.doc_groups, docGroup.metadata.name)) and theme.config.global?.show_site_name != false}"
th:text="${site.title}"
></div>
<div
class="mdocs-docsme-sidebar-controls minidocs-toolbar docgroup-toolbar"
th:if="${theme.config.docs?.doc_groups != null and not #lists.isEmpty(theme.config.docs?.doc_groups) and docGroup != null and #lists.contains(theme.config.docs?.doc_groups, docGroup.metadata.name)}"
>
<button
type="button"
class="mdocs-docsme-back"
onclick="history.back()"
>
<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="19" y1="12" x2="5" y2="12"></line>
<polyline points="12 19 5 12 12 5"></polyline>
</svg>
<span>返回</span>
</button>
<button
type="button"
class="minidocs-toolbar-btn"
onclick="navigator.clipboard?.writeText(window.location.href);this.textContent='已复制'"
>
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="19" r="3"></circle>
<line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line>
</svg>
<span>分享</span>
</button>
</div>
<div
class="minidocs-kb-heading"
th:if="${theme.config.docs?.doc_groups != null and not #lists.isEmpty(theme.config.docs?.doc_groups) and docGroup != null and #lists.contains(theme.config.docs?.doc_groups, docGroup.metadata.name)}"
>
<span class="minidocs-kb-cover">
<img
th:if="${not #strings.isEmpty(docGroup.spec.cover)}"
th:src="${docGroup.spec.cover}"
alt=""
/>
<span
class="minidocs-kb-cover-fallback"
th:unless="${not #strings.isEmpty(docGroup.spec.cover)}"
th:text="${not #strings.isEmpty(docGroup.spec.displayName) ? #strings.substring(docGroup.spec.displayName, 0, 1) : '文'}"
></span>
</span>
<div class="minidocs-kb-info">
<div class="doc-sidebar-kb-title" th:text="${docGroup.spec.displayName}"></div>
<div class="minidocs-kb-stats">
<span
th:if="${theme.config.docs?.sidebar_show_count != false}"
th:text="|共 ${docGroup.status != null and docGroup.status.postCount != null ? docGroup.status.postCount : 0} 篇文章|"
>共 0 篇文章</span>
</div>
</div>
</div>
<nav
class="doc-sidebar-nav"
>
<th:block
th:with="docTree = ${categoryFinder.listAsTree(docGroup.metadata.name)},
docChildren = ${#lists.isEmpty(docTree) ? null : docTree[0].children},
rootPosts = ${postFinder.listByCategory(1, 999, docGroup.metadata.name).items}"
>
<ul class="doc-nav-list">
<li
class="doc-nav-post"
th:each="rootPost : ${rootPosts}"
th:if="${#lists.contains(rootPost.spec.categories, docGroup.metadata.name)}"
>
<a
th:href="@{${rootPost.status.permalink}}"
th:text="${rootPost.spec.title}"
></a>
</li>
</ul>
<th:block
th:replace="~{sidebar :: sidebar(${docChildren}, ${category.metadata.name}, '')}"
/>
</th:block>
</nav>
</aside>
<div class="doc-content">
<div
class="doc-section category-overview"
th:with="categoryTree = ${categoryFinder.listAsTree(category.metadata.name)},
categoryNode = ${#lists.isEmpty(categoryTree) ? null : categoryTree[0]},
childNodes = ${categoryNode != null ? categoryNode.children : null},
categoryPosts = ${postFinder.listByCategory(1, 999, category.metadata.name).items}"
>
<div class="doc-section-header overview-header">
<h1 class="doc-section-title" th:text="${category.spec.displayName}"></h1>
</div>
<p class="doc-section-desc overview-desc" th:if="${not #strings.isEmpty(category.spec.description)}" th:text="${category.spec.description}"></p>
<div class="filter-bar" th:if="${theme.config.docs.show_filter}">
<input type="search" class="filter-input" placeholder="筛选本页内容" data-filter-target=".overview-groups" />
</div>
<div class="overview-groups">
<!-- 当前分类直属文章 -->
<section class="card-group overview-group overview-direct" th:if="${not #lists.isEmpty(categoryPosts)}">
<div class="overview-group-head">
<h2 class="card-group-title overview-group-title">
<a th:href="@{${category.status.permalink}}">本分类文章</a>
</h2>
</div>
<ul class="card-grid overview-grid">
<li
class="card-cell"
th:each="post : ${categoryPosts}"
th:if="${#lists.contains(post.spec.categories, category.metadata.name)}"
>
<a class="card-cell-link overview-card-link" th:href="@{${post.status.permalink}}">
<span class="overview-card-title" th:text="${post.spec.title}"></span>
<span
class="overview-card-desc"
th:if="${post.status != null and not #strings.isEmpty(post.status.excerpt)}"
th:text="${post.status.excerpt}"
></span>
</a>
</li>
</ul>
</section>
<!-- 直接子分类(按 Halo 分类树的子分类顺序) -->
<th:block th:if="${not #lists.isEmpty(childNodes)}">
<th:block th:each="child : ${childNodes}">
<section
class="card-group overview-group"
th:with="childPosts = ${postFinder.listByCategory(1, 999, child.metadata.name).items}"
>
<div class="overview-group-head">
<h2 class="card-group-title overview-group-title">
<a th:href="@{${child.status.permalink}}" th:text="${child.spec.displayName}"></a>
</h2>
<span
class="overview-group-count"
th:text="|共 ${#lists.size(childPosts)} 篇|"
>共 0 篇</span>
</div>
<ul class="card-grid overview-grid" th:if="${not #lists.isEmpty(childPosts)}">
<li class="card-cell" th:each="post : ${childPosts}">
<a class="card-cell-link overview-card-link" th:href="@{${post.status.permalink}}">
<span class="overview-card-title" th:text="${post.spec.title}"></span>
<span
class="overview-card-desc"
th:if="${post.status != null and not #strings.isEmpty(post.status.excerpt)}"
th:text="${post.status.excerpt}"
></span>
</a>
</li>
</ul>
<p class="overview-empty" th:unless="${not #lists.isEmpty(childPosts)}">该分类下暂无文章</p>
</section>
</th:block>
</th:block>
<p
class="doc-section-empty"
th:if="${#lists.isEmpty(categoryPosts) and #lists.isEmpty(childNodes)}"
>该分类下暂无文章</p>
</div>
<!-- 用于复制 Markdown 的数据 -->
<script type="application/json" id="page-md-data" th:text="${#strings.concat('#', category.spec.displayName, '\n\n', category.spec.description ?: '', '\n')}"></script>
</div>
</div>
</div>
</th:block>
<!-- 普通分类页面:非文档组分类 -->
<div class="container page-wrap category-page" th:unless="${docEnabled}">
<h1 class="page-title" th:text="${category.spec.displayName}"></h1>
<p class="doc-section-desc" th:if="${not #strings.isEmpty(category.spec.description)}" th:text="${category.spec.description}"></p>
<ul class="post-list category-post-list">
<li class="post-item" th:each="item : ${posts.items}">
<h2 class="post-title">
<a th:href="@{${item.status.permalink}}" th:text="${item.spec.title}"></a>
</h2>
<p class="post-meta">
<time th:text="${#temporals.format(item.spec.publishTime, 'yyyy-MM-dd')}"></time>
</p>
</li>
</ul>
<nav class="pagination" th:if="${posts.hasPrevious() or posts.hasNext()}">
<a th:if="${posts.hasPrevious()}" th:href="@{${posts.prevUrl}}">上一页</a>
<span th:text="|${posts.page} / ${posts.totalPages}|"></span>
<a th:if="${posts.hasNext()}" th:href="@{${posts.nextUrl}}">下一页</a>
</nav>
</div>
</th:block>
</main>
<footer
class="site-footer"
th:with="separateFooterLogo = ${theme.config.footer?.footer_separate_logo == true}, footerLogo = ${separateFooterLogo ? (not #strings.isEmpty(theme.config.footer?.footer_logo) ? theme.config.footer?.footer_logo : siteLogo) : siteLogo}, footerLogoDark = ${separateFooterLogo ? (not #strings.isEmpty(theme.config.footer?.footer_logo_dark) ? theme.config.footer?.footer_logo_dark : (not #strings.isEmpty(theme.config.footer?.footer_logo) ? theme.config.footer?.footer_logo : siteLogoDark)) : siteLogoDark}, showFooterName = ${theme.config.footer?.show_footer_name != false}"
>
<div class="footer-container">
<th:block
th:if="${not #lists.isEmpty(theme.config.footer?.footer_menus)}"
>
<div class="footer-menu-columns">
<th:block
th:each="menuName : ${theme.config.footer?.footer_menus}"
>
<th:block
th:with="footerMenu = ${not #strings.isEmpty(menuName) ? menuFinder.getByName(menuName) : null}"
>
<div
class="footer-menu-col"
th:if="${footerMenu != null and not #lists.isEmpty(footerMenu.menuItems)}"
>
<h4 th:text="${footerMenu.spec.displayName}"></h4>
<ul>
<li th:each="menuItem : ${footerMenu.menuItems}">
<a
th:href="@{${menuItem.status.href}}"
th:text="${menuItem.status.displayName}"
th:target="${menuItem.spec.target?.value}"
th:rel="${menuItem.spec.target?.value == '_blank'} ? 'noopener' : null"
></a>
</li>
</ul>
</div>
</th:block>
</th:block>
</div>
</th:block>
<div class="footer-bottom">
<a
class="footer-bottom-brand"
th:href="@{/}"
th:if="${not #strings.isEmpty(footerLogo) or showFooterName}"
>
<img
th:if="${not #strings.isEmpty(footerLogo)}"
th:src="${footerLogo}"
th:attr="data-logo-light=${footerLogo},data-logo-dark=${footerLogoDark}"
class="footer-bottom-logo"
alt="logo"
/>
<span
class="footer-bottom-name"
th:if="${showFooterName}"
th:text="${site.title}"
></span>
</a>
<span
class="footer-copyright"
th:if="${not #strings.isEmpty(theme.config.footer?.copyright)}"
th:text="${theme.config.footer?.copyright}"
></span>
<span
class="footer-beian"
th:if="${not #strings.isEmpty(theme.config.footer?.icp) or not #strings.isEmpty(theme.config.footer?.gongan)}"
>
<a
th:if="${not #strings.isEmpty(theme.config.footer?.icp)}"
th:href="${theme.config.footer?.icp_link ?: 'https://beian.miit.gov.cn/'}"
target="_blank"
rel="noopener"
th:text="${theme.config.footer?.icp}"
></a>
<a
class="footer-gongan"
th:if="${not #strings.isEmpty(theme.config.footer?.gongan)}"
th:href="${theme.config.footer?.gongan_link ?: 'https://beian.mps.gov.cn/#/query/webSearch'}"
target="_blank"
rel="noopener"
>
<img
th:src="@{/assets/img/gongan.svg}"
class="footer-gongan-icon"
alt=""
/>
<span th:text="${theme.config.footer?.gongan}"></span>
</a>
</span>
</div>
</div>
<halo:footer />
</footer>
<script th:inline="javascript">
window.themePluginPage = {
docs: /*[[${theme.config.docs}]]*/ {},
docsme: /*[[${theme.config.docsme}]]*/ {},
minidocs: /*[[${theme.config.minidocs}]]*/ {},
};
</script>
</body>
</html>
+384
View File
@@ -0,0 +1,384 @@
<!doctype html>
<html
lang="zh-CN"
xmlns:th="https://www.thymeleaf.org"
th:lang="${#locale.toLanguageTag}"
class="color-scheme-auto"
data-color-scheme="auto"
>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title th:text="|${project.spec.displayName} - ${site.title}|">文档项目</title>
<th:block th:if="${pluginFinder.available('plugin-docsme')}">
<link rel="stylesheet" th:href="@{/plugins/plugin-docsme/assets/static/dist/main.css?v={version}(version=${plugin.version})}" />
<script th:src="@{/plugins/plugin-docsme/assets/static/dist/main.iife.js?v={version}(version=${plugin.version})}"></script>
<th:block th:replace="~{plugin:plugin-docsme:modules/plugin-scripts}" />
</th:block>
<script type="module" crossorigin src="/themes/theme-mdocs/assets/main-BAX9Eehy.js"></script>
<link rel="stylesheet" crossorigin href="/themes/theme-mdocs/assets/main-OuYKDL0n.css">
</head>
<body
th:style="'--radius:' + (${theme.config.global?.border_radius} ?: '12px') + ';--font-size:' + (${theme.config.global?.font_size} ?: '16px')"
th:attr="data-bg-light=${theme.config.global?.bg_color_light ?: '#ffffff'},data-bg-dark=${theme.config.global?.bg_color_dark ?: '#0d1117'},data-bg-image=${theme.config.global?.background_image},data-primary=${theme.config.global?.primary_color}"
th:with="siteLogo = ${not #strings.isEmpty(theme.config.global?.site_logo) ? theme.config.global?.site_logo : site.logo}, siteLogoDark = ${not #strings.isEmpty(theme.config.global?.site_logo_dark) ? theme.config.global?.site_logo_dark : siteLogo}, showSiteName = ${theme.config.global?.show_site_name != false}"
>
<header class="site-header">
<div
class="container header-inner"
th:with="primaryMenu = ${menuFinder.getPrimary()}"
>
<a class="site-brand" th:href="@{/}">
<img
th:if="${not #strings.isEmpty(siteLogo)}"
th:src="${siteLogo}"
th:attr="data-logo-light=${siteLogo},data-logo-dark=${siteLogoDark}"
class="brand-logo"
alt="logo"
/>
<span class="brand-name" th:if="${showSiteName}" th:text="${site.title}">源蝶工作室</span>
</a>
<nav class="primary-nav" th:if="${not #lists.isEmpty(theme.config.nav?.doc_nav_buttons)}">
<a
class="primary-nav-link"
th:each="btn : ${theme.config.nav?.doc_nav_buttons ?: T(java.util.Collections).emptyList()}"
th:href="@{${btn.url ?: '/'}}"
th:text="${btn.title}"
></a>
</nav>
<div
class="header-right"
>
<button
class="search-trigger"
id="search-trigger"
type="button"
th:if="${pluginFinder.available('PluginSearchWidget')}"
onclick="SearchWidget.open()"
aria-label="搜索"
>
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
<span class="search-trigger-text">搜索</span>
<kbd class="search-trigger-kbd">⌘K</kbd>
</button>
<button
class="mobile-menu-toggle"
id="mobile-menu-toggle"
type="button"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
aria-label="打开菜单"
aria-controls="header-menu-panel"
aria-expanded="false"
>
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</button>
<div
class="header-menu-wrap"
id="header-menu-panel"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
>
<th:block th:replace="~{header-menu :: menu}" />
</div>
<div
class="header-divider"
aria-hidden="true"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
></div>
<div class="header-actions">
<th:block
th:if="${not #lists.isEmpty(theme.config.nav?.user_buttons)}"
th:each="btn : ${theme.config.nav?.user_buttons ?: T(java.util.Collections).emptyList()}"
>
<a
class="header-action-btn header-icon-btn header-action-icon-btn"
th:if="${not #strings.isEmpty(btn.svg)}"
th:href="@{${btn.url ?: '#'}}"
th:title="${btn.label}"
th:style="${not #strings.isEmpty(btn.color)} ? '--icon-color:' + btn.color : ''"
>
<span class="header-action-icon" th:utext="${btn.svg}"></span>
</a>
</th:block>
<button
class="theme-toggle header-icon-btn"
id="theme-toggle"
aria-label="切换明暗主题"
>
<svg class="icon-sun" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
<svg class="icon-moon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
</svg>
</button>
<th:block th:replace="~{user-menu :: user-menu}" />
</div>
</div>
<div
class="mobile-menu-backdrop"
id="mobile-menu-backdrop"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
></div>
</div>
</header>
<main class="site-main">
<!-- Docsme:项目目录 / 目录页 -->
<div class="dm-container mdocs-docsme-page" th:if="${pluginFinder.available('plugin-docsme')}">
<div class="dm-layout">
<aside class="dm-sidebar" dm-data dm-bind:class="{'dm-sidebar--show' : $store.sidebar.open}">
<div class="dm-sidebar__content">
<div class="mdocs-docsme-sidebar-controls">
<button
class="mdocs-docsme-back"
type="button"
onclick="history.back()"
aria-label="返回上一页"
>
<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="19" y1="12" x2="5" y2="12"></line>
<polyline points="12 19 5 12 12 5"></polyline>
</svg>
<span>返回</span>
</button>
<div class="mdocs-docsme-version">
<span class="mdocs-docsme-version-label">版本</span>
<div
class="mdocs-docsme-version-switch"
th:if="${versions != null and #lists.size(versions) gt 1}"
>
<button
class="mdocs-docsme-version-btn"
type="button"
data-mdocs-version-trigger
>
<span th:text="${currentVersion.spec.slug}">default</span>
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</button>
<div class="mdocs-docsme-version-panel" data-mdocs-version-panel hidden>
<a
th:each="version : ${versions}"
th:href="@{${#strings.replace(docTree.status.permalink, currentVersion.status.permalink, version.status.permalink)}}"
th:text="${version.spec.slug}"
>default</a>
</div>
</div>
<span
class="mdocs-docsme-version-current"
th:unless="${versions != null and #lists.size(versions) gt 1}"
th:text="${currentVersion.spec.slug}"
>default</span>
</div>
</div>
<div class="minidocs-kb-heading mdocs-docsme-heading" th:if="${project != null}">
<span class="minidocs-kb-cover">
<img
th:if="${not #strings.isEmpty(project.spec.icon)}"
th:src="${project.spec.icon}"
alt=""
/>
<span
class="minidocs-kb-cover-fallback"
th:unless="${not #strings.isEmpty(project.spec.icon)}"
th:text="${not #strings.isEmpty(project.spec.displayName) ? #strings.substring(project.spec.displayName, 0, 1) : '档'}"
></span>
</span>
<div class="minidocs-kb-info">
<a
class="doc-sidebar-kb-title"
th:href="${project.status.permalink}"
th:text="${project.spec.displayName}"
></a>
<div class="minidocs-kb-stats">
<span
th:if="${theme.config.docsme?.nav_show_count != false}"
th:text="|共 ${project.status.totalDocs} 篇文档|"
>共 0 篇文档</span>
</div>
</div>
</div>
<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>
<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' ? '📁' : '📝'}"
></div>
<div class="dm-directory-card__content">
<h3 class="dm-directory-card__title" th:text="${node.spec.title}"></h3>
<p
th:if="${!#strings.isEmpty(node.spec.description)}"
class="dm-directory-card__description"
th:text="${node.spec.description}"
></p>
</div>
</a>
</div>
</article>
<th:block th:replace="~{plugin:plugin-docsme:modules/navigation}" />
</div>
</main>
</div>
</div>
<section class="section" th:unless="${pluginFinder.available('plugin-docsme')}">
<div class="container">
<p class="mdocs-plugin-empty">Docsme 插件不可用,无法渲染此页面。</p>
</div>
</section>
</main>
<footer
class="site-footer"
th:with="separateFooterLogo = ${theme.config.footer?.footer_separate_logo == true}, footerLogo = ${separateFooterLogo ? (not #strings.isEmpty(theme.config.footer?.footer_logo) ? theme.config.footer?.footer_logo : siteLogo) : siteLogo}, footerLogoDark = ${separateFooterLogo ? (not #strings.isEmpty(theme.config.footer?.footer_logo_dark) ? theme.config.footer?.footer_logo_dark : (not #strings.isEmpty(theme.config.footer?.footer_logo) ? theme.config.footer?.footer_logo : siteLogoDark)) : siteLogoDark}, showFooterName = ${theme.config.footer?.show_footer_name != false}"
>
<div class="footer-container">
<th:block
th:if="${not #lists.isEmpty(theme.config.footer?.footer_menus)}"
>
<div class="footer-menu-columns">
<th:block
th:each="menuName : ${theme.config.footer?.footer_menus}"
>
<th:block
th:with="footerMenu = ${not #strings.isEmpty(menuName) ? menuFinder.getByName(menuName) : null}"
>
<div
class="footer-menu-col"
th:if="${footerMenu != null and not #lists.isEmpty(footerMenu.menuItems)}"
>
<h4 th:text="${footerMenu.spec.displayName}"></h4>
<ul>
<li th:each="menuItem : ${footerMenu.menuItems}">
<a
th:href="@{${menuItem.status.href}}"
th:text="${menuItem.status.displayName}"
th:target="${menuItem.spec.target?.value}"
th:rel="${menuItem.spec.target?.value == '_blank'} ? 'noopener' : null"
></a>
</li>
</ul>
</div>
</th:block>
</th:block>
</div>
</th:block>
<div class="footer-bottom">
<a
class="footer-bottom-brand"
th:href="@{/}"
th:if="${not #strings.isEmpty(footerLogo) or showFooterName}"
>
<img
th:if="${not #strings.isEmpty(footerLogo)}"
th:src="${footerLogo}"
th:attr="data-logo-light=${footerLogo},data-logo-dark=${footerLogoDark}"
class="footer-bottom-logo"
alt="logo"
/>
<span
class="footer-bottom-name"
th:if="${showFooterName}"
th:text="${site.title}"
></span>
</a>
<span
class="footer-copyright"
th:if="${not #strings.isEmpty(theme.config.footer?.copyright)}"
th:text="${theme.config.footer?.copyright}"
></span>
<span
class="footer-beian"
th:if="${not #strings.isEmpty(theme.config.footer?.icp) or not #strings.isEmpty(theme.config.footer?.gongan)}"
>
<a
th:if="${not #strings.isEmpty(theme.config.footer?.icp)}"
th:href="${theme.config.footer?.icp_link ?: 'https://beian.miit.gov.cn/'}"
target="_blank"
rel="noopener"
th:text="${theme.config.footer?.icp}"
></a>
<a
class="footer-gongan"
th:if="${not #strings.isEmpty(theme.config.footer?.gongan)}"
th:href="${theme.config.footer?.gongan_link ?: 'https://beian.mps.gov.cn/#/query/webSearch'}"
target="_blank"
rel="noopener"
>
<img
th:src="@{/assets/img/gongan.svg}"
class="footer-gongan-icon"
alt=""
/>
<span th:text="${theme.config.footer?.gongan}"></span>
</a>
</span>
</div>
</div>
<halo:footer />
</footer>
<script th:inline="javascript">
window.themePluginPage = {
docs: /*[[${theme.config.docs}]]*/ {},
docsme: /*[[${theme.config.docsme}]]*/ {},
minidocs: /*[[${theme.config.minidocs}]]*/ {},
};
</script>
</body>
</html>
+402
View File
@@ -0,0 +1,402 @@
<!doctype html>
<html
lang="zh-CN"
xmlns:th="https://www.thymeleaf.org"
th:lang="${#locale.toLanguageTag}"
class="color-scheme-auto"
data-color-scheme="auto"
>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title th:text="|文档 - ${site.title}|">文档</title>
<th:block th:if="${pluginFinder.available('plugin-docsme') and docTree != null}">
<link rel="stylesheet" th:href="@{/plugins/plugin-docsme/assets/static/dist/main.css?v={version}(version=${plugin.version})}" />
<script th:src="@{/plugins/plugin-docsme/assets/static/dist/main.iife.js?v={version}(version=${plugin.version})}"></script>
<th:block th:replace="~{plugin:plugin-docsme:modules/plugin-scripts}" />
</th:block>
<script type="module" crossorigin src="/themes/theme-mdocs/assets/main-BAX9Eehy.js"></script>
<link rel="stylesheet" crossorigin href="/themes/theme-mdocs/assets/main-OuYKDL0n.css">
</head>
<body
th:style="'--radius:' + (${theme.config.global?.border_radius} ?: '12px') + ';--font-size:' + (${theme.config.global?.font_size} ?: '16px')"
th:attr="data-bg-light=${theme.config.global?.bg_color_light ?: '#ffffff'},data-bg-dark=${theme.config.global?.bg_color_dark ?: '#0d1117'},data-bg-image=${theme.config.global?.background_image},data-primary=${theme.config.global?.primary_color}"
th:with="siteLogo = ${not #strings.isEmpty(theme.config.global?.site_logo) ? theme.config.global?.site_logo : site.logo}, siteLogoDark = ${not #strings.isEmpty(theme.config.global?.site_logo_dark) ? theme.config.global?.site_logo_dark : siteLogo}, showSiteName = ${theme.config.global?.show_site_name != false}"
>
<header class="site-header">
<div
class="container header-inner"
th:with="primaryMenu = ${menuFinder.getPrimary()}"
>
<a class="site-brand" th:href="@{/}">
<img
th:if="${not #strings.isEmpty(siteLogo)}"
th:src="${siteLogo}"
th:attr="data-logo-light=${siteLogo},data-logo-dark=${siteLogoDark}"
class="brand-logo"
alt="logo"
/>
<span class="brand-name" th:if="${showSiteName}" th:text="${site.title}">源蝶工作室</span>
</a>
<nav class="primary-nav" th:if="${not #lists.isEmpty(theme.config.nav?.doc_nav_buttons)}">
<a
class="primary-nav-link"
th:each="btn : ${theme.config.nav?.doc_nav_buttons ?: T(java.util.Collections).emptyList()}"
th:href="@{${btn.url ?: '/'}}"
th:text="${btn.title}"
></a>
</nav>
<div
class="header-right"
>
<button
class="search-trigger"
id="search-trigger"
type="button"
th:if="${pluginFinder.available('PluginSearchWidget')}"
onclick="SearchWidget.open()"
aria-label="搜索"
>
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
<span class="search-trigger-text">搜索</span>
<kbd class="search-trigger-kbd">⌘K</kbd>
</button>
<button
class="mobile-menu-toggle"
id="mobile-menu-toggle"
type="button"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
aria-label="打开菜单"
aria-controls="header-menu-panel"
aria-expanded="false"
>
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</button>
<div
class="header-menu-wrap"
id="header-menu-panel"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
>
<th:block th:replace="~{header-menu :: menu}" />
</div>
<div
class="header-divider"
aria-hidden="true"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
></div>
<div class="header-actions">
<th:block
th:if="${not #lists.isEmpty(theme.config.nav?.user_buttons)}"
th:each="btn : ${theme.config.nav?.user_buttons ?: T(java.util.Collections).emptyList()}"
>
<a
class="header-action-btn header-icon-btn header-action-icon-btn"
th:if="${not #strings.isEmpty(btn.svg)}"
th:href="@{${btn.url ?: '#'}}"
th:title="${btn.label}"
th:style="${not #strings.isEmpty(btn.color)} ? '--icon-color:' + btn.color : ''"
>
<span class="header-action-icon" th:utext="${btn.svg}"></span>
</a>
</th:block>
<button
class="theme-toggle header-icon-btn"
id="theme-toggle"
aria-label="切换明暗主题"
>
<svg class="icon-sun" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
<svg class="icon-moon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
</svg>
</button>
<th:block th:replace="~{user-menu :: user-menu}" />
</div>
</div>
<div
class="mobile-menu-backdrop"
id="mobile-menu-backdrop"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
></div>
</div>
</header>
<main class="site-main">
<!-- ================= Docsme:文档详情页 ================= -->
<div
class="dm-container mdocs-docsme-page"
th:if="${pluginFinder.available('plugin-docsme') and docTree != null}"
>
<div class="dm-layout">
<aside class="dm-sidebar" dm-data dm-bind:class="{'dm-sidebar--show' : $store.sidebar.open}">
<div class="dm-sidebar__content">
<div class="mdocs-docsme-sidebar-controls">
<button
class="mdocs-docsme-back"
type="button"
onclick="history.back()"
aria-label="返回上一页"
>
<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="19" y1="12" x2="5" y2="12"></line>
<polyline points="12 19 5 12 12 5"></polyline>
</svg>
<span>返回</span>
</button>
<div class="mdocs-docsme-version">
<span class="mdocs-docsme-version-label">版本</span>
<div
class="mdocs-docsme-version-switch"
th:if="${versions != null and #lists.size(versions) gt 1}"
>
<button
class="mdocs-docsme-version-btn"
type="button"
data-mdocs-version-trigger
>
<span th:text="${currentVersion.spec.slug}">default</span>
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</button>
<div class="mdocs-docsme-version-panel" data-mdocs-version-panel hidden>
<a
th:each="version : ${versions}"
th:href="@{${#strings.replace(docTree.status.permalink, currentVersion.status.permalink, version.status.permalink)}}"
th:text="${version.spec.slug}"
>default</a>
</div>
</div>
<span
class="mdocs-docsme-version-current"
th:unless="${versions != null and #lists.size(versions) gt 1}"
th:text="${currentVersion.spec.slug}"
>default</span>
</div>
</div>
<div class="minidocs-kb-heading mdocs-docsme-heading" th:if="${project != null}">
<span class="minidocs-kb-cover">
<img
th:if="${not #strings.isEmpty(project.spec.icon)}"
th:src="${project.spec.icon}"
alt=""
/>
<span
class="minidocs-kb-cover-fallback"
th:unless="${not #strings.isEmpty(project.spec.icon)}"
th:text="${not #strings.isEmpty(project.spec.displayName) ? #strings.substring(project.spec.displayName, 0, 1) : '档'}"
></span>
</span>
<div class="minidocs-kb-info">
<a
class="doc-sidebar-kb-title"
th:href="${project.status.permalink}"
th:text="${project.spec.displayName}"
></a>
<div class="minidocs-kb-stats">
<span
th:if="${theme.config.docsme?.nav_show_count != false}"
th:text="|共 ${project.status.totalDocs} 篇文档|"
>共 0 篇文档</span>
</div>
</div>
</div>
<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>
<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"
th:utext="${docInfo.content.content}"
></article>
<div id="docsme-raw-markdown" hidden th:text="${docInfo.content.raw}"></div>
<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}" />
</div>
<aside
class="dm-toc"
data-toc-container
dm-data
dm-bind:class="{'dm-toc--show' : $store.toc.open}"
th:if="${theme.config.docsme?.toc_enabled != false}"
>
<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"
th:if="${theme.config.docsme?.toc_enabled != false}"
></div>
</main>
</div>
</div>
<!-- ================= MiniDocs:知识库阅读页 ================= -->
<th:block
th:if="${pluginFinder.available('halo-plugin-minidocs') and docTree == null and kbSlug != null}"
>
<th:block
th:replace="~{minidocs-reader :: minidocsReader(kbSlug=${kbSlug}, activeDocSlug=${docSlug}, viewBaseUrl=${'/docs/view/' + kbSlug})}"
/>
</th:block>
</main>
<footer
class="site-footer"
th:with="separateFooterLogo = ${theme.config.footer?.footer_separate_logo == true}, footerLogo = ${separateFooterLogo ? (not #strings.isEmpty(theme.config.footer?.footer_logo) ? theme.config.footer?.footer_logo : siteLogo) : siteLogo}, footerLogoDark = ${separateFooterLogo ? (not #strings.isEmpty(theme.config.footer?.footer_logo_dark) ? theme.config.footer?.footer_logo_dark : (not #strings.isEmpty(theme.config.footer?.footer_logo) ? theme.config.footer?.footer_logo : siteLogoDark)) : siteLogoDark}, showFooterName = ${theme.config.footer?.show_footer_name != false}"
>
<div class="footer-container">
<th:block
th:if="${not #lists.isEmpty(theme.config.footer?.footer_menus)}"
>
<div class="footer-menu-columns">
<th:block
th:each="menuName : ${theme.config.footer?.footer_menus}"
>
<th:block
th:with="footerMenu = ${not #strings.isEmpty(menuName) ? menuFinder.getByName(menuName) : null}"
>
<div
class="footer-menu-col"
th:if="${footerMenu != null and not #lists.isEmpty(footerMenu.menuItems)}"
>
<h4 th:text="${footerMenu.spec.displayName}"></h4>
<ul>
<li th:each="menuItem : ${footerMenu.menuItems}">
<a
th:href="@{${menuItem.status.href}}"
th:text="${menuItem.status.displayName}"
th:target="${menuItem.spec.target?.value}"
th:rel="${menuItem.spec.target?.value == '_blank'} ? 'noopener' : null"
></a>
</li>
</ul>
</div>
</th:block>
</th:block>
</div>
</th:block>
<div class="footer-bottom">
<a
class="footer-bottom-brand"
th:href="@{/}"
th:if="${not #strings.isEmpty(footerLogo) or showFooterName}"
>
<img
th:if="${not #strings.isEmpty(footerLogo)}"
th:src="${footerLogo}"
th:attr="data-logo-light=${footerLogo},data-logo-dark=${footerLogoDark}"
class="footer-bottom-logo"
alt="logo"
/>
<span
class="footer-bottom-name"
th:if="${showFooterName}"
th:text="${site.title}"
></span>
</a>
<span
class="footer-copyright"
th:if="${not #strings.isEmpty(theme.config.footer?.copyright)}"
th:text="${theme.config.footer?.copyright}"
></span>
<span
class="footer-beian"
th:if="${not #strings.isEmpty(theme.config.footer?.icp) or not #strings.isEmpty(theme.config.footer?.gongan)}"
>
<a
th:if="${not #strings.isEmpty(theme.config.footer?.icp)}"
th:href="${theme.config.footer?.icp_link ?: 'https://beian.miit.gov.cn/'}"
target="_blank"
rel="noopener"
th:text="${theme.config.footer?.icp}"
></a>
<a
class="footer-gongan"
th:if="${not #strings.isEmpty(theme.config.footer?.gongan)}"
th:href="${theme.config.footer?.gongan_link ?: 'https://beian.mps.gov.cn/#/query/webSearch'}"
target="_blank"
rel="noopener"
>
<img
th:src="@{/assets/img/gongan.svg}"
class="footer-gongan-icon"
alt=""
/>
<span th:text="${theme.config.footer?.gongan}"></span>
</a>
</span>
</div>
</div>
<halo:footer />
</footer>
<script th:inline="javascript">
window.themePluginPage = {
docs: /*[[${theme.config.docs}]]*/ {},
docsme: /*[[${theme.config.docsme}]]*/ {},
minidocs: /*[[${theme.config.minidocs}]]*/ {},
};
</script>
</body>
</html>
+347
View File
@@ -0,0 +1,347 @@
<!doctype html>
<html
lang="zh-CN"
xmlns:th="https://www.thymeleaf.org"
th:lang="${#locale.toLanguageTag}"
class="color-scheme-auto"
data-color-scheme="auto"
>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title th:text="|文档中心 - ${site.title}|">文档中心</title>
<script type="module" crossorigin src="/themes/theme-mdocs/assets/main-BAX9Eehy.js"></script>
<link rel="stylesheet" crossorigin href="/themes/theme-mdocs/assets/main-OuYKDL0n.css">
</head>
<body
th:style="'--radius:' + (${theme.config.global?.border_radius} ?: '12px') + ';--font-size:' + (${theme.config.global?.font_size} ?: '16px')"
th:attr="data-bg-light=${theme.config.global?.bg_color_light ?: '#ffffff'},data-bg-dark=${theme.config.global?.bg_color_dark ?: '#0d1117'},data-bg-image=${theme.config.global?.background_image},data-primary=${theme.config.global?.primary_color}"
th:with="siteLogo = ${not #strings.isEmpty(theme.config.global?.site_logo) ? theme.config.global?.site_logo : site.logo}, siteLogoDark = ${not #strings.isEmpty(theme.config.global?.site_logo_dark) ? theme.config.global?.site_logo_dark : siteLogo}, showSiteName = ${theme.config.global?.show_site_name != false}"
>
<header class="site-header">
<div
class="container header-inner"
th:with="primaryMenu = ${menuFinder.getPrimary()}"
>
<a class="site-brand" th:href="@{/}">
<img
th:if="${not #strings.isEmpty(siteLogo)}"
th:src="${siteLogo}"
th:attr="data-logo-light=${siteLogo},data-logo-dark=${siteLogoDark}"
class="brand-logo"
alt="logo"
/>
<span class="brand-name" th:if="${showSiteName}" th:text="${site.title}">源蝶工作室</span>
</a>
<nav class="primary-nav" th:if="${not #lists.isEmpty(theme.config.nav?.doc_nav_buttons)}">
<a
class="primary-nav-link"
th:each="btn : ${theme.config.nav?.doc_nav_buttons ?: T(java.util.Collections).emptyList()}"
th:href="@{${btn.url ?: '/'}}"
th:text="${btn.title}"
></a>
</nav>
<div
class="header-right"
>
<button
class="search-trigger"
id="search-trigger"
type="button"
th:if="${pluginFinder.available('PluginSearchWidget')}"
onclick="SearchWidget.open()"
aria-label="搜索"
>
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
<span class="search-trigger-text">搜索</span>
<kbd class="search-trigger-kbd">⌘K</kbd>
</button>
<button
class="mobile-menu-toggle"
id="mobile-menu-toggle"
type="button"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
aria-label="打开菜单"
aria-controls="header-menu-panel"
aria-expanded="false"
>
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</button>
<div
class="header-menu-wrap"
id="header-menu-panel"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
>
<th:block th:replace="~{header-menu :: menu}" />
</div>
<div
class="header-divider"
aria-hidden="true"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
></div>
<div class="header-actions">
<th:block
th:if="${not #lists.isEmpty(theme.config.nav?.user_buttons)}"
th:each="btn : ${theme.config.nav?.user_buttons ?: T(java.util.Collections).emptyList()}"
>
<a
class="header-action-btn header-icon-btn header-action-icon-btn"
th:if="${not #strings.isEmpty(btn.svg)}"
th:href="@{${btn.url ?: '#'}}"
th:title="${btn.label}"
th:style="${not #strings.isEmpty(btn.color)} ? '--icon-color:' + btn.color : ''"
>
<span class="header-action-icon" th:utext="${btn.svg}"></span>
</a>
</th:block>
<button
class="theme-toggle header-icon-btn"
id="theme-toggle"
aria-label="切换明暗主题"
>
<svg class="icon-sun" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
<svg class="icon-moon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
</svg>
</button>
<th:block th:replace="~{user-menu :: user-menu}" />
</div>
</div>
<div
class="mobile-menu-backdrop"
id="mobile-menu-backdrop"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
></div>
</div>
</header>
<main class="site-main">
<!-- Docsme/docs 项目列表页 -->
<th:block
th:if="${pluginFinder.available('plugin-docsme') and projects != null and theme.config.docsme?.plugin_enabled != false}"
>
<div class="mdocs-plugin-list mdocs-docsme-list">
<div class="container">
<header class="mdocs-plugin-list-head">
<h1 th:text="${not #strings.isEmpty(theme.config.docsme?.archive_title) ? theme.config.docsme?.archive_title : '文档'}">文档</h1>
<p th:text="${not #strings.isEmpty(theme.config.docsme?.archive_description) ? theme.config.docsme?.archive_description : '所有 Docsme 文档项目'}">所有 Docsme 文档项目</p>
</header>
<div
class="mdocs-plugin-card-grid"
th:if="${not #lists.isEmpty(projects)}"
>
<a
class="mdocs-plugin-card mdocs-project-card"
th:each="project : ${projects}"
th:href="${project.status.permalink}"
>
<span
class="mdocs-plugin-card-banner"
th:if="${not #strings.isEmpty(project.spec.icon)}"
>
<img th:src="${project.spec.icon}" th:alt="${project.spec.displayName}" />
</span>
<span class="mdocs-plugin-card-body">
<span class="mdocs-plugin-card-title" th:text="${project.spec.displayName}"></span>
<span class="mdocs-plugin-card-meta">
<span th:text="|共 ${project.status.totalDocs ?: 0} 篇文档|">共 0 篇文档</span>
<span class="mdocs-plugin-card-link" th:text="${project.status.permalink}"></span>
</span>
<span class="mdocs-plugin-card-action">访问项目 →</span>
</span>
</a>
</div>
<p class="mdocs-plugin-empty" th:if="${#lists.isEmpty(projects)}">
暂无 Docsme 文档项目。
</p>
</div>
</div>
</th:block>
<section class="section" th:if="${projects != null and theme.config.docsme?.plugin_enabled == false}">
<div class="container">
<p class="mdocs-plugin-empty">Docsme 适配页面已关闭,请在主题设置中开启后再访问。</p>
</div>
</section>
<!-- MiniDocs/docs 知识库列表页 -->
<th:block th:if="${pluginFinder.available('halo-plugin-minidocs') and projects == null}">
<div class="mdocs-plugin-list mdocs-minidocs-list">
<div class="container" th:with="kbResult = ${minidocsFinder.listKnowledgeBases(1, 1000)}">
<header class="mdocs-plugin-list-head">
<h1>知识库</h1>
<p>全部 MiniDocs 知识库</p>
</header>
<div class="mdocs-plugin-card-grid">
<a
class="mdocs-plugin-card mdocs-kb-card"
th:each="kb : ${kbResult.items}"
th:href="@{'/docs/view/' + ${not #strings.isEmpty(kb.spec.slug) ? kb.spec.slug : kb.metadata.name}}"
>
<span class="mdocs-plugin-card-banner" th:if="${not #strings.isEmpty(kb.spec.cover)}">
<img th:src="${kb.spec.cover}" th:alt="${kb.spec.displayName}" />
</span>
<span
class="mdocs-plugin-card-banner mdocs-plugin-card-fallback"
th:unless="${not #strings.isEmpty(kb.spec.cover)}"
>
<span
th:text="${not #strings.isEmpty(kb.spec.displayName) ? #strings.substring(kb.spec.displayName, 0, 1) : '库'}"
></span>
</span>
<span class="mdocs-plugin-card-body">
<span class="mdocs-plugin-card-title" th:text="${kb.spec.displayName}"></span>
<span
class="mdocs-plugin-card-desc"
th:if="${not #strings.isEmpty(kb.spec.description)}"
th:text="${kb.spec.description}"
></span>
<span class="mdocs-plugin-card-meta">
<span th:text="|共 ${kb.status != null and kb.status.docCount != null ? kb.status.docCount : 0} 篇文档|">共 0 篇文档</span>
</span>
</span>
</a>
</div>
<p class="mdocs-plugin-empty" th:if="${#lists.isEmpty(kbResult.items)}">
暂无 MiniDocs 知识库。
</p>
</div>
</div>
</th:block>
</main>
<footer
class="site-footer"
th:with="separateFooterLogo = ${theme.config.footer?.footer_separate_logo == true}, footerLogo = ${separateFooterLogo ? (not #strings.isEmpty(theme.config.footer?.footer_logo) ? theme.config.footer?.footer_logo : siteLogo) : siteLogo}, footerLogoDark = ${separateFooterLogo ? (not #strings.isEmpty(theme.config.footer?.footer_logo_dark) ? theme.config.footer?.footer_logo_dark : (not #strings.isEmpty(theme.config.footer?.footer_logo) ? theme.config.footer?.footer_logo : siteLogoDark)) : siteLogoDark}, showFooterName = ${theme.config.footer?.show_footer_name != false}"
>
<div class="footer-container">
<th:block
th:if="${not #lists.isEmpty(theme.config.footer?.footer_menus)}"
>
<div class="footer-menu-columns">
<th:block
th:each="menuName : ${theme.config.footer?.footer_menus}"
>
<th:block
th:with="footerMenu = ${not #strings.isEmpty(menuName) ? menuFinder.getByName(menuName) : null}"
>
<div
class="footer-menu-col"
th:if="${footerMenu != null and not #lists.isEmpty(footerMenu.menuItems)}"
>
<h4 th:text="${footerMenu.spec.displayName}"></h4>
<ul>
<li th:each="menuItem : ${footerMenu.menuItems}">
<a
th:href="@{${menuItem.status.href}}"
th:text="${menuItem.status.displayName}"
th:target="${menuItem.spec.target?.value}"
th:rel="${menuItem.spec.target?.value == '_blank'} ? 'noopener' : null"
></a>
</li>
</ul>
</div>
</th:block>
</th:block>
</div>
</th:block>
<div class="footer-bottom">
<a
class="footer-bottom-brand"
th:href="@{/}"
th:if="${not #strings.isEmpty(footerLogo) or showFooterName}"
>
<img
th:if="${not #strings.isEmpty(footerLogo)}"
th:src="${footerLogo}"
th:attr="data-logo-light=${footerLogo},data-logo-dark=${footerLogoDark}"
class="footer-bottom-logo"
alt="logo"
/>
<span
class="footer-bottom-name"
th:if="${showFooterName}"
th:text="${site.title}"
></span>
</a>
<span
class="footer-copyright"
th:if="${not #strings.isEmpty(theme.config.footer?.copyright)}"
th:text="${theme.config.footer?.copyright}"
></span>
<span
class="footer-beian"
th:if="${not #strings.isEmpty(theme.config.footer?.icp) or not #strings.isEmpty(theme.config.footer?.gongan)}"
>
<a
th:if="${not #strings.isEmpty(theme.config.footer?.icp)}"
th:href="${theme.config.footer?.icp_link ?: 'https://beian.miit.gov.cn/'}"
target="_blank"
rel="noopener"
th:text="${theme.config.footer?.icp}"
></a>
<a
class="footer-gongan"
th:if="${not #strings.isEmpty(theme.config.footer?.gongan)}"
th:href="${theme.config.footer?.gongan_link ?: 'https://beian.mps.gov.cn/#/query/webSearch'}"
target="_blank"
rel="noopener"
>
<img
th:src="@{/assets/img/gongan.svg}"
class="footer-gongan-icon"
alt=""
/>
<span th:text="${theme.config.footer?.gongan}"></span>
</a>
</span>
</div>
</div>
<halo:footer />
</footer>
<script th:inline="javascript">
window.themePluginPage = {
docs: /*[[${theme.config.docs}]]*/ {},
docsme: /*[[${theme.config.docsme}]]*/ {},
minidocs: /*[[${theme.config.minidocs}]]*/ {},
};
</script>
</body>
</html>
+321
View File
@@ -0,0 +1,321 @@
<!doctype html>
<html
lang="zh-CN"
xmlns:th="https://www.thymeleaf.org"
th:lang="${#locale.toLanguageTag}"
class="color-scheme-auto"
data-color-scheme="auto"
>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title
th:text="|${not #strings.isEmpty(singlePage.spec.title) ? singlePage.spec.title : 'Docsme 文档'} - ${site.title}|"
>Docsme 文档</title>
<script type="module" crossorigin src="/themes/theme-mdocs/assets/main-BAX9Eehy.js"></script>
<link rel="stylesheet" crossorigin href="/themes/theme-mdocs/assets/main-OuYKDL0n.css">
</head>
<body
th:style="'--radius:' + (${theme.config.global?.border_radius} ?: '12px') + ';--font-size:' + (${theme.config.global?.font_size} ?: '16px')"
th:attr="data-bg-light=${theme.config.global?.bg_color_light ?: '#ffffff'},data-bg-dark=${theme.config.global?.bg_color_dark ?: '#0d1117'},data-bg-image=${theme.config.global?.background_image},data-primary=${theme.config.global?.primary_color}"
th:with="siteLogo = ${not #strings.isEmpty(theme.config.global?.site_logo) ? theme.config.global?.site_logo : site.logo}, siteLogoDark = ${not #strings.isEmpty(theme.config.global?.site_logo_dark) ? theme.config.global?.site_logo_dark : siteLogo}, showSiteName = ${theme.config.global?.show_site_name != false}"
>
<header class="site-header">
<div
class="container header-inner"
th:with="primaryMenu = ${menuFinder.getPrimary()}"
>
<a class="site-brand" th:href="@{/}">
<img
th:if="${not #strings.isEmpty(siteLogo)}"
th:src="${siteLogo}"
th:attr="data-logo-light=${siteLogo},data-logo-dark=${siteLogoDark}"
class="brand-logo"
alt="logo"
/>
<span class="brand-name" th:if="${showSiteName}" th:text="${site.title}">源蝶工作室</span>
</a>
<nav class="primary-nav" th:if="${not #lists.isEmpty(theme.config.nav?.doc_nav_buttons)}">
<a
class="primary-nav-link"
th:each="btn : ${theme.config.nav?.doc_nav_buttons ?: T(java.util.Collections).emptyList()}"
th:href="@{${btn.url ?: '/'}}"
th:text="${btn.title}"
></a>
</nav>
<div
class="header-right"
>
<button
class="search-trigger"
id="search-trigger"
type="button"
th:if="${pluginFinder.available('PluginSearchWidget')}"
onclick="SearchWidget.open()"
aria-label="搜索"
>
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
<span class="search-trigger-text">搜索</span>
<kbd class="search-trigger-kbd">⌘K</kbd>
</button>
<button
class="mobile-menu-toggle"
id="mobile-menu-toggle"
type="button"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
aria-label="打开菜单"
aria-controls="header-menu-panel"
aria-expanded="false"
>
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</button>
<div
class="header-menu-wrap"
id="header-menu-panel"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
>
<th:block th:replace="~{header-menu :: menu}" />
</div>
<div
class="header-divider"
aria-hidden="true"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
></div>
<div class="header-actions">
<th:block
th:if="${not #lists.isEmpty(theme.config.nav?.user_buttons)}"
th:each="btn : ${theme.config.nav?.user_buttons ?: T(java.util.Collections).emptyList()}"
>
<a
class="header-action-btn header-icon-btn header-action-icon-btn"
th:if="${not #strings.isEmpty(btn.svg)}"
th:href="@{${btn.url ?: '#'}}"
th:title="${btn.label}"
th:style="${not #strings.isEmpty(btn.color)} ? '--icon-color:' + btn.color : ''"
>
<span class="header-action-icon" th:utext="${btn.svg}"></span>
</a>
</th:block>
<button
class="theme-toggle header-icon-btn"
id="theme-toggle"
aria-label="切换明暗主题"
>
<svg class="icon-sun" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
<svg class="icon-moon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
</svg>
</button>
<th:block th:replace="~{user-menu :: user-menu}" />
</div>
</div>
<div
class="mobile-menu-backdrop"
id="mobile-menu-backdrop"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
></div>
</div>
</header>
<main class="site-main">
<!-- Docsme:独立页面模板(docsme-docs),展示全部 Docsme 文档项目 -->
<th:block
th:if="${pluginFinder.available('plugin-docsme') and theme.config.docsme?.plugin_enabled != false}"
>
<th:block th:with="projectResult = ${docsmeProjectsFinder.list(1, 1000)}">
<div class="mdocs-plugin-list mdocs-docsme-list">
<div class="container">
<header class="mdocs-plugin-list-head">
<h1
th:text="${not #strings.isEmpty(theme.config.docsme?.archive_title) ? theme.config.docsme?.archive_title : (not #strings.isEmpty(singlePage.spec.title) ? singlePage.spec.title : 'Docsme 文档')}"
>Docsme 文档</h1>
<p th:text="${not #strings.isEmpty(theme.config.docsme?.archive_description) ? theme.config.docsme?.archive_description : ('共 ' + projectResult.total + ' 个 Docsme 文档项目')}">共 0 个 Docsme 文档项目</p>
</header>
<div
class="mdocs-plugin-card-grid"
th:if="${projectResult != null and not #lists.isEmpty(projectResult.items)}"
>
<a
class="mdocs-plugin-card mdocs-project-card"
th:each="project : ${projectResult.items}"
th:href="@{${project.status.permalink}}"
>
<span
class="mdocs-plugin-card-banner"
th:if="${not #strings.isEmpty(project.spec.icon)}"
>
<img
th:src="${project.spec.icon}"
th:alt="${project.spec.displayName}"
/>
</span>
<span class="mdocs-plugin-card-body">
<span class="mdocs-plugin-card-title" th:text="${project.spec.displayName}"></span>
<span class="mdocs-plugin-card-meta">
<span th:text="|共 ${project.status.totalDocs ?: 0} 篇文档|">共 0 篇文档</span>
<span class="mdocs-plugin-card-link" th:text="${project.status.permalink}"></span>
</span>
<span class="mdocs-plugin-card-action">访问项目 →</span>
</span>
</a>
</div>
<p
class="mdocs-plugin-empty"
th:if="${projectResult == null or #lists.isEmpty(projectResult.items)}"
>
暂无 Docsme 文档项目。
</p>
</div>
</div>
</th:block>
</th:block>
<section
class="section"
th:if="${pluginFinder.available('plugin-docsme') and theme.config.docsme?.plugin_enabled == false}"
>
<div class="container">
<p class="mdocs-plugin-empty">Docsme 适配页面已关闭,请在主题设置中开启后再访问。</p>
</div>
</section>
<section class="section" th:unless="${pluginFinder.available('plugin-docsme') or theme.config.docsme?.plugin_enabled == false}">
<div class="container">
<p class="mdocs-plugin-empty">Docsme 插件不可用,无法渲染此页面。</p>
</div>
</section>
</main>
<footer
class="site-footer"
th:with="separateFooterLogo = ${theme.config.footer?.footer_separate_logo == true}, footerLogo = ${separateFooterLogo ? (not #strings.isEmpty(theme.config.footer?.footer_logo) ? theme.config.footer?.footer_logo : siteLogo) : siteLogo}, footerLogoDark = ${separateFooterLogo ? (not #strings.isEmpty(theme.config.footer?.footer_logo_dark) ? theme.config.footer?.footer_logo_dark : (not #strings.isEmpty(theme.config.footer?.footer_logo) ? theme.config.footer?.footer_logo : siteLogoDark)) : siteLogoDark}, showFooterName = ${theme.config.footer?.show_footer_name != false}"
>
<div class="footer-container">
<th:block
th:if="${not #lists.isEmpty(theme.config.footer?.footer_menus)}"
>
<div class="footer-menu-columns">
<th:block
th:each="menuName : ${theme.config.footer?.footer_menus}"
>
<th:block
th:with="footerMenu = ${not #strings.isEmpty(menuName) ? menuFinder.getByName(menuName) : null}"
>
<div
class="footer-menu-col"
th:if="${footerMenu != null and not #lists.isEmpty(footerMenu.menuItems)}"
>
<h4 th:text="${footerMenu.spec.displayName}"></h4>
<ul>
<li th:each="menuItem : ${footerMenu.menuItems}">
<a
th:href="@{${menuItem.status.href}}"
th:text="${menuItem.status.displayName}"
th:target="${menuItem.spec.target?.value}"
th:rel="${menuItem.spec.target?.value == '_blank'} ? 'noopener' : null"
></a>
</li>
</ul>
</div>
</th:block>
</th:block>
</div>
</th:block>
<div class="footer-bottom">
<a
class="footer-bottom-brand"
th:href="@{/}"
th:if="${not #strings.isEmpty(footerLogo) or showFooterName}"
>
<img
th:if="${not #strings.isEmpty(footerLogo)}"
th:src="${footerLogo}"
th:attr="data-logo-light=${footerLogo},data-logo-dark=${footerLogoDark}"
class="footer-bottom-logo"
alt="logo"
/>
<span
class="footer-bottom-name"
th:if="${showFooterName}"
th:text="${site.title}"
></span>
</a>
<span
class="footer-copyright"
th:if="${not #strings.isEmpty(theme.config.footer?.copyright)}"
th:text="${theme.config.footer?.copyright}"
></span>
<span
class="footer-beian"
th:if="${not #strings.isEmpty(theme.config.footer?.icp) or not #strings.isEmpty(theme.config.footer?.gongan)}"
>
<a
th:if="${not #strings.isEmpty(theme.config.footer?.icp)}"
th:href="${theme.config.footer?.icp_link ?: 'https://beian.miit.gov.cn/'}"
target="_blank"
rel="noopener"
th:text="${theme.config.footer?.icp}"
></a>
<a
class="footer-gongan"
th:if="${not #strings.isEmpty(theme.config.footer?.gongan)}"
th:href="${theme.config.footer?.gongan_link ?: 'https://beian.mps.gov.cn/#/query/webSearch'}"
target="_blank"
rel="noopener"
>
<img
th:src="@{/assets/img/gongan.svg}"
class="footer-gongan-icon"
alt=""
/>
<span th:text="${theme.config.footer?.gongan}"></span>
</a>
</span>
</div>
</div>
<halo:footer />
</footer>
<script th:inline="javascript">
window.themePluginPage = {
docs: /*[[${theme.config.docs}]]*/ {},
docsme: /*[[${theme.config.docsme}]]*/ {},
minidocs: /*[[${theme.config.minidocs}]]*/ {},
};
</script>
</body>
</html>
+49
View File
@@ -0,0 +1,49 @@
<th:block
xmlns:th="https://www.thymeleaf.org"
th:fragment="menu"
th:with="primaryMenu = ${menuFinder.getPrimary()}"
>
<nav
class="header-menu"
th:if="${not (primaryMenu == null) and not #lists.isEmpty(primaryMenu.menuItems)}"
>
<div
class="header-menu-item"
th:each="item : ${primaryMenu.menuItems}"
th:classappend="${not #lists.isEmpty(item.children)} ? ' has-dropdown'"
>
<a
th:if="${#lists.isEmpty(item.children)}"
th:href="@{${item.status.href}}"
th:text="${item.status.displayName}"
class="header-menu-link"
th:target="${item.spec.target?.value}"
th:rel="${item.spec.target?.value == '_blank'} ? 'noopener' : null"
><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 17 17 7"></path><path d="M7 7h10v10"></path></svg></a>
<button
th:unless="${#lists.isEmpty(item.children)}"
class="header-menu-trigger"
type="button"
aria-haspopup="true"
>
<span th:text="${item.status.displayName}"></span>
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</button>
<div
th:unless="${#lists.isEmpty(item.children)}"
class="header-menu-dropdown"
>
<a
th:each="child : ${item.children}"
th:href="@{${child.status.href}}"
th:text="${child.status.displayName}"
th:target="${child.spec.target?.value}"
th:rel="${child.spec.target?.value == '_blank'} ? 'noopener' : null"
></a>
</div>
</div>
</nav>
</th:block>
+910
View File
@@ -0,0 +1,910 @@
<!doctype html>
<html
lang="zh-CN"
xmlns:th="https://www.thymeleaf.org"
th:lang="${#locale.toLanguageTag}"
class="color-scheme-auto"
data-color-scheme="auto"
>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title th:text="${site.title}">源蝶工作室</title>
<script type="module" crossorigin src="/themes/theme-mdocs/assets/main-BAX9Eehy.js"></script>
<link rel="stylesheet" crossorigin href="/themes/theme-mdocs/assets/main-OuYKDL0n.css">
</head>
<body
th:style="'--radius:' + (${theme.config.global?.border_radius} ?: '12px') + ';--font-size:' + (${theme.config.global?.font_size} ?: '16px')"
th:attr="data-bg-light=${theme.config.global?.bg_color_light ?: '#ffffff'},data-bg-dark=${theme.config.global?.bg_color_dark ?: '#0d1117'},data-bg-image=${theme.config.global?.background_image},data-primary=${theme.config.global?.primary_color}"
th:with="siteLogo = ${not #strings.isEmpty(theme.config.global?.site_logo) ? theme.config.global?.site_logo : site.logo}, siteLogoDark = ${not #strings.isEmpty(theme.config.global?.site_logo_dark) ? theme.config.global?.site_logo_dark : siteLogo}, showSiteName = ${theme.config.global?.show_site_name != false}"
>
<header class="site-header">
<div
class="container header-inner"
th:with="primaryMenu = ${menuFinder.getPrimary()}"
>
<a class="site-brand" th:href="@{/}">
<img
th:if="${not #strings.isEmpty(siteLogo)}"
th:src="${siteLogo}"
th:attr="data-logo-light=${siteLogo},data-logo-dark=${siteLogoDark}"
class="brand-logo"
alt="logo"
/>
<span class="brand-name" th:if="${showSiteName}" th:text="${site.title}">源蝶工作室</span>
</a>
<nav class="primary-nav" th:if="${not #lists.isEmpty(theme.config.nav?.doc_nav_buttons)}">
<a
class="primary-nav-link"
th:each="btn : ${theme.config.nav?.doc_nav_buttons ?: T(java.util.Collections).emptyList()}"
th:href="@{${btn.url ?: '/'}}"
th:text="${btn.title}"
></a>
</nav>
<div
class="header-right"
>
<button
class="search-trigger"
id="search-trigger"
type="button"
th:if="${pluginFinder.available('PluginSearchWidget')}"
onclick="SearchWidget.open()"
aria-label="搜索"
>
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
<span class="search-trigger-text">搜索</span>
<kbd class="search-trigger-kbd">⌘K</kbd>
</button>
<button
class="mobile-menu-toggle"
id="mobile-menu-toggle"
type="button"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
aria-label="打开菜单"
aria-controls="header-menu-panel"
aria-expanded="false"
>
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</button>
<div
class="header-menu-wrap"
id="header-menu-panel"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
>
<th:block th:replace="~{header-menu :: menu}" />
</div>
<div
class="header-divider"
aria-hidden="true"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
></div>
<div class="header-actions">
<th:block
th:if="${not #lists.isEmpty(theme.config.nav?.user_buttons)}"
th:each="btn : ${theme.config.nav?.user_buttons ?: T(java.util.Collections).emptyList()}"
>
<a
class="header-action-btn header-icon-btn header-action-icon-btn"
th:if="${not #strings.isEmpty(btn.svg)}"
th:href="@{${btn.url ?: '#'}}"
th:title="${btn.label}"
th:style="${not #strings.isEmpty(btn.color)} ? '--icon-color:' + btn.color : ''"
>
<span class="header-action-icon" th:utext="${btn.svg}"></span>
</a>
</th:block>
<button
class="theme-toggle header-icon-btn"
id="theme-toggle"
aria-label="切换明暗主题"
>
<svg class="icon-sun" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
<svg class="icon-moon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
</svg>
</button>
<th:block th:replace="~{user-menu :: user-menu}" />
</div>
</div>
<div
class="mobile-menu-backdrop"
id="mobile-menu-backdrop"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
></div>
</div>
</header>
<main class="site-main">
<div class="homepage">
<div class="homepage-container">
<th:block th:with="modules = ${theme.config.homepage?.modules ?: T(java.util.Collections).emptyList()}">
<th:block th:each="module : ${modules}">
<th:block th:if="${module.type == 'hero-carousel'}">
<section class="hero">
<div class="container hero-inner">
<div class="hero-text">
<h1 class="hero-title" th:text="${module.hc_title}"></h1>
<p class="hero-subtitle" th:text="${module.hc_subtitle}"></p>
<div class="hero-actions">
<a
class="hero-btn hero-primary"
th:classappend="${not #strings.isEmpty(module.hc_primary_color)} ? 'hero-primary-custom'"
th:href="@{${module.hc_primary_url}}"
th:text="${module.hc_primary_text}"
th:style="${not #strings.isEmpty(module.hc_primary_color)} ? 'background:' + module.hc_primary_color : ''"
></a>
<a
class="hero-btn hero-secondary"
th:classappend="${not #strings.isEmpty(module.hc_secondary_color)} ? 'hero-secondary-custom'"
th:href="@{${module.hc_secondary_url}}"
th:text="${module.hc_secondary_text}"
th:style="${not #strings.isEmpty(module.hc_secondary_color)} ? 'color:' + module.hc_secondary_color + ';border-color:' + module.hc_secondary_color : ''"
></a>
</div>
<div class="hero-install" th:if="${not #lists.isEmpty(module.hc_install_options)}">
<a class="install-pill" th:each="opt : ${module.hc_install_options ?: T(java.util.Collections).emptyList()}" th:href="@{${opt.url}}">
<span
class="install-pill-icon"
th:if="${not #strings.isEmpty(opt.icon)}"
th:utext="${opt.icon}"
></span>
<span class="install-pill-label" th:text="${opt.label}"></span>
</a>
</div>
</div>
<div
class="hero-carousel"
th:if="${module.hc_carousel_enabled and (not #strings.isEmpty(module.hc_default_image) or not #lists.isEmpty(module.hc_carousel_images))}"
th:attr="data-speed=${module.hc_carousel_speed},data-show-dots=${module.hc_carousel_show_dots}"
>
<div class="hero-carousel-track">
<div class="hero-carousel-slide active" th:if="${not #strings.isEmpty(module.hc_default_image)}">
<img th:src="@{${module.hc_default_image}}" alt="" />
</div>
<a
th:each="img,stat : ${module.hc_carousel_images ?: T(java.util.Collections).emptyList()}"
th:href="@{${not #strings.isEmpty(img.href)} ? ${img.href} : '#'}"
class="hero-carousel-slide"
th:classappend="${#strings.isEmpty(module.hc_default_image) and stat.first} ? 'active'"
>
<img th:src="@{${img.src}}" alt="" />
</a>
</div>
<div class="hero-carousel-dots" th:if="${module.hc_carousel_show_dots}">
<button class="hero-carousel-dot active" th:if="${not #strings.isEmpty(module.hc_default_image)}" type="button"></button>
<button
class="hero-carousel-dot"
th:each="img,stat : ${module.hc_carousel_images ?: T(java.util.Collections).emptyList()}"
th:classappend="${#strings.isEmpty(module.hc_default_image) and stat.first} ? 'active'"
type="button"
></button>
</div>
</div>
<div class="hero-image" th:unless="${module.hc_carousel_enabled and (not #strings.isEmpty(module.hc_default_image) or not #lists.isEmpty(module.hc_carousel_images))}">
<img th:if="${not #strings.isEmpty(module.hc_default_image)}" th:src="@{${module.hc_default_image}}" alt="preview" />
</div>
</div>
</section>
</th:block>
<th:block th:if="${module.type == 'quick-start'}">
<section class="section quick-start">
<div class="container">
<div class="section-head">
<h2 class="section-title" th:text="${module.title}"></h2>
<a class="section-more" th:if="${not #strings.isEmpty(module.qs_more_url)}" th:href="@{${module.qs_more_url}}"><span th:text="${module.qs_more_text}"></span></a>
</div>
<th:block th:with="cards = ${module.qs_cards ?: T(java.util.Collections).emptyList()}">
<div class="quick-default" th:if="${not #lists.isEmpty(cards)}">
<a
class="quick-card quick-default-main"
th:href="@{${cards[0].url}}"
>
<span class="quick-card-media" th:if="${not #strings.isEmpty(cards[0].image)}">
<img th:src="@{${cards[0].image}}" alt="" />
<span
class="quick-card-overlay"
th:if="${cards[0].overlay_enabled == true}"
th:style="'background-image:linear-gradient(to top,' + ${#strings.isEmpty(cards[0].overlay_color) ? 'var(--primary-color)' : cards[0].overlay_color} + ',transparent)'"
></span>
</span>
<span class="quick-card-content" th:classappend="${not #strings.isEmpty(cards[0].image)} ? 'on-media'">
<span class="quick-card-subtitle" th:text="${cards[0].subtitle}"></span>
<h3 class="quick-card-title" th:text="${cards[0].title}"></h3>
<p class="quick-card-desc" th:text="${cards[0].description}"></p>
</span>
</a>
<div class="quick-default-side" th:if="${#lists.size(cards) > 1}">
<a
class="quick-card"
th:each="card,stat : ${cards}"
th:if="${stat.index > 0}"
th:href="@{${card.url}}"
>
<span class="quick-card-media" th:if="${not #strings.isEmpty(card.image)}">
<img th:src="@{${card.image}}" alt="" />
<span
class="quick-card-overlay"
th:if="${card.overlay_enabled == true}"
th:style="'background-image:linear-gradient(to top,' + ${#strings.isEmpty(card.overlay_color) ? 'var(--primary-color)' : card.overlay_color} + ',transparent)'"
></span>
</span>
<span class="quick-card-content" th:classappend="${not #strings.isEmpty(card.image)} ? 'on-media'">
<span class="quick-card-subtitle" th:text="${card.subtitle}"></span>
<h3 class="quick-card-title" th:text="${card.title}"></h3>
<p class="quick-card-desc" th:text="${card.description}"></p>
</span>
</a>
</div>
</div>
</th:block>
</div>
</section>
</th:block>
<th:block th:if="${module.type == 'cards'}">
<section class="section cards-section">
<div class="container">
<div
class="section-head"
th:if="${not #strings.isEmpty(module.title) or (module.more_enabled != false and not #strings.isEmpty(module.more_url))}"
>
<h2 class="section-title" th:text="${module.title}"></h2>
<a
class="section-more"
th:if="${module.more_enabled != false and not #strings.isEmpty(module.more_url)}"
th:href="@{${module.more_url}}"
>
<span th:text="${not #strings.isEmpty(module.more_text) ? module.more_text : '查看更多'}">查看更多</span>
</a>
</div>
<div class="cards-grid cards-type1" th:if="${module.cards_layout == 'type1' or #strings.isEmpty(module.cards_layout)}">
<div class="card-item" th:each="item : ${module.cards_items ?: T(java.util.Collections).emptyList()}">
<div class="card-item-cover" th:if="${not #strings.isEmpty(item.cover)}">
<img th:src="@{${item.cover}}" alt="" />
</div>
<div class="card-item-body">
<h3 class="card-item-title" th:text="${item.title}"></h3>
<p class="card-item-desc" th:if="${not (item.show_description == false) and not #strings.isEmpty(item.description)}" th:text="${item.description}"></p>
</div>
</div>
</div>
<div class="cards-grid cards-type3" th:if="${module.cards_layout == 'type3' or module.cards_layout == 'type2'}">
<div class="card-item card-item-simple" th:each="item : ${module.cards_items ?: T(java.util.Collections).emptyList()}">
<a class="card-item-link" th:href="@{${item.url}}">
<span
class="card-item-icon"
th:if="${not #strings.isEmpty(item.icon)}"
th:utext="${item.icon}"
></span>
<span class="card-item-content">
<span class="card-item-title" th:text="${item.title}"></span>
<span
class="card-item-desc"
th:if="${not (item.show_description == false) and not #strings.isEmpty(item.description)}"
th:text="${item.description}"
></span>
</span>
</a>
</div>
</div>
</div>
</section>
</th:block>
<th:block th:if="${module.type == 'plugin-docsme-module' or module.type == 'plugin-minidocs-module'}">
<section class="section plugin-doc-section">
<div class="container">
<div
class="section-head"
th:if="${not #strings.isEmpty(module.title) or (module.doc_more_enabled != false and not #strings.isEmpty(module.doc_more_url))}"
>
<h2 class="section-title" th:text="${module.title}"></h2>
<a
class="section-more"
th:if="${module.doc_more_enabled != false and not #strings.isEmpty(module.doc_more_url)}"
th:href="@{${module.doc_more_url}}"
>
<span th:text="${not #strings.isEmpty(module.doc_more_text) ? module.doc_more_text : '查看更多'}">查看更多</span>
</a>
</div>
<!-- 模块七/八:自动读取插件归档页卡片 -->
<th:block
th:if="${module.doc_content_source == 'archive' and module.type == 'plugin-docsme-module'}"
>
<th:block th:if="${pluginFinder.available('plugin-docsme')}">
<th:block
th:with="projectResult = ${docsmeProjectsFinder.list(1, 1000)}, projects = ${projectResult != null ? projectResult.items : null}"
>
<div
class="mdocs-plugin-card-grid"
th:if="${projects != null and not #lists.isEmpty(projects)}"
>
<a
class="mdocs-plugin-card mdocs-project-card"
th:each="project,stat : ${projects}"
th:if="${stat.index lt 8}"
th:href="@{${project.status.permalink}}"
th:title="${project.spec.displayName}"
>
<span
class="mdocs-plugin-card-banner"
th:if="${not #strings.isEmpty(project.spec.icon)}"
>
<img
th:src="${project.spec.icon}"
th:alt="${project.spec.displayName}"
loading="lazy"
/>
</span>
<span
class="mdocs-plugin-card-banner mdocs-plugin-card-fallback"
th:unless="${not #strings.isEmpty(project.spec.icon)}"
>
<span
th:text="${not #strings.isEmpty(project.spec.displayName) ? #strings.substring(project.spec.displayName, 0, 1) : '文'}"
></span>
</span>
<span class="mdocs-plugin-card-body">
<span class="mdocs-plugin-card-title" th:text="${project.spec.displayName}"></span>
<span class="mdocs-plugin-card-meta">
<span
th:text="|共 ${project.status != null and project.status.totalDocs != null ? project.status.totalDocs : 0} 篇文档|"
>共 0 篇文档</span>
<span class="mdocs-plugin-card-link" th:text="${project.status.permalink}"></span>
</span>
<span class="mdocs-plugin-card-action">访问项目 →</span>
</span>
</a>
</div>
<p
class="mdocs-plugin-empty"
th:if="${projects == null or #lists.isEmpty(projects)}"
>
暂无 Docsme 文档项目。
</p>
</th:block>
</th:block>
<p
class="mdocs-plugin-empty"
th:if="${not pluginFinder.available('plugin-docsme')}"
>
Docsme 插件不可用,无法自动读取归档卡片。
</p>
</th:block>
<th:block
th:if="${module.doc_content_source == 'archive' and module.type == 'plugin-minidocs-module'}"
>
<th:block th:if="${pluginFinder.available('halo-plugin-minidocs')}">
<th:block
th:with="kbResult = ${minidocsFinder.listKnowledgeBases(1, 1000)},
kbs = ${kbResult != null ? kbResult.items : null},
archiveBase = ${not #strings.isEmpty(module.doc_more_url) ? module.doc_more_url : '/minidocs'}"
>
<div
class="mdocs-plugin-card-grid"
th:if="${kbs != null and not #lists.isEmpty(kbs)}"
>
<th:block
th:each="kb,stat : ${kbs}"
th:if="${stat.index lt 8}"
>
<th:block
th:with="kbSlug = ${not #strings.isEmpty(kb.spec.slug) ? kb.spec.slug : kb.metadata.name},
kbUrl = ${archiveBase + '?kb=' + kbSlug}"
>
<a
class="mdocs-plugin-card mdocs-kb-card"
th:href="@{${kbUrl}}"
th:title="${kb.spec.displayName}"
>
<span
class="mdocs-plugin-card-banner"
th:if="${not #strings.isEmpty(kb.spec.cover)}"
>
<img
th:src="${kb.spec.cover}"
th:alt="${kb.spec.displayName}"
loading="lazy"
/>
</span>
<span
class="mdocs-plugin-card-banner mdocs-plugin-card-fallback"
th:unless="${not #strings.isEmpty(kb.spec.cover)}"
>
<span
th:text="${not #strings.isEmpty(kb.spec.displayName) ? #strings.substring(kb.spec.displayName, 0, 1) : '库'}"
></span>
</span>
<span class="mdocs-plugin-card-body">
<span class="mdocs-plugin-card-title" th:text="${kb.spec.displayName}"></span>
<span
class="mdocs-plugin-card-desc"
th:if="${not #strings.isEmpty(kb.spec.description)}"
th:text="${kb.spec.description}"
></span>
<span class="mdocs-plugin-card-meta">
<span
th:text="|共 ${kb.status != null and kb.status.docCount != null ? kb.status.docCount : 0} 篇文档|"
>共 0 篇文档</span>
</span>
</span>
</a>
</th:block>
</th:block>
</div>
<p
class="mdocs-plugin-empty"
th:if="${kbs == null or #lists.isEmpty(kbs)}"
>
暂无 MiniDocs 知识库,或当前访客没有阅读权限。
</p>
</th:block>
</th:block>
<p
class="mdocs-plugin-empty"
th:if="${not pluginFinder.available('halo-plugin-minidocs')}"
>
MiniDocs 插件不可用,无法自动读取归档卡片。
</p>
</th:block>
<!-- 手动添加文档(子模块一/二,兼容旧数据) -->
<th:block th:unless="${module.doc_content_source == 'archive'}">
<th:block
th:if="${module.doc_layout == 'cover' or #strings.isEmpty(module.doc_layout)}"
>
<ul
class="plugin-doc-card-grid plugin-doc-cover-grid"
th:if="${not #lists.isEmpty(module.doc_cover_items)}"
>
<li
class="plugin-doc-cover-card"
th:each="item,stat : ${module.doc_cover_items ?: T(java.util.Collections).emptyList()}"
th:if="${stat.index lt 8}"
>
<a
class="plugin-doc-card-link"
th:href="@{${item.url ?: '#'}}"
th:title="${item.title}"
>
<span
class="plugin-doc-cover-media"
th:if="${not #strings.isEmpty(item.cover)}"
>
<img th:src="@{${item.cover}}" alt="" loading="lazy" />
</span>
<span class="plugin-doc-cover-body">
<span class="plugin-doc-card-title" th:text="${item.title}"></span>
<span
class="plugin-doc-card-desc"
th:if="${not #strings.isEmpty(item.description)}"
th:text="${item.description}"
></span>
</span>
</a>
</li>
</ul>
</th:block>
<th:block th:if="${module.doc_layout == 'tree'}">
<ul
class="plugin-doc-card-grid plugin-doc-tree-grid"
th:if="${not #lists.isEmpty(module.doc_tree_items)}"
>
<li
class="plugin-doc-tree-card"
th:each="item,stat : ${module.doc_tree_items ?: T(java.util.Collections).emptyList()}"
th:if="${stat.index lt 8}"
>
<a
class="plugin-doc-card-title-link"
th:href="@{${item.url ?: '#'}}"
th:title="${item.title}"
>
<span
class="plugin-doc-card-icon"
th:if="${not #strings.isEmpty(item.icon)}"
th:utext="${item.icon}"
></span>
<span class="plugin-doc-card-title" th:text="${item.title}"></span>
</a>
<ul
class="plugin-doc-tree-list"
th:if="${not #lists.isEmpty(item.doc_children)}"
>
<li
class="plugin-doc-tree-item"
th:each="child,childStat : ${item.doc_children}"
th:if="${childStat.index lt 3}"
>
<a
th:href="@{${child.url ?: '#'}}"
th:text="${child.title}"
th:title="${child.title}"
></a>
</li>
</ul>
</li>
</ul>
</th:block>
</th:block>
</div>
</section>
</th:block>
<th:block th:if="${module.type == 'ocean-categories'}">
<section class="section ocean-section">
<div class="container">
<th:block th:if="${not #lists.isEmpty(module.ocean_blocks)}">
<div
class="section-head"
th:if="${not #strings.isEmpty(module.title) or (module.more_enabled != false and not #strings.isEmpty(module.more_url))}"
>
<h2 class="section-title" th:text="${module.title ?: '按产品浏览'}">按产品浏览</h2>
<a
class="section-more"
th:if="${module.more_enabled != false and not #strings.isEmpty(module.more_url)}"
th:href="@{${module.more_url}}"
>
<span th:text="${not #strings.isEmpty(module.more_text) ? module.more_text : '查看更多'}">查看更多</span>
</a>
</div>
<ul
class="ocean-category-grid"
th:style="'--ocean-cols:' + ${#lists.size(module.ocean_blocks)}"
>
<th:block th:each="block : ${module.ocean_blocks ?: T(java.util.Collections).emptyList()}">
<th:block th:if="${block != null and not #strings.isEmpty(block.category)}">
<th:block th:with="category = ${categoryFinder.getByName(block.category)}">
<li
class="ocean-category-item"
th:if="${category != null}"
th:style="'--ocean-block-color:' + ${#strings.isEmpty(block.color) ? '#0c7be0' : block.color}"
>
<div class="ocean-category-hover">
<span class="ocean-category-accent"></span>
<a class="ocean-category-card" th:href="@{${category.status.permalink}}">
<img
class="ocean-category-cover"
th:if="${not #strings.isEmpty(category.spec.cover)}"
th:src="${category.spec.cover}"
th:alt="${category.spec.displayName}"
/>
<span
class="ocean-category-name"
th:text="${not #strings.isEmpty(block.title) ? block.title : category.spec.displayName}"
></span>
<span
class="ocean-category-desc"
th:if="${not #strings.isEmpty(category.spec.description)}"
th:text="${category.spec.description}"
></span>
<span class="ocean-category-stars">
<img
alt="GitHub stars"
th:if="${#annotations.contains(category, 'stars')}"
th:src="${#annotations.get(category, 'stars')}"
/>
</span>
</a>
</div>
<div
class="ocean-category-extra"
th:if="${not #strings.isEmpty(block.code)}"
th:utext="${block.code}"
></div>
</li>
</th:block>
</th:block>
</th:block>
</ul>
</th:block>
</div>
</section>
</th:block>
<th:block th:if="${module.type == 'ocean-menu'}">
<section class="section ocean-section">
<div class="container">
<div
class="section-head"
th:if="${not #strings.isEmpty(module.title) or (module.more_enabled != false and not #strings.isEmpty(module.more_url))}"
>
<h2 class="section-title" th:text="${module.title ?: '常见主题'}">常见主题</h2>
<a
class="section-more"
th:if="${module.more_enabled != false and not #strings.isEmpty(module.more_url)}"
th:href="@{${module.more_url}}"
>
<span th:text="${not #strings.isEmpty(module.more_text) ? module.more_text : '查看更多'}">查看更多</span>
</a>
</div>
<th:block
th:if="${not #lists.isEmpty(module.ocean_menus)}"
>
<ul class="ocean-menu-card-grid">
<th:block
th:each="menuBlock : ${module.ocean_menus}"
>
<th:block
th:with="menu = ${menuBlock != null ? menuFinder.getByName(menuBlock.menu) : null}"
>
<li
class="ocean-menu-card"
th:if="${menu != null and not #lists.isEmpty(menu.menuItems)}"
>
<div class="ocean-menu-card-head">
<h3 class="ocean-menu-card-title" th:text="${menu.spec.displayName}"></h3>
</div>
<ul class="ocean-menu-card-list">
<th:block
th:each="menuItem,itemStat : ${menu.menuItems}"
>
<li
class="ocean-menu-card-child"
th:if="${itemStat.index lt 4}"
>
<span
class="ocean-menu-card-child-icon"
th:if="${#annotations.contains(menuItem, 'icon')}"
>
<img
th:src="${#annotations.get(menuItem, 'icon')}"
th:title="${menuItem.status.displayName}"
th:alt="${menuItem.status.displayName}"
/>
</span>
<a
class="ocean-menu-card-child-link"
th:href="@{${menuItem.status.href}}"
th:title="${menuItem.status.displayName}"
>
<span
class="ocean-menu-card-child-text"
th:text="${menuItem.status.displayName}"
></span>
</a>
</li>
</th:block>
</ul>
</li>
</th:block>
</th:block>
</ul>
</th:block>
</div>
</section>
</th:block>
<th:block th:if="${module.type == 'ocean-posts'}">
<section class="section ocean-section">
<div class="container">
<th:block
th:with="source = ${module.ocean_posts_source ?: 'tag'}, sourceTag = ${module.ocean_posts_tag}, sourceCategory = ${module.ocean_posts_category}"
>
<th:block
th:if="${(source == 'tag' and not #strings.isEmpty(sourceTag)) or (source == 'categories' and not #strings.isEmpty(sourceCategory))}"
>
<th:block
th:with="posts = ${source == 'tag' ? postFinder.listByTag(1, 6, sourceTag).items : postFinder.listByCategory(1, 6, sourceCategory).items}"
>
<div th:if="${not #lists.isEmpty(posts)}">
<div class="section-head">
<h2 class="section-title" th:text="${module.title ?: '推荐阅读'}">推荐阅读</h2>
</div>
<ul class="grid grid-cols-2 gap-4 font-semibold text-gray-900 sm:grid-cols-3 sm:gap-6 xl:grid-cols-3 xl:gap-8">
<li
class="group rounded-xl bg-white p-3 px-5 duration-1000 hover:-translate-y-2 hover:shadow dark:bg-zinc-800"
th:each="post : ${posts}"
>
<div class="flex w-full flex-1 items-center justify-between gap-2 opacity-0 transition duration-1000 group-hover:opacity-100">
<a
class="mr-2 rounded-xl px-2 text-xs font-light tabular-nums hover:text-gray-900"
th:style="'color:'+${#annotations.getOrDefault(post.categories[0], 'color', 'var(--primary-color)')}+';background-color:'+${#annotations.getOrDefault(post.categories[0], 'color', 'var(--primary-color)')}+'30'"
th:if="${#lists.size(post.categories)} gt 0"
th:href="@{${post.categories[0].status.permalink}}"
th:title="${post.categories[0].spec.displayName}"
th:text="${post.categories[0].spec.displayName}"
></a>
<span
class="truncate text-right text-sm font-normal"
th:if="${#lists.size(post.categories)} gt 0"
th:style="'color:'+${#annotations.getOrDefault(post.categories[0], 'color', 'var(--primary-color)')}"
th:text="${#dates.format(post.spec.publishTime,'yyyy年MM月dd日')}"
></span>
</div>
<div class="mb-1 mt-2 flex h-[59px] w-full flex-1 items-center">
<h3 class="hover-within text-lg font-medium">
<a
class="ocean-post-title-link hover:text-zinc-500 dark:text-zinc-200"
th:href="@{${post.status.permalink}}"
th:title="${post.spec.title}"
>
<span
class="ocean-post-title-icon"
th:if="${not #strings.isEmpty(module.ocean_posts_icon)}"
th:utext="${module.ocean_posts_icon}"
></span>
<span class="line-clamp-2" th:text="${post.spec.title}"></span>
</a>
</h3>
</div>
<div class="flex w-full flex-1 items-center justify-between gap-2 opacity-0 transition duration-1000 group-hover:opacity-100">
<p
class="text-sm font-light text-[#929eaf] dark:text-zinc-200"
th:text="${post.status.excerpt}"
></p>
</div>
</li>
</ul>
</div>
</th:block>
</th:block>
</th:block>
</div>
</section>
</th:block>
</th:block>
</th:block>
</div>
<section class="section browse" th:if="${theme.config.homepage == null or #lists.isEmpty(theme.config.homepage?.modules)}">
<div class="container">
<h2 class="section-title">按主题浏览</h2>
<div class="browse-grid">
<a class="browse-card" th:each="group : ${categoryFinder.listAsTree()}" th:href="@{${group.status.permalink}}">
<h3 class="browse-card-title" th:text="${group.spec.displayName}"></h3>
<p class="browse-card-desc" th:if="${not #strings.isEmpty(group.spec.description)}" th:text="${group.spec.description}"></p>
</a>
</div>
</div>
</section>
</div>
</main>
<footer
class="site-footer"
th:with="separateFooterLogo = ${theme.config.footer?.footer_separate_logo == true}, footerLogo = ${separateFooterLogo ? (not #strings.isEmpty(theme.config.footer?.footer_logo) ? theme.config.footer?.footer_logo : siteLogo) : siteLogo}, footerLogoDark = ${separateFooterLogo ? (not #strings.isEmpty(theme.config.footer?.footer_logo_dark) ? theme.config.footer?.footer_logo_dark : (not #strings.isEmpty(theme.config.footer?.footer_logo) ? theme.config.footer?.footer_logo : siteLogoDark)) : siteLogoDark}, showFooterName = ${theme.config.footer?.show_footer_name != false}"
>
<div class="footer-container">
<th:block
th:if="${not #lists.isEmpty(theme.config.footer?.footer_menus)}"
>
<div class="footer-menu-columns">
<th:block
th:each="menuName : ${theme.config.footer?.footer_menus}"
>
<th:block
th:with="footerMenu = ${not #strings.isEmpty(menuName) ? menuFinder.getByName(menuName) : null}"
>
<div
class="footer-menu-col"
th:if="${footerMenu != null and not #lists.isEmpty(footerMenu.menuItems)}"
>
<h4 th:text="${footerMenu.spec.displayName}"></h4>
<ul>
<li th:each="menuItem : ${footerMenu.menuItems}">
<a
th:href="@{${menuItem.status.href}}"
th:text="${menuItem.status.displayName}"
th:target="${menuItem.spec.target?.value}"
th:rel="${menuItem.spec.target?.value == '_blank'} ? 'noopener' : null"
></a>
</li>
</ul>
</div>
</th:block>
</th:block>
</div>
</th:block>
<div class="footer-bottom">
<a
class="footer-bottom-brand"
th:href="@{/}"
th:if="${not #strings.isEmpty(footerLogo) or showFooterName}"
>
<img
th:if="${not #strings.isEmpty(footerLogo)}"
th:src="${footerLogo}"
th:attr="data-logo-light=${footerLogo},data-logo-dark=${footerLogoDark}"
class="footer-bottom-logo"
alt="logo"
/>
<span
class="footer-bottom-name"
th:if="${showFooterName}"
th:text="${site.title}"
></span>
</a>
<span
class="footer-copyright"
th:if="${not #strings.isEmpty(theme.config.footer?.copyright)}"
th:text="${theme.config.footer?.copyright}"
></span>
<span
class="footer-beian"
th:if="${not #strings.isEmpty(theme.config.footer?.icp) or not #strings.isEmpty(theme.config.footer?.gongan)}"
>
<a
th:if="${not #strings.isEmpty(theme.config.footer?.icp)}"
th:href="${theme.config.footer?.icp_link ?: 'https://beian.miit.gov.cn/'}"
target="_blank"
rel="noopener"
th:text="${theme.config.footer?.icp}"
></a>
<a
class="footer-gongan"
th:if="${not #strings.isEmpty(theme.config.footer?.gongan)}"
th:href="${theme.config.footer?.gongan_link ?: 'https://beian.mps.gov.cn/#/query/webSearch'}"
target="_blank"
rel="noopener"
>
<img
th:src="@{/assets/img/gongan.svg}"
class="footer-gongan-icon"
alt=""
/>
<span th:text="${theme.config.footer?.gongan}"></span>
</a>
</span>
</div>
</div>
<halo:footer />
</footer>
<script th:inline="javascript">
window.themePluginPage = {
docs: /*[[${theme.config.docs}]]*/ {},
docsme: /*[[${theme.config.docsme}]]*/ {},
minidocs: /*[[${theme.config.minidocs}]]*/ {},
};
</script>
</body>
</html>
+100
View File
@@ -0,0 +1,100 @@
<!doctype html>
<html
lang="zh-CN"
xmlns:th="https://www.thymeleaf.org"
th:lang="${#locale.toLanguageTag}"
th:fragment="html (head, content)"
th:with="primaryColor = ${theme.config.global?.primary_color}, brandName = ${site.title}"
>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title th:if="${head == null}" th:text="${brandName}">源蝶工作室</title>
<th:block th:if="${head != null}">
<th:block th:replace="${head}" />
</th:block>
<script type="module" crossorigin src="/themes/theme-mdocs/assets/main-BAX9Eehy.js"></script>
<link rel="stylesheet" crossorigin href="/themes/theme-mdocs/assets/main-OuYKDL0n.css">
</head>
<body
th:style="'--radius:' + (${theme.config.global?.border_radius} ?: '12px') + ';--font-size:' + (${theme.config.global?.font_size} ?: '16px')"
th:attr="data-bg-light=${theme.config.global?.bg_color_light ?: '#ffffff'},data-bg-dark=${theme.config.global?.bg_color_dark ?: '#0d1117'},data-bg-image=${theme.config.global?.background_image},data-primary=${primaryColor}"
th:with="siteLogo = ${not #strings.isEmpty(theme.config.global?.site_logo) ? theme.config.global?.site_logo : site.logo}, showSiteName = ${theme.config.global?.show_site_name != false}, headerMenu = ${menuFinder.getPrimary()}"
>
<header class="site-header">
<div class="container header-inner">
<a class="site-brand" th:href="@{/}">
<img
th:if="${not #strings.isEmpty(siteLogo)}"
th:src="${siteLogo}"
class="brand-logo"
alt="logo"
/>
<span class="brand-name" th:if="${showSiteName}" th:text="${brandName}">源蝶工作室</span>
</a>
<nav class="site-nav" th:if="${headerMenu != null && not #lists.isEmpty(headerMenu.menuItems)}">
<ul class="site-nav-list">
<li class="site-nav-item" th:each="item : ${headerMenu.menuItems}">
<a
class="site-nav-link"
th:href="@{${item.status.href}}"
th:text="${item.status.displayName}"
th:target="${item.spec.target?.value}"
></a>
<ul class="site-nav-dropdown" th:if="${not #lists.isEmpty(item.children)}">
<li th:each="child : ${item.children}">
<a
th:href="@{${child.status.href}}"
th:text="${child.status.displayName}"
th:target="${child.spec.target?.value}"
></a>
</li>
</ul>
</li>
</ul>
</nav>
<button
class="theme-toggle"
id="theme-toggle"
aria-label="切换明暗主题"
>
<svg class="icon-sun" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
<svg class="icon-moon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
</svg>
</button>
</div>
</header>
<main class="site-main">
<th:block th:replace="${content}" />
</main>
<footer class="site-footer">
<div class="container footer-inner">
<div class="footer-brand">
<div class="footer-brand-name" th:text="${brandName}">源蝶工作室</div>
<p
class="footer-copyright"
th:if="${not #strings.isEmpty(theme.config.footer?.copyright)}"
th:text="${theme.config.footer?.copyright}"
></p>
<p class="footer-icp" th:if="${not #strings.isEmpty(theme.config.footer?.icp)}" th:text="${theme.config.footer?.icp}"></p>
</div>
</div>
<halo:footer />
</footer>
</body>
</html>
+340
View File
@@ -0,0 +1,340 @@
<!doctype html>
<html
lang="zh-CN"
xmlns:th="https://www.thymeleaf.org"
th:lang="${#locale.toLanguageTag}"
class="color-scheme-auto"
data-color-scheme="auto"
>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title
th:text="|${not #strings.isEmpty(singlePage.spec.title) ? singlePage.spec.title : 'MiniDocs 知识库'} - ${site.title}|"
>MiniDocs 知识库</title>
<script type="module" crossorigin src="/themes/theme-mdocs/assets/main-BAX9Eehy.js"></script>
<link rel="stylesheet" crossorigin href="/themes/theme-mdocs/assets/main-OuYKDL0n.css">
</head>
<body
th:style="'--radius:' + (${theme.config.global?.border_radius} ?: '12px') + ';--font-size:' + (${theme.config.global?.font_size} ?: '16px')"
th:attr="data-bg-light=${theme.config.global?.bg_color_light ?: '#ffffff'},data-bg-dark=${theme.config.global?.bg_color_dark ?: '#0d1117'},data-bg-image=${theme.config.global?.background_image},data-primary=${theme.config.global?.primary_color}"
th:with="siteLogo = ${not #strings.isEmpty(theme.config.global?.site_logo) ? theme.config.global?.site_logo : site.logo}, siteLogoDark = ${not #strings.isEmpty(theme.config.global?.site_logo_dark) ? theme.config.global?.site_logo_dark : siteLogo}, showSiteName = ${theme.config.global?.show_site_name != false}"
>
<header class="site-header">
<div
class="container header-inner"
th:with="primaryMenu = ${menuFinder.getPrimary()}"
>
<a class="site-brand" th:href="@{/}">
<img
th:if="${not #strings.isEmpty(siteLogo)}"
th:src="${siteLogo}"
th:attr="data-logo-light=${siteLogo},data-logo-dark=${siteLogoDark}"
class="brand-logo"
alt="logo"
/>
<span class="brand-name" th:if="${showSiteName}" th:text="${site.title}">源蝶工作室</span>
</a>
<nav class="primary-nav" th:if="${not #lists.isEmpty(theme.config.nav?.doc_nav_buttons)}">
<a
class="primary-nav-link"
th:each="btn : ${theme.config.nav?.doc_nav_buttons ?: T(java.util.Collections).emptyList()}"
th:href="@{${btn.url ?: '/'}}"
th:text="${btn.title}"
></a>
</nav>
<div
class="header-right"
>
<button
class="search-trigger"
id="search-trigger"
type="button"
th:if="${pluginFinder.available('PluginSearchWidget')}"
onclick="SearchWidget.open()"
aria-label="搜索"
>
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
<span class="search-trigger-text">搜索</span>
<kbd class="search-trigger-kbd">⌘K</kbd>
</button>
<button
class="mobile-menu-toggle"
id="mobile-menu-toggle"
type="button"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
aria-label="打开菜单"
aria-controls="header-menu-panel"
aria-expanded="false"
>
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</button>
<div
class="header-menu-wrap"
id="header-menu-panel"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
>
<th:block th:replace="~{header-menu :: menu}" />
</div>
<div
class="header-divider"
aria-hidden="true"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
></div>
<div class="header-actions">
<th:block
th:if="${not #lists.isEmpty(theme.config.nav?.user_buttons)}"
th:each="btn : ${theme.config.nav?.user_buttons ?: T(java.util.Collections).emptyList()}"
>
<a
class="header-action-btn header-icon-btn header-action-icon-btn"
th:if="${not #strings.isEmpty(btn.svg)}"
th:href="@{${btn.url ?: '#'}}"
th:title="${btn.label}"
th:style="${not #strings.isEmpty(btn.color)} ? '--icon-color:' + btn.color : ''"
>
<span class="header-action-icon" th:utext="${btn.svg}"></span>
</a>
</th:block>
<button
class="theme-toggle header-icon-btn"
id="theme-toggle"
aria-label="切换明暗主题"
>
<svg class="icon-sun" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
<svg class="icon-moon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
</svg>
</button>
<th:block th:replace="~{user-menu :: user-menu}" />
</div>
</div>
<div
class="mobile-menu-backdrop"
id="mobile-menu-backdrop"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
></div>
</div>
</header>
<main class="site-main">
<!-- MiniDocs:独立页面模板(minidocs-docs),保持知识库归档/列表形态 -->
<th:block
th:if="${pluginFinder.available('halo-plugin-minidocs') and theme.config.minidocs?.plugin_enabled != false}"
>
<th:block
th:with="kbResult = ${minidocsFinder.listKnowledgeBases(1, 1000)},
archiveBase = ${not #strings.isEmpty(singlePage.status?.permalink) ? singlePage.status.permalink : '/minidocs'}"
>
<div class="mdocs-plugin-list mdocs-minidocs-list" id="minidocs-archive">
<div class="container">
<header class="mdocs-plugin-list-head">
<h1
th:text="${not #strings.isEmpty(theme.config.minidocs?.archive_title) ? theme.config.minidocs?.archive_title : (not #strings.isEmpty(singlePage.spec.title) ? singlePage.spec.title : '知识库')}"
>MiniDocs 知识库</h1>
<p th:text="${not #strings.isEmpty(theme.config.minidocs?.archive_description) ? theme.config.minidocs?.archive_description : ('共 ' + kbResult.total + ' 个知识库')}">共 0 个知识库</p>
</header>
<div class="mdocs-plugin-card-grid">
<th:block th:each="kb : ${kbResult.items}">
<th:block
th:with="kbSlug = ${not #strings.isEmpty(kb.spec.slug) ? kb.spec.slug : kb.metadata.name},
kbUrl = ${archiveBase + '?kb=' + kbSlug}"
>
<a
class="mdocs-plugin-card mdocs-kb-card"
th:href="@{${kbUrl}}"
th:attr="data-kb=${kbSlug},data-title=${kb.spec.displayName},data-cover=${kb.spec.cover}"
>
<span
class="mdocs-plugin-card-banner"
th:if="${not #strings.isEmpty(kb.spec.cover)}"
>
<img th:src="${kb.spec.cover}" th:alt="${kb.spec.displayName}" />
</span>
<span
class="mdocs-plugin-card-banner mdocs-plugin-card-fallback"
th:unless="${not #strings.isEmpty(kb.spec.cover)}"
>
<span
th:text="${not #strings.isEmpty(kb.spec.displayName) ? #strings.substring(kb.spec.displayName, 0, 1) : '库'}"
></span>
</span>
<span class="mdocs-plugin-card-body">
<span class="mdocs-plugin-card-title" th:text="${kb.spec.displayName}"></span>
<span
class="mdocs-plugin-card-desc"
th:if="${not #strings.isEmpty(kb.spec.description)}"
th:text="${kb.spec.description}"
></span>
<span class="mdocs-plugin-card-meta">
<span
th:text="|共 ${kb.status != null and kb.status.docCount != null ? kb.status.docCount : 0} 篇文档|"
>共 0 篇文档</span>
</span>
</span>
</a>
</th:block>
</th:block>
</div>
<p
class="mdocs-plugin-empty"
th:if="${kbResult == null or #lists.isEmpty(kbResult.items)}"
>
暂无 MiniDocs 知识库,或当前访客没有阅读权限。
</p>
</div>
</div>
<div class="minidocs-viewer-host" id="minidocs-viewer-host" hidden></div>
</th:block>
</th:block>
<section
class="section"
th:if="${pluginFinder.available('halo-plugin-minidocs') and theme.config.minidocs?.plugin_enabled == false}"
>
<div class="container">
<p class="mdocs-plugin-empty">MiniDocs 适配页面已关闭,请在主题设置中开启后再访问。</p>
</div>
</section>
<section class="section" th:unless="${pluginFinder.available('halo-plugin-minidocs') or theme.config.minidocs?.plugin_enabled == false}">
<div class="container">
<p class="mdocs-plugin-empty">MiniDocs 插件不可用,无法渲染此页面。</p>
</div>
</section>
</main>
<footer
class="site-footer"
th:with="separateFooterLogo = ${theme.config.footer?.footer_separate_logo == true}, footerLogo = ${separateFooterLogo ? (not #strings.isEmpty(theme.config.footer?.footer_logo) ? theme.config.footer?.footer_logo : siteLogo) : siteLogo}, footerLogoDark = ${separateFooterLogo ? (not #strings.isEmpty(theme.config.footer?.footer_logo_dark) ? theme.config.footer?.footer_logo_dark : (not #strings.isEmpty(theme.config.footer?.footer_logo) ? theme.config.footer?.footer_logo : siteLogoDark)) : siteLogoDark}, showFooterName = ${theme.config.footer?.show_footer_name != false}"
>
<div class="footer-container">
<th:block
th:if="${not #lists.isEmpty(theme.config.footer?.footer_menus)}"
>
<div class="footer-menu-columns">
<th:block
th:each="menuName : ${theme.config.footer?.footer_menus}"
>
<th:block
th:with="footerMenu = ${not #strings.isEmpty(menuName) ? menuFinder.getByName(menuName) : null}"
>
<div
class="footer-menu-col"
th:if="${footerMenu != null and not #lists.isEmpty(footerMenu.menuItems)}"
>
<h4 th:text="${footerMenu.spec.displayName}"></h4>
<ul>
<li th:each="menuItem : ${footerMenu.menuItems}">
<a
th:href="@{${menuItem.status.href}}"
th:text="${menuItem.status.displayName}"
th:target="${menuItem.spec.target?.value}"
th:rel="${menuItem.spec.target?.value == '_blank'} ? 'noopener' : null"
></a>
</li>
</ul>
</div>
</th:block>
</th:block>
</div>
</th:block>
<div class="footer-bottom">
<a
class="footer-bottom-brand"
th:href="@{/}"
th:if="${not #strings.isEmpty(footerLogo) or showFooterName}"
>
<img
th:if="${not #strings.isEmpty(footerLogo)}"
th:src="${footerLogo}"
th:attr="data-logo-light=${footerLogo},data-logo-dark=${footerLogoDark}"
class="footer-bottom-logo"
alt="logo"
/>
<span
class="footer-bottom-name"
th:if="${showFooterName}"
th:text="${site.title}"
></span>
</a>
<span
class="footer-copyright"
th:if="${not #strings.isEmpty(theme.config.footer?.copyright)}"
th:text="${theme.config.footer?.copyright}"
></span>
<span
class="footer-beian"
th:if="${not #strings.isEmpty(theme.config.footer?.icp) or not #strings.isEmpty(theme.config.footer?.gongan)}"
>
<a
th:if="${not #strings.isEmpty(theme.config.footer?.icp)}"
th:href="${theme.config.footer?.icp_link ?: 'https://beian.miit.gov.cn/'}"
target="_blank"
rel="noopener"
th:text="${theme.config.footer?.icp}"
></a>
<a
class="footer-gongan"
th:if="${not #strings.isEmpty(theme.config.footer?.gongan)}"
th:href="${theme.config.footer?.gongan_link ?: 'https://beian.mps.gov.cn/#/query/webSearch'}"
target="_blank"
rel="noopener"
>
<img
th:src="@{/assets/img/gongan.svg}"
class="footer-gongan-icon"
alt=""
/>
<span th:text="${theme.config.footer?.gongan}"></span>
</a>
</span>
</div>
</div>
<halo:footer />
</footer>
<script th:inline="javascript">
window.themePluginPage = {
docs: /*[[${theme.config.docs}]]*/ {},
docsme: /*[[${theme.config.docsme}]]*/ {},
minidocs: /*[[${theme.config.minidocs}]]*/ {},
};
</script>
</body>
</html>
+130
View File
@@ -0,0 +1,130 @@
<th:block
xmlns:th="https://www.thymeleaf.org"
th:fragment="minidocsReader(kbSlug, activeDocSlug, viewBaseUrl)"
>
<button class="sidebar-toggle" id="sidebar-toggle" aria-label="切换目录">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
<span>目录</span>
</button>
<div class="sidebar-backdrop" id="sidebar-backdrop"></div>
<div
class="doc-layout minidocs-doc-layout"
th:with="knowledgeBase = ${minidocsFinder.getKnowledgeBase(kbSlug)}, docTree = ${minidocsFinder.getDocTree(kbSlug)}, currentDocSlug = ${not #strings.isEmpty(activeDocSlug) ? activeDocSlug : (docTree == null or #lists.isEmpty(docTree) ? null : docTree[0].slug)}, doc = ${not #strings.isEmpty(currentDocSlug) ? minidocsFinder.getDocBySlug(kbSlug, currentDocSlug) : null}"
>
<aside class="doc-sidebar">
<div class="minidocs-kb-heading" th:if="${knowledgeBase != null}">
<span class="minidocs-kb-cover">
<img
th:if="${not #strings.isEmpty(knowledgeBase.spec.cover)}"
th:src="${knowledgeBase.spec.cover}"
alt=""
/>
<span
class="minidocs-kb-cover-fallback"
th:unless="${not #strings.isEmpty(knowledgeBase.spec.cover)}"
th:text="${not #strings.isEmpty(knowledgeBase.spec.displayName) ? #strings.substring(knowledgeBase.spec.displayName, 0, 1) : '库'}"
></span>
</span>
<div class="minidocs-kb-info">
<a
class="doc-sidebar-kb-title"
th:href="@{${viewBaseUrl}}"
th:text="${knowledgeBase.spec.displayName}"
></a>
<div class="minidocs-kb-stats">
<span
th:if="${theme.config.minidocs?.nav_show_count != false}"
th:text="|共 ${knowledgeBase.status != null and knowledgeBase.status.docCount != null ? knowledgeBase.status.docCount : 0} 篇|"
>共 0 篇</span>
<span
th:if="${knowledgeBase.spec.updateTime != null}"
th:text="|更新于 ${#temporals.format(knowledgeBase.spec.updateTime, 'yyyy年MM月dd日 HH:mm')}|"
></span>
</div>
</div>
</div>
<nav class="doc-sidebar-nav">
<ul class="doc-nav-list">
<th:block th:each="node : ${docTree}">
<th:block th:replace="~{:: minidocsNode(node=${node})}" />
</th:block>
</ul>
</nav>
</aside>
<div class="doc-content">
<article class="doc-article" th:if="${doc != null}">
<nav class="doc-breadcrumb">
<a th:href="@{${viewBaseUrl}}" th:if="${knowledgeBase != null}" th:text="${knowledgeBase.spec.displayName}"></a>
</nav>
<header class="doc-article-header">
<h1 class="doc-article-title" th:text="${doc.spec.title}"></h1>
</header>
<div class="doc-article-meta">
<time
th:if="${doc.spec.updateTime != null}"
th:text="${#temporals.format(doc.spec.updateTime, 'yyyy-MM-dd')}"
></time>
<span th:if="${not #strings.isEmpty(doc.spec.author)}" th:text="| · ${doc.spec.author}|"></span>
</div>
<div class="doc-article-body" th:utext="${doc.spec.content}"></div>
</article>
<p class="mdocs-plugin-empty" th:unless="${doc != null}">
该知识库暂无已发布文档。
</p>
</div>
<aside class="doc-toc">
<div class="toc-title">目录</div>
<nav class="toc-list" id="toc-list"></nav>
</aside>
</div>
<script th:inline="javascript">
window.minidocsViewer = {
kbSlug: /*[[${kbSlug}]]*/ "",
viewBaseUrl: /*[[${viewBaseUrl}]]*/ "",
};
</script>
<!-- MiniDocs 文档树递归片段 -->
<th:block th:fragment="minidocsNode(node)">
<li
class="doc-nav-item"
th:classappend="${node.children != null and not #lists.isEmpty(node.children)} ? ' has-children'"
>
<div class="doc-nav-row">
<a
class="doc-nav-link"
th:href="@{${viewBaseUrl} + '?docSlug=' + node.slug}"
th:classappend="${node.slug == currentDocSlug} ? ' active'"
th:text="${node.title}"
></a>
<button
class="mdocs-nav-toggle"
type="button"
aria-label="折叠/展开"
th:if="${node.children != null and not #lists.isEmpty(node.children)}"
>
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</button>
</div>
<ul class="doc-nav-list doc-nav-children" th:if="${node.children != null and not #lists.isEmpty(node.children)}">
<th:block th:each="child : ${node.children}">
<th:block th:replace="~{:: minidocsNode(node=${child})}" />
</th:block>
</ul>
</li>
</th:block>
</th:block>
+259
View File
@@ -0,0 +1,259 @@
<!doctype html>
<html
lang="zh-CN"
xmlns:th="https://www.thymeleaf.org"
th:lang="${#locale.toLanguageTag}"
class="color-scheme-auto"
data-color-scheme="auto"
>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title th:text="|${singlePage.spec.title} - ${site.title}|">页面</title>
<script type="module" crossorigin src="/themes/theme-mdocs/assets/main-BAX9Eehy.js"></script>
<link rel="stylesheet" crossorigin href="/themes/theme-mdocs/assets/main-OuYKDL0n.css">
</head>
<body
th:style="'--radius:' + (${theme.config.global?.border_radius} ?: '12px') + ';--font-size:' + (${theme.config.global?.font_size} ?: '16px')"
th:attr="data-bg-light=${theme.config.global?.bg_color_light ?: '#ffffff'},data-bg-dark=${theme.config.global?.bg_color_dark ?: '#0d1117'},data-bg-image=${theme.config.global?.background_image},data-primary=${theme.config.global?.primary_color}"
th:with="siteLogo = ${not #strings.isEmpty(theme.config.global?.site_logo) ? theme.config.global?.site_logo : site.logo}, siteLogoDark = ${not #strings.isEmpty(theme.config.global?.site_logo_dark) ? theme.config.global?.site_logo_dark : siteLogo}, showSiteName = ${theme.config.global?.show_site_name != false}"
>
<header class="site-header">
<div
class="container header-inner"
th:with="primaryMenu = ${menuFinder.getPrimary()}"
>
<a class="site-brand" th:href="@{/}">
<img
th:if="${not #strings.isEmpty(siteLogo)}"
th:src="${siteLogo}"
th:attr="data-logo-light=${siteLogo},data-logo-dark=${siteLogoDark}"
class="brand-logo"
alt="logo"
/>
<span class="brand-name" th:if="${showSiteName}" th:text="${site.title}">源蝶工作室</span>
</a>
<nav class="primary-nav" th:if="${not #lists.isEmpty(theme.config.nav?.doc_nav_buttons)}">
<a
class="primary-nav-link"
th:each="btn : ${theme.config.nav?.doc_nav_buttons ?: T(java.util.Collections).emptyList()}"
th:href="@{${btn.url ?: '/'}}"
th:text="${btn.title}"
></a>
</nav>
<div
class="header-right"
>
<button
class="search-trigger"
id="search-trigger"
type="button"
th:if="${pluginFinder.available('PluginSearchWidget')}"
onclick="SearchWidget.open()"
aria-label="搜索"
>
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
<span class="search-trigger-text">搜索</span>
<kbd class="search-trigger-kbd">⌘K</kbd>
</button>
<button
class="mobile-menu-toggle"
id="mobile-menu-toggle"
type="button"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
aria-label="打开菜单"
aria-controls="header-menu-panel"
aria-expanded="false"
>
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</button>
<div
class="header-menu-wrap"
id="header-menu-panel"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
>
<th:block th:replace="~{header-menu :: menu}" />
</div>
<div
class="header-divider"
aria-hidden="true"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
></div>
<div class="header-actions">
<th:block
th:if="${not #lists.isEmpty(theme.config.nav?.user_buttons)}"
th:each="btn : ${theme.config.nav?.user_buttons ?: T(java.util.Collections).emptyList()}"
>
<a
class="header-action-btn header-icon-btn header-action-icon-btn"
th:if="${not #strings.isEmpty(btn.svg)}"
th:href="@{${btn.url ?: '#'}}"
th:title="${btn.label}"
th:style="${not #strings.isEmpty(btn.color)} ? '--icon-color:' + btn.color : ''"
>
<span class="header-action-icon" th:utext="${btn.svg}"></span>
</a>
</th:block>
<button
class="theme-toggle header-icon-btn"
id="theme-toggle"
aria-label="切换明暗主题"
>
<svg class="icon-sun" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
<svg class="icon-moon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
</svg>
</button>
<th:block th:replace="~{user-menu :: user-menu}" />
</div>
</div>
<div
class="mobile-menu-backdrop"
id="mobile-menu-backdrop"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
></div>
</div>
</header>
<main class="site-main">
<div class="container">
<article class="article">
<header class="article-header">
<h1 class="article-title" th:text="${singlePage.spec.title}"></h1>
</header>
<div class="article-content" th:utext="${singlePage.content.content}"></div>
</article>
</div>
</main>
<footer
class="site-footer"
th:with="separateFooterLogo = ${theme.config.footer?.footer_separate_logo == true}, footerLogo = ${separateFooterLogo ? (not #strings.isEmpty(theme.config.footer?.footer_logo) ? theme.config.footer?.footer_logo : siteLogo) : siteLogo}, footerLogoDark = ${separateFooterLogo ? (not #strings.isEmpty(theme.config.footer?.footer_logo_dark) ? theme.config.footer?.footer_logo_dark : (not #strings.isEmpty(theme.config.footer?.footer_logo) ? theme.config.footer?.footer_logo : siteLogoDark)) : siteLogoDark}, showFooterName = ${theme.config.footer?.show_footer_name != false}"
>
<div class="footer-container">
<th:block
th:if="${not #lists.isEmpty(theme.config.footer?.footer_menus)}"
>
<div class="footer-menu-columns">
<th:block
th:each="menuName : ${theme.config.footer?.footer_menus}"
>
<th:block
th:with="footerMenu = ${not #strings.isEmpty(menuName) ? menuFinder.getByName(menuName) : null}"
>
<div
class="footer-menu-col"
th:if="${footerMenu != null and not #lists.isEmpty(footerMenu.menuItems)}"
>
<h4 th:text="${footerMenu.spec.displayName}"></h4>
<ul>
<li th:each="menuItem : ${footerMenu.menuItems}">
<a
th:href="@{${menuItem.status.href}}"
th:text="${menuItem.status.displayName}"
th:target="${menuItem.spec.target?.value}"
th:rel="${menuItem.spec.target?.value == '_blank'} ? 'noopener' : null"
></a>
</li>
</ul>
</div>
</th:block>
</th:block>
</div>
</th:block>
<div class="footer-bottom">
<a
class="footer-bottom-brand"
th:href="@{/}"
th:if="${not #strings.isEmpty(footerLogo) or showFooterName}"
>
<img
th:if="${not #strings.isEmpty(footerLogo)}"
th:src="${footerLogo}"
th:attr="data-logo-light=${footerLogo},data-logo-dark=${footerLogoDark}"
class="footer-bottom-logo"
alt="logo"
/>
<span
class="footer-bottom-name"
th:if="${showFooterName}"
th:text="${site.title}"
></span>
</a>
<span
class="footer-copyright"
th:if="${not #strings.isEmpty(theme.config.footer?.copyright)}"
th:text="${theme.config.footer?.copyright}"
></span>
<span
class="footer-beian"
th:if="${not #strings.isEmpty(theme.config.footer?.icp) or not #strings.isEmpty(theme.config.footer?.gongan)}"
>
<a
th:if="${not #strings.isEmpty(theme.config.footer?.icp)}"
th:href="${theme.config.footer?.icp_link ?: 'https://beian.miit.gov.cn/'}"
target="_blank"
rel="noopener"
th:text="${theme.config.footer?.icp}"
></a>
<a
class="footer-gongan"
th:if="${not #strings.isEmpty(theme.config.footer?.gongan)}"
th:href="${theme.config.footer?.gongan_link ?: 'https://beian.mps.gov.cn/#/query/webSearch'}"
target="_blank"
rel="noopener"
>
<img
th:src="@{/assets/img/gongan.svg}"
class="footer-gongan-icon"
alt=""
/>
<span th:text="${theme.config.footer?.gongan}"></span>
</a>
</span>
</div>
</div>
<halo:footer />
</footer>
<script th:inline="javascript">
window.themePluginPage = {
docs: /*[[${theme.config.docs}]]*/ {},
docsme: /*[[${theme.config.docsme}]]*/ {},
minidocs: /*[[${theme.config.minidocs}]]*/ {},
};
</script>
</body>
</html>
+492
View File
@@ -0,0 +1,492 @@
<!doctype html>
<html
lang="zh-CN"
xmlns:th="https://www.thymeleaf.org"
th:lang="${#locale.toLanguageTag}"
class="color-scheme-auto"
data-color-scheme="auto"
>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title th:text="|${post.spec.title} - ${site.title}|">文档</title>
<script type="module" crossorigin src="/themes/theme-mdocs/assets/main-BAX9Eehy.js"></script>
<link rel="stylesheet" crossorigin href="/themes/theme-mdocs/assets/main-OuYKDL0n.css">
</head>
<body
th:style="'--radius:' + (${theme.config.global?.border_radius} ?: '12px') + ';--font-size:' + (${theme.config.global?.font_size} ?: '16px')"
th:attr="data-bg-light=${theme.config.global?.bg_color_light ?: '#ffffff'},data-bg-dark=${theme.config.global?.bg_color_dark ?: '#0d1117'},data-bg-image=${theme.config.global?.background_image},data-primary=${theme.config.global?.primary_color}"
th:with="siteLogo = ${not #strings.isEmpty(theme.config.global?.site_logo) ? theme.config.global?.site_logo : site.logo}, siteLogoDark = ${not #strings.isEmpty(theme.config.global?.site_logo_dark) ? theme.config.global?.site_logo_dark : siteLogo}, showSiteName = ${theme.config.global?.show_site_name != false}"
>
<header class="site-header">
<div
class="container header-inner"
th:with="primaryMenu = ${menuFinder.getPrimary()}"
>
<a class="site-brand" th:href="@{/}">
<img
th:if="${not #strings.isEmpty(siteLogo)}"
th:src="${siteLogo}"
th:attr="data-logo-light=${siteLogo},data-logo-dark=${siteLogoDark}"
class="brand-logo"
alt="logo"
/>
<span class="brand-name" th:if="${showSiteName}" th:text="${site.title}">源蝶工作室</span>
</a>
<nav class="primary-nav" th:if="${not #lists.isEmpty(theme.config.nav?.doc_nav_buttons)}">
<a
class="primary-nav-link"
th:each="btn : ${theme.config.nav?.doc_nav_buttons ?: T(java.util.Collections).emptyList()}"
th:href="@{${btn.url ?: '/'}}"
th:text="${btn.title}"
></a>
</nav>
<div
class="header-right"
>
<button
class="search-trigger"
id="search-trigger"
type="button"
th:if="${pluginFinder.available('PluginSearchWidget')}"
onclick="SearchWidget.open()"
aria-label="搜索"
>
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
<span class="search-trigger-text">搜索</span>
<kbd class="search-trigger-kbd">⌘K</kbd>
</button>
<button
class="mobile-menu-toggle"
id="mobile-menu-toggle"
type="button"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
aria-label="打开菜单"
aria-controls="header-menu-panel"
aria-expanded="false"
>
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</button>
<div
class="header-menu-wrap"
id="header-menu-panel"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
>
<th:block th:replace="~{header-menu :: menu}" />
</div>
<div
class="header-divider"
aria-hidden="true"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
></div>
<div class="header-actions">
<th:block
th:if="${not #lists.isEmpty(theme.config.nav?.user_buttons)}"
th:each="btn : ${theme.config.nav?.user_buttons ?: T(java.util.Collections).emptyList()}"
>
<a
class="header-action-btn header-icon-btn header-action-icon-btn"
th:if="${not #strings.isEmpty(btn.svg)}"
th:href="@{${btn.url ?: '#'}}"
th:title="${btn.label}"
th:style="${not #strings.isEmpty(btn.color)} ? '--icon-color:' + btn.color : ''"
>
<span class="header-action-icon" th:utext="${btn.svg}"></span>
</a>
</th:block>
<button
class="theme-toggle header-icon-btn"
id="theme-toggle"
aria-label="切换明暗主题"
>
<svg class="icon-sun" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
<svg class="icon-moon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
</svg>
</button>
<th:block th:replace="~{user-menu :: user-menu}" />
</div>
</div>
<div
class="mobile-menu-backdrop"
id="mobile-menu-backdrop"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
></div>
</div>
</header>
<main class="site-main">
<th:block
th:with="currentCat = ${#lists.isEmpty(post.spec.categories) ? '' : post.spec.categories[0]},
breadcrumbs = ${#strings.isEmpty(currentCat) ? null : categoryFinder.getBreadcrumbs(currentCat)},
docGroup = ${breadcrumbs != null && !#lists.isEmpty(breadcrumbs) ? breadcrumbs[0] : null},
docEnabled = ${not #lists.isEmpty(theme.config.docs?.doc_groups) and docGroup != null and #lists.contains(theme.config.docs?.doc_groups, docGroup.metadata.name)}"
>
<th:block th:if="${docEnabled}">
<button class="sidebar-toggle" id="sidebar-toggle" aria-label="切换目录">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
<span>目录</span>
</button>
<div class="sidebar-backdrop" id="sidebar-backdrop"></div>
<div
class="doc-layout"
th:with="currentCat = ${#lists.isEmpty(post.spec.categories) ? '' : post.spec.categories[0]}, breadcrumbs = ${#strings.isEmpty(currentCat) ? null : categoryFinder.getBreadcrumbs(currentCat)}, docGroup = ${breadcrumbs != null && !#lists.isEmpty(breadcrumbs) ? breadcrumbs[0] : null}"
th:attr="data-current-group=${docGroup != null ? docGroup.metadata.name : ''}, data-default-expanded=${theme.config.docs.sidebar_default_expanded}"
>
<aside class="doc-sidebar">
<div
class="doc-sidebar-header"
th:if="${not (theme.config.docs?.doc_groups != null and not #lists.isEmpty(theme.config.docs?.doc_groups) and docGroup != null and #lists.contains(theme.config.docs?.doc_groups, docGroup.metadata.name)) and theme.config.global?.show_site_name != false}"
th:text="${site.title}"
></div>
<div
class="mdocs-docsme-sidebar-controls minidocs-toolbar docgroup-toolbar"
th:if="${theme.config.docs?.doc_groups != null and not #lists.isEmpty(theme.config.docs?.doc_groups) and docGroup != null and #lists.contains(theme.config.docs?.doc_groups, docGroup.metadata.name)}"
>
<button
type="button"
class="mdocs-docsme-back"
onclick="history.back()"
>
<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="19" y1="12" x2="5" y2="12"></line>
<polyline points="12 19 5 12 12 5"></polyline>
</svg>
<span>返回</span>
</button>
<div class="minidocs-toolbar-actions">
<button
type="button"
class="minidocs-toolbar-btn"
onclick="navigator.clipboard?.writeText(window.location.href);this.textContent='已复制'"
>
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="19" r="3"></circle>
<line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line>
</svg>
<span>分享</span>
</button>
<span class="minidocs-view-count" title="浏览次数">
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
<circle cx="12" cy="12" r="3"></circle>
</svg>
<span th:text="${post.stats != null and post.stats.visit != null ? post.stats.visit : 0}">0</span>
<span>浏览</span>
</span>
</div>
</div>
<div
class="minidocs-kb-heading"
th:if="${theme.config.docs?.doc_groups != null and not #lists.isEmpty(theme.config.docs?.doc_groups) and docGroup != null and #lists.contains(theme.config.docs?.doc_groups, docGroup.metadata.name)}"
>
<span class="minidocs-kb-cover">
<img
th:if="${not #strings.isEmpty(docGroup.spec.cover)}"
th:src="${docGroup.spec.cover}"
alt=""
/>
<span
class="minidocs-kb-cover-fallback"
th:unless="${not #strings.isEmpty(docGroup.spec.cover)}"
th:text="${not #strings.isEmpty(docGroup.spec.displayName) ? #strings.substring(docGroup.spec.displayName, 0, 1) : '文'}"
></span>
</span>
<div class="minidocs-kb-info">
<div class="doc-sidebar-kb-title" th:text="${docGroup.spec.displayName}"></div>
<div class="minidocs-kb-stats">
<span
th:if="${theme.config.docs?.sidebar_show_count != false}"
th:text="|共 ${docGroup.status != null and docGroup.status.postCount != null ? docGroup.status.postCount : 0} 篇文章|"
>共 0 篇文章</span>
</div>
</div>
</div>
<nav
class="doc-sidebar-nav"
>
<th:block
th:with="docTree = ${categoryFinder.listAsTree(docGroup.metadata.name)},
docChildren = ${#lists.isEmpty(docTree) ? null : docTree[0].children},
rootPosts = ${postFinder.listByCategory(1, 999, docGroup.metadata.name).items}"
>
<ul class="doc-nav-list">
<li
class="doc-nav-post"
th:each="rootPost : ${rootPosts}"
th:if="${#lists.contains(rootPost.spec.categories, docGroup.metadata.name)}"
>
<a
th:href="@{${rootPost.status.permalink}}"
th:text="${rootPost.spec.title}"
th:classappend="${#strings.equals(rootPost.metadata.name, post.metadata.name)} ? ' active'"
></a>
</li>
</ul>
<th:block
th:replace="~{sidebar :: sidebar(${docChildren}, ${currentCat}, ${post.metadata.name})}"
/>
</th:block>
</nav>
</aside>
<div class="doc-content">
<article class="doc-article">
<nav class="doc-breadcrumb" th:if="${not #lists.isEmpty(breadcrumbs)}">
<a
th:each="bc,stat : ${breadcrumbs}"
th:href="@{${bc.status.permalink}}"
><span th:text="${bc.spec.displayName}"></span></a>
</nav>
<header class="doc-article-header">
<h1 class="doc-article-title" th:text="${post.spec.title}"></h1>
</header>
<div class="doc-article-meta">
<time th:text="${#temporals.format(post.spec.publishTime, 'yyyy-MM-dd')}"></time>
</div>
<div class="filter-bar" th:if="${theme.config.docs.show_filter}">
<input type="search" class="filter-input" placeholder="筛选本页内容" data-filter-target=".doc-article-body" />
</div>
<div class="doc-article-body" th:utext="${post.content.content}"></div>
<div class="article-tags" th:if="${not #lists.isEmpty(post.tags)}">
<a
th:each="tag : ${post.tags}"
th:href="@{${tag.status.permalink}}"
th:text="${tag.spec.displayName}"
class="tag"
></a>
</div>
<nav
class="doc-pager"
th:with="cursor = ${postFinder.cursorByCategory(post.metadata.name)}"
>
<a
class="doc-pager-prev"
th:if="${cursor.hasPrevious()}"
th:href="@{${cursor.previous.status.permalink}}"
th:text="|← ${cursor.previous.spec.title}|"
></a>
<a
class="doc-pager-next"
th:if="${cursor.hasNext()}"
th:href="@{${cursor.next.status.permalink}}"
th:text="|${cursor.next.spec.title} →|"
></a>
</nav>
</article>
<div id="page-md-data" hidden th:text="${post.content.raw}"></div>
<p class="doc-footer-text" th:if="${not #strings.isEmpty(theme.config.docs.doc_footer_text)}" th:text="${theme.config.docs.doc_footer_text}"></p>
</div>
<aside class="doc-toc" id="doc-toc" th:if="${theme.config.docs.show_toc}">
<div class="toc-title">目录</div>
<nav class="toc-list" id="toc-list"></nav>
</aside>
</div>
</th:block>
<!-- 普通文章页面:非文档组文章 -->
<div class="doc-layout article-normal-layout" th:unless="${docEnabled}">
<div class="doc-content">
<article class="doc-article">
<div class="doc-article-cover" th:if="${not #strings.isEmpty(post.spec.cover)}">
<img th:src="${post.spec.cover}" th:alt="${post.spec.title}" />
</div>
<header class="doc-article-header">
<h1 class="doc-article-title" th:text="${post.spec.title}"></h1>
</header>
<div class="doc-article-meta">
<time
th:if="${post.spec.publishTime != null}"
th:text="${#temporals.format(post.spec.publishTime, 'yyyy-MM-dd')}"
></time>
<span th:if="${not #strings.isEmpty(post.spec.owner)}" th:text="| · ${post.spec.owner}|"></span>
<span
class="doc-article-visits"
th:if="${post.stats != null and post.stats.visit != null}"
th:text="| · ${post.stats.visit} 次浏览|"
></span>
</div>
<div class="doc-article-body" th:utext="${post.content.content}"></div>
<nav
class="doc-pager"
th:with="cursor = ${postFinder.cursorByCategory(post.metadata.name)}"
>
<a
class="doc-pager-prev"
th:if="${cursor.hasPrevious()}"
th:href="@{${cursor.previous.status.permalink}}"
th:text="|← ${cursor.previous.spec.title}|"
></a>
<a
class="doc-pager-next"
th:if="${cursor.hasNext()}"
th:href="@{${cursor.next.status.permalink}}"
th:text="|${cursor.next.spec.title} →|"
></a>
</nav>
</article>
<div id="page-md-data" hidden th:text="${post.content.raw}"></div>
</div>
<aside class="doc-toc" id="doc-toc" th:if="${theme.config.docs?.show_toc != false}">
<div class="toc-title">目录</div>
<nav class="toc-list" id="toc-list"></nav>
</aside>
</div>
</th:block>
</main>
<footer
class="site-footer"
th:with="separateFooterLogo = ${theme.config.footer?.footer_separate_logo == true}, footerLogo = ${separateFooterLogo ? (not #strings.isEmpty(theme.config.footer?.footer_logo) ? theme.config.footer?.footer_logo : siteLogo) : siteLogo}, footerLogoDark = ${separateFooterLogo ? (not #strings.isEmpty(theme.config.footer?.footer_logo_dark) ? theme.config.footer?.footer_logo_dark : (not #strings.isEmpty(theme.config.footer?.footer_logo) ? theme.config.footer?.footer_logo : siteLogoDark)) : siteLogoDark}, showFooterName = ${theme.config.footer?.show_footer_name != false}"
>
<div class="footer-container">
<th:block
th:if="${not #lists.isEmpty(theme.config.footer?.footer_menus)}"
>
<div class="footer-menu-columns">
<th:block
th:each="menuName : ${theme.config.footer?.footer_menus}"
>
<th:block
th:with="footerMenu = ${not #strings.isEmpty(menuName) ? menuFinder.getByName(menuName) : null}"
>
<div
class="footer-menu-col"
th:if="${footerMenu != null and not #lists.isEmpty(footerMenu.menuItems)}"
>
<h4 th:text="${footerMenu.spec.displayName}"></h4>
<ul>
<li th:each="menuItem : ${footerMenu.menuItems}">
<a
th:href="@{${menuItem.status.href}}"
th:text="${menuItem.status.displayName}"
th:target="${menuItem.spec.target?.value}"
th:rel="${menuItem.spec.target?.value == '_blank'} ? 'noopener' : null"
></a>
</li>
</ul>
</div>
</th:block>
</th:block>
</div>
</th:block>
<div class="footer-bottom">
<a
class="footer-bottom-brand"
th:href="@{/}"
th:if="${not #strings.isEmpty(footerLogo) or showFooterName}"
>
<img
th:if="${not #strings.isEmpty(footerLogo)}"
th:src="${footerLogo}"
th:attr="data-logo-light=${footerLogo},data-logo-dark=${footerLogoDark}"
class="footer-bottom-logo"
alt="logo"
/>
<span
class="footer-bottom-name"
th:if="${showFooterName}"
th:text="${site.title}"
></span>
</a>
<span
class="footer-copyright"
th:if="${not #strings.isEmpty(theme.config.footer?.copyright)}"
th:text="${theme.config.footer?.copyright}"
></span>
<span
class="footer-beian"
th:if="${not #strings.isEmpty(theme.config.footer?.icp) or not #strings.isEmpty(theme.config.footer?.gongan)}"
>
<a
th:if="${not #strings.isEmpty(theme.config.footer?.icp)}"
th:href="${theme.config.footer?.icp_link ?: 'https://beian.miit.gov.cn/'}"
target="_blank"
rel="noopener"
th:text="${theme.config.footer?.icp}"
></a>
<a
class="footer-gongan"
th:if="${not #strings.isEmpty(theme.config.footer?.gongan)}"
th:href="${theme.config.footer?.gongan_link ?: 'https://beian.mps.gov.cn/#/query/webSearch'}"
target="_blank"
rel="noopener"
>
<img
th:src="@{/assets/img/gongan.svg}"
class="footer-gongan-icon"
alt=""
/>
<span th:text="${theme.config.footer?.gongan}"></span>
</a>
</span>
</div>
</div>
<halo:footer />
</footer>
<script th:inline="javascript">
window.themePluginPage = {
docs: /*[[${theme.config.docs}]]*/ {},
docsme: /*[[${theme.config.docsme}]]*/ {},
minidocs: /*[[${theme.config.minidocs}]]*/ {},
};
</script>
</body>
</html>
+320
View File
@@ -0,0 +1,320 @@
<!doctype html>
<html
lang="zh-CN"
xmlns:th="https://www.thymeleaf.org"
th:lang="${#locale.toLanguageTag}"
class="color-scheme-auto"
data-color-scheme="auto"
>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title th:text="|${post.spec.title} - ${site.title}|">文档</title>
<script type="module" crossorigin src="/themes/theme-mdocs/assets/main-BAX9Eehy.js"></script>
<link rel="stylesheet" crossorigin href="/themes/theme-mdocs/assets/main-OuYKDL0n.css">
</head>
<body
th:style="'--radius:' + (${theme.config.global?.border_radius} ?: '12px') + ';--font-size:' + (${theme.config.global?.font_size} ?: '16px')"
th:attr="data-bg-light=${theme.config.global?.bg_color_light ?: '#ffffff'},data-bg-dark=${theme.config.global?.bg_color_dark ?: '#0d1117'},data-bg-image=${theme.config.global?.background_image},data-primary=${theme.config.global?.primary_color}"
th:with="siteLogo = ${not #strings.isEmpty(theme.config.global?.site_logo) ? theme.config.global?.site_logo : site.logo}, siteLogoDark = ${not #strings.isEmpty(theme.config.global?.site_logo_dark) ? theme.config.global?.site_logo_dark : siteLogo}, showSiteName = ${theme.config.global?.show_site_name != false}"
>
<header class="site-header">
<div
class="container header-inner"
th:with="primaryMenu = ${menuFinder.getPrimary()}"
>
<a class="site-brand" th:href="@{/}">
<img
th:if="${not #strings.isEmpty(siteLogo)}"
th:src="${siteLogo}"
th:attr="data-logo-light=${siteLogo},data-logo-dark=${siteLogoDark}"
class="brand-logo"
alt="logo"
/>
<span class="brand-name" th:if="${showSiteName}" th:text="${site.title}">源蝶工作室</span>
</a>
<nav class="primary-nav" th:if="${not #lists.isEmpty(theme.config.nav?.doc_nav_buttons)}">
<a
class="primary-nav-link"
th:each="btn : ${theme.config.nav?.doc_nav_buttons ?: T(java.util.Collections).emptyList()}"
th:href="@{${btn.url ?: '/'}}"
th:text="${btn.title}"
></a>
</nav>
<div
class="header-right"
>
<button
class="search-trigger"
id="search-trigger"
type="button"
th:if="${pluginFinder.available('PluginSearchWidget')}"
onclick="SearchWidget.open()"
aria-label="搜索"
>
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
<span class="search-trigger-text">搜索</span>
<kbd class="search-trigger-kbd">⌘K</kbd>
</button>
<button
class="mobile-menu-toggle"
id="mobile-menu-toggle"
type="button"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
aria-label="打开菜单"
aria-controls="header-menu-panel"
aria-expanded="false"
>
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</button>
<div
class="header-menu-wrap"
id="header-menu-panel"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
>
<th:block th:replace="~{header-menu :: menu}" />
</div>
<div
class="header-divider"
aria-hidden="true"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
></div>
<div class="header-actions">
<th:block
th:if="${not #lists.isEmpty(theme.config.nav?.user_buttons)}"
th:each="btn : ${theme.config.nav?.user_buttons ?: T(java.util.Collections).emptyList()}"
>
<a
class="header-action-btn header-icon-btn header-action-icon-btn"
th:if="${not #strings.isEmpty(btn.svg)}"
th:href="@{${btn.url ?: '#'}}"
th:title="${btn.label}"
th:style="${not #strings.isEmpty(btn.color)} ? '--icon-color:' + btn.color : ''"
>
<span class="header-action-icon" th:utext="${btn.svg}"></span>
</a>
</th:block>
<button
class="theme-toggle header-icon-btn"
id="theme-toggle"
aria-label="切换明暗主题"
>
<svg class="icon-sun" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
<svg class="icon-moon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
</svg>
</button>
<th:block th:replace="~{user-menu :: user-menu}" />
</div>
</div>
<div
class="mobile-menu-backdrop"
id="mobile-menu-backdrop"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
></div>
</div>
</header>
<main class="site-main">
<button class="sidebar-toggle" id="sidebar-toggle" aria-label="切换目录">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
<span>目录</span>
</button>
<div class="sidebar-backdrop" id="sidebar-backdrop"></div>
<div
class="doc-layout"
th:with="currentCat = ${#lists.isEmpty(post.spec.categories) ? '' : post.spec.categories[0]}, breadcrumbs = ${#strings.isEmpty(currentCat) ? null : categoryFinder.getBreadcrumbs(currentCat)}, docGroup = ${breadcrumbs != null && !#lists.isEmpty(breadcrumbs) ? breadcrumbs[0] : null}"
th:attr="data-current-group=${docGroup != null ? docGroup.metadata.name : ''}, data-default-expanded=${theme.config.docs.sidebar_default_expanded}"
>
<aside class="doc-sidebar">
<div
class="doc-sidebar-header"
th:if="${theme.config.global?.show_site_name != false}"
th:text="${site.title}"
>源蝶工作室</div>
<nav
class="doc-sidebar-nav"
th:with="tree = ${docGroup != null ? categoryFinder.listAsTree(docGroup.metadata.name) : categoryFinder.listAsTree()}"
>
<th:block th:replace="~{sidebar :: sidebar(${tree}, ${currentCat}, ${post.metadata.name})}" />
</nav>
</aside>
<div class="doc-content">
<article class="doc-article">
<header class="doc-article-header">
<h1 class="doc-article-title" th:text="${post.spec.title}"></h1>
<div class="doc-article-meta">
<span th:if="${not #lists.isEmpty(post.categories)}">
<a
th:each="cat : ${post.categories}"
th:href="@{${cat.status.permalink}}"
th:text="${cat.spec.displayName}"
class="doc-article-cat"
></a>
</span>
</div>
</header>
<div class="doc-article-body" th:utext="${post.content.content}"></div>
<nav
class="doc-pager"
th:with="cursor = ${postFinder.cursorByCategory(post.metadata.name)}"
>
<a
class="doc-pager-prev"
th:if="${cursor.hasPrevious()}"
th:href="@{${cursor.previous.status.permalink}}"
th:text="|← ${cursor.previous.spec.title}|"
></a>
<a
class="doc-pager-next"
th:if="${cursor.hasNext()}"
th:href="@{${cursor.next.status.permalink}}"
th:text="|${cursor.next.spec.title} →|"
></a>
</nav>
</article>
<p class="doc-footer-text" th:if="${not #strings.isEmpty(theme.config.docs.doc_footer_text)}" th:text="${theme.config.docs.doc_footer_text}"></p>
</div>
</div>
</main>
<footer
class="site-footer"
th:with="separateFooterLogo = ${theme.config.footer?.footer_separate_logo == true}, footerLogo = ${separateFooterLogo ? (not #strings.isEmpty(theme.config.footer?.footer_logo) ? theme.config.footer?.footer_logo : siteLogo) : siteLogo}, footerLogoDark = ${separateFooterLogo ? (not #strings.isEmpty(theme.config.footer?.footer_logo_dark) ? theme.config.footer?.footer_logo_dark : (not #strings.isEmpty(theme.config.footer?.footer_logo) ? theme.config.footer?.footer_logo : siteLogoDark)) : siteLogoDark}, showFooterName = ${theme.config.footer?.show_footer_name != false}"
>
<div class="footer-container">
<th:block
th:if="${not #lists.isEmpty(theme.config.footer?.footer_menus)}"
>
<div class="footer-menu-columns">
<th:block
th:each="menuName : ${theme.config.footer?.footer_menus}"
>
<th:block
th:with="footerMenu = ${not #strings.isEmpty(menuName) ? menuFinder.getByName(menuName) : null}"
>
<div
class="footer-menu-col"
th:if="${footerMenu != null and not #lists.isEmpty(footerMenu.menuItems)}"
>
<h4 th:text="${footerMenu.spec.displayName}"></h4>
<ul>
<li th:each="menuItem : ${footerMenu.menuItems}">
<a
th:href="@{${menuItem.status.href}}"
th:text="${menuItem.status.displayName}"
th:target="${menuItem.spec.target?.value}"
th:rel="${menuItem.spec.target?.value == '_blank'} ? 'noopener' : null"
></a>
</li>
</ul>
</div>
</th:block>
</th:block>
</div>
</th:block>
<div class="footer-bottom">
<a
class="footer-bottom-brand"
th:href="@{/}"
th:if="${not #strings.isEmpty(footerLogo) or showFooterName}"
>
<img
th:if="${not #strings.isEmpty(footerLogo)}"
th:src="${footerLogo}"
th:attr="data-logo-light=${footerLogo},data-logo-dark=${footerLogoDark}"
class="footer-bottom-logo"
alt="logo"
/>
<span
class="footer-bottom-name"
th:if="${showFooterName}"
th:text="${site.title}"
></span>
</a>
<span
class="footer-copyright"
th:if="${not #strings.isEmpty(theme.config.footer?.copyright)}"
th:text="${theme.config.footer?.copyright}"
></span>
<span
class="footer-beian"
th:if="${not #strings.isEmpty(theme.config.footer?.icp) or not #strings.isEmpty(theme.config.footer?.gongan)}"
>
<a
th:if="${not #strings.isEmpty(theme.config.footer?.icp)}"
th:href="${theme.config.footer?.icp_link ?: 'https://beian.miit.gov.cn/'}"
target="_blank"
rel="noopener"
th:text="${theme.config.footer?.icp}"
></a>
<a
class="footer-gongan"
th:if="${not #strings.isEmpty(theme.config.footer?.gongan)}"
th:href="${theme.config.footer?.gongan_link ?: 'https://beian.mps.gov.cn/#/query/webSearch'}"
target="_blank"
rel="noopener"
>
<img
th:src="@{/assets/img/gongan.svg}"
class="footer-gongan-icon"
alt=""
/>
<span th:text="${theme.config.footer?.gongan}"></span>
</a>
</span>
</div>
</div>
<halo:footer />
</footer>
<script th:inline="javascript">
window.themePluginPage = {
docs: /*[[${theme.config.docs}]]*/ {},
docsme: /*[[${theme.config.docsme}]]*/ {},
minidocs: /*[[${theme.config.minidocs}]]*/ {},
};
</script>
</body>
</html>
+53
View File
@@ -0,0 +1,53 @@
<ul
class="doc-nav-list"
xmlns:th="https://www.thymeleaf.org"
th:fragment="sidebar(nodes, currentName, currentPostName)"
>
<li
class="doc-nav-item"
th:each="node : ${nodes}"
th:classappend="${#lists.isEmpty(node.children)} ? '' : ' has-children'"
>
<div class="doc-nav-row">
<a
class="doc-nav-link"
th:href="@{${node.status.permalink}}"
th:text="${node.spec.displayName}"
th:classappend="${#strings.equals(node.metadata.name, currentName)} ? ' active'"
></a>
<button
class="mdocs-nav-toggle"
type="button"
th:if="${not #lists.isEmpty(node.children)}"
aria-label="展开或折叠子分类"
>
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</button>
</div>
<div class="doc-nav-children">
<ul
class="doc-nav-posts"
th:with="catPosts = ${postFinder.listByCategory(1, 999, node.metadata.name)}"
>
<li
class="doc-nav-post"
th:each="post : ${catPosts.items}"
th:if="${#lists.contains(post.spec.categories, node.metadata.name)}"
>
<a
th:href="@{${post.status.permalink}}"
th:text="${post.spec.title}"
th:classappend="${#strings.equals(post.metadata.name, currentPostName)} ? ' active'"
></a>
</li>
</ul>
<th:block th:if="${not #lists.isEmpty(node.children)}">
<th:block th:replace="~{sidebar :: sidebar(${node.children}, ${currentName}, ${currentPostName})}" />
</th:block>
</div>
</li>
</ul>
+265
View File
@@ -0,0 +1,265 @@
<!doctype html>
<html
lang="zh-CN"
xmlns:th="https://www.thymeleaf.org"
th:lang="${#locale.toLanguageTag}"
class="color-scheme-auto"
data-color-scheme="auto"
>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title th:text="|${tag.spec.displayName} - ${site.title}|">标签归档</title>
<script type="module" crossorigin src="/themes/theme-mdocs/assets/main-BAX9Eehy.js"></script>
<link rel="stylesheet" crossorigin href="/themes/theme-mdocs/assets/main-OuYKDL0n.css">
</head>
<body
th:style="'--radius:' + (${theme.config.global?.border_radius} ?: '12px') + ';--font-size:' + (${theme.config.global?.font_size} ?: '16px')"
th:attr="data-bg-light=${theme.config.global?.bg_color_light ?: '#ffffff'},data-bg-dark=${theme.config.global?.bg_color_dark ?: '#0d1117'},data-bg-image=${theme.config.global?.background_image},data-primary=${theme.config.global?.primary_color}"
th:with="siteLogo = ${not #strings.isEmpty(theme.config.global?.site_logo) ? theme.config.global?.site_logo : site.logo}, siteLogoDark = ${not #strings.isEmpty(theme.config.global?.site_logo_dark) ? theme.config.global?.site_logo_dark : siteLogo}, showSiteName = ${theme.config.global?.show_site_name != false}"
>
<header class="site-header">
<div
class="container header-inner"
th:with="primaryMenu = ${menuFinder.getPrimary()}"
>
<a class="site-brand" th:href="@{/}">
<img
th:if="${not #strings.isEmpty(siteLogo)}"
th:src="${siteLogo}"
th:attr="data-logo-light=${siteLogo},data-logo-dark=${siteLogoDark}"
class="brand-logo"
alt="logo"
/>
<span class="brand-name" th:if="${showSiteName}" th:text="${site.title}">源蝶工作室</span>
</a>
<nav class="primary-nav" th:if="${not #lists.isEmpty(theme.config.nav?.doc_nav_buttons)}">
<a
class="primary-nav-link"
th:each="btn : ${theme.config.nav?.doc_nav_buttons ?: T(java.util.Collections).emptyList()}"
th:href="@{${btn.url ?: '/'}}"
th:text="${btn.title}"
></a>
</nav>
<div
class="header-right"
>
<button
class="search-trigger"
id="search-trigger"
type="button"
th:if="${pluginFinder.available('PluginSearchWidget')}"
onclick="SearchWidget.open()"
aria-label="搜索"
>
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
<span class="search-trigger-text">搜索</span>
<kbd class="search-trigger-kbd">⌘K</kbd>
</button>
<button
class="mobile-menu-toggle"
id="mobile-menu-toggle"
type="button"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
aria-label="打开菜单"
aria-controls="header-menu-panel"
aria-expanded="false"
>
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</button>
<div
class="header-menu-wrap"
id="header-menu-panel"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
>
<th:block th:replace="~{header-menu :: menu}" />
</div>
<div
class="header-divider"
aria-hidden="true"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
></div>
<div class="header-actions">
<th:block
th:if="${not #lists.isEmpty(theme.config.nav?.user_buttons)}"
th:each="btn : ${theme.config.nav?.user_buttons ?: T(java.util.Collections).emptyList()}"
>
<a
class="header-action-btn header-icon-btn header-action-icon-btn"
th:if="${not #strings.isEmpty(btn.svg)}"
th:href="@{${btn.url ?: '#'}}"
th:title="${btn.label}"
th:style="${not #strings.isEmpty(btn.color)} ? '--icon-color:' + btn.color : ''"
>
<span class="header-action-icon" th:utext="${btn.svg}"></span>
</a>
</th:block>
<button
class="theme-toggle header-icon-btn"
id="theme-toggle"
aria-label="切换明暗主题"
>
<svg class="icon-sun" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
<svg class="icon-moon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
</svg>
</button>
<th:block th:replace="~{user-menu :: user-menu}" />
</div>
</div>
<div
class="mobile-menu-backdrop"
id="mobile-menu-backdrop"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
></div>
</div>
</header>
<main class="site-main">
<div class="container page-wrap">
<h1 class="page-title" th:text="${tag.spec.displayName}"></h1>
<ul class="post-list">
<!-- Partial error: Could not load post-card.html -->
</ul>
<nav class="pagination" th:if="${posts.hasPrevious() || posts.hasNext()}">
<a th:if="${posts.hasPrevious()}" th:href="@{${posts.prevUrl}}">上一页</a>
<span th:text="|${posts.page} / ${posts.totalPages}|"></span>
<a th:if="${posts.hasNext()}" th:href="@{${posts.nextUrl}}">下一页</a>
</nav>
</div>
</main>
<footer
class="site-footer"
th:with="separateFooterLogo = ${theme.config.footer?.footer_separate_logo == true}, footerLogo = ${separateFooterLogo ? (not #strings.isEmpty(theme.config.footer?.footer_logo) ? theme.config.footer?.footer_logo : siteLogo) : siteLogo}, footerLogoDark = ${separateFooterLogo ? (not #strings.isEmpty(theme.config.footer?.footer_logo_dark) ? theme.config.footer?.footer_logo_dark : (not #strings.isEmpty(theme.config.footer?.footer_logo) ? theme.config.footer?.footer_logo : siteLogoDark)) : siteLogoDark}, showFooterName = ${theme.config.footer?.show_footer_name != false}"
>
<div class="footer-container">
<th:block
th:if="${not #lists.isEmpty(theme.config.footer?.footer_menus)}"
>
<div class="footer-menu-columns">
<th:block
th:each="menuName : ${theme.config.footer?.footer_menus}"
>
<th:block
th:with="footerMenu = ${not #strings.isEmpty(menuName) ? menuFinder.getByName(menuName) : null}"
>
<div
class="footer-menu-col"
th:if="${footerMenu != null and not #lists.isEmpty(footerMenu.menuItems)}"
>
<h4 th:text="${footerMenu.spec.displayName}"></h4>
<ul>
<li th:each="menuItem : ${footerMenu.menuItems}">
<a
th:href="@{${menuItem.status.href}}"
th:text="${menuItem.status.displayName}"
th:target="${menuItem.spec.target?.value}"
th:rel="${menuItem.spec.target?.value == '_blank'} ? 'noopener' : null"
></a>
</li>
</ul>
</div>
</th:block>
</th:block>
</div>
</th:block>
<div class="footer-bottom">
<a
class="footer-bottom-brand"
th:href="@{/}"
th:if="${not #strings.isEmpty(footerLogo) or showFooterName}"
>
<img
th:if="${not #strings.isEmpty(footerLogo)}"
th:src="${footerLogo}"
th:attr="data-logo-light=${footerLogo},data-logo-dark=${footerLogoDark}"
class="footer-bottom-logo"
alt="logo"
/>
<span
class="footer-bottom-name"
th:if="${showFooterName}"
th:text="${site.title}"
></span>
</a>
<span
class="footer-copyright"
th:if="${not #strings.isEmpty(theme.config.footer?.copyright)}"
th:text="${theme.config.footer?.copyright}"
></span>
<span
class="footer-beian"
th:if="${not #strings.isEmpty(theme.config.footer?.icp) or not #strings.isEmpty(theme.config.footer?.gongan)}"
>
<a
th:if="${not #strings.isEmpty(theme.config.footer?.icp)}"
th:href="${theme.config.footer?.icp_link ?: 'https://beian.miit.gov.cn/'}"
target="_blank"
rel="noopener"
th:text="${theme.config.footer?.icp}"
></a>
<a
class="footer-gongan"
th:if="${not #strings.isEmpty(theme.config.footer?.gongan)}"
th:href="${theme.config.footer?.gongan_link ?: 'https://beian.mps.gov.cn/#/query/webSearch'}"
target="_blank"
rel="noopener"
>
<img
th:src="@{/assets/img/gongan.svg}"
class="footer-gongan-icon"
alt=""
/>
<span th:text="${theme.config.footer?.gongan}"></span>
</a>
</span>
</div>
</div>
<halo:footer />
</footer>
<script th:inline="javascript">
window.themePluginPage = {
docs: /*[[${theme.config.docs}]]*/ {},
docsme: /*[[${theme.config.docsme}]]*/ {},
minidocs: /*[[${theme.config.minidocs}]]*/ {},
};
</script>
</body>
</html>
+265
View File
@@ -0,0 +1,265 @@
<!doctype html>
<html
lang="zh-CN"
xmlns:th="https://www.thymeleaf.org"
th:lang="${#locale.toLanguageTag}"
class="color-scheme-auto"
data-color-scheme="auto"
>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title th:text="|标签 - ${site.title}|">标签</title>
<script type="module" crossorigin src="/themes/theme-mdocs/assets/main-BAX9Eehy.js"></script>
<link rel="stylesheet" crossorigin href="/themes/theme-mdocs/assets/main-OuYKDL0n.css">
</head>
<body
th:style="'--radius:' + (${theme.config.global?.border_radius} ?: '12px') + ';--font-size:' + (${theme.config.global?.font_size} ?: '16px')"
th:attr="data-bg-light=${theme.config.global?.bg_color_light ?: '#ffffff'},data-bg-dark=${theme.config.global?.bg_color_dark ?: '#0d1117'},data-bg-image=${theme.config.global?.background_image},data-primary=${theme.config.global?.primary_color}"
th:with="siteLogo = ${not #strings.isEmpty(theme.config.global?.site_logo) ? theme.config.global?.site_logo : site.logo}, siteLogoDark = ${not #strings.isEmpty(theme.config.global?.site_logo_dark) ? theme.config.global?.site_logo_dark : siteLogo}, showSiteName = ${theme.config.global?.show_site_name != false}"
>
<header class="site-header">
<div
class="container header-inner"
th:with="primaryMenu = ${menuFinder.getPrimary()}"
>
<a class="site-brand" th:href="@{/}">
<img
th:if="${not #strings.isEmpty(siteLogo)}"
th:src="${siteLogo}"
th:attr="data-logo-light=${siteLogo},data-logo-dark=${siteLogoDark}"
class="brand-logo"
alt="logo"
/>
<span class="brand-name" th:if="${showSiteName}" th:text="${site.title}">源蝶工作室</span>
</a>
<nav class="primary-nav" th:if="${not #lists.isEmpty(theme.config.nav?.doc_nav_buttons)}">
<a
class="primary-nav-link"
th:each="btn : ${theme.config.nav?.doc_nav_buttons ?: T(java.util.Collections).emptyList()}"
th:href="@{${btn.url ?: '/'}}"
th:text="${btn.title}"
></a>
</nav>
<div
class="header-right"
>
<button
class="search-trigger"
id="search-trigger"
type="button"
th:if="${pluginFinder.available('PluginSearchWidget')}"
onclick="SearchWidget.open()"
aria-label="搜索"
>
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
<span class="search-trigger-text">搜索</span>
<kbd class="search-trigger-kbd">⌘K</kbd>
</button>
<button
class="mobile-menu-toggle"
id="mobile-menu-toggle"
type="button"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
aria-label="打开菜单"
aria-controls="header-menu-panel"
aria-expanded="false"
>
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</button>
<div
class="header-menu-wrap"
id="header-menu-panel"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
>
<th:block th:replace="~{header-menu :: menu}" />
</div>
<div
class="header-divider"
aria-hidden="true"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
></div>
<div class="header-actions">
<th:block
th:if="${not #lists.isEmpty(theme.config.nav?.user_buttons)}"
th:each="btn : ${theme.config.nav?.user_buttons ?: T(java.util.Collections).emptyList()}"
>
<a
class="header-action-btn header-icon-btn header-action-icon-btn"
th:if="${not #strings.isEmpty(btn.svg)}"
th:href="@{${btn.url ?: '#'}}"
th:title="${btn.label}"
th:style="${not #strings.isEmpty(btn.color)} ? '--icon-color:' + btn.color : ''"
>
<span class="header-action-icon" th:utext="${btn.svg}"></span>
</a>
</th:block>
<button
class="theme-toggle header-icon-btn"
id="theme-toggle"
aria-label="切换明暗主题"
>
<svg class="icon-sun" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
<svg class="icon-moon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
</svg>
</button>
<th:block th:replace="~{user-menu :: user-menu}" />
</div>
</div>
<div
class="mobile-menu-backdrop"
id="mobile-menu-backdrop"
th:if="${primaryMenu != null && not #lists.isEmpty(primaryMenu.menuItems)}"
></div>
</div>
</header>
<main class="site-main">
<div class="container page-wrap">
<h1 class="page-title">标签</h1>
<ul class="tag-list">
<li th:each="tag : ${tags.items}">
<a th:href="@{${tag.status.permalink}}" th:text="${tag.spec.displayName}" class="tag"></a>
</li>
</ul>
<nav class="pagination" th:if="${tags.hasPrevious() || tags.hasNext()}">
<a th:if="${tags.hasPrevious()}" th:href="@{${tags.prevUrl}}">上一页</a>
<span th:text="|${tags.page} / ${tags.totalPages}|"></span>
<a th:if="${tags.hasNext()}" th:href="@{${tags.nextUrl}}">下一页</a>
</nav>
</div>
</main>
<footer
class="site-footer"
th:with="separateFooterLogo = ${theme.config.footer?.footer_separate_logo == true}, footerLogo = ${separateFooterLogo ? (not #strings.isEmpty(theme.config.footer?.footer_logo) ? theme.config.footer?.footer_logo : siteLogo) : siteLogo}, footerLogoDark = ${separateFooterLogo ? (not #strings.isEmpty(theme.config.footer?.footer_logo_dark) ? theme.config.footer?.footer_logo_dark : (not #strings.isEmpty(theme.config.footer?.footer_logo) ? theme.config.footer?.footer_logo : siteLogoDark)) : siteLogoDark}, showFooterName = ${theme.config.footer?.show_footer_name != false}"
>
<div class="footer-container">
<th:block
th:if="${not #lists.isEmpty(theme.config.footer?.footer_menus)}"
>
<div class="footer-menu-columns">
<th:block
th:each="menuName : ${theme.config.footer?.footer_menus}"
>
<th:block
th:with="footerMenu = ${not #strings.isEmpty(menuName) ? menuFinder.getByName(menuName) : null}"
>
<div
class="footer-menu-col"
th:if="${footerMenu != null and not #lists.isEmpty(footerMenu.menuItems)}"
>
<h4 th:text="${footerMenu.spec.displayName}"></h4>
<ul>
<li th:each="menuItem : ${footerMenu.menuItems}">
<a
th:href="@{${menuItem.status.href}}"
th:text="${menuItem.status.displayName}"
th:target="${menuItem.spec.target?.value}"
th:rel="${menuItem.spec.target?.value == '_blank'} ? 'noopener' : null"
></a>
</li>
</ul>
</div>
</th:block>
</th:block>
</div>
</th:block>
<div class="footer-bottom">
<a
class="footer-bottom-brand"
th:href="@{/}"
th:if="${not #strings.isEmpty(footerLogo) or showFooterName}"
>
<img
th:if="${not #strings.isEmpty(footerLogo)}"
th:src="${footerLogo}"
th:attr="data-logo-light=${footerLogo},data-logo-dark=${footerLogoDark}"
class="footer-bottom-logo"
alt="logo"
/>
<span
class="footer-bottom-name"
th:if="${showFooterName}"
th:text="${site.title}"
></span>
</a>
<span
class="footer-copyright"
th:if="${not #strings.isEmpty(theme.config.footer?.copyright)}"
th:text="${theme.config.footer?.copyright}"
></span>
<span
class="footer-beian"
th:if="${not #strings.isEmpty(theme.config.footer?.icp) or not #strings.isEmpty(theme.config.footer?.gongan)}"
>
<a
th:if="${not #strings.isEmpty(theme.config.footer?.icp)}"
th:href="${theme.config.footer?.icp_link ?: 'https://beian.miit.gov.cn/'}"
target="_blank"
rel="noopener"
th:text="${theme.config.footer?.icp}"
></a>
<a
class="footer-gongan"
th:if="${not #strings.isEmpty(theme.config.footer?.gongan)}"
th:href="${theme.config.footer?.gongan_link ?: 'https://beian.mps.gov.cn/#/query/webSearch'}"
target="_blank"
rel="noopener"
>
<img
th:src="@{/assets/img/gongan.svg}"
class="footer-gongan-icon"
alt=""
/>
<span th:text="${theme.config.footer?.gongan}"></span>
</a>
</span>
</div>
</div>
<halo:footer />
</footer>
<script th:inline="javascript">
window.themePluginPage = {
docs: /*[[${theme.config.docs}]]*/ {},
docsme: /*[[${theme.config.docsme}]]*/ {},
minidocs: /*[[${theme.config.minidocs}]]*/ {},
};
</script>
</body>
</html>
+40
View File
@@ -0,0 +1,40 @@
<th:block
xmlns:th="https://www.thymeleaf.org"
xmlns:sec="https://www.thymeleaf.org/extras/spring-security"
th:fragment="user-menu"
th:with="currentUser = ${contributorFinder.getContributor(#authentication.name)}"
>
<div class="user-menu" id="user-menu">
<a
sec:authorize="isAnonymous()"
th:href="@{/login}"
class="user-avatar header-icon-btn user-anonymous"
title="点击登录"
>未登录</a>
<button
sec:authorize="isAuthenticated()"
class="user-avatar header-icon-btn user-authenticated"
type="button"
aria-haspopup="true"
>
<img
th:if="${not (currentUser == null) and not #strings.isEmpty(currentUser.avatar)}"
th:src="${currentUser.avatar}"
class="user-avatar-img"
alt="avatar"
/>
<span
th:unless="${not (currentUser == null) and not #strings.isEmpty(currentUser.avatar)}"
class="user-avatar-text"
th:text="${not (currentUser == null)} ? ${currentUser.displayName} : 'U'"
>U</span>
</button>
<div class="user-dropdown" sec:authorize="isAuthenticated()">
<a th:href="@{/uc}" class="user-dropdown-item">用户中心</a>
<a sec:authorize="hasRole('super-role')" th:href="@{/console}" class="user-dropdown-item">控制台</a>
<a th:href="@{/logout}" class="user-dropdown-item user-dropdown-logout">退出登录</a>
</div>
</div>
</th:block>