Render mentions in previews & refactor
This commit is contained in:
parent
bd62ad099d
commit
8344a86412
|
@ -568,6 +568,7 @@
|
||||||
B845B4D4230CD09100D759F0 /* GroupChatPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = B845B4D3230CD09000D759F0 /* GroupChatPoller.swift */; };
|
B845B4D4230CD09100D759F0 /* GroupChatPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = B845B4D3230CD09000D759F0 /* GroupChatPoller.swift */; };
|
||||||
B846365B22B7418B00AF1514 /* Identicon+ObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B846365A22B7418B00AF1514 /* Identicon+ObjC.swift */; };
|
B846365B22B7418B00AF1514 /* Identicon+ObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B846365A22B7418B00AF1514 /* Identicon+ObjC.swift */; };
|
||||||
B84664F3234FE4540083A1CD /* Mention.swift in Sources */ = {isa = PBXBuildFile; fileRef = B84664F2234FE4530083A1CD /* Mention.swift */; };
|
B84664F3234FE4540083A1CD /* Mention.swift in Sources */ = {isa = PBXBuildFile; fileRef = B84664F2234FE4530083A1CD /* Mention.swift */; };
|
||||||
|
B84664F5235022F30083A1CD /* MentionUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B84664F4235022F30083A1CD /* MentionUtilities.swift */; };
|
||||||
B86BD08123399883000F5AE3 /* QRCodeModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08023399883000F5AE3 /* QRCodeModal.swift */; };
|
B86BD08123399883000F5AE3 /* QRCodeModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08023399883000F5AE3 /* QRCodeModal.swift */; };
|
||||||
B86BD08423399ACF000F5AE3 /* Modal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08323399ACF000F5AE3 /* Modal.swift */; };
|
B86BD08423399ACF000F5AE3 /* Modal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08323399ACF000F5AE3 /* Modal.swift */; };
|
||||||
B86BD08623399CEF000F5AE3 /* SeedModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08523399CEF000F5AE3 /* SeedModal.swift */; };
|
B86BD08623399CEF000F5AE3 /* SeedModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08523399CEF000F5AE3 /* SeedModal.swift */; };
|
||||||
|
@ -1380,6 +1381,7 @@
|
||||||
B845B4D3230CD09000D759F0 /* GroupChatPoller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupChatPoller.swift; sourceTree = "<group>"; };
|
B845B4D3230CD09000D759F0 /* GroupChatPoller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupChatPoller.swift; sourceTree = "<group>"; };
|
||||||
B846365A22B7418B00AF1514 /* Identicon+ObjC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Identicon+ObjC.swift"; sourceTree = "<group>"; };
|
B846365A22B7418B00AF1514 /* Identicon+ObjC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Identicon+ObjC.swift"; sourceTree = "<group>"; };
|
||||||
B84664F2234FE4530083A1CD /* Mention.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mention.swift; sourceTree = "<group>"; };
|
B84664F2234FE4530083A1CD /* Mention.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mention.swift; sourceTree = "<group>"; };
|
||||||
|
B84664F4235022F30083A1CD /* MentionUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionUtilities.swift; sourceTree = "<group>"; };
|
||||||
B86BD08023399883000F5AE3 /* QRCodeModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeModal.swift; sourceTree = "<group>"; };
|
B86BD08023399883000F5AE3 /* QRCodeModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeModal.swift; sourceTree = "<group>"; };
|
||||||
B86BD08323399ACF000F5AE3 /* Modal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modal.swift; sourceTree = "<group>"; };
|
B86BD08323399ACF000F5AE3 /* Modal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modal.swift; sourceTree = "<group>"; };
|
||||||
B86BD08523399CEF000F5AE3 /* SeedModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedModal.swift; sourceTree = "<group>"; };
|
B86BD08523399CEF000F5AE3 /* SeedModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedModal.swift; sourceTree = "<group>"; };
|
||||||
|
@ -2701,6 +2703,7 @@
|
||||||
children = (
|
children = (
|
||||||
B86BD08323399ACF000F5AE3 /* Modal.swift */,
|
B86BD08323399ACF000F5AE3 /* Modal.swift */,
|
||||||
B885D5F52334A32100EE0D8E /* UIView+Constraint.swift */,
|
B885D5F52334A32100EE0D8E /* UIView+Constraint.swift */,
|
||||||
|
B84664F4235022F30083A1CD /* MentionUtilities.swift */,
|
||||||
);
|
);
|
||||||
path = Utilities;
|
path = Utilities;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -3882,6 +3885,7 @@
|
||||||
345BC30C2047030700257B7C /* OWS2FASettingsViewController.m in Sources */,
|
345BC30C2047030700257B7C /* OWS2FASettingsViewController.m in Sources */,
|
||||||
340FC8B7204DAC8D007AEB0F /* OWSConversationSettingsViewController.m in Sources */,
|
340FC8B7204DAC8D007AEB0F /* OWSConversationSettingsViewController.m in Sources */,
|
||||||
34BECE2E1F7ABCE000D7438D /* GifPickerViewController.swift in Sources */,
|
34BECE2E1F7ABCE000D7438D /* GifPickerViewController.swift in Sources */,
|
||||||
|
B84664F5235022F30083A1CD /* MentionUtilities.swift in Sources */,
|
||||||
B84664F3234FE4540083A1CD /* Mention.swift in Sources */,
|
B84664F3234FE4540083A1CD /* Mention.swift in Sources */,
|
||||||
34D1F0C01F8EC1760066283D /* MessageRecipientStatusUtils.swift in Sources */,
|
34D1F0C01F8EC1760066283D /* MessageRecipientStatusUtils.swift in Sources */,
|
||||||
45F659731E1BD99C00444429 /* CallKitCallUIAdaptee.swift in Sources */,
|
45F659731E1BD99C00444429 /* CallKitCallUIAdaptee.swift in Sources */,
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
|
||||||
|
@objc(LKMentionUtilities)
|
||||||
|
public final class MentionUtilities : NSObject {
|
||||||
|
|
||||||
|
override private init() { }
|
||||||
|
|
||||||
|
@objc public static func highlightMentions(in string: String, thread: TSThread) -> String {
|
||||||
|
return highlightMentions(in: string, isOutgoingMessage: false, thread: thread, attributes: [:]).string // isOutgoingMessage and attributes are irrelevant
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc public static func highlightMentions(in string: String, isOutgoingMessage: Bool, thread: TSThread, attributes: [NSAttributedString.Key:Any]) -> NSAttributedString {
|
||||||
|
var string = string
|
||||||
|
let regex = try! NSRegularExpression(pattern: "@[0-9a-fA-F]*", options: [])
|
||||||
|
let knownUserIDs = LokiAPI.userIDCache[thread.uniqueId!] ?? [] // Should always be populated at this point
|
||||||
|
var mentions: [NSRange] = []
|
||||||
|
var outerMatch = regex.firstMatch(in: string, options: .withoutAnchoringBounds, range: NSRange(location: 0, length: string.count))
|
||||||
|
while let match = outerMatch, thread.isGroupThread() {
|
||||||
|
let userID = String((string as NSString).substring(with: match.range).dropFirst()) // Drop the @
|
||||||
|
let matchEnd: Int
|
||||||
|
if knownUserIDs.contains(userID) {
|
||||||
|
var userDisplayName: String?
|
||||||
|
if userID == OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey {
|
||||||
|
userDisplayName = OWSProfileManager.shared().localProfileName()
|
||||||
|
} else {
|
||||||
|
OWSPrimaryStorage.shared().dbReadConnection.read { transaction in
|
||||||
|
let collection = "\(LokiGroupChatAPI.publicChatServer).\(LokiGroupChatAPI.publicChatServerID)"
|
||||||
|
userDisplayName = transaction.object(forKey: userID, inCollection: collection) as! String?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let userDisplayName = userDisplayName {
|
||||||
|
string = (string as NSString).replacingCharacters(in: match.range, with: "@\(userDisplayName)")
|
||||||
|
mentions.append(NSRange(location: match.range.location, length: userDisplayName.count + 1)) // + 1 to include the @
|
||||||
|
matchEnd = match.range.location + userDisplayName.count
|
||||||
|
} else {
|
||||||
|
matchEnd = match.range.location + match.range.length
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
matchEnd = match.range.location + match.range.length
|
||||||
|
}
|
||||||
|
outerMatch = regex.firstMatch(in: string, options: .withoutAnchoringBounds, range: NSRange(location: matchEnd, length: string.count - matchEnd))
|
||||||
|
}
|
||||||
|
let result = NSMutableAttributedString(string: string, attributes: attributes)
|
||||||
|
mentions.forEach { mention in
|
||||||
|
let color: UIColor = isOutgoingMessage ? .lokiDarkGray() : .lokiGreen()
|
||||||
|
result.addAttribute(.backgroundColor, value: color, range: mention)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,8 +19,6 @@
|
||||||
#import "UIColor+OWS.h"
|
#import "UIColor+OWS.h"
|
||||||
#import <SignalMessaging/UIView+OWS.h>
|
#import <SignalMessaging/UIView+OWS.h>
|
||||||
|
|
||||||
// TODO: Reduce code duplication around mention detection
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
@interface OWSMessageBubbleView () <OWSQuotedMessageViewDelegate, OWSContactShareButtonsViewDelegate>
|
@interface OWSMessageBubbleView () <OWSQuotedMessageViewDelegate, OWSContactShareButtonsViewDelegate>
|
||||||
|
@ -685,7 +683,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
font:self.textMessageFont
|
font:self.textMessageFont
|
||||||
shouldIgnoreEvents:shouldIgnoreEvents
|
shouldIgnoreEvents:shouldIgnoreEvents
|
||||||
thread:self.viewItem.interaction.thread
|
thread:self.viewItem.interaction.thread
|
||||||
isOutgoing:[self.viewItem.interaction isKindOfClass:TSOutgoingMessage.self]];
|
isOutgoingMessage:[self.viewItem.interaction isKindOfClass:TSOutgoingMessage.self]];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (void)loadForTextDisplay:(OWSMessageTextView *)textView
|
+ (void)loadForTextDisplay:(OWSMessageTextView *)textView
|
||||||
|
@ -695,7 +693,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
font:(UIFont *)font
|
font:(UIFont *)font
|
||||||
shouldIgnoreEvents:(BOOL)shouldIgnoreEvents
|
shouldIgnoreEvents:(BOOL)shouldIgnoreEvents
|
||||||
thread:(TSThread *)thread
|
thread:(TSThread *)thread
|
||||||
isOutgoing:(BOOL)isOutgoing
|
isOutgoingMessage:(BOOL)isOutgoingMessage
|
||||||
{
|
{
|
||||||
textView.hidden = NO;
|
textView.hidden = NO;
|
||||||
textView.textColor = textColor;
|
textView.textColor = textColor;
|
||||||
|
@ -709,57 +707,18 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
NSString *text = displayableText.displayText;
|
NSString *text = displayableText.displayText;
|
||||||
|
|
||||||
NSError *error1;
|
NSMutableAttributedString *attributedText = [LKMentionUtilities highlightMentionsIn:text isOutgoingMessage:isOutgoingMessage thread:thread attributes:@{ NSFontAttributeName : font, NSForegroundColorAttributeName : textColor }].mutableCopy;
|
||||||
NSRegularExpression *regex1 = [[NSRegularExpression alloc] initWithPattern:@"@[0-9a-fA-F]*" options:0 error:&error1];
|
|
||||||
OWSAssertDebug(error1 == nil);
|
|
||||||
NSSet<NSString *> *knownUserIDs = LKAPI.userIDCache[thread.uniqueId];
|
|
||||||
NSMutableArray<NSValue *> *mentions = [NSMutableArray new];
|
|
||||||
NSTextCheckingResult *match1 = [regex1 firstMatchInString:text options:NSMatchingWithoutAnchoringBounds range:NSMakeRange(0, text.length)];
|
|
||||||
if (match1 != nil && thread.isGroupThread) {
|
|
||||||
while (YES) {
|
|
||||||
NSString *userID = [[text substringWithRange:match1.range] stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:@""];
|
|
||||||
NSUInteger matchEnd;
|
|
||||||
if ([knownUserIDs containsObject:userID]) {
|
|
||||||
__block NSString *userDisplayName;
|
|
||||||
if ([userID isEqual:OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey]) {
|
|
||||||
userDisplayName = OWSProfileManager.sharedManager.localProfileName;
|
|
||||||
} else {
|
|
||||||
[OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
|
||||||
NSString *collection = [NSString stringWithFormat:@"%@.%llu", LKGroupChatAPI.publicChatServer, LKGroupChatAPI.publicChatServerID];
|
|
||||||
userDisplayName = [transaction objectForKey:userID inCollection:collection];
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
if (userDisplayName != nil) {
|
|
||||||
text = [text stringByReplacingCharactersInRange:match1.range withString:[NSString stringWithFormat:@"@%@", userDisplayName]];
|
|
||||||
[mentions addObject:[NSValue valueWithRange:NSMakeRange(match1.range.location, userDisplayName.length + 1)]];
|
|
||||||
matchEnd = match1.range.location + userDisplayName.length;
|
|
||||||
} else {
|
|
||||||
matchEnd = match1.range.location + match1.range.length;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
matchEnd = match1.range.location + match1.range.length;
|
|
||||||
}
|
|
||||||
match1 = [regex1 firstMatchInString:text options:NSMatchingWithoutAnchoringBounds range:NSMakeRange(matchEnd, text.length - matchEnd)];
|
|
||||||
if (match1 == nil) { break; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString:text attributes:@{ NSFontAttributeName : font, NSForegroundColorAttributeName : textColor }];
|
|
||||||
for (NSValue *mention in mentions) {
|
|
||||||
NSRange range = mention.rangeValue;
|
|
||||||
UIColor *highlightColor = isOutgoing ? UIColor.lokiDarkGray : UIColor.lokiGreen;
|
|
||||||
[attributedText addAttribute:NSBackgroundColorAttributeName value:highlightColor range:range];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (searchText.length >= ConversationSearchController.kMinimumSearchTextLength) {
|
if (searchText.length >= ConversationSearchController.kMinimumSearchTextLength) {
|
||||||
NSString *searchableText = [FullTextSearchFinder normalizeWithText:searchText];
|
NSString *searchableText = [FullTextSearchFinder normalizeWithText:searchText];
|
||||||
NSError *error2;
|
NSError *error;
|
||||||
NSRegularExpression *regex2 = [[NSRegularExpression alloc] initWithPattern:[NSRegularExpression escapedPatternForString:searchableText] options:NSRegularExpressionCaseInsensitive error:&error2];
|
NSRegularExpression *regex = [[NSRegularExpression alloc] initWithPattern:[NSRegularExpression escapedPatternForString:searchableText] options:NSRegularExpressionCaseInsensitive error:&error];
|
||||||
OWSAssertDebug(error2 == nil);
|
OWSAssertDebug(error == nil);
|
||||||
for (NSTextCheckingResult *match2 in
|
for (NSTextCheckingResult *match in
|
||||||
[regex2 matchesInString:text options:NSMatchingWithoutAnchoringBounds range:NSMakeRange(0, text.length)]) {
|
[regex matchesInString:text options:NSMatchingWithoutAnchoringBounds range:NSMakeRange(0, text.length)]) {
|
||||||
OWSAssertDebug(match2.range.length >= ConversationSearchController.kMinimumSearchTextLength);
|
OWSAssertDebug(match.range.length >= ConversationSearchController.kMinimumSearchTextLength);
|
||||||
[attributedText addAttribute:NSBackgroundColorAttributeName value:UIColor.yellowColor range:match2.range];
|
[attributedText addAttribute:NSBackgroundColorAttributeName value:UIColor.yellowColor range:match.range];
|
||||||
[attributedText addAttribute:NSForegroundColorAttributeName value:UIColor.ows_blackColor range:match2.range];
|
[attributedText addAttribute:NSForegroundColorAttributeName value:UIColor.ows_blackColor range:match.range];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1197,40 +1156,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
- (DisplayableText *)getDisplayableQuotedText
|
- (DisplayableText *)getDisplayableQuotedText
|
||||||
{
|
{
|
||||||
if (!self.viewItem.hasQuotedText) { return nil; }
|
if (!self.viewItem.hasQuotedText) { return nil; }
|
||||||
NSString *text = self.viewItem.displayableQuotedText.fullText;
|
NSString *rawText = self.viewItem.displayableQuotedText.fullText;
|
||||||
TSThread *thread = self.viewItem.interaction.thread;
|
TSThread *thread = self.viewItem.interaction.thread;
|
||||||
NSError *error;
|
NSString *text = [LKMentionUtilities highlightMentionsIn:rawText thread:thread];
|
||||||
NSRegularExpression *regex = [[NSRegularExpression alloc] initWithPattern:@"@[0-9a-fA-F]*" options:0 error:&error];
|
|
||||||
OWSAssertDebug(error == nil);
|
|
||||||
NSSet<NSString *> *knownUserIDs = LKAPI.userIDCache[thread.uniqueId];
|
|
||||||
NSTextCheckingResult *match = [regex firstMatchInString:text options:NSMatchingWithoutAnchoringBounds range:NSMakeRange(0, text.length)];
|
|
||||||
if (match != nil && thread.isGroupThread) {
|
|
||||||
while (YES) {
|
|
||||||
NSString *userID = [[text substringWithRange:match.range] stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:@""];
|
|
||||||
NSUInteger matchEnd;
|
|
||||||
if ([knownUserIDs containsObject:userID]) {
|
|
||||||
__block NSString *userDisplayName;
|
|
||||||
if ([userID isEqual:OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey]) {
|
|
||||||
userDisplayName = OWSProfileManager.sharedManager.localProfileName;
|
|
||||||
} else {
|
|
||||||
[OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
|
||||||
NSString *collection = [NSString stringWithFormat:@"%@.%llu", LKGroupChatAPI.publicChatServer, LKGroupChatAPI.publicChatServerID];
|
|
||||||
userDisplayName = [transaction objectForKey:userID inCollection:collection];
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
if (userDisplayName != nil) {
|
|
||||||
text = [text stringByReplacingCharactersInRange:match.range withString:[NSString stringWithFormat:@"@%@", userDisplayName]];
|
|
||||||
matchEnd = match.range.location + userDisplayName.length;
|
|
||||||
} else {
|
|
||||||
matchEnd = match.range.location + match.range.length;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
matchEnd = match.range.location + match.range.length;
|
|
||||||
}
|
|
||||||
match = [regex firstMatchInString:text options:NSMatchingWithoutAnchoringBounds range:NSMakeRange(matchEnd, text.length - matchEnd)];
|
|
||||||
if (match == nil) { break; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [DisplayableText displayableText:text];
|
return [DisplayableText displayableText:text];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -395,6 +395,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
}
|
}
|
||||||
NSString *displayableText = thread.lastMessageText;
|
NSString *displayableText = thread.lastMessageText;
|
||||||
if (displayableText) {
|
if (displayableText) {
|
||||||
|
[LKAPI populateUserIDCacheIfNeededFor:thread.threadRecord.uniqueId in:nil]; // TODO: Terrible place to do this, but okay for now
|
||||||
|
displayableText = [LKMentionUtilities highlightMentionsIn:displayableText thread:thread.threadRecord];
|
||||||
[snippetText appendAttributedString:[[NSAttributedString alloc]
|
[snippetText appendAttributedString:[[NSAttributedString alloc]
|
||||||
initWithString:displayableText
|
initWithString:displayableText
|
||||||
attributes:@{
|
attributes:@{
|
||||||
|
|
|
@ -3,7 +3,7 @@ import PromiseKit
|
||||||
@objc(LKAPI)
|
@objc(LKAPI)
|
||||||
public final class LokiAPI : NSObject {
|
public final class LokiAPI : NSObject {
|
||||||
private static var lastDeviceLinkUpdate: [String:Date] = [:] // Hex encoded public key to date
|
private static var lastDeviceLinkUpdate: [String:Date] = [:] // Hex encoded public key to date
|
||||||
@objc static var userIDCache: [String:Set<String>] = [:] // Thread ID to set of user hex encoded public keys
|
@objc public static var userIDCache: [String:Set<String>] = [:] // Thread ID to set of user hex encoded public keys
|
||||||
|
|
||||||
// MARK: Convenience
|
// MARK: Convenience
|
||||||
internal static let storage = OWSPrimaryStorage.shared()
|
internal static let storage = OWSPrimaryStorage.shared()
|
||||||
|
|
Loading…
Reference in New Issue