plate icon indicating copy to clipboard operation
plate copied to clipboard

Editor SetValue stripping out listStart from deserialized Markdown breaking list rendering.

Open tycomo opened this issue 4 months ago • 1 comments

Description

Hi all,

I noticed that I was having issues with certain markdown lists not being displayed correctly after performing deserialization and setting the markdown value in the editor (see the hardcoded markdown I used and the output of the editor below).

First, I initialized a basic editor from the starter kit with deserialization and logging.

const markdown = `
# Random Markdown Questions

## Question 1
1. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
   - A) Aliquam tortor purus
   - B) Aliquam tortor purus
   - C) Aliquam tortor purus
   - D) Aliquam tortor purus

---

## Question 2
2. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
   - A) Aliquam tortor purus
   - B) Aliquam tortor purus 
   - C) Aliquam tortor purus
   - D) Aliquam tortor purus

---
`


export function PlateEditor() {
  const editor = usePlateEditor({
    plugins: EditorKit,
  });

  const markdownValue = editor.getApi(MarkdownPlugin).markdown.deserialize(markdown, {
    remarkPlugins: [remarkMath, remarkGfm, remarkMdx, remarkMention],
  })

  console.log('markdownValue', markdownValue);

  //set the const markdown to the editor
  editor.tf.setValue(markdownValue);
  console.log('editor.children', editor.children);

  if (!editor) return null;

  return (
    <Plate editor={editor}>
      <EditorContainer variant="default">
        <Editor variant="default" />
      </EditorContainer>
    </Plate>
  );
}

Here is the editor output where both questions start with 1:

Image

I thought this was an issue with deserialization but in my console logs I see that the correct start value is defined.

Image

What is unexpected is that after this I see that the editor children do not have this value

Image

Because of this, it seems in my block-list component (left the same from the sample template) listStart is always null.

function List(props: PlateElementProps) {
  const { listStart, listStyleType } = props.element as TListElement;
  const { Li, Marker } = config[listStyleType] ?? {};
  const List = isOrderedList(props.element) ? 'ol' : 'ul';

  return (
    <List
      className="relative m-0 p-0"
      style={{ listStyleType }}
      start={listStart}
    >
      {Marker && <Marker {...props} />}
      {Li ? <Li {...props} /> : <li>{props.children}</li>}
    </List>
  );
}

I was able to work around this by overriding the deserializer and adding a custom property to the list items that "survives" setValue in the editor and then use that in block-list but that is just a temporary workaround.

I believe the expected behavior is that the listStart value from deserialization are accessible in the component to be able to render the correct values and that they also need to be available to be used for exporting to maintain list numbering (haven't been able to test export yet).

Reproduction URL

You can see the issue with the platejs block here: https://platejs.org/blocks/markdown-to-slate-demo

Reproduction steps

1. Setup Editor Similar to Above.
2. Deserialize the hardcoded markdown.
3. Notice that listStart is captured but not accessible to be used in the UI.

Plate version

49.2.21

Slate React version

0.117.4

Screenshots

See attached above.

Logs

See above.

Browsers

Chrome

tycomo avatar Sep 24 '25 18:09 tycomo

The reason listStart is missing after setValue is that Plate's normalization and schema do not recognize or preserve custom properties like listStart by default. Even if deserialization attaches listStart, it gets stripped unless the schema and plugins are extended to support it. This is a known limitation—Plate's list schema only includes properties like type, listStyleType, and indent, not listStart, so normalization removes anything extra source.

To keep listStart, you need to override the MarkdownPlugin's deserialization rules to add and propagate it, and also extend your list element types and normalization logic to preserve it through the editor lifecycle. This pattern is similar to how others have worked around missing properties in Plate source.

If you want a concrete example of how to override the deserializer and extend the schema to support listStart, let me know!

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

dosubot[bot] avatar Sep 24 '25 18:09 dosubot[bot]