From ec6e3c233230e91fd75838b15563ecd033797953 Mon Sep 17 00:00:00 2001 From: Johnson elangbam Date: Thu, 16 Dec 2021 18:00:59 +0530 Subject: [PATCH 1/8] Fix Li tag when switching the list style (issue #1307) --- Aztec/Classes/TextKit/TextView.swift | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Aztec/Classes/TextKit/TextView.swift b/Aztec/Classes/TextKit/TextView.swift index 186429c92..eaea30685 100644 --- a/Aztec/Classes/TextKit/TextView.swift +++ b/Aztec/Classes/TextKit/TextView.swift @@ -1105,8 +1105,12 @@ open class TextView: UITextView { toggle(formatter: formatter, atRange: range) let liFormatter = LiFormatter(placeholderAttributes: typingAttributes) - toggle(formatter: liFormatter, atRange: range) + let isOlTagPresent = formatter.present(in: storage, at: range) + let isLiTagPresent = liFormatter.present(in: storage, at: range) + if (!isOlTagPresent && isLiTagPresent) || (isOlTagPresent && !isLiTagPresent) { + toggle(formatter: liFormatter, atRange: range) + } forceRedrawCursorAfterDelay() } @@ -1122,8 +1126,12 @@ open class TextView: UITextView { toggle(formatter: formatter, atRange: range) let liFormatter = LiFormatter(placeholderAttributes: typingAttributes) - toggle(formatter: liFormatter, atRange: range) - + let isOlTagPresent = formatter.present(in: storage, at: range) + let isLiTagPresent = liFormatter.present(in: storage, at: range) + + if (!isOlTagPresent && isLiTagPresent) || (isOlTagPresent && !isLiTagPresent) { + toggle(formatter: liFormatter, atRange: range) + } forceRedrawCursorAfterDelay() } From 3936da525bb72963d02d174153cde7f47fcff102 Mon Sep 17 00:00:00 2001 From: "Tanner W. Stokes" Date: Mon, 11 Oct 2021 21:47:52 -0400 Subject: [PATCH 2/8] Add heading representation attribute if missing. --- Aztec/Classes/TextKit/TextStorage.swift | 38 ++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/Aztec/Classes/TextKit/TextStorage.swift b/Aztec/Classes/TextKit/TextStorage.swift index 5e9f63127..da3c1d310 100644 --- a/Aztec/Classes/TextKit/TextStorage.swift +++ b/Aztec/Classes/TextKit/TextStorage.swift @@ -145,8 +145,9 @@ open class TextStorage: NSTextStorage { private func preprocessAttributesForInsertion(_ attributedString: NSAttributedString) -> NSAttributedString { let stringWithAttachments = preprocessAttachmentsForInsertion(attributedString) + let preprocessedString = preprocessHeadingsForInsertion(stringWithAttachments) - return stringWithAttachments + return preprocessedString } /// Preprocesses an attributed string's attachments for insertion in the storage. @@ -211,6 +212,41 @@ open class TextStorage: NSTextStorage { return finalString } + /// Preprocesses an attributed string that is missing a `headingRepresentation` attribute for insertion in the storage. + /// + /// - Important: This method adds the `headingRepresentation` attribute if it determines the string should contain it. + /// This works around a problem where autocorrected text didn't contain the attribute. This may change in future versions. + /// + /// - Parameters: + /// - attributedString: the string we need to preprocess. + /// + /// - Returns: the preprocessed string. + /// + fileprivate func preprocessHeadingsForInsertion(_ attributedString: NSAttributedString) -> NSAttributedString { + // Ref. https://github.com/wordpress-mobile/AztecEditor-iOS/pull/1334 + + guard textStore.length > 0, attributedString.length > 0 else { + return attributedString + } + + // Get the attributes of the start of the current string in storage. + let currentAttrs = attributes(at: 0, effectiveRange: nil) + + guard + let headerSize = currentAttrs[.headingRepresentation], + attributedString.attribute(.headingRepresentation, at: 0, effectiveRange: nil) == nil + else { + // Either the heading attribute wasn't present in the existing string, + // or the attributed string already had it. + return attributedString + } + + let processedString = NSMutableAttributedString(attributedString: attributedString) + processedString.addAttribute(.headingRepresentation, value: headerSize, range: attributedString.rangeOfEntireString) + + return processedString + } + fileprivate func detectAttachmentRemoved(in range: NSRange) { // Ref. https://github.com/wordpress-mobile/AztecEditor-iOS/issues/727: // If the delegate is not set, we *Explicitly* do not want to crash here. From cce2da7a6465d8e0cdca737b847754121f6e13c6 Mon Sep 17 00:00:00 2001 From: "Tanner W. Stokes" Date: Fri, 28 Jan 2022 00:01:39 -0500 Subject: [PATCH 3/8] Add test for missing Heading attribute. --- AztecTests/TextKit/TextStorageTests.swift | 41 +++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/AztecTests/TextKit/TextStorageTests.swift b/AztecTests/TextKit/TextStorageTests.swift index c80a35a36..40b77079b 100644 --- a/AztecTests/TextKit/TextStorageTests.swift +++ b/AztecTests/TextKit/TextStorageTests.swift @@ -579,4 +579,45 @@ class TextStorageTests: XCTestCase { let result = storage.getHTML() XCTAssertEqual(expectedResult, result) } + + /// Verifies that missing Heading attributes are retained on string replacements + /// + func testMissingHeadingAttributeIsRetained() { + let defaultAttributes: [NSAttributedString.Key: Any] = [ + .font: UIFont.systemFont(ofSize: 14), + .paragraphStyle: ParagraphStyle.default, + .headingRepresentation: 2 + ] + + let headerString = NSAttributedString( + string: "Hello i'm a header", + attributes: defaultAttributes + ) + + storage.setAttributedString(headerString) + + let originalAttributes = storage.attributes(at: 0, effectiveRange: nil) + XCTAssertEqual(storage.string, "Hello i'm a header") + XCTAssertEqual(originalAttributes.count, 3) + XCTAssertNotNil(originalAttributes[.headingRepresentation]) + + // autocorrect seemed to strip custom keys, so remove .headingRepresentation + let autoCorrectedAttributes: [NSAttributedString.Key: Any] = [ + .font: UIFont.systemFont(ofSize: 14), + .paragraphStyle: ParagraphStyle.default + ] + + let autoCorrectedString = NSAttributedString( + string: "I'm", + attributes: autoCorrectedAttributes + ) + + let range = NSRange(location: 6, length: 3) + storage.replaceCharacters(in: range, with: autoCorrectedString) + + let finalAttributes = storage.attributes(at: range.location, effectiveRange: nil) + XCTAssertEqual(storage.string, "Hello I'm a header") + XCTAssertEqual(finalAttributes.count, 3) + XCTAssertNotNil(finalAttributes[.headingRepresentation]) + } } From 1aa8dd8a4008e74dd3911eb69f3c2c31bfe92792 Mon Sep 17 00:00:00 2001 From: Johnson Date: Fri, 28 Jan 2022 18:29:36 +0530 Subject: [PATCH 4/8] Refactoring: Addressing PR comments --- Aztec/Classes/TextKit/TextView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Aztec/Classes/TextKit/TextView.swift b/Aztec/Classes/TextKit/TextView.swift index eaea30685..c7f867001 100644 --- a/Aztec/Classes/TextKit/TextView.swift +++ b/Aztec/Classes/TextKit/TextView.swift @@ -1108,7 +1108,7 @@ open class TextView: UITextView { let isOlTagPresent = formatter.present(in: storage, at: range) let isLiTagPresent = liFormatter.present(in: storage, at: range) - if (!isOlTagPresent && isLiTagPresent) || (isOlTagPresent && !isLiTagPresent) { + if (isOlTagPresent != isLiTagPresent) { toggle(formatter: liFormatter, atRange: range) } forceRedrawCursorAfterDelay() @@ -1129,7 +1129,7 @@ open class TextView: UITextView { let isOlTagPresent = formatter.present(in: storage, at: range) let isLiTagPresent = liFormatter.present(in: storage, at: range) - if (!isOlTagPresent && isLiTagPresent) || (isOlTagPresent && !isLiTagPresent) { + if (isOlTagPresent != isLiTagPresent){ toggle(formatter: liFormatter, atRange: range) } forceRedrawCursorAfterDelay() From 5d4042d6dd6c2efaac8d29c3511a6ba3ea70b2f6 Mon Sep 17 00:00:00 2001 From: Johnson Date: Fri, 28 Jan 2022 18:36:20 +0530 Subject: [PATCH 5/8] Addressing PR comments --- Aztec/Classes/TextKit/TextView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Aztec/Classes/TextKit/TextView.swift b/Aztec/Classes/TextKit/TextView.swift index c7f867001..fd3321abf 100644 --- a/Aztec/Classes/TextKit/TextView.swift +++ b/Aztec/Classes/TextKit/TextView.swift @@ -1108,7 +1108,7 @@ open class TextView: UITextView { let isOlTagPresent = formatter.present(in: storage, at: range) let isLiTagPresent = liFormatter.present(in: storage, at: range) - if (isOlTagPresent != isLiTagPresent) { + if isOlTagPresent != isLiTagPresent { toggle(formatter: liFormatter, atRange: range) } forceRedrawCursorAfterDelay() @@ -1129,7 +1129,7 @@ open class TextView: UITextView { let isOlTagPresent = formatter.present(in: storage, at: range) let isLiTagPresent = liFormatter.present(in: storage, at: range) - if (isOlTagPresent != isLiTagPresent){ + if isOlTagPresent != isLiTagPresent { toggle(formatter: liFormatter, atRange: range) } forceRedrawCursorAfterDelay() From 8d72edcde64eb2a05175ae877fad97985153c26c Mon Sep 17 00:00:00 2001 From: "Tanner W. Stokes" Date: Thu, 3 Feb 2022 14:23:25 -0500 Subject: [PATCH 6/8] Account for Header to Paragraph conversions. --- Aztec/Classes/TextKit/TextStorage.swift | 8 +++- AztecTests/TextKit/TextStorageTests.swift | 53 ++++++++++++++--------- 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/Aztec/Classes/TextKit/TextStorage.swift b/Aztec/Classes/TextKit/TextStorage.swift index da3c1d310..3496b0122 100644 --- a/Aztec/Classes/TextKit/TextStorage.swift +++ b/Aztec/Classes/TextKit/TextStorage.swift @@ -233,8 +233,14 @@ open class TextStorage: NSTextStorage { let currentAttrs = attributes(at: 0, effectiveRange: nil) guard + // the text currently in storage has a headingRepresentation key let headerSize = currentAttrs[.headingRepresentation], - attributedString.attribute(.headingRepresentation, at: 0, effectiveRange: nil) == nil + // the text coming in doesn't have a headingRepresentation key + attributedString.attribute(.headingRepresentation, at: 0, effectiveRange: nil) == nil, + // the text coming in has a paragraph style attribute + let paragraphStyle = attributedString.attributes(at: 0, effectiveRange: nil)[.paragraphStyle] as? ParagraphStyle, + // the paragraph style contains a property that's a Header type + paragraphStyle.properties.contains(where: { $0 is Header }) else { // Either the heading attribute wasn't present in the existing string, // or the attributed string already had it. diff --git a/AztecTests/TextKit/TextStorageTests.swift b/AztecTests/TextKit/TextStorageTests.swift index 40b77079b..9847e2fcb 100644 --- a/AztecTests/TextKit/TextStorageTests.swift +++ b/AztecTests/TextKit/TextStorageTests.swift @@ -580,32 +580,19 @@ class TextStorageTests: XCTestCase { XCTAssertEqual(expectedResult, result) } - /// Verifies that missing Heading attributes are retained on string replacements + /// Verifies that missing Heading attributes are retained on string replacements when appropriate /// func testMissingHeadingAttributeIsRetained() { - let defaultAttributes: [NSAttributedString.Key: Any] = [ - .font: UIFont.systemFont(ofSize: 14), - .paragraphStyle: ParagraphStyle.default, - .headingRepresentation: 2 - ] - - let headerString = NSAttributedString( - string: "Hello i'm a header", - attributes: defaultAttributes - ) - - storage.setAttributedString(headerString) + let formatter = HeaderFormatter(headerLevel: .h2) + storage.replaceCharacters(in: storage.rangeOfEntireString, with: "Hello i'm a header") + formatter.applyAttributes(to: storage, at: storage.rangeOfEntireString) let originalAttributes = storage.attributes(at: 0, effectiveRange: nil) XCTAssertEqual(storage.string, "Hello i'm a header") XCTAssertEqual(originalAttributes.count, 3) XCTAssertNotNil(originalAttributes[.headingRepresentation]) - // autocorrect seemed to strip custom keys, so remove .headingRepresentation - let autoCorrectedAttributes: [NSAttributedString.Key: Any] = [ - .font: UIFont.systemFont(ofSize: 14), - .paragraphStyle: ParagraphStyle.default - ] + let autoCorrectedAttributes = originalAttributes.filter { $0.key != .headingRepresentation } let autoCorrectedString = NSAttributedString( string: "I'm", @@ -617,7 +604,33 @@ class TextStorageTests: XCTestCase { let finalAttributes = storage.attributes(at: range.location, effectiveRange: nil) XCTAssertEqual(storage.string, "Hello I'm a header") - XCTAssertEqual(finalAttributes.count, 3) - XCTAssertNotNil(finalAttributes[.headingRepresentation]) + XCTAssertEqual(originalAttributes.keys, finalAttributes.keys) + } + + /// Verifies that converting a Heading to a Paragraph doesn't retain the heading attribute + /// + func testHeadingToParagraphDoesNotRetainHeadingAttribute() { + let headerFormatter = HeaderFormatter(headerLevel: .h2) + storage.replaceCharacters(in: storage.rangeOfEntireString, with: "Hello I'm a header") + headerFormatter.applyAttributes(to: storage, at: storage.rangeOfEntireString) + + let originalAttributes = storage.attributes(at: 0, effectiveRange: nil) + XCTAssertEqual(storage.string, "Hello I'm a header") + XCTAssertNotNil(originalAttributes[.headingRepresentation]) + + let paragraphAttributes: [NSAttributedString.Key: Any] = [ + .font: UIFont.systemFont(ofSize: 14), + .paragraphStyle: ParagraphStyle.default + ] + + let paragraphString = NSAttributedString( + string: "Hello I'm a paragraph", + attributes: paragraphAttributes + ) + storage.replaceCharacters(in: storage.rangeOfEntireString, with: paragraphString) + + let finalAttributes = storage.attributes(at: 0, effectiveRange: nil) + XCTAssertEqual(storage.string, "Hello I'm a paragraph") + XCTAssertNil(finalAttributes[.headingRepresentation]) } } From 79b196fd51241d41b18792fd32baf991624bda6f Mon Sep 17 00:00:00 2001 From: Tanner Stokes Date: Tue, 8 Feb 2022 10:42:47 -0500 Subject: [PATCH 7/8] Update Changelog and bump version. --- CHANGELOG.md | 4 ++++ WordPress-Aztec-iOS.podspec | 2 +- WordPress-Editor-iOS.podspec | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4c74771e..b0867ec08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +1.19.8 +------- +* Retain Heading attribute when headings are autocorrected. + 1.19.7 ------- * Add variable to control whether typing attributes should be recalculated when deleting backward. diff --git a/WordPress-Aztec-iOS.podspec b/WordPress-Aztec-iOS.podspec index 8eacfefaa..b0e0e60a0 100644 --- a/WordPress-Aztec-iOS.podspec +++ b/WordPress-Aztec-iOS.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'WordPress-Aztec-iOS' - s.version = '1.19.7' + s.version = '1.19.8' s.summary = 'The native HTML Editor.' s.description = <<-DESC diff --git a/WordPress-Editor-iOS.podspec b/WordPress-Editor-iOS.podspec index 729a5c3b1..52fd2f2ee 100644 --- a/WordPress-Editor-iOS.podspec +++ b/WordPress-Editor-iOS.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'WordPress-Editor-iOS' - s.version = '1.19.7' + s.version = '1.19.8' s.summary = 'The WordPress HTML Editor.' s.description = <<-DESC From 3b9119816541b037dcf9abaef346fc50941dc1c9 Mon Sep 17 00:00:00 2001 From: "Tanner W. Stokes" Date: Tue, 8 Feb 2022 10:55:13 -0500 Subject: [PATCH 8/8] Update changelog to include list fix. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0867ec08..66e08e7e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ 1.19.8 ------- +* Fix Li tag when switching the list style. * Retain Heading attribute when headings are autocorrected. 1.19.7