From 62fea876d5a0a5988d496960599ef9b4fb6d8e7b Mon Sep 17 00:00:00 2001 From: Alexey Ivanov Date: Wed, 7 Aug 2019 18:17:14 +0500 Subject: [PATCH 01/10] Add nested paths - Changed sidebar format so it can now have any number of levels - All json transformations and normalizations moved to src/Documentation/SidebarMenu/helper.js and done on the first init - Simplified docs.js, Markdown.js and SidebarMenu.js --- pages/doc.js | 116 +--- src/Documentation/Markdown/Markdown.js | 29 +- src/Documentation/SidebarMenu/SidebarMenu.js | 187 ++----- src/Documentation/SidebarMenu/helper.js | 121 ++++ src/Documentation/sidebar.json | 519 +++++++++++------- .../{cache_dir.md => cache/dir.md} | 0 .../{cache.md => cache/index.md} | 0 .../{metrics_add.md => metrics/add.md} | 0 .../{metrics.md => metrics/index.md} | 0 .../{metrics_modify.md => metrics/modify.md} | 0 .../{metrics_remove.md => metrics/remove.md} | 0 .../{metrics_show.md => metrics/show.md} | 4 +- .../{pipeline.md => pipeline/index.md} | 0 .../{pipeline_list.md => pipeline/list.md} | 0 .../{pipeline_show.md => pipeline/show.md} | 0 .../{remote_add.md => remote/add.md} | 0 .../{remote_default.md => remote/default.md} | 0 .../{remote.md => remote/index.md} | 0 .../{remote_list.md => remote/list.md} | 0 .../{remote_modify.md => remote/modify.md} | 0 .../{remote_remove.md => remote/remove.md} | 0 21 files changed, 537 insertions(+), 439 deletions(-) create mode 100644 src/Documentation/SidebarMenu/helper.js rename static/docs/commands-reference/{cache_dir.md => cache/dir.md} (100%) rename static/docs/commands-reference/{cache.md => cache/index.md} (100%) rename static/docs/commands-reference/{metrics_add.md => metrics/add.md} (100%) rename static/docs/commands-reference/{metrics.md => metrics/index.md} (100%) rename static/docs/commands-reference/{metrics_modify.md => metrics/modify.md} (100%) rename static/docs/commands-reference/{metrics_remove.md => metrics/remove.md} (100%) rename static/docs/commands-reference/{metrics_show.md => metrics/show.md} (99%) rename static/docs/commands-reference/{pipeline.md => pipeline/index.md} (100%) rename static/docs/commands-reference/{pipeline_list.md => pipeline/list.md} (100%) rename static/docs/commands-reference/{pipeline_show.md => pipeline/show.md} (100%) rename static/docs/commands-reference/{remote_add.md => remote/add.md} (100%) rename static/docs/commands-reference/{remote_default.md => remote/default.md} (100%) rename static/docs/commands-reference/{remote.md => remote/index.md} (100%) rename static/docs/commands-reference/{remote_list.md => remote/list.md} (100%) rename static/docs/commands-reference/{remote_modify.md => remote/modify.md} (100%) rename static/docs/commands-reference/{remote_remove.md => remote/remove.md} (100%) diff --git a/pages/doc.js b/pages/doc.js index 61bcd1ed71..7e897bea4b 100644 --- a/pages/doc.js +++ b/pages/doc.js @@ -8,27 +8,22 @@ import { RightPanel } from '../src/Documentation/RightPanel/RightPanel' import Page from '../src/Page' import SearchForm from '../src/SearchForm' import Page404 from '../src/Page404' -import PerfectScrollbar from 'perfect-scrollbar' import Hamburger from '../src/Hamburger' // utils import fetch from 'isomorphic-fetch' import kebabCase from 'lodash.kebabcase' -import compact from 'lodash.compact' -import flatten from 'lodash.flatten' import { scroller, animateScroll } from 'react-scroll' -import 'core-js/fn/array/find-index' // styles import styled from 'styled-components' import { media } from '../src/styles' -// json -import sidebar from '../src/Documentation/sidebar' +// sidebar data and helpers +import sidebar, { getItemByPath } from '../src/Documentation/SidebarMenu/helper' export default class Documentation extends Component { constructor() { super() this.state = { - currentSection: 0, - currentFile: null, + currentItem: {}, markdown: '', headings: [], pageNotFound: false, @@ -54,52 +49,14 @@ export default class Documentation extends Component { search: false }) } - window.addEventListener('popstate', this.loadStateFromURL) - this.ps = new PerfectScrollbar('#sidebar-menu', { - // wheelPropagation: window.innerWidth <= 572 - wheelPropagation: true - }) - } - componentDidUpdate() { - this.ps.update() + window.addEventListener('popstate', this.loadStateFromURL) } componentWillUnmount() { window.removeEventListener('popstate', this.loadStateFromURL) } - loadStateFromURL = () => { - const { pathname } = window.location - const sectionURL = pathname.split('/')[2] // match section from URL - const sectionIndex = sidebar.findIndex( - section => (section.slug || kebabCase(section.name)) === sectionURL - ) - if (sectionIndex === -1) { - sectionURL - ? this.setState({ pageNotFound: true }) - : this.onSectionSelect(0) - } else { - const fileURL = pathname.split('/')[3] // match file from URL - const sectionFiles = flatten(sidebar[sectionIndex].files) - const fileIndex = sectionFiles.findIndex( - file => kebabCase(file.slice(0, -3)) === fileURL - ) - if (fileIndex === -1) { - fileURL - ? this.setState({ pageNotFound: true }) - : this.onSectionSelect(sectionIndex) - } else { - this.loadFile({ - section: sectionIndex, - file: sectionFiles[fileIndex], - parseHeadings: true, - pageNotFound: false - }) - } - } - } - initDocsearch = () => { docsearch({ apiKey: '755929839e113a981f481601c4f52082', @@ -109,47 +66,37 @@ export default class Documentation extends Component { }) } - getLinkHref = (section, file) => { - const sectionSlug = - sidebar[section].slug || kebabCase(sidebar[section].name) - const fileSlug = file ? kebabCase(file.slice(0, -3)) : undefined - return `/doc/${compact([sectionSlug, fileSlug]).join('/')}` + onNavigate = (path, e) => { + e && e.preventDefault() + window.history.pushState(null, null, path) + this.loadPath(path) } - setCurrentPath = (section, file) => { - window.history.pushState(null, null, this.getLinkHref(section, file)) - } + loadStateFromURL = () => this.loadPath(window.location.pathname) - onSectionSelect = (section, e) => { - e && e.preventDefault() - const { indexFile, files } = sidebar[section] - const file = indexFile || flatten(files)[0] - e && this.setCurrentPath(section, indexFile ? undefined : file) - this.loadFile({ file, section, parseHeadings: false }) - } + loadPath = path => { + const item = getItemByPath(path) - onFileSelect = (file, section, e) => { - e && e.preventDefault() - this.setCurrentPath(section, file) - this.loadFile({ file, section, parseHeadings: true }) - } + if (!item) { + this.setState({ pageNotFound: true, currentItem: {} }) + + return + } - loadFile = ({ file, section, parseHeadings }) => { - fetch(`${sidebar[section].folder}/${file}`) + fetch(item.source) .then(res => { res.text().then(text => { this.setState( { - currentSection: section, - currentFile: file, markdown: text, headings: [], pageNotFound: false, - isMenuOpen: false + isMenuOpen: false, + currentItem: item }, () => { this.scrollTop() - parseHeadings && this.parseHeadings(text) + this.parseHeadings(text) } ) }) @@ -208,21 +155,18 @@ export default class Documentation extends Component { render() { const { - currentSection, - currentFile, + currentItem: { source, path, label, next, prev }, headings, markdown, pageNotFound, isMenuOpen } = this.state - const directory = sidebar[currentSection].folder - const githubLink = `https://github.com/iterative/dvc.org/blob/master${directory}/${currentFile}` - const sectionName = sidebar[currentSection].name + const githubLink = `https://github.com/iterative/dvc.org/blob/master${source}` return ( - + @@ -239,13 +183,10 @@ export default class Documentation extends Component { @@ -255,12 +196,11 @@ export default class Documentation extends Component { )} - { if (this.isCodeBlock) return - const { section, file, onFileSelect } = this.props - const files = sidebar[section].files - const fileIndex = files.findIndex(f => f === file) - const showPrev = fileIndex > 0 - const showNext = fileIndex + 1 < sidebar[section].files.length + const { prev, next, onNavigate } = this.props if (this.touchstartX - this.touchendX > 100) { - showNext && onFileSelect(files[fileIndex + 1], section) + next && onNavigate(next) } if (this.touchendX - this.touchstartX > 100) { - showPrev && onFileSelect(files[fileIndex - 1], section) + prev && onNavigate(prev) } } render() { - const { markdown, githubLink, section, file, onFileSelect } = this.props - const files = sidebar[section].files - const fileIndex = files.findIndex(f => f === file) - const showPrev = fileIndex > 0 - const showNext = fileIndex + 1 < sidebar[section].files.length + const { markdown, githubLink, prev, next, onNavigate } = this.props return ( @@ -143,7 +133,6 @@ export default class Markdown extends Component { Edit on Github - - diff --git a/src/Documentation/SidebarMenu/SidebarMenu.js b/src/Documentation/SidebarMenu/SidebarMenu.js index 7a19f4fed9..d88ebea8ef 100644 --- a/src/Documentation/SidebarMenu/SidebarMenu.js +++ b/src/Documentation/SidebarMenu/SidebarMenu.js @@ -1,13 +1,41 @@ -import React, { Fragment } from 'react' +import React from 'react' import $ from 'jquery' +import PerfectScrollbar from 'perfect-scrollbar' // components import DownloadButton from '../../DownloadButton' -// utils -import startCase from 'lodash.startcase' -import includes from 'lodash.includes' // styles import styled from 'styled-components' import { media, OnlyDesktop } from '../../styles' +// sidebar helpers +import { getParentsListFromPath } from './helper' + +function SidebarMenuItem({ children, label, path, activePaths, onNavigate }) { + const isActive = activePaths && activePaths.includes(path) + + return ( + <> + onNavigate(path, e)} + isActive={isActive} + > + {label} + + {children && ( + + {children.map(item => ( + + ))} + + )} + + ) +} export default class SidebarMenu extends React.Component { constructor(props) { @@ -22,112 +50,38 @@ export default class SidebarMenu extends React.Component { } componentDidMount() { this.collapse() + + this.ps = new PerfectScrollbar('#sidebar-menu', { + // wheelPropagation: window.innerWidth <= 572 + wheelPropagation: true + }) } + + componentDidUpdate() { + this.ps.update() + } + componentWillReceiveProps(nextProps) { - if ( - nextProps.currentSection !== this.props.currentSection || - nextProps.currentFile !== this.props.currentFile - ) { + if (nextProps.currentPath !== this.props.currentPath) { this.collapse() } } render() { - let self = this - const { - sidebar, - currentSection, - currentFile, - onSectionSelect, - onFileSelect, - getLinkHref - } = this.props + const { sidebar, currentPath, onNavigate } = this.props + const activePaths = currentPath && getParentsListFromPath(currentPath) return ( - {sidebar.map( - ({ name, files = [], labels = {}, indexFile }, index) => { - const isSectionActive = currentSection === index - return ( -
- onSectionSelect(index, e)} - className={isSectionActive ? 'docSearch-lvl0' : ''} - isActive={isSectionActive} - > - {name} - - - {/* Section Files */} - - {files && - files.map((fileOrGroup, fileIndex) => { - const file = Array.isArray(fileOrGroup) - ? fileOrGroup[0] - : fileOrGroup - const subgroup = Array.isArray(fileOrGroup) - ? fileOrGroup.slice(1) - : null - const isFileActive = currentFile === file - return ( - -
- onFileSelect(file, index, e)} - isActive={isFileActive} - > - {labels[file] || startCase(file.slice(0, -3))} - -
- - {/* Subgroup files */} - {subgroup && ( - - {subgroup.map((sub, subIndex) => { - return ( -
- - onFileSelect(sub, index, e) - } - isActive={currentFile === sub} - > - {labels[sub] || - startCase(sub.slice(0, -3))} - -
- ) - })} -
- )} -
- ) - })} -
-
- ) - } - )} + {sidebar.map(item => ( + + ))}
@@ -183,7 +137,13 @@ const SectionLink = styled.a` padding-bottom: 5px; padding-left: 15px; cursor: pointer; - margin: 0; + margin: 0 0 0 5px; + + ${props => + props.isActive && + ` + color: #40364d; + `}; &:hover { color: #3c3937; @@ -205,36 +165,11 @@ const SectionLink = styled.a` transform: rotate(-90deg); `}; } - - ${props => - props.level === 1 && - ` - margin-left: 5px; - `} ${props => - props.level === 2 && - ` - margin-left: 30px; - `}; - - ${props => - props.level === 3 && - ` - margin-left: 45px; - - &::before { - display: none; - } - `}; - - ${props => - props.isActive && - ` - color: #40364d; - `}; ` const Collapse = styled.div` display: none; + padding-left: 20px; ` const SideFooter = styled.div` diff --git a/src/Documentation/SidebarMenu/helper.js b/src/Documentation/SidebarMenu/helper.js new file mode 100644 index 0000000000..7b45056380 --- /dev/null +++ b/src/Documentation/SidebarMenu/helper.js @@ -0,0 +1,121 @@ +import startCase from 'lodash.startcase' + +import sidebar from '../sidebar' + +/* + We will use this function to normalize sidebar structure and create + all of the resurces we need to prevent future recalculations. + + Target structure example: + + { + label: "Add Files or Directories", + path: "/doc/get-started/add-files", + source: "/static/docs/get-started/add-files.md", + prev: "/doc/get-started/configure", + next: "/doc/get-started/share-data", + children: [] + } +*/ + +const PATH_ROOT = '/doc/' +const FILE_ROOT = '/static/docs/' +const FILE_EXTENSION = '.md' + +let prevReference // We will save prev item reference here to use for the next and prev fields + +function normalizeSidebar(data, parentPath) { + return data.map(item => { + let normalizedItem + + if (typeof item === 'string') { + normalizedItem = { + path: PATH_ROOT + parentPath + item, + source: FILE_ROOT + parentPath + item + FILE_EXTENSION, + label: startCase(item), + prev: prevReference && prevReference.path, + next: undefined + } + } else { + const { label, slug, source, children } = item + + if (!slug) { + throw Error("'slug' field is required in objects in sidebar.json") + } + + const isSourceDisabled = source === false // is source explictly set to 'false'? + const sourceFileName = source ? source : slug + FILE_EXTENSION + const sourcePath = FILE_ROOT + parentPath + sourceFileName + + normalizedItem = { + path: PATH_ROOT + parentPath + slug, + source: isSourceDisabled ? false : sourcePath, + label: label ? label : startCase(slug), + prev: prevReference && prevReference.path, + next: undefined + } + + if (children) { + const newParentPath = `${parentPath}${slug}/` + normalizedItem.children = normalizeSidebar(children, newParentPath) + } + } + + if (prevReference) { + prevReference.next = normalizedItem.path + } + + prevReference = normalizedItem + + return normalizedItem + }) +} + +const normalizedSidebar = normalizeSidebar(sidebar, '') + +function findItem(data, targetPath) { + if (data.length) { + for (let i = 0; i < data.length; i++) { + const { path, source, children } = data[i] + + if (path === targetPath && !source && children && children[0]) { + // If parent have blank source and children, then return first child instead + return children[0] + } else if (path === targetPath) { + // Return item normally + return data[i] + } else if (children) { + // Search for match in children recursevly + const result = findItem(children, targetPath) + if (result) { + return result + } + } + } + } +} + +export function getItemByPath(path) { + // Edge case for the root url, return first item instead + if (path === PATH_ROOT.slice(0, -1)) { + return normalizedSidebar[0] + } + + return findItem(normalizedSidebar, path) +} + +export function getParentsListFromPath(path) { + let currentPath = PATH_ROOT.slice(0, -1) + + return path + .replace(PATH_ROOT, '') + .split('/') + .map(part => { + const path = `${currentPath}/${part}` + currentPath = path + + return path + }) +} + +export default normalizedSidebar diff --git a/src/Documentation/sidebar.json b/src/Documentation/sidebar.json index 9e685fa17c..6085745ff3 100644 --- a/src/Documentation/sidebar.json +++ b/src/Documentation/sidebar.json @@ -1,217 +1,336 @@ [ { - "name": "Get Started", - "folder": "/static/docs/get-started", - "indexFile": "index.md", - "files": [ - "agenda.md", - "install.md", - "initialize.md", - "configure.md", - "add-files.md", - "share-data.md", - "retrieve-data.md", - "connect-code-and-data.md", - "pipeline.md", - "visualize.md", - "reproduce.md", - "metrics.md", - "experiments.md", - "compare-experiments.md", - "older-versions.md", - "example-versioning.md", - "example-pipeline.md" - ], - "labels": { - "connect-code-and-data.md": "Connect with Code", - "example-pipeline.md": "Example: Pipelines", - "example-versioning.md": "Example: Versioning", - "older-versions.md": "Get Older Files" - } + "slug": "get-started", + "source": "get-started/index.md", + "children": [ + "agenda", + "install", + "initialize", + "configure", + "add-files", + "share-data", + "retrieve-data", + { + "label": "Connect with Code", + "slug": "connect-code-and-data" + }, + "pipeline", + "visualize", + "reproduce", + "metrics", + "experiments", + "compare-experiments", + { + "label": "Get Older Files", + "slug": "older-versions" + }, + { + "label": "Example: Versioning", + "slug": "example-versioning" + }, + { + "label": "Example: Pipelines", + "slug": "example-pipeline" + } + ] }, { - "name": "Use Cases", - "folder": "/static/docs/use-cases", - "indexFile": "index.md", - "files": [ - "data-and-model-files-versioning.md", - "share-data-and-model-files.md", - "multiple-data-scientists-on-a-single-machine.md" - ], - "labels": { - "data-and-model-files-versioning.md": "Data & Model Files Versioning", - "share-data-and-model-files.md": "Share Data & Model Files", - "multiple-data-scientists-on-a-single-machine.md": "Shared Development Machine" - } + "slug": "use-cases", + "source": "use-cases/index.md", + "children": [ + { + "label": "Data & Model Files Versioning", + "slug": "data-and-model-files-versioning" + }, + { + "label": "Share Data & Model Files", + "slug": "share-data-and-model-files" + }, + { + "label": "Shared Development Machine", + "slug": "multiple-data-scientists-on-a-single-machine" + } + ] }, { - "name": "User Guide", - "folder": "/static/docs/user-guide", - "files": [ - "dvc-files-and-directories.md", - "dvc-file-format.md", - "large-dataset-optimization.md", - "external-dependencies.md", - "external-outputs.md", - "development.md", - "contributing.md", - "contributing-documentation.md", - "update-tracked-file.md", - "autocomplete.md", - "plugins.md", - "analytics.md", - "dvcignore.md", - "running-dvc-on-windows.md" - ], - "labels": { - "dvc-files-and-directories.md": "Files and Directories", - "dvc-file-format.md": "File Format (.dvc)", - "large-dataset-optimization.md": "Large Dataset Optimization", - "development.md": "Development Version", - "external-dependencies.md": "External Dependencies", - "external-outputs.md": "Managing External Data", - "contributing.md": "Contributing", - "contributing-documentation.md": "Contributing Documentation", - "update-tracked-file.md": "Update a Tracked File", - "autocomplete.md": "Shell Autocomplete", - "plugins.md": "Plugins & Integrations", - "analytics.md": "Anonymized Usage Analytics", - "running-dvc-on-windows.md": "Running DVC on Windows" - } + "slug": "user-guide", + "source": false, + "children": [ + { + "label": "Files and Directories", + "slug": "dvc-files-and-directories" + }, + { + "label": "File Format (.dvc)", + "slug": "dvc-file-format" + }, + "large-dataset-optimization", + "external-dependencies", + { + "label": "Managing External Data", + "slug": "external-outputs" + }, + { + "label": "Development Version", + "slug": "development" + }, + "contributing", + "contributing-documentation", + { + "label": "Update a Tracked File", + "slug": "update-tracked-file" + }, + { + "label": "Shell Autocomplete", + "slug": "autocomplete" + }, + { + "label": "Plugins & Integrations", + "slug": "plugins" + }, + { + "label": "Anonymized Usage Analytics", + "slug": "analytics" + }, + "dvcignore", + { + "label": "Running DVC on Windows", + "slug": "running-dvc-on-windows" + } + ] }, { - "name": "Commands Reference", - "folder": "/static/docs/commands-reference", - "indexFile": "index.md", - "files": [ - "add.md", - ["cache.md", "cache_dir.md"], - "checkout.md", - "commit.md", - "config.md", - "destroy.md", - "diff.md", - "fetch.md", - "get-url.md", - "get.md", - "gc.md", - "import-url.md", - "import.md", - "init.md", - "install.md", - "lock.md", - [ - "metrics.md", - "metrics_add.md", - "metrics_modify.md", - "metrics_remove.md", - "metrics_show.md" - ], - "move.md", - ["pipeline.md", "pipeline_list.md", "pipeline_show.md"], - "pull.md", - "push.md", - [ - "remote.md", - "remote_add.md", - "remote_default.md", - "remote_list.md", - "remote_modify.md", - "remote_remove.md" - ], - "remove.md", - "repro.md", - "root.md", - "run.md", - "status.md", - "unlock.md", - "unprotect.md", - "update.md", - "version.md" - ], - "labels": { - "add.md": "add", - "cache.md": "cache", - "cache_dir.md": "cache dir", - "checkout.md": "checkout", - "commit.md": "commit", - "config.md": "config", - "destroy.md": "destroy", - "diff.md": "diff", - "fetch.md": "fetch", - "get-url.md": "get-url", - "get.md": "get", - "gc.md": "gc", - "import-url.md": "import-url", - "import.md": "import", - "init.md": "init", - "install.md": "install", - "lock.md": "lock", - "metrics.md": "metrics", - "metrics_add.md": "metrics add", - "metrics_show.md": "metrics show", - "metrics_modify.md": "metrics modify", - "metrics_remove.md": "metrics remove", - "move.md": "move", - "pipeline.md": "pipeline", - "pipeline_list.md": "pipeline list", - "pipeline_show.md": "pipeline show", - "pull.md": "pull", - "push.md": "push", - "remote.md": "remote", - "remote_add.md": "remote add", - "remote_default.md": "remote default", - "remote_list.md": "remote list", - "remote_modify.md": "remote modify", - "remote_remove.md": "remote remove", - "remove.md": "remove", - "repro.md": "repro", - "root.md": "root", - "run.md": "run", - "status.md": "status", - "unlock.md": "unlock", - "unprotect.md": "unprotect", - "update.md": "update", - "version.md": "version" - } + "slug": "commands-reference", + "source": "commands-reference/index.md", + "children": [ + { + "label": "add", + "slug": "add" + }, + { + "label": "cache", + "slug": "cache", + "source": "cache/index.md", + "children": [ + { + "label": "cache dir", + "slug": "dir" + } + ] + }, + { + "label": "checkout", + "slug": "checkout" + }, + { + "label": "commit", + "slug": "commit" + }, + { + "label": "config", + "slug": "config" + }, + { + "label": "destroy", + "slug": "destroy" + }, + { + "label": "diff", + "slug": "diff" + }, + { + "label": "fetch", + "slug": "fetch" + }, + { + "label": "get-url", + "slug": "get-url" + }, + { + "label": "get", + "slug": "get" + }, + { + "label": "gc", + "slug": "gc" + }, + { + "label": "import-url", + "slug": "import-url" + }, + { + "label": "import", + "slug": "import" + }, + { + "label": "init", + "slug": "init" + }, + { + "label": "install", + "slug": "install" + }, + { + "label": "lock", + "slug": "lock" + }, + { + "label": "metrics", + "slug": "metrics", + "source": "metrics/index.md", + "children": [ + { + "label": "metrics add", + "slug": "add" + }, + { + "label": "metrics modify", + "slug": "modify" + }, + { + "label": "metrics remove", + "slug": "remove" + }, + { + "label": "metrics show", + "slug": "show" + } + ] + }, + { + "label": "move", + "slug": "move" + }, + { + "label": "pipeline", + "slug": "pipeline", + "source": "pipeline/index.md", + "children": [ + { + "label": "pipeline list", + "slug": "list" + }, + { + "label": "pipeline show", + "slug": "show" + } + ] + }, + { + "label": "pull", + "slug": "pull" + }, + { + "label": "push", + "slug": "push" + }, + { + "label": "remote", + "slug": "remote", + "source": "remote/index.md", + "children": [ + { + "label": "remote add", + "slug": "add" + }, + { + "label": "remote default", + "slug": "default" + }, + { + "label": "remote list", + "slug": "list" + }, + { + "label": "remote modify", + "slug": "modify" + }, + { + "label": "remote remove", + "slug": "remove" + } + ] + }, + { + "label": "remove", + "slug": "remove" + }, + { + "label": "repro", + "slug": "repro" + }, + { + "label": "root", + "slug": "root" + }, + { + "label": "run", + "slug": "run" + }, + { + "label": "status", + "slug": "status" + }, + { + "label": "unlock", + "slug": "unlock" + }, + { + "label": "unprotect", + "slug": "unprotect" + }, + { + "label": "update", + "slug": "update" + }, + { + "label": "version", + "slug": "version" + } + ] }, { - "name": "Tutorial", - "folder": "/static/docs/tutorial", - "indexFile": "index.md", - "files": [ - "preparation.md", - "define-ml-pipeline.md", - "reproducibility.md", - "sharing-data.md" - ], - "labels": { - "define-ml-pipeline.md": "Define ML Pipeline" - } + "slug": "tutorial", + "source": "tutorial/index.md", + "children": [ + "preparation", + { + "label": "Define ML Pipeline", + "slug": "define-ml-pipeline" + }, + "reproducibility", + "sharing-data" + ] }, { - "name": "Understanding DVC", - "folder": "/static/docs/understanding-dvc", + "label": "Understanding DVC", + "slug": "understanding-dvc", + "source": false, "files": [ - "collaboration-issues.md", - "existing-tools.md", - "what-is-dvc.md", - "core-features.md", - "how-it-works.md", - "related-technologies.md", - "resources.md" - ], - "labels": { - "what-is-dvc.md": "What is DVC?" - } + "collaboration-issues", + "existing-tools", + { + "label": "What is DVC?", + "slug": "what-is-dvc.md" + }, + "core-features", + "how-it-works", + "related-technologies", + "resources" + ] }, { - "name": "Changelog", - "folder": "/static/docs/changelog", - "files": ["0.35.md", "0.18.md"], - "labels": { - "0.18.md": "v0.12 - v0.18", - "0.35.md": "v0.19 - v0.35" - } + "slug": "changelog", + "source": false, + "children": [ + { + "label": "v0.12 - v0.18", + "slug": "0.18" + }, + { + "label": "v0.19 - v0.35", + "slug": "0.25" + } + ] } ] diff --git a/static/docs/commands-reference/cache_dir.md b/static/docs/commands-reference/cache/dir.md similarity index 100% rename from static/docs/commands-reference/cache_dir.md rename to static/docs/commands-reference/cache/dir.md diff --git a/static/docs/commands-reference/cache.md b/static/docs/commands-reference/cache/index.md similarity index 100% rename from static/docs/commands-reference/cache.md rename to static/docs/commands-reference/cache/index.md diff --git a/static/docs/commands-reference/metrics_add.md b/static/docs/commands-reference/metrics/add.md similarity index 100% rename from static/docs/commands-reference/metrics_add.md rename to static/docs/commands-reference/metrics/add.md diff --git a/static/docs/commands-reference/metrics.md b/static/docs/commands-reference/metrics/index.md similarity index 100% rename from static/docs/commands-reference/metrics.md rename to static/docs/commands-reference/metrics/index.md diff --git a/static/docs/commands-reference/metrics_modify.md b/static/docs/commands-reference/metrics/modify.md similarity index 100% rename from static/docs/commands-reference/metrics_modify.md rename to static/docs/commands-reference/metrics/modify.md diff --git a/static/docs/commands-reference/metrics_remove.md b/static/docs/commands-reference/metrics/remove.md similarity index 100% rename from static/docs/commands-reference/metrics_remove.md rename to static/docs/commands-reference/metrics/remove.md diff --git a/static/docs/commands-reference/metrics_show.md b/static/docs/commands-reference/metrics/show.md similarity index 99% rename from static/docs/commands-reference/metrics_show.md rename to static/docs/commands-reference/metrics/show.md index 64ecca7ea0..4dba62d165 100644 --- a/static/docs/commands-reference/metrics_show.md +++ b/static/docs/commands-reference/metrics/show.md @@ -23,8 +23,8 @@ The optional `path` argument can represent a DVC metric file or a directory. If with the `-R` option. Providing `type` (via `-t` CLI option), overrides the full metric specification -(both, `type` and `xpath`) defined in the DVC-file (usually, using `dvc metrics -modify` command). +(both, `type` and `xpath`) defined in the DVC-file (usually, using +`dvc metrics modify` command). If `type` (via `-t`) is not specified and only `xpath` (`-x`) is, only `xpath` is overridden. It will try to read type from the DVC-file. The `type` can be diff --git a/static/docs/commands-reference/pipeline.md b/static/docs/commands-reference/pipeline/index.md similarity index 100% rename from static/docs/commands-reference/pipeline.md rename to static/docs/commands-reference/pipeline/index.md diff --git a/static/docs/commands-reference/pipeline_list.md b/static/docs/commands-reference/pipeline/list.md similarity index 100% rename from static/docs/commands-reference/pipeline_list.md rename to static/docs/commands-reference/pipeline/list.md diff --git a/static/docs/commands-reference/pipeline_show.md b/static/docs/commands-reference/pipeline/show.md similarity index 100% rename from static/docs/commands-reference/pipeline_show.md rename to static/docs/commands-reference/pipeline/show.md diff --git a/static/docs/commands-reference/remote_add.md b/static/docs/commands-reference/remote/add.md similarity index 100% rename from static/docs/commands-reference/remote_add.md rename to static/docs/commands-reference/remote/add.md diff --git a/static/docs/commands-reference/remote_default.md b/static/docs/commands-reference/remote/default.md similarity index 100% rename from static/docs/commands-reference/remote_default.md rename to static/docs/commands-reference/remote/default.md diff --git a/static/docs/commands-reference/remote.md b/static/docs/commands-reference/remote/index.md similarity index 100% rename from static/docs/commands-reference/remote.md rename to static/docs/commands-reference/remote/index.md diff --git a/static/docs/commands-reference/remote_list.md b/static/docs/commands-reference/remote/list.md similarity index 100% rename from static/docs/commands-reference/remote_list.md rename to static/docs/commands-reference/remote/list.md diff --git a/static/docs/commands-reference/remote_modify.md b/static/docs/commands-reference/remote/modify.md similarity index 100% rename from static/docs/commands-reference/remote_modify.md rename to static/docs/commands-reference/remote/modify.md diff --git a/static/docs/commands-reference/remote_remove.md b/static/docs/commands-reference/remote/remove.md similarity index 100% rename from static/docs/commands-reference/remote_remove.md rename to static/docs/commands-reference/remote/remove.md From 3b2f1c80a228efbedf33c496b329485b4c93d6b9 Mon Sep 17 00:00:00 2001 From: Alexey Ivanov Date: Wed, 7 Aug 2019 21:00:03 +0500 Subject: [PATCH 02/10] Fixed typo in config --- src/Documentation/sidebar.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Documentation/sidebar.json b/src/Documentation/sidebar.json index 6085745ff3..22a0a0e642 100644 --- a/src/Documentation/sidebar.json +++ b/src/Documentation/sidebar.json @@ -306,7 +306,7 @@ "label": "Understanding DVC", "slug": "understanding-dvc", "source": false, - "files": [ + "children": [ "collaboration-issues", "existing-tools", { From aef9d62f9fb6c54e75df6697d3ae250e0dd116ea Mon Sep 17 00:00:00 2001 From: Alexey Ivanov Date: Wed, 7 Aug 2019 21:48:11 +0500 Subject: [PATCH 03/10] Return docSearch-lvl0 class for parent dir --- src/Documentation/SidebarMenu/SidebarMenu.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Documentation/SidebarMenu/SidebarMenu.js b/src/Documentation/SidebarMenu/SidebarMenu.js index d88ebea8ef..37d2f9c155 100644 --- a/src/Documentation/SidebarMenu/SidebarMenu.js +++ b/src/Documentation/SidebarMenu/SidebarMenu.js @@ -11,6 +11,8 @@ import { getParentsListFromPath } from './helper' function SidebarMenuItem({ children, label, path, activePaths, onNavigate }) { const isActive = activePaths && activePaths.includes(path) + const isRootParent = + activePaths && activePaths.length > 1 && activePaths[0] === path return ( <> @@ -18,6 +20,7 @@ function SidebarMenuItem({ children, label, path, activePaths, onNavigate }) { href={path} onClick={e => onNavigate(path, e)} isActive={isActive} + className={isRootParent ? 'docSearch-lvl0' : ''} > {label} From 5f5de796a016631b77ff18613da4d5f0e0fffa91 Mon Sep 17 00:00:00 2001 From: Alexey Ivanov Date: Wed, 7 Aug 2019 23:00:52 +0500 Subject: [PATCH 04/10] Return key for ReactMarkdown to force animations --- src/Documentation/Markdown/Markdown.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Documentation/Markdown/Markdown.js b/src/Documentation/Markdown/Markdown.js index 5b56400bf2..30ee934449 100644 --- a/src/Documentation/Markdown/Markdown.js +++ b/src/Documentation/Markdown/Markdown.js @@ -133,6 +133,7 @@ export default class Markdown extends Component { Edit on Github Date: Wed, 7 Aug 2019 23:18:49 +0500 Subject: [PATCH 05/10] Fix next/prev order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix bug then parent was added to prev/next order after children - Fix bug the prev item didn’t have source and was redirecting back to first child - Add check for items with source = false and without children, they throw erros now --- src/Documentation/SidebarMenu/helper.js | 29 +++++++++++++++++++------ 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/Documentation/SidebarMenu/helper.js b/src/Documentation/SidebarMenu/helper.js index 7b45056380..538e712516 100644 --- a/src/Documentation/SidebarMenu/helper.js +++ b/src/Documentation/SidebarMenu/helper.js @@ -28,12 +28,20 @@ function normalizeSidebar(data, parentPath) { return data.map(item => { let normalizedItem + /* + Edge case: If parent don't have source, we will need to return it's prev instead. + Because only items with children can be sourceless, it's safe to go back only once. + */ + const prev = + prevReference && + (prevReference.source ? prevReference.path : prevReference.prev) + if (typeof item === 'string') { normalizedItem = { path: PATH_ROOT + parentPath + item, source: FILE_ROOT + parentPath + item + FILE_EXTENSION, label: startCase(item), - prev: prevReference && prevReference.path, + prev, next: undefined } } else { @@ -44,6 +52,13 @@ function normalizeSidebar(data, parentPath) { } const isSourceDisabled = source === false // is source explictly set to 'false'? + + if (isSourceDisabled && (!children || !children.length)) { + throw Error( + "If you set 'source' to false, you had to add at least one child" + ) + } + const sourceFileName = source ? source : slug + FILE_EXTENSION const sourcePath = FILE_ROOT + parentPath + sourceFileName @@ -51,14 +66,9 @@ function normalizeSidebar(data, parentPath) { path: PATH_ROOT + parentPath + slug, source: isSourceDisabled ? false : sourcePath, label: label ? label : startCase(slug), - prev: prevReference && prevReference.path, + prev, next: undefined } - - if (children) { - const newParentPath = `${parentPath}${slug}/` - normalizedItem.children = normalizeSidebar(children, newParentPath) - } } if (prevReference) { @@ -67,6 +77,11 @@ function normalizeSidebar(data, parentPath) { prevReference = normalizedItem + if (item.children) { + const newParentPath = `${parentPath}${item.slug}/` + normalizedItem.children = normalizeSidebar(item.children, newParentPath) + } + return normalizedItem }) } From e125a81997df698d1207a376496ad1ac6e170b08 Mon Sep 17 00:00:00 2001 From: Alexey Ivanov Date: Thu, 8 Aug 2019 01:18:07 +0500 Subject: [PATCH 06/10] Fix for nested source = false Fixed bug with nested intems with osurce false, it required large refactor of the normalization function. --- src/Documentation/SidebarMenu/helper.js | 159 +++++++++++++----------- 1 file changed, 86 insertions(+), 73 deletions(-) diff --git a/src/Documentation/SidebarMenu/helper.js b/src/Documentation/SidebarMenu/helper.js index 538e712516..d4eec28df8 100644 --- a/src/Documentation/SidebarMenu/helper.js +++ b/src/Documentation/SidebarMenu/helper.js @@ -3,7 +3,7 @@ import startCase from 'lodash.startcase' import sidebar from '../sidebar' /* - We will use this function to normalize sidebar structure and create + We will use this helper to normalize sidebar structure and create all of the resurces we need to prevent future recalculations. Target structure example: @@ -22,101 +22,114 @@ const PATH_ROOT = '/doc/' const FILE_ROOT = '/static/docs/' const FILE_EXTENSION = '.md' -let prevReference // We will save prev item reference here to use for the next and prev fields - -function normalizeSidebar(data, parentPath) { - return data.map(item => { - let normalizedItem - - /* - Edge case: If parent don't have source, we will need to return it's prev instead. - Because only items with children can be sourceless, it's safe to go back only once. - */ - const prev = - prevReference && - (prevReference.source ? prevReference.path : prevReference.prev) - - if (typeof item === 'string') { - normalizedItem = { - path: PATH_ROOT + parentPath + item, - source: FILE_ROOT + parentPath + item + FILE_EXTENSION, - label: startCase(item), - prev, - next: undefined - } - } else { - const { label, slug, source, children } = item +// Inner helpers - if (!slug) { - throw Error("'slug' field is required in objects in sidebar.json") +function findItem(data, targetPath) { + if (data.length) { + for (let i = 0; i < data.length; i++) { + const { path, children } = data[i] + + if (path === targetPath) { + return data[i] + } else if (children) { + const result = findItem(children, targetPath) + if (result) { + return result + } } + } + } +} - const isSourceDisabled = source === false // is source explictly set to 'false'? +function findChildWithSource(item) { + return item.source ? item : findChildWithSource(item.children[0]) +} - if (isSourceDisabled && (!children || !children.length)) { - throw Error( - "If you set 'source' to false, you had to add at least one child" - ) - } +function findPrevItemWithSource(data, item) { + if (item.source) { + return item + } else if (item.prev) { + const prevItem = findItem(data, item.prev) - const sourceFileName = source ? source : slug + FILE_EXTENSION - const sourcePath = FILE_ROOT + parentPath + sourceFileName + return findPrevItemWithSource(data, prevItem) + } +} - normalizedItem = { - path: PATH_ROOT + parentPath + slug, - source: isSourceDisabled ? false : sourcePath, - label: label ? label : startCase(slug), - prev, - next: undefined - } - } +function validateRawItem({ slug, source, children }) { + const isSourceDisabled = source === false // is source set to 'false'? + + if (!slug) { + throw Error("'slug' field is required in objects in sidebar.json") + } + + if (isSourceDisabled && (!children || !children.length)) { + throw Error( + "If you set 'source' to false, you had to add at least one child" + ) + } +} + +// Global cache vars used in normalization + +let prevReference // Save last item here to generate the prev field +const normalizedSidebar = [] // Current state of sidebar to search for prev + +// Normalization + +function normalizeItem(item, parentPath) { + validateRawItem(item) + + const { label, slug, source } = item + + // If prev item don't have source we need to recirsively search for it + const prevItemWithSource = + prevReference && findPrevItemWithSource(normalizedSidebar, prevReference) + + const prev = prevItemWithSource && prevItemWithSource.path + + const sourceFileName = source ? source : slug + FILE_EXTENSION + const sourcePath = FILE_ROOT + parentPath + sourceFileName + + return { + path: PATH_ROOT + parentPath + slug, + source: source === false ? false : sourcePath, + label: label ? label : startCase(slug), + prev, + next: undefined + } +} + +function normalizeSidebar(data, parentPath, currentSidebar) { + data.forEach(item => { + const isShortcut = typeof item === 'string' + const fullItem = isShortcut ? { slug: item } : item + const normalizedItem = normalizeItem(fullItem, parentPath) if (prevReference) { prevReference.next = normalizedItem.path } - prevReference = normalizedItem + prevReference = normalizedItem // Set it before children to preserve order if (item.children) { const newParentPath = `${parentPath}${item.slug}/` - normalizedItem.children = normalizeSidebar(item.children, newParentPath) + normalizedItem.children = [] + normalizeSidebar(item.children, newParentPath, normalizedItem.children) } - return normalizedItem + currentSidebar.push(normalizedItem) }) } -const normalizedSidebar = normalizeSidebar(sidebar, '') - -function findItem(data, targetPath) { - if (data.length) { - for (let i = 0; i < data.length; i++) { - const { path, source, children } = data[i] +normalizeSidebar(sidebar, '', normalizedSidebar) // Init normalization - if (path === targetPath && !source && children && children[0]) { - // If parent have blank source and children, then return first child instead - return children[0] - } else if (path === targetPath) { - // Return item normally - return data[i] - } else if (children) { - // Search for match in children recursevly - const result = findItem(children, targetPath) - if (result) { - return result - } - } - } - } -} +// Exports export function getItemByPath(path) { - // Edge case for the root url, return first item instead - if (path === PATH_ROOT.slice(0, -1)) { - return normalizedSidebar[0] - } + const isRoot = path === PATH_ROOT.slice(0, -1) + const item = isRoot ? normalizedSidebar[0] : findItem(normalizedSidebar, path) - return findItem(normalizedSidebar, path) + return item && findChildWithSource(item) } export function getParentsListFromPath(path) { From 21e86d7f6840cc69b020f8e15d34195359cbb5c9 Mon Sep 17 00:00:00 2001 From: Alexey Ivanov Date: Thu, 8 Aug 2019 15:52:29 +0500 Subject: [PATCH 07/10] Fix issues form review - Fix typo in comment text - Fix error in slug name in sidebar.json - Move fetch inside else clause --- pages/doc.js | 46 ++++++++++++------------- src/Documentation/SidebarMenu/helper.js | 2 +- src/Documentation/sidebar.json | 2 +- 3 files changed, 24 insertions(+), 26 deletions(-) diff --git a/pages/doc.js b/pages/doc.js index 7e897bea4b..6ea34a2147 100644 --- a/pages/doc.js +++ b/pages/doc.js @@ -79,31 +79,29 @@ export default class Documentation extends Component { if (!item) { this.setState({ pageNotFound: true, currentItem: {} }) - - return - } - - fetch(item.source) - .then(res => { - res.text().then(text => { - this.setState( - { - markdown: text, - headings: [], - pageNotFound: false, - isMenuOpen: false, - currentItem: item - }, - () => { - this.scrollTop() - this.parseHeadings(text) - } - ) + } else { + fetch(item.source) + .then(res => { + res.text().then(text => { + this.setState( + { + markdown: text, + headings: [], + pageNotFound: false, + isMenuOpen: false, + currentItem: item + }, + () => { + this.scrollTop() + this.parseHeadings(text) + } + ) + }) }) - }) - .catch(() => { - window.location.reload() - }) + .catch(() => { + window.location.reload() + }) + } } parseHeadings = text => { diff --git a/src/Documentation/SidebarMenu/helper.js b/src/Documentation/SidebarMenu/helper.js index d4eec28df8..7a671035df 100644 --- a/src/Documentation/SidebarMenu/helper.js +++ b/src/Documentation/SidebarMenu/helper.js @@ -81,7 +81,7 @@ function normalizeItem(item, parentPath) { const { label, slug, source } = item - // If prev item don't have source we need to recirsively search for it + // If prev item doesn't have source we need to recirsively search for it const prevItemWithSource = prevReference && findPrevItemWithSource(normalizedSidebar, prevReference) diff --git a/src/Documentation/sidebar.json b/src/Documentation/sidebar.json index 22a0a0e642..2c24b159cb 100644 --- a/src/Documentation/sidebar.json +++ b/src/Documentation/sidebar.json @@ -311,7 +311,7 @@ "existing-tools", { "label": "What is DVC?", - "slug": "what-is-dvc.md" + "slug": "what-is-dvc" }, "core-features", "how-it-works", From 5749ace60b39fa74f78676b47fb3f35ccc72832c Mon Sep 17 00:00:00 2001 From: Alexey Ivanov Date: Thu, 8 Aug 2019 16:11:22 +0500 Subject: [PATCH 08/10] Move prevReference and current result inside --- src/Documentation/SidebarMenu/helper.js | 57 ++++++++++++++++--------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/src/Documentation/SidebarMenu/helper.js b/src/Documentation/SidebarMenu/helper.js index 7a671035df..4cd9fba9ec 100644 --- a/src/Documentation/SidebarMenu/helper.js +++ b/src/Documentation/SidebarMenu/helper.js @@ -69,21 +69,16 @@ function validateRawItem({ slug, source, children }) { } } -// Global cache vars used in normalization - -let prevReference // Save last item here to generate the prev field -const normalizedSidebar = [] // Current state of sidebar to search for prev - // Normalization -function normalizeItem(item, parentPath) { +function normalizeItem({ item, parentPath, resultRef, prevRef }) { validateRawItem(item) const { label, slug, source } = item // If prev item doesn't have source we need to recirsively search for it const prevItemWithSource = - prevReference && findPrevItemWithSource(normalizedSidebar, prevReference) + prevRef && findPrevItemWithSource(resultRef, prevRef) const prev = prevItemWithSource && prevItemWithSource.path @@ -99,29 +94,51 @@ function normalizeItem(item, parentPath) { } } -function normalizeSidebar(data, parentPath, currentSidebar) { - data.forEach(item => { - const isShortcut = typeof item === 'string' - const fullItem = isShortcut ? { slug: item } : item - const normalizedItem = normalizeItem(fullItem, parentPath) +function normalizeSidebar({ + data, + parentPath, + parentResultRef, + startingPrevRef +}) { + const currentResult = [] + const resultRef = parentResultRef || currentResult + let prevRef = startingPrevRef + + data.forEach(rawItem => { + const isShortcut = typeof rawItem === 'string' + const item = isShortcut ? { slug: rawItem } : rawItem + const normalizedItem = normalizeItem({ + item, + parentPath, + resultRef, + prevRef + }) - if (prevReference) { - prevReference.next = normalizedItem.path + if (prevRef) { + prevRef.next = normalizedItem.path } - prevReference = normalizedItem // Set it before children to preserve order + prevRef = normalizedItem // Set it before children to preserve order if (item.children) { - const newParentPath = `${parentPath}${item.slug}/` - normalizedItem.children = [] - normalizeSidebar(item.children, newParentPath, normalizedItem.children) + normalizedItem.children = normalizeSidebar({ + data: item.children, + parentPath: `${parentPath}${item.slug}/`, + parentResultRef: resultRef, + startingPrevRef: prevRef + }) } - currentSidebar.push(normalizedItem) + currentResult.push(normalizedItem) }) + + return currentResult } -normalizeSidebar(sidebar, '', normalizedSidebar) // Init normalization +const normalizedSidebar = normalizeSidebar({ + data: sidebar, + parentPath: '' +}) // Exports From ebcf470aaf59c1bbc8da82a19ced941493389fdc Mon Sep 17 00:00:00 2001 From: Alexey Ivanov Date: Thu, 8 Aug 2019 16:21:10 +0500 Subject: [PATCH 09/10] Remove useles comment --- src/Documentation/SidebarMenu/helper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Documentation/SidebarMenu/helper.js b/src/Documentation/SidebarMenu/helper.js index 4cd9fba9ec..47979890a0 100644 --- a/src/Documentation/SidebarMenu/helper.js +++ b/src/Documentation/SidebarMenu/helper.js @@ -56,7 +56,7 @@ function findPrevItemWithSource(data, item) { } function validateRawItem({ slug, source, children }) { - const isSourceDisabled = source === false // is source set to 'false'? + const isSourceDisabled = source === false if (!slug) { throw Error("'slug' field is required in objects in sidebar.json") From 6dc71275bac59e24d5fc4005a2848a154be0f063 Mon Sep 17 00:00:00 2001 From: Alexey Ivanov Date: Thu, 8 Aug 2019 21:02:10 +0500 Subject: [PATCH 10/10] Switch to loadash.includes --- src/Documentation/SidebarMenu/SidebarMenu.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Documentation/SidebarMenu/SidebarMenu.js b/src/Documentation/SidebarMenu/SidebarMenu.js index 37d2f9c155..e746fa7461 100644 --- a/src/Documentation/SidebarMenu/SidebarMenu.js +++ b/src/Documentation/SidebarMenu/SidebarMenu.js @@ -3,6 +3,8 @@ import $ from 'jquery' import PerfectScrollbar from 'perfect-scrollbar' // components import DownloadButton from '../../DownloadButton' +// utils +import includes from 'lodash.includes' // styles import styled from 'styled-components' import { media, OnlyDesktop } from '../../styles' @@ -10,7 +12,7 @@ import { media, OnlyDesktop } from '../../styles' import { getParentsListFromPath } from './helper' function SidebarMenuItem({ children, label, path, activePaths, onNavigate }) { - const isActive = activePaths && activePaths.includes(path) + const isActive = activePaths && includes(activePaths, path) const isRootParent = activePaths && activePaths.length > 1 && activePaths[0] === path