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

Emphasis and strong when immediately followed by emphasis in the same word causes extra asterisks to appear #162

Open
4 tasks done
puopg opened this issue Jan 31, 2024 · 4 comments
Labels
👍 phase/yes Post is accepted and can be worked on

Comments

@puopg
Copy link

puopg commented Jan 31, 2024

Issue from react-markdown: remarkjs/react-markdown#812

But potentially the root of the issue could live in the md parser. Below I have linked the repro links and comments from the other issue:

When processing the MD string

***123****456*

<em>
  <strong>123</strong>
</em>
<em>456</em>

React markdown renders what seems to be some additional asterisks?

Screenshot 2024-01-31 at 1 15 46 PM

Initial checklist

Affected packages and versions

react-markdown

Link to runnable example

No response

Steps to reproduce

Compare the result of:

This is just 1 word, where the first half is both italicized and bolded, the 2nd half is only italicized.

The MDAST that gets created from unified() => rehypeParse => rehypeRemark looks correct, so to me the issue seems to be either:

  1. The syntax generated from the processing flow is incorrect.
  2. The syntax is correct, and its React-Markdown's rendering of the syntax that is not correct.

Expected behavior

Screenshot 2024-01-31 at 1 15 38 PM

Actual behavior

Screenshot 2024-01-31 at 1 15 46 PM

Runtime

No response

Package manager

No response

OS

No response

Build and bundle tools

No response

@github-actions github-actions bot added 👋 phase/new Post is being triaged automatically 🤞 phase/open Post is being triaged manually and removed 👋 phase/new Post is being triaged automatically labels Jan 31, 2024
@wooorm
Copy link
Member

wooorm commented Feb 1, 2024

It says it can’t find the codesandbox, but the stackblitz shows it: https://stackblitz.com/edit/stackblitz-starters-n2yquc

@wooorm wooorm added the 👍 phase/yes Post is accepted and can be worked on label Feb 1, 2024
@github-actions github-actions bot removed the 🤞 phase/open Post is being triaged manually label Feb 1, 2024
Copy link

github-actions bot commented Feb 1, 2024

Hi! This was marked as ready to be worked on! Note that while this is ready to be worked on, nothing is said about priority: it may take a while for this to be solved.

Is this something you can and want to work on?

Team: please use the area/* (to describe the scope of the change), platform/* (if this is related to a specific one), and semver/* and type/* labels to annotate this. If this is first-timers friendly, add good first issue and if this could use help, add help wanted.

@puopg
Copy link
Author

puopg commented Feb 1, 2024

It says it can’t find the codesandbox, but the stackblitz shows it: https://stackblitz.com/edit/stackblitz-starters-n2yquc

oops, didnt have it as public haha.

@brunolm
Copy link

brunolm commented Apr 30, 2024

Workaround

import { Parser as CommonMarkParser } from "commonmark";
import remarkHtml from "remark-html";
import slate from "remark-slate";
import { type Descendant } from "slate";
import * as slateMark from "slate-mark";
import { unified, type Parser, type Processor } from "unified";

interface Node {
  type: string;
  children?: Node[];
  value?: string;
}

// https://github.com/remarkjs/remark/blob/main/packages/remark-parse/lib/index.js
const parser: Parser = (document) => {
  const parser = new CommonMarkParser();

  const r = parser.parse(document);

  const walker = r.walker();

  const node: Node = {
    type: "root",
    children: [],
  };

  const stack: Node[] = [];
  for (let event = walker.next(); event; event = walker.next()) {
    const isLeaf = !!event.node.literal;

    if (!stack.length) {
      stack.push(node);
      continue;
    }

    const newNode = {
      type: event.node.type === "emph" ? "emphasis" : event.node.type,
      ...(isLeaf
        ? {
            value: event.node.literal ?? undefined,
          }
        : {
            children: [],
          }),
    };

    if (event.entering || isLeaf) {
      stack[stack.length - 1]!.children?.push(newNode);
    }

    if (event.entering && !isLeaf) {
      stack.push(newNode);
    } else if (!event.entering && !isLeaf) {
      stack.pop();
    }
  }

  return node;
};

export default function commonMarkPlugin(this: Processor) {
  this.parser = parser;
}

const markdown2slate = (markdown: string) => {
  const processor = unified().use(commonMarkPlugin).use(slate);
  const d = processor.processSync(markdown);

  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  if ((d.result as any[]).length) {
    return d.result as Descendant[];
  }

  return [{ type: "paragraph", children: [{ text: "" }] }];
};

const slate2markdown = (value: unknown) => {
  return slateMark.plateToMarkdown(value as slateMark.SlateNode[]);
};

const markdown2html = (markdown: string) => {
  const processor = unified().use(commonMarkPlugin).use(remarkHtml);
  const d = processor.processSync(markdown);
  const html = d.value;
  return html;
};

export { markdown2html, markdown2slate, slate2markdown };

tests

import { describe, expect, it } from "vitest";

import { markdown2slate, slate2markdown } from "./serializer";

describe("text-editor", () => {
  describe("slate2markdown", () => {
    describe("when rendering ibi with italic bold+italic italic", () => {
      it("returns *i****b****i*", async () => {
        const md = slate2markdown([
          {
            type: "paragraph",
            children: [
              { italic: true, text: "i" },
              { italic: true, text: "b", bold: true },
              { italic: true, text: "i" },
            ],
          },
        ]);

        expect(md.startsWith("*i****b****i*")).toBeTruthy();
      });
    });
  });

  describe("markdown2slate", () => {
    describe("when rendering *i****b****i*", () => {
      it("returns italic italic+bold italic", async () => {
        const slate = markdown2slate("*i****b****i*");

        expect(slate).toEqual([
          {
            type: "paragraph",
            children: [
              { italic: true, text: "i" },
              { italic: true, bold: true, text: "b" },
              { italic: true, text: "i" },
            ],
          },
        ]);
      });
    });
  });
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
👍 phase/yes Post is accepted and can be worked on
Development

No branches or pull requests

3 participants