Skip to content

Commit 1079d89

Browse files
Merge pull request #1253 from MadeByDouglas/issue/1086_blockquote_multilevel_support
Issue/1086 blockquote multilevel support
2 parents 4237063 + 369775c commit 1079d89

7 files changed

Lines changed: 190 additions & 55 deletions

File tree

Aztec/Classes/Converters/ElementsToAttributedString/Implementations/GenericElementConverter.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class GenericElementConverter: ElementConverter {
1515

1616
// MARK: - Built-in formatter instances
1717

18-
lazy var blockquoteFormatter = BlockquoteFormatter()
18+
lazy var blockquoteFormatter = BlockquoteFormatter(increaseDepth: true)
1919
lazy var boldFormatter = BoldFormatter()
2020
lazy var divFormatter = HTMLDivFormatter()
2121
lazy var h1Formatter = HeaderFormatter(headerLevel: .h1)

Aztec/Classes/Formatters/Implementations/BlockquoteFormatter.swift

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,15 @@ class BlockquoteFormatter: ParagraphAttributeFormatter {
1010
///
1111
let placeholderAttributes: [NSAttributedString.Key: Any]?
1212

13+
/// Tells if the formatter is increasing the depth of a list or simple changing the current one if any
14+
let increaseDepth: Bool
15+
1316

1417
/// Designated Initializer
1518
///
16-
init(placeholderAttributes: [NSAttributedString.Key: Any]? = nil) {
19+
init(placeholderAttributes: [NSAttributedString.Key: Any]? = nil, increaseDepth: Bool = false) {
1720
self.placeholderAttributes = placeholderAttributes
21+
self.increaseDepth = increaseDepth
1822
}
1923

2024

@@ -26,11 +30,18 @@ class BlockquoteFormatter: ParagraphAttributeFormatter {
2630
if let paragraphStyle = attributes[.paragraphStyle] as? NSParagraphStyle {
2731
newParagraphStyle.setParagraphStyle(paragraphStyle)
2832
}
29-
30-
newParagraphStyle.appendProperty(Blockquote(with: representation))
33+
34+
let newQuote = Blockquote(with: representation)
35+
36+
if newParagraphStyle.blockquotes.isEmpty || increaseDepth {
37+
newParagraphStyle.insertProperty(newQuote, afterLastOfType: Blockquote.self)
38+
} else {
39+
newParagraphStyle.replaceProperty(ofType: Blockquote.self, with: newQuote)
40+
}
3141

3242
var resultingAttributes = attributes
3343
resultingAttributes[.paragraphStyle] = newParagraphStyle
44+
3445
return resultingAttributes
3546
}
3647

@@ -47,6 +58,7 @@ class BlockquoteFormatter: ParagraphAttributeFormatter {
4758

4859
var resultingAttributes = attributes
4960
resultingAttributes[.paragraphStyle] = newParagraphStyle
61+
5062
return resultingAttributes
5163
}
5264

@@ -56,4 +68,9 @@ class BlockquoteFormatter: ParagraphAttributeFormatter {
5668
}
5769
return !style.blockquotes.isEmpty
5870
}
71+
72+
static func blockquotes(in attributes: [NSAttributedString.Key: Any]) -> [Blockquote] {
73+
let style = attributes[.paragraphStyle] as? ParagraphStyle
74+
return style?.blockquotes ?? []
75+
}
5976
}

Aztec/Classes/Formatters/Implementations/TextListFormatter.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class TextListFormatter: ParagraphAttributeFormatter {
3535
}
3636

3737
let newList = TextList(style: self.listStyle, with: representation)
38+
3839
if newParagraphStyle.lists.isEmpty || increaseDepth {
3940
newParagraphStyle.insertProperty(newList, afterLastOfType: HTMLLi.self)
4041
} else {

Aztec/Classes/TextKit/LayoutManager.swift

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import QuartzCore
88
class LayoutManager: NSLayoutManager {
99

1010
/// Blockquote's Left Border Color
11-
///
12-
var blockquoteBorderColor: UIColor? = UIColor(red: 0.52, green: 0.65, blue: 0.73, alpha: 1.0)
11+
/// Set the array with how many colors you like, they appear in the order they are set in the array.
12+
var blockquoteBorderColors = [UIColor.systemGray]
1313

1414
/// Blockquote's Background Color
1515
///
@@ -62,17 +62,30 @@ private extension LayoutManager {
6262
guard let paragraphStyle = object as? ParagraphStyle, !paragraphStyle.blockquotes.isEmpty else {
6363
return
6464
}
65-
66-
let blockquoteIndent = paragraphStyle.blockquoteIndent
65+
6766
let blockquoteGlyphRange = glyphRange(forCharacterRange: range, actualCharacterRange: nil)
6867

6968
enumerateLineFragments(forGlyphRange: blockquoteGlyphRange) { (rect, usedRect, textContainer, glyphRange, stop) in
69+
70+
let startIndent = paragraphStyle.blockquoteIndent
71+
7072
let lineRange = self.characterRange(forGlyphRange: glyphRange, actualGlyphRange: nil)
7173
let lineCharacters = textStorage.attributedSubstring(from: lineRange).string
7274
let lineEndsParagraph = lineCharacters.isEndOfParagraph(before: lineCharacters.endIndex)
73-
let blockquoteRect = self.blockquoteRect(origin: origin, lineRect: rect, blockquoteIndent: blockquoteIndent, lineEndsParagraph: lineEndsParagraph)
75+
let blockquoteRect = self.blockquoteRect(origin: origin, lineRect: rect, blockquoteIndent: startIndent, lineEndsParagraph: lineEndsParagraph)
76+
77+
self.drawBlockquoteBackground(in: blockquoteRect.integral, with: context)
78+
79+
let nestDepth = paragraphStyle.blockquoteNestDepth
80+
for index in 0...nestDepth {
81+
let indent = startIndent + CGFloat(index) * Metrics.listTextIndentation
82+
83+
let nestRect = self.blockquoteRect(origin: origin, lineRect: rect, blockquoteIndent: indent, lineEndsParagraph: lineEndsParagraph)
7484

75-
self.drawBlockquote(in: blockquoteRect.integral, with: context)
85+
self.drawBlockquoteBorder(in: nestRect.integral, with: context, at: index)
86+
}
87+
88+
7689
}
7790
}
7891

@@ -90,7 +103,8 @@ private extension LayoutManager {
90103
let extraIndent = paragraphStyle.blockquoteIndent
91104
let extraRect = blockquoteRect(origin: origin, lineRect: extraLineFragmentRect, blockquoteIndent: extraIndent, lineEndsParagraph: false)
92105

93-
drawBlockquote(in: extraRect.integral, with: context)
106+
drawBlockquoteBackground(in: extraRect.integral, with: context)
107+
drawBlockquoteBorder(in: extraRect.integral, with: context, at: 0)
94108
}
95109

96110

@@ -122,21 +136,28 @@ private extension LayoutManager {
122136

123137
return blockquoteRect
124138
}
139+
140+
private func drawBlockquoteBorder(in rect: CGRect, with context: CGContext, at depth: Int) {
141+
let quoteCount = blockquoteBorderColors.count
142+
let index = min(depth, quoteCount-1)
143+
144+
guard quoteCount > 0 && index < quoteCount else {
145+
return
146+
}
147+
148+
let borderColor = blockquoteBorderColors[index]
149+
let borderRect = CGRect(origin: rect.origin, size: CGSize(width: blockquoteBorderWidth, height: rect.height))
150+
borderColor.setFill()
151+
context.fill(borderRect)
152+
}
125153

126154
/// Draws a single Blockquote Line Fragment, in the specified Rectangle, using a given Graphics Context.
127155
///
128-
private func drawBlockquote(in rect: CGRect, with context: CGContext) {
129-
if let blockquoteBackgroundColor = blockquoteBackgroundColor {
130-
blockquoteBackgroundColor.setFill()
131-
context.fill(rect)
132-
133-
}
134-
135-
if let blockquoteBorderColor = blockquoteBorderColor {
136-
let borderRect = CGRect(origin: rect.origin, size: CGSize(width: blockquoteBorderWidth, height: rect.height))
137-
blockquoteBorderColor.setFill()
138-
context.fill(borderRect)
139-
}
156+
private func drawBlockquoteBackground(in rect: CGRect, with context: CGContext) {
157+
guard let color = blockquoteBackgroundColor else {return}
158+
159+
color.setFill()
160+
context.fill(rect)
140161
}
141162
}
142163

Aztec/Classes/TextKit/ParagraphStyle.swift

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -175,22 +175,41 @@ open class ParagraphStyle: NSMutableParagraphStyle, CustomReflectable {
175175
super.tailIndent = newValue
176176
}
177177
}
178-
179-
/// The amount of indent for the blockquote of the paragraph if any.
180-
///
178+
179+
/// The level of indent to start the blockquote of the paragraph. Includes list indentation.
180+
/// Handles whether blockquote is outside or insdie of a list.
181181
public var blockquoteIndent: CGFloat {
182-
let blockquoteIndex = properties.filter({ property in
182+
let listAndBlockquotes = properties.filter({ property in
183183
return property is Blockquote || property is TextList
184-
}).firstIndex(where: { property in
185-
return property is Blockquote
186184
})
187-
188-
guard let depth = blockquoteIndex else {
185+
if listAndBlockquotes.first is Blockquote {
189186
return 0
190187
}
191-
188+
var depth = 0
189+
for position in (0..<listAndBlockquotes.count).reversed() {
190+
if listAndBlockquotes[position] is Blockquote {
191+
depth = position
192+
break
193+
}
194+
}
192195
return CGFloat(depth) * Metrics.listTextIndentation
193196
}
197+
198+
/// The level of depth for the nested blockquote of the paragraph. Excludes list indentation.
199+
///
200+
public var blockquoteNestDepth: Int {
201+
let listAndBlockquotes = properties.filter({ property in
202+
return property is Blockquote
203+
})
204+
var depth = 0
205+
for position in (0..<listAndBlockquotes.count).reversed() {
206+
if listAndBlockquotes[position] is Blockquote {
207+
depth = position
208+
break
209+
}
210+
}
211+
return depth
212+
}
194213

195214
/// The amount of indent for the list of the paragraph if any.
196215
///

Aztec/Classes/TextKit/TextView.swift

Lines changed: 99 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,12 @@ open class TextView: UITextView {
223223
// MARK: - Properties: Text Lists
224224

225225
var maximumListIndentationLevels = 7
226+
227+
// MARK: - Properties: Blockquotes
228+
229+
/// The max levels of quote indentation allowed
230+
/// Default is 9
231+
public var maximumBlockquoteIndentationLevels = 9
226232

227233
// MARK: - Properties: UI Defaults
228234

@@ -295,17 +301,18 @@ open class TextView: UITextView {
295301

296302
// MARK: - Apparance Properties
297303

298-
/// Blockquote Blocks Border Color.
304+
305+
/// Blockquote Blocks Border and Text Colors
299306
///
300-
@objc dynamic public var blockquoteBorderColor: UIColor? {
307+
@objc dynamic public var blockquoteBorderColors: [UIColor] {
301308
get {
302-
return layout.blockquoteBorderColor
309+
return layout.blockquoteBorderColors
303310
}
304311
set {
305-
layout.blockquoteBorderColor = newValue
312+
layout.blockquoteBorderColors = newValue
306313
}
307314
}
308-
315+
309316
/// Blockquote Blocks Background Color.
310317
///
311318
@objc dynamic public var blockquoteBackgroundColor: UIColor? {
@@ -547,41 +554,110 @@ open class TextView: UITextView {
547554
}
548555

549556
@objc func handleShiftTab(command: UIKeyCommand) {
550-
guard let list = TextListFormatter.lists(in: typingAttributes).last else {
557+
558+
let lists = TextListFormatter.lists(in: typingAttributes)
559+
let quotes = BlockquoteFormatter.blockquotes(in: typingAttributes)
560+
561+
if let list = lists.last {
562+
indent(list: list, increase: false)
563+
564+
} else if let quote = quotes.last {
565+
indent(blockquote: quote, increase: false)
566+
567+
} else {
551568
return
552569
}
570+
}
571+
572+
@objc func handleTab(command: UIKeyCommand) {
573+
574+
let lists = TextListFormatter.lists(in: typingAttributes)
575+
let quotes = BlockquoteFormatter.blockquotes(in: typingAttributes)
576+
577+
if let list = lists.last, lists.count < maximumListIndentationLevels {
578+
indent(list: list)
579+
580+
} else if let quote = quotes.last, quotes.count < maximumBlockquoteIndentationLevels {
581+
indent(blockquote: quote)
582+
583+
} else {
584+
insertText(String(.tab))
585+
}
586+
587+
}
588+
589+
590+
// MARK: - Text List indent methods
591+
592+
private func indent(list: TextList, increase: Bool = true) {
553593

554594
let formatter = TextListFormatter(style: list.style, placeholderAttributes: nil, increaseDepth: true)
555-
let liFormatter = LiFormatter(placeholderAttributes: nil)
595+
let li = LiFormatter(placeholderAttributes: nil)
596+
556597
let targetRange = formatter.applicationRange(for: selectedRange, in: storage)
557598

558599
performUndoable(at: targetRange) {
559-
let finalRange = formatter.removeAttributes(from: storage, at: targetRange)
560-
liFormatter.removeAttributes(from: storage, at: targetRange)
600+
let finalRange: NSRange
601+
if increase {
602+
finalRange = increaseIndent(listFormatter: formatter, liFormatter: li, targetRange: targetRange)
603+
} else {
604+
finalRange = decreaseIndent(listFormatter: formatter, liFormatter: li, targetRange: targetRange)
605+
}
561606
typingAttributes = textStorage.attributes(at: targetRange.location, effectiveRange: nil)
562607
return finalRange
563608
}
564-
}
565-
566-
@objc func handleTab(command: UIKeyCommand) {
567-
let lists = TextListFormatter.lists(in: typingAttributes)
568-
guard let list = lists.last, lists.count < maximumListIndentationLevels else {
569-
insertText(String(.tab))
570-
return
571-
}
572609

573-
let formatter = TextListFormatter(style: list.style, placeholderAttributes: nil, increaseDepth: true)
574-
let liFormatter = LiFormatter(placeholderAttributes: nil)
610+
}
611+
612+
// MARK: Text List increase or decrease indentation
613+
private func increaseIndent(listFormatter: TextListFormatter, liFormatter: LiFormatter, targetRange: NSRange) -> NSRange {
614+
let finalRange = listFormatter.applyAttributes(to: storage, at: targetRange)
615+
liFormatter.applyAttributes(to: storage, at: targetRange)
616+
617+
return finalRange
618+
}
619+
620+
private func decreaseIndent(listFormatter: TextListFormatter, liFormatter: LiFormatter, targetRange: NSRange) -> NSRange {
621+
let finalRange = listFormatter.removeAttributes(from: storage, at: targetRange)
622+
liFormatter.removeAttributes(from: storage, at: targetRange)
623+
624+
return finalRange
625+
}
626+
627+
628+
// MARK: - Blockquote indent methods
629+
630+
private func indent(blockquote: Blockquote, increase: Bool = true) {
631+
632+
633+
634+
let formatter = BlockquoteFormatter(placeholderAttributes: typingAttributes, increaseDepth: true)
575635
let targetRange = formatter.applicationRange(for: selectedRange, in: storage)
576636

577-
performUndoable(at: targetRange) {
578-
let finalRange = formatter.applyAttributes(to: storage, at: targetRange)
579-
liFormatter.applyAttributes(to: storage, at: targetRange)
637+
performUndoable(at: targetRange) {
638+
let finalRange: NSRange
639+
if increase {
640+
finalRange = increaseIndent(quoteFormatter: formatter, targetRange: targetRange)
641+
} else {
642+
finalRange = decreaseIndent(quoteFormatter: formatter, targetRange: targetRange)
643+
}
580644
typingAttributes = textStorage.attributes(at: targetRange.location, effectiveRange: nil)
581645
return finalRange
582646
}
647+
583648
}
584649

650+
// MARK: Blockquote increase or decrease indentation
651+
private func increaseIndent(quoteFormatter: BlockquoteFormatter, targetRange: NSRange) -> NSRange {
652+
let finalRange = quoteFormatter.applyAttributes(to: storage, at: targetRange)
653+
return finalRange
654+
}
655+
656+
private func decreaseIndent(quoteFormatter: BlockquoteFormatter, targetRange: NSRange) -> NSRange {
657+
let finalRange = quoteFormatter.removeAttributes(from: storage, at: targetRange)
658+
return finalRange
659+
}
660+
585661

586662
// MARK: - Pasteboard Helpers
587663

@@ -1004,6 +1080,7 @@ open class TextView: UITextView {
10041080
ensureInsertionOfEndOfLineForEmptyParagraphAtEndOfFile(forApplicationRange: range)
10051081

10061082
let formatter = BlockquoteFormatter(placeholderAttributes: typingAttributes)
1083+
10071084
toggle(formatter: formatter, atRange: range)
10081085

10091086
let citeFormatter = CiteFormatter()

0 commit comments

Comments
 (0)