diff --git a/pages/_document.js b/pages/_document.js
index e4c4865665..2301b46d23 100644
--- a/pages/_document.js
+++ b/pages/_document.js
@@ -85,6 +85,23 @@ export default class Page extends Document {
type="text/css"
href="/static/fonts/fonts.css"
/>
+
+
+
+
{this.props.styleTags}
diff --git a/pages/doc.js b/pages/doc.js
index 87e044d42f..4784850956 100644
--- a/pages/doc.js
+++ b/pages/doc.js
@@ -1,21 +1,20 @@
/* global docsearch:readonly */
-import React, { Component } from 'react'
+import React, { useCallback, useState, useEffect } from 'react'
+import PropTypes from 'prop-types'
+import Error from 'next/error'
+import Router from 'next/router'
// components
import Page from '../src/Page'
import { HeadInjector } from '../src/Documentation/HeadInjector'
import Hamburger from '../src/Hamburger'
import SearchForm from '../src/SearchForm'
import SidebarMenu from '../src/Documentation/SidebarMenu/SidebarMenu'
-import Loader from '../src/Loader/Loader'
-import Page404 from '../src/Page404'
import Markdown from '../src/Documentation/Markdown/Markdown'
import RightPanel from '../src/Documentation/RightPanel/RightPanel'
// utils
import fetch from 'isomorphic-fetch'
import kebabCase from 'lodash.kebabcase'
-// constants
-import { HEADER } from '../src/consts'
// sidebar data and helpers
import sidebar, { getItemByPath } from '../src/Documentation/SidebarMenu/helper'
// styles
@@ -25,216 +24,149 @@ import { media } from '../src/styles'
const ROOT_ELEMENT = 'bodybag'
const SIDEBAR_MENU = 'sidebar-menu'
-export default class Documentation extends Component {
- constructor() {
- super()
- this.state = {
- currentItem: {},
- headings: [],
- isLoading: false,
- isMenuOpen: false,
- isSmoothScrollEnabled: true,
- search: false,
- markdown: '',
- pageNotFound: false
- }
+const parseHeadings = text => {
+ const headingRegex = /\n(## \s*)(.*)/g
+ const matches = []
+ let match
+ do {
+ match = headingRegex.exec(text)
+ if (match)
+ matches.push({
+ text: match[2],
+ slug: kebabCase(match[2])
+ })
+ } while (match)
+
+ return matches
+}
+
+export default function Documentation({ item, headings, markdown, errorCode }) {
+ if (errorCode) {
+ return
}
- componentDidMount() {
- this.loadStateFromURL()
+ const { source, path, label, next, prev, tutorials } = item
+
+ const [isMenuOpen, setIsMenuOpen] = useState(false)
+ const [isSearchAvaible, setIsSearchAvaible] = useState(false)
+
+ const toggleMenu = useCallback(() => setIsMenuOpen(!isMenuOpen), [isMenuOpen])
+
+ useEffect(() => {
try {
docsearch
- this.setState(
- {
- search: true
- },
- () => {
- this.initDocsearch()
- }
- )
+
+ setIsSearchAvaible(true)
+
+ if (isSearchAvaible) {
+ docsearch({
+ apiKey: '755929839e113a981f481601c4f52082',
+ indexName: 'dvc',
+ inputSelector: '#doc-search',
+ debug: false // Set debug to true if you want to inspect the dropdown
+ })
+ }
} catch (ReferenceError) {
- this.setState({
- search: false
- })
+ // nothing there
}
+ }, [isSearchAvaible])
- window.addEventListener('popstate', this.loadStateFromURL)
- }
+ useEffect(() => {
+ const handleRouteChange = () => {
+ const rootElement = document.getElementById(ROOT_ELEMENT)
+ if (rootElement) {
+ rootElement.scrollTop = 0
+ }
+ }
- componentWillUnmount() {
- window.removeEventListener('popstate', this.loadStateFromURL)
- }
+ Router.events.on('routeChangeComplete', handleRouteChange)
- initDocsearch = () => {
- docsearch({
- apiKey: '755929839e113a981f481601c4f52082',
- indexName: 'dvc',
- inputSelector: '#doc-search',
- debug: false // Set debug to true if you want to inspect the dropdown
- })
- }
+ return () => Router.events.off('routeChangeComplete', handleRouteChange)
+ }, [])
- onNavigate = (path, e) => {
- if (e && (e.ctrlKey || e.metaKey)) return
+ const githubLink = `https://github.com/iterative/dvc.org/blob/master${source}`
- if (e) e.preventDefault()
+ return (
+
+
+
+
- window.history.pushState(null, null, path)
- this.loadPath(path)
- }
+
+
+
- loadStateFromURL = () => this.loadPath(window.location.pathname)
-
- loadPath = path => {
- const { currentItem } = this.state
- const item = getItemByPath(path)
- const isPageChanged = currentItem !== item
- const isFirstPage = !currentItem.path
-
- if (!item) {
- this.setState({ pageNotFound: true, currentItem: {} })
- } else if (!isFirstPage && !isPageChanged) {
- this.updateScroll(isPageChanged)
- } else {
- this.setState({ isLoading: true, headings: [] })
- fetch(item.source)
- .then(res => {
- res.text().then(text => {
- this.setState(
- {
- currentItem: item,
- headings: this.parseHeadings(text),
- isLoading: false,
- isMenuOpen: false,
- markdown: text,
- pageNotFound: false
- },
- () => this.updateScroll(!isFirstPage && isPageChanged)
- )
- })
- })
- .catch(() => {
- window.location.reload()
- })
- }
- }
+
+ {isSearchAvaible && (
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+ )
+}
- updateScroll(isPageChanged) {
- const { hash } = window.location
+Documentation.getInitialProps = async ({ asPath, req }) => {
+ const item = getItemByPath(asPath)
- if (isPageChanged) {
- this.setState({ isSmoothScrollEnabled: false }, () => {
- this.scrollTop()
- this.setState({ isSmoothScrollEnabled: true }, () => {
- if (hash) this.scrollToLink(hash)
- })
- })
- } else if (hash) {
- this.scrollToLink(hash)
+ if (!item) {
+ return {
+ errorCode: 404
}
}
- parseHeadings = text => {
- const headingRegex = /\n(## \s*)(.*)/g
- const matches = []
- let match
- do {
- match = headingRegex.exec(text)
- if (match)
- matches.push({
- text: match[2],
- slug: kebabCase(match[2])
- })
- } while (match)
-
- return matches
- }
+ const host = req ? req.headers['host'] : window.location.host
+ const protocol = host.indexOf('localhost') > -1 ? 'http:' : 'https:'
- scrollToLink = hash => {
- const element = document.getElementById(hash.replace(/^#/, ''))
+ try {
+ const res = await fetch(`${protocol}//${host}${item.source}`)
- if (element) {
- const headerHeight = document.getElementById(HEADER).offsetHeight
- const elementBoundary = element.getBoundingClientRect()
- const rootElement = document.getElementById(ROOT_ELEMENT)
- rootElement.scroll({ top: elementBoundary.top - headerHeight })
+ if (res.status !== 200) {
+ return {
+ errorCode: res.status
+ }
}
- }
- scrollTop = () => {
- const rootElement = document.getElementById(ROOT_ELEMENT)
- if (rootElement) {
- rootElement.scrollTop = 0
- }
- }
+ const text = await res.text()
- toggleMenu = () => {
- this.setState(prevState => ({
- isMenuOpen: !prevState.isMenuOpen
- }))
+ return {
+ item: item,
+ headings: parseHeadings(text),
+ markdown: text
+ }
+ } catch {
+ window.location.reload()
}
+}
- render() {
- const {
- currentItem: { source, path, label, tutorials, next, prev },
- headings,
- markdown,
- pageNotFound,
- isLoading,
- isMenuOpen,
- isSmoothScrollEnabled
- } = this.state
-
- const githubLink = `https://github.com/iterative/dvc.org/blob/master${source}`
-
- return (
-
-
-
-
-
-
-
-
-
-
- {this.state.search && (
-
-
-
- )}
-
-
-
-
- {isLoading ? (
-
- ) : pageNotFound ? (
-
- ) : (
-
- )}
-
-
-
- )
- }
+Documentation.propTypes = {
+ item: PropTypes.object,
+ headings: PropTypes.array,
+ markdown: PropTypes.string,
+ errorCode: PropTypes.bool
}
const Container = styled.div`
diff --git a/src/Diagram/index.js b/src/Diagram/index.js
index ff25ee4e4f..a42bfc6fea 100644
--- a/src/Diagram/index.js
+++ b/src/Diagram/index.js
@@ -1,5 +1,8 @@
+/* eslint jsx-a11y/anchor-is-valid: off */
+
import React, { Component } from 'react'
import PropTypes from 'prop-types'
+import NextLink from 'next/link'
import styled from 'styled-components'
import {
media,
@@ -13,16 +16,18 @@ import { Element } from 'react-scroll'
import Slider from 'react-slick'
const LearnMore = ({ href }) => (
-
-
- Learn more
-
-
+
+
+
+ Learn more
+
+
+
)
diff --git a/src/Documentation/HeadInjector.js b/src/Documentation/HeadInjector.js
index 4a9b06c1d1..b00cab0f29 100644
--- a/src/Documentation/HeadInjector.js
+++ b/src/Documentation/HeadInjector.js
@@ -6,23 +6,6 @@ import { META_BASE_TITLE } from '../consts'
export const HeadInjector = ({ sectionName = 'Documentation' }) => (
-
-
-
-
{sectionName} | {META_BASE_TITLE}
diff --git a/src/Documentation/Markdown/Markdown.js b/src/Documentation/Markdown/Markdown.js
index 048a0f545d..e0068f2371 100644
--- a/src/Documentation/Markdown/Markdown.js
+++ b/src/Documentation/Markdown/Markdown.js
@@ -1,5 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
+import NextLink from 'next/link'
+import Router from 'next/router'
// components
import ReactMarkdown from 'react-markdown'
import { LightButton } from '../LightButton'
@@ -18,6 +20,8 @@ import vim from 'react-syntax-highlighter/dist/cjs/languages/hljs/vim'
import usage from './lang/usage'
import dvc from './lang/dvc'
import linker from './utils/remark-linker'
+// consts
+import { PAGE_DOC } from '../../consts'
// utils
import kebabCase from 'lodash.kebabcase'
// styles
@@ -105,8 +109,8 @@ CodeBlock.propTypes = {
value: PropTypes.node.isRequired
}
-const Link = ({ children, ...props }) => {
- const externalLink = props.href.match(/^(\/\/|http(s)?:\/\/)/)
+const Link = ({ children, href, ...props }) => {
+ const externalLink = href.match(/^(\/\/|http(s)?:\/\/)/)
const showIcon =
externalLink && children && typeof children[0].props.children === 'string'
@@ -115,10 +119,22 @@ const Link = ({ children, ...props }) => {
: props
if (showIcon) {
- return {children}
+ return (
+
+ {children}
+
+ )
}
- return {children}
+ const nextProps = href.match(/^\/doc/)
+ ? { href: PAGE_DOC, as: href }
+ : { href }
+
+ return (
+
+ {children}
+
+ )
}
Link.propTypes = {
@@ -164,26 +180,19 @@ export default class Markdown extends React.PureComponent {
handleSwipeGesture = () => {
if (this.isCodeBlock) return
- const { prev, next, onNavigate } = this.props
+ const { prev, next } = this.props
if (this.touchstartX - this.touchendX > 100) {
- next && onNavigate(next)
+ Router.push({ asPath: PAGE_DOC, pathname: next })
}
if (this.touchendX - this.touchstartX > 100) {
- prev && onNavigate(prev)
+ Router.push({ asPath: PAGE_DOC, pathname: prev })
}
}
render() {
- const {
- markdown,
- githubLink,
- tutorials,
- prev,
- next,
- onNavigate
- } = this.props
+ const { markdown, githubLink, prev, next, tutorials } = this.props
return (
@@ -209,14 +218,18 @@ export default class Markdown extends React.PureComponent {
astPlugins={[linker()]}
/>
-
-
+
+
+
+
+
+
)
@@ -228,8 +241,7 @@ Markdown.propTypes = {
githubLink: PropTypes.string.isRequired,
tutorials: PropTypes.object,
prev: PropTypes.string,
- next: PropTypes.string,
- onNavigate: PropTypes.func.isRequired
+ next: PropTypes.string
}
const Content = styled.article`
@@ -337,8 +349,8 @@ const NavigationButtons = styled.div`
font-size: 14px;
`
-const Button = styled.div`
- border: none;
+const Button = styled.a`
+ text-decoration: none;
background: white;
padding: 10px 15px;
text-transform: uppercase;
@@ -346,7 +358,6 @@ const Button = styled.div`
border-bottom: 3px solid #13adc7;
display: inline-flex;
align-items: center;
- cursor: pointer;
transition: 0.2s border-color ease-out;
&:hover {
diff --git a/src/Documentation/SidebarMenu/SidebarMenu.js b/src/Documentation/SidebarMenu/SidebarMenu.js
index 6239581260..77b54e82b9 100644
--- a/src/Documentation/SidebarMenu/SidebarMenu.js
+++ b/src/Documentation/SidebarMenu/SidebarMenu.js
@@ -1,8 +1,10 @@
-import React, { Component } from 'react'
+import React, { useEffect, useRef, useState } from 'react'
import PerfectScrollbar from 'perfect-scrollbar'
import scrollIntoView from 'dom-scroll-into-view'
import PropTypes from 'prop-types'
-
+import NextLink from 'next/link'
+// consts
+import { PAGE_DOC } from '../../consts'
// components
import DownloadButton from '../../DownloadButton'
// utils
@@ -32,121 +34,117 @@ function calculateHeight({ activePaths, path }) {
return height
}
-class SidebarMenuItem extends React.PureComponent {
- componentDidMount() {
- heightMap[this.props.path] = this.props.children
- ? this.linkRef.scrollHeight
- : 0
- }
+function SidebarMenuItem({ children, label, path, activePaths, onClick }) {
+ const linkRef = useRef()
+ const isActive = activePaths && includes(activePaths, path)
+ const isRootParent =
+ activePaths && activePaths.length > 1 && activePaths[0] === path
- render() {
- const { children, label, path, activePaths, onNavigate } = this.props
- const isActive = activePaths && includes(activePaths, path)
- const isRootParent =
- activePaths && activePaths.length > 1 && activePaths[0] === path
+ useEffect(() => {
+ heightMap[path] = children ? linkRef.current.scrollHeight : 0
+ }, [])
- return (
- <>
+ return (
+ <>
+
onNavigate(path, e)}
isActive={isActive}
+ onClick={onClick}
className={isRootParent ? 'docSearch-lvl0' : ''}
>
{label}
- {children && (
- (this.linkRef = r)}
- >
- {children.map(item => (
-
- ))}
-
- )}
- >
- )
- }
+
+ {children && (
+
+ {children.map(item => (
+
+ ))}
+
+ )}
+ >
+ )
}
SidebarMenuItem.propTypes = {
children: PropTypes.arrayOf(PropTypes.object),
label: PropTypes.string.isRequired,
path: PropTypes.string.isRequired,
+ onClick: PropTypes.func,
activePaths: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.string),
PropTypes.bool
- ]).isRequired,
- onNavigate: PropTypes.func.isRequired
+ ]).isRequired
}
-export default class SidebarMenu extends Component {
- state = {
- isScrollHidden: false
- }
-
- componentDidMount() {
- this.ps = new PerfectScrollbar(`#${this.props.id}`, {
- // wheelPropagation: window.innerWidth <= 572
- wheelPropagation: true
- })
- }
-
- componentDidUpdate(prevProps) {
- if (prevProps.currentPath === this.props.currentPath) return
-
- const node = document.getElementById(this.props.currentPath)
- const parent = document.getElementById(this.props.id)
-
- this.setState({ isScrollHidden: true }, () =>
- setTimeout(() => {
- this.ps.update()
- scrollIntoView(node, parent, { onlyScrollIfNeeded: true })
- this.setState({ isScrollHidden: false })
- }, 400)
- )
- }
-
- render() {
- const { id, sidebar, currentPath, onNavigate } = this.props
- const activePaths = currentPath && getParentsListFromPath(currentPath)
-
- return (
-
- )
- }
+export default function SidebarMenu({ id, sidebar, currentPath, onClick }) {
+ const psRef = useRef()
+ const [isScrollHidden, setIsScrollHidden] = useState(false)
+ const activePaths = currentPath && getParentsListFromPath(currentPath)
+
+ useEffect(() => {
+ if (!psRef.current) {
+ psRef.current = new PerfectScrollbar(`#${id}`, {
+ wheelPropagation: true
+ })
+ }
+
+ const node = document.getElementById(currentPath)
+ const parent = document.getElementById(id)
+
+ setIsScrollHidden(true)
+
+ setTimeout(() => {
+ psRef.current.update()
+ scrollIntoView(node, parent, { onlyScrollIfNeeded: true })
+ setIsScrollHidden(false)
+ }, 400)
+
+ return () => {
+ psRef.current.destroy()
+ psRef.current = null
+ }
+ }, [currentPath])
+
+ return (
+
+ )
}
SidebarMenu.propTypes = {
id: PropTypes.string.isRequired,
sidebar: PropTypes.arrayOf(PropTypes.object).isRequired,
currentPath: PropTypes.string,
- onNavigate: PropTypes.func.isRequired
+ onClick: PropTypes.func
}
const Menu = styled.div`
@@ -198,7 +196,6 @@ const SectionLink = styled.a`
min-height: 26px;
padding-bottom: 5px;
padding-left: 15px;
- cursor: pointer;
margin: 0 0 0 5px;
${props =>
diff --git a/src/Footer/index.js b/src/Footer/index.js
index 286516eaf1..3c851adf18 100644
--- a/src/Footer/index.js
+++ b/src/Footer/index.js
@@ -1,6 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
+import NextLink from 'next/link'
import { columns, container, media } from '../styles'
const SocialLink = ({ src, href, children }) => (
@@ -20,32 +21,44 @@ export default function Footer(props) {
-
-
-
+
+
+
+
+
Product
- Overview
- Features
+
+ Overview
+
+
+ Features
+
Help
- Support
- Get started
+
+ Support
+
+
+ Get started
+
Chat
- Documentation
+
+ Documentation
+
diff --git a/src/HamburgerMenu/index.js b/src/HamburgerMenu/index.js
index 57cbbf2852..fb47841237 100644
--- a/src/HamburgerMenu/index.js
+++ b/src/HamburgerMenu/index.js
@@ -1,6 +1,7 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
+import NextLink from 'next/link'
import { media } from '../styles'
import Hamburger from '../Hamburger'
@@ -62,39 +63,40 @@ export default class HamburgerMenu extends Component {
diff --git a/src/TrySection/index.js b/src/TrySection/index.js
index 41fae8b8d9..2ee29a6eb1 100644
--- a/src/TrySection/index.js
+++ b/src/TrySection/index.js
@@ -1,6 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
+import NextLink from 'next/link'
import { media, container } from '../styles'
@@ -11,9 +12,9 @@ export default function TrySection({ title, buttonText = 'Get Started' }) {
{title}
-
+
-
+
diff --git a/src/consts.js b/src/consts.js
index 4d3a55259a..9d6a2573f5 100644
--- a/src/consts.js
+++ b/src/consts.js
@@ -13,3 +13,5 @@ export const META_SOCIAL_IMAGE = `https://${WEBSITE_HOST}/static/social-share.pn
export const FORUM_URL = `https://discuss.${WEBSITE_HOST}`
export const BLOG_URL = `https://blog.${WEBSITE_HOST}`
+
+export const PAGE_DOC = '/doc'