diff --git a/Aztec/Classes/Constants/Metrics.swift b/Aztec/Classes/Constants/Metrics.swift index 146e14042..ca632cced 100644 --- a/Aztec/Classes/Constants/Metrics.swift +++ b/Aztec/Classes/Constants/Metrics.swift @@ -8,8 +8,10 @@ public enum Metrics { public static var defaultIndentation = CGFloat(12) public static var maxIndentation = CGFloat(200) - public static var listTextIndentation = CGFloat(16) - public static var tabStepInterval = 8 + public static var listTextIndentation = CGFloat(12) + public static var listTextCharIndentation = CGFloat(8) + public static var listMinimumIndentChars = 3 + public static var tabStepInterval = 4 public static var tabStepCount = 12 public static var paragraphSpacing = CGFloat(6) } diff --git a/Aztec/Classes/Libxml2/DOM/Data/Element.swift b/Aztec/Classes/Libxml2/DOM/Data/Element.swift index 09acd1410..2f208af83 100644 --- a/Aztec/Classes/Libxml2/DOM/Data/Element.swift +++ b/Aztec/Classes/Libxml2/DOM/Data/Element.swift @@ -30,7 +30,7 @@ public struct Element: RawRepresentable, Hashable { public static var mergeableBlockLevelElements = Set([.blockquote, .div, .figure, .figcaption, .h1, .h2, .h3, .h4, .h5, .h6, .hr, .li, .ol, .ul, .p, .pre]) /// List of style HTML elements that can be merged together when they are sibling to each other - public static var mergeableStyleElements = Set([.i, .em, .b, .strong, .strike, .u, .code, .cite]) + public static var mergeableStyleElements = Set([.i, .em, .b, .strong, .strike, .u, .code, .cite, .a]) /// List of block level elements that can be merged but only when they have a single children that is also mergeable /// diff --git a/Aztec/Classes/TextKit/LayoutManager.swift b/Aztec/Classes/TextKit/LayoutManager.swift index 7a5e4467e..5ffdca08a 100644 --- a/Aztec/Classes/TextKit/LayoutManager.swift +++ b/Aztec/Classes/TextKit/LayoutManager.swift @@ -67,7 +67,7 @@ private extension LayoutManager { enumerateLineFragments(forGlyphRange: blockquoteGlyphRange) { (rect, usedRect, textContainer, glyphRange, stop) in - let startIndent = paragraphStyle.blockquoteIndent + let startIndent = paragraphStyle.indentToFirst(Blockquote.self) - Metrics.listTextIndentation let lineRange = self.characterRange(forGlyphRange: glyphRange, actualGlyphRange: nil) let lineCharacters = textStorage.attributedSubstring(from: lineRange).string @@ -78,13 +78,12 @@ private extension LayoutManager { let nestDepth = paragraphStyle.blockquoteNestDepth for index in 0...nestDepth { - let indent = startIndent + CGFloat(index) * Metrics.listTextIndentation + let indent = paragraphStyle.indent(to: index, of: Blockquote.self) - Metrics.listTextIndentation - let nestRect = self.blockquoteRect(origin: origin, lineRect: rect, blockquoteIndent: indent, lineEndsParagraph: lineEndsParagraph) + let nestRect = self.blockquoteRect(origin: origin, lineRect: rect, blockquoteIndent: indent, lineEndsParagraph: lineEndsParagraph) - self.drawBlockquoteBorder(in: nestRect.integral, with: context, at: index) + self.drawBlockquoteBorder(in: nestRect.integral, with: context, at: index) } - } } @@ -100,7 +99,7 @@ private extension LayoutManager { return } - let extraIndent = paragraphStyle.blockquoteIndent + let extraIndent = paragraphStyle.indentToLast(Blockquote.self) let extraRect = blockquoteRect(origin: origin, lineRect: extraLineFragmentRect, blockquoteIndent: extraIndent, lineEndsParagraph: false) drawBlockquoteBackground(in: extraRect.integral, with: context) @@ -121,11 +120,7 @@ private extension LayoutManager { private func blockquoteRect(origin: CGPoint, lineRect: CGRect, blockquoteIndent: CGFloat, lineEndsParagraph: Bool) -> CGRect { var blockquoteRect = lineRect.offsetBy(dx: origin.x, dy: origin.y) - guard blockquoteIndent != 0 else { - return blockquoteRect - } - - let paddingWidth = Metrics.listTextIndentation * 0.5 + blockquoteIndent + let paddingWidth = blockquoteIndent blockquoteRect.origin.x += paddingWidth blockquoteRect.size.width -= paddingWidth @@ -228,7 +223,7 @@ private extension LayoutManager { else { return } - + let attributes = textStorage.attributes(at: enclosingRange.location, effectiveRange: nil) let glyphRange = self.glyphRange(forCharacterRange: enclosingRange, actualCharacterRange: nil) let markerRect = rectForItem(range: glyphRange, origin: origin, paragraphStyle: paragraphStyle) var markerNumber = textStorage.itemNumber(in: list, at: enclosingRange.location) @@ -240,8 +235,8 @@ private extension LayoutManager { } } markerNumber += start - - drawItem(number: markerNumber, in: markerRect, from: list, using: paragraphStyle, at: enclosingRange.location) + let markerString = list.style.markerText(forItemNumber: markerNumber) + drawItem(markerString, in: markerRect, styled: attributes, at: enclosingRange.location) } } @@ -278,31 +273,36 @@ private extension LayoutManager { /// Draws the specified List Item Number, at a given location. /// /// - Parameters: - /// - number: Marker Number of the item to be drawn + /// - markerText: Marker String of the item to be drawn /// - rect: Visible Rect in which the Marker should be rendered - /// - list: Associated TextList - /// - style: ParagraphStyle associated to the list + /// - styled: Paragraph attributes associated to the list /// - location: Text Location that should get the marker rendered. /// - private func drawItem(number: Int, in rect: CGRect, from list: TextList, using style: ParagraphStyle, at location: Int) { - guard let textStorage = textStorage else { + private func drawItem(_ markerText: String, in rect: CGRect, styled paragraphAttributes: [NSAttributedString.Key: Any], at location: Int) { + guard let style = paragraphAttributes[.paragraphStyle] as? ParagraphStyle else { return } - - let paragraphAttributes = textStorage.attributes(at: location, effectiveRange: nil) let markerAttributes = markerAttributesBasedOnParagraph(attributes: paragraphAttributes) - - let markerPlain = list.style.markerText(forItemNumber: number) - let markerText = NSAttributedString(string: markerPlain, attributes: markerAttributes) + let markerAttributedText = NSAttributedString(string: markerText, attributes: markerAttributes) var yOffset = CGFloat(0) + var xOffset = CGFloat(0) + let indentWidth = style.indentToLast(TextList.self) + let markerWidth = markerAttributedText.size().width * 1.5 if location > 0 { yOffset += style.paragraphSpacingBefore } + // If the marker width is larger than the indent available let's offset the area to draw to the left + if markerWidth > indentWidth { + xOffset = indentWidth - markerWidth + } + + var markerRect = rect.offsetBy(dx: xOffset, dy: yOffset) + + markerRect.size.width = max(indentWidth, markerWidth) - let markerRect = rect.offsetBy(dx: 0, dy: yOffset) - markerText.draw(in: markerRect) + markerAttributedText.draw(in: markerRect) } @@ -310,10 +310,7 @@ private extension LayoutManager { /// private func markerAttributesBasedOnParagraph(attributes: [NSAttributedString.Key: Any]) -> [NSAttributedString.Key: Any] { var resultAttributes = attributes - var indent: CGFloat = 0 - if let style = attributes[.paragraphStyle] as? ParagraphStyle { - indent = style.listIndent + Metrics.listTextIndentation - (Metrics.listTextIndentation / 4) - } + let indent: CGFloat = CGFloat(Metrics.tabStepInterval) resultAttributes[.paragraphStyle] = markerParagraphStyle(indent: indent) resultAttributes.removeValue(forKey: .underlineStyle) @@ -328,13 +325,14 @@ private extension LayoutManager { } - /// Returns the Marker Paratraph Attributes + /// Returns the Marker Paragraph Attributes /// private func markerParagraphStyle(indent: CGFloat) -> NSParagraphStyle { - let tabStop = NSTextTab(textAlignment: .right, location: indent, options: [:]) + let paragraphStyle = NSMutableParagraphStyle() - - paragraphStyle.tabStops = [tabStop] + paragraphStyle.alignment = .right + paragraphStyle.tailIndent = -indent + paragraphStyle.lineBreakMode = .byClipping return paragraphStyle } diff --git a/Aztec/Classes/TextKit/ParagraphProperty/TextList.swift b/Aztec/Classes/TextKit/ParagraphProperty/TextList.swift index 8c4d0bc44..a2cf28306 100644 --- a/Aztec/Classes/TextKit/ParagraphProperty/TextList.swift +++ b/Aztec/Classes/TextKit/ParagraphProperty/TextList.swift @@ -16,8 +16,8 @@ open class TextList: ParagraphProperty { func markerText(forItemNumber number: Int) -> String { switch self { - case .ordered: return "\t\(number)." - case .unordered: return "\t\u{2022}" + case .ordered: return "\(number)." + case .unordered: return "\u{2022}" } } } diff --git a/Aztec/Classes/TextKit/ParagraphStyle.swift b/Aztec/Classes/TextKit/ParagraphStyle.swift index c52a20ae2..eb01f7029 100644 --- a/Aztec/Classes/TextKit/ParagraphStyle.swift +++ b/Aztec/Classes/TextKit/ParagraphStyle.swift @@ -140,7 +140,7 @@ open class ParagraphStyle: NSMutableParagraphStyle, CustomReflectable { open override var headIndent: CGFloat { get { - let extra: CGFloat = (CGFloat(lists.count + blockquotes.count) * Metrics.listTextIndentation) + let extra: CGFloat = (CGFloat(blockquotes.count) * Metrics.listTextIndentation) + listIndent return baseHeadIndent + extra } @@ -152,7 +152,8 @@ open class ParagraphStyle: NSMutableParagraphStyle, CustomReflectable { open override var firstLineHeadIndent: CGFloat { get { - let extra: CGFloat = (CGFloat(lists.count + blockquotes.count) * Metrics.listTextIndentation) + + let extra: CGFloat = (CGFloat(blockquotes.count) * Metrics.listTextIndentation) + listIndent return baseFirstLineHeadIndent + extra } @@ -175,57 +176,74 @@ open class ParagraphStyle: NSMutableParagraphStyle, CustomReflectable { super.tailIndent = newValue } } - - /// The level of indent to start the blockquote of the paragraph. Includes list indentation. - /// Handles whether blockquote is outside or insdie of a list. - public var blockquoteIndent: CGFloat { - let listAndBlockquotes = properties.filter({ property in - return property is Blockquote || property is TextList - }) - if listAndBlockquotes.first is Blockquote { + + /// Calculates the indentation of the paragraph up, for up to a certain depth of nesting of the type provided + /// - Parameters: + /// - depth: the depth up to check + /// - type: the type to check + public func indent(to depth: Int, of type: T.Type) -> CGFloat { + var position = -1 + var currentDepth = -1 + for property in properties { + position += 1 + if property is T { + currentDepth += 1 + } + if depth == currentDepth { + break + } + } + if position == -1 || currentDepth == -1 { return 0 } - var depth = 0 - for position in (0..(_ type: T.Type) -> CGFloat { + let depth = properties.firstIndex(where: {$0 is T}) ?? 0 + return indent(through: depth) + } + + /// Calculates the indentation of the paragraph up the last property of the type specified + /// - Parameter type: the paragraph property type to check + public func indentToLast(_ type: T.Type) -> CGFloat { + let depth = properties.lastIndex(where: {$0 is T}) ?? 0 + return indent(through: depth) + } + + /// Calculates the level of indent up to a certain depth + private func indent(through depth: Int) -> CGFloat { + let totalIndent = properties.prefix(through: depth).reduce(CGFloat(0)) { (total, property) in + if let list = property as? TextList { + return total + indent(for: list) + } else if property is Blockquote { + return total + Metrics.listTextIndentation } + return total } - return CGFloat(depth) * Metrics.listTextIndentation + return totalIndent } /// The level of depth for the nested blockquote of the paragraph. Excludes list indentation. /// public var blockquoteNestDepth: Int { - let listAndBlockquotes = properties.filter({ property in - return property is Blockquote - }) - var depth = 0 - for position in (0.. CGFloat { + let markerSize = CGFloat(list.style.markerText(forItemNumber: list.start ?? 1).count) + let markerMinimum = max(CGFloat(Metrics.listMinimumIndentChars), markerSize) + return Metrics.listTextIndentation + (markerMinimum * Metrics.listTextCharIndentation) + } /// The amount of indent for the list of the paragraph if any. /// public var listIndent: CGFloat { - let listAndBlockquotes = properties.filter({ property in - return property is Blockquote || property is TextList - }) - var depth = 0 - for position in (0..WordPress 워드 프레스

") + let html = textView.getHTML() + XCTAssertEqual(html, "

WordPress 워드 프레스

") + + textView.setHTML("

WordPress워드 프레스

") + let htmlTwoLinks = textView.getHTML() + XCTAssertEqual(htmlTwoLinks, "

WordPress워드 프레스

") + } + } diff --git a/CHANGELOG.md b/CHANGELOG.md index 661ef9b60..2547e7ef1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +1.16.0 +----- + * Improve display of ordered lists with large bullet numbers + * Fix bug where links with text that had a mix of Latin and non-Latin characters were getting split. + 1.15.0 ----- * Allow to use headers fonts without bold effect applied diff --git a/Example/AztecExample.xcodeproj/project.pbxproj b/Example/AztecExample.xcodeproj/project.pbxproj index f715d733d..b4e81ccd2 100644 --- a/Example/AztecExample.xcodeproj/project.pbxproj +++ b/Example/AztecExample.xcodeproj/project.pbxproj @@ -43,6 +43,7 @@ FF629DC9223BC418004C4106 /* videoShortcodes.html in Resources */ = {isa = PBXBuildFile; fileRef = FF629DC8223BC418004C4106 /* videoShortcodes.html */; }; FF9AF5481DB0E4E200C42ED3 /* AttachmentDetailsViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FF9AF5471DB0E4E200C42ED3 /* AttachmentDetailsViewController.storyboard */; }; FFC41BDE20DBC7BA004DFB4D /* video.html in Resources */ = {isa = PBXBuildFile; fileRef = FFC41BDD20DBC7BA004DFB4D /* video.html */; }; + FFC6772223D07E3E00B76815 /* bigLists.html in Resources */ = {isa = PBXBuildFile; fileRef = FFC6772123D07E3E00B76815 /* bigLists.html */; }; FFFA53D023C4A64200829A43 /* MediaInserter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFFA53CF23C4A64200829A43 /* MediaInserter.swift */; }; FFFA53D523C4AD0B00829A43 /* TextViewAttachmentDelegateProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFFA53D423C4AD0B00829A43 /* TextViewAttachmentDelegateProvider.swift */; }; FFFA53D723C4C43700829A43 /* UIImage+SaveTo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFFA53D623C4C43700829A43 /* UIImage+SaveTo.swift */; }; @@ -163,6 +164,7 @@ FF629DC8223BC418004C4106 /* videoShortcodes.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = videoShortcodes.html; sourceTree = ""; }; FF9AF5471DB0E4E200C42ED3 /* AttachmentDetailsViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = AttachmentDetailsViewController.storyboard; sourceTree = ""; }; FFC41BDD20DBC7BA004DFB4D /* video.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = video.html; sourceTree = ""; }; + FFC6772123D07E3E00B76815 /* bigLists.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = bigLists.html; sourceTree = ""; }; FFFA53CF23C4A64200829A43 /* MediaInserter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaInserter.swift; sourceTree = ""; }; FFFA53D423C4AD0B00829A43 /* TextViewAttachmentDelegateProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextViewAttachmentDelegateProvider.swift; sourceTree = ""; }; FFFA53D623C4C43700829A43 /* UIImage+SaveTo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+SaveTo.swift"; sourceTree = ""; }; @@ -211,6 +213,7 @@ FFC41BDD20DBC7BA004DFB4D /* video.html */, FF629DC8223BC418004C4106 /* videoShortcodes.html */, FF5CDACC239E78B200CF235B /* failedMedia.html */, + FFC6772123D07E3E00B76815 /* bigLists.html */, ); path = SampleContent; sourceTree = ""; @@ -457,6 +460,7 @@ 59280F2A1D47CAF40083FB59 /* content.html in Resources */, FF5CDACD239E78B200CF235B /* failedMedia.html in Resources */, B5FB212A1FEC38470067D597 /* captions.html in Resources */, + FFC6772223D07E3E00B76815 /* bigLists.html in Resources */, FF1FD05C20932EDE00186384 /* gutenberg.html in Resources */, 59280F2B1D47CAF40083FB59 /* SampleText.rtf in Resources */, 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */, diff --git a/Example/AztecExample.xcodeproj/xcshareddata/xcschemes/AztecExample.xcscheme b/Example/AztecExample.xcodeproj/xcshareddata/xcschemes/AztecExample.xcscheme index 22ca47bca..56d832f51 100644 --- a/Example/AztecExample.xcodeproj/xcshareddata/xcschemes/AztecExample.xcscheme +++ b/Example/AztecExample.xcodeproj/xcshareddata/xcschemes/AztecExample.xcscheme @@ -40,9 +40,18 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + shouldUseLaunchSchemeArgsEnv = "YES" codeCoverageEnabled = "YES" - onlyGenerateCoverageForSpecifiedTargets = "YES" - shouldUseLaunchSchemeArgsEnv = "YES"> + onlyGenerateCoverageForSpecifiedTargets = "YES"> + + + + - - - - - - - - One digit list +
    +
  1. First
  2. +
  3. Second
  4. +
  5. Third
  6. +
  7. Fourth
  8. +
+

Two digits list

+
    +
  1. First
  2. +
  3. Second
  4. +
  5. Third
  6. +
  7. Fourth +
      +
    1. First
    2. +
    3. Second
    4. +
    5. Third
    6. +
    7. Fourth +
    +
  8. +
+

Three digits list

+
    +
  1. First
  2. +
  3. Second
  4. +
  5. Third
  6. +
  7. Fourth
  8. +
+

Four digits list

+
    +
  1. First
  2. +
  3. Second
  4. +
  5. Third
  6. +
  7. Fourth +
      +
    1. First
    2. +
    3. Second
    4. +
    5. Third
    6. +
    7. Fourth +
    +
  8. +
+ +

Five digits list

+
    +
  1. First
  2. +
  3. Second
  4. +
  5. Third
  6. +
  7. Fourth +
      +
    1. First
    2. +
    3. Second
    4. +
    5. Third
    6. +
    7. Fourth +
    +
  8. +
diff --git a/Example/Example/ViewController.swift b/Example/Example/ViewController.swift index ce4a71df9..7ccafeb8d 100644 --- a/Example/Example/ViewController.swift +++ b/Example/Example/ViewController.swift @@ -20,6 +20,7 @@ class ViewController: UITableViewController DemoRow(title: "Image Overlays", action: { self.showEditorDemo(filename: "imagesOverlays") }), DemoRow(title: "Video Demo", action: { self.showEditorDemo(filename: "video", wordPressMode: false) }), DemoRow(title: "Failed Media", action: { self.showEditorDemo(filename: "failedMedia") }), + DemoRow(title: "Big Lists", action: { self.showEditorDemo(filename: "bigLists") }), DemoRow(title: "Empty Demo", action: { self.showEditorDemo() }) ] ), diff --git a/WordPress-Aztec-iOS.podspec b/WordPress-Aztec-iOS.podspec index a6aabfd54..e991ddd83 100644 --- a/WordPress-Aztec-iOS.podspec +++ b/WordPress-Aztec-iOS.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'WordPress-Aztec-iOS' - s.version = '1.15.0' + s.version = '1.16.0' s.summary = 'The native HTML Editor.' # This description is used to generate tags and improve search results. diff --git a/WordPress-Editor-iOS.podspec b/WordPress-Editor-iOS.podspec index 8a29e48ab..c1f383a7d 100644 --- a/WordPress-Editor-iOS.podspec +++ b/WordPress-Editor-iOS.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'WordPress-Editor-iOS' - s.version = '1.15.0' + s.version = '1.16.0' s.summary = 'The WordPress HTML Editor.' # This description is used to generate tags and improve search results.