Git Product home page Git Product logo

remark-slate's Introduction

remark-slate

Transform the contents of a slate 0.50+ editor into markdown and back again.

Downloads Size

remark plugin to compile Markdown as a Slate 0.50+ compatible object.

Usage

Slate object to Markdown:

remark-slate exports an opinionated serialize function that is meant to be invoked with a slate 0.50+ state object and will transform the object into a markdown document.

import { serialize } from 'remark-slate';

export default ({ onChange }) => {
  const [value, setValue] = useState(initialValue);

  const handleChange = useCallback((nextValue) => {
    setValue(nextValue);
    // serialize slate state to a markdown string
    onChange(value.map((v) => serialize(v)).join(''));
  }, [onChange]);

  return (
    <Slate editor={editor} value={value} onChange={handleChange}>
      <Editable
        renderElement={renderElement}
        renderLeaf={renderLeaf}
        placeholder="Enter some rich text…"
        ...
      />
      ...
    </Slate>
  );
};

Markdown to Slate object:

When deserializing from markdown to slate, this package is meant to be used with remark-parse and unified.

Our JS looks something like this:

import fs from 'fs';
import unified from 'unified';
import markdown from 'remark-parse';
import slate from 'remark-slate';

unified()
  .use(markdown)
  .use(slate)
  .process(fs.readFileSync('example.md'), (err, file) => {
    if (err) throw err;
    console.log({ file });
  });

And example.md looks like this:

# Heading one

## Heading two

### Heading three

#### Heading four

##### Heading five

###### Heading six

Normal paragraph

_italic text_

**bold text**

~~strike through text~~

[hyperlink](https://jackhanford.com)

> A block quote.

- bullet list item 1
- bullet list item 2

1. ordered list item 1
1. ordered list item 2

Results in the following Slate object

Reveal
[
  {
    "type": "heading_one",
    "children": [
      {
        "text": "Heading one"
      }
    ]
  },
  {
    "type": "heading_two",
    "children": [
      {
        "text": "Heading two"
      }
    ]
  },
  {
    "type": "heading_three",
    "children": [
      {
        "text": "Heading three"
      }
    ]
  },
  {
    "type": "heading_four",
    "children": [
      {
        "text": "Heading four"
      }
    ]
  },
  {
    "type": "heading_five",
    "children": [
      {
        "text": "Heading five"
      }
    ]
  },
  {
    "type": "heading_six",
    "children": [
      {
        "text": "Heading six"
      }
    ]
  },
  {
    "type": "paragraph",
    "children": [
      {
        "text": "Normal paragraph"
      }
    ]
  },
  {
    "type": "paragraph",
    "children": [
      {
        "text": "italic text",
        "italic": true
      }
    ]
  },
  {
    "type": "paragraph",
    "children": [
      {
        "text": "bold text",
        "italic": true
      }
    ]
  },
  {
    "type": "paragraph",
    "children": [
      {
        "text": "strike through text",
        "strikeThrough": true
      }
    ]
  },
  {
    "type": "paragraph",
    "children": [
      {
        "type": "link",
        "link": "https://jackhanford.com",
        "children": [
          {
            "text": "hyperkink"
          }
        ]
      }
    ]
  },
  {
    "type": "block_quote",
    "children": [
      {
        "type": "paragraph",
        "children": [
          {
            "text": "A block quote."
          }
        ]
      }
    ]
  },
  {
    "type": "ul_list",
    "children": [
      {
        "type": "list_item",
        "children": [
          {
            "type": "paragraph",
            "children": [
              {
                "text": "bullet list item 1"
              }
            ]
          }
        ]
      },
      {
        "type": "list_item",
        "children": [
          {
            "type": "paragraph",
            "children": [
              {
                "text": "bullet list item 2"
              }
            ]
          }
        ]
      }
    ]
  },
  {
    "type": "ol_list",
    "children": [
      {
        "type": "list_item",
        "children": [
          {
            "type": "paragraph",
            "children": [
              {
                "text": "ordered list item 1"
              }
            ]
          }
        ]
      },
      {
        "type": "list_item",
        "children": [
          {
            "type": "paragraph",
            "children": [
              {
                "text": "ordered list item 2"
              }
            ]
          }
        ]
      }
    ]
  }
]

Miscellaneous

remark-slate makes some assumptions around unordered and ordered lists, the package pairs nicely with slate-edit-list

Local Development

Below is a list of commands you will probably find useful.

npm start or yarn start

Runs the project in development/watch mode. The project will be rebuilt upon changes.

Your library will be rebuilt if you make edits.

npm run build or yarn build

Bundles the package to the dist folder. The package is optimized and bundled with Rollup into multiple formats (CommonJS, UMD, and ES Module).

npm test or yarn test

Runs the test watcher (Jest) in an interactive mode. By default, runs tests related to files changed since the last commit.

License (MIT)

WWWWWW||WWWWWW
 W W W||W W W
      ||
    ( OO )__________
     /  |           \
    /o o|    MIT     \
    \___/||_||__||_|| *
         || ||  || ||
        _||_|| _||_||
       (__|__|(__|__|

Copyright © 2020-present Jack Hanford, [email protected]

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

remark-slate's People

Contributors

andarist avatar aweary avatar cwittofur avatar davidgoli avatar davidgovea avatar dependabot[bot] avatar dylans avatar hadrysmateusz avatar hanford avatar horacioh avatar janpaepke avatar julienng avatar newtmex avatar petrogad avatar wwsalmon avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

remark-slate's Issues

Support for underline text

Is there a way to format underline text. I understand that there is no official markdown for underline. But can we create a custom format to handle underline text?

List serialization/deserialization issues

Hello! Firstly, thank you for your work on this project.

We noticed two issues with list item behaviour specifically.

The first is that serialized output lacks line breaks between list items (the output looks like -item one-item two-item three\n). Your serialize function applies line breaks to almost all elements, but not list items. We presume this is because the mdast standard (not what Slate uses, but used by remark, and which a lot of plugins seem to have adopted) insists that list items should wrap their text in paragraphs, so your serializer is expecting to apply a line break following a paragraph child node. However Slate currently does not require this structure as of 0.58.0.

Would you accept a PR that adds a line break to the list item if there is no child paragraph? We have tested this in a fork, and it makes the serialization behave as expected.

The second issue is that running remark-parse against markdown generated by your serializer does generate an mdast with paragraphs wrapping text in list items, which the deserializer converts into paragraph nodes in the Slate state format. This in turn causes slate to render list items with paragraphs. This creates a usability issue whereby pressing enter within a list does not add a list item (it creates a line break within a paragraph). The behaviour of remark-parse is "correct" according to the specifications. Their suggestion is that this case should be handled in the parser, which in this case would be the deserialize function of this library. We were able to resolve it by using the mdast-flatten-listitem-paragraphs plugin. It isn't clear if this is an issue that should be resolved here, but it does create the situation where a round trip serialize/deserialize with this package mutates the content in Slate. I am happy to create a separate issue or PR if you would like a fix implementing based on the plugin above.

Sample Slate slate object from 0.59.0:
[
    {
      "type": "paragraph",
      "children": [
        {
          "text": "Unordered"
        }
      ]
    },
    {
      "type": "ul_list",
      "children": [
        {
          "type": "list_item",
          "children": [
            {
              "text": "first"
            }
          ]
        },
        {
          "type": "list_item",
          "children": [
            {
              "text": "second"
            }
          ]
        },
        {
          "type": "list_item",
          "children": [
            {
              "text": "third"
            }
          ]
        }
      ]
    },
    {
      "type": "paragraph",
      "children": [
        {
          "text": "Ordered"
        }
      ]
    },
    {
      "type": "ol_list",
      "children": [
        {
          "type": "list_item",
          "children": [
            {
              "text": "first"
            }
          ]
        },
        {
          "type": "list_item",
          "children": [
            {
              "text": "second"
            }
          ]
        },
        {
          "type": "list_item",
          "children": [
            {
              "text": "third"
            }
          ]
        }
      ]
    }
  ]

Screenshot of editor:

Screen Shot 2021-03-24 at 8 15 30 PM

remark-slate serialized output:

Screen Shot 2021-03-25 at 10 33 37 AM

deserialized mdast from remark-parse based on "fixed" markdown

Screen Shot 2021-03-25 at 10 39 50 AM

MDAST output:
[
    {
      "type": "paragraph",
      "children": [
        {
          "text": "Unordered"
        }
      ]
    },
    {
      "type": "ul_list",
      "children": [
        {
          "type": "list_item",
          "children": [
            {
              "type": "paragraph",
              "children": [
                {
                  "text": "first"
                }
              ]
            }
          ]
        },
        {
          "type": "list_item",
          "children": [
            {
              "type": "paragraph",
              "children": [
                {
                  "text": "second"
                }
              ]
            }
          ]
        },
        {
          "type": "list_item",
          "children": [
            {
              "type": "paragraph",
              "children": [
                {
                  "text": "third"
                }
              ]
            }
          ]
        }
      ]
    },
    {
      "type": "paragraph",
      "children": [
        {
          "text": "Ordered"
        }
      ]
    },
    {
      "type": "ol_list",
      "children": [
        {
          "type": "list_item",
          "children": [
            {
              "type": "paragraph",
              "children": [
                {
                  "text": "first"
                }
              ]
            }
          ]
        },
        {
          "type": "list_item",
          "children": [
            {
              "type": "paragraph",
              "children": [
                {
                  "text": "second"
                }
              ]
            }
          ]
        },
        {
          "type": "list_item",
          "children": [
            {
              "type": "paragraph",
              "children": [
                {
                  "text": "third"
                }
              ]
            }
          ]
        }
      ]
    }
  ]

Results in broken editor:
Screen Shot 2021-03-25 at 10 40 46 AM

serialize working right?

Here is a codesandbox illustrating problem. Basically serialize does not output what I expect.

I am using this markdown (basically same from README, but with an image):

# Heading one

## Heading two

### Heading three

#### Heading four

##### Heading five

###### Heading six

Normal paragraph

_italic text_

**bold text**

~~strike through text~~

[hyperlink](https://jackhanford.com)

![kitten](http://placekitten.com/g/200/200)

> A block quote.

- bullet list item 1
- bullet list item 2

1. ordered list item 1
1. ordered list item 2

Here are my 2 functions:

import { unified } from 'unified'
import remarkSlate, { serialize } from 'remark-slate'
import remarkParse from 'remark-parse'

// turn AST into MD
export const deserialize = src => {
  const { result } = unified()
    .use(remarkParse)
    .use(remarkSlate)
    .processSync(src)
  return result
}

// turn MD into AST
export { serialize }

If I run this:

const deserialized = deserialize(plainMd)
console.log({ deserialized, serialized: deserialized.map((v) => serialize(v)).join('') })

I get

# Heading one
## Heading two
### Heading three
#### Heading four
##### Heading five
###### Heading six
Normal paragraph
_italic text_
**bold text**
~~strike through text~~
[hyperlink](https://jackhanford.com)
<br>

> A block quote.



- bullet list item 1
- bullet list item 2


1. ordered list item 1
1. ordered list item 2

for serialized. It's missing image and the formatting is wrong.

and deserialized looks like this:
Screenshot 2023-06-02 at 10 12 38 AM
deserialize.json.zip

Versions:

Mac M1 running 13.3.1
Node v20.0.0
Npm 9.6.4

"remark-slate": "^1.8.6"
slate": "^0.94.1"
"unified": "^10.1.2"
"remark-parse": "^10.0.2"

Am I doing something wrong?

Help implementing custom deserializers

Hello. I am trying to take something that looks like @foo or #bar in the text and deserialize it into custom mention types (mention_user and mention_hashtag). Do you have any suggestion on how I could achieve this?

Unexpected escaping

Hi @hanford thanks for your work on this library. I was testing it out and I think I came across a bug. Not 100% sure what's going on but I think with the recursion the concatenated output string is hitting the default switch case and callling escapeHtml(children) 2 more times than needed.

When calling serialize({"children":[{"type":"p","children":[{"text":"& , \" < >"}]}]})...

Expected output: '&amp; &quot; &lt; &gt;'

Actual output: '&amp;amp;amp; , &amp;amp;quot; &amp;amp;lt; &amp;amp;gt;'

Inserting commas at newlines?

Thanks so much for working on this!

When using the serializer as you've laid out in the example code:

return nodes.map((v) => serialize(v)).join()

I get this JSON:

[
  {
    "type":"paragraph",
    "children":
      [{"text":"Testing the markdown serializer"}]
  },
  {
    "type":"paragraph",
    "children":
      [{"text":"It keeps inserting commas"}]
  },
  {
    "type":"paragraph",
    "children":
      [{"text":"What am I doing wrong?","bold":true}]
  }
]

And when I put that JSON into the serialize function, I get this Markdown:

Testing the markdown serializer
,It keeps inserting commas
,**What am I doing wrong?**

With commas inserted at every new line. So close! Any help would be appreciate. Thanks again!

Option to customize links for serialization

I saw that the option to customise the key for link destinations was added to the deserialization function, but it'd be good to have it in the serializer as well.

I expect we'd need to add linkDestinationKey to the serializer options, then use it here:

case nodeTypes.link:
      return `[${children}](${(chunk as BlockType)[linkDestinationKey] || ''})`;

Unfortunately, BlockType expects 'link' only, so I'm not sure whether it would make sense to make that property type more permissive or maybe I'm missing an alternative way to handle this. Happy to submit a PR with some guidance.

Example Slate object:

{
    "type": "a",
    "url": "https://google.co.uk", // referenced by `linkDestinationKey`
    "children": [
        {
            "text": "Google"
        }
    ],
}

Would convert to

[Google](https://google.co.uk)

accept custom slate node types

Currently I'm using this plugin to parse markdown content to slate nodes and works perfect!!

one thing that it would be nice is to accept the user's custom slate node types to make it more customisable.

for example: my paragraph type key is p instead of paragraph

The solution I suggest

we can just accept a key-value object to match the MDAST type to whatever you want using a closure like so:

import fs from 'fs';
import unified from 'unified';
import markdown from 'remark-parse';
import slate from 'remark-slate';

const myNodeTypes = {
  'paragraph': 'p',
  'ul_list': 'ul',
  'ol_list': 'ol',
  'list_item': 'li'
  // ...
}
unified()
  .use(markdown)
  .use(slate(myNodeTypes))
  .process(fs.readFileSync('example.md'), (err, file) => {
    if (err) throw err;
    console.log({ file });
  });

this is kinda simple change to do, but is a breaking change, that's why i found it better to discuss it first

hope it's clear!, and let me know any questions/feedback!
cc @hanford

Customizable marks

It would be great if the mark types could be customized like nodes.

For example, to change strikeThrough to strikethrough or strike-through.

Image as inline vs. block

I'm not sure of a great solution, but I think the ast that gets generated doesn't work well for an image if it is an inline element vs. a block.

Perhaps it should get fixed via normalization, but I think for inline it's probably ideal to have an image element have an empty text element before and after. Perhaps this could be handled via a mode, or perhaps there's a good way to infer it from usage.

Add support to serialize slate tree to remark tree

Currently there is a limited options to serialize slate tree to md only using the serialize function exported for javascript without the option to use remark to convert to remark Syntax tree.

Another good option is to be able to use remark to serialize slate to markdown and then to remark syntax tree.

The ol and ul lists are broken when editor content is created from markdown

Going to new line is broken on ordered and numbered lists (as well as nested lists).

There are numbered list, clicking enter should add number in new line as well. Instead an empty space is added.

I get the same issue with remark-slate-transformer. Maybe there is an issue with markdown serialization? I have recored the video below from remark-slate-transformer demo website. But I get exactly same issue in our internal editor using remark-slate. I would really appreciate if you have any input why this might be happening.

markdown-to-slate-issue.mov

Support for inline code

There is only support for code blocks and not inline code.

Markdown HTML Rendered Output
At the command prompt, type `nano`. At the command prompt, type <code>nano</code>. At the command prompt, type nano.

Create new release for image component

Version 1.8.0 does not contain the latest fix that adds the image to the list of void elements (pr #46).

@hanford would it be possible to get a new release please 🙏

Edit: released a forked version under @sigle/remark-slate while waiting for an official release if anyone needs the release too :)

Mismatch between Slate Node type and Leaf/Block types

I may be misunderstanding something here.

In my typescript project I've typed the value of my Slate editor as Array<Node> (Node type coming from Slate).
When I use serialize, the first argument for chunk is typed as BlockType or LeafType as defined in the remark-slate package. Where have these types come from and do they have equivalents from Slate itself?

image

Any guidance appreciated, thanks.

Ordered lists only serialise items as ".1"

I've happened upon this part of the serialiser:

    case nodeTypes.listItem:
      // whether it's an ordered or unordered list
      const isOL = chunk && chunk.parentType === nodeTypes.ol_list;
      const treatAsLeaf =
        (chunk as BlockType).children.length === 1 &&
        isLeafNode((chunk as BlockType).children[0]);

      let spacer = "";
      for (let k = 0; listDepth > k; k++) {
        if (isOL) {
          // https://github.com/remarkjs/remark-react/issues/65
          spacer += "   ";
        } else {
          spacer += "  ";
        }
      }
      return `${spacer}${isOL ? "1." : "-"} ${children}${
        treatAsLeaf ? "\n" : ""
      }`;

If the list is ordered it will always start any items with .1:

${isOL ? "1." : "-"}

This seems intentional but I can't figure out why. Any idea on how I could count the actual number?

Different node types in remark-slate vs slate?

I'm new to Slate, so there's probably something obvious I'm missing. However, when I try to serialize the Slate editor's JSON tree to Markdown using this lib's serialize function, it spits out the text from the leaves and ignores any block elements. This appears to be because the node types in the value passed to my editor's onChange are different from the default node types that remark-slate uses.

E.g., when I create a blockquote, it will be represented as this object with a type of blockquote:

{
    "type": "blockquote",
    "id": 1647999105634,
    "children": [
        {
            "text": "test quote"
        }
    ]
}

but this library looks for a node with a type of block_quote instead. The same goes for headings too as the types my editor uses are different from this library's. Is this a versioning issue or is there something basic I'm misunderstanding about how Slate works?

remark@next (13)

Hi!

remark is switching to a new parser (and compiler) internally (micromark, see remarkjs/remark#536 for more info).
From a quick glance at the code, it seems this plugin should be fine. However, it would be good to check that in the future (there is 13.0.0-alpha.0 published now, and one or two more will come before the release)

Issue with Image node

I'm using remark-slate with Plate. Plate can load most markdown except for image (see differences below). I can pass an option to the deserialize function to change type=image to type=img. I don't think I can change how caption field is created. Any tips on how to fix this issue?

Plate image node: (working)

  {
    "type": "img",
    "url": "path to image",
    "children": [
      {
        "text": ""
      }
    ],
    "caption": [
      {
        "text": "foos"
      }
    ]
  }

remark-slate -> Plate: (not working)

      {
        "type": "image",  <-- expecting 'img'
        "children": [
          {
            "text": ""
          }
        ],
        "url": "path to image",
        "caption": "foos" <-- expecting an array 
      }

Custom Elements

Slate has the power to create all sorts of custom elements that cannot be easily represented in Markdown. If one passes an element that remark-slate does not recognize, it will basically just ignore it and render its element as text. This is somewhat limiting as Markdown can just bypass regular HTML, and it means you can't really use Slate to its full potential as an editor while still relying on Markdown as a wire format.

On my own project I extended the serialize() function to only escape HTML if there's no a recognizable element type, and to encode the attributes for each node into HTML attributes:

    default:
      if (!type) return escapeHtml(children)

      const {
        type: _type,
        children: _children,
        parentType: _parentType,
        ...element
      } = chunk as BlockType
      const attributes = Object.entries(element)
        .map(([attribute, value]) => `${attribute}="${value as string}"`)
        .join(' ')

      return `<myproject-${type} ${attributes} />`

This seems to work pretty well, the custom elements are represented in Markdown as <myproject-element id="foo" />, and I can use a Rehype plugin to transform them into React components when these need to get rendered. Wondering if this is the right approach and whether this could be utilized in remark-slate directly if anyone else is having this problem?

On NodeJS - TypeError: Cannot `process` without `Compiler`

Hi,
Thanks for everybody's efforts on this project.

I've been having issues getting it to work on NodeJS and all things point to the issue being with remark-slate with Node. The same code worked fine in the browser within a React app. I want to use this on the backend to handle read/write from markdown persistence.

I've tried debugging myself but haven't made much progress. I've also tried different versions of dependencies with no luck.

Seems like somebody else has had issues trying to use for a similar use case. See thread here.

fwiw I seem to be making a bit more progress with remark-slate-transformer.

Any ideas?

Much appreciated.

My test code

  unified()
    .use(remarkParse)
    .use(remarkSlate)
    .process('[my link](https://github.com)', (err, file) => {
      if (err) throw err;
      if (!file) throw "file is undefined"
      console.log(file);
    });

The error thrown by Unified.
node_modules/unified/lib/index.js:520 the assertCompiler function.

TypeError: Cannot `process` without `Compiler`

with ESM + Typescript

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.