mirror of
https://github.com/viq/NewsBlur.git
synced 2025-09-18 21:43:31 +00:00
Merge branch 'master' into offline
# By Samuel Clay (29) and ojiikun (3) # Via Samuel Clay (3) and ojiikun (1) * master: (32 commits) New user queue every 5 minutes. Fixing unknown host error on DO servers. Using All Site Stories instead of Top Level on folder chooser. Adding new preference for auto-opening a default folder on load. Unread counter overlay widget for feeds and social feeds. Fixing memory leak when reloading feeds (after add/remove/move), found entirely through @mihaip's hard work. Thanks! Disallowing single subscriber feeds to be loaded by non-subscribers. Fixing #383. Need a way to retrieve starred stories by hash. Previous story button takes you up, no longer to previous story. Should've done this a while ago. Adding story intelligence score tip to API. Adding user_profile to /reader/feeds. Has is_premium field, as well as web preferences. Switching feed link to point ot the feed's permalink, not a feed filter on the blurblog. Preventing popular from sharing stories from the same feed id in the same batch. Upping delay on new user queue. Fixing jenny holzer quote at end of blurblogs. Adding better error logging/messaging for marking stories as starred. Adding better error logging/messaging for marking stories as starred. Fixing broken icon bug. Fixing missing user_id in comment's replies and likes. Posting popular shared stories to twitter. ...
This commit is contained in:
commit
ceb63caa52
40 changed files with 342 additions and 131 deletions
|
@ -270,9 +270,9 @@ class Profile(models.Model):
|
|||
txn_type='subscr_payment')[0]
|
||||
refund = paypal.refund_transaction(transaction.txn_id)
|
||||
try:
|
||||
refunded = int(float(refund['raw']['TOTALREFUNDEDAMOUNT'][0]))
|
||||
refunded = int(float(refund.raw['TOTALREFUNDEDAMOUNT'][0]))
|
||||
except KeyError:
|
||||
refunded = int(transaction.amount)
|
||||
refunded = int(transaction.payment_gross)
|
||||
logging.user(self.user, "~FRRefunding paypal payment: $%s" % refunded)
|
||||
self.cancel_premium()
|
||||
|
||||
|
|
|
@ -280,6 +280,7 @@ def load_feeds(request):
|
|||
'social_feeds': social_feeds,
|
||||
'social_profile': social_profile,
|
||||
'social_services': social_services,
|
||||
'user_profile': user.profile,
|
||||
'folders': json.decode(folders.folders),
|
||||
'starred_count': starred_count,
|
||||
'categories': categories
|
||||
|
@ -666,6 +667,8 @@ def load_single_feed(request, feed_id):
|
|||
if dupe_feed_id: data['dupe_feed_id'] = dupe_feed_id
|
||||
if not usersub:
|
||||
data.update(feed.canonical())
|
||||
if not usersub and feed.num_subscribers <= 1:
|
||||
data = dict(code=-1, message="You must be subscribed to this feed.")
|
||||
|
||||
# if page <= 1:
|
||||
# import random
|
||||
|
@ -732,6 +735,7 @@ def load_starred_stories(request):
|
|||
limit = int(request.REQUEST.get('limit', 10))
|
||||
page = int(request.REQUEST.get('page', 0))
|
||||
query = request.REQUEST.get('query')
|
||||
story_hashes = request.REQUEST.getlist('h')[:100]
|
||||
now = localtime_for_timezone(datetime.datetime.now(), user.profile.timezone)
|
||||
message = None
|
||||
if page: offset = limit * (page - 1)
|
||||
|
@ -744,6 +748,12 @@ def load_starred_stories(request):
|
|||
else:
|
||||
stories = []
|
||||
message = "You must be a premium subscriber to search."
|
||||
elif story_hashes:
|
||||
mstories = MStarredStory.objects(
|
||||
user_id=user.pk,
|
||||
story_hash__in=story_hashes
|
||||
).order_by('-starred_date')[offset:offset+limit]
|
||||
stories = Feed.format_stories(mstories)
|
||||
else:
|
||||
mstories = MStarredStory.objects(
|
||||
user_id=user.pk
|
||||
|
@ -1615,13 +1625,14 @@ def login_as(request):
|
|||
def iframe_buster(request):
|
||||
logging.user(request, "~FB~SBiFrame bust!")
|
||||
return HttpResponse(status=204)
|
||||
|
||||
|
||||
@required_params('story_id', feed_id=int)
|
||||
@ajax_login_required
|
||||
@json.json_view
|
||||
def mark_story_as_starred(request):
|
||||
code = 1
|
||||
feed_id = int(request.POST['feed_id'])
|
||||
story_id = request.POST['story_id']
|
||||
feed_id = int(request.REQUEST['feed_id'])
|
||||
story_id = request.REQUEST['story_id']
|
||||
message = ""
|
||||
story, _ = MStory.find_story(story_feed_id=feed_id, story_id=story_id)
|
||||
if not story:
|
||||
|
@ -1651,6 +1662,7 @@ def mark_story_as_starred(request):
|
|||
|
||||
return {'code': code, 'message': message}
|
||||
|
||||
@required_params('story_id')
|
||||
@ajax_login_required
|
||||
@json.json_view
|
||||
def mark_story_as_unstarred(request):
|
||||
|
|
|
@ -139,7 +139,10 @@ class IconImporter(object):
|
|||
return
|
||||
else:
|
||||
# Load XOR bitmap
|
||||
image = BmpImagePlugin.DibImageFile(image_file)
|
||||
try:
|
||||
image = BmpImagePlugin.DibImageFile(image_file)
|
||||
except IOError:
|
||||
return
|
||||
if image.mode == 'RGBA':
|
||||
# Windows XP 32-bit color depth icon without AND bitmap
|
||||
pass
|
||||
|
|
|
@ -1432,12 +1432,12 @@ class MSharedStory(mongo.Document):
|
|||
if not days:
|
||||
days = 3
|
||||
if not cutoff:
|
||||
cutoff = 7
|
||||
cutoff = 6
|
||||
if not shared_feed_ids:
|
||||
shared_feed_ids = []
|
||||
# shared_stories_count = sum(json.decode(MStatistics.get('stories_shared')))
|
||||
# cutoff = cutoff or max(math.floor(.025 * shared_stories_count), 3)
|
||||
today = datetime.datetime.now() - datetime.timedelta(days=days)
|
||||
if not shared_feed_ids:
|
||||
shared_feed_ids = []
|
||||
|
||||
map_f = """
|
||||
function() {
|
||||
|
@ -1494,6 +1494,9 @@ class MSharedStory(mongo.Document):
|
|||
if not story:
|
||||
logging.user(popular_user, "~FRPopular stories, story not found: %s" % story_info)
|
||||
continue
|
||||
if story.story_feed_id in shared_feed_ids:
|
||||
logging.user(popular_user, "~FRPopular stories, story feed just shared: %s" % story_info)
|
||||
continue
|
||||
|
||||
if interactive:
|
||||
feed = Feed.get_by_id(story.story_feed_id)
|
||||
|
@ -1516,13 +1519,15 @@ class MSharedStory(mongo.Document):
|
|||
}
|
||||
shared_story, created = MSharedStory.objects.get_or_create(**story_values)
|
||||
if created:
|
||||
shared_story.post_to_service('twitter')
|
||||
shared += 1
|
||||
shared_feed_ids.append(story.story_feed_id)
|
||||
publish_new_stories = True
|
||||
logging.user(popular_user, "~FCSharing: ~SB~FM%s (%s shares, %s min)" % (
|
||||
story.story_title[:50],
|
||||
story_info['count'],
|
||||
cutoff))
|
||||
|
||||
|
||||
if publish_new_stories:
|
||||
socialsubs = MSocialSubscription.objects.filter(subscription_user_id=popular_user.pk)
|
||||
for socialsub in socialsubs:
|
||||
|
@ -1745,9 +1750,11 @@ class MSharedStory(mongo.Document):
|
|||
comment['source_user'] = profiles[comment['source_user_id']]
|
||||
|
||||
for r, reply in enumerate(comment['replies']):
|
||||
if reply['user_id'] not in profiles: continue
|
||||
comment['replies'][r]['user'] = profiles[reply['user_id']]
|
||||
comment['liking_user_ids'] = list(comment['liking_users'])
|
||||
for u, user_id in enumerate(comment['liking_users']):
|
||||
if user_id not in profiles: continue
|
||||
comment['liking_users'][u] = profiles[user_id]
|
||||
|
||||
return comment
|
||||
|
|
|
@ -67,9 +67,7 @@ class SharePopularStories(Task):
|
|||
|
||||
def run(self, **kwargs):
|
||||
logging.debug(" ---> Sharing popular stories...")
|
||||
shared = MSharedStory.share_popular_stories(interactive=False)
|
||||
if not shared:
|
||||
shared = MSharedStory.share_popular_stories(interactive=False, days=2)
|
||||
MSharedStory.share_popular_stories(interactive=False)
|
||||
|
||||
|
||||
class UpdateRecalcForSubscription(Task):
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
android:background="@drawable/selector_overlay_bg_left"
|
||||
android:textSize="14sp"
|
||||
android:padding="6dp"
|
||||
android:layout_marginRight="1dp"
|
||||
android:onClick="overlayLeft" />
|
||||
|
||||
<Button
|
||||
|
@ -45,4 +46,22 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/reading_overlay_count"
|
||||
android:text=""
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="100dp"
|
||||
android:layout_marginBottom="18dp"
|
||||
android:background="@drawable/neutral_count_rect"
|
||||
android:paddingLeft="3dp"
|
||||
android:paddingRight="3dp"
|
||||
android:shadowDy="1"
|
||||
android:shadowRadius="1"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentRight="true" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
|
|
@ -37,13 +37,6 @@ public class AllSharedStoriesReading extends Reading {
|
|||
addStoryToMarkAsRead(readingAdapter.getStory(passedPosition));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
super.onPageSelected(position);
|
||||
addStoryToMarkAsRead(readingAdapter.getStory(position));
|
||||
checkStoryCount(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void triggerRefresh() {
|
||||
triggerRefresh(1);
|
||||
|
|
|
@ -38,13 +38,6 @@ public class AllStoriesReading extends Reading {
|
|||
addStoryToMarkAsRead(readingAdapter.getStory(passedPosition));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
super.onPageSelected(position);
|
||||
addStoryToMarkAsRead(readingAdapter.getStory(position));
|
||||
checkStoryCount(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void triggerRefresh() {
|
||||
triggerRefresh(1);
|
||||
|
|
|
@ -12,6 +12,7 @@ import com.newsblur.domain.Classifier;
|
|||
import com.newsblur.domain.Feed;
|
||||
import com.newsblur.fragment.SyncUpdateFragment;
|
||||
import com.newsblur.service.SyncService;
|
||||
import com.newsblur.util.FeedUtils;
|
||||
import com.newsblur.util.PrefsUtils;
|
||||
import com.newsblur.util.StoryOrder;
|
||||
|
||||
|
@ -45,6 +46,8 @@ public class FeedReading extends Reading {
|
|||
feed = Feed.fromCursor(feedCursor);
|
||||
setTitle(feed.title);
|
||||
|
||||
this.unreadCount = FeedUtils.getFeedUnreadCount(this.feed, this.currentState);
|
||||
|
||||
readingAdapter = new FeedReadingAdapter(getSupportFragmentManager(), feed, stories, classifier);
|
||||
|
||||
setupPager();
|
||||
|
@ -58,15 +61,6 @@ public class FeedReading extends Reading {
|
|||
addStoryToMarkAsRead(readingAdapter.getStory(passedPosition));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
super.onPageSelected(position);
|
||||
if (readingAdapter.getStory(position) != null) {
|
||||
addStoryToMarkAsRead(readingAdapter.getStory(position));
|
||||
checkStoryCount(position);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAfterSync() {
|
||||
requestedPage = false;
|
||||
|
@ -112,5 +106,4 @@ public class FeedReading extends Reading {
|
|||
@Override
|
||||
public void closeAfterUpdate() { }
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -37,13 +37,6 @@ public class FolderReading extends Reading {
|
|||
addStoryToMarkAsRead(readingAdapter.getStory(passedPosition));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
addStoryToMarkAsRead(readingAdapter.getStory(position));
|
||||
checkStoryCount(position);
|
||||
super.onPageSelected(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void triggerRefresh() {
|
||||
triggerRefresh(1);
|
||||
|
|
|
@ -17,6 +17,7 @@ import android.view.View;
|
|||
import android.widget.Button;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.SeekBar.OnSeekBarChangeListener;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.actionbarsherlock.view.Menu;
|
||||
import com.actionbarsherlock.view.MenuInflater;
|
||||
|
@ -37,6 +38,7 @@ import com.newsblur.util.PrefsUtils;
|
|||
import com.newsblur.util.ReadFilter;
|
||||
import com.newsblur.util.StoryOrder;
|
||||
import com.newsblur.util.UIUtils;
|
||||
import com.newsblur.util.ViewUtils;
|
||||
import com.newsblur.view.NonfocusScrollview.ScrollChangeListener;
|
||||
|
||||
public abstract class Reading extends NbFragmentActivity implements OnPageChangeListener, SyncUpdateFragment.SyncUpdateFragmentInterface, OnSeekBarChangeListener, ScrollChangeListener {
|
||||
|
@ -57,15 +59,24 @@ public abstract class Reading extends NbFragmentActivity implements OnPageChange
|
|||
|
||||
protected ViewPager pager;
|
||||
protected Button overlayLeft, overlayRight;
|
||||
protected TextView overlayCount;
|
||||
protected FragmentManager fragmentManager;
|
||||
protected ReadingAdapter readingAdapter;
|
||||
protected ContentResolver contentResolver;
|
||||
private APIManager apiManager;
|
||||
protected SyncUpdateFragment syncFragment;
|
||||
protected Cursor stories;
|
||||
|
||||
private Set<Story> storiesToMarkAsRead;
|
||||
|
||||
// subclasses may set this to a nonzero value to enable the unread count overlay
|
||||
protected int unreadCount = 0;
|
||||
|
||||
// keep a local cache of stories we have viewed within this activity cycle. We need
|
||||
// this to track unread counts since it would be too costly to query and update the DB
|
||||
// on every page change.
|
||||
private Set<Story> storiesAlreadySeen;
|
||||
|
||||
|
||||
private float overlayRangeTopPx;
|
||||
private float overlayRangeBotPx;
|
||||
|
||||
|
@ -78,10 +89,12 @@ public abstract class Reading extends NbFragmentActivity implements OnPageChange
|
|||
setContentView(R.layout.activity_reading);
|
||||
this.overlayLeft = (Button) findViewById(R.id.reading_overlay_left);
|
||||
this.overlayRight = (Button) findViewById(R.id.reading_overlay_right);
|
||||
this.overlayCount = (TextView) findViewById(R.id.reading_overlay_count);
|
||||
|
||||
fragmentManager = getSupportFragmentManager();
|
||||
|
||||
storiesToMarkAsRead = new HashSet<Story>();
|
||||
storiesAlreadySeen = new HashSet<Story>();
|
||||
|
||||
passedPosition = getIntent().getIntExtra(EXTRA_POSITION, 0);
|
||||
currentState = getIntent().getIntExtra(ItemsList.EXTRA_STATE, 0);
|
||||
|
@ -94,6 +107,11 @@ public abstract class Reading extends NbFragmentActivity implements OnPageChange
|
|||
this.overlayRangeTopPx = (float) UIUtils.convertDPsToPixels(this, OVERLAY_RANGE_TOP_DP);
|
||||
this.overlayRangeBotPx = (float) UIUtils.convertDPsToPixels(this, OVERLAY_RANGE_BOT_DP);
|
||||
|
||||
// the unread count overlay defaults to neutral colour. set it to positive if we are in focus mode
|
||||
if (this.currentState == AppConstants.STATE_BEST) {
|
||||
ViewUtils.setViewBackground(this.overlayCount, R.drawable.positive_count_rect);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected void setupPager() {
|
||||
|
@ -182,8 +200,12 @@ public abstract class Reading extends NbFragmentActivity implements OnPageChange
|
|||
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
this.setOverlayAlpha(1.0f);
|
||||
this.enableOverlays();
|
||||
|
||||
if (readingAdapter.getStory(position) != null) {
|
||||
addStoryToMarkAsRead(readingAdapter.getStory(position));
|
||||
checkStoryCount(position);
|
||||
}
|
||||
}
|
||||
|
||||
// interface ScrollChangeListener
|
||||
|
@ -194,7 +216,11 @@ public abstract class Reading extends NbFragmentActivity implements OnPageChange
|
|||
int posFromBot = (scrollMax - vPos);
|
||||
|
||||
float newAlpha = 0.0f;
|
||||
if (vPos < this.overlayRangeTopPx) {
|
||||
if ((vPos < this.overlayRangeTopPx) && (posFromBot < this.overlayRangeBotPx)) {
|
||||
// if we have a super-tiny scroll window such that we never leave either top or bottom,
|
||||
// just leave us at full alpha.
|
||||
newAlpha = 1.0f;
|
||||
} else if (vPos < this.overlayRangeTopPx) {
|
||||
float delta = this.overlayRangeTopPx - ((float) vPos);
|
||||
newAlpha = delta / this.overlayRangeTopPx;
|
||||
} else if (posFromBot < this.overlayRangeBotPx) {
|
||||
|
@ -208,13 +234,26 @@ public abstract class Reading extends NbFragmentActivity implements OnPageChange
|
|||
private void setOverlayAlpha(float a) {
|
||||
UIUtils.setViewAlpha(this.overlayLeft, a);
|
||||
UIUtils.setViewAlpha(this.overlayRight, a);
|
||||
|
||||
if (this.unreadCount > 0) {
|
||||
UIUtils.setViewAlpha(this.overlayCount, a);
|
||||
} else {
|
||||
UIUtils.setViewAlpha(this.overlayCount, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check and correct the display status of the overlays. Call this any time
|
||||
* an event happens that might change out list position.
|
||||
*/
|
||||
private void enableOverlays() {
|
||||
int page = this.pager.getCurrentItem();
|
||||
this.overlayLeft.setEnabled(page > 0);
|
||||
this.overlayRight.setEnabled(page < (this.readingAdapter.getCount()-1));
|
||||
this.overlayRight.setText((page < (this.readingAdapter.getCount()-1)) ? R.string.overlay_next : R.string.overlay_done);
|
||||
|
||||
this.overlayCount.setText(Integer.toString(this.unreadCount));
|
||||
this.setOverlayAlpha(1.0f);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -264,6 +303,11 @@ public abstract class Reading extends NbFragmentActivity implements OnPageChange
|
|||
if (this.storiesToMarkAsRead.size() >= AppConstants.MAX_MARK_READ_BATCH) {
|
||||
flushStoriesMarkedRead();
|
||||
}
|
||||
if (this.storiesAlreadySeen.add(story)) {
|
||||
// only decrement the cached story count if the story wasn't already read
|
||||
this.unreadCount--;
|
||||
}
|
||||
this.enableOverlays();
|
||||
}
|
||||
|
||||
private void flushStoriesMarkedRead() {
|
||||
|
@ -284,6 +328,10 @@ public abstract class Reading extends NbFragmentActivity implements OnPageChange
|
|||
// operation, or it was read long before now.
|
||||
FeedUtils.markStoryUnread(story, Reading.this, this.apiManager);
|
||||
|
||||
this.unreadCount++;
|
||||
this.storiesAlreadySeen.remove(story);
|
||||
|
||||
this.enableOverlays();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -29,12 +29,6 @@ public class SavedStoriesReading extends Reading {
|
|||
setupPager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
super.onPageSelected(position);
|
||||
checkStoryCount(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void triggerRefresh() {
|
||||
triggerRefresh(1);
|
||||
|
|
|
@ -14,6 +14,7 @@ import com.newsblur.database.MixedFeedsReadingAdapter;
|
|||
import com.newsblur.domain.SocialFeed;
|
||||
import com.newsblur.domain.Story;
|
||||
import com.newsblur.service.SyncService;
|
||||
import com.newsblur.util.FeedUtils;
|
||||
|
||||
public class SocialFeedReading extends Reading {
|
||||
|
||||
|
@ -40,6 +41,8 @@ public class SocialFeedReading extends Reading {
|
|||
stories = contentResolver.query(storiesURI, null, DatabaseConstants.getStorySelectionFromState(currentState), null, null);
|
||||
setTitle(getIntent().getStringExtra(EXTRA_USERNAME));
|
||||
|
||||
this.unreadCount = FeedUtils.getFeedUnreadCount(this.socialFeed, this.currentState);
|
||||
|
||||
readingAdapter = new MixedFeedsReadingAdapter(getSupportFragmentManager(), getContentResolver(), stories);
|
||||
|
||||
setupPager();
|
||||
|
@ -47,13 +50,6 @@ public class SocialFeedReading extends Reading {
|
|||
addStoryToMarkAsRead(readingAdapter.getStory(passedPosition));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
super.onPageSelected(position);
|
||||
addStoryToMarkAsRead(readingAdapter.getStory(position));
|
||||
checkStoryCount(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void triggerRefresh() {
|
||||
triggerRefresh(0);
|
||||
|
|
|
@ -80,27 +80,32 @@ public class FeedItemListFragment extends StoryItemListFragment implements Loade
|
|||
Uri feedUri = FeedProvider.FEEDS_URI.buildUpon().appendPath(feedId).build();
|
||||
Cursor feedCursor = contentResolver.query(feedUri, null, null, null, null);
|
||||
|
||||
if (feedCursor.getCount() > 0) {
|
||||
feedCursor.moveToFirst();
|
||||
Feed feed = Feed.fromCursor(feedCursor);
|
||||
|
||||
String[] groupFrom = new String[] { DatabaseConstants.STORY_TITLE, DatabaseConstants.STORY_AUTHORS, DatabaseConstants.STORY_READ, DatabaseConstants.STORY_SHORTDATE, DatabaseConstants.STORY_INTELLIGENCE_AUTHORS };
|
||||
int[] groupTo = new int[] { R.id.row_item_title, R.id.row_item_author, R.id.row_item_title, R.id.row_item_date, R.id.row_item_sidebar };
|
||||
|
||||
// create the adapter before starting the loader, since the callback updates the adapter
|
||||
adapter = new FeedItemsAdapter(getActivity(), feed, R.layout.row_item, storiesCursor, groupFrom, groupTo, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
|
||||
|
||||
getLoaderManager().initLoader(ITEMLIST_LOADER , null, this);
|
||||
|
||||
itemList.setOnScrollListener(this);
|
||||
|
||||
adapter.setViewBinder(new FeedItemViewBinder(getActivity()));
|
||||
itemList.setAdapter(adapter);
|
||||
itemList.setOnItemClickListener(this);
|
||||
itemList.setOnCreateContextMenuListener(this);
|
||||
} else {
|
||||
Log.w(this.getClass().getName(), "Feed not found in DB, can't load.");
|
||||
if (feedCursor.getCount() < 1) {
|
||||
// This shouldn't happen, but crash reports indicate that it does (very rarely).
|
||||
// If we are told to create an item list for a feed, but then can't find that feed ID in the DB,
|
||||
// something is very wrong, and we won't be able to recover, so just force the user back to the
|
||||
// feed list until we have a better understanding of how to prevent this.
|
||||
Log.w(this.getClass().getName(), "Feed not found in DB, can't create item list.");
|
||||
getActivity().finish();
|
||||
}
|
||||
|
||||
feedCursor.moveToFirst();
|
||||
Feed feed = Feed.fromCursor(feedCursor);
|
||||
|
||||
String[] groupFrom = new String[] { DatabaseConstants.STORY_TITLE, DatabaseConstants.STORY_AUTHORS, DatabaseConstants.STORY_READ, DatabaseConstants.STORY_SHORTDATE, DatabaseConstants.STORY_INTELLIGENCE_AUTHORS };
|
||||
int[] groupTo = new int[] { R.id.row_item_title, R.id.row_item_author, R.id.row_item_title, R.id.row_item_date, R.id.row_item_sidebar };
|
||||
|
||||
// create the adapter before starting the loader, since the callback updates the adapter
|
||||
adapter = new FeedItemsAdapter(getActivity(), feed, R.layout.row_item, storiesCursor, groupFrom, groupTo, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
|
||||
|
||||
getLoaderManager().initLoader(ITEMLIST_LOADER , null, this);
|
||||
|
||||
itemList.setOnScrollListener(this);
|
||||
|
||||
adapter.setViewBinder(new FeedItemViewBinder(getActivity()));
|
||||
itemList.setAdapter(adapter);
|
||||
itemList.setOnItemClickListener(this);
|
||||
itemList.setOnCreateContextMenuListener(this);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
|
|
@ -241,9 +241,9 @@ public class FolderListFragment extends Fragment implements OnGroupClickListener
|
|||
resolver.update(FeedProvider.FEEDS_URI.buildUpon().appendPath(feedId).build(), values, null, null);
|
||||
}
|
||||
folderAdapter.notifyDataSetChanged();
|
||||
Toast.makeText(getActivity(), R.string.toast_marked_all_stories_as_read, Toast.LENGTH_SHORT).show();
|
||||
UIUtils.safeToast(getActivity(), R.string.toast_marked_all_stories_as_read, Toast.LENGTH_SHORT);
|
||||
} else {
|
||||
Toast.makeText(getActivity(), R.string.toast_error_marking_feed_as_read, Toast.LENGTH_SHORT).show();
|
||||
UIUtils.safeToast(getActivity(), R.string.toast_error_marking_feed_as_read, Toast.LENGTH_SHORT);
|
||||
}
|
||||
};
|
||||
}.execute();
|
||||
|
|
|
@ -26,6 +26,7 @@ import com.newsblur.domain.Story;
|
|||
import com.newsblur.domain.UserDetails;
|
||||
import com.newsblur.network.APIManager;
|
||||
import com.newsblur.util.PrefsUtils;
|
||||
import com.newsblur.util.UIUtils;
|
||||
|
||||
public class ShareDialogFragment extends DialogFragment {
|
||||
|
||||
|
@ -132,10 +133,10 @@ public class ShareDialogFragment extends DialogFragment {
|
|||
protected void onPostExecute(Boolean result) {
|
||||
if (result) {
|
||||
hasShared = true;
|
||||
Toast.makeText(getActivity(), R.string.shared, Toast.LENGTH_LONG).show();
|
||||
UIUtils.safeToast(getActivity(), R.string.shared, Toast.LENGTH_LONG);
|
||||
callback.sharedCallback(shareComment, hasBeenShared);
|
||||
} else {
|
||||
Toast.makeText(getActivity(), R.string.error_sharing, Toast.LENGTH_LONG).show();
|
||||
UIUtils.safeToast(getActivity(), R.string.error_sharing, Toast.LENGTH_LONG);
|
||||
}
|
||||
v.setEnabled(true);
|
||||
ShareDialogFragment.this.dismiss();
|
||||
|
|
|
@ -5,7 +5,7 @@ public class AppConstants {
|
|||
// Enables high-volume logging that may be useful for debugging. This should
|
||||
// never be enabled for releases, as it not only slows down the app considerably,
|
||||
// it will log sensitive info such as passwords!
|
||||
public static final boolean VERBOSE_LOG = false;
|
||||
public static final boolean VERBOSE_LOG = true;
|
||||
|
||||
public static final int STATE_ALL = 0;
|
||||
public static final int STATE_SOME = 1;
|
||||
|
@ -27,7 +27,8 @@ public class AppConstants {
|
|||
public static final String LAST_APP_VERSION = "LAST_APP_VERSION";
|
||||
|
||||
// the max number of mark-as-read ops to batch up before flushing to the server
|
||||
public static final int MAX_MARK_READ_BATCH = 5;
|
||||
// set to 1 to effectively disable batching
|
||||
public static final int MAX_MARK_READ_BATCH = 1;
|
||||
|
||||
// a pref for the time we completed the last full sync of the feed/fodler list
|
||||
public static final String LAST_SYNC_TIME = "LAST_SYNC_TIME";
|
||||
|
|
|
@ -23,11 +23,14 @@ import com.newsblur.R;
|
|||
import com.newsblur.database.DatabaseConstants;
|
||||
import com.newsblur.database.FeedProvider;
|
||||
import com.newsblur.domain.Classifier;
|
||||
import com.newsblur.domain.Feed;
|
||||
import com.newsblur.domain.SocialFeed;
|
||||
import com.newsblur.domain.Story;
|
||||
import com.newsblur.domain.ValueMultimap;
|
||||
import com.newsblur.network.APIManager;
|
||||
import com.newsblur.network.domain.NewsBlurResponse;
|
||||
import com.newsblur.service.SyncService;
|
||||
import com.newsblur.util.AppConstants;
|
||||
|
||||
public class FeedUtils {
|
||||
|
||||
|
@ -202,4 +205,34 @@ public class FeedUtils {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the unread story count for a feed, filtered by view state.
|
||||
*/
|
||||
public static int getFeedUnreadCount(Feed feed, int currentState) {
|
||||
if (feed == null ) return 0;
|
||||
int count = 0;
|
||||
count += feed.positiveCount;
|
||||
if ((currentState == AppConstants.STATE_ALL) || (currentState == AppConstants.STATE_SOME)) {
|
||||
count += feed.neutralCount;
|
||||
}
|
||||
if (currentState == AppConstants.STATE_ALL ) {
|
||||
count += feed.negativeCount;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public static int getFeedUnreadCount(SocialFeed feed, int currentState) {
|
||||
if (feed == null ) return 0;
|
||||
int count = 0;
|
||||
count += feed.positiveCount;
|
||||
if ((currentState == AppConstants.STATE_ALL) || (currentState == AppConstants.STATE_SOME)) {
|
||||
count += feed.neutralCount;
|
||||
}
|
||||
if (currentState == AppConstants.STATE_ALL ) {
|
||||
count += feed.negativeCount;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -45,7 +45,13 @@ public class ImageLoader {
|
|||
Bitmap bitmap = memoryCache.get(url);
|
||||
if (bitmap == null) {
|
||||
File f = fileCache.getFile(url);
|
||||
bitmap = BitmapFactory.decodeFile(f.getAbsolutePath());
|
||||
try {
|
||||
bitmap = BitmapFactory.decodeFile(f.getAbsolutePath());
|
||||
} catch (Exception e) {
|
||||
Log.e(this.getClass().getName(), "error decoding image, using default.", e);
|
||||
// this can rarely happen if the device is low on memory and is not recoverable.
|
||||
// just leave bitmap null and the default placeholder image will be used
|
||||
}
|
||||
}
|
||||
if (bitmap != null) {
|
||||
if (doRound) {
|
||||
|
|
|
@ -11,6 +11,7 @@ import android.graphics.PorterDuffXfermode;
|
|||
import android.graphics.RectF;
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class UIUtils {
|
||||
|
||||
|
@ -88,4 +89,16 @@ public class UIUtils {
|
|||
v.setAlpha(alpha);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a toast in a circumstance where the context might be null. This can very
|
||||
* rarely happen when toasts are done from async tasks and the context is finished
|
||||
* before the task completes, resulting in a crash. This prevents the crash at the
|
||||
* cost of the toast not being shown.
|
||||
*/
|
||||
public static void safeToast(Context c, int rid, int duration) {
|
||||
if (c != null) {
|
||||
Toast.makeText(c, rid, duration).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -84,27 +84,19 @@ public class ViewUtils {
|
|||
|
||||
TextView tagText = (TextView) v.findViewById(R.id.tag_text);
|
||||
|
||||
// due to a framework bug, the below modification of background resource also resets the declared
|
||||
// padding on the view. save a copy of said padding so it can be re-applied after the change.
|
||||
int oldPadL = v.getPaddingLeft();
|
||||
int oldPadT = v.getPaddingTop();
|
||||
int oldPadR = v.getPaddingRight();
|
||||
int oldPadB = v.getPaddingBottom();
|
||||
|
||||
tagText.setText(tag);
|
||||
|
||||
if (classifier != null && classifier.tags.containsKey(tag)) {
|
||||
switch (classifier.tags.get(tag)) {
|
||||
case Classifier.LIKE:
|
||||
tagText.setBackgroundResource(R.drawable.tag_background_positive);
|
||||
setViewBackground(tagText, R.drawable.tag_background_positive);
|
||||
tagText.setTextColor(tag_green_text);
|
||||
break;
|
||||
case Classifier.DISLIKE:
|
||||
tagText.setBackgroundResource(R.drawable.tag_background_negative);
|
||||
setViewBackground(tagText, R.drawable.tag_background_negative);
|
||||
tagText.setTextColor(tag_red_text);
|
||||
break;
|
||||
}
|
||||
v.setPadding(oldPadL, oldPadT, oldPadR, oldPadB);
|
||||
}
|
||||
|
||||
v.setOnClickListener(new OnClickListener() {
|
||||
|
@ -118,4 +110,21 @@ public class ViewUtils {
|
|||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the background resource of a view, working around a platform bug that causes the declared
|
||||
* padding to get reset.
|
||||
*/
|
||||
public static void setViewBackground(View v, int resId) {
|
||||
// due to a framework bug, the below modification of background resource also resets the declared
|
||||
// padding on the view. save a copy of said padding so it can be re-applied after the change.
|
||||
int oldPadL = v.getPaddingLeft();
|
||||
int oldPadT = v.getPaddingTop();
|
||||
int oldPadR = v.getPaddingRight();
|
||||
int oldPadB = v.getPaddingBottom();
|
||||
|
||||
v.setBackgroundResource(resId);
|
||||
|
||||
v.setPadding(oldPadL, oldPadT, oldPadR, oldPadB);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
8
fabfile.py
vendored
8
fabfile.py
vendored
|
@ -14,6 +14,7 @@ import os
|
|||
import time
|
||||
import sys
|
||||
import re
|
||||
|
||||
try:
|
||||
import dop.client
|
||||
except ImportError:
|
||||
|
@ -32,9 +33,9 @@ except ImportError:
|
|||
# = DEFAULTS =
|
||||
# ============
|
||||
|
||||
env.NEWSBLUR_PATH = "~/projects/newsblur"
|
||||
env.SECRETS_PATH = "~/projects/secrets-newsblur"
|
||||
env.VENDOR_PATH = "~/projects/code"
|
||||
env.NEWSBLUR_PATH = "/srv/newsblur"
|
||||
env.SECRETS_PATH = "/srv/secrets-newsblur"
|
||||
env.VENDOR_PATH = "/srv/code"
|
||||
env.user = 'sclay'
|
||||
env.key_filename = os.path.join(env.SECRETS_PATH, 'keys/newsblur.key')
|
||||
|
||||
|
@ -439,6 +440,7 @@ def setup_supervisor():
|
|||
@parallel
|
||||
def setup_hosts():
|
||||
put(os.path.join(env.SECRETS_PATH, 'configs/hosts'), '/etc/hosts', use_sudo=True)
|
||||
sudo('echo "\n\n127.0.0.1 `hostname`" >> /etc/hosts')
|
||||
|
||||
def config_pgbouncer():
|
||||
put('config/pgbouncer.conf', '/etc/pgbouncer/pgbouncer.ini', use_sudo=True)
|
||||
|
|
|
@ -1244,6 +1244,8 @@ blockquote {
|
|||
.NB-page-controls-end .NB-page-controls-text {
|
||||
height: auto;
|
||||
position: static;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.NB-page-controls-end:hover {
|
||||
background-color: #3B3E49;
|
||||
|
|
|
@ -1168,10 +1168,12 @@ blockquote {
|
|||
cursor: default;
|
||||
height: auto;
|
||||
margin-bottom: 0;
|
||||
|
||||
|
||||
.NB-page-controls-text {
|
||||
height: auto;
|
||||
position: static;
|
||||
color: rgba(255, 255, 255, .6);
|
||||
text-shadow: 0 1px 0 rgba(0, 0, 0, .1);
|
||||
}
|
||||
&:hover {
|
||||
background-color: #3B3E49;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
NEWSBLUR.Router = Backbone.Router.extend({
|
||||
|
||||
routes: {
|
||||
"": "index",
|
||||
"add/?": "add_site",
|
||||
"try/?": "try_site",
|
||||
"site/:site_id/:slug": "site",
|
||||
|
@ -15,11 +14,6 @@ NEWSBLUR.Router = Backbone.Router.extend({
|
|||
"user/*user": "user"
|
||||
},
|
||||
|
||||
index: function() {
|
||||
// NEWSBLUR.log(["index"]);
|
||||
NEWSBLUR.reader.show_splash_page();
|
||||
},
|
||||
|
||||
add_site: function() {
|
||||
NEWSBLUR.log(["add", window.location, $.getQueryString('url')]);
|
||||
NEWSBLUR.reader.open_add_feed_modal({url: $.getQueryString('url')});
|
||||
|
|
|
@ -129,10 +129,20 @@ NEWSBLUR.Collections.Folders = Backbone.Collection.extend({
|
|||
this.comparator = NEWSBLUR.Collections.Folders.comparator;
|
||||
this.bind('change:feed_selected', this.propagate_feed_selected);
|
||||
this.bind('change:counts', this.propagate_change_counts);
|
||||
this.bind('reset', this.reset_folder_views);
|
||||
},
|
||||
|
||||
model: NEWSBLUR.Models.FeedOrFolder,
|
||||
|
||||
reset_folder_views: function() {
|
||||
this.each(function(item) {
|
||||
if (item.is_feed()) {
|
||||
item.feed.views = [];
|
||||
item.feed.folders = [];
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
folders: function() {
|
||||
return this.select(function(item) {
|
||||
return item.is_folder();
|
||||
|
|
|
@ -2837,7 +2837,7 @@
|
|||
$.make('div', { className: 'NB-menu-manage-confirm-position'}, [
|
||||
$.make('div', { className: 'NB-menu-manage-move-save NB-menu-manage-feed-move-save NB-modal-submit-green NB-modal-submit-button' }, 'Save'),
|
||||
$.make('div', { className: 'NB-menu-manage-image' }),
|
||||
$.make('div', { className: 'NB-add-folders' }, NEWSBLUR.utils.make_folders(this.model))
|
||||
$.make('div', { className: 'NB-add-folders' }, NEWSBLUR.utils.make_folders())
|
||||
])
|
||||
]),
|
||||
$.make('li', { className: 'NB-menu-item NB-menu-manage-rename NB-menu-manage-feed-rename' }, [
|
||||
|
@ -2939,7 +2939,7 @@
|
|||
$.make('div', { className: 'NB-menu-manage-confirm-position'}, [
|
||||
$.make('div', { className: 'NB-menu-manage-move-save NB-menu-manage-folder-move-save NB-modal-submit-green NB-modal-submit-button' }, 'Save'),
|
||||
$.make('div', { className: 'NB-menu-manage-image' }),
|
||||
$.make('div', { className: 'NB-add-folders' }, NEWSBLUR.utils.make_folders(this.model))
|
||||
$.make('div', { className: 'NB-add-folders' }, NEWSBLUR.utils.make_folders())
|
||||
])
|
||||
]),
|
||||
$.make('li', { className: 'NB-menu-item NB-menu-manage-rename NB-menu-manage-folder-rename' }, [
|
||||
|
@ -5229,7 +5229,7 @@
|
|||
});
|
||||
$.targetIs(e, { tagSelector: '.NB-taskbar-button.NB-task-story-previous' }, function($t, $p){
|
||||
e.preventDefault();
|
||||
self.show_previous_story();
|
||||
self.show_next_story(-1);
|
||||
});
|
||||
$.targetIs(e, { tagSelector: '.NB-taskbar-button.NB-task-layout-full' }, function($t, $p){
|
||||
e.preventDefault();
|
||||
|
|
|
@ -62,7 +62,7 @@ NEWSBLUR.ReaderAddFeed = NEWSBLUR.ReaderPopover.extend({
|
|||
$.make('input', { type: 'text', id: 'NB-add-url', className: 'NB-input NB-add-url', name: 'url', value: self.options.url })
|
||||
]),
|
||||
$.make('div', { className: 'NB-group NB-add-site' }, [
|
||||
NEWSBLUR.utils.make_folders(this.model, this.options.folder_title),
|
||||
NEWSBLUR.utils.make_folders(this.options.folder_title),
|
||||
$.make('div', { className: 'NB-add-folder-icon' }),
|
||||
$.make('div', { className: 'NB-modal-submit-button NB-modal-submit-green NB-add-url-submit' }, 'Add site'),
|
||||
$.make('div', { className: 'NB-loading' })
|
||||
|
@ -293,7 +293,7 @@ NEWSBLUR.ReaderAddFeed = NEWSBLUR.ReaderPopover.extend({
|
|||
if (data.code > 0) {
|
||||
$submit.text('Added!');
|
||||
NEWSBLUR.assets.load_feeds(_.bind(function() {
|
||||
var $folders = NEWSBLUR.utils.make_folders(this.model, $folder.val());
|
||||
var $folders = NEWSBLUR.utils.make_folders($folder.val());
|
||||
this.$(".NB-folders").replaceWith($folders);
|
||||
this.open_add_folder();
|
||||
$submit.text('Add Folder');
|
||||
|
|
|
@ -182,6 +182,25 @@ _.extend(NEWSBLUR.ReaderPreferences.prototype, {
|
|||
'Window title'
|
||||
])
|
||||
]),
|
||||
$.make('div', { className: 'NB-preference NB-preference-autoopenfolder' }, [
|
||||
$.make('div', { className: 'NB-preference-options' }, [
|
||||
$.make('div', [
|
||||
$.make('input', { id: 'NB-preference-autoopenfolder-1', type: 'radio', name: 'autoopen_folder', value: 0 }),
|
||||
$.make('label', { 'for': 'NB-preference-autoopenfolder-1' }, [
|
||||
'Show the dashboard when loading NewsBlur'
|
||||
])
|
||||
]),
|
||||
$.make('div', [
|
||||
$.make('input', { id: 'NB-preference-autoopenfolder-2', type: 'radio', name: 'autoopen_folder', value: 1 }),
|
||||
$.make('label', { 'for': 'NB-preference-autoopenfolder-2' }, [
|
||||
this.make_autoopen_folders()
|
||||
])
|
||||
])
|
||||
]),
|
||||
$.make('div', { className: 'NB-preference-label'}, [
|
||||
'Default folder'
|
||||
])
|
||||
]),
|
||||
$.make('div', { className: 'NB-preference NB-preference-animations' }, [
|
||||
$.make('div', { className: 'NB-preference-options' }, [
|
||||
$.make('div', [
|
||||
|
@ -739,6 +758,15 @@ _.extend(NEWSBLUR.ReaderPreferences.prototype, {
|
|||
]);
|
||||
},
|
||||
|
||||
make_autoopen_folders: function() {
|
||||
var autoopen_folder = NEWSBLUR.Preferences.autoopen_folder;
|
||||
var $folders = NEWSBLUR.utils.make_folders(autoopen_folder, {
|
||||
name: 'default_folder',
|
||||
toplevel: "All Site Stories"
|
||||
});
|
||||
return $folders;
|
||||
},
|
||||
|
||||
resize_modal: function() {
|
||||
var $scroll = $('.NB-tab.NB-active', this.$modal);
|
||||
var $modal = this.$modal;
|
||||
|
@ -763,6 +791,12 @@ _.extend(NEWSBLUR.ReaderPreferences.prototype, {
|
|||
});
|
||||
}
|
||||
|
||||
$('select[name=default_folder] option', $modal).each(function() {
|
||||
if ($(this).val() == NEWSBLUR.Preferences.default_folder) {
|
||||
$(this).attr('selected', true);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
$('input[name=default_view]', $modal).each(function() {
|
||||
if ($(this).val() == NEWSBLUR.Preferences.default_view) {
|
||||
$(this).attr('checked', true);
|
||||
|
@ -793,6 +827,12 @@ _.extend(NEWSBLUR.ReaderPreferences.prototype, {
|
|||
return false;
|
||||
}
|
||||
});
|
||||
$('input[name=autoopen_folder]', $modal).each(function() {
|
||||
if ($(this).val() == NEWSBLUR.Preferences.autoopen_folder) {
|
||||
$(this).attr('checked', true);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
$('input[name=title_counts]', $modal).each(function() {
|
||||
if (NEWSBLUR.Preferences.title_counts) {
|
||||
$(this).attr('checked', true);
|
||||
|
|
|
@ -146,11 +146,12 @@ NEWSBLUR.utils = {
|
|||
return this.dayNames[dayOfWeek] + ", " + this.monthNames[month] + " " + day + ", " + year;
|
||||
},
|
||||
|
||||
make_folders: function(model, selected_folder_title) {
|
||||
var folders = model.get_folders();
|
||||
var $options = $.make('select', { className: 'NB-folders'});
|
||||
make_folders: function(selected_folder_title, options) {
|
||||
options = options || {};
|
||||
var folders = NEWSBLUR.assets.get_folders();
|
||||
var $options = $.make('select', { className: 'NB-folders', name: options.name });
|
||||
|
||||
var $option = $.make('option', { value: '' }, "Top Level");
|
||||
var $option = $.make('option', { value: '' }, options.toplevel || "Top Level");
|
||||
$options.append($option);
|
||||
|
||||
$options = this.make_folder_options($options, folders, ' ', selected_folder_title);
|
||||
|
|
|
@ -160,7 +160,10 @@ NEWSBLUR.Views.FeedList = Backbone.View.extend({
|
|||
if (!NEWSBLUR.router) {
|
||||
NEWSBLUR.router = new NEWSBLUR.Router;
|
||||
var route_found = Backbone.history.start({pushState: true});
|
||||
this.load_url_next_param(route_found);
|
||||
var next = this.load_url_next_param(route_found);
|
||||
if (!next && !route_found && NEWSBLUR.assets.preference("autoopen_folder")) {
|
||||
this.load_default_folder();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -189,6 +192,21 @@ NEWSBLUR.Views.FeedList = Backbone.View.extend({
|
|||
// In case this needs to be found again: window.location.href = BACKBONE
|
||||
window.history.replaceState({}, null, '/');
|
||||
}
|
||||
|
||||
return next;
|
||||
},
|
||||
|
||||
load_default_folder: function() {
|
||||
var default_folder = NEWSBLUR.assets.preference('default_folder');
|
||||
|
||||
if (!default_folder || default_folder == "") {
|
||||
NEWSBLUR.reader.open_river_stories();
|
||||
} else {
|
||||
var folder = NEWSBLUR.assets.get_folder(default_folder);
|
||||
if (folder) {
|
||||
NEWSBLUR.reader.open_river_stories(folder.folder_view.$el, folder);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
update_dashboard_count: function() {
|
||||
|
|
|
@ -43,10 +43,12 @@ NEWSBLUR.Views.Folder = Backbone.View.extend({
|
|||
},
|
||||
|
||||
destroy: function() {
|
||||
console.log(["destroy", this]);
|
||||
if (this.model) {
|
||||
this.model.unbind(null, this);
|
||||
}
|
||||
this.$el.remove();
|
||||
delete this.views;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
|
17
settings.py
17
settings.py
|
@ -50,7 +50,6 @@ HELLO_EMAIL = 'hello@newsblur.com'
|
|||
NEWSBLUR_URL = 'http://www.newsblur.com'
|
||||
SECRET_KEY = 'YOUR_SECRET_KEY'
|
||||
|
||||
|
||||
# ===================
|
||||
# = Global Settings =
|
||||
# ===================
|
||||
|
@ -71,7 +70,6 @@ MEDIA_URL = '/media/'
|
|||
# trailing slash.
|
||||
# Examples: "http://foo.com/media/", "/media/".
|
||||
ADMIN_MEDIA_PREFIX = '/media/admin/'
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||
CIPHER_USERNAMES = False
|
||||
DEBUG_ASSETS = DEBUG
|
||||
HOMEPAGE_USERNAME = 'popular'
|
||||
|
@ -207,6 +205,11 @@ SESSION_COOKIE_AGE = 60*60*24*365*2 # 2 years
|
|||
SESSION_COOKIE_DOMAIN = '.newsblur.com'
|
||||
SENTRY_DSN = 'https://XXXNEWSBLURXXX@app.getsentry.com/99999999'
|
||||
|
||||
if not DEVELOPMENT:
|
||||
EMAIL_BACKEND = 'django_mailgun.MailgunBackend'
|
||||
else:
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||
|
||||
# ==============
|
||||
# = Subdomains =
|
||||
# ==============
|
||||
|
@ -366,7 +369,7 @@ CELERYBEAT_SCHEDULE = {
|
|||
},
|
||||
'share-popular-stories': {
|
||||
'task': 'share-popular-stories',
|
||||
'schedule': datetime.timedelta(hours=1),
|
||||
'schedule': datetime.timedelta(minutes=10),
|
||||
'options': {'queue': 'beat_tasks'},
|
||||
},
|
||||
'clean-analytics': {
|
||||
|
@ -381,7 +384,7 @@ CELERYBEAT_SCHEDULE = {
|
|||
},
|
||||
'activate-next-new-user': {
|
||||
'task': 'activate-next-new-user',
|
||||
'schedule': datetime.timedelta(minutes=3.5),
|
||||
'schedule': datetime.timedelta(minutes=5),
|
||||
'options': {'queue': 'beat_tasks'},
|
||||
},
|
||||
}
|
||||
|
@ -489,9 +492,10 @@ if not DEVELOPMENT:
|
|||
INSTALLED_APPS += (
|
||||
'gunicorn',
|
||||
'raven.contrib.django',
|
||||
'django_ses',
|
||||
'django_ses',
|
||||
|
||||
)
|
||||
RAVEN_CLIENT = raven.Client(SENTRY_DSN)
|
||||
|
||||
COMPRESS = not DEBUG
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
|
@ -510,9 +514,6 @@ DEBUG_TOOLBAR_CONFIG = {
|
|||
'SHOW_TOOLBAR_CALLBACK': custom_show_toolbar,
|
||||
'HIDE_DJANGO_SQL': False,
|
||||
}
|
||||
if not DEVELOPMENT:
|
||||
RAVEN_CLIENT = raven.Client(SENTRY_DSN)
|
||||
EMAIL_BACKEND = 'django_ses.SESBackend'
|
||||
|
||||
if DEBUG:
|
||||
TEMPLATE_LOADERS = (
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
'timezone' : "{{ user_profile.timezone }}",
|
||||
'title_counts' : true,
|
||||
'truncate_story' : 'social',
|
||||
'autoopen_folder' : false,
|
||||
'story_share_twitter' : true,
|
||||
'story_share_facebook' : true,
|
||||
'story_share_readitlater' : false,
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
<img src="{{ story.feed.favicon_url }}" />
|
||||
</div>
|
||||
<div class="NB-feed-title">
|
||||
<a href="/site/{{ story.feed.id }}/">{{ story.feed.feed_title }}</a>
|
||||
<a href="{{ story.feed.feed_link }}">{{ story.feed.feed_title }}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
|
@ -195,6 +195,10 @@
|
|||
optional: true
|
||||
default: "true"
|
||||
example: "false"
|
||||
tips:
|
||||
- >
|
||||
The story score (red, neutral, green) can be computed by following
|
||||
the function <a href="https://github.com/samuelclay/NewsBlur/blob/master/media/js/newsblur/reader/reader_utils.js"> `compute_story_score` in reader_utils.js</a>.
|
||||
|
||||
- url: /reader/starred_stories
|
||||
method: GET
|
||||
|
@ -207,6 +211,10 @@
|
|||
optional: true
|
||||
default: 1
|
||||
example: 2
|
||||
- key: h
|
||||
desc: "Pass up to 100 story_hashes. Use with starred_story_hashes."
|
||||
optional: true
|
||||
example: "h=a1b2c3&h=d4e5f6"
|
||||
|
||||
- url: /reader/starred_story_hashes
|
||||
method: GET
|
||||
|
@ -218,6 +226,8 @@
|
|||
desc: "Including timestamps for starred_date"
|
||||
optional: true
|
||||
default: false
|
||||
tips:
|
||||
- "Use with /reader/starred_stories and pass up to 100 story_hashes as the `h` param."
|
||||
|
||||
- url: /reader/river_stories
|
||||
method: GET
|
||||
|
|
|
@ -14,7 +14,7 @@ class NBMuninGraph(MuninGraph):
|
|||
# 'feed_errors.label': 'Feed Errors',
|
||||
'feed_success.label': 'Feed Success',
|
||||
# 'page_errors.label': 'Page Errors',
|
||||
'page_success.label': 'Page Success',
|
||||
# 'page_success.label': 'Page Success',
|
||||
}
|
||||
|
||||
def calculate_metrics(self):
|
||||
|
@ -23,7 +23,6 @@ class NBMuninGraph(MuninGraph):
|
|||
|
||||
return {
|
||||
'feed_success': statistics['feeds_fetched'],
|
||||
'page_success': statistics['pages_fetched'],
|
||||
}
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -20,4 +20,4 @@ WHITE='\033[01;37m'
|
|||
|
||||
ipaddr=`python /srv/newsblur/utils/hostname_ssh.py $1`
|
||||
printf "\n ${BLUE}---> ${LBLUE}Connecting to ${LGREEN}$1${BLUE} / ${LRED}$ipaddr${BLUE} <--- ${RESTORE}\n\n"
|
||||
ssh -i ~/projects/secrets-newsblur/keys/newsblur.key $ipaddr
|
||||
ssh -l sclay -i /srv/secrets-newsblur/keys/newsblur.key $ipaddr
|
|
@ -11,6 +11,7 @@ sys.path.insert(0, '/srv/newsblur')
|
|||
os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
|
||||
import fabfile
|
||||
|
||||
NEWSBLUR_USERNAME = 'sclay'
|
||||
IGNORE_HOSTS = [
|
||||
'push',
|
||||
]
|
||||
|
@ -58,10 +59,15 @@ def create_streams_for_roles(role, role2, command=None, path=None):
|
|||
if any(h in hostname for h in IGNORE_HOSTS): continue
|
||||
if hostname in found: continue
|
||||
if 'ec2' in hostname:
|
||||
s = subprocess.Popen(["ssh", "-i", os.path.expanduser("~/.ec2/sclay.pem"),
|
||||
s = subprocess.Popen(["ssh",
|
||||
"-i", os.path.expanduser(os.path.join(fabfile.env.SECRETS_PATH,
|
||||
"keys/ec2.pem")),
|
||||
address, "%s %s" % (command, path)], stdout=subprocess.PIPE)
|
||||
else:
|
||||
s = subprocess.Popen(["ssh", address, "%s %s" % (command, path)], stdout=subprocess.PIPE)
|
||||
s = subprocess.Popen(["ssh", "-l", NEWSBLUR_USERNAME,
|
||||
"-i", os.path.expanduser(os.path.join(fabfile.env.SECRETS_PATH,
|
||||
"keys/newsblur.key")),
|
||||
address, "%s %s" % (command, path)], stdout=subprocess.PIPE)
|
||||
s.name = hostname
|
||||
streams.append(s)
|
||||
found.add(hostname)
|
||||
|
|
6
utils/tlnbw.py
Executable file
6
utils/tlnbw.py
Executable file
|
@ -0,0 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import tlnb
|
||||
|
||||
if __name__ == "__main__":
|
||||
tlnb.main(role="work", role2="work")
|
Loading…
Add table
Reference in a new issue