keystatic icon indicating copy to clipboard operation
keystatic copied to clipboard

gracefully handle `footnoteReference` and `footnoteDefinition` nodes in mdx field

Open stefanprobst opened this issue 1 year ago • 4 comments

currently, when an mdx document has a footnote, the keystatic mdx field errors with the following error message:

Field validation failed: content: Unexpected error: Error: Unhandled type footnoteReference Unhandled type footnoteDefinition

Screenshot_20240307_190715

mdx document used:

---
title: test
---

Some text.[^1]

[^1]: Footnote content.

stefanprobst avatar Mar 07 '24 18:03 stefanprobst

in case it's useful to someone, i am currently replacing native markdown footnotes with a custom mark <Footnote> component, and using a remark plugin to split that into footnote reference and definition:

/** @typedef {import("mdast-util-mdx").MdxJsxAttribute} MdxJsxAttribute */
/** @typedef {import("mdast-util-mdx").MdxJsxTextElement} MdxJsxTextElement */
/** @typedef {import("mdast-util-mdx").MdxJsxFlowElement} MdxJsxFlowElement */

import { SKIP, visit } from "unist-util-visit";

export function withMdxFootnotes() {
  return function transformer(/** @type {import("mdast").Root} */ tree) {
    let count = 1;

    /** @type {MdxJsxTextElement[]} */
    const footnotes = [];

    visit(tree, "mdxJsxTextElement", (node, index, parent) => {
      if (node.name === "Footnote") {
        /** @type {MdxJsxAttribute} */
        const countAttribute = {
          type: "mdxJsxAttribute",
          name: "count",
          value: String(count),
        };

        /** @type {MdxJsxTextElement} */
        const reference = {
          type: "mdxJsxTextElement",
          name: "FootnoteReference",
          attributes: [countAttribute],
          children: [],
        };

        /** @type {MdxJsxTextElement} */
        const content = {
          type: "mdxJsxTextElement",
          name: "FootnoteContent",
          attributes: [countAttribute],
          children: node.children,
        };

        // @ts-expect-error Parent node exists.
        parent.children.splice(index, 1, reference);
        footnotes.push(content);

        count++;
      }

      return SKIP;
    });

    if (footnotes.length > 0) {
      /** @type {MdxJsxFlowElement} */
      const section = {
        type: "mdxJsxFlowElement",
        name: "FootnotesSection",
        attributes: [],
        // @ts-expect-error Should be fine to set `MdxJsxTextElement` children.
        children: footnotes,
      };

      tree.children.push(section);
    }
  };
}

stefanprobst avatar Mar 12 '24 07:03 stefanprobst

@stefanprobst How did you manage to fix this? How to use this code snippet exactly? I'm struggling with the same issue.

adamlewkowicz avatar May 09 '24 20:05 adamlewkowicz

@adamlewkowicz i put together an example here: https://github.com/stefanprobst/keystatic-footnotes

in an .mdx file it will look like this:

This text has a footnote.<Footnote>This is the footnote text.</Footnote> This is the rest of the paragraph.

in the keystatic ui, it will look like this:

Screenshot_20240511_080409

stefanprobst avatar May 11 '24 06:05 stefanprobst

@stefanprobst Thanks a lot for the example! I've used this setup with remote-mdx and it's working 👍

adamlewkowicz avatar May 14 '24 07:05 adamlewkowicz