0 简介
我想在Gatsby网站上创建Markdown页面时自动添加侧边栏。
有一个 starter “ gatsby-gitbook-starter” 可以支持markdown文件的侧边栏,但仅支持1级。 我希望能够支持更多级别。
你可以通过下面的命令安装这个starter。
gatsby new gatsby-gitbook-starter https://github.com/hasura/gatsby-gitbook-starter
相关文档可以在这里找到 this link
1. 从 “gatsby-gitbook-starter” 学到的
1.1 如何获取 markdown files 相关的数据
// in /src/templates/docs.js
export const pageQuery = graphql`query($id: String!) {site {siteMetadata {titledocsLocation}}mdx(fields: { id: { eq: $id } }) {fields {idtitleslug}bodytableOfContentsparent {... on File {relativePath}}frontmatter {metaTitlemetaDescription}}allMdx {edges {node {fields {slugtitle}}}}}
`;
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;
1.2 如何把数据转化为 component
// in /src/components/rightSidebar.jsif (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。
2. 使用递归函数来改进
2.1 第一次尝试
定义下面的递归函数:
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>.
这是因为’<a>’ 不支持嵌套使用, “<a> <a> <a/> </a>” 在html中是不支持的.
所以需要改变实现方式。
2.2 使用递归的方式来“拍平”嵌套的数据结构.
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>)});
2.3 为sidebar添加样式
高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 链接
这篇文章的英文版发到了medium上。链接地址