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, 2019NEW
Introducing the
Annotation
model. This is very similar to what used to be stored invalue.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 includesadd_annotation
,remove_annotation
andset_annotation
. They are similar to the existing*_mark
operations.Introducing "iterable" model methods. This introduces several iteratable-producing methods on the
Element
interface, whichDocument
,Block
andInline
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 easilybreak
orreturn
out of the loops directlyβa much nicer DX than remembering toreturn false
.BREAKING
The
value.decorations
property is nowvalue.annotations
. Following with the split of decorations into annotations, this property was also renamed. They must now contain uniquekey
properties, as they are stored as aMap
instead of aList
. This allows for much more performant updates.The
Decoration
model no longer has a nestedmark
property. Previously a realMark
object was used as a property on decorations, but now thetype
anddata
properties are first class properties instead.{ object: 'decoration', type: String, data: Map, anchor: Point, focus: Point, }
-
v0.46.0 Changes
May 01, 2019BREAKING
Mark operations no longer have
offset
orlength
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 asplit_node
operation as well as anadd_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 aninsert_text
operation as well as anadd_mark
operation.Using
Text.create
orText.createList
with aleaves
property will error. Now that text nodes no longer have leaves, you will need to pass in thetext
string andmarks
directly when creating a new text node. (However, you can still create entire values usingValue.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. AlthoughValue.fromJSON
andValue.create
allow the old format in deprecated mode, callingValue.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.*
andNode.*
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 aleaves
property is deprecated. In this new version of Slate, creating a new value withValue.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-levelText.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, 2019BREAKING
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. Thevalue
,selection
,node
, andmark
propertiesβwhich contained references to Immutable.js objectsβhave all been removed. In their place, we have standardized aproperties
andnewProperties
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, 2018NEW
Introducing the
child_min_invalid
andchild_max_invalid
schema errors. These new schema errors map directly to themix
andmax
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 aregetRootBlocksAtRange
andgetRootInlinesAtRange
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
andmax
rules have changed. Previously they would result in errors ofchild_required
,child_object_invalid
,child_type_invalid
andchild_unknown
. Now that we have the newchild_min_invalid
andchild_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
andgetInlinesAtRange
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 togetLeafBlocksAtRange
andgetLeafInlinesAtRange
to clarify that they retrieve the bottom-most nodes. And now there are two additional methods calledgetRootBlocksAtRange
andgetRootInlinesAtRange
for cases where you want the top-most nodes instead.
-
v0.43.0 Changes
October 27, 2018NEW
The
editor.command
andeditor.query
methods can take functions. Previously they only accepted atype
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. TheChange
object as we know it previously has been removed, and all of its behaviors have been folded into theEditor
controller. This includes the top-level commands and queries methods, as well as methods likeapplyOperation
andnormalize
. All places that used to receivechange
now receiveeditor
, 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 likesetState
.The
normalize*
andvalidate*
middleware signatures have changed! Previously thenormalize*
andvalidate*
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 receiveeditor
andnext
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 useeditor.run
directly to simulate events:editor.run('onKeyDown', { key: 'Tab', ... })
π ###### DEPRECATED
π The
editor.change
method is deprecated. With the removal of theChange
object, there's no need anymore to create the small closures witheditor.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 usingapplyOperation
. This is to reduce the number of methods exposed on theEditor
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 callingeditor.command(fn)
instead.
-
v0.42.0 Changes
October 09, 2018NEW
Introducing the
Editor
controller. Previously there was a vagueeditor
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 JavaScriptEditor
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
orinsertText
. 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 anext
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
andqueries
concept are all implemented as plugins that attach varying middleware as well. For example, commands are processed using theonCommand
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 newEditor
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 newChange
by callingvalue.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 anEditor
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 theeditor
to be able to access itseditor.change
method in your React components.β Remove the
Stack
"model", in favor of the newEditor
. Previously there was a pseudo-model called theStack
that was very low level, and not really a model. This concept has now been rolled into the newEditor
controller, which can be used in any environment because it's just plain JavaScript. There was almost no need to directly use aStack
instance previously, so this change shouldn't affect almost anyone.β Remove the
Schema
"model", in favor of the newEditor
. Previously there was another pseudo-model called theSchema
, that was used to contain validation logic. All of the same validation features are still available, but the oldSchema
model is now rolled into theEditor
controller as well, in the form of an internalSchemaPlugin
that isn't exposed.β Remove the
schema.isVoid
andschema.isAtomic
in favor of queries. Previously these two methods were used to query the schema about the behavior of a specificnode
ordecoration
. Now these same queries as possible using the "queries" concept, and are available directly on thechange
object:if (change.isVoid(node)) { ... }
The middleware stack must now be explicitly continued, using
next
. Previously returningundefined
from a middleware would (usually) continue the stack onto the next middleware. Now, with middleware taking anext
function argument you must explicitly decide to continue the stack by callnext()
yourself.β Remove the
History
model, in favor of commands. Previously there was aHistory
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 invalue.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 theEditor
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 theChange
class no longer makes sense. Instead, you can use theeditor.change()
API to receive a new change object with the commands and queries specific to your editor's plugins.The
getClosestVoid
,getDecorations
andhasVoidParent
method now take aneditor
. Previously theseNode
methods took aschema
argument, but this has been replaced with the neweditor
controller instead now that theSchema
model has been removed.