Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add heading level hierarchy to table of contents #2101

Merged
merged 7 commits into from
Nov 29, 2022

Conversation

chenxsan
Copy link
Contributor

What kind of changes does this PR include?

  1. Refactor the HTML structure of table of contents
  2. Since the HTML structure is changed, some changes to css is needed so we can keep the UI same as before

Description

@netlify
Copy link

netlify bot commented Nov 23, 2022

Deploy Preview for astro-docs-2 ready!

Built without sensitive environment variables

Name Link
🔨 Latest commit c3b4864
🔍 Latest deploy log https://app.netlify.com/sites/astro-docs-2/deploys/6385f89a9983250008589004
😎 Deploy Preview https://deploy-preview-2101--astro-docs-2.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site settings.

@chenxsan
Copy link
Contributor Author

chenxsan commented Nov 24, 2022

Here's the test file I had in my project which can be run with vitest:

import generateToc from './generateToc'
import { describe, expect, test } from 'vitest'
describe('generateToc', () => {
  test('should throw when heading 3 comes before heading 2', () => {
    expect(() =>
      generateToc([
        {
          slug: 'heading 3',
          text: 'heading 3',
          depth: 3,
        },
        {
          slug: 'heading 2',
          text: 'heading 2',
          depth: 2,
        },
      ])
    ).toThrowError(/^Orphan heading found: heading 2.$/)
  })
  test('should pass when heading 1 comes before heading 2', () => {
    const toc = generateToc([
      {
        slug: 'heading 1',
        text: 'heading 1',
        depth: 1,
      },
      {
        slug: 'heading 2',
        text: 'heading 2',
        depth: 2,
      },
    ])
    expect(toc).toEqual([
      {
        slug: 'heading 1',
        text: 'heading 1',
        depth: 1,
        children: [
          {
            slug: 'heading 2',
            text: 'heading 2',
            depth: 2,
            children: [],
          },
        ],
      },
    ])
  })

  test('should pass when heading 2 comes before heading 2', () => {
    const toc = generateToc([
      {
        slug: 'heading 2',
        text: 'heading 2',
        depth: 2,
      },
      {
        slug: 'heading 2',
        text: 'heading 2',
        depth: 2,
      },
      {
        slug: 'heading 3',
        text: 'heading 3',
        depth: 3,
      },
      {
        slug: 'heading 2',
        text: 'heading 2',
        depth: 2,
      },
    ])
    expect(toc).toEqual([
      {
        slug: 'heading 2',
        text: 'heading 2',
        depth: 2,
        children: [],
      },
      {
        slug: 'heading 2',
        text: 'heading 2',
        depth: 2,
        children: [
          {
            slug: 'heading 3',
            text: 'heading 3',
            depth: 3,
            children: [],
          },
        ],
      },
      {
        slug: 'heading 2',
        text: 'heading 2',
        depth: 2,
        children: [],
      },
    ])
  })
})

I didn't include the test in this pull request because there seems no way to run such an unit test in this project at the moment. Correct me if I am wrong.

@sarah11918 sarah11918 added the site improvement Some thing that improves the website functionality - ask @delucis for help! label Nov 28, 2022
Copy link
Member

@delucis delucis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this @chenxsan! I’ve left one suggestion.

There’s also one small styling difference in this PR:

Production site This PR preview
image image

The PR preview is missing the .current-header-link a styling that makes the text a different shade. Looks like we’ll need to change that in index.css to a.current-header-link.

} else {
const lastItemInToc = toc[toc.length - 1];
if (heading.depth < lastItemInToc.depth) {
throw new Error(`Orphan heading found: ${heading.text}.`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because this Preact code will run in the browser, it would be good to avoid throwing errors that could break there. One option would be to move all this generateToc logic into an Astro component that wraps this one. That way we could safely throw at build time. It would also have the advantage of having all this code run only at build time and never be shipped to users. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. However I have no idea how to make it with another Astro component since we can't use Astro component inside tsx. Wrapping TableOfContents.tsx inside Astro component would result some warnings since we pass client:media to an Astro component.

Anyway, I've refactor the TableOfContents.tsx component to receive a toc prop received at build time. Note that since there's no more easy way to get the currentHeading from toc data, I had to refactor currentID state into currentHeading.

Copy link
Member

@delucis delucis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome! Thanks so much. Just left a few tidy-up suggestions, but then this should be good to go 🚀

src/components/RightSidebar/RightSidebar.astro Outdated Show resolved Hide resolved
src/components/RightSidebar/TableOfContents.tsx Outdated Show resolved Hide resolved
src/components/RightSidebar/RightSidebar.astro Outdated Show resolved Hide resolved
src/layouts/MainLayout.astro Outdated Show resolved Hide resolved
Co-authored-by: Chris Swithinbank <swithinbank@gmail.com>
Copy link
Member

@delucis delucis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this high quality contribution @chenxsan! Really great work.

By the way, you mentioned you had a test for this ready to go. Is setting up some testing for the docs site something you’d be interested in? We don’t have anything currently but some tests that run in our CI would certainly be helpful. Let me know if you’d be interested in that!

@delucis delucis changed the title Refactor table of contents Add heading level hierarchy to table of contents Nov 29, 2022
@delucis delucis merged commit 58e78f3 into withastro:main Nov 29, 2022
@chenxsan chenxsan deleted the refactor/toc branch November 29, 2022 13:24
@chenxsan
Copy link
Contributor Author

Thanks for this high quality contribution @chenxsan! Really great work.

By the way, you mentioned you had a test for this ready to go. Is setting up some testing for the docs site something you’d be interested in? We don’t have anything currently but some tests that run in our CI would certainly be helpful. Let me know if you’d be interested in that!

Yeah, I had vitest to run that unit test. Since Astro is built on vite, it's sort of seamless to run unit tests with vitest. I can file a pull request if you'd like one.

@delucis
Copy link
Member

delucis commented Nov 29, 2022

I can file a pull request if you'd like one.

Sure! That sounds really helpful.

@chenxsan chenxsan mentioned this pull request Nov 30, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
site improvement Some thing that improves the website functionality - ask @delucis for help!
Projects
None yet
Development

Successfully merging this pull request may close these issues.

No hierarchy in table of contents
3 participants