Skip to content

Commit 40398e8

Browse files
author
Amy Worrall
committed
[Work in progress] new styles system
1 parent afdaea8 commit 40398e8

File tree

14 files changed

+524
-169
lines changed

14 files changed

+524
-169
lines changed

Lexical/Core/Constants.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ public enum DirtyType {
7070
case fullReconcile
7171
}
7272

73+
@available(*, deprecated, message: "Use new styles system")
7374
@objc public enum TextFormatType: Int {
7475
case bold
7576
case italic

Lexical/Core/Editor.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ public class Editor: NSObject {
9494

9595
internal var nodeTransforms: [NodeType: [(Int, NodeTransform)]] = [:]
9696

97+
// Styles. For all methods to manipulate these, see Styles.swift
98+
internal var registeredStyles: StylesRegistrationDict = [:]
99+
97100
// Used to help co-ordinate selection and events
98101
internal var compositionKey: NodeKey?
99102
public var dirtyType: DirtyType = .noDirtyNodes // TODO: I made this public to work around an issue in playground. @amyworrall

Lexical/Core/Errors.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ public enum LexicalError: Error {
1313
case sanityCheck(errorMessage: String, textViewText: String, fullReconcileText: String)
1414
case reconciler(String)
1515
case rangeCacheSearch(String)
16+
case styleValidation(String)
1617
}

Lexical/Core/Events.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,10 @@ internal func handleIndentAndOutdent(insertTab: (Node) -> Void, indentOrOutdent:
217217

218218
public func registerRichText(editor: Editor) {
219219

220+
// Style defaults and commands are handled in Styles.swift
221+
registerDefaultStyles(editor: editor)
222+
registerStyleCommands(editor: editor)
223+
220224
_ = editor.registerCommand(type: .insertLineBreak, listener: { [weak editor] payload in
221225
guard let editor else { return false }
222226
do {

Lexical/Core/Nodes/HeadingNode.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,21 +36,21 @@ public class HeadingNode: ElementNode {
3636
self.tag = tag
3737

3838
super.init()
39-
self.type = NodeType.heading
4039
}
4140

4241
public required init(_ key: NodeKey?, tag: HeadingTagType) {
4342
self.tag = tag
4443
super.init(key)
45-
self.type = NodeType.heading
44+
}
45+
46+
override class public func getType() -> NodeType {
47+
return .heading
4648
}
4749

4850
public required init(from decoder: Decoder) throws {
4951
let container = try decoder.container(keyedBy: CodingKeys.self)
5052
self.tag = try container.decode(HeadingTagType.self, forKey: .tag)
5153
try super.init(from: decoder)
52-
53-
self.type = NodeType.heading
5454
}
5555

5656
override public func encode(to encoder: Encoder) throws {

Lexical/Core/Nodes/LineBreakNode.swift

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,34 +6,31 @@
66
*/
77

88
public class LineBreakNode: Node {
9-
override public init() {
10-
super.init()
11-
self.type = NodeType.linebreak
12-
}
13-
14-
override required init(_ key: NodeKey?) {
15-
super.init(key)
16-
self.type = NodeType.linebreak
9+
required public init(styles: StylesDict, key: NodeKey?) {
10+
super.init(styles: styles, key: key)
1711
}
1812

1913
public required init(from decoder: Decoder) throws {
2014
try super.init(from: decoder)
21-
self.type = NodeType.linebreak
15+
}
16+
17+
public override class func getType() -> NodeType {
18+
.linebreak
2219
}
2320

2421
override public func encode(to encoder: Encoder) throws {
2522
try super.encode(to: encoder)
2623
}
2724

2825
override public func clone() -> Self {
29-
Self(key)
26+
Self(styles: styles, key: key)
3027
}
3128

3229
override public func getPostamble() -> String {
3330
return "\n"
3431
}
3532

3633
public func createLineBreakNode() -> LineBreakNode {
37-
return LineBreakNode()
34+
return LineBreakNode(styles: [:], key: nil)
3835
}
3936
}

Lexical/Core/Nodes/Node.swift

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,24 +18,22 @@ open class Node: Codable {
1818
enum CodingKeys: String, CodingKey {
1919
case type
2020
case version
21+
case styles
2122
}
2223

2324
public var key: NodeKey
2425
var parent: NodeKey?
25-
public var type: NodeType
2626
public var version: Int
2727

28-
public init() {
29-
self.type = Node.getType()
30-
self.version = 1
31-
self.key = LexicalConstants.uninitializedNodeKey
28+
// Use style APIs to manipulate styles: see Styles.swift
29+
var styles: StylesDict = [:]
3230

33-
_ = try? generateKey(node: self)
34-
}
35-
36-
public init(_ key: NodeKey?) {
37-
self.type = Node.getType()
31+
// This change will be a code-breaking change for subclasses, which must now all implement this method. However,
32+
// the effort required to update each class will be minimal. The rationale for this breaking change is to avoid
33+
// bugs where styles disappear when nodes are copied, etc.
34+
public required init(styles: StylesDict, key: NodeKey?) {
3835
self.version = 1
36+
self.styles = styles
3937

4038
if let key, key != LexicalConstants.uninitializedNodeKey {
4139
self.key = key
@@ -49,17 +47,45 @@ open class Node: Codable {
4947
public required init(from decoder: Decoder) throws {
5048
let values = try decoder.container(keyedBy: CodingKeys.self)
5149
key = LexicalConstants.uninitializedNodeKey
52-
type = try NodeType(rawValue: values.decode(String.self, forKey: .type))
5350
version = try values.decode(Int.self, forKey: .version)
5451

52+
// styles
53+
if let styleContainer = try? values.nestedContainer(keyedBy: StyleCodingKeys.self, forKey: .styles) {
54+
guard let editor = getActiveEditor() else {
55+
throw LexicalError.internal("Could not get active editor")
56+
}
57+
// try each style and see if there's a value
58+
var newStyles: StylesDict = [:]
59+
for (styleName, style) in editor.registeredStyles {
60+
guard let styleKey = StyleCodingKeys(stringValue: styleName.rawValue) else { continue }
61+
guard let superDecoder = try? styleContainer.superDecoder(forKey: styleKey) else { continue }
62+
if let value = try? styleValueFromDecoder(style, decoder: superDecoder) {
63+
newStyles[styleName] = value
64+
}
65+
}
66+
self.styles = newStyles
67+
}
68+
5569
_ = try? generateKey(node: self)
5670
}
5771

5872
/// Used when serialising node to JSON
5973
open func encode(to encoder: Encoder) throws {
6074
var container = encoder.container(keyedBy: CodingKeys.self)
61-
try container.encode(self.type.rawValue, forKey: .type)
6275
try container.encode(self.version, forKey: .version)
76+
77+
// styles
78+
if styles.count > 0 {
79+
var stylesContainer = container.nestedContainer(keyedBy: StyleCodingKeys.self, forKey: .styles)
80+
guard let editor = getActiveEditor() else {
81+
throw LexicalError.internal("Could not get active editor")
82+
}
83+
for (styleName, style) in editor.registeredStyles {
84+
guard let styleValue = self.getStyle(style),
85+
let styleKey = StyleCodingKeys(stringValue: styleName.rawValue) else { continue }
86+
try stylesContainer.encode(styleValue, forKey: styleKey)
87+
}
88+
}
6389
}
6490

6591
/**
@@ -68,13 +94,16 @@ open class Node: Codable {
6894
*/
6995
open func didMoveTo(newEditor editor: Editor) {}
7096

71-
// This is an initial value for `type`.
72-
// static methods cannot be overridden in swift so,
73-
// each subclass needs to assign the type property in their init method
74-
static func getType() -> NodeType {
97+
open class func getType() -> NodeType {
7598
NodeType.unknown
7699
}
77100

101+
public var type: NodeType {
102+
get {
103+
Self.getType()
104+
}
105+
}
106+
78107
/// Provides the **preamble** part of the node's content. Typically the preamble is used for control characters to represent embedded objects (see ``DecoratorNode``).
79108
///
80109
/// In Lexical iOS, a node's content is split into four parts: preamble, children, text, postamble. ``ElementNode`` subclasses can implement preamble/postamble, and TextNode subclasses can implement the text part.
@@ -124,7 +153,17 @@ open class Node: Codable {
124153

125154
/// Lets the node provide attributes for TextKit to use to render the node's content.
126155
open func getAttributedStringAttributes(theme: Theme) -> [NSAttributedString.Key: Any] {
127-
[:]
156+
let node = getLatest()
157+
let attributesDict = theme.getValue(node.type, withSubtype: nil) ?? [:]
158+
guard let editor = getActiveEditor() else {
159+
return attributesDict
160+
}
161+
let styleAttributeDicts: [Theme.AttributeDict] = node.styles.map { (key: StyleName, value: Any) in
162+
guard let styleType = editor.registeredStyles[key] else { return [:] }
163+
return styleAttributesDictFor(node: node, style: styleType, theme: theme)
164+
}
165+
let dicts = [attributesDict] + styleAttributeDicts
166+
return dicts.reduce([:]) { $0.merging($1) { (_, next) in next } }
128167
}
129168

130169
/**

Lexical/Core/Nodes/TextNode.swift

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ public struct SerializedTextFormat: OptionSet, Codable {
8383
}
8484
}
8585

86+
@available(*, deprecated, message: "use new styles system")
8687
public struct TextFormat: Equatable, Codable {
8788

8889
public var bold: Bool
@@ -187,16 +188,12 @@ open class TextNode: Node {
187188
enum CodingKeys: String, CodingKey {
188189
case text
189190
case mode
190-
case format
191191
case detail
192-
case style
193192
}
194193

195194
private var text: String = ""
196195
var mode: Mode = .normal
197-
var format: TextFormat = TextFormat()
198196
var detail = TextNodeDetail()
199-
var style: String = ""
200197

201198
override public init() {
202199
super.init()
@@ -260,12 +257,12 @@ open class TextNode: Node {
260257

261258
public func setBold(_ isBold: Bool) throws {
262259
try errorOnReadOnly()
263-
try getWritable().format.bold = isBold
260+
try getWritable().setStyle(Styles.Bold.self, isBold)
264261
}
265262

266263
public func setItalic(_ isItalic: Bool) throws {
267264
try errorOnReadOnly()
268-
try getWritable().format.italic = isItalic
265+
try getWritable().setStyle(Styles.Italic.self, isItalic)
269266
}
270267

271268
public func canInsertTextAfter() -> Bool {
@@ -394,28 +391,29 @@ open class TextNode: Node {
394391
return true
395392
}
396393

394+
@available(*, deprecated, message: "Use new styles system")
397395
public func getFormat() -> TextFormat {
398396
let node = getLatest() as TextNode
399-
return node.format
397+
return compatibilityFormatFromStyles(node.styles)
400398
}
401399

400+
@available(*, deprecated, message: "Use new styles system")
402401
@discardableResult
403402
public func setFormat(format: TextFormat) throws -> TextNode {
404403
try errorOnReadOnly()
405404
let node = try getWritable() as TextNode
406-
node.format = format
405+
let newStyles = compatibilityStylesFromFormat(format)
406+
node.styles = compatibilityMergeStylesAssumingAllFormats(old: node.styles, newFormats: newStyles)
407407
return node
408408
}
409409

410+
@available(*, deprecated, message: "Use new styles system")
410411
public func getStyle() -> String {
411-
let node = getLatest() as TextNode
412-
return node.style
412+
return ""
413413
}
414414

415-
public func setStyle(_ style: String) throws {
416-
let writable = try getWritable()
417-
writable.style = style
418-
}
415+
@available(*, deprecated, message: "Use new styles system")
416+
public func setStyle(_ style: String) throws {}
419417

420418
public func splitText(splitOffsets: [Int]) throws -> [TextNode] {
421419
try errorOnReadOnly()

Lexical/Core/Selection/BaseSelection.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,10 @@ public protocol BaseSelection: AnyObject, CustomDebugStringConvertible {
6262

6363
/// Handles user-provided text to insert, applying a series of insertion heuristics based on the selection type and position.
6464
func insertText(_ text: String) throws
65+
66+
/// Applies a style to the text nodes in the selection, splitting if necessary
67+
func applyTextStyle<T: Style>(_ style: T.Type, value: T.StyleValueType?) throws
68+
69+
/// Applies a style to the top level element nodes that are inside or overlap the selection
70+
func applyBlockStyle<T: Style>(_ style: T.Type, value: T.StyleValueType?) throws
6571
}

0 commit comments

Comments
 (0)