From 9e9dfc6c38974f6ec3ab95ef759a21ccc90c1c08 Mon Sep 17 00:00:00 2001 From: mazevedo Date: Tue, 2 Jan 2024 17:28:20 -0300 Subject: [PATCH 01/36] Add subscript and underline formatting objc empty classes --- .../ComponentsObjC/WKSourceEditorFormatterSubscript.h | 9 +++++++++ .../ComponentsObjC/WKSourceEditorFormatterSubscript.m | 6 ++++++ .../ComponentsObjC/WKSourceEditorFormatterUnderline.h | 9 +++++++++ .../ComponentsObjC/WKSourceEditorFormatterUnderline.m | 5 +++++ .../Sources/ComponentsObjC/include/ComponentsObjC.h | 2 ++ .../include/WKSourceEditorFormatterSubscript.h | 1 + .../include/WKSourceEditorFormatterUnderline.h | 1 + 7 files changed, 33 insertions(+) create mode 100644 Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.h create mode 100644 Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m create mode 100644 Components/Sources/ComponentsObjC/WKSourceEditorFormatterUnderline.h create mode 100644 Components/Sources/ComponentsObjC/WKSourceEditorFormatterUnderline.m create mode 120000 Components/Sources/ComponentsObjC/include/WKSourceEditorFormatterSubscript.h create mode 120000 Components/Sources/ComponentsObjC/include/WKSourceEditorFormatterUnderline.h diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.h b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.h new file mode 100644 index 00000000000..17c1b518b12 --- /dev/null +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.h @@ -0,0 +1,9 @@ +#import "WKSourceEditorFormatter.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface WKSourceEditorFormatterSubscript: WKSourceEditorFormatter + +@end + +NS_ASSUME_NONNULL_END diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m new file mode 100644 index 00000000000..2e007ef6716 --- /dev/null +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m @@ -0,0 +1,6 @@ +#import "WKSourceEditorFormatterSubscript.h" + +@implementation WKSourceEditorFormatterSubscript + +@end + diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterUnderline.h b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterUnderline.h new file mode 100644 index 00000000000..7921e57a693 --- /dev/null +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterUnderline.h @@ -0,0 +1,9 @@ +#import "WKSourceEditorFormatter.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface WKSourceEditorFormatterUnderline : WKSourceEditorFormatter + +@end + +NS_ASSUME_NONNULL_END diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterUnderline.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterUnderline.m new file mode 100644 index 00000000000..bfdcff52467 --- /dev/null +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterUnderline.m @@ -0,0 +1,5 @@ +#import "WKSourceEditorFormatterUnderline.h" + +@implementation WKSourceEditorFormatterUnderline + +@end diff --git a/Components/Sources/ComponentsObjC/include/ComponentsObjC.h b/Components/Sources/ComponentsObjC/include/ComponentsObjC.h index 078e6dec5d6..4050d840391 100644 --- a/Components/Sources/ComponentsObjC/include/ComponentsObjC.h +++ b/Components/Sources/ComponentsObjC/include/ComponentsObjC.h @@ -7,6 +7,8 @@ #import "WKSourceEditorFormatter.h" #import "WKSourceEditorFormatterBase.h" #import "WKSourceEditorFormatterBoldItalics.h" +#import "WKSourceEditorFormatterUnderline.h" +#import "WKSourceEditorFormatterSubscript.h" #import "WKSourceEditorFormatterTemplate.h" #import "WKSourceEditorStorageDelegate.h" diff --git a/Components/Sources/ComponentsObjC/include/WKSourceEditorFormatterSubscript.h b/Components/Sources/ComponentsObjC/include/WKSourceEditorFormatterSubscript.h new file mode 120000 index 00000000000..94f2fa45b05 --- /dev/null +++ b/Components/Sources/ComponentsObjC/include/WKSourceEditorFormatterSubscript.h @@ -0,0 +1 @@ +../WKSourceEditorFormatterSubscript.h \ No newline at end of file diff --git a/Components/Sources/ComponentsObjC/include/WKSourceEditorFormatterUnderline.h b/Components/Sources/ComponentsObjC/include/WKSourceEditorFormatterUnderline.h new file mode 120000 index 00000000000..02e3913ab5f --- /dev/null +++ b/Components/Sources/ComponentsObjC/include/WKSourceEditorFormatterUnderline.h @@ -0,0 +1 @@ +../WKSourceEditorFormatterUnderline.h \ No newline at end of file From c9444b6d3ae555d349de18019238e233b116eb0d Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Thu, 14 Dec 2023 14:49:28 -0600 Subject: [PATCH 02/36] Add common custom attribute key for green editor text --- Components/Sources/ComponentsObjC/WKSourceEditorFormatter.h | 3 +++ Components/Sources/ComponentsObjC/WKSourceEditorFormatter.m | 5 +++++ .../Sources/ComponentsObjC/WKSourceEditorFormatterBase.m | 5 ++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatter.h b/Components/Sources/ComponentsObjC/WKSourceEditorFormatter.h index f3cb213f291..9a67168c282 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 WKSourceEditorCustomKeyColorGreen; + - (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..63c589b2461 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 + +NSString * const WKSourceEditorCustomKeyColorGreen = @"WKSourceEditorKeyColorGreen"; + - (nonnull instancetype)initWithColors:(nonnull WKSourceEditorColors *)colors fonts:(nonnull WKSourceEditorFonts *)fonts { self = [super init]; return self; diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBase.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBase.m index d7ad812afa3..a441ebcce07 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:WKSourceEditorCustomKeyColorGreen range:range]; + [attributedString addAttributes:self.attributes range:range]; } From 4c842cc8a56c38377508f538bbe17828996a8bf0 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Thu, 14 Dec 2023 15:36:16 -0600 Subject: [PATCH 03/36] Add new green color to WKSourceEditorColors --- .../WKSourceEditorTextFrameworkMediator.swift | 1 + Components/Sources/Components/Style/WKTheme.swift | 13 +++++++++---- .../Sources/ComponentsObjC/WKSourceEditorColors.h | 1 + 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift index c414a31bee6..2f856b8e3e6 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift @@ -201,6 +201,7 @@ extension WKSourceEditorTextFrameworkMediator: WKSourceEditorStorageDelegate { colors.baseForegroundColor = WKAppEnvironment.current.theme.text colors.orangeForegroundColor = isSyntaxHighlightingEnabled ? WKAppEnvironment.current.theme.editorOrange : WKAppEnvironment.current.theme.text colors.purpleForegroundColor = isSyntaxHighlightingEnabled ? WKAppEnvironment.current.theme.editorPurple : WKAppEnvironment.current.theme.text + colors.greenForegroundColor = isSyntaxHighlightingEnabled ? WKAppEnvironment.current.theme.editorGreen : WKAppEnvironment.current.theme.text return colors } diff --git a/Components/Sources/Components/Style/WKTheme.swift b/Components/Sources/Components/Style/WKTheme.swift index 5be44a40c79..e5751d2af00 100644 --- a/Components/Sources/Components/Style/WKTheme.swift +++ b/Components/Sources/Components/Style/WKTheme.swift @@ -26,6 +26,7 @@ public struct WKTheme: Equatable { public let diffCompareAccent: UIColor public let editorOrange: UIColor public let editorPurple: UIColor + public let editorGreen: UIColor public static let light = WKTheme( name: "Light", @@ -50,7 +51,8 @@ public struct WKTheme: Equatable { keyboardBarSearchFieldBackground: WKColor.gray200, diffCompareAccent: WKColor.orange600, editorOrange: WKColor.orange600, - editorPurple: WKColor.purple600 + editorPurple: WKColor.purple600, + editorGreen: WKColor.green600 ) public static let sepia = WKTheme( @@ -76,7 +78,8 @@ public struct WKTheme: Equatable { keyboardBarSearchFieldBackground: WKColor.gray200, diffCompareAccent: WKColor.orange600, editorOrange: WKColor.orange600, - editorPurple: WKColor.purple600 + editorPurple: WKColor.purple600, + editorGreen: WKColor.green600 ) public static let dark = WKTheme( @@ -102,7 +105,8 @@ public struct WKTheme: Equatable { keyboardBarSearchFieldBackground: WKColor.gray650, diffCompareAccent: WKColor.orange600, editorOrange: WKColor.yellow600, - editorPurple: WKColor.red100 + editorPurple: WKColor.red100, + editorGreen: WKColor.green600 ) public static let black = WKTheme( @@ -128,7 +132,8 @@ public struct WKTheme: Equatable { keyboardBarSearchFieldBackground: WKColor.gray650, diffCompareAccent: WKColor.orange600, editorOrange: WKColor.yellow600, - editorPurple: WKColor.red100 + editorPurple: WKColor.red100, + editorGreen: WKColor.green600 ) } diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorColors.h b/Components/Sources/ComponentsObjC/WKSourceEditorColors.h index b9df307bd49..9b3bf77a05f 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorColors.h +++ b/Components/Sources/ComponentsObjC/WKSourceEditorColors.h @@ -6,6 +6,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, strong) UIColor *baseForegroundColor; @property (nonatomic, strong) UIColor *orangeForegroundColor; @property (nonatomic, strong) UIColor *purpleForegroundColor; +@property (nonatomic, strong) UIColor *greenForegroundColor; @end NS_ASSUME_NONNULL_END From b377f0e15eb324391fdc7233409ff2176455eaf6 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Fri, 15 Dec 2023 14:02:36 -0600 Subject: [PATCH 04/36] Add empty WKSourceEditorFormatterStrikethrough --- .../WKSourceEditorFormatterStrikethrough.h | 9 +++++++++ .../WKSourceEditorFormatterStrikethrough.m | 5 +++++ .../Sources/ComponentsObjC/include/ComponentsObjC.h | 1 + .../include/WKSourceEditorFormatterStrikethrough.h | 1 + 4 files changed, 16 insertions(+) create mode 100644 Components/Sources/ComponentsObjC/WKSourceEditorFormatterStrikethrough.h create mode 100644 Components/Sources/ComponentsObjC/WKSourceEditorFormatterStrikethrough.m create mode 120000 Components/Sources/ComponentsObjC/include/WKSourceEditorFormatterStrikethrough.h diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterStrikethrough.h b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterStrikethrough.h new file mode 100644 index 00000000000..42b7caab5cd --- /dev/null +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterStrikethrough.h @@ -0,0 +1,9 @@ +#import "WKSourceEditorFormatter.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface WKSourceEditorFormatterStrikethrough : WKSourceEditorFormatter + +@end + +NS_ASSUME_NONNULL_END diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterStrikethrough.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterStrikethrough.m new file mode 100644 index 00000000000..b5157f83ade --- /dev/null +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterStrikethrough.m @@ -0,0 +1,5 @@ +#import "WKSourceEditorFormatterStrikethrough.h" + +@implementation WKSourceEditorFormatterStrikethrough + +@end diff --git a/Components/Sources/ComponentsObjC/include/ComponentsObjC.h b/Components/Sources/ComponentsObjC/include/ComponentsObjC.h index 4050d840391..bc1a8c224c7 100644 --- a/Components/Sources/ComponentsObjC/include/ComponentsObjC.h +++ b/Components/Sources/ComponentsObjC/include/ComponentsObjC.h @@ -10,6 +10,7 @@ #import "WKSourceEditorFormatterUnderline.h" #import "WKSourceEditorFormatterSubscript.h" #import "WKSourceEditorFormatterTemplate.h" +#import "WKSourceEditorFormatterStrikethrough.h" #import "WKSourceEditorStorageDelegate.h" #endif /* Header_h */ diff --git a/Components/Sources/ComponentsObjC/include/WKSourceEditorFormatterStrikethrough.h b/Components/Sources/ComponentsObjC/include/WKSourceEditorFormatterStrikethrough.h new file mode 120000 index 00000000000..3c2ef217a96 --- /dev/null +++ b/Components/Sources/ComponentsObjC/include/WKSourceEditorFormatterStrikethrough.h @@ -0,0 +1 @@ +../WKSourceEditorFormatterStrikethrough.h \ No newline at end of file From 3d04707b8569be95cbe952d2dd1853bb0931a5ea Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Fri, 15 Dec 2023 14:23:41 -0600 Subject: [PATCH 05/36] Add syntax highlight logic in formatter --- .../WKSourceEditorFormatterStrikethrough.m | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterStrikethrough.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterStrikethrough.m index b5157f83ade..c2349c9b581 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterStrikethrough.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterStrikethrough.m @@ -1,5 +1,70 @@ #import "WKSourceEditorFormatterStrikethrough.h" +#import "WKSourceEditorColors.h" + +@interface WKSourceEditorFormatterStrikethrough () + +@property (nonatomic, strong) NSDictionary *strikethroughAttributes; +@property (nonatomic, strong) NSRegularExpression *strikethroughRegex; + +@end @implementation WKSourceEditorFormatterStrikethrough +- (instancetype)initWithColors:(WKSourceEditorColors *)colors fonts:(WKSourceEditorFonts *)fonts { + self = [super initWithColors:colors fonts:fonts]; + if (self) { + _strikethroughAttributes = @{ + NSForegroundColorAttributeName: colors.greenForegroundColor, + WKSourceEditorCustomKeyColorGreen: [NSNumber numberWithBool:YES] + }; + + _strikethroughRegex = [[NSRegularExpression alloc] initWithPattern:@"()(\\s*.*?)(<\\/s>)" options:0 error:nil]; + } + + return self; +} + +- (void)addSyntaxHighlightingToAttributedString:(nonnull NSMutableAttributedString *)attributedString inRange:(NSRange)range { + [self.strikethroughRegex enumerateMatchesInString:attributedString.string + options:0 + range:range + usingBlock:^(NSTextCheckingResult *_Nullable result, NSMatchingFlags flags, BOOL *_Nonnull stop) { + NSRange fullMatch = [result rangeAtIndex:0]; + NSRange openingRange = [result rangeAtIndex:1]; + NSRange contentRange = [result rangeAtIndex:2]; + NSRange closingRange = [result rangeAtIndex:3]; + + if (openingRange.location != NSNotFound) { + [attributedString addAttributes:self.strikethroughAttributes range:openingRange]; + } + + if (closingRange.location != NSNotFound) { + [attributedString addAttributes:self.strikethroughAttributes range:closingRange]; + } + }]; +} + +- (void)updateColors:(WKSourceEditorColors *)colors inAttributedString:(NSMutableAttributedString *)attributedString inRange:(NSRange)range { + + NSMutableDictionary *mutAttributes = [[NSMutableDictionary alloc] initWithDictionary:self.strikethroughAttributes]; + [mutAttributes setObject:colors.greenForegroundColor forKey:NSForegroundColorAttributeName]; + self.strikethroughAttributes = [[NSDictionary alloc] initWithDictionary:mutAttributes]; + + [attributedString enumerateAttribute:WKSourceEditorCustomKeyColorGreen + inRange:range + options:nil + usingBlock:^(id value, NSRange localRange, BOOL *stop) { + if ([value isKindOfClass: [NSNumber class]]) { + NSNumber *numValue = (NSNumber *)value; + if ([numValue boolValue] == YES) { + [attributedString addAttributes:self.strikethroughAttributes range:localRange]; + } + } + }]; +} + +- (void)updateFonts:(WKSourceEditorFonts *)fonts inAttributedString:(NSMutableAttributedString *)attributedString inRange:(NSRange)range { + // No special font handling needed for references +} + @end From 3293badd6d800639b4148cb321a85be1946b6875 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Fri, 15 Dec 2023 14:23:47 -0600 Subject: [PATCH 06/36] Use formatter in editor --- .../WKSourceEditorTextFrameworkMediator.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift index 2f856b8e3e6..dbe15ce1e3d 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 strikethroughFormatter: WKSourceEditorFormatterStrikethrough? 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 strikethroughFormatter = WKSourceEditorFormatterStrikethrough(colors: colors, fonts: fonts) + self.formatters = [WKSourceEditorFormatterBase(colors: colors, fonts: fonts, textAlignment: viewModel.textAlignment), templateFormatter, - boldItalicsFormatter] + boldItalicsFormatter, + strikethroughFormatter] self.boldItalicsFormatter = boldItalicsFormatter - self.templateFormatter = templateFormatter + self.strikethroughFormatter = strikethroughFormatter if needsTextKit2 { if #available(iOS 16.0, *) { From b803331a22fb7962f883fd1fa90d6c86f3203262 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Fri, 15 Dec 2023 14:23:51 -0600 Subject: [PATCH 07/36] Add tests --- .../WKSourceEditorFormatterTests.swift | 59 ++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift index 6330fe0ba7c..a3764296794 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 strikethroughFormatter: WKSourceEditorFormatterStrikethrough! var formatters: [WKSourceEditorFormatter] { - return [baseFormatter, templateFormatter, boldItalicsFormatter] + return [baseFormatter, templateFormatter, boldItalicsFormatter, strikethroughFormatter] } override func setUpWithError() throws { @@ -21,6 +22,7 @@ final class WKSourceEditorFormatterTests: XCTestCase { self.colors.baseForegroundColor = WKTheme.light.text self.colors.orangeForegroundColor = WKTheme.light.editorOrange self.colors.purpleForegroundColor = WKTheme.light.editorPurple + self.colors.greenForegroundColor = WKTheme.light.editorGreen self.fonts = WKSourceEditorFonts() self.fonts.baseFont = WKFont.for(.body, compatibleWith: traitCollection) @@ -31,6 +33,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.strikethroughFormatter = WKSourceEditorFormatterStrikethrough(colors: colors, fonts: fonts) } override func tearDownWithError() throws { @@ -709,4 +712,58 @@ 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 testStrikethrough() { + let string = "Testing. Strikethrough. Testing" + let mutAttributedString = NSMutableAttributedString(string: string) + + for formatter in formatters { + formatter.addSyntaxHighlighting(to: mutAttributedString, in: NSRange(location: 0, length: string.count)) + } + + var base1Range = NSRange(location: 0, length: 0) + let base1Attributes = mutAttributedString.attributes(at: 0, effectiveRange: &base1Range) + + var strikethroughOpenRange = NSRange(location: 0, length: 0) + let strikethroughOpenAttributes = mutAttributedString.attributes(at: 9, effectiveRange: &strikethroughOpenRange) + + var strikethroughContentRange = NSRange(location: 0, length: 0) + let strikethroughContentAttributes = mutAttributedString.attributes(at: 12, effectiveRange: &strikethroughContentRange) + + var strikethroughCloseRange = NSRange(location: 0, length: 0) + let strikethroughCloseAttributes = mutAttributedString.attributes(at: 26, effectiveRange: &strikethroughCloseRange) + + var base2Range = NSRange(location: 0, length: 0) + let base2Attributes = mutAttributedString.attributes(at: 32, effectiveRange: &base2Range) + + // "Testing. " + XCTAssertEqual(base1Range.location, 0, "Incorrect base formatting") + XCTAssertEqual(base1Range.length, 9, "Incorrect base formatting") + XCTAssertEqual(base1Attributes[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") + XCTAssertEqual(base1Attributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect base formatting") + + // "" + XCTAssertEqual(strikethroughOpenRange.location, 9, "Incorrect strikethrough formatting") + XCTAssertEqual(strikethroughOpenRange.length, 3, "Incorrect strikethrough formatting") + XCTAssertEqual(strikethroughOpenAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect strikethrough formatting") + XCTAssertEqual(strikethroughOpenAttributes[.foregroundColor] as! UIColor, colors.greenForegroundColor, "Incorrect strikethrough formatting") + + // "Strikethrough." + XCTAssertEqual(strikethroughContentRange.location, 12, "Incorrect content formatting") + XCTAssertEqual(strikethroughContentRange.length, 14, "Incorrect content formatting") + XCTAssertEqual(strikethroughContentAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect content formatting") + XCTAssertEqual(strikethroughContentAttributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect content formatting") + + // "" + XCTAssertEqual(strikethroughCloseRange.location, 26, "Incorrect strikethrough formatting") + XCTAssertEqual(strikethroughCloseRange.length, 4, "Incorrect strikethrough formatting") + XCTAssertEqual(strikethroughCloseAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect strikethrough formatting") + XCTAssertEqual(strikethroughCloseAttributes[.foregroundColor] as! UIColor, colors.greenForegroundColor, "Incorrect strikethrough formatting") + + // " Testing" + XCTAssertEqual(base2Range.location, 30, "Incorrect base formatting") + XCTAssertEqual(base2Range.length, 8, "Incorrect base formatting") + XCTAssertEqual(base2Attributes[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") + XCTAssertEqual(base2Attributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect base formatting") + } } From 8249affe44e00562db266bb882580dd2083828fe Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Fri, 15 Dec 2023 14:28:03 -0600 Subject: [PATCH 08/36] Allow formatter to detect if range contains custom content key --- .../WKSourceEditorFormatterStrikethrough.h | 2 + .../WKSourceEditorFormatterStrikethrough.m | 61 +++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterStrikethrough.h b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterStrikethrough.h index 42b7caab5cd..cf4c85d441f 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterStrikethrough.h +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterStrikethrough.h @@ -4,6 +4,8 @@ NS_ASSUME_NONNULL_BEGIN @interface WKSourceEditorFormatterStrikethrough : WKSourceEditorFormatter +- (BOOL)attributedString:(NSMutableAttributedString *)attributedString isStrikethroughInRange:(NSRange)range; + @end NS_ASSUME_NONNULL_END diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterStrikethrough.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterStrikethrough.m index c2349c9b581..4a016dc935e 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterStrikethrough.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterStrikethrough.m @@ -4,12 +4,17 @@ @interface WKSourceEditorFormatterStrikethrough () @property (nonatomic, strong) NSDictionary *strikethroughAttributes; +@property (nonatomic, strong) NSDictionary *strikethroughContentAttributes; @property (nonatomic, strong) NSRegularExpression *strikethroughRegex; @end @implementation WKSourceEditorFormatterStrikethrough +#pragma mark - Custom Attributed String Keys + +NSString * const WKSourceEditorCustomKeyContentStrikethrough = @"WKSourceEditorCustomKeyContentStrikethrough"; + - (instancetype)initWithColors:(WKSourceEditorColors *)colors fonts:(WKSourceEditorFonts *)fonts { self = [super initWithColors:colors fonts:fonts]; if (self) { @@ -18,6 +23,10 @@ - (instancetype)initWithColors:(WKSourceEditorColors *)colors fonts:(WKSourceEdi WKSourceEditorCustomKeyColorGreen: [NSNumber numberWithBool:YES] }; + _strikethroughContentAttributes = @{ + WKSourceEditorCustomKeyContentStrikethrough: [NSNumber numberWithBool:YES] + }; + _strikethroughRegex = [[NSRegularExpression alloc] initWithPattern:@"()(\\s*.*?)(<\\/s>)" options:0 error:nil]; } @@ -25,6 +34,10 @@ - (instancetype)initWithColors:(WKSourceEditorColors *)colors fonts:(WKSourceEdi } - (void)addSyntaxHighlightingToAttributedString:(nonnull NSMutableAttributedString *)attributedString inRange:(NSRange)range { + + // Reset + [attributedString removeAttribute:WKSourceEditorCustomKeyContentStrikethrough range:range]; + [self.strikethroughRegex enumerateMatchesInString:attributedString.string options:0 range:range @@ -37,6 +50,10 @@ - (void)addSyntaxHighlightingToAttributedString:(nonnull NSMutableAttributedStri if (openingRange.location != NSNotFound) { [attributedString addAttributes:self.strikethroughAttributes range:openingRange]; } + + if (contentRange.location != NSNotFound) { + [attributedString addAttributes:self.strikethroughContentAttributes range:contentRange]; + } if (closingRange.location != NSNotFound) { [attributedString addAttributes:self.strikethroughAttributes range:closingRange]; @@ -67,4 +84,48 @@ - (void)updateFonts:(WKSourceEditorFonts *)fonts inAttributedString:(NSMutableAt // No special font handling needed for references } +#pragma mark - Public + +- (BOOL)attributedString:(NSMutableAttributedString *)attributedString isStrikethroughInRange:(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[WKSourceEditorCustomKeyContentStrikethrough] != nil) { + isContentKey = YES; + } else { + // Edge case, check previous character if we are up against closing string + if (attrs[WKSourceEditorCustomKeyColorGreen]) { + attrs = [attributedString attributesAtIndex:range.location - 1 effectiveRange:nil]; + if (attrs[WKSourceEditorCustomKeyContentStrikethrough] != 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[WKSourceEditorCustomKeyContentStrikethrough] != nil) { + if (unionRange.location == NSNotFound) { + unionRange = loopRange; + } else { + unionRange = NSUnionRange(unionRange, loopRange); + } + stop = YES; + } + }]; + + if (NSEqualRanges(unionRange, range)) { + isContentKey = YES; + } + } + + return isContentKey; +} + @end From 2746498762c118886e566898848744932c17ef8f Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Fri, 15 Dec 2023 14:31:11 -0600 Subject: [PATCH 09/36] Add strikethrough properties to WKSourceEditorSelectionSta --- .../WKSourceEditorTextFrameworkMediator.swift | 14 +++++++++----- 1 file changed, 9 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 dbe15ce1e3d..f485c3b82f8 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift @@ -17,11 +17,13 @@ fileprivate var needsTextKit2: Bool { let isBold: Bool let isItalics: Bool let isHorizontalTemplate: Bool + let isStrikethrough: Bool - init(isBold: Bool, isItalics: Bool, isHorizontalTemplate: Bool) { + init(isBold: Bool, isItalics: Bool, isHorizontalTemplate: Bool, isStrikethrough: Bool) { self.isBold = isBold self.isItalics = isItalics self.isHorizontalTemplate = isHorizontalTemplate + self.isStrikethrough = isStrikethrough } } @@ -151,24 +153,26 @@ 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, isStrikethrough: 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 isStrikethrough = strikethroughFormatter?.attributedString(textKit2Data.paragraphAttributedString, isStrikethroughIn: textKit2Data.paragraphSelectedRange) ?? false - return WKSourceEditorSelectionState(isBold: isBold, isItalics: isItalics, isHorizontalTemplate: isHorizontalTemplate) + return WKSourceEditorSelectionState(isBold: isBold, isItalics: isItalics, isHorizontalTemplate: isHorizontalTemplate, isStrikethrough: isStrikethrough) } else { guard let textKit1Storage else { - return WKSourceEditorSelectionState(isBold: false, isItalics: false, isHorizontalTemplate: false) + return WKSourceEditorSelectionState(isBold: false, isItalics: false, isHorizontalTemplate: false, isStrikethrough: 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 isStrikethrough = strikethroughFormatter?.attributedString(textKit1Storage, isStrikethroughIn: selectedDocumentRange) ?? false - return WKSourceEditorSelectionState(isBold: isBold, isItalics: isItalics, isHorizontalTemplate: isHorizontalTemplate) + return WKSourceEditorSelectionState(isBold: isBold, isItalics: isItalics, isHorizontalTemplate: isHorizontalTemplate, isStrikethrough: isStrikethrough) } } From 3e0b0d46024eb5473c1ceafa8cfead45195e12d4 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Fri, 15 Dec 2023 14:33:06 -0600 Subject: [PATCH 10/36] Add delegate callbacks --- .../Common Views/Input Views/WKEditorInputViewController.swift | 1 + .../Editors/Source Editor/WKSourceEditorViewController.swift | 3 +++ 2 files changed, 4 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..1ffe9565031 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 didTapStrikethrough(isSelected: Bool) } 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..f4343eafa4d 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift @@ -359,6 +359,9 @@ extension WKSourceEditorViewController: WKEditorInputViewDelegate { textFrameworkMediator.templateFormatter?.toggleTemplateFormatting(action: action, in: textView) } + func didTapStrikethrough(isSelected: Bool) { + } + func didTapClose() { inputViewType = nil let isRangeSelected = textView.selectedRange.length > 0 From 084bbac4385807c2455a45046331e0866f02be2e Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Fri, 15 Dec 2023 14:36:20 -0600 Subject: [PATCH 11/36] Apply strikethrough selection state to input view --- .../WKEditorToolbarGroupedView.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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..ffb02e3009f 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 @@ -49,6 +49,18 @@ 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 + } + + strikethroughButton.isSelected = selectionState.isStrikethrough } // MARK: - Button Actions From dab3c6672da8a0dbf3a6da5773292e4f97c37ecc Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Fri, 15 Dec 2023 14:44:18 -0600 Subject: [PATCH 12/36] Add more delegate callbacks --- .../WKEditorToolbarGroupedCell.swift | 13 +++++++++++-- .../WKEditorToolbarGroupedView.swift | 3 +++ .../Main/WKEditorInputMainViewController.swift | 5 +++++ 3 files changed, 19 insertions(+), 2 deletions(-) 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 ffb02e3009f..34c00e0b6c4 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() { @@ -87,6 +89,7 @@ class WKEditorToolbarGroupedView: WKEditorToolbarView { } @objc private func tappedStrikethrough() { + delegate?.didTapStrikethrough(isSelected: strikethroughButton.isSelected) } } 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: From 0e40674046e469aad524c0ace323af391bdf373a Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Fri, 15 Dec 2023 14:45:47 -0600 Subject: [PATCH 13/36] Add formatter extension to toggle formatting --- ...SourceEditorFormatterStrikethrough+ButtonActions.swift | 8 ++++++++ .../Source Editor/WKSourceEditorViewController.swift | 2 ++ 2 files changed, 10 insertions(+) create mode 100644 Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterStrikethrough+ButtonActions.swift diff --git a/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterStrikethrough+ButtonActions.swift b/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterStrikethrough+ButtonActions.swift new file mode 100644 index 00000000000..d020477527d --- /dev/null +++ b/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterStrikethrough+ButtonActions.swift @@ -0,0 +1,8 @@ +import Foundation +import ComponentsObjC + +extension WKSourceEditorFormatterStrikethrough { + func toggleStrikethroughFormatting(action: WKSourceEditorFormatterButtonAction, in textView: UITextView) { + toggleFormatting(startingFormattingString: "", endingFormattingString: "", action: action, 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 f4343eafa4d..1eb2abfc40c 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift @@ -360,6 +360,8 @@ extension WKSourceEditorViewController: WKEditorInputViewDelegate { } func didTapStrikethrough(isSelected: Bool) { + let action: WKSourceEditorFormatterButtonAction = isSelected ? .remove : .add + textFrameworkMediator.strikethroughFormatter?.toggleStrikethroughFormatting(action: action, in: textView) } func didTapClose() { From 9000b2cb8fbb95cf4e0810d133b21452e0ad03e5 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Fri, 15 Dec 2023 14:58:56 -0600 Subject: [PATCH 14/36] Add tests --- ...urceEditorFormatterButtonActionTests.swift | 10 ++++++++ ...urceEditorTextFrameworkMediatorTests.swift | 24 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift index ee99fe49cd6..35d8f3b5a64 100644 --- a/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift +++ b/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift @@ -122,4 +122,14 @@ final class WKSourceEditorFormatterButtonActionTests: XCTestCase { mediator.templateFormatter?.toggleTemplateFormatting(action: .remove, in: mediator.textView) XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four") } + + func testStrikethroughInsertAndRemove() throws { + let text = "One Two Three Four" + mediator.textView.attributedText = NSAttributedString(string: text) + mediator.textView.selectedRange = NSRange(location: 4, length:3) + mediator.strikethroughFormatter?.toggleStrikethroughFormatting(action: .add, in: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four") + mediator.strikethroughFormatter?.toggleStrikethroughFormatting(action: .remove, in: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four") + } } diff --git a/Components/Tests/ComponentsTests/WKSourceEditorTextFrameworkMediatorTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorTextFrameworkMediatorTests.swift index 5f01b4b4c71..183d81456fb 100644 --- a/Components/Tests/ComponentsTests/WKSourceEditorTextFrameworkMediatorTests.swift +++ b/Components/Tests/ComponentsTests/WKSourceEditorTextFrameworkMediatorTests.swift @@ -121,4 +121,28 @@ final class WKSourceEditorTextFrameworkMediatorTests: XCTestCase { let selectionStates1 = mediator.selectionState(selectedDocumentRange: NSRange(location: 1, length: 0)) XCTAssertFalse(selectionStates1.isHorizontalTemplate) } + + func testStrikethroughSelectionState() throws { + let text = "Testing Strikethrough Testing." + mediator.textView.attributedText = NSAttributedString(string: text) + + let selectionStates1 = mediator.selectionState(selectedDocumentRange: NSRange(location: 0, length: 7)) + let selectionStates2 = mediator.selectionState(selectedDocumentRange: NSRange(location: 11, length: 13)) + let selectionStates3 = mediator.selectionState(selectedDocumentRange: NSRange(location: 29, length: 7)) + XCTAssertFalse(selectionStates1.isStrikethrough) + XCTAssertTrue(selectionStates2.isStrikethrough) + XCTAssertFalse(selectionStates3.isStrikethrough) + } + + func testStrikethroughSelectionStateCursor() throws { + let text = "Testing Strikethrough Testing." + mediator.textView.attributedText = NSAttributedString(string: text) + + let selectionStates1 = mediator.selectionState(selectedDocumentRange: NSRange(location: 3, length: 0)) + let selectionStates2 = mediator.selectionState(selectedDocumentRange: NSRange(location: 17, length: 0)) + let selectionStates3 = mediator.selectionState(selectedDocumentRange: NSRange(location: 33, length: 0)) + XCTAssertFalse(selectionStates1.isStrikethrough) + XCTAssertTrue(selectionStates2.isStrikethrough) + XCTAssertFalse(selectionStates3.isStrikethrough) + } } From 467bb145ae4b0d9a67a9b86040deedc3449cb8e6 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Fri, 15 Dec 2023 15:03:44 -0600 Subject: [PATCH 15/36] Fix failing template tests --- .../Source Editor/WKSourceEditorTextFrameworkMediator.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift index f485c3b82f8..b01e9dfb207 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift @@ -113,6 +113,7 @@ final class WKSourceEditorTextFrameworkMediator: NSObject { boldItalicsFormatter, strikethroughFormatter] self.boldItalicsFormatter = boldItalicsFormatter + self.templateFormatter = templateFormatter self.strikethroughFormatter = strikethroughFormatter if needsTextKit2 { From 6a329ac76e6f1a6bc0a0fb7ab5057d40a976e173 Mon Sep 17 00:00:00 2001 From: mazevedo Date: Tue, 2 Jan 2024 18:15:23 -0300 Subject: [PATCH 16/36] Add superscript formatting objc empty classes --- .../WKSourceEditorFormatterSuperscript.h | 11 +++++++++++ .../WKSourceEditorFormatterSuperscript.m | 5 +++++ .../include/WKSourceEditorFormatterSuperscript.h | 1 + 3 files changed, 17 insertions(+) create mode 100644 Components/Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.h create mode 100644 Components/Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.m create mode 120000 Components/Sources/ComponentsObjC/include/WKSourceEditorFormatterSuperscript.h diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.h b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.h new file mode 100644 index 00000000000..8deed9d1f48 --- /dev/null +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.h @@ -0,0 +1,11 @@ +#import "WKSourceEditorFormatter.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface WKSourceEditorFormatterSuperscript : WKSourceEditorFormatter + + +@end + +NS_ASSUME_NONNULL_END + diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.m new file mode 100644 index 00000000000..54d013dfda3 --- /dev/null +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.m @@ -0,0 +1,5 @@ +#import "WKSourceEditorFormatterSuperscript.h" + +@implementation WKSourceEditorFormatterSuperscript + +@end diff --git a/Components/Sources/ComponentsObjC/include/WKSourceEditorFormatterSuperscript.h b/Components/Sources/ComponentsObjC/include/WKSourceEditorFormatterSuperscript.h new file mode 120000 index 00000000000..cffffda0ca9 --- /dev/null +++ b/Components/Sources/ComponentsObjC/include/WKSourceEditorFormatterSuperscript.h @@ -0,0 +1 @@ +../WKSourceEditorFormatterSuperscript.h \ No newline at end of file From 693812a10837012d6b65fcbb5e22790c2eaff702 Mon Sep 17 00:00:00 2001 From: mazevedo Date: Tue, 2 Jan 2024 18:17:28 -0300 Subject: [PATCH 17/36] Add superscript formatter to header file --- Components/Sources/ComponentsObjC/include/ComponentsObjC.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Components/Sources/ComponentsObjC/include/ComponentsObjC.h b/Components/Sources/ComponentsObjC/include/ComponentsObjC.h index bc1a8c224c7..505b104af5b 100644 --- a/Components/Sources/ComponentsObjC/include/ComponentsObjC.h +++ b/Components/Sources/ComponentsObjC/include/ComponentsObjC.h @@ -9,6 +9,7 @@ #import "WKSourceEditorFormatterBoldItalics.h" #import "WKSourceEditorFormatterUnderline.h" #import "WKSourceEditorFormatterSubscript.h" +#import "WKSourceEditorFormatterSuperscript.h" #import "WKSourceEditorFormatterTemplate.h" #import "WKSourceEditorFormatterStrikethrough.h" #import "WKSourceEditorStorageDelegate.h" From a6354f185d466ae97fbf549624cf17c3f86cd5c8 Mon Sep 17 00:00:00 2001 From: mazevedo Date: Wed, 3 Jan 2024 20:35:51 -0300 Subject: [PATCH 18/36] Add subscrprit formatting --- .../WKEditorToolbarGroupedView.swift | 2 + .../WKEditorInputViewController.swift | 1 + ...itorSubscriptFormatter+ButtonActions.swift | 8 ++ .../WKSourceEditorTextFrameworkMediator.swift | 29 ++-- .../WKSourceEditorViewController.swift | 7 +- .../WKSourceEditorFormatterSubscript.h | 2 + .../WKSourceEditorFormatterSubscript.m | 126 ++++++++++++++++++ ...urceEditorFormatterButtonActionTests.swift | 3 + .../WKSourceEditorFormatterTests.swift | 8 +- Wikipedia/Code/ExploreViewController.swift | 4 +- 10 files changed, 175 insertions(+), 15 deletions(-) create mode 100644 Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorSubscriptFormatter+ButtonActions.swift 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 34c00e0b6c4..8165daacbce 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 @@ -63,6 +63,7 @@ class WKEditorToolbarGroupedView: WKEditorToolbarView { } strikethroughButton.isSelected = selectionState.isStrikethrough + subscriptButton.isSelected = selectionState.isSubscript } // MARK: - Button Actions @@ -83,6 +84,7 @@ class WKEditorToolbarGroupedView: WKEditorToolbarView { } @objc private func tappedSubscript() { + delegate?.didTapSubscript(isSelected: subscriptButton.isSelected) } @objc private func tappedUnderline() { 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 1ffe9565031..d3d940fa896 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 @@ -7,6 +7,7 @@ protocol WKEditorInputViewDelegate: AnyObject { func didTapItalics(isSelected: Bool) func didTapTemplate(isSelected: Bool) func didTapStrikethrough(isSelected: Bool) + func didTapSubscript(isSelected: Bool) } class WKEditorInputViewController: WKComponentViewController { diff --git a/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorSubscriptFormatter+ButtonActions.swift b/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorSubscriptFormatter+ButtonActions.swift new file mode 100644 index 00000000000..40d9bfe0835 --- /dev/null +++ b/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorSubscriptFormatter+ButtonActions.swift @@ -0,0 +1,8 @@ +import Foundation +import ComponentsObjC + +extension WKSourceEditorFormatterSubscript { + func toggleSubscriptFormatting(action: WKSourceEditorFormatterButtonAction, in textView: UITextView) { + toggleFormatting(startingFormattingString: "", endingFormattingString: "", action: action, in: textView) + } +} diff --git a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift index b01e9dfb207..b56fc1e78e7 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift @@ -18,12 +18,14 @@ fileprivate var needsTextKit2: Bool { let isItalics: Bool let isHorizontalTemplate: Bool let isStrikethrough: Bool - - init(isBold: Bool, isItalics: Bool, isHorizontalTemplate: Bool, isStrikethrough: Bool) { + let isSubscript: Bool + + init(isBold: Bool, isItalics: Bool, isHorizontalTemplate: Bool, isStrikethrough: Bool, isSubscript: Bool) { self.isBold = isBold self.isItalics = isItalics self.isHorizontalTemplate = isHorizontalTemplate self.isStrikethrough = isStrikethrough + self.isSubscript = isSubscript } } @@ -38,7 +40,8 @@ final class WKSourceEditorTextFrameworkMediator: NSObject { private(set) var boldItalicsFormatter: WKSourceEditorFormatterBoldItalics? private(set) var templateFormatter: WKSourceEditorFormatterTemplate? private(set) var strikethroughFormatter: WKSourceEditorFormatterStrikethrough? - + private(set) var subscriptFormatter: WKSourceEditorFormatterSubscript? + var isSyntaxHighlightingEnabled: Bool = true { didSet { updateColorsAndFonts() @@ -107,7 +110,8 @@ final class WKSourceEditorTextFrameworkMediator: NSObject { let boldItalicsFormatter = WKSourceEditorFormatterBoldItalics(colors: colors, fonts: fonts) let templateFormatter = WKSourceEditorFormatterTemplate(colors: colors, fonts: fonts) let strikethroughFormatter = WKSourceEditorFormatterStrikethrough(colors: colors, fonts: fonts) - + let subscriptFormatter = WKSourceEditorFormatterSubscript(colors: colors, fonts: fonts) + self.formatters = [WKSourceEditorFormatterBase(colors: colors, fonts: fonts, textAlignment: viewModel.textAlignment), templateFormatter, boldItalicsFormatter, @@ -115,7 +119,8 @@ final class WKSourceEditorTextFrameworkMediator: NSObject { self.boldItalicsFormatter = boldItalicsFormatter self.templateFormatter = templateFormatter self.strikethroughFormatter = strikethroughFormatter - + self.subscriptFormatter = subscriptFormatter + if needsTextKit2 { if #available(iOS 16.0, *) { let textContentManager = textView.textLayoutManager?.textContentManager @@ -154,26 +159,28 @@ final class WKSourceEditorTextFrameworkMediator: NSObject { if needsTextKit2 { guard let textKit2Data = textkit2SelectionData(selectedDocumentRange: selectedDocumentRange) else { - return WKSourceEditorSelectionState(isBold: false, isItalics: false, isHorizontalTemplate: false, isStrikethrough: false) + return WKSourceEditorSelectionState(isBold: false, isItalics: false, isHorizontalTemplate: false, isStrikethrough: false, isSubscript: 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 isStrikethrough = strikethroughFormatter?.attributedString(textKit2Data.paragraphAttributedString, isStrikethroughIn: textKit2Data.paragraphSelectedRange) ?? false - - return WKSourceEditorSelectionState(isBold: isBold, isItalics: isItalics, isHorizontalTemplate: isHorizontalTemplate, isStrikethrough: isStrikethrough) + let isSubscript = subscriptFormatter?.attributedString(textKit2Data.paragraphAttributedString, isSubscriptIn: textKit2Data.paragraphSelectedRange) ?? false + + return WKSourceEditorSelectionState(isBold: isBold, isItalics: isItalics, isHorizontalTemplate: isHorizontalTemplate, isStrikethrough: isStrikethrough, isSubscript: isSubscript) } else { guard let textKit1Storage else { - return WKSourceEditorSelectionState(isBold: false, isItalics: false, isHorizontalTemplate: false, isStrikethrough: false) + return WKSourceEditorSelectionState(isBold: false, isItalics: false, isHorizontalTemplate: false, isStrikethrough: false, isSubscript: 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 isStrikethrough = strikethroughFormatter?.attributedString(textKit1Storage, isStrikethroughIn: selectedDocumentRange) ?? false - - return WKSourceEditorSelectionState(isBold: isBold, isItalics: isItalics, isHorizontalTemplate: isHorizontalTemplate, isStrikethrough: isStrikethrough) + let isSubscript = subscriptFormatter?.attributedString(textKit1Storage, isSubscriptIn: selectedDocumentRange) ?? false + + return WKSourceEditorSelectionState(isBold: isBold, isItalics: isItalics, isHorizontalTemplate: isHorizontalTemplate, isStrikethrough: isStrikethrough, isSubscript: isSubscript) } } diff --git a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift index 1eb2abfc40c..e062d2a0bbc 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift @@ -363,7 +363,12 @@ extension WKSourceEditorViewController: WKEditorInputViewDelegate { let action: WKSourceEditorFormatterButtonAction = isSelected ? .remove : .add textFrameworkMediator.strikethroughFormatter?.toggleStrikethroughFormatting(action: action, in: textView) } - + + func didTapSubscript(isSelected: Bool) { + let action: WKSourceEditorFormatterButtonAction = isSelected ? .remove : .add + textFrameworkMediator.subscriptFormatter?.toggleSubscriptFormatting(action: action, in: textView) + } + func didTapClose() { inputViewType = nil let isRangeSelected = textView.selectedRange.length > 0 diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.h b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.h index 17c1b518b12..0f771588b3f 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.h +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.h @@ -4,6 +4,8 @@ NS_ASSUME_NONNULL_BEGIN @interface WKSourceEditorFormatterSubscript: WKSourceEditorFormatter +- (BOOL) attributedString:(NSMutableAttributedString *)attributedString isSubscriptInRange:(NSRange)range; + @end NS_ASSUME_NONNULL_END diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m index 2e007ef6716..a455db3e73e 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m @@ -1,6 +1,132 @@ #import "WKSourceEditorFormatterSubscript.h" +#import "WKSourceEditorColors.h" + +@interface WKSourceEditorFormatterSubscript () + +@property (nonatomic, strong) NSDictionary *subscriptAttributes; +@property (nonatomic, strong) NSDictionary *subscriptContentAttributes; +@property (nonatomic, strong) NSRegularExpression *subscriptRegex; + +@end @implementation WKSourceEditorFormatterSubscript +#pragma mark - Custom Attributed String Keys +NSString * const WKSourceEditorCustomKeyContentSubscript = @"WKSourceEditorCustomKeyContentSubscript"; + +- (instancetype)initWithColors:(WKSourceEditorColors *)colors fonts:(WKSourceEditorFonts *)fonts { + self = [super initWithColors:colors fonts:fonts]; + if (self) { + _subscriptAttributes = @{ + NSForegroundColorAttributeName: colors.greenForegroundColor, + WKSourceEditorCustomKeyColorGreen: [NSNumber numberWithBool:YES] + }; + + _subscriptContentAttributes = @{ + WKSourceEditorCustomKeyContentSubscript: [NSNumber numberWithBool:YES] + }; + + _subscriptRegex = [[NSRegularExpression alloc] initWithPattern:@"()(\\sub*.*?)(<\\/sub>)" options:0 error:nil]; + //()(.*?)() + + } + + return self; +} +- (void)addSyntaxHighlightingToAttributedString:(NSMutableAttributedString *)attributedString inRange:(NSRange)range { + + [attributedString removeAttribute:WKSourceEditorCustomKeyContentSubscript range:range]; + + [self.subscriptRegex enumerateMatchesInString:attributedString.string + options:0 + range:range + usingBlock:^(NSTextCheckingResult *_Nullable result, NSMatchingFlags flags, BOOL *_Nonnull stop) { + NSRange fullMatch = [result rangeAtIndex:0]; + NSRange openingRange = [result rangeAtIndex:1]; + NSRange contentRange = [result rangeAtIndex:2]; + NSRange closingRange = [result rangeAtIndex:3]; + + if (openingRange.location != NSNotFound) { + [attributedString addAttributes:self.subscriptAttributes range:openingRange]; + } + + if (contentRange.location != NSNotFound) { + [attributedString addAttributes:self.subscriptContentAttributes range:contentRange]; + } + + if (closingRange.location != NSNotFound) { + [attributedString addAttributes:self.subscriptAttributes range:closingRange]; + } + }]; +} + +- (void)updateFonts:(WKSourceEditorFonts *)fonts inAttributedString:(NSMutableAttributedString *)attributedString inRange:(NSRange)range { + // No special font handling needed for references +} + +- (void)updateColors:(WKSourceEditorColors *)colors inAttributedString:(NSMutableAttributedString *)attributedString inRange:(NSRange)range { + + NSMutableDictionary *mutAttributes = [[NSMutableDictionary alloc] initWithDictionary:self.subscriptAttributes]; + [mutAttributes setObject:colors.greenForegroundColor forKey:NSForegroundColorAttributeName]; + self.subscriptAttributes = [[NSDictionary alloc] initWithDictionary:mutAttributes]; + + [attributedString enumerateAttribute:WKSourceEditorCustomKeyColorGreen + inRange:range + options:nil + usingBlock:^(id value, NSRange localRange, BOOL *stop) { + if ([value isKindOfClass: [NSNumber class]]) { + NSNumber *numValue = (NSNumber *)value; + if ([numValue boolValue] == YES) { + [attributedString addAttributes:self.subscriptAttributes range:localRange]; + } + } + }]; + +} + +#pragma mark - Public + +- (BOOL)attributedString:(nonnull NSMutableAttributedString *)attributedString isSubscriptInRange:(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[WKSourceEditorCustomKeyContentSubscript] != nil) { + isContentKey = YES; + } else { + // Edge case, check previous character if we are up against closing string + if (attrs[WKSourceEditorCustomKeyColorGreen]) { + attrs = [attributedString attributesAtIndex:range.location - 1 effectiveRange:nil]; + if (attrs[WKSourceEditorCustomKeyContentSubscript] != 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[WKSourceEditorCustomKeyContentSubscript] != nil) { + if (unionRange.location == NSNotFound) { + unionRange = loopRange; + } else { + unionRange = NSUnionRange(unionRange, loopRange); + } + stop = YES; + } + }]; + + if (NSEqualRanges(unionRange, range)) { + isContentKey = YES; + } + } + + return isContentKey; +} + @end diff --git a/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift index 35d8f3b5a64..55183ac1549 100644 --- a/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift +++ b/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift @@ -132,4 +132,7 @@ final class WKSourceEditorFormatterButtonActionTests: XCTestCase { mediator.strikethroughFormatter?.toggleStrikethroughFormatting(action: .remove, in: mediator.textView) XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four") } + + func testSubscriptInsertAndRemove() throws { + } } diff --git a/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift index a3764296794..8288c36950e 100644 --- a/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift +++ b/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift @@ -11,8 +11,9 @@ final class WKSourceEditorFormatterTests: XCTestCase { var boldItalicsFormatter: WKSourceEditorFormatterBoldItalics! var templateFormatter: WKSourceEditorFormatterTemplate! var strikethroughFormatter: WKSourceEditorFormatterStrikethrough! + var subscriptFormatter: WKSourceEditorFormatterSubscript! var formatters: [WKSourceEditorFormatter] { - return [baseFormatter, templateFormatter, boldItalicsFormatter, strikethroughFormatter] + return [baseFormatter, templateFormatter, boldItalicsFormatter, strikethroughFormatter, subscriptFormatter] } override func setUpWithError() throws { @@ -34,6 +35,7 @@ final class WKSourceEditorFormatterTests: XCTestCase { self.boldItalicsFormatter = WKSourceEditorFormatterBoldItalics(colors: colors, fonts: fonts) self.templateFormatter = WKSourceEditorFormatterTemplate(colors: colors, fonts: fonts) self.strikethroughFormatter = WKSourceEditorFormatterStrikethrough(colors: colors, fonts: fonts) + self.subscriptFormatter = WKSourceEditorFormatterSubscript(colors: colors, fonts: fonts) } override func tearDownWithError() throws { @@ -766,4 +768,8 @@ final class WKSourceEditorFormatterTests: XCTestCase { XCTAssertEqual(base2Attributes[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") XCTAssertEqual(base2Attributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect base formatting") } + + func testSubscript() { + + } } diff --git a/Wikipedia/Code/ExploreViewController.swift b/Wikipedia/Code/ExploreViewController.swift index d8e734ab223..78b73f995e9 100644 --- a/Wikipedia/Code/ExploreViewController.swift +++ b/Wikipedia/Code/ExploreViewController.swift @@ -60,9 +60,9 @@ class ExploreViewController: ColumnarCollectionViewController, ExploreCardViewCo detailTransitionSourceRect = nil logFeedImpressionAfterDelay() dataStore.remoteNotificationsController.loadNotifications(force: false) - #if UITEST +// #if UITEST presentUITestHelperController() - #endif +// #endif } override func viewWillHaveFirstAppearance(_ animated: Bool) { From 2ac0613d6ad20295bb0da1a4b52c5c6ec6979cf4 Mon Sep 17 00:00:00 2001 From: mazevedo Date: Wed, 3 Jan 2024 20:53:22 -0300 Subject: [PATCH 19/36] Fix selection when tag exists --- .../Source Editor/WKSourceEditorTextFrameworkMediator.swift | 3 ++- 1 file changed, 2 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 b56fc1e78e7..512b228ae6b 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift @@ -115,7 +115,8 @@ final class WKSourceEditorTextFrameworkMediator: NSObject { self.formatters = [WKSourceEditorFormatterBase(colors: colors, fonts: fonts, textAlignment: viewModel.textAlignment), templateFormatter, boldItalicsFormatter, - strikethroughFormatter] + strikethroughFormatter, + subscriptFormatter] self.boldItalicsFormatter = boldItalicsFormatter self.templateFormatter = templateFormatter self.strikethroughFormatter = strikethroughFormatter From 8d219f62e6b161d21829264b75f220fe7a680aa7 Mon Sep 17 00:00:00 2001 From: mazevedo Date: Wed, 3 Jan 2024 20:56:32 -0300 Subject: [PATCH 20/36] Undo test code --- .../Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m | 2 +- Wikipedia/Code/ExploreViewController.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m index a455db3e73e..8244a7727ba 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m @@ -33,7 +33,7 @@ - (instancetype)initWithColors:(WKSourceEditorColors *)colors fonts:(WKSourceEdi return self; } -- (void)addSyntaxHighlightingToAttributedString:(NSMutableAttributedString *)attributedString inRange:(NSRange)range { +- (void)addSyntaxHighlightingToAttributedString:(nonnull NSMutableAttributedString *)attributedString inRange:(NSRange)range { [attributedString removeAttribute:WKSourceEditorCustomKeyContentSubscript range:range]; diff --git a/Wikipedia/Code/ExploreViewController.swift b/Wikipedia/Code/ExploreViewController.swift index 78b73f995e9..d8e734ab223 100644 --- a/Wikipedia/Code/ExploreViewController.swift +++ b/Wikipedia/Code/ExploreViewController.swift @@ -60,9 +60,9 @@ class ExploreViewController: ColumnarCollectionViewController, ExploreCardViewCo detailTransitionSourceRect = nil logFeedImpressionAfterDelay() dataStore.remoteNotificationsController.loadNotifications(force: false) -// #if UITEST + #if UITEST presentUITestHelperController() -// #endif + #endif } override func viewWillHaveFirstAppearance(_ animated: Bool) { From a65b276c2d54260abc1e40839e0312d54b888cd6 Mon Sep 17 00:00:00 2001 From: mazevedo Date: Wed, 3 Jan 2024 21:08:11 -0300 Subject: [PATCH 21/36] Fix subscript regex --- .../Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m index 8244a7727ba..0aa179f0526 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m @@ -26,8 +26,7 @@ - (instancetype)initWithColors:(WKSourceEditorColors *)colors fonts:(WKSourceEdi WKSourceEditorCustomKeyContentSubscript: [NSNumber numberWithBool:YES] }; - _subscriptRegex = [[NSRegularExpression alloc] initWithPattern:@"()(\\sub*.*?)(<\\/sub>)" options:0 error:nil]; - //()(.*?)() + _subscriptRegex = [[NSRegularExpression alloc] initWithPattern:@"()(\\s*u*b*.*?)(<\\/sub>)" options:0 error:nil]; } From 294562d93382ddf755c390cfd9b8635b14ddae75 Mon Sep 17 00:00:00 2001 From: mazevedo Date: Thu, 4 Jan 2024 12:51:46 -0300 Subject: [PATCH 22/36] Add superscript formatting --- .../WKEditorToolbarGroupedView.swift | 2 + .../WKEditorInputViewController.swift | 1 + .../WKSourceEditorFormatterSuperscript.swift | 8 ++ .../WKSourceEditorTextFrameworkMediator.swift | 20 ++- .../WKSourceEditorViewController.swift | 5 + .../WKSourceEditorFormatterSubscript.m | 2 - .../WKSourceEditorFormatterSuperscript.h | 1 + .../WKSourceEditorFormatterSuperscript.m | 121 ++++++++++++++++++ ...urceEditorFormatterButtonActionTests.swift | 3 + .../WKSourceEditorFormatterTests.swift | 8 +- 10 files changed, 162 insertions(+), 9 deletions(-) create mode 100644 Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterSuperscript.swift 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 8165daacbce..4bfa037ee5e 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 @@ -64,6 +64,7 @@ class WKEditorToolbarGroupedView: WKEditorToolbarView { strikethroughButton.isSelected = selectionState.isStrikethrough subscriptButton.isSelected = selectionState.isSubscript + superscriptButton.isSelected = selectionState.isSuperscript } // MARK: - Button Actions @@ -81,6 +82,7 @@ class WKEditorToolbarGroupedView: WKEditorToolbarView { } @objc private func tappedSuperscript() { + delegate?.didTapSuperscript(isSelected: superscriptButton.isSelected) } @objc private func tappedSubscript() { 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 d3d940fa896..13f92df1f16 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 @@ -8,6 +8,7 @@ protocol WKEditorInputViewDelegate: AnyObject { func didTapTemplate(isSelected: Bool) func didTapStrikethrough(isSelected: Bool) func didTapSubscript(isSelected: Bool) + func didTapSuperscript(isSelected: Bool) } class WKEditorInputViewController: WKComponentViewController { diff --git a/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterSuperscript.swift b/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterSuperscript.swift new file mode 100644 index 00000000000..86ad1715c81 --- /dev/null +++ b/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterSuperscript.swift @@ -0,0 +1,8 @@ +import Foundation +import ComponentsObjC + +extension WKSourceEditorFormatterSuperscript { + func toggleSuperscriptFormatting(action: WKSourceEditorFormatterButtonAction, in textView: UITextView) { + toggleFormatting(startingFormattingString: "", endingFormattingString: "", action: action, in: textView) + } +} diff --git a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift index 512b228ae6b..4e27487d3a5 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift @@ -19,13 +19,15 @@ fileprivate var needsTextKit2: Bool { let isHorizontalTemplate: Bool let isStrikethrough: Bool let isSubscript: Bool + let isSuperscript: Bool - init(isBold: Bool, isItalics: Bool, isHorizontalTemplate: Bool, isStrikethrough: Bool, isSubscript: Bool) { + init(isBold: Bool, isItalics: Bool, isHorizontalTemplate: Bool, isStrikethrough: Bool, isSubscript: Bool, isSuperscript: Bool) { self.isBold = isBold self.isItalics = isItalics self.isHorizontalTemplate = isHorizontalTemplate self.isStrikethrough = isStrikethrough self.isSubscript = isSubscript + self.isSuperscript = isSuperscript } } @@ -41,6 +43,7 @@ final class WKSourceEditorTextFrameworkMediator: NSObject { private(set) var templateFormatter: WKSourceEditorFormatterTemplate? private(set) var strikethroughFormatter: WKSourceEditorFormatterStrikethrough? private(set) var subscriptFormatter: WKSourceEditorFormatterSubscript? + private(set) var superscriptFormatter: WKSourceEditorFormatterSuperscript? var isSyntaxHighlightingEnabled: Bool = true { didSet { @@ -111,16 +114,19 @@ final class WKSourceEditorTextFrameworkMediator: NSObject { let templateFormatter = WKSourceEditorFormatterTemplate(colors: colors, fonts: fonts) let strikethroughFormatter = WKSourceEditorFormatterStrikethrough(colors: colors, fonts: fonts) let subscriptFormatter = WKSourceEditorFormatterSubscript(colors: colors, fonts: fonts) + let superscriptFormatter = WKSourceEditorFormatterSuperscript(colors: colors, fonts: fonts) self.formatters = [WKSourceEditorFormatterBase(colors: colors, fonts: fonts, textAlignment: viewModel.textAlignment), templateFormatter, boldItalicsFormatter, strikethroughFormatter, - subscriptFormatter] + subscriptFormatter, + superscriptFormatter] self.boldItalicsFormatter = boldItalicsFormatter self.templateFormatter = templateFormatter self.strikethroughFormatter = strikethroughFormatter self.subscriptFormatter = subscriptFormatter + self.superscriptFormatter = superscriptFormatter if needsTextKit2 { if #available(iOS 16.0, *) { @@ -160,7 +166,7 @@ final class WKSourceEditorTextFrameworkMediator: NSObject { if needsTextKit2 { guard let textKit2Data = textkit2SelectionData(selectedDocumentRange: selectedDocumentRange) else { - return WKSourceEditorSelectionState(isBold: false, isItalics: false, isHorizontalTemplate: false, isStrikethrough: false, isSubscript: false) + return WKSourceEditorSelectionState(isBold: false, isItalics: false, isHorizontalTemplate: false, isStrikethrough: false, isSubscript: false, isSuperscript: false) } let isBold = boldItalicsFormatter?.attributedString(textKit2Data.paragraphAttributedString, isBoldIn: textKit2Data.paragraphSelectedRange) ?? false @@ -168,11 +174,12 @@ final class WKSourceEditorTextFrameworkMediator: NSObject { let isHorizontalTemplate = templateFormatter?.attributedString(textKit2Data.paragraphAttributedString, isHorizontalTemplateIn: textKit2Data.paragraphSelectedRange) ?? false let isStrikethrough = strikethroughFormatter?.attributedString(textKit2Data.paragraphAttributedString, isStrikethroughIn: textKit2Data.paragraphSelectedRange) ?? false let isSubscript = subscriptFormatter?.attributedString(textKit2Data.paragraphAttributedString, isSubscriptIn: textKit2Data.paragraphSelectedRange) ?? false + let isSuperscript = superscriptFormatter?.attributedString(textKit2Data.paragraphAttributedString, isSuperscriptIn: textKit2Data.paragraphSelectedRange) ?? false - return WKSourceEditorSelectionState(isBold: isBold, isItalics: isItalics, isHorizontalTemplate: isHorizontalTemplate, isStrikethrough: isStrikethrough, isSubscript: isSubscript) + return WKSourceEditorSelectionState(isBold: isBold, isItalics: isItalics, isHorizontalTemplate: isHorizontalTemplate, isStrikethrough: isStrikethrough, isSubscript: isSubscript, isSuperscript: isSuperscript) } else { guard let textKit1Storage else { - return WKSourceEditorSelectionState(isBold: false, isItalics: false, isHorizontalTemplate: false, isStrikethrough: false, isSubscript: false) + return WKSourceEditorSelectionState(isBold: false, isItalics: false, isHorizontalTemplate: false, isStrikethrough: false, isSubscript: false, isSuperscript: false) } let isBold = boldItalicsFormatter?.attributedString(textKit1Storage, isBoldIn: selectedDocumentRange) ?? false @@ -180,8 +187,9 @@ final class WKSourceEditorTextFrameworkMediator: NSObject { let isHorizontalTemplate = templateFormatter?.attributedString(textKit1Storage, isHorizontalTemplateIn: selectedDocumentRange) ?? false let isStrikethrough = strikethroughFormatter?.attributedString(textKit1Storage, isStrikethroughIn: selectedDocumentRange) ?? false let isSubscript = subscriptFormatter?.attributedString(textKit1Storage, isSubscriptIn: selectedDocumentRange) ?? false + let isSuperscript = superscriptFormatter?.attributedString(textKit1Storage, isSuperscriptIn: selectedDocumentRange) ?? false - return WKSourceEditorSelectionState(isBold: isBold, isItalics: isItalics, isHorizontalTemplate: isHorizontalTemplate, isStrikethrough: isStrikethrough, isSubscript: isSubscript) + return WKSourceEditorSelectionState(isBold: isBold, isItalics: isItalics, isHorizontalTemplate: isHorizontalTemplate, isStrikethrough: isStrikethrough, isSubscript: isSubscript, isSuperscript: isSuperscript) } } diff --git a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift index e062d2a0bbc..436dab3ec68 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift @@ -369,6 +369,11 @@ extension WKSourceEditorViewController: WKEditorInputViewDelegate { textFrameworkMediator.subscriptFormatter?.toggleSubscriptFormatting(action: action, in: textView) } + func didTapSuperscript(isSelected: Bool) { + let action: WKSourceEditorFormatterButtonAction = isSelected ? .remove : .add + textFrameworkMediator.superscriptFormatter?.toggleSuperscriptFormatting(action: action, in: textView) + } + func didTapClose() { inputViewType = nil let isRangeSelected = textView.selectedRange.length > 0 diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m index 0aa179f0526..79f65520b56 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m @@ -60,7 +60,6 @@ - (void)addSyntaxHighlightingToAttributedString:(nonnull NSMutableAttributedStri } - (void)updateFonts:(WKSourceEditorFonts *)fonts inAttributedString:(NSMutableAttributedString *)attributedString inRange:(NSRange)range { - // No special font handling needed for references } - (void)updateColors:(WKSourceEditorColors *)colors inAttributedString:(NSMutableAttributedString *)attributedString inRange:(NSRange)range { @@ -96,7 +95,6 @@ - (BOOL)attributedString:(nonnull NSMutableAttributedString *)attributedString i if (attrs[WKSourceEditorCustomKeyContentSubscript] != nil) { isContentKey = YES; } else { - // Edge case, check previous character if we are up against closing string if (attrs[WKSourceEditorCustomKeyColorGreen]) { attrs = [attributedString attributesAtIndex:range.location - 1 effectiveRange:nil]; if (attrs[WKSourceEditorCustomKeyContentSubscript] != nil) { diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.h b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.h index 8deed9d1f48..9e1dcb9788d 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.h +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.h @@ -4,6 +4,7 @@ NS_ASSUME_NONNULL_BEGIN @interface WKSourceEditorFormatterSuperscript : WKSourceEditorFormatter +- (BOOL) attributedString:(NSMutableAttributedString *)attributedString isSuperscriptInRange:(NSRange)range; @end diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.m index 54d013dfda3..27d78f1587d 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.m @@ -1,5 +1,126 @@ #import "WKSourceEditorFormatterSuperscript.h" +#import "WKSourceEditorColors.h" + +@interface WKSourceEditorFormatterSuperscript () + +@property (nonatomic, strong) NSDictionary *superscripttAttributes; +@property (nonatomic, strong) NSDictionary *superscriptContentAttributes; +@property (nonatomic, strong) NSRegularExpression *superscriptRegex; + +@end @implementation WKSourceEditorFormatterSuperscript +#pragma mark - Custom Attributed String Keys +NSString * const WKSourceEditorCustomKeyContentSuperscript = @"WKSourceEditorCustomKeyContentSuperscript"; + +- (instancetype)initWithColors:(WKSourceEditorColors *)colors fonts:(WKSourceEditorFonts *)fonts { + self = [super initWithColors:colors fonts:fonts]; + if (self) { + _superscripttAttributes = @{ + NSForegroundColorAttributeName: colors.greenForegroundColor, + WKSourceEditorCustomKeyColorGreen: [NSNumber numberWithBool:YES] + }; + + _superscriptContentAttributes = @{ + WKSourceEditorCustomKeyContentSuperscript: [NSNumber numberWithBool:YES] + }; + + _superscriptRegex = [[NSRegularExpression alloc] initWithPattern:@"()(\\s*u*p*.*?)(<\\/sup>)" options:0 error:nil]; + } + + return self; +} +- (void)addSyntaxHighlightingToAttributedString:(nonnull NSMutableAttributedString *)attributedString inRange:(NSRange)range { + + [attributedString removeAttribute:WKSourceEditorCustomKeyContentSuperscript range:range]; + + [self.superscriptRegex enumerateMatchesInString:attributedString.string + options:0 + range:range + usingBlock:^(NSTextCheckingResult *_Nullable result, NSMatchingFlags flags, BOOL *_Nonnull stop) { + NSRange fullMatch = [result rangeAtIndex:0]; + NSRange openingRange = [result rangeAtIndex:1]; + NSRange contentRange = [result rangeAtIndex:2]; + NSRange closingRange = [result rangeAtIndex:3]; + + if (openingRange.location != NSNotFound) { + [attributedString addAttributes:self.superscripttAttributes range:openingRange]; + } + + if (contentRange.location != NSNotFound) { + [attributedString addAttributes:self.superscriptContentAttributes range:contentRange]; + } + + if (closingRange.location != NSNotFound) { + [attributedString addAttributes:self.superscripttAttributes range:closingRange]; + } + }]; +} + +- (void)updateFonts:(WKSourceEditorFonts *)fonts inAttributedString:(NSMutableAttributedString *)attributedString inRange:(NSRange)range { +} + +- (void)updateColors:(WKSourceEditorColors *)colors inAttributedString:(NSMutableAttributedString *)attributedString inRange:(NSRange)range { + + NSMutableDictionary *mutAttributes = [[NSMutableDictionary alloc] initWithDictionary:self.superscripttAttributes]; + [mutAttributes setObject:colors.greenForegroundColor forKey:NSForegroundColorAttributeName]; + self.superscripttAttributes = [[NSDictionary alloc] initWithDictionary:mutAttributes]; + + [attributedString enumerateAttribute:WKSourceEditorCustomKeyColorGreen + inRange:range + options:nil + usingBlock:^(id value, NSRange localRange, BOOL *stop) { + if ([value isKindOfClass: [NSNumber class]]) { + NSNumber *numValue = (NSNumber *)value; + if ([numValue boolValue] == YES) { + [attributedString addAttributes:self.superscripttAttributes range:localRange]; + } + } + }]; + +} + +#pragma mark - Public + +- (BOOL)attributedString:(nonnull NSMutableAttributedString *)attributedString isSuperscriptInRange:(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[WKSourceEditorCustomKeyContentSuperscript] != nil) { + isContentKey = YES; + } else { + if (attrs[WKSourceEditorCustomKeyColorGreen]) { + attrs = [attributedString attributesAtIndex:range.location - 1 effectiveRange:nil]; + if (attrs[WKSourceEditorCustomKeyContentSuperscript] != 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[WKSourceEditorCustomKeyContentSuperscript] != nil) { + if (unionRange.location == NSNotFound) { + unionRange = loopRange; + } else { + unionRange = NSUnionRange(unionRange, loopRange); + } + stop = YES; + } + }]; + + if (NSEqualRanges(unionRange, range)) { + isContentKey = YES; + } + } + return isContentKey; +} + @end diff --git a/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift index 55183ac1549..0ab1f13ace8 100644 --- a/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift +++ b/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift @@ -135,4 +135,7 @@ final class WKSourceEditorFormatterButtonActionTests: XCTestCase { func testSubscriptInsertAndRemove() throws { } + + func testSuperscriptInsertAndRemove() throws { + } } diff --git a/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift index 8288c36950e..a0eb059b3ec 100644 --- a/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift +++ b/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift @@ -12,8 +12,9 @@ final class WKSourceEditorFormatterTests: XCTestCase { var templateFormatter: WKSourceEditorFormatterTemplate! var strikethroughFormatter: WKSourceEditorFormatterStrikethrough! var subscriptFormatter: WKSourceEditorFormatterSubscript! + var superscriptFormatter: WKSourceEditorFormatterSuperscript! var formatters: [WKSourceEditorFormatter] { - return [baseFormatter, templateFormatter, boldItalicsFormatter, strikethroughFormatter, subscriptFormatter] + return [baseFormatter, templateFormatter, boldItalicsFormatter, strikethroughFormatter, subscriptFormatter, superscriptFormatter] } override func setUpWithError() throws { @@ -36,6 +37,7 @@ final class WKSourceEditorFormatterTests: XCTestCase { self.templateFormatter = WKSourceEditorFormatterTemplate(colors: colors, fonts: fonts) self.strikethroughFormatter = WKSourceEditorFormatterStrikethrough(colors: colors, fonts: fonts) self.subscriptFormatter = WKSourceEditorFormatterSubscript(colors: colors, fonts: fonts) + self.superscriptFormatter = WKSourceEditorFormatterSuperscript(colors: colors, fonts: fonts) } override func tearDownWithError() throws { @@ -772,4 +774,8 @@ final class WKSourceEditorFormatterTests: XCTestCase { func testSubscript() { } + + func testSuperscript() { + + } } From 73e0e835c717d5756238b055bac3f84e9cea0567 Mon Sep 17 00:00:00 2001 From: mazevedo Date: Thu, 4 Jan 2024 15:38:29 -0300 Subject: [PATCH 23/36] Add underline formatter --- .../WKEditorToolbarGroupedView.swift | 1 + .../WKEditorInputViewController.swift | 1 + .../WKSourceEditorFormatterUnderline.swift | 8 ++ .../WKSourceEditorTextFrameworkMediator.swift | 22 +++- .../WKSourceEditorViewController.swift | 5 + .../WKSourceEditorFormatterSuperscript.m | 14 +- .../WKSourceEditorFormatterUnderline.h | 2 + .../WKSourceEditorFormatterUnderline.m | 121 ++++++++++++++++++ ...urceEditorFormatterButtonActionTests.swift | 3 + .../WKSourceEditorFormatterTests.swift | 8 +- 10 files changed, 170 insertions(+), 15 deletions(-) create mode 100644 Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterUnderline.swift 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 4bfa037ee5e..55886b64d5b 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 @@ -90,6 +90,7 @@ class WKEditorToolbarGroupedView: WKEditorToolbarView { } @objc private func tappedUnderline() { + delegate?.didTapUnderline(isSelected: underlineButton.isSelected) } @objc private func tappedStrikethrough() { 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 13f92df1f16..fd358215d55 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 @@ -7,6 +7,7 @@ protocol WKEditorInputViewDelegate: AnyObject { func didTapItalics(isSelected: Bool) func didTapTemplate(isSelected: Bool) func didTapStrikethrough(isSelected: Bool) + func didTapUnderline(isSelected: Bool) func didTapSubscript(isSelected: Bool) func didTapSuperscript(isSelected: Bool) } diff --git a/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterUnderline.swift b/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterUnderline.swift new file mode 100644 index 00000000000..9e91ebe66d3 --- /dev/null +++ b/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterUnderline.swift @@ -0,0 +1,8 @@ +import Foundation +import ComponentsObjC + +extension WKSourceEditorFormatterUnderline { + func toggleUnderlineFormatting(action: WKSourceEditorFormatterButtonAction, in textView: UITextView) { + toggleFormatting(startingFormattingString: "", endingFormattingString: "", action: action, in: textView) + } +} diff --git a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift index 4e27487d3a5..41b1c634762 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift @@ -18,14 +18,16 @@ fileprivate var needsTextKit2: Bool { let isItalics: Bool let isHorizontalTemplate: Bool let isStrikethrough: Bool + let isUnderline: Bool let isSubscript: Bool let isSuperscript: Bool - init(isBold: Bool, isItalics: Bool, isHorizontalTemplate: Bool, isStrikethrough: Bool, isSubscript: Bool, isSuperscript: Bool) { + init(isBold: Bool, isItalics: Bool, isHorizontalTemplate: Bool, isStrikethrough: Bool, isUnderline: Bool, isSubscript: Bool, isSuperscript: Bool) { self.isBold = isBold self.isItalics = isItalics self.isHorizontalTemplate = isHorizontalTemplate self.isStrikethrough = isStrikethrough + self.isUnderline = isUnderline self.isSubscript = isSubscript self.isSuperscript = isSuperscript } @@ -42,6 +44,7 @@ final class WKSourceEditorTextFrameworkMediator: NSObject { private(set) var boldItalicsFormatter: WKSourceEditorFormatterBoldItalics? private(set) var templateFormatter: WKSourceEditorFormatterTemplate? private(set) var strikethroughFormatter: WKSourceEditorFormatterStrikethrough? + private(set) var underlineFormatter: WKSourceEditorFormatterUnderline? private(set) var subscriptFormatter: WKSourceEditorFormatterSubscript? private(set) var superscriptFormatter: WKSourceEditorFormatterSuperscript? @@ -102,7 +105,7 @@ final class WKSourceEditorTextFrameworkMediator: NSObject { textKit1Storage?.storageDelegate = self } } - + // MARK: Internal func updateColorsAndFonts() { @@ -113,6 +116,7 @@ final class WKSourceEditorTextFrameworkMediator: NSObject { let boldItalicsFormatter = WKSourceEditorFormatterBoldItalics(colors: colors, fonts: fonts) let templateFormatter = WKSourceEditorFormatterTemplate(colors: colors, fonts: fonts) let strikethroughFormatter = WKSourceEditorFormatterStrikethrough(colors: colors, fonts: fonts) + let underlineFormatter = WKSourceEditorFormatterUnderline(colors: colors, fonts: fonts) let subscriptFormatter = WKSourceEditorFormatterSubscript(colors: colors, fonts: fonts) let superscriptFormatter = WKSourceEditorFormatterSuperscript(colors: colors, fonts: fonts) @@ -121,12 +125,14 @@ final class WKSourceEditorTextFrameworkMediator: NSObject { boldItalicsFormatter, strikethroughFormatter, subscriptFormatter, - superscriptFormatter] + superscriptFormatter, + underlineFormatter] self.boldItalicsFormatter = boldItalicsFormatter self.templateFormatter = templateFormatter self.strikethroughFormatter = strikethroughFormatter self.subscriptFormatter = subscriptFormatter self.superscriptFormatter = superscriptFormatter + self.underlineFormatter = underlineFormatter if needsTextKit2 { if #available(iOS 16.0, *) { @@ -166,7 +172,7 @@ final class WKSourceEditorTextFrameworkMediator: NSObject { if needsTextKit2 { guard let textKit2Data = textkit2SelectionData(selectedDocumentRange: selectedDocumentRange) else { - return WKSourceEditorSelectionState(isBold: false, isItalics: false, isHorizontalTemplate: false, isStrikethrough: false, isSubscript: false, isSuperscript: false) + return WKSourceEditorSelectionState(isBold: false, isItalics: false, isHorizontalTemplate: false, isStrikethrough: false, isUnderline: false, isSubscript: false, isSuperscript: false) } let isBold = boldItalicsFormatter?.attributedString(textKit2Data.paragraphAttributedString, isBoldIn: textKit2Data.paragraphSelectedRange) ?? false @@ -175,11 +181,12 @@ final class WKSourceEditorTextFrameworkMediator: NSObject { let isStrikethrough = strikethroughFormatter?.attributedString(textKit2Data.paragraphAttributedString, isStrikethroughIn: textKit2Data.paragraphSelectedRange) ?? false let isSubscript = subscriptFormatter?.attributedString(textKit2Data.paragraphAttributedString, isSubscriptIn: textKit2Data.paragraphSelectedRange) ?? false let isSuperscript = superscriptFormatter?.attributedString(textKit2Data.paragraphAttributedString, isSuperscriptIn: textKit2Data.paragraphSelectedRange) ?? false + let isUnderline = underlineFormatter?.attributedString(textKit2Data.paragraphAttributedString, isUnderlineIn: textKit2Data.paragraphSelectedRange) ?? false - return WKSourceEditorSelectionState(isBold: isBold, isItalics: isItalics, isHorizontalTemplate: isHorizontalTemplate, isStrikethrough: isStrikethrough, isSubscript: isSubscript, isSuperscript: isSuperscript) + return WKSourceEditorSelectionState(isBold: isBold, isItalics: isItalics, isHorizontalTemplate: isHorizontalTemplate, isStrikethrough: isStrikethrough, isUnderline: isUnderline, isSubscript: isSubscript, isSuperscript: isSuperscript) } else { guard let textKit1Storage else { - return WKSourceEditorSelectionState(isBold: false, isItalics: false, isHorizontalTemplate: false, isStrikethrough: false, isSubscript: false, isSuperscript: false) + return WKSourceEditorSelectionState(isBold: false, isItalics: false, isHorizontalTemplate: false, isStrikethrough: false, isUnderline: false, isSubscript: false, isSuperscript: false) } let isBold = boldItalicsFormatter?.attributedString(textKit1Storage, isBoldIn: selectedDocumentRange) ?? false @@ -188,8 +195,9 @@ final class WKSourceEditorTextFrameworkMediator: NSObject { let isStrikethrough = strikethroughFormatter?.attributedString(textKit1Storage, isStrikethroughIn: selectedDocumentRange) ?? false let isSubscript = subscriptFormatter?.attributedString(textKit1Storage, isSubscriptIn: selectedDocumentRange) ?? false let isSuperscript = superscriptFormatter?.attributedString(textKit1Storage, isSuperscriptIn: selectedDocumentRange) ?? false + let isUnderline = superscriptFormatter?.attributedString(textKit1Storage, isSuperscriptIn: selectedDocumentRange) ?? false - return WKSourceEditorSelectionState(isBold: isBold, isItalics: isItalics, isHorizontalTemplate: isHorizontalTemplate, isStrikethrough: isStrikethrough, isSubscript: isSubscript, isSuperscript: isSuperscript) + return WKSourceEditorSelectionState(isBold: isBold, isItalics: isItalics, isHorizontalTemplate: isHorizontalTemplate, isStrikethrough: isStrikethrough, isUnderline: isUnderline, isSubscript: isSubscript, isSuperscript: isSuperscript) } } diff --git a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift index 436dab3ec68..b7ba3c05a96 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift @@ -364,6 +364,11 @@ extension WKSourceEditorViewController: WKEditorInputViewDelegate { textFrameworkMediator.strikethroughFormatter?.toggleStrikethroughFormatting(action: action, in: textView) } + func didTapUnderline(isSelected: Bool) { + let action: WKSourceEditorFormatterButtonAction = isSelected ? .remove : .add + textFrameworkMediator.underlineFormatter?.toggleUnderlineFormatting(action: action, in: textView) + } + func didTapSubscript(isSelected: Bool) { let action: WKSourceEditorFormatterButtonAction = isSelected ? .remove : .add textFrameworkMediator.subscriptFormatter?.toggleSubscriptFormatting(action: action, in: textView) diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.m index 27d78f1587d..1cacf9c191e 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.m @@ -3,7 +3,7 @@ @interface WKSourceEditorFormatterSuperscript () -@property (nonatomic, strong) NSDictionary *superscripttAttributes; +@property (nonatomic, strong) NSDictionary *superscriptAttributes; @property (nonatomic, strong) NSDictionary *superscriptContentAttributes; @property (nonatomic, strong) NSRegularExpression *superscriptRegex; @@ -17,7 +17,7 @@ @implementation WKSourceEditorFormatterSuperscript - (instancetype)initWithColors:(WKSourceEditorColors *)colors fonts:(WKSourceEditorFonts *)fonts { self = [super initWithColors:colors fonts:fonts]; if (self) { - _superscripttAttributes = @{ + _superscriptAttributes = @{ NSForegroundColorAttributeName: colors.greenForegroundColor, WKSourceEditorCustomKeyColorGreen: [NSNumber numberWithBool:YES] }; @@ -45,7 +45,7 @@ - (void)addSyntaxHighlightingToAttributedString:(nonnull NSMutableAttributedStri NSRange closingRange = [result rangeAtIndex:3]; if (openingRange.location != NSNotFound) { - [attributedString addAttributes:self.superscripttAttributes range:openingRange]; + [attributedString addAttributes:self.superscriptAttributes range:openingRange]; } if (contentRange.location != NSNotFound) { @@ -53,7 +53,7 @@ - (void)addSyntaxHighlightingToAttributedString:(nonnull NSMutableAttributedStri } if (closingRange.location != NSNotFound) { - [attributedString addAttributes:self.superscripttAttributes range:closingRange]; + [attributedString addAttributes:self.superscriptAttributes range:closingRange]; } }]; } @@ -63,9 +63,9 @@ - (void)updateFonts:(WKSourceEditorFonts *)fonts inAttributedString:(NSMutableAt - (void)updateColors:(WKSourceEditorColors *)colors inAttributedString:(NSMutableAttributedString *)attributedString inRange:(NSRange)range { - NSMutableDictionary *mutAttributes = [[NSMutableDictionary alloc] initWithDictionary:self.superscripttAttributes]; + NSMutableDictionary *mutAttributes = [[NSMutableDictionary alloc] initWithDictionary:self.superscriptAttributes]; [mutAttributes setObject:colors.greenForegroundColor forKey:NSForegroundColorAttributeName]; - self.superscripttAttributes = [[NSDictionary alloc] initWithDictionary:mutAttributes]; + self.superscriptAttributes = [[NSDictionary alloc] initWithDictionary:mutAttributes]; [attributedString enumerateAttribute:WKSourceEditorCustomKeyColorGreen inRange:range @@ -74,7 +74,7 @@ - (void)updateColors:(WKSourceEditorColors *)colors inAttributedString:(NSMutabl if ([value isKindOfClass: [NSNumber class]]) { NSNumber *numValue = (NSNumber *)value; if ([numValue boolValue] == YES) { - [attributedString addAttributes:self.superscripttAttributes range:localRange]; + [attributedString addAttributes:self.superscriptAttributes range:localRange]; } } }]; diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterUnderline.h b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterUnderline.h index 7921e57a693..5960c0a0821 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterUnderline.h +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterUnderline.h @@ -4,6 +4,8 @@ NS_ASSUME_NONNULL_BEGIN @interface WKSourceEditorFormatterUnderline : WKSourceEditorFormatter +- (BOOL) attributedString:(NSMutableAttributedString *)attributedString isUnderlineInRange:(NSRange)range; + @end NS_ASSUME_NONNULL_END diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterUnderline.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterUnderline.m index bfdcff52467..ee768b59250 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterUnderline.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterUnderline.m @@ -1,5 +1,126 @@ #import "WKSourceEditorFormatterUnderline.h" +#import "WKSourceEditorColors.h" + +@interface WKSourceEditorFormatterUnderline () + +@property (nonatomic, strong) NSDictionary *underlineAttributes; +@property (nonatomic, strong) NSDictionary *underlineContentAttributes; +@property (nonatomic, strong) NSRegularExpression *underlineRegex; + +@end @implementation WKSourceEditorFormatterUnderline +#pragma mark - Custom Attributed String Keys +NSString * const WKSourceEditorCustomKeyContentUnderline = @"WKSourceEditorCustomKeyContentUnderline"; + +- (instancetype)initWithColors:(WKSourceEditorColors *)colors fonts:(WKSourceEditorFonts *)fonts { + self = [super initWithColors:colors fonts:fonts]; + if (self) { + _underlineAttributes = @{ + NSForegroundColorAttributeName: colors.greenForegroundColor, + WKSourceEditorCustomKeyColorGreen: [NSNumber numberWithBool:YES] + }; + + _underlineContentAttributes = @{ + WKSourceEditorCustomKeyContentUnderline: [NSNumber numberWithBool:YES] + }; + + _underlineRegex = [[NSRegularExpression alloc] initWithPattern:@"()(.*?)(<\\/u>)" options:0 error:nil]; + } + + return self; +} +- (void)addSyntaxHighlightingToAttributedString:(nonnull NSMutableAttributedString *)attributedString inRange:(NSRange)range { + + [attributedString removeAttribute:WKSourceEditorCustomKeyContentUnderline range:range]; + + [self.underlineRegex enumerateMatchesInString:attributedString.string + options:0 + range:range + usingBlock:^(NSTextCheckingResult *_Nullable result, NSMatchingFlags flags, BOOL *_Nonnull stop) { + NSRange fullMatch = [result rangeAtIndex:0]; + NSRange openingRange = [result rangeAtIndex:1]; + NSRange contentRange = [result rangeAtIndex:2]; + NSRange closingRange = [result rangeAtIndex:3]; + + if (openingRange.location != NSNotFound) { + [attributedString addAttributes:self.underlineAttributes range:openingRange]; + } + + if (contentRange.location != NSNotFound) { + [attributedString addAttributes:self.underlineContentAttributes range:contentRange]; + } + + if (closingRange.location != NSNotFound) { + [attributedString addAttributes:self.underlineAttributes range:closingRange]; + } + }]; +} + +- (void)updateFonts:(WKSourceEditorFonts *)fonts inAttributedString:(NSMutableAttributedString *)attributedString inRange:(NSRange)range { +} + +- (void)updateColors:(WKSourceEditorColors *)colors inAttributedString:(NSMutableAttributedString *)attributedString inRange:(NSRange)range { + + NSMutableDictionary *mutAttributes = [[NSMutableDictionary alloc] initWithDictionary:self.underlineAttributes]; + [mutAttributes setObject:colors.greenForegroundColor forKey:NSForegroundColorAttributeName]; + self.underlineAttributes = [[NSDictionary alloc] initWithDictionary:mutAttributes]; + + [attributedString enumerateAttribute:WKSourceEditorCustomKeyColorGreen + inRange:range + options:nil + usingBlock:^(id value, NSRange localRange, BOOL *stop) { + if ([value isKindOfClass: [NSNumber class]]) { + NSNumber *numValue = (NSNumber *)value; + if ([numValue boolValue] == YES) { + [attributedString addAttributes:self.underlineAttributes range:localRange]; + } + } + }]; + +} + +#pragma mark - Public + +- (BOOL)attributedString:(nonnull NSMutableAttributedString *)attributedString isUnderlineInRange:(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[WKSourceEditorCustomKeyContentUnderline] != nil) { + isContentKey = YES; + } else { + if (attrs[WKSourceEditorCustomKeyColorGreen]) { + attrs = [attributedString attributesAtIndex:range.location - 1 effectiveRange:nil]; + if (attrs[WKSourceEditorCustomKeyContentUnderline] != 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[WKSourceEditorCustomKeyContentUnderline] != nil) { + if (unionRange.location == NSNotFound) { + unionRange = loopRange; + } else { + unionRange = NSUnionRange(unionRange, loopRange); + } + stop = YES; + } + }]; + + if (NSEqualRanges(unionRange, range)) { + isContentKey = YES; + } + } + return isContentKey; +} + @end diff --git a/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift index 0ab1f13ace8..5a436293814 100644 --- a/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift +++ b/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift @@ -138,4 +138,7 @@ final class WKSourceEditorFormatterButtonActionTests: XCTestCase { func testSuperscriptInsertAndRemove() throws { } + + func testUnderlineInsertAndRemove() throws { + } } diff --git a/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift index a0eb059b3ec..6ae7398d79e 100644 --- a/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift +++ b/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift @@ -13,8 +13,9 @@ final class WKSourceEditorFormatterTests: XCTestCase { var strikethroughFormatter: WKSourceEditorFormatterStrikethrough! var subscriptFormatter: WKSourceEditorFormatterSubscript! var superscriptFormatter: WKSourceEditorFormatterSuperscript! + var underlineFormatter: WKSourceEditorFormatterUnderline! var formatters: [WKSourceEditorFormatter] { - return [baseFormatter, templateFormatter, boldItalicsFormatter, strikethroughFormatter, subscriptFormatter, superscriptFormatter] + return [baseFormatter, templateFormatter, boldItalicsFormatter, strikethroughFormatter, subscriptFormatter, superscriptFormatter, underlineFormatter] } override func setUpWithError() throws { @@ -38,6 +39,7 @@ final class WKSourceEditorFormatterTests: XCTestCase { self.strikethroughFormatter = WKSourceEditorFormatterStrikethrough(colors: colors, fonts: fonts) self.subscriptFormatter = WKSourceEditorFormatterSubscript(colors: colors, fonts: fonts) self.superscriptFormatter = WKSourceEditorFormatterSuperscript(colors: colors, fonts: fonts) + self.underlineFormatter = WKSourceEditorFormatterUnderline(colors: colors, fonts: fonts) } override func tearDownWithError() throws { @@ -778,4 +780,8 @@ final class WKSourceEditorFormatterTests: XCTestCase { func testSuperscript() { } + + func testUnderline() { + + } } From 7592d44618c8e6b611e904a608543c9e5694e926 Mon Sep 17 00:00:00 2001 From: mazevedo Date: Thu, 4 Jan 2024 16:19:43 -0300 Subject: [PATCH 24/36] Add missing state update and remove test code --- .../Grouped Toolbar Table Item/WKEditorToolbarGroupedView.swift | 1 + ...ift => WKSourceEditorFormatterSubscript+ButtonActions.swift} | 0 ...t => WKSourceEditorFormatterSuperscript+ButtonActions.swift} | 0 ...ift => WKSourceEditorFormatterUnderline+ButtonActions.swift} | 0 .../Source Editor/WKSourceEditorTextFrameworkMediator.swift | 2 +- 5 files changed, 2 insertions(+), 1 deletion(-) rename Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/{WKSourceEditorSubscriptFormatter+ButtonActions.swift => WKSourceEditorFormatterSubscript+ButtonActions.swift} (100%) rename Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/{WKSourceEditorFormatterSuperscript.swift => WKSourceEditorFormatterSuperscript+ButtonActions.swift} (100%) rename Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/{WKSourceEditorFormatterUnderline.swift => WKSourceEditorFormatterUnderline+ButtonActions.swift} (100%) 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 55886b64d5b..9dc93aeb5d6 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,6 +65,7 @@ class WKEditorToolbarGroupedView: WKEditorToolbarView { strikethroughButton.isSelected = selectionState.isStrikethrough subscriptButton.isSelected = selectionState.isSubscript superscriptButton.isSelected = selectionState.isSuperscript + underlineButton.isSelected = selectionState.isUnderline } // MARK: - Button Actions diff --git a/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorSubscriptFormatter+ButtonActions.swift b/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterSubscript+ButtonActions.swift similarity index 100% rename from Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorSubscriptFormatter+ButtonActions.swift rename to Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterSubscript+ButtonActions.swift diff --git a/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterSuperscript.swift b/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterSuperscript+ButtonActions.swift similarity index 100% rename from Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterSuperscript.swift rename to Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterSuperscript+ButtonActions.swift diff --git a/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterUnderline.swift b/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterUnderline+ButtonActions.swift similarity index 100% rename from Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterUnderline.swift rename to Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterUnderline+ButtonActions.swift diff --git a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift index 41b1c634762..074fa31a2d3 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift @@ -195,7 +195,7 @@ final class WKSourceEditorTextFrameworkMediator: NSObject { let isStrikethrough = strikethroughFormatter?.attributedString(textKit1Storage, isStrikethroughIn: selectedDocumentRange) ?? false let isSubscript = subscriptFormatter?.attributedString(textKit1Storage, isSubscriptIn: selectedDocumentRange) ?? false let isSuperscript = superscriptFormatter?.attributedString(textKit1Storage, isSuperscriptIn: selectedDocumentRange) ?? false - let isUnderline = superscriptFormatter?.attributedString(textKit1Storage, isSuperscriptIn: selectedDocumentRange) ?? false + let isUnderline = underlineFormatter?.attributedString(textKit1Storage, isUnderlineIn: selectedDocumentRange) ?? false return WKSourceEditorSelectionState(isBold: isBold, isItalics: isItalics, isHorizontalTemplate: isHorizontalTemplate, isStrikethrough: isStrikethrough, isUnderline: isUnderline, isSubscript: isSubscript, isSuperscript: isSuperscript) } From 7ffba3bd6a243164d4145aa1acd5d60d466e24f3 Mon Sep 17 00:00:00 2001 From: mazevedo Date: Thu, 4 Jan 2024 16:42:00 -0300 Subject: [PATCH 25/36] Fix out of bounds error when calculating attribute range When a tag is the first item on the editor, if you click inside the attributed string range and then before it, as to insert a string before this tag, it would throw and out of bounds exception --- .../ComponentsObjC/WKSourceEditorFormatterStrikethrough.m | 2 +- .../Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m | 2 +- .../Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.m | 2 +- .../Sources/ComponentsObjC/WKSourceEditorFormatterUnderline.m | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterStrikethrough.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterStrikethrough.m index 4a016dc935e..a68d26191fc 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterStrikethrough.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterStrikethrough.m @@ -98,7 +98,7 @@ - (BOOL)attributedString:(NSMutableAttributedString *)attributedString isStriket isContentKey = YES; } else { // Edge case, check previous character if we are up against closing string - if (attrs[WKSourceEditorCustomKeyColorGreen]) { + if (attrs[WKSourceEditorCustomKeyColorGreen] && range.location < 0) { attrs = [attributedString attributesAtIndex:range.location - 1 effectiveRange:nil]; if (attrs[WKSourceEditorCustomKeyContentStrikethrough] != nil) { isContentKey = YES; diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m index 79f65520b56..a564f4a8637 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m @@ -95,7 +95,7 @@ - (BOOL)attributedString:(nonnull NSMutableAttributedString *)attributedString i if (attrs[WKSourceEditorCustomKeyContentSubscript] != nil) { isContentKey = YES; } else { - if (attrs[WKSourceEditorCustomKeyColorGreen]) { + if (attrs[WKSourceEditorCustomKeyColorGreen] && range.location < 0) { attrs = [attributedString attributesAtIndex:range.location - 1 effectiveRange:nil]; if (attrs[WKSourceEditorCustomKeyContentSubscript] != nil) { isContentKey = YES; diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.m index 1cacf9c191e..42397143ff6 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.m @@ -94,7 +94,7 @@ - (BOOL)attributedString:(nonnull NSMutableAttributedString *)attributedString i if (attrs[WKSourceEditorCustomKeyContentSuperscript] != nil) { isContentKey = YES; } else { - if (attrs[WKSourceEditorCustomKeyColorGreen]) { + if (attrs[WKSourceEditorCustomKeyColorGreen] && range.location < 0) { attrs = [attributedString attributesAtIndex:range.location - 1 effectiveRange:nil]; if (attrs[WKSourceEditorCustomKeyContentSuperscript] != nil) { isContentKey = YES; diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterUnderline.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterUnderline.m index ee768b59250..964c48b8261 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterUnderline.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterUnderline.m @@ -94,7 +94,7 @@ - (BOOL)attributedString:(nonnull NSMutableAttributedString *)attributedString i if (attrs[WKSourceEditorCustomKeyContentUnderline] != nil) { isContentKey = YES; } else { - if (attrs[WKSourceEditorCustomKeyColorGreen]) { + if (attrs[WKSourceEditorCustomKeyColorGreen] && range.location < 0) { attrs = [attributedString attributesAtIndex:range.location - 1 effectiveRange:nil]; if (attrs[WKSourceEditorCustomKeyContentUnderline] != nil) { isContentKey = YES; From f316dbcaf774179bcd6df7673b491064e4d75599 Mon Sep 17 00:00:00 2001 From: mazevedo Date: Fri, 5 Jan 2024 11:41:25 -0300 Subject: [PATCH 26/36] Add formatter tests for u, sub and sup formatters --- .../WKSourceEditorFormatterTests.swift | 154 ++++++++++++++++++ 1 file changed, 154 insertions(+) diff --git a/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift index 6ae7398d79e..0f4b0c3b71d 100644 --- a/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift +++ b/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift @@ -774,14 +774,168 @@ final class WKSourceEditorFormatterTests: XCTestCase { } func testSubscript() { + let string = "Testing. Subscript. Testing" + let mutAttributedString = NSMutableAttributedString(string: string) + + for formatter in formatters { + formatter.addSyntaxHighlighting(to: mutAttributedString, in: NSRange(location: 0, length: string.count)) + } + + var base1Range = NSRange(location: 0, length: 0) + let base1Attributes = mutAttributedString.attributes(at: 0, effectiveRange: &base1Range) + + var subscriptOpenRange = NSRange(location: 0, length: 0) + let subscriptOpenAttributes = mutAttributedString.attributes(at: 9, effectiveRange: &subscriptOpenRange) + + var subscriptContentRange = NSRange(location: 0, length: 0) + let subscriptContentAttributes = mutAttributedString.attributes(at: 14, effectiveRange: &subscriptContentRange) + + var subscriptCloseRange = NSRange(location: 0, length: 0) + let subscriptCloseAttributes = mutAttributedString.attributes(at: 27, effectiveRange: &subscriptCloseRange) + + var base2Range = NSRange(location: 0, length: 0) + let base2Attributes = mutAttributedString.attributes(at: 35, effectiveRange: &base2Range) + + // "Testing. " + XCTAssertEqual(base1Range.location, 0, "Incorrect base formatting") + XCTAssertEqual(base1Range.length, 9, "Incorrect base formatting") + XCTAssertEqual(base1Attributes[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") + XCTAssertEqual(base1Attributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect base formatting") + + // "" + XCTAssertEqual(subscriptContentRange.location, 14, "Incorrect strikethrough formatting") + XCTAssertEqual(subscriptContentRange.length, 10, "Incorrect strikethrough formatting") + XCTAssertEqual(subscriptOpenAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect strikethrough formatting") + XCTAssertEqual(subscriptOpenAttributes[.foregroundColor] as! UIColor, colors.greenForegroundColor, "Incorrect strikethrough formatting") + + // "Subscript." + XCTAssertEqual(subscriptContentRange.location, 14, "Incorrect content formatting") + XCTAssertEqual(subscriptContentRange.length, 10, "Incorrect content formatting") + XCTAssertEqual(subscriptContentAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect content formatting") + XCTAssertEqual(subscriptContentAttributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect content formatting") + + // "" + XCTAssertEqual(subscriptCloseRange.location, 24, "Incorrect strikethrough formatting") + XCTAssertEqual(subscriptCloseRange.length, 6, "Incorrect strikethrough formatting") + XCTAssertEqual(subscriptCloseAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect strikethrough formatting") + XCTAssertEqual(subscriptCloseAttributes[.foregroundColor] as! UIColor, colors.greenForegroundColor, "Incorrect strikethrough formatting") + + // " Testing" + XCTAssertEqual(base2Range.location, 30, "Incorrect base formatting") + XCTAssertEqual(base2Range.length, 8, "Incorrect base formatting") + XCTAssertEqual(base2Attributes[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") + XCTAssertEqual(base2Attributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect base formatting") + } func testSuperscript() { + let string = "Testing. Superscript. Testing" + let mutAttributedString = NSMutableAttributedString(string: string) + + for formatter in formatters { + formatter.addSyntaxHighlighting(to: mutAttributedString, in: NSRange(location: 0, length: string.count)) + } + + var base1Range = NSRange(location: 0, length: 0) + let base1Attributes = mutAttributedString.attributes(at: 0, effectiveRange: &base1Range) + + var superscriptOpenRange = NSRange(location: 0, length: 0) + let superscriptOpenAttributes = mutAttributedString.attributes(at: 9, effectiveRange: &superscriptOpenRange) + var superscriptContentRange = NSRange(location: 0, length: 0) + let superscriptContentAttributes = mutAttributedString.attributes(at: 14, effectiveRange: &superscriptContentRange) + + var superscriptCloseRange = NSRange(location: 0, length: 0) + let superscriptCloseAttributes = mutAttributedString.attributes(at: 28, effectiveRange: &superscriptCloseRange) + + var base2Range = NSRange(location: 0, length: 0) + let base2Attributes = mutAttributedString.attributes(at: 36, effectiveRange: &base2Range) + + + // "Testing. " + XCTAssertEqual(base1Range.location, 0, "Incorrect base formatting") + XCTAssertEqual(base1Range.length, 9, "Incorrect base formatting") + XCTAssertEqual(base1Attributes[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") + XCTAssertEqual(base1Attributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect base formatting") + + // "" + XCTAssertEqual(superscriptContentRange.location, 14, "Incorrect strikethrough formatting") + XCTAssertEqual(superscriptContentRange.length, 12, "Incorrect strikethrough formatting") + XCTAssertEqual(superscriptOpenAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect strikethrough formatting") + XCTAssertEqual(superscriptOpenAttributes[.foregroundColor] as! UIColor, colors.greenForegroundColor, "Incorrect strikethrough formatting") + + // "Superscript." + XCTAssertEqual(superscriptContentRange.location, 14, "Incorrect content formatting") + XCTAssertEqual(superscriptContentRange.length, 12, "Incorrect content formatting") + XCTAssertEqual(superscriptContentAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect content formatting") + XCTAssertEqual(superscriptContentAttributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect content formatting") + + // "" + XCTAssertEqual(superscriptCloseRange.location, 26, "Incorrect strikethrough formatting") + XCTAssertEqual(superscriptCloseRange.length, 6, "Incorrect strikethrough formatting") + XCTAssertEqual(superscriptCloseAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect strikethrough formatting") + XCTAssertEqual(superscriptCloseAttributes[.foregroundColor] as! UIColor, colors.greenForegroundColor, "Incorrect strikethrough formatting") + + // " Testing" + XCTAssertEqual(base2Range.location, 32, "Incorrect base formatting") + XCTAssertEqual(base2Range.length, 8, "Incorrect base formatting") + XCTAssertEqual(base2Attributes[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") + XCTAssertEqual(base2Attributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect base formatting") } func testUnderline() { + let string = "Testing. Underline. Testing" + let mutAttributedString = NSMutableAttributedString(string: string) + + for formatter in formatters { + formatter.addSyntaxHighlighting(to: mutAttributedString, in: NSRange(location: 0, length: string.count)) + } + var base1Range = NSRange(location: 0, length: 0) + let base1Attributes = mutAttributedString.attributes(at: 0, effectiveRange: &base1Range) + + var underlineOpenRange = NSRange(location: 0, length: 0) + let underlineOpenAttributes = mutAttributedString.attributes(at: 9, effectiveRange: &underlineOpenRange) + + var underlineContentRange = NSRange(location: 0, length: 0) + let underlineContentAttributes = mutAttributedString.attributes(at: 12, effectiveRange: &underlineContentRange) + + var underlineCloseRange = NSRange(location: 0, length: 0) + let underlineCloseAttributes = mutAttributedString.attributes(at: 24, effectiveRange: &underlineCloseRange) + + var base2Range = NSRange(location: 0, length: 0) + let base2Attributes = mutAttributedString.attributes(at: 31, effectiveRange: &base2Range) + + // "Testing. " + XCTAssertEqual(base1Range.location, 0, "Incorrect base formatting") + XCTAssertEqual(base1Range.length, 9, "Incorrect base formatting") + XCTAssertEqual(base1Attributes[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") + XCTAssertEqual(base1Attributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect base formatting") + + // "" + XCTAssertEqual(underlineOpenRange.location, 9, "Incorrect strikethrough formatting") + XCTAssertEqual(underlineOpenRange.length, 3, "Incorrect strikethrough formatting") + XCTAssertEqual(underlineOpenAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect strikethrough formatting") + XCTAssertEqual(underlineOpenAttributes[.foregroundColor] as! UIColor, colors.greenForegroundColor, "Incorrect strikethrough formatting") + + // "Underline." + XCTAssertEqual(underlineContentRange.location, 12, "Incorrect content formatting") + XCTAssertEqual(underlineContentRange.length, 10, "Incorrect content formatting") + XCTAssertEqual(underlineContentAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect content formatting") + XCTAssertEqual(underlineContentAttributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect content formatting") + + // "" + XCTAssertEqual(underlineCloseRange.location, 22, "Incorrect strikethrough formatting") + XCTAssertEqual(underlineCloseRange.length, 4, "Incorrect strikethrough formatting") + XCTAssertEqual(underlineCloseAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect strikethrough formatting") + XCTAssertEqual(underlineCloseAttributes[.foregroundColor] as! UIColor, colors.greenForegroundColor, "Incorrect strikethrough formatting") + + // " Testing" + XCTAssertEqual(base2Range.location, 26, "Incorrect base formatting") + XCTAssertEqual(base2Range.length, 8, "Incorrect base formatting") + XCTAssertEqual(base2Attributes[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") + XCTAssertEqual(base2Attributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect base formatting") } + } From 78638afd357ce90eef5af3d06577ce891031efe7 Mon Sep 17 00:00:00 2001 From: mazevedo Date: Fri, 5 Jan 2024 11:45:32 -0300 Subject: [PATCH 27/36] Add tests for adding and removing u, sup and sup tags --- ...urceEditorFormatterButtonActionTests.swift | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift index 5a436293814..9d65b467a95 100644 --- a/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift +++ b/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift @@ -134,11 +134,32 @@ final class WKSourceEditorFormatterButtonActionTests: XCTestCase { } func testSubscriptInsertAndRemove() throws { + let text = "One Two Three Four" + mediator.textView.attributedText = NSAttributedString(string: text) + mediator.textView.selectedRange = NSRange(location: 4, length:3) + mediator.subscriptFormatter?.toggleSubscriptFormatting(action: .add, in: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four") + mediator.subscriptFormatter?.toggleSubscriptFormatting(action: .remove, in: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four") } func testSuperscriptInsertAndRemove() throws { + let text = "One Two Three Four" + mediator.textView.attributedText = NSAttributedString(string: text) + mediator.textView.selectedRange = NSRange(location: 4, length:3) + mediator.superscriptFormatter?.toggleSuperscriptFormatting(action: .add, in: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four") + mediator.superscriptFormatter?.toggleSuperscriptFormatting(action: .remove, in: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four") } func testUnderlineInsertAndRemove() throws { + let text = "One Two Three Four" + mediator.textView.attributedText = NSAttributedString(string: text) + mediator.textView.selectedRange = NSRange(location: 4, length:3) + mediator.underlineFormatter?.toggleUnderlineFormatting(action: .add, in: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four") + mediator.underlineFormatter?.toggleUnderlineFormatting(action: .remove, in: mediator.textView) + XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four") } } From 6ddef39d9993a3dbefc2d9f1bc41c0d6be07daf9 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Tue, 9 Jan 2024 16:57:13 -0600 Subject: [PATCH 28/36] Bug fixes after merge --- .../Editors/Common Views/Input Views/WKEditorInputView.swift | 3 +++ 1 file changed, 3 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..021cfe9949e 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 @@ -7,6 +7,9 @@ protocol WKEditorInputViewDelegate: AnyObject { func didTapItalics(isSelected: Bool) func didTapTemplate(isSelected: Bool) func didTapStrikethrough(isSelected: Bool) + func didTapSubscript(isSelected: Bool) + func didTapSuperscript(isSelected: Bool) + func didTapUnderline(isSelected: Bool) } class WKEditorInputView: WKComponentView { From a86db94206a77e257d0f5c60f4eeef16bcfca373 Mon Sep 17 00:00:00 2001 From: mazevedo Date: Fri, 12 Jan 2024 15:03:50 -0300 Subject: [PATCH 29/36] Fix regexes --- .../Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m | 2 +- .../Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.m | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m index a564f4a8637..4c62f2a988c 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m @@ -26,7 +26,7 @@ - (instancetype)initWithColors:(WKSourceEditorColors *)colors fonts:(WKSourceEdi WKSourceEditorCustomKeyContentSubscript: [NSNumber numberWithBool:YES] }; - _subscriptRegex = [[NSRegularExpression alloc] initWithPattern:@"()(\\s*u*b*.*?)(<\\/sub>)" options:0 error:nil]; + _subscriptRegex = [[NSRegularExpression alloc] initWithPattern:@"()(.*?)(<\\/sub>)" options:0 error:nil]; } diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.m index 42397143ff6..d5c9fd07e91 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.m @@ -26,7 +26,7 @@ - (instancetype)initWithColors:(WKSourceEditorColors *)colors fonts:(WKSourceEdi WKSourceEditorCustomKeyContentSuperscript: [NSNumber numberWithBool:YES] }; - _superscriptRegex = [[NSRegularExpression alloc] initWithPattern:@"()(\\s*u*p*.*?)(<\\/sup>)" options:0 error:nil]; + _superscriptRegex = [[NSRegularExpression alloc] initWithPattern:@"()(.*?)(<\\/sup>)" options:0 error:nil]; } return self; From 42dc4e81e039378e0f03b23f055e5c6ea8468e67 Mon Sep 17 00:00:00 2001 From: mazevedo Date: Fri, 12 Jan 2024 15:09:21 -0300 Subject: [PATCH 30/36] Fix formatting tests --- .../WKSourceEditorFormatterTests.swift | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift index 14a8ce6c5ff..8472c296223 100644 --- a/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift +++ b/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift @@ -1392,10 +1392,10 @@ final class WKSourceEditorFormatterTests: XCTestCase { let subscriptContentAttributes = mutAttributedString.attributes(at: 14, effectiveRange: &subscriptContentRange) var subscriptCloseRange = NSRange(location: 0, length: 0) - let subscriptCloseAttributes = mutAttributedString.attributes(at: 27, effectiveRange: &subscriptCloseRange) + let subscriptCloseAttributes = mutAttributedString.attributes(at: 24, effectiveRange: &subscriptCloseRange) var base2Range = NSRange(location: 0, length: 0) - let base2Attributes = mutAttributedString.attributes(at: 35, effectiveRange: &base2Range) + let base2Attributes = mutAttributedString.attributes(at: 30, effectiveRange: &base2Range) // "Testing. " XCTAssertEqual(base1Range.location, 0, "Incorrect base formatting") @@ -1404,8 +1404,8 @@ final class WKSourceEditorFormatterTests: XCTestCase { XCTAssertEqual(base1Attributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect base formatting") // "" - XCTAssertEqual(subscriptContentRange.location, 14, "Incorrect subscript formatting") - XCTAssertEqual(subscriptContentRange.length, 10, "Incorrect subscript formatting") + XCTAssertEqual(subscriptContentRange.location, 9, "Incorrect subscript formatting") + XCTAssertEqual(subscriptContentRange.length, 5, "Incorrect subscript formatting") XCTAssertEqual(subscriptOpenAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect subscript formatting") XCTAssertEqual(subscriptOpenAttributes[.foregroundColor] as! UIColor, colors.greenForegroundColor, "Incorrect strikethrough formatting") @@ -1447,10 +1447,10 @@ final class WKSourceEditorFormatterTests: XCTestCase { let superscriptContentAttributes = mutAttributedString.attributes(at: 14, effectiveRange: &superscriptContentRange) var superscriptCloseRange = NSRange(location: 0, length: 0) - let superscriptCloseAttributes = mutAttributedString.attributes(at: 28, effectiveRange: &superscriptCloseRange) + let superscriptCloseAttributes = mutAttributedString.attributes(at: 26, effectiveRange: &superscriptCloseRange) var base2Range = NSRange(location: 0, length: 0) - let base2Attributes = mutAttributedString.attributes(at: 36, effectiveRange: &base2Range) + let base2Attributes = mutAttributedString.attributes(at: 32, effectiveRange: &base2Range) // "Testing. " XCTAssertEqual(base1Range.location, 0, "Incorrect base formatting") @@ -1459,8 +1459,8 @@ final class WKSourceEditorFormatterTests: XCTestCase { XCTAssertEqual(base1Attributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect base formatting") // "" - XCTAssertEqual(superscriptContentRange.location, 14, "Incorrect superscript formatting") - XCTAssertEqual(superscriptContentRange.length, 12, "Incorrect superscript formatting") + XCTAssertEqual(superscriptContentRange.location, 9, "Incorrect superscript formatting") + XCTAssertEqual(superscriptContentRange.length, 5, "Incorrect superscript formatting") XCTAssertEqual(superscriptOpenAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect superscript formatting") XCTAssertEqual(superscriptOpenAttributes[.foregroundColor] as! UIColor, colors.greenForegroundColor, "Incorrect superscript formatting") @@ -1503,10 +1503,10 @@ final class WKSourceEditorFormatterTests: XCTestCase { let underlineContentAttributes = mutAttributedString.attributes(at: 12, effectiveRange: &underlineContentRange) var underlineCloseRange = NSRange(location: 0, length: 0) - let underlineCloseAttributes = mutAttributedString.attributes(at: 24, effectiveRange: &underlineCloseRange) + let underlineCloseAttributes = mutAttributedString.attributes(at: 22, effectiveRange: &underlineCloseRange) var base2Range = NSRange(location: 0, length: 0) - let base2Attributes = mutAttributedString.attributes(at: 31, effectiveRange: &base2Range) + let base2Attributes = mutAttributedString.attributes(at: 26, effectiveRange: &base2Range) // "Testing. " XCTAssertEqual(base1Range.location, 0, "Incorrect base formatting") From ce8d4801a77e75521519faf065417c637808abc8 Mon Sep 17 00:00:00 2001 From: mazevedo Date: Fri, 12 Jan 2024 16:19:55 -0300 Subject: [PATCH 31/36] Fix failing formatting tests --- .../ComponentsTests/WKSourceEditorFormatterTests.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift index 8472c296223..96ae35c4f61 100644 --- a/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift +++ b/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift @@ -1404,8 +1404,8 @@ final class WKSourceEditorFormatterTests: XCTestCase { XCTAssertEqual(base1Attributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect base formatting") // "" - XCTAssertEqual(subscriptContentRange.location, 9, "Incorrect subscript formatting") - XCTAssertEqual(subscriptContentRange.length, 5, "Incorrect subscript formatting") + XCTAssertEqual(subscriptOpenRange.location, 9, "Incorrect subscript formatting") + XCTAssertEqual(subscriptOpenRange.length, 5, "Incorrect subscript formatting") XCTAssertEqual(subscriptOpenAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect subscript formatting") XCTAssertEqual(subscriptOpenAttributes[.foregroundColor] as! UIColor, colors.greenForegroundColor, "Incorrect strikethrough formatting") @@ -1459,8 +1459,8 @@ final class WKSourceEditorFormatterTests: XCTestCase { XCTAssertEqual(base1Attributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect base formatting") // "" - XCTAssertEqual(superscriptContentRange.location, 9, "Incorrect superscript formatting") - XCTAssertEqual(superscriptContentRange.length, 5, "Incorrect superscript formatting") + XCTAssertEqual(superscriptOpenRange.location, 9, "Incorrect superscript formatting") + XCTAssertEqual(superscriptOpenRange.length, 5, "Incorrect superscript formatting") XCTAssertEqual(superscriptOpenAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect superscript formatting") XCTAssertEqual(superscriptOpenAttributes[.foregroundColor] as! UIColor, colors.greenForegroundColor, "Incorrect superscript formatting") From 691943206e4105427d3038262712353c43d34d11 Mon Sep 17 00:00:00 2001 From: mazevedo Date: Mon, 15 Jan 2024 15:43:57 -0300 Subject: [PATCH 32/36] Fix out of range crash on formatters --- .../Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m | 2 +- .../Sources/ComponentsObjC/WKSourceEditorFormatterHeading.m | 2 +- Components/Sources/ComponentsObjC/WKSourceEditorFormatterLink.m | 2 +- Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.m | 2 +- .../Sources/ComponentsObjC/WKSourceEditorFormatterReference.m | 2 +- .../ComponentsObjC/WKSourceEditorFormatterStrikethrough.m | 2 +- .../Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m | 2 +- .../Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.m | 2 +- .../Sources/ComponentsObjC/WKSourceEditorFormatterUnderline.m | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m index fd12af389ff..1339ad62735 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterBoldItalics.m @@ -293,7 +293,7 @@ - (BOOL)attributedString:(NSMutableAttributedString *)attributedString isFormatt isFormatted = YES; } else { // Edge case, check previous character if we are up against a closing bold or italic - if (attrs[WKSourceEditorCustomKeyColorOrange]) { + if (attrs[WKSourceEditorCustomKeyColorOrange] && attributedString.length > range.location - 1) { attrs = [attributedString attributesAtIndex:range.location - 1 effectiveRange:nil]; if (attrs[WKSourceEditorCustomKeyFontBoldItalics] != nil || attrs[formattingKey] != nil) { isFormatted = YES; diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterHeading.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterHeading.m index 0d36a57eaac..269799aeea2 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterHeading.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterHeading.m @@ -176,7 +176,7 @@ - (BOOL)attributedString:(NSMutableAttributedString *)attributedString isContent isContentKey = YES; } else { // Edge case, check previous character if we are up against closing string - if (attrs[WKSourceEditorCustomKeyColorOrange]) { + if (attrs[WKSourceEditorCustomKeyColorOrange] && attributedString.length > range.location - 1) { attrs = [attributedString attributesAtIndex:range.location - 1 effectiveRange:nil]; if (attrs[contentKey] != nil) { isContentKey = YES; diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterLink.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterLink.m index e3547df8c5e..66353cfce2d 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterLink.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterLink.m @@ -209,7 +209,7 @@ - (BOOL)attributedString:(NSMutableAttributedString *)attributedString isKey:(NS } // Edge case, check previous character if we are up against opening markup - if (attrs[WKSourceEditorCustomKeyLink]) { + if (attrs[WKSourceEditorCustomKeyLink] && attributedString.length > range.location - 1) { if (attributedString.length > range.location - 1) { attrs = [attributedString attributesAtIndex:range.location - 1 effectiveRange:nil]; if (attrs[key] == nil) { diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.m index de8ef933334..00469020c39 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.m @@ -185,7 +185,7 @@ - (BOOL)attributedString:(NSMutableAttributedString *)attributedString isContent } // 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)) { + if ((attributedString.length > attributedString.length > range.location - 1)) { NSDictionary *attrs = [attributedString attributesAtIndex:range.location-1 effectiveRange:nil]; if (attrs[contentKey] != nil) { diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterReference.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterReference.m index f427197c3b4..1dd93498c0f 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterReference.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterReference.m @@ -146,7 +146,7 @@ - (BOOL)attributedString:(NSMutableAttributedString *)attributedString isHorizon isContentKey = YES; } else { // Edge case, check previous character if we are up against closing string - if (attrs[WKSourceEditorCustomKeyColorGreen]) { + if (attrs[WKSourceEditorCustomKeyColorGreen] && attributedString.length > range.location - 1) { attrs = [attributedString attributesAtIndex:range.location - 1 effectiveRange:nil]; if (attrs[WKSourceEditorCustomKeyContentReference] != nil) { isContentKey = YES; diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterStrikethrough.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterStrikethrough.m index a68d26191fc..59112ee4d33 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterStrikethrough.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterStrikethrough.m @@ -98,7 +98,7 @@ - (BOOL)attributedString:(NSMutableAttributedString *)attributedString isStriket isContentKey = YES; } else { // Edge case, check previous character if we are up against closing string - if (attrs[WKSourceEditorCustomKeyColorGreen] && range.location < 0) { + if (attrs[WKSourceEditorCustomKeyColorGreen] && attributedString.length > range.location - 1) { attrs = [attributedString attributesAtIndex:range.location - 1 effectiveRange:nil]; if (attrs[WKSourceEditorCustomKeyContentStrikethrough] != nil) { isContentKey = YES; diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m index 4c62f2a988c..94722bb0334 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSubscript.m @@ -95,7 +95,7 @@ - (BOOL)attributedString:(nonnull NSMutableAttributedString *)attributedString i if (attrs[WKSourceEditorCustomKeyContentSubscript] != nil) { isContentKey = YES; } else { - if (attrs[WKSourceEditorCustomKeyColorGreen] && range.location < 0) { + if (attrs[WKSourceEditorCustomKeyColorGreen] && attributedString.length > range.location - 1) { attrs = [attributedString attributesAtIndex:range.location - 1 effectiveRange:nil]; if (attrs[WKSourceEditorCustomKeyContentSubscript] != nil) { isContentKey = YES; diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.m index d5c9fd07e91..19a6ad1db39 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterSuperscript.m @@ -94,7 +94,7 @@ - (BOOL)attributedString:(nonnull NSMutableAttributedString *)attributedString i if (attrs[WKSourceEditorCustomKeyContentSuperscript] != nil) { isContentKey = YES; } else { - if (attrs[WKSourceEditorCustomKeyColorGreen] && range.location < 0) { + if (attrs[WKSourceEditorCustomKeyColorGreen] && attributedString.length > range.location - 1) { attrs = [attributedString attributesAtIndex:range.location - 1 effectiveRange:nil]; if (attrs[WKSourceEditorCustomKeyContentSuperscript] != nil) { isContentKey = YES; diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterUnderline.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterUnderline.m index 964c48b8261..6073d106e90 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterUnderline.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterUnderline.m @@ -94,7 +94,7 @@ - (BOOL)attributedString:(nonnull NSMutableAttributedString *)attributedString i if (attrs[WKSourceEditorCustomKeyContentUnderline] != nil) { isContentKey = YES; } else { - if (attrs[WKSourceEditorCustomKeyColorGreen] && range.location < 0) { + if (attrs[WKSourceEditorCustomKeyColorGreen] && attributedString.length > range.location - 1) { attrs = [attributedString attributesAtIndex:range.location - 1 effectiveRange:nil]; if (attrs[WKSourceEditorCustomKeyContentUnderline] != nil) { isContentKey = YES; From 7e37df0c30df0ac29f654517b272419a27236666 Mon Sep 17 00:00:00 2001 From: mazevedo Date: Mon, 15 Jan 2024 16:25:31 -0300 Subject: [PATCH 33/36] Run build From cf676984f9a4c0288509004f24fe196e48d6d3b6 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Tue, 16 Jan 2024 16:43:48 -0600 Subject: [PATCH 34/36] Undo unnecessary change to list formatter --- Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.m index 00469020c39..eb3b0bfc9bb 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterList.m @@ -185,7 +185,7 @@ - (BOOL)attributedString:(NSMutableAttributedString *)attributedString isContent } // Edge case, check previous character in case we're at the end of the line and list isn't detected - if ((attributedString.length > attributedString.length > range.location - 1)) { + if (attributedString.length > range.location - 1) { NSDictionary *attrs = [attributedString attributesAtIndex:range.location-1 effectiveRange:nil]; if (attrs[contentKey] != nil) { From ae7b75339ad533d959c60972a2da681682dfd3bc Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Tue, 16 Jan 2024 16:44:02 -0600 Subject: [PATCH 35/36] Test comment fixes --- .../ComponentsTests/WKSourceEditorFormatterTests.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift index 96ae35c4f61..d087a2fab41 100644 --- a/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift +++ b/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift @@ -1408,7 +1408,7 @@ final class WKSourceEditorFormatterTests: XCTestCase { XCTAssertEqual(subscriptOpenRange.length, 5, "Incorrect subscript formatting") XCTAssertEqual(subscriptOpenAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect subscript formatting") - XCTAssertEqual(subscriptOpenAttributes[.foregroundColor] as! UIColor, colors.greenForegroundColor, "Incorrect strikethrough formatting") + XCTAssertEqual(subscriptOpenAttributes[.foregroundColor] as! UIColor, colors.greenForegroundColor, "Incorrect subscript formatting") // "Subscript." XCTAssertEqual(subscriptContentRange.location, 14, "Incorrect content formatting") @@ -1420,7 +1420,7 @@ final class WKSourceEditorFormatterTests: XCTestCase { XCTAssertEqual(subscriptCloseRange.location, 24, "Incorrect subscript formatting") XCTAssertEqual(subscriptCloseRange.length, 6, "Incorrect subscript formatting") XCTAssertEqual(subscriptCloseAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect subscript formatting") - XCTAssertEqual(subscriptCloseAttributes[.foregroundColor] as! UIColor, colors.greenForegroundColor, "Incorrect strikethrough formatting") + XCTAssertEqual(subscriptCloseAttributes[.foregroundColor] as! UIColor, colors.greenForegroundColor, "Incorrect subscript formatting") // " Testing" XCTAssertEqual(base2Range.location, 30, "Incorrect base formatting") @@ -1475,7 +1475,7 @@ final class WKSourceEditorFormatterTests: XCTestCase { XCTAssertEqual(superscriptCloseRange.location, 26, "Incorrect superscript formatting") XCTAssertEqual(superscriptCloseRange.length, 6, "Incorrect superscript formatting") XCTAssertEqual(superscriptCloseAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect superscript formatting") - XCTAssertEqual(superscriptCloseAttributes[.foregroundColor] as! UIColor, colors.greenForegroundColor, "Incorrect strikethrough formatting") + XCTAssertEqual(superscriptCloseAttributes[.foregroundColor] as! UIColor, colors.greenForegroundColor, "Incorrect superscript formatting") // " Testing" XCTAssertEqual(base2Range.location, 32, "Incorrect base formatting") @@ -1518,7 +1518,7 @@ final class WKSourceEditorFormatterTests: XCTestCase { XCTAssertEqual(underlineOpenRange.location, 9, "Incorrect underline formatting") XCTAssertEqual(underlineOpenRange.length, 3, "Incorrect underline formatting") XCTAssertEqual(underlineOpenAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect underline formatting") - XCTAssertEqual(underlineOpenAttributes[.foregroundColor] as! UIColor, colors.greenForegroundColor, "Incorrect strikethrough formatting") + XCTAssertEqual(underlineOpenAttributes[.foregroundColor] as! UIColor, colors.greenForegroundColor, "Incorrect underline formatting") // "Underline." XCTAssertEqual(underlineContentRange.location, 12, "Incorrect content formatting") From 4e766fa220c6847d9fcd35908d35877350ce2268 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Tue, 16 Jan 2024 17:03:08 -0600 Subject: [PATCH 36/36] Change to another available environment variable --- ci_scripts/copy_sourceroot.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci_scripts/copy_sourceroot.sh b/ci_scripts/copy_sourceroot.sh index f77c42a193b..1fc2e72fa02 100755 --- a/ci_scripts/copy_sourceroot.sh +++ b/ci_scripts/copy_sourceroot.sh @@ -6,7 +6,7 @@ set -e if [[ ${CI_XCODEBUILD_ACTION} == "build-for-testing" ]]; then cd ../WikipediaUnitTests/ - SOURCEROOT="${CI_WORKSPACE}/ci_scripts" + SOURCEROOT="${CI_PRIMARY_REPOSITORY_PATH}/ci_scripts" plutil -replace SOURCE_ROOT_DIR -string $SOURCEROOT Info.plist plutil -p Info.plist echo "CI_WORKSPACE value successfully copied into Info.plist SOURCE_ROOT_DIR key."