A completely customizable framework for building rich text editors. (Currently in beta.)

Overview

A completely customizable framework
for building rich text editors.


Why? · Principles · Demo · Examples · Documentation · Contributing!



Slate lets you build rich, intuitive editors like those in Medium, Dropbox Paper or Google Docs—which are becoming table stakes for applications on the web—without your codebase getting mired in complexity.

It can do this because all of its logic is implemented with a series of plugins, so you aren't ever constrained by what is or isn't in "core". You can think of it like a pluggable implementation of contenteditable built on top of React. It was inspired by libraries like Draft.js, Prosemirror and Quill.

🤖 Slate is currently in beta. Its core API is useable right now, but you might need to pull request improvements for advanced use cases, or fixes for some bugs. Some of its APIs are not "finalized" and will have breaking changes over time as we discover better solutions. There isn't currently a 1.0 release schedule, we're still getting the architecture right.

🤖 Slate is also contributor-driven. It is not backed by any huge company, which means that all contributions are voluntary and done by the people who need them. If you need something improved, added, or fixed, please contribute it yourself or no one will. And if you want to become a more active maintainer, let us know in the Slack channel.


Why?

Why create Slate? Well... (Beware: this section has a few of my opinions!)

Before creating Slate, I tried a lot of the other rich text libraries out there—Draft.js, Prosemirror, Quill, etc. What I found was that while getting simple examples to work was easy enough, once you started trying to build something like Medium, Dropbox Paper or Google Docs, you ran into deeper issues...

  • The editor's "schema" was hardcoded and hard to customize. Things like bold and italic were supported out of the box, but what about comments, or embeds, or even more domain-specific needs?

  • Transforming the documents programmatically was very convoluted. Writing as a user may have worked, but making programmatic changes, which is critical for building advanced behaviors, was needlessly complex.

  • Serializing to HTML, Markdown, etc. seemed like an afterthought. Simple things like transforming a document to HTML or Markdown involved writing lots of boilerplate code, for what seemed like very common use cases.

  • Re-inventing the view layer seemed inefficient and limiting. Most editors rolled their own views, instead of using existing technologies like React, so you have to learn a whole new system with new "gotchas".

  • Collaborative editing wasn't designed for in advance. Often the editor's internal representation of data made it impossible to use to for a realtime, collaborative editing use case without basically rewriting the editor.

  • The repositories were monolithic, not small and reusable. The code bases for many of the editors often didn't expose the internal tooling that could have been re-used by developers, leading to having to reinvent the wheel.

  • Building complex, nested documents was impossible. Many editors were designed around simplistic "flat" documents, making things like tables, embeds and captions difficult to reason about and sometimes impossible.

Of course not every editor exhibits all of these issues, but if you've tried using another editor you might have run into similar problems. To get around the limitations of their API's and achieve the user experience you're after, you have to resort to very hacky things. And some experiences are just plain impossible to achieve.

If that sounds familiar, you might like Slate.

Which brings me to how Slate solves all of that...


Principles

Slate tries to solve the question of "Why?" with a few principles:

  1. First-class plugins. The most important part of Slate is that plugins are first-class entities. That means you can completely customize the editing experience, to build complex editors like Medium's or Dropbox's, without having to fight against the library's assumptions.

  2. Schema-less core. Slate's core logic assumes very little about the schema of the data you'll be editing, which means that there are no assumptions baked into the library that'll trip you up when you need to go beyond the most basic use cases.

  3. Nested document model. The document model used for Slate is a nested, recursive tree, just like the DOM itself. This means that creating complex components like tables or nested block quotes are possible for advanced use cases. But it's also easy to keep it simple by only using a single level of hierarchy.

  4. Parallel to the DOM. Slate's data model is based on the DOM—the document is a nested tree, it uses selections and ranges, and it exposes all the standard event handlers. This means that advanced behaviors like tables or nested block quotes are possible. Pretty much anything you can do in the DOM, you can do in Slate.

  5. Intuitive commands. Slate documents are edited using "commands", that are designed to be high-level and extremely intuitive to write and read, so that custom functionality is as expressive as possible. This greatly increases your ability to reason about your code.

  6. Collaboration-ready data model. The data model Slate uses—specifically how operations are applied to the document—has been designed to allow for collaborative editing to be layered on top, so you won't need to rethink everything if you decide to make your editor collaborative.

  7. Clear "core" boundaries. With a plugin-first architecture, and a schema-less core, it becomes a lot clearer where the boundary is between "core" and "custom", which means that the core experience doesn't get bogged down in edge cases.


Demo

Check out the live demo of all of the examples!


Examples

To get a sense for how you might use Slate, check out a few of the examples:

  • Plain text — showing the most basic case: a glorified <textarea>.
  • Rich text — showing the features you'd expect from a basic editor.
  • Markdown preview — showing how to add key handlers for Markdown-like shortcuts.
  • Links — showing how wrap text in inline nodes with associated data.
  • Images — showing how to use void (text-less) nodes to add images.
  • Hovering toolbar — showing how a hovering toolbar can be implemented.
  • Tables — showing how to nest blocks to render more advanced components.
  • Paste HTML — showing how to use an HTML serializer to handle pasted HTML.
  • Mentions — showing how to use inline void nodes for simple @-mentions.
  • See all the examples...

If you have an idea for an example that shows a common use case, pull request it!


Documentation

If you're using Slate for the first time, check out the Getting Started walkthroughs and the Concepts to familiarize yourself with Slate's architecture and mental models.

If even that's not enough, you can always read the source itself, which is heavily commented.

There are also translations of the documentation into other languages:

If you're maintaining a translation, feel free to pull request it here!


Packages

Slate's codebase is monorepo managed with Lerna. It consists of a handful of packages—although you won't always use all of them. They are:

Package Version Size Description
slate Slate's core data model logic.
slate-history A plugin that adds undo/redo history to Slate.
slate-hyperscript A hyperscript tool to write JSX Slate documents!
slate-react React components for rendering Slate editors.

Contributing!

All contributions are super welcome! Check out the Contributing instructions for more info!

Slate is MIT-licensed.

Comments
  • fix editing with

    fix editing with "soft keyboards" (eg. Android, IMEs)

    Do you want to request a feature or report a bug?

    Bug.

    What's the current behavior?

    The issue with using Slate on Android is complex, and due to a difference in how its OS keyboard is designed. On mobile devices, keyboards are starting to move away from the "keys" concepts in many ways...

    • Autocorrect will make changes without any "key" being press.
    • Autosuggest will insert words that don't map to keys.
    • Swipe-to-type will insert entire words in one go, instead of using keys.
    • etc.

    Because of this, it sounds like the Android team (reasonably from their point of view) decided to not reliably fire key-related events.

    As soft input methods can use multiple and inventive ways of inputting text, there is no guarantee that any key press on a soft keyboard will generate a key event: this is left to the IME's discretion, and in fact sending such events is discouraged. You should never rely on receiving KeyEvents for any key on a soft input method. —KeyEvent, Android Reference

    It sounds like the behavior is:

    • The keypress event is never triggered, which is fine for us.
    • Text-based keys like a, b, etc. fire a keydown event but always with a key code of 229, indicating that the key is unidentifiable because the keyboard is still busy processing IME input, which may invalidate the actual key pressed.
    • Pressing keys like enter fires a keydown event as normal, with an event.key that can be recognized? (This is unclear whether this happens or not.)
    • Pressing backspace does not fire a proper keydown event.

    A few different resources:

    • Great Android postmortem writeup: https://docs.google.com/document/d/1Hex89Di-r-Wfpo1DLAtxpetoX588ziXVoNyC87Je3Xc/edit#
    • Chrome "bug": https://bugs.chromium.org/p/chromium/issues/detail?id=118639
    • React issues: https://github.com/facebook/react/issues/4079 https://github.com/facebook/react/issues/6176 https://github.com/facebook/react/issues/11231
    • VSCode's IME test docs: https://github.com/Microsoft/vscode/wiki/IME-Test
    • @danburzo's input methods: https://github.com/danburzo/input-methods

    What's the expected behavior?

    The fix for this is also complicated. There are a handful of different, overlapping pieces of logic that need to change, to accommodate a handful of different input types...

    The first stage is to handle basic insertions, and auto-suggestions...

    • [ ] Remove the preventDefault in onBeforeInput, so that the DOM is updated, and the onInput logic will trigger, diffing the insertion and then "fixing" it.
    • [ ] Update the <Leaf> (and other?) components to increment their key such that React properly unmounts and reconciles the DOM, since it has changed out from under it.

    This is actually the same starting steps as is required for https://github.com/ianstormtaylor/slate/issues/2060, so I'd recommend we solve that issue in its entirety first, to work from a solid base.

    This fixes the actual text insertion pieces, and probably deletions as well. Splitting blocks can still be handled by enter because it still provide proper key codes.

    • [ ] Check that all other behaviors that aren't text insertions (eg. splitting blocks) are handled properly without access to many keydown events.

    And then there's some selection issues, which apparently can mess up Android's IME (and potentially others) if the selection is manually changed during a composition.

    • [ ] Prevent re-rendering the editor on compositionstart and compositionend.
    • [ ] Prevent updating the selection while a composition is taking place.

    I think this would solve the 90% case for soft keyboard input.


    Separately, there's still another question of how to properly handle these kinds of behaviors other plugins. For example if a plugin uses backspace at the start of a block to reset that block, that won't work on Android. So after solving the input issues, we need to step back to an architectural level and solve this plugin handling problem. But that can wait.

    bug ♥ help ⚑ cross platform ⚑ ime ⚑ mobile 
    opened by ianstormtaylor 222
  • Collaborative editing

    Collaborative editing

    @yatskevich and me did a research and tried to design an appropriate architecture for supporting collaborative editing. There are some key points with explanation below.

    The main idea is to implement a kind of Redux-like behavior within Slate. <Editor store={Store} … /> The goal of having Store is ability to have clear separation and understandable flow.

    UseCase:

    • User press key (types symbol)
    • Action is generated
    • Action is dispatched to Store
    • Store updates State
    • setState called
    • Editor re-rendered

    This way allows us to have ability to manage operations in any suitable way (CRDT, OT, etc.) within Store.

    Let’s see how to handle text paste operation in more details:

    1. Action pasteText dispatched to Store (data: {position, text}) No need to get current state, call transform() and return new state. Just dispatch an Action.
    2. Store has a Reducer for handling such an Action If we’re talking about ColloborativeStore - one type, if about SimpleStore - another one. We can even have few implementation of CollaborativeStores (CRDT and OT).

    CollaborativeStore:

    • Splits pasteText operation into a set of atomic insertCharacter operations
    • Applies operations to State
    • Translates this operations set to server
    • Listens for updates from server (i.e. smb else is editing this doc and generates the same type of operations set)

    SimpleStore:

    • Applies operations to State (no need to split to atomic operations)

    In our mind such architecture allows Slate to be more flexible and extensible. But this is not a completely analyzed proposal - just an idea.

    And a few pictures for clarification.

    Workflow Chart: fullsizerender

    Implementation example fullsizerender 5

    pastText in depth: fullsizerender 3

    P.S. Possibly, Store should send Transfroms (not Actions) to Server and get Transforms back.

    @ianstormtaylor: Could you please take a look at this and share your opinion?

    discussion 
    opened by davolokh 125
  • Screen Jumps/Scrolls cursor out of view during editing on iOS (iPad, iPhone)

    Screen Jumps/Scrolls cursor out of view during editing on iOS (iPad, iPhone)

    Do you want to request a feature or report a bug?

    Report a Bug

    What's the current behavior?

    Using the rich text editor on the SlateJS demo site (or any of the demos for that matter):

    http://slatejs.org/#/rich-text

    If you start editing and the cursor goes below the virtual keyboard, the text doesn't scroll up properly. Instead, it jumps all the way to the top of the screen.

    image-2

    Note that the jump happened as soon as I typed anything. In this case, I hit the SPACE on the virtual keyboard.

    This is on an iPad iOS 11 but the same happens on my iPad. The latest version of Slate as on the demo as of today.

    From what I can remember, this did not happen before; however, it may have been quite an old version where it worked properly.

    What's the expected behavior?

    Normally, the screen will stay scrolled to the part of the editor we are editing.

    bug ♥ help ⚑ cross platform 
    opened by thesunny 75
  • consider migrating from Immutable.js

    consider migrating from Immutable.js "Records" to plain objects

    Do you want to request a feature or report a bug?

    Discussion.

    What's the current behavior?

    When Slate was first created, Immutable.js was the best and most popular way to handle immutability in React. However, it has many downsides for our use case:

    • CON: It requires a fromJS step to build the collections/records. Since the objects aren't the native JavaScript data types you get from JSON, we have to have an interim step that instantiates the Immutable.js data model. This can be costly for large documents, and there is no way around it. This is especially problematic in server-side environments where serializing to/from JSON blocks Node's single thread.

    • CON: Reading values is more expensive. Immutable.js is optimized for non-crazy-slow writes, at the expense of read operations being much slower than native JavaScript. Since Slate's model is a tree, with many node lists, this ends up having a significant impact on many of the common operations that take place on a document. (See this question for more information.)

    • CON: It introduces a fairly steep learning curve. People getting started with Slate often get tripped up by not knowing how Immutable.js works, and its documentation isn't easy to understand. This results in lots of "wheel reinvention" because people don't even realize that helper methods are available to them.

    • CON: It makes debugging harder. In addition to a learning curve, debugging Immutable.js objects adds extra challenges. There's a browser extension that helps, but that's not good enough in lots of places, and you end up having to us toJS to print the objects out which is very tedious.

    • CON: It increases bundle size. The first increase in size comes from just including the immutable package in the bundle at all, which adds ~50kb. But there is also a more insidious bundle size increase because the class-based Record API encourages monolithic objects that can't be easy tree-shaken to eliminate unused code. Whereas using a utility-function-based API, similar to Lodash, would not have this issue.

    However, it does have some benefits:

    • PRO: Custom records allow for methods/getters. This has been the primary way that people read values from Slate objects. Because Slate (and rich text in general) deals with some complex data structures, having common things packaged up as methods allows us to reduce a lot of boilerplate and reinventing the wheel for common use cases.

    • PRO: It offers built-in concepts like OrderedSet. These allow for more expressive code in core than otherwise, because we'd need to reinvent the wheel a bit to account for JavaScript not having some of these concepts. Although truth be told this is probably a fairly minimal gain.


    Since Slate was first created, immer has come on the scene (thanks to @klis87 for kickstarting discussion of it in #2190) which offers a way to use native JavaScript data structures while maintaining immutability. This is really interesting, because it could potentially have big performance and simplicity benefits for us.

    All of the CON's above would go away. But we'd also lose the PRO's. That's what I'm most concerned about, and what I'd like to discuss in this issue... to see what a Slate without Immutable.js might look like, and how we could mitigate losing some of its benefits.

    Without the ability to use classes and prototypes for our data models, we'd need to switch to using a more functional approach—exporting helpers in a namespace, like we currently already do for PathUtils. One question is whether this will be painful...

    Looking at our rich-text example, we'd need to change how we do things in several places.

    https://github.com/ianstormtaylor/slate/blob/8eb8e26958ef6c9337e838bfd46fc2cc55d7fe51/examples/rich-text/index.js#L54

    We no longer have getters on our models, so value.activeMarks doesn't work. Instead, we'd need to change this to:

    return Value.getActiveMarks(value).some(mark => mark.type == type)
    

    Similarly, there's no value.blocks any more:

    https://github.com/ianstormtaylor/slate/blob/8eb8e26958ef6c9337e838bfd46fc2cc55d7fe51/examples/rich-text/index.js#L66

    So we'd need:

    return Value.getClosestBlocks(value).some(node => node.type == type)
    

    This is actually nice because we're no longer using potentially expensive getters to handle common use cases—calling functions is more clear.

    But we also can't use helper methods like document.getParent.

    https://github.com/ianstormtaylor/slate/blob/8eb8e26958ef6c9337e838bfd46fc2cc55d7fe51/examples/rich-text/index.js#L148

    Instead we'd need to use:

    const blocks = Value.getClosestBlocks(value)
    const parent = Node.getParent(value.document, blocks[0].key)
    

    Similarly, we can't do:

    https://github.com/ianstormtaylor/slate/blob/8eb8e26958ef6c9337e838bfd46fc2cc55d7fe51/examples/rich-text/index.js#L293-L295

    And would have to instead do:

    const blocks = Value.getBlocks(value)
    const isType = blocks.some(block => {
      return !!Node.getClosest(document, block.key, parent => parent.type == type)
    })
    

    But we also get to remove some expensive code, since we don't need to deserialize anymore:

    https://github.com/ianstormtaylor/slate/blob/8eb8e26958ef6c9337e838bfd46fc2cc55d7fe51/examples/rich-text/index.js#L41-L43

    That's all for the rich text example, but it would definitely be a big change.


    I'm curious to get other folks's input. Are there other PROS or CONS to Immutable.js that I haven't listed above? Are there other ways of solving this that I haven't considered? Any thoughts! Thank you!

    ⚑ perf improvement ✶ breaking discussion ⚑ filesize 
    opened by ianstormtaylor 70
  • Proposal for Sponsored Open Source Android Support

    Proposal for Sponsored Open Source Android Support

    I am considering a sponsored version of an Open Source Android Plugin for Slate for USD $25,000.

    1. This is an alternative to the proposal for a Paid Android Plugin (also proposed by me) for USD $500 per license as described here https://github.com/ianstormtaylor/slate/issues/3573

    2. The Android Plugin will be released as open source under the same license as Slate and will support at least the latest version of Android with similar features and limitations as the 0.47x Android Support

    3. Building Android support is a lot of work. We would love to be able to contribute for free (like Ian!), but the cost is high to burden alone. We contributed Android Support for Slate 0.47 which was working although not perfect and that cost about USD $40,000. That doesn’t include research we did prior to starting or the devices we had to purchase to test on.

    4. It's uncertain how much it will cost to complete the 0.50x version of Slate Android. It may cost more or it may cost less. Either way, we are committing to completing it if we raise the funds. If it costs less, we will not be returning unused funds; if it costs more, we will cover the difference. Note that the 0.50x port is not trivial as another user worked for 3 months on it and it was never completed.

    5. For clarity, we already spent USD $40,000, so even if the port costs $0, there will be no profit from this.

    The port will be completed in partnership with Wayne Leroux with assistance from me. Wayne has built an impressive collaborative editor based on Slate (https://github.com/wleroux/slate-collaborative) so I have confidence in his ability to complete this project. He has also contributed other open source projects (https://github.com/wleroux?tab=repositories). I expect him to be able to complete the project; however, if for any reason he cannot, I will either complete it myself or will return the funds. Wayne and I intend to work together on a different Slate related project which would benefit from Android support.

    If we are unable to raise the funds through sponsorship, we will revert to the paid model at USD $500 per license. For clarity, if we don't raise the funds through sponsorship and use a paid model but later receive the equivalent revenue in licensing fees from the paid model (USD $25,000 or 50 licenses), we will not then open source the code.

    I recognize that this is a non-trivial amount to raise. I expect that companies that can afford to do so, will need to contribute more in order to meet this fundraising goal. To put the amount into perspective, this is the equivalent of contracting a senior developer for about 6 weeks. 3 organizations, contributing the equivalent of a contract valued at 2 weeks worth of work will reach the sponsorship goal (about USD $8000 each) or 50 smaller contributors paying what would be the licensing fee of USD $500.

    Update: Now Live!

    Add Android Support to Slate - KickStarter Campaign Now Live

    opened by thesunny 58
  • Benchmark consistence && Allow users to select benches to run

    Benchmark consistence && Allow users to select benches to run

    How to use this PR before merge

    1. fecth this branch to your local repo
    2. git checkout benchmark-consistence && yarn build && yarn benchmark:save
    3. git checout your-repo && yarn build
    4. git checkout benchmark-consistence && yarn benchmark

    Problem:

    We always have an inconsistent benchmark result. The problems are caused by Most important problem:

    1. before and after in the Matcha is called by each suite, not by each bench.
      Other problems:
    2. We need to call global.gc() before each bench. Avoid calculating gc times in bench
    3. We shall not use babel-node in benchmark. Because code parsing time and other time related in babel-code will be introduced in the benchmark.
    4. We shall have different input in each bench run, to avoid some potential optimization. (Generate new blocks/value with same data)
    5. a user-side problem: I do not want to wait with no output when running the benchmark.
    6. Perhaps we shall use process.cpuUsage, to eliminate the effects of node optimization and influence of other processes.
    7. [Not in this PR] Perhaps we shall run node --expose-gc benchmark multi times for better robustness.

    Proposal: Why we need a benchmark framework instead of using the existing one:

    1. : Other benchmark framework uses process.hrtime() to evaluate the time consuming. It is good for I/O heavy tasks, but our focus is CPU heavy. If we want to eliminate the problem by other processes, we would prefer process.cpuUsage. (For benchmarkJS, it uses lodash.now, which is a slow Date.now system call)

    2. Most benchmark frameworks requires a fixed run number, but our tasks varies from 4 ops/sec (from-json) to 10,000 ops/sec (get-blocks). It is very unhappy to special numbers for each tasks.

    3. There is an adaptive mode in matcha, without specializing the run number, but 3.1 It is out of maintenance 3.2 It calls process.hrtime() in each loop for adaptive mode. I am not sure whether the time waiting for process.hrtime is comparable to 10,000 ops/sec.
      3.3 It does not solve other problems.

    4. We want to exclude the time of gc in our processes, then we want to node --expose-gc and run global.gc each time before we run each bench.

    5. Allow users to config about which benchmarks to run, we do not want to re-run all benchmarks every time.

    ⚑ perf debt 
    opened by zhujinxuan 54
  • Expose transforms

    Expose transforms

    Fixes #903 Fixes #973 Fixes #970 Fixes #912 Fixes #1003 Fixes #841 Fixes #833 Fixes #813 Fixes #767 Fixes #683 Fixes #787 Fixes #1030

    This is a breaking change to the API.

    This PR adds the last piece of functionality to core that I think is required for #259 to have everything needed for Operational Transform–style collaborative editing.

    (Still in progress, but if you have thoughts please comment!)

    A summary of the breaking changes so far...


    onChange now receives a Change object (previously named Transform) instead of a State. Plugins and users will need to now use:

    onChange(change) {
      this.setState({ state: change.state })
    }
    

    Or more tersely:

    onChange({ state }) {
      this.setState({ state })
    }
    

    Which achieves the same behavior as before. This is needed because it enforces that all changes are represented by a single set of operations. Otherwise right now it's possible to do things like state.transform()....apply({ save: false }).transform()....apply() and result in losing the operation information in the history. With OT, we need all transforms that may happen to be exposed and emitted by the editor.


    Similarly, handlers now receive e, data, change instead of e, data, state. Instead of doing return state.transform()....apply() the plugins can now act on the change object directly.

    function onKeyDown(e, data, change) {
      if (data.key == 'enter') {
        return change.splitBlock()
      }
    }
    

    Plugins can still return change... if they want to break the stack from continuing on to other plugins. (Any != null value will break out.) But they can also now not return anything, and the stack will apply their changes and continue onwards. This was previously impossible.


    The onChange and on[Before]Change handlers now receive Change objects. Previously they would also receive a state object, but now they receive change objects like the rest of the plugin API.


    The transform.apply() method is deprecated. Previously this is where the saving into the history would happen, but it created an awkward convention that wasn't necessary. Now operations are saved into the history as they are created with change methods, instead of waiting until the end. You can access the new State of a change at any time via change.state.


    The .apply({ save }) option is now state.change({ save }) instead. This is the easiest way to use it, but requires that you know whether to save or not up front. If you want to use it inline after already saving some changes, you can use the change.setSave(save) flag instead. This shouldn't be necessary for 99% of use cases though.


    The .undo() and .redo() transforms don't save by default. Previously you had to specifically tell these transforms not to save into the history, which was awkward. Now they won't save the operations they're undoing/redoing by default.


    onBeforeChange is no longer called from componentWillReceiveProps, when a new state is passed in as props to the <Editor> component. This caused lots of state-management issues and was weird in the first place because passing in props would result in changes firing. It is now the parent component's responsibility to not pass in improperly formatted State objects.


    The splitNodeByKey change method has changed to be shallow. Previously, it would deeply split to an offset. But now it is shallow and another splitDescendantsByKey change method has been added (with a different signature) for the deep splitting behavior. This is needed because splitting and joining operations have been changed to all be shallow, which is required so that operational transforms can be written against them.


    The shape of many operations has changed. This was needed to make operations completely invertible without any extra context. The operations were never really exposed in a consumable way, so I won't detail all of the changes here, but feel free to look at the source to see the details.


    All references to "joining" nodes is now called "merging". This is to be slightly clearer, since merging can only happen with adjacent nodes already, and to have a nicer parallel with "splitting", as in cells. The operation is now called merge_node, and the transforms are now merge*.


    The .length property of nodes has been removed. This property caused issues with code like in Lodash that checked for "array-likeness" by simply looking for a .length property that was a number.

    ✶ breaking 
    opened by ianstormtaylor 44
  • Delete all will fail when there are three blocks or more

    Delete all will fail when there are three blocks or more

    EDIT: previously this was only an issue when the last block ended in a inline void node. Now it is always an issue.

    Do you want to request a feature or report a bug?

    Bug

    What's the current behavior?

    If you have three blocks or more, where the last block ends with an inline void node, and you select everything and hit backspace (delete), it will fail with error:

    Uncaught Error: `Node.assertNode` could not find node with path or key: List [ 1 ]
    

    Reproduction:

    1. Goto the emoji-example
    2. Add an emoji at the end of the last block.
    3. Select everything (command/ctrl-A)
    4. Hit backspace

    What's the expected behavior?

    That everything will be deleted without error.

    I have noticed that when deleting all (and you have three or more blocks) the change will include an operation with type move_node where the path property is a block path (i.ie [2] with the same value as the newPath prop [2]. I think Slate will regenerate keys for this operation and it may lead to issues.

    bug ♥ help 
    opened by skogsmaskin 42
  • RFC: Fix for Android devices

    RFC: Fix for Android devices

    Is this adding or improving a feature or fixing a bug?

    Fixing a bug

    What's the new behavior?

    It works

    Proof: https://photos.google.com/share/AF1QipO2uaYQFgq8PuYPq2ojGLokBHhqPaLoFQif8fQFyq220giwtcfz6x1FFdl8HWp7XQ/photo/AF1QipMrSTxFBM5GeXc98E_2gh7p9SKveXRmphrDQfJ1?key=Q19FaENxc01ka3VKZnZINXgzNGxfZThhSG1XYXhn

    Device is an OG Pixel, using GBoard on Android 8.1.0. YMMV. PLEASE TEST ON YOUR DEVICES!

    How does this change work?

    1. It doesn't hijack events. it just lets android do its thing
    2. it debounces updates to slate so the IME auto-suggest isn't constantly refreshing when the selection gets reset every render. Without a debounce, you can't type faster than ~3 keys per second and holding the backspace doesn't work.

    NOTE:

    • I use snapshotting, so react version is pinned at 16.3, making this a breaking change. you can use cWU if you want, but the package will need to be made react 17-ready soon anyways...
    • Before merging, please consider using snapshotting to replace updateSelection for all devices, not just android.

    Does this fix any issues or need any specific reviewers?

    fix #725 fix #1857

    Reviewers: @Slapbox @ianstormtaylor

    opened by mattkrick 42
  • Input is wonky on Android devices

    Input is wonky on Android devices

    I just did some testing on Chrome on a Samsung Galaxy J3, and the input was pretty wonky (iPhone was ok). It's hard to nail down exactly what's happening, but I see a couple things:

    • Often the first space press while at the end of a word doesn't add a space, instead selecting a default autocorrect
    • Sometimes words are duplicated or appear and disappear while typing

    This project looks great so far, and I'd love to use it, but I need Android support. I don't know anything about the nuances of input on contenteditable and/or Android, but if you have a rough direction/hypothesis for why this may be happening and don't have time to fix it now, I can dig in more and see what I can do.

    Thanks!

    bug ♥ help ⚑ cross platform ⚑ mobile 
    opened by iEchoic 42
  • Next

    Next

    This work is ongoing and not ready yet.


    Differences (incomplete)

    There are lots of changes in this branch. But here's an overview of the big differences from an architectural point of view.

    More coming, still in progress…

    JSON

    The data model is now comprised of simple JSON objects. Previously, it used Immutable.js data structures. This is a huge change, and one that unlocks many other things. Hopefully it will also increase the average performance when using Slate. It also makes it much easier to get started for newcomers. This will be a large change to migrate from, but it will be worth it.

    Interfaces & Namespaces

    The data model is interface-based. Previously each model was an instance of a class. Now, not only is the data plain objects, but Slate only expects that the objects implement an interface. So custom properties that used to live in node.data can now live at the top-level of the nodes. Helpers are exposed as a collection of helper functions on a namespace. For example, Node.get(root, path) or Range.isCollapsed(range). This ends up making code much clearer because you can always quickly see what interface you're working with.

    TypeScript

    The codebase now uses TypeScript. Working with pure JSON as a data model, and using an interface-based API are two things that have been made easier by migrating to TypeScript. You don't need to use it yourself, but if you do you'll get a lot more security when using the APIs. (And if you use VS Code you'll get nice autocompletion regardless!)

    Fewer Concepts

    The number of interfaces and commands has been reduced. Previously Selection, Annotation, Decoration used to all be separate classes. Now they are simply objects that implement the Range interface. Previously Block and Inline were separate, now they are objects that implement the Element interface. Previously there was a Document and Value, but now the top-level Editor contains the children nodes of the document itself.

    The number of commands has been reduced too. Previously we had commands for every type of input, like insertText, insertTextAtRange, insertTextAtPath. These have been merged into a smaller set of more customizable commands, eg. insertText which can take at: Path | Range | Point.

    Fewer Packages

    In attempt to decrease the maintenance burden, and because the new abstraction and APIs in Slate's core packages make things much easier, the total number of packages has been reduced. Things like slate-plain-serializer, slate-base64-serializer, etc. have been removed and can be implemented in userland easily if needed. Even the slate-html-deserializer can now be implemented in userland (in ~10 LOC leveraging slate-hyperscript). And internal packages like slate-dev-environment, slate-dev-test-utils, etc. are no longer exposed because they are implementation details.

    Plugins

    Plugins are now plain functions that augment the Editor object they receive and return it again. For example can augment the command execution by composing the editor.exec function. Or listen to operations by composing editor.apply. Previously they relied on a custom middleware stack, and they were just bags of handlers that got merged onto an editor. Now we're using plain old function composition (aka wrapping) instead.

    Elements

    Block-ness and inline-ness is now a runtime choice. Previously it was baked into the data model with the object: 'block' or object: 'inline' attributes. Now, it checks whether an "element" is inline or not at runtime. For example, you might check to see that element.type === 'link' and treat it as inline.

    beforeinput

    We now use the beforeinput event almost exclusively. Instead of having relying on a series of shims and the quirks of React synthetic events, we're now using the standardized beforeinput event as our baseline. It is fully supported in Safari and Chrome, will soon be supported in the new Chromium-based Edge, and is currently being worked on in Firefox. In the meantime there are a few patches to make Firefox work. Internet Explorer is no longer supported in core out of the box.

    More React-ish

    Rendering and event-handling is no longer a plugin's concern. Previously plugins had full control over the rendering logic, and event-handling logic in the editor. This creates a bad incentive to start putting all rendering logic in plugins, which puts Slate in a position of being a wrapper around all of React, which is very hard to do well. Instead, the new architecture has plugins focused purely on the rich-text aspects, and leaves the rendering and event handling aspects to React.

    Context

    Previously the <Editor> component was doing double duty as a sort of "controller" object and also the contenteditable DOM element. This led to a lot of awkwardness in how other components worked with Slate. In the new version, there is a new <Slate> context provider and a simpler <Editable> contenteditable-like component. By putting the <Slate> provider higher up in your component tree, you can share the editor directly with toolbars, buttons, etc. using the useSlate hook.

    Hooks

    In addition to the useSlate hook, there are a handful of other hooks. For example the useSelected and useFocused hooks help with knowing when to render selected states (often for void nodes). And since the use React's Content API they will automatically re-render when their state changes.


    Reductions

    One of the goals was to dramatically simplify a lot of the logic in Slate to make it easier to maintain and iterate on. This was done by refactoring to better base abstractions that can be built on, by leveraging modern DOM APIs, and by migrating to simpler React patterns.

    To give you a sense for the change in total lines of code:

    slate                       8,436  ->  4,038  (48%)
    slate-react                 3,905  ->    715  (18%)
    
    slate-base64-serializer        38  ->      0
    slate-dev-benchmark           340  ->      0
    slate-dev-environment         102  ->      0
    slate-dev-test-utils           44  ->      0
    slate-history                   0  ->    201
    slate-hotkeys                  62  ->      0
    slate-html-serializer         253  ->      0
    slate-hyperscript             447  ->    410
    slate-plain-serializer         56  ->      0
    slate-prop-types               62  ->      0
    slate-react-placeholder        62  ->      0
    slate-schema                    0  ->    504
    
    total                      13,807  ->  5,868  (43%)
    

    It's quite a big difference, although it's not done so the final sizes will likely grow a bit before it's ready. But that doesn't even include the dependencies that were shed in the process too.


    Fixes

    This is an estimate of which issues are fixed by this pull request. There are a lot of them, because it changes a lot of things. There might be a few incorrectly "fixed" ones here, so if one of them is your issue and you don't think it's fixed feel free to reopen a new issue.

    Fixes https://github.com/ianstormtaylor/slate/issues/3087 Fixes https://github.com/ianstormtaylor/slate/issues/3056 Fixes https://github.com/ianstormtaylor/slate/issues/3090 Fixes https://github.com/ianstormtaylor/slate/issues/3075 Fixes https://github.com/ianstormtaylor/slate/issues/2890 Fixes https://github.com/ianstormtaylor/slate/issues/2325 Fixes https://github.com/ianstormtaylor/slate/issues/3007 Fixes https://github.com/ianstormtaylor/slate/issues/3061 Fixes https://github.com/ianstormtaylor/slate/issues/560 Fixes https://github.com/ianstormtaylor/slate/issues/2869 Fixes https://github.com/ianstormtaylor/slate/issues/3028 Fixes https://github.com/ianstormtaylor/slate/issues/3027 Fixes https://github.com/ianstormtaylor/slate/issues/1762 Fixes https://github.com/ianstormtaylor/slate/issues/1022 Fixes https://github.com/ianstormtaylor/slate/issues/3020 Fixes https://github.com/ianstormtaylor/slate/issues/2746 Fixes https://github.com/ianstormtaylor/slate/issues/2991 Fixes https://github.com/ianstormtaylor/slate/issues/2333 Fixes https://github.com/ianstormtaylor/slate/issues/2711 Fixes https://github.com/ianstormtaylor/slate/issues/2413 Fixes https://github.com/ianstormtaylor/slate/issues/2345 Fixes https://github.com/ianstormtaylor/slate/issues/2812 Fixes https://github.com/ianstormtaylor/slate/issues/2859 Fixes https://github.com/ianstormtaylor/slate/issues/2862 Fixes https://github.com/ianstormtaylor/slate/issues/2863 Fixes https://github.com/ianstormtaylor/slate/issues/2495 Fixes https://github.com/ianstormtaylor/slate/issues/2864 Fixes https://github.com/ianstormtaylor/slate/issues/2860 Fixes https://github.com/ianstormtaylor/slate/issues/2878 Fixes https://github.com/ianstormtaylor/slate/issues/2858 Fixes https://github.com/ianstormtaylor/slate/issues/2861 Fixes https://github.com/ianstormtaylor/slate/issues/2985 Fixes https://github.com/ianstormtaylor/slate/issues/2987 Fixes https://github.com/ianstormtaylor/slate/issues/1883 Fixes https://github.com/ianstormtaylor/slate/issues/2002 Fixes https://github.com/ianstormtaylor/slate/issues/2022 Fixes https://github.com/ianstormtaylor/slate/issues/2989 Fixes https://github.com/ianstormtaylor/slate/issues/2990 Fixes https://github.com/ianstormtaylor/slate/issues/2876 Fixes https://github.com/ianstormtaylor/slate/issues/2983 Fixes https://github.com/ianstormtaylor/slate/issues/2945 Fixes https://github.com/ianstormtaylor/slate/issues/2986 Fixes https://github.com/ianstormtaylor/slate/issues/2939 Fixes https://github.com/ianstormtaylor/slate/issues/2968 Fixes https://github.com/ianstormtaylor/slate/issues/2895 Fixes https://github.com/ianstormtaylor/slate/issues/2900 Fixes https://github.com/ianstormtaylor/slate/issues/2873 Fixes https://github.com/ianstormtaylor/slate/issues/2567 Fixes https://github.com/ianstormtaylor/slate/issues/2868 Fixes https://github.com/ianstormtaylor/slate/issues/2867 Fixes https://github.com/ianstormtaylor/slate/issues/2668 Fixes https://github.com/ianstormtaylor/slate/issues/2029 Fixes https://github.com/ianstormtaylor/slate/issues/2759 Fixes https://github.com/ianstormtaylor/slate/issues/2701 Fixes https://github.com/ianstormtaylor/slate/issues/1884 Fixes https://github.com/ianstormtaylor/slate/issues/2503 Fixes https://github.com/ianstormtaylor/slate/issues/2620 Fixes https://github.com/ianstormtaylor/slate/issues/2420 Fixes https://github.com/ianstormtaylor/slate/issues/2708 Fixes https://github.com/ianstormtaylor/slate/issues/2538 Fixes https://github.com/ianstormtaylor/slate/issues/1247 Fixes https://github.com/ianstormtaylor/slate/issues/2466 Fixes https://github.com/ianstormtaylor/slate/issues/2111 Fixes https://github.com/ianstormtaylor/slate/issues/2297 Fixes https://github.com/ianstormtaylor/slate/issues/2321 Fixes https://github.com/ianstormtaylor/slate/issues/2329 Fixes https://github.com/ianstormtaylor/slate/issues/2060 Fixes https://github.com/ianstormtaylor/slate/issues/2274 Fixes https://github.com/ianstormtaylor/slate/issues/2108 Fixes https://github.com/ianstormtaylor/slate/issues/1466 Fixes https://github.com/ianstormtaylor/slate/issues/1759 Fixes https://github.com/ianstormtaylor/slate/issues/2043 Fixes https://github.com/ianstormtaylor/slate/issues/1128 Fixes https://github.com/ianstormtaylor/slate/issues/1464 Fixes https://github.com/ianstormtaylor/slate/issues/721 Fixes https://github.com/ianstormtaylor/slate/issues/674 Fixes https://github.com/ianstormtaylor/slate/issues/2977 Fixes https://github.com/ianstormtaylor/slate/issues/2336 Fixes https://github.com/ianstormtaylor/slate/issues/2361 Fixes https://github.com/ianstormtaylor/slate/issues/2093 Fixes https://github.com/ianstormtaylor/slate/issues/802 Fixes https://github.com/ianstormtaylor/slate/pull/2871 Fixes https://github.com/ianstormtaylor/slate/pull/3113 Fixes https://github.com/ianstormtaylor/slate/pull/2734 Fixes https://github.com/ianstormtaylor/slate/pull/3077 Fixes https://github.com/ianstormtaylor/slate/pull/3040 Fixes https://github.com/ianstormtaylor/slate/pull/2144 Fixes https://github.com/ianstormtaylor/slate/pull/3041 Fixes https://github.com/ianstormtaylor/slate/pull/2743 Fixes https://github.com/ianstormtaylor/slate/pull/2680 Fixes https://github.com/ianstormtaylor/slate/pull/2937 Fixes https://github.com/ianstormtaylor/slate/pull/2907 Fixes https://github.com/ianstormtaylor/slate/pull/2931 Fixes https://github.com/ianstormtaylor/slate/pull/2871 Fixes https://github.com/ianstormtaylor/slate/pull/2850 Fixes https://github.com/ianstormtaylor/slate/pull/2436 Fixes https://github.com/ianstormtaylor/slate/pull/1823 Fixes https://github.com/ianstormtaylor/slate/pull/2073 Fixes https://github.com/ianstormtaylor/slate/pull/1576 Fixes https://github.com/ianstormtaylor/slate/pull/1490 Fixes https://github.com/ianstormtaylor/slate/pull/1202 Fixes https://github.com/ianstormtaylor/slate/pull/1027


    Eliminates

    In addition to fixing a lot of things, the significant changes to the architecture mean that a lot of open issues are no longer relevant because they are talking about concepts that no longer exist.

    https://github.com/ianstormtaylor/slate/issues/3022 https://github.com/ianstormtaylor/slate/issues/3050 https://github.com/ianstormtaylor/slate/issues/3061 https://github.com/ianstormtaylor/slate/issues/3013 https://github.com/ianstormtaylor/slate/issues/3012 https://github.com/ianstormtaylor/slate/issues/3006 https://github.com/ianstormtaylor/slate/issues/2886 https://github.com/ianstormtaylor/slate/issues/2932 https://github.com/ianstormtaylor/slate/issues/2829 https://github.com/ianstormtaylor/slate/issues/2328 https://github.com/ianstormtaylor/slate/issues/2856 https://github.com/ianstormtaylor/slate/issues/2634 https://github.com/ianstormtaylor/slate/issues/2472 https://github.com/ianstormtaylor/slate/issues/2742 https://github.com/ianstormtaylor/slate/issues/2744 https://github.com/ianstormtaylor/slate/issues/2549 https://github.com/ianstormtaylor/slate/issues/2672 https://github.com/ianstormtaylor/slate/issues/2673 https://github.com/ianstormtaylor/slate/issues/2640 https://github.com/ianstormtaylor/slate/issues/2296 https://github.com/ianstormtaylor/slate/issues/826 https://github.com/ianstormtaylor/slate/issues/2330 https://github.com/ianstormtaylor/slate/issues/1915 https://github.com/ianstormtaylor/slate/issues/2331 https://github.com/ianstormtaylor/slate/issues/1749 https://github.com/ianstormtaylor/slate/issues/2289 https://github.com/ianstormtaylor/slate/issues/2082 https://github.com/ianstormtaylor/slate/issues/2078 https://github.com/ianstormtaylor/slate/issues/1843 https://github.com/ianstormtaylor/slate/issues/1703 https://github.com/ianstormtaylor/slate/issues/1794 https://github.com/ianstormtaylor/slate/issues/1821 https://github.com/ianstormtaylor/slate/issues/1652 https://github.com/ianstormtaylor/slate/issues/1464 https://github.com/ianstormtaylor/slate/issues/803 https://github.com/ianstormtaylor/slate/issues/1367 https://github.com/ianstormtaylor/slate/issues/651 https://github.com/ianstormtaylor/slate/issues/873 https://github.com/ianstormtaylor/slate/pull/1023 https://github.com/ianstormtaylor/slate/pull/784 https://github.com/ianstormtaylor/slate/pull/1756 https://github.com/ianstormtaylor/slate/pull/1626 https://github.com/ianstormtaylor/slate/pull/1827 https://github.com/ianstormtaylor/slate/pull/2280 https://github.com/ianstormtaylor/slate/pull/2410 https://github.com/ianstormtaylor/slate/pull/2448 https://github.com/ianstormtaylor/slate/pull/2660 https://github.com/ianstormtaylor/slate/pull/2602 https://github.com/ianstormtaylor/slate/pull/2505 https://github.com/ianstormtaylor/slate/pull/2674 https://github.com/ianstormtaylor/slate/pull/2928 https://github.com/ianstormtaylor/slate/pull/3003 https://github.com/ianstormtaylor/slate/pull/3092

    improvement debt ✶ breaking 
    opened by ianstormtaylor 38
  • Accept type-keyed react render functions

    Accept type-keyed react render functions

    Status

    The contents of this PR represent an in-progress implementation of a proposal. If it is decided to move forward in this direction, tests, a changeset, and additional documentation will be written and added to this PR.

    Description These changes allow type-specific render functions to passed into the Editable component directly. The renderElement and renderLeaf properties now accept either a single render function (the current standard) or an object mapping the type property of the relevant Descendant to its render function.

    Example

    This PR uses the new pattern for element renderers in the inlines example:

    custom-types.d.ts

    export type LinkElement = { type: 'link'; url: string; children: Descendant[] }
    export type ButtonElement = { type: 'button'; children: Descendant[] }
    
    declare module 'slate' {
        interface CustomTypes {
            Element: ButtonElement | LinkElement
        }
    }
    

    inlines.tsx

    const EditableButtonComponent: SlateReact.ElementRenderer<'button'> = ({
      attributes,
      children,
      element,
    }) => {
      // `element` is `ButtonElement`
      //...
    }
    
    // ...
    
    const LinkComponent: SlateReact.ElementRenderer<'link'> = ({
      attributes,
      children,
      element,
    }) => {
      // `element` is `LinkElement`
      //...
    }
    
    // ...
    
    <Editable
      renderElement={{
        button: EditableButtonComponent,
        link: LinkComponent,
      }}
      // ...
    />
    

    Context In my experience, defining and maintaining Text and Element render functions is tedious. As seen in several of the examples, these render functions are usually just a huge, awkward switch statements.

    These changes provide a more direct, less cumbersome method of passing typed render functions to Slate. Care has been taken to make these changes non-breaking.

    Checks

    • [x] The new code matches the existing patterns and styles.
    • [x] The tests pass with yarn test.
    • [x] The linter passes with yarn lint. (Fix errors with yarn fix.)
    • [x] The relevant examples still work. (Run examples with yarn start.)
    • [ ] You've added a changeset if changing functionality. (Add one with yarn changeset add.)
    opened by MichaelDarr 1
  • Bugs for example rich text

    Bugs for example rich text

    Problem There is a bug in the case code: error template: image

    example code : rich text https://github.com/ianstormtaylor/slate/blob/main/site/examples/richtext.tsx env:
    "@emotion/css": "^11.10.5", "is-hotkey": "^0.2.0", "slate": "^0.87.0", "slate-history": "^0.86.0", "slate-react": "^0.88.0"

    Solution A clear and concise description of what you want to happen.

    Alternatives A clear and concise description of any alternative solutions or features you've considered.

    Context I also use the MUI default plugin. When I copy the case code exactly, this is what happens. Maybe the code should be updated to accommodate the latest version?

    improvement 
    opened by Birddle 0
  • Bump yarn from 3.0.1 to 3.3.1

    Bump yarn from 3.0.1 to 3.3.1

    Description

    These changes upgrade yarn, fixing package installation (yarn install) for Node 17+ environments.

    Context

    Package installation has been broken in CI flows since #5237, which updated their environments to use Node 18. I've reproduced the installation error (ERR_STREAM_PREMATURE_CLOSE) on my local machine while running Node 18. The issue disappeared when I downgraded to Node 16. After upgrading yarn to 3.3.1, installation worked with either node version.

    This closed Yarn issue led me to this solution.

    Checks

    • [x] The new code matches the existing patterns and styles.
    • [x] The tests pass with yarn test.
    • [x] The linter passes with yarn lint. (Fix errors with yarn fix.)
    • [x] The relevant examples still work. (Run examples with yarn start.)
    • [x] You've added a changeset if changing functionality. (Add one with yarn changeset add.)
    opened by MichaelDarr 1
  • Allow customization of clipboard fragment format name

    Allow customization of clipboard fragment format name

    Problem When copying content from a react slate editor, the withReact plugin will populate the clipboard DataTransfer with three fomats; text/plain, text/html, and application/x-slate-fragment. Because slate is so flexible in schema, it is possible, albeit unlikely, that the application/x-slate-fragment on the clipboard when pasting is from a different slate editor, with a different schema. This could cause nodes to be inserted which are not correctly typed for the receiving editor, corrupting the document (depending how resiliently the rest of the recipient editor is to schema violations).

    Solution It would be great if the fragment format could be specified when instantiating the withReact plugin such that the clipboard will be populated with content with the specified format name, and only fragments with the specified format name will be inserted as-is into the document.

    Alternatives I currently work around this by implementing my own setFragmentData in a plug, then clone the application/x-slate-fragment to my own application/x-<customname>-slate-fragment, then on paste only paste the fragment contents from application/x-<customname>-slate-fragment.

    improvement 
    opened by rcbevans 0
  • Fail Gracefully In Selection

    Fail Gracefully In Selection

    Description Any time I run into an error like "Cannot resolve DOM point/node" pertaining to Slate logic, my app just crashes. Is it not possible to catch these errors (internally) somehow? To fail gracefully?

    As a workaround I'm wrapping vulnerable selection logic in try/catch blocks ... but it doesn't feel enough. When the editor itself crashes, only thing that catches this is my ErrorBoundary. But again, I don't want a crash in the first place. I'd like to know how others handle this for production apps.

    Expectation A quiet failure, possibly returning null values, warning, or something else.

    Environment Slate Version: all Operating System: all Browser: all TypeScript Version: all

    improvement 
    opened by Nikhil22 1
  • Increase number of max normalization iterations

    Increase number of max normalization iterations

    Problem

    The amount of max normalization iterations isn't enough for some large content in my app. It is set here as the number of dirty paths multiplied by 42. There's a comment next to the code that says //HACK: better way?. I'd love to learn more if bumping this multiplier is okay.

    Solution

    Either allow that 42 number to be configurable by the user, or increase that number. Locally, I've increased it to 100 and it fixes our use cases.

    Alternatives Open to suggestions!

    Context PR will be incoming.

    I am curious if there are risks or concerns associated with this increase. I've applied the fix locally on my app and it fixes the issues. Thank you for your input!

    improvement 
    opened by Jacfem 0
Owner
Ian Storm Taylor
Ian Storm Taylor
A React framework for building text editors.

Draft.js Draft.js is a JavaScript rich text editor framework, built for React and backed by an immutable model. Extensible and Customizable: We provid

Facebook 22.3k Jan 4, 2023
Export HTML to word (.docx) only by browser (currently in beta) 🎉

editor-to-docx Export rich-text editor content as word file (.docx) by browser ?? . English | 简体中文 Demo online demo Snapshot Architecture This project

han 15 Oct 21, 2022
A JS library for building WYSIWYG editors for HTML content.

For information on the ContentTools 2.x roadmap please view the: Roadmap repo ContentTools A JS library for building WYSIWYG editors for HTML content.

getme 3.9k Jan 8, 2023
A toolkit for building WYSIWYG editors with Mobiledoc

Mobiledoc Kit Mobiledoc Kit is a framework-agnostic library for building WYSIWYG editors supporting rich content via cards. Libraries This repository

Bustle 1.5k Jan 3, 2023
Simple rich text editor (contentEditable) for jQuery UI

Hallo - contentEditable for jQuery UI Hallo is a very simple in-place rich text editor for web pages. It uses jQuery UI and the HTML5 contentEditable

Henri Bergius 2.4k Dec 17, 2022
A modern, simple and elegant WYSIWYG rich text editor.

jQuery-Notebook A simple, clean and elegant WYSIWYG rich text editor for web aplications Note: Check out the fully functional demo and examples here.

Raphael Cruzeiro 1.7k Dec 12, 2022
Medium.com WYSIWYG editor clone. Uses contenteditable API to implement a rich text solution.

If you would be interested in helping to maintain one of the most successful WYSIWYG text editors on github, let us know! (See issue #1503) MediumEdit

yabwe 15.7k Jan 4, 2023
Tiny bootstrap-compatible WISWYG rich text editor

bootstrap-wysiwyg Important information for Github requests/issues Please do not submit issues/comments to this repo. Instead, submit it to https://gi

MindMup 5.6k Jan 3, 2023
HTML5 rich text editor. Try the demo integration at

Squire Squire is an HTML5 rich text editor, which provides powerful cross-browser normalisation in a flexible lightweight package (only 16.5KB of JS a

Neil Jenkins 4.4k Dec 28, 2022
The world's #1 JavaScript library for rich text editing. Available for React, Vue and Angular

TinyMCE TinyMCE is the world's most advanced open source core rich text editor. Trusted by millions of developers, and used by some of the world's lar

Tiny 12.4k Jan 4, 2023
A rich text editor for everyday writing

Trix A Rich Text Editor for Everyday Writing Compose beautifully formatted text in your web application. Trix is a WYSIWYG editor for writing messages

Basecamp 17.3k Jan 3, 2023
Open source rich text editor based on HTML5 and the progressive-enhancement approach. Uses a sophisticated security concept and aims to generate fully valid HTML5 markup by preventing unmaintainable tag soups and inline styles.

This project isn’t maintained anymore Please check out this fork. wysihtml5 0.3.0 wysihtml5 is an open source rich text editor based on HTML5 technolo

Christopher Blum 6.5k Jan 7, 2023
Popline is an HTML5 Rich-Text-Editor Toolbar

popline Popline is a non-intrusive WYSIWYG editor that shows up only after selecting a piece of text on the page, inspired by popclip. Usage Load jQue

kenshin 1k Nov 4, 2022
Open source rich text editor based on HTML5 and the progressive-enhancement approach. Uses a sophisticated security concept and aims to generate fully valid HTML5 markup by preventing unmaintainable tag soups and inline styles.

This project isn’t maintained anymore Please check out this fork. wysihtml5 0.3.0 wysihtml5 is an open source rich text editor based on HTML5 technolo

Christopher Blum 6.5k Dec 30, 2022
A powerful WYSIWYG rich text web editor by pure javascript

KothingEditor A powerful WYSIWYG rich text web editor by pure javascript Demo : kothing.github.io/editor The KothingEditor is a lightweight, flexible,

Kothing 34 Dec 25, 2022
Override the rich text editor in Strapi admin with ToastUI Editor.

strapi-plugin-wysiwyg-tui-editor ⚠️ This is a strapi v4 plugin which does not support any earlier version! A Strapi plugin to replace the default rich

Zhuo Chen 12 Dec 23, 2022
🧩 Create code snippets anywhere (beta). Built with Nextjs & Supabase.

?? Codetypes (beta) Codetypes is a web application to create code snippets in the cloud, without installation and totally free. You can run Codetypes

Pablo Hdez 3 Feb 25, 2022
An Open, Extensible Framework for building Web3D Engine, Editor

Meta3D (Meta3D is under development, not product ready) Meta3D is an Open, Extensible Framework for building Web3D Engine, Editor. read Meta3D介绍 for m

Wonder Technology 54 Dec 29, 2022
Verbum is a fully flexible text editor based on lexical framework.

Verbum Verbum - Flexible Text Editor for React Verbum is a fully flexible rich text editor based on lexical-playground and lexical framework. ⚠️ As th

Ozan Yurtsever 560 Dec 29, 2022