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

TOC does not work when importing one MDX into another #3915

Closed
oriooctopus opened this issue Dec 14, 2020 · 53 comments · Fixed by #9684
Closed

TOC does not work when importing one MDX into another #3915

oriooctopus opened this issue Dec 14, 2020 · 53 comments · Fixed by #9684
Labels
bug An error in the Docusaurus core causing instability or issues with its execution difficulty: advanced Issues that are complex, e.g. large scoping for long-term maintainability. domain: markdown Related to Markdown parsing or syntax help wanted Asking for outside help and/or contributions to this particular issue or PR. status: accepting pr This issue has been accepted, and we are looking for community contributors to implement this
Milestone

Comments

@oriooctopus
Copy link
Contributor

🐛 Bug Report

On our site we have a couple of duplicate doc pages. To avoid repeating content we have one doc as the source of truth then import the content like so:

import Content from './doc1.md';

<Content />

The issue is, the table of contents is not picked up on the pages that import the content. Please let me know if there is a better way to handle duplicate pages and if this usage is just conceptually wrong

Have you read the Contributing Guidelines on issues?

Yes

To Reproduce

npx @docusaurus/init@latest init my-website classic

in doc2:

import Content from './doc1.md';

<Content />

It's worth noting that this also happens on my 6 month old version, so I don't think that it's due to anything recent

@oriooctopus oriooctopus added bug An error in the Docusaurus core causing instability or issues with its execution status: needs triage This issue has not been triaged by maintainers labels Dec 14, 2020
@slorber
Copy link
Collaborator

slorber commented Dec 14, 2020

This usage should be supported, but it's a current limitation of the existing system.

Unfortunately, I'm not sure how to implement this technically, and still busy on i18n.

In the meantime I'd recommend to only put raw text in mdx and duplicate the headings. That's not ideal but I don't have any other solution for now 😓

You could also add a build script that copy a file to multiple locations, or use a remark plugin that substitute a placeholder with a content string in multiple docs

@oriooctopus
Copy link
Contributor Author

How difficult would it be to fix? I could possibly put up a PR

@slorber
Copy link
Collaborator

slorber commented Dec 15, 2020

@oriooctopus unfortunately I suspect this to be quite complex to implement, and can't even give you much help to do so because I'm not sure how this should be done.

You can look at the code in this folder: packages/docusaurus-mdx-loader/src/remark/toc
If you are comfortable with ASTs and visitors you can give it a try

@jknoxville
Copy link
Contributor

@slorber I think we can address this by getting docusaurus-mdx-loader to follow jsx tags into their imported sources when traversing the AST.

I did something similar to this before, using the fs api to read the file and getting remark to parse this in-place of the jsx node (so you can continue to recurse into the imported file and visit its nodes).

The only downside in my eyes is that we'd be using the node fs api, which means this package wouldn't work at runtime in the browser anymore (if it does now).

Do you think this would be an acceptable change?

@slorber
Copy link
Collaborator

slorber commented Dec 16, 2020

Yes @jknoxville this is what I have in mind: a recursive visitor that is able to "flatten" the TOC structure of multiple MDX docs (and preserve the correct TOC ordering).

This code already runs in node (it's a Webpack loader)

@oriooctopus
Copy link
Contributor Author

@jknoxville do you still have any interest in fixing this? If not, maybe we could sync on what you had in mind and I might be able to implement it?

@jknoxville
Copy link
Contributor

@oriooctopus I'm interested, but it's not top of my queue right now, so feel free to take a stab!

Here's a prototype I made a while ago, for a remark plugin that effectively re-implements mdx transclusions by parsing the target of any imported markdown files, and then replacing any JSX nodes that use it, with the nodes of the target AST.
https://gist.github.com/jknoxville/8ed4cb8bcef348362a99dd70bb8006ae

It's scrappy, and not production-ready (currently uses eval for one thing - I'd recommend using babel to parse the imports instead), but it works. Maybe there's a more elegant way to do it.

What I imagine, is modifying search.js to do what that prototype does before it starts visiting headings to assemble its TOC.

I haven't tried this, but don't see why it wouldn't work.

@slorber
Copy link
Collaborator

slorber commented Jan 20, 2021

if that helps I remembered that Gatsby has a way to extract all imports/exports from MDX.

https://github.com/gatsbyjs/gatsby/blob/2e42197025e2e1bac06c721c3cc44135bf8ef526/packages/gatsby-plugin-mdx/utils/gen-mdx.js#L199

@Laptopmini

This comment has been minimized.

@slorber

This comment has been minimized.

@Laptopmini

This comment has been minimized.

@Laptopmini

This comment has been minimized.

@sweeneyskirt-sl

This comment has been minimized.

@Laptopmini

This comment has been minimized.

@slorber
Copy link
Collaborator

slorber commented May 7, 2021

@Laptopmini what I understand is that you want to create a TOC dynamically from data fetched from an API.

This is unrelated to the current issue which is about importing one MDX doc inside another.

Please open another issue if you want to discuss dynamically generated TOC, and let's keep this issue focused on its initial problem. I believe you could just swizzle the TOC component and add your stateful logic here.

@sweeneyskirt-sl
Copy link

sweeneyskirt-sl commented May 7, 2021

Hi @slorber -- Thanks for your reply. So I have a file that defines a number of configuration variables that are common to several environments, so I have a single doc that I am importing into the other docs, but I need each of the variables to be included in the floating TOC, which they are not, as clearly confirmed by this issue. I see from your earlier response that fixing this issue is not a priority for docusauraus right now -- do you know if it will be at any point in the near roadmap? Are there any viable workarounds (the gatsby plugin you mention, for example)? Manually adding a header for each variable is not viable for my content. It would defeat the purpose of the transclusion. Thank you for any advice!

@khushal87
Copy link

Any solution for this? @slorber @oriooctopus

@sweeneyskirt-sl
Copy link

I have not heard of any updates to this issue yet, @khushal87. I did throw in a feature request at https://docusaurus.canny.io/feature-requests/p/include-transclusion-content-headings-in-toc so please feel free to vote it up so we can get this on the roadmap at least! :)

@khushal87
Copy link

khushal87 commented Jun 8, 2021

Done, hope to get this soon. @sweeneyskirt-sl 🤞

@slorber slorber changed the title TOC does not work when importing content TOC does not work when importing one MDX into another Jun 10, 2021
@slorber
Copy link
Collaborator

slorber commented Jul 15, 2021

Some related notes:

@rajkaramchedu
Copy link

For anyone waiting to fix this bug, I am happy to say that today I fixed this confusing behaviour in [email protected] via the custom remark plugin written by me.

After receiving feedback from my community I want to contribute to Docusaurus too via a generic remark plugin that we could use in both frameworks Docusaurus & Nextra.

Stay tuned!

Here you can see a test with snapshot result

https://github.com/shuding/nextra/blob/main/packages/nextra/__test__/compile.test.ts#L83-L123

Wonderful. I needed the target file combining the partials to show the right sidebar. Because of this bug, I have decided to take a somewhat circuitous route of adding the ## headers in the target file and then including the partial below it, for several such subsections. Your fix, when it arrives, will be godsend. Thanks!

@juniorgasparotto
Copy link

Any news on this subject?

@albertothedev
Copy link
Contributor

Any news on this subject?

Seems like we are waiting for @dimaMachina to create a remark plugin with his fix.

@dimaMachina
Copy link

Any news on this subject?

Seems like we are waiting for @dimaMachina to create a remark plugin with his fix.

Yes! Sorry, currently I am busy with Nextra 3 release and future speak in Amsterdam on 8 November, I hope to do it next month ✌️

@ohsayan
Copy link

ohsayan commented Dec 16, 2023

I think we just ran into this as well. We basically wanted to have a component that renders a small label next to a heading to note if the thing being discussed is a beta item or not. While the component works flawlessly, the TOC does not render the heading. As a workaround, for now is there a way to manually create a TOC for a given MD doc?

@rajkaramchedu
Copy link

I think we just ran into this as well. We basically wanted to have a component that renders a small label next to a heading to note if the thing being discussed is a beta item or not. While the component works flawlessly, the TOC does not render the heading. As a workaround, for now is there a way to manually create a TOC for a given MD doc?

This is how I did it. Quite sure there are better ways. I split the Markdown document I wanted to import into multiple MDX files. Each MDX file contains only the content from a specific section of the Markdown doc. So in the below, my MD doc had sections like Description, Parameters and Returns in one doc. After splitting it and saving into multiple files, each beginning with the underscore character, I imported them into another MDX doc like this. This way, Docusaurus displayed all the section headers, like it would for any normal MD doc.

import Description from "/path/to/_partials/_mydoc-description.mdx";

<Description />

## Parameters

import Params from "/path/to/_partials/_mydoc_-parameters.mdx";

<Params />

## Returns

import Returns from "/path/to/_partials/_mydoc_-returns.mdx";

<Returns />

@robodl
Copy link

robodl commented Dec 19, 2023

We've done it like this

import Introduction, {
    toc as IntroductionToc,
} from "../shared/introduction.mdx";
import IntroductionContent, {toc as IntroductionContentToc} from "./.introduction_content.mdx";

export const toc = [...IntroductionContentToc, ...IntroductionToc]

<IntroductionContent />

<Introduction />

But then you can't mix it with other headers, like this

...

export const toc = [...IntroductionContentToc, ...IntroductionToc]

<IntroductionContent />

## Other header <---- WONT WORK PROPERLY

<Introduction />

@anatolykopyl
Copy link
Contributor

anatolykopyl commented Dec 29, 2023

I submitted a PR for Docusaurus v2, that I'll be willing to work on, but in the meantime, until it is accepted, I made a quick drop in replacement remark plugin that fixes this behaviour: @akopyl/docusaurus-toc-patcher.

It implements the logic from shuding/nextra#2199.

@slorber
Copy link
Collaborator

slorber commented Jan 19, 2024

This has been fixed (main/canary) as part of #9684, and will be released in Docusaurus v3.2

Thanks for the inspiration @dimaMachina and the initial port @anatolykopyl 🤗

@jbltx
Copy link

jbltx commented Jan 29, 2024

Nice feature, do you plan to also support headings from custom JSX/TSX components to appear in ToC ? I have a React component that reads a JSON file and create headings and paragraphs based on it (the JSON file is loaded as a module but I guess it means the ToC should be generated dynamically at runtime and not at compile time?)

@slorber
Copy link
Collaborator

slorber commented Feb 1, 2024

@jbltx we only support importing other MDX files atm

However, it could be technically possible to also support React imported components, and try to get the compiler to output something looking like this:

import Partial, 
  {toc as __tocPartial} from "_partial.mdx"
import MyComponent from "MyComponent"

# Doc

## Doc heading

<Partial/>

<MyComponent/>

export const toc = [
  {
    "value": "Doc heading",
    "id": "doc-heading",
    "level": 2
  }, 
  ...__tocPartial,
  ...MyComponent?.toc?.() 
] 

Note that in any case, it would be your responsibility to implement the toc function attribute:

const MyComponent = () => <h2 id="hello">Hello</h2>

MyComponent.toc = () => {
  return [{id: "hello", value: "Hello", level: 2}];
};

export default MyComponent;

We can't execute the React code and infer the TOC from that execution.

Would that be a helpful addition, or is this too limited?

@jbltx
Copy link

jbltx commented Feb 2, 2024

Would that be a helpful addition, or is this too limited?

Thank you for the suggestion @slorber, I think your example is enough for my use case indeed! 👍

@felipecrs
Copy link
Contributor

I don't know if I'm doing something wrong, but this is still not working for me, even after the fix:

_release.mdx:

## Release {props.release}

releases.mdx:

import Release from "./_release.mdx"

# Releases

## Top heading

{["v1", "v2", "v3"].map((release) => <Release release={release} />)}

And the result:

image

@slorber
Copy link
Collaborator

slorber commented Apr 12, 2024

@felipecrs this is unlikely we will ever support this. The TOC is computed statically at MDX compilation time. We are not running the JS to see what the output is and if it contains any heading. Hence we don't support such loop, nor using props inside partial headings.

If you want that, you'd have to use the TOC function form described just above, a possible enhancement that we haven't implemented yet.

@felipecrs
Copy link
Contributor

@slorber I see.

Implementing my own toc function is 100% acceptable for my use case, I would appreciate this capability.

@felipecrs
Copy link
Contributor

felipecrs commented Apr 12, 2024

Ops, sorry, did you mean that I could already implement my toc function?

I tried it with:

import Release from "./_release.mdx"

# Releases

## Top heading

{["v1", "v2", "v3"].map((release) => <Release release={release} />)}

export const toc = () => ["v1", "v2", "v3"].map((release) => ({
  "value": `Release ${release}`,
  "id": `release-${release}`,
  "level": 2
}))

And then the toc disappeared entirely. (Also tried toc = [] like in one of the examples)

@slorber
Copy link
Collaborator

slorber commented Apr 13, 2024

@felipecrs we don't support a toc function yet, only a toc array export.

You should rather do that instead of a loop

import ReleaseV1 from "./_releaseV1.mdx"
import ReleaseV2 from "./_releaseV2.mdx"
import ReleaseV3 from "./_releaseV3.mdx"

# Releases

## Top heading

<ReleaseV1/>

<ReleaseV2/>

<ReleaseV3/>

Note, we don't support "dynamic headings" such as ## Release {props.version}, so you will need to provide a TOC export if you want dynamic headings (you can provide the toc export either on the root doc or the partial). There's also a non-implemented proposal to solve that problem that you can track here: #9773

@felipecrs
Copy link
Contributor

Great. Thank you, I'll watch the pull request, because my data is very dynamic so I can't define them statically.

honzajavorek added a commit to honzajavorek/docusaurus that referenced this issue Jul 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug An error in the Docusaurus core causing instability or issues with its execution difficulty: advanced Issues that are complex, e.g. large scoping for long-term maintainability. domain: markdown Related to Markdown parsing or syntax help wanted Asking for outside help and/or contributions to this particular issue or PR. status: accepting pr This issue has been accepted, and we are looking for community contributors to implement this
Projects
None yet