wiki.js 虽然是动态服务,但目前并没有提供自动生成分类目录的功能。但是他有API,有graphql
,老哥们就是擅长把一点点可能性给玩出花儿来。
下文所有代码假定站点默认语言代码为
zh
首先,去后台,主题,代码注入,Head部插入HTMl这个框框,把下面的东西贴进去。这部分是我根据参考资料改过的,理论上更适应zh
语言环境,至少在2.5.307
版本确定能用。
<script type="application/javascript">
window.addEventListener("load", async () => {
const placeholder = document.querySelector(".children-placeholder");
// Check if the placeholder element exists
if (!placeholder) {
console.log("🛈 No .children-placeholder found.");
return;
}
// Read configuration from data attributes
const limit = parseInt(placeholder.getAttribute("data-limit") || "100", 10);
const maxDepth = parseInt(placeholder.getAttribute("data-depth") || "1", 10);
const sortAttr = placeholder.getAttribute("data-sort") || "path:asc";
const debug = placeholder.getAttribute("data-debug") === "true";
const [sortField, sortDirection] = sortAttr.split(":");
const sortAsc = sortDirection !== "desc";
const log = (...args) => debug && console.log(...args);
// Parse the URL path to determine the base path and locale
let fullPath = window.location.pathname;
let [, locale, ...pathParts] = fullPath.split("/");
locale = placeholder.getAttribute("data-locale") || locale || "zh";
let path = pathParts.join("/").replace(/^\/+|\/+$/g, "");
const basePath_pre = path ? `${path}/` : "";
const basePath = placeholder.getAttribute("data-basepath") || basePath_pre;
log("🌍 Locale:", locale);
log("🔍 Searching for subpages of path:", basePath);
// Show loading message
placeholder.innerHTML = "Loading subpages…";
// GraphQL query to fetch pages
const query = {
query: `
query ($query: String!, $locale: String!) {
pages {
search(query: "", path: $query, locale: $locale) {
results {
title
path
description
}
}
}
}
`,
variables: {
query: basePath,
locale: locale
}
};
try {
// Send GraphQL query to server
const response = await fetch("/graphql", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(query)
});
const json = await response.json();
// Check for errors in response
if (!response.ok || json.errors) {
throw new Error("GraphQL error: " + JSON.stringify(json.errors));
}
const results = json?.data?.pages?.search?.results ?? [];
log("📄 Found pages:", results.map(p => p.path));
// Filter and sort child pages
const children = results
// below 2 lines are necessary since we already filtered path in query
// .filter(p => p.path !== path)
// .filter(p => p.path.startsWith(path + "/"))
.sort((a, b) => {
const aVal = a[sortField]?.toLowerCase?.() || "";
const bVal = b[sortField]?.toLowerCase?.() || "";
if (aVal < bVal) return sortAsc ? -1 : 1;
if (aVal > bVal) return sortAsc ? 1 : -1;
return 0;
})
.slice(0, limit);
log("✅ Filtered & sorted subpages:", children.map(p => p.path));
// Show message if no children were found
if (children.length === 0) {
placeholder.innerHTML = "<em>No subpages available.</em>";
return;
}
// Build a tree structure from the page paths
const tree = {};
children.forEach(page => {
const relPath = page.path.slice(basePath.length).replace(/^\/+|\/+$/g, "");
const parts = relPath.split("/");
let node = tree;
parts.forEach((part, idx) => {
if (!node[part]) {
node[part] = { __meta: null, __children: {} };
}
if (idx === parts.length - 1) {
node[part].__meta = page;
}
node = node[part].__children;
});
});
// Escape HTML to prevent XSS
function escapeHtml(str) {
return str.replace(/[&<>"']/g, (m) =>
({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[m])
);
}
// Recursively render the tree into a nested list
function renderTree(treeObj, depth = 1) {
if (depth > maxDepth) return null;
const ul = document.createElement("ul");
ul.className = `children-tree level-${depth}`;
for (const key of Object.keys(treeObj)) {
const node = treeObj[key];
const hasChildren = Object.keys(node.__children).length > 0;
const hasMeta = !!node.__meta;
if (!hasMeta && !hasChildren) continue;
const li = document.createElement("li");
li.className = "children-item";
if (hasMeta) {
const p = node.__meta;
li.innerHTML = `<a href="/${locale}/${p.path}">${escapeHtml(p.title)}</a><br><small>${escapeHtml(p.description || "")}</small>`;
} else {
li.innerHTML = `<strong>${key}</strong>`;
}
const childList = renderTree(node.__children, depth + 1);
if (childList) li.appendChild(childList);
ul.appendChild(li);
}
return ul;
}
// Create the final HTML structure and replace the placeholder
const wrapper = document.createElement("div");
wrapper.className = "children-list";
const treeHtml = renderTree(tree);
if (treeHtml) wrapper.appendChild(treeHtml);
placeholder.replaceWith(wrapper);
log("🌲 Tree structure successfully rendered.");
} catch (err) {
console.error("❌ Error loading subpages:", err);
placeholder.innerHTML = "<em>Error loading subpages.</em>";
}
});
</script>
这时候刷新某个文章页面,不会有任何变化,但如果查看源代码,应该发现有这段代码。
然后,在你想插入正文的文章中,直接插入html
标记。例如,对于markdown
类型的文章,可以这样写:
# 分类目录
<div class="children-placeholder">
保存,刷新,不出意外的话,页面加载完毕之后,这段js
会自动开始运行,通过页面url
获取当前页面分类,通过graphql
接口搜到当前分类下的所有文章,然后在页面上渲染出来。
如果是其他类型文章,需要用各自合适的方式插入裸html代码
其中,这几句写出来了几个可以配置的参数及其默认值:
// Read configuration from data attributes
const limit = parseInt(placeholder.getAttribute("data-limit") || "100", 10);
const maxDepth = parseInt(placeholder.getAttribute("data-depth") || "1", 10);
const sortAttr = placeholder.getAttribute("data-sort") || "path:asc";
const debug = placeholder.getAttribute("data-debug") === "true";
// ...
locale = placeholder.getAttribute("data-locale") || locale || "zh";
可以通过类似下面方式设定:
<div class="children-placeholder" data-limit="30" data-locale="zh"></div>
如果出现了一些错误,可以加入data-debug="true"
来在网页控制台输出运行过程,通过调试工具即可查看。
首页是比较特殊的,我想在里面一节“最近文章”,可以通过类似的方法查询最近文章。这一段代码不要在后台主题那里注入,因为那样每一个页面都会加载,浪费了。
可以在文章编辑页面,页面信息,脚本这里,在个别页面上注入这段代码:
<script type="application/javascript">
window.addEventListener("load", async () => {
const placeholder = document.querySelector(".children-latest-article");
// Check if placeholder element exists
if (!placeholder) {
console.log("🛈 No .children-latest-article found.");
return;
}
// Read configuration from data attributes
const limit = parseInt(placeholder.getAttribute("data-limit") || "10", 10);
const debug = placeholder.getAttribute("data-debug") === "true";
const log = (...args) => debug && console.log(...args);
// Parse the URL path to determine locale
let fullPath = window.location.pathname;
let [, locale, ...pathParts] = fullPath.split("/");
locale = placeholder.getAttribute("data-locale") || locale || "zh";
log("🌍 Locale:", locale);
// Show loading message
placeholder.innerHTML = "Loading latest articles...";
// GraphQL query definition
const query = {
query: `
query ($limit: Int!, $locale: String!) {
pages {
list(limit: $limit, orderBy: UPDATED, orderByDirection: DESC, locale: $locale) {
id
path
title
description
}
}
}
`,
variables: {
limit: limit,
locale: locale
}
};
try {
// Send GraphQL request
const response = await fetch("/graphql", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(query)
});
const json = await response.json();
// Error handling
if (!response.ok || json.errors) {
throw new Error("GraphQL error: " + JSON.stringify(json.errors));
}
// Process results
const results = json?.data?.pages?.list || [];
log("📄 Found articles:", results.map(p => p.path));
// Show message if no results
if (results.length === 0) {
placeholder.innerHTML = "<em>No recent articles found.</em>";
return;
}
// HTML escaping function
const escapeHtml = str => str.replace(/[&<>"']/g, m =>
({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[m]));
// Build list structure
const wrapper = document.createElement("div");
wrapper.className = "latest-articles-list";
const list = document.createElement("ul");
list.className = "articles-list";
results.forEach(article => {
const listItem = document.createElement("li");
listItem.className = "article-item";
listItem.innerHTML = `
<a href="/${locale}/${escapeHtml(article.path)}">${escapeHtml(article.title)}</a>
${article.description ? `<br><small>${escapeHtml(article.description)}</small>` : ""}
`;
list.appendChild(listItem);
});
wrapper.appendChild(list);
placeholder.replaceWith(wrapper);
log("✅ Latest articles rendered successfully.");
} catch (err) {
console.error("❌ Error loading articles:", err);
placeholder.innerHTML = "<em>Error loading latest articles.</em>";
}
});
</script>
然后,类似地,在页面上插入html
代码:
<div class="children-latest-article" data-limit="30" data-locale="zh"></div>
https://feedback.js.wiki/wiki/p/sub-pages-list
https://gist.github.com/Psycho0verload/b0057b5b3bba480cd8d80c3f4eab6822