Fuzzy site name search and highlight fuzzy results.
This commit is contained in:
parent
d642cb1aee
commit
8615f6af5d
2
External/KCOrderedAccessorFix
vendored
2
External/KCOrderedAccessorFix
vendored
@ -1 +1 @@
|
||||
Subproject commit e1955221bf52d53736e7d3e7d38465c509e02562
|
||||
Subproject commit 1b8f8b79ad12b70976c7a417ff1a9d29e8c0ed73
|
2
External/Pearl
vendored
2
External/Pearl
vendored
@ -1 +1 @@
|
||||
Subproject commit ef628f388e70459725de21e3528831c705f88795
|
||||
Subproject commit 2237aaf4295f74627b63040c4c246cddff339958
|
@ -29,7 +29,6 @@
|
||||
@property(nonatomic) BOOL alternatePressed;
|
||||
@property(nonatomic) BOOL locked;
|
||||
@property(nonatomic) BOOL newUser;
|
||||
@property(nonatomic) BOOL alwaysYes;
|
||||
|
||||
@property(nonatomic, weak) IBOutlet NSArrayController *sitesController;
|
||||
@property(nonatomic, weak) IBOutlet NSImageView *blurView;
|
||||
|
@ -36,7 +36,7 @@
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPPasswordWindowController { BOOL _skipTextChange; }
|
||||
@implementation MPPasswordWindowController
|
||||
|
||||
#pragma mark - Life
|
||||
|
||||
@ -54,31 +54,33 @@
|
||||
// }];
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowDidBecomeKeyNotification object:self.window
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
[self fadeIn];
|
||||
[self updateUser];
|
||||
}];
|
||||
[self fadeIn];
|
||||
[self updateUser];
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowWillCloseNotification object:self.window
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
NSWindow *sheet = [self.window attachedSheet];
|
||||
if (sheet)
|
||||
[NSApp endSheet:sheet];
|
||||
}];
|
||||
NSWindow *sheet = [self.window attachedSheet];
|
||||
if (sheet)
|
||||
[NSApp endSheet:sheet];
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillResignActiveNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
#ifndef DEBUG
|
||||
[self fadeOut];
|
||||
}];
|
||||
#endif
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:MPSignedInNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
[self updateUser];
|
||||
}];
|
||||
[self updateUser];
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:MPSignedOutNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
[self updateUser];
|
||||
}];
|
||||
[self updateUser];
|
||||
}];
|
||||
[self observeKeyPath:@"sitesController.selection"
|
||||
withBlock:^(id from, id to, NSKeyValueChange cause, id _self) {
|
||||
[_self updateSelection];
|
||||
}];
|
||||
[_self updateSelection];
|
||||
}];
|
||||
|
||||
NSSearchFieldCell *siteFieldCell = self.siteField.cell;
|
||||
siteFieldCell.searchButtonCell = nil;
|
||||
@ -124,17 +126,15 @@
|
||||
- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector {
|
||||
|
||||
if (control == self.siteField) {
|
||||
if ([NSStringFromSelector( commandSelector ) rangeOfString:@"delete"].location == 0) {
|
||||
_skipTextChange = YES;
|
||||
dbg_return_tr( NO, @, control, NSStringFromSelector( commandSelector ) );
|
||||
}
|
||||
if ([NSStringFromSelector( commandSelector ) rangeOfString:@"delete"].location == 0)
|
||||
return NO;
|
||||
}
|
||||
if (control == self.securePasswordField || control == self.revealPasswordField) {
|
||||
if (commandSelector == @selector( insertNewline: ))
|
||||
dbg_return_tr( NO, @, control, NSStringFromSelector( commandSelector ) );
|
||||
return NO;
|
||||
}
|
||||
|
||||
dbg_return_tr( [self handleCommand:commandSelector], @, control, NSStringFromSelector( commandSelector ) );
|
||||
return [self handleCommand:commandSelector];
|
||||
}
|
||||
|
||||
- (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor {
|
||||
@ -174,7 +174,6 @@
|
||||
|
||||
- (BOOL)textView:(NSTextView *)textView doCommandBySelector:(SEL)commandSelector {
|
||||
|
||||
dbg( @"textView:%@doCommandBySelector:%@", textView, NSStringFromSelector( commandSelector ) );
|
||||
return [self handleCommand:commandSelector];
|
||||
}
|
||||
|
||||
@ -227,9 +226,9 @@
|
||||
// "Create" button.
|
||||
[[MPMacAppDelegate get] addSiteNamed:[self.siteField stringValue] completion:
|
||||
^(MPSiteEntity *site, NSManagedObjectContext *context) {
|
||||
if (site)
|
||||
PearlMainQueue( ^{ [self updateSites]; } );
|
||||
}];
|
||||
if (site)
|
||||
PearlMainQueue( ^{ [self updateSites]; } );
|
||||
}];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@ -305,19 +304,6 @@
|
||||
|
||||
#pragma mark - State
|
||||
|
||||
- (NSString *)query {
|
||||
|
||||
return [self.siteField.stringValue stringByReplacingCharactersInRange:self.siteField.currentEditor.selectedRange withString:@""]?: @"";
|
||||
}
|
||||
|
||||
- (BOOL)alwaysYes {
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)setAlwaysYes:(BOOL)alwaysYes {
|
||||
}
|
||||
|
||||
- (void)insertObject:(MPSiteModel *)model inSitesAtIndex:(NSUInteger)index {
|
||||
|
||||
[self.sites insertObject:model atIndex:index];
|
||||
@ -347,7 +333,7 @@
|
||||
[alert addButtonWithTitle:@"Delete"];
|
||||
[alert addButtonWithTitle:@"Cancel"];
|
||||
[alert setMessageText:@"Delete Site?"];
|
||||
[alert setInformativeText:strf( @"Do you want to delete the site named:\n\n%@", self.selectedSite.siteName )];
|
||||
[alert setInformativeText:strf( @"Do you want to delete the site named:\n\n%@", self.selectedSite.name )];
|
||||
[alert beginSheetModalForWindow:self.window modalDelegate:self
|
||||
didEndSelector:@selector( alertDidEnd:returnCode:contextInfo: ) contextInfo:MPAlertDeleteSite];
|
||||
}
|
||||
@ -358,7 +344,7 @@
|
||||
[alert addButtonWithTitle:@"Save"];
|
||||
[alert addButtonWithTitle:@"Cancel"];
|
||||
[alert setMessageText:@"Change Login Name"];
|
||||
[alert setInformativeText:strf( @"Enter the login name for: %@", self.selectedSite.siteName )];
|
||||
[alert setInformativeText:strf( @"Enter the login name for: %@", self.selectedSite.name )];
|
||||
NSTextField *loginField = [[NSTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
|
||||
loginField.stringValue = self.selectedSite.loginName?: @"";
|
||||
[loginField selectText:self];
|
||||
@ -392,7 +378,7 @@
|
||||
[alert addButtonWithTitle:@"Save"];
|
||||
[alert addButtonWithTitle:@"Cancel"];
|
||||
[alert setMessageText:@"Change Password"];
|
||||
[alert setInformativeText:strf( @"Enter the new password for: %@", self.selectedSite.siteName )];
|
||||
[alert setInformativeText:strf( @"Enter the new password for: %@", self.selectedSite.name )];
|
||||
[alert setAccessoryView:[[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )]];
|
||||
[alert layout];
|
||||
[alert beginSheetModalForWindow:self.window modalDelegate:self
|
||||
@ -408,8 +394,9 @@
|
||||
MPSiteType type = [types[t] unsignedIntegerValue];
|
||||
NSString *title = [site.algorithm nameOfType:type];
|
||||
if (type & MPSiteTypeClassGenerated)
|
||||
title = [site.algorithm generatePasswordForSiteNamed:site.siteName ofType:type
|
||||
withCounter:site.counter usingKey:[MPMacAppDelegate get].key];
|
||||
title = [site.algorithm generatePasswordForSiteNamed:site.name ofType:type
|
||||
withCounter:site.counter
|
||||
usingKey:[MPMacAppDelegate get].key];
|
||||
|
||||
NSButtonCell *cell = [self.passwordTypesMatrix cellAtRow:(NSInteger)t column:0];
|
||||
cell.tag = type;
|
||||
@ -421,7 +408,7 @@
|
||||
[alert addButtonWithTitle:@"Save"];
|
||||
[alert addButtonWithTitle:@"Cancel"];
|
||||
[alert setMessageText:@"Change Password Type"];
|
||||
[alert setInformativeText:strf( @"Choose a new password type for: %@", site.siteName )];
|
||||
[alert setInformativeText:strf( @"Choose a new password type for: %@", site.name )];
|
||||
[alert setAccessoryView:self.passwordTypesBox];
|
||||
[alert layout];
|
||||
[alert beginSheetModalForWindow:self.window modalDelegate:self
|
||||
@ -464,9 +451,9 @@
|
||||
NSUserNotification *notification = [NSUserNotification new];
|
||||
notification.title = @"Password Copied";
|
||||
if (selectedSite.loginName.length)
|
||||
notification.subtitle = strf( @"%@ at %@", selectedSite.loginName, selectedSite.siteName );
|
||||
notification.subtitle = strf( @"%@ at %@", selectedSite.loginName, selectedSite.name );
|
||||
else
|
||||
notification.subtitle = selectedSite.siteName;
|
||||
notification.subtitle = selectedSite.name;
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
|
||||
}
|
||||
else {
|
||||
@ -515,12 +502,24 @@
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *query = [self query];
|
||||
static NSRegularExpression *fuzzyRE;
|
||||
static dispatch_once_t once = 0;
|
||||
dispatch_once( &once, ^{
|
||||
fuzzyRE = [NSRegularExpression regularExpressionWithPattern:@"(.)" options:0 error:nil];
|
||||
} );
|
||||
|
||||
NSString *queryString = self.siteField.stringValue;
|
||||
NSString *queryPattern = [queryString stringByReplacingMatchesOfExpression:fuzzyRE withTemplate:@"*$1*"];
|
||||
NSMutableArray *fuzzyGroups = [NSMutableArray new];
|
||||
[fuzzyRE enumerateMatchesInString:queryString options:0 range:NSMakeRange( 0, queryString.length )
|
||||
usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
|
||||
[fuzzyGroups addObject:[queryString substringWithRange:result.range] ];
|
||||
}];
|
||||
[MPMacAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
|
||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
|
||||
fetchRequest.sortDescriptors = @[ [[NSSortDescriptor alloc] initWithKey:@"lastUsed" ascending:NO] ];
|
||||
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND user == %@",
|
||||
query, query, [[MPMacAppDelegate get] activeUserInContext:context]];
|
||||
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name LIKE[cd] %@) AND user == %@",
|
||||
queryPattern, queryPattern, [[MPMacAppDelegate get] activeUserInContext:context]];
|
||||
|
||||
NSError *error = nil;
|
||||
NSArray *siteResults = [context executeFetchRequest:fetchRequest error:&error];
|
||||
@ -531,36 +530,14 @@
|
||||
|
||||
NSMutableArray *newSites = [NSMutableArray arrayWithCapacity:[siteResults count]];
|
||||
for (MPSiteEntity *site in siteResults)
|
||||
[newSites addObject:[[MPSiteModel alloc] initWithEntity:site]];
|
||||
[newSites addObject:[[MPSiteModel alloc] initWithEntity:site fuzzyGroups:fuzzyGroups]];
|
||||
self.sites = newSites;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)updateSelection {
|
||||
|
||||
if (_skipTextChange) {
|
||||
_skipTextChange = NO;
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *siteName = self.selectedSite.siteName;
|
||||
if (!siteName)
|
||||
return;
|
||||
|
||||
if ([self.window isKeyWindow] && [self.siteField isEqual:[self.window firstResponder]]) {
|
||||
NSRange siteNameQueryRange = [siteName rangeOfString:[self query]];
|
||||
self.siteField.stringValue = siteName;
|
||||
|
||||
if (siteNameQueryRange.location == 0)
|
||||
self.siteField.currentEditor.selectedRange =
|
||||
NSMakeRange( siteNameQueryRange.length, siteName.length - siteNameQueryRange.length );
|
||||
}
|
||||
|
||||
[self.siteTable scrollRowToVisible:(NSInteger)self.sitesController.selectionIndex];
|
||||
[self updateGradient];
|
||||
}
|
||||
|
||||
- (void)updateGradient {
|
||||
|
||||
NSView *siteScrollView = self.siteTable.superview.superview;
|
||||
NSRect selectedCellFrame = [self.siteTable frameOfCellAtColumn:0 row:((NSInteger)self.sitesController.selectionIndex)];
|
||||
|
@ -48,7 +48,7 @@
|
||||
</ciFilter>
|
||||
<ciFilter name="CIExposureAdjust">
|
||||
<configuration>
|
||||
<real key="inputEV" value="-1"/>
|
||||
<real key="inputEV" value="0.0"/>
|
||||
<null key="inputImage"/>
|
||||
</configuration>
|
||||
</ciFilter>
|
||||
@ -181,11 +181,11 @@
|
||||
</shadow>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" alignment="center" title="apple.com" id="o0g-Zv-pH4">
|
||||
<font key="font" size="24" name="HelveticaNeue-Thin"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="textColor" name="alternateSelectedControlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="xQb-o5-M5U" name="value" keyPath="objectValue.siteName" id="CZi-gD-5Ec">
|
||||
<binding destination="xQb-o5-M5U" name="value" keyPath="objectValue.displayedName" id="CZi-gD-5Ec">
|
||||
<dictionary key="options">
|
||||
<bool key="NSContinuouslyUpdatesValue" value="YES"/>
|
||||
</dictionary>
|
||||
@ -438,7 +438,7 @@
|
||||
<string key="NSValueTransformerName">NSNegateBoolean</string>
|
||||
</dictionary>
|
||||
</binding>
|
||||
<binding destination="mcS-ik-b0n" name="value" keyPath="selection.contentDisplay" id="djg-i5-pwt"/>
|
||||
<binding destination="mcS-ik-b0n" name="value" keyPath="selection.displayedContent" id="djg-i5-pwt"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Ia6-7b-dFr">
|
||||
|
@ -17,15 +17,17 @@
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "MPSiteEntity.h"
|
||||
@class MPSiteEntity;
|
||||
|
||||
@interface MPSiteModel : NSObject
|
||||
|
||||
@property (nonatomic) NSString *siteName;
|
||||
@property (nonatomic) NSString *name;
|
||||
@property (nonatomic) NSAttributedString *displayedName;
|
||||
@property (nonatomic) MPSiteType type;
|
||||
@property (nonatomic) NSString *typeName;
|
||||
@property (nonatomic) NSString *content;
|
||||
@property (nonatomic) NSString *contentDisplay;
|
||||
@property (nonatomic) NSString *displayedContent;
|
||||
@property (nonatomic) NSString *loginName;
|
||||
@property (nonatomic) NSNumber *uses;
|
||||
@property (nonatomic) NSUInteger counter;
|
||||
@ -34,7 +36,7 @@
|
||||
@property (nonatomic) BOOL generated;
|
||||
@property (nonatomic) BOOL stored;
|
||||
|
||||
- (id)initWithEntity:(MPSiteEntity *)entity;
|
||||
- (instancetype)initWithEntity:(MPSiteEntity *)entity fuzzyGroups:(NSArray *)fuzzyGroups;
|
||||
- (MPSiteEntity *)entityInContext:(NSManagedObjectContext *)moc;
|
||||
|
||||
- (void)updateContent;
|
||||
|
@ -28,25 +28,41 @@
|
||||
BOOL _initialized;
|
||||
}
|
||||
|
||||
- (id)initWithEntity:(MPSiteEntity *)entity {
|
||||
- (id)initWithEntity:(MPSiteEntity *)entity fuzzyGroups:(NSArray *)fuzzyGroups {
|
||||
|
||||
if (!(self = [super init]))
|
||||
return nil;
|
||||
|
||||
[self setEntity:entity];
|
||||
[self setEntity:entity fuzzyGroups:fuzzyGroups];
|
||||
_initialized = YES;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setEntity:(MPSiteEntity *)entity {
|
||||
- (void)setEntity:(MPSiteEntity *)entity fuzzyGroups:(NSArray *)fuzzyGroups {
|
||||
|
||||
if ([_entityOID isEqual:entity.objectID])
|
||||
return;
|
||||
_entityOID = entity.objectID;
|
||||
|
||||
NSString *siteName = entity.name;
|
||||
NSMutableAttributedString *attributedSiteName = [[NSMutableAttributedString alloc] initWithString:siteName];
|
||||
for (NSUInteger f = 0, s = (NSUInteger)-1; f < [fuzzyGroups count]; ++f) {
|
||||
s = [siteName rangeOfString:fuzzyGroups[f] options:NSDiacriticInsensitiveSearch | NSCaseInsensitiveSearch
|
||||
range:NSMakeRange( s + 1, [siteName length] - (s + 1) )].location;
|
||||
if (s == NSNotFound)
|
||||
break;
|
||||
|
||||
[attributedSiteName addAttribute:NSBackgroundColorAttributeName value:[NSColor alternateSelectedControlColor]
|
||||
range:NSMakeRange( s, [fuzzyGroups[f] length] )];
|
||||
}
|
||||
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
|
||||
paragraphStyle.alignment = NSCenterTextAlignment;
|
||||
[attributedSiteName addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange( 0, [siteName length] )];
|
||||
|
||||
self.displayedName = attributedSiteName;
|
||||
self.name = siteName;
|
||||
self.algorithm = entity.algorithm;
|
||||
self.siteName = entity.name;
|
||||
self.lastUsed = entity.lastUsed;
|
||||
self.type = entity.type;
|
||||
self.typeName = entity.typeName;
|
||||
@ -123,7 +139,7 @@
|
||||
|
||||
PearlMainQueue( ^{
|
||||
self.content = result;
|
||||
self.contentDisplay = displayResult;
|
||||
self.displayedContent = displayResult;
|
||||
} );
|
||||
}];
|
||||
[entity resolveLoginUsingKey:[MPAppDelegate_Shared get].key result:^(NSString *result) {
|
||||
|
Loading…
Reference in New Issue
Block a user