From 4ff2fe280ebd41c4b491936fdd4ae75b7805ed61 Mon Sep 17 00:00:00 2001 From: Elian Ibaj <elianibaj@gmail.com> Date: Sun, 4 Mar 2018 06:23:59 +0100 Subject: [PATCH] Add separate on-page navigation sidebar (#475) --- lib/core/DocsLayout.js | 8 ++++- lib/core/Site.js | 7 +++- lib/core/getTOC.js | 7 ++++ lib/core/nav/OnPageNav.js | 40 +++++++++++++++++++++++ lib/static/css/main.css | 67 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 lib/core/nav/OnPageNav.js diff --git a/lib/core/DocsLayout.js b/lib/core/DocsLayout.js index 0c6633ed3551..9ef37bf73df4 100644 --- a/lib/core/DocsLayout.js +++ b/lib/core/DocsLayout.js @@ -9,6 +9,7 @@ const React = require('react'); const Container = require('./Container.js'); const Doc = require('./Doc.js'); const DocsSidebar = require('./DocsSidebar.js'); +const OnPageNav = require('./nav/OnPageNav.js'); const Site = require('./Site.js'); const translation = require('../server/translation.js'); @@ -25,7 +26,7 @@ class DocsLayout extends React.Component { return ( <Site config={this.props.config} - className="sideNavVisible" + className="sideNavVisible doc" title={ i18n ? translation[this.props.metadata.language]['localized-strings'][ @@ -90,6 +91,11 @@ class DocsLayout extends React.Component { )} </div> </Container> + {this.props.config.onPageNav == 'separate' && ( + <nav className="onPageNav"> + <OnPageNav rawContent={this.props.children} /> + </nav> + )} </div> </Site> ); diff --git a/lib/core/Site.js b/lib/core/Site.js index 6ac9a235b286..6506e0ed74a5 100644 --- a/lib/core/Site.js +++ b/lib/core/Site.js @@ -11,6 +11,7 @@ const HeaderNav = require('./nav/HeaderNav.js'); const Head = require('./Head.js'); const Footer = require(process.cwd() + '/core/Footer.js'); const translation = require('../server/translation.js'); +const classNames = require('classnames'); const CWD = process.cwd(); @@ -63,7 +64,11 @@ class Site extends React.Component { title={title} url={url} /> - <body className={this.props.className}> + <body + className={classNames({ + [this.props.className]: true, + separateOnPageNav: this.props.config.onPageNav == 'separate', + })}> <HeaderNav config={this.props.config} baseUrl={this.props.config.baseUrl} diff --git a/lib/core/getTOC.js b/lib/core/getTOC.js index 182d88058241..3d99813ccab7 100644 --- a/lib/core/getTOC.js +++ b/lib/core/getTOC.js @@ -1,3 +1,10 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + const Remarkable = require('remarkable'); const toSlug = require('./toSlug'); diff --git a/lib/core/nav/OnPageNav.js b/lib/core/nav/OnPageNav.js new file mode 100644 index 000000000000..1afd3079f468 --- /dev/null +++ b/lib/core/nav/OnPageNav.js @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +const React = require('react'); + +const siteConfig = require(process.cwd() + '/siteConfig.js'); +const getTOC = require('../getTOC'); + +const Link = ({hashLink, text}) => <a href={`#${hashLink}`}>{text}</a>; + +const Headings = ({headings}) => { + if (!headings.length) return null; + return ( + <ul className="toc-headings"> + {headings.map((heading, i) => ( + <li key={i}> + <Link hashLink={heading.hashLink} text={heading.text} /> + <Headings headings={heading.children} /> + </li> + ))} + </ul> + ); +}; + +class OnPageNav extends React.Component { + render() { + const customTags = siteConfig.onPageNavHeadings; + const headings = customTags + ? getTOC(this.props.rawContent, customTags.topLevel, customTags.sub) + : getTOC(this.props.rawContent); + + return <Headings headings={headings} />; + } +} + +module.exports = OnPageNav; diff --git a/lib/static/css/main.css b/lib/static/css/main.css index 5bc67e47c375..3dd560e23300 100644 --- a/lib/static/css/main.css +++ b/lib/static/css/main.css @@ -1494,6 +1494,73 @@ nav.toc .toggleNav .navBreadcrumb h2 { padding: 0 10px; } } + +.onPageNav { + display: none; +} +.onPageNav::-webkit-scrollbar { + display: none; +} + +@supports(position: sticky) { + @media only screen and (min-width: 1024px) { + .separateOnPageNav.doc .wrapper, + .separateOnPageNav .headerWrapper.wrapper { + max-width: 1400px; + } + + .doc.separateOnPageNav .docsNavContainer { + flex: 0 0 240px; + margin: 40px 0 0; + } + + .doc.separateOnPageNav nav.toc:last-child { + padding-bottom: 0; + width: auto; + } + + .doc.separateOnPageNav.sideNavVisible .navPusher .mainContainer { + max-width: 100%; + flex: 1 auto; + padding: 0 40px 50px; + min-width: 0; + } + + .onPageNav { + display: block; + position: -webkit-sticky; + position: sticky; + top: 110px; + flex: 0 0 240px; + overflow-y: auto; + max-height: calc(100vh - 110px); + } + + .onPageNav > ul { + border-left: 1px solid #e0e0e0; + padding: 10px 0 2px 15px; + } + + .onPageNav a { + color: #717171; + } + + .onPageNav ul li { + font-size: 12px; + line-height: 14px; + padding-bottom: 12px; + } + + .onPageNav ul ul { + padding: 8px 0 0 20px; + } + + .onPageNav ul ul li { + padding-bottom: 8px; + } + } +} + table { background: #F8F8F8; border: 1px solid #B0B0B0;