自动为 Gatsby网站中的 Markdown 页面添加 sidebar

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 {
        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 来查看.
自动为 Gatsby网站中的 Markdown 页面添加 sidebar_第1张图片

为了简便起见,使用下面的 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.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。

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>. 

这是因为’’ 不支持嵌套使用, “ ” 在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>
    );
};

最终的结果是像下面这样的:

自动为 Gatsby网站中的 Markdown 页面添加 sidebar_第2张图片

medium 链接

这篇文章的英文版发到了medium上。链接地址

你可能感兴趣的:(react,算法,gatsby,react,sidebar,markdown,recursive)