diff --git a/Aztec/Classes/Converters/ElementsToAttributedString/Implementations/GenericElementConverter.swift b/Aztec/Classes/Converters/ElementsToAttributedString/Implementations/GenericElementConverter.swift index 435e69014..1b2a8408a 100644 --- a/Aztec/Classes/Converters/ElementsToAttributedString/Implementations/GenericElementConverter.swift +++ b/Aztec/Classes/Converters/ElementsToAttributedString/Implementations/GenericElementConverter.swift @@ -15,7 +15,7 @@ class GenericElementConverter: ElementConverter { // MARK: - Built-in formatter instances - lazy var blockquoteFormatter = BlockquoteFormatter() + lazy var blockquoteFormatter = BlockquoteFormatter(increaseDepth: true) lazy var boldFormatter = BoldFormatter() lazy var divFormatter = HTMLDivFormatter() lazy var h1Formatter = HeaderFormatter(headerLevel: .h1) diff --git a/Aztec/Classes/Formatters/Implementations/BlockquoteFormatter.swift b/Aztec/Classes/Formatters/Implementations/BlockquoteFormatter.swift index 5a1348431..2b33770ff 100644 --- a/Aztec/Classes/Formatters/Implementations/BlockquoteFormatter.swift +++ b/Aztec/Classes/Formatters/Implementations/BlockquoteFormatter.swift @@ -6,17 +6,55 @@ import UIKit // class BlockquoteFormatter: ParagraphAttributeFormatter { + typealias TextAttributes = [NSAttributedString.Key: Any]? + /// Attributes to be added by default /// - let placeholderAttributes: [NSAttributedString.Key: Any]? - + let typingAttributes: TextAttributes + + let defaultTextAttributes: TextAttributes + let borderColors: [UIColor]? + /// Tells if the formatter is increasing the depth of a blockquote or simple changing the current one if any + let increaseDepth: Bool + + let currentDepth: Int + /// Designated Initializer /// - init(placeholderAttributes: [NSAttributedString.Key: Any]? = nil) { - self.placeholderAttributes = placeholderAttributes + init(typingAttributes: TextAttributes = nil, defaultTextAttributes: TextAttributes = nil, borderColors: [UIColor]? = nil, increaseDepth: Bool = false, currentDepth: Int = 0) { + self.typingAttributes = typingAttributes + self.defaultTextAttributes = defaultTextAttributes + self.borderColors = borderColors + self.increaseDepth = increaseDepth + + self.currentDepth = typingAttributes?.paragraphStyle().blockquoteDepth ?? 0 + } + + private func colorForNext(depth: Int) -> UIColor { + guard let colors = borderColors else { return UIColor.darkText } + + if increaseDepth { + let index = min(depth+1, colors.count-1) + return colors[index] + } else { + return colors[0] + } } + + private func colorForPrev(depth: Int) -> UIColor { + guard let colors = borderColors else { return UIColor.darkText } + //increaseDepth is not used here as shift tab and pressing quote toggle button behave differently + if depth > 0 { + let positiveIndex = max(depth-1, 0) + let index = min(positiveIndex, colors.count-1) + return colors[index] + } else { + guard let att = defaultTextAttributes else { return UIColor.darkText } + return att[.foregroundColor] as! UIColor + } + } // MARK: - Overwriten Methods @@ -26,11 +64,20 @@ class BlockquoteFormatter: ParagraphAttributeFormatter { if let paragraphStyle = attributes[.paragraphStyle] as? NSParagraphStyle { newParagraphStyle.setParagraphStyle(paragraphStyle) } - - newParagraphStyle.appendProperty(Blockquote(with: representation)) + + let newQuote = Blockquote(with: representation) + + if newParagraphStyle.blockquotes.isEmpty || increaseDepth { + newParagraphStyle.insertProperty(newQuote, afterLastOfType: Blockquote.self) + } else { + newParagraphStyle.replaceProperty(ofType: Blockquote.self, with: newQuote) + } var resultingAttributes = attributes resultingAttributes[.paragraphStyle] = newParagraphStyle + resultingAttributes[.foregroundColor] = colorForNext(depth: currentDepth) + + return resultingAttributes } @@ -47,6 +94,13 @@ class BlockquoteFormatter: ParagraphAttributeFormatter { var resultingAttributes = attributes resultingAttributes[.paragraphStyle] = newParagraphStyle + + //remove quote color + resultingAttributes.removeValue(forKey: .foregroundColor) + + resultingAttributes[.foregroundColor] = colorForPrev(depth: currentDepth) + + return resultingAttributes } @@ -56,4 +110,9 @@ class BlockquoteFormatter: ParagraphAttributeFormatter { } return !style.blockquotes.isEmpty } + + static func blockquotes(in attributes: [NSAttributedString.Key: Any]) -> [Blockquote] { + let style = attributes[.paragraphStyle] as? ParagraphStyle + return style?.blockquotes ?? [] + } } diff --git a/Aztec/Classes/Formatters/Implementations/TextListFormatter.swift b/Aztec/Classes/Formatters/Implementations/TextListFormatter.swift index 6e9b7c692..cc5c5e8ba 100644 --- a/Aztec/Classes/Formatters/Implementations/TextListFormatter.swift +++ b/Aztec/Classes/Formatters/Implementations/TextListFormatter.swift @@ -35,6 +35,7 @@ class TextListFormatter: ParagraphAttributeFormatter { } let newList = TextList(style: self.listStyle, with: representation) + if newParagraphStyle.lists.isEmpty || increaseDepth { newParagraphStyle.insertProperty(newList, afterLastOfType: HTMLLi.self) } else { diff --git a/Aztec/Classes/TextKit/LayoutManager.swift b/Aztec/Classes/TextKit/LayoutManager.swift index 2f7a8ca9b..2935d360d 100644 --- a/Aztec/Classes/TextKit/LayoutManager.swift +++ b/Aztec/Classes/TextKit/LayoutManager.swift @@ -8,8 +8,12 @@ import QuartzCore class LayoutManager: NSLayoutManager { /// Blockquote's Left Border Color - /// - var blockquoteBorderColor: UIColor? = UIColor(red: 0.52, green: 0.65, blue: 0.73, alpha: 1.0) + /// Set the array with how many colors you like, they appear in the order they are set in the array. + var blockquoteBorderColors = [UIColor(hexString: "9252FF")!, + UIColor(hexString: "5A4AE8")!, + UIColor(hexString: "5D77FE")!, + UIColor(hexString: "4A8EE8")!, + UIColor(hexString: "52CAFF")!] /// Blockquote's Background Color /// @@ -62,17 +66,27 @@ private extension LayoutManager { guard let paragraphStyle = object as? ParagraphStyle, !paragraphStyle.blockquotes.isEmpty else { return } - - let blockquoteIndent = paragraphStyle.blockquoteIndent + let blockquoteGlyphRange = glyphRange(forCharacterRange: range, actualCharacterRange: nil) enumerateLineFragments(forGlyphRange: blockquoteGlyphRange) { (rect, usedRect, textContainer, glyphRange, stop) in let lineRange = self.characterRange(forGlyphRange: glyphRange, actualGlyphRange: nil) let lineCharacters = textStorage.attributedSubstring(from: lineRange).string let lineEndsParagraph = lineCharacters.isEndOfParagraph(before: lineCharacters.endIndex) - let blockquoteRect = self.blockquoteRect(origin: origin, lineRect: rect, blockquoteIndent: blockquoteIndent, lineEndsParagraph: lineEndsParagraph) + let blockquoteRect = self.blockquoteRect(origin: origin, lineRect: rect, blockquoteIndent: 0.0, lineEndsParagraph: lineEndsParagraph) + + self.drawBlockquoteBackground(in: blockquoteRect.integral, with: context) + + let nestDepth = paragraphStyle.blockquoteDepth + for index in 0...nestDepth { + let indent = CGFloat(index) * Metrics.listTextIndentation + + let nestRect = self.blockquoteRect(origin: origin, lineRect: rect, blockquoteIndent: indent, lineEndsParagraph: lineEndsParagraph) - self.drawBlockquote(in: blockquoteRect.integral, with: context) + self.drawBlockquoteBorder(in: nestRect.integral, with: context, at: index) + } + + } } @@ -90,7 +104,8 @@ private extension LayoutManager { let extraIndent = paragraphStyle.blockquoteIndent let extraRect = blockquoteRect(origin: origin, lineRect: extraLineFragmentRect, blockquoteIndent: extraIndent, lineEndsParagraph: false) - drawBlockquote(in: extraRect.integral, with: context) + drawBlockquoteBackground(in: extraRect.integral, with: context) + drawBlockquoteBorder(in: extraRect.integral, with: context, at: 0) } @@ -122,21 +137,26 @@ private extension LayoutManager { return blockquoteRect } + + private func drawBlockquoteBorder(in rect: CGRect, with context: CGContext, at depth: Int) { + let quoteCount = blockquoteBorderColors.count + let index = min(depth, quoteCount-1) + + guard index < quoteCount else {return} + + let borderColor = blockquoteBorderColors[index] + let borderRect = CGRect(origin: rect.origin, size: CGSize(width: blockquoteBorderWidth, height: rect.height)) + borderColor.setFill() + context.fill(borderRect) + } /// Draws a single Blockquote Line Fragment, in the specified Rectangle, using a given Graphics Context. /// - private func drawBlockquote(in rect: CGRect, with context: CGContext) { - if let blockquoteBackgroundColor = blockquoteBackgroundColor { - blockquoteBackgroundColor.setFill() - context.fill(rect) - - } - - if let blockquoteBorderColor = blockquoteBorderColor { - let borderRect = CGRect(origin: rect.origin, size: CGSize(width: blockquoteBorderWidth, height: rect.height)) - blockquoteBorderColor.setFill() - context.fill(borderRect) - } + private func drawBlockquoteBackground(in rect: CGRect, with context: CGContext) { + guard let color = blockquoteBackgroundColor else {return} + + color.setFill() + context.fill(rect) } } diff --git a/Aztec/Classes/TextKit/ParagraphStyle.swift b/Aztec/Classes/TextKit/ParagraphStyle.swift index 201dddee0..3fa2bdb60 100644 --- a/Aztec/Classes/TextKit/ParagraphStyle.swift +++ b/Aztec/Classes/TextKit/ParagraphStyle.swift @@ -175,21 +175,27 @@ open class ParagraphStyle: NSMutableParagraphStyle, CustomReflectable { super.tailIndent = newValue } } - - /// The amount of indent for the blockquote of the paragraph if any. + + /// The amount of indent for the nested blockquote of the paragraph if any. /// public var blockquoteIndent: CGFloat { - let blockquoteIndex = properties.filter({ property in + return CGFloat(blockquoteDepth) * Metrics.listTextIndentation + } + + /// The level of depth for the nested blockquote of the paragraph if any. + /// + public var blockquoteDepth: Int { + let listAndBlockquotes = properties.filter({ property in return property is Blockquote || property is TextList - }).firstIndex(where: { property in - return property is Blockquote }) - - guard let depth = blockquoteIndex else { - return 0 + var depth = 0 + for position in (0.. NSRange { + let finalRange = listFormatter.applyAttributes(to: storage, at: targetRange) + liFormatter.applyAttributes(to: storage, at: targetRange) + + return finalRange + } + + private func decreaseIndent(listFormatter: TextListFormatter, liFormatter: LiFormatter, targetRange: NSRange) -> NSRange { + let finalRange = listFormatter.removeAttributes(from: storage, at: targetRange) + liFormatter.removeAttributes(from: storage, at: targetRange) + + return finalRange + } + + + // MARK: - Blockquote indent methods + + private func indent(blockquote: Blockquote, increase: Bool = true) { + + + + let formatter = BlockquoteFormatter(typingAttributes: typingAttributes, defaultTextAttributes: defaultAttributes, borderColors: blockquoteBorderColors, increaseDepth: true) let targetRange = formatter.applicationRange(for: selectedRange, in: storage) - performUndoable(at: targetRange) { - let finalRange = formatter.applyAttributes(to: storage, at: targetRange) - liFormatter.applyAttributes(to: storage, at: targetRange) + performUndoable(at: targetRange) { + let finalRange: NSRange + if increase { + finalRange = increaseIndent(quoteFormatter: formatter, targetRange: targetRange) + } else { + finalRange = decreaseIndent(quoteFormatter: formatter, targetRange: targetRange) + } typingAttributes = textStorage.attributes(at: targetRange.location, effectiveRange: nil) return finalRange } + } + // MARK: Blockquote increase or decrease indentation + private func increaseIndent(quoteFormatter: BlockquoteFormatter, targetRange: NSRange) -> NSRange { + let finalRange = quoteFormatter.applyAttributes(to: storage, at: targetRange) + return finalRange + } + + private func decreaseIndent(quoteFormatter: BlockquoteFormatter, targetRange: NSRange) -> NSRange { + let finalRange = quoteFormatter.removeAttributes(from: storage, at: targetRange) + return finalRange + } + // MARK: - Pasteboard Helpers @@ -983,7 +1059,8 @@ open class TextView: UITextView { open func toggleBlockquote(range: NSRange) { ensureInsertionOfEndOfLineForEmptyParagraphAtEndOfFile(forApplicationRange: range) - let formatter = BlockquoteFormatter(placeholderAttributes: typingAttributes) + let formatter = BlockquoteFormatter(typingAttributes: typingAttributes, defaultTextAttributes: defaultAttributes, borderColors: blockquoteBorderColors) + toggle(formatter: formatter, atRange: range) let citeFormatter = CiteFormatter() @@ -1803,7 +1880,7 @@ private extension TextView { /// private func removeBlockquoteAndCite() { // Blockquote + cite removal - let formatter = BlockquoteFormatter(placeholderAttributes: typingAttributes) + let formatter = BlockquoteFormatter(typingAttributes: typingAttributes) let citeFormatter = CiteFormatter() if formatter.present(in: typingAttributes) @@ -1932,6 +2009,7 @@ private extension TextView { } typingAttributes[.paragraphStyle] = paragraphStyleWithoutProperties(from: paragraphStyle) + typingAttributes[.foregroundColor] = defaultTextColor } private func removeParagraphPropertiesFromParagraph(spanning range: NSRange) { @@ -1943,6 +2021,7 @@ private extension TextView { } attributes[.paragraphStyle] = paragraphStyleWithoutProperties(from: paragraphStyle) + attributes[.foregroundColor] = defaultTextColor storage.setAttributes(attributes, range: range) } diff --git a/Example/Example/EditorDemoController.swift b/Example/Example/EditorDemoController.swift index a1bd3d008..209194829 100644 --- a/Example/Example/EditorDemoController.swift +++ b/Example/Example/EditorDemoController.swift @@ -181,7 +181,7 @@ class EditorDemoController: UIViewController { editorView.richTextView.textColor = UIColor.label editorView.richTextView.blockquoteBackgroundColor = UIColor.secondarySystemBackground editorView.richTextView.preBackgroundColor = UIColor.secondarySystemBackground - editorView.richTextView.blockquoteBorderColor = UIColor.secondarySystemFill + editorView.richTextView.blockquoteBorderColors = [.red, .blue, .green] var attributes = editorView.richTextView.linkTextAttributes attributes?[.foregroundColor] = UIColor.link } else {