All Versions
139
Latest Version
Avg Release Cycle
8 days
Latest Release
966 days ago

Changelog History
Page 5

  • v0.47.6

    August 19, 2019
  • v0.47.5

    August 19, 2019
  • v0.47.4

    June 07, 2019
  • v0.47.3

    May 21, 2019
  • v0.47.0 Changes

    May 08, 2019
    NEW

    Introducing the Annotation model. This is very similar to what used to be stored in value.decorations, except they also contain a unique "key" to be identified by. They can be used for things like comments, suggestions, collaborative cursors, etc.

    {
      object: 'annotation',
      key: String,
      type: String,
      data: Map,
      anchor: Point,
      focus: Point,
    }
    

    There are three new *_annotation operations. The set of operations now includes add_annotation, remove_annotation and set_annotation. They are similar to the existing *_mark operations.

    Introducing "iterable" model methods. This introduces several iteratable-producing methods on the Element interface, which Document, Block and Inline all implement. There are iterables for traversing the entire tree:

    element.blocks(options)
    element.descendants(options)
    element.inlines(options)
    element.texts(options)
    
    element.ancestors(path, options)
    element.siblings(path, options)
    

    You can use them just like the native JavaScript iterables. For example, you can loop through the text nodes after a specific node:

    for (const next of document.texts({ path: start.path })) {
      const [node, path] = next
      // do something with the text node or its path
    }
    

    Or you can traverse all of the "leaf" blocks:

    for (const [block] of document.blocks({ onlyLeaves: true })) {
      // ...
    }
    

    And because these iterations use native for/of loops, you can easily break or return out of the loops directlyβ€”a much nicer DX than remembering to return false.

    BREAKING

    The value.decorations property is now value.annotations. Following with the split of decorations into annotations, this property was also renamed. They must now contain unique key properties, as they are stored as a Map instead of a List. This allows for much more performant updates.

    The Decoration model no longer has a nested mark property. Previously a real Mark object was used as a property on decorations, but now the type and data properties are first class properties instead.

    {
      object: 'decoration',
      type: String,
      data: Map,
      anchor: Point,
      focus: Point,
    }
    

  • v0.46.0 Changes

    May 01, 2019
    BREAKING

    Mark operations no longer have offset or length properties. Since text nodes now contain a unique set of marks, it wouldn't make sense for a single mark-related operation to result in a splitting of nodes. Instead, when a mark is added to only part of a text node, it will result in a split_node operation as well as an add_mark operation.

    Text operations no longer have a marks property. Previously it was used to add text with a specific set of marks. However this is no longer necessary, and when text is added with marks it will result in an insert_text operation as well as an add_mark operation.

    Using Text.create or Text.createList with a leaves property will error. Now that text nodes no longer have leaves, you will need to pass in the text string and marks directly when creating a new text node. (However, you can still create entire values using Value.create in a backwards compatible way for convenience while migrating.)

    // This works, although deprecated, which is the common case...
    Value.create(oldValueJson)
    
    // ...but this will error!
    Text.create(oldTextJson)
    

    Value.toJSON returns the new data model format, without leaves. Although Value.fromJSON and Value.create allow the old format in deprecated mode, calling Value.toJSON will return the new data format. If you still need the old one you'll need to iterate the document tree converting text nodes yourself.

    The low-level Value.* and Node.* mutation methods have changed. These changes follow the operation signature changes, since the methods take the same arguments as the operations themselves. For example:

    // Previously...
    value.addMark(path, offset, length, mark)
    
    // ...is now:
    value.addMark(path, mark)
    

    These are low-level methods, so this change shouldn't affect the majority of use cases.

    πŸ—„ ###### DEPRECATED

    πŸŽ‰ Initializing editors with Text nodes with a leaves property is deprecated. In this new version of Slate, creating a new value with Value.create with the old leaf data model is still allowed for convenience in migration, but it will be removed in a coming version. (However, using the low-level Text.create will throw an error!)

    // This works, although deprecated, which is the common case...
    Value.create(oldValueJson)
    
    // ...but this will error!
    Text.create(oldTextJson)
    

  • v0.45.0 Changes

    April 02, 2019
    BREAKING

    A few properties of Operation objects have changed. In an effort to standardize and streamline operations, their properties have changed. This won't affect 90% of use cases, since operations are usually low-level concerns. However, if you are using operational transform or some other low-level parts of Slate, this may affect you. The value, selection, node, and mark propertiesβ€”which contained references to Immutable.js objectsβ€”have all been removed. In their place, we have standardized a properties and newProperties pair. This will greatly reduce the size of operations stored in memory, and makes dealing with them easier when serialized as well.


  • v0.44.0 Changes

    November 08, 2018
    NEW

    Introducing the child_min_invalid and child_max_invalid schema errors. These new schema errors map directly to the mix and max schema rule definitions, and make it easier to determine exactly what your normalization logic needs to do to fix the document.

    βž• Added new node retrieval methods. There are three new methdos for node retrieval. The first is getNodesAtRange which will retrieve all of the nodes in the tree in a given range. And the second two are getRootBlocksAtRange and getRootInlinesAtRange for retrieving the top-most blocks or inlines in a given range. These should be helpful in defining your own command logic.

    BREAKING

    Schema errors for min and max rules have changed. Previously they would result in errors of child_required, child_object_invalid, child_type_invalid and child_unknown. Now that we have the new child_min_invalid and child_max_invalid errors, these schema rules will return them instead, making it much easier to determine exactly which rule is causing a schema error.

    πŸ—„ ###### DEPRECATED

    The getBlocksAtRange and getInlinesAtRange methods have been renamed. To clear up confusion about which blocks and inlines are retrieve in the case of nesting, these two methods have been renamed to getLeafBlocksAtRange and getLeafInlinesAtRange to clarify that they retrieve the bottom-most nodes. And now there are two additional methods called getRootBlocksAtRange and getRootInlinesAtRange for cases where you want the top-most nodes instead.


  • v0.43.0 Changes

    October 27, 2018
    NEW

    The editor.command and editor.query methods can take functions. Previously they only accepted a type string and would look up the command or query by type. Now, they also accept a custom function. This is helpful for plugin authors, who want to accept a "command option", since it gives users more flexibility to write one-off commands or queries. For example a plugin could be passed either:

    Hotkey({
      hotkey: 'cmd+b',
      command: 'addBoldMark',
    })
    

    Or a custom command function:

    Hotkey({
      hotkey: 'cmd+b',
      command: editor => editor.addBoldMark().moveToEnd(),
    })
    
    BREAKING

    🚚 The Change object has been removed. The Change object as we know it previously has been removed, and all of its behaviors have been folded into the Editor controller. This includes the top-level commands and queries methods, as well as methods like applyOperation and normalize. All places that used to receive change now receive editor, which is API equivalent.

    πŸ”„ Changes are now flushed to onChange asynchronously. Previously this was done synchronously, which resulted in some strange race conditions in React environments. Now they will always be flushed asynchronously, just like setState.

    The normalize* and validate* middleware signatures have changed! Previously the normalize* and validate* middleware was passed (node, next). However now, for consistency with the other middleware they are all passed (node, editor, next). This way, all middleware always receive editor and next as their final two arguments.

    🚚 The editor.event method has been removed. Previously this is what you'd use when writing tests to simulate events being firedβ€”which were slightly different to other running other middleware. With the simplification to the editor and to the newly-consistent middleware signatures, you can now use editor.run directly to simulate events:

    editor.run('onKeyDown', { key: 'Tab', ... })
    

    πŸ—„ ###### DEPRECATED

    πŸ—„ The editor.change method is deprecated. With the removal of the Change object, there's no need anymore to create the small closures with editor.change(). Instead you can directly invoke commands on the editor in series, and all of the changes will be emitted asynchronously on the next tick.

    editor
      .insertText('word')
      .moveFocusForward(10)
      .addMark('bold')
    

    πŸ—„ The applyOperations method is deprecated. Instead you can loop a set of operations and apply each one using applyOperation. This is to reduce the number of methods exposed on the Editor to keep it simpler.

    πŸ—„ The change.call method is deprecated. Previously this was used to call a one-off function as a change method. Now this behavior is equivalent to calling editor.command(fn) instead.


  • v0.42.0 Changes

    October 09, 2018
    NEW

    Introducing the Editor controller. Previously there was a vague editor concept, that was the React component itself. This was helpful, but because it was tightly coupled to React and the browser, it didn't lend itself to non-browser use cases well. This meant that the line between "model" and "controller/view" was blurred, and some concepts lived in both places at once, in inconsistent ways.

    A new Editor controller now makes this relationship clear. It borrows many of its behaviors from the React <Editor> component. And the component actually just instantiates its own plain JavaScript Editor under the covers to delegate the work to.

    βœ… This new concept powers a lot of the thinking in this new version, unlocking a lot of changes that bring a clearer separation of responsibilities to Slate. It allows us to create editors in any environment, which makes server-side use cases easier, brings parity to testing, and even opens us up to supporting other view layers like React Native or Vue.js in the future.

    It has a familiar API, based on the existing editor concept:

    const editor = new Editor({ plugins, value, onChange })
    
    editor.change(change => {
      ...
    })
    

    βœ… However it also introduces imperative methods to make testing easier:

    editor.run('renderNode', props)
    
    editor.event('onKeyDown', event)
    
    editor.command('addMark', 'bold')
    
    editor.query('isVoid', node)
    

    I'm very excited about it, so I hope you like it!

    Introducing the "commands" concept. Previously, "change methods" were treated in a first-class way, but plugins had no easy way to add their own change methods that were reusable elsewhere. And they had no way to override the built-in logic for certain commands, for example splitBlock or insertText. However, now this is all customizable by plugins, with the core Slate plugin providing all of the previous default commands.

    const plugin = {
      commands: {
        wrapQuote(change) {
          change.wrapBlock('quote')
        },
      },
    }
    

    Those commands are then available directly on the change objects, which are now editor-specific:

    change.wrapQuote()
    

    βœ… This allows you to define all of your commands in a single, easily-testable place. And then "behavioral" plugins can simply take command names as options, so that you have full control over the logic they trigger.

    Introducing the "queries" concept. Similarly to the commands, queries allow plugins to define specific behaviors that the editor can be queried for in a reusable way, to be used when rendering buttons, or deciding on command behaviors, etc.

    For example, you might define an getActiveList query:

    const plugin = {
      queries: {
        getActiveList(editor) {},
      },
    }
    

    πŸ”Œ And then be able to re-use that logic easily in different places in your codebase, or pass in the query name to a plugin that can use your custom logic itself:

    const list = change.getActiveList()
    
    if (list) {
      ...
    } else {
      ...
    }
    

    πŸ”Œ Taken together, commands and queries offer a better way for plugins to manage their inter-dependencies. They can take in command or query names as options to change their behaviors, or they can export new commands and queries that you can reuse in your codebase.

    The middleware stack is now deferrable. With the introduction of the Editor controller, the middleware stack in Slate has also been upgraded. Each middleware now receives a next function (similar to Express or Koa) that allows you to choose whether to iterating the stack or not.

    // Previously, you'd return `undefined` to continue.
    function onKeyDown(event, editor, next) {
      if (event.key !== 'Enter') return
      ...
    }
    
    // Now, you call `next()` to continue...
    function onKeyDown(event, editor, next) {
      if (event.key !== 'Enter') return next()
      ...
    }
    

    πŸ‘€ While that may seem inconvenient, it opens up an entire new behavior, which is deferring to the plugins later in the stack to see if they "handle" a specific case, and if not, handling it yourself:

    function onKeyDown(event, editor, next) {
      if (event.key === 'Enter') {
        const handled = next()
        if (handled) return handled
    
        // Otherwise, handle `Enter` yourself...
      }
    }
    

    πŸ”Œ This is how all of the core logic in slate-react is now implemented, eliminating the need for a "before" and an "after" plugin that duplicate logic.

    πŸ”Œ Under the covers, the schema, commands and queries concept are all implemented as plugins that attach varying middleware as well. For example, commands are processed using the onCommand middleware under the covers:

    const plugin = {
      onCommand(command, editor, next) {
        ...
      }
    }
    

    This allows you to actually listen in to all commands, and override individual behaviors if you choose to do so, without having to override the command itself. This is a very advanced feature, which most people won't need, but it shows the flexibility provided by migrating all of the previously custom internal logic to be based on the new middleware stack.

    πŸ”Œ Plugins can now be defined in nested arrays. This is a small addition, but it means that you no longer need to differentiate between individual plugins and multiple plugins in an array. This allows plugins to be more easily composed up from multiple other plugins themselves, without the end user having to change how they use them. Small, but encourages reuse just a little bit more.

    πŸ—„ ###### DEPRECATED

    πŸ—„ The slate-simulator is deprecated. Previously this was used as a pseudo-controller for testing purposes. However, now with the new Editor controller as a first-class concept, everything the simulator could do can now be done directly in the library. This should make testing in non-browser environments much easier to do.

    BREAKING

    The Value object is no longer tied to changes. Previously, you could create a new Change by calling value.change() and retrieve a new value. With the re-architecture to properly decouple the schema, commands, queries and plugins from the core Slate data models, this is no longer possible. Instead, changes are always created via an Editor instance, where those concepts live.

    // Instead of...
    const { value } = this.state
    const change = value.change()
    ...
    this.onChange(change)
    
    // You now would do...
    this.editor.change(change => {
      const { value } = change
      ...
    })
    

    Sometimes this means you will need to store the React ref of the editor to be able to access its editor.change method in your React components.

    βœ‚ Remove the Stack "model", in favor of the new Editor. Previously there was a pseudo-model called the Stack that was very low level, and not really a model. This concept has now been rolled into the new Editor controller, which can be used in any environment because it's just plain JavaScript. There was almost no need to directly use a Stack instance previously, so this change shouldn't affect almost anyone.

    βœ‚ Remove the Schema "model", in favor of the new Editor. Previously there was another pseudo-model called the Schema, that was used to contain validation logic. All of the same validation features are still available, but the old Schema model is now rolled into the Editor controller as well, in the form of an internal SchemaPlugin that isn't exposed.

    βœ‚ Remove the schema.isVoid and schema.isAtomic in favor of queries. Previously these two methods were used to query the schema about the behavior of a specific node or decoration. Now these same queries as possible using the "queries" concept, and are available directly on the change object:

    if (change.isVoid(node)) {
      ...
    }
    

    The middleware stack must now be explicitly continued, using next. Previously returning undefined from a middleware would (usually) continue the stack onto the next middleware. Now, with middleware taking a next function argument you must explicitly decide to continue the stack by call next() yourself.

    βœ‚ Remove the History model, in favor of commands. Previously there was a History model that stored the undo/redo stacks, and managing saving new operations to those stacks. All of this logic has been folded into the new "commands" concept, and the undo/redo stacks now live in value.data. This has the benefit of allowing the history behavior to be completely overridable by userland plugins, which was not an easy feat to manage before.

    Values can no longer be normalized on creation. With the decoupling of the data model and the plugin layer, the schema rules are no longer available inside the Value model. This means that you can no longer receive a "normalized" value without having access to the Editor and its plugins.

    // While previously you could attach a `schema` to a value...
    const normalized = Value.create({ ..., schema })
    
    // Now you'd need to do that with the `editor`...
    const value = Value.create({ ... })
    const editor = new Editor({ value, plugins: [{ schema }] })
    const normalized = editor.value
    

    πŸ‘€ While this seems inconvenient, it makes the boundaries in the API much more clear, and keeps the immutable and mutable concepts separated. This specific code sample gets longer, but the complexities elsewhere in the library are removed.

    The Change class is no longer exported. Changes are now editor-specific, so exporting the Change class no longer makes sense. Instead, you can use the editor.change() API to receive a new change object with the commands and queries specific to your editor's plugins.

    The getClosestVoid, getDecorations and hasVoidParent method now take an editor. Previously these Node methods took a schema argument, but this has been replaced with the new editor controller instead now that the Schema model has been removed.