Vuepress: can support nested sidebar?

Created on 28 Apr 2018  ยท  9Comments  ยท  Source: vuejs/vuepress

current sidebar only support

.
โ”œโ”€ README.md
โ”œโ”€ contact.md
โ”œโ”€ about.md
โ”œโ”€ foo/
โ”‚  โ”œโ”€ README.md
โ”‚  โ”œโ”€ one.md
โ”‚  โ””โ”€ two.md
โ””โ”€ bar/
   โ”œโ”€ README.md
   โ”œโ”€ three.md
   โ””โ”€ four.md

but can support this?

โ”œโ”€ README.md
โ”œโ”€ contact.md
โ”œโ”€ about.md
โ”œโ”€ foo/
|  โ”œโ”€foo1/
|  |    โ”œโ”€ README.md
|  โ”œโ”€foo2/
|  |    โ”œโ”€ README.md
โ”‚  โ”œโ”€ README.md
โ”‚  โ”œโ”€ one.md
โ”‚  โ””โ”€ two.md
โ””โ”€ bar/
   โ”œโ”€ README.md
   โ”œโ”€ three.md
   โ””โ”€ four.md

I find vuepress/lib/default-theme/util.js

if (isNested) {
  console.error(
    '[vuepress] Nested sidebar groups are not supported. ' +
    'Consider using navbar + categories instead.'
  )
}
contribution welcome

Most helpful comment

Is it possible to have nested collapsible sidebar? Currently I can only get top-level-collapsible sidebar to work.

Directory structure:

โ”œโ”€โ”€ foo
โ”‚ย ย  โ”œโ”€โ”€ bar.md
โ”‚ย ย  โ””โ”€โ”€ README.md
โ””โ”€โ”€ README.md

With only top-level-collapsible sidebar: (working)

themeConfig: {
  sidebar: {
    '/docs/': [{
      title: 'Docs',
      collapasble: true,
      children: [
        '',
        'foo/'
      ]
    }]
  }
}

With nested-collapsible sidebar: (expected)

themeConfig: {
  sidebar: {
    '/docs/': [{
      title: 'Docs',
      collapsable: true,
      children: [
        '',
        ['foo/', {
          title: 'Foo',
          collapsable: true,
          children: [
            '',
            'bar'
          ]
        }]
      ]
    }]
  }
}

All 9 comments

The answer is Yes!

I made it possible with the document structure such as

./docs/foo/foo1/example.md

And it works well by now, but the only problem is 404 was got when refreshing or opening the link directly.

image

I have this working.
It might be a better idea to separate out the sidebar into its own file and import it into the config, as it looks like nesting sidebars will get out of control quickly all in one place.

@hustjiangtao the key to stopping those 404's is to make sure that the nested sidebars are called before their root directory in the config.js. If /docs/ executes first here, it will break the sidebars for /docs/foo/ and /docs/bar/

But if you notice at the bottom, I actually link to those nested sidebars from the "parent" sidebar. The only thing that's missing now is a back button inside of the nested sidebar.

Because once you reach the nested page, the only way to get back to the previous sidebar page, is the browser back button, or circling back around through Nav links.

  '/docs/foo/': [
        '',
        [ '/docs/foo/failover', 'Failover'],
        'VPNs'
      ],
      '/docs/bar/': [
        '',
        'bar'
      ],
      '/docs/': [
        '',
        'one',
        'two',
        'three',
        'four',
        [ '/docs/foo/', 'Foo'],
        [ '/docs/bar/', 'Bar']
      ],

Edit: Confirmed working at least three levels deep, looks like you can just nest inception with this pattern.

      '/docs/foo/nestor/': [
        '',
        [ '/docs/foo/nestor/testor', 'Testor']
      ],
      '/docs/foo/': [
        '',
        [ '/docs/foo/failover', 'Failover'],
        'VPNs',
        [ '/docs/foo/nestor/', 'Nestor']
      ],
     '/docs/: [
...

It looks like setting yml

sidebar: auto
---

In any md files breaks this behavior though. Probably however the frontmatter is parsed?

We're closing this issue as stale as it's more than 20 days without activity, and without an associated Pull Request. Please feel free to continue discussion. We'll reopen this issue if anything actionable is posted.

Is it possible to have nested collapsible sidebar? Currently I can only get top-level-collapsible sidebar to work.

Directory structure:

โ”œโ”€โ”€ foo
โ”‚ย ย  โ”œโ”€โ”€ bar.md
โ”‚ย ย  โ””โ”€โ”€ README.md
โ””โ”€โ”€ README.md

With only top-level-collapsible sidebar: (working)

themeConfig: {
  sidebar: {
    '/docs/': [{
      title: 'Docs',
      collapasble: true,
      children: [
        '',
        'foo/'
      ]
    }]
  }
}

With nested-collapsible sidebar: (expected)

themeConfig: {
  sidebar: {
    '/docs/': [{
      title: 'Docs',
      collapsable: true,
      children: [
        '',
        ['foo/', {
          title: 'Foo',
          collapsable: true,
          children: [
            '',
            'bar'
          ]
        }]
      ]
    }]
  }
}

Hey @ulivz, I've been looking at this and would be happy to get involved and submit a PR.

I've got a multi-level sidebar working with the default theme (ejected) and would be happy to jump in and give all the various edge cases a go, update the theme, write tests, update docs, etc.

I've also built some helpers which make it much easier to build sidebar structures, as follows:

image

Have also made some investigation into adding "meta" information so sidebars like the following (additional page types, icons, or such like) could be easily built by users or theme extenders...

image

...just by supplying their own helpers:

demo (path, title) {
  return { path, title, meta: { type: 'demo', icon: 'code' } }
}
const sidebar = group('pages', [
  'intro',
  demo('getting-started'),
  group('demos', [
    // add more demos
  ])
])

This meta information would be additional to the front matter from pages.

I've had a good dig around the source and understand how the pages are resolved, but I imagine that there are various use cases or design decisions to be taken into account if a multi-level sidebar is to be supported.

What would be the best way for me to continue working on this, and getting a knowledge of the various use cases?

I have submitted a pull request to implement this feature~

@davestewart Hi, is it possible to share somewhere those helpers/themes (like a gist) please ?
I'm trying to achieve exactly the same thing, I'd like to understand exactly the definition of the group helper.

Thanks !

EDIT: Doesn't seem to work on build.

@Seraf:
Not sure id this is as smart solution, but you only need to change one file. I tried to add a SidebarGroup inside another SidebarGroup, so it will reference itself. My (ejected) SidebarGroup.vue looks like this now:

<template>
  <div
    class="sidebar-group"
    :class="{ first, collapsable }"
  >
    <p
      class="sidebar-heading"
      :class="{ open }"
      @click="$emit('toggle')"
    >
      <span>{{ item.title }}</span>
      <span
        class="arrow"
        v-if="collapsable"
        :class="open ? 'down' : 'right'">
      </span>
    </p>

    <DropdownTransition>
      <ul
        ref="items"
        class="sidebar-group-items"
        v-if="open || !collapsable"
      >
        <li v-for="(child, i) in item.children">
          <SidebarGroup
            v-if="child.type === 'group'"
            :item="child"
            :first="i === 0"
            :open="i === openGroupIndex"
            :collapsable="child.collapsable || child.collapsible"
            @toggle="toggleGroup(i)"
          />
          <SidebarLink v-else :item="child"/>
        </li>
      </ul>
    </DropdownTransition>
  </div>
</template>

<script>
import SidebarLink from './SidebarLink.vue'
import DropdownTransition from './DropdownTransition.vue'

export default {
  name: 'SidebarGroup',
  props: ['item', 'first', 'open', 'collapsable'],
  components: { SidebarLink, DropdownTransition },

  data () {
    return {
      openGroupIndex: 0
    }
  },

  methods: {
    toggleGroup (index) {
      this.openGroupIndex = index === this.openGroupIndex ? -1 : index
    },
  }
}
</script>

<style lang="stylus">
.sidebar-group
  &:not(.first)
    margin-top 1em
  .sidebar-group
    padding-left 0.5em
  &:not(.collapsable)
    .sidebar-heading
      cursor auto
      color inherit

.sidebar-heading
  color #999
  transition color .15s ease
  cursor pointer
  font-size 1.1em
  font-weight bold
  // text-transform uppercase
  padding 0 1.5rem
  margin-top 0
  margin-bottom 0.5rem
  &.open, &:hover
    color inherit
  .arrow
    position relative
    top -0.12em
    left 0.5em
  &:.open .arrow
    top -0.18em

.sidebar-group-items
  transition height .1s ease-out
  overflow hidden
</style>

And I'm using it like you could imagine in config.js:

module.exports = {
  themeConfig: {
    sidebar: [
      {
        title: 'API',
        collapsable: true,
        children: [
          '/api/',
          {
            title: 'Modules',
            collapsable: true,
            children: [
              '/api/page-1/',
              '/api/page-2/',
            ]
          },
          {
            title: 'Components',
            collapsable: false,
            children: [
              '/api/page-1/',
              '/api/page-2/',
            ]
          },
        ]
      },
      {
        title: 'Learn more',
        children: [
          '/page-1/',
          '/page-2',
        ]
      }
    ]
  }
}

@davestewart Hi, is it possible to share somewhere those helpers/themes (like a gist) please ?

Hey @Seraf, sorry - I missed this!

It's been ages since I worked on this, so not sure where the code is now, but group is just a wrapper. It works like this:

function page (link, title) {
    return { type: 'page', link, title }
}

function group (title, children) {
    return { type: 'group', title, children }
}

var sidebar = [
    page ('foo', 'Foo'),
    page ('bar', 'Bar'),
    group('Children 1', [
        page ('baz', 'Baz'),
        page ('qux', 'Qux'),
        group('Children 2', [
            page ('abc', 'ABC'),
            page ('def', 'DEF'),
        ])
    ])
]
console.log(JSON.stringify(sidebar, null, 2))
[
  {
    "type": "page",
    "link": "foo",
    "title": "Foo"
  },
  {
    "type": "page",
    "link": "bar",
    "title": "Bar"
  },
  {
    "type": "group",
    "title": "Children 1",
    "children": [
      {
        "type": "page",
        "link": "baz",
        "title": "Baz"
      },
      {
        "type": "page",
        "link": "qux",
        "title": "Qux"
      },
      {
        "type": "group",
        "title": "Children 2",
        "children": [
          {
            "type": "page",
            "link": "abc",
            "title": "ABC"
          },
          {
            "type": "page",
            "link": "def",
            "title": "DEF"
          }
        ]
      }
    ]
  }
]

I use a similar approach to clean up my routes files:

Hope that helps :)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

kid1412621 picture kid1412621  ยท  3Comments

genedronek picture genedronek  ยท  3Comments

higuoxing picture higuoxing  ยท  3Comments

sankincn picture sankincn  ยท  3Comments

ynnelson picture ynnelson  ยท  3Comments