From 18f0e21993c9451c90a4c3d59cc9ca77f3a6515f Mon Sep 17 00:00:00 2001 From: Sergio Estevao Date: Mon, 8 May 2017 22:00:15 +0100 Subject: [PATCH 1/7] Just change textList to and array of textLists. --- .../Extensions/NSAttributedString+Lists.swift | 10 +++---- .../Formatters/TextListFormatter.swift | 17 ++++++----- Aztec/Classes/TextKit/LayoutManager.swift | 2 +- Aztec/Classes/TextKit/ParagraphStyle.swift | 30 ++++++++----------- Aztec/Classes/TextKit/TextStorage.swift | 2 +- 5 files changed, 30 insertions(+), 31 deletions(-) diff --git a/Aztec/Classes/Extensions/NSAttributedString+Lists.swift b/Aztec/Classes/Extensions/NSAttributedString+Lists.swift index 1d2b2f78c..84e2121c5 100644 --- a/Aztec/Classes/Extensions/NSAttributedString+Lists.swift +++ b/Aztec/Classes/Extensions/NSAttributedString+Lists.swift @@ -66,7 +66,7 @@ extension NSAttributedString let targetRange = rangeOfEntireString guard let paragraphStyle = attribute(NSParagraphStyleAttributeName, at: location, longestEffectiveRange: &effectiveRange, in: targetRange) as? ParagraphStyle, - let foundList = paragraphStyle.textList, + let foundList = paragraphStyle.textLists.last, foundList == list else { return nil @@ -78,7 +78,7 @@ extension NSAttributedString while resultRange.location > 0 { if let paragraphStyle = attribute(NSParagraphStyleAttributeName, at: resultRange.location-1, longestEffectiveRange: &effectiveRange, in: targetRange) as? ParagraphStyle, - let foundList = paragraphStyle.textList, + let foundList = paragraphStyle.textLists.last, foundList == list { resultRange = resultRange.union(withRange: effectiveRange) } else { @@ -88,7 +88,7 @@ extension NSAttributedString while resultRange.endLocation < self.length { if let paragraphStyle = attribute(NSParagraphStyleAttributeName, at: resultRange.endLocation, longestEffectiveRange: &effectiveRange, in: targetRange) as? ParagraphStyle, - let foundList = paragraphStyle.textList, + let foundList = paragraphStyle.textLists.last, foundList == list { resultRange = resultRange.union(withRange: effectiveRange) } else { @@ -177,7 +177,7 @@ extension NSAttributedString /// - Returns: A TextList optional. /// func textListAttribute(atIndex index: Int) -> TextList? { - return (attribute(NSParagraphStyleAttributeName, at: index, effectiveRange: nil) as? ParagraphStyle)?.textList + return (attribute(NSParagraphStyleAttributeName, at: index, effectiveRange: nil) as? ParagraphStyle)?.textLists.last } /// Returns the TextList attribute, assuming that there is one, spanning the specified Range. @@ -196,7 +196,7 @@ extension NSAttributedString enumerateAttribute(NSParagraphStyleAttributeName, in: range, options: []) { (attribute, range, stop) in if let paragraphStyle = attribute as? ParagraphStyle { - list = paragraphStyle.textList + list = paragraphStyle.textLists.last } stop.pointee = true } diff --git a/Aztec/Classes/Formatters/TextListFormatter.swift b/Aztec/Classes/Formatters/TextListFormatter.swift index 0db9e6844..f9286a797 100644 --- a/Aztec/Classes/Formatters/TextListFormatter.swift +++ b/Aztec/Classes/Formatters/TextListFormatter.swift @@ -31,12 +31,12 @@ class TextListFormatter: ParagraphAttributeFormatter { newParagraphStyle.setParagraphStyle(paragraphStyle) } - if newParagraphStyle.textList == nil { + if newParagraphStyle.textLists.last == nil { newParagraphStyle.headIndent += Metrics.listTextIndentation newParagraphStyle.firstLineHeadIndent += Metrics.listTextIndentation } - newParagraphStyle.textList = TextList(style: self.listStyle) + newParagraphStyle.textLists.append(TextList(style: self.listStyle)) var resultingAttributes = attributes resultingAttributes[NSParagraphStyleAttributeName] = newParagraphStyle @@ -46,7 +46,8 @@ class TextListFormatter: ParagraphAttributeFormatter { func remove(from attributes: [String: Any]) -> [String: Any] { guard let paragraphStyle = attributes[NSParagraphStyleAttributeName] as? ParagraphStyle, - paragraphStyle.textList?.style == self.listStyle + let currentList = paragraphStyle.textLists.last, + currentList.style == self.listStyle else { return attributes } @@ -55,7 +56,7 @@ class TextListFormatter: ParagraphAttributeFormatter { newParagraphStyle.setParagraphStyle(paragraphStyle) newParagraphStyle.headIndent -= Metrics.listTextIndentation newParagraphStyle.firstLineHeadIndent -= Metrics.listTextIndentation - newParagraphStyle.textList = nil + newParagraphStyle.textLists.removeLast() var resultingAttributes = attributes resultingAttributes[NSParagraphStyleAttributeName] = newParagraphStyle @@ -64,7 +65,7 @@ class TextListFormatter: ParagraphAttributeFormatter { } func present(in attributes: [String : Any]) -> Bool { - guard let style = attributes[NSParagraphStyleAttributeName] as? ParagraphStyle, let list = style.textList else { + guard let style = attributes[NSParagraphStyleAttributeName] as? ParagraphStyle, let list = style.textLists.last else { return false } @@ -72,8 +73,10 @@ class TextListFormatter: ParagraphAttributeFormatter { } static func listsOfAnyKindPresent(in attributes: [String: Any]) -> Bool { - let style = attributes[NSParagraphStyleAttributeName] as? ParagraphStyle - return style?.textList != nil + guard let style = attributes[NSParagraphStyleAttributeName] as? ParagraphStyle else { + return false + } + return !(style.textLists.isEmpty) } } diff --git a/Aztec/Classes/TextKit/LayoutManager.swift b/Aztec/Classes/TextKit/LayoutManager.swift index 38ecd522c..e41d0a1a9 100644 --- a/Aztec/Classes/TextKit/LayoutManager.swift +++ b/Aztec/Classes/TextKit/LayoutManager.swift @@ -85,7 +85,7 @@ private extension LayoutManager { let characterRange = self.characterRange(forGlyphRange: glyphsToShow, actualGlyphRange: nil) textStorage.enumerateAttribute(NSParagraphStyleAttributeName, in: characterRange, options: []) { (object, range, stop) in - guard let paragraphStyle = object as? ParagraphStyle, let list = paragraphStyle.textList else { + guard let paragraphStyle = object as? ParagraphStyle, let list = paragraphStyle.textLists.last else { return } diff --git a/Aztec/Classes/TextKit/ParagraphStyle.swift b/Aztec/Classes/TextKit/ParagraphStyle.swift index b20018ca9..f3fe965d4 100644 --- a/Aztec/Classes/TextKit/ParagraphStyle.swift +++ b/Aztec/Classes/TextKit/ParagraphStyle.swift @@ -7,7 +7,7 @@ open class ParagraphStyle: NSMutableParagraphStyle, CustomReflectable { public var customMirror: Mirror { get { - return Mirror(self, children: ["blockquote": blockquote as Any, "headerLevel": headerLevel, "htmlParagraph": htmlParagraph as Any, "textList": textList as Any]) + return Mirror(self, children: ["blockquote": blockquote as Any, "headerLevel": headerLevel, "htmlParagraph": htmlParagraph as Any, "textList": textLists as Any]) } } @@ -17,7 +17,7 @@ open class ParagraphStyle: NSMutableParagraphStyle, CustomReflectable { var blockquote: Blockquote? var htmlParagraph: HTMLParagraph? - var textList: TextList? + var textLists: [TextList] = [] var headerLevel: Int = 0 @@ -26,11 +26,8 @@ open class ParagraphStyle: NSMutableParagraphStyle, CustomReflectable { } public required init?(coder aDecoder: NSCoder) { - if aDecoder.containsValue(forKey: String(describing: TextList.self)) { - let styleRaw = aDecoder.decodeInteger(forKey: String(describing: TextList.self)) - if let style = TextList.Style(rawValue:styleRaw) { - textList = TextList(style: style) - } + if let encodedLists = aDecoder.decodeObject(forKey:String(describing: TextList.self)) as? [TextList] { + textLists = encodedLists } if aDecoder.containsValue(forKey: String(describing: Blockquote.self)) { blockquote = aDecoder.decodeObject(forKey: String(describing: Blockquote.self)) as? Blockquote @@ -43,9 +40,8 @@ open class ParagraphStyle: NSMutableParagraphStyle, CustomReflectable { override open func encode(with aCoder: NSCoder) { super.encode(with: aCoder) - if let textListSet = textList { - aCoder.encode(textListSet.style.rawValue, forKey: String(describing: TextList.self)) - } + + aCoder.encode(textLists, forKey: String(describing: TextList.self)) if let blockquote = self.blockquote { aCoder.encode(blockquote, forKey: String(describing: Blockquote.self)) @@ -60,7 +56,7 @@ open class ParagraphStyle: NSMutableParagraphStyle, CustomReflectable { blockquote = paragrahStyle.blockquote headerLevel = paragrahStyle.headerLevel htmlParagraph = paragrahStyle.htmlParagraph - textList = paragrahStyle.textList + textLists = paragrahStyle.textLists } } @@ -89,7 +85,7 @@ open class ParagraphStyle: NSMutableParagraphStyle, CustomReflectable { if blockquote != otherParagraph.blockquote || headerLevel != otherParagraph.headerLevel || htmlParagraph != otherParagraph.htmlParagraph - || textList != otherParagraph.textList { + || textLists != otherParagraph.textLists { return false } @@ -107,7 +103,7 @@ open class ParagraphStyle: NSMutableParagraphStyle, CustomReflectable { copy.blockquote = blockquote copy.headerLevel = headerLevel copy.htmlParagraph = htmlParagraph - copy.textList = textList + copy.textLists = textLists return copy } @@ -119,7 +115,7 @@ open class ParagraphStyle: NSMutableParagraphStyle, CustomReflectable { copy.blockquote = blockquote copy.headerLevel = headerLevel copy.htmlParagraph = htmlParagraph - copy.textList = textList + copy.textLists = textLists return copy } @@ -129,8 +125,8 @@ open class ParagraphStyle: NSMutableParagraphStyle, CustomReflectable { if blockquote != nil { hash = hash ^ String(describing: Blockquote.self).hashValue } - if let listStyle = textList?.style { - hash = hash ^ listStyle.hashValue + for list in textLists { + hash = hash ^ list.style.hashValue } hash = hash ^ headerLevel.hashValue @@ -142,6 +138,6 @@ open class ParagraphStyle: NSMutableParagraphStyle, CustomReflectable { } open override var description: String { - return super.description + " Blockquote: \(String(describing:blockquote)),\n HeaderLevel: \(headerLevel),\n HTMLParagraph: \(String(describing: htmlParagraph)),\n TextList: \(String(describing: textList?.style))" + return super.description + " Blockquote: \(String(describing:blockquote)),\n HeaderLevel: \(headerLevel),\n HTMLParagraph: \(String(describing: htmlParagraph)),\n TextLists: \(textLists)" } } diff --git a/Aztec/Classes/TextKit/TextStorage.swift b/Aztec/Classes/TextKit/TextStorage.swift index 3ef2284be..6e19b8fc5 100644 --- a/Aztec/Classes/TextKit/TextStorage.swift +++ b/Aztec/Classes/TextKit/TextStorage.swift @@ -504,7 +504,7 @@ open class TextStorage: NSTextStorage { let targetStyle = targetValue as? ParagraphStyle processBlockquoteDifferences(in: domRange, betweenOriginal: sourceStyle?.blockquote, andNew: targetStyle?.blockquote) - processListDifferences(in: domRange, betweenOriginal: sourceStyle?.textList, andNew: targetStyle?.textList, canMergeLeft: canMergeLeft, canMergeRight: canMergeRight) + processListDifferences(in: domRange, betweenOriginal: sourceStyle?.textLists.last, andNew: targetStyle?.textLists.last, canMergeLeft: canMergeLeft, canMergeRight: canMergeRight) processHeaderDifferences(in: domRange, betweenOriginal: sourceStyle?.headerLevel, andNew: targetStyle?.headerLevel) processHTMLParagraphDifferences(in: domRange, betweenOriginal: sourceStyle?.htmlParagraph, andNew: targetStyle?.htmlParagraph) case NSLinkAttributeName: From f3cd5fc8a6af555202bd05ef91ada6c4645b92a9 Mon Sep 17 00:00:00 2001 From: Sergio Estevao Date: Mon, 8 May 2017 22:09:06 +0100 Subject: [PATCH 2/7] Fix unit tests. --- AztecTests/Extensions/NSAttributedStringListsTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AztecTests/Extensions/NSAttributedStringListsTests.swift b/AztecTests/Extensions/NSAttributedStringListsTests.swift index a953a6d12..fcf606c97 100644 --- a/AztecTests/Extensions/NSAttributedStringListsTests.swift +++ b/AztecTests/Extensions/NSAttributedStringListsTests.swift @@ -503,7 +503,7 @@ extension NSAttributedStringListsTests let range = (sample.string as NSString).range(of: sampleListContents) let listParagraphStyle = ParagraphStyle() - listParagraphStyle.textList = TextList(style: .ordered) + listParagraphStyle.textLists.append(TextList(style: .ordered)) let attributes = [NSParagraphStyleAttributeName: listParagraphStyle] sample.addAttributes(attributes, range: range) From 8b94730628ac4ae57c2a6c897567ee61dccf2c48 Mon Sep 17 00:00:00 2001 From: Sergio Estevao Date: Mon, 8 May 2017 22:10:49 +0100 Subject: [PATCH 3/7] Add sample content. --- Example/Example/SampleContent/content.html | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Example/Example/SampleContent/content.html b/Example/Example/SampleContent/content.html index 364c112d0..74f772c89 100644 --- a/Example/Example/SampleContent/content.html +++ b/Example/Example/SampleContent/content.html @@ -38,6 +38,15 @@

Ordered List:

  • Two
  • Three
  • + +

    Nested lists:

    +
      +
    1. One
    2. +
      1. One
      2. Two
      +
    3. Two
    4. +
      • One
      • Two
      +
    5. Three
    6. +


    From a7bd0b4c4d7e3e3aa7eed1868874f49c4ab7d0ef Mon Sep 17 00:00:00 2001 From: Sergio Estevao Date: Mon, 8 May 2017 22:15:25 +0100 Subject: [PATCH 4/7] All multiple indentations on the same paragraph. --- Aztec/Classes/Formatters/TextListFormatter.swift | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Aztec/Classes/Formatters/TextListFormatter.swift b/Aztec/Classes/Formatters/TextListFormatter.swift index f9286a797..01cef2f2d 100644 --- a/Aztec/Classes/Formatters/TextListFormatter.swift +++ b/Aztec/Classes/Formatters/TextListFormatter.swift @@ -31,10 +31,8 @@ class TextListFormatter: ParagraphAttributeFormatter { newParagraphStyle.setParagraphStyle(paragraphStyle) } - if newParagraphStyle.textLists.last == nil { - newParagraphStyle.headIndent += Metrics.listTextIndentation - newParagraphStyle.firstLineHeadIndent += Metrics.listTextIndentation - } + newParagraphStyle.headIndent += Metrics.listTextIndentation + newParagraphStyle.firstLineHeadIndent += Metrics.listTextIndentation newParagraphStyle.textLists.append(TextList(style: self.listStyle)) From 60a1b3b381bd5e031c5aac4aac90616d9c3ba84e Mon Sep 17 00:00:00 2001 From: Sergio Estevao Date: Mon, 8 May 2017 23:18:05 +0100 Subject: [PATCH 5/7] Refactor the list detection algorithms for markers and position to support nested lists. --- .../Extensions/NSAttributedString+Lists.swift | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/Aztec/Classes/Extensions/NSAttributedString+Lists.swift b/Aztec/Classes/Extensions/NSAttributedString+Lists.swift index 84e2121c5..9f6f1f735 100644 --- a/Aztec/Classes/Extensions/NSAttributedString+Lists.swift +++ b/Aztec/Classes/Extensions/NSAttributedString+Lists.swift @@ -71,25 +71,35 @@ extension NSAttributedString else { return nil } + let listDepth = paragraphStyle.textLists.count + var resultRange = effectiveRange //Note: The effective range will only return the range of the in location NSParagraphStyleAttributed // but this can be different on preceding or suceeding range but is the same TextList, // so we need to expand the range to grab all the TextList coverage. while resultRange.location > 0 { - if + guard let paragraphStyle = attribute(NSParagraphStyleAttributeName, at: resultRange.location-1, longestEffectiveRange: &effectiveRange, in: targetRange) as? ParagraphStyle, - let foundList = paragraphStyle.textLists.last, - foundList == list { - resultRange = resultRange.union(withRange: effectiveRange) + let foundList = paragraphStyle.textLists.last + else { + break; + } + if ((listDepth == paragraphStyle.textLists.count && foundList == list) || + listDepth < paragraphStyle.textLists.count) { + resultRange = resultRange.union(withRange: effectiveRange) } else { break; } } while resultRange.endLocation < self.length { - if + guard let paragraphStyle = attribute(NSParagraphStyleAttributeName, at: resultRange.endLocation, longestEffectiveRange: &effectiveRange, in: targetRange) as? ParagraphStyle, - let foundList = paragraphStyle.textLists.last, - foundList == list { + let foundList = paragraphStyle.textLists.last + else { + break; + } + if ((listDepth == paragraphStyle.textLists.count && foundList == list) || + listDepth < paragraphStyle.textLists.count) { resultRange = resultRange.union(withRange: effectiveRange) } else { break; @@ -108,6 +118,12 @@ extension NSAttributedString /// - Returns: Returns the index within the list. /// func itemNumber(in list: TextList, at location: Int) -> Int { + guard + let paragraphStyle = attribute(NSParagraphStyleAttributeName, at: location, effectiveRange: nil) as? ParagraphStyle + else { + return NSNotFound + } + let listDepth = paragraphStyle.textLists.count guard let rangeOfList = range(of:list, at: location) else { return NSNotFound } @@ -118,7 +134,10 @@ extension NSAttributedString if NSLocationInRange(location, range) { return numberInList } - numberInList += 1 + if let paragraphStyle = attribute(NSParagraphStyleAttributeName, at: range.location, effectiveRange: nil) as? ParagraphStyle, + listDepth == paragraphStyle.textLists.count { + numberInList += 1 + } } return NSNotFound } From 67965ac1845bac10d946c34490e98dec725529f6 Mon Sep 17 00:00:00 2001 From: Sergio Estevao Date: Mon, 8 May 2017 23:18:31 +0100 Subject: [PATCH 6/7] Fix the sample content to be proper nested lists html. --- Example/Example/SampleContent/content.html | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Example/Example/SampleContent/content.html b/Example/Example/SampleContent/content.html index 74f772c89..6069db6a5 100644 --- a/Example/Example/SampleContent/content.html +++ b/Example/Example/SampleContent/content.html @@ -41,10 +41,18 @@

    Ordered List:

    Nested lists:

      -
    1. One
    2. -
      1. One
      2. Two
      -
    3. Two
    4. -
      • One
      • Two
      +
    5. One +
        +
      1. One
      2. +
      3. Two
      4. +
      +
    6. +
    7. Two +
        +
      • One
      • +
      • Two
      • +
      +
    8. Three

    From d6387a2411565d4ba12a1008fe470265b95f16e8 Mon Sep 17 00:00:00 2001 From: Sergio Estevao Date: Tue, 9 May 2017 10:49:43 +0100 Subject: [PATCH 7/7] Support list formatter modes for increasing or maintaining depth of lists. --- .../HTMLNodeToNSAttributedString.swift | 4 ++-- .../Classes/Formatters/TextListFormatter.swift | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Aztec/Classes/Converters/HTMLNodeToNSAttributedString.swift b/Aztec/Classes/Converters/HTMLNodeToNSAttributedString.swift index 0c6b5b229..1d85675fd 100644 --- a/Aztec/Classes/Converters/HTMLNodeToNSAttributedString.swift +++ b/Aztec/Classes/Converters/HTMLNodeToNSAttributedString.swift @@ -275,8 +275,8 @@ class HMTLNodeToNSAttributedString: SafeConverter { } public let elementToFormattersMap: [StandardElementType: AttributeFormatter] = [ - .ol: TextListFormatter(style: .ordered), - .ul: TextListFormatter(style: .unordered), + .ol: TextListFormatter(style: .ordered, increaseDepth: true), + .ul: TextListFormatter(style: .unordered, increaseDepth: true), .blockquote: BlockquoteFormatter(), .strong: BoldFormatter(), .em: ItalicFormatter(), diff --git a/Aztec/Classes/Formatters/TextListFormatter.swift b/Aztec/Classes/Formatters/TextListFormatter.swift index 01cef2f2d..3eb5d6b92 100644 --- a/Aztec/Classes/Formatters/TextListFormatter.swift +++ b/Aztec/Classes/Formatters/TextListFormatter.swift @@ -14,12 +14,15 @@ class TextListFormatter: ParagraphAttributeFormatter { /// let placeholderAttributes: [String : Any]? + /// Tells if the formatter is increasing the depth of a list or simple changing the current one if any + let increaseDepth: Bool /// Designated Initializer /// - init(style: TextList.Style, placeholderAttributes: [String : Any]? = nil) { + init(style: TextList.Style, placeholderAttributes: [String : Any]? = nil, increaseDepth: Bool = false) { self.listStyle = style self.placeholderAttributes = placeholderAttributes + self.increaseDepth = increaseDepth } @@ -31,10 +34,14 @@ class TextListFormatter: ParagraphAttributeFormatter { newParagraphStyle.setParagraphStyle(paragraphStyle) } - newParagraphStyle.headIndent += Metrics.listTextIndentation - newParagraphStyle.firstLineHeadIndent += Metrics.listTextIndentation - - newParagraphStyle.textLists.append(TextList(style: self.listStyle)) + if (increaseDepth || newParagraphStyle.textLists.isEmpty) { + newParagraphStyle.headIndent += Metrics.listTextIndentation + newParagraphStyle.firstLineHeadIndent += Metrics.listTextIndentation + newParagraphStyle.textLists.append(TextList(style: self.listStyle)) + } else { + newParagraphStyle.textLists.removeLast() + newParagraphStyle.textLists.append(TextList(style: self.listStyle)) + } var resultingAttributes = attributes resultingAttributes[NSParagraphStyleAttributeName] = newParagraphStyle