This change adds support for complex attributes, which might have
arbitrarily nested properties:
```json
{
"insert": "some text",
"attributes": {
"complexAttribute": {
"prop1": {
"subPropA": "a",
"subPropB": 2
},
"prop2": true
}
}
}
```
## Motivation
The particular use case with which this was written in mind is the
addition of comments to Quill. That is, the ability to select part of a
document, and add some extra information about that selection.
Comments can be applied to arbitrary content, so cannot be represented
as an embed (for example). They naturally belong to the attributes:
```json
{
"insert": "This text is commented",
"attributes": {
"comment": "comment-id"
}
}
```
The issue with comments is that multiple comments may exist on the same
range:
```json
{
"insert": "this text is commented",
"attributes": {
"comment": {
"comment-id-1": true,
"comment-id-2": true
}
}
}
```
These cannot be "flattened", because the IDs are arbitrary strings,
which:
- could collide with other attributes who rely on IDs like this
- does not map neatly to a Quill attributor / blot
Any other metadata-like attributes may also benefit from this sort of
deeply nested structure. For example, a naive git-blame-like feature
might look like:
```json
{
"insert": "\n",
"attributes": {
"blame": {
"hash1": {
"author": "Alec Gibson",
"timestamp": 1582123032,
}
}
}
}
```
## Implementation
This change adds support to all of `compose`, `diff`, `invert` and
`transform` for complex attributes.
The implementation is recursive, and each depth of attributes is
treated similarly to the top level. In particular:
- `null` will remove a property
- an empty object is treated as `null`
```javascript
const attributes = {complex: {foo: 123}}
const update = {complex: {foo: null}}
AttributeMap.compose(attributes, update) // => undefined
```
### Arrays
Note that arrays are out-of-scope of this change. Their behaviour is not
well defined when composing deltas together, and they are treated as if
they are primitive values (ie they directly overwrite one another,
rather than attempt a deep change).
## Quill integration
Quill will not natively support these changes, and further work will be
required if this capability is to be adopted there. Given that Quill v2
is currently in development, this may be a good time to do this work, if
desirable.
In particular:
- `format()` methods need to `compose` values rather than simply
overwrite
- `merge()` methods need to perform deep equality checks, rather than
object reference checks
Note that Quill can currently support these complex attributes, but only
with blots that override the above methods.
## Backwards compatibility
This change is not technically backwards-compatible. If anyone is
already using deeply nested attributes and relying on the current
all-or-nothing behaviour, then this change will break their code.