remark-heading-id icon indicating copy to clipboard operation
remark-heading-id copied to clipboard

Removes id when used with remarkStringify

Open dmca-glasgow opened this issue 8 months ago • 4 comments

Hello,

I've been trying your package in my project, but I've found when I stringify my mdast tree back into markdown, the heading id is now lost.

I've tried to hack together a solution which works for basic text titles, but I think it's an over-simplification of the problem and probably breaks a lot of stuff:

import { unified, Processor } from 'unified';
import { Heading, Root, Text } from 'mdast';
import remarkParse from 'remark-parse';
import remarkStringify from 'remark-stringify';
import { visit } from 'unist-util-visit';

function remarkHeadingId() {
  // @ts-expect-error
  const self = this as Processor;
  const data = self.data();
  const toMarkdownExtensions = data.toMarkdownExtensions || [];

  toMarkdownExtensions.push({
    handlers: {
      heading(node: Heading) {
        const text = node.children[0] as Text;
        const idValue = String(node.data?.hProperties?.id || '');
        const id = idValue === '' ? '' : `{#${idValue}}`;
        return `${'#'.repeat(node.depth)} ${text.value} ${id}`;
      },
    },
  });

  return (tree: Root) => {
    visit(tree, 'heading', (node) => {
      const text = node.children[0] as Text;
      const match = text.value.match(/ {#([^]+?)}$/);

      if (match !== null) {
        node.data = {
          hProperties: {
            id: match[1],
          },
        };
        text.value = text.value.slice(0, match.index);
      }
    });
  };
}

const processor = unified()
  .use(remarkParse)
  .use(remarkHeadingId)
  .use(remarkStringify)

const markdown = `### Hello {#hi}`;
const mdast = processor.parse(markdown);
const transformed = await processor.run(mdast);

console.dir(transformed, { depth: null });

console.log(processor.stringify(transformed as Root))

Transformed (position data removed):

{
  type: 'root',
  children: [
    {
      type: 'heading',
      depth: 3,
      children: [
        {
          type: 'text',
          value: 'Hello'
        }
      ],
      data: { hProperties: { id: 'hi' } }
    }
  ]
}

Stringify Result:

### Hello {#hi}

With your plugin, the result is:

### Hello

I had a look at the way Micromark parses ATX headings to see if I could replicate the logic but it looks pretty complex!

Thanks.

dmca-glasgow avatar Jun 05 '24 14:06 dmca-glasgow