Repository: facebook/lexical (TypeScript)

Issue:
Bug: Checklist markdown not exported correctly
<!--
  Please provide a clear and concise description of what the bug is. Include
  screenshots if needed. Please test using the latest version of the relevant
  Lexical packages to make sure your issue has not already been fixed.
-->

Lexical version: 0.37.0

## Steps To Reproduce

1. Go to playground
2. Create a checklist by typing `[ ]`
3. See that the outputted markdown is `[[ ]`

## The current behavior 

https://github.com/user-attachments/assets/d1dd79fb-da85-4b19-ac41-63f6737f7ee2


## The expected behavior

The outputted markdown is  `[ ]`

I assume this bug was introduced by this pr: https://github.com/facebook/lexical/pull/7892



Discussion:

Patch:
diff --git a/packages/lexical-markdown/src/MarkdownTransformers.ts b/packages/lexical-markdown/src/MarkdownTransformers.ts
index 758eade8b87..fc1bee2ed59 100644
--- a/packages/lexical-markdown/src/MarkdownTransformers.ts
+++ b/packages/lexical-markdown/src/MarkdownTransformers.ts
@@ -218,7 +218,7 @@ const ENDS_WITH = (regex: RegExp) =>
   new RegExp(`(?:${regex.source})$`, regex.flags);
 
 export const listMarkerState = createState('mdListMarker', {
-  parse: (v) => (typeof v === 'string' ? v : '-'),
+  parse: (v) => (typeof v === 'string' && /^[-*+]$/.test(v) ? v : '-'),
 });
 
 const createBlockNode = (
@@ -262,9 +262,11 @@ const listReplace = (listType: ListType): ElementTransformer['replace'] => {
     const listItem = $createListItemNode(
       listType === 'check' ? match[3] === 'x' : undefined,
     );
+    const firstMatchChar = match[0].trim()[0];
     const listMarker =
-      listType === 'bullet' || listType === 'check'
-        ? match[0].trim()[0]
+      (listType === 'bullet' || listType === 'check') &&
+      firstMatchChar === listMarkerState.parse(firstMatchChar)
+        ? firstMatchChar
         : undefined;
     if ($isListNode(nextNode) && nextNode.getListType() === listType) {
       if (listMarker) {


Test patch:
diff --git a/packages/lexical-markdown/src/__tests__/unit/LexicalMarkdown.test.ts b/packages/lexical-markdown/src/__tests__/unit/LexicalMarkdown.test.ts
index 0478b41969f..514df0bfb5a 100644
--- a/packages/lexical-markdown/src/__tests__/unit/LexicalMarkdown.test.ts
+++ b/packages/lexical-markdown/src/__tests__/unit/LexicalMarkdown.test.ts
@@ -38,6 +38,7 @@ import {
   Transformer,
 } from '../..';
 import {
+  CHECK_LIST,
   CODE,
   ElementTransformer,
   HEADING,
@@ -960,6 +961,54 @@ describe('Markdown', () => {
         expect(marker).toBe('+');
       });
     });
+    it('should not use [ as a marker for an implicit check list', () => {
+      const editor = createHeadlessEditor({
+        nodes: [ListNode, ListItemNode],
+      });
+      registerMarkdownShortcuts(editor, [CHECK_LIST]);
+      editor.update(
+        () =>
+          $getRoot()
+            .clear()
+            .append($createParagraphNode())
+            .selectEnd()
+            .insertText('[]'),
+        {discrete: true},
+      );
+      editor.update(() => $getSelection()!.insertText(' '), {
+        discrete: true,
+      });
+      editor.read(() => {
+        const node = $getRoot().getFirstChild();
+        expect(node).toBeInstanceOf(ListNode);
+        const marker = node ? $getState(node, listMarkerState) : undefined;
+        expect(marker).toBe('-');
+      });
+    });
+    it('should remember the marker for checkbox with an explicit marker', () => {
+      const editor = createHeadlessEditor({
+        nodes: [ListNode, ListItemNode],
+      });
+      registerMarkdownShortcuts(editor, [CHECK_LIST]);
+      editor.update(
+        () =>
+          $getRoot()
+            .clear()
+            .append($createParagraphNode())
+            .selectEnd()
+            .insertText('* []'),
+        {discrete: true},
+      );
+      editor.update(() => $getSelection()!.insertText(' '), {
+        discrete: true,
+      });
+      editor.read(() => {
+        const node = $getRoot().getFirstChild();
+        expect(node).toBeInstanceOf(ListNode);
+        const marker = node ? $getState(node, listMarkerState) : undefined;
+        expect(marker).toBe('*');
+      });
+    });
 
     it('should remember marker used on export', () => {
       const editor = createHeadlessEditor({