diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Expanding/WKEditorToolbarExpandingView.swift b/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Expanding/WKEditorToolbarExpandingView.swift index 5dc203a4944..20448b349b0 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Expanding/WKEditorToolbarExpandingView.swift +++ b/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Expanding/WKEditorToolbarExpandingView.swift @@ -4,6 +4,7 @@ protocol WKEditorToolbarExpandingViewDelegate: AnyObject { func toolbarExpandingViewDidTapFind(toolbarView: WKEditorToolbarExpandingView) func toolbarExpandingViewDidTapFormatText(toolbarView: WKEditorToolbarExpandingView) func toolbarExpandingViewDidTapTemplate(toolbarView: WKEditorToolbarExpandingView, isSelected: Bool) + func toolbarExpandingViewDidTapReference(toolbarView: WKEditorToolbarExpandingView, isSelected: Bool) func toolbarExpandingViewDidTapLink(toolbarView: WKEditorToolbarExpandingView, isSelected: Bool) func toolbarExpandingViewDidTapImage(toolbarView: WKEditorToolbarExpandingView) func toolbarExpandingViewDidTapUnorderedList(toolbarView: WKEditorToolbarExpandingView, isSelected: Bool) @@ -45,7 +46,7 @@ class WKEditorToolbarExpandingView: WKEditorToolbarView { @IBOutlet weak var secondaryContainerView: UIView! @IBOutlet private weak var formatTextButton: WKEditorToolbarButton! - @IBOutlet private weak var citationButton: WKEditorToolbarButton! + @IBOutlet private weak var referenceButton: WKEditorToolbarButton! @IBOutlet private weak var linkButton: WKEditorToolbarButton! @IBOutlet private weak var templateButton: WKEditorToolbarButton! @IBOutlet private weak var imageButton: WKEditorToolbarButton! @@ -86,9 +87,9 @@ class WKEditorToolbarExpandingView: WKEditorToolbarView { formatTextButton.accessibilityIdentifier = WKSourceEditorAccessibilityIdentifiers.current?.formatTextButton formatTextButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current.accessibilityLabelButtonFormatText - citationButton.setImage(WKSFSymbolIcon.for(symbol: .quoteOpening), for: .normal) - citationButton.addTarget(self, action: #selector(tappedCitation), for: .touchUpInside) - citationButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current.accessibilityLabelButtonCitation + referenceButton.setImage(WKSFSymbolIcon.for(symbol: .quoteOpening), for: .normal) + referenceButton.addTarget(self, action: #selector(tappedReference), for: .touchUpInside) + referenceButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current.accessibilityLabelButtonCitation linkButton.setImage(WKSFSymbolIcon.for(symbol: .link), for: .normal) linkButton.addTarget(self, action: #selector(tappedLink), for: .touchUpInside) @@ -152,6 +153,7 @@ class WKEditorToolbarExpandingView: WKEditorToolbarView { } templateButton.isSelected = selectionState.isHorizontalTemplate + referenceButton.isSelected = selectionState.isHorizontalReference linkButton.isSelected = selectionState.isSimpleLink imageButton.isEnabled = !selectionState.isBold && !selectionState.isItalics && !selectionState.isSimpleLink @@ -224,7 +226,8 @@ class WKEditorToolbarExpandingView: WKEditorToolbarView { @objc private func tappedFormatHeading() { } - @objc private func tappedCitation() { + @objc private func tappedReference() { + delegate?.toolbarExpandingViewDidTapReference(toolbarView: self, isSelected: referenceButton.isSelected) } @objc private func tappedLink() { diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Expanding/WKEditorToolbarExpandingView.xib b/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Expanding/WKEditorToolbarExpandingView.xib index cf669071ea9..4b9c61e75cc 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Expanding/WKEditorToolbarExpandingView.xib +++ b/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Expanding/WKEditorToolbarExpandingView.xib @@ -267,7 +267,6 @@ - @@ -281,6 +280,7 @@ + diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Highlight/WKEditorToolbarHighlightView.swift b/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Highlight/WKEditorToolbarHighlightView.swift index ecf0c1b4b27..33238ec9cd9 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Highlight/WKEditorToolbarHighlightView.swift +++ b/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Highlight/WKEditorToolbarHighlightView.swift @@ -4,6 +4,7 @@ protocol WKEditorToolbarHighlightViewDelegate: AnyObject { func toolbarHighlightViewDidTapBold(toolbarView: WKEditorToolbarHighlightView, isSelected: Bool) func toolbarHighlightViewDidTapItalics(toolbarView: WKEditorToolbarHighlightView, isSelected: Bool) func toolbarHighlightViewDidTapTemplate(toolbarView: WKEditorToolbarHighlightView, isSelected: Bool) + func toolbarHighlightViewDidTapReference(toolbarView: WKEditorToolbarHighlightView, isSelected: Bool) func toolbarHighlightViewDidTapLink(toolbarView: WKEditorToolbarHighlightView, isSelected: Bool) func toolbarHighlightViewDidTapShowMore(toolbarView: WKEditorToolbarHighlightView) } @@ -16,7 +17,7 @@ class WKEditorToolbarHighlightView: WKEditorToolbarView { @IBOutlet private weak var boldButton: WKEditorToolbarButton! @IBOutlet private weak var italicsButton: WKEditorToolbarButton! - @IBOutlet private weak var citationButton: WKEditorToolbarButton! + @IBOutlet private weak var referenceButton: WKEditorToolbarButton! @IBOutlet private weak var linkButton: WKEditorToolbarButton! @IBOutlet private weak var templateButton: WKEditorToolbarButton! @IBOutlet private weak var showMoreButton: WKEditorToolbarNavigatorButton! @@ -39,9 +40,9 @@ class WKEditorToolbarHighlightView: WKEditorToolbarView { italicsButton.addTarget(self, action: #selector(tappedItalics), for: .touchUpInside) italicsButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current?.accessibilityLabelButtonItalics - citationButton.setImage(WKSFSymbolIcon.for(symbol: .quoteOpening), for: .normal) - citationButton.addTarget(self, action: #selector(tappedCitation), for: .touchUpInside) - citationButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current?.accessibilityLabelButtonCitation + referenceButton.setImage(WKSFSymbolIcon.for(symbol: .quoteOpening), for: .normal) + referenceButton.addTarget(self, action: #selector(tappedReference), for: .touchUpInside) + referenceButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current?.accessibilityLabelButtonCitation linkButton.setImage(WKSFSymbolIcon.for(symbol: .link), for: .normal) linkButton.addTarget(self, action: #selector(tappedLink), for: .touchUpInside) @@ -68,6 +69,7 @@ class WKEditorToolbarHighlightView: WKEditorToolbarView { boldButton.isSelected = selectionState.isBold italicsButton.isSelected = selectionState.isItalics templateButton.isSelected = selectionState.isHorizontalTemplate + referenceButton.isSelected = selectionState.isHorizontalReference linkButton.isSelected = selectionState.isSimpleLink } @@ -84,7 +86,8 @@ class WKEditorToolbarHighlightView: WKEditorToolbarView { @objc private func tappedFormatHeading() { } - @objc private func tappedCitation() { + @objc private func tappedReference() { + delegate?.toolbarHighlightViewDidTapReference(toolbarView: self, isSelected: referenceButton.isSelected) } @objc private func tappedLink() { diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Highlight/WKEditorToolbarHighlightView.xib b/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Highlight/WKEditorToolbarHighlightView.xib index 78e7480b361..37ecb6e927b 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Highlight/WKEditorToolbarHighlightView.xib +++ b/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Highlight/WKEditorToolbarHighlightView.xib @@ -90,9 +90,9 @@ - + diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorInputView.swift b/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorInputView.swift index bcb20840fb6..7aa62ae047f 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorInputView.swift +++ b/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorInputView.swift @@ -6,6 +6,7 @@ protocol WKEditorInputViewDelegate: AnyObject { func didTapBold(isSelected: Bool) func didTapItalics(isSelected: Bool) func didTapTemplate(isSelected: Bool) + func didTapReference(isSelected: Bool) func didTapBulletList(isSelected: Bool) func didTapNumberList(isSelected: Bool) func didTapIncreaseIndent() diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorToolbarPlainView.swift b/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorToolbarPlainView.swift index b39cd5a7cbd..57468fe1075 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorToolbarPlainView.swift +++ b/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorToolbarPlainView.swift @@ -6,7 +6,7 @@ class WKEditorToolbarPlainView: WKEditorToolbarView { @IBOutlet private weak var boldButton: WKEditorToolbarButton! @IBOutlet private weak var italicsButton: WKEditorToolbarButton! - @IBOutlet private weak var citationButton: WKEditorToolbarButton! + @IBOutlet private weak var referenceButton: WKEditorToolbarButton! @IBOutlet private weak var linkButton: WKEditorToolbarButton! @IBOutlet private weak var templateButton: WKEditorToolbarButton! @IBOutlet private weak var commentButton: WKEditorToolbarButton! @@ -26,9 +26,9 @@ class WKEditorToolbarPlainView: WKEditorToolbarView { italicsButton.addTarget(self, action: #selector(tappedItalics), for: .touchUpInside) italicsButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current?.accessibilityLabelButtonItalics - citationButton.setImage(WKSFSymbolIcon.for(symbol: .quoteOpening), for: .normal) - citationButton.addTarget(self, action: #selector(tappedCitation), for: .touchUpInside) - citationButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current?.accessibilityLabelButtonCitation + referenceButton.setImage(WKSFSymbolIcon.for(symbol: .quoteOpening), for: .normal) + referenceButton.addTarget(self, action: #selector(tappedReference), for: .touchUpInside) + referenceButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current?.accessibilityLabelButtonCitation linkButton.setImage(WKIcon.link, for: .normal) linkButton.addTarget(self, action: #selector(tappedLink), for: .touchUpInside) @@ -55,6 +55,7 @@ class WKEditorToolbarPlainView: WKEditorToolbarView { boldButton.isSelected = selectionState.isBold italicsButton.isSelected = selectionState.isItalics templateButton.isSelected = selectionState.isHorizontalTemplate + referenceButton.isSelected = selectionState.isHorizontalReference linkButton.isSelected = selectionState.isSimpleLink } @@ -68,7 +69,8 @@ class WKEditorToolbarPlainView: WKEditorToolbarView { delegate?.didTapItalics(isSelected: italicsButton.isSelected) } - @objc private func tappedCitation() { + @objc private func tappedReference() { + delegate?.didTapReference(isSelected: referenceButton.isSelected) } @objc private func tappedTemplate() { diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorToolbarPlainView.xib b/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorToolbarPlainView.xib index 0e700570dac..61d1e9b3fb0 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorToolbarPlainView.xib +++ b/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorToolbarPlainView.xib @@ -1,9 +1,8 @@ - + - - + @@ -54,10 +53,10 @@ - + diff --git a/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatter+ButtonActions.swift b/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatter+ButtonActions.swift index ce9f1869e4e..7364b92825b 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatter+ButtonActions.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatter+ButtonActions.swift @@ -14,19 +14,26 @@ extension WKSourceEditorFormatter { toggleFormatting(startingFormattingString: formattingString, endingFormattingString: formattingString, action: action, in: textView) } - func toggleFormatting(startingFormattingString: String, endingFormattingString: String, action: WKSourceEditorFormatterButtonAction, in textView: UITextView) { - + func toggleFormatting(startingFormattingString: String, wildcardStartingFormattingString: String? = nil, endingFormattingString: String, action: WKSourceEditorFormatterButtonAction, in textView: UITextView) { + + var resolvedStartingFormattingString = startingFormattingString + if let wildcardStartingFormattingString { + resolvedStartingFormattingString = getModifiedStartingFormattingStringForSingleWildcard(startingFormattingString: wildcardStartingFormattingString, textView: textView) + } + if textView.selectedRange.length == 0 { + switch action { case .remove: - expandSelectedRangeUpToNearestFormattingStrings(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView) + + expandSelectedRangeUpToNearestFormattingStrings(startingFormattingString: resolvedStartingFormattingString, endingFormattingString: endingFormattingString, in: textView) - if selectedRangeIsSurroundedByFormattingStrings(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView) { - removeSurroundingFormattingStringsFromSelectedRange(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView) + if selectedRangeIsSurroundedByFormattingStrings(startingFormattingString: resolvedStartingFormattingString, endingFormattingString: endingFormattingString, in: textView) { + removeSurroundingFormattingStringsFromSelectedRange(startingFormattingString: resolvedStartingFormattingString, endingFormattingString: endingFormattingString, in: textView) } case .add: - if selectedRangeIsSurroundedByFormattingStrings(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView) { - removeSurroundingFormattingStringsFromSelectedRange(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView) + if selectedRangeIsSurroundedByFormattingStrings(startingFormattingString: resolvedStartingFormattingString, endingFormattingString: endingFormattingString, in: textView) { + removeSurroundingFormattingStringsFromSelectedRange(startingFormattingString: resolvedStartingFormattingString, endingFormattingString: endingFormattingString, in: textView) } else { addStringFormattingCharacters(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView) } @@ -36,8 +43,8 @@ extension WKSourceEditorFormatter { switch action { case .remove: - if selectedRangeIsSurroundedByFormattingStrings(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView) { - removeSurroundingFormattingStringsFromSelectedRange(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView) + if selectedRangeIsSurroundedByFormattingStrings(startingFormattingString: resolvedStartingFormattingString, endingFormattingString: endingFormattingString, in: textView) { + removeSurroundingFormattingStringsFromSelectedRange(startingFormattingString: resolvedStartingFormattingString, endingFormattingString: endingFormattingString, in: textView) } else { // Note the flipped formatting string params. @@ -77,6 +84,64 @@ extension WKSourceEditorFormatter { } } + + /// Takes a starting formatting string with a wildcard. Searches backwards from the selection point and seeks out a match (up until the first line break) and sends back a resolved starting formatting string + /// For example: + /// If you send in "", it can match and send back "" + /// - Parameters: + /// - originalStartingFormattingString: Starting formatting string with a single wildcard character (*) + /// - textView: UITextView to search + /// - Returns: A new resolved starting formatting string with no wildcard. + func getModifiedStartingFormattingStringForSingleWildcard(startingFormattingString originalStartingFormattingString: String, textView: UITextView) -> String { + guard originalStartingFormattingString.contains("*") else { + return originalStartingFormattingString + } + + let splitStrings = originalStartingFormattingString.split(separator: "*").map { String($0) } + guard splitStrings.count == 2 else { + return originalStartingFormattingString + } + + guard let originalSelectedRange = textView.selectedTextRange else { + return originalStartingFormattingString + } + + // Loop backwards and find + var i = 0 + var closingPosition: UITextPosition? + var startingPosition: UITextPosition? + while let loopPosition = textView.position(from: originalSelectedRange.start, offset: i) { + let newRange = textView.textRange(from: loopPosition, to: originalSelectedRange.start) + + if closingPosition == nil && rangeIsPrecededByFormattingString(range: newRange, formattingString: splitStrings[1], in: textView) { + + closingPosition = loopPosition + continue + } + + if closingPosition != nil && rangeIsPrecededByFormattingString(range: newRange, formattingString: splitStrings[0], in: textView) { + startingPosition = textView.position(from: loopPosition, offset: -splitStrings[0].count) + break + } + + // Stop searching if you encounter a line break + if rangeIsPrecededByFormattingString(range: newRange, formattingString: "\n", in: textView) { + break + } + + i = i - 1 + } + + guard let startingPosition, + let closingPosition, + let newTextRange = textView.textRange(from: startingPosition, to: closingPosition), + let modifiedStartingFormattingString = textView.text(in: newTextRange) else { + return originalStartingFormattingString + } + + return modifiedStartingFormattingString + } + // MARK: - Expanding selected range methods func expandSelectedRangeUpToNearestFormattingStrings(startingFormattingString: String, endingFormattingString: String, in textView: UITextView) { diff --git a/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorReference+ButtonActions.swift b/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorReference+ButtonActions.swift new file mode 100644 index 00000000000..baf19113136 --- /dev/null +++ b/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorReference+ButtonActions.swift @@ -0,0 +1,8 @@ +import Foundation +import ComponentsObjC + +extension WKSourceEditorFormatterReference { + func toggleReferenceFormatting(action: WKSourceEditorFormatterButtonAction, in textView: UITextView) { + toggleFormatting(startingFormattingString: "", wildcardStartingFormattingString: "", endingFormattingString: "", action: action, in: textView) + } +} diff --git a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift index 11f3897ed6d..9859fff34ec 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift @@ -17,6 +17,7 @@ fileprivate var needsTextKit2: Bool { let isBold: Bool let isItalics: Bool let isHorizontalTemplate: Bool + let isHorizontalReference: Bool let isBulletSingleList: Bool let isBulletMultipleList: Bool let isNumberSingleList: Bool @@ -29,11 +30,11 @@ fileprivate var needsTextKit2: Bool { let isStrikethrough: Bool let isSimpleLink: Bool let isLinkWithNestedLink: Bool - - init(isBold: Bool, isItalics: Bool, isHorizontalTemplate: Bool, isBulletSingleList: Bool, isBulletMultipleList: Bool, isNumberSingleList: Bool, isNumberMultipleList: Bool, isHeading: Bool, isSubheading1: Bool, isSubheading2: Bool, isSubheading3: Bool, isSubheading4: Bool, isStrikethrough: Bool, isSimpleLink: Bool, isLinkWithNestedLink: Bool) { + init(isBold: Bool, isItalics: Bool, isHorizontalTemplate: Bool, isHorizontalReference: Bool, isBulletSingleList: Bool, isBulletMultipleList: Bool, isNumberSingleList: Bool, isNumberMultipleList: Bool, isHeading: Bool, isSubheading1: Bool, isSubheading2: Bool, isSubheading3: Bool, isSubheading4: Bool, isStrikethrough: Bool, isSimpleLink: Bool, isLinkWithNestedLink: Bool) { self.isBold = isBold self.isItalics = isItalics self.isHorizontalTemplate = isHorizontalTemplate + self.isHorizontalReference = isHorizontalReference self.isBulletSingleList = isBulletSingleList self.isBulletMultipleList = isBulletMultipleList self.isNumberSingleList = isNumberSingleList @@ -47,6 +48,7 @@ fileprivate var needsTextKit2: Bool { self.isSimpleLink = isSimpleLink self.isLinkWithNestedLink = isLinkWithNestedLink } + } final class WKSourceEditorTextFrameworkMediator: NSObject { @@ -59,6 +61,7 @@ final class WKSourceEditorTextFrameworkMediator: NSObject { private(set) var formatters: [WKSourceEditorFormatter] = [] private(set) var boldItalicsFormatter: WKSourceEditorFormatterBoldItalics? private(set) var templateFormatter: WKSourceEditorFormatterTemplate? + private(set) var referenceFormatter: WKSourceEditorFormatterReference? private(set) var listFormatter: WKSourceEditorFormatterList? private(set) var headingFormatter: WKSourceEditorFormatterHeading? private(set) var strikethroughFormatter: WKSourceEditorFormatterStrikethrough? @@ -130,6 +133,7 @@ final class WKSourceEditorTextFrameworkMediator: NSObject { let fonts = self.fonts let templateFormatter = WKSourceEditorFormatterTemplate(colors: colors, fonts: fonts) + let referenceFormatter = WKSourceEditorFormatterReference(colors: colors, fonts: fonts) let boldItalicsFormatter = WKSourceEditorFormatterBoldItalics(colors: colors, fonts: fonts) let listFormatter = WKSourceEditorFormatterList(colors: colors, fonts: fonts) let headingFormatter = WKSourceEditorFormatterHeading(colors: colors, fonts: fonts) @@ -138,16 +142,18 @@ final class WKSourceEditorTextFrameworkMediator: NSObject { self.formatters = [WKSourceEditorFormatterBase(colors: colors, fonts: fonts, textAlignment: viewModel.textAlignment), templateFormatter, boldItalicsFormatter, + referenceFormatter, listFormatter, headingFormatter, strikethroughFormatter, linkFormatter] self.boldItalicsFormatter = boldItalicsFormatter self.templateFormatter = templateFormatter + self.referenceFormatter = referenceFormatter self.listFormatter = listFormatter self.headingFormatter = headingFormatter self.strikethroughFormatter = strikethroughFormatter self.linkFormatter = linkFormatter - + if needsTextKit2 { if #available(iOS 16.0, *) { let textContentManager = textView.textLayoutManager?.textContentManager @@ -186,12 +192,14 @@ final class WKSourceEditorTextFrameworkMediator: NSObject { if needsTextKit2 { guard let textKit2Data = textkit2SelectionData(selectedDocumentRange: selectedDocumentRange) else { - return WKSourceEditorSelectionState(isBold: false, isItalics: false, isHorizontalTemplate: false, isBulletSingleList: false, isBulletMultipleList: false, isNumberSingleList: false, isNumberMultipleList: false, isHeading: false, isSubheading1: false, isSubheading2: false, isSubheading3: false, isSubheading4: false, isStrikethrough: false, isSimpleLink: false, isLinkWithNestedLink: false) + return WKSourceEditorSelectionState(isBold: false, isItalics: false, isHorizontalTemplate: false, isHorizontalReference: false, isBulletSingleList: false, isBulletMultipleList: false, isNumberSingleList: false, isNumberMultipleList: false, isHeading: false, isSubheading1: false, isSubheading2: false, isSubheading3: false, isSubheading4: false, isStrikethrough: false, isSimpleLink: false, isLinkWithNestedLink: false) + } let isBold = boldItalicsFormatter?.attributedString(textKit2Data.paragraphAttributedString, isBoldIn: textKit2Data.paragraphSelectedRange) ?? false let isItalics = boldItalicsFormatter?.attributedString(textKit2Data.paragraphAttributedString, isItalicsIn: textKit2Data.paragraphSelectedRange) ?? false let isHorizontalTemplate = templateFormatter?.attributedString(textKit2Data.paragraphAttributedString, isHorizontalTemplateIn: textKit2Data.paragraphSelectedRange) ?? false + let isHorizontalReference = referenceFormatter?.attributedString(textKit2Data.paragraphAttributedString, isHorizontalReferenceIn: textKit2Data.paragraphSelectedRange) ?? false let isBulletSingleList = listFormatter?.attributedString(textKit2Data.paragraphAttributedString, isBulletSingleIn: textKit2Data.paragraphSelectedRange) ?? false let isBulletMultipleList = listFormatter?.attributedString(textKit2Data.paragraphAttributedString, isBulletMultipleIn: textKit2Data.paragraphSelectedRange) ?? false let isNumberSingleList = listFormatter?.attributedString(textKit2Data.paragraphAttributedString, isNumberSingleIn: textKit2Data.paragraphSelectedRange) ?? false @@ -205,15 +213,16 @@ final class WKSourceEditorTextFrameworkMediator: NSObject { let isSimpleLink = linkFormatter?.attributedString(textKit2Data.paragraphAttributedString, isSimpleLinkIn: textKit2Data.paragraphSelectedRange) ?? false let isLinkWithNestedLink = linkFormatter?.attributedString(textKit2Data.paragraphAttributedString, isLinkWithNestedLinkIn: textKit2Data.paragraphSelectedRange) ?? false - return WKSourceEditorSelectionState(isBold: isBold, isItalics: isItalics, isHorizontalTemplate: isHorizontalTemplate, isBulletSingleList: isBulletSingleList, isBulletMultipleList: isBulletMultipleList, isNumberSingleList: isNumberSingleList, isNumberMultipleList: isNumberMultipleList, isHeading: isHeading, isSubheading1: isSubheading1, isSubheading2: isSubheading2, isSubheading3: isSubheading3, isSubheading4: isSubheading4, isStrikethrough: isStrikethrough, isSimpleLink: isSimpleLink, isLinkWithNestedLink: isLinkWithNestedLink) + return WKSourceEditorSelectionState(isBold: isBold, isItalics: isItalics, isHorizontalTemplate: isHorizontalTemplate, isHorizontalReference: isHorizontalReference, isBulletSingleList: isBulletSingleList, isBulletMultipleList: isBulletMultipleList, isNumberSingleList: isNumberSingleList, isNumberMultipleList: isNumberMultipleList, isHeading: isHeading, isSubheading1: isSubheading1, isSubheading2: isSubheading2, isSubheading3: isSubheading3, isSubheading4: isSubheading4, isStrikethrough: isStrikethrough, isSimpleLink: isSimpleLink, isLinkWithNestedLink: isLinkWithNestedLink) } else { guard let textKit1Storage else { - return WKSourceEditorSelectionState(isBold: false, isItalics: false, isHorizontalTemplate: false, isBulletSingleList: false, isBulletMultipleList: false, isNumberSingleList: false, isNumberMultipleList: false, isHeading: false, isSubheading1: false, isSubheading2: false, isSubheading3: false, isSubheading4: false, isStrikethrough: false, isSimpleLink: false, isLinkWithNestedLink: false) + return WKSourceEditorSelectionState(isBold: false, isItalics: false, isHorizontalTemplate: false, isHorizontalReference: false, isBulletSingleList: false, isBulletMultipleList: false, isNumberSingleList: false, isNumberMultipleList: false, isHeading: false, isSubheading1: false, isSubheading2: false, isSubheading3: false, isSubheading4: false, isStrikethrough: false, isSimpleLink: false, isLinkWithNestedLink: false) } - + let isBold = boldItalicsFormatter?.attributedString(textKit1Storage, isBoldIn: selectedDocumentRange) ?? false let isItalics = boldItalicsFormatter?.attributedString(textKit1Storage, isItalicsIn: selectedDocumentRange) ?? false let isHorizontalTemplate = templateFormatter?.attributedString(textKit1Storage, isHorizontalTemplateIn: selectedDocumentRange) ?? false + let isHorizontalReference = referenceFormatter?.attributedString(textKit1Storage, isHorizontalReferenceIn: selectedDocumentRange) ?? false let isBulletSingleList = listFormatter?.attributedString(textKit1Storage, isBulletSingleIn: selectedDocumentRange) ?? false let isBulletMultipleList = listFormatter?.attributedString(textKit1Storage, isBulletMultipleIn: selectedDocumentRange) ?? false let isNumberSingleList = listFormatter?.attributedString(textKit1Storage, isNumberSingleIn: selectedDocumentRange) ?? false @@ -227,7 +236,7 @@ final class WKSourceEditorTextFrameworkMediator: NSObject { let isSimpleLink = linkFormatter?.attributedString(textKit1Storage, isSimpleLinkIn: selectedDocumentRange) ?? false let isLinkWithNestedLink = linkFormatter?.attributedString(textKit1Storage, isLinkWithNestedLinkIn: selectedDocumentRange) ?? false - return WKSourceEditorSelectionState(isBold: isBold, isItalics: isItalics, isHorizontalTemplate: isHorizontalTemplate, isBulletSingleList: isBulletSingleList, isBulletMultipleList: isBulletMultipleList, isNumberSingleList: isNumberSingleList, isNumberMultipleList: isNumberMultipleList, isHeading: isHeading, isSubheading1: isSubheading1, isSubheading2: isSubheading2, isSubheading3: isSubheading3, isSubheading4: isSubheading4, isStrikethrough: isStrikethrough, isSimpleLink: isSimpleLink, isLinkWithNestedLink: isLinkWithNestedLink) + return WKSourceEditorSelectionState(isBold: isBold, isItalics: isItalics, isHorizontalTemplate: isHorizontalTemplate, isHorizontalReference: isHorizontalReference, isBulletSingleList: isBulletSingleList, isBulletMultipleList: isBulletMultipleList, isNumberSingleList: isNumberSingleList, isNumberMultipleList: isNumberMultipleList, isHeading: isHeading, isSubheading1: isSubheading1, isSubheading2: isSubheading2, isSubheading3: isSubheading3, isSubheading4: isSubheading4, isStrikethrough: isStrikethrough, isSimpleLink: isSimpleLink, isLinkWithNestedLink: isLinkWithNestedLink) } } diff --git a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift index 9e874c794bd..95555725aa5 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift @@ -314,7 +314,12 @@ extension WKSourceEditorViewController: WKEditorToolbarExpandingViewDelegate { let action: WKSourceEditorFormatterButtonAction = isSelected ? .remove : .add textFrameworkMediator.templateFormatter?.toggleTemplateFormatting(action: action, in: textView) } - + + func toolbarExpandingViewDidTapReference(toolbarView: WKEditorToolbarExpandingView, isSelected: Bool) { + let action: WKSourceEditorFormatterButtonAction = isSelected ? .remove : .add + textFrameworkMediator.referenceFormatter?.toggleReferenceFormatting(action: action, in: textView) + } + func toolbarExpandingViewDidTapLink(toolbarView: WKEditorToolbarExpandingView, isSelected: Bool) { presentLinkWizard(linkButtonIsSelected: isSelected) } @@ -360,9 +365,15 @@ extension WKSourceEditorViewController: WKEditorToolbarHighlightViewDelegate { let action: WKSourceEditorFormatterButtonAction = isSelected ? .remove : .add textFrameworkMediator.templateFormatter?.toggleTemplateFormatting(action: action, in: textView) } - + + func toolbarHighlightViewDidTapReference(toolbarView: WKEditorToolbarHighlightView, isSelected: Bool) { + let action: WKSourceEditorFormatterButtonAction = isSelected ? .remove : .add + textFrameworkMediator.referenceFormatter?.toggleReferenceFormatting(action: action, in: textView) + } + func toolbarHighlightViewDidTapLink(toolbarView: WKEditorToolbarHighlightView, isSelected: Bool) { presentLinkWizard(linkButtonIsSelected: isSelected) + } func toolbarHighlightViewDidTapShowMore(toolbarView: WKEditorToolbarHighlightView) { @@ -392,6 +403,11 @@ extension WKSourceEditorViewController: WKEditorInputViewDelegate { let action: WKSourceEditorFormatterButtonAction = isSelected ? .remove : .add textFrameworkMediator.templateFormatter?.toggleTemplateFormatting(action: action, in: textView) } + + func didTapReference(isSelected: Bool) { + let action: WKSourceEditorFormatterButtonAction = isSelected ? .remove : .add + textFrameworkMediator.referenceFormatter?.toggleReferenceFormatting(action: action, in: textView) + } func didTapBulletList(isSelected: Bool) { let action: WKSourceEditorFormatterButtonAction = isSelected ? .remove : .add diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterReference.h b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterReference.h new file mode 100644 index 00000000000..a3296aedfe2 --- /dev/null +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterReference.h @@ -0,0 +1,9 @@ +#import "WKSourceEditorFormatter.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface WKSourceEditorFormatterReference : WKSourceEditorFormatter +- (BOOL)attributedString:(NSMutableAttributedString *)attributedString isHorizontalReferenceInRange:(NSRange)range; +@end + +NS_ASSUME_NONNULL_END diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterReference.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterReference.m new file mode 100644 index 00000000000..f427197c3b4 --- /dev/null +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterReference.m @@ -0,0 +1,179 @@ +#import "WKSourceEditorFormatterReference.h" +#import "WKSourceEditorColors.h" + +@interface WKSourceEditorFormatterReference () +@property (nonatomic, strong) NSDictionary *refAttributes; +@property (nonatomic, strong) NSDictionary *refEmptyAttributes; +@property (nonatomic, strong) NSDictionary *refContentAttributes; + +@property (nonatomic, strong) NSRegularExpression *refHorizontalRegex; +@property (nonatomic, strong) NSRegularExpression *refOpenRegex; +@property (nonatomic, strong) NSRegularExpression *refCloseRegex; +@property (nonatomic, strong) NSRegularExpression *refEmptyRegex; +@end + +@implementation WKSourceEditorFormatterReference + +#pragma mark - Custom Attributed String Keys + +NSString * const WKSourceEditorCustomKeyContentReference = @"WKSourceEditorCustomKeyContentReference"; + +#pragma mark - Overrides + +- (instancetype)initWithColors:(WKSourceEditorColors *)colors fonts:(WKSourceEditorFonts *)fonts { + self = [super initWithColors:colors fonts:fonts]; + if (self) { + _refAttributes = @{ + NSForegroundColorAttributeName: colors.greenForegroundColor, + WKSourceEditorCustomKeyColorGreen: [NSNumber numberWithBool:YES] + }; + + _refEmptyAttributes = @{ + NSForegroundColorAttributeName: colors.greenForegroundColor, + WKSourceEditorCustomKeyColorGreen: [NSNumber numberWithBool:YES] + }; + + _refContentAttributes = @{ + WKSourceEditorCustomKeyContentReference: [NSNumber numberWithBool:YES] + }; + + _refHorizontalRegex = [[NSRegularExpression alloc] initWithPattern:@"(]+?)?>)(.*?)(<\\/ref>)" options:0 error:nil]; + _refOpenRegex = [[NSRegularExpression alloc] initWithPattern:@"]+?)?>" options:0 error:nil]; + _refCloseRegex = [[NSRegularExpression alloc] initWithPattern:@"<\\/ref>" options:0 error:nil]; + _refEmptyRegex = [[NSRegularExpression alloc] initWithPattern:@"]+?\\/>" options:0 error:nil]; + } + + return self; +} + +- (void)addSyntaxHighlightingToAttributedString:(nonnull NSMutableAttributedString *)attributedString inRange:(NSRange)range { + + // Reset + [attributedString removeAttribute:WKSourceEditorCustomKeyContentReference range:range]; + + [self.refHorizontalRegex enumerateMatchesInString:attributedString.string + options:0 + range:range + usingBlock:^(NSTextCheckingResult *_Nullable result, NSMatchingFlags flags, BOOL *_Nonnull stop) { + NSRange fullMatch = [result rangeAtIndex:0]; + NSRange openingRange = [result rangeAtIndex:1]; + NSRange contentRange = [result rangeAtIndex:2]; + NSRange closingRange = [result rangeAtIndex:3]; + + if (openingRange.location != NSNotFound) { + [attributedString addAttributes:self.refAttributes range:openingRange]; + } + + if (contentRange.location != NSNotFound) { + [attributedString addAttributes:self.refContentAttributes range:contentRange]; + } + + if (closingRange.location != NSNotFound) { + [attributedString addAttributes:self.refAttributes range:closingRange]; + } + }]; + + [self.refEmptyRegex enumerateMatchesInString:attributedString.string + options:0 + range:range + usingBlock:^(NSTextCheckingResult *_Nullable result, NSMatchingFlags flags, BOOL *_Nonnull stop) { + NSRange fullMatch = [result rangeAtIndex:0]; + + if (fullMatch.location != NSNotFound) { + [attributedString addAttributes:self.refAttributes range:fullMatch]; + } + }]; + + // refOpenAndClose regex doesn't match everything. This scoops up extra open and closing ref tags that do not have a matching tag on the same line + + [self.refOpenRegex enumerateMatchesInString:attributedString.string + options:0 + range:range + usingBlock:^(NSTextCheckingResult *_Nullable result, NSMatchingFlags flags, BOOL *_Nonnull stop) { + NSRange fullMatch = [result rangeAtIndex:0]; + + if (fullMatch.location != NSNotFound) { + [attributedString addAttributes:self.refAttributes range:fullMatch]; + } + }]; + + [self.refCloseRegex enumerateMatchesInString:attributedString.string + options:0 + range:range + usingBlock:^(NSTextCheckingResult *_Nullable result, NSMatchingFlags flags, BOOL *_Nonnull stop) { + NSRange fullMatch = [result rangeAtIndex:0]; + + if (fullMatch.location != NSNotFound) { + [attributedString addAttributes:self.refAttributes range:fullMatch]; + } + }]; +} + +- (void)updateColors:(WKSourceEditorColors *)colors inAttributedString:(NSMutableAttributedString *)attributedString inRange:(NSRange)range { + + NSMutableDictionary *mutAttributes = [[NSMutableDictionary alloc] initWithDictionary:self.refAttributes]; + [mutAttributes setObject:colors.greenForegroundColor forKey:NSForegroundColorAttributeName]; + self.refAttributes = [[NSDictionary alloc] initWithDictionary:mutAttributes]; + + [attributedString enumerateAttribute:WKSourceEditorCustomKeyColorGreen + inRange:range + options:nil + usingBlock:^(id value, NSRange localRange, BOOL *stop) { + if ([value isKindOfClass: [NSNumber class]]) { + NSNumber *numValue = (NSNumber *)value; + if ([numValue boolValue] == YES) { + [attributedString addAttributes:self.refAttributes range:localRange]; + } + } + }]; +} + +- (void)updateFonts:(WKSourceEditorFonts *)fonts inAttributedString:(NSMutableAttributedString *)attributedString inRange:(NSRange)range { + // No special font handling needed for references +} + +#pragma mark - Public + +- (BOOL)attributedString:(NSMutableAttributedString *)attributedString isHorizontalReferenceInRange:(NSRange)range { + __block BOOL isContentKey = NO; + + if (range.length == 0) { + + if (attributedString.length > range.location) { + NSDictionary *attrs = [attributedString attributesAtIndex:range.location effectiveRange:nil]; + + if (attrs[WKSourceEditorCustomKeyContentReference] != nil) { + isContentKey = YES; + } else { + // Edge case, check previous character if we are up against closing string + if (attrs[WKSourceEditorCustomKeyColorGreen]) { + attrs = [attributedString attributesAtIndex:range.location - 1 effectiveRange:nil]; + if (attrs[WKSourceEditorCustomKeyContentReference] != nil) { + isContentKey = YES; + } + } + } + } + + } else { + __block NSRange unionRange = NSMakeRange(NSNotFound, 0); + [attributedString enumerateAttributesInRange:range options:nil usingBlock:^(NSDictionary * _Nonnull attrs, NSRange loopRange, BOOL * _Nonnull stop) { + if (attrs[WKSourceEditorCustomKeyContentReference] != nil) { + if (unionRange.location == NSNotFound) { + unionRange = loopRange; + } else { + unionRange = NSUnionRange(unionRange, loopRange); + } + stop = YES; + } + }]; + + if (NSEqualRanges(unionRange, range)) { + isContentKey = YES; + } + } + + return isContentKey; +} + +@end diff --git a/Components/Sources/ComponentsObjC/include/ComponentsObjC.h b/Components/Sources/ComponentsObjC/include/ComponentsObjC.h index 13a96ff2f1b..c7ef8ee727c 100644 --- a/Components/Sources/ComponentsObjC/include/ComponentsObjC.h +++ b/Components/Sources/ComponentsObjC/include/ComponentsObjC.h @@ -8,6 +8,7 @@ #import "WKSourceEditorFormatterBase.h" #import "WKSourceEditorFormatterBoldItalics.h" #import "WKSourceEditorFormatterTemplate.h" +#import "WKSourceEditorFormatterReference.h" #import "WKSourceEditorFormatterList.h" #import "WKSourceEditorFormatterHeading.h" #import "WKSourceEditorFormatterStrikethrough.h" diff --git a/Components/Sources/ComponentsObjC/include/WKSourceEditorFormatterReference.h b/Components/Sources/ComponentsObjC/include/WKSourceEditorFormatterReference.h new file mode 120000 index 00000000000..5443694ba1a --- /dev/null +++ b/Components/Sources/ComponentsObjC/include/WKSourceEditorFormatterReference.h @@ -0,0 +1 @@ +../WKSourceEditorFormatterReference.h \ No newline at end of file diff --git a/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift index cc97bb19df2..94f3420f164 100644 --- a/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift +++ b/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift @@ -163,6 +163,46 @@ final class WKSourceEditorFormatterButtonActionTests: XCTestCase { XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four") } + func testReferenceInsertAndRemove() throws { + let text = "One Two Three Four" + mediator.textView.attributedText = NSAttributedString(string: text) + mediator.textView.selectedRange = NSRange(location: 4, length: 3) + mediator.referenceFormatter?.toggleReferenceFormatting(action: .add, in: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four") + mediator.referenceFormatter?.toggleReferenceFormatting(action: .remove, in: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four") + } + + func testReferenceInsertAndRemoveCursor() throws { + let text = "One Two Three Four" + mediator.textView.attributedText = NSAttributedString(string: text) + mediator.textView.selectedRange = NSRange(location: 4, length: 0) + mediator.referenceFormatter?.toggleReferenceFormatting(action: .add, in: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four") + mediator.referenceFormatter?.toggleReferenceFormatting(action: .remove, in: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four") + } + + func testReferenceNamedRemoveAndInsert() throws { + let text = "One Two Three Four" + mediator.textView.attributedText = NSAttributedString(string: text) + mediator.textView.selectedRange = NSRange(location: 24, length: 3) + mediator.referenceFormatter?.toggleReferenceFormatting(action: .remove, in: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four") + mediator.referenceFormatter?.toggleReferenceFormatting(action: .add, in: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four") + } + + func testReferenceNamedRemoveAndInsertCursor() throws { + let text = "One Two Three Four" + mediator.textView.attributedText = NSAttributedString(string: text) + mediator.textView.selectedRange = NSRange(location: 25, length: 0) + mediator.referenceFormatter?.toggleReferenceFormatting(action: .remove, in: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four") + mediator.referenceFormatter?.toggleReferenceFormatting(action: .add, in: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four") + } + func testListBulletInsertAndRemove() throws { let text = "Test" mediator.textView.attributedText = NSAttributedString(string: text) diff --git a/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift index 4664ceadaeb..5f694f68043 100644 --- a/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift +++ b/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift @@ -10,12 +10,13 @@ final class WKSourceEditorFormatterTests: XCTestCase { var baseFormatter: WKSourceEditorFormatterBase! var boldItalicsFormatter: WKSourceEditorFormatterBoldItalics! var templateFormatter: WKSourceEditorFormatterTemplate! + var referenceFormatter: WKSourceEditorFormatterReference! var listFormatter: WKSourceEditorFormatterList! var headingFormatter: WKSourceEditorFormatterHeading! var strikethroughFormatter: WKSourceEditorFormatterStrikethrough! var linkFormatter: WKSourceEditorFormatterLink! var formatters: [WKSourceEditorFormatter] { - return [baseFormatter, templateFormatter, boldItalicsFormatter, listFormatter, headingFormatter, strikethroughFormatter, linkFormatter] + return [baseFormatter, templateFormatter, boldItalicsFormatter, referenceFormatter, listFormatter, headingFormatter, strikethroughFormatter, linkFormatter] } override func setUpWithError() throws { @@ -42,6 +43,7 @@ final class WKSourceEditorFormatterTests: XCTestCase { self.baseFormatter = WKSourceEditorFormatterBase(colors: colors, fonts: fonts, textAlignment: .left) self.boldItalicsFormatter = WKSourceEditorFormatterBoldItalics(colors: colors, fonts: fonts) self.templateFormatter = WKSourceEditorFormatterTemplate(colors: colors, fonts: fonts) + self.referenceFormatter = WKSourceEditorFormatterReference(colors: colors, fonts: fonts) self.listFormatter = WKSourceEditorFormatterList(colors: colors, fonts: fonts) self.headingFormatter = WKSourceEditorFormatterHeading(colors: colors, fonts: fonts) self.strikethroughFormatter = WKSourceEditorFormatterStrikethrough(colors: colors, fonts: fonts) @@ -630,7 +632,7 @@ final class WKSourceEditorFormatterTests: XCTestCase { XCTAssertEqual(refOpeningRange.location, 0, "Incorrect ref formatting") XCTAssertEqual(refOpeningRange.length, 5, "Incorrect ref formatting") XCTAssertEqual(refOpeningAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect ref formatting") - XCTAssertEqual(refOpeningAttributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect ref formatting") + XCTAssertEqual(refOpeningAttributes[.foregroundColor] as! UIColor, colors.greenForegroundColor, "Incorrect ref formatting") // "{{cite web |url=https://en.wikipedia.org |title=English Wikipedia}}" XCTAssertEqual(templateRange.location, 5, "Incorrect template formatting") @@ -642,7 +644,7 @@ final class WKSourceEditorFormatterTests: XCTestCase { XCTAssertEqual(refClosingRange.location, 72, "Incorrect ref formatting") XCTAssertEqual(refClosingRange.length, 6, "Incorrect ref formatting") XCTAssertEqual(refClosingAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect ref formatting") - XCTAssertEqual(refClosingAttributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect ref formatting") + XCTAssertEqual(refClosingAttributes[.foregroundColor] as! UIColor, colors.greenForegroundColor, "Incorrect ref formatting") } func testHorizontalNestedTemplate() { @@ -785,7 +787,223 @@ final class WKSourceEditorFormatterTests: XCTestCase { XCTAssertEqual(refRange.location, 2, "Incorrect ref formatting") XCTAssertEqual(refRange.length, 6, "Incorrect ref formatting") XCTAssertEqual(refAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect ref formatting") - XCTAssertEqual(refAttributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect ref formatting") + XCTAssertEqual(refAttributes[.foregroundColor] as! UIColor, colors.greenForegroundColor, "Incorrect ref formatting") + } + + func testOpenAndClosingReference() { + let string = "Testing.{{cite web | url=https://en.wikipedia.org}} Testing" + let mutAttributedString = NSMutableAttributedString(string: string) + + for formatter in formatters { + formatter.addSyntaxHighlighting(to: mutAttributedString, in: NSRange(location: 0, length: string.count)) + } + + var base1Range = NSRange(location: 0, length: 0) + let base1Attributes = mutAttributedString.attributes(at: 0, effectiveRange: &base1Range) + + var refOpenRange = NSRange(location: 0, length: 0) + let refOpenAttributes = mutAttributedString.attributes(at: 8, effectiveRange: &refOpenRange) + + var templateRange = NSRange(location: 0, length: 0) + let templateAttributes = mutAttributedString.attributes(at: 13, effectiveRange: &templateRange) + + var refCloseRange = NSRange(location: 0, length: 0) + let refCloseAttributes = mutAttributedString.attributes(at: 56, effectiveRange: &refCloseRange) + + var base2Range = NSRange(location: 0, length: 0) + let base2Attributes = mutAttributedString.attributes(at: 62, effectiveRange: &base2Range) + + // "Testing." + XCTAssertEqual(base1Range.location, 0, "Incorrect base formatting") + XCTAssertEqual(base1Range.length, 8, "Incorrect base formatting") + XCTAssertEqual(base1Attributes[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") + XCTAssertEqual(base1Attributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect base formatting") + + // "" + XCTAssertEqual(refOpenRange.location, 8, "Incorrect template formatting") + XCTAssertEqual(refOpenRange.length, 5, "Incorrect template formatting") + XCTAssertEqual(refOpenAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect template formatting") + XCTAssertEqual(refOpenAttributes[.foregroundColor] as! UIColor, colors.greenForegroundColor, "Incorrect template formatting") + + // "{{cite web | url=https://en.wikipedia.org}}" + XCTAssertEqual(templateRange.location, 13, "Incorrect base formatting") + XCTAssertEqual(templateRange.length, 43, "Incorrect base formatting") + XCTAssertEqual(templateAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") + XCTAssertEqual(templateAttributes[.foregroundColor] as! UIColor, colors.purpleForegroundColor, "Incorrect base formatting") + + // "" + XCTAssertEqual(refCloseRange.location, 56, "Incorrect base formatting") + XCTAssertEqual(refCloseRange.length, 6, "Incorrect base formatting") + XCTAssertEqual(refCloseAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") + XCTAssertEqual(refCloseAttributes[.foregroundColor] as! UIColor, colors.greenForegroundColor, "Incorrect base formatting") + + // " Testing" + XCTAssertEqual(base2Range.location, 62, "Incorrect base formatting") + XCTAssertEqual(base2Range.length, 8, "Incorrect base formatting") + XCTAssertEqual(base2Attributes[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") + XCTAssertEqual(base2Attributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect base formatting") + } + + func testOpenAndClosingReferenceWithName() { + let string = "Testing.{{cite web | url=https://en.wikipedia.org}} Testing" + let mutAttributedString = NSMutableAttributedString(string: string) + + for formatter in formatters { + formatter.addSyntaxHighlighting(to: mutAttributedString, in: NSRange(location: 0, length: string.count)) + } + + var base1Range = NSRange(location: 0, length: 0) + let base1Attributes = mutAttributedString.attributes(at: 0, effectiveRange: &base1Range) + + var refOpenRange = NSRange(location: 0, length: 0) + let refOpenAttributes = mutAttributedString.attributes(at: 8, effectiveRange: &refOpenRange) + + var templateRange = NSRange(location: 0, length: 0) + let templateAttributes = mutAttributedString.attributes(at: 25, effectiveRange: &templateRange) + + var refCloseRange = NSRange(location: 0, length: 0) + let refCloseAttributes = mutAttributedString.attributes(at: 68, effectiveRange: &refCloseRange) + + var base2Range = NSRange(location: 0, length: 0) + let base2Attributes = mutAttributedString.attributes(at: 74, effectiveRange: &base2Range) + + // "Testing." + XCTAssertEqual(base1Range.location, 0, "Incorrect base formatting") + XCTAssertEqual(base1Range.length, 8, "Incorrect base formatting") + XCTAssertEqual(base1Attributes[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") + XCTAssertEqual(base1Attributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect base formatting") + + // "" + XCTAssertEqual(refOpenRange.location, 8, "Incorrect reference formatting") + XCTAssertEqual(refOpenRange.length, 17, "Incorrect reference formatting") + XCTAssertEqual(refOpenAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect reference formatting") + XCTAssertEqual(refOpenAttributes[.foregroundColor] as! UIColor, colors.greenForegroundColor, "Incorrect template formatting") + + // "{{cite web | url=https://en.wikipedia.org}}" + XCTAssertEqual(templateRange.location, 25, "Incorrect template formatting") + XCTAssertEqual(templateRange.length, 43, "Incorrect template formatting") + XCTAssertEqual(templateAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect template formatting") + XCTAssertEqual(templateAttributes[.foregroundColor] as! UIColor, colors.purpleForegroundColor, "Incorrect base formatting") + + // "" + XCTAssertEqual(refCloseRange.location, 68, "Incorrect reference formatting") + XCTAssertEqual(refCloseRange.length, 6, "Incorrect reference formatting") + XCTAssertEqual(refCloseAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect reference formatting") + XCTAssertEqual(refCloseAttributes[.foregroundColor] as! UIColor, colors.greenForegroundColor, "Incorrect base formatting") + + // " Testing" + XCTAssertEqual(base2Range.location, 74, "Incorrect base formatting") + XCTAssertEqual(base2Range.length, 8, "Incorrect base formatting") + XCTAssertEqual(base2Attributes[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") + XCTAssertEqual(base2Attributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect base formatting") + } + + func testEmptyReference() { + let string = "Testing. Testing" + let mutAttributedString = NSMutableAttributedString(string: string) + + for formatter in formatters { + formatter.addSyntaxHighlighting(to: mutAttributedString, in: NSRange(location: 0, length: string.count)) + } + + var base1Range = NSRange(location: 0, length: 0) + let base1Attributes = mutAttributedString.attributes(at: 0, effectiveRange: &base1Range) + + var refRange = NSRange(location: 0, length: 0) + let refAttributes = mutAttributedString.attributes(at: 8, effectiveRange: &refRange) + + var base2Range = NSRange(location: 0, length: 0) + let base2Attributes = mutAttributedString.attributes(at: 27, effectiveRange: &base2Range) + + // "Testing." + XCTAssertEqual(base1Range.location, 0, "Incorrect base formatting") + XCTAssertEqual(base1Range.length, 8, "Incorrect base formatting") + XCTAssertEqual(base1Attributes[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") + XCTAssertEqual(base1Attributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect base formatting") + + // "" + XCTAssertEqual(refRange.location, 8, "Incorrect reference formatting") + XCTAssertEqual(refRange.length, 19, "Incorrect reference formatting") + XCTAssertEqual(refAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect reference formatting") + XCTAssertEqual(refAttributes[.foregroundColor] as! UIColor, colors.greenForegroundColor, "Incorrect reference formatting") + + // " Testing" + XCTAssertEqual(base2Range.location, 27, "Incorrect base formatting") + XCTAssertEqual(base2Range.length, 8, "Incorrect base formatting") + XCTAssertEqual(base2Attributes[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") + XCTAssertEqual(base2Attributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect base formatting") + } + + func testOpenOnlyReference() { + let string = "Testing. Testing" + let mutAttributedString = NSMutableAttributedString(string: string) + + for formatter in formatters { + formatter.addSyntaxHighlighting(to: mutAttributedString, in: NSRange(location: 0, length: string.count)) + } + + var base1Range = NSRange(location: 0, length: 0) + let base1Attributes = mutAttributedString.attributes(at: 0, effectiveRange: &base1Range) + + var refRange = NSRange(location: 0, length: 0) + let refAttributes = mutAttributedString.attributes(at: 8, effectiveRange: &refRange) + + var base2Range = NSRange(location: 0, length: 0) + let base2Attributes = mutAttributedString.attributes(at: 13, effectiveRange: &base2Range) + + // "Testing." + XCTAssertEqual(base1Range.location, 0, "Incorrect base formatting") + XCTAssertEqual(base1Range.length, 8, "Incorrect base formatting") + XCTAssertEqual(base1Attributes[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") + XCTAssertEqual(base1Attributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect base formatting") + + // "" + XCTAssertEqual(refRange.location, 8, "Incorrect reference formatting") + XCTAssertEqual(refRange.length, 5, "Incorrect reference formatting") + XCTAssertEqual(refAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect reference formatting") + XCTAssertEqual(refAttributes[.foregroundColor] as! UIColor, colors.greenForegroundColor, "Incorrect reference formatting") + + // " Testing" + XCTAssertEqual(base2Range.location, 13, "Incorrect base formatting") + XCTAssertEqual(base2Range.length, 8, "Incorrect base formatting") + XCTAssertEqual(base2Attributes[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") + XCTAssertEqual(base2Attributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect base formatting") + } + + func testCloseOnlyReference() { + let string = "Testing. Testing" + let mutAttributedString = NSMutableAttributedString(string: string) + + for formatter in formatters { + formatter.addSyntaxHighlighting(to: mutAttributedString, in: NSRange(location: 0, length: string.count)) + } + + var base1Range = NSRange(location: 0, length: 0) + let base1Attributes = mutAttributedString.attributes(at: 0, effectiveRange: &base1Range) + + var refRange = NSRange(location: 0, length: 0) + let refAttributes = mutAttributedString.attributes(at: 8, effectiveRange: &refRange) + + var base2Range = NSRange(location: 0, length: 0) + let base2Attributes = mutAttributedString.attributes(at: 14, effectiveRange: &base2Range) + + // "Testing." + XCTAssertEqual(base1Range.location, 0, "Incorrect base formatting") + XCTAssertEqual(base1Range.length, 8, "Incorrect base formatting") + XCTAssertEqual(base1Attributes[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") + XCTAssertEqual(base1Attributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect base formatting") + + // "" + XCTAssertEqual(refRange.location, 8, "Incorrect reference formatting") + XCTAssertEqual(refRange.length, 6, "Incorrect reference formatting") + XCTAssertEqual(refAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect reference formatting") + XCTAssertEqual(refAttributes[.foregroundColor] as! UIColor, colors.greenForegroundColor, "Incorrect reference formatting") + + // " Testing" + XCTAssertEqual(base2Range.location, 14, "Incorrect base formatting") + XCTAssertEqual(base2Range.length, 8, "Incorrect base formatting") + XCTAssertEqual(base2Attributes[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") + XCTAssertEqual(base2Attributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect base formatting") } func testListSingleBullet() { diff --git a/Components/Tests/ComponentsTests/WKSourceEditorTextFrameworkMediatorTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorTextFrameworkMediatorTests.swift index dbbf840a4d8..d35cbb2c6ca 100644 --- a/Components/Tests/ComponentsTests/WKSourceEditorTextFrameworkMediatorTests.swift +++ b/Components/Tests/ComponentsTests/WKSourceEditorTextFrameworkMediatorTests.swift @@ -157,6 +157,38 @@ final class WKSourceEditorTextFrameworkMediatorTests: XCTestCase { XCTAssertFalse(selectionStates1.isHorizontalTemplate) } + func testReferenceSelectionState() throws { + let text = "Testing Testing Testing" + mediator.textView.attributedText = NSAttributedString(string: text) + + let selectionStates = mediator.selectionState(selectedDocumentRange: NSRange(location: 13, length: 7)) + XCTAssertTrue(selectionStates.isHorizontalReference) + } + + func testReferenceSelectionStateCursor() throws { + let text = "Testing Testing Testing" + mediator.textView.attributedText = NSAttributedString(string: text) + + let selectionStates = mediator.selectionState(selectedDocumentRange: NSRange(location: 16, length: 0)) + XCTAssertTrue(selectionStates.isHorizontalReference) + } + + func testReferenceNamedSelectionState() throws { + let text = "Testing Testing Testing" + mediator.textView.attributedText = NSAttributedString(string: text) + + let selectionStates = mediator.selectionState(selectedDocumentRange: NSRange(location: 28, length: 7)) + XCTAssertTrue(selectionStates.isHorizontalReference) + } + + func testReferenceNamedSelectionStateCursor() throws { + let text = "Testing Testing Testing" + mediator.textView.attributedText = NSAttributedString(string: text) + + let selectionStates = mediator.selectionState(selectedDocumentRange: NSRange(location: 31, length: 0)) + XCTAssertTrue(selectionStates.isHorizontalReference) + } + func testListBulletSingleSelectionState() throws { let text = "* Test"