From 68d2076616822641457c18efd6c96b923f5917c2 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Fri, 8 Dec 2023 16:11:07 -0600 Subject: [PATCH 01/35] Fix indentions and add editor heading fonts --- .../Sources/Components/Style/WKFont.swift | 130 ++++++++++-------- 1 file changed, 73 insertions(+), 57 deletions(-) diff --git a/Components/Sources/Components/Style/WKFont.swift b/Components/Sources/Components/Style/WKFont.swift index 1314ee54e95..fd8b50b5d1b 100644 --- a/Components/Sources/Components/Style/WKFont.swift +++ b/Components/Sources/Components/Style/WKFont.swift @@ -6,69 +6,85 @@ public enum WKFont { case headline case title case boldTitle - case body + case body case boldBody case italicsBody case boldItalicsBody - case smallBody - case callout - case subheadline - case boldSubheadline + case smallBody + case callout + case subheadline + case boldSubheadline case mediumSubheadline case caption1 case footnote - case boldFootnote + case boldFootnote + case editorHeading + case editorSubheading1 + case editorSubheading2 + case editorSubheading3 + case editorSubheading4 - static func `for`(_ font: WKFont, compatibleWith traitCollection: UITraitCollection = WKAppEnvironment.current.traitCollection) -> UIFont { - switch font { - case .headline: - return UIFont.preferredFont(forTextStyle: .headline, compatibleWith: traitCollection) - case .title: - return UIFont.preferredFont(forTextStyle: .title1, compatibleWith: traitCollection) - case .boldTitle: - guard let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .title1, compatibleWith: traitCollection).withSymbolicTraits(.traitBold) else { - fatalError() - } - return UIFont(descriptor: descriptor, size: 0) - case .body: - return UIFont.preferredFont(forTextStyle: .body, compatibleWith: traitCollection) - case .boldBody: - guard let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .body, compatibleWith: traitCollection).withSymbolicTraits(.traitBold) else { - fatalError() - } - return UIFont(descriptor: descriptor, size: 0) - case .italicsBody: - guard let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .body, compatibleWith: traitCollection).withSymbolicTraits(.traitItalic) else { - fatalError() - } - return UIFont(descriptor: descriptor, size: 0) - case .boldItalicsBody: - guard let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .body, compatibleWith: traitCollection).withSymbolicTraits(.traitBold.union(.traitItalic)) else { - fatalError() - } - return UIFont(descriptor: descriptor, size: 0) - case .smallBody: - return UIFontMetrics(forTextStyle: .body).scaledFont(for: UIFont.systemFont(ofSize: 15, weight: .regular)) - case .callout: - return UIFont.preferredFont(forTextStyle: .callout, compatibleWith: traitCollection) - case .subheadline: - return UIFont.preferredFont(forTextStyle: .subheadline, compatibleWith: traitCollection) - case .mediumSubheadline: - return UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: UIFont.systemFont(ofSize: 15, weight: .medium)) - case .boldSubheadline: - guard let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .subheadline, compatibleWith: traitCollection).withSymbolicTraits(.traitBold) else { - fatalError() - } - return UIFont(descriptor: descriptor, size: 0) - case .caption1: - return UIFont.preferredFont(forTextStyle: .caption1, compatibleWith: traitCollection) - case .footnote: - return UIFont.preferredFont(forTextStyle: .footnote, compatibleWith: traitCollection) - case .boldFootnote: - guard let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .footnote, compatibleWith: traitCollection).withSymbolicTraits(.traitBold) else { - fatalError() - } - return UIFont(descriptor: descriptor, size: 0) - } + static func `for`(_ font: WKFont, compatibleWith traitCollection: UITraitCollection = WKAppEnvironment.current.traitCollection) -> UIFont { + switch font { + case .headline: + return UIFont.preferredFont(forTextStyle: .headline, compatibleWith: traitCollection) + case .title: + return UIFont.preferredFont(forTextStyle: .title1, compatibleWith: traitCollection) + case .boldTitle: + guard let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .title1, compatibleWith: traitCollection).withSymbolicTraits(.traitBold) else { + fatalError() + } + return UIFont(descriptor: descriptor, size: 0) + case .body: + return UIFont.preferredFont(forTextStyle: .body, compatibleWith: traitCollection) + case .boldBody: + guard let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .body, compatibleWith: traitCollection).withSymbolicTraits(.traitBold) else { + fatalError() + } + return UIFont(descriptor: descriptor, size: 0) + case .italicsBody: + guard let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .body, compatibleWith: traitCollection).withSymbolicTraits(.traitItalic) else { + fatalError() + } + return UIFont(descriptor: descriptor, size: 0) + case .boldItalicsBody: + guard let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .body, compatibleWith: traitCollection).withSymbolicTraits(.traitBold.union(.traitItalic)) else { + fatalError() + } + return UIFont(descriptor: descriptor, size: 0) + case .smallBody: + return UIFontMetrics(forTextStyle: .body).scaledFont(for: UIFont.systemFont(ofSize: 15, weight: .regular)) + case .callout: + return UIFont.preferredFont(forTextStyle: .callout, compatibleWith: traitCollection) + case .subheadline: + return UIFont.preferredFont(forTextStyle: .subheadline, compatibleWith: traitCollection) + case .mediumSubheadline: + return UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: UIFont.systemFont(ofSize: 15, weight: .medium)) + case .boldSubheadline: + guard let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .subheadline, compatibleWith: traitCollection).withSymbolicTraits(.traitBold) else { + fatalError() + } + return UIFont(descriptor: descriptor, size: 0) + case .caption1: + return UIFont.preferredFont(forTextStyle: .caption1, compatibleWith: traitCollection) + case .footnote: + return UIFont.preferredFont(forTextStyle: .footnote, compatibleWith: traitCollection) + case .boldFootnote: + guard let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .footnote, compatibleWith: traitCollection).withSymbolicTraits(.traitBold) else { + fatalError() + } + return UIFont(descriptor: descriptor, size: 0) + case .editorHeading: + return UIFontMetrics(forTextStyle: .headline).scaledFont(for: UIFont.systemFont(ofSize: 28, weight: .semibold), maximumPointSize: 32, compatibleWith: traitCollection) + case .editorSubheading1: + return UIFontMetrics(forTextStyle: .headline).scaledFont(for: UIFont.systemFont(ofSize: 26, weight: .semibold), compatibleWith: traitCollection) + case .editorSubheading2: + return UIFontMetrics(forTextStyle: .headline).scaledFont(for: UIFont.systemFont(ofSize: 24, weight: .semibold), compatibleWith: traitCollection) + case .editorSubheading3: + return UIFontMetrics(forTextStyle: .headline).scaledFont(for: UIFont.systemFont(ofSize: 22, weight: .semibold), compatibleWith: traitCollection) + case .editorSubheading4: + return UIFontMetrics(forTextStyle: .headline).scaledFont(for: UIFont.systemFont(ofSize: 20, weight: .semibold), compatibleWith: traitCollection) + } + } } From fcb0e6a2bec66ac7c257498e8b1563fc24c5637d Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Fri, 8 Dec 2023 16:13:03 -0600 Subject: [PATCH 02/35] Add new fonts to WKSourceEditorFonts --- .../Source Editor/WKSourceEditorTextFrameworkMediator.swift | 5 +++++ Components/Sources/ComponentsObjC/WKSourceEditorFonts.h | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift index c414a31bee6..d701a037b4f 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift @@ -213,6 +213,11 @@ extension WKSourceEditorTextFrameworkMediator: WKSourceEditorStorageDelegate { fonts.boldFont = isSyntaxHighlightingEnabled ? WKFont.for(.boldBody, compatibleWith: traitCollection) : baseFont fonts.italicsFont = isSyntaxHighlightingEnabled ? WKFont.for(.italicsBody, compatibleWith: traitCollection) : baseFont fonts.boldItalicsFont = isSyntaxHighlightingEnabled ? WKFont.for(.boldItalicsBody, compatibleWith: traitCollection) : baseFont + fonts.headingFont = isSyntaxHighlightingEnabled ? WKFont.for(.editorHeading, compatibleWith: traitCollection) : baseFont + fonts.subheading1Font = isSyntaxHighlightingEnabled ? WKFont.for(.editorSubheading1, compatibleWith: traitCollection) : baseFont + fonts.subheading2Font = isSyntaxHighlightingEnabled ? WKFont.for(.editorSubheading2, compatibleWith: traitCollection) : baseFont + fonts.subheading3Font = isSyntaxHighlightingEnabled ? WKFont.for(.editorSubheading3, compatibleWith: traitCollection) : baseFont + fonts.subheading4Font = isSyntaxHighlightingEnabled ? WKFont.for(.editorSubheading4, compatibleWith: traitCollection) : baseFont return fonts } } diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFonts.h b/Components/Sources/ComponentsObjC/WKSourceEditorFonts.h index e7be6dd21f7..a5bdf06e53f 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFonts.h +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFonts.h @@ -7,6 +7,11 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, strong) UIFont *boldFont; @property (nonatomic, strong) UIFont *italicsFont; @property (nonatomic, strong) UIFont *boldItalicsFont; +@property (nonatomic, strong) UIFont *headingFont; +@property (nonatomic, strong) UIFont *subheading1Font; +@property (nonatomic, strong) UIFont *subheading2Font; +@property (nonatomic, strong) UIFont *subheading3Font; +@property (nonatomic, strong) UIFont *subheading4Font; @end NS_ASSUME_NONNULL_END From a6bbd8c29de1fd39cbba2dcfff46eb33c968d391 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Fri, 8 Dec 2023 16:13:31 -0600 Subject: [PATCH 03/35] Move common custom orange key to superclass --- Components/Sources/ComponentsObjC/WKSourceEditorFormatter.h | 3 +++ Components/Sources/ComponentsObjC/WKSourceEditorFormatter.m | 5 +++++ .../ComponentsObjC/WKSourceEditorFormatterBoldItalics.m | 3 --- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatter.h b/Components/Sources/ComponentsObjC/WKSourceEditorFormatter.h index f3cb213f291..e6ff6d9cf6f 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatter.h +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatter.h @@ -4,6 +4,9 @@ NS_ASSUME_NONNULL_BEGIN @interface WKSourceEditorFormatter : NSObject + +extern NSString *const WKSourceEditorCustomKeyColorOrange; + - (instancetype)initWithColors:(nonnull WKSourceEditorColors *)colors fonts:(nonnull WKSourceEditorFonts *)fonts; - (void)addSyntaxHighlightingToAttributedString:(NSMutableAttributedString *)attributedString inRange:(NSRange)range; diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatter.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatter.m index 5c4fb125102..7d69fde7dd5 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatter.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatter.m @@ -3,6 +3,11 @@ #import "WKSourceEditorFonts.h" @implementation WKSourceEditorFormatter + +#pragma mark - Common Custom Attributed String Keys +// Font and Color custom attributes allow us to easily target already-formatted ranges. This is handy for speedy updates upon theme and text size change, as well as determining keyboard button selection states. +NSString * const WKSourceEditorCustomKeyColorOrange = @"WKSourceEditorKeyColorOrange"; + - (nonnull instancetype)initWithColors:(nonnull WKSourceEditorColors *)colors fonts:(nonnull WKSourceEditorFonts *)fonts { self = [super init]; return self; diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m index 763e2947cd0..aeff59f7baf 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m @@ -18,9 +18,6 @@ @interface WKSourceEditorFormatterBoldItalics () @implementation WKSourceEditorFormatterBoldItalics #pragma mark - Custom Attributed String Keys - -// Font and Color custom attributes allow us to easily target already-formatted ranges. This is handy for speedy updates upon theme and text size change, as well as determining keyboard button selection states. -NSString * const WKSourceEditorCustomKeyColorOrange = @"WKSourceEditorKeyColorOrange"; NSString * const WKSourceEditorCustomKeyFontBoldItalics = @"WKSourceEditorKeyFontBoldItalics"; NSString * const WKSourceEditorCustomKeyFontBold = @"WKSourceEditorKeyFontBold"; NSString * const WKSourceEditorCustomKeyFontItalics = @"WKSourceEditorKeyFontItalics"; From 91e53f925ab7f57038aecc1efd5e969a5f21ede4 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Fri, 8 Dec 2023 16:14:03 -0600 Subject: [PATCH 04/35] Add new WKSourceEditorFormatterHeading for syntax highlighting --- .../WKSourceEditorFormatterHeading.h | 9 + .../WKSourceEditorFormatterHeading.m | 236 ++++++++++++++++++ .../ComponentsObjC/include/ComponentsObjC.h | 1 + .../include/WKSourceEditorFormatterHeading.h | 1 + 4 files changed, 247 insertions(+) create mode 100644 Components/Sources/ComponentsObjC/WKSourceEditorFormatterHeading.h create mode 100644 Components/Sources/ComponentsObjC/WKSourceEditorFormatterHeading.m create mode 120000 Components/Sources/ComponentsObjC/include/WKSourceEditorFormatterHeading.h diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterHeading.h b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterHeading.h new file mode 100644 index 00000000000..72a8c6eed35 --- /dev/null +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterHeading.h @@ -0,0 +1,9 @@ +#import "WKSourceEditorFormatter.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface WKSourceEditorFormatterHeading : WKSourceEditorFormatter + +@end + +NS_ASSUME_NONNULL_END diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterHeading.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterHeading.m new file mode 100644 index 00000000000..1d8e6a0febf --- /dev/null +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterHeading.m @@ -0,0 +1,236 @@ +#import "WKSourceEditorFormatterHeading.h" +#import "WKSourceEditorColors.h" +#import "WKSourceEditorFonts.h" + +@interface WKSourceEditorFormatterHeading () +@property (nonatomic, strong) NSDictionary *headingFontAttributes; +@property (nonatomic, strong) NSDictionary *subheading1FontAttributes; +@property (nonatomic, strong) NSDictionary *subheading2FontAttributes; +@property (nonatomic, strong) NSDictionary *subheading3FontAttributes; +@property (nonatomic, strong) NSDictionary *subheading4FontAttributes; +@property (nonatomic, strong) NSDictionary *orangeAttributes; + +@property (nonatomic, strong) NSRegularExpression *headingRegex; +@property (nonatomic, strong) NSRegularExpression *subheading1Regex; +@property (nonatomic, strong) NSRegularExpression *subheading2Regex; +@property (nonatomic, strong) NSRegularExpression *subheading3Regex; +@property (nonatomic, strong) NSRegularExpression *subheading4Regex; +@end + +@implementation WKSourceEditorFormatterHeading + +#pragma mark - Custom Attributed String Keys + +NSString * const WKSourceEditorCustomKeyFontHeading = @"WKSourceEditorCustomKeyFontHeading"; +NSString * const WKSourceEditorCustomKeyFontSubheading1 = @"WKSourceEditorCustomKeyFontSubheading1"; +NSString * const WKSourceEditorCustomKeyFontSubheading2 = @"WKSourceEditorCustomKeyFontSubheading2"; +NSString * const WKSourceEditorCustomKeyFontSubheading3 = @"WKSourceEditorCustomKeyFontSubheading3"; +NSString * const WKSourceEditorCustomKeyFontSubheading4 = @"WKSourceEditorCustomKeyFontSubheading4"; + +#pragma mark - Public + +- (instancetype)initWithColors:(WKSourceEditorColors *)colors fonts:(WKSourceEditorFonts *)fonts { + self = [super initWithColors:colors fonts:fonts]; + if (self) { + _orangeAttributes = @{ + NSForegroundColorAttributeName: colors.orangeForegroundColor, + WKSourceEditorCustomKeyColorOrange: [NSNumber numberWithBool:YES] + }; + + _headingFontAttributes = @{ + NSFontAttributeName: fonts.headingFont, + WKSourceEditorCustomKeyFontSubheading1: [NSNumber numberWithBool:YES] + }; + + _subheading1FontAttributes = @{ + NSFontAttributeName: fonts.subheading1Font, + WKSourceEditorCustomKeyFontSubheading2: [NSNumber numberWithBool:YES] + }; + + _subheading2FontAttributes = @{ + NSFontAttributeName: fonts.subheading2Font, + WKSourceEditorCustomKeyFontSubheading3: [NSNumber numberWithBool:YES] + }; + + _subheading3FontAttributes = @{ + NSFontAttributeName: fonts.subheading3Font, + WKSourceEditorCustomKeyFontSubheading4: [NSNumber numberWithBool:YES] + }; + + _subheading4FontAttributes = @{ + NSFontAttributeName: fonts.subheading4Font, + WKSourceEditorCustomKeyFontSubheading4: [NSNumber numberWithBool:YES] + }; + + _headingRegex = [[NSRegularExpression alloc] initWithPattern:@"^(={2})([^=]*)(={2})(?!=)$" options:NSRegularExpressionAnchorsMatchLines error:nil]; + _subheading1Regex = [[NSRegularExpression alloc] initWithPattern:@"^(={3})([^=]*)(={3})(?!=)$" options:NSRegularExpressionAnchorsMatchLines error:nil]; + _subheading2Regex = [[NSRegularExpression alloc] initWithPattern:@"^(={4})([^=]*)(={4})(?!=)$" options:NSRegularExpressionAnchorsMatchLines error:nil]; + _subheading3Regex = [[NSRegularExpression alloc] initWithPattern:@"^(={5})([^=]*)(={5})(?!=)$" options:NSRegularExpressionAnchorsMatchLines error:nil]; + _subheading4Regex = [[NSRegularExpression alloc] initWithPattern:@"^(={6})([^=]*)(={6})(?!=)$" options:NSRegularExpressionAnchorsMatchLines error:nil]; + } + return self; +} + +#pragma mark - Overrides + +- (void)addSyntaxHighlightingToAttributedString:(nonnull NSMutableAttributedString *)attributedString inRange:(NSRange)range { + + // Reset + [attributedString removeAttribute:WKSourceEditorCustomKeyColorOrange range:range]; + [attributedString removeAttribute:WKSourceEditorCustomKeyFontHeading range:range]; + [attributedString removeAttribute:WKSourceEditorCustomKeyFontSubheading1 range:range]; + [attributedString removeAttribute:WKSourceEditorCustomKeyFontSubheading2 range:range]; + [attributedString removeAttribute:WKSourceEditorCustomKeyFontSubheading3 range:range]; + [attributedString removeAttribute:WKSourceEditorCustomKeyFontSubheading4 range:range]; + + [self enumerateAndHighlightAttributedString:attributedString range:range regex:self.headingRegex attributes:self.headingFontAttributes]; + [self enumerateAndHighlightAttributedString:attributedString range:range regex:self.subheading1Regex attributes:self.subheading1FontAttributes]; + [self enumerateAndHighlightAttributedString:attributedString range:range regex:self.subheading2Regex attributes:self.subheading2FontAttributes]; + [self enumerateAndHighlightAttributedString:attributedString range:range regex:self.subheading3Regex attributes:self.subheading3FontAttributes]; + [self enumerateAndHighlightAttributedString:attributedString range:range regex:self.subheading4Regex attributes:self.subheading4FontAttributes]; +} + +- (void)updateColors:(WKSourceEditorColors *)colors inAttributedString:(NSMutableAttributedString *)attributedString inRange:(NSRange)range { + + [self updateColorAttributesWithColors:colors]; + [self enumerateAndUpdateColorsInAttributedString:attributedString range:range]; +} + +- (void)updateFonts:(WKSourceEditorFonts *)fonts inAttributedString:(NSMutableAttributedString *)attributedString inRange:(NSRange)range { + + [self updateFontAttributesWithFonts:fonts]; + [self enumerateAndUpdateFontsInAttributedString:attributedString range:range]; +} + +#pragma mark - Private + +- (void)enumerateAndHighlightAttributedString: (nonnull NSMutableAttributedString *)attributedString range:(NSRange)range regex:(NSRegularExpression *)regex attributes:(NSDictionary *)fontAttributes { + + [regex 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 textRange = [result rangeAtIndex:2]; + NSRange closingRange = [result rangeAtIndex:3]; + + if (fullMatch.location != NSNotFound) { + [attributedString addAttributes:fontAttributes range:fullMatch]; + } + + if (openingRange.location != NSNotFound) { + [attributedString addAttributes:self.orangeAttributes range:openingRange]; + } + + if (closingRange.location != NSNotFound) { + [attributedString addAttributes:self.orangeAttributes range:closingRange]; + } + }]; +} + +- (void)updateColorAttributesWithColors: (WKSourceEditorColors *)colors { + NSMutableDictionary *mutAttributes = [[NSMutableDictionary alloc] initWithDictionary:self.orangeAttributes]; + [mutAttributes setObject:colors.orangeForegroundColor forKey:NSForegroundColorAttributeName]; + self.orangeAttributes = [[NSDictionary alloc] initWithDictionary:mutAttributes]; +} + +- (void)enumerateAndUpdateColorsInAttributedString: (NSMutableAttributedString *)attributedString range: (NSRange)range { + [attributedString enumerateAttribute:WKSourceEditorCustomKeyColorOrange + 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.orangeAttributes range:localRange]; + } + } + }]; +} + +- (void)updateFontAttributesWithFonts: (WKSourceEditorFonts *)fonts { + NSMutableDictionary *mutHeadingAttributes = [[NSMutableDictionary alloc] initWithDictionary:self.headingFontAttributes]; + [mutHeadingAttributes setObject:fonts.headingFont forKey:NSFontAttributeName]; + self.headingFontAttributes = [[NSDictionary alloc] initWithDictionary:mutHeadingAttributes]; + + NSMutableDictionary *mutSubheading1Attributes = [[NSMutableDictionary alloc] initWithDictionary:self.subheading1FontAttributes]; + [mutSubheading1Attributes setObject:fonts.subheading1Font forKey:NSFontAttributeName]; + self.subheading1FontAttributes = [[NSDictionary alloc] initWithDictionary:mutSubheading1Attributes]; + + NSMutableDictionary *mutSubheading2Attributes = [[NSMutableDictionary alloc] initWithDictionary:self.subheading2FontAttributes]; + [mutSubheading2Attributes setObject:fonts.subheading2Font forKey:NSFontAttributeName]; + self.subheading2FontAttributes = [[NSDictionary alloc] initWithDictionary:mutSubheading2Attributes]; + + NSMutableDictionary *mutSubheading3Attributes = [[NSMutableDictionary alloc] initWithDictionary:self.subheading3FontAttributes]; + [mutSubheading3Attributes setObject:fonts.subheading3Font forKey:NSFontAttributeName]; + self.subheading3FontAttributes = [[NSDictionary alloc] initWithDictionary:mutSubheading3Attributes]; + + NSMutableDictionary *mutSubheading4Attributes = [[NSMutableDictionary alloc] initWithDictionary:self.subheading4FontAttributes]; + [mutSubheading4Attributes setObject:fonts.subheading4Font forKey:NSFontAttributeName]; + self.subheading4FontAttributes = [[NSDictionary alloc] initWithDictionary:mutSubheading4Attributes]; +} + +- (void)enumerateAndUpdateFontsInAttributedString: (NSMutableAttributedString *)attributedString range: (NSRange)range { + [attributedString enumerateAttribute:WKSourceEditorCustomKeyFontHeading + inRange:range + options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired + usingBlock:^(id value, NSRange localRange, BOOL *stop) { + if ([value isKindOfClass: [NSNumber class]]) { + NSNumber *numValue = (NSNumber *)value; + if ([numValue boolValue] == YES) { + [attributedString addAttributes:self.headingFontAttributes range:localRange]; + } + } + }]; + + [attributedString enumerateAttribute:WKSourceEditorCustomKeyFontSubheading1 + inRange:range + options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired + usingBlock:^(id value, NSRange localRange, BOOL *stop) { + if ([value isKindOfClass: [NSNumber class]]) { + NSNumber *numValue = (NSNumber *)value; + if ([numValue boolValue] == YES) { + [attributedString addAttributes:self.subheading1FontAttributes range:localRange]; + } + } + }]; + + [attributedString enumerateAttribute:WKSourceEditorCustomKeyFontSubheading2 + inRange:range + options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired + usingBlock:^(id value, NSRange localRange, BOOL *stop) { + if ([value isKindOfClass: [NSNumber class]]) { + NSNumber *numValue = (NSNumber *)value; + if ([numValue boolValue] == YES) { + [attributedString addAttributes:self.subheading2FontAttributes range:localRange]; + } + } + }]; + + [attributedString enumerateAttribute:WKSourceEditorCustomKeyFontSubheading3 + inRange:range + options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired + usingBlock:^(id value, NSRange localRange, BOOL *stop) { + if ([value isKindOfClass: [NSNumber class]]) { + NSNumber *numValue = (NSNumber *)value; + if ([numValue boolValue] == YES) { + [attributedString addAttributes:self.subheading3FontAttributes range:localRange]; + } + } + }]; + + [attributedString enumerateAttribute:WKSourceEditorCustomKeyFontSubheading4 + inRange:range + options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired + usingBlock:^(id value, NSRange localRange, BOOL *stop) { + if ([value isKindOfClass: [NSNumber class]]) { + NSNumber *numValue = (NSNumber *)value; + if ([numValue boolValue] == YES) { + [attributedString addAttributes:self.subheading4FontAttributes range:localRange]; + } + } + }]; +} + +@end diff --git a/Components/Sources/ComponentsObjC/include/ComponentsObjC.h b/Components/Sources/ComponentsObjC/include/ComponentsObjC.h index 078e6dec5d6..cd20c737823 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 "WKSourceEditorFormatterHeading.h" #import "WKSourceEditorStorageDelegate.h" #endif /* Header_h */ diff --git a/Components/Sources/ComponentsObjC/include/WKSourceEditorFormatterHeading.h b/Components/Sources/ComponentsObjC/include/WKSourceEditorFormatterHeading.h new file mode 120000 index 00000000000..5fc70400b83 --- /dev/null +++ b/Components/Sources/ComponentsObjC/include/WKSourceEditorFormatterHeading.h @@ -0,0 +1 @@ +../WKSourceEditorFormatterHeading.h \ No newline at end of file From 4a573ea4c84440520676e341fab3f25ff83f32b4 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Fri, 8 Dec 2023 16:14:12 -0600 Subject: [PATCH 05/35] Use new formatter in editor --- .../Source Editor/WKSourceEditorTextFrameworkMediator.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift index d701a037b4f..46d961923b0 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift @@ -35,6 +35,7 @@ final class WKSourceEditorTextFrameworkMediator: NSObject { private(set) var formatters: [WKSourceEditorFormatter] = [] private(set) var boldItalicsFormatter: WKSourceEditorFormatterBoldItalics? private(set) var templateFormatter: WKSourceEditorFormatterTemplate? + private(set) var headingFormatter: WKSourceEditorFormatterHeading? var isSyntaxHighlightingEnabled: Bool = true { didSet { @@ -103,11 +104,14 @@ final class WKSourceEditorTextFrameworkMediator: NSObject { let boldItalicsFormatter = WKSourceEditorFormatterBoldItalics(colors: colors, fonts: fonts) let templateFormatter = WKSourceEditorFormatterTemplate(colors: colors, fonts: fonts) + let headingFormatter = WKSourceEditorFormatterHeading(colors: colors, fonts: fonts) self.formatters = [WKSourceEditorFormatterBase(colors: colors, fonts: fonts, textAlignment: viewModel.textAlignment), templateFormatter, - boldItalicsFormatter] + boldItalicsFormatter, + headingFormatter] self.boldItalicsFormatter = boldItalicsFormatter self.templateFormatter = templateFormatter + self.headingFormatter = headingFormatter if needsTextKit2 { if #available(iOS 16.0, *) { From 896661ddeff668a8b3a78bcd3cfdd22b253e9e8e Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Fri, 8 Dec 2023 16:29:48 -0600 Subject: [PATCH 06/35] Fix bugs --- .../ComponentsObjC/WKSourceEditorFormatterHeading.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterHeading.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterHeading.m index 1d8e6a0febf..dafa1f8ec77 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterHeading.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterHeading.m @@ -39,22 +39,22 @@ - (instancetype)initWithColors:(WKSourceEditorColors *)colors fonts:(WKSourceEdi _headingFontAttributes = @{ NSFontAttributeName: fonts.headingFont, - WKSourceEditorCustomKeyFontSubheading1: [NSNumber numberWithBool:YES] + WKSourceEditorCustomKeyFontHeading: [NSNumber numberWithBool:YES] }; _subheading1FontAttributes = @{ NSFontAttributeName: fonts.subheading1Font, - WKSourceEditorCustomKeyFontSubheading2: [NSNumber numberWithBool:YES] + WKSourceEditorCustomKeyFontSubheading1: [NSNumber numberWithBool:YES] }; _subheading2FontAttributes = @{ NSFontAttributeName: fonts.subheading2Font, - WKSourceEditorCustomKeyFontSubheading3: [NSNumber numberWithBool:YES] + WKSourceEditorCustomKeyFontSubheading2: [NSNumber numberWithBool:YES] }; _subheading3FontAttributes = @{ NSFontAttributeName: fonts.subheading3Font, - WKSourceEditorCustomKeyFontSubheading4: [NSNumber numberWithBool:YES] + WKSourceEditorCustomKeyFontSubheading3: [NSNumber numberWithBool:YES] }; _subheading4FontAttributes = @{ From 9d3fcccd267c911a4e3186d41922594837a19089 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Fri, 8 Dec 2023 16:48:43 -0600 Subject: [PATCH 07/35] Add tests --- .../WKSourceEditorFormatterTests.swift | 208 +++++++++++++++++- 1 file changed, 207 insertions(+), 1 deletion(-) diff --git a/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift index 6330fe0ba7c..9d4279fb0aa 100644 --- a/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift +++ b/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift @@ -10,8 +10,9 @@ final class WKSourceEditorFormatterTests: XCTestCase { var baseFormatter: WKSourceEditorFormatterBase! var boldItalicsFormatter: WKSourceEditorFormatterBoldItalics! var templateFormatter: WKSourceEditorFormatterTemplate! + var headingFormatter: WKSourceEditorFormatterHeading! var formatters: [WKSourceEditorFormatter] { - return [baseFormatter, templateFormatter, boldItalicsFormatter] + return [baseFormatter, templateFormatter, boldItalicsFormatter, headingFormatter] } override func setUpWithError() throws { @@ -27,10 +28,16 @@ final class WKSourceEditorFormatterTests: XCTestCase { self.fonts.boldFont = WKFont.for(.boldBody, compatibleWith: traitCollection) self.fonts.italicsFont = WKFont.for(.italicsBody, compatibleWith: traitCollection) self.fonts.boldItalicsFont = WKFont.for(.boldItalicsBody, compatibleWith: traitCollection) + self.fonts.headingFont = WKFont.for(.editorHeading, compatibleWith: traitCollection) + self.fonts.subheading1Font = WKFont.for(.editorSubheading1, compatibleWith: traitCollection) + self.fonts.subheading2Font = WKFont.for(.editorSubheading2, compatibleWith: traitCollection) + self.fonts.subheading3Font = WKFont.for(.editorSubheading3, compatibleWith: traitCollection) + self.fonts.subheading4Font = WKFont.for(.editorSubheading4, compatibleWith: traitCollection) self.baseFormatter = WKSourceEditorFormatterBase(colors: colors, fonts: fonts, textAlignment: .left) self.boldItalicsFormatter = WKSourceEditorFormatterBoldItalics(colors: colors, fonts: fonts) self.templateFormatter = WKSourceEditorFormatterTemplate(colors: colors, fonts: fonts) + self.headingFormatter = WKSourceEditorFormatterHeading(colors: colors, fonts: fonts) } override func tearDownWithError() throws { @@ -709,4 +716,203 @@ final class WKSourceEditorFormatterTests: XCTestCase { XCTAssertEqual(refAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect ref formatting") XCTAssertEqual(refAttributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect ref formatting") } + + func testHeading() { + let string = "== Test ==" + let mutAttributedString = NSMutableAttributedString(string: string) + + for formatter in formatters { + formatter.addSyntaxHighlighting(to: mutAttributedString, in: NSRange(location: 0, length: string.count)) + } + + var openingRange = NSRange(location: 0, length: 0) + let openingAttributes = mutAttributedString.attributes(at: 0, effectiveRange: &openingRange) + + var contentRange = NSRange(location: 0, length: 0) + let contentAttributes = mutAttributedString.attributes(at: 2, effectiveRange: &contentRange) + + var closingRange = NSRange(location: 0, length: 0) + let closingAttributes = mutAttributedString.attributes(at: 8, effectiveRange: &closingRange) + + // "==" + XCTAssertEqual(openingRange.location, 0, "Incorrect heading formatting") + XCTAssertEqual(openingRange.length, 2, "Incorrect heading formatting") + XCTAssertEqual(openingAttributes[.font] as! UIFont, fonts.headingFont, "Incorrect heading formatting") + XCTAssertEqual(openingAttributes[.foregroundColor] as! UIColor, colors.orangeForegroundColor, "Incorrect heading formatting") + + // " Heading Test " + XCTAssertEqual(contentRange.location, 2, "Incorrect heading formatting") + XCTAssertEqual(contentRange.length, 6, "Incorrect heading formatting") + XCTAssertEqual(contentAttributes[.font] as! UIFont, fonts.headingFont, "Incorrect heading formatting") + XCTAssertEqual(contentAttributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect heading formatting") + + // "==" + XCTAssertEqual(closingRange.location, 8, "Incorrect heading formatting") + XCTAssertEqual(closingRange.length, 2, "Incorrect heading formatting") + XCTAssertEqual(closingAttributes[.font] as! UIFont, fonts.headingFont, "Incorrect heading formatting") + XCTAssertEqual(closingAttributes[.foregroundColor] as! UIColor, colors.orangeForegroundColor, "Incorrect heading formatting") + } + + func testSubheading1() { + let string = "=== Test ===" + let mutAttributedString = NSMutableAttributedString(string: string) + + for formatter in formatters { + formatter.addSyntaxHighlighting(to: mutAttributedString, in: NSRange(location: 0, length: string.count)) + } + + var openingRange = NSRange(location: 0, length: 0) + let openingAttributes = mutAttributedString.attributes(at: 0, effectiveRange: &openingRange) + + var contentRange = NSRange(location: 0, length: 0) + let contentAttributes = mutAttributedString.attributes(at: 3, effectiveRange: &contentRange) + + var closingRange = NSRange(location: 0, length: 0) + let closingAttributes = mutAttributedString.attributes(at: 9, effectiveRange: &closingRange) + + // "==" + XCTAssertEqual(openingRange.location, 0, "Incorrect subheading1 formatting") + XCTAssertEqual(openingRange.length, 3, "Incorrect subheading1 formatting") + XCTAssertEqual(openingAttributes[.font] as! UIFont, fonts.subheading1Font, "Incorrect subheading1 formatting") + XCTAssertEqual(openingAttributes[.foregroundColor] as! UIColor, colors.orangeForegroundColor, "Incorrect subheading1 formatting") + + // " Test " + XCTAssertEqual(contentRange.location, 3, "Incorrect subheading1 formatting") + XCTAssertEqual(contentRange.length, 6, "Incorrect subheading1 formatting") + XCTAssertEqual(contentAttributes[.font] as! UIFont, fonts.subheading1Font, "Incorrect subheading1 formatting") + XCTAssertEqual(contentAttributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect subheading1 formatting") + + // "==" + XCTAssertEqual(closingRange.location, 9, "Incorrect subheading1 formatting") + XCTAssertEqual(closingRange.length, 3, "Incorrect subheading1 formatting") + XCTAssertEqual(closingAttributes[.font] as! UIFont, fonts.subheading1Font, "Incorrect subheading1 formatting") + XCTAssertEqual(closingAttributes[.foregroundColor] as! UIColor, colors.orangeForegroundColor, "Incorrect subheading1 formatting") + } + + func testSubheading2() { + let string = "==== Test ====" + let mutAttributedString = NSMutableAttributedString(string: string) + + for formatter in formatters { + formatter.addSyntaxHighlighting(to: mutAttributedString, in: NSRange(location: 0, length: string.count)) + } + + var openingRange = NSRange(location: 0, length: 0) + let openingAttributes = mutAttributedString.attributes(at: 0, effectiveRange: &openingRange) + + var contentRange = NSRange(location: 0, length: 0) + let contentAttributes = mutAttributedString.attributes(at: 4, effectiveRange: &contentRange) + + var closingRange = NSRange(location: 0, length: 0) + let closingAttributes = mutAttributedString.attributes(at: 10, effectiveRange: &closingRange) + + // "==" + XCTAssertEqual(openingRange.location, 0, "Incorrect subheading2 formatting") + XCTAssertEqual(openingRange.length, 4, "Incorrect subheading2 formatting") + XCTAssertEqual(openingAttributes[.font] as! UIFont, fonts.subheading2Font, "Incorrect subheading2 formatting") + XCTAssertEqual(openingAttributes[.foregroundColor] as! UIColor, colors.orangeForegroundColor, "Incorrect subheading2 formatting") + + // " Test " + XCTAssertEqual(contentRange.location, 4, "Incorrect subheading2 formatting") + XCTAssertEqual(contentRange.length, 6, "Incorrect subheading2 formatting") + XCTAssertEqual(contentAttributes[.font] as! UIFont, fonts.subheading2Font, "Incorrect subheading2 formatting") + XCTAssertEqual(contentAttributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect subheading2 formatting") + + // "==" + XCTAssertEqual(closingRange.location, 10, "Incorrect subheading2 formatting") + XCTAssertEqual(closingRange.length, 4, "Incorrect subheading2 formatting") + XCTAssertEqual(closingAttributes[.font] as! UIFont, fonts.subheading2Font, "Incorrect subheading2 formatting") + XCTAssertEqual(closingAttributes[.foregroundColor] as! UIColor, colors.orangeForegroundColor, "Incorrect subheading2 formatting") + } + + func testSubheading3() { + let string = "===== Test =====" + let mutAttributedString = NSMutableAttributedString(string: string) + + for formatter in formatters { + formatter.addSyntaxHighlighting(to: mutAttributedString, in: NSRange(location: 0, length: string.count)) + } + + var openingRange = NSRange(location: 0, length: 0) + let openingAttributes = mutAttributedString.attributes(at: 0, effectiveRange: &openingRange) + + var contentRange = NSRange(location: 0, length: 0) + let contentAttributes = mutAttributedString.attributes(at: 5, effectiveRange: &contentRange) + + var closingRange = NSRange(location: 0, length: 0) + let closingAttributes = mutAttributedString.attributes(at: 11, effectiveRange: &closingRange) + + // "==" + XCTAssertEqual(openingRange.location, 0, "Incorrect subheading3 formatting") + XCTAssertEqual(openingRange.length, 5, "Incorrect subheading3 formatting") + XCTAssertEqual(openingAttributes[.font] as! UIFont, fonts.subheading3Font, "Incorrect subheading3 formatting") + XCTAssertEqual(openingAttributes[.foregroundColor] as! UIColor, colors.orangeForegroundColor, "Incorrect subheading3 formatting") + + // " Test " + XCTAssertEqual(contentRange.location, 5, "Incorrect subheading3 formatting") + XCTAssertEqual(contentRange.length, 6, "Incorrect subheading3 formatting") + XCTAssertEqual(contentAttributes[.font] as! UIFont, fonts.subheading3Font, "Incorrect subheading3 formatting") + XCTAssertEqual(contentAttributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect subheading3 formatting") + + // "==" + XCTAssertEqual(closingRange.location, 11, "Incorrect subheading3 formatting") + XCTAssertEqual(closingRange.length, 5, "Incorrect subheading3 formatting") + XCTAssertEqual(closingAttributes[.font] as! UIFont, fonts.subheading3Font, "Incorrect subheading3 formatting") + XCTAssertEqual(closingAttributes[.foregroundColor] as! UIColor, colors.orangeForegroundColor, "Incorrect subheading3 formatting") + } + + func testSubeading4() { + let string = "====== Test ======" + let mutAttributedString = NSMutableAttributedString(string: string) + + for formatter in formatters { + formatter.addSyntaxHighlighting(to: mutAttributedString, in: NSRange(location: 0, length: string.count)) + } + + var openingRange = NSRange(location: 0, length: 0) + let openingAttributes = mutAttributedString.attributes(at: 0, effectiveRange: &openingRange) + + var contentRange = NSRange(location: 0, length: 0) + let contentAttributes = mutAttributedString.attributes(at: 6, effectiveRange: &contentRange) + + var closingRange = NSRange(location: 0, length: 0) + let closingAttributes = mutAttributedString.attributes(at: 12, effectiveRange: &closingRange) + + // "==" + XCTAssertEqual(openingRange.location, 0, "Incorrect subheading4 formatting") + XCTAssertEqual(openingRange.length, 6, "Incorrect subheading4 formatting") + XCTAssertEqual(openingAttributes[.font] as! UIFont, fonts.subheading4Font, "Incorrect subheading4 formatting") + XCTAssertEqual(openingAttributes[.foregroundColor] as! UIColor, colors.orangeForegroundColor, "Incorrect subheading4 formatting") + + // " Test " + XCTAssertEqual(contentRange.location, 6, "Incorrect subheading4 formatting") + XCTAssertEqual(contentRange.length, 6, "Incorrect subheading4 formatting") + XCTAssertEqual(contentAttributes[.font] as! UIFont, fonts.subheading4Font, "Incorrect subheading4 formatting") + XCTAssertEqual(contentAttributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect subheading4 formatting") + + // "==" + XCTAssertEqual(closingRange.location, 12, "Incorrect subheading4 formatting") + XCTAssertEqual(closingRange.length, 6, "Incorrect subheading4 formatting") + XCTAssertEqual(closingAttributes[.font] as! UIFont, fonts.subheading4Font, "Incorrect subheading4 formatting") + XCTAssertEqual(closingAttributes[.foregroundColor] as! UIColor, colors.orangeForegroundColor, "Incorrect subheading4 formatting") + } + + func testInlineHeading() { + let string = "Test == Test == Test" + let mutAttributedString = NSMutableAttributedString(string: string) + + for formatter in formatters { + formatter.addSyntaxHighlighting(to: mutAttributedString, in: NSRange(location: 0, length: string.count)) + } + + var range = NSRange(location: 0, length: 0) + let attributes = mutAttributedString.attributes(at: 0, effectiveRange: &range) + + // "Test == Test == Test" + // Inline headings should not format - they must be on their own line. + XCTAssertEqual(range.location, 0, "Incorrect inline heading formatting") + XCTAssertEqual(range.length, 20, "Incorrect inline heading formatting") + XCTAssertEqual(attributes[.font] as! UIFont, fonts.baseFont, "Incorrect inline heading formatting") + XCTAssertEqual(attributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect inline heading formatting") + } } From 1e874262b65afc1247babf396cd8eaead3fc63f6 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Mon, 11 Dec 2023 17:31:54 -0600 Subject: [PATCH 08/35] Allow formatter to detect if range contains custom heading keys - Will use these for button selection states later --- .../WKSourceEditorFormatterHeading.h | 6 + .../WKSourceEditorFormatterHeading.m | 103 +++++++++++++++++- 2 files changed, 103 insertions(+), 6 deletions(-) diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterHeading.h b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterHeading.h index 72a8c6eed35..78a6013e552 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterHeading.h +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterHeading.h @@ -4,6 +4,12 @@ NS_ASSUME_NONNULL_BEGIN @interface WKSourceEditorFormatterHeading : WKSourceEditorFormatter +- (BOOL)attributedString:(NSMutableAttributedString *)attributedString isHeadingInRange:(NSRange)range; +- (BOOL)attributedString:(NSMutableAttributedString *)attributedString isSubheading1InRange:(NSRange)range; +- (BOOL)attributedString:(NSMutableAttributedString *)attributedString isSubheading2InRange:(NSRange)range; +- (BOOL)attributedString:(NSMutableAttributedString *)attributedString isSubheading3InRange:(NSRange)range; +- (BOOL)attributedString:(NSMutableAttributedString *)attributedString isSubheading4InRange:(NSRange)range; + @end NS_ASSUME_NONNULL_END diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterHeading.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterHeading.m index dafa1f8ec77..ed2eb3dd9dd 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterHeading.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterHeading.m @@ -10,6 +10,12 @@ @interface WKSourceEditorFormatterHeading () @property (nonatomic, strong) NSDictionary *subheading4FontAttributes; @property (nonatomic, strong) NSDictionary *orangeAttributes; +@property (nonatomic, strong) NSDictionary *headingContentAttributes; +@property (nonatomic, strong) NSDictionary *subheading1ContentAttributes; +@property (nonatomic, strong) NSDictionary *subheading2ContentAttributes; +@property (nonatomic, strong) NSDictionary *subheading3ContentAttributes; +@property (nonatomic, strong) NSDictionary *subheading4ContentAttributes; + @property (nonatomic, strong) NSRegularExpression *headingRegex; @property (nonatomic, strong) NSRegularExpression *subheading1Regex; @property (nonatomic, strong) NSRegularExpression *subheading2Regex; @@ -21,12 +27,20 @@ @implementation WKSourceEditorFormatterHeading #pragma mark - Custom Attributed String Keys +// Font custom keys span across entire match, i.e. "== Test ==". The entire match is a particular font. This helps us quickly seek and update fonts upon popover change. NSString * const WKSourceEditorCustomKeyFontHeading = @"WKSourceEditorCustomKeyFontHeading"; NSString * const WKSourceEditorCustomKeyFontSubheading1 = @"WKSourceEditorCustomKeyFontSubheading1"; NSString * const WKSourceEditorCustomKeyFontSubheading2 = @"WKSourceEditorCustomKeyFontSubheading2"; NSString * const WKSourceEditorCustomKeyFontSubheading3 = @"WKSourceEditorCustomKeyFontSubheading3"; NSString * const WKSourceEditorCustomKeyFontSubheading4 = @"WKSourceEditorCustomKeyFontSubheading4"; +// Content custom keys span across only the content, i.e. " Test ". This helps us detect for button selection states. +NSString * const WKSourceEditorCustomKeyContentHeading = @"WKSourceEditorCustomKeyContentHeading"; +NSString * const WKSourceEditorCustomKeyContentSubheading1 = @"WKSourceEditorCustomKeyContentSubheading1"; +NSString * const WKSourceEditorCustomKeyContentSubheading2 = @"WKSourceEditorCustomKeyContentSubheading2"; +NSString * const WKSourceEditorCustomKeyContentSubheading3 = @"WKSourceEditorCustomKeyContentSubheading3"; +NSString * const WKSourceEditorCustomKeyContentSubheading4 = @"WKSourceEditorCustomKeyContentSubheading4"; + #pragma mark - Public - (instancetype)initWithColors:(WKSourceEditorColors *)colors fonts:(WKSourceEditorFonts *)fonts { @@ -62,6 +76,26 @@ - (instancetype)initWithColors:(WKSourceEditorColors *)colors fonts:(WKSourceEdi WKSourceEditorCustomKeyFontSubheading4: [NSNumber numberWithBool:YES] }; + _headingContentAttributes = @{ + WKSourceEditorCustomKeyContentHeading: [NSNumber numberWithBool:YES] + }; + + _subheading1ContentAttributes = @{ + WKSourceEditorCustomKeyContentSubheading1: [NSNumber numberWithBool:YES] + }; + + _subheading2ContentAttributes = @{ + WKSourceEditorCustomKeyContentSubheading2: [NSNumber numberWithBool:YES] + }; + + _subheading3ContentAttributes = @{ + WKSourceEditorCustomKeyContentSubheading3: [NSNumber numberWithBool:YES] + }; + + _subheading4ContentAttributes = @{ + WKSourceEditorCustomKeyContentSubheading4: [NSNumber numberWithBool:YES] + }; + _headingRegex = [[NSRegularExpression alloc] initWithPattern:@"^(={2})([^=]*)(={2})(?!=)$" options:NSRegularExpressionAnchorsMatchLines error:nil]; _subheading1Regex = [[NSRegularExpression alloc] initWithPattern:@"^(={3})([^=]*)(={3})(?!=)$" options:NSRegularExpressionAnchorsMatchLines error:nil]; _subheading2Regex = [[NSRegularExpression alloc] initWithPattern:@"^(={4})([^=]*)(={4})(?!=)$" options:NSRegularExpressionAnchorsMatchLines error:nil]; @@ -82,12 +116,17 @@ - (void)addSyntaxHighlightingToAttributedString:(nonnull NSMutableAttributedStri [attributedString removeAttribute:WKSourceEditorCustomKeyFontSubheading2 range:range]; [attributedString removeAttribute:WKSourceEditorCustomKeyFontSubheading3 range:range]; [attributedString removeAttribute:WKSourceEditorCustomKeyFontSubheading4 range:range]; + [attributedString removeAttribute:WKSourceEditorCustomKeyContentHeading range:range]; + [attributedString removeAttribute:WKSourceEditorCustomKeyContentSubheading1 range:range]; + [attributedString removeAttribute:WKSourceEditorCustomKeyContentSubheading2 range:range]; + [attributedString removeAttribute:WKSourceEditorCustomKeyContentSubheading3 range:range]; + [attributedString removeAttribute:WKSourceEditorCustomKeyContentSubheading4 range:range]; - [self enumerateAndHighlightAttributedString:attributedString range:range regex:self.headingRegex attributes:self.headingFontAttributes]; - [self enumerateAndHighlightAttributedString:attributedString range:range regex:self.subheading1Regex attributes:self.subheading1FontAttributes]; - [self enumerateAndHighlightAttributedString:attributedString range:range regex:self.subheading2Regex attributes:self.subheading2FontAttributes]; - [self enumerateAndHighlightAttributedString:attributedString range:range regex:self.subheading3Regex attributes:self.subheading3FontAttributes]; - [self enumerateAndHighlightAttributedString:attributedString range:range regex:self.subheading4Regex attributes:self.subheading4FontAttributes]; + [self enumerateAndHighlightAttributedString:attributedString range:range regex:self.headingRegex fontAttributes:self.headingFontAttributes contentAttributes:self.headingContentAttributes]; + [self enumerateAndHighlightAttributedString:attributedString range:range regex:self.subheading1Regex fontAttributes:self.subheading1FontAttributes contentAttributes:self.subheading1ContentAttributes]; + [self enumerateAndHighlightAttributedString:attributedString range:range regex:self.subheading2Regex fontAttributes:self.subheading2FontAttributes contentAttributes:self.subheading2ContentAttributes]; + [self enumerateAndHighlightAttributedString:attributedString range:range regex:self.subheading3Regex fontAttributes:self.subheading3FontAttributes contentAttributes:self.subheading3ContentAttributes]; + [self enumerateAndHighlightAttributedString:attributedString range:range regex:self.subheading4Regex fontAttributes:self.subheading4FontAttributes contentAttributes:self.subheading4ContentAttributes]; } - (void)updateColors:(WKSourceEditorColors *)colors inAttributedString:(NSMutableAttributedString *)attributedString inRange:(NSRange)range { @@ -102,9 +141,57 @@ - (void)updateFonts:(WKSourceEditorFonts *)fonts inAttributedString:(NSMutableAt [self enumerateAndUpdateFontsInAttributedString:attributedString range:range]; } +#pragma mark - Public + +- (BOOL)attributedString:(NSMutableAttributedString *)attributedString isHeadingInRange:(NSRange)range { + return [self attributedString:attributedString isContentKey:WKSourceEditorCustomKeyContentHeading inRange:range]; +} + +- (BOOL)attributedString:(NSMutableAttributedString *)attributedString isSubheading1InRange:(NSRange)range { + return [self attributedString:attributedString isContentKey:WKSourceEditorCustomKeyContentSubheading1 inRange:range]; +} + +- (BOOL)attributedString:(NSMutableAttributedString *)attributedString isSubheading2InRange:(NSRange)range { + return [self attributedString:attributedString isContentKey:WKSourceEditorCustomKeyContentSubheading2 inRange:range]; +} + +- (BOOL)attributedString:(NSMutableAttributedString *)attributedString isSubheading3InRange:(NSRange)range { + return [self attributedString:attributedString isContentKey:WKSourceEditorCustomKeyContentSubheading3 inRange:range]; +} + +- (BOOL)attributedString:(NSMutableAttributedString *)attributedString isSubheading4InRange:(NSRange)range { + return [self attributedString:attributedString isContentKey:WKSourceEditorCustomKeyContentSubheading4 inRange:range]; +} + #pragma mark - Private -- (void)enumerateAndHighlightAttributedString: (nonnull NSMutableAttributedString *)attributedString range:(NSRange)range regex:(NSRegularExpression *)regex attributes:(NSDictionary *)fontAttributes { +- (BOOL)attributedString:(NSMutableAttributedString *)attributedString isContentKey:(NSString *)contentKey inRange:(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[contentKey] != nil) { + isContentKey = YES; + } + } + + } else { + [attributedString enumerateAttributesInRange:range options:nil usingBlock:^(NSDictionary * _Nonnull attrs, NSRange loopRange, BOOL * _Nonnull stop) { + if ((attrs[contentKey] != nil) && + (loopRange.location == range.location && loopRange.length == range.length)) { + isContentKey = YES; + stop = YES; + } + }]; + } + + return isContentKey; +} + +- (void)enumerateAndHighlightAttributedString: (nonnull NSMutableAttributedString *)attributedString range:(NSRange)range regex:(NSRegularExpression *)regex fontAttributes:(NSDictionary *)fontAttributes contentAttributes:(NSDictionary *)contentAttributes { [regex enumerateMatchesInString:attributedString.string options:0 @@ -123,6 +210,10 @@ - (void)enumerateAndHighlightAttributedString: (nonnull NSMutableAttributedStrin [attributedString addAttributes:self.orangeAttributes range:openingRange]; } + if (textRange.location != NSNotFound) { + [attributedString addAttributes:contentAttributes range:textRange]; + } + if (closingRange.location != NSNotFound) { [attributedString addAttributes:self.orangeAttributes range:closingRange]; } From a101a0af03f00f067c724fd0dd28d898b9f0aade Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Mon, 11 Dec 2023 17:32:39 -0600 Subject: [PATCH 09/35] Add heading properties to WKSourceEditorSelectionState --- .../WKSourceEditorTextFrameworkMediator.swift | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift index 46d961923b0..544904a6dde 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift @@ -17,11 +17,22 @@ fileprivate var needsTextKit2: Bool { let isBold: Bool let isItalics: Bool let isHorizontalTemplate: Bool + let isHeading: Bool + let isSubheading1: Bool + let isSubheading2: Bool + let isSubheading3: Bool + let isSubheading4: Bool - init(isBold: Bool, isItalics: Bool, isHorizontalTemplate: Bool) { + init(isBold: Bool, isItalics: Bool, isHorizontalTemplate: Bool, isHeading: Bool, isSubheading1: Bool, isSubheading2: Bool, isSubheading3: Bool, isSubheading4: Bool) { self.isBold = isBold self.isItalics = isItalics self.isHorizontalTemplate = isHorizontalTemplate + self.isHeading = isHeading + self.isSubheading1 = isSubheading1 + self.isSubheading2 = isSubheading2 + self.isSubheading3 = isSubheading3 + self.isSubheading4 = isSubheading4 + } } @@ -151,24 +162,34 @@ final class WKSourceEditorTextFrameworkMediator: NSObject { if needsTextKit2 { guard let textKit2Data = textkit2SelectionData(selectedDocumentRange: selectedDocumentRange) else { - return WKSourceEditorSelectionState(isBold: false, isItalics: false, isHorizontalTemplate: false) + return WKSourceEditorSelectionState(isBold: false, isItalics: false, isHorizontalTemplate: false, isHeading: false, isSubheading1: false, isSubheading2: false, isSubheading3: false, isSubheading4: 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 isHeading = headingFormatter?.attributedString(textKit2Data.paragraphAttributedString, isHeadingIn: textKit2Data.paragraphSelectedRange) ?? false + let isSubheading1 = headingFormatter?.attributedString(textKit2Data.paragraphAttributedString, isSubheading1In: textKit2Data.paragraphSelectedRange) ?? false + let isSubheading2 = headingFormatter?.attributedString(textKit2Data.paragraphAttributedString, isSubheading2In: textKit2Data.paragraphSelectedRange) ?? false + let isSubheading3 = headingFormatter?.attributedString(textKit2Data.paragraphAttributedString, isSubheading3In: textKit2Data.paragraphSelectedRange) ?? false + let isSubheading4 = headingFormatter?.attributedString(textKit2Data.paragraphAttributedString, isSubheading4In: textKit2Data.paragraphSelectedRange) ?? false - return WKSourceEditorSelectionState(isBold: isBold, isItalics: isItalics, isHorizontalTemplate: isHorizontalTemplate) + return WKSourceEditorSelectionState(isBold: isBold, isItalics: isItalics, isHorizontalTemplate: isHorizontalTemplate, isHeading: isHeading, isSubheading1: isSubheading1, isSubheading2: isSubheading2, isSubheading3: isSubheading3, isSubheading4: isSubheading4) } else { guard let textKit1Storage else { - return WKSourceEditorSelectionState(isBold: false, isItalics: false, isHorizontalTemplate: false) + return WKSourceEditorSelectionState(isBold: false, isItalics: false, isHorizontalTemplate: false, isHeading: false, isSubheading1: false, isSubheading2: false, isSubheading3: false, isSubheading4: 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 isHeading = headingFormatter?.attributedString(textKit1Storage, isHeadingIn: selectedDocumentRange) ?? false + let isSubheading1 = headingFormatter?.attributedString(textKit1Storage, isSubheading1In: selectedDocumentRange) ?? false + let isSubheading2 = headingFormatter?.attributedString(textKit1Storage, isSubheading2In: selectedDocumentRange) ?? false + let isSubheading3 = headingFormatter?.attributedString(textKit1Storage, isSubheading3In: selectedDocumentRange) ?? false + let isSubheading4 = headingFormatter?.attributedString(textKit1Storage, isSubheading4In: selectedDocumentRange) ?? false - return WKSourceEditorSelectionState(isBold: isBold, isItalics: isItalics, isHorizontalTemplate: isHorizontalTemplate) + return WKSourceEditorSelectionState(isBold: isBold, isItalics: isItalics, isHorizontalTemplate: isHorizontalTemplate, isHeading: isHeading, isSubheading1: isSubheading1, isSubheading2: isSubheading2, isSubheading3: isSubheading3, isSubheading4: isSubheading4) } } From 52ed78ddafa9e07066381bd43c7510fa38e7d279 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Mon, 11 Dec 2023 17:35:06 -0600 Subject: [PATCH 10/35] Add didTapHeading delegate callback --- .../Input Views/WKEditorInputViewController.swift | 1 + .../Editors/Source Editor/WKSourceEditorViewController.swift | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorInputViewController.swift b/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorInputViewController.swift index a2ae309b88f..422cd813ac3 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorInputViewController.swift +++ b/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorInputViewController.swift @@ -6,6 +6,7 @@ protocol WKEditorInputViewDelegate: AnyObject { func didTapBold(isSelected: Bool) func didTapItalics(isSelected: Bool) func didTapTemplate(isSelected: Bool) + func didTapHeading(selectedHeading: WKEditorHeaderSelectViewModel.Configuration) } class WKEditorInputViewController: WKComponentViewController { diff --git a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift index 5a5d7447287..b7f45f3e0c1 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift @@ -344,6 +344,10 @@ extension WKSourceEditorViewController: WKEditorToolbarHighlightViewDelegate { // MARK: - WKEditorInputViewDelegate extension WKSourceEditorViewController: WKEditorInputViewDelegate { + func didTapHeading(selectedHeading: WKEditorHeaderSelectViewModel.Configuration) { + + } + func didTapBold(isSelected: Bool) { let action: WKSourceEditorFormatterButtonAction = isSelected ? .remove : .add textFrameworkMediator.boldItalicsFormatter?.toggleBoldFormatting(action: action, in: textView) From 1844bbecd278c2098a02a243e3d34e7c30710662 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Mon, 11 Dec 2023 17:37:59 -0600 Subject: [PATCH 11/35] Apply selection states to keyboard input views - Listening for selection state changed notification in applicable views - Remove unused WKEditorSelectionDetailViewModel - Post selection state notification when format heading menu is triggered --- ...ditorInputHeaderSelectViewController.swift | 44 +++++++++++++++++++ .../WKEditorSelectionDetailCell.swift | 10 ++--- .../WKEditorSelectionDetailView.swift | 36 ++++++++++++--- .../WKEditorSelectionDetailViewModel.swift | 6 --- .../WKSourceEditorViewController.swift | 2 + 5 files changed, 81 insertions(+), 17 deletions(-) delete mode 100644 Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Detail Table Item/WKEditorSelectionDetailViewModel.swift diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Views/Header Selection/WKEditorInputHeaderSelectViewController.swift b/Components/Sources/Components/Components/Editors/Common Views/Input Views/Header Selection/WKEditorInputHeaderSelectViewController.swift index 9dc6a88819b..d0edf016a22 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Views/Header Selection/WKEditorInputHeaderSelectViewController.swift +++ b/Components/Sources/Components/Components/Editors/Common Views/Input Views/Header Selection/WKEditorInputHeaderSelectViewController.swift @@ -73,6 +73,46 @@ class WKEditorInputHeaderSelectViewController: WKComponentViewController { ]) tableView.register(WKEditorHeaderSelectCell.self, forCellReuseIdentifier: reuseIdentifier) + + NotificationCenter.default.addObserver(self, selector: #selector(updateButtonSelectionState(_:)), name: Notification.WKSourceEditorSelectionState, object: nil) + } + + // MARK: - Notifications + + @objc private func updateButtonSelectionState(_ notification: NSNotification) { + guard let selectionState = notification.userInfo?[Notification.WKSourceEditorSelectionStateKey] as? WKSourceEditorSelectionState else { + return + } + + configure(selectionState: selectionState) + tableView.reloadData() + } + + // MARK: Public + + func configure(selectionState: WKSourceEditorSelectionState) { + viewModels.forEach { $0.isSelected = false } + + let paragraphViewModel = viewModels[0] + let headingViewModel = viewModels[1] + let subheading1ViewModel = viewModels[2] + let subheading2ViewModel = viewModels[3] + let subheading3ViewModel = viewModels[4] + let subheading4ViewModel = viewModels[5] + + if selectionState.isHeading { + headingViewModel.isSelected = true + } else if selectionState.isSubheading1 { + subheading1ViewModel.isSelected = true + } else if selectionState.isSubheading2 { + subheading2ViewModel.isSelected = true + } else if selectionState.isSubheading3 { + subheading3ViewModel.isSelected = true + } else if selectionState.isSubheading4 { + subheading4ViewModel.isSelected = true + } else { + paragraphViewModel.isSelected = true + } } // MARK: Overrides @@ -147,7 +187,11 @@ extension WKEditorInputHeaderSelectViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { for (index, viewModel) in viewModels.enumerated() { + let alreadySelected = viewModel.isSelected && index == indexPath.row viewModel.isSelected = index == indexPath.row + if viewModel.isSelected && !alreadySelected { + delegate?.didTapHeading(selectedHeading: viewModel.configuration) + } } tableView.reloadData() } diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Detail Table Item/WKEditorSelectionDetailCell.swift b/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Detail Table Item/WKEditorSelectionDetailCell.swift index 17cdfb5fc96..8f986c15ec3 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Detail Table Item/WKEditorSelectionDetailCell.swift +++ b/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Detail Table Item/WKEditorSelectionDetailCell.swift @@ -11,6 +11,10 @@ class WKEditorSelectionDetailCell: UITableViewCell { return view }() + var lastSelectionState: WKSourceEditorSelectionState? { + return componentView.lastSelectionState + } + // MARK: - Lifecycle override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { @@ -33,10 +37,4 @@ class WKEditorSelectionDetailCell: UITableViewCell { selectedBackgroundView?.backgroundColor = .clear } - - // MARK: - Internal - - func configure(viewModel: WKEditorSelectionDetailViewModel) { - componentView.configure(viewModel: viewModel) - } } diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Detail Table Item/WKEditorSelectionDetailView.swift b/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Detail Table Item/WKEditorSelectionDetailView.swift index 70ac124065d..e5b61d7784c 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Detail Table Item/WKEditorSelectionDetailView.swift +++ b/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Detail Table Item/WKEditorSelectionDetailView.swift @@ -11,6 +11,7 @@ class WKEditorSelectionDetailView: WKComponentView { label.setContentHuggingPriority(.defaultLow, for: .horizontal) label.adjustsFontForContentSizeCategory = true label.font = WKFont.for(.body, compatibleWith: appEnvironment.traitCollection) + label.text = WKSourceEditorLocalizedStrings.current.inputViewStyle return label }() @@ -30,8 +31,8 @@ class WKEditorSelectionDetailView: WKComponentView { return imageView }() - private var viewModel: WKEditorSelectionDetailViewModel? - + private(set) var lastSelectionState: WKSourceEditorSelectionState? + // MARK: - Lifecycle required init() { @@ -62,13 +63,38 @@ class WKEditorSelectionDetailView: WKComponentView { ]) updateColors() + + NotificationCenter.default.addObserver(self, selector: #selector(updateButtonSelectionState(_:)), name: Notification.WKSourceEditorSelectionState, object: nil) + } + + // MARK: - Notifications + + @objc private func updateButtonSelectionState(_ notification: NSNotification) { + guard let selectionState = notification.userInfo?[Notification.WKSourceEditorSelectionStateKey] as? WKSourceEditorSelectionState else { + return + } + + self.lastSelectionState = selectionState + + configure(selectionState: selectionState) } // MARK: - Internal - func configure(viewModel: WKEditorSelectionDetailViewModel) { - typeLabel.text = viewModel.typeText - selectionLabel.text = viewModel.selectionText + func configure(selectionState: WKSourceEditorSelectionState) { + if selectionState.isHeading { + selectionLabel.text = WKSourceEditorLocalizedStrings.current.inputViewHeading + } else if selectionState.isSubheading1 { + selectionLabel.text = WKSourceEditorLocalizedStrings.current.inputViewSubheading1 + } else if selectionState.isSubheading2 { + selectionLabel.text = WKSourceEditorLocalizedStrings.current.inputViewSubheading2 + } else if selectionState.isSubheading3 { + selectionLabel.text = WKSourceEditorLocalizedStrings.current.inputViewSubheading3 + } else if selectionState.isSubheading4 { + selectionLabel.text = WKSourceEditorLocalizedStrings.current.inputViewSubheading4 + } else { + selectionLabel.text = WKSourceEditorLocalizedStrings.current.inputViewParagraph + } } // MARK: - Overrides diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Detail Table Item/WKEditorSelectionDetailViewModel.swift b/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Detail Table Item/WKEditorSelectionDetailViewModel.swift deleted file mode 100644 index 6ad53cd739d..00000000000 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Detail Table Item/WKEditorSelectionDetailViewModel.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Foundation - -struct WKEditorSelectionDetailViewModel { - let typeText: String - let selectionText: String -} diff --git a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift index b7f45f3e0c1..4d594860296 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift @@ -304,6 +304,7 @@ extension WKSourceEditorViewController: WKEditorToolbarExpandingViewDelegate { func toolbarExpandingViewDidTapFormatHeading(toolbarView: WKEditorToolbarExpandingView) { inputViewType = .headerSelect + postUpdateButtonSelectionStatesNotification(withDelay: true) } func toolbarExpandingViewDidTapTemplate(toolbarView: WKEditorToolbarExpandingView, isSelected: Bool) { @@ -338,6 +339,7 @@ extension WKSourceEditorViewController: WKEditorToolbarHighlightViewDelegate { func toolbarHighlightViewDidTapFormatHeading(toolbarView: WKEditorToolbarHighlightView) { inputViewType = .headerSelect + postUpdateButtonSelectionStatesNotification(withDelay: true) } } From 16626adafe8b330faa45179ed80d5ffbeac95a5a Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Mon, 11 Dec 2023 17:38:39 -0600 Subject: [PATCH 12/35] Update selection state when format header view is pushed onto navigation stack --- .../Main/WKEditorInputMainViewController.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/WKEditorInputMainViewController.swift b/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/WKEditorInputMainViewController.swift index bc0c898f25a..93b12a5f207 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/WKEditorInputMainViewController.swift +++ b/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/WKEditorInputMainViewController.swift @@ -108,7 +108,6 @@ extension WKEditorInputMainViewController: UITableViewDataSource { cell = tableView.dequeueReusableCell(withIdentifier: detailReuseIdentifier, for: indexPath) if let detailCell = cell as? WKEditorSelectionDetailCell { - detailCell.configure(viewModel: WKEditorSelectionDetailViewModel(typeText: WKSourceEditorLocalizedStrings.current.inputViewStyle, selectionText: WKSourceEditorLocalizedStrings.current.inputViewParagraph)) detailCell.accessibilityTraits = [.button] } case 3: @@ -134,9 +133,14 @@ extension WKEditorInputMainViewController: UITableViewDelegate { let cell = tableView.cellForRow(at: indexPath) cell?.isSelected = false - if indexPath.row == 2 { + if let detailCell = cell as? WKEditorSelectionDetailCell { navigationItem.backButtonTitle = WKSourceEditorLocalizedStrings.current.inputViewTextFormatting let headerVC = WKEditorInputHeaderSelectViewController(configuration: .standard, delegate: delegate) + + if let selectionState = detailCell.lastSelectionState { + headerVC.configure(selectionState: selectionState) + } + navigationController?.pushViewController(headerVC, animated: true) } } From 2c87242b8d271f2179f6499f1aa8e006376dd1af Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Mon, 11 Dec 2023 17:39:55 -0600 Subject: [PATCH 13/35] Expose formatter base class methods for heading button action needs --- .../WKSourceEditorFormatter+ButtonActions.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 e0e70e9d2f6..4ff96c46667 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 @@ -35,7 +35,7 @@ extension WKSourceEditorFormatter { // MARK: - Expanding selected range methods - private func expandSelectedRangeUpToNearestFormattingStrings(startingFormattingString: String, endingFormattingString: String, in textView: UITextView) { + func expandSelectedRangeUpToNearestFormattingStrings(startingFormattingString: String, endingFormattingString: String, in textView: UITextView) { if let textPositions = textPositionsCloserToNearestFormattingStrings(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView) { textView.selectedTextRange = textView.textRange(from: textPositions.startPosition, to: textPositions.endPosition) } @@ -108,7 +108,7 @@ extension WKSourceEditorFormatter { // MARK: - Nearby formatting string determination - private func selectedRangeIsSurroundedByFormattingString(formattingString: String, in textView: UITextView) -> Bool { + func selectedRangeIsSurroundedByFormattingString(formattingString: String, in textView: UITextView) -> Bool { selectedRangeIsSurroundedByFormattingStrings(startingFormattingString: formattingString, endingFormattingString: formattingString, in: textView) } @@ -130,7 +130,7 @@ extension WKSourceEditorFormatter { return startingString == formattingString } - private func rangeIsFollowedByFormattingString(range: UITextRange?, formattingString: String, in textView: UITextView) -> Bool { + func rangeIsFollowedByFormattingString(range: UITextRange?, formattingString: String, in textView: UITextView) -> Bool { guard let range = range, let newEnd = textView.position(from: range.end, offset: formattingString.count) else { return false @@ -146,7 +146,7 @@ extension WKSourceEditorFormatter { // MARK: Adding and removing text - private func addStringFormattingCharacters(startingFormattingString: String, endingFormattingString: String, in textView: UITextView) { + func addStringFormattingCharacters(startingFormattingString: String, endingFormattingString: String, in textView: UITextView) { let startingCursorOffset = startingFormattingString.count let endingCursorOffset = endingFormattingString.count @@ -173,7 +173,7 @@ extension WKSourceEditorFormatter { } } - private func removeSurroundingFormattingStringsFromSelectedRange(startingFormattingString: String, endingFormattingString: String, in textView: UITextView) { + func removeSurroundingFormattingStringsFromSelectedRange(startingFormattingString: String, endingFormattingString: String, in textView: UITextView) { guard let originalSelectedTextRange = textView.selectedTextRange, let formattingTextStart = textView.position(from: originalSelectedTextRange.start, offset: -startingFormattingString.count), From 4b17b3adae2d65338484e6ca5d62050d454a497c Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Mon, 11 Dec 2023 17:40:39 -0600 Subject: [PATCH 14/35] Create heading formatter extension to toggle formatting --- ...EditorFormatterHeading+ButtonActions.swift | 54 +++++++++++++++++++ .../WKSourceEditorViewController.swift | 2 +- 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterHeading+ButtonActions.swift diff --git a/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterHeading+ButtonActions.swift b/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterHeading+ButtonActions.swift new file mode 100644 index 00000000000..123e10f1903 --- /dev/null +++ b/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterHeading+ButtonActions.swift @@ -0,0 +1,54 @@ +import Foundation +import ComponentsObjC + +extension WKSourceEditorFormatterHeading { + func toggleHeadingFormatting(selectedHeading: WKEditorHeaderSelectViewModel.Configuration, currentSelectionState: WKSourceEditorSelectionState, textView: UITextView) { + + var currentStateIsParagraph = false + if currentSelectionState.isHeading { + expandSelectedRangeUpToNearestFormattingStrings(startingFormattingString: "==", endingFormattingString: "==", in: textView) + removeSurroundingFormattingStringsFromSelectedRange(startingFormattingString: "==", endingFormattingString: "==", in: textView) + } else if currentSelectionState.isSubheading1 { + expandSelectedRangeUpToNearestFormattingStrings(startingFormattingString: "===", endingFormattingString: "===", in: textView) + removeSurroundingFormattingStringsFromSelectedRange(startingFormattingString: "===", endingFormattingString: "===", in: textView) + } else if currentSelectionState.isSubheading2 { + expandSelectedRangeUpToNearestFormattingStrings(startingFormattingString: "====", endingFormattingString: "====", in: textView) + removeSurroundingFormattingStringsFromSelectedRange(startingFormattingString: "====", endingFormattingString: "====", in: textView) + } else if currentSelectionState.isSubheading3 { + expandSelectedRangeUpToNearestFormattingStrings(startingFormattingString: "=====", endingFormattingString: "=====", in: textView) + removeSurroundingFormattingStringsFromSelectedRange(startingFormattingString: "=====", endingFormattingString: "=====", in: textView) + } else if currentSelectionState.isSubheading4 { + expandSelectedRangeUpToNearestFormattingStrings(startingFormattingString: "======", endingFormattingString: "======", in: textView) + removeSurroundingFormattingStringsFromSelectedRange(startingFormattingString: "======", endingFormattingString: "======", in: textView) + } else { + currentStateIsParagraph = true + } + + let currentlySurroundedByLineBreaks = selectedRangeIsSurroundedByFormattingString(formattingString: "\n", in: textView) || textView.selectedRange.location == 0 && rangeIsFollowedByFormattingString(range: textView.selectedTextRange, formattingString: "\n", in: textView) + + let surroundingLineBreak = currentStateIsParagraph && !currentlySurroundedByLineBreaks ? "\n" : "" + let startingFormattingString: String + let endingFormattingString: String + switch selectedHeading { + case .paragraph: + return + case .heading: + startingFormattingString = surroundingLineBreak + "==" + endingFormattingString = "==" + surroundingLineBreak + case .subheading1: + startingFormattingString = surroundingLineBreak + "===" + endingFormattingString = "===" + surroundingLineBreak + case .subheading2: + startingFormattingString = surroundingLineBreak + "====" + endingFormattingString = "====" + surroundingLineBreak + case .subheading3: + startingFormattingString = surroundingLineBreak + "=====" + endingFormattingString = "=====" + surroundingLineBreak + case .subheading4: + startingFormattingString = surroundingLineBreak + "======" + endingFormattingString = "======" + surroundingLineBreak + } + + addStringFormattingCharacters(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView) + } +} diff --git a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift index 4d594860296..9b38cef9b71 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift @@ -347,7 +347,7 @@ extension WKSourceEditorViewController: WKEditorToolbarHighlightViewDelegate { extension WKSourceEditorViewController: WKEditorInputViewDelegate { func didTapHeading(selectedHeading: WKEditorHeaderSelectViewModel.Configuration) { - + textFrameworkMediator.headingFormatter?.toggleHeadingFormatting(selectedHeading: selectedHeading, currentSelectionState: selectionState(), textView: textView) } func didTapBold(isSelected: Bool) { From 8ecf9bfd2b707ff2211832f05d6a1e4435dd6d4c Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Mon, 11 Dec 2023 19:43:50 -0600 Subject: [PATCH 15/35] Fix missing bold/italic syntax highlighting on iOS 15/6 --- .../Sources/ComponentsObjC/WKSourceEditorFormatterBase.m | 5 ++++- .../ComponentsObjC/WKSourceEditorFormatterBoldItalics.m | 1 - .../Sources/ComponentsObjC/WKSourceEditorFormatterHeading.m | 1 - 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBase.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBase.m index d7ad812afa3..049ab29f0fd 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBase.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBase.m @@ -29,10 +29,13 @@ - (instancetype)initWithColors:(nonnull WKSourceEditorColors *)colors fonts:(non - (void)addSyntaxHighlightingToAttributedString:(NSMutableAttributedString *)attributedString inRange:(NSRange)range { - // reset old attributes + // reset base attributes [attributedString removeAttribute:NSFontAttributeName range:range]; [attributedString removeAttribute:NSForegroundColorAttributeName range:range]; + // reset shared custom attributes + [attributedString removeAttribute:WKSourceEditorCustomKeyColorOrange range:range]; + [attributedString addAttributes:self.attributes range:range]; } diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m index aeff59f7baf..fa17430e996 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m @@ -59,7 +59,6 @@ - (instancetype)initWithColors:(WKSourceEditorColors *)colors fonts:(WKSourceEdi - (void)addSyntaxHighlightingToAttributedString:(nonnull NSMutableAttributedString *)attributedString inRange:(NSRange)range { // Reset - [attributedString removeAttribute:WKSourceEditorCustomKeyColorOrange range:range]; [attributedString removeAttribute:WKSourceEditorCustomKeyFontBoldItalics range:range]; [attributedString removeAttribute:WKSourceEditorCustomKeyFontBold range:range]; [attributedString removeAttribute:WKSourceEditorCustomKeyFontItalics range:range]; diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterHeading.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterHeading.m index dafa1f8ec77..ea50496ee21 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterHeading.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterHeading.m @@ -76,7 +76,6 @@ - (instancetype)initWithColors:(WKSourceEditorColors *)colors fonts:(WKSourceEdi - (void)addSyntaxHighlightingToAttributedString:(nonnull NSMutableAttributedString *)attributedString inRange:(NSRange)range { // Reset - [attributedString removeAttribute:WKSourceEditorCustomKeyColorOrange range:range]; [attributedString removeAttribute:WKSourceEditorCustomKeyFontHeading range:range]; [attributedString removeAttribute:WKSourceEditorCustomKeyFontSubheading1 range:range]; [attributedString removeAttribute:WKSourceEditorCustomKeyFontSubheading2 range:range]; From d4a4c2849eded9a5e529f77b5fc4e88fdc1253e8 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Tue, 12 Dec 2023 09:07:51 -0600 Subject: [PATCH 16/35] Add tests --- ...urceEditorFormatterButtonActionTests.swift | 110 ++++++++++++++++++ ...urceEditorTextFrameworkMediatorTests.swift | 100 ++++++++++++++++ 2 files changed, 210 insertions(+) diff --git a/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift index ee99fe49cd6..1854eed06ce 100644 --- a/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift +++ b/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift @@ -122,4 +122,114 @@ final class WKSourceEditorFormatterButtonActionTests: XCTestCase { mediator.templateFormatter?.toggleTemplateFormatting(action: .remove, in: mediator.textView) XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four") } + + func testHeadingAdd() throws { + let text = "Test" + let selectedRange = NSRange(location: 0, length: 4) + let textView = mediator.textView + textView.attributedText = NSAttributedString(string: text) + textView.selectedRange = selectedRange + mediator.headingFormatter?.toggleHeadingFormatting(selectedHeading: .heading, currentSelectionState: mediator.selectionState(selectedDocumentRange: selectedRange), textView: textView) + XCTAssertEqual(mediator.textView.attributedText.string, "\n==Test==\n") + } + + func testHeadingRemove() throws { + let text = "==Test==" + let selectedRange = NSRange(location: 2, length: 4) + let textView = mediator.textView + textView.attributedText = NSAttributedString(string: text) + textView.selectedRange = selectedRange + mediator.headingFormatter?.toggleHeadingFormatting(selectedHeading: .paragraph, currentSelectionState: mediator.selectionState(selectedDocumentRange: selectedRange), textView: textView) + XCTAssertEqual(mediator.textView.attributedText.string, "Test") + } + + func testSubheading1Add() throws { + let text = "Test" + let selectedRange = NSRange(location: 0, length: 4) + let textView = mediator.textView + textView.attributedText = NSAttributedString(string: text) + textView.selectedRange = selectedRange + mediator.headingFormatter?.toggleHeadingFormatting(selectedHeading: .subheading1, currentSelectionState: mediator.selectionState(selectedDocumentRange: selectedRange), textView: textView) + XCTAssertEqual(mediator.textView.attributedText.string, "\n===Test===\n") + } + + func testSubheading1Remove() throws { + let text = "===Test===" + let selectedRange = NSRange(location: 3, length: 4) + let textView = mediator.textView + textView.attributedText = NSAttributedString(string: text) + textView.selectedRange = selectedRange + mediator.headingFormatter?.toggleHeadingFormatting(selectedHeading: .paragraph, currentSelectionState: mediator.selectionState(selectedDocumentRange: selectedRange), textView: textView) + XCTAssertEqual(mediator.textView.attributedText.string, "Test") + } + + func testSubheading2Add() throws { + let text = "Test" + let selectedRange = NSRange(location: 0, length: 4) + let textView = mediator.textView + textView.attributedText = NSAttributedString(string: text) + textView.selectedRange = selectedRange + mediator.headingFormatter?.toggleHeadingFormatting(selectedHeading: .subheading2, currentSelectionState: mediator.selectionState(selectedDocumentRange: selectedRange), textView: textView) + XCTAssertEqual(mediator.textView.attributedText.string, "\n====Test====\n") + } + + func testSubheading2Remove() throws { + let text = "====Test====" + let selectedRange = NSRange(location: 4, length: 4) + let textView = mediator.textView + textView.attributedText = NSAttributedString(string: text) + textView.selectedRange = selectedRange + mediator.headingFormatter?.toggleHeadingFormatting(selectedHeading: .paragraph, currentSelectionState: mediator.selectionState(selectedDocumentRange: selectedRange), textView: textView) + XCTAssertEqual(mediator.textView.attributedText.string, "Test") + } + + func testSubheading3Add() throws { + let text = "Test" + let selectedRange = NSRange(location: 0, length: 4) + let textView = mediator.textView + textView.attributedText = NSAttributedString(string: text) + textView.selectedRange = selectedRange + mediator.headingFormatter?.toggleHeadingFormatting(selectedHeading: .subheading3, currentSelectionState: mediator.selectionState(selectedDocumentRange: selectedRange), textView: textView) + XCTAssertEqual(mediator.textView.attributedText.string, "\n=====Test=====\n") + } + + func testSubheading3Remove() throws { + let text = "=====Test=====" + let selectedRange = NSRange(location: 5, length: 4) + let textView = mediator.textView + textView.attributedText = NSAttributedString(string: text) + textView.selectedRange = selectedRange + mediator.headingFormatter?.toggleHeadingFormatting(selectedHeading: .paragraph, currentSelectionState: mediator.selectionState(selectedDocumentRange: selectedRange), textView: textView) + XCTAssertEqual(mediator.textView.attributedText.string, "Test") + } + + func testSubheading4Add() throws { + let text = "Test" + let selectedRange = NSRange(location: 0, length: 4) + let textView = mediator.textView + textView.attributedText = NSAttributedString(string: text) + textView.selectedRange = selectedRange + mediator.headingFormatter?.toggleHeadingFormatting(selectedHeading: .subheading4, currentSelectionState: mediator.selectionState(selectedDocumentRange: selectedRange), textView: textView) + XCTAssertEqual(mediator.textView.attributedText.string, "\n======Test======\n") + } + + func testSubheading4Remove() throws { + let text = "======Test======" + let selectedRange = NSRange(location: 6, length: 4) + let textView = mediator.textView + textView.attributedText = NSAttributedString(string: text) + textView.selectedRange = selectedRange + mediator.headingFormatter?.toggleHeadingFormatting(selectedHeading: .paragraph, currentSelectionState: mediator.selectionState(selectedDocumentRange: selectedRange), textView: textView) + XCTAssertEqual(mediator.textView.attributedText.string, "Test") + } + + func testHeadingSwitchToSubheading3() throws { + let text = "==Test==" + let selectedRange = NSRange(location: 2, length: 4) + let textView = mediator.textView + textView.attributedText = NSAttributedString(string: text) + textView.selectedRange = selectedRange + mediator.headingFormatter?.toggleHeadingFormatting(selectedHeading: .subheading3, currentSelectionState: mediator.selectionState(selectedDocumentRange: selectedRange), textView: textView) + XCTAssertEqual(mediator.textView.attributedText.string, "=====Test=====") + } } diff --git a/Components/Tests/ComponentsTests/WKSourceEditorTextFrameworkMediatorTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorTextFrameworkMediatorTests.swift index 5f01b4b4c71..91dfb53cf63 100644 --- a/Components/Tests/ComponentsTests/WKSourceEditorTextFrameworkMediatorTests.swift +++ b/Components/Tests/ComponentsTests/WKSourceEditorTextFrameworkMediatorTests.swift @@ -121,4 +121,104 @@ final class WKSourceEditorTextFrameworkMediatorTests: XCTestCase { let selectionStates1 = mediator.selectionState(selectedDocumentRange: NSRange(location: 1, length: 0)) XCTAssertFalse(selectionStates1.isHorizontalTemplate) } + + func testHeadingSelectionState() throws { + + let text = "== Test ==" + mediator.textView.attributedText = NSAttributedString(string: text) + + let selectionStates1 = mediator.selectionState(selectedDocumentRange: NSRange(location: 3, length: 4)) + XCTAssertTrue(selectionStates1.isHeading) + XCTAssertFalse(selectionStates1.isSubheading1) + XCTAssertFalse(selectionStates1.isSubheading2) + XCTAssertFalse(selectionStates1.isSubheading3) + XCTAssertFalse(selectionStates1.isSubheading4) + + let selectionStates2 = mediator.selectionState(selectedDocumentRange: NSRange(location: 6, length: 0)) + XCTAssertTrue(selectionStates2.isHeading) + XCTAssertFalse(selectionStates2.isSubheading1) + XCTAssertFalse(selectionStates2.isSubheading2) + XCTAssertFalse(selectionStates2.isSubheading3) + XCTAssertFalse(selectionStates2.isSubheading4) + } + + func testSubheading1SelectionState() throws { + + let text = "=== Test ===" + mediator.textView.attributedText = NSAttributedString(string: text) + + let selectionStates1 = mediator.selectionState(selectedDocumentRange: NSRange(location: 4, length: 4)) + XCTAssertFalse(selectionStates1.isHeading) + XCTAssertTrue(selectionStates1.isSubheading1) + XCTAssertFalse(selectionStates1.isSubheading2) + XCTAssertFalse(selectionStates1.isSubheading3) + XCTAssertFalse(selectionStates1.isSubheading4) + + let selectionStates2 = mediator.selectionState(selectedDocumentRange: NSRange(location: 6, length: 0)) + XCTAssertFalse(selectionStates2.isHeading) + XCTAssertTrue(selectionStates2.isSubheading1) + XCTAssertFalse(selectionStates2.isSubheading2) + XCTAssertFalse(selectionStates2.isSubheading3) + XCTAssertFalse(selectionStates2.isSubheading4) + } + + func testSubheading2SelectionState() throws { + + let text = "==== Test ====" + mediator.textView.attributedText = NSAttributedString(string: text) + + let selectionStates1 = mediator.selectionState(selectedDocumentRange: NSRange(location: 5, length: 4)) + XCTAssertFalse(selectionStates1.isHeading) + XCTAssertFalse(selectionStates1.isSubheading1) + XCTAssertTrue(selectionStates1.isSubheading2) + XCTAssertFalse(selectionStates1.isSubheading3) + XCTAssertFalse(selectionStates1.isSubheading4) + + let selectionStates2 = mediator.selectionState(selectedDocumentRange: NSRange(location: 7, length: 0)) + XCTAssertFalse(selectionStates2.isHeading) + XCTAssertFalse(selectionStates2.isSubheading1) + XCTAssertTrue(selectionStates2.isSubheading2) + XCTAssertFalse(selectionStates2.isSubheading3) + XCTAssertFalse(selectionStates2.isSubheading4) + } + + func testSubheading3SelectionState() throws { + + let text = "===== Test =====" + mediator.textView.attributedText = NSAttributedString(string: text) + + let selectionStates1 = mediator.selectionState(selectedDocumentRange: NSRange(location: 6, length: 4)) + XCTAssertFalse(selectionStates1.isHeading) + XCTAssertFalse(selectionStates1.isSubheading1) + XCTAssertFalse(selectionStates1.isSubheading2) + XCTAssertTrue(selectionStates1.isSubheading3) + XCTAssertFalse(selectionStates1.isSubheading4) + + let selectionStates2 = mediator.selectionState(selectedDocumentRange: NSRange(location: 8, length: 0)) + XCTAssertFalse(selectionStates2.isHeading) + XCTAssertFalse(selectionStates2.isSubheading1) + XCTAssertFalse(selectionStates2.isSubheading2) + XCTAssertTrue(selectionStates2.isSubheading3) + XCTAssertFalse(selectionStates2.isSubheading4) + } + + func testSubheading4SelectionState() throws { + + let text = "====== Test ======" + mediator.textView.attributedText = NSAttributedString(string: text) + + let selectionStates1 = mediator.selectionState(selectedDocumentRange: NSRange(location: 7, length: 4)) + XCTAssertFalse(selectionStates1.isHeading) + XCTAssertFalse(selectionStates1.isSubheading1) + XCTAssertFalse(selectionStates1.isSubheading2) + XCTAssertFalse(selectionStates1.isSubheading3) + XCTAssertTrue(selectionStates1.isSubheading4) + + let selectionStates2 = mediator.selectionState(selectedDocumentRange: NSRange(location: 9, length: 0)) + XCTAssertFalse(selectionStates2.isHeading) + XCTAssertFalse(selectionStates2.isSubheading1) + XCTAssertFalse(selectionStates2.isSubheading2) + XCTAssertFalse(selectionStates2.isSubheading3) + XCTAssertTrue(selectionStates2.isSubheading4) + } } From bc00a5ad2cf9ced76d95672283ad25b0d1ca8f6f Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Tue, 12 Dec 2023 09:52:12 -0600 Subject: [PATCH 17/35] Fix RTL and last character cursor selection state bugs --- .../WKSourceEditorFormatterHeading.m | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterHeading.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterHeading.m index a5fd86edfa4..0d36a57eaac 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterHeading.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterHeading.m @@ -174,17 +174,33 @@ - (BOOL)attributedString:(NSMutableAttributedString *)attributedString isContent if (attrs[contentKey] != nil) { isContentKey = YES; + } else { + // Edge case, check previous character if we are up against closing string + if (attrs[WKSourceEditorCustomKeyColorOrange]) { + attrs = [attributedString attributesAtIndex:range.location - 1 effectiveRange:nil]; + if (attrs[contentKey] != 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[contentKey] != nil) && - (loopRange.location == range.location && loopRange.length == range.length)) { - isContentKey = YES; + if (attrs[contentKey] != nil) { + if (unionRange.location == NSNotFound) { + unionRange = loopRange; + } else { + unionRange = NSUnionRange(unionRange, loopRange); + } stop = YES; } }]; + + if (NSEqualRanges(unionRange, range)) { + isContentKey = YES; + } } return isContentKey; From 6c6207f2ac040e6f1b5308db6307710fd804f705 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Wed, 13 Dec 2023 10:28:27 -0600 Subject: [PATCH 18/35] Add empty list formatter --- .../Sources/ComponentsObjC/WKSourceEditorFormatterList.h | 9 +++++++++ .../Sources/ComponentsObjC/WKSourceEditorFormatterList.m | 5 +++++ .../Sources/ComponentsObjC/include/ComponentsObjC.h | 1 + .../ComponentsObjC/include/WKSourceEditorFormatterList.h | 1 + 4 files changed, 16 insertions(+) create mode 100644 Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.h create mode 100644 Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.m create mode 120000 Components/Sources/ComponentsObjC/include/WKSourceEditorFormatterList.h diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.h b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.h new file mode 100644 index 00000000000..8047362f35e --- /dev/null +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.h @@ -0,0 +1,9 @@ +#import "WKSourceEditorFormatter.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface WKSourceEditorFormatterList : WKSourceEditorFormatter + +@end + +NS_ASSUME_NONNULL_END diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.m new file mode 100644 index 00000000000..c6f5872cc3d --- /dev/null +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.m @@ -0,0 +1,5 @@ +#import "WKSourceEditorFormatterList.h" + +@implementation WKSourceEditorFormatterList + +@end diff --git a/Components/Sources/ComponentsObjC/include/ComponentsObjC.h b/Components/Sources/ComponentsObjC/include/ComponentsObjC.h index 078e6dec5d6..db147fc99b9 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 "WKSourceEditorFormatterList.h" #import "WKSourceEditorStorageDelegate.h" #endif /* Header_h */ diff --git a/Components/Sources/ComponentsObjC/include/WKSourceEditorFormatterList.h b/Components/Sources/ComponentsObjC/include/WKSourceEditorFormatterList.h new file mode 120000 index 00000000000..ca459437160 --- /dev/null +++ b/Components/Sources/ComponentsObjC/include/WKSourceEditorFormatterList.h @@ -0,0 +1 @@ +../WKSourceEditorFormatterList.h \ No newline at end of file From e5b32a0a4a3e3499e34ffa744452e9f7c5df8d75 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Wed, 13 Dec 2023 12:34:21 -0600 Subject: [PATCH 19/35] Move common custom orange key to superclass --- Components/Sources/ComponentsObjC/WKSourceEditorFormatter.h | 3 +++ Components/Sources/ComponentsObjC/WKSourceEditorFormatter.m | 5 +++++ .../ComponentsObjC/WKSourceEditorFormatterBoldItalics.m | 3 --- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatter.h b/Components/Sources/ComponentsObjC/WKSourceEditorFormatter.h index f3cb213f291..e6ff6d9cf6f 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatter.h +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatter.h @@ -4,6 +4,9 @@ NS_ASSUME_NONNULL_BEGIN @interface WKSourceEditorFormatter : NSObject + +extern NSString *const WKSourceEditorCustomKeyColorOrange; + - (instancetype)initWithColors:(nonnull WKSourceEditorColors *)colors fonts:(nonnull WKSourceEditorFonts *)fonts; - (void)addSyntaxHighlightingToAttributedString:(NSMutableAttributedString *)attributedString inRange:(NSRange)range; diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatter.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatter.m index 5c4fb125102..7d69fde7dd5 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatter.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatter.m @@ -3,6 +3,11 @@ #import "WKSourceEditorFonts.h" @implementation WKSourceEditorFormatter + +#pragma mark - Common Custom Attributed String Keys +// Font and Color custom attributes allow us to easily target already-formatted ranges. This is handy for speedy updates upon theme and text size change, as well as determining keyboard button selection states. +NSString * const WKSourceEditorCustomKeyColorOrange = @"WKSourceEditorKeyColorOrange"; + - (nonnull instancetype)initWithColors:(nonnull WKSourceEditorColors *)colors fonts:(nonnull WKSourceEditorFonts *)fonts { self = [super init]; return self; diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m index 763e2947cd0..aeff59f7baf 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m @@ -18,9 +18,6 @@ @interface WKSourceEditorFormatterBoldItalics () @implementation WKSourceEditorFormatterBoldItalics #pragma mark - Custom Attributed String Keys - -// Font and Color custom attributes allow us to easily target already-formatted ranges. This is handy for speedy updates upon theme and text size change, as well as determining keyboard button selection states. -NSString * const WKSourceEditorCustomKeyColorOrange = @"WKSourceEditorKeyColorOrange"; NSString * const WKSourceEditorCustomKeyFontBoldItalics = @"WKSourceEditorKeyFontBoldItalics"; NSString * const WKSourceEditorCustomKeyFontBold = @"WKSourceEditorKeyFontBold"; NSString * const WKSourceEditorCustomKeyFontItalics = @"WKSourceEditorKeyFontItalics"; From 83fc8e839e2d6b4acfeda5864d3e80ee80fedef8 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Wed, 13 Dec 2023 12:34:55 -0600 Subject: [PATCH 20/35] Complete highlighting for WKSourceEditorFormatterList --- .../WKSourceEditorFormatterList.m | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.m index c6f5872cc3d..334c0a2e142 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.m @@ -1,5 +1,83 @@ #import "WKSourceEditorFormatterList.h" +#import "WKSourceEditorColors.h" + +@interface WKSourceEditorFormatterList () + +@property (nonatomic, strong) NSDictionary *orangeAttributes; + +@property (nonatomic, strong) NSRegularExpression *bulletRegex; +@property (nonatomic, strong) NSRegularExpression *numberRegex; + +@end @implementation WKSourceEditorFormatterList +#pragma mark - Overrides + +- (instancetype)initWithColors:(WKSourceEditorColors *)colors fonts:(WKSourceEditorFonts *)fonts { + self = [super initWithColors:colors fonts:fonts]; + if (self) { + _orangeAttributes = @{ + NSForegroundColorAttributeName: colors.orangeForegroundColor, + WKSourceEditorCustomKeyColorOrange: [NSNumber numberWithBool:YES] + }; + + _bulletRegex = [[NSRegularExpression alloc] initWithPattern:@"^(\\*+)(.*)$" options:NSRegularExpressionAnchorsMatchLines error:nil]; + _numberRegex = [[NSRegularExpression alloc] initWithPattern:@"^(#+)(.*)$" options:NSRegularExpressionAnchorsMatchLines error:nil]; + } + return self; +} + +- (void)addSyntaxHighlightingToAttributedString:(nonnull NSMutableAttributedString *)attributedString inRange:(NSRange)range { + + [self.bulletRegex enumerateMatchesInString:attributedString.string + options:0 + range:range + usingBlock:^(NSTextCheckingResult *_Nullable result, NSMatchingFlags flags, BOOL *_Nonnull stop) { + NSRange fullMatch = [result rangeAtIndex:0]; + NSRange bulletRange = [result rangeAtIndex:1]; + + if (bulletRange.location != NSNotFound) { + [attributedString addAttributes:self.orangeAttributes range:bulletRange]; + } + }]; + + [self.numberRegex enumerateMatchesInString:attributedString.string + options:0 + range:range + usingBlock:^(NSTextCheckingResult *_Nullable result, NSMatchingFlags flags, BOOL *_Nonnull stop) { + NSRange fullMatch = [result rangeAtIndex:0]; + NSRange numberRange = [result rangeAtIndex:1]; + + if (numberRange.location != NSNotFound) { + [attributedString addAttributes:self.orangeAttributes range:numberRange]; + } + }]; +} + +- (void)updateColors:(WKSourceEditorColors *)colors inAttributedString:(NSMutableAttributedString *)attributedString inRange:(NSRange)range { + + // First update orangeAttributes property so that addSyntaxHighlighting has the correct color the next time it is called + NSMutableDictionary *mutAttributes = [[NSMutableDictionary alloc] initWithDictionary:self.orangeAttributes]; + [mutAttributes setObject:colors.orangeForegroundColor forKey:NSForegroundColorAttributeName]; + self.orangeAttributes = [[NSDictionary alloc] initWithDictionary:mutAttributes]; + + // Then update entire attributed string orange color + [attributedString enumerateAttribute:WKSourceEditorCustomKeyColorOrange + 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.orangeAttributes range:localRange]; + } + } + }]; +} + +- (void)updateFonts:(WKSourceEditorFonts *)fonts inAttributedString:(NSMutableAttributedString *)attributedString inRange:(NSRange)range { + // No special font handling needed for lists +} + @end From 1bf8c2dff3b65f0276b281aa149fee3f250303b9 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Wed, 13 Dec 2023 12:35:04 -0600 Subject: [PATCH 21/35] Use new formatter in editor --- .../Source Editor/WKSourceEditorTextFrameworkMediator.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift index c414a31bee6..25b16ade1d0 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift @@ -35,6 +35,7 @@ final class WKSourceEditorTextFrameworkMediator: NSObject { private(set) var formatters: [WKSourceEditorFormatter] = [] private(set) var boldItalicsFormatter: WKSourceEditorFormatterBoldItalics? private(set) var templateFormatter: WKSourceEditorFormatterTemplate? + private(set) var listFormatter: WKSourceEditorFormatterList? var isSyntaxHighlightingEnabled: Bool = true { didSet { @@ -103,11 +104,14 @@ final class WKSourceEditorTextFrameworkMediator: NSObject { let boldItalicsFormatter = WKSourceEditorFormatterBoldItalics(colors: colors, fonts: fonts) let templateFormatter = WKSourceEditorFormatterTemplate(colors: colors, fonts: fonts) + let listFormatter = WKSourceEditorFormatterList(colors: colors, fonts: fonts) self.formatters = [WKSourceEditorFormatterBase(colors: colors, fonts: fonts, textAlignment: viewModel.textAlignment), templateFormatter, - boldItalicsFormatter] + boldItalicsFormatter, + listFormatter] self.boldItalicsFormatter = boldItalicsFormatter self.templateFormatter = templateFormatter + self.listFormatter = listFormatter if needsTextKit2 { if #available(iOS 16.0, *) { From dcc4937bcc9611408174889f7f8fdf3520eb1216 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Wed, 13 Dec 2023 12:35:09 -0600 Subject: [PATCH 22/35] Add tests --- .../WKSourceEditorFormatterTests.swift | 112 +++++++++++++++++- 1 file changed, 111 insertions(+), 1 deletion(-) diff --git a/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift index 6330fe0ba7c..eeb4375ba1c 100644 --- a/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift +++ b/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift @@ -10,8 +10,9 @@ final class WKSourceEditorFormatterTests: XCTestCase { var baseFormatter: WKSourceEditorFormatterBase! var boldItalicsFormatter: WKSourceEditorFormatterBoldItalics! var templateFormatter: WKSourceEditorFormatterTemplate! + var listFormatter: WKSourceEditorFormatterList! var formatters: [WKSourceEditorFormatter] { - return [baseFormatter, templateFormatter, boldItalicsFormatter] + return [baseFormatter, templateFormatter, boldItalicsFormatter, listFormatter] } override func setUpWithError() throws { @@ -31,6 +32,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.listFormatter = WKSourceEditorFormatterList(colors: colors, fonts: fonts) } override func tearDownWithError() throws { @@ -709,4 +711,112 @@ final class WKSourceEditorFormatterTests: XCTestCase { XCTAssertEqual(refAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect ref formatting") XCTAssertEqual(refAttributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect ref formatting") } + + func testListSingleBullet() { + let string = "* Testing" + let mutAttributedString = NSMutableAttributedString(string: string) + + for formatter in formatters { + formatter.addSyntaxHighlighting(to: mutAttributedString, in: NSRange(location: 0, length: string.count)) + } + + var bulletRange = NSRange(location: 0, length: 0) + let bulletAttributes = mutAttributedString.attributes(at: 0, effectiveRange: &bulletRange) + + var textRange = NSRange(location: 0, length: 0) + let textAttributes = mutAttributedString.attributes(at: 1, effectiveRange: &textRange) + + // "*" + XCTAssertEqual(bulletRange.location, 0, "Incorrect list formatting") + XCTAssertEqual(bulletRange.length, 1, "Incorrect list formatting") + XCTAssertEqual(bulletAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect list formatting") + XCTAssertEqual(bulletAttributes[.foregroundColor] as! UIColor, colors.orangeForegroundColor, "Incorrect list formatting") + + // " Testing" + XCTAssertEqual(textRange.location, 1, "Incorrect list formatting") + XCTAssertEqual(textRange.length, 8, "Incorrect list formatting") + XCTAssertEqual(textAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect list formatting") + XCTAssertEqual(textAttributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect list formatting") + } + + func testListSingleNumber() { + let string = "# Testing" + let mutAttributedString = NSMutableAttributedString(string: string) + + for formatter in formatters { + formatter.addSyntaxHighlighting(to: mutAttributedString, in: NSRange(location: 0, length: string.count)) + } + + var numberRange = NSRange(location: 0, length: 0) + let numberAttributes = mutAttributedString.attributes(at: 0, effectiveRange: &numberRange) + + var textRange = NSRange(location: 0, length: 0) + let textAttributes = mutAttributedString.attributes(at: 1, effectiveRange: &textRange) + + // "*" + XCTAssertEqual(numberRange.location, 0, "Incorrect list formatting") + XCTAssertEqual(numberRange.length, 1, "Incorrect list formatting") + XCTAssertEqual(numberAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect list formatting") + XCTAssertEqual(numberAttributes[.foregroundColor] as! UIColor, colors.orangeForegroundColor, "Incorrect list formatting") + + // " Testing" + XCTAssertEqual(textRange.location, 1, "Incorrect list formatting") + XCTAssertEqual(textRange.length, 8, "Incorrect list formatting") + XCTAssertEqual(textAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect list formatting") + XCTAssertEqual(textAttributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect list formatting") + } + + func testListMultipleBulletNoSpace() { + let string = "***Testing" + let mutAttributedString = NSMutableAttributedString(string: string) + + for formatter in formatters { + formatter.addSyntaxHighlighting(to: mutAttributedString, in: NSRange(location: 0, length: string.count)) + } + + var bulletRange = NSRange(location: 0, length: 0) + let bulletAttributes = mutAttributedString.attributes(at: 0, effectiveRange: &bulletRange) + + var textRange = NSRange(location: 0, length: 0) + let textAttributes = mutAttributedString.attributes(at: 3, effectiveRange: &textRange) + + // "*" + XCTAssertEqual(bulletRange.location, 0, "Incorrect list formatting") + XCTAssertEqual(bulletRange.length, 3, "Incorrect list formatting") + XCTAssertEqual(bulletAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect list formatting") + XCTAssertEqual(bulletAttributes[.foregroundColor] as! UIColor, colors.orangeForegroundColor, "Incorrect list formatting") + + // " Testing" + XCTAssertEqual(textRange.location, 3, "Incorrect list formatting") + XCTAssertEqual(textRange.length, 7, "Incorrect list formatting") + XCTAssertEqual(textAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect list formatting") + XCTAssertEqual(textAttributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect list formatting") + } + + func testListMultipleNumberNoSpace() { + let string = "###Testing" + let mutAttributedString = NSMutableAttributedString(string: string) + + for formatter in formatters { + formatter.addSyntaxHighlighting(to: mutAttributedString, in: NSRange(location: 0, length: string.count)) + } + + var numberRange = NSRange(location: 0, length: 0) + let numberAttributes = mutAttributedString.attributes(at: 0, effectiveRange: &numberRange) + + var textRange = NSRange(location: 0, length: 0) + let textAttributes = mutAttributedString.attributes(at: 3, effectiveRange: &textRange) + + // "*" + XCTAssertEqual(numberRange.location, 0, "Incorrect list formatting") + XCTAssertEqual(numberRange.length, 3, "Incorrect list formatting") + XCTAssertEqual(numberAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect list formatting") + XCTAssertEqual(numberAttributes[.foregroundColor] as! UIColor, colors.orangeForegroundColor, "Incorrect list formatting") + + // " Testing" + XCTAssertEqual(textRange.location, 3, "Incorrect list formatting") + XCTAssertEqual(textRange.length, 7, "Incorrect list formatting") + XCTAssertEqual(textAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect list formatting") + XCTAssertEqual(textAttributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect list formatting") + } } From 770c0a48a2ee641232ce1e92dd8c7a3785f06856 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Wed, 13 Dec 2023 14:20:55 -0600 Subject: [PATCH 23/35] Allow formatter to detect if range contains custom list keys --- .../WKSourceEditorFormatterList.h | 5 +- .../WKSourceEditorFormatterList.m | 151 ++++++++++++++---- 2 files changed, 125 insertions(+), 31 deletions(-) diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.h b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.h index 8047362f35e..a42dba04e1f 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.h +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.h @@ -3,7 +3,10 @@ NS_ASSUME_NONNULL_BEGIN @interface WKSourceEditorFormatterList : WKSourceEditorFormatter - +- (BOOL)attributedString:(NSMutableAttributedString *)attributedString isBulletSingleInRange:(NSRange)range; +- (BOOL)attributedString:(NSMutableAttributedString *)attributedString isBulletMultipleInRange:(NSRange)range; +- (BOOL)attributedString:(NSMutableAttributedString *)attributedString isNumberSingleInRange:(NSRange)range; +- (BOOL)attributedString:(NSMutableAttributedString *)attributedString isNumberMultipleInRange:(NSRange)range; @end NS_ASSUME_NONNULL_END diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.m index 334c0a2e142..39edaa20598 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.m @@ -5,13 +5,27 @@ @interface WKSourceEditorFormatterList () @property (nonatomic, strong) NSDictionary *orangeAttributes; -@property (nonatomic, strong) NSRegularExpression *bulletRegex; -@property (nonatomic, strong) NSRegularExpression *numberRegex; +@property (nonatomic, strong) NSDictionary *bulletSingleContentAttributes; +@property (nonatomic, strong) NSDictionary *bulletMultipleContentAttributes; +@property (nonatomic, strong) NSDictionary *numberSingleContentAttributes; +@property (nonatomic, strong) NSDictionary *numberMultipleContentAttributes; + +@property (nonatomic, strong) NSRegularExpression *bulletSingleRegex; +@property (nonatomic, strong) NSRegularExpression *bulletMultipleRegex; +@property (nonatomic, strong) NSRegularExpression *numberSingleRegex; +@property (nonatomic, strong) NSRegularExpression *numberMultipleRegex; @end @implementation WKSourceEditorFormatterList +#pragma mark - Custom Attributed String Keys + +NSString * const WKSourceEditorCustomKeyContentBulletSingle = @"WKSourceEditorCustomKeyContentBulletSingle"; +NSString * const WKSourceEditorCustomKeyContentBulletMultiple = @"WKSourceEditorCustomKeyContentBulletMultiple"; +NSString * const WKSourceEditorCustomKeyContentNumberSingle = @"WKSourceEditorCustomKeyContentNumberSingle"; +NSString * const WKSourceEditorCustomKeyContentNumberMultiple = @"WKSourceEditorCustomKeyContentNumberMultiple"; + #pragma mark - Overrides - (instancetype)initWithColors:(WKSourceEditorColors *)colors fonts:(WKSourceEditorFonts *)fonts { @@ -22,37 +36,41 @@ - (instancetype)initWithColors:(WKSourceEditorColors *)colors fonts:(WKSourceEdi WKSourceEditorCustomKeyColorOrange: [NSNumber numberWithBool:YES] }; - _bulletRegex = [[NSRegularExpression alloc] initWithPattern:@"^(\\*+)(.*)$" options:NSRegularExpressionAnchorsMatchLines error:nil]; - _numberRegex = [[NSRegularExpression alloc] initWithPattern:@"^(#+)(.*)$" options:NSRegularExpressionAnchorsMatchLines error:nil]; + _bulletSingleContentAttributes = @{ + WKSourceEditorCustomKeyContentBulletSingle: [NSNumber numberWithBool:YES] + }; + + _bulletMultipleContentAttributes = @{ + WKSourceEditorCustomKeyContentBulletMultiple: [NSNumber numberWithBool:YES] + }; + + _numberSingleContentAttributes = @{ + WKSourceEditorCustomKeyContentNumberSingle: [NSNumber numberWithBool:YES] + }; + + _numberMultipleContentAttributes = @{ + WKSourceEditorCustomKeyContentNumberMultiple: [NSNumber numberWithBool:YES] + }; + + _bulletSingleRegex = [[NSRegularExpression alloc] initWithPattern:@"^(\\*{1})(.*)$" options:NSRegularExpressionAnchorsMatchLines error:nil]; + _bulletMultipleRegex = [[NSRegularExpression alloc] initWithPattern:@"^(\\*{2,})(.*)$" options:NSRegularExpressionAnchorsMatchLines error:nil]; + _numberSingleRegex = [[NSRegularExpression alloc] initWithPattern:@"^(#{1})(.*)$" options:NSRegularExpressionAnchorsMatchLines error:nil]; + _numberMultipleRegex = [[NSRegularExpression alloc] initWithPattern:@"^(#{2,})(.*)$" options:NSRegularExpressionAnchorsMatchLines error:nil]; } return self; } - (void)addSyntaxHighlightingToAttributedString:(nonnull NSMutableAttributedString *)attributedString inRange:(NSRange)range { - - [self.bulletRegex enumerateMatchesInString:attributedString.string - options:0 - range:range - usingBlock:^(NSTextCheckingResult *_Nullable result, NSMatchingFlags flags, BOOL *_Nonnull stop) { - NSRange fullMatch = [result rangeAtIndex:0]; - NSRange bulletRange = [result rangeAtIndex:1]; - - if (bulletRange.location != NSNotFound) { - [attributedString addAttributes:self.orangeAttributes range:bulletRange]; - } - }]; - [self.numberRegex enumerateMatchesInString:attributedString.string - options:0 - range:range - usingBlock:^(NSTextCheckingResult *_Nullable result, NSMatchingFlags flags, BOOL *_Nonnull stop) { - NSRange fullMatch = [result rangeAtIndex:0]; - NSRange numberRange = [result rangeAtIndex:1]; - - if (numberRange.location != NSNotFound) { - [attributedString addAttributes:self.orangeAttributes range:numberRange]; - } - }]; + [attributedString removeAttribute:WKSourceEditorCustomKeyContentBulletSingle range:range]; + [attributedString removeAttribute:WKSourceEditorCustomKeyContentBulletMultiple range:range]; + [attributedString removeAttribute:WKSourceEditorCustomKeyContentNumberSingle range:range]; + [attributedString removeAttribute:WKSourceEditorCustomKeyContentNumberMultiple range:range]; + + [self enumerateAndHighlightAttributedString:attributedString range:range regex:self.bulletSingleRegex contentAttributes:self.bulletSingleContentAttributes]; + [self enumerateAndHighlightAttributedString:attributedString range:range regex:self.bulletMultipleRegex contentAttributes:self.bulletMultipleContentAttributes]; + [self enumerateAndHighlightAttributedString:attributedString range:range regex:self.numberSingleRegex contentAttributes:self.numberSingleContentAttributes]; + [self enumerateAndHighlightAttributedString:attributedString range:range regex:self.numberMultipleRegex contentAttributes:self.numberMultipleContentAttributes]; } - (void)updateColors:(WKSourceEditorColors *)colors inAttributedString:(NSMutableAttributedString *)attributedString inRange:(NSRange)range { @@ -64,9 +82,9 @@ - (void)updateColors:(WKSourceEditorColors *)colors inAttributedString:(NSMutabl // Then update entire attributed string orange color [attributedString enumerateAttribute:WKSourceEditorCustomKeyColorOrange - inRange:range - options:nil - usingBlock:^(id value, NSRange localRange, BOOL *stop) { + inRange:range + options:nil + usingBlock:^(id value, NSRange localRange, BOOL *stop) { if ([value isKindOfClass: [NSNumber class]]) { NSNumber *numValue = (NSNumber *)value; if ([numValue boolValue] == YES) { @@ -80,4 +98,77 @@ - (void)updateFonts:(WKSourceEditorFonts *)fonts inAttributedString:(NSMutableAt // No special font handling needed for lists } +#pragma mark - Public + +- (BOOL)attributedString:(NSMutableAttributedString *)attributedString isBulletSingleInRange:(NSRange)range { + return [self attributedString:attributedString isContentKey:WKSourceEditorCustomKeyContentBulletSingle inRange:range]; +} + +- (BOOL)attributedString:(NSMutableAttributedString *)attributedString isBulletMultipleInRange:(NSRange)range { + return [self attributedString:attributedString isContentKey:WKSourceEditorCustomKeyContentBulletMultiple inRange:range]; +} + +- (BOOL)attributedString:(NSMutableAttributedString *)attributedString isNumberSingleInRange:(NSRange)range { + return [self attributedString:attributedString isContentKey:WKSourceEditorCustomKeyContentNumberSingle inRange:range]; +} + +- (BOOL)attributedString:(NSMutableAttributedString *)attributedString isNumberMultipleInRange:(NSRange)range { + return [self attributedString:attributedString isContentKey:WKSourceEditorCustomKeyContentNumberMultiple inRange:range]; +} + +#pragma mark - Private + +- (void)enumerateAndHighlightAttributedString: (nonnull NSMutableAttributedString *)attributedString range:(NSRange)range regex:(NSRegularExpression *)regex contentAttributes:(NSDictionary *)contentAttributes { + [regex enumerateMatchesInString:attributedString.string + options:0 + range:range + usingBlock:^(NSTextCheckingResult *_Nullable result, NSMatchingFlags flags, BOOL *_Nonnull stop) { + NSRange fullMatch = [result rangeAtIndex:0]; + NSRange orangeRange = [result rangeAtIndex:1]; + + if (orangeRange.location != NSNotFound) { + [attributedString addAttributes:self.orangeAttributes range:orangeRange]; + } + + if (fullMatch.location != NSNotFound) { + [attributedString addAttributes:contentAttributes range:fullMatch]; + } + }]; +} + +- (BOOL)attributedString:(NSMutableAttributedString *)attributedString isContentKey:(NSString *)contentKey inRange:(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[contentKey] != nil) { + isContentKey = YES; + } + } else if (attributedString.length == range.location) { + // Edge case, check previous character in case we're at the end of the line and list isn't detected + if ((attributedString.length > range.location - 1)) { + NSDictionary *attrs = [attributedString attributesAtIndex:range.location-1 effectiveRange:nil]; + + if (attrs[contentKey] != nil) { + isContentKey = YES; + } + } + } + + } else { + [attributedString enumerateAttributesInRange:range options:nil usingBlock:^(NSDictionary * _Nonnull attrs, NSRange loopRange, BOOL * _Nonnull stop) { + if ((attrs[contentKey] != nil) && + (loopRange.location == range.location && loopRange.length == range.length)) { + isContentKey = YES; + stop = YES; + } + }]; + } + + return isContentKey; +} + @end From d93d54fc5b8b0a657551b24cb39eac9909dc1ee5 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Wed, 13 Dec 2023 14:21:25 -0600 Subject: [PATCH 24/35] Add list properties to WKSourceEditorSelectionState --- .../WKSourceEditorTextFrameworkMediator.swift | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift index 25b16ade1d0..25cbcd29459 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift @@ -17,11 +17,19 @@ fileprivate var needsTextKit2: Bool { let isBold: Bool let isItalics: Bool let isHorizontalTemplate: Bool + let isBulletSingleList: Bool + let isBulletMultipleList: Bool + let isNumberSingleList: Bool + let isNumberMultipleList: Bool - init(isBold: Bool, isItalics: Bool, isHorizontalTemplate: Bool) { + init(isBold: Bool, isItalics: Bool, isHorizontalTemplate: Bool, isBulletSingleList: Bool, isBulletMultipleList: Bool, isNumberSingleList: Bool, isNumberMultipleList: Bool) { self.isBold = isBold self.isItalics = isItalics self.isHorizontalTemplate = isHorizontalTemplate + self.isBulletSingleList = isBulletSingleList + self.isBulletMultipleList = isBulletMultipleList + self.isNumberSingleList = isNumberSingleList + self.isNumberMultipleList = isNumberMultipleList } } @@ -151,24 +159,32 @@ final class WKSourceEditorTextFrameworkMediator: NSObject { if needsTextKit2 { guard let textKit2Data = textkit2SelectionData(selectedDocumentRange: selectedDocumentRange) else { - return WKSourceEditorSelectionState(isBold: false, isItalics: false, isHorizontalTemplate: false) + return WKSourceEditorSelectionState(isBold: false, isItalics: false, isHorizontalTemplate: false, isBulletSingleList: false, isBulletMultipleList: false, isNumberSingleList: false, isNumberMultipleList: 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 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 + let isNumberMultipleList = listFormatter?.attributedString(textKit2Data.paragraphAttributedString, isNumberMultipleIn: textKit2Data.paragraphSelectedRange) ?? false - return WKSourceEditorSelectionState(isBold: isBold, isItalics: isItalics, isHorizontalTemplate: isHorizontalTemplate) + return WKSourceEditorSelectionState(isBold: isBold, isItalics: isItalics, isHorizontalTemplate: isHorizontalTemplate, isBulletSingleList: isBulletSingleList, isBulletMultipleList: isBulletMultipleList, isNumberSingleList: isNumberSingleList, isNumberMultipleList: isNumberMultipleList) } else { guard let textKit1Storage else { - return WKSourceEditorSelectionState(isBold: false, isItalics: false, isHorizontalTemplate: false) + return WKSourceEditorSelectionState(isBold: false, isItalics: false, isHorizontalTemplate: false, isBulletSingleList: false, isBulletMultipleList: false, isNumberSingleList: false, isNumberMultipleList: 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 isBulletSingleList = listFormatter?.attributedString(textKit1Storage, isBulletSingleIn: selectedDocumentRange) ?? false + let isBulletMultipleList = listFormatter?.attributedString(textKit1Storage, isBulletMultipleIn: selectedDocumentRange) ?? false + let isNumberSingleList = listFormatter?.attributedString(textKit1Storage, isNumberSingleIn: selectedDocumentRange) ?? false + let isNumberMultipleList = listFormatter?.attributedString(textKit1Storage, isNumberMultipleIn: selectedDocumentRange) ?? false - return WKSourceEditorSelectionState(isBold: isBold, isItalics: isItalics, isHorizontalTemplate: isHorizontalTemplate) + return WKSourceEditorSelectionState(isBold: isBold, isItalics: isItalics, isHorizontalTemplate: isHorizontalTemplate, isBulletSingleList: isBulletSingleList, isBulletMultipleList: isBulletMultipleList, isNumberSingleList: isNumberSingleList, isNumberMultipleList: isNumberMultipleList) } } From 266d7b856edc55ef2664673f12f8cb494dc17bb6 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Wed, 13 Dec 2023 14:21:39 -0600 Subject: [PATCH 25/35] Add delegate callback --- .../WKEditorInputViewController.swift | 4 ++++ .../WKSourceEditorViewController.swift | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorInputViewController.swift b/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorInputViewController.swift index a2ae309b88f..71ce26020db 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorInputViewController.swift +++ b/Components/Sources/Components/Components/Editors/Common Views/Input Views/WKEditorInputViewController.swift @@ -6,6 +6,10 @@ protocol WKEditorInputViewDelegate: AnyObject { func didTapBold(isSelected: Bool) func didTapItalics(isSelected: Bool) func didTapTemplate(isSelected: Bool) + func didTapBulletList(isSelected: Bool) + func didTapNumberList(isSelected: Bool) + func didTapIncreaseIndent() + func didTapDecreaseIndent() } class WKEditorInputViewController: WKComponentViewController { diff --git a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift index 5a5d7447287..5c5646d02b2 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift @@ -344,6 +344,7 @@ extension WKSourceEditorViewController: WKEditorToolbarHighlightViewDelegate { // MARK: - WKEditorInputViewDelegate extension WKSourceEditorViewController: WKEditorInputViewDelegate { + func didTapBold(isSelected: Bool) { let action: WKSourceEditorFormatterButtonAction = isSelected ? .remove : .add textFrameworkMediator.boldItalicsFormatter?.toggleBoldFormatting(action: action, in: textView) @@ -359,6 +360,21 @@ extension WKSourceEditorViewController: WKEditorInputViewDelegate { textFrameworkMediator.templateFormatter?.toggleTemplateFormatting(action: action, in: textView) } + func didTapBulletList(isSelected: Bool) { + + } + + func didTapNumberList(isSelected: Bool) { + } + + func didTapIncreaseIndent() { + + } + + func didTapDecreaseIndent() { + + } + func didTapClose() { inputViewType = nil let isRangeSelected = textView.selectedRange.length > 0 From 1eb7bac1b6b61740e019b098e50ae373af17d0ae Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Wed, 13 Dec 2023 14:22:10 -0600 Subject: [PATCH 26/35] Apply selection and enabled states to keyboard views --- .../Base/WKEditorToolbarButton.swift | 11 +++++++ .../WKEditorToolbarExpandingView.swift | 20 +++++++++++++ .../WKEditorToolbarGroupedView.swift | 29 +++++++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/Components/Sources/Components/Components/Editors/Common Views/Base/WKEditorToolbarButton.swift b/Components/Sources/Components/Components/Editors/Common Views/Base/WKEditorToolbarButton.swift index 59b10a283f7..d96c45e9fcf 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Base/WKEditorToolbarButton.swift +++ b/Components/Sources/Components/Components/Editors/Common Views/Base/WKEditorToolbarButton.swift @@ -68,6 +68,17 @@ class WKEditorToolbarButton: WKComponentView { } } + var isEnabled: Bool { + get { + return button.isEnabled + } + set { + button.isEnabled = newValue + updateColors() + accessibilityTraits = newValue ? [.button, .selected] : [.button, .notEnabled] + } + } + func setImage(_ image: UIImage?, for state: UIControl.State) { button.setImage(image, for: state) } 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 bbf183ad600..bbfdce1aa9c 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 @@ -119,10 +119,12 @@ class WKEditorToolbarExpandingView: WKEditorToolbarView { decreaseIndentionButton.setImage(WKSFSymbolIcon.for(symbol: .decreaseIndent), for: .normal) decreaseIndentionButton.addTarget(self, action: #selector(tappedDecreaseIndentation), for: .touchUpInside) decreaseIndentionButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current.accessibilityLabelButtonDecreaseIndent + decreaseIndentionButton.isEnabled = false increaseIndentionButton.setImage(WKSFSymbolIcon.for(symbol: .increaseIndent), for: .normal) increaseIndentionButton.addTarget(self, action: #selector(tappedIncreaseIndentation), for: .touchUpInside) increaseIndentionButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current.accessibilityLabelButtonInceaseIndent + increaseIndentionButton.isEnabled = false cursorUpButton.setImage(WKIcon.chevronUp, for: .normal) cursorUpButton.addTarget(self, action: #selector(tappedCursorUp), for: .touchUpInside) @@ -150,6 +152,24 @@ class WKEditorToolbarExpandingView: WKEditorToolbarView { } templateButton.isSelected = selectionState.isHorizontalTemplate + + unorderedListButton.isSelected = selectionState.isBulletSingleList || selectionState.isBulletMultipleList + orderedListButton.isSelected = selectionState.isNumberSingleList || selectionState.isNumberMultipleList + + decreaseIndentionButton.isEnabled = false + if selectionState.isBulletMultipleList || selectionState.isNumberMultipleList { + decreaseIndentionButton.isEnabled = true + } + + if selectionState.isBulletSingleList || + selectionState.isBulletMultipleList || + selectionState.isNumberSingleList || + selectionState.isNumberMultipleList { + increaseIndentionButton.isEnabled = true + } else { + increaseIndentionButton.isEnabled = false + } + cursorRightButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current.accessibilityLabelButtonCursorRight } diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Grouped Toolbar Table Item/WKEditorToolbarGroupedView.swift b/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Grouped Toolbar Table Item/WKEditorToolbarGroupedView.swift index 3f519383556..f6683be6b36 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Grouped Toolbar Table Item/WKEditorToolbarGroupedView.swift +++ b/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Grouped Toolbar Table Item/WKEditorToolbarGroupedView.swift @@ -29,10 +29,12 @@ class WKEditorToolbarGroupedView: WKEditorToolbarView { decreaseIndentButton.setImage(WKSFSymbolIcon.for(symbol: .decreaseIndent), for: .normal) decreaseIndentButton.addTarget(self, action: #selector(tappedDecreaseIndent), for: .touchUpInside) decreaseIndentButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current?.accessibilityLabelButtonDecreaseIndent + decreaseIndentButton.isEnabled = false increaseIndentButton.setImage(WKSFSymbolIcon.for(symbol: .increaseIndent), for: .normal) increaseIndentButton.addTarget(self, action: #selector(tappedIncreaseIndent), for: .touchUpInside) increaseIndentButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current?.accessibilityLabelButtonInceaseIndent + increaseIndentButton.isEnabled = false superscriptButton.setImage(WKSFSymbolIcon.for(symbol: .textFormatSuperscript), for: .normal) superscriptButton.addTarget(self, action: #selector(tappedSuperscript), for: .touchUpInside) @@ -49,6 +51,33 @@ class WKEditorToolbarGroupedView: WKEditorToolbarView { strikethroughButton.setImage(WKSFSymbolIcon.for(symbol: .strikethrough), for: .normal) strikethroughButton.addTarget(self, action: #selector(tappedStrikethrough), for: .touchUpInside) strikethroughButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current?.accessibilityLabelButtonStrikethrough + + NotificationCenter.default.addObserver(self, selector: #selector(updateButtonSelectionState(_:)), name: Notification.WKSourceEditorSelectionState, object: nil) + } + + // MARK: - Notifications + + @objc private func updateButtonSelectionState(_ notification: NSNotification) { + guard let selectionState = notification.userInfo?[Notification.WKSourceEditorSelectionStateKey] as? WKSourceEditorSelectionState else { + return + } + + unorderedListButton.isSelected = selectionState.isBulletSingleList || selectionState.isBulletMultipleList + orderedListButton.isSelected = selectionState.isNumberSingleList || selectionState.isNumberMultipleList + + decreaseIndentButton.isEnabled = false + if selectionState.isBulletMultipleList || selectionState.isNumberMultipleList { + decreaseIndentButton.isEnabled = true + } + + if selectionState.isBulletSingleList || + selectionState.isBulletMultipleList || + selectionState.isNumberSingleList || + selectionState.isNumberMultipleList { + increaseIndentButton.isEnabled = true + } else { + increaseIndentButton.isEnabled = false + } } // MARK: - Button Actions From 05af8c92debd8642f45b13d7cda8211e48f4985b Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Wed, 13 Dec 2023 16:40:29 -0600 Subject: [PATCH 27/35] Fix a couple of selection bugs in list formatter --- .../Sources/ComponentsObjC/WKSourceEditorFormatterList.m | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.m index 39edaa20598..1abd15820d9 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.m @@ -125,13 +125,14 @@ - (void)enumerateAndHighlightAttributedString: (nonnull NSMutableAttributedStrin usingBlock:^(NSTextCheckingResult *_Nullable result, NSMatchingFlags flags, BOOL *_Nonnull stop) { NSRange fullMatch = [result rangeAtIndex:0]; NSRange orangeRange = [result rangeAtIndex:1]; + NSRange contentRange = [result rangeAtIndex:2]; if (orangeRange.location != NSNotFound) { [attributedString addAttributes:self.orangeAttributes range:orangeRange]; } - if (fullMatch.location != NSNotFound) { - [attributedString addAttributes:contentAttributes range:fullMatch]; + if (contentRange.location != NSNotFound) { + [attributedString addAttributes:contentAttributes range:contentRange]; } }]; } @@ -147,7 +148,7 @@ - (BOOL)attributedString:(NSMutableAttributedString *)attributedString isContent if (attrs[contentKey] != nil) { isContentKey = YES; } - } else if (attributedString.length == range.location) { + // Edge case, check previous character in case we're at the end of the line and list isn't detected if ((attributedString.length > range.location - 1)) { NSDictionary *attrs = [attributedString attributesAtIndex:range.location-1 effectiveRange:nil]; From 7cc2727ee2e11fa489fd6ac17b84e64bb612b553 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Wed, 13 Dec 2023 16:42:22 -0600 Subject: [PATCH 28/35] Add more delegate plumbing --- .../WKEditorToolbarExpandingView.swift | 9 ++++++++ .../WKEditorToolbarGroupedCell.swift | 13 ++++++++++-- .../WKEditorToolbarGroupedView.swift | 7 +++++++ .../WKEditorInputMainViewController.swift | 5 +++++ .../WKSourceEditorViewController.swift | 21 +++++++++++++++++-- 5 files changed, 51 insertions(+), 4 deletions(-) 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 bbfdce1aa9c..6cf9f66bfb3 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 @@ -5,6 +5,10 @@ protocol WKEditorToolbarExpandingViewDelegate: AnyObject { func toolbarExpandingViewDidTapFormatText(toolbarView: WKEditorToolbarExpandingView) func toolbarExpandingViewDidTapFormatHeading(toolbarView: WKEditorToolbarExpandingView) func toolbarExpandingViewDidTapTemplate(toolbarView: WKEditorToolbarExpandingView, isSelected: Bool) + func toolbarExpandingViewDidTapUnorderedList(toolbarView: WKEditorToolbarExpandingView, isSelected: Bool) + func toolbarExpandingViewDidTapOrderedList(toolbarView: WKEditorToolbarExpandingView, isSelected: Bool) + func toolbarExpandingViewDidTapIncreaseIndent(toolbarView: WKEditorToolbarExpandingView) + func toolbarExpandingViewDidTapDecreaseIndent(toolbarView: WKEditorToolbarExpandingView) } class WKEditorToolbarExpandingView: WKEditorToolbarView { @@ -154,6 +158,7 @@ class WKEditorToolbarExpandingView: WKEditorToolbarView { templateButton.isSelected = selectionState.isHorizontalTemplate unorderedListButton.isSelected = selectionState.isBulletSingleList || selectionState.isBulletMultipleList + orderedListButton.isSelected = selectionState.isNumberSingleList || selectionState.isNumberMultipleList decreaseIndentionButton.isEnabled = false @@ -227,15 +232,19 @@ class WKEditorToolbarExpandingView: WKEditorToolbarView { } @objc private func tappedUnorderedList() { + delegate?.toolbarExpandingViewDidTapUnorderedList(toolbarView: self, isSelected: unorderedListButton.isSelected) } @objc private func tappedOrderedList() { + delegate?.toolbarExpandingViewDidTapOrderedList(toolbarView: self, isSelected: orderedListButton.isSelected) } @objc private func tappedDecreaseIndentation() { + delegate?.toolbarExpandingViewDidTapDecreaseIndent(toolbarView: self) } @objc private func tappedIncreaseIndentation() { + delegate?.toolbarExpandingViewDidTapIncreaseIndent(toolbarView: self) } @objc private func tappedCursorUp() { diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Grouped Toolbar Table Item/WKEditorToolbarGroupedCell.swift b/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Grouped Toolbar Table Item/WKEditorToolbarGroupedCell.swift index ada9819fa06..5f48bbd0aa6 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Grouped Toolbar Table Item/WKEditorToolbarGroupedCell.swift +++ b/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Grouped Toolbar Table Item/WKEditorToolbarGroupedCell.swift @@ -5,12 +5,21 @@ class WKEditorToolbarGroupedCell: UITableViewCell { // MARK: - Properties - private lazy var componentView: UIView = { - let view = UINib(nibName: String(describing: WKEditorToolbarGroupedView.self), bundle: Bundle.module).instantiate(withOwner: nil).first as! UIView + private lazy var componentView: WKEditorToolbarGroupedView = { + let view = UINib(nibName: String(describing: WKEditorToolbarGroupedView.self), bundle: Bundle.module).instantiate(withOwner: nil).first as! WKEditorToolbarGroupedView return view }() + var delegate: WKEditorInputViewDelegate? { + get { + return componentView.delegate + } + set { + componentView.delegate = newValue + } + } + // MARK: - Lifecycle override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Grouped Toolbar Table Item/WKEditorToolbarGroupedView.swift b/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Grouped Toolbar Table Item/WKEditorToolbarGroupedView.swift index f6683be6b36..36920491889 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Grouped Toolbar Table Item/WKEditorToolbarGroupedView.swift +++ b/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Grouped Toolbar Table Item/WKEditorToolbarGroupedView.swift @@ -13,6 +13,8 @@ class WKEditorToolbarGroupedView: WKEditorToolbarView { @IBOutlet private weak var underlineButton: WKEditorToolbarButton! @IBOutlet private weak var strikethroughButton: WKEditorToolbarButton! + weak var delegate: WKEditorInputViewDelegate? + // MARK: - Lifecycle override func awakeFromNib() { @@ -63,6 +65,7 @@ class WKEditorToolbarGroupedView: WKEditorToolbarView { } unorderedListButton.isSelected = selectionState.isBulletSingleList || selectionState.isBulletMultipleList + orderedListButton.isSelected = selectionState.isNumberSingleList || selectionState.isNumberMultipleList decreaseIndentButton.isEnabled = false @@ -83,15 +86,19 @@ class WKEditorToolbarGroupedView: WKEditorToolbarView { // MARK: - Button Actions @objc private func tappedIncreaseIndent() { + delegate?.didTapIncreaseIndent() } @objc private func tappedDecreaseIndent() { + delegate?.didTapDecreaseIndent() } @objc private func tappedUnorderedList() { + delegate?.didTapBulletList(isSelected: unorderedListButton.isSelected) } @objc private func tappedOrderedList() { + delegate?.didTapNumberList(isSelected: orderedListButton.isSelected) } @objc private func tappedSuperscript() { diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/WKEditorInputMainViewController.swift b/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/WKEditorInputMainViewController.swift index bc0c898f25a..5673f3df806 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/WKEditorInputMainViewController.swift +++ b/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/WKEditorInputMainViewController.swift @@ -102,6 +102,11 @@ extension WKEditorInputMainViewController: UITableViewDataSource { cell.selectionStyle = .none case 1: cell = tableView.dequeueReusableCell(withIdentifier: groupedReuseIdentifier, for: indexPath) + + if let groupedCell = cell as? WKEditorToolbarGroupedCell { + groupedCell.delegate = delegate + } + cell.selectionStyle = .none case 2: diff --git a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift index 5c5646d02b2..b49853e52a2 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift @@ -310,6 +310,22 @@ extension WKSourceEditorViewController: WKEditorToolbarExpandingViewDelegate { let action: WKSourceEditorFormatterButtonAction = isSelected ? .remove : .add textFrameworkMediator.templateFormatter?.toggleTemplateFormatting(action: action, in: textView) } + + func toolbarExpandingViewDidTapUnorderedList(toolbarView: WKEditorToolbarExpandingView, isSelected: Bool) { + + } + + func toolbarExpandingViewDidTapOrderedList(toolbarView: WKEditorToolbarExpandingView, isSelected: Bool) { + + } + + func toolbarExpandingViewDidTapIncreaseIndent(toolbarView: WKEditorToolbarExpandingView) { + + } + + func toolbarExpandingViewDidTapDecreaseIndent(toolbarView: WKEditorToolbarExpandingView) { + + } } // MARK: - WKEditorToolbarHighlightViewDelegate @@ -365,14 +381,15 @@ extension WKSourceEditorViewController: WKEditorInputViewDelegate { } func didTapNumberList(isSelected: Bool) { + } func didTapIncreaseIndent() { - + } func didTapDecreaseIndent() { - + } func didTapClose() { From c0613273438726677677a970b9c8bbc328ca9417 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Wed, 13 Dec 2023 16:43:27 -0600 Subject: [PATCH 29/35] Create list formatter extension to toggle formatting --- ...rceEditorFormatterList+ButtonActions.swift | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterList+ButtonActions.swift diff --git a/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterList+ButtonActions.swift b/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterList+ButtonActions.swift new file mode 100644 index 00000000000..66ed0f55ca4 --- /dev/null +++ b/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterList+ButtonActions.swift @@ -0,0 +1,120 @@ +import Foundation +import ComponentsObjC + +extension WKSourceEditorFormatterList { + func toggleListBullet(action: WKSourceEditorFormatterButtonAction, in textView: UITextView) { + toggleListItem(action: action, formattingCharacter: "*", in: textView) + } + + func toggleListNumber(action: WKSourceEditorFormatterButtonAction, in textView: UITextView) { + toggleListItem(action: action, formattingCharacter: "#", in: textView) + } + + func tappedIncreaseIndent(currentSelectionState: WKSourceEditorSelectionState, textView: UITextView) { + + guard currentSelectionState.isBulletSingleList || + currentSelectionState.isBulletMultipleList || + currentSelectionState.isNumberSingleList || + currentSelectionState.isNumberMultipleList else { + assertionFailure("Increase / Decrease indent buttons should have been disabled.") + return + } + + let formattingCharacter = (currentSelectionState.isBulletSingleList || + currentSelectionState.isBulletMultipleList) ? "*" : "#" + + let nsString = textView.attributedText.string as NSString + let lineRange = nsString.lineRange(for: textView.selectedRange) + + textView.textStorage.insert(NSAttributedString(string: String(formattingCharacter)), at: lineRange.location) + + // reset cursor so it doesn't move + if let selectedRange = textView.selectedTextRange { + if let newStart = textView.position(from: selectedRange.start, offset: 1), + let newEnd = textView.position(from: selectedRange.end, offset: 1) { + textView.selectedTextRange = textView.textRange(from: newStart, to: newEnd) + } + } + } + + func tappedDecreaseIndent(currentSelectionState: WKSourceEditorSelectionState, textView: UITextView) { + + guard currentSelectionState.isBulletSingleList || + currentSelectionState.isBulletMultipleList || + currentSelectionState.isNumberSingleList || + currentSelectionState.isNumberMultipleList else { + assertionFailure("Increase / Decrease indent buttons should have been disabled.") + return + } + + let nsString = textView.attributedText.string as NSString + let lineRange = nsString.lineRange(for: textView.selectedRange) + + guard textView.textStorage.length > lineRange.location else { + return + } + + textView.textStorage.replaceCharacters(in: NSRange(location: lineRange.location, length: 1), with: "") + + // reset cursor so it doesn't move + if let selectedRange = textView.selectedTextRange { + if let newStart = textView.position(from: selectedRange.start, offset: -1), + let newEnd = textView.position(from: selectedRange.end, offset: -1) { + textView.selectedTextRange = textView.textRange(from: newStart, to: newEnd) + } + } + } + + private func toggleListItem(action: WKSourceEditorFormatterButtonAction, formattingCharacter: Character, in textView: UITextView) { + let nsString = textView.attributedText.string as NSString + let lineRange = nsString.lineRange(for: textView.selectedRange) + switch action { + case .add: + + var numBullets = 0 + for char in textView.textStorage.attributedSubstring(from: lineRange).string { + if char == formattingCharacter { + numBullets += 1 + } + } + + let insertString = numBullets == 0 ? "\(String(formattingCharacter)) " : String(formattingCharacter) + textView.textStorage.insert(NSAttributedString(string: insertString), at: lineRange.location) + + // reset cursor so it doesn't move + if let selectedRange = textView.selectedTextRange { + if let newStart = textView.position(from: selectedRange.start, offset: insertString.count), + let newEnd = textView.position(from: selectedRange.end, offset: insertString.count) { + textView.selectedTextRange = textView.textRange(from: newStart, to: newEnd) + } + } + case .remove: + + var numBullets = 0 + var hasSpace = false + for char in textView.textStorage.attributedSubstring(from: lineRange).string { + if char != formattingCharacter { + if char == " " { + hasSpace = true + } + break + } + + if char == formattingCharacter { + numBullets += 1 + } + } + + let replacementLength = numBullets + (hasSpace ? 1 : 0) + textView.textStorage.replaceCharacters(in: NSRange(location: lineRange.location, length: replacementLength), with: "") + + // reset cursor so it doesn't move + if let selectedRange = textView.selectedTextRange { + if let newStart = textView.position(from: selectedRange.start, offset: -1 * replacementLength), + let newEnd = textView.position(from: selectedRange.end, offset: -1 * replacementLength) { + textView.selectedTextRange = textView.textRange(from: newStart, to: newEnd) + } + } + } + } +} From d114a8a11a3d214f2cc7e42ec2063c3a5290751e Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Wed, 13 Dec 2023 16:43:55 -0600 Subject: [PATCH 30/35] Call list button actions from view controller --- .../WKSourceEditorViewController.swift | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift index b49853e52a2..86a1bce1f96 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift @@ -312,19 +312,21 @@ extension WKSourceEditorViewController: WKEditorToolbarExpandingViewDelegate { } func toolbarExpandingViewDidTapUnorderedList(toolbarView: WKEditorToolbarExpandingView, isSelected: Bool) { - + let action: WKSourceEditorFormatterButtonAction = isSelected ? .remove : .add + textFrameworkMediator.listFormatter?.toggleListBullet(action: action, in: textView) } func toolbarExpandingViewDidTapOrderedList(toolbarView: WKEditorToolbarExpandingView, isSelected: Bool) { - + let action: WKSourceEditorFormatterButtonAction = isSelected ? .remove : .add + textFrameworkMediator.listFormatter?.toggleListNumber(action: action, in: textView) } func toolbarExpandingViewDidTapIncreaseIndent(toolbarView: WKEditorToolbarExpandingView) { - + textFrameworkMediator.listFormatter?.tappedIncreaseIndent(currentSelectionState: selectionState(), textView: textView) } func toolbarExpandingViewDidTapDecreaseIndent(toolbarView: WKEditorToolbarExpandingView) { - + textFrameworkMediator.listFormatter?.tappedDecreaseIndent(currentSelectionState: selectionState(), textView: textView) } } @@ -377,19 +379,21 @@ extension WKSourceEditorViewController: WKEditorInputViewDelegate { } func didTapBulletList(isSelected: Bool) { - + let action: WKSourceEditorFormatterButtonAction = isSelected ? .remove : .add + textFrameworkMediator.listFormatter?.toggleListBullet(action: action, in: textView) } func didTapNumberList(isSelected: Bool) { - + let action: WKSourceEditorFormatterButtonAction = isSelected ? .remove : .add + textFrameworkMediator.listFormatter?.toggleListNumber(action: action, in: textView) } func didTapIncreaseIndent() { - + textFrameworkMediator.listFormatter?.tappedIncreaseIndent(currentSelectionState: selectionState(), textView: textView) } func didTapDecreaseIndent() { - + textFrameworkMediator.listFormatter?.tappedDecreaseIndent(currentSelectionState: selectionState(), textView: textView) } func didTapClose() { From 6260caa7ddd2b7d91957707688953679407d1ad8 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Wed, 13 Dec 2023 16:44:18 -0600 Subject: [PATCH 31/35] Don't allow bullets and numbers to be selected at the same time --- .../Expanding/WKEditorToolbarExpandingView.swift | 2 ++ .../Grouped Toolbar Table Item/WKEditorToolbarGroupedView.swift | 2 ++ 2 files changed, 4 insertions(+) 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 6cf9f66bfb3..900041210cf 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 @@ -158,8 +158,10 @@ class WKEditorToolbarExpandingView: WKEditorToolbarView { templateButton.isSelected = selectionState.isHorizontalTemplate unorderedListButton.isSelected = selectionState.isBulletSingleList || selectionState.isBulletMultipleList + unorderedListButton.isEnabled = !selectionState.isNumberSingleList && !selectionState.isNumberMultipleList orderedListButton.isSelected = selectionState.isNumberSingleList || selectionState.isNumberMultipleList + orderedListButton.isEnabled = !selectionState.isBulletSingleList && !selectionState.isBulletMultipleList decreaseIndentionButton.isEnabled = false if selectionState.isBulletMultipleList || selectionState.isNumberMultipleList { diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Grouped Toolbar Table Item/WKEditorToolbarGroupedView.swift b/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Grouped Toolbar Table Item/WKEditorToolbarGroupedView.swift index 36920491889..764cbb3d5dd 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Grouped Toolbar Table Item/WKEditorToolbarGroupedView.swift +++ b/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Grouped Toolbar Table Item/WKEditorToolbarGroupedView.swift @@ -65,8 +65,10 @@ class WKEditorToolbarGroupedView: WKEditorToolbarView { } unorderedListButton.isSelected = selectionState.isBulletSingleList || selectionState.isBulletMultipleList + unorderedListButton.isEnabled = !selectionState.isNumberSingleList && !selectionState.isNumberMultipleList orderedListButton.isSelected = selectionState.isNumberSingleList || selectionState.isNumberMultipleList + orderedListButton.isEnabled = !selectionState.isBulletSingleList && !selectionState.isBulletMultipleList decreaseIndentButton.isEnabled = false if selectionState.isBulletMultipleList || selectionState.isNumberMultipleList { From 6b1f356841828ca6c4747b0dd08a93000dd2a219 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Wed, 13 Dec 2023 17:41:29 -0600 Subject: [PATCH 32/35] Add tests, fix selection state bug from tests --- .../WKSourceEditorFormatterList.m | 51 ++++++++++++--- ...urceEditorFormatterButtonActionTests.swift | 64 +++++++++++++++++++ ...urceEditorTextFrameworkMediatorTests.swift | 48 ++++++++++++++ 3 files changed, 155 insertions(+), 8 deletions(-) diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.m index 1abd15820d9..de8ef933334 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.m @@ -66,11 +66,9 @@ - (void)addSyntaxHighlightingToAttributedString:(nonnull NSMutableAttributedStri [attributedString removeAttribute:WKSourceEditorCustomKeyContentBulletMultiple range:range]; [attributedString removeAttribute:WKSourceEditorCustomKeyContentNumberSingle range:range]; [attributedString removeAttribute:WKSourceEditorCustomKeyContentNumberMultiple range:range]; - - [self enumerateAndHighlightAttributedString:attributedString range:range regex:self.bulletSingleRegex contentAttributes:self.bulletSingleContentAttributes]; - [self enumerateAndHighlightAttributedString:attributedString range:range regex:self.bulletMultipleRegex contentAttributes:self.bulletMultipleContentAttributes]; - [self enumerateAndHighlightAttributedString:attributedString range:range regex:self.numberSingleRegex contentAttributes:self.numberSingleContentAttributes]; - [self enumerateAndHighlightAttributedString:attributedString range:range regex:self.numberMultipleRegex contentAttributes:self.numberMultipleContentAttributes]; + + [self enumerateAndHighlightAttributedString:attributedString range:range singleRegex:self.bulletSingleRegex multipleRegex:self.bulletMultipleRegex singleContentAttributes:self.bulletSingleContentAttributes singleContentAttributes:self.bulletMultipleContentAttributes]; + [self enumerateAndHighlightAttributedString:attributedString range:range singleRegex:self.numberSingleRegex multipleRegex:self.numberMultipleRegex singleContentAttributes:self.numberSingleContentAttributes singleContentAttributes:self.numberMultipleContentAttributes]; } - (void)updateColors:(WKSourceEditorColors *)colors inAttributedString:(NSMutableAttributedString *)attributedString inRange:(NSRange)range { @@ -118,8 +116,32 @@ - (BOOL)attributedString:(NSMutableAttributedString *)attributedString isNumberM #pragma mark - Private -- (void)enumerateAndHighlightAttributedString: (nonnull NSMutableAttributedString *)attributedString range:(NSRange)range regex:(NSRegularExpression *)regex contentAttributes:(NSDictionary *)contentAttributes { - [regex enumerateMatchesInString:attributedString.string +- (void)enumerateAndHighlightAttributedString: (nonnull NSMutableAttributedString *)attributedString range:(NSRange)range singleRegex:(NSRegularExpression *)singleRegex multipleRegex:(NSRegularExpression *)multipleRegex singleContentAttributes:(NSDictionary *)singleContentAttributes singleContentAttributes:(NSDictionary *)multipleContentAttributes { + + NSMutableArray *multipleRanges = [[NSMutableArray alloc] init]; + + [multipleRegex enumerateMatchesInString:attributedString.string + options:0 + range:range + usingBlock:^(NSTextCheckingResult *_Nullable result, NSMatchingFlags flags, BOOL *_Nonnull stop) { + NSRange fullMatch = [result rangeAtIndex:0]; + NSRange orangeRange = [result rangeAtIndex:1]; + NSRange contentRange = [result rangeAtIndex:2]; + + if (fullMatch.location != NSNotFound) { + [multipleRanges addObject:[NSValue valueWithRange:fullMatch]]; + } + + if (orangeRange.location != NSNotFound) { + [attributedString addAttributes:self.orangeAttributes range:orangeRange]; + } + + if (contentRange.location != NSNotFound) { + [attributedString addAttributes:multipleContentAttributes range:contentRange]; + } + }]; + + [singleRegex enumerateMatchesInString:attributedString.string options:0 range:range usingBlock:^(NSTextCheckingResult *_Nullable result, NSMatchingFlags flags, BOOL *_Nonnull stop) { @@ -127,12 +149,25 @@ - (void)enumerateAndHighlightAttributedString: (nonnull NSMutableAttributedStrin NSRange orangeRange = [result rangeAtIndex:1]; NSRange contentRange = [result rangeAtIndex:2]; + BOOL alreadyMultiple = NO; + for (NSValue *value in multipleRanges) { + NSRange multipleRange = value.rangeValue; + if (NSIntersectionRange(multipleRange, fullMatch).length != 0) { + alreadyMultiple = YES; + } + } + + if (alreadyMultiple) { + return; + } + + if (orangeRange.location != NSNotFound) { [attributedString addAttributes:self.orangeAttributes range:orangeRange]; } if (contentRange.location != NSNotFound) { - [attributedString addAttributes:contentAttributes range:contentRange]; + [attributedString addAttributes:singleContentAttributes range:contentRange]; } }]; } diff --git a/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift index ee99fe49cd6..2c14d91f95d 100644 --- a/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift +++ b/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift @@ -122,4 +122,68 @@ final class WKSourceEditorFormatterButtonActionTests: XCTestCase { mediator.templateFormatter?.toggleTemplateFormatting(action: .remove, in: mediator.textView) XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four") } + + func testListBulletInsertAndRemove() throws { + let text = "Test" + mediator.textView.attributedText = NSAttributedString(string: text) + mediator.textView.selectedRange = NSRange(location: 2, length: 0) + mediator.listFormatter?.toggleListBullet(action: .add, in: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "* Test") + mediator.listFormatter?.toggleListBullet(action: .remove, in: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "Test") + } + + func testListBulletInsertAndIncreaseIndent() throws { + let text = "Test" + mediator.textView.attributedText = NSAttributedString(string: text) + mediator.textView.selectedRange = NSRange(location: 2, length: 0) + mediator.listFormatter?.toggleListBullet(action: .add, in: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "* Test") + mediator.listFormatter?.tappedIncreaseIndent(currentSelectionState: mediator.selectionState(selectedDocumentRange: mediator.textView.selectedRange), textView: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "** Test") + } + + func testListBulletDecreaseIndentAndRemove() throws { + let text = "*** Test" + mediator.textView.attributedText = NSAttributedString(string: text) + mediator.textView.selectedRange = NSRange(location: 4, length: 0) + mediator.listFormatter?.tappedDecreaseIndent(currentSelectionState: mediator.selectionState(selectedDocumentRange: mediator.textView.selectedRange), textView: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "** Test") + mediator.listFormatter?.tappedDecreaseIndent(currentSelectionState: mediator.selectionState(selectedDocumentRange: mediator.textView.selectedRange), textView: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "* Test") + mediator.listFormatter?.toggleListBullet(action: .remove, in: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "Test") + } + + func testListNumberInsertAndRemove() throws { + let text = "Test" + mediator.textView.attributedText = NSAttributedString(string: text) + mediator.textView.selectedRange = NSRange(location: 2, length: 0) + mediator.listFormatter?.toggleListNumber(action: .add, in: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "# Test") + mediator.listFormatter?.toggleListNumber(action: .remove, in: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "Test") + } + + func testListNumberInsertAndIncreaseIndent() throws { + let text = "Test" + mediator.textView.attributedText = NSAttributedString(string: text) + mediator.textView.selectedRange = NSRange(location: 2, length: 0) + mediator.listFormatter?.toggleListNumber(action: .add, in: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "# Test") + mediator.listFormatter?.tappedIncreaseIndent(currentSelectionState: mediator.selectionState(selectedDocumentRange: mediator.textView.selectedRange), textView: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "## Test") + } + + func testListNumberDecreaseIndentAndRemove() throws { + let text = "### Test" + mediator.textView.attributedText = NSAttributedString(string: text) + mediator.textView.selectedRange = NSRange(location: 4, length: 0) + mediator.listFormatter?.tappedDecreaseIndent(currentSelectionState: mediator.selectionState(selectedDocumentRange: mediator.textView.selectedRange), textView: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "## Test") + mediator.listFormatter?.tappedDecreaseIndent(currentSelectionState: mediator.selectionState(selectedDocumentRange: mediator.textView.selectedRange), textView: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "# Test") + mediator.listFormatter?.toggleListNumber(action: .remove, in: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "Test") + } } diff --git a/Components/Tests/ComponentsTests/WKSourceEditorTextFrameworkMediatorTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorTextFrameworkMediatorTests.swift index 5f01b4b4c71..6df7ab65bce 100644 --- a/Components/Tests/ComponentsTests/WKSourceEditorTextFrameworkMediatorTests.swift +++ b/Components/Tests/ComponentsTests/WKSourceEditorTextFrameworkMediatorTests.swift @@ -121,4 +121,52 @@ final class WKSourceEditorTextFrameworkMediatorTests: XCTestCase { let selectionStates1 = mediator.selectionState(selectedDocumentRange: NSRange(location: 1, length: 0)) XCTAssertFalse(selectionStates1.isHorizontalTemplate) } + + func testListBulletSingleSelectionState() throws { + + let text = "* Test" + mediator.textView.attributedText = NSAttributedString(string: text) + + let selectionStates = mediator.selectionState(selectedDocumentRange: NSRange(location: 2, length: 4)) + XCTAssertTrue(selectionStates.isBulletSingleList) + XCTAssertFalse(selectionStates.isBulletMultipleList) + XCTAssertFalse(selectionStates.isNumberSingleList) + XCTAssertFalse(selectionStates.isNumberMultipleList) + } + + func testListBulletMultipleSelectionState() throws { + + let text = "** Test" + mediator.textView.attributedText = NSAttributedString(string: text) + + let selectionStates = mediator.selectionState(selectedDocumentRange: NSRange(location: 3, length: 0)) + XCTAssertFalse(selectionStates.isBulletSingleList) + XCTAssertTrue(selectionStates.isBulletMultipleList) + XCTAssertFalse(selectionStates.isNumberSingleList) + XCTAssertFalse(selectionStates.isNumberMultipleList) + } + + func testListNumberSingleSelectionState() throws { + + let text = "# Test" + mediator.textView.attributedText = NSAttributedString(string: text) + + let selectionStates = mediator.selectionState(selectedDocumentRange: NSRange(location: 2, length: 4)) + XCTAssertFalse(selectionStates.isBulletSingleList) + XCTAssertFalse(selectionStates.isBulletMultipleList) + XCTAssertTrue(selectionStates.isNumberSingleList) + XCTAssertFalse(selectionStates.isNumberMultipleList) + } + + func testListNumberMultipleSelectionState() throws { + + let text = "## Test" + mediator.textView.attributedText = NSAttributedString(string: text) + + let selectionStates = mediator.selectionState(selectedDocumentRange: NSRange(location: 3, length: 0)) + XCTAssertFalse(selectionStates.isBulletSingleList) + XCTAssertFalse(selectionStates.isBulletMultipleList) + XCTAssertFalse(selectionStates.isNumberSingleList) + XCTAssertTrue(selectionStates.isNumberMultipleList) + } } From 742404d1b1a630faa2dd650126971c3f366aa195 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Mon, 8 Jan 2024 13:51:09 -0600 Subject: [PATCH 33/35] Bug fixes from merge --- .../Source Editor/WKSourceEditorTextFrameworkMediator.swift | 5 +++++ .../Sources/ComponentsObjC/WKSourceEditorFormatterBase.m | 1 + 2 files changed, 6 insertions(+) diff --git a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift index b5ebf00b5e8..d1af809454f 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift @@ -123,6 +123,11 @@ final class WKSourceEditorTextFrameworkMediator: NSObject { listFormatter, strikethroughFormatter] + self.boldItalicsFormatter = boldItalicsFormatter + self.templateFormatter = templateFormatter + self.listFormatter = listFormatter + self.strikethroughFormatter = strikethroughFormatter + if needsTextKit2 { if #available(iOS 16.0, *) { let textContentManager = textView.textLayoutManager?.textContentManager diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBase.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBase.m index a441ebcce07..19042761268 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBase.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBase.m @@ -35,6 +35,7 @@ - (void)addSyntaxHighlightingToAttributedString:(NSMutableAttributedString *)att // reset shared custom attributes [attributedString removeAttribute:WKSourceEditorCustomKeyColorGreen range:range]; + [attributedString removeAttribute:WKSourceEditorCustomKeyColorOrange range:range]; [attributedString addAttributes:self.attributes range:range]; } From b5e74291d646cc418bf427212f4c16e3f103ac21 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Tue, 9 Jan 2024 16:19:08 -0600 Subject: [PATCH 34/35] Bug fixes after merge --- .../Input Views/WKEditorInputView.swift | 45 ++++++++++++++++--- ...EditorFormatterHeading+ButtonActions.swift | 2 +- .../WKSourceEditorViewController.swift | 4 +- 3 files changed, 42 insertions(+), 9 deletions(-) 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 9e6fead4fc2..3b14344aa86 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 didTapHeading(type: WKEditorInputView.HeadingButtonType) func didTapStrikethrough(isSelected: Bool) } @@ -221,6 +222,18 @@ class WKEditorInputView: WKComponentView { ]) updateColors() + + NotificationCenter.default.addObserver(self, selector: #selector(updateButtonSelectionState(_:)), name: Notification.WKSourceEditorSelectionState, object: nil) + } + + // MARK: - Notifications + + @objc private func updateButtonSelectionState(_ notification: NSNotification) { + guard let selectionState = notification.userInfo?[Notification.WKSourceEditorSelectionStateKey] as? WKSourceEditorSelectionState else { + return + } + + configure(selectionState: selectionState) } // MARK: - Overrides @@ -283,20 +296,40 @@ class WKEditorInputView: WKComponentView { switch type { case .paragraph: - paragraphButton.isSelected.toggle() + paragraphButton.isSelected = true case .heading: - headerButton.isSelected.toggle() + headerButton.isSelected = true case .subheading1: - subheader1Button.isSelected.toggle() + subheader1Button.isSelected = true case .subheading2: - subheader2Button.isSelected.toggle() + subheader2Button.isSelected = true case .subheading3: - subheader3Button.isSelected.toggle() + subheader3Button.isSelected = true case .subheading4: - subheader4Button.isSelected.toggle() + subheader4Button.isSelected = true } + + delegate?.didTapHeading(type: type) }) return UIButton(configuration: configuration, primaryAction: action) } + + func configure(selectionState: WKSourceEditorSelectionState) { + headingButtons.forEach { $0.isSelected = false } + + if selectionState.isHeading { + headerButton.isSelected = true + } else if selectionState.isSubheading1 { + subheader1Button.isSelected = true + } else if selectionState.isSubheading2 { + subheader2Button.isSelected = true + } else if selectionState.isSubheading3 { + subheader3Button.isSelected = true + } else if selectionState.isSubheading4 { + subheader4Button.isSelected = true + } else { + paragraphButton.isSelected = true + } + } } diff --git a/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterHeading+ButtonActions.swift b/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterHeading+ButtonActions.swift index 123e10f1903..869c6502bcd 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterHeading+ButtonActions.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterHeading+ButtonActions.swift @@ -2,7 +2,7 @@ import Foundation import ComponentsObjC extension WKSourceEditorFormatterHeading { - func toggleHeadingFormatting(selectedHeading: WKEditorHeaderSelectViewModel.Configuration, currentSelectionState: WKSourceEditorSelectionState, textView: UITextView) { + func toggleHeadingFormatting(selectedHeading: WKEditorInputView.HeadingButtonType, currentSelectionState: WKSourceEditorSelectionState, textView: UITextView) { var currentStateIsParagraph = false if currentSelectionState.isHeading { diff --git a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift index bdea5b34ccd..bb43e24f3b9 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift @@ -291,8 +291,8 @@ extension WKSourceEditorViewController: WKEditorToolbarHighlightViewDelegate { // MARK: - WKEditorInputViewDelegate extension WKSourceEditorViewController: WKEditorInputViewDelegate { - func didTapHeading(selectedHeading: WKEditorHeaderSelectViewModel.Configuration) { - textFrameworkMediator.headingFormatter?.toggleHeadingFormatting(selectedHeading: selectedHeading, currentSelectionState: selectionState(), textView: textView) + func didTapHeading(type: WKEditorInputView.HeadingButtonType) { + textFrameworkMediator.headingFormatter?.toggleHeadingFormatting(selectedHeading: type, currentSelectionState: selectionState(), textView: textView) } func didTapBold(isSelected: Bool) { From ade32b2e876eb8548df932fa0ab4af740271d833 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Tue, 9 Jan 2024 16:30:29 -0600 Subject: [PATCH 35/35] Bug fixes after merge --- .../Editors/Common Views/Input Views/WKEditorInputView.swift | 4 ++++ 1 file changed, 4 insertions(+) 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 9e6fead4fc2..e8a54ec1102 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,10 @@ protocol WKEditorInputViewDelegate: AnyObject { func didTapBold(isSelected: Bool) func didTapItalics(isSelected: Bool) func didTapTemplate(isSelected: Bool) + func didTapBulletList(isSelected: Bool) + func didTapNumberList(isSelected: Bool) + func didTapIncreaseIndent() + func didTapDecreaseIndent() func didTapStrikethrough(isSelected: Bool) }