parse checkboxes in MMMarkdown (#1269)

This commit is contained in:
Ryan Nystrom
2017-12-17 09:16:31 -05:00
committed by GitHub
parent dbe722f1c4
commit fed5e9bdc8
5 changed files with 92 additions and 41 deletions

View File

@@ -65,9 +65,8 @@ func CreateCommentModels(
let emojiMarkdown = replaceGithubEmojiRegex(string: markdown)
let replaceHTMLentities = emojiMarkdown.removingHTMLEntities
let replaceCheckmarks = annotateCheckmarks(markdown: replaceHTMLentities)
guard let document = createCommentAST(markdown: replaceCheckmarks)
guard let document = createCommentAST(markdown: replaceHTMLentities)
else { return [emptyDescriptionModel(width: width)] }
var results = [ListDiffable]()
@@ -267,6 +266,16 @@ func travelAST(
if substring != newlineString || listLevel == 0 {
attributedString.append(NSAttributedString(string: substring, attributes: pushedAttributes))
}
} else if element.type == .checkbox {
let appendDisabled = viewerCanUpdate ? "" : "-disabled"
if let image = UIImage(named: (element.checked ? "checked" : "unchecked") + appendDisabled) {
let attachment = NSTextAttachment()
attachment.image = image
// nudge bounds to align better with baseline text
attachment.bounds = CGRect(x: 0, y: -2, width: image.size.width, height: image.size.height)
attributedString.append(NSAttributedString(attachment: attachment))
attributedString.append(NSAttributedString(string: " ", attributes: pushedAttributes))
}
} else if element.type == .listItem {
// append list styles at the beginning of each list item
let isInsideBulletedList = element.parent?.type == .bulletedList
@@ -404,40 +413,6 @@ func shortenGitHubLinks(attributedString: NSAttributedString,
return mutableAttributedString
}
private let checkedIdentifier = ">/CHECKED>/"
private let uncheckedIdentifier = ">/UNCHECKED>/"
private let checkmarkRegex = try! NSRegularExpression(pattern: "^- (\\[([ |x])\\])", options: [.anchorsMatchLines])
private func annotateCheckmarks(markdown: String) -> String {
let matches = checkmarkRegex.matches(in: markdown, options: [], range: markdown.nsrange)
let result = NSMutableString(string: markdown)
for match in matches.reversed() {
let checked = markdown.substring(with: match.range(at: 2)) == "x"
result.replaceCharacters(in: match.range(at: 1), with: checked ? checkedIdentifier : uncheckedIdentifier)
}
return result as String
}
private let checkmarkIdentifierRegex = try! NSRegularExpression(pattern: "(\(checkedIdentifier)|\(uncheckedIdentifier))", options: [])
func addCheckmarkAttachments(
attributedString: NSAttributedString,
viewerCanUpdate: Bool
) -> NSAttributedString {
let string = attributedString.string
let matches = checkmarkIdentifierRegex.matches(in: string, options: [], range: string.nsrange)
let result = NSMutableAttributedString(attributedString: attributedString)
for match in matches.reversed() {
let checked = string.substring(with: match.range) == checkedIdentifier
let attachment = NSTextAttachment()
let appendDisabled = viewerCanUpdate ? "" : "-disabled"
guard let image = UIImage(named: (checked ? "checked" : "unchecked") + appendDisabled) else { continue }
attachment.image = image
// nudge bounds to align better with baseline text
attachment.bounds = CGRect(x: 0, y: -2, width: image.size.width, height: image.size.height)
result.replaceCharacters(in: match.range, with: NSAttributedString(attachment: attachment))
}
return result
}
func createTextModelUpdatingGitHubFeatures(
attributedString: NSAttributedString,
width: CGFloat,
@@ -448,11 +423,10 @@ func createTextModelUpdatingGitHubFeatures(
let issues = updateIssueShorthand(attributedString: attributedString, options: options)
let shorten = shortenGitHubLinks(attributedString: issues, options: options)
let checkmarked = addCheckmarkAttachments(attributedString: shorten, viewerCanUpdate: viewerCanUpdate)
return NSAttributedStringSizing(
containerWidth: width,
attributedText: checkmarked,
attributedText: shorten,
inset: inset
)
}

View File

@@ -55,6 +55,7 @@ typedef NS_ENUM(NSInteger, MMElementType)
MMElementTypeTableRow,
MMElementTypeTableRowCell,
MMElementTypeUsername,
MMElementTypeCheckbox,
};
typedef NS_ENUM(NSInteger, MMTableCellAlignment)
@@ -79,6 +80,7 @@ typedef NS_ENUM(NSInteger, MMTableCellAlignment)
@property (copy, nonatomic, nullable) NSString *stringValue;
@property (assign, nonatomic) NSUInteger numberedListPosition;
@property (copy, nonatomic, nullable) NSString *username;
@property (assign, nonatomic) BOOL checked;
@property (weak, nonatomic, nullable) MMElement *parent;
@property (copy, nonatomic, nonnull) NSArray<MMElement *> *children;

View File

@@ -82,6 +82,8 @@ static NSString * __MMStringFromElementType(MMElementType type)
return @"table-header-cell";
case MMElementTypeUsername:
return @"username";
case MMElementTypeCheckbox:
return @"checkbox";
}
}

View File

@@ -41,7 +41,8 @@ typedef NS_OPTIONS(NSUInteger, MMMarkdownExtensions)
MMMarkdownExtensionsUnderscoresInWords = 1 << 9,
MMMarkdownExtensionsUsernames = 1 << 10,
MMMarkdownExtensionsCheckboxes = 1 << 11,
MMMarkdownExtensionsGitHubFlavored = MMMarkdownExtensionsAutolinkedURLs|MMMarkdownExtensionsFencedCodeBlocks|MMMarkdownExtensionsHardNewlines|MMMarkdownExtensionsStrikethroughs|MMMarkdownExtensionsTables|MMMarkdownExtensionsUnderscoresInWords|MMMarkdownExtensionsUsernames,
MMMarkdownExtensionsGitHubFlavored = MMMarkdownExtensionsAutolinkedURLs|MMMarkdownExtensionsFencedCodeBlocks|MMMarkdownExtensionsHardNewlines|MMMarkdownExtensionsStrikethroughs|MMMarkdownExtensionsTables|MMMarkdownExtensionsUnderscoresInWords|MMMarkdownExtensionsUsernames|MMMarkdownExtensionsCheckboxes,
};

View File

@@ -910,7 +910,13 @@ static NSString * __HTMLEntityForCharacter(unichar character)
}
else
{
element.children = [self.spanParser parseSpansInBlockElement:element withScanner:preListScanner];
NSMutableArray *children = [NSMutableArray new];
MMElement *checkbox = [self _parseListCheckboxWithScanner:preListScanner];
if (checkbox) {
[children addObject:checkbox];
}
[children addObjectsFromArray:[self.spanParser parseSpansInBlockElement:element withScanner:preListScanner]];
element.children = children;
}
element.children = [element.children arrayByAddingObjectsFromArray:[self _parseElementsWithScanner:postListScanner]];
@@ -924,7 +930,13 @@ static NSString * __HTMLEntityForCharacter(unichar character)
}
else
{
element.children = [self.spanParser parseSpansInBlockElement:element withScanner:innerScanner];
NSMutableArray *children = [NSMutableArray new];
MMElement *checkbox = [self _parseListCheckboxWithScanner:innerScanner];
if (checkbox) {
[children addObject:checkbox];
}
[children addObjectsFromArray:[self.spanParser parseSpansInBlockElement:element withScanner:innerScanner]];
element.children = children;
}
}
}
@@ -932,6 +944,66 @@ static NSString * __HTMLEntityForCharacter(unichar character)
return element;
}
- (MMElement *)_parseListCheckboxWithScanner:(MMScanner *)scanner
{
[scanner skipWhitespace];
const NSRange range = NSMakeRange(scanner.location, 3);
NSString *string = scanner.string;
if (string.length - range.location < range.length) {
return nil;
}
NSString *substring = [string substringWithRange:range];
BOOL checked = NO;
if ([substring isEqualToString:@"[x]"] || [substring isEqualToString:@"[X]"]) {
checked = YES;
} else if (![substring isEqualToString:@"[ ]"]) {
return nil;
}
scanner.location += range.length;
MMElement *element = [MMElement new];
element.type = MMElementTypeCheckbox;
element.range = range;
element.checked = checked;
return element;
// const NSInteger start = scanner.startLocation;
//
// if (scanner.nextCharacter != '[') {
// return nil;
// }
//
// [scanner advance];
//
// BOOL checked = NO;
// if (scanner.nextCharacter == 'x') {
// checked = YES;
// } else if (scanner.nextCharacter != ' ') {
// return nil;
// }
//
// [scanner advance];
//
// if (scanner.nextCharacter != ']') {
// return nil;
// }
//
// [scanner advance];
//
// MMElement *element = [MMElement new];
// element.type = MMElementTypeCheckboxes;
// element.range = NSMakeRange(start, 3);
// element.checked = checked;
//
// return element;
}
- (MMElement *)_parseListWithScanner:(MMScanner *)scanner
{
[scanner beginTransaction];