diff --git a/media/ios/Classes/NewsBlurAppDelegate.h b/media/ios/Classes/NewsBlurAppDelegate.h
index 157638a76..9bcefd645 100644
--- a/media/ios/Classes/NewsBlurAppDelegate.h
+++ b/media/ios/Classes/NewsBlurAppDelegate.h
@@ -293,6 +293,12 @@
- (UIView *)makeFeedTitleGradient:(NSDictionary *)feed withRect:(CGRect)rect;
- (UIView *)makeFeedTitle:(NSDictionary *)feed;
- (UIButton *)makeRightFeedTitle:(NSDictionary *)feed;
+
+- (void)toggleAuthorClassifier:(NSString *)author feedId:(NSString *)feedId;
+- (void)toggleTagClassifier:(NSString *)tag feedId:(NSString *)feedId;
+- (void)toggleTitleClassifier:(NSString *)title feedId:(NSString *)feedId score:(int)score;
+- (void)toggleFeedClassifier:(NSString *)feedId;
+
@end
@interface UnreadCounts : NSObject {
diff --git a/media/ios/Classes/NewsBlurAppDelegate.m b/media/ios/Classes/NewsBlurAppDelegate.m
index bf775ddf1..5fbc20c4c 100644
--- a/media/ios/Classes/NewsBlurAppDelegate.m
+++ b/media/ios/Classes/NewsBlurAppDelegate.m
@@ -679,7 +679,7 @@
for (NSString *feed in [classifiers objectForKey:@"feeds"]) {
if ([[intelligence objectForKey:@"feed"] intValue] <= 0 &&
- [[story objectForKey:@"story_feed_id"] isEqualToString:feed]) {
+ [storyFeedId isEqualToString:feed]) {
int score = [[[classifiers objectForKey:@"feeds"] objectForKey:feed] intValue];
[intelligence setObject:[NSNumber numberWithInt:score] forKey:@"feed"];
}
@@ -1669,6 +1669,181 @@
return titleImageButton;
}
+#pragma mark -
+#pragma mark Classifiers
+
+- (void)toggleAuthorClassifier:(NSString *)author feedId:(NSString *)feedId {
+ int authorScore = [[[[self.activeClassifiers objectForKey:feedId]
+ objectForKey:@"authors"]
+ objectForKey:author] intValue];
+ if (authorScore > 0) {
+ authorScore = -1;
+ } else if (authorScore < 0) {
+ authorScore = 0;
+ } else {
+ authorScore = 1;
+ }
+ NSMutableDictionary *feedClassifiers = [[self.activeClassifiers objectForKey:feedId]
+ mutableCopy];
+ NSMutableDictionary *authors = [[feedClassifiers objectForKey:@"authors"] mutableCopy];
+ [authors setObject:[NSNumber numberWithInt:authorScore] forKey:author];
+ [feedClassifiers setObject:authors forKey:@"authors"];
+ [self.activeClassifiers setObject:feedClassifiers forKey:feedId];
+ [self.storyPageControl refreshHeaders];
+ [self.trainerViewController refresh];
+
+ NSString *urlString = [NSString stringWithFormat:@"http://%@/classifier/save",
+ NEWSBLUR_URL];
+ NSURL *url = [NSURL URLWithString:urlString];
+ __block ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
+ [request setPostValue:author
+ forKey:authorScore >= 1 ? @"like_author" :
+ authorScore <= -1 ? @"dislike_author" :
+ @"remove_like_author"];
+ [request setPostValue:feedId forKey:@"feed_id"];
+ [request setCompletionBlock:^{
+ [self.feedsViewController refreshFeedList:feedId];
+ }];
+ [request setDidFailSelector:@selector(requestFailed:)];
+ [request setDelegate:self];
+ [request startAsynchronous];
+
+ [self recalculateIntelligenceScores:feedId];
+ [self.feedDetailViewController.storyTitlesTable reloadData];
+}
+
+- (void)toggleTagClassifier:(NSString *)tag feedId:(NSString *)feedId {
+ NSLog(@"toggleTagClassifier: %@", tag);
+ int tagScore = [[[[self.activeClassifiers objectForKey:feedId]
+ objectForKey:@"tags"]
+ objectForKey:tag] intValue];
+
+ if (tagScore > 0) {
+ tagScore = -1;
+ } else if (tagScore < 0) {
+ tagScore = 0;
+ } else {
+ tagScore = 1;
+ }
+
+ NSMutableDictionary *feedClassifiers = [[self.activeClassifiers objectForKey:feedId]
+ mutableCopy];
+ NSMutableDictionary *tags = [[feedClassifiers objectForKey:@"tags"] mutableCopy];
+ [tags setObject:[NSNumber numberWithInt:tagScore] forKey:tag];
+ [feedClassifiers setObject:tags forKey:@"tags"];
+ [self.activeClassifiers setObject:feedClassifiers forKey:feedId];
+ [self.storyPageControl refreshHeaders];
+ [self.trainerViewController refresh];
+
+ NSString *urlString = [NSString stringWithFormat:@"http://%@/classifier/save",
+ NEWSBLUR_URL];
+ NSURL *url = [NSURL URLWithString:urlString];
+ __block ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
+ [request setPostValue:tag
+ forKey:tagScore >= 1 ? @"like_tag" :
+ tagScore <= -1 ? @"dislike_tag" :
+ @"remove_like_tag"];
+ [request setPostValue:feedId forKey:@"feed_id"];
+ [request setCompletionBlock:^{
+ [self.feedsViewController refreshFeedList:feedId];
+ }];
+ [request setDidFailSelector:@selector(requestFailed:)];
+ [request setDelegate:self];
+ [request startAsynchronous];
+
+ [self recalculateIntelligenceScores:feedId];
+ [self.feedDetailViewController.storyTitlesTable reloadData];
+}
+
+- (void)toggleTitleClassifier:(NSString *)title feedId:(NSString *)feedId score:(int)score {
+ NSLog(@"toggle Title: %@ (%@) / %d", title, feedId, score);
+ int titleScore = [[[[self.activeClassifiers objectForKey:feedId]
+ objectForKey:@"titles"]
+ objectForKey:title] intValue];
+
+ if (score) {
+ titleScore = score;
+ } else {
+ if (titleScore > 0) {
+ titleScore = -1;
+ } else if (titleScore < 0) {
+ titleScore = 0;
+ } else {
+ titleScore = 1;
+ }
+ }
+
+ NSMutableDictionary *feedClassifiers = [[self.activeClassifiers objectForKey:feedId]
+ mutableCopy];
+ NSMutableDictionary *titles = [[feedClassifiers objectForKey:@"titles"] mutableCopy];
+ [titles setObject:[NSNumber numberWithInt:titleScore] forKey:title];
+ [feedClassifiers setObject:titles forKey:@"titles"];
+ [self.activeClassifiers setObject:feedClassifiers forKey:feedId];
+ [self.storyPageControl refreshHeaders];
+ [self.trainerViewController refresh];
+
+ NSString *urlString = [NSString stringWithFormat:@"http://%@/classifier/save",
+ NEWSBLUR_URL];
+ NSURL *url = [NSURL URLWithString:urlString];
+ __block ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
+ [request setPostValue:title
+ forKey:titleScore >= 1 ? @"like_title" :
+ titleScore <= -1 ? @"dislike_title" :
+ @"remove_like_title"];
+ [request setPostValue:feedId forKey:@"feed_id"];
+ [request setCompletionBlock:^{
+ [self.feedsViewController refreshFeedList:feedId];
+ }];
+ [request setDidFailSelector:@selector(requestFailed:)];
+ [request setDelegate:self];
+ [request startAsynchronous];
+
+ [self recalculateIntelligenceScores:feedId];
+ [self.feedDetailViewController.storyTitlesTable reloadData];
+}
+
+- (void)toggleFeedClassifier:(NSString *)feedId {
+ int feedScore = [[[[self.activeClassifiers objectForKey:feedId]
+ objectForKey:@"feeds"]
+ objectForKey:feedId] intValue];
+
+ if (feedScore > 0) {
+ feedScore = -1;
+ } else if (feedScore < 0) {
+ feedScore = 0;
+ } else {
+ feedScore = 1;
+ }
+
+ NSMutableDictionary *feedClassifiers = [[self.activeClassifiers objectForKey:feedId]
+ mutableCopy];
+ NSMutableDictionary *feeds = [[feedClassifiers objectForKey:@"feeds"] mutableCopy];
+ [feeds setObject:[NSNumber numberWithInt:feedScore] forKey:feedId];
+ [feedClassifiers setObject:feeds forKey:@"feeds"];
+ [self.activeClassifiers setObject:feedClassifiers forKey:feedId];
+ [self.storyPageControl refreshHeaders];
+ [self.trainerViewController refresh];
+
+ NSString *urlString = [NSString stringWithFormat:@"http://%@/classifier/save",
+ NEWSBLUR_URL];
+ NSURL *url = [NSURL URLWithString:urlString];
+ __block ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
+ [request setPostValue:feedId
+ forKey:feedScore >= 1 ? @"like_feed" :
+ feedScore <= -1 ? @"dislike_feed" :
+ @"remove_like_feed"];
+ [request setPostValue:feedId forKey:@"feed_id"];
+ [request setCompletionBlock:^{
+ [self.feedsViewController refreshFeedList:feedId];
+ }];
+ [request setDidFailSelector:@selector(requestFailed:)];
+ [request setDelegate:self];
+ [request startAsynchronous];
+
+ [self recalculateIntelligenceScores:feedId];
+ [self.feedDetailViewController.storyTitlesTable reloadData];
+}
+
@end
diff --git a/media/ios/Classes/NewsBlurViewController.m b/media/ios/Classes/NewsBlurViewController.m
index 66559cc5e..c7129fbf8 100644
--- a/media/ios/Classes/NewsBlurViewController.m
+++ b/media/ios/Classes/NewsBlurViewController.m
@@ -287,8 +287,12 @@ static const CGFloat kFolderTitleHeight = 28;
if ([request responseStatusCode] == 403) {
return [appDelegate showLogin];
} else if ([request responseStatusCode] == 404 ||
+ [request responseStatusCode] == 429 ||
[request responseStatusCode] >= 500) {
[pull finishedLoading];
+ if ([request responseStatusCode] == 429) {
+ return [self informError:@"Slow down. You're rate-limited."];
+ }
return [self informError:@"The server barfed!"];
}
diff --git a/media/ios/Classes/StoryDetailViewController.h b/media/ios/Classes/StoryDetailViewController.h
index 58cc72bee..e464257aa 100644
--- a/media/ios/Classes/StoryDetailViewController.h
+++ b/media/ios/Classes/StoryDetailViewController.h
@@ -66,9 +66,6 @@
- (NSString *)getAvatars:(NSString *)key;
- (NSDictionary *)getUser:(int)user_id;
-- (void)toggleAuthorClassifier:(NSString *)author;
-- (void)toggleTagClassifier:(NSString *)tag;
-- (void)finishTrain:(ASIHTTPRequest *)request;
- (void)refreshHeader;
diff --git a/media/ios/Classes/StoryDetailViewController.m b/media/ios/Classes/StoryDetailViewController.m
index ce1cee632..cac436496 100644
--- a/media/ios/Classes/StoryDetailViewController.m
+++ b/media/ios/Classes/StoryDetailViewController.m
@@ -69,6 +69,10 @@
self.webView.scalesPageToFit = YES;
self.webView.multipleTouchEnabled = NO;
+
+ [self.webView.scrollView setDelaysContentTouches:NO];
+ [self.webView.scrollView setDecelerationRate:UIScrollViewDecelerationRateNormal];
+
self.pageIndex = -2;
}
@@ -831,6 +835,8 @@ shouldStartLoadWithRequest:(NSURLRequest *)request
NSURL *url = [request URL];
NSArray *urlComponents = [url pathComponents];
NSString *action = @"";
+ NSString *feedId = [NSString stringWithFormat:@"%@", [self.activeStory
+ objectForKey:@"story_feed_id"]];
if ([urlComponents count] > 1) {
action = [NSString stringWithFormat:@"%@", [urlComponents objectAtIndex:1]];
}
@@ -904,11 +910,11 @@ shouldStartLoadWithRequest:(NSURLRequest *)request
return NO;
} else if ([action isEqualToString:@"classify-author"]) {
NSString *author = [NSString stringWithFormat:@"%@", [urlComponents objectAtIndex:2]];
- [self toggleAuthorClassifier:author];
+ [self.appDelegate toggleAuthorClassifier:author feedId:feedId];
return NO;
} else if ([action isEqualToString:@"classify-tag"]) {
NSString *tag = [NSString stringWithFormat:@"%@", [urlComponents objectAtIndex:2]];
- [self toggleTagClassifier:tag];
+ [self.appDelegate toggleTagClassifier:tag feedId:feedId];
return NO;
} else if ([action isEqualToString:@"show-profile"]) {
appDelegate.activeUserProfileId = [NSString stringWithFormat:@"%@", [urlComponents objectAtIndex:2]];
@@ -1279,95 +1285,6 @@ shouldStartLoadWithRequest:(NSURLRequest *)request
// self.webView.hidden = NO;
}
-#pragma mark -
-#pragma mark Classifiers
-
-- (void)toggleAuthorClassifier:(NSString *)author {
- NSString *feedId = [NSString stringWithFormat:@"%@", [self.activeStory
- objectForKey:@"story_feed_id"]];
- int authorScore = [[[[appDelegate.activeClassifiers objectForKey:feedId]
- objectForKey:@"authors"]
- objectForKey:author] intValue];
- if (authorScore > 0) {
- authorScore = -1;
- } else if (authorScore < 0) {
- authorScore = 0;
- } else {
- authorScore = 1;
- }
- NSMutableDictionary *feedClassifiers = [[appDelegate.activeClassifiers objectForKey:feedId]
- mutableCopy];
- NSMutableDictionary *authors = [[feedClassifiers objectForKey:@"authors"] mutableCopy];
- [authors setObject:[NSNumber numberWithInt:authorScore] forKey:author];
- [feedClassifiers setObject:authors forKey:@"authors"];
- [appDelegate.activeClassifiers setObject:feedClassifiers forKey:feedId];
- [appDelegate.storyPageControl refreshHeaders];
-
- NSString *urlString = [NSString stringWithFormat:@"http://%@/classifier/save",
- NEWSBLUR_URL];
- NSURL *url = [NSURL URLWithString:urlString];
- ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
- [request setPostValue:author
- forKey:authorScore >= 1 ? @"like_author" :
- authorScore <= -1 ? @"dislike_author" :
- @"remove_like_author"];
- [request setPostValue:feedId forKey:@"feed_id"];
- [request setDidFinishSelector:@selector(finishTrain:)];
- [request setDidFailSelector:@selector(requestFailed:)];
- [request setDelegate:self];
- [request startAsynchronous];
-
- [appDelegate recalculateIntelligenceScores:feedId];
- [appDelegate.feedDetailViewController.storyTitlesTable reloadData];
-}
-
-- (void)toggleTagClassifier:(NSString *)tag {
- NSLog(@"toggleTagClassifier: %@", tag);
- NSString *feedId = [NSString stringWithFormat:@"%@", [self.activeStory
- objectForKey:@"story_feed_id"]];
- int tagScore = [[[[appDelegate.activeClassifiers objectForKey:feedId]
- objectForKey:@"tags"]
- objectForKey:tag] intValue];
-
- if (tagScore > 0) {
- tagScore = -1;
- } else if (tagScore < 0) {
- tagScore = 0;
- } else {
- tagScore = 1;
- }
-
- NSMutableDictionary *feedClassifiers = [[appDelegate.activeClassifiers objectForKey:feedId]
- mutableCopy];
- NSMutableDictionary *tags = [[feedClassifiers objectForKey:@"tags"] mutableCopy];
- [tags setObject:[NSNumber numberWithInt:tagScore] forKey:tag];
- [feedClassifiers setObject:tags forKey:@"tags"];
- [appDelegate.activeClassifiers setObject:feedClassifiers forKey:feedId];
- [appDelegate.storyPageControl refreshHeaders];
-
- NSString *urlString = [NSString stringWithFormat:@"http://%@/classifier/save",
- NEWSBLUR_URL];
- NSURL *url = [NSURL URLWithString:urlString];
- ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
- [request setPostValue:tag
- forKey:tagScore >= 1 ? @"like_tag" :
- tagScore <= -1 ? @"dislike_tag" :
- @"remove_like_tag"];
- [request setPostValue:feedId forKey:@"feed_id"];
- [request setDidFinishSelector:@selector(finishTrain:)];
- [request setDidFailSelector:@selector(requestFailed:)];
- [request setDelegate:self];
- [request startAsynchronous];
-
- [appDelegate recalculateIntelligenceScores:feedId];
- [appDelegate.feedDetailViewController.storyTitlesTable reloadData];
-}
-
-- (void)finishTrain:(ASIHTTPRequest *)request {
- id feedId = [self.activeStory objectForKey:@"story_feed_id"];
- [appDelegate.feedsViewController refreshFeedList:feedId];
-}
-
- (void)refreshHeader {
NSString *headerString = [[[self getHeader] stringByReplacingOccurrencesOfString:@"\'" withString:@"\\'"]
stringByReplacingOccurrencesOfString:@"\n" withString:@" "];
diff --git a/media/ios/Classes/TrainerViewController.h b/media/ios/Classes/TrainerViewController.h
index 55851221e..c6273a716 100644
--- a/media/ios/Classes/TrainerViewController.h
+++ b/media/ios/Classes/TrainerViewController.h
@@ -13,7 +13,8 @@
@interface TrainerWebView : UIWebView {}
-- (void)changeTitle:(id)sender;
+- (void)focusTitle:(id)sender;
+- (void)hideTitle:(id)sender;
@end
@@ -37,6 +38,8 @@
@property (nonatomic, assign) BOOL feedTrainer;
@property (nonatomic, assign) BOOL storyTrainer;
+- (void)refresh;
+- (NSString *)makeTrainerHTML;
- (NSString *)makeTrainerSections;
- (NSString *)makeStoryAuthor;
- (NSString *)makeFeedAuthors;
@@ -47,6 +50,5 @@
- (NSString *)makeClassifier:(NSString *)classifierName withType:(NSString *)classifierType score:(int)score;
- (IBAction)doCloseDialog:(id)sender;
-- (void)changeTitle:(id)sender;
@end
\ No newline at end of file
diff --git a/media/ios/Classes/TrainerViewController.m b/media/ios/Classes/TrainerViewController.m
index 17f2eeb7b..58f292017 100644
--- a/media/ios/Classes/TrainerViewController.m
+++ b/media/ios/Classes/TrainerViewController.m
@@ -34,6 +34,8 @@
navBar.tintColor = UIColorFromRGB(0x183353);
[self hideGradientBackground:webView];
+ [self.webView.scrollView setDelaysContentTouches:YES];
+ [self.webView.scrollView setDecelerationRate:UIScrollViewDecelerationRateNormal];
}
- (void) hideGradientBackground:(UIView*)theView
{
@@ -48,18 +50,33 @@
- (void)viewWillAppear:(BOOL)animated {
[[UIMenuController sharedMenuController]
setMenuItems:[NSArray arrayWithObjects:
- [[UIMenuItem alloc] initWithTitle:@"👎 Hide" action:@selector(changeTitle:)],
- [[UIMenuItem alloc] initWithTitle:@"👍 Focus" action:@selector(changeTitle:)],
+ [[UIMenuItem alloc] initWithTitle:@"👎 Hide" action:@selector(hideTitle:)],
+ [[UIMenuItem alloc] initWithTitle:@"👍 Focus" action:@selector(focusTitle:)],
nil]];
UILabel *titleLabel = (UILabel *)[appDelegate makeFeedTitle:appDelegate.activeFeed];
titleLabel.shadowColor = UIColorFromRGB(0x306070);
navBar.topItem.titleView = titleLabel;
-
+
NSString *path = [[NSBundle mainBundle] bundlePath];
NSURL *baseURL = [NSURL fileURLWithPath:path];
+ [self.webView loadHTMLString:[self makeTrainerHTML] baseURL:baseURL];
+}
+
+- (void)refresh {
+ if (self.view.hidden || self.view.superview == nil) {
+ NSLog(@"Trainer hidden, ignoring redraw.");
+ return;
+ }
+ NSString *headerString = [[[self makeTrainerSections]
+ stringByReplacingOccurrencesOfString:@"\'" withString:@"\\'"]
+ stringByReplacingOccurrencesOfString:@"\n" withString:@" "];
+ NSString *jsString = [NSString stringWithFormat:@"document.getElementById('NB-trainer').innerHTML = '%@';",
+ headerString];
- [self.webView loadHTMLString:[self makeTrainerSections] baseURL:baseURL];
+ [self.webView stringByEvaluatingJavaScriptFromString:jsString];
+
+ [self.webView stringByEvaluatingJavaScriptFromString:@"attachFastClick({skipEvent: true});"];
}
- (void)viewDidAppear:(BOOL)animated {
@@ -74,11 +91,8 @@
#pragma mark -
#pragma mark Story layout
-- (NSString *)makeTrainerSections {
- NSString *storyAuthor = self.feedTrainer ? [self makeFeedAuthors] : [self makeStoryAuthor];
- NSString *storyTags = self.feedTrainer ? [self makeFeedTags] : [self makeStoryTags];
- NSString *storyTitle = self.feedTrainer ? @"" : [self makeTitle];
- NSString *storyPublisher = [self makePublisher];
+- (NSString *)makeTrainerHTML {
+ NSString *trainerSections = [self makeTrainerSections];
int contentWidth = self.view.frame.size.width;
NSString *contentWidthClass;
@@ -105,21 +119,13 @@
""
"
%@" // header string
""
- " "
- "
%@
"
- "
%@
"
- "
%@
"
- "
%@
"
- "
"
+ "%@
"
"%@" // footer
""
"",
headerString,
contentWidthClass,
- storyTitle,
- storyAuthor,
- storyTags,
- storyPublisher,
+ trainerSections,
footerString
];
@@ -128,6 +134,26 @@
return htmlString;
}
+- (NSString *)makeTrainerSections {
+ NSString *storyAuthor = self.feedTrainer ? [self makeFeedAuthors] : [self makeStoryAuthor];
+ NSString *storyTags = self.feedTrainer ? [self makeFeedTags] : [self makeStoryTags];
+ NSString *storyTitle = self.feedTrainer ? @"" : [self makeTitle];
+ NSString *storyPublisher = [self makePublisher];
+
+ NSString *htmlString = [NSString stringWithFormat:@
+ ""
+ "
%@
"
+ "
%@
"
+ "
%@
"
+ "
%@
"
+ "
",
+ storyTitle,
+ storyAuthor,
+ storyTags,
+ storyPublisher];
+
+ return htmlString;
+}
- (NSString *)makeStoryAuthor {
NSString *feedId = [NSString stringWithFormat:@"%@", [appDelegate.activeStory
objectForKey:@"story_feed_id"]];
@@ -292,13 +318,13 @@
" Publisher
"
" "
" 0 ? @"NB-story-publisher-positive" : publisherScore < 0 ? @"NB-story-publisher-negative" : @"",
+ publisherScore > 0 ? @"positive" : publisherScore < 0 ? @"negative" : @"",
[self makeClassifier:feedTitle withType:@"publisher" score:publisherScore]];
return storyPublisher;
@@ -308,31 +334,42 @@
NSString *feedId = [NSString stringWithFormat:@"%@", [appDelegate.activeStory
objectForKey:@"story_feed_id"]];
NSString *storyTitle = [appDelegate.activeStory objectForKey:@"story_title"];
-
+
if (!storyTitle) {
return @"";
}
- NSMutableDictionary *titleClassifiers = [[appDelegate.activeClassifiers objectForKey:feedId]
+ NSMutableDictionary *classifiers = [[appDelegate.activeClassifiers objectForKey:feedId]
objectForKey:@"titles"];
- for (NSString *titleClassifier in titleClassifiers) {
- if ([storyTitle containsString:titleClassifier]) {
- int titleScore = [[titleClassifiers objectForKey:titleClassifier] intValue];
- storyTitle = [storyTitle
- stringByReplacingOccurrencesOfString:titleClassifier
- withString:[NSString stringWithFormat:@" %@",
- titleScore > 0 ? @"positive" : titleScore < 0 ? @"negative" : @"",
- titleClassifier]];
+ NSMutableArray *titleStrings = [NSMutableArray array];
+ for (NSString *title in classifiers) {
+ if ([storyTitle containsString:title]) {
+ int titleScore = [[classifiers objectForKey:title] intValue];
+ NSString *titleClassifier = [NSString stringWithFormat:@
+ "",
+ title,
+ titleScore > 0 ? @"positive" : titleScore < 0 ? @"negative" : @"",
+ [self makeClassifier:title withType:@"title" score:titleScore]];
+ [titleStrings addObject:titleClassifier];
}
}
+ NSString *titleClassifiers;
+ if ([titleStrings count]) {
+ titleClassifiers = [titleStrings componentsJoinedByString:@""];
+ } else {
+ titleClassifiers = @"Tap and hold the title
";
+ }
NSString *titleTrainer = [NSString stringWithFormat:@""
"
Story Title
"
"
"
"
%@
"
- "
Tap and hold the title
"
+ " %@"
"
"
- "
", storyTitle];
+ "", storyTitle, titleClassifiers];
return titleTrainer;
}
@@ -359,17 +396,51 @@
[appDelegate.trainerViewController dismissModalViewControllerAnimated:YES];
}
-- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
- if (action == @selector(changeTitle:)) {
- return YES;
- } else {
- return NO;
- }
+- (void)changeTitle:(id)sender score:(int)score {
+ NSString *feedId = [NSString stringWithFormat:@"%@", [appDelegate.activeStory
+ objectForKey:@"story_feed_id"]];
+ NSString *selectedTitle = [self.webView
+ stringByEvaluatingJavaScriptFromString:@"window.getSelection().toString()"];
+
+ [self.appDelegate toggleTitleClassifier:selectedTitle feedId:feedId score:score];
}
-- (void)changeTitle:(id)sender {
- NSString *selectedTitle = [self.webView stringByEvaluatingJavaScriptFromString:@"window.getSelection().toString()"];
- NSLog(@"Selected: %@", selectedTitle);
+
+- (BOOL)webView:(UIWebView *)webView
+shouldStartLoadWithRequest:(NSURLRequest *)request
+ navigationType:(UIWebViewNavigationType)navigationType {
+ NSURL *url = [request URL];
+ NSArray *urlComponents = [url pathComponents];
+ NSString *action = @"";
+ NSString *feedId = [NSString stringWithFormat:@"%@", [appDelegate.activeFeed
+ objectForKey:@"id"]];
+ if ([urlComponents count] > 1) {
+ action = [NSString stringWithFormat:@"%@", [urlComponents objectAtIndex:1]];
+ }
+
+ NSLog(@"Tapped url: %@", url);
+ if ([[url host] isEqualToString: @"ios.newsblur.com"]){
+
+ if ([action isEqualToString:@"classify-author"]) {
+ NSString *author = [NSString stringWithFormat:@"%@", [urlComponents objectAtIndex:2]];
+ [self.appDelegate toggleAuthorClassifier:author feedId:feedId];
+ return NO;
+ } else if ([action isEqualToString:@"classify-tag"]) {
+ NSString *tag = [NSString stringWithFormat:@"%@", [urlComponents objectAtIndex:2]];
+ [self.appDelegate toggleTagClassifier:tag feedId:feedId];
+ return NO;
+ } else if ([action isEqualToString:@"classify-title"]) {
+ NSString *title = [NSString stringWithFormat:@"%@", [urlComponents objectAtIndex:2]];
+ [self.appDelegate toggleTitleClassifier:title feedId:feedId score:0];
+ return NO;
+ } else if ([action isEqualToString:@"classify-feed"]) {
+ NSString *feedId = [NSString stringWithFormat:@"%@", [urlComponents objectAtIndex:2]];
+ [self.appDelegate toggleFeedClassifier:feedId];
+ return NO;
+ }
+ }
+
+ return YES;
}
@end
@@ -378,16 +449,21 @@
@implementation TrainerWebView
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
- if (action == @selector(changeTitle:)) {
+ if (action == @selector(focusTitle:) || action == @selector(hideTitle:)) {
return YES;
} else {
return NO;
}
}
-- (void)changeTitle:(id)sender {
+- (void)focusTitle:(id)sender {
NewsBlurAppDelegate *appDelegate = [NewsBlurAppDelegate sharedAppDelegate];
- [appDelegate.trainerViewController changeTitle:sender];
+ [appDelegate.trainerViewController changeTitle:sender score:1];
+}
+
+- (void)hideTitle:(id)sender {
+ NewsBlurAppDelegate *appDelegate = [NewsBlurAppDelegate sharedAppDelegate];
+ [appDelegate.trainerViewController changeTitle:sender score:-1];
}
@end
diff --git a/media/ios/NewsBlur.xcodeproj/xcuserdata/sclay.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist b/media/ios/NewsBlur.xcodeproj/xcuserdata/sclay.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist
index 4a1d0981f..9c93bf1b3 100644
--- a/media/ios/NewsBlur.xcodeproj/xcuserdata/sclay.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist
+++ b/media/ios/NewsBlur.xcodeproj/xcuserdata/sclay.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist
@@ -16,10 +16,23 @@
landmarkName = "-applyNewIndex:pageController:"
landmarkType = "5">
+
+
25
+
delegate
@@ -4603,7 +4611,7 @@ AAgAAAAIAAIACAACAAAAAgAAAAEAAQABAAE
- 25
+ 26
diff --git a/media/ios/static/fastTouch.js b/media/ios/static/fastTouch.js
index 7af5abfbd..21e6bde06 100644
--- a/media/ios/static/fastTouch.js
+++ b/media/ios/static/fastTouch.js
@@ -1,85 +1,578 @@
+/**
+ * @preserve FastClick: polyfill to remove click delays on browsers with touch UIs.
+ *
+ * @version 0.4.6
+ * @codingstandard ftlabs-jsv2
+ * @copyright The Financial Times Limited [All Rights Reserved]
+ * @license MIT License (see LICENSE.txt)
+ */
-function NoClickDelay(el) {
- this.element = typeof el == 'object' ? el : document.getElementById(el);
- if( window.Touch ) {
- this.element.removeEventListener('touchstart', this.element.notouch, false);
- this.element.notouch = this;
- this.element.addEventListener('touchstart', this.element.notouch, false);
- }
-}
-NoClickDelay.prototype = {
-handleEvent: function(e) {
- switch(e.type) {
- case 'touchstart': this.onTouchStart(e); break;
- case 'touchmove': this.onTouchMove(e); break;
- case 'touchend': this.onTouchEnd(e); break;
- }
-},
-onTouchStart: function(e) {
- e.preventDefault();
- this.moved = false;
- this.x = e.targetTouches[0].clientX;
- this.y = e.targetTouches[0].clientY;
- this.theTarget = document.elementFromPoint(e.targetTouches[0].clientX, e.targetTouches[0].clientY);
- this.theTarget = $(this.theTarget).closest('a').get(0);
- // if(this.theTarget.nodeType == 3) this.theTarget = theTarget.parentNode;
- this.theTarget.className+= ' pressed';
- this.element.addEventListener('touchmove', this, false);
- this.element.addEventListener('touchend', this, false);
-},
-onTouchMove: function(e) {
- var x = e.targetTouches[0].clientX;
- var y = e.targetTouches[0].clientY;
- if( Math.sqrt(Math.pow(x-this.x,2)+Math.pow(y-this.y,2))>50){
- this.moved = true;
- this.theTarget.className = this.theTarget.className.replace(/ ?pressed/gi, '');
- this.theTarget.className = this.theTarget.className.replace(/ ?active/gi, '');
- } else {
- if(this.moved==true){
- this.moved=false;
- this.theTarget.className+= ' pressed';
- }
- }
-},
-onTouchEnd: function(e) {
- this.element.removeEventListener('touchmove', this, false);
- this.element.removeEventListener('touchend', this, false);
- if( !this.moved && this.theTarget ) {
- this.theTarget.className = this.theTarget.className.replace(/ ?pressed/gi, '');
- this.theTarget.className+= ' active';
- var theEvent = document.createEvent('MouseEvents');
- theEvent.initEvent('click', true, true);
- this.theTarget.dispatchEvent(theEvent);
- }
- this.theTarget = undefined;
+/*jslint browser:true, node:true*/
+/*global define, Event, Node*/
+
+
+/**
+ * Instantiate fast-clicking listeners on the specificed layer.
+ *
+ * @constructor
+ * @param {Element} layer The layer to listen on
+ */
+function FastClick(layer) {
+ 'use strict';
+ var oldOnClick, self = this;
+
+
+ /**
+ * Whether a click is currently being tracked.
+ *
+ * @type boolean
+ */
+ this.trackingClick = false;
+
+
+ /**
+ * Timestamp for when when click tracking started.
+ *
+ * @type number
+ */
+ this.trackingClickStart = 0;
+
+
+ /**
+ * The element being tracked for a click.
+ *
+ * @type EventTarget
+ */
+ this.targetElement = null;
+
+
+ /**
+ * X-coordinate of touch start event.
+ *
+ * @type number
+ */
+ this.touchStartX = 0;
+
+
+ /**
+ * Y-coordinate of touch start event.
+ *
+ * @type number
+ */
+ this.touchStartY = 0;
+
+
+ /**
+ * ID of the last touch, retrieved from Touch.identifier.
+ *
+ * @type number
+ */
+ this.lastTouchIdentifier = 0;
+
+
+ /**
+ * The FastClick layer.
+ *
+ * @type Element
+ */
+ this.layer = layer;
+
+ if (!layer || !layer.nodeType) {
+ throw new TypeError('Layer must be a document node');
+ }
+
+ /** @type function() */
+ this.onClick = function() { FastClick.prototype.onClick.apply(self, arguments); };
+
+ /** @type function() */
+ this.onTouchStart = function() { FastClick.prototype.onTouchStart.apply(self, arguments); };
+
+ /** @type function() */
+ this.onTouchMove = function() { FastClick.prototype.onTouchMove.apply(self, arguments); };
+
+ /** @type function() */
+ this.onTouchEnd = function() { FastClick.prototype.onTouchEnd.apply(self, arguments); };
+
+ /** @type function() */
+ this.onTouchCancel = function() { FastClick.prototype.onTouchCancel.apply(self, arguments); };
+
+ // Devices that don't support touch don't need FastClick
+ if (typeof window.ontouchstart === 'undefined') {
+ return;
+ }
+
+ // Set up event handlers as required
+ layer.addEventListener('click', this.onClick, true);
+ layer.addEventListener('touchstart', this.onTouchStart, false);
+ layer.addEventListener('touchmove', this.onTouchMove, false);
+ layer.addEventListener('touchend', this.onTouchEnd, false);
+ layer.addEventListener('touchcancel', this.onTouchCancel, false);
+
+ // Hack is required for browsers that don't support Event#stopImmediatePropagation (e.g. Android 2)
+ // which is how FastClick normally stops click events bubbling to callbacks registered on the FastClick
+ // layer when they are cancelled.
+ if (!Event.prototype.stopImmediatePropagation) {
+ layer.removeEventListener = function(type, callback, capture) {
+ var rmv = Node.prototype.removeEventListener;
+ if (type === 'click') {
+ rmv.call(layer, type, callback.hijacked || callback, capture);
+ } else {
+ rmv.call(layer, type, callback, capture);
+ }
+ };
+
+ layer.addEventListener = function(type, callback, capture) {
+ var adv = Node.prototype.addEventListener;
+ if (type === 'click') {
+ adv.call(layer, type, callback.hijacked || (callback.hijacked = function(event) {
+ if (!event.propagationStopped) {
+ callback(event);
+ }
+ }), capture);
+ } else {
+ adv.call(layer, type, callback, capture);
+ }
+ };
+ }
+
+ // If a handler is already declared in the element's onclick attribute, it will be fired before
+ // FastClick's onClick handler. Fix this by pulling out the user-defined handler function and
+ // adding it as listener.
+ if (typeof layer.onclick === 'function') {
+
+ // Android browser on at least 3.2 requires a new reference to the function in layer.onclick
+ // - the old one won't work if passed to addEventListener directly.
+ oldOnClick = layer.onclick;
+ layer.addEventListener('click', function(event) {
+ oldOnClick(event);
+ }, false);
+ layer.onclick = null;
+ }
}
+
+
+/**
+ * Android requires an exception for labels.
+ *
+ * @type boolean
+ */
+FastClick.prototype.deviceIsAndroid = navigator.userAgent.indexOf('Android') > 0;
+
+
+/**
+ * iOS requires an exception for alert confirm dialogs.
+ *
+ * @type boolean
+ */
+FastClick.prototype.deviceIsIOS = /iP(ad|hone|od)/.test(navigator.userAgent);
+
+
+/**
+ * iOS 4 requires an exception for select elements.
+ *
+ * @type boolean
+ */
+FastClick.prototype.deviceIsIOS4 = FastClick.prototype.deviceIsIOS && (/OS 4_\d(_\d)?/).test(navigator.userAgent);
+
+
+/**
+ * Determine whether a given element requires a native click.
+ *
+ * @param {EventTarget|Element} target Target DOM element
+ * @returns {boolean} Returns true if the element needs a native click
+ */
+FastClick.prototype.needsClick = function(target) {
+ 'use strict';
+ switch (target.nodeName.toLowerCase()) {
+ case 'label':
+ case 'video':
+ return true;
+ default:
+ return (/\bneedsclick\b/).test(target.className);
+ }
};
-function attachFastClick() {
+
+/**
+ * Determine whether a given element requires a call to focus to simulate click into element.
+ *
+ * @param {EventTarget|Element} target Target DOM element
+ * @returns {boolean} Returns true if the element requires a call to focus to simulate native click.
+ */
+FastClick.prototype.needsFocus = function(target) {
+ 'use strict';
+ switch (target.nodeName.toLowerCase()) {
+ case 'textarea':
+ case 'select':
+ return true;
+ case 'input':
+ switch (target.type) {
+ case 'button':
+ case 'checkbox':
+ case 'file':
+ case 'image':
+ case 'radio':
+ case 'submit':
+ return false;
+ }
+ return true;
+ default:
+ return (/\bneedsfocus\b/).test(target.className);
+ }
+};
+
+
+/**
+ * Send a click event to the specified element.
+ *
+ * @param {EventTarget|Element} targetElement
+ * @param {Event} event
+ */
+FastClick.prototype.sendClick = function(targetElement, event) {
+ 'use strict';
+ var clickEvent, touch;
+
+ // On some Android devices activeElement needs to be blurred otherwise the synthetic click will have no effect (#24)
+ if (document.activeElement && document.activeElement !== targetElement) {
+ document.activeElement.blur();
+ }
+
+ touch = event.changedTouches[0];
+
+ // Synthesise a click event, with an extra attribute so it can be tracked
+ clickEvent = document.createEvent('MouseEvents');
+ clickEvent.initMouseEvent('click', true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
+ clickEvent.forwardedTouchEvent = true;
+ targetElement.dispatchEvent(clickEvent);
+};
+
+
+/**
+ * On touch start, record the position and scroll offset.
+ *
+ * @param {Event} event
+ * @returns {boolean}
+ */
+FastClick.prototype.onTouchStart = function(event) {
+ 'use strict';
+ var touch = event.targetTouches[0];
+
+ this.trackingClick = true;
+ this.trackingClickStart = event.timeStamp;
+ this.targetElement = event.target;
+ this.theTarget = $(this.targetElement).closest('a').get(0);
+
+ this.theTarget.className += ' pressed';
+
+ this.touchStartX = touch.pageX;
+ this.touchStartY = touch.pageY;
+ this.startClickTime = new Date;
+
+ // Prevent phantom clicks on fast double-tap (issue #36)
+ if ((event.timeStamp - this.lastClickTime) < 200) {
+ event.preventDefault();
+ }
+
+ return true;
+};
+
+
+/**
+ * Based on a touchmove event object, check whether the touch has moved past a boundary since it started.
+ *
+ * @param {Event} event
+ * @returns {boolean}
+ */
+FastClick.prototype.touchHasMoved = function(event) {
+ 'use strict';
+ var touch = event.targetTouches[0];
+
+ if (Math.abs(touch.pageX - this.touchStartX) > 10 || Math.abs(touch.pageY - this.touchStartY) > 10) {
+ return true;
+ }
+
+ return false;
+};
+
+
+/**
+ * Update the last position.
+ *
+ * @param {Event} event
+ * @returns {boolean}
+ */
+FastClick.prototype.onTouchMove = function(event) {
+ 'use strict';
+ if (!this.trackingClick) {
+ return true;
+ }
+
+ // If the touch has moved, cancel the click tracking
+ if (this.targetElement !== event.target || this.touchHasMoved(event)) {
+ this.trackingClick = false;
+ this.theTarget.className = this.theTarget.className.replace(/ ?pressed/gi, '');
+ this.targetElement = null;
+ }
+
+ return true;
+};
+
+
+/**
+ * Attempt to find the labelled control for the given label element.
+ *
+ * @param {EventTarget|HTMLLabelElement} labelElement
+ * @returns {Element|null}
+ */
+FastClick.prototype.findControl = function(labelElement) {
+ 'use strict';
+
+ // Fast path for newer browsers supporting the HTML5 control attribute
+ if (labelElement.control !== undefined) {
+ return labelElement.control;
+ }
+
+ // All browsers under test that support touch events also support the HTML5 htmlFor attribute
+ if (labelElement.htmlFor) {
+ return document.getElementById(labelElement.htmlFor);
+ }
+
+ // If no for attribute exists, attempt to retrieve the first labellable descendant element
+ // the list of which is defined here: http://www.w3.org/TR/html5/forms.html#category-label
+ return labelElement.querySelector('button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea');
+};
+
+
+/**
+ * On touch end, determine whether to send a click event at once.
+ *
+ * @param {Event} event
+ * @returns {boolean}
+ */
+FastClick.prototype.onTouchEnd = function(event) {
+ 'use strict';
+ var forElement, trackingClickStart, targetElement = this.targetElement, touch = event.changedTouches[0];
+
+ if (!this.trackingClick) {
+ return true;
+ }
+
+ // Weird things happen on iOS when an alert or confirm dialog is opened from a click event callback (issue #23):
+ // when the user next taps anywhere else on the page, new touchstart and touchend events are dispatched
+ // with the same identifier as the touch event that previously triggered the click that triggered the alert.
+ if (this.deviceIsIOS) {
+ if (touch.identifier === this.lastTouchIdentifier) {
+ event.preventDefault();
+ return false;
+ }
+
+ this.lastTouchIdentifier = touch.identifier;
+ }
+
+ // Prevent phantom clicks on fast double-tap (issue #36)
+ if ((event.timeStamp - this.lastClickTime) < 200) {
+ this.cancelNextClick = true;
+ return true;
+ }
+
+ if ((new Date - this.startClickTime) > 115) {
+ return false;
+ }
+ this.lastClickTime = event.timeStamp;
+
+ trackingClickStart = this.trackingClickStart;
+ this.trackingClick = false;
+ this.theTarget.className = this.theTarget.className.replace(/ ?pressed/gi, '');
+ this.trackingClickStart = 0;
+
+ if (targetElement.nodeName.toLowerCase() === 'label') {
+ forElement = this.findControl(targetElement);
+ if (forElement) {
+ targetElement.focus();
+ if (this.deviceIsAndroid) {
+ return false;
+ }
+
+ if (!this.needsClick(forElement)) {
+ event.preventDefault();
+ this.sendClick(forElement, event);
+ }
+
+ return false;
+ }
+ } else if (this.needsFocus(targetElement)) {
+
+ // If the touch started a while ago (best guess is 100ms based on tests for issue #36) then focus will be triggered anyway. Return early and unset the target element reference so that the subsequent click will be allowed through.
+ if ((event.timeStamp - trackingClickStart) > 100) {
+ this.targetElement = null;
+ return true;
+ }
+
+ targetElement.focus();
+
+ // Select elements need the event to go through on iOS 4, otherwise the selector menu won't open.
+ if (!this.deviceIsIOS4 || targetElement.tagName.toLowerCase() !== 'select') {
+ this.targetElement = null;
+ event.preventDefault();
+ }
+
+ return false;
+ }
+
+ // Prevent the actual click from going though - unless the target node is marked as requiring
+ // real clicks or if it is in the whitelist in which case only non-programmatic clicks are permitted.
+ if (!this.needsClick(targetElement)) {
+ event.preventDefault();
+ this.sendClick(targetElement, event);
+ }
+
+ return false;
+};
+
+
+/**
+ * On touch cancel, stop tracking the click.
+ *
+ * @returns {void}
+ */
+FastClick.prototype.onTouchCancel = function() {
+ 'use strict';
+ this.theTarget.className = this.theTarget.className.replace(/ ?pressed/gi, '');
+ this.trackingClick = false;
+ this.targetElement = null;
+};
+
+
+/**
+ * On actual clicks, determine whether this is a touch-generated click, a click action occurring
+ * naturally after a delay after a touch (which needs to be cancelled to avoid duplication), or
+ * an actual click which should be permitted.
+ *
+ * @param {Event} event
+ * @returns {boolean}
+ */
+FastClick.prototype.onClick = function(event) {
+ 'use strict';
+
+ var oldTargetElement;
+
+ // If a target element was never set (because a touch event was never fired) allow the click
+ if (!this.targetElement) {
+ return true;
+ }
+
+ if (event.forwardedTouchEvent) {
+ return true;
+ }
+
+ oldTargetElement = this.targetElement;
+ this.targetElement = null;
+
+ // It's possible for another FastClick-like library delivered with third-party code to fire a click event before FastClick does (issue #44). In that case, set the click-tracking flag back to false and return early. This will cause onTouchEnd to return early.
+ if (this.trackingClick) {
+ this.trackingClick = false;
+ return true;
+ }
+
+ // Programmatically generated events targeting a specific element should be permitted
+ if (!event.cancelable) {
+ return true;
+ }
+
+ // Very odd behaviour on iOS (issue #18): if a submit element is present inside a form and the user hits enter in the iOS simulator or clicks the Go button on the pop-up OS keyboard the a kind of 'fake' click event will be triggered with the submit-type input element as the target.
+ if (event.target.type === 'submit' && event.detail === 0) {
+ return true;
+ }
+
+ // Derive and check the target element to see whether the click needs to be permitted;
+ // unless explicitly enabled, prevent non-touch click events from triggering actions,
+ // to prevent ghost/doubleclicks.
+ if (!this.needsClick(oldTargetElement) || this.cancelNextClick) {
+ this.cancelNextClick = false;
+
+ // Prevent any user-added listeners declared on FastClick element from being fired.
+ if (event.stopImmediatePropagation) {
+ event.stopImmediatePropagation();
+ } else {
+
+ // Part of the hack for browsers that don't support Event#stopImmediatePropagation (e.g. Android 2)
+ event.propagationStopped = true;
+ }
+
+ // Cancel the event
+ event.stopPropagation();
+ event.preventDefault();
+
+ return false;
+ }
+
+ // If clicks are permitted, return true for the action to go through.
+ return true;
+};
+
+
+/**
+ * Remove all FastClick's event listeners.
+ *
+ * @returns {void}
+ */
+FastClick.prototype.destroy = function() {
+ 'use strict';
+ var layer = this.layer;
+
+ layer.removeEventListener('click', this.onClick, true);
+ layer.removeEventListener('touchstart', this.onTouchStart, false);
+ layer.removeEventListener('touchmove', this.onTouchMove, false);
+ layer.removeEventListener('touchend', this.onTouchEnd, false);
+ layer.removeEventListener('touchcancel', this.onTouchCancel, false);
+};
+
+
+if (typeof define !== 'undefined' && define.amd) {
+
+ // AMD. Register as an anonymous module.
+ define(function() {
+ 'use strict';
+ return FastClick;
+ });
+}
+
+if (typeof module !== 'undefined' && module.exports) {
+ module.exports = function(layer) {
+ 'use strict';
+ return new FastClick(layer);
+ };
+
+ module.exports.FastClick = FastClick;
+}
+
+
+function attachFastClick(options) {
+ options = options || {};
var avatars = document.getElementsByClassName("NB-show-profile");
Array.prototype.slice.call(avatars, 0).forEach(function(avatar) {
- new NoClickDelay(avatar);
+ new FastClick(avatar, options);
});
var tags = document.getElementsByClassName("NB-story-tag");
Array.prototype.slice.call(tags, 0).forEach(function(tag) {
- new NoClickDelay(tag);
+ new FastClick(tag, options);
});
var authors = document.getElementsByClassName("NB-story-author");
Array.prototype.slice.call(authors, 0).forEach(function(author) {
- new NoClickDelay(author);
+ new FastClick(author, options);
});
var publishers = document.getElementsByClassName("NB-story-publisher");
Array.prototype.slice.call(publishers, 0).forEach(function(publisher) {
- new NoClickDelay(publisher);
+ new FastClick(publisher, options);
});
var titles = document.getElementsByClassName("NB-story-title");
Array.prototype.slice.call(titles, 0).forEach(function(title) {
- new NoClickDelay(title);
+ new FastClick(title, options);
});
var author = document.getElementById("NB-story-author");
if (author) {
- new NoClickDelay(author);
+ new FastClick(author, options);
}
}
diff --git a/media/ios/static/storyDetailView.css b/media/ios/static/storyDetailView.css
index 04dbf2830..34c5206cc 100644
--- a/media/ios/static/storyDetailView.css
+++ b/media/ios/static/storyDetailView.css
@@ -313,6 +313,8 @@ div + p {
text-shadow: 1px 1px 0 #EFEFEF;
overflow: hidden;
max-width: none;
+
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
.NB-iphone .NB-header {
diff --git a/media/ios/static/trainer.css b/media/ios/static/trainer.css
index 6229b6ce7..da9ed90ff 100644
--- a/media/ios/static/trainer.css
+++ b/media/ios/static/trainer.css
@@ -53,9 +53,10 @@
}
.NB-trainer-title .NB-title-trainer {
- padding: 12px 0;
+ padding: 6px 0 12px;
font-size: 18px;
- line-height: 22px;
+ font-weight: bold;
+ line-height: 24px;
-webkit-user-select: auto;
-webkit-touch-callout: default;
-webkit-highlight: auto;
@@ -67,7 +68,7 @@
color: #C0C0C0;
font-weight: bold;
font-size: 12px;
- padding: 12px 0;
+ padding: 2px 0 4px;
}
.NB-story-title-positive {
@@ -152,10 +153,14 @@
top: 2px;
opacity: .2;
}
-.pressed .NB-classifier .NB-classifier-icon-like,
-.pressed .NB-classifier.NB-classifier-dislike .NB-classifier-icon-like {
+.NB-classifier.NB-classifier-like .NB-classifier-icon-like {
opacity: 1;
}
+.pressed .NB-classifier .NB-classifier-icon-like,
+.pressed .NB-classifier.NB-classifier-dislike .NB-classifier-icon-like,
+.pressed .NB-classifier.NB-classifier-like .NB-classifier-icon-dislike {
+ opacity: .8;
+}
.pressed .NB-classifier.NB-classifier-like .NB-classifier-icon-like {
opacity: 0;
}
diff --git a/media/ios/static/trainer.js b/media/ios/static/trainer.js
index c36cfdd98..ea4fcf796 100644
--- a/media/ios/static/trainer.js
+++ b/media/ios/static/trainer.js
@@ -14,5 +14,7 @@ Trainer.prototype = {
Zepto(function($) {
new Trainer();
- attachFastClick();
+ attachFastClick({
+ skipEvent: true
+ });
});