From ea052800b9c2467288efd1d48cda55307601a71a Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Fri, 5 Jan 2024 11:10:19 -0600 Subject: [PATCH 01/14] Add link syntax highlighting with WKSourceEditorFormatterLink --- .../WKSourceEditorTextFrameworkMediator.swift | 7 +- .../Sources/Components/Style/WKTheme.swift | 13 +- .../ComponentsObjC/WKSourceEditorColors.h | 1 + .../WKSourceEditorFormatterLink.h | 9 + .../WKSourceEditorFormatterLink.m | 201 ++++++++++++++++++ .../ComponentsObjC/include/ComponentsObjC.h | 1 + .../include/WKSourceEditorFormatterLink.h | 1 + 7 files changed, 228 insertions(+), 5 deletions(-) create mode 100644 Components/Sources/ComponentsObjC/WKSourceEditorFormatterLink.h create mode 100644 Components/Sources/ComponentsObjC/WKSourceEditorFormatterLink.m create mode 120000 Components/Sources/ComponentsObjC/include/WKSourceEditorFormatterLink.h diff --git a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift index b01e9dfb207..0db217fc725 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift @@ -38,6 +38,7 @@ final class WKSourceEditorTextFrameworkMediator: NSObject { private(set) var boldItalicsFormatter: WKSourceEditorFormatterBoldItalics? private(set) var templateFormatter: WKSourceEditorFormatterTemplate? private(set) var strikethroughFormatter: WKSourceEditorFormatterStrikethrough? + private(set) var linkFormatter: WKSourceEditorFormatterLink? var isSyntaxHighlightingEnabled: Bool = true { didSet { @@ -107,14 +108,17 @@ 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 linkFormatter = WKSourceEditorFormatterLink(colors: colors, fonts: fonts) self.formatters = [WKSourceEditorFormatterBase(colors: colors, fonts: fonts, textAlignment: viewModel.textAlignment), templateFormatter, boldItalicsFormatter, - strikethroughFormatter] + strikethroughFormatter, + linkFormatter] self.boldItalicsFormatter = boldItalicsFormatter self.templateFormatter = templateFormatter self.strikethroughFormatter = strikethroughFormatter + self.linkFormatter = linkFormatter if needsTextKit2 { if #available(iOS 16.0, *) { @@ -211,6 +215,7 @@ extension WKSourceEditorTextFrameworkMediator: WKSourceEditorStorageDelegate { 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 + colors.blueForegroundColor = isSyntaxHighlightingEnabled ? WKAppEnvironment.current.theme.editorBlue : WKAppEnvironment.current.theme.text return colors } diff --git a/Components/Sources/Components/Style/WKTheme.swift b/Components/Sources/Components/Style/WKTheme.swift index e5751d2af00..5d894d75da0 100644 --- a/Components/Sources/Components/Style/WKTheme.swift +++ b/Components/Sources/Components/Style/WKTheme.swift @@ -27,6 +27,7 @@ public struct WKTheme: Equatable { public let editorOrange: UIColor public let editorPurple: UIColor public let editorGreen: UIColor + public let editorBlue: UIColor public static let light = WKTheme( name: "Light", @@ -52,7 +53,8 @@ public struct WKTheme: Equatable { diffCompareAccent: WKColor.orange600, editorOrange: WKColor.orange600, editorPurple: WKColor.purple600, - editorGreen: WKColor.green600 + editorGreen: WKColor.green600, + editorBlue: WKColor.blue600 ) public static let sepia = WKTheme( @@ -79,7 +81,8 @@ public struct WKTheme: Equatable { diffCompareAccent: WKColor.orange600, editorOrange: WKColor.orange600, editorPurple: WKColor.purple600, - editorGreen: WKColor.green600 + editorGreen: WKColor.green600, + editorBlue: WKColor.blue600 ) public static let dark = WKTheme( @@ -106,7 +109,8 @@ public struct WKTheme: Equatable { diffCompareAccent: WKColor.orange600, editorOrange: WKColor.yellow600, editorPurple: WKColor.red100, - editorGreen: WKColor.green600 + editorGreen: WKColor.green600, + editorBlue: WKColor.blue300 ) public static let black = WKTheme( @@ -133,7 +137,8 @@ public struct WKTheme: Equatable { diffCompareAccent: WKColor.orange600, editorOrange: WKColor.yellow600, editorPurple: WKColor.red100, - editorGreen: WKColor.green600 + editorGreen: WKColor.green600, + editorBlue: WKColor.blue300 ) } diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorColors.h b/Components/Sources/ComponentsObjC/WKSourceEditorColors.h index 9b3bf77a05f..ca3a79843fd 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorColors.h +++ b/Components/Sources/ComponentsObjC/WKSourceEditorColors.h @@ -7,6 +7,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, strong) UIColor *orangeForegroundColor; @property (nonatomic, strong) UIColor *purpleForegroundColor; @property (nonatomic, strong) UIColor *greenForegroundColor; +@property (nonatomic, strong) UIColor *blueForegroundColor; @end NS_ASSUME_NONNULL_END diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterLink.h b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterLink.h new file mode 100644 index 00000000000..0c51bf39747 --- /dev/null +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterLink.h @@ -0,0 +1,9 @@ +#import "WKSourceEditorFormatter.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface WKSourceEditorFormatterLink : WKSourceEditorFormatter + +@end + +NS_ASSUME_NONNULL_END diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterLink.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterLink.m new file mode 100644 index 00000000000..575b97dbe5d --- /dev/null +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterLink.m @@ -0,0 +1,201 @@ +#import "WKSourceEditorFormatterLink.h" +#import "WKSourceEditorColors.h" + +@interface WKSourceEditorFormatterLink () + +@property (nonatomic, strong) NSDictionary *linkMarkupAttributes; +@property (nonatomic, strong) NSDictionary *linkContentAttributes; +@property (nonatomic, strong) NSDictionary *linkWithNestedLinkMarkupAndContentAttributes; +@property (nonatomic, strong) NSRegularExpression *linkRegex; +@property (nonatomic, strong) NSRegularExpression *linkWithNestedLinkRegex; + +@end + +#pragma mark - Custom Attributed String Keys + +NSString * const WKSourceEditorCustomKeyColorBlue = @"WKSourceEditorCustomKeyColorBlue"; +NSString * const WKSourceEditorCustomKeyMarkupLink = @"WKSourceEditorCustomKeyMarkupLink"; +NSString * const WKSourceEditorCustomKeyContentLink = @"WKSourceEditorCustomKeyContentLink"; +NSString * const WKSourceEditorCustomKeyMarkupAndContentLinkWithNestedLink = @"WKSourceEditorCustomKeyMarkupAndContentLinkWithNestedLink"; + +@implementation WKSourceEditorFormatterLink + +#pragma mark - Public + +- (instancetype)initWithColors:(nonnull WKSourceEditorColors *)colors fonts:(nonnull WKSourceEditorFonts *)fonts { + self = [super initWithColors:colors fonts:fonts]; + if (self) { + _linkMarkupAttributes = @{ + WKSourceEditorCustomKeyMarkupLink: [NSNumber numberWithBool:YES], + NSForegroundColorAttributeName: colors.blueForegroundColor, + WKSourceEditorCustomKeyColorBlue: [NSNumber numberWithBool:YES] + }; + + _linkContentAttributes = @{ + WKSourceEditorCustomKeyContentLink: [NSNumber numberWithBool:YES], + NSForegroundColorAttributeName: colors.blueForegroundColor, + WKSourceEditorCustomKeyColorBlue: [NSNumber numberWithBool:YES] + }; + + _linkRegex = [[NSRegularExpression alloc] initWithPattern:@"(\\[{2})([^\\[\\]\\n]*)(\\]{2})" options:0 error:nil]; + + _linkWithNestedLinkMarkupAndContentAttributes = @{ + WKSourceEditorCustomKeyMarkupAndContentLinkWithNestedLink: [NSNumber numberWithBool:YES], + NSForegroundColorAttributeName: colors.blueForegroundColor, + WKSourceEditorCustomKeyColorBlue: [NSNumber numberWithBool:YES] + }; + + _linkWithNestedLinkRegex = [[NSRegularExpression alloc] initWithPattern:@"\\[{2}[^\\[\\]\\n]*\\[{2}" options:0 error:nil]; + } + + return self; +} + +#pragma mark - Overrides + +- (void)addSyntaxHighlightingToAttributedString:(nonnull NSMutableAttributedString *)attributedString inRange:(NSRange)range { + + // Reset + [attributedString removeAttribute:WKSourceEditorCustomKeyColorBlue range:range]; + [attributedString removeAttribute:WKSourceEditorCustomKeyContentLink range:range]; + [attributedString removeAttribute:WKSourceEditorCustomKeyMarkupAndContentLinkWithNestedLink range:range]; + + // This section finds and highlights simple links that do NOT contain nested links, e.g. [[Cat]] and [[Dog|puppy]]. + [self.linkRegex 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.linkMarkupAttributes range:openingRange]; + } + + if (contentRange.location != NSNotFound) { + [attributedString addAttributes:self.linkContentAttributes range:contentRange]; + } + + if (closingRange.location != NSNotFound) { + [attributedString addAttributes:self.linkMarkupAttributes range:closingRange]; + } + }]; + + // Note: This section finds and highlights links with nested links, which is common in image links. The regex matches any opening markup [[ followed by non-markup characters, then another opening markup [[. We then start to loop character-by-character, matching opening and closing tags to find and highlight links that contain other links. + // Originally I tried to allow for infinite nested links via regex alone, but it performed too poorly. + [self.linkWithNestedLinkRegex enumerateMatchesInString:attributedString.string + options:0 + range:range + usingBlock:^(NSTextCheckingResult *_Nullable result, NSMatchingFlags flags, BOOL *_Nonnull stop) { + + NSRange match = [result rangeAtIndex:0]; + + if (match.location != NSNotFound) { + + NSArray *linkWithNestedLinkRanges = [self linkWithNestedLinkRangesInString:attributedString.string startingIndex:match.location]; + + for (NSValue *value in linkWithNestedLinkRanges) { + NSRange range = [value rangeValue]; + if (range.location != NSNotFound) { + [attributedString addAttributes:self.linkWithNestedLinkMarkupAndContentAttributes range:range]; + } + } + } + }]; +} + +- (void)updateColors:(WKSourceEditorColors *)colors inAttributedString:(NSMutableAttributedString *)attributedString inRange:(NSRange)range { + + NSMutableDictionary *mutLinkMarkupAttributes = [[NSMutableDictionary alloc] initWithDictionary:self.linkMarkupAttributes]; + [mutLinkMarkupAttributes setObject:colors.blueForegroundColor forKey:NSForegroundColorAttributeName]; + self.linkMarkupAttributes = [[NSDictionary alloc] initWithDictionary:mutLinkMarkupAttributes]; + + NSMutableDictionary *mutLinkContentAttributes = [[NSMutableDictionary alloc] initWithDictionary:self.linkContentAttributes]; + [mutLinkContentAttributes setObject:colors.blueForegroundColor forKey:NSForegroundColorAttributeName]; + self.linkContentAttributes = [[NSDictionary alloc] initWithDictionary:mutLinkContentAttributes]; + + NSMutableDictionary *mutLinkWithNestedLinkMarkupAndContentAttributes = [[NSMutableDictionary alloc] initWithDictionary:self.linkWithNestedLinkMarkupAndContentAttributes]; + [mutLinkWithNestedLinkMarkupAndContentAttributes setObject:colors.blueForegroundColor forKey:NSForegroundColorAttributeName]; + self.linkWithNestedLinkMarkupAndContentAttributes = [[NSDictionary alloc] initWithDictionary:mutLinkWithNestedLinkMarkupAndContentAttributes]; + + [attributedString enumerateAttribute:WKSourceEditorCustomKeyColorBlue + 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:@{NSForegroundColorAttributeName: colors.blueForegroundColor} range:localRange]; + } + } + }]; +} + +- (void)updateFonts:(WKSourceEditorFonts *)fonts inAttributedString:(NSMutableAttributedString *)attributedString inRange:(NSRange)range { + // No special font handling needed +} + +#pragma mark - Private + +- (NSArray *)linkWithNestedLinkRangesInString: (NSString *)string startingIndex: (NSUInteger)index { + NSMutableArray *openingRanges = [[NSMutableArray alloc] init]; + NSMutableArray *completedLinkRanges = [[NSMutableArray alloc] init]; + NSMutableArray *completedLinkWithNestedLinkRanges = [[NSMutableArray alloc] init]; + + // Loop through and evaluate characters in pairs, keeping track of opening and closing pairs + BOOL lastCompletedLinkRangeWasNested = NO; + for (NSUInteger i = index; i < string.length; i++) { + + unichar currentChar = [string characterAtIndex:i]; + + if (currentChar == '\n') { + break; + } + + if (i + 1 >= string.length) { + break; + } + + NSString *currentCharString = [NSString stringWithFormat:@"%c", currentChar]; + unichar nextChar = [string characterAtIndex:i + 1]; + NSString *nextCharString = [NSString stringWithFormat:@"%c", nextChar]; + NSString *pair = [NSString stringWithFormat:@"%@%@", currentCharString, nextCharString]; + + if ([pair isEqualToString:@"[["]) { + [openingRanges addObject:[NSValue valueWithRange:NSMakeRange(i, 2)]]; + } + + if ([pair isEqualToString:@"]]"] && openingRanges.count == 0) { + // invalid, closed markup before opening + break; + } + + if ([pair isEqualToString:@"]]"]) { + + NSValue *lastOpeningRange = openingRanges.lastObject; + if (lastOpeningRange) { + [openingRanges removeLastObject]; + } + + NSRange unionRange = NSUnionRange(lastOpeningRange.rangeValue, NSMakeRange(i, 2)); + NSValue *linkRange = [NSValue valueWithRange:unionRange]; + [completedLinkRanges addObject: linkRange]; + + if (lastCompletedLinkRangeWasNested && openingRanges.count == 0) { + [completedLinkWithNestedLinkRanges addObject:linkRange]; + } + + if (openingRanges.count > 0) { + lastCompletedLinkRangeWasNested = YES; + } else { + lastCompletedLinkRangeWasNested = NO; + } + } + } + + return [[NSArray alloc] initWithArray:completedLinkWithNestedLinkRanges]; +} + +@end diff --git a/Components/Sources/ComponentsObjC/include/ComponentsObjC.h b/Components/Sources/ComponentsObjC/include/ComponentsObjC.h index 7a22f776263..e3f0f6651b5 100644 --- a/Components/Sources/ComponentsObjC/include/ComponentsObjC.h +++ b/Components/Sources/ComponentsObjC/include/ComponentsObjC.h @@ -9,6 +9,7 @@ #import "WKSourceEditorFormatterBoldItalics.h" #import "WKSourceEditorFormatterTemplate.h" #import "WKSourceEditorFormatterStrikethrough.h" +#import "WKSourceEditorFormatterLink.h" #import "WKSourceEditorStorageDelegate.h" #endif /* Header_h */ diff --git a/Components/Sources/ComponentsObjC/include/WKSourceEditorFormatterLink.h b/Components/Sources/ComponentsObjC/include/WKSourceEditorFormatterLink.h new file mode 120000 index 00000000000..aec15185f91 --- /dev/null +++ b/Components/Sources/ComponentsObjC/include/WKSourceEditorFormatterLink.h @@ -0,0 +1 @@ +../WKSourceEditorFormatterLink.h \ No newline at end of file From 0a02192f422551acfd55ac57e38c38393ec0f546 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Fri, 5 Jan 2024 11:35:15 -0600 Subject: [PATCH 02/14] Add syntax highlighting tests --- .../WKSourceEditorFormatterTests.swift | 167 +++++++++++++++++- 1 file changed, 166 insertions(+), 1 deletion(-) diff --git a/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift index a3764296794..f54b394874f 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 linkFormatter: WKSourceEditorFormatterLink! var formatters: [WKSourceEditorFormatter] { - return [baseFormatter, templateFormatter, boldItalicsFormatter, strikethroughFormatter] + return [baseFormatter, templateFormatter, boldItalicsFormatter, strikethroughFormatter, linkFormatter] } override func setUpWithError() throws { @@ -23,6 +24,7 @@ final class WKSourceEditorFormatterTests: XCTestCase { self.colors.orangeForegroundColor = WKTheme.light.editorOrange self.colors.purpleForegroundColor = WKTheme.light.editorPurple self.colors.greenForegroundColor = WKTheme.light.editorGreen + self.colors.blueForegroundColor = WKTheme.light.editorBlue self.fonts = WKSourceEditorFonts() self.fonts.baseFont = WKFont.for(.body, compatibleWith: traitCollection) @@ -34,6 +36,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.linkFormatter = WKSourceEditorFormatterLink(colors: colors, fonts: fonts) } override func tearDownWithError() throws { @@ -766,4 +769,166 @@ 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 testPlainLink() { + let string = "Testing. [[Link with spaces]]. 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 linkOpenRange = NSRange(location: 0, length: 0) + let linkOpenAttributes = mutAttributedString.attributes(at: 9, effectiveRange: &linkOpenRange) + + var linkContentRange = NSRange(location: 0, length: 0) + let linkContentAttributes = mutAttributedString.attributes(at: 11, effectiveRange: &linkContentRange) + + var linkCloseRange = NSRange(location: 0, length: 0) + let linkCloseAttributes = mutAttributedString.attributes(at: 27, effectiveRange: &linkCloseRange) + + var base2Range = NSRange(location: 0, length: 0) + let base2Attributes = mutAttributedString.attributes(at: 29, 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(linkOpenRange.location, 9, "Incorrect link formatting") + XCTAssertEqual(linkOpenRange.length, 2, "Incorrect link formatting") + XCTAssertEqual(linkOpenAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect link formatting") + XCTAssertEqual(linkOpenAttributes[.foregroundColor] as! UIColor, colors.blueForegroundColor, "Incorrect link formatting") + + // "Link with spaces" + XCTAssertEqual(linkContentRange.location, 11, "Incorrect link content formatting") + XCTAssertEqual(linkContentRange.length, 16, "Incorrect link content formatting") + XCTAssertEqual(linkContentAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect link content formatting") + XCTAssertEqual(linkContentAttributes[.foregroundColor] as! UIColor, colors.blueForegroundColor, "Incorrect content formatting") + + // "]]" + XCTAssertEqual(linkCloseRange.location, 27, "Incorrect link formatting") + XCTAssertEqual(linkCloseRange.length, 2, "Incorrect link formatting") + XCTAssertEqual(linkCloseAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect link formatting") + XCTAssertEqual(linkCloseAttributes[.foregroundColor] as! UIColor, colors.blueForegroundColor, "Incorrect link formatting") + + // ". Testing" + XCTAssertEqual(base2Range.location, 29, "Incorrect base formatting") + XCTAssertEqual(base2Range.length, 9, "Incorrect base formatting") + XCTAssertEqual(base2Attributes[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") + XCTAssertEqual(base2Attributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect base formatting") + } + + func testPipedLink() { + let string = "Testing. [[Link|Link with spaces]]. 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 linkOpenRange = NSRange(location: 0, length: 0) + let linkOpenAttributes = mutAttributedString.attributes(at: 9, effectiveRange: &linkOpenRange) + + var linkContentRange = NSRange(location: 0, length: 0) + let linkContentAttributes = mutAttributedString.attributes(at: 11, effectiveRange: &linkContentRange) + + var linkCloseRange = NSRange(location: 0, length: 0) + let linkCloseAttributes = mutAttributedString.attributes(at: 32, effectiveRange: &linkCloseRange) + + var base2Range = NSRange(location: 0, length: 0) + let base2Attributes = mutAttributedString.attributes(at: 34, 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(linkOpenRange.location, 9, "Incorrect link formatting") + XCTAssertEqual(linkOpenRange.length, 2, "Incorrect link formatting") + XCTAssertEqual(linkOpenAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect link formatting") + XCTAssertEqual(linkOpenAttributes[.foregroundColor] as! UIColor, colors.blueForegroundColor, "Incorrect link formatting") + + // "Link|Link with spaces" + XCTAssertEqual(linkContentRange.location, 11, "Incorrect link content formatting") + XCTAssertEqual(linkContentRange.length, 21, "Incorrect link content formatting") + XCTAssertEqual(linkContentAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect link content formatting") + XCTAssertEqual(linkContentAttributes[.foregroundColor] as! UIColor, colors.blueForegroundColor, "Incorrect content formatting") + + // "]]" + XCTAssertEqual(linkCloseRange.location, 32, "Incorrect link formatting") + XCTAssertEqual(linkCloseRange.length, 2, "Incorrect link formatting") + XCTAssertEqual(linkCloseAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect link formatting") + XCTAssertEqual(linkCloseAttributes[.foregroundColor] as! UIColor, colors.blueForegroundColor, "Incorrect link formatting") + + // ". Testing" + XCTAssertEqual(base2Range.location, 34, "Incorrect base formatting") + XCTAssertEqual(base2Range.length, 9, "Incorrect base formatting") + XCTAssertEqual(base2Attributes[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") + XCTAssertEqual(base2Attributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect base formatting") + } + + func testLinkFileWithNestedLinks() { + let string = "[[File:Cat with fish.jpg|thumb|left|Cat with [[fish]]|alt=Photo of cat looking at fish]]" + let mutAttributedString = NSMutableAttributedString(string: string) + + for formatter in formatters { + formatter.addSyntaxHighlighting(to: mutAttributedString, in: NSRange(location: 0, length: string.count)) + } + + var linkRange1 = NSRange(location: 0, length: 0) + let linkAttributes1 = mutAttributedString.attributes(at: 0, effectiveRange: &linkRange1) + + var linkRange2 = NSRange(location: 0, length: 0) + let linkAttributes2 = mutAttributedString.attributes(at: 45, effectiveRange: &linkRange2) + + var linkRange3 = NSRange(location: 0, length: 0) + let linkAttributes3 = mutAttributedString.attributes(at: 47, effectiveRange: &linkRange3) + + var linkRange4 = NSRange(location: 0, length: 0) + let linkAttributes4 = mutAttributedString.attributes(at: 51, effectiveRange: &linkRange4) + + var linkRange5 = NSRange(location: 0, length: 0) + let linkAttributes5 = mutAttributedString.attributes(at: 53, effectiveRange: &linkRange5) + + //"[[File:Cat with fish.jpg|thumb|left|Cat with " + XCTAssertEqual(linkRange1.location, 0, "Incorrect link formatting") + XCTAssertEqual(linkRange1.length, 45, "Incorrect link formatting") + XCTAssertEqual(linkAttributes1[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") + XCTAssertEqual(linkAttributes1[.foregroundColor] as! UIColor, colors.blueForegroundColor, "Incorrect link formatting") + + //"[[" + XCTAssertEqual(linkRange2.location, 45, "Incorrect link formatting") + XCTAssertEqual(linkRange2.length, 2, "Incorrect link formatting") + XCTAssertEqual(linkAttributes2[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") + XCTAssertEqual(linkAttributes2[.foregroundColor] as! UIColor, colors.blueForegroundColor, "Incorrect link formatting") + + //"fish" + XCTAssertEqual(linkRange3.location, 47, "Incorrect link formatting") + XCTAssertEqual(linkRange3.length, 4, "Incorrect link formatting") + XCTAssertEqual(linkAttributes3[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") + XCTAssertEqual(linkAttributes3[.foregroundColor] as! UIColor, colors.blueForegroundColor, "Incorrect link formatting") + + //"]]" + XCTAssertEqual(linkRange4.location, 51, "Incorrect link formatting") + XCTAssertEqual(linkRange4.length, 2, "Incorrect link formatting") + XCTAssertEqual(linkAttributes4[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") + XCTAssertEqual(linkAttributes4[.foregroundColor] as! UIColor, colors.blueForegroundColor, "Incorrect link formatting") + + //"|alt=Photo of cat looking at fish]]" + XCTAssertEqual(linkRange5.location, 53, "Incorrect link formatting") + XCTAssertEqual(linkRange5.length, 35, "Incorrect link formatting") + XCTAssertEqual(linkAttributes5[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") + XCTAssertEqual(linkAttributes5[.foregroundColor] as! UIColor, colors.blueForegroundColor, "Incorrect link formatting") + } } From a2db26a83ef9618f5c83987fdb3091cc22290b8d Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Fri, 5 Jan 2024 11:49:48 -0600 Subject: [PATCH 03/14] Add 'simple' prefix for clarity --- .../WKSourceEditorFormatterLink.m | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterLink.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterLink.m index 575b97dbe5d..1d3bd1382bc 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterLink.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterLink.m @@ -3,10 +3,10 @@ @interface WKSourceEditorFormatterLink () -@property (nonatomic, strong) NSDictionary *linkMarkupAttributes; -@property (nonatomic, strong) NSDictionary *linkContentAttributes; +@property (nonatomic, strong) NSDictionary *simpleLinkMarkupAttributes; +@property (nonatomic, strong) NSDictionary *simpleLinkContentAttributes; @property (nonatomic, strong) NSDictionary *linkWithNestedLinkMarkupAndContentAttributes; -@property (nonatomic, strong) NSRegularExpression *linkRegex; +@property (nonatomic, strong) NSRegularExpression *simpleLinkRegex; @property (nonatomic, strong) NSRegularExpression *linkWithNestedLinkRegex; @end @@ -25,19 +25,19 @@ @implementation WKSourceEditorFormatterLink - (instancetype)initWithColors:(nonnull WKSourceEditorColors *)colors fonts:(nonnull WKSourceEditorFonts *)fonts { self = [super initWithColors:colors fonts:fonts]; if (self) { - _linkMarkupAttributes = @{ + _simpleLinkMarkupAttributes = @{ WKSourceEditorCustomKeyMarkupLink: [NSNumber numberWithBool:YES], NSForegroundColorAttributeName: colors.blueForegroundColor, WKSourceEditorCustomKeyColorBlue: [NSNumber numberWithBool:YES] }; - _linkContentAttributes = @{ + _simpleLinkContentAttributes = @{ WKSourceEditorCustomKeyContentLink: [NSNumber numberWithBool:YES], NSForegroundColorAttributeName: colors.blueForegroundColor, WKSourceEditorCustomKeyColorBlue: [NSNumber numberWithBool:YES] }; - _linkRegex = [[NSRegularExpression alloc] initWithPattern:@"(\\[{2})([^\\[\\]\\n]*)(\\]{2})" options:0 error:nil]; + _simpleLinkRegex = [[NSRegularExpression alloc] initWithPattern:@"(\\[{2})([^\\[\\]\\n]*)(\\]{2})" options:0 error:nil]; _linkWithNestedLinkMarkupAndContentAttributes = @{ WKSourceEditorCustomKeyMarkupAndContentLinkWithNestedLink: [NSNumber numberWithBool:YES], @@ -61,7 +61,7 @@ - (void)addSyntaxHighlightingToAttributedString:(nonnull NSMutableAttributedStri [attributedString removeAttribute:WKSourceEditorCustomKeyMarkupAndContentLinkWithNestedLink range:range]; // This section finds and highlights simple links that do NOT contain nested links, e.g. [[Cat]] and [[Dog|puppy]]. - [self.linkRegex enumerateMatchesInString:attributedString.string + [self.simpleLinkRegex enumerateMatchesInString:attributedString.string options:0 range:range usingBlock:^(NSTextCheckingResult *_Nullable result, NSMatchingFlags flags, BOOL *_Nonnull stop) { @@ -71,15 +71,15 @@ - (void)addSyntaxHighlightingToAttributedString:(nonnull NSMutableAttributedStri NSRange closingRange = [result rangeAtIndex:3]; if (openingRange.location != NSNotFound) { - [attributedString addAttributes:self.linkMarkupAttributes range:openingRange]; + [attributedString addAttributes:self.simpleLinkMarkupAttributes range:openingRange]; } if (contentRange.location != NSNotFound) { - [attributedString addAttributes:self.linkContentAttributes range:contentRange]; + [attributedString addAttributes:self.simpleLinkContentAttributes range:contentRange]; } if (closingRange.location != NSNotFound) { - [attributedString addAttributes:self.linkMarkupAttributes range:closingRange]; + [attributedString addAttributes:self.simpleLinkMarkupAttributes range:closingRange]; } }]; @@ -108,13 +108,13 @@ - (void)addSyntaxHighlightingToAttributedString:(nonnull NSMutableAttributedStri - (void)updateColors:(WKSourceEditorColors *)colors inAttributedString:(NSMutableAttributedString *)attributedString inRange:(NSRange)range { - NSMutableDictionary *mutLinkMarkupAttributes = [[NSMutableDictionary alloc] initWithDictionary:self.linkMarkupAttributes]; - [mutLinkMarkupAttributes setObject:colors.blueForegroundColor forKey:NSForegroundColorAttributeName]; - self.linkMarkupAttributes = [[NSDictionary alloc] initWithDictionary:mutLinkMarkupAttributes]; + NSMutableDictionary *mutSimpleLinkMarkupAttributes = [[NSMutableDictionary alloc] initWithDictionary:self.simpleLinkMarkupAttributes]; + [mutSimpleLinkMarkupAttributes setObject:colors.blueForegroundColor forKey:NSForegroundColorAttributeName]; + self.simpleLinkMarkupAttributes = [[NSDictionary alloc] initWithDictionary:mutSimpleLinkMarkupAttributes]; - NSMutableDictionary *mutLinkContentAttributes = [[NSMutableDictionary alloc] initWithDictionary:self.linkContentAttributes]; - [mutLinkContentAttributes setObject:colors.blueForegroundColor forKey:NSForegroundColorAttributeName]; - self.linkContentAttributes = [[NSDictionary alloc] initWithDictionary:mutLinkContentAttributes]; + NSMutableDictionary *mutSimpleLinkContentAttributes = [[NSMutableDictionary alloc] initWithDictionary:self.simpleLinkContentAttributes]; + [mutSimpleLinkContentAttributes setObject:colors.blueForegroundColor forKey:NSForegroundColorAttributeName]; + self.simpleLinkContentAttributes = [[NSDictionary alloc] initWithDictionary:mutSimpleLinkContentAttributes]; NSMutableDictionary *mutLinkWithNestedLinkMarkupAndContentAttributes = [[NSMutableDictionary alloc] initWithDictionary:self.linkWithNestedLinkMarkupAndContentAttributes]; [mutLinkWithNestedLinkMarkupAndContentAttributes setObject:colors.blueForegroundColor forKey:NSForegroundColorAttributeName]; From 896acbe425eca9aae3b2077515c775e66738fc6a Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Fri, 5 Jan 2024 12:01:06 -0600 Subject: [PATCH 04/14] Allow formatter to detect if range contains custom link keys --- .../WKSourceEditorFormatterLink.h | 3 +- .../WKSourceEditorFormatterLink.m | 78 +++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterLink.h b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterLink.h index 0c51bf39747..321d69279e5 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterLink.h +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterLink.h @@ -3,7 +3,8 @@ NS_ASSUME_NONNULL_BEGIN @interface WKSourceEditorFormatterLink : WKSourceEditorFormatter - +- (BOOL)attributedString:(NSMutableAttributedString *)attributedString isSimpleLinkInRange:(NSRange)range; +- (BOOL)attributedString:(NSMutableAttributedString *)attributedString isLinkWithNestedLinkInRange:(NSRange)range; @end NS_ASSUME_NONNULL_END diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterLink.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterLink.m index 1d3bd1382bc..700a64a8460 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterLink.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterLink.m @@ -137,6 +137,84 @@ - (void)updateFonts:(WKSourceEditorFonts *)fonts inAttributedString:(NSMutableAt // No special font handling needed } +#pragma mark - Public + +- (BOOL)attributedString:(NSMutableAttributedString *)attributedString isSimpleLinkInRange:(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[WKSourceEditorCustomKeyContentLink] != nil) { + isContentKey = YES; + } else { + // Edge case, check previous character if we are up against closing markup + if (attrs[WKSourceEditorCustomKeyMarkupLink]) { + attrs = [attributedString attributesAtIndex:range.location - 1 effectiveRange:nil]; + if (attrs[WKSourceEditorCustomKeyContentLink] != 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[WKSourceEditorCustomKeyContentLink] != nil) { + if (unionRange.location == NSNotFound) { + unionRange = loopRange; + } else { + unionRange = NSUnionRange(unionRange, loopRange); + } + stop = YES; + } + }]; + + if (NSEqualRanges(unionRange, range)) { + isContentKey = YES; + } + } + + return isContentKey; +} + +- (BOOL)attributedString:(NSMutableAttributedString *)attributedString isLinkWithNestedLinkInRange:(NSRange)range { + + __block BOOL isKey = NO; + if (range.length == 0) { + + if (attributedString.length > range.location) { + NSDictionary *attrs = [attributedString attributesAtIndex:range.location effectiveRange:nil]; + + if (attrs[WKSourceEditorCustomKeyMarkupAndContentLinkWithNestedLink] != nil) { + isKey = YES; + } + } + + } else { + __block NSRange unionRange = NSMakeRange(NSNotFound, 0); + [attributedString enumerateAttributesInRange:range options:nil usingBlock:^(NSDictionary * _Nonnull attrs, NSRange loopRange, BOOL * _Nonnull stop) { + if (attrs[WKSourceEditorCustomKeyMarkupAndContentLinkWithNestedLink] != nil) { + if (unionRange.location == NSNotFound) { + unionRange = loopRange; + } else { + unionRange = NSUnionRange(unionRange, loopRange); + } + stop = YES; + } + }]; + + if (NSEqualRanges(unionRange, range)) { + isKey = YES; + } + } + + return isKey; + +} + #pragma mark - Private - (NSArray *)linkWithNestedLinkRangesInString: (NSString *)string startingIndex: (NSUInteger)index { From 7b03f84d9eb02dd6e3ee08d833ff42c4e4203a3b Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Fri, 5 Jan 2024 12:01:18 -0600 Subject: [PATCH 05/14] Add link properties to WKSourceEditorSelectionState --- .../WKSourceEditorTextFrameworkMediator.swift | 18 +++++++++++++----- 1 file changed, 13 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 0db217fc725..f383b31e574 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorTextFrameworkMediator.swift @@ -18,12 +18,16 @@ fileprivate var needsTextKit2: Bool { let isItalics: Bool let isHorizontalTemplate: Bool let isStrikethrough: Bool + let isSimpleLink: Bool + let isLinkWithNestedLink: Bool - init(isBold: Bool, isItalics: Bool, isHorizontalTemplate: Bool, isStrikethrough: Bool) { + init(isBold: Bool, isItalics: Bool, isHorizontalTemplate: Bool, isStrikethrough: Bool, isSimpleLink: Bool, isLinkWithNestedLink: Bool) { self.isBold = isBold self.isItalics = isItalics self.isHorizontalTemplate = isHorizontalTemplate self.isStrikethrough = isStrikethrough + self.isSimpleLink = isSimpleLink + self.isLinkWithNestedLink = isLinkWithNestedLink } } @@ -158,26 +162,30 @@ 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, isSimpleLink: false, isLinkWithNestedLink: false) } let isBold = boldItalicsFormatter?.attributedString(textKit2Data.paragraphAttributedString, isBoldIn: textKit2Data.paragraphSelectedRange) ?? false let isItalics = boldItalicsFormatter?.attributedString(textKit2Data.paragraphAttributedString, isItalicsIn: textKit2Data.paragraphSelectedRange) ?? false let isHorizontalTemplate = templateFormatter?.attributedString(textKit2Data.paragraphAttributedString, isHorizontalTemplateIn: textKit2Data.paragraphSelectedRange) ?? false let isStrikethrough = strikethroughFormatter?.attributedString(textKit2Data.paragraphAttributedString, isStrikethroughIn: textKit2Data.paragraphSelectedRange) ?? false + let isSimpleLink = linkFormatter?.attributedString(textKit2Data.paragraphAttributedString, isSimpleLinkIn: textKit2Data.paragraphSelectedRange) ?? false + let isLinkWithNestedLink = linkFormatter?.attributedString(textKit2Data.paragraphAttributedString, isLinkWithNestedLinkIn: textKit2Data.paragraphSelectedRange) ?? false - return WKSourceEditorSelectionState(isBold: isBold, isItalics: isItalics, isHorizontalTemplate: isHorizontalTemplate, isStrikethrough: isStrikethrough) + return WKSourceEditorSelectionState(isBold: isBold, isItalics: isItalics, isHorizontalTemplate: isHorizontalTemplate, isStrikethrough: isStrikethrough, isSimpleLink: isSimpleLink, isLinkWithNestedLink: isLinkWithNestedLink) } 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, isSimpleLink: false, isLinkWithNestedLink: false) } let isBold = boldItalicsFormatter?.attributedString(textKit1Storage, isBoldIn: selectedDocumentRange) ?? false let isItalics = boldItalicsFormatter?.attributedString(textKit1Storage, isItalicsIn: selectedDocumentRange) ?? false let isHorizontalTemplate = templateFormatter?.attributedString(textKit1Storage, isHorizontalTemplateIn: selectedDocumentRange) ?? false let isStrikethrough = strikethroughFormatter?.attributedString(textKit1Storage, isStrikethroughIn: selectedDocumentRange) ?? false + let isSimpleLink = linkFormatter?.attributedString(textKit1Storage, isSimpleLinkIn: selectedDocumentRange) ?? false + let isLinkWithNestedLink = linkFormatter?.attributedString(textKit1Storage, isLinkWithNestedLinkIn: selectedDocumentRange) ?? false - return WKSourceEditorSelectionState(isBold: isBold, isItalics: isItalics, isHorizontalTemplate: isHorizontalTemplate, isStrikethrough: isStrikethrough) + return WKSourceEditorSelectionState(isBold: isBold, isItalics: isItalics, isHorizontalTemplate: isHorizontalTemplate, isStrikethrough: isStrikethrough, isSimpleLink: isSimpleLink, isLinkWithNestedLink: isLinkWithNestedLink) } } From e430cb45b25d9decc0e87773db5b67c6e34cf96f Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Fri, 5 Jan 2024 12:10:10 -0600 Subject: [PATCH 06/14] Add delegate callback methods, swiftlint changes --- .../WKEditorToolbarExpandingView.swift | 4 ++++ .../WKEditorToolbarHighlightView.swift | 2 ++ .../WKEditorToolbarPlainView.swift | 1 + .../WKEditorInputViewController.swift | 3 ++- .../WKSourceEditorViewController.swift | 19 ++++++++++++++++++- .../WKSourceEditorFormatterTests.swift | 10 +++++----- 6 files changed, 32 insertions(+), 7 deletions(-) diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Expanding/WKEditorToolbarExpandingView.swift b/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Expanding/WKEditorToolbarExpandingView.swift index bbf183ad600..51b0691c643 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Expanding/WKEditorToolbarExpandingView.swift +++ b/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Expanding/WKEditorToolbarExpandingView.swift @@ -5,6 +5,8 @@ protocol WKEditorToolbarExpandingViewDelegate: AnyObject { func toolbarExpandingViewDidTapFormatText(toolbarView: WKEditorToolbarExpandingView) func toolbarExpandingViewDidTapFormatHeading(toolbarView: WKEditorToolbarExpandingView) func toolbarExpandingViewDidTapTemplate(toolbarView: WKEditorToolbarExpandingView, isSelected: Bool) + func toolbarExpandingViewDidTapLink(toolbarView: WKEditorToolbarExpandingView, isSelected: Bool) + func toolbarExpandingViewDidTapImage(toolbarView: WKEditorToolbarExpandingView) } class WKEditorToolbarExpandingView: WKEditorToolbarView { @@ -204,6 +206,7 @@ class WKEditorToolbarExpandingView: WKEditorToolbarView { } @objc private func tappedLink() { + delegate?.toolbarExpandingViewDidTapLink(toolbarView: self, isSelected: linkButton.isSelected) } @objc private func tappedUnorderedList() { @@ -239,6 +242,7 @@ class WKEditorToolbarExpandingView: WKEditorToolbarView { } @objc private func tappedMedia() { + delegate?.toolbarExpandingViewDidTapImage(toolbarView: self) } } diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Highlight/WKEditorToolbarHighlightView.swift b/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Highlight/WKEditorToolbarHighlightView.swift index 7003ab32d0c..92606443801 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Highlight/WKEditorToolbarHighlightView.swift +++ b/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Highlight/WKEditorToolbarHighlightView.swift @@ -4,6 +4,7 @@ protocol WKEditorToolbarHighlightViewDelegate: AnyObject { func toolbarHighlightViewDidTapBold(toolbarView: WKEditorToolbarHighlightView, isSelected: Bool) func toolbarHighlightViewDidTapItalics(toolbarView: WKEditorToolbarHighlightView, isSelected: Bool) func toolbarHighlightViewDidTapTemplate(toolbarView: WKEditorToolbarHighlightView, isSelected: Bool) + func toolbarHighlightViewDidTapLink(toolbarView: WKEditorToolbarHighlightView, isSelected: Bool) func toolbarHighlightViewDidTapShowMore(toolbarView: WKEditorToolbarHighlightView) func toolbarHighlightViewDidTapFormatHeading(toolbarView: WKEditorToolbarHighlightView) } @@ -100,6 +101,7 @@ class WKEditorToolbarHighlightView: WKEditorToolbarView { } @objc private func tappedLink() { + delegate?.toolbarHighlightViewDidTapLink(toolbarView: self, isSelected: linkButton.isSelected) } @objc private func tappedTemplate() { diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Plain Toolbar Table Item/WKEditorToolbarPlainView.swift b/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Plain Toolbar Table Item/WKEditorToolbarPlainView.swift index 8f9a0be668d..0e4ebf054a1 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Plain Toolbar Table Item/WKEditorToolbarPlainView.swift +++ b/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Plain Toolbar Table Item/WKEditorToolbarPlainView.swift @@ -78,5 +78,6 @@ class WKEditorToolbarPlainView: WKEditorToolbarView { } @objc private func tappedLink() { + delegate?.didTapLink(isSelected: linkButton.isSelected) } } 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..fff38b7b15e 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 @@ -2,11 +2,12 @@ import Foundation import UIKit protocol WKEditorInputViewDelegate: AnyObject { - func didTapClose() func didTapBold(isSelected: Bool) func didTapItalics(isSelected: Bool) func didTapTemplate(isSelected: Bool) func didTapStrikethrough(isSelected: Bool) + func didTapLink(isSelected: Bool) + func didTapClose() } 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 1eb2abfc40c..d3f645adf52 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift @@ -310,12 +310,20 @@ extension WKSourceEditorViewController: WKEditorToolbarExpandingViewDelegate { let action: WKSourceEditorFormatterButtonAction = isSelected ? .remove : .add textFrameworkMediator.templateFormatter?.toggleTemplateFormatting(action: action, in: textView) } + + func toolbarExpandingViewDidTapLink(toolbarView: WKEditorToolbarExpandingView, isSelected: Bool) { + + } + + func toolbarExpandingViewDidTapImage(toolbarView: WKEditorToolbarExpandingView) { + + } } // MARK: - WKEditorToolbarHighlightViewDelegate extension WKSourceEditorViewController: WKEditorToolbarHighlightViewDelegate { - + func toolbarHighlightViewDidTapBold(toolbarView: WKEditorToolbarHighlightView, isSelected: Bool) { let action: WKSourceEditorFormatterButtonAction = isSelected ? .remove : .add textFrameworkMediator.boldItalicsFormatter?.toggleBoldFormatting(action: action, in: textView) @@ -331,6 +339,10 @@ extension WKSourceEditorViewController: WKEditorToolbarHighlightViewDelegate { textFrameworkMediator.templateFormatter?.toggleTemplateFormatting(action: action, in: textView) } + func toolbarHighlightViewDidTapLink(toolbarView: WKEditorToolbarHighlightView, isSelected: Bool) { + + } + func toolbarHighlightViewDidTapShowMore(toolbarView: WKEditorToolbarHighlightView) { inputViewType = .main postUpdateButtonSelectionStatesNotification(withDelay: true) @@ -344,6 +356,7 @@ extension WKSourceEditorViewController: WKEditorToolbarHighlightViewDelegate { // MARK: - WKEditorInputViewDelegate extension WKSourceEditorViewController: WKEditorInputViewDelegate { + func didTapBold(isSelected: Bool) { let action: WKSourceEditorFormatterButtonAction = isSelected ? .remove : .add textFrameworkMediator.boldItalicsFormatter?.toggleBoldFormatting(action: action, in: textView) @@ -364,6 +377,10 @@ extension WKSourceEditorViewController: WKEditorInputViewDelegate { textFrameworkMediator.strikethroughFormatter?.toggleStrikethroughFormatting(action: action, in: textView) } + func didTapLink(isSelected: Bool) { + + } + func didTapClose() { inputViewType = nil let isRangeSelected = textView.selectedRange.length > 0 diff --git a/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift index f54b394874f..5a14925c774 100644 --- a/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift +++ b/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift @@ -901,31 +901,31 @@ final class WKSourceEditorFormatterTests: XCTestCase { var linkRange5 = NSRange(location: 0, length: 0) let linkAttributes5 = mutAttributedString.attributes(at: 53, effectiveRange: &linkRange5) - //"[[File:Cat with fish.jpg|thumb|left|Cat with " + // "[[File:Cat with fish.jpg|thumb|left|Cat with " XCTAssertEqual(linkRange1.location, 0, "Incorrect link formatting") XCTAssertEqual(linkRange1.length, 45, "Incorrect link formatting") XCTAssertEqual(linkAttributes1[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") XCTAssertEqual(linkAttributes1[.foregroundColor] as! UIColor, colors.blueForegroundColor, "Incorrect link formatting") - //"[[" + // "[[" XCTAssertEqual(linkRange2.location, 45, "Incorrect link formatting") XCTAssertEqual(linkRange2.length, 2, "Incorrect link formatting") XCTAssertEqual(linkAttributes2[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") XCTAssertEqual(linkAttributes2[.foregroundColor] as! UIColor, colors.blueForegroundColor, "Incorrect link formatting") - //"fish" + // "fish" XCTAssertEqual(linkRange3.location, 47, "Incorrect link formatting") XCTAssertEqual(linkRange3.length, 4, "Incorrect link formatting") XCTAssertEqual(linkAttributes3[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") XCTAssertEqual(linkAttributes3[.foregroundColor] as! UIColor, colors.blueForegroundColor, "Incorrect link formatting") - //"]]" + // "]]" XCTAssertEqual(linkRange4.location, 51, "Incorrect link formatting") XCTAssertEqual(linkRange4.length, 2, "Incorrect link formatting") XCTAssertEqual(linkAttributes4[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") XCTAssertEqual(linkAttributes4[.foregroundColor] as! UIColor, colors.blueForegroundColor, "Incorrect link formatting") - //"|alt=Photo of cat looking at fish]]" + // "|alt=Photo of cat looking at fish]]" XCTAssertEqual(linkRange5.location, 53, "Incorrect link formatting") XCTAssertEqual(linkRange5.length, 35, "Incorrect link formatting") XCTAssertEqual(linkAttributes5[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") From 13470738ccfe5455ba71df09e5691850456134b3 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Fri, 5 Jan 2024 13:45:32 -0600 Subject: [PATCH 07/14] Add link and image button selection and enabled states, formatter cleanup --- .../Base/WKEditorToolbarButton.swift | 13 +- .../WKEditorToolbarExpandingView.swift | 12 +- .../WKEditorToolbarExpandingView.xib | 8 +- .../WKEditorToolbarHighlightView.swift | 1 + .../WKEditorToolbarPlainView.swift | 1 + .../WKSourceEditorFormatterLink.m | 166 +++++++----------- 6 files changed, 90 insertions(+), 111 deletions(-) diff --git a/Components/Sources/Components/Components/Editors/Common Views/Base/WKEditorToolbarButton.swift b/Components/Sources/Components/Components/Editors/Common Views/Base/WKEditorToolbarButton.swift index 59b10a283f7..cf60cdc87e2 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Base/WKEditorToolbarButton.swift +++ b/Components/Sources/Components/Components/Editors/Common Views/Base/WKEditorToolbarButton.swift @@ -64,7 +64,18 @@ class WKEditorToolbarButton: WKComponentView { set { button.isSelected = newValue updateColors() - accessibilityTraits = newValue ? [.button, .selected] : [.button] + accessibilityTraits = button.accessibilityTraits + } + } + + var isEnabled: Bool { + get { + return button.isEnabled + } + set { + button.isEnabled = newValue + updateColors() + accessibilityTraits = button.accessibilityTraits } } diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Expanding/WKEditorToolbarExpandingView.swift b/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Expanding/WKEditorToolbarExpandingView.swift index 51b0691c643..69b61463663 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Expanding/WKEditorToolbarExpandingView.swift +++ b/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Expanding/WKEditorToolbarExpandingView.swift @@ -46,7 +46,7 @@ class WKEditorToolbarExpandingView: WKEditorToolbarView { @IBOutlet private weak var citationButton: WKEditorToolbarButton! @IBOutlet private weak var linkButton: WKEditorToolbarButton! @IBOutlet private weak var templateButton: WKEditorToolbarButton! - @IBOutlet private weak var mediaButton: WKEditorToolbarButton! + @IBOutlet private weak var imageButton: WKEditorToolbarButton! @IBOutlet private weak var findInPageButton: WKEditorToolbarButton! @IBOutlet private weak var unorderedListButton: WKEditorToolbarButton! @@ -101,9 +101,9 @@ class WKEditorToolbarExpandingView: WKEditorToolbarView { templateButton.addTarget(self, action: #selector(tappedTemplate), for: .touchUpInside) templateButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current.accessibilityLabelButtonTemplate - mediaButton.setImage(WKSFSymbolIcon.for(symbol: .photo), for: .normal) - mediaButton.addTarget(self, action: #selector(tappedMedia), for: .touchUpInside) - mediaButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current.accessibilityLabelButtonMedia + imageButton.setImage(WKSFSymbolIcon.for(symbol: .photo), for: .normal) + imageButton.addTarget(self, action: #selector(tappedMedia), for: .touchUpInside) + imageButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current.accessibilityLabelButtonMedia findInPageButton.setImage(WKSFSymbolIcon.for(symbol: .docTextMagnifyingGlass), for: .normal) findInPageButton.addTarget(self, action: #selector(tappedFindInPage), for: .touchUpInside) @@ -140,6 +140,7 @@ class WKEditorToolbarExpandingView: WKEditorToolbarView { cursorRightButton.setImage(WKIcon.chevronRight, for: .normal) cursorRightButton.addTarget(self, action: #selector(tappedCursorRight), for: .touchUpInside) + cursorRightButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current.accessibilityLabelButtonCursorRight NotificationCenter.default.addObserver(self, selector: #selector(updateButtonSelectionState(_:)), name: Notification.WKSourceEditorSelectionState, object: nil) } @@ -152,7 +153,8 @@ class WKEditorToolbarExpandingView: WKEditorToolbarView { } templateButton.isSelected = selectionState.isHorizontalTemplate - cursorRightButton.accessibilityLabel = WKSourceEditorLocalizedStrings.current.accessibilityLabelButtonCursorRight + linkButton.isSelected = selectionState.isSimpleLink + imageButton.isEnabled = !selectionState.isBold && !selectionState.isItalics && !selectionState.isSimpleLink } // MARK: - Button Actions diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Expanding/WKEditorToolbarExpandingView.xib b/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Expanding/WKEditorToolbarExpandingView.xib index a303c68443c..1a1b1d5af93 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Expanding/WKEditorToolbarExpandingView.xib +++ b/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Expanding/WKEditorToolbarExpandingView.xib @@ -1,9 +1,9 @@ - + - + @@ -281,11 +281,11 @@ + - - + diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Highlight/WKEditorToolbarHighlightView.swift b/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Highlight/WKEditorToolbarHighlightView.swift index 92606443801..577e5cb2c8e 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Highlight/WKEditorToolbarHighlightView.swift +++ b/Components/Sources/Components/Components/Editors/Common Views/Input Accessory Views/Highlight/WKEditorToolbarHighlightView.swift @@ -81,6 +81,7 @@ class WKEditorToolbarHighlightView: WKEditorToolbarView { boldButton.isSelected = selectionState.isBold italicsButton.isSelected = selectionState.isItalics templateButton.isSelected = selectionState.isHorizontalTemplate + linkButton.isSelected = selectionState.isSimpleLink } // MARK: - Button Actions diff --git a/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Plain Toolbar Table Item/WKEditorToolbarPlainView.swift b/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Plain Toolbar Table Item/WKEditorToolbarPlainView.swift index 0e4ebf054a1..b39cd5a7cbd 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Plain Toolbar Table Item/WKEditorToolbarPlainView.swift +++ b/Components/Sources/Components/Components/Editors/Common Views/Input Views/Main/Plain Toolbar Table Item/WKEditorToolbarPlainView.swift @@ -55,6 +55,7 @@ class WKEditorToolbarPlainView: WKEditorToolbarView { boldButton.isSelected = selectionState.isBold italicsButton.isSelected = selectionState.isItalics templateButton.isSelected = selectionState.isHorizontalTemplate + linkButton.isSelected = selectionState.isSimpleLink } // MARK: Button Actions diff --git a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterLink.m b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterLink.m index 700a64a8460..e3547df8c5e 100644 --- a/Components/Sources/ComponentsObjC/WKSourceEditorFormatterLink.m +++ b/Components/Sources/ComponentsObjC/WKSourceEditorFormatterLink.m @@ -3,9 +3,8 @@ @interface WKSourceEditorFormatterLink () -@property (nonatomic, strong) NSDictionary *simpleLinkMarkupAttributes; -@property (nonatomic, strong) NSDictionary *simpleLinkContentAttributes; -@property (nonatomic, strong) NSDictionary *linkWithNestedLinkMarkupAndContentAttributes; +@property (nonatomic, strong) NSDictionary *simpleLinkAttributes; +@property (nonatomic, strong) NSDictionary *linkWithNestedLinkAttributes; @property (nonatomic, strong) NSRegularExpression *simpleLinkRegex; @property (nonatomic, strong) NSRegularExpression *linkWithNestedLinkRegex; @@ -14,9 +13,8 @@ @interface WKSourceEditorFormatterLink () #pragma mark - Custom Attributed String Keys NSString * const WKSourceEditorCustomKeyColorBlue = @"WKSourceEditorCustomKeyColorBlue"; -NSString * const WKSourceEditorCustomKeyMarkupLink = @"WKSourceEditorCustomKeyMarkupLink"; -NSString * const WKSourceEditorCustomKeyContentLink = @"WKSourceEditorCustomKeyContentLink"; -NSString * const WKSourceEditorCustomKeyMarkupAndContentLinkWithNestedLink = @"WKSourceEditorCustomKeyMarkupAndContentLinkWithNestedLink"; +NSString * const WKSourceEditorCustomKeyLink = @"WKSourceEditorCustomKeyLink"; +NSString * const WKSourceEditorCustomKeyLinkWithNestedLink = @"WKSourceEditorCustomKeyLinkWithNestedLink"; @implementation WKSourceEditorFormatterLink @@ -25,22 +23,16 @@ @implementation WKSourceEditorFormatterLink - (instancetype)initWithColors:(nonnull WKSourceEditorColors *)colors fonts:(nonnull WKSourceEditorFonts *)fonts { self = [super initWithColors:colors fonts:fonts]; if (self) { - _simpleLinkMarkupAttributes = @{ - WKSourceEditorCustomKeyMarkupLink: [NSNumber numberWithBool:YES], - NSForegroundColorAttributeName: colors.blueForegroundColor, - WKSourceEditorCustomKeyColorBlue: [NSNumber numberWithBool:YES] - }; - - _simpleLinkContentAttributes = @{ - WKSourceEditorCustomKeyContentLink: [NSNumber numberWithBool:YES], + _simpleLinkAttributes = @{ + WKSourceEditorCustomKeyLink: [NSNumber numberWithBool:YES], NSForegroundColorAttributeName: colors.blueForegroundColor, WKSourceEditorCustomKeyColorBlue: [NSNumber numberWithBool:YES] }; _simpleLinkRegex = [[NSRegularExpression alloc] initWithPattern:@"(\\[{2})([^\\[\\]\\n]*)(\\]{2})" options:0 error:nil]; - _linkWithNestedLinkMarkupAndContentAttributes = @{ - WKSourceEditorCustomKeyMarkupAndContentLinkWithNestedLink: [NSNumber numberWithBool:YES], + _linkWithNestedLinkAttributes = @{ + WKSourceEditorCustomKeyLinkWithNestedLink: [NSNumber numberWithBool:YES], NSForegroundColorAttributeName: colors.blueForegroundColor, WKSourceEditorCustomKeyColorBlue: [NSNumber numberWithBool:YES] }; @@ -57,8 +49,8 @@ - (void)addSyntaxHighlightingToAttributedString:(nonnull NSMutableAttributedStri // Reset [attributedString removeAttribute:WKSourceEditorCustomKeyColorBlue range:range]; - [attributedString removeAttribute:WKSourceEditorCustomKeyContentLink range:range]; - [attributedString removeAttribute:WKSourceEditorCustomKeyMarkupAndContentLinkWithNestedLink range:range]; + [attributedString removeAttribute:WKSourceEditorCustomKeyLink range:range]; + [attributedString removeAttribute:WKSourceEditorCustomKeyLinkWithNestedLink range:range]; // This section finds and highlights simple links that do NOT contain nested links, e.g. [[Cat]] and [[Dog|puppy]]. [self.simpleLinkRegex enumerateMatchesInString:attributedString.string @@ -71,15 +63,15 @@ - (void)addSyntaxHighlightingToAttributedString:(nonnull NSMutableAttributedStri NSRange closingRange = [result rangeAtIndex:3]; if (openingRange.location != NSNotFound) { - [attributedString addAttributes:self.simpleLinkMarkupAttributes range:openingRange]; + [attributedString addAttributes:self.simpleLinkAttributes range:openingRange]; } if (contentRange.location != NSNotFound) { - [attributedString addAttributes:self.simpleLinkContentAttributes range:contentRange]; + [attributedString addAttributes:self.simpleLinkAttributes range:contentRange]; } if (closingRange.location != NSNotFound) { - [attributedString addAttributes:self.simpleLinkMarkupAttributes range:closingRange]; + [attributedString addAttributes:self.simpleLinkAttributes range:closingRange]; } }]; @@ -99,7 +91,7 @@ - (void)addSyntaxHighlightingToAttributedString:(nonnull NSMutableAttributedStri for (NSValue *value in linkWithNestedLinkRanges) { NSRange range = [value rangeValue]; if (range.location != NSNotFound) { - [attributedString addAttributes:self.linkWithNestedLinkMarkupAndContentAttributes range:range]; + [attributedString addAttributes:self.linkWithNestedLinkAttributes range:range]; } } } @@ -108,17 +100,13 @@ - (void)addSyntaxHighlightingToAttributedString:(nonnull NSMutableAttributedStri - (void)updateColors:(WKSourceEditorColors *)colors inAttributedString:(NSMutableAttributedString *)attributedString inRange:(NSRange)range { - NSMutableDictionary *mutSimpleLinkMarkupAttributes = [[NSMutableDictionary alloc] initWithDictionary:self.simpleLinkMarkupAttributes]; - [mutSimpleLinkMarkupAttributes setObject:colors.blueForegroundColor forKey:NSForegroundColorAttributeName]; - self.simpleLinkMarkupAttributes = [[NSDictionary alloc] initWithDictionary:mutSimpleLinkMarkupAttributes]; - - NSMutableDictionary *mutSimpleLinkContentAttributes = [[NSMutableDictionary alloc] initWithDictionary:self.simpleLinkContentAttributes]; - [mutSimpleLinkContentAttributes setObject:colors.blueForegroundColor forKey:NSForegroundColorAttributeName]; - self.simpleLinkContentAttributes = [[NSDictionary alloc] initWithDictionary:mutSimpleLinkContentAttributes]; + NSMutableDictionary *mutSimpleLinkAttributes = [[NSMutableDictionary alloc] initWithDictionary:self.simpleLinkAttributes]; + [mutSimpleLinkAttributes setObject:colors.blueForegroundColor forKey:NSForegroundColorAttributeName]; + self.simpleLinkAttributes = [[NSDictionary alloc] initWithDictionary:mutSimpleLinkAttributes]; - NSMutableDictionary *mutLinkWithNestedLinkMarkupAndContentAttributes = [[NSMutableDictionary alloc] initWithDictionary:self.linkWithNestedLinkMarkupAndContentAttributes]; - [mutLinkWithNestedLinkMarkupAndContentAttributes setObject:colors.blueForegroundColor forKey:NSForegroundColorAttributeName]; - self.linkWithNestedLinkMarkupAndContentAttributes = [[NSDictionary alloc] initWithDictionary:mutLinkWithNestedLinkMarkupAndContentAttributes]; + NSMutableDictionary *mutLinkWithNestedLinkAttributes = [[NSMutableDictionary alloc] initWithDictionary:self.linkWithNestedLinkAttributes]; + [mutLinkWithNestedLinkAttributes setObject:colors.blueForegroundColor forKey:NSForegroundColorAttributeName]; + self.linkWithNestedLinkAttributes = [[NSDictionary alloc] initWithDictionary:mutLinkWithNestedLinkAttributes]; [attributedString enumerateAttribute:WKSourceEditorCustomKeyColorBlue inRange:range @@ -140,79 +128,12 @@ - (void)updateFonts:(WKSourceEditorFonts *)fonts inAttributedString:(NSMutableAt #pragma mark - Public - (BOOL)attributedString:(NSMutableAttributedString *)attributedString isSimpleLinkInRange:(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[WKSourceEditorCustomKeyContentLink] != nil) { - isContentKey = YES; - } else { - // Edge case, check previous character if we are up against closing markup - if (attrs[WKSourceEditorCustomKeyMarkupLink]) { - attrs = [attributedString attributesAtIndex:range.location - 1 effectiveRange:nil]; - if (attrs[WKSourceEditorCustomKeyContentLink] != 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[WKSourceEditorCustomKeyContentLink] != nil) { - if (unionRange.location == NSNotFound) { - unionRange = loopRange; - } else { - unionRange = NSUnionRange(unionRange, loopRange); - } - stop = YES; - } - }]; - - if (NSEqualRanges(unionRange, range)) { - isContentKey = YES; - } - } - - return isContentKey; + return [self attributedString:attributedString isKey:WKSourceEditorCustomKeyLink inRange:range]; } - (BOOL)attributedString:(NSMutableAttributedString *)attributedString isLinkWithNestedLinkInRange:(NSRange)range { - __block BOOL isKey = NO; - if (range.length == 0) { - - if (attributedString.length > range.location) { - NSDictionary *attrs = [attributedString attributesAtIndex:range.location effectiveRange:nil]; - - if (attrs[WKSourceEditorCustomKeyMarkupAndContentLinkWithNestedLink] != nil) { - isKey = YES; - } - } - - } else { - __block NSRange unionRange = NSMakeRange(NSNotFound, 0); - [attributedString enumerateAttributesInRange:range options:nil usingBlock:^(NSDictionary * _Nonnull attrs, NSRange loopRange, BOOL * _Nonnull stop) { - if (attrs[WKSourceEditorCustomKeyMarkupAndContentLinkWithNestedLink] != nil) { - if (unionRange.location == NSNotFound) { - unionRange = loopRange; - } else { - unionRange = NSUnionRange(unionRange, loopRange); - } - stop = YES; - } - }]; - - if (NSEqualRanges(unionRange, range)) { - isKey = YES; - } - } - - return isKey; - + return [self attributedString:attributedString isKey:WKSourceEditorCustomKeyLinkWithNestedLink inRange:range]; } #pragma mark - Private @@ -276,4 +197,47 @@ - (NSArray *)linkWithNestedLinkRangesInString: (NSString *)string startingIndex: return [[NSArray alloc] initWithArray:completedLinkWithNestedLinkRanges]; } +- (BOOL)attributedString:(NSMutableAttributedString *)attributedString isKey:(NSString *)key inRange:(NSRange)range { + __block BOOL isKey = NO; + if (range.length == 0) { + + if (attributedString.length > range.location) { + NSDictionary *attrs = [attributedString attributesAtIndex:range.location effectiveRange:nil]; + + if (attrs[key] != nil) { + isKey = YES; + } + + // Edge case, check previous character if we are up against opening markup + if (attrs[WKSourceEditorCustomKeyLink]) { + if (attributedString.length > range.location - 1) { + attrs = [attributedString attributesAtIndex:range.location - 1 effectiveRange:nil]; + if (attrs[key] == nil) { + isKey = NO; + } + } + } + } + + } else { + __block NSRange unionRange = NSMakeRange(NSNotFound, 0); + [attributedString enumerateAttributesInRange:range options:nil usingBlock:^(NSDictionary * _Nonnull attrs, NSRange loopRange, BOOL * _Nonnull stop) { + if (attrs[key] != nil) { + if (unionRange.location == NSNotFound) { + unionRange = loopRange; + } else { + unionRange = NSUnionRange(unionRange, loopRange); + } + stop = YES; + } + }]; + + if (NSEqualRanges(unionRange, range)) { + isKey = YES; + } + } + + return isKey; +} + @end From 45856eaf25feb22112ce30cb552262d61d22dd6d Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Fri, 5 Jan 2024 13:45:39 -0600 Subject: [PATCH 08/14] Add selection state tests --- ...urceEditorTextFrameworkMediatorTests.swift | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/Components/Tests/ComponentsTests/WKSourceEditorTextFrameworkMediatorTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorTextFrameworkMediatorTests.swift index 183d81456fb..c5e092f4ec5 100644 --- a/Components/Tests/ComponentsTests/WKSourceEditorTextFrameworkMediatorTests.swift +++ b/Components/Tests/ComponentsTests/WKSourceEditorTextFrameworkMediatorTests.swift @@ -145,4 +145,100 @@ final class WKSourceEditorTextFrameworkMediatorTests: XCTestCase { XCTAssertTrue(selectionStates2.isStrikethrough) XCTAssertFalse(selectionStates3.isStrikethrough) } + + func testLinkState() throws { + let text = "Testing [[Link with space]] Testing." + mediator.textView.attributedText = NSAttributedString(string: text) + + let selectionStates1 = mediator.selectionState(selectedDocumentRange: NSRange(location: 0, length: 7)) + let selectionStates2 = mediator.selectionState(selectedDocumentRange: NSRange(location: 10, length: 15)) + let selectionStates3 = mediator.selectionState(selectedDocumentRange: NSRange(location: 28, length: 7)) + XCTAssertFalse(selectionStates1.isSimpleLink) + XCTAssertFalse(selectionStates1.isLinkWithNestedLink) + + XCTAssertTrue(selectionStates2.isSimpleLink) + XCTAssertFalse(selectionStates3.isLinkWithNestedLink) + + XCTAssertFalse(selectionStates3.isSimpleLink) + XCTAssertFalse(selectionStates3.isLinkWithNestedLink) + } + + func testLinkCursorState() throws { + let text = "Testing [[Link with space]] Testing." + mediator.textView.attributedText = NSAttributedString(string: text) + + let selectionStates1 = mediator.selectionState(selectedDocumentRange: NSRange(location: 3, length: 0)) + let selectionStates2 = mediator.selectionState(selectedDocumentRange: NSRange(location: 12, length: 0)) + let selectionStates3 = mediator.selectionState(selectedDocumentRange: NSRange(location: 30, length: 0)) + XCTAssertFalse(selectionStates1.isSimpleLink) + XCTAssertFalse(selectionStates1.isLinkWithNestedLink) + + XCTAssertTrue(selectionStates2.isSimpleLink) + XCTAssertFalse(selectionStates2.isLinkWithNestedLink) + + XCTAssertFalse(selectionStates3.isSimpleLink) + XCTAssertFalse(selectionStates3.isLinkWithNestedLink) + } + + func testNestedLinkState() throws { + let text = "Test [[File:Cat with fish.jpg|thumb|left|Cat with [[fish]]|alt=Photo of cat looking at fish]] Test" + mediator.textView.attributedText = NSAttributedString(string: text) + + let selectionStates1 = mediator.selectionState(selectedDocumentRange: NSRange(location: 0, length: 4)) + let selectionStates2 = mediator.selectionState(selectedDocumentRange: NSRange(location: 12, length: 3)) + let selectionStates3 = mediator.selectionState(selectedDocumentRange: NSRange(location: 52, length: 3)) + let selectionStates4 = mediator.selectionState(selectedDocumentRange: NSRange(location: 72, length: 3)) + let selectionStates5 = mediator.selectionState(selectedDocumentRange: NSRange(location: 94, length: 4)) + + // "Test" + XCTAssertFalse(selectionStates1.isSimpleLink) + XCTAssertFalse(selectionStates1.isLinkWithNestedLink) + + // "Cat" + XCTAssertFalse(selectionStates2.isSimpleLink) + XCTAssertTrue(selectionStates2.isLinkWithNestedLink) + + // "fish" + XCTAssertTrue(selectionStates3.isSimpleLink) + XCTAssertTrue(selectionStates3.isLinkWithNestedLink) + + // "cat" + XCTAssertFalse(selectionStates4.isSimpleLink) + XCTAssertTrue(selectionStates4.isLinkWithNestedLink) + + // "Test" + XCTAssertFalse(selectionStates5.isSimpleLink) + XCTAssertFalse(selectionStates5.isLinkWithNestedLink) + } + + func testNestedLinkStateCursor() throws { + let text = "Test [[File:Cat with fish.jpg|thumb|left|Cat with [[fish]]|alt=Photo of cat looking at fish]] Test" + mediator.textView.attributedText = NSAttributedString(string: text) + + let selectionStates1 = mediator.selectionState(selectedDocumentRange: NSRange(location: 0, length: 0)) + let selectionStates2 = mediator.selectionState(selectedDocumentRange: NSRange(location: 13, length: 0)) + let selectionStates3 = mediator.selectionState(selectedDocumentRange: NSRange(location: 54, length: 0)) + let selectionStates4 = mediator.selectionState(selectedDocumentRange: NSRange(location: 73, length: 0)) + let selectionStates5 = mediator.selectionState(selectedDocumentRange: NSRange(location: 96, length: 0)) + + // "Test" + XCTAssertFalse(selectionStates1.isSimpleLink) + XCTAssertFalse(selectionStates1.isLinkWithNestedLink) + + // "Cat" + XCTAssertFalse(selectionStates2.isSimpleLink) + XCTAssertTrue(selectionStates2.isLinkWithNestedLink) + + // "fish" + XCTAssertTrue(selectionStates3.isSimpleLink) + XCTAssertTrue(selectionStates3.isLinkWithNestedLink) + + // "cat" + XCTAssertFalse(selectionStates4.isSimpleLink) + XCTAssertTrue(selectionStates4.isLinkWithNestedLink) + + // "Test" + XCTAssertFalse(selectionStates5.isSimpleLink) + XCTAssertFalse(selectionStates5.isLinkWithNestedLink) + } } From a2d80e7bcf39ae2b07ef62964c7aa4300b37cef8 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Fri, 5 Jan 2024 18:10:36 -0600 Subject: [PATCH 09/14] Add link button action support --- ...KSourceEditorFormatter+ButtonActions.swift | 2 +- ...rceEditorFormatterLink+ButtonActions.swift | 101 ++++++++++++++++++ .../WKSourceEditorViewController.swift | 54 +++++++++- Wikipedia/Code/PageEditorViewController.swift | 70 ++++++++++++ .../UITestHelperViewController.swift | 4 + 5 files changed, 227 insertions(+), 4 deletions(-) create mode 100644 Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterLink+ButtonActions.swift diff --git a/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatter+ButtonActions.swift b/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatter+ButtonActions.swift index e0e70e9d2f6..8dd3b76589f 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatter+ButtonActions.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatter+ButtonActions.swift @@ -35,7 +35,7 @@ extension WKSourceEditorFormatter { // MARK: - Expanding selected range methods - private func expandSelectedRangeUpToNearestFormattingStrings(startingFormattingString: String, endingFormattingString: String, in textView: UITextView) { + func expandSelectedRangeUpToNearestFormattingStrings(startingFormattingString: String, endingFormattingString: String, in textView: UITextView) { if let textPositions = textPositionsCloserToNearestFormattingStrings(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView) { textView.selectedTextRange = textView.textRange(from: textPositions.startPosition, to: textPositions.endPosition) } diff --git a/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterLink+ButtonActions.swift b/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterLink+ButtonActions.swift new file mode 100644 index 00000000000..1f0dcbfe832 --- /dev/null +++ b/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterLink+ButtonActions.swift @@ -0,0 +1,101 @@ +import Foundation +import ComponentsObjC + +enum WKSourceEditorFormatterLinkButtonAction { + case edit + case insert +} + +public struct WKSourceEditorFormatterLinkWizardParameters { + public let editPageTitle: String? + public let editPageLabel: String? + public let insertSearchTerm: String? + let preselectedTextRange: UITextRange +} + +extension WKSourceEditorFormatterLink { + + func linkWizardParameters(action: WKSourceEditorFormatterLinkButtonAction, in textView: UITextView) -> WKSourceEditorFormatterLinkWizardParameters? { + + switch action { + case .edit: + expandSelectedRangeUpToNearestFormattingStrings(startingFormattingString: "[[", endingFormattingString: "]]", in: textView) + + guard let selectedTextRange = textView.selectedTextRange, + let selectedText = textView.text(in: selectedTextRange) else { + return nil + } + + let splitText = selectedText.split(separator: "|").map {String($0)} + + switch splitText.count { + case 1: + return WKSourceEditorFormatterLinkWizardParameters(editPageTitle: splitText[0], editPageLabel: nil, insertSearchTerm: nil, preselectedTextRange: selectedTextRange) + case 2: + return WKSourceEditorFormatterLinkWizardParameters(editPageTitle: splitText[0], editPageLabel: splitText[1], insertSearchTerm: nil, preselectedTextRange: selectedTextRange) + default: + return nil + } + + case .insert: + + guard let selectedTextRange = textView.selectedTextRange, + let selectedText = textView.text(in: selectedTextRange) else { + return nil + } + + return WKSourceEditorFormatterLinkWizardParameters(editPageTitle: nil, editPageLabel: nil, insertSearchTerm: selectedText, preselectedTextRange: selectedTextRange) + } + + + } + + func insertLink(in textView: UITextView, pageTitle: String, preselectedTextRange: UITextRange) { + var content = "[[\(pageTitle)]]" + + guard let selectedText = textView.text(in: preselectedTextRange) else { + return + } + + if pageTitle != selectedText { + content = "[[\(pageTitle)|\(selectedText)]]" + } + + textView.replace(preselectedTextRange, withText: content) + + if let newStartPosition = textView.position(from: preselectedTextRange.start, offset: 2), + let newEndPosition = textView.position(from: preselectedTextRange.start, offset: content.count-2) { + textView.selectedTextRange = textView.textRange(from: newStartPosition, to: newEndPosition) + } + + } + + func editLink(in textView: UITextView, newPageTitle: String, newPageLabel: String?, preselectedTextRange: UITextRange) { + + expandSelectedRangeUpToNearestFormattingStrings(startingFormattingString: "[[", endingFormattingString: "]]", in: textView) + + if let newPageLabel, !newPageLabel.isEmpty { + textView.replace(preselectedTextRange, withText: "\(newPageTitle)|\(newPageLabel)") + } else { + textView.replace(preselectedTextRange, withText: "\(newPageTitle)") + } + } + + func removeLink(in textView: UITextView, preselectedTextRange: UITextRange) { + + guard let selectedText = textView.text(in: preselectedTextRange) else { + return + } + + if let markupStartPosition = textView.position(from: preselectedTextRange.start, offset: -2), + let markupEndPosition = textView.position(from: preselectedTextRange.end, offset: 2), + let newSelectedRange = textView.textRange(from: markupStartPosition, to: markupEndPosition) { + textView.replace(newSelectedRange, withText: selectedText) + + if let newStartPosition = textView.position(from: preselectedTextRange.start, offset: -2), + let newEndPosition = textView.position(from: preselectedTextRange.end, offset: -2) { + textView.selectedTextRange = textView.textRange(from: newStartPosition, to: newEndPosition) + } + } + } +} diff --git a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift index d3f645adf52..c8215dc9240 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift @@ -4,6 +4,7 @@ import UIKit public protocol WKSourceEditorViewControllerDelegate: AnyObject { func sourceEditorViewControllerDidTapFind(sourceEditorViewController: WKSourceEditorViewController) func sourceEditorViewControllerDidRemoveFindInputAccessoryView(sourceEditorViewController: WKSourceEditorViewController) + func sourceEditorViewControllerDidTapLink(parameters: WKSourceEditorFormatterLinkWizardParameters) } // MARK: NSNotification Names @@ -37,6 +38,7 @@ public class WKSourceEditorViewController: WKComponentViewController { private let viewModel: WKSourceEditorViewModel private weak var delegate: WKSourceEditorViewControllerDelegate? private let textFrameworkMediator: WKSourceEditorTextFrameworkMediator + private var preselectedTextRange: UITextRange? var textView: UITextView { return textFrameworkMediator.textView @@ -231,6 +233,39 @@ public class WKSourceEditorViewController: WKComponentViewController { viewModel.isSyntaxHighlightingEnabled.toggle() update(viewModel: viewModel) } + + public func insertLink(pageTitle: String) { + + guard let preselectedTextRange else { + return + } + + textFrameworkMediator.linkFormatter?.insertLink(in: textView, pageTitle: pageTitle, preselectedTextRange: preselectedTextRange) + + self.preselectedTextRange = nil + } + + public func editLink(newPageTitle: String, newPageLabel: String?) { + + guard let preselectedTextRange else { + return + } + + textFrameworkMediator.linkFormatter?.editLink(in: textView, newPageTitle: newPageTitle, newPageLabel: newPageLabel, preselectedTextRange: preselectedTextRange) + + self.preselectedTextRange = nil + } + + public func removeLink() { + + guard let preselectedTextRange else { + return + } + + textFrameworkMediator.linkFormatter?.removeLink(in: textView, preselectedTextRange: preselectedTextRange) + + self.preselectedTextRange = nil + } } // MARK: - Private @@ -272,6 +307,19 @@ private extension WKSourceEditorViewController { NotificationCenter.default.post(name: Notification.WKSourceEditorSelectionState, object: nil, userInfo: [Notification.WKSourceEditorSelectionStateKey: selectionState]) } } + + func presentLinkWizard(linkButtonIsSelected: Bool) { + + let action: WKSourceEditorFormatterLinkButtonAction = linkButtonIsSelected ? .edit : .insert + + guard let parameters = textFrameworkMediator.linkFormatter?.linkWizardParameters(action: action, in: textView) else { + return + } + + // For some reason the editor text view can lose its selectedTextRange when presenting the link wizard, which we need in the formatter button action extension to determine how to change the text after wizard dismissal. We keep track of it here and send it back into the formatter later. + self.preselectedTextRange = parameters.preselectedTextRange + delegate?.sourceEditorViewControllerDidTapLink(parameters: parameters) + } } // MARK: - UITextViewDelegate @@ -312,7 +360,7 @@ extension WKSourceEditorViewController: WKEditorToolbarExpandingViewDelegate { } func toolbarExpandingViewDidTapLink(toolbarView: WKEditorToolbarExpandingView, isSelected: Bool) { - + presentLinkWizard(linkButtonIsSelected: isSelected) } func toolbarExpandingViewDidTapImage(toolbarView: WKEditorToolbarExpandingView) { @@ -340,7 +388,7 @@ extension WKSourceEditorViewController: WKEditorToolbarHighlightViewDelegate { } func toolbarHighlightViewDidTapLink(toolbarView: WKEditorToolbarHighlightView, isSelected: Bool) { - + presentLinkWizard(linkButtonIsSelected: isSelected) } func toolbarHighlightViewDidTapShowMore(toolbarView: WKEditorToolbarHighlightView) { @@ -378,7 +426,7 @@ extension WKSourceEditorViewController: WKEditorInputViewDelegate { } func didTapLink(isSelected: Bool) { - + presentLinkWizard(linkButtonIsSelected: isSelected) } func didTapClose() { diff --git a/Wikipedia/Code/PageEditorViewController.swift b/Wikipedia/Code/PageEditorViewController.swift index be88b879715..5ed15e64b62 100644 --- a/Wikipedia/Code/PageEditorViewController.swift +++ b/Wikipedia/Code/PageEditorViewController.swift @@ -1,6 +1,7 @@ import UIKit import Components import WMF +import CocoaLumberjackSwift protocol PageEditorViewControllerDelegate: AnyObject { func pageEditorDidCancelEditing(_ pageEditor: PageEditorViewController, navigateToURL: URL?) @@ -239,6 +240,39 @@ extension PageEditorViewController: WKSourceEditorViewControllerDelegate { func sourceEditorViewControllerDidRemoveFindInputAccessoryView(sourceEditorViewController: Components.WKSourceEditorViewController) { hideFocusNavigationView() } + + + func sourceEditorViewControllerDidTapLink(parameters: WKSourceEditorFormatterLinkWizardParameters) { + guard let siteURL = pageURL.wmf_site else { + return + } + + if let editPageTitle = parameters.editPageTitle { + guard let link = Link(page: editPageTitle, label: parameters.editPageLabel, exists: true) else { + return + } + + guard let editLinkViewController = EditLinkViewController(link: link, siteURL: pageURL.wmf_site, dataStore: dataStore) else { + return + } + + editLinkViewController.delegate = self + let navigationController = WMFThemeableNavigationController(rootViewController: editLinkViewController, theme: self.theme) + navigationController.isNavigationBarHidden = true + present(navigationController, animated: true) + } + + if let insertSearchTerm = parameters.insertSearchTerm { + guard let link = Link(page: insertSearchTerm, label: nil, exists: false) else { + return + } + + let insertLinkViewController = InsertLinkViewController(link: link, siteURL: siteURL, dataStore: dataStore) + insertLinkViewController.delegate = self + let navigationController = WMFThemeableNavigationController(rootViewController: insertLinkViewController, theme: self.theme) + present(navigationController, animated: true) + } + } } // MARK: - PageEditorNavigationItemControllerDelegate @@ -314,6 +348,42 @@ extension PageEditorViewController: ReadingThemesControlsPresenting { } } +// MARK: - EditLinkViewControllerDelegate + +extension PageEditorViewController: EditLinkViewControllerDelegate { + func editLinkViewController(_ editLinkViewController: EditLinkViewController, didTapCloseButton button: UIBarButtonItem) { + dismiss(animated: true) + } + + func editLinkViewController(_ editLinkViewController: EditLinkViewController, didFinishEditingLink displayText: String?, linkTarget: String) { + dismiss(animated: true) + sourceEditor.editLink(newPageTitle: linkTarget, newPageLabel: displayText) + } + + func editLinkViewController(_ editLinkViewController: EditLinkViewController, didFailToExtractArticleTitleFromArticleURL articleURL: URL) { + DDLogError("Failed to extract article title from \(pageURL)") + dismiss(animated: true) + } + + func editLinkViewControllerDidRemoveLink(_ editLinkViewController: EditLinkViewController) { + dismiss(animated: true) + sourceEditor.removeLink() + } +} + +// MARK: - InsertLinkViewControllerDelegate + +extension PageEditorViewController: InsertLinkViewControllerDelegate { + func insertLinkViewController(_ insertLinkViewController: InsertLinkViewController, didTapCloseButton button: UIBarButtonItem) { + dismiss(animated: true) + } + + func insertLinkViewController(_ insertLinkViewController: InsertLinkViewController, didInsertLinkFor page: String, withLabel label: String?) { + sourceEditor.insertLink(pageTitle: page) + dismiss(animated: true) + } +} + enum SourceEditorAccessibilityIdentifiers: String { case entryButton = "Source Editor Entry Button" case textView = "Source Editor TextView" diff --git a/WikipediaUITests/UITestHelperViewController.swift b/WikipediaUITests/UITestHelperViewController.swift index e37203818af..79965132862 100644 --- a/WikipediaUITests/UITestHelperViewController.swift +++ b/WikipediaUITests/UITestHelperViewController.swift @@ -154,6 +154,10 @@ public class UITestHelperViewController: WKCanvasViewController { extension UITestHelperViewController: WKSourceEditorViewControllerDelegate { + public func sourceEditorViewControllerDidTapLink(parameters: Components.WKSourceEditorFormatterLinkWizardParameters) { + + } + public func sourceEditorViewControllerDidRemoveFindInputAccessoryView(sourceEditorViewController: Components.WKSourceEditorViewController) { } From c6a3cad9a45ce8549e81caa592d87926fadafc62 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Fri, 5 Jan 2024 18:32:38 -0600 Subject: [PATCH 10/14] Add image button action support --- ...rceEditorFormatterLink+ButtonActions.swift | 9 ++++++++ .../WKSourceEditorViewController.swift | 7 +++++- .../Code/InsertMediaViewController.swift | 3 +-- Wikipedia/Code/PageEditorViewController.swift | 23 +++++++++++++++++++ .../UITestHelperViewController.swift | 4 ++++ 5 files changed, 43 insertions(+), 3 deletions(-) diff --git a/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterLink+ButtonActions.swift b/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterLink+ButtonActions.swift index 1f0dcbfe832..604decca42f 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterLink+ButtonActions.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterLink+ButtonActions.swift @@ -98,4 +98,13 @@ extension WKSourceEditorFormatterLink { } } } + + func insertImage(wikitext: String, in textView: UITextView) { + + guard let selectedTextRange = textView.selectedTextRange else { + return + } + + textView.replace(selectedTextRange, withText: wikitext) + } } diff --git a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift index c8215dc9240..9743907515c 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/WKSourceEditorViewController.swift @@ -5,6 +5,7 @@ public protocol WKSourceEditorViewControllerDelegate: AnyObject { func sourceEditorViewControllerDidTapFind(sourceEditorViewController: WKSourceEditorViewController) func sourceEditorViewControllerDidRemoveFindInputAccessoryView(sourceEditorViewController: WKSourceEditorViewController) func sourceEditorViewControllerDidTapLink(parameters: WKSourceEditorFormatterLinkWizardParameters) + func sourceEditorViewControllerDidTapImage() } // MARK: NSNotification Names @@ -266,6 +267,10 @@ public class WKSourceEditorViewController: WKComponentViewController { self.preselectedTextRange = nil } + + public func insertImage(wikitext: String) { + textFrameworkMediator.linkFormatter?.insertImage(wikitext: wikitext, in: textView) + } } // MARK: - Private @@ -364,7 +369,7 @@ extension WKSourceEditorViewController: WKEditorToolbarExpandingViewDelegate { } func toolbarExpandingViewDidTapImage(toolbarView: WKEditorToolbarExpandingView) { - + delegate?.sourceEditorViewControllerDidTapImage() } } diff --git a/Wikipedia/Code/InsertMediaViewController.swift b/Wikipedia/Code/InsertMediaViewController.swift index c7f142f84ef..39fd8bbd271 100644 --- a/Wikipedia/Code/InsertMediaViewController.swift +++ b/Wikipedia/Code/InsertMediaViewController.swift @@ -130,8 +130,7 @@ final class InsertMediaViewController: ViewController { switch (mediaSettings.caption, mediaSettings.alternativeText) { case (let caption?, let alternativeText?): wikitext = """ - [[\(searchResult.fileTitle) | \(mediaSettings.advanced.imageType.rawValue) | \(mediaSettings.advanced.imageSize.rawValue) | \(mediaSettings.advanced.imagePosition.rawValue) | alt= \(alternativeText) | - \(caption)]] + [[\(searchResult.fileTitle) | \(mediaSettings.advanced.imageType.rawValue) | \(mediaSettings.advanced.imageSize.rawValue) | \(mediaSettings.advanced.imagePosition.rawValue) | alt= \(alternativeText) | \(caption)]] """ case (let caption?, nil): wikitext = """ diff --git a/Wikipedia/Code/PageEditorViewController.swift b/Wikipedia/Code/PageEditorViewController.swift index 5ed15e64b62..01b317bc44b 100644 --- a/Wikipedia/Code/PageEditorViewController.swift +++ b/Wikipedia/Code/PageEditorViewController.swift @@ -233,6 +233,7 @@ extension PageEditorViewController: Themeable { // MARK: - WKSourceEditorViewControllerDelegate extension PageEditorViewController: WKSourceEditorViewControllerDelegate { + func sourceEditorViewControllerDidTapFind(sourceEditorViewController: WKSourceEditorViewController) { showFocusNavigationView() } @@ -273,6 +274,15 @@ extension PageEditorViewController: WKSourceEditorViewControllerDelegate { present(navigationController, animated: true) } } + + func sourceEditorViewControllerDidTapImage() { + let insertMediaViewController = InsertMediaViewController(articleTitle: pageURL.wmf_title, siteURL: pageURL.wmf_site) + insertMediaViewController.delegate = self + insertMediaViewController.apply(theme: theme) + let navigationController = WMFThemeableNavigationController(rootViewController: insertMediaViewController, theme: theme) + navigationController.isNavigationBarHidden = true + present(navigationController, animated: true) + } } // MARK: - PageEditorNavigationItemControllerDelegate @@ -384,6 +394,19 @@ extension PageEditorViewController: InsertLinkViewControllerDelegate { } } +// MARK: - Insert + +extension PageEditorViewController: InsertMediaViewControllerDelegate { + func insertMediaViewController(_ insertMediaViewController: InsertMediaViewController, didTapCloseButton button: UIBarButtonItem) { + dismiss(animated: true) + } + + func insertMediaViewController(_ insertMediaViewController: InsertMediaViewController, didPrepareWikitextToInsert wikitext: String) { + sourceEditor.insertImage(wikitext: wikitext) + dismiss(animated: true) + } +} + enum SourceEditorAccessibilityIdentifiers: String { case entryButton = "Source Editor Entry Button" case textView = "Source Editor TextView" diff --git a/WikipediaUITests/UITestHelperViewController.swift b/WikipediaUITests/UITestHelperViewController.swift index 79965132862..f8c486ab0d7 100644 --- a/WikipediaUITests/UITestHelperViewController.swift +++ b/WikipediaUITests/UITestHelperViewController.swift @@ -154,6 +154,10 @@ public class UITestHelperViewController: WKCanvasViewController { extension UITestHelperViewController: WKSourceEditorViewControllerDelegate { + public func sourceEditorViewControllerDidTapImage() { + + } + public func sourceEditorViewControllerDidTapLink(parameters: Components.WKSourceEditorFormatterLinkWizardParameters) { } From 52964a169bb41b25d1a7d6f9ef5375d14aad120b Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Mon, 8 Jan 2024 08:57:16 -0600 Subject: [PATCH 11/14] Add button action tests --- ...rceEditorFormatterLink+ButtonActions.swift | 3 - ...urceEditorFormatterButtonActionTests.swift | 119 ++++++++++++++++++ .../ComponentsTests/WKSourceEditorTests.swift | 9 ++ 3 files changed, 128 insertions(+), 3 deletions(-) diff --git a/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterLink+ButtonActions.swift b/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterLink+ButtonActions.swift index 604decca42f..e1e1dfa2a77 100644 --- a/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterLink+ButtonActions.swift +++ b/Components/Sources/Components/Components/Editors/Source Editor/Formatter Extensions/WKSourceEditorFormatterLink+ButtonActions.swift @@ -71,9 +71,6 @@ extension WKSourceEditorFormatterLink { } func editLink(in textView: UITextView, newPageTitle: String, newPageLabel: String?, preselectedTextRange: UITextRange) { - - expandSelectedRangeUpToNearestFormattingStrings(startingFormattingString: "[[", endingFormattingString: "]]", in: textView) - if let newPageLabel, !newPageLabel.isEmpty { textView.replace(preselectedTextRange, withText: "\(newPageTitle)|\(newPageLabel)") } else { diff --git a/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift index 35d8f3b5a64..30cf8aae558 100644 --- a/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift +++ b/Components/Tests/ComponentsTests/WKSourceEditorFormatterButtonActionTests.swift @@ -132,4 +132,123 @@ final class WKSourceEditorFormatterButtonActionTests: XCTestCase { mediator.strikethroughFormatter?.toggleStrikethroughFormatting(action: .remove, in: mediator.textView) XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four") } + + func testLinkWizardParametersEdit() throws { + let text = "Testing [[Cat]] Testing" + mediator.textView.attributedText = NSAttributedString(string: text) + mediator.textView.selectedRange = NSRange(location: 10, length:3) + let wizardParameters = mediator.linkFormatter?.linkWizardParameters(action: .edit, in: mediator.textView) + XCTAssertEqual(wizardParameters?.editPageTitle, "Cat") + XCTAssertNil(wizardParameters?.editPageLabel) + XCTAssertEqual(wizardParameters?.preselectedTextRange, mediator.textView.selectedTextRange) + } + + func testLinkWizardParametersEditWithLabel() throws { + let text = "Testing [[Cat|Kitty]] Testing" + mediator.textView.attributedText = NSAttributedString(string: text) + mediator.textView.selectedRange = NSRange(location: 10, length:3) + let wizardParameters = mediator.linkFormatter?.linkWizardParameters(action: .edit, in: mediator.textView) + XCTAssertEqual(wizardParameters?.editPageTitle, "Cat") + XCTAssertEqual(wizardParameters?.editPageLabel, "Kitty") + XCTAssertEqual(wizardParameters?.preselectedTextRange, mediator.textView.selectedTextRange) + } + + func testLinkWizardParametersInsert() throws { + let text = "Testing Cat Testing" + mediator.textView.attributedText = NSAttributedString(string: text) + mediator.textView.selectedRange = NSRange(location: 8, length:3) + let wizardParameters = mediator.linkFormatter?.linkWizardParameters(action: .insert, in: mediator.textView) + XCTAssertEqual(wizardParameters?.insertSearchTerm, "Cat") + XCTAssertEqual(wizardParameters?.preselectedTextRange, mediator.textView.selectedTextRange) + } + + func testLinkInsert() { + let text = "One Two Three Four" + let textView = mediator.textView + textView.attributedText = NSAttributedString(string: text) + + guard let startPos = textView.position(from: textView.beginningOfDocument, offset: 4), + let endPos = textView.position(from: textView.beginningOfDocument, offset: 7), + let preselectedTextRange = textView.textRange(from: startPos, to: endPos) else { + XCTFail("Failure creating preselectedTextRange") + return + } + + mediator.linkFormatter?.insertLink(in: textView, pageTitle: "Two", preselectedTextRange: preselectedTextRange) + XCTAssertEqual(mediator.textView.attributedText.string, "One [[Two]] Three Four") + } + + func testLinkEdit() { + let text = "One Two [[Three]] Four" + let textView = mediator.textView + textView.attributedText = NSAttributedString(string: text) + + guard let startPos = textView.position(from: textView.beginningOfDocument, offset: 10), + let endPos = textView.position(from: textView.beginningOfDocument, offset: 15), + let preselectedTextRange = textView.textRange(from: startPos, to: endPos) else { + XCTFail("Failure creating preselectedTextRange") + return + } + + mediator.linkFormatter?.editLink(in: textView, newPageTitle: "Five", newPageLabel: nil, preselectedTextRange: preselectedTextRange) + XCTAssertEqual(mediator.textView.attributedText.string, "One Two [[Five]] Four") + } + + func testLinkEditWithLabel() { + let text = "One Two [[Three]] Four" + let textView = mediator.textView + textView.attributedText = NSAttributedString(string: text) + + guard let startPos = textView.position(from: textView.beginningOfDocument, offset: 10), + let endPos = textView.position(from: textView.beginningOfDocument, offset: 15), + let preselectedTextRange = textView.textRange(from: startPos, to: endPos) else { + XCTFail("Failure creating preselectedTextRange") + return + } + + mediator.linkFormatter?.editLink(in: textView, newPageTitle: "Five", newPageLabel: "fiver", preselectedTextRange: preselectedTextRange) + XCTAssertEqual(mediator.textView.attributedText.string, "One Two [[Five|fiver]] Four") + } + + func testLinkRemove() { + let text = "One Two [[Three]] Four" + let textView = mediator.textView + textView.attributedText = NSAttributedString(string: text) + + guard let startPos = textView.position(from: textView.beginningOfDocument, offset: 10), + let endPos = textView.position(from: textView.beginningOfDocument, offset: 15), + let preselectedTextRange = textView.textRange(from: startPos, to: endPos) else { + XCTFail("Failure creating preselectedTextRange") + return + } + + mediator.linkFormatter?.removeLink(in: textView, preselectedTextRange: preselectedTextRange) + XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four") + } + + func testLinkRemoveWithLabel() { + let text = "One Two [[Three|3]] Four" + let textView = mediator.textView + textView.attributedText = NSAttributedString(string: text) + + guard let startPos = textView.position(from: textView.beginningOfDocument, offset: 10), + let endPos = textView.position(from: textView.beginningOfDocument, offset: 17), + let preselectedTextRange = textView.textRange(from: startPos, to: endPos) else { + XCTFail("Failure creating preselectedTextRange") + return + } + + mediator.linkFormatter?.removeLink(in: textView, preselectedTextRange: preselectedTextRange) + XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three|3 Four") + } + + func testLinkInsertImage() { + let text = "One Two Three Four" + let textView = mediator.textView + textView.attributedText = NSAttributedString(string: text) + mediator.textView.selectedRange = NSRange(location: 8, length:0) + + mediator.linkFormatter?.insertImage(wikitext: "[[File:Cat November 2010-1a.jpg | thumb | 220x124px | right]]", in: textView) + XCTAssertEqual(mediator.textView.attributedText.string, "One Two [[File:Cat November 2010-1a.jpg | thumb | 220x124px | right]]Three Four") + } } diff --git a/Components/Tests/ComponentsTests/WKSourceEditorTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorTests.swift index 3e2af01ed72..1e31ab74143 100644 --- a/Components/Tests/ComponentsTests/WKSourceEditorTests.swift +++ b/Components/Tests/ComponentsTests/WKSourceEditorTests.swift @@ -34,6 +34,7 @@ final class WKSourceEditorTests: XCTestCase { } extension WKSourceEditorTests: WKSourceEditorViewControllerDelegate { + func sourceEditorViewControllerDidRemoveFindInputAccessoryView(sourceEditorViewController: Components.WKSourceEditorViewController) { } @@ -41,6 +42,14 @@ extension WKSourceEditorTests: WKSourceEditorViewControllerDelegate { func sourceEditorViewControllerDidTapFind(sourceEditorViewController: Components.WKSourceEditorViewController) { } + + func sourceEditorViewControllerDidTapLink(parameters: Components.WKSourceEditorFormatterLinkWizardParameters) { + + } + + func sourceEditorViewControllerDidTapImage() { + + } } extension WKSourceEditorLocalizedStrings { From 96f80ca4d141dee03ddafd6979a791fc45019648 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Mon, 8 Jan 2024 09:25:16 -0600 Subject: [PATCH 12/14] Fix tests --- .../WKSourceEditorFormatterTests.swift | 96 ++++--------------- 1 file changed, 21 insertions(+), 75 deletions(-) diff --git a/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift b/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift index 5a14925c774..d421a5bd65f 100644 --- a/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift +++ b/Components/Tests/ComponentsTests/WKSourceEditorFormatterTests.swift @@ -781,14 +781,8 @@ final class WKSourceEditorFormatterTests: XCTestCase { var base1Range = NSRange(location: 0, length: 0) let base1Attributes = mutAttributedString.attributes(at: 0, effectiveRange: &base1Range) - var linkOpenRange = NSRange(location: 0, length: 0) - let linkOpenAttributes = mutAttributedString.attributes(at: 9, effectiveRange: &linkOpenRange) - - var linkContentRange = NSRange(location: 0, length: 0) - let linkContentAttributes = mutAttributedString.attributes(at: 11, effectiveRange: &linkContentRange) - - var linkCloseRange = NSRange(location: 0, length: 0) - let linkCloseAttributes = mutAttributedString.attributes(at: 27, effectiveRange: &linkCloseRange) + var linkRange = NSRange(location: 0, length: 0) + let linkRangeAttributes = mutAttributedString.attributes(at: 9, effectiveRange: &linkRange) var base2Range = NSRange(location: 0, length: 0) let base2Attributes = mutAttributedString.attributes(at: 29, effectiveRange: &base2Range) @@ -799,23 +793,11 @@ final class WKSourceEditorFormatterTests: XCTestCase { XCTAssertEqual(base1Attributes[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") XCTAssertEqual(base1Attributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect base formatting") - // "[[" - XCTAssertEqual(linkOpenRange.location, 9, "Incorrect link formatting") - XCTAssertEqual(linkOpenRange.length, 2, "Incorrect link formatting") - XCTAssertEqual(linkOpenAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect link formatting") - XCTAssertEqual(linkOpenAttributes[.foregroundColor] as! UIColor, colors.blueForegroundColor, "Incorrect link formatting") - - // "Link with spaces" - XCTAssertEqual(linkContentRange.location, 11, "Incorrect link content formatting") - XCTAssertEqual(linkContentRange.length, 16, "Incorrect link content formatting") - XCTAssertEqual(linkContentAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect link content formatting") - XCTAssertEqual(linkContentAttributes[.foregroundColor] as! UIColor, colors.blueForegroundColor, "Incorrect content formatting") - - // "]]" - XCTAssertEqual(linkCloseRange.location, 27, "Incorrect link formatting") - XCTAssertEqual(linkCloseRange.length, 2, "Incorrect link formatting") - XCTAssertEqual(linkCloseAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect link formatting") - XCTAssertEqual(linkCloseAttributes[.foregroundColor] as! UIColor, colors.blueForegroundColor, "Incorrect link formatting") + // "[[Link with spaces]]" + XCTAssertEqual(linkRange.location, 9, "Incorrect link formatting") + XCTAssertEqual(linkRange.length, 20, "Incorrect link formatting") + XCTAssertEqual(linkRangeAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect link formatting") + XCTAssertEqual(linkRangeAttributes[.foregroundColor] as! UIColor, colors.blueForegroundColor, "Incorrect link formatting") // ". Testing" XCTAssertEqual(base2Range.location, 29, "Incorrect base formatting") @@ -835,14 +817,8 @@ final class WKSourceEditorFormatterTests: XCTestCase { var base1Range = NSRange(location: 0, length: 0) let base1Attributes = mutAttributedString.attributes(at: 0, effectiveRange: &base1Range) - var linkOpenRange = NSRange(location: 0, length: 0) - let linkOpenAttributes = mutAttributedString.attributes(at: 9, effectiveRange: &linkOpenRange) - - var linkContentRange = NSRange(location: 0, length: 0) - let linkContentAttributes = mutAttributedString.attributes(at: 11, effectiveRange: &linkContentRange) - - var linkCloseRange = NSRange(location: 0, length: 0) - let linkCloseAttributes = mutAttributedString.attributes(at: 32, effectiveRange: &linkCloseRange) + var linkRange = NSRange(location: 0, length: 0) + let linkAttributes = mutAttributedString.attributes(at: 9, effectiveRange: &linkRange) var base2Range = NSRange(location: 0, length: 0) let base2Attributes = mutAttributedString.attributes(at: 34, effectiveRange: &base2Range) @@ -853,23 +829,11 @@ final class WKSourceEditorFormatterTests: XCTestCase { XCTAssertEqual(base1Attributes[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") XCTAssertEqual(base1Attributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect base formatting") - // "[[" - XCTAssertEqual(linkOpenRange.location, 9, "Incorrect link formatting") - XCTAssertEqual(linkOpenRange.length, 2, "Incorrect link formatting") - XCTAssertEqual(linkOpenAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect link formatting") - XCTAssertEqual(linkOpenAttributes[.foregroundColor] as! UIColor, colors.blueForegroundColor, "Incorrect link formatting") - - // "Link|Link with spaces" - XCTAssertEqual(linkContentRange.location, 11, "Incorrect link content formatting") - XCTAssertEqual(linkContentRange.length, 21, "Incorrect link content formatting") - XCTAssertEqual(linkContentAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect link content formatting") - XCTAssertEqual(linkContentAttributes[.foregroundColor] as! UIColor, colors.blueForegroundColor, "Incorrect content formatting") - - // "]]" - XCTAssertEqual(linkCloseRange.location, 32, "Incorrect link formatting") - XCTAssertEqual(linkCloseRange.length, 2, "Incorrect link formatting") - XCTAssertEqual(linkCloseAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect link formatting") - XCTAssertEqual(linkCloseAttributes[.foregroundColor] as! UIColor, colors.blueForegroundColor, "Incorrect link formatting") + // "[[Link|Link with spaces]]" + XCTAssertEqual(linkRange.location, 9, "Incorrect link formatting") + XCTAssertEqual(linkRange.length, 25, "Incorrect link formatting") + XCTAssertEqual(linkAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect link formatting") + XCTAssertEqual(linkAttributes[.foregroundColor] as! UIColor, colors.blueForegroundColor, "Incorrect link formatting") // ". Testing" XCTAssertEqual(base2Range.location, 34, "Incorrect base formatting") @@ -891,15 +855,9 @@ final class WKSourceEditorFormatterTests: XCTestCase { var linkRange2 = NSRange(location: 0, length: 0) let linkAttributes2 = mutAttributedString.attributes(at: 45, effectiveRange: &linkRange2) - + var linkRange3 = NSRange(location: 0, length: 0) - let linkAttributes3 = mutAttributedString.attributes(at: 47, effectiveRange: &linkRange3) - - var linkRange4 = NSRange(location: 0, length: 0) - let linkAttributes4 = mutAttributedString.attributes(at: 51, effectiveRange: &linkRange4) - - var linkRange5 = NSRange(location: 0, length: 0) - let linkAttributes5 = mutAttributedString.attributes(at: 53, effectiveRange: &linkRange5) + let linkAttributes3 = mutAttributedString.attributes(at: 53, effectiveRange: &linkRange3) // "[[File:Cat with fish.jpg|thumb|left|Cat with " XCTAssertEqual(linkRange1.location, 0, "Incorrect link formatting") @@ -907,28 +865,16 @@ final class WKSourceEditorFormatterTests: XCTestCase { XCTAssertEqual(linkAttributes1[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") XCTAssertEqual(linkAttributes1[.foregroundColor] as! UIColor, colors.blueForegroundColor, "Incorrect link formatting") - // "[[" + // "[[fish]]" XCTAssertEqual(linkRange2.location, 45, "Incorrect link formatting") - XCTAssertEqual(linkRange2.length, 2, "Incorrect link formatting") + XCTAssertEqual(linkRange2.length, 8, "Incorrect link formatting") XCTAssertEqual(linkAttributes2[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") XCTAssertEqual(linkAttributes2[.foregroundColor] as! UIColor, colors.blueForegroundColor, "Incorrect link formatting") - // "fish" - XCTAssertEqual(linkRange3.location, 47, "Incorrect link formatting") - XCTAssertEqual(linkRange3.length, 4, "Incorrect link formatting") + // "|alt=Photo of cat looking at fish]]" + XCTAssertEqual(linkRange3.location, 53, "Incorrect link formatting") + XCTAssertEqual(linkRange3.length, 35, "Incorrect link formatting") XCTAssertEqual(linkAttributes3[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") XCTAssertEqual(linkAttributes3[.foregroundColor] as! UIColor, colors.blueForegroundColor, "Incorrect link formatting") - - // "]]" - XCTAssertEqual(linkRange4.location, 51, "Incorrect link formatting") - XCTAssertEqual(linkRange4.length, 2, "Incorrect link formatting") - XCTAssertEqual(linkAttributes4[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") - XCTAssertEqual(linkAttributes4[.foregroundColor] as! UIColor, colors.blueForegroundColor, "Incorrect link formatting") - - // "|alt=Photo of cat looking at fish]]" - XCTAssertEqual(linkRange5.location, 53, "Incorrect link formatting") - XCTAssertEqual(linkRange5.length, 35, "Incorrect link formatting") - XCTAssertEqual(linkAttributes5[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting") - XCTAssertEqual(linkAttributes5[.foregroundColor] as! UIColor, colors.blueForegroundColor, "Incorrect link formatting") } } From 8008601112ec5231e8f076a2a480d2920c932f5d Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Tue, 9 Jan 2024 17:02:49 -0600 Subject: [PATCH 13/14] Bug fixes after merge --- .../Editors/Common Views/Input Views/WKEditorInputView.swift | 1 + 1 file changed, 1 insertion(+) 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..91a696264e2 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,7 @@ protocol WKEditorInputViewDelegate: AnyObject { func didTapItalics(isSelected: Bool) func didTapTemplate(isSelected: Bool) func didTapStrikethrough(isSelected: Bool) + func didTapLink(isSelected: Bool) } class WKEditorInputView: WKComponentView { From 118b0d5a7ff8c9ad6784818975ee0b8998494249 Mon Sep 17 00:00:00 2001 From: Toni Sevener Date: Wed, 10 Jan 2024 14:33:40 -0600 Subject: [PATCH 14/14] Bug fixes after merging --- .../Common Views/Base/WKEditorToolbarButton.swift | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Components/Sources/Components/Components/Editors/Common Views/Base/WKEditorToolbarButton.swift b/Components/Sources/Components/Components/Editors/Common Views/Base/WKEditorToolbarButton.swift index b9a9338814f..cf60cdc87e2 100644 --- a/Components/Sources/Components/Components/Editors/Common Views/Base/WKEditorToolbarButton.swift +++ b/Components/Sources/Components/Components/Editors/Common Views/Base/WKEditorToolbarButton.swift @@ -79,17 +79,6 @@ class WKEditorToolbarButton: WKComponentView { } } - var isEnabled: Bool { - get { - return button.isEnabled - } - set { - button.isEnabled = newValue - updateColors() - accessibilityTraits = newValue ? [.button, .selected] : [.button, .notEnabled] - } - } - func setImage(_ image: UIImage?, for state: UIControl.State) { button.setImage(image, for: state) }