我想在Gatsby网站上创建Markdown页面时自动添加侧边栏。
有一个 starter “ gatsby-gitbook-starter” 可以支持markdown文件的侧边栏,但仅支持1级。 我希望能够支持更多级别。
你可以通过下面的命令安装这个starter。
gatsby new gatsby-gitbook-starter https://github.com/hasura/gatsby-gitbook-starter
相关文档可以在这里找到 this link
// in /src/templates/docs.js
export const pageQuery = graphql`
query($id: String!) {
site {
siteMetadata {
title
docsLocation
}
}
mdx(fields: { id: { eq: $id } }) {
fields {
id
title
slug
}
body
tableOfContents
parent {
... on File {
relativePath
}
}
frontmatter {
metaTitle
metaDescription
}
}
allMdx {
edges {
node {
fields {
slug
title
}
}
}
}
}
`;
tableOfContents 就是 sidebar navigation 需要的数据.
可以 GraphiQL query 来查看.
为了简便起见,使用下面的 sample data:
a={
"tableOfContents": {
"items": [
{
"url": "#heading-h11",
"title": "Heading H11"
},
{
"url": "#heading-h12",
"title": "Heading H12",
"items": [
{
"url": "#heading-h2",
"title": "Heading H2",
"items": [
{
"url": "#heading-h3",
"title": "Heading H3",
}
]
}
]
}
]
}
};
b = a.tableOfContents.items;
// in /src/components/rightSidebar.js
if (item.node.tableOfContents.items) {
innerItems = item.node.tableOfContents.items.map((innerItem, index) => {
const itemId = innerItem.title
? innerItem.title.replace(/\s+/g, '').toLowerCase()
: '#';
return (
<ListItem key={index} to={`#${itemId}`} level={1}>
{innerItem.title}
</ListItem>
);
});
}
}
}
if (innerItems) {
finalNavItems = innerItems;
}
});
}
if (finalNavItems && finalNavItems.length) {
return (
<Sidebar>
<ul className={'rightSideBarUL'}>
<li className={'rightSideTitle'}>CONTENTS</li>
{finalNavItems}
</ul>
</Sidebar>
);
} else {
return (
<Sidebar>
<ul></ul>
</Sidebar>
);
}
}}
从代码我们可以看到,只支持1级 title。
定义下面的递归函数:
function items2Components(items, depth) {
let res = [];
for (let i = 0; i < items.length; i++) {
let innerItem = items[i];
const itemId = innerItem.title
? innerItem.title.replace(/\s+/g, '').toLowerCase()
: '#';
if (items[i].items) {
res.push(
<ListItem key={index} to={`#${itemId}`} level={depth}>
{innerItem.title}
{items2Components(innerItem.items, depth+1)}
</ListItem>
)
} else {
res.push(
<ListItem key={index} to={`#${itemId}`} level={depth}>
{innerItem.title}
</ListItem>
)
}
}
return res;
}
来替换 starter中原来的代码
innerItems = item.node.tableOfContents.items.map((innerItem, index) => {
const itemId = innerItem.title
? innerItem.title.replace(/\s+/g, '').toLowerCase()
: '#';
return (
<ListItem key={index} to={`#${itemId}`} level={1}>
{innerItem.title}
</ListItem>
);
});
这样可以工作,但是会有warning,而且点击后还会出现异常。
Warning: validateDOMNesting(...): <a> cannot appear as a descendant of <a>.
这是因为’’ 不支持嵌套使用, “ ” 在html中是不支持的.
所以需要改变实现方式。
function items2ObjectInfo(items, res, depth) {
for (let i = 0; i < items.length; i++) {
var innerItem = items[i];
const itemId = innerItem.title
? innerItem.title.replace(/\s+/g, '').toLowerCase()
: '#';
if (innerItem.items) {
res.push({
title: innerItem.title,
to: `#${itemId}`,
level: depth,
});
items2ObjectInfo(innerItem.items, res, depth+1)
} else {
res.push({
title: innerItem.title,
to: `#${itemId}`,
level: depth,
}
);
}
}
return res;
};
这个函数会将sample data “拍平” 成下面的样子:
[{
"to": "#heading-h11",
"title": "Heading H11",
"level": "1"
},
{
"url": "#heading-h12",
"title": "Heading H12"
"level": "1"
},
{
"url": "#heading-h2",
"title": "Heading H2"
"level": "2"
},
{
"url": "#heading-h3",
"title": "Heading H3",
"level": "3"
}
]
然后我们可以将拍平后的数据来创建components.
items2ObjectInfo(item.node.tableOfContents.items, listLinkInfo);
console.log(listLinkInfo);
innerItems = listLinkInfo.map((item, index) => {
return (
<ListItem key={index} to={item.to} level={item.level}>
{item.title}
</ListItem>
)
});
高level的标题字体更大。
function calcFontsizeByLevel(level) {
const maxSize = 18;
let fontSize = maxSize - level*1;
if (fontSize < 5) {
fontSize = 5;
}
return fontSize;
}
const ListItem = (props) => {
return (
<li>
<a style={{fontSize: calcFontsizeByLevel(props.level)}} href={props.to} {...props}>
{props.children}
</a>
</li>
);
};
最终的结果是像下面这样的:
这篇文章的英文版发到了medium上。链接地址