diff --git a/Sources/API/Converter.swift b/Sources/API/Converter.swift index 71b2414f..8c83f277 100644 --- a/Sources/API/Converter.swift +++ b/Sources/API/Converter.swift @@ -427,7 +427,9 @@ extension Converter { pbTreeStyleOperation.parentCreatedAt = toTimeTicket(treeStyleOperation.parentCreatedAt) pbTreeStyleOperation.from = toTreePos(treeStyleOperation.fromPos) pbTreeStyleOperation.to = toTreePos(treeStyleOperation.toPos) - + treeStyleOperation.maxCreatedAtMapByActor.forEach { key, value in + pbTreeStyleOperation.createdAtMapByActor[key] = toTimeTicket(value) + } treeStyleOperation.attributes.forEach { key, value in pbTreeStyleOperation.attributes[key] = value } @@ -511,7 +513,8 @@ extension Converter { return TreeStyleOperation(parentCreatedAt: fromTimeTicket(pbTreeStyleOperation.parentCreatedAt), fromPos: fromTreePos(pbTreeStyleOperation.from), toPos: fromTreePos(pbTreeStyleOperation.to), - attributes: pbTreeStyleOperation.attributes, + maxCreatedAtMapByActor: pbTreeStyleOperation.createdAtMapByActor.compactMapValues({ fromTimeTicket($0) }), + attributes: pbTreeStyleOperation.attributes, attributesToRemove: pbTreeStyleOperation.attributesToRemove, executedAt: fromTimeTicket(pbTreeStyleOperation.executedAt)) } else { diff --git a/Sources/API/V1/yorkie/v1/resources.pb.swift b/Sources/API/V1/yorkie/v1/resources.pb.swift index 14cfac82..1c45f6e6 100644 --- a/Sources/API/V1/yorkie/v1/resources.pb.swift +++ b/Sources/API/V1/yorkie/v1/resources.pb.swift @@ -957,6 +957,11 @@ struct Yorkie_V1_Operation { set {_uniqueStorage()._attributesToRemove = newValue} } + var createdAtMapByActor: Dictionary { + get {return _storage._createdAtMapByActor} + set {_uniqueStorage()._createdAtMapByActor = newValue} + } + var unknownFields = SwiftProtobuf.UnknownStorage() init() {} @@ -3344,6 +3349,7 @@ extension Yorkie_V1_Operation.TreeStyle: SwiftProtobuf.Message, SwiftProtobuf._M 4: .same(proto: "attributes"), 5: .standard(proto: "executed_at"), 6: .standard(proto: "attributes_to_remove"), + 7: .standard(proto: "created_at_map_by_actor"), ] fileprivate class _StorageClass { @@ -3353,6 +3359,7 @@ extension Yorkie_V1_Operation.TreeStyle: SwiftProtobuf.Message, SwiftProtobuf._M var _attributes: Dictionary = [:] var _executedAt: Yorkie_V1_TimeTicket? = nil var _attributesToRemove: [String] = [] + var _createdAtMapByActor: Dictionary = [:] #if swift(>=5.10) // This property is used as the initial default value for new instances of the type. @@ -3373,6 +3380,7 @@ extension Yorkie_V1_Operation.TreeStyle: SwiftProtobuf.Message, SwiftProtobuf._M _attributes = source._attributes _executedAt = source._executedAt _attributesToRemove = source._attributesToRemove + _createdAtMapByActor = source._createdAtMapByActor } } @@ -3397,6 +3405,7 @@ extension Yorkie_V1_Operation.TreeStyle: SwiftProtobuf.Message, SwiftProtobuf._M case 4: try { try decoder.decodeMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: &_storage._attributes) }() case 5: try { try decoder.decodeSingularMessageField(value: &_storage._executedAt) }() case 6: try { try decoder.decodeRepeatedStringField(value: &_storage._attributesToRemove) }() + case 7: try { try decoder.decodeMapField(fieldType: SwiftProtobuf._ProtobufMessageMap.self, value: &_storage._createdAtMapByActor) }() default: break } } @@ -3427,6 +3436,9 @@ extension Yorkie_V1_Operation.TreeStyle: SwiftProtobuf.Message, SwiftProtobuf._M if !_storage._attributesToRemove.isEmpty { try visitor.visitRepeatedStringField(value: _storage._attributesToRemove, fieldNumber: 6) } + if !_storage._createdAtMapByActor.isEmpty { + try visitor.visitMapField(fieldType: SwiftProtobuf._ProtobufMessageMap.self, value: _storage._createdAtMapByActor, fieldNumber: 7) + } } try unknownFields.traverse(visitor: &visitor) } @@ -3442,6 +3454,7 @@ extension Yorkie_V1_Operation.TreeStyle: SwiftProtobuf.Message, SwiftProtobuf._M if _storage._attributes != rhs_storage._attributes {return false} if _storage._executedAt != rhs_storage._executedAt {return false} if _storage._attributesToRemove != rhs_storage._attributesToRemove {return false} + if _storage._createdAtMapByActor != rhs_storage._createdAtMapByActor {return false} return true } if !storagesAreEqual {return false} diff --git a/Sources/API/V1/yorkie/v1/resources.proto b/Sources/API/V1/yorkie/v1/resources.proto index 1b186a94..a93eb7f4 100644 --- a/Sources/API/V1/yorkie/v1/resources.proto +++ b/Sources/API/V1/yorkie/v1/resources.proto @@ -134,6 +134,7 @@ message Operation { map attributes = 4; TimeTicket executed_at = 5; repeated string attributes_to_remove = 6; + map created_at_map_by_actor = 7; } oneof body { diff --git a/Sources/Document/CRDT/CRDTText.swift b/Sources/Document/CRDT/CRDTText.swift index 849295c2..6c300f39 100644 --- a/Sources/Document/CRDT/CRDTText.swift +++ b/Sources/Document/CRDT/CRDTText.swift @@ -163,7 +163,7 @@ final class CRDTText: CRDTGCElement { _ content: String, _ editedAt: TimeTicket, _ attributes: [String: String]? = nil, - _ latestCreatedAtMapByActor: [String: TimeTicket]? = nil) throws -> ([String: TimeTicket], [TextChange], RGATreeSplitPosRange) + _ maxCreatedAtMapByActor: [String: TimeTicket]? = nil) throws -> ([String: TimeTicket], [TextChange], RGATreeSplitPosRange) { let value = !content.isEmpty ? TextValue(content) : nil if !content.isEmpty, let attributes { @@ -172,11 +172,11 @@ final class CRDTText: CRDTGCElement { } } - let (caretPos, latestCreatedAtMap, contentChanges) = try self.rgaTreeSplit.edit( + let (caretPos, maxCreatedAtMap, contentChanges) = try self.rgaTreeSplit.edit( range, editedAt, value, - latestCreatedAtMapByActor + maxCreatedAtMapByActor ) let changes = contentChanges.compactMap { TextChange(type: .content, actor: $0.actor, from: $0.from, to: $0.to, content: $0.content?.toString) } @@ -187,7 +187,7 @@ final class CRDTText: CRDTGCElement { } } - return (latestCreatedAtMap, changes, (caretPos, caretPos)) + return (maxCreatedAtMap, changes, (caretPos, caretPos)) } /** @@ -203,7 +203,7 @@ final class CRDTText: CRDTGCElement { func setStyle(_ range: RGATreeSplitPosRange, _ attributes: [String: String], _ editedAt: TimeTicket, - _ latestCreatedAtMapByActor: [String: TimeTicket] = [:]) throws -> ([String: TimeTicket], [TextChange]) + _ maxCreatedAtMapByActor: [String: TimeTicket] = [:]) throws -> ([String: TimeTicket], [TextChange]) { // 01. split nodes with from and to let toRight = try self.rgaTreeSplit.findNodeWithSplit(range.1, editedAt).1 @@ -217,19 +217,19 @@ final class CRDTText: CRDTGCElement { for node in nodes { let actorID = node.createdAt.actorID - let latestCreatedAt: TimeTicket + let maxCreatedAt: TimeTicket - if latestCreatedAtMapByActor.isEmpty { - latestCreatedAt = TimeTicket.max + if maxCreatedAtMapByActor.isEmpty { + maxCreatedAt = TimeTicket.max } else { - latestCreatedAt = latestCreatedAtMapByActor[actorID] ?? TimeTicket.initial + maxCreatedAt = maxCreatedAtMapByActor[actorID] ?? TimeTicket.initial } - if node.canStyle(editedAt, latestCreatedAt) { - let latestCreatedAt = createdAtMapByActor[actorID] + if node.canStyle(editedAt, maxCreatedAt) { + let maxCreatedAt = createdAtMapByActor[actorID] let createdAt = node.createdAt - if latestCreatedAt == nil || createdAt.after(latestCreatedAt!) { + if maxCreatedAt == nil || createdAt.after(maxCreatedAt!) { createdAtMapByActor[actorID] = createdAt } toBeStyleds.append(node) diff --git a/Sources/Document/CRDT/CRDTTree.swift b/Sources/Document/CRDT/CRDTTree.swift index 07dedeb9..dc8e019f 100644 --- a/Sources/Document/CRDT/CRDTTree.swift +++ b/Sources/Document/CRDT/CRDTTree.swift @@ -405,8 +405,19 @@ final class CRDTTreeNode: IndexTreeNode { /** * `canDelete` checks if node is able to delete. */ - func canDelete(_ editedAt: TimeTicket, _ latestCreatedAt: TimeTicket) -> Bool { - !self.createdAt.after(latestCreatedAt) && (self.removedAt == nil || editedAt.after(self.removedAt!)) + func canDelete(_ editedAt: TimeTicket, _ maxCreatedAt: TimeTicket) -> Bool { + !self.createdAt.after(maxCreatedAt) && (self.removedAt == nil || editedAt.after(self.removedAt!)) + } + + /** + * `canStyle` checks if node is able to style. + */ + func canStyle(_ editedAt: TimeTicket, _ maxCreatedAt: TimeTicket) -> Bool { + if self.isText { + return false + } + + return !self.createdAt.after(maxCreatedAt) && (self.removedAt == nil || editedAt.after(self.removedAt!)) } /** @@ -572,8 +583,46 @@ class CRDTTree: CRDTGCElement { * `style` applies the given attributes of the given range. */ @discardableResult - func style(_ range: TreePosRange, _ attributes: [String: String]?, _ editedAt: TimeTicket) throws -> [TreeChange] { - try self.performChangeStyle(range, attributes, nil, editedAt) + func style(_ range: TreePosRange, _ attributes: [String: String]?, _ editedAt: TimeTicket, _ maxCreatedAtMapByActor: [String: TimeTicket]?) throws -> ([String: TimeTicket], [TreeChange]) { + let (fromParent, fromLeft) = try self.findNodesAndSplitText(range.0, editedAt) + let (toParent, toLeft) = try self.findNodesAndSplitText(range.1, editedAt) + + var changes: [TreeChange] = [] + let value = attributes != nil ? TreeChangeValue.attributes(attributes!) : nil + var createdAtMapByActor = [String: TimeTicket]() + + try self.traverseInPosRange(fromParent, fromLeft, toParent, toLeft) { token, _ in + let (node, _) = token + let actorID = node.createdAt.actorID + var maxCreatedAt: TimeTicket? = maxCreatedAtMapByActor != nil ? maxCreatedAtMapByActor?[actorID] ?? TimeTicket.initial : TimeTicket.max + + if node.canStyle(editedAt, maxCreatedAt!), !node.isText, attributes != nil { + maxCreatedAt = createdAtMapByActor[actorID] + let createdAt = node.createdAt + if maxCreatedAt == nil || createdAt.after(maxCreatedAt!) { + createdAtMapByActor[actorID] = createdAt + } + + if node.attrs == nil { + node.attrs = RHT() + } + for (key, value) in attributes ?? [:] { + node.attrs?.set(key: key, value: value, executedAt: editedAt) + } + + try changes.append(TreeChange(actor: editedAt.actorID, + type: .style, + from: self.toIndex(fromParent, fromLeft), + to: self.toIndex(toParent, toLeft), + fromPath: self.toPath(fromParent, fromLeft), + toPath: self.toPath(toParent, toLeft), + value: value, + splitLevel: 0) // dummy value. + ) + } + } + + return (createdAtMapByActor, changes) } /** @@ -581,26 +630,11 @@ class CRDTTree: CRDTGCElement { */ @discardableResult func removeStyle(_ range: TreePosRange, _ attributesToRemove: [String], _ editedAt: TimeTicket) throws -> [TreeChange] { - try self.performChangeStyle(range, nil, attributesToRemove, editedAt) - } - - private func performChangeStyle(_ range: TreePosRange, _ attributes: [String: String]?, _ attributesToRemove: [String]?, _ editedAt: TimeTicket) throws -> [TreeChange] { let (fromParent, fromLeft) = try self.findNodesAndSplitText(range.0, editedAt) let (toParent, toLeft) = try self.findNodesAndSplitText(range.1, editedAt) var changes: [TreeChange] = [] - let value: TreeChangeValue? - let type: TreeChangeType - - if let attributes { - value = .attributes(attributes) - type = .style - } else if let attributesToRemove { - value = .attributesToRemove(attributesToRemove) - type = .removeStyle - } else { - fatalError() - } + let value = TreeChangeValue.attributesToRemove(attributesToRemove) try self.traverseInPosRange(fromParent, fromLeft, toParent, toLeft) { token, _ in let (node, _) = token @@ -608,15 +642,12 @@ class CRDTTree: CRDTGCElement { if node.attrs == nil { node.attrs = RHT() } - for (key, value) in attributes ?? [:] { - node.attrs?.set(key: key, value: value, executedAt: editedAt) - } - for key in attributesToRemove ?? [] { + for key in attributesToRemove { node.attrs?.remove(key: key, executedAt: editedAt) } try changes.append(TreeChange(actor: editedAt.actorID, - type: type, + type: .removeStyle, from: self.toIndex(fromParent, fromLeft), to: self.toIndex(toParent, toLeft), fromPath: self.toPath(fromParent, fromLeft), @@ -635,7 +666,7 @@ class CRDTTree: CRDTGCElement { * If the content is undefined, the range will be removed. */ @discardableResult - func edit(_ range: TreePosRange, _ contents: [CRDTTreeNode]?, _ splitLevel: Int32, _ editedAt: TimeTicket, _ latestCreatedAtMapByActor: [String: TimeTicket] = [:], _ issueTimeTicket: () -> TimeTicket) throws -> ([TreeChange], [String: TimeTicket]) { + func edit(_ range: TreePosRange, _ contents: [CRDTTreeNode]?, _ splitLevel: Int32, _ editedAt: TimeTicket, _ maxCreatedAtMapByActor: [String: TimeTicket] = [:], _ issueTimeTicket: () -> TimeTicket) throws -> ([TreeChange], [String: TimeTicket]) { // 01. find nodes from the given range and split nodes. let (fromParent, fromLeft) = try self.findNodesAndSplitText(range.0, editedAt) let (toParent, toLeft) = try self.findNodesAndSplitText(range.1, editedAt) @@ -646,7 +677,7 @@ class CRDTTree: CRDTGCElement { var nodesToBeRemoved = [CRDTTreeNode]() var tokensToBeRemoved = [TreeToken]() var toBeMovedToFromParents = [CRDTTreeNode]() - var latestCreatedAtMap = [String: TimeTicket]() + var maxCreatedAtMap = [String: TimeTicket]() try self.traverseInPosRange(fromParent, fromLeft, toParent, toLeft) { treeToken, ended in // NOTE(hackerwins): If the node overlaps as a start tag with the // range then we need to move the remaining children to fromParent. @@ -665,16 +696,16 @@ class CRDTTree: CRDTGCElement { let actorID = node.createdAt.actorID - let latestCreatedAt = latestCreatedAtMapByActor.isEmpty == false ? latestCreatedAtMapByActor[actorID] ?? TimeTicket.initial : TimeTicket.max + let maxCreatedAt = maxCreatedAtMapByActor.isEmpty == false ? maxCreatedAtMapByActor[actorID] ?? TimeTicket.initial : TimeTicket.max // NOTE(sejongk): If the node is removable or its parent is going to // be removed, then this node should be removed. - if node.canDelete(editedAt, latestCreatedAt) || nodesToBeRemoved.contains(where: { $0 === node.parent }) { - let latestCreatedAt = latestCreatedAtMap[actorID] + if node.canDelete(editedAt, maxCreatedAt) || nodesToBeRemoved.contains(where: { $0 === node.parent }) { + let maxCreatedAt = maxCreatedAtMap[actorID] let createdAt = node.createdAt - if latestCreatedAt == nil || createdAt.after(latestCreatedAt!) { - latestCreatedAtMap[actorID] = createdAt + if maxCreatedAt == nil || createdAt.after(maxCreatedAt!) { + maxCreatedAtMap[actorID] = createdAt } // NOTE(hackerwins): If the node overlaps as an end token with the @@ -780,7 +811,7 @@ class CRDTTree: CRDTGCElement { } } - return (changes, latestCreatedAtMap) + return (changes, maxCreatedAtMap) } /** diff --git a/Sources/Document/CRDT/RGATreeSplit.swift b/Sources/Document/CRDT/RGATreeSplit.swift index 9566d1fb..49ef3465 100644 --- a/Sources/Document/CRDT/RGATreeSplit.swift +++ b/Sources/Document/CRDT/RGATreeSplit.swift @@ -348,15 +348,15 @@ class RGATreeSplitNode: SplayNode { /** * `canDelete` checks if node is able to delete. */ - public func canDelete(_ editedAt: TimeTicket, _ latestCreatedAt: TimeTicket) -> Bool { - !self.createdAt.after(latestCreatedAt) && (self.removedAt == nil || editedAt.after(self.removedAt!)) + public func canDelete(_ editedAt: TimeTicket, _ maxCreatedAt: TimeTicket) -> Bool { + !self.createdAt.after(maxCreatedAt) && (self.removedAt == nil || editedAt.after(self.removedAt!)) } /** * `canStyle` checks if node is able to set style. */ - public func canStyle(_ editedAt: TimeTicket, _ latestCreatedAt: TimeTicket) -> Bool { - !self.createdAt.after(latestCreatedAt) && (self.removedAt == nil || editedAt.after(self.removedAt!)) + public func canStyle(_ editedAt: TimeTicket, _ maxCreatedAt: TimeTicket) -> Bool { + !self.createdAt.after(maxCreatedAt) && (self.removedAt == nil || editedAt.after(self.removedAt!)) } /** @@ -427,14 +427,14 @@ class RGATreeSplit { * @param range - range of RGATreeSplitNode * @param editedAt - edited time * @param value - value - * @param latestCreatedAtMapByActor - latestCreatedAtMapByActor + * @param maxCreatedAtMapByActor - maxCreatedAtMapByActor * @returns `[RGATreeSplitNodePos, Map, Array]` */ @discardableResult public func edit(_ range: RGATreeSplitPosRange, _ editedAt: TimeTicket, _ value: T?, - _ latestCreatedAtMapByActor: [String: TimeTicket]? = nil) throws -> (RGATreeSplitPos, [String: TimeTicket], [ContentChange]) + _ maxCreatedAtMapByActor: [String: TimeTicket]? = nil) throws -> (RGATreeSplitPos, [String: TimeTicket], [ContentChange]) { // 01. split nodes with from and to let (toLeft, toRight) = try self.findNodeWithSplit(range.1, editedAt) @@ -442,7 +442,7 @@ class RGATreeSplit { // 02. delete between from and to let nodesToDelete = self.findBetween(fromRight, toRight) - var (changes, latestCreatedAtMap, removedNodeMapByNodeKey) = try self.deleteNodes(nodesToDelete, editedAt, latestCreatedAtMapByActor) + var (changes, maxCreatedAtMap, removedNodeMapByNodeKey) = try self.deleteNodes(nodesToDelete, editedAt, maxCreatedAtMapByActor) let caretID = toRight?.id ?? toLeft.id var caretPos = RGATreeSplitPos(caretID, 0) @@ -470,7 +470,7 @@ class RGATreeSplit { self.removedNodeMap[key] = removedNode } - return (caretPos, latestCreatedAtMap, changes) + return (caretPos, maxCreatedAtMap, changes) } /** @@ -692,9 +692,9 @@ class RGATreeSplit { private func deleteNodes(_ candidates: [RGATreeSplitNode], _ editedAt: TimeTicket, - _ latestCreatedAtMapByActor: [String: TimeTicket]?) throws -> ([ContentChange], - [String: TimeTicket], - [String: RGATreeSplitNode]) + _ maxCreatedAtMapByActor: [String: TimeTicket]?) throws -> ([ContentChange], + [String: TimeTicket], + [String: RGATreeSplitNode]) { guard !candidates.isEmpty else { return ([], [:], [:]) @@ -703,7 +703,7 @@ class RGATreeSplit { // There are 2 types of nodes in `candidates`: should delete, should not delete. // `nodesToKeep` contains nodes should not delete, // then is used to find the boundary of the range to be deleted. - let (nodesToDelete, nodesToKeep) = try self.filterNodes(candidates, editedAt, latestCreatedAtMapByActor) + let (nodesToDelete, nodesToKeep) = try self.filterNodes(candidates, editedAt, maxCreatedAtMapByActor) var createdAtMapByActor = [ActorID: TimeTicket]() var removedNodeMap = [ActorID: RGATreeSplitNode]() @@ -730,9 +730,9 @@ class RGATreeSplit { private func filterNodes(_ candidates: [RGATreeSplitNode], _ editedAt: TimeTicket, - _ latestCreatedAtMapByActor: [ActorID: TimeTicket]?) throws -> ([RGATreeSplitNode], [RGATreeSplitNode?]) + _ maxCreatedAtMapByActor: [ActorID: TimeTicket]?) throws -> ([RGATreeSplitNode], [RGATreeSplitNode?]) { - let isRemote = latestCreatedAtMapByActor != nil + let isRemote = maxCreatedAtMapByActor != nil var nodesToDelete = [RGATreeSplitNode]() var nodesToKeep = [RGATreeSplitNode?]() @@ -740,19 +740,19 @@ class RGATreeSplit { nodesToKeep.append(leftEdge) for node in candidates { - let latestCreatedAt: TimeTicket + let maxCreatedAt: TimeTicket if isRemote { - if let latest = latestCreatedAtMapByActor?[node.createdAt.actorID] { - latestCreatedAt = latest + if let latest = maxCreatedAtMapByActor?[node.createdAt.actorID] { + maxCreatedAt = latest } else { - latestCreatedAt = TimeTicket.initial + maxCreatedAt = TimeTicket.initial } } else { - latestCreatedAt = TimeTicket.max + maxCreatedAt = TimeTicket.max } - if node.canDelete(editedAt, latestCreatedAt) { + if node.canDelete(editedAt, maxCreatedAt) { nodesToDelete.append(node) } else { nodesToKeep.append(node) diff --git a/Sources/Document/Json/JSONTree.swift b/Sources/Document/Json/JSONTree.swift index 442b8f83..9764c06c 100644 --- a/Sources/Document/Json/JSONTree.swift +++ b/Sources/Document/Json/JSONTree.swift @@ -329,12 +329,13 @@ public class JSONTree { let (fromPos, toPos) = try tree.pathToPosRange(path) let ticket = context.issueTimeTicket - try tree.style((fromPos, toPos), stringAttrs, ticket) + let (maxCreationMapByActor, _) = try tree.style((fromPos, toPos), stringAttrs, ticket, nil) // TreeStyleOperation context.push(operation: TreeStyleOperation(parentCreatedAt: tree.createdAt, fromPos: fromPos, toPos: toPos, + maxCreatedAtMapByActor: maxCreationMapByActor, attributes: stringAttrs, attributesToRemove: [], executedAt: ticket) @@ -365,11 +366,12 @@ public class JSONTree { let toPos = try tree.findPos(toIdx) let ticket = context.issueTimeTicket - try tree.style((fromPos, toPos), stringAttrs, ticket) + let (maxCreationMapByActor, _) = try tree.style((fromPos, toPos), stringAttrs, ticket, nil) context.push(operation: TreeStyleOperation(parentCreatedAt: tree.createdAt, fromPos: fromPos, toPos: toPos, + maxCreatedAtMapByActor: maxCreationMapByActor, attributes: stringAttrs, attributesToRemove: [], executedAt: ticket) @@ -397,6 +399,7 @@ public class JSONTree { context.push(operation: TreeStyleOperation(parentCreatedAt: tree.createdAt, fromPos: fromPos, toPos: toPos, + maxCreatedAtMapByActor: [:], attributes: [:], attributesToRemove: attributesToRemove, executedAt: ticket) diff --git a/Sources/Document/Operation/TreeSytleOperation.swift b/Sources/Document/Operation/TreeSytleOperation.swift index fe8f366e..4cd7e793 100644 --- a/Sources/Document/Operation/TreeSytleOperation.swift +++ b/Sources/Document/Operation/TreeSytleOperation.swift @@ -32,16 +32,18 @@ class TreeStyleOperation: Operation { * `toPos` returns the end point of the editing range. */ let toPos: CRDTTreePos + let maxCreatedAtMapByActor: [String: TimeTicket] /** * `attributes` returns the content of Edit. */ let attributes: [String: String] let attributesToRemove: [String] - init(parentCreatedAt: TimeTicket, fromPos: CRDTTreePos, toPos: CRDTTreePos, attributes: [String: String], attributesToRemove: [String], executedAt: TimeTicket) { + init(parentCreatedAt: TimeTicket, fromPos: CRDTTreePos, toPos: CRDTTreePos, maxCreatedAtMapByActor: [String: TimeTicket], attributes: [String: String], attributesToRemove: [String], executedAt: TimeTicket) { self.parentCreatedAt = parentCreatedAt self.fromPos = fromPos self.toPos = toPos + self.maxCreatedAtMapByActor = maxCreatedAtMapByActor self.attributes = attributes self.attributesToRemove = attributesToRemove self.executedAt = executedAt @@ -65,7 +67,7 @@ class TreeStyleOperation: Operation { let changes: [TreeChange] if self.attributes.isEmpty == false { - changes = try tree.style((self.fromPos, self.toPos), self.attributes, self.executedAt) + (_, changes) = try tree.style((self.fromPos, self.toPos), self.attributes, self.executedAt, self.maxCreatedAtMapByActor) } else { changes = try tree.removeStyle((self.fromPos, self.toPos), self.attributesToRemove, self.executedAt) }