mirror of
https://github.com/samuelclay/NewsBlur.git
synced 2025-09-18 21:50:56 +00:00
Merge branch 'master' into dejal
This commit is contained in:
commit
aa6700cedc
24 changed files with 203 additions and 63 deletions
|
@ -39,8 +39,8 @@
|
|||
|
||||
* [jQuery](http://www.jquery.com): Cross-browser compliant JavaScript code. IE works without effort.
|
||||
* [Underscore.js](http://underscorejs.org/): Functional programming for JavaScript.
|
||||
Indispensible.
|
||||
* [Backbone.js](http://backbonejs.org/): Framework for the web app. Also indispensible.
|
||||
Indispensable.
|
||||
* [Backbone.js](http://backbonejs.org/): Framework for the web app. Also indispensable.
|
||||
* Miscellaneous jQuery Plugins: Everything from resizable layouts, to progress
|
||||
bars, sortables, date handling, colors, corners, JSON, animations.
|
||||
[See the complete list](https://github.com/samuelclay/NewsBlur/tree/master/media/js).
|
||||
|
|
|
@ -46,7 +46,7 @@ from utils.feed_functions import relative_timesince
|
|||
from utils.feed_functions import seconds_timesince
|
||||
from utils.story_functions import strip_tags, htmldiff, strip_comments, strip_comments__lxml
|
||||
from utils.story_functions import prep_for_search
|
||||
from utils.story_functions import create_signed_url
|
||||
from utils.story_functions import create_camo_signed_url
|
||||
|
||||
ENTRY_NEW, ENTRY_UPDATED, ENTRY_SAME, ENTRY_ERR = range(4)
|
||||
|
||||
|
@ -1908,9 +1908,9 @@ class Feed(models.Model):
|
|||
|
||||
@classmethod
|
||||
def secure_image_urls(cls, urls):
|
||||
signed_urls = [create_signed_url(settings.IMAGES_URL,
|
||||
settings.IMAGES_SECRET_KEY,
|
||||
url) for url in urls]
|
||||
signed_urls = [create_camo_signed_url(settings.IMAGES_URL,
|
||||
settings.IMAGES_SECRET_KEY,
|
||||
url) for url in urls]
|
||||
return dict(zip(urls, signed_urls))
|
||||
|
||||
def get_tags(self, entry):
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
android:layout_marginTop="1dp"
|
||||
android:layout_marginBottom="1dp"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:visibility="gone" />
|
||||
|
||||
<ImageView
|
||||
|
|
5
clients/android/NewsBlur/res/values/dimens.xml
Normal file
5
clients/android/NewsBlur/res/values/dimens.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<dimen name="thumbnails_small_size">50dp</dimen>
|
||||
<dimen name="thumbnails_size">90dp</dimen>
|
||||
</resources>
|
|
@ -324,9 +324,26 @@
|
|||
<string name="settings_immersive_enter_single_tap">Immersive Mode Via Single Tap</string>
|
||||
<string name="settings_show_content_preview">Show Content Preview Text</string>
|
||||
<string name="settings_show_thumbnails">Show Image Preview Thumbnails</string>
|
||||
<string name="settings_thumbnails_style">Image Preview Thumbnails</string>
|
||||
<string name="settings_notifications">Notifications</string>
|
||||
<string name="settings_enable_notifications">Enable Notifications</string>
|
||||
|
||||
<string name="large">Large</string>
|
||||
<string name="small">Small</string>
|
||||
<string-array name="thumbnails_style_entries">
|
||||
<item>@string/large</item>
|
||||
<item>@string/small</item>
|
||||
<item>@string/off</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="thumbnails_style_values">
|
||||
<item>LARGE</item>
|
||||
<item>SMALL</item>
|
||||
<item>OFF</item>
|
||||
</string-array>
|
||||
|
||||
<string name="thumbnails_style_value">LARGE</string>
|
||||
|
||||
<string name="infrequent_choice_title">Stories from sites with</string>
|
||||
<string name="infrequent_5">< 5 STORIES/MONTH</string>
|
||||
<string name="infrequent_15">< 15 STORIES/MONTH</string>
|
||||
|
|
|
@ -88,10 +88,13 @@
|
|||
android:defaultValue="true"
|
||||
android:key="pref_show_content_preview"
|
||||
android:title="@string/settings_show_content_preview" />
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="true"
|
||||
android:key="pref_show_thumbnails"
|
||||
android:title="@string/settings_show_thumbnails" />
|
||||
<ListPreference
|
||||
android:key="pref_thumbnails_style"
|
||||
android:title="@string/settings_thumbnails_style"
|
||||
android:dialogTitle="@string/settings_thumbnails_style"
|
||||
android:entries="@array/thumbnails_style_entries"
|
||||
android:entryValues="@array/thumbnails_style_values"
|
||||
android:defaultValue="@string/thumbnails_style_value" />
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="false"
|
||||
android:key="pref_mark_read_on_scroll"
|
||||
|
|
|
@ -121,6 +121,8 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
|
|||
public void afterTextChanged(Editable s) {}
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
});
|
||||
|
||||
FeedUtils.currentFolderName = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -767,6 +767,7 @@ public class BlurDatabaseHelper {
|
|||
Cursor c = dbRO.query(DatabaseConstants.FEED_TABLE, null, selection, selArgs, null, null, null);
|
||||
while (c.moveToNext()) {
|
||||
Feed f = Feed.fromCursor(c);
|
||||
if(!f.active) continue;
|
||||
result += f.positiveCount;
|
||||
if ((stateFilter == StateFilter.SOME) || (stateFilter == StateFilter.ALL)) result += f.neutralCount;
|
||||
if (stateFilter == StateFilter.ALL) result += f.negativeCount;
|
||||
|
|
|
@ -18,6 +18,7 @@ import android.view.ViewGroup;
|
|||
import android.view.ViewParent;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import butterknife.Bind;
|
||||
|
@ -42,6 +43,7 @@ import com.newsblur.util.ImageLoader;
|
|||
import com.newsblur.util.PrefsUtils;
|
||||
import com.newsblur.util.StoryListStyle;
|
||||
import com.newsblur.util.StoryUtils;
|
||||
import com.newsblur.util.ThumbnailStyle;
|
||||
import com.newsblur.util.UIUtils;
|
||||
|
||||
/**
|
||||
|
@ -391,11 +393,12 @@ public class StoryViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
|
|||
return true;
|
||||
|
||||
case R.id.menu_save_story:
|
||||
FeedUtils.setStorySaved(story, true, context);
|
||||
//TODO get folder name
|
||||
FeedUtils.setStorySaved(story, true, context, null);
|
||||
return true;
|
||||
|
||||
case R.id.menu_unsave_story:
|
||||
FeedUtils.setStorySaved(story, false, context);
|
||||
FeedUtils.setStorySaved(story, false, context, null);
|
||||
return true;
|
||||
|
||||
case R.id.menu_intel:
|
||||
|
@ -443,10 +446,10 @@ public class StoryViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
|
|||
FeedUtils.markStoryUnread(story, context);
|
||||
break;
|
||||
case GEST_ACTION_SAVE:
|
||||
FeedUtils.setStorySaved(story, true, context);
|
||||
FeedUtils.setStorySaved(story, true, context, null);
|
||||
break;
|
||||
case GEST_ACTION_UNSAVE:
|
||||
FeedUtils.setStorySaved(story, false, context);
|
||||
FeedUtils.setStorySaved(story, false, context, null);
|
||||
break;
|
||||
case GEST_ACTION_NONE:
|
||||
default:
|
||||
|
@ -510,11 +513,12 @@ public class StoryViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
|
|||
*/
|
||||
private void bindCommon(StoryViewHolder vh, int position, Story story) {
|
||||
if ((vh instanceof StoryTileViewHolder) ||
|
||||
((PrefsUtils.isShowThumbnails(context)) && (story.thumbnailUrl != null))) {
|
||||
((PrefsUtils.getThumbnailStyle(context) != ThumbnailStyle.OFF) && (story.thumbnailUrl != null))) {
|
||||
// when first created, tiles' views tend to not yet have their dimensions calculated, but
|
||||
// upon being recycled they will often have a known size, which lets us give a max size to
|
||||
// the image loader, which in turn can massively optimise loading. the image loader will
|
||||
// reject nonsene values
|
||||
|
||||
int thumbSizeGuess = vh.thumbView.getMeasuredHeight();
|
||||
// there is a not-unlikely chance that the recycler will re-use a tile for a story with the
|
||||
// same thumbnail. only load it if it is different.
|
||||
|
@ -627,6 +631,16 @@ public class StoryViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
|
|||
vh.storyAuthor.setTextSize(textSize * defaultTextSize_story_item_author);
|
||||
vh.storySnippet.setTextSize(textSize * defaultTextSize_story_item_snip);
|
||||
|
||||
ThumbnailStyle thumbnailStyle = PrefsUtils.getThumbnailStyle(context);
|
||||
int sizeRes = thumbnailStyle == ThumbnailStyle.SMALL ? R.dimen.thumbnails_small_size : R.dimen.thumbnails_size;
|
||||
int sizeDp = context.getResources().getDimensionPixelSize(sizeRes);
|
||||
|
||||
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) vh.thumbView.getLayoutParams();
|
||||
if (params.height != sizeDp) {
|
||||
params.height = sizeDp;
|
||||
params.width = sizeDp;
|
||||
}
|
||||
|
||||
if (this.ignoreReadStatus || (! story.read)) {
|
||||
vh.storyAuthor.setAlpha(1.0f);
|
||||
vh.storySnippet.setAlpha(1.0f);
|
||||
|
|
|
@ -80,6 +80,7 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
|
|||
super.onCreate(savedInstanceState);
|
||||
currentState = PrefsUtils.getStateFilter(getActivity());
|
||||
adapter = new FolderListAdapter(getActivity(), currentState);
|
||||
FeedUtils.currentFolderName = null;
|
||||
// NB: it is by design that loaders are not started until we get a
|
||||
// ping from the sync service indicating that it has initialised
|
||||
}
|
||||
|
@ -502,6 +503,7 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
|
|||
|
||||
@Override
|
||||
public boolean onChildClick(ExpandableListView list, View childView, int groupPosition, int childPosition, long id) {
|
||||
FeedUtils.currentFolderName = null;
|
||||
FeedSet fs = adapter.getChild(groupPosition, childPosition);
|
||||
if (adapter.isRowAllSharedStories(groupPosition)) {
|
||||
SocialFeed socialFeed = adapter.getSocialFeed(groupPosition, childPosition);
|
||||
|
@ -519,6 +521,12 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
|
|||
// a folder name on the FeedSet and making it into a folder-type set. it is just a single feed,
|
||||
// and the folder name is a bit of metadata needed by the UI/API
|
||||
String folderName = adapter.getGroupFolderName(groupPosition);
|
||||
if(folderName == null || folderName.equals(AppConstants.ROOT_FOLDER)){
|
||||
FeedUtils.currentFolderName = null;
|
||||
}else{
|
||||
|
||||
FeedUtils.currentFolderName = folderName;
|
||||
}
|
||||
Intent intent = new Intent(getActivity(), FeedItemsList.class);
|
||||
intent.putExtra(ItemsList.EXTRA_FEED_SET, fs);
|
||||
intent.putExtra(FeedItemsList.EXTRA_FEED, feed);
|
||||
|
|
|
@ -40,6 +40,7 @@ import com.newsblur.R;
|
|||
import com.newsblur.activity.NbActivity;
|
||||
import com.newsblur.activity.Reading;
|
||||
import com.newsblur.domain.Classifier;
|
||||
import com.newsblur.domain.Feed;
|
||||
import com.newsblur.domain.Story;
|
||||
import com.newsblur.domain.UserDetails;
|
||||
import com.newsblur.service.OriginalTextService;
|
||||
|
@ -56,8 +57,10 @@ import com.newsblur.view.FlowLayout;
|
|||
import com.newsblur.view.NewsblurWebview;
|
||||
import com.newsblur.view.ReadingScrollView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
@ -244,7 +247,6 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
|
|||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
|
||||
HitTestResult result = web.getHitTestResult();
|
||||
if (result.getType() == HitTestResult.IMAGE_TYPE ||
|
||||
result.getType() == HitTestResult.SRC_ANCHOR_TYPE ||
|
||||
result.getType() == HitTestResult.SRC_IMAGE_ANCHOR_TYPE ) {
|
||||
// if the long-pressed item was an image, see if we can pop up a little dialogue
|
||||
// that presents the alt text. Note that images wrapped in links tend to get detected
|
||||
|
@ -282,6 +284,13 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
|
|||
}
|
||||
});
|
||||
builder.show();
|
||||
} else if (result.getType() == HitTestResult.SRC_ANCHOR_TYPE) {
|
||||
String url = result.getExtra();
|
||||
Intent intent = new Intent(Intent.ACTION_SEND);
|
||||
intent.setType("text/plain");
|
||||
intent.putExtra(Intent.EXTRA_SUBJECT, UIUtils.fromHtml(story.title).toString());
|
||||
intent.putExtra(Intent.EXTRA_TEXT, url);
|
||||
startActivity(Intent.createChooser(intent, "Share using"));
|
||||
} else {
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
}
|
||||
|
@ -341,9 +350,9 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
|
|||
return true;
|
||||
} else if (item.getItemId() == R.id.menu_reading_save) {
|
||||
if (story.starred) {
|
||||
FeedUtils.setStorySaved(story, false, getActivity());
|
||||
FeedUtils.setStorySaved(story, false, getActivity(), null);
|
||||
} else {
|
||||
FeedUtils.setStorySaved(story, true, getActivity());
|
||||
FeedUtils.setStorySaved(story.storyHash, true, getActivity());
|
||||
}
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.menu_reading_markunread) {
|
||||
|
@ -373,9 +382,9 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
|
|||
|
||||
@OnClick(R.id.save_story_button) void clickSave() {
|
||||
if (story.starred) {
|
||||
FeedUtils.setStorySaved(story, false, getActivity());
|
||||
FeedUtils.setStorySaved(story.storyHash, false, getActivity());
|
||||
} else {
|
||||
FeedUtils.setStorySaved(story,true, getActivity());
|
||||
FeedUtils.setStorySaved(story.storyHash,true, getActivity());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -760,6 +769,8 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
|
|||
builder.append(font.forWebView(currentSize));
|
||||
builder.append("<link rel=\"stylesheet\" type=\"text/css\" href=\"reading.css\" />");
|
||||
if (themeValue == ThemeValue.LIGHT) {
|
||||
// builder.append("<meta name=\"color-scheme\" content=\"light\"/>");
|
||||
// builder.append("<meta name=\"supported-color-schemes\" content=\"light\"/>");
|
||||
builder.append("<link rel=\"stylesheet\" type=\"text/css\" href=\"light_reading.css\" />");
|
||||
} else if (themeValue == ThemeValue.DARK) {
|
||||
builder.append("<link rel=\"stylesheet\" type=\"text/css\" href=\"dark_reading.css\" />");
|
||||
|
|
|
@ -116,7 +116,8 @@ public class APIConstants {
|
|||
public static final String PARAMETER_TAG = "tag";
|
||||
public static final String PARAMETER_APPROVED_FEEDS = "approved_feeds";
|
||||
public static final String PARAMETER_NOTIFICATION_TYPES = "notification_types";
|
||||
public static final String PARAMETER_NOTIFICATION_FILTER = "notification_filter";
|
||||
public static final String PAREMETER_USER_TAGS = "user_tags";
|
||||
public static final String PARAMETER_NOTIFICATION_FILTER = "notification_filter";
|
||||
public static final String PARAMETER_RESET_FETCH = "reset_fetch";
|
||||
public static final String PARAMETER_INFREQUENT = "infrequent";
|
||||
public static final String PARAMETER_FEEDTITLE = "feed_title";
|
||||
|
|
|
@ -198,9 +198,12 @@ public class APIManager {
|
|||
return response.getResponse(gson, NewsBlurResponse.class);
|
||||
}
|
||||
|
||||
public NewsBlurResponse markStoryAsStarred(String storyHash) {
|
||||
public NewsBlurResponse markStoryAsStarred(String storyHash, List<String> userTags) {
|
||||
ValueMultimap values = new ValueMultimap();
|
||||
values.put(APIConstants.PARAMETER_STORY_HASH, storyHash);
|
||||
for (String tag : userTags) {
|
||||
values.put(APIConstants.PAREMETER_USER_TAGS, tag);
|
||||
}
|
||||
APIResponse response = post(buildUrl(APIConstants.PATH_MARK_STORY_AS_STARRED), values);
|
||||
return response.getResponse(gson, NewsBlurResponse.class);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package com.newsblur.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import android.content.Context;
|
||||
|
@ -34,6 +36,12 @@ public class FeedUtils {
|
|||
public static ImageLoader thumbnailLoader;
|
||||
public static FileCache storyImageCache;
|
||||
|
||||
// this is gross, but the feedset can't hold a folder title
|
||||
// without being mistaken for a folder feed.
|
||||
// The alternative is to pass it through alongside all instances
|
||||
// of the feedset
|
||||
public static String currentFolderName;
|
||||
|
||||
public static void offerInitContext(Context context) {
|
||||
if (dbHelper == null) {
|
||||
dbHelper = new BlurDatabaseHelper(context.getApplicationContext());
|
||||
|
@ -76,15 +84,22 @@ public class FeedUtils {
|
|||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
public static void setStorySaved(final Story story, final boolean saved, final Context context) {
|
||||
setStorySaved(story.storyHash, saved, context);
|
||||
public static void setStorySaved(final String storyHash, final boolean saved, final Context context) {
|
||||
List<String> userTags = new ArrayList<>();
|
||||
if(FeedUtils.currentFolderName != null){
|
||||
userTags.add(FeedUtils.currentFolderName);
|
||||
}
|
||||
setStorySaved(storyHash, saved, context, userTags);
|
||||
}
|
||||
public static void setStorySaved(final Story story, final boolean saved, final Context context, final List<String> userTags) {
|
||||
setStorySaved(story.storyHash, saved, context, userTags);
|
||||
}
|
||||
|
||||
public static void setStorySaved(final String storyHash, final boolean saved, final Context context) {
|
||||
public static void setStorySaved(final String storyHash, final boolean saved, final Context context, final List<String> userTags) {
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... arg) {
|
||||
ReadingAction ra = (saved ? ReadingAction.saveStory(storyHash) : ReadingAction.unsaveStory(storyHash));
|
||||
ReadingAction ra = (saved ? ReadingAction.saveStory(storyHash, userTags) : ReadingAction.unsaveStory(storyHash));
|
||||
ra.doLocal(dbHelper);
|
||||
NbActivity.updateAllActivities(NbActivity.UPDATE_STORY);
|
||||
dbHelper.enqueueAction(ra);
|
||||
|
@ -307,6 +322,7 @@ public class FeedUtils {
|
|||
Intent intent = new Intent(android.content.Intent.ACTION_SEND);
|
||||
intent.setType("text/plain");
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.putExtra(Intent.EXTRA_SUBJECT, UIUtils.fromHtml(story.title).toString());
|
||||
intent.putExtra(Intent.EXTRA_TEXT, String.format(context.getResources().getString(R.string.send_brief), new Object[]{UIUtils.fromHtml(story.title), story.permalink}));
|
||||
context.startActivity(Intent.createChooser(intent, "Send using"));
|
||||
}
|
||||
|
|
|
@ -59,6 +59,7 @@ public class PrefConstants {
|
|||
public static final String STORIES_MARK_READ_ON_SCROLL = "pref_mark_read_on_scroll";
|
||||
public static final String STORIES_SHOW_PREVIEWS = "pref_show_content_preview";
|
||||
public static final String STORIES_SHOW_THUMBNAILS = "pref_show_thumbnails";
|
||||
public static final String STORIES_THUMBNAILS_STYLE = "pref_thumbnails_style";
|
||||
|
||||
public static final String ENABLE_OFFLINE = "enable_offline";
|
||||
public static final String ENABLE_IMAGE_PREFETCH = "enable_image_prefetch";
|
||||
|
|
|
@ -685,11 +685,18 @@ public class PrefsUtils {
|
|||
return prefs.getBoolean(PrefConstants.STORIES_SHOW_PREVIEWS, true);
|
||||
}
|
||||
|
||||
public static boolean isShowThumbnails(Context context) {
|
||||
private static boolean isShowThumbnails(Context context) {
|
||||
SharedPreferences prefs = context.getSharedPreferences(PrefConstants.PREFERENCES, 0);
|
||||
return prefs.getBoolean(PrefConstants.STORIES_SHOW_THUMBNAILS, true);
|
||||
}
|
||||
|
||||
public static ThumbnailStyle getThumbnailStyle(Context context) {
|
||||
SharedPreferences prefs = context.getSharedPreferences(PrefConstants.PREFERENCES, 0);
|
||||
boolean isShowThumbnails = isShowThumbnails(context);
|
||||
ThumbnailStyle defValue = isShowThumbnails ? ThumbnailStyle.LARGE : ThumbnailStyle.OFF;
|
||||
return ThumbnailStyle.valueOf(prefs.getString(PrefConstants.STORIES_THUMBNAILS_STYLE, defValue.toString()));
|
||||
}
|
||||
|
||||
public static boolean isAutoOpenFirstUnread(Context context) {
|
||||
SharedPreferences prefs = context.getSharedPreferences(PrefConstants.PREFERENCES, 0);
|
||||
return prefs.getBoolean(PrefConstants.STORIES_AUTO_OPEN_FIRST, false);
|
||||
|
|
|
@ -59,6 +59,7 @@ public class ReadingAction implements Serializable {
|
|||
private String replyId;
|
||||
private String notifyFilter;
|
||||
private List<String> notifyTypes;
|
||||
private List<String> userTags;
|
||||
private Classifier classifier;
|
||||
private String newFeedName;
|
||||
|
||||
|
@ -96,10 +97,15 @@ public class ReadingAction implements Serializable {
|
|||
return ra;
|
||||
}
|
||||
|
||||
public static ReadingAction saveStory(String hash) {
|
||||
public static ReadingAction saveStory(String hash, List<String> userTags) {
|
||||
ReadingAction ra = new ReadingAction();
|
||||
ra.type = ActionType.SAVE;
|
||||
ra.storyHash = hash;
|
||||
if (userTags == null) {
|
||||
ra.userTags = new ArrayList<String>();
|
||||
} else {
|
||||
ra.userTags = userTags;
|
||||
}
|
||||
return ra;
|
||||
}
|
||||
|
||||
|
@ -289,7 +295,7 @@ public class ReadingAction implements Serializable {
|
|||
break;
|
||||
|
||||
case SAVE:
|
||||
result = apiManager.markStoryAsStarred(storyHash);
|
||||
result = apiManager.markStoryAsStarred(storyHash, userTags);
|
||||
break;
|
||||
|
||||
case UNSAVE:
|
||||
|
|
|
@ -6,6 +6,7 @@ import android.text.format.DateFormat;
|
|||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Created by mark on 04/02/2014.
|
||||
|
@ -67,6 +68,20 @@ public class StoryUtils {
|
|||
int month = storyCalendar.get(Calendar.DAY_OF_MONTH);
|
||||
|
||||
SimpleDateFormat timeFormat = getTimeFormat(context);
|
||||
Locale locale = context.getResources().getConfiguration().locale;
|
||||
|
||||
if(!locale.getLanguage().equals("en")){
|
||||
String pattern = null;
|
||||
|
||||
if (storyDate.getTime() > beginningOfMonth.getTime()) {
|
||||
// localized pattern, without year
|
||||
pattern = android.text.format.DateFormat.getBestDateTimePattern(locale, "EEEE MMMM d");
|
||||
}else {
|
||||
// localized pattern, with year
|
||||
pattern = android.text.format.DateFormat.getBestDateTimePattern(locale, "EEEE MMMM d yyyy");
|
||||
}
|
||||
return DateFormat.format(pattern, storyDate).toString() + " " + timeFormat.format(storyDate);
|
||||
}
|
||||
|
||||
if (storyDate.getTime() > midnightToday.getTime()) {
|
||||
// Today, January 1st 00:00
|
||||
|
@ -105,7 +120,7 @@ public class StoryUtils {
|
|||
}
|
||||
|
||||
private static SimpleDateFormat getTimeFormat(Context context) {
|
||||
if (DateFormat.is24HourFormat(context)) {
|
||||
if (android.text.format.DateFormat.is24HourFormat(context)) {
|
||||
return twentyFourHourFormat.get();
|
||||
} else {
|
||||
return twelveHourFormat.get();
|
||||
|
@ -134,6 +149,17 @@ public class StoryUtils {
|
|||
|
||||
SimpleDateFormat timeFormat = getTimeFormat(context);
|
||||
|
||||
Locale locale = context.getResources().getConfiguration().locale;
|
||||
|
||||
if(!locale.getLanguage().equals("en")){
|
||||
if (storyDate.getTime() > midnightToday.getTime()) {
|
||||
return timeFormat.format(storyDate);
|
||||
}else {
|
||||
String pattern = android.text.format.DateFormat.getBestDateTimePattern(locale, "d MMM yyyy " + timeFormat.toPattern());
|
||||
return DateFormat.format(pattern, storyDate).toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (storyDate.getTime() > midnightToday.getTime()) {
|
||||
// 00:00
|
||||
return timeFormat.format(storyDate);
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
package com.newsblur.util;
|
||||
|
||||
public enum ThumbnailStyle {
|
||||
LARGE,
|
||||
SMALL,
|
||||
OFF
|
||||
}
|
||||
|
|
@ -1,9 +1,16 @@
|
|||
upstream camo_server {
|
||||
server 0.0.0.0:8081 fail_timeout=10 max_fails=3;
|
||||
server 127.0.0.1:8081 fail_timeout=10 max_fails=3;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80 default_server;
|
||||
|
||||
server_name _;
|
||||
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen 443 ssl;
|
||||
|
||||
ssl_certificate /srv/newsblur/config/certificates/newsblur.com.pem;
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
bleach==3.1.0
|
||||
BeautifulSoup==3.2.1
|
||||
beautifulsoup4==4.8.0
|
||||
boto==2.43.0
|
||||
celery==3.1.25
|
||||
chardet==3.0.4
|
||||
|
|
|
@ -74,8 +74,7 @@ NEWSBLUR.Models.Story = Backbone.Model.extend({
|
|||
secure_content: function(content_attr) {
|
||||
var content = this.get(content_attr);
|
||||
|
||||
if (window.location.protocol == 'https:' &&
|
||||
NEWSBLUR.Globals.is_staff) {
|
||||
if (window.location.protocol == 'https:') {
|
||||
_.each(this.get('secure_image_urls'), function(secure_url, url) {
|
||||
if (_.str.startsWith(url, "http://")) {
|
||||
console.log(['Securing image url', url, secure_url]);
|
||||
|
|
|
@ -14,7 +14,7 @@ __all__ = ['Scrubber', 'SelectiveScriptScrubber', 'ScrubberWarning', 'Unapproved
|
|||
import re, string
|
||||
from urlparse import urljoin
|
||||
from itertools import chain
|
||||
from BeautifulSoup import BeautifulSoup, Comment
|
||||
from bs4 import BeautifulSoup, Comment
|
||||
|
||||
def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False):
|
||||
"""Converts any URLs in text into clickable links.
|
||||
|
@ -157,21 +157,23 @@ class Scrubber(object):
|
|||
continue
|
||||
|
||||
# Remove disallowed attributes
|
||||
attrs = []
|
||||
for k, v in node.attrs:
|
||||
if not v:
|
||||
continue
|
||||
attrs = {}
|
||||
if hasattr(node, 'attrs') and isinstance(node.attrs, dict):
|
||||
for k, v in node.attrs.items():
|
||||
if not v:
|
||||
continue
|
||||
|
||||
if k.lower() not in self.allowed_attributes:
|
||||
continue
|
||||
if k.lower() not in self.allowed_attributes:
|
||||
continue
|
||||
|
||||
# TODO: This probably needs to be more robust
|
||||
v2 = v.lower()
|
||||
if any(x in v2 for x in ('javascript:', 'vbscript:', 'expression(')):
|
||||
continue
|
||||
# TODO: This probably needs to be more robust
|
||||
if isinstance(v, str):
|
||||
v2 = v.lower()
|
||||
if any(x in v2 for x in ('javascript:', 'vbscript:', 'expression(')):
|
||||
continue
|
||||
|
||||
attrs.append((k,v))
|
||||
node.attrs = attrs
|
||||
attrs[k] = v
|
||||
node.attrs = attrs
|
||||
|
||||
self._remove_nodes(toremove)
|
||||
|
||||
|
@ -203,10 +205,10 @@ class Scrubber(object):
|
|||
|
||||
def _scrub_tag_a(self, a):
|
||||
if self.nofollow:
|
||||
a['rel'] = "nofollow"
|
||||
a['rel'] = ["nofollow"]
|
||||
|
||||
if not a.get('class', None):
|
||||
a['class'] = "external"
|
||||
a['class'] = ["external"]
|
||||
|
||||
self._clean_path(a, 'href')
|
||||
|
||||
|
@ -223,17 +225,18 @@ class Scrubber(object):
|
|||
self._clean_path(img, 'src')
|
||||
|
||||
def _scrub_tag_font(self, node):
|
||||
attrs = []
|
||||
for k, v in node.attrs:
|
||||
if k.lower() == 'size' and v.startswith('+'):
|
||||
# Remove "size=+0"
|
||||
continue
|
||||
attrs.append((k, v))
|
||||
node.attrs = attrs
|
||||
attrs = {}
|
||||
if hasattr(node, 'attrs') and isinstance(node.attrs, dict):
|
||||
for k, v in node.attrs.items():
|
||||
if k.lower() == 'size' and v.startswith('+'):
|
||||
# Remove "size=+0"
|
||||
continue
|
||||
attrs[k] = v
|
||||
node.attrs = attrs
|
||||
|
||||
if len(node.attrs) == 0:
|
||||
# IE renders font tags with no attributes differently then other browsers so remove them
|
||||
return "keep_contents"
|
||||
if len(node.attrs) == 0:
|
||||
# IE renders font tags with no attributes differently then other browsers so remove them
|
||||
return "keep_contents"
|
||||
|
||||
def _scrub_html_pre(self, html):
|
||||
"""Process the html before sanitization"""
|
||||
|
@ -251,7 +254,7 @@ class Scrubber(object):
|
|||
|
||||
toremove = []
|
||||
for tag_name, scrubbers in self.tag_scrubbers.items():
|
||||
for node in soup(tag_name):
|
||||
for node in soup.find_all(tag_name):
|
||||
for scrub in scrubbers:
|
||||
remove = scrub(node)
|
||||
if remove:
|
||||
|
@ -267,9 +270,8 @@ class Scrubber(object):
|
|||
"""Return a sanitized version of the given html."""
|
||||
|
||||
self.warnings = []
|
||||
|
||||
html = self._scrub_html_pre(html)
|
||||
soup = BeautifulSoup(html)
|
||||
soup = BeautifulSoup(html, features="lxml")
|
||||
self._scrub_soup(soup)
|
||||
html = unicode(soup)
|
||||
return self._scrub_html_post(html)
|
||||
|
|
|
@ -366,7 +366,7 @@ def htmldiff(old_html, new_html):
|
|||
return fixup_ins_del_tags(result)
|
||||
|
||||
|
||||
def create_signed_url(base_url, hmac_key, url):
|
||||
def create_camo_signed_url(base_url, hmac_key, url):
|
||||
"""Create a camo signed URL for the specified image URL
|
||||
Args:
|
||||
base_url: Base URL of the camo installation
|
||||
|
|
Loading…
Add table
Reference in a new issue