From ba4a0713eca6937361074eb46d5c783ebf4cc335 Mon Sep 17 00:00:00 2001 From: James Sherlock Date: Sat, 14 Oct 2017 00:42:23 +0100 Subject: [PATCH] Add support for shortening GitHub issues links (#531) * Add support for shortening GitHub issues links Relates to #411 * Added Tests --- .../Markdown/CommentModelsFromMarkdown.swift | 50 ++++++++++++++++++- .../Markdown/MMElement+Attributes.swift | 1 + .../Comments/Markdown/MarkdownAttribute.swift | 1 + FreetimeTests/MMMarkdownASTTests.swift | 28 +++++++++++ 4 files changed, 79 insertions(+), 1 deletion(-) diff --git a/Classes/Issues/Comments/Markdown/CommentModelsFromMarkdown.swift b/Classes/Issues/Comments/Markdown/CommentModelsFromMarkdown.swift index f2e69ff6..c8370fc8 100644 --- a/Classes/Issues/Comments/Markdown/CommentModelsFromMarkdown.swift +++ b/Classes/Issues/Comments/Markdown/CommentModelsFromMarkdown.swift @@ -357,6 +357,53 @@ func updateIssueShorthand( return mutableAttributedString } +private let issueURLRegex = try! NSRegularExpression(pattern: "https?:\\/\\/.*github.com\\/(\\w*)\\/([^/]*?)\\/issues\\/([0-9]+)", options: []) +func shortenGitHubLinks(attributedString: NSAttributedString, + options: GitHubMarkdownOptions) -> NSAttributedString { + + guard options.flavors.contains(.issueShorthand) else { return attributedString } + + let string = attributedString.string + let mutableAttributedString = NSMutableAttributedString(attributedString: attributedString) + let matches = issueURLRegex.matches(in: string, options: [], range: string.nsrange) + + for match in matches.reversed() { + let ownerRange = match.range(at: 1) + let repoRange = match.range(at: 2) + let numberRange = match.range(at: 3) + + guard let ownerSubstring = string.substring(with: ownerRange), + let repoSubstring = string.substring(with: repoRange), + let numberSubstring = string.substring(with: numberRange) else { continue } + + var attributes = attributedString.attributes(at: match.range.location, effectiveRange: nil) + + // manually disable link shortening for some text (namely code) + guard attributes[MarkdownAttribute.linkShorteningDisabled] == nil else { continue } + + attributes[.foregroundColor] = Styles.Colors.Blue.medium.color + attributes[MarkdownAttribute.issue] = IssueDetailsModel( + owner: ownerSubstring, + repo: repoSubstring, + number: (numberSubstring as NSString).integerValue + ) + attributes[MarkdownAttribute.url] = nil + + var shortenedText: String + + if ownerSubstring == options.owner && repoSubstring == options.repo { + shortenedText = "#\(numberSubstring)" + } else { + shortenedText = "\(ownerSubstring)/\(repoSubstring)#\(numberSubstring)" + } + + let linkAttributedString = NSAttributedString(string: shortenedText, attributes: attributes) + mutableAttributedString.replaceCharacters(in: match.range, with: linkAttributedString) + } + + return mutableAttributedString +} + func createTextModelUpdatingGitHubFeatures( attributedString: NSAttributedString, width: CGFloat, @@ -366,10 +413,11 @@ func createTextModelUpdatingGitHubFeatures( let usernames = updateUsernames(attributedString: attributedString, options: options) let issues = updateIssueShorthand(attributedString: usernames, options: options) + let shorten = shortenGitHubLinks(attributedString: issues, options: options) return NSAttributedStringSizing( containerWidth: width, - attributedText: issues, + attributedText: shorten, inset: inset ) } diff --git a/Classes/Issues/Comments/Markdown/MMElement+Attributes.swift b/Classes/Issues/Comments/Markdown/MMElement+Attributes.swift index 01c3b14b..5b606cff 100644 --- a/Classes/Issues/Comments/Markdown/MMElement+Attributes.swift +++ b/Classes/Issues/Comments/Markdown/MMElement+Attributes.swift @@ -42,6 +42,7 @@ func PushAttributes( NSAttributedStringKey.backgroundColor: Styles.Colors.Gray.lighter.color, .foregroundColor: Styles.Colors.Gray.dark.color, MarkdownAttribute.usernameDisabled: true, + MarkdownAttribute.linkShorteningDisabled: true, ] case .link: newAttributes = [ .foregroundColor: Styles.Colors.Blue.medium.color, diff --git a/Classes/Issues/Comments/Markdown/MarkdownAttribute.swift b/Classes/Issues/Comments/Markdown/MarkdownAttribute.swift index 991bc4fa..312f2ba9 100644 --- a/Classes/Issues/Comments/Markdown/MarkdownAttribute.swift +++ b/Classes/Issues/Comments/Markdown/MarkdownAttribute.swift @@ -13,5 +13,6 @@ enum MarkdownAttribute { static let email = NSAttributedStringKey(rawValue: "com.freetime.Markdown.email-name") static let username = NSAttributedStringKey(rawValue: "com.freetime.Markdown.username-name") static let usernameDisabled = NSAttributedStringKey(rawValue: "com.freetime.Markdown.username-disabled-name") + static let linkShorteningDisabled = NSAttributedStringKey(rawValue: "com.freetime.Markdown.link-shortening-disabled-name") static let issue = NSAttributedStringKey(rawValue: "com.freetime.Markdown.issue") } diff --git a/FreetimeTests/MMMarkdownASTTests.swift b/FreetimeTests/MMMarkdownASTTests.swift index de596e51..5038e87d 100644 --- a/FreetimeTests/MMMarkdownASTTests.swift +++ b/FreetimeTests/MMMarkdownASTTests.swift @@ -124,5 +124,33 @@ final class MMMarkdownASTTests: XCTestCase { XCTAssertEqual(comboDetails.repo, "bar") XCTAssertEqual(comboDetails.number, 456) } + + func test_shortenLinks() { + let options = GitHubMarkdownOptions(owner: "owner", repo: "repo", flavors: [.issueShorthand]) + + // Test Same Repository + let testOne = "https://github.com/owner/repo/issues/123" + let resultOne = CreateCommentModels(markdown: testOne, width: 300, options: options) + let textOne = resultOne.first as! NSAttributedStringSizing + XCTAssertEqual(textOne.attributedText.string, "#123") + XCTAssertNotNil(textOne.attributedText.attributes(at: 1, effectiveRange: nil)[MarkdownAttribute.issue]) + + let detailsOne = textOne.attributedText.attributes(at: 1, effectiveRange: nil)[MarkdownAttribute.issue] as! IssueDetailsModel + XCTAssertEqual(detailsOne.owner, "owner") + XCTAssertEqual(detailsOne.repo, "repo") + XCTAssertEqual(detailsOne.number, 123) + + // Test Cross Repository + let testTwo = "https://github.com/differentOwner/differentRepo/issues/321" + let resultTwo = CreateCommentModels(markdown: testTwo, width: 300, options: options) + let textTwo = resultTwo.first as! NSAttributedStringSizing + XCTAssertEqual(textTwo.attributedText.string, "differentOwner/differentRepo#321") + XCTAssertNotNil(textTwo.attributedText.attributes(at: 1, effectiveRange: nil)[MarkdownAttribute.issue]) + + let detailsTwo = textTwo.attributedText.attributes(at: 1, effectiveRange: nil)[MarkdownAttribute.issue] as! IssueDetailsModel + XCTAssertEqual(detailsTwo.owner, "differentOwner") + XCTAssertEqual(detailsTwo.repo, "differentRepo") + XCTAssertEqual(detailsTwo.number, 321) + } }