vuejs / vitepress

Vite & Vue powered static site generator.
https://vitepress.dev
MIT License
11.47k stars 1.86k forks source link

feat(theme): support nested headings in outline #965

Closed fi3ework closed 1 year ago

fi3ework commented 1 year ago

resolve #954

fi3ework commented 1 year ago

@brc-dd Friendly ping. Could you take a review or is there something else I could provide to this PR to ensure the functionality?

brc-dd commented 1 year ago

Hi! Kia will have a look on this one.

brc-dd commented 1 year ago

Rest of the stuff seems fine. I still haven't checked the UI though.

fi3ework commented 1 year ago

Made an update with a new commit and some refactoring. And I think I made another breaking change 😅.

before:

There would be an outline title without items but only a title when the content does not have h2/h3 headings, such as ##### xx.

now:

Outline will only be displayed when there are really outline items to display.

fi3ework commented 1 year ago

Rest of the stuff seems fine. I still haven't checked the UI though.

The UI is like this, I'm also wondering is there a better way to release a preview build for custom config and docs that can reveal the different UI.

image
brc-dd commented 1 year ago

Made an update with a new commit and some refactoring. And I think I made another breaking change 😅.

before:

There would be an outline title without items but only a title when the content does not have h2/h3 headings, such as ##### xx.

This was probably a bug 😅. Thanks for fixing that! 🙌

fi3ework commented 1 year ago

I created a stackblitz project to preview the affection on UI.

I think we can do more things to make preview more easy in two ways:

  1. Provide a stackblitz like which will be created from the branch of the PR via a comment by GitHub bot for each PR automatically. So the reviewer can know the UI affection by changing some file or the PR author could provide the demo as above.
  2. Add example docs that contain different use cases of Vitepress which could also be used by E2E test, and deploy a preview link by Netlify for each PR.
sishen654 commented 1 year ago

@fi3ework Is there a way not to add this on every file: outline: "deep", otherwise it would be too much trouble

fi3ework commented 1 year ago

@fi3ework Is there a way not to add this on every file: outline: "deep", otherwise it would be too much trouble

Yes, it should support. Seems like I lost some code with the app configuration. 😅 I'll bring it back later.

sishen654 commented 1 year ago

@fi3ework Is there a way not to add this on every file: outline: "deep", otherwise it would be too much trouble

Yes, it should support. Seems like I lost some code with the app configuration. 😅 I'll bring it back later. Haha, trouble you,😁😁😁

fi3ework commented 1 year ago

Ready to be reviewed again.


I kind of want to add preview deploy for examples before this PR. But multiple sites deploy Netlify (doc) require setting permission to add a new site for the example site that I don't have. If there is any vuejs member who has permission also thinks of a preview deployment of examples to facilitate observing UI changes of code, let me know and I would like to come up with the PR with your help of setting Netlify.

trwnh commented 1 year ago

would it make sense to add a max depth in this pr?

fi3ework commented 1 year ago

would it make sense to add a max depth in this pr?

max depth is supported in this PR. Using like this - [2, 4]

brc-dd commented 1 year ago

Can we fix #785 here itself? I can fix that separately too, but then this would need rebase and might cause other issues. Here is the diff on vite-3 branch. You will need to modify then manually apply it. That query selector h2 thing probably need to be made h2, h3, h4, h5, h6. Also probably we need something like .innerText.split('\n')[0] instead of textContent.

diff --git a/src/client/theme-default/components/VPDocAside.vue b/src/client/theme-default/components/VPDocAside.vue
index 4232d15..2f590ab 100644
--- a/src/client/theme-default/components/VPDocAside.vue
+++ b/src/client/theme-default/components/VPDocAside.vue
@@ -3,7 +3,7 @@ import { useData } from 'vitepress'
 import VPDocAsideOutline from './VPDocAsideOutline.vue'
 import VPDocAsideCarbonAds from './VPDocAsideCarbonAds.vue'

-const { page, theme } = useData()
+const { theme } = useData()
 </script>

 <template>
@@ -11,7 +11,7 @@ const { page, theme } = useData()
     <slot name="aside-top" />

     <slot name="aside-outline-before" />
-    <VPDocAsideOutline v-if="page.headers.length" />
+    <ClientOnly><VPDocAsideOutline /></ClientOnly>
     <slot name="aside-outline-after" />

     <div class="spacer" />
diff --git a/src/client/theme-default/components/VPDocAsideOutline.vue b/src/client/theme-default/components/VPDocAsideOutline.vue
index dad4378..42acb27 100644
--- a/src/client/theme-default/components/VPDocAsideOutline.vue
+++ b/src/client/theme-default/components/VPDocAsideOutline.vue
@@ -1,25 +1,35 @@
 <script setup lang="ts">
-import { ref, computed } from 'vue'
-import { useData } from 'vitepress'
-import {
-  resolveHeaders,
-  useOutline,
-  useActiveAnchor
-} from '../composables/outline.js'
-
-const { page, frontmatter, theme } = useData()
-
-const { hasOutline } = useOutline()
+import { useData, useRoute, type Header } from 'vitepress'
+import { computed, ref, watch } from 'vue'
+import { resolveHeaders, useActiveAnchor } from '../composables/outline.js'
+
+const { frontmatter, theme } = useData()
+const route = useRoute()
+
+const headers = ref<Header[]>([])
+
+watch(
+  () => route.path,
+  () => {
+    document.querySelectorAll('h2').forEach((el) => {
+      if (el.textContent && el.id)
+        headers.value.push({
+          level: Number(el.tagName[1]),
+          title: el.textContent,
+          slug: el.id
+        })
+    })
+  },
+  { immediate: true }
+)
+
+const hasOutline = computed(() => !!headers.value.length)
+const resolvedHeaders = computed(() => resolveHeaders(headers.value))

 const container = ref()
 const marker = ref()
-
 useActiveAnchor(container, marker)

-const resolvedHeaders = computed(() => {
-  return resolveHeaders(page.value.headers)
-})
-
 function handleClick({ target: el }: Event) {
   const id = '#' + (el as HTMLAnchorElement).href!.split('#')[1]
   const heading = document.querySelector<HTMLAnchorElement>(
Charles7c commented 1 year ago

Looking forward this feature.