diff --git a/Lexical/Core/Editor.swift b/Lexical/Core/Editor.swift index 4e2e9e3c..caa7ae3b 100644 --- a/Lexical/Core/Editor.swift +++ b/Lexical/Core/Editor.swift @@ -799,7 +799,7 @@ public class Editor: NSObject { for nodeKey in nodesToProcess where nodeKey != compositionKey && nodeKey != rootNode.key { guard let node = getNodeByKey(key: nodeKey), - let transforms = nodeTransforms[node.type], + let transforms = nodeTransforms[type(of: node).type], node.isAttached() else { continue diff --git a/Lexical/Core/LexicalMacros.swift b/Lexical/Core/LexicalMacros.swift new file mode 100644 index 00000000..3d478d7b --- /dev/null +++ b/Lexical/Core/LexicalMacros.swift @@ -0,0 +1,11 @@ +// +// File.swift +// +// +// Created by Amy Worrall on 18/07/2023. +// + +import Foundation + +@attached(member, names: arbitrary) +public macro LexicalNode(_ type: NodeType) = #externalMacro(module: "LexicalMacrosBase", type: "NodeMacro") diff --git a/Lexical/Core/Nodes/CodeHighlightNode.swift b/Lexical/Core/Nodes/CodeHighlightNode.swift index aa0ed0d2..6b81a5a7 100644 --- a/Lexical/Core/Nodes/CodeHighlightNode.swift +++ b/Lexical/Core/Nodes/CodeHighlightNode.swift @@ -14,15 +14,17 @@ public class CodeHighlightNode: TextNode { public var highlightType: String? + open override class var type: NodeType { + .codeHighlight + } + override public init() { super.init() - self.type = NodeType.codeHighlight } required init(text: String, highlightType: String?, key: NodeKey? = nil) { super.init(text: text, key: key) self.highlightType = highlightType - self.type = NodeType.codeHighlight } public required init(from decoder: Decoder) throws { @@ -30,7 +32,6 @@ public class CodeHighlightNode: TextNode { try super.init(from: decoder) self.highlightType = try container.decode(String.self, forKey: .highlightType) - self.type = NodeType.codeHighlight } public required convenience init(text: String, key: NodeKey?) { diff --git a/Lexical/Core/Nodes/CodeNode.swift b/Lexical/Core/Nodes/CodeNode.swift index 581051b7..3a41f7d3 100644 --- a/Lexical/Core/Nodes/CodeNode.swift +++ b/Lexical/Core/Nodes/CodeNode.swift @@ -42,15 +42,17 @@ public class CodeNode: ElementNode { private var language: String = "" + open override class var type: NodeType { + .code + } + override public init() { super.init() - self.type = NodeType.code } required init(language: String, key: NodeKey? = nil) { super.init(key) self.language = language - self.type = NodeType.code } public required init(from decoder: Decoder) throws { @@ -58,7 +60,6 @@ public class CodeNode: ElementNode { try super.init(from: decoder) self.language = try container.decode(String.self, forKey: .language) - self.type = NodeType.code } override public func encode(to encoder: Encoder) throws { diff --git a/Lexical/Core/Nodes/HeadingNode.swift b/Lexical/Core/Nodes/HeadingNode.swift index 41728789..dad582f5 100644 --- a/Lexical/Core/Nodes/HeadingNode.swift +++ b/Lexical/Core/Nodes/HeadingNode.swift @@ -23,49 +23,10 @@ enum HeadingDefaultFontSize: Float { case h5 = 20 } +@LexicalNode(.heading) public class HeadingNode: ElementNode { - enum CodingKeys: String, CodingKey { - case tag - } - - private var tag: HeadingTagType - - // MARK: - Init - - public init(tag: HeadingTagType) { - self.tag = tag - - super.init() - self.type = NodeType.heading - } - - public required init(_ key: NodeKey?, tag: HeadingTagType) { - self.tag = tag - super.init(key) - self.type = NodeType.heading - } - public required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.tag = try container.decode(HeadingTagType.self, forKey: .tag) - try super.init(from: decoder) - - self.type = NodeType.heading - } - - override public func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(self.tag, forKey: .tag) - } - - public func getTag() -> HeadingTagType { - tag - } - - override public func clone() -> Self { - Self(key, tag: tag) - } + private var _tag: HeadingTagType override public func getAttributedStringAttributes(theme: Theme) -> [NSAttributedString.Key: Any] { switch tag { diff --git a/Lexical/Core/Nodes/LineBreakNode.swift b/Lexical/Core/Nodes/LineBreakNode.swift index 6b44210e..231fce8e 100644 --- a/Lexical/Core/Nodes/LineBreakNode.swift +++ b/Lexical/Core/Nodes/LineBreakNode.swift @@ -6,19 +6,21 @@ */ public class LineBreakNode: Node { + + open override class var type: NodeType { + .linebreak + } + override public init() { super.init() - self.type = NodeType.linebreak } override required init(_ key: NodeKey?) { super.init(key) - self.type = NodeType.linebreak } public required init(from decoder: Decoder) throws { try super.init(from: decoder) - self.type = NodeType.linebreak } override public func encode(to encoder: Encoder) throws { diff --git a/Lexical/Core/Nodes/Node.swift b/Lexical/Core/Nodes/Node.swift index fe0e6255..4128fcbc 100644 --- a/Lexical/Core/Nodes/Node.swift +++ b/Lexical/Core/Nodes/Node.swift @@ -20,13 +20,19 @@ open class Node: Codable { case version } + open class var type: NodeType { + .unknown + } + + public func getType() -> NodeType { + return Swift.type(of: self).type + } + public var key: NodeKey var parent: NodeKey? - public var type: NodeType public var version: Int public init() { - self.type = Node.getType() self.version = 1 self.key = LexicalConstants.uninitializedNodeKey @@ -34,7 +40,6 @@ open class Node: Codable { } public init(_ key: NodeKey?) { - self.type = Node.getType() self.version = 1 if let key, key != LexicalConstants.uninitializedNodeKey { @@ -49,7 +54,6 @@ open class Node: Codable { public required init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) key = LexicalConstants.uninitializedNodeKey - type = try NodeType(rawValue: values.decode(String.self, forKey: .type)) version = try values.decode(Int.self, forKey: .version) _ = try? generateKey(node: self) @@ -58,7 +62,7 @@ open class Node: Codable { /// Used when serialising node to JSON open func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(self.type.rawValue, forKey: .type) + try container.encode(Self.type.rawValue, forKey: .type) try container.encode(self.version, forKey: .version) } @@ -68,13 +72,6 @@ open class Node: Codable { */ open func didMoveTo(newEditor editor: Editor) {} - // This is an initial value for `type`. - // static methods cannot be overridden in swift so, - // each subclass needs to assign the type property in their init method - static func getType() -> NodeType { - NodeType.unknown - } - /// Provides the **preamble** part of the node's content. Typically the preamble is used for control characters to represent embedded objects (see ``DecoratorNode``). /// /// 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. @@ -137,7 +134,7 @@ open class Node: Codable { Lexical's paragraph nodes!) */ open func getBlockLevelAttributes(theme: Theme) -> BlockLevelAttributes? { - return theme.getBlockLevelAttributes(self.type) + return theme.getBlockLevelAttributes(Self.type) } /// Returns a mutable version of the node. Will throw an error if called outside of a Lexical Editor ``Editor/update(_:)`` callback. diff --git a/Lexical/Core/Nodes/ParagraphNode.swift b/Lexical/Core/Nodes/ParagraphNode.swift index 63c6cf36..e9b93bc8 100644 --- a/Lexical/Core/Nodes/ParagraphNode.swift +++ b/Lexical/Core/Nodes/ParagraphNode.swift @@ -8,19 +8,20 @@ import UIKit public class ParagraphNode: ElementNode { + open override class var type: NodeType { + .paragraph + } + override public init() { super.init() - self.type = NodeType.paragraph } override required init(_ key: NodeKey?) { super.init(key) - self.type = NodeType.paragraph } public required init(from decoder: Decoder) throws { try super.init(from: decoder) - self.type = NodeType.paragraph } override public func encode(to encoder: Encoder) throws { diff --git a/Lexical/Core/Nodes/QuoteNode.swift b/Lexical/Core/Nodes/QuoteNode.swift index 64e99a38..3aaf8766 100644 --- a/Lexical/Core/Nodes/QuoteNode.swift +++ b/Lexical/Core/Nodes/QuoteNode.swift @@ -8,20 +8,20 @@ import UIKit public class QuoteNode: ElementNode { + open override class var type: NodeType { + .quote + } + override public init() { super.init() - self.type = NodeType.quote } override public required init(_ key: NodeKey?) { super.init(key) - self.type = NodeType.quote } public required init(from decoder: Decoder) throws { try super.init(from: decoder) - - self.type = NodeType.quote } override public func encode(to encoder: Encoder) throws { diff --git a/Lexical/Core/Nodes/RootNode.swift b/Lexical/Core/Nodes/RootNode.swift index 49115a3c..fd0faacc 100644 --- a/Lexical/Core/Nodes/RootNode.swift +++ b/Lexical/Core/Nodes/RootNode.swift @@ -9,14 +9,16 @@ import UIKit public class RootNode: ElementNode { + open override class var type: NodeType { + .root + } + override required init() { super.init(kRootNodeKey) - self.type = NodeType.root } public required init(from decoder: Decoder) throws { try super.init(from: decoder) - self.type = NodeType.root } override public func encode(to encoder: Encoder) throws { diff --git a/Lexical/Core/Nodes/TextNode.swift b/Lexical/Core/Nodes/TextNode.swift index 3d262728..30a2b7b5 100644 --- a/Lexical/Core/Nodes/TextNode.swift +++ b/Lexical/Core/Nodes/TextNode.swift @@ -198,15 +198,17 @@ open class TextNode: Node { var detail = TextNodeDetail() var style: String = "" + open override class var type: NodeType { + .text + } + override public init() { super.init() - self.type = NodeType.text } public required init(text: String, key: NodeKey?) { super.init(key) self.text = text - self.type = NodeType.text } public convenience init(text: String) { @@ -224,7 +226,6 @@ open class TextNode: Node { let serializedDetail = try container.decode(SerializedTextNodeDetail.self, forKey: .detail) self.detail = SerializedTextNodeDetail.convertToTextDetail(from: serializedDetail) self.style = try container.decode(String.self, forKey: .style) - self.type = NodeType.text } override open func encode(to encoder: Encoder) throws { @@ -530,7 +531,7 @@ open class TextNode: Node { } public func isSimpleText() -> Bool { - return type == NodeType.text && mode == .normal + return getType() == .text && mode == .normal } @discardableResult diff --git a/Lexical/Core/Nodes/UnknownNode.swift b/Lexical/Core/Nodes/UnknownNode.swift index 7aa18164..c8ae6abc 100644 --- a/Lexical/Core/Nodes/UnknownNode.swift +++ b/Lexical/Core/Nodes/UnknownNode.swift @@ -111,6 +111,8 @@ public class UnknownNode: Node { } } + private var type: NodeType = .unknown + public required init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() diff --git a/Lexical/Core/TextUtils.swift b/Lexical/Core/TextUtils.swift index 71e5b9dd..d9c80105 100644 --- a/Lexical/Core/TextUtils.swift +++ b/Lexical/Core/TextUtils.swift @@ -41,7 +41,7 @@ internal func canShowPlaceholder(isComposing: Bool) -> Bool { for childNode in children { guard let childNode = childNode as? ElementNode else { return true } - if childNode.type != NodeType.paragraph { + if type(of: childNode).type != NodeType.paragraph { return false } diff --git a/Lexical/Core/Utils.swift b/Lexical/Core/Utils.swift index 60888d04..74e7c564 100644 --- a/Lexical/Core/Utils.swift +++ b/Lexical/Core/Utils.swift @@ -271,9 +271,9 @@ public func getNodeHierarchy(editorState: EditorState?) throws -> String { } else { formatString = "" } - description.append("\(indentation)(\(node.key)) \(node.type.rawValue) \"\(textNode.getTextPart())\" \(formatString)") + description.append("\(indentation)(\(node.key)) \(type(of: node).type.rawValue) \"\(textNode.getTextPart())\" \(formatString)") } else { - description.append("\(indentation)(\(node.key)) \(node.type.rawValue)") + description.append("\(indentation)(\(node.key)) \(type(of: node).type.rawValue)") } if let elementNode = node as? ElementNode { @@ -283,7 +283,7 @@ public func getNodeHierarchy(editorState: EditorState?) throws -> String { } while !currentNodes.isEmpty hierarchyString = description.joined(separator: "\n") - cacheString = sortedNodeMap.map({ node in "\(node.key): \(node.value.type)" }).joined(separator: ", ") + cacheString = sortedNodeMap.map({ node in "\(node.key): \(type(of: node.value).type)" }).joined(separator: ", ") } return "Tree:\n\(hierarchyString)\nCache:\n\(cacheString)" @@ -463,7 +463,7 @@ public func getNearestNodeOfType( var parent: Node? = node while let unwrappedParent = parent { - if unwrappedParent.type == type, let typedParent = unwrappedParent as? T { + if unwrappedParent.getType() == type, let typedParent = unwrappedParent as? T { return typedParent as T } diff --git a/LexicalMacrosBase/LexicalMacrosBase.swift b/LexicalMacrosBase/LexicalMacrosBase.swift new file mode 100644 index 00000000..2355e6a4 --- /dev/null +++ b/LexicalMacrosBase/LexicalMacrosBase.swift @@ -0,0 +1,241 @@ +import SwiftCompilerPlugin +import SwiftSyntax +import SwiftSyntaxBuilder +import SwiftSyntaxMacros + +enum LexicalMacroError: Error, CustomStringConvertible { + case requiresPrivate + case requiresUnderscores + case shouldBeClass + case custom(String) + + var description: String { + switch self { + case .requiresPrivate: + return "Nodes using the @Node macro should define all their properties as private" + case .requiresUnderscores: + return "Nodes using the @Node macro should define all their properties starting with an underscore" + case .shouldBeClass: + return "Should be applied to a class" + case .custom(let s): + return s + } + } +} + +struct ParsedBinding { + let name: String + let publicName: String + let capitalizedPublicName: String + let type: String +} + +public struct NodeMacro: MemberMacro { + public static func expansion( + of node: SwiftSyntax.AttributeSyntax, + providingMembersOf declaration: some SwiftSyntax.DeclGroupSyntax, + in context: some SwiftSyntaxMacros.MacroExpansionContext + ) throws -> [SwiftSyntax.DeclSyntax] { + guard let classDecl = declaration.as(ClassDeclSyntax.self) else { + throw LexicalMacroError.shouldBeClass + } + + guard let arguments = node.argument?.as(TupleExprElementListSyntax.self) else { + throw LexicalMacroError.custom("no arg list") + } + guard let firstArgument = arguments.first else { + throw LexicalMacroError.custom("no arg") + } + guard let nodeType = firstArgument.expression.as(MemberAccessExprSyntax.self)?.name.text else { + throw LexicalMacroError.custom("Must include a Lexical node type") + } + + let vars = classDecl + .memberBlock + .members + .compactMap { $0.decl.as(VariableDeclSyntax.self) } + .filter { $0.bindings.filter { $0.accessor != nil } == [] } + + let nonPrivateVars = vars + .filter { $0.modifiers?.compactMap{ $0.as(DeclModifierSyntax.self) }.compactMap{ $0.name.text == "private" }.isEmpty ?? true } + if nonPrivateVars.count > 0 { + throw LexicalMacroError.requiresPrivate + } + + // each binding corresponds to one variable + let bindings = vars.flatMap { $0.bindings } + .map { binding in + let name = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier.text ?? "" + return ParsedBinding(name: name, + publicName: String(name.dropFirst()), + capitalizedPublicName: String(name.dropFirst()).capitalizedFirstLetter, + type: binding.typeAnnotation?.type.description ?? "") + } + + var declarations: [DeclSyntax] = [] + + declarations.append(typeMethod(nodeType)) + declarations.append(codingKeys(bindings)) + declarations.append(initWithoutKeyMethod(bindings)) + declarations.append(initWithKeyMethod(bindings)) + declarations.append(cloneMethod(bindings)) + declarations.append(encodeMethod(bindings)) + declarations.append(decodeMethod(bindings)) + + for binding in bindings { + if !binding.name.hasPrefix("_") { + throw LexicalMacroError.requiresUnderscores + } + declarations.append(accessors(for: binding)) + } + + return declarations + } + + static func accessors(for binding: ParsedBinding) -> DeclSyntax { + return DeclSyntax(""" + public var \(raw: binding.publicName): \(raw: binding.type) { + return getLatest().\(raw: binding.name) + } + + public func get\(raw: binding.capitalizedPublicName)() -> \(raw: binding.type) { + return getLatest().\(raw: binding.name) + } + + public func set\(raw: binding.capitalizedPublicName)(_ val: \(raw: binding.type)) throws { + try getWritable().\(raw: binding.name) = val + } + + """) + } + + private static func paramList(_ bindings: [ParsedBinding]) -> String { + var builder = "" + for (i, binding) in bindings.enumerated() { + builder.append(binding.publicName) + builder.append(": ") + builder.append(binding.type) + builder.append(i < bindings.count - 1 ? ", " : "") + } + return builder + } + + private static func paramCall(_ bindings: [ParsedBinding]) -> String { + var builder = "" + for (i, binding) in bindings.enumerated() { + builder.append(binding.publicName) + builder.append(": ") + builder.append(binding.publicName) + builder.append(i < bindings.count - 1 ? ", " : "") + } + return builder + } + + private static func paramCallPrivate(_ bindings: [ParsedBinding]) -> String { + var builder = "" + for (i, binding) in bindings.enumerated() { + builder.append(binding.publicName) + builder.append(": ") + builder.append(binding.name) + builder.append(i < bindings.count - 1 ? ", " : "") + } + return builder + } + + static func initWithoutKeyMethod(_ bindings: [ParsedBinding] ) -> DeclSyntax { + return DeclSyntax(""" + public convenience init(\(raw: paramList(bindings))) { + self.init(\(raw: paramCall(bindings)), key: nil) + } + """) + } + + static func initWithKeyMethod(_ bindings: [ParsedBinding] ) -> DeclSyntax { + var builder = "" + for binding in bindings { + builder.append("self.\(binding.name) = \(binding.publicName)\n") + } + + return DeclSyntax(""" + public required init(\(raw: paramList(bindings)), key: NodeKey?) { + \(raw: builder) + super.init(key) + } + """) + } + + static func cloneMethod(_ bindings: [ParsedBinding] ) -> DeclSyntax { + return DeclSyntax(""" + override public func clone() -> Self { + Self(\(raw: paramCallPrivate(bindings)), key: key) + } + """) + } + + static func codingKeys(_ bindings: [ParsedBinding] ) -> DeclSyntax { + var items = "" + for binding in bindings { + items.append("case \(binding.publicName)\n") + } + + return DeclSyntax(""" + enum CodingKeys: String, CodingKey { + \(raw: items) + } + """) + } + + static func encodeMethod(_ bindings: [ParsedBinding] ) -> DeclSyntax { + var builder = "" + for binding in bindings { + builder.append("try container.encode(\(binding.name), forKey: .\(binding.publicName))\n") + } + + return DeclSyntax(""" + override public func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + \(raw: builder) + } + """) + } + + static func decodeMethod(_ bindings: [ParsedBinding] ) -> DeclSyntax { + var builder = "" + for binding in bindings { + builder.append("self.\(binding.name) = try container.decode(\(binding.type).self, forKey: .\(binding.publicName))\n") + } + + return DeclSyntax(""" + public required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + \(raw: builder) + try super.init(from: decoder) + } + """) + } + + static func typeMethod(_ type: String ) -> DeclSyntax { + return DeclSyntax(""" + open override class var type: NodeType { + .\(raw: type) + } + """) + } + +} + +@main +struct LexicalMacrosBase: CompilerPlugin { + let providingMacros: [Macro.Type] = [ + NodeMacro.self, + ] +} + +extension String { + var capitalizedFirstLetter: String { + let firstLetter = self.prefix(1).capitalized + let remainingLetters = self.dropFirst() + return firstLetter + remainingLetters + } +} diff --git a/Package.swift b/Package.swift index e2b2c16f..d8f74b40 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.6 +// swift-tools-version: 5.9 /* * Copyright (c) Meta Platforms, Inc. and affiliates. * @@ -7,10 +7,11 @@ */ import PackageDescription +import CompilerPluginSupport let package = Package( name: "Lexical", - platforms: [.iOS(.v13)], + platforms: [.macOS(.v10_15), .iOS(.v13)], products: [ .library( name: "Lexical", @@ -39,11 +40,21 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/scinfu/SwiftSoup.git", from: "2.6.0"), + .package(url: "https://github.com/apple/swift-syntax.git", from: "509.0.0-swift-5.9-DEVELOPMENT-SNAPSHOT-2023-04-25-b"), ], targets: [ + .macro( + name: "LexicalMacrosBase", + dependencies: [ + .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), + .product(name: "SwiftCompilerPlugin", package: "swift-syntax") + ], + path: "./LexicalMacrosBase" + ), + .target( name: "Lexical", - dependencies: [], + dependencies: ["LexicalMacrosBase"], path: "./Lexical"), .testTarget( name: "LexicalTests", diff --git a/Playground/LexicalPlayground.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Playground/LexicalPlayground.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index f71cd7f3..d39c649d 100644 --- a/Playground/LexicalPlayground.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Playground/LexicalPlayground.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,14 @@ { "pins" : [ + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-syntax.git", + "state" : { + "revision" : "67c5007099d9ffdd292f421f81f4efe5ee42963e", + "version" : "509.0.0-swift-DEVELOPMENT-SNAPSHOT-2023-07-10-a" + } + }, { "identity" : "swiftsoup", "kind" : "remoteSourceControl", diff --git a/Playground/LexicalPlayground/ToolbarPlugin.swift b/Playground/LexicalPlayground/ToolbarPlugin.swift index 610da3a7..d02c7a60 100644 --- a/Playground/LexicalPlayground/ToolbarPlugin.swift +++ b/Playground/LexicalPlayground/ToolbarPlugin.swift @@ -172,7 +172,7 @@ public class ToolbarPlugin: Plugin { // derive paragraph style if let heading = element as? HeadingNode { - if heading.getTag() == .h1 { + if heading.tag == .h1 { paragraphButton?.image = UIImage(named: "h1") paragraphMenuSelectedItem = .h1 } else { diff --git a/Plugins/LexicalHTML/LexicalHTML/CoreNodesHTMLSupport.swift b/Plugins/LexicalHTML/LexicalHTML/CoreNodesHTMLSupport.swift index 698e2527..d6da5dbb 100644 --- a/Plugins/LexicalHTML/LexicalHTML/CoreNodesHTMLSupport.swift +++ b/Plugins/LexicalHTML/LexicalHTML/CoreNodesHTMLSupport.swift @@ -92,7 +92,7 @@ extension Lexical.HeadingNode: NodeHTMLSupport { } public func exportDOM(editor: Lexical.Editor) throws -> DOMExportOutput { - let tag = self.getTag().rawValue + let tag = self.tag.rawValue let dom = SwiftSoup.Element(Tag(tag), "") return (after: nil, element: dom) } diff --git a/Plugins/LexicalInlineImagePlugin/LexicalInlineImagePlugin/Nodes/ImageNode.swift b/Plugins/LexicalInlineImagePlugin/LexicalInlineImagePlugin/Nodes/ImageNode.swift index 905395e0..dbff05c2 100644 --- a/Plugins/LexicalInlineImagePlugin/LexicalInlineImagePlugin/Nodes/ImageNode.swift +++ b/Plugins/LexicalInlineImagePlugin/LexicalInlineImagePlugin/Nodes/ImageNode.swift @@ -23,25 +23,24 @@ public class ImageNode: DecoratorNode { var size = CGSize.zero var sourceID: String = "" + open override class var type: NodeType { + .image + } + public required init(url: String, size: CGSize, sourceID: String, key: NodeKey? = nil) { super.init(key) self.url = URL(string: url) self.size = size - self.type = NodeType.image self.sourceID = sourceID } required init(_ key: NodeKey? = nil) { super.init(key) - - self.type = NodeType.image } public required init(from decoder: Decoder) throws { try super.init(from: decoder) - - self.type = NodeType.image } override public func encode(to encoder: Encoder) throws { diff --git a/Plugins/LexicalInlineImagePlugin/LexicalInlineImagePlugin/Nodes/SelectableImageNode.swift b/Plugins/LexicalInlineImagePlugin/LexicalInlineImagePlugin/Nodes/SelectableImageNode.swift index a51ab229..218937b1 100644 --- a/Plugins/LexicalInlineImagePlugin/LexicalInlineImagePlugin/Nodes/SelectableImageNode.swift +++ b/Plugins/LexicalInlineImagePlugin/LexicalInlineImagePlugin/Nodes/SelectableImageNode.swift @@ -20,25 +20,24 @@ public class SelectableImageNode: SelectableDecoratorNode { var size = CGSize.zero var sourceID: String = "" + open override class var type: NodeType { + .selectableImage + } + public required init(url: String, size: CGSize, sourceID: String, key: NodeKey? = nil) { super.init(key) self.url = URL(string: url) self.size = size - self.type = NodeType.image self.sourceID = sourceID } required init(_ key: NodeKey? = nil) { super.init(key) - - self.type = NodeType.image } public required init(from decoder: Decoder) throws { try super.init(from: decoder) - - self.type = NodeType.image } override public func encode(to encoder: Encoder) throws { diff --git a/Plugins/LexicalLinkPlugin/LexicalLinkPlugin/Nodes/LinkNode.swift b/Plugins/LexicalLinkPlugin/LexicalLinkPlugin/Nodes/LinkNode.swift index fbeb239f..6a4d5944 100644 --- a/Plugins/LexicalLinkPlugin/LexicalLinkPlugin/Nodes/LinkNode.swift +++ b/Plugins/LexicalLinkPlugin/LexicalLinkPlugin/Nodes/LinkNode.swift @@ -19,15 +19,17 @@ open class LinkNode: ElementNode { public var url: String = "" + open override class var type: NodeType { + .link + } + override public init() { super.init() - self.type = NodeType.link } public required init(url: String, key: NodeKey?) { super.init(key) self.url = url - self.type = NodeType.link } public required init(from decoder: Decoder) throws { @@ -35,7 +37,6 @@ open class LinkNode: ElementNode { try super.init(from: decoder) self.url = try container.decode(String.self, forKey: .url) - self.type = NodeType.link } override open func encode(to encoder: Encoder) throws { diff --git a/Plugins/LexicalListPlugin/LexicalListPlugin/ListItemNode.swift b/Plugins/LexicalListPlugin/LexicalListPlugin/ListItemNode.swift index 9ad53bef..9aa08dc1 100644 --- a/Plugins/LexicalListPlugin/LexicalListPlugin/ListItemNode.swift +++ b/Plugins/LexicalListPlugin/LexicalListPlugin/ListItemNode.swift @@ -17,19 +17,20 @@ public class ListItemNode: ElementNode { private var value: Int = 0 + open override class var type: NodeType { + .listItem + } + override public init() { super.init() - self.type = NodeType.listItem } override public required init(_ key: NodeKey?) { super.init(key) - self.type = NodeType.listItem } public required init(from decoder: Decoder) throws { try super.init(from: decoder) - self.type = NodeType.listItem } override open func encode(to encoder: Encoder) throws { diff --git a/Plugins/LexicalListPlugin/LexicalListPlugin/ListNode.swift b/Plugins/LexicalListPlugin/LexicalListPlugin/ListNode.swift index 82cbb237..dfcbbd53 100644 --- a/Plugins/LexicalListPlugin/LexicalListPlugin/ListNode.swift +++ b/Plugins/LexicalListPlugin/LexicalListPlugin/ListNode.swift @@ -8,7 +8,7 @@ import Foundation import Lexical -public enum ListType { +public enum ListType: String, Codable { case bullet case number case check @@ -18,60 +18,8 @@ extension NodeType { public static let list = NodeType(rawValue: "list") } +@LexicalNode(.list) public class ListNode: ElementNode { - private var listType: ListType = .bullet - private var start: Int = 1 - - public required convenience init(listType: ListType, start: Int, key: NodeKey? = nil) { - self.init(key) - self.listType = listType - self.start = start - } - - override public init() { - super.init() - self.type = NodeType.list - } - - override public init(_ key: NodeKey?) { - super.init(key) - self.type = NodeType.list - } - - public required init(from decoder: Decoder) throws { - try super.init(from: decoder) - self.type = NodeType.list - } - - // MARK: getters/setters - - public func getListType() -> ListType { - return getLatest().listType - } - - @discardableResult - public func setListType(_ type: ListType) throws -> ListNode { - let node: ListNode = try getWritable() - node.listType = type - return node - } - - public func getStart() -> Int { - return getLatest().start - } - - @discardableResult - public func setStart(_ start: Int) throws -> ListNode { - let node: ListNode = try getWritable() - node.start = start - return node - } - - override public func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - } - - override public func clone() -> Self { - Self(listType: listType, start: start, key: key) - } + private var _listType: ListType = .bullet + private var _start: Int = 1 }