Merge branch 'master' into dejal

This commit is contained in:
David Sinclair 2022-03-07 19:19:08 -07:00
commit 6c218be586
56 changed files with 1867 additions and 896 deletions

12
.vscode/settings.json vendored
View file

@ -4,7 +4,7 @@
"python.linting.flake8Enabled": true,
"python.linting.pylamaEnabled": false,
"python.linting.flake8Args": [
"--ignore=E501,W293,W503,W504,E302,E722,E226,E221"
"--ignore=E501,W293,W503,W504,E302,E722,E226,E221,E402,E401"
],
"python.pythonPath": "~/.virtualenvs/newsblur3/bin/python",
"git.ignoreLimitWarning": true,
@ -16,7 +16,7 @@
"ansible/playbooks/*/*": true,
// "archive/*": true,
"logs/*": true,
// "static/*": true,
"static/*": true,
"media/fonts": true,
"static/*.css": true,
"static/js/*.*.js": true,
@ -25,4 +25,12 @@
"docker/volumes": true,
"requirements.txt": true, // It's just a symlink to config/requirements.txt, which has git history
},
"python.formatting.provider": "black",
"python.formatting.blackArgs": [
"--line-length=110",
"--skip-string-normalization"
],
"files.associations": {
"*.yml": "ansible"
},
}

View file

@ -21,6 +21,7 @@ rebuild:
collectstatic:
- rm -fr static
- docker pull newsblur/newsblur_deploy
- docker run --rm -v $(shell pwd):/srv/newsblur newsblur/newsblur_deploy
#creates newsblur, builds new images, and creates/refreshes SSL keys

View file

@ -13,6 +13,7 @@
- name: Update Sentry release
connection: local
run_once: yes
shell: >
curl {{ sentry_web_release_webhook }}/ \
-X POST \
@ -80,15 +81,6 @@
tags:
- never
- static
- name: Pull newsblur_web github
git:
repo: https://github.com/samuelclay/NewsBlur.git
dest: /srv/newsblur/
version: master
register: pulled
tags:
- static
- name: Downloading JS/CSS assets from S3
vars:
@ -114,8 +106,17 @@
tags:
- never
- static
- name: Pull newsblur_web github
git:
repo: https://github.com/samuelclay/NewsBlur.git
dest: /srv/newsblur/
version: master
register: pulled
tags:
- static
- name: Reload gunicorn
- name: Reload gunicorn due to no git upstream changes
become: yes
block:
- name: Find gunicorn master process
@ -123,7 +124,7 @@
register: psaux
- name: Reload gunicorn
command: "kill -HUP {{ psaux.stdout }}"
# when: pulled.changed
when: not pulled.changed
tags:
- static

View file

@ -31,7 +31,7 @@
- service_name: task-celery
- service_name: task-work
rescue:
- name: Restart celery
- name: Start celery
become: yes
command: "docker start {{ item.service_name }}"
when: item.service_name in inventory_hostname

View file

@ -10,5 +10,4 @@
dest: "/srv/newsblur/config/certificates/{{ item }}"
mode: 0400
with_items:
- aps.pem
- aps.p12.pem

View file

@ -0,0 +1,7 @@
---
- name: reload sentry
become: yes
command:
chdir: /srv/sentry/
cmd: ./install.sh
listen: reload sentry

View file

@ -4,6 +4,7 @@
repo: https://github.com/getsentry/self-hosted.git
dest: /srv/sentry/
version: master
notify: reload sentry
- name: Register sentry in consul
tags: consul

View file

@ -12,5 +12,6 @@ urlpatterns = [
url(r'^share_story/(?P<token>\w+)', views.share_story, name='api-share-story'),
url(r'^save_story/(?P<token>\w+)', views.save_story, name='api-save-story'),
url(r'^share_story/?$', views.share_story),
url(r'^save_story/?$', views.save_story),
url(r'^ip_addresses/?$', views.ip_addresses),
]

View file

@ -9,10 +9,16 @@ from django.contrib.auth.models import User
from django.contrib.sites.models import Site
from django.template.loader import render_to_string
from django.core.mail import EmailMultiAlternatives
# from django.utils.html import strip_tags
from apps.rss_feeds.models import MStory, Feed
from apps.reader.models import UserSubscription
from apps.analyzer.models import MClassifierTitle, MClassifierAuthor, MClassifierFeed, MClassifierTag
from apps.analyzer.models import (
MClassifierTitle,
MClassifierAuthor,
MClassifierFeed,
MClassifierTag,
)
from apps.analyzer.models import compute_story_score
from utils.view_functions import is_true
from utils.story_functions import truncate_chars
@ -24,6 +30,7 @@ from apns2.payload import Payload
from bs4 import BeautifulSoup
import urllib.parse
class NotificationFrequency(enum.Enum):
immediately = 1
hour_1 = 2
@ -31,20 +38,25 @@ class NotificationFrequency(enum.Enum):
hour_12 = 4
hour_24 = 5
class MUserNotificationTokens(mongo.Document):
'''A user's push notification tokens'''
user_id = mongo.IntField()
ios_tokens = mongo.ListField(mongo.StringField(max_length=1024))
use_sandbox = mongo.BooleanField(default=False)
user_id = mongo.IntField()
ios_tokens = mongo.ListField(mongo.StringField(max_length=1024))
use_sandbox = mongo.BooleanField(default=False)
meta = {
'collection': 'notification_tokens',
'indexes': [{'fields': ['user_id'],
'unique': True,
}],
'indexes': [
{
'fields': ['user_id'],
'unique': True,
}
],
'allow_inheritance': False,
}
@classmethod
def get_tokens_for_user(cls, user_id):
try:
@ -54,35 +66,43 @@ class MUserNotificationTokens(mongo.Document):
return tokens
class MUserFeedNotification(mongo.Document):
'''A user's notifications of a single feed.'''
user_id = mongo.IntField()
feed_id = mongo.IntField()
frequency = mongoengine_fields.IntEnumField(NotificationFrequency)
is_focus = mongo.BooleanField()
last_notification_date = mongo.DateTimeField(default=datetime.datetime.now)
is_email = mongo.BooleanField()
is_web = mongo.BooleanField()
is_ios = mongo.BooleanField()
is_android = mongo.BooleanField()
ios_tokens = mongo.ListField(mongo.StringField(max_length=1024))
user_id = mongo.IntField()
feed_id = mongo.IntField()
frequency = mongoengine_fields.IntEnumField(NotificationFrequency)
is_focus = mongo.BooleanField()
last_notification_date = mongo.DateTimeField(default=datetime.datetime.now)
is_email = mongo.BooleanField()
is_web = mongo.BooleanField()
is_ios = mongo.BooleanField()
is_android = mongo.BooleanField()
ios_tokens = mongo.ListField(mongo.StringField(max_length=1024))
meta = {
'collection': 'notifications',
'indexes': ['feed_id',
{'fields': ['user_id', 'feed_id'],
'unique': True,
}],
'indexes': [
'feed_id',
{
'fields': ['user_id', 'feed_id'],
'unique': True,
},
],
'allow_inheritance': False,
}
def __str__(self):
notification_types = []
if self.is_email: notification_types.append('email')
if self.is_web: notification_types.append('web')
if self.is_ios: notification_types.append('ios')
if self.is_android: notification_types.append('android')
if self.is_email:
notification_types.append('email')
if self.is_web:
notification_types.append('web')
if self.is_ios:
notification_types.append('ios')
if self.is_android:
notification_types.append('android')
return "%s/%s: %s -> %s" % (
User.objects.get(pk=self.user_id).username,
@ -90,17 +110,17 @@ class MUserFeedNotification(mongo.Document):
','.join(notification_types),
self.last_notification_date,
)
@classmethod
def feed_has_users(cls, feed_id):
return cls.users_for_feed(feed_id).count()
@classmethod
def users_for_feed(cls, feed_id):
notifications = cls.objects.filter(feed_id=feed_id)
return notifications
@classmethod
def feeds_for_user(cls, user_id):
notifications = cls.objects.filter(user_id=user_id)
@ -111,37 +131,52 @@ class MUserFeedNotification(mongo.Document):
'notification_types': [],
'notification_filter': "focus" if feed.is_focus else "unread",
}
if feed.is_email: notifications_by_feed[feed.feed_id]['notification_types'].append('email')
if feed.is_web: notifications_by_feed[feed.feed_id]['notification_types'].append('web')
if feed.is_ios: notifications_by_feed[feed.feed_id]['notification_types'].append('ios')
if feed.is_android: notifications_by_feed[feed.feed_id]['notification_types'].append('android')
if feed.is_email:
notifications_by_feed[feed.feed_id]['notification_types'].append('email')
if feed.is_web:
notifications_by_feed[feed.feed_id]['notification_types'].append('web')
if feed.is_ios:
notifications_by_feed[feed.feed_id]['notification_types'].append('ios')
if feed.is_android:
notifications_by_feed[feed.feed_id]['notification_types'].append('android')
return notifications_by_feed
@classmethod
def push_feed_notifications(cls, feed_id, new_stories, force=False):
feed = Feed.get_by_id(feed_id)
notifications = MUserFeedNotification.users_for_feed(feed.pk)
logging.debug(" ---> [%-30s] ~FCPushing out notifications to ~SB%s users~SN for ~FB~SB%s stories" % (
feed, len(notifications), new_stories))
logging.debug(
" ---> [%-30s] ~FCPushing out notifications to ~SB%s users~SN for ~FB~SB%s stories"
% (feed, len(notifications), new_stories)
)
r = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL)
latest_story_hashes = r.zrange("zF:%s" % feed.pk, -1 * new_stories, -1)
mstories = MStory.objects.filter(story_hash__in=latest_story_hashes).order_by('-story_date')
stories = Feed.format_stories(mstories)
total_sent_count = 0
for user_feed_notification in notifications:
sent_count = 0
try:
user = User.objects.get(pk=user_feed_notification.user_id)
except User.DoesNotExist:
continue
months_ago = datetime.datetime.now() - datetime.timedelta(days=90)
if user.profile.last_seen_on < months_ago:
logging.user(user, f"~FBSkipping notifications, last seen: ~SB{user.profile.last_seen_on}")
continue
last_notification_date = user_feed_notification.last_notification_date
try:
usersub = UserSubscription.objects.get(user=user_feed_notification.user_id,
feed=user_feed_notification.feed_id)
usersub = UserSubscription.objects.get(
user=user_feed_notification.user_id, feed=user_feed_notification.feed_id
)
except UserSubscription.DoesNotExist:
continue
classifiers = user_feed_notification.classifiers(usersub)
if classifiers == None:
if classifiers is None:
if settings.DEBUG:
logging.debug("Has no usersubs")
continue
@ -150,35 +185,47 @@ class MUserFeedNotification(mongo.Document):
if sent_count >= 3:
if settings.DEBUG:
logging.debug("Sent too many, ignoring...")
continue
continue
if story['story_date'] <= last_notification_date and not force:
if settings.DEBUG:
logging.debug("Story date older than last notification date: %s <= %s" % (story['story_date'], last_notification_date))
logging.debug(
"Story date older than last notification date: %s <= %s"
% (story['story_date'], last_notification_date)
)
continue
if story['story_date'] > user_feed_notification.last_notification_date:
user_feed_notification.last_notification_date = story['story_date']
user_feed_notification.save()
story['story_content'] = html.unescape(story['story_content'])
sent = user_feed_notification.push_story_notification(story, classifiers, usersub)
if sent:
if sent:
sent_count += 1
total_sent_count += 1
return total_sent_count, len(notifications)
def classifiers(self, usersub):
classifiers = {}
if usersub.is_trained:
classifiers['feeds'] = list(MClassifierFeed.objects(user_id=self.user_id, feed_id=self.feed_id,
social_user_id=0))
classifiers['authors'] = list(MClassifierAuthor.objects(user_id=self.user_id, feed_id=self.feed_id))
classifiers['titles'] = list(MClassifierTitle.objects(user_id=self.user_id, feed_id=self.feed_id))
classifiers['tags'] = list(MClassifierTag.objects(user_id=self.user_id, feed_id=self.feed_id))
classifiers['feeds'] = list(
MClassifierFeed.objects(
user_id=self.user_id, feed_id=self.feed_id, social_user_id=0
)
)
classifiers['authors'] = list(
MClassifierAuthor.objects(user_id=self.user_id, feed_id=self.feed_id)
)
classifiers['titles'] = list(
MClassifierTitle.objects(user_id=self.user_id, feed_id=self.feed_id)
)
classifiers['tags'] = list(
MClassifierTag.objects(user_id=self.user_id, feed_id=self.feed_id)
)
return classifiers
def title_and_body(self, story, usersub, notification_title_only=False):
def replace_with_newlines(element):
text = ''
@ -191,33 +238,36 @@ class MUserFeedNotification(mongo.Document):
text += '\n\n'
text = re.sub(r' +', ' ', text).strip()
return text
feed_title = usersub.user_title or usersub.feed.feed_title
# title = "%s: %s" % (feed_title, story['story_title'])
title = feed_title
soup = BeautifulSoup(story['story_content'].strip(), features="lxml")
if notification_title_only:
subtitle = None
body_title = html.unescape(story['story_title']).strip()
body_content = replace_with_newlines(soup)
if body_content:
if body_title == body_content[:len(body_title)] or body_content[:100] == body_title[:100]:
body_content = ""
else:
body_content = f"\n{body_content}"
body = f"{body_title}{body_content}"
else:
subtitle = html.unescape(story['story_title'])
body = replace_with_newlines(soup)
# if notification_title_only:
subtitle = None
body_title = html.unescape(story['story_title']).strip()
body_content = replace_with_newlines(soup)
if body_content:
if (
body_title == body_content[: len(body_title)]
or body_content[:100] == body_title[:100]
):
body_content = ""
else:
body_content = f"\n{body_content}"
body = f"{body_title}{body_content}"
# else:
# subtitle = html.unescape(story['story_title'])
# body = replace_with_newlines(soup)
body = truncate_chars(body.strip(), 600)
if not body:
body = " "
if not usersub.user.profile.is_premium:
body = "Please upgrade to a premium subscription to receive full push notifications."
return title, subtitle, body
def push_story_notification(self, story, classifiers, usersub):
story_score = self.story_score(story, classifiers)
if self.is_focus and story_score <= 0:
@ -228,49 +278,72 @@ class MUserFeedNotification(mongo.Document):
if settings.DEBUG:
logging.debug("Is unread, but story is hidden")
return False
user = User.objects.get(pk=self.user_id)
logging.user(user, "~FCSending push notification: %s/%s (score: %s)" % (story['story_title'][:40], story['story_hash'], story_score))
logging.user(
user,
"~FCSending push notification: %s/%s (score: %s)"
% (story['story_title'][:40], story['story_hash'], story_score),
)
self.send_web(story, user)
self.send_ios(story, user, usersub)
self.send_android(story)
self.send_email(story, usersub)
return True
def send_web(self, story, user):
if not self.is_web: return
if not self.is_web:
return
r = redis.Redis(connection_pool=settings.REDIS_PUBSUB_POOL)
r.publish(user.username, 'notification:%s,%s' % (story['story_hash'], story['story_title']))
def send_ios(self, story, user, usersub):
if not self.is_ios: return
if not self.is_ios:
return
tokens = MUserNotificationTokens.get_tokens_for_user(self.user_id)
apns = APNsClient('/srv/newsblur/config/certificates/aps.p12.pem', use_sandbox=tokens.use_sandbox)
# To update APNS:
# 1. Create certificate signing requeswt in Keychain Access
# 2. Upload to https://developer.apple.com/account/resources/certificates/list
# 3. Download to secrets/certificates/ios/aps.cer
# 4. Open in Keychain Access and export as aps.p12
# 4. Export private key as aps_key.p12 WITH A PASSPHRASE (removed later)
# 5. openssl pkcs12 -in aps.p12 -out aps.pem -nodes -clcerts -nokeys
# 6. openssl pkcs12 -clcerts -nokeys -out aps.pem -in aps.p12
# 7. cat aps.pem aps_key.noenc.pem > aps.p12.pem
# 8. Verify: openssl s_client -connect gateway.push.apple.com:2195 -cert aps.p12.pem
# 9. Deploy: aps -l work -t apns,repo,celery
apns = APNsClient(
'/srv/newsblur/config/certificates/aps.p12.pem', use_sandbox=tokens.use_sandbox
)
notification_title_only = is_true(user.profile.preference_value('notification_title_only'))
title, subtitle, body = self.title_and_body(story, usersub, notification_title_only)
image_url = None
if len(story['image_urls']):
image_url = story['image_urls'][0]
# print image_url
confirmed_ios_tokens = []
for token in tokens.ios_tokens:
logging.user(user, '~BMStory notification by iOS: ~FY~SB%s~SN~BM~FY/~SB%s' %
(story['story_title'][:50], usersub.feed.feed_title[:50]))
payload = Payload(alert={'title': title,
'subtitle': subtitle,
'body': body},
category="STORY_CATEGORY",
mutable_content=True,
custom={'story_hash': story['story_hash'],
'story_feed_id': story['story_feed_id'],
'image_url': image_url,
})
logging.user(
user,
'~BMStory notification by iOS: ~FY~SB%s~SN~BM~FY/~SB%s'
% (story['story_title'][:50], usersub.feed.feed_title[:50]),
)
payload = Payload(
alert={'title': title, 'subtitle': subtitle, 'body': body},
category="STORY_CATEGORY",
mutable_content=True,
custom={
'story_hash': story['story_hash'],
'story_feed_id': story['story_feed_id'],
'image_url': image_url,
},
)
try:
apns.send_notification(token, payload, topic="com.newsblur.NewsBlur")
except (BadDeviceToken, Unregistered):
@ -278,64 +351,77 @@ class MUserFeedNotification(mongo.Document):
else:
confirmed_ios_tokens.append(token)
if settings.DEBUG:
logging.user(user, '~BMiOS token good: ~FB~SB%s / %s' % (token[:50], len(confirmed_ios_tokens)))
logging.user(
user,
'~BMiOS token good: ~FB~SB%s / %s'
% (token[:50], len(confirmed_ios_tokens)),
)
if len(confirmed_ios_tokens) < len(tokens.ios_tokens):
tokens.ios_tokens = confirmed_ios_tokens
tokens.save()
def send_android(self, story):
if not self.is_android: return
if not self.is_android:
return
def send_email(self, story, usersub):
if not self.is_email: return
if not self.is_email:
return
feed = usersub.feed
story_content = self.sanitize_story(story['story_content'])
params = {
params = {
"story": story,
"story_content": story_content,
"feed": feed,
"feed_title": usersub.user_title or feed.feed_title,
"favicon_border": feed.favicon_color,
}
from_address = 'share@newsblur.com'
from_address = 'notifications@newsblur.com'
to_address = '%s <%s>' % (usersub.user.username, usersub.user.email)
text = render_to_string('mail/email_story_notification.txt', params)
html = render_to_string('mail/email_story_notification.xhtml', params)
text = render_to_string('mail/email_story_notification.txt', params)
html = render_to_string('mail/email_story_notification.xhtml', params)
subject = '%s: %s' % (usersub.user_title or usersub.feed.feed_title, story['story_title'])
subject = subject.replace('\n', ' ')
msg = EmailMultiAlternatives(subject, text,
from_email='NewsBlur <%s>' % from_address,
to=[to_address])
msg = EmailMultiAlternatives(
subject, text, from_email='NewsBlur <%s>' % from_address, to=[to_address]
)
msg.attach_alternative(html, "text/html")
# try:
msg.send()
# except BotoServerError as e:
# logging.user(usersub.user, '~BMStory notification by email error: ~FR%s' % e)
# return
logging.user(usersub.user, '~BMStory notification by email: ~FY~SB%s~SN~BM~FY/~SB%s' %
(story['story_title'][:50], usersub.feed.feed_title[:50]))
logging.user(
usersub.user,
'~BMStory notification by email: ~FY~SB%s~SN~BM~FY/~SB%s'
% (story['story_title'][:50], usersub.feed.feed_title[:50]),
)
def sanitize_story(self, story_content):
soup = BeautifulSoup(story_content.strip(), features="lxml")
fqdn = Site.objects.get_current().domain
# Convert videos in newsletters to images
for iframe in soup("iframe"):
url = dict(iframe.attrs).get('src', "")
youtube_id = self.extract_youtube_id(url)
if youtube_id:
a = soup.new_tag('a', href=url)
img = soup.new_tag('img', style="display: block; 'background-image': \"url(https://%s/img/reader/youtube_play.png), url(http://img.youtube.com/vi/%s/0.jpg)\"" % (fqdn, youtube_id), src='http://img.youtube.com/vi/%s/0.jpg' % youtube_id)
img = soup.new_tag(
'img',
style="display: block; 'background-image': \"url(https://%s/img/reader/youtube_play.png), url(http://img.youtube.com/vi/%s/0.jpg)\""
% (fqdn, youtube_id),
src='http://img.youtube.com/vi/%s/0.jpg' % youtube_id,
)
a.insert(0, img)
iframe.replaceWith(a)
else:
iframe.extract()
return str(soup)
def extract_youtube_id(self, url):
youtube_id = None
@ -343,14 +429,16 @@ class MUserFeedNotification(mongo.Document):
youtube_parts = urllib.parse.urlparse(url)
if '/embed/' in youtube_parts.path:
youtube_id = youtube_parts.path.replace('/embed/', '')
return youtube_id
def story_score(self, story, classifiers):
score = compute_story_score(story, classifier_titles=classifiers.get('titles', []),
classifier_authors=classifiers.get('authors', []),
classifier_tags=classifiers.get('tags', []),
classifier_feeds=classifiers.get('feeds', []))
score = compute_story_score(
story,
classifier_titles=classifiers.get('titles', []),
classifier_authors=classifiers.get('authors', []),
classifier_tags=classifiers.get('tags', []),
classifier_feeds=classifiers.get('feeds', []),
)
return score

View file

@ -11,32 +11,41 @@ from django.template import Template, Context
from apps.statistics.rstats import round_time
from utils import json_functions as json
class LastSeenMiddleware(object):
def __init__(self, get_response=None):
self.get_response = get_response
def process_response(self, request, response):
if ((request.path == '/' or
request.path.startswith('/reader/refresh_feeds') or
request.path.startswith('/reader/load_feeds') or
request.path.startswith('/reader/feeds'))
if (
(
request.path == '/'
or request.path.startswith('/reader/refresh_feeds')
or request.path.startswith('/reader/load_feeds')
or request.path.startswith('/reader/feeds')
)
and hasattr(request, 'user')
and request.user.is_authenticated):
and request.user.is_authenticated
):
hour_ago = datetime.datetime.utcnow() - datetime.timedelta(minutes=60)
ip = request.META.get('HTTP_X_FORWARDED_FOR', None) or request.META['REMOTE_ADDR']
if request.user.profile.last_seen_on < hour_ago:
logging.user(request, "~FG~BBRepeat visitor: ~SB%s (%s)" % (
request.user.profile.last_seen_on, ip))
logging.user(
request, "~FG~BBRepeat visitor: ~SB%s (%s)" % (request.user.profile.last_seen_on, ip)
)
from apps.profile.tasks import CleanupUser
CleanupUser.delay(user_id=request.user.pk)
elif settings.DEBUG:
logging.user(request, "~FG~BBRepeat visitor (ignored): ~SB%s (%s)" % (
request.user.profile.last_seen_on, ip))
logging.user(
request,
"~FG~BBRepeat visitor (ignored): ~SB%s (%s)" % (request.user.profile.last_seen_on, ip),
)
request.user.profile.last_seen_on = datetime.datetime.utcnow()
request.user.profile.last_seen_ip = ip[-15:]
request.user.profile.save()
return response
def __call__(self, request):
@ -50,29 +59,31 @@ class LastSeenMiddleware(object):
return response
class DBProfilerMiddleware:
def __init__(self, get_response=None):
self.get_response = get_response
def process_request(self, request):
def process_request(self, request):
setattr(request, 'activated_segments', [])
if ((request.path.startswith('/reader/feed') or
request.path.startswith('/reader/river')) and
random.random() < .01):
if (
# request.path.startswith('/reader/feed') or
request.path.startswith('/reader/feed/')
) and random.random() < 0.01:
request.activated_segments.append('db_profiler')
connection.use_debug_cursor = True
setattr(settings, 'ORIGINAL_DEBUG', settings.DEBUG)
settings.DEBUG = True
def process_celery(self):
setattr(self, 'activated_segments', [])
if random.random() < .01:
def process_celery(self):
setattr(self, 'activated_segments', [])
if random.random() < 0.01:
self.activated_segments.append('db_profiler')
connection.use_debug_cursor = True
setattr(settings, 'ORIGINAL_DEBUG', settings.DEBUG)
settings.DEBUG = True
return self
def process_exception(self, request, exception):
if hasattr(request, 'sql_times_elapsed'):
self._save_times(request.sql_times_elapsed)
@ -84,25 +95,25 @@ class DBProfilerMiddleware:
# logging.debug(" ---> ~FGProfiling~FB app: %s" % request.sql_times_elapsed)
self._save_times(request.sql_times_elapsed)
return response
def process_celery_finished(self):
middleware = SQLLogToConsoleMiddleware()
middleware.process_celery(self)
if hasattr(self, 'sql_times_elapsed'):
logging.debug(" ---> ~FGProfiling~FB task: %s" % self.sql_times_elapsed)
self._save_times(self.sql_times_elapsed, 'task_')
def process_request_finished(self):
middleware = SQLLogToConsoleMiddleware()
middleware.process_celery(self)
if hasattr(self, 'sql_times_elapsed'):
logging.debug(" ---> ~FGProfiling~FB app: %s" % self.sql_times_elapsed)
self._save_times(self.sql_times_elapsed, 'app_')
def _save_times(self, db_times, prefix=""):
if not db_times:
if not db_times:
return
r = redis.Redis(connection_pool=settings.REDIS_STATISTICS_POOL)
pipe = r.pipeline()
minute = round_time(round_to=60)
@ -126,17 +137,19 @@ class DBProfilerMiddleware:
return response
class SQLLogToConsoleMiddleware:
def __init__(self, get_response=None):
self.get_response = get_response
def activated(self, request):
return (settings.DEBUG_QUERIES or
(hasattr(request, 'activated_segments') and
'db_profiler' in request.activated_segments))
return settings.DEBUG_QUERIES or (
hasattr(request, 'activated_segments') and 'db_profiler' in request.activated_segments
)
def process_response(self, request, response):
if not self.activated(request): return response
def process_response(self, request, response):
if not self.activated(request):
return response
if connection.queries:
time_elapsed = sum([float(q['time']) for q in connection.queries])
queries = connection.queries
@ -164,20 +177,37 @@ class SQLLogToConsoleMiddleware:
query['sql'] = re.sub(r'INSERT', '~FGINSERT', query['sql'])
query['sql'] = re.sub(r'UPDATE', '~FY~SBUPDATE', query['sql'])
query['sql'] = re.sub(r'DELETE', '~FR~SBDELETE', query['sql'])
if settings.DEBUG and settings.DEBUG_QUERIES and not getattr(settings, 'DEBUG_QUERIES_SUMMARY_ONLY', False):
t = Template("{% for sql in sqllog %}{% if not forloop.first %} {% endif %}[{{forloop.counter}}] ~FC{{sql.time}}s~FW: {{sql.sql|safe}}{% if not forloop.last %}\n{% endif %}{% endfor %}")
logging.debug(t.render(Context({
'sqllog': queries,
'count': len(queries),
'time': time_elapsed,
})))
if (
settings.DEBUG
and settings.DEBUG_QUERIES
and not getattr(settings, 'DEBUG_QUERIES_SUMMARY_ONLY', False)
):
t = Template(
"{% for sql in sqllog %}{% if not forloop.first %} {% endif %}[{{forloop.counter}}] ~FC{{sql.time}}s~FW: {{sql.sql|safe}}{% if not forloop.last %}\n{% endif %}{% endfor %}"
)
logging.debug(
t.render(
Context(
{
'sqllog': queries,
'count': len(queries),
'time': time_elapsed,
}
)
)
)
times_elapsed = {
'sql': sum([float(q['time'])
for q in queries if not q.get('mongo') and
not q.get('redis_user') and
not q.get('redis_story') and
not q.get('redis_session') and
not q.get('redis_pubsub')]),
'sql': sum(
[
float(q['time'])
for q in queries
if not q.get('mongo')
and not q.get('redis_user')
and not q.get('redis_story')
and not q.get('redis_session')
and not q.get('redis_pubsub')
]
),
'mongo': sum([float(q['time']) for q in queries if q.get('mongo')]),
'redis_user': sum([float(q['time']) for q in queries if q.get('redis_user')]),
'redis_story': sum([float(q['time']) for q in queries if q.get('redis_story')]),
@ -191,7 +221,7 @@ class SQLLogToConsoleMiddleware:
settings.DEBUG = False
return response
def process_celery(self, profiler):
self.process_response(profiler, None)
if not getattr(settings, 'ORIGINAL_DEBUG', settings.DEBUG):
@ -208,110 +238,247 @@ class SQLLogToConsoleMiddleware:
return response
SIMPSONS_QUOTES = [
("Homer", "D'oh."),
("Ralph", "Me fail English? That's unpossible."),
("Lionel Hutz", "This is the greatest case of false advertising I've seen since I sued the movie \"The Never Ending Story.\""),
(
"Lionel Hutz",
"This is the greatest case of false advertising I've seen since I sued the movie \"The Never Ending Story.\"",
),
("Sideshow Bob", "No children have ever meddled with the Republican Party and lived to tell about it."),
("Troy McClure", "Don't kid yourself, Jimmy. If a cow ever got the chance, he'd eat you and everyone you care about!"),
(
"Troy McClure",
"Don't kid yourself, Jimmy. If a cow ever got the chance, he'd eat you and everyone you care about!",
),
("Comic Book Guy", "The Internet King? I wonder if he could provide faster nudity..."),
("Homer", "Oh, so they have Internet on computers now!"),
("Ned Flanders", "I've done everything the Bible says - even the stuff that contradicts the other stuff!"),
("Comic Book Guy", "Your questions have become more redundant and annoying than the last three \"Highlander\" movies."),
(
"Ned Flanders",
"I've done everything the Bible says - even the stuff that contradicts the other stuff!",
),
(
"Comic Book Guy",
"Your questions have become more redundant and annoying than the last three \"Highlander\" movies.",
),
("Chief Wiggum", "Uh, no, you got the wrong number. This is 9-1...2."),
("Sideshow Bob", "I'll be back. You can't keep the Democrats out of the White House forever, and when they get in, I'm back on the streets, with all my criminal buddies."),
("Homer", "When I held that gun in my hand, I felt a surge of power...like God must feel when he's holding a gun."),
("Nelson", "Dad didn't leave... When he comes back from the store, he's going to wave those pop-tarts right in your face!"),
("Milhouse", "Remember the time he ate my goldfish? And you lied and said I never had goldfish. Then why did I have the bowl, Bart? *Why did I have the bowl?*"),
("Lionel Hutz", "Well, he's kind of had it in for me ever since I accidentally ran over his dog. Actually, replace \"accidentally\" with \"repeatedly\" and replace \"dog\" with \"son.\""),
("Comic Book Guy", "Last night's \"Itchy and Scratchy Show\" was, without a doubt, the worst episode *ever.* Rest assured, I was on the Internet within minutes, registering my disgust throughout the world."),
(
"Sideshow Bob",
"I'll be back. You can't keep the Democrats out of the White House forever, and when they get in, I'm back on the streets, with all my criminal buddies.",
),
(
"Homer",
"When I held that gun in my hand, I felt a surge of power...like God must feel when he's holding a gun.",
),
(
"Nelson",
"Dad didn't leave... When he comes back from the store, he's going to wave those pop-tarts right in your face!",
),
(
"Milhouse",
"Remember the time he ate my goldfish? And you lied and said I never had goldfish. Then why did I have the bowl, Bart? *Why did I have the bowl?*",
),
(
"Lionel Hutz",
"Well, he's kind of had it in for me ever since I accidentally ran over his dog. Actually, replace \"accidentally\" with \"repeatedly\" and replace \"dog\" with \"son.\"",
),
(
"Comic Book Guy",
"Last night's \"Itchy and Scratchy Show\" was, without a doubt, the worst episode *ever.* Rest assured, I was on the Internet within minutes, registering my disgust throughout the world.",
),
("Homer", "I'm normally not a praying man, but if you're up there, please save me, Superman."),
("Homer", "Save me, Jeebus."),
("Mayor Quimby", "I stand by my racial slur."),
("Comic Book Guy", "Oh, loneliness and cheeseburgers are a dangerous mix."),
("Homer", "You don't like your job, you don't strike. You go in every day and do it really half-assed. That's the American way."),
("Chief Wiggum", "Fat Tony is a cancer on this fair city! He is the cancer and I am the...uh...what cures cancer?"),
("Homer", "Bart, with $10,000 we'd be millionaires! We could buy all kinds of useful things like...love!"),
(
"Homer",
"You don't like your job, you don't strike. You go in every day and do it really half-assed. That's the American way.",
),
(
"Chief Wiggum",
"Fat Tony is a cancer on this fair city! He is the cancer and I am the...uh...what cures cancer?",
),
(
"Homer",
"Bart, with $10,000 we'd be millionaires! We could buy all kinds of useful things like...love!",
),
("Homer", "Fame was like a drug. But what was even more like a drug were the drugs."),
("Homer", "Books are useless! I only ever read one book, \"To Kill A Mockingbird,\" and it gave me absolutely no insight on how to kill mockingbirds! Sure it taught me not to judge a man by the color of his skin...but what good does *that* do me?"),
("Chief Wiggum", "Can't you people take the law into your own hands? I mean, we can't be policing the entire city!"),
("Homer", "Weaseling out of things is important to learn. It's what separates us from the animals...except the weasel."),
("Reverend Lovejoy", "Marge, just about everything's a sin. [holds up a Bible] Y'ever sat down and read this thing? Technically we're not supposed to go to the bathroom."),
("Homer", "You know, the one with all the well meaning rules that don't work out in real life, uh, Christianity."),
(
"Homer",
"Books are useless! I only ever read one book, \"To Kill A Mockingbird,\" and it gave me absolutely no insight on how to kill mockingbirds! Sure it taught me not to judge a man by the color of his skin...but what good does *that* do me?",
),
(
"Chief Wiggum",
"Can't you people take the law into your own hands? I mean, we can't be policing the entire city!",
),
(
"Homer",
"Weaseling out of things is important to learn. It's what separates us from the animals...except the weasel.",
),
(
"Reverend Lovejoy",
"Marge, just about everything's a sin. [holds up a Bible] Y'ever sat down and read this thing? Technically we're not supposed to go to the bathroom.",
),
(
"Homer",
"You know, the one with all the well meaning rules that don't work out in real life, uh, Christianity.",
),
("Smithers", "Uh, no, they're saying \"Boo-urns, Boo-urns.\""),
("Hans Moleman", "I was saying \"Boo-urns.\""),
("Homer", "Kids, you tried your best and you failed miserably. The lesson is, never try."),
("Homer", "Here's to alcohol, the cause of - and solution to - all life's problems."),
("Homer", "When will I learn? The answers to life's problems aren't at the bottom of a bottle, they're on TV!"),
(
"Homer",
"When will I learn? The answers to life's problems aren't at the bottom of a bottle, they're on TV!",
),
("Chief Wiggum", "I hope this has taught you kids a lesson: kids never learn."),
("Homer", "How is education supposed to make me feel smarter? Besides, every time I learn something new, it pushes some old stuff out of my brain. Remember when I took that home winemaking course, and I forgot how to drive?"),
(
"Homer",
"How is education supposed to make me feel smarter? Besides, every time I learn something new, it pushes some old stuff out of my brain. Remember when I took that home winemaking course, and I forgot how to drive?",
),
("Homer", "Homer no function beer well without."),
("Duffman", "Duffman can't breathe! OH NO!"),
("Grandpa Simpson", "Dear Mr. President, There are too many states nowadays. Please, eliminate three. P.S. I am not a crackpot."),
("Homer", "Old people don't need companionship. They need to be isolated and studied so it can be determined what nutrients they have that might be extracted for our personal use."),
("Troy McClure", "Hi. I'm Troy McClure. You may remember me from such self-help tapes as \"Smoke Yourself Thin\" and \"Get Some Confidence, Stupid!\""),
(
"Grandpa Simpson",
"Dear Mr. President, There are too many states nowadays. Please, eliminate three. P.S. I am not a crackpot.",
),
(
"Homer",
"Old people don't need companionship. They need to be isolated and studied so it can be determined what nutrients they have that might be extracted for our personal use.",
),
(
"Troy McClure",
"Hi. I'm Troy McClure. You may remember me from such self-help tapes as \"Smoke Yourself Thin\" and \"Get Some Confidence, Stupid!\"",
),
("Homer", "A woman is a lot like a refrigerator. Six feet tall, 300 pounds...it makes ice."),
("Homer", "Son, a woman is like a beer. They smell good, they look good, you'd step over your own mother just to get one! But you can't stop at one. You wanna drink another woman!"),
(
"Homer",
"Son, a woman is like a beer. They smell good, they look good, you'd step over your own mother just to get one! But you can't stop at one. You wanna drink another woman!",
),
("Homer", "Facts are meaningless. You could use facts to prove anything that's even remotely true!"),
("Mr Burns", "I'll keep it short and sweet - Family. Religion. Friendship. These are the three demons you must slay if you wish to succeed in business."),
("Kent Brockman", "...And the fluffy kitten played with that ball of string all through the night. On a lighter note, a Kwik-E-Mart clerk was brutally murdered last night."),
("Ralph", "Mrs. Krabappel and Principal Skinner were in the closet making babies and I saw one of the babies and then the baby looked at me."),
(
"Mr Burns",
"I'll keep it short and sweet - Family. Religion. Friendship. These are the three demons you must slay if you wish to succeed in business.",
),
(
"Kent Brockman",
"...And the fluffy kitten played with that ball of string all through the night. On a lighter note, a Kwik-E-Mart clerk was brutally murdered last night.",
),
(
"Ralph",
"Mrs. Krabappel and Principal Skinner were in the closet making babies and I saw one of the babies and then the baby looked at me.",
),
("Apu", "Please do not offer my god a peanut."),
("Homer", "You don't win friends with salad."),
("Mr Burns", "I don't like being outdoors, Smithers. For one thing, there's too many fat children."),
("Sideshow Bob", "Attempted murder? Now honestly, what is that? Do they give a Nobel Prize for attempted chemistry?"),
(
"Sideshow Bob",
"Attempted murder? Now honestly, what is that? Do they give a Nobel Prize for attempted chemistry?",
),
("Chief Wiggum", "They only come out in the night. Or in this case, the day."),
("Mr Burns", "Whoa, slow down there, maestro. There's a *New* Mexico?"),
("Homer", "He didn't give you gay, did he? Did he?!"),
("Comic Book Guy", "But, Aquaman, you cannot marry a woman without gills. You're from two different worlds... Oh, I've wasted my life."),
(
"Comic Book Guy",
"But, Aquaman, you cannot marry a woman without gills. You're from two different worlds... Oh, I've wasted my life.",
),
("Homer", "Marge, it takes two to lie. One to lie and one to listen."),
("Superintendent Chalmers", "I've had it with this school, Skinner. Low test scores, class after class of ugly, ugly children..."),
(
"Superintendent Chalmers",
"I've had it with this school, Skinner. Low test scores, class after class of ugly, ugly children...",
),
("Mr Burns", "What good is money if it can't inspire terror in your fellow man?"),
("Homer", "Oh, everything looks bad if you remember it."),
("Ralph", "Slow down, Bart! My legs don't know how to be as long as yours."),
("Homer", "Donuts. Is there anything they can't do?"),
("Frink", "Brace yourselves gentlemen. According to the gas chromatograph, the secret ingredient is... Love!? Who's been screwing with this thing?"),
("Apu", "Yes! I am a citizen! Now which way to the welfare office? I'm kidding, I'm kidding. I work, I work."),
(
"Frink",
"Brace yourselves gentlemen. According to the gas chromatograph, the secret ingredient is... Love!? Who's been screwing with this thing?",
),
(
"Apu",
"Yes! I am a citizen! Now which way to the welfare office? I'm kidding, I'm kidding. I work, I work.",
),
("Milhouse", "We started out like Romeo and Juliet, but it ended up in tragedy."),
("Mr Burns", "A lifetime of working with nuclear power has left me with a healthy green glow...and left me as impotent as a Nevada boxing commissioner."),
(
"Mr Burns",
"A lifetime of working with nuclear power has left me with a healthy green glow...and left me as impotent as a Nevada boxing commissioner.",
),
("Homer", "Kids, kids. I'm not going to die. That only happens to bad people."),
("Milhouse", "Look out, Itchy! He's Irish!"),
("Homer", "I'm going to the back seat of my car, with the woman I love, and I won't be back for ten minutes!"),
(
"Homer",
"I'm going to the back seat of my car, with the woman I love, and I won't be back for ten minutes!",
),
("Smithers", "I'm allergic to bee stings. They cause me to, uh, die."),
("Barney", "Aaah! Natural light! Get it off me! Get it off me!"),
("Principal Skinner", "That's why I love elementary school, Edna. The children believe anything you tell them."),
("Sideshow Bob", "Your guilty consciences may make you vote Democratic, but secretly you all yearn for a Republican president to lower taxes, brutalize criminals, and rule you like a king!"),
(
"Principal Skinner",
"That's why I love elementary school, Edna. The children believe anything you tell them.",
),
(
"Sideshow Bob",
"Your guilty consciences may make you vote Democratic, but secretly you all yearn for a Republican president to lower taxes, brutalize criminals, and rule you like a king!",
),
("Barney", "Jesus must be spinning in his grave!"),
("Superintendent Chalmers", "\"Thank the Lord\"? That sounded like a prayer. A prayer in a public school. God has no place within these walls, just like facts don't have a place within an organized religion."),
(
"Superintendent Chalmers",
"\"Thank the Lord\"? That sounded like a prayer. A prayer in a public school. God has no place within these walls, just like facts don't have a place within an organized religion.",
),
("Mr Burns", "[answering the phone] Ahoy hoy?"),
("Comic Book Guy", "Oh, a *sarcasm* detector. Oh, that's a *really* useful invention!"),
("Marge", "Our differences are only skin deep, but our sames go down to the bone."),
("Homer", "What's the point of going out? We're just going to wind up back here anyway."),
("Marge", "Get ready, skanks! It's time for the truth train!"),
("Bill Gates", "I didn't get rich by signing checks."),
("Principal Skinner", "Fire can be our friend; whether it's toasting marshmallows or raining down on Charlie."),
("Homer", "Oh, I'm in no condition to drive. Wait a minute. I don't have to listen to myself. I'm drunk."),
(
"Principal Skinner",
"Fire can be our friend; whether it's toasting marshmallows or raining down on Charlie.",
),
(
"Homer",
"Oh, I'm in no condition to drive. Wait a minute. I don't have to listen to myself. I'm drunk.",
),
("Homer", "And here I am using my own lungs like a sucker."),
("Comic Book Guy", "Human contact: the final frontier."),
("Homer", "I hope I didn't brain my damage."),
("Krusty the Clown", "And now, in the spirit of the season: start shopping. And for every dollar of Krusty merchandise you buy, I will be nice to a sick kid. For legal purposes, sick kids may include hookers with a cold."),
(
"Krusty the Clown",
"And now, in the spirit of the season: start shopping. And for every dollar of Krusty merchandise you buy, I will be nice to a sick kid. For legal purposes, sick kids may include hookers with a cold.",
),
("Homer", "I'm a Spalding Gray in a Rick Dees world."),
("Dr Nick", "Inflammable means flammable? What a country."),
("Homer", "Beer. Now there's a temporary solution."),
("Comic Book Guy", "Stan Lee never left. I'm afraid his mind is no longer in mint condition."),
("Nelson", "Shoplifting is a victimless crime. Like punching someone in the dark."),
("Krusty the Clown", "Kids, we need to talk for a moment about Krusty Brand Chew Goo Gum Like Substance. We all knew it contained spider eggs, but the hantavirus? That came out of left field. So if you're experiencing numbness and/or comas, send five dollars to antidote, PO box..."),
(
"Krusty the Clown",
"Kids, we need to talk for a moment about Krusty Brand Chew Goo Gum Like Substance. We all knew it contained spider eggs, but the hantavirus? That came out of left field. So if you're experiencing numbness and/or comas, send five dollars to antidote, PO box...",
),
("Milhouse", "I can't go to juvie. They use guys like me as currency."),
("Homer", "Son, when you participate in sporting events, it's not whether you win or lose: it's how drunk you get."),
(
"Homer",
"Son, when you participate in sporting events, it's not whether you win or lose: it's how drunk you get.",
),
("Homer", "I like my beer cold, my TV loud and my homosexuals flaming."),
("Apu", "Thank you, steal again."),
("Homer", "Marge, you being a cop makes you the man! Which makes me the woman - and I have no interest in that, besides occasionally wearing the underwear, which as we discussed, is strictly a comfort thing."),
("Ed Begley Jr", "I prefer a vehicle that doesn't hurt Mother Earth. It's a go-cart, powered by my own sense of self-satisfaction."),
(
"Homer",
"Marge, you being a cop makes you the man! Which makes me the woman - and I have no interest in that, besides occasionally wearing the underwear, which as we discussed, is strictly a comfort thing.",
),
(
"Ed Begley Jr",
"I prefer a vehicle that doesn't hurt Mother Earth. It's a go-cart, powered by my own sense of self-satisfaction.",
),
("Bart", "I didn't think it was physically possible, but this both sucks *and* blows."),
("Homer", "How could you?! Haven't you learned anything from that guy who gives those sermons at church? Captain Whatshisname? We live in a society of laws! Why do you think I took you to all those Police Academy movies? For fun? Well, I didn't hear anybody laughing, did you? Except at that guy who made sound effects. Makes sound effects and laughs. Where was I? Oh yeah! Stay out of my booze."),
(
"Homer",
"How could you?! Haven't you learned anything from that guy who gives those sermons at church? Captain Whatshisname? We live in a society of laws! Why do you think I took you to all those Police Academy movies? For fun? Well, I didn't hear anybody laughing, did you? Except at that guy who made sound effects. Makes sound effects and laughs. Where was I? Oh yeah! Stay out of my booze.",
),
("Homer", "Lisa, vampires are make-believe, like elves, gremlins, and Eskimos."),
]
class SimpsonsMiddleware:
def __init__(self, get_response=None):
self.get_response = get_response
@ -333,9 +500,9 @@ class SimpsonsMiddleware:
response = self.process_response(request, response)
return response
class ServerHostnameMiddleware:
class ServerHostnameMiddleware:
def __init__(self, get_response=None):
self.get_response = get_response
@ -355,8 +522,8 @@ class ServerHostnameMiddleware:
return response
class TimingMiddleware:
class TimingMiddleware:
def __init__(self, get_response=None):
self.get_response = get_response
@ -369,13 +536,15 @@ class TimingMiddleware:
response = self.get_response(request)
return response
BANNED_USER_AGENTS = (
'feed reader-background',
'missing',
)
BANNED_USERNAMES = (
)
BANNED_USERNAMES = ()
class UserAgentBanMiddleware:
def __init__(self, get_response=None):
@ -383,31 +552,38 @@ class UserAgentBanMiddleware:
def process_request(self, request):
user_agent = request.environ.get('HTTP_USER_AGENT', 'missing').lower()
if 'profile' in request.path: return
if 'haproxy' in request.path: return
if 'dbcheck' in request.path: return
if 'account' in request.path: return
if 'push' in request.path: return
if getattr(settings, 'TEST_DEBUG'): return
if 'profile' in request.path:
return
if 'haproxy' in request.path:
return
if 'dbcheck' in request.path:
return
if 'account' in request.path:
return
if 'push' in request.path:
return
if getattr(settings, 'TEST_DEBUG'):
return
if any(ua in user_agent for ua in BANNED_USER_AGENTS):
data = {
'error': 'User agent banned: %s' % user_agent,
'code': -1
}
logging.user(request, "~FB~SN~BBBanned UA: ~SB%s / %s (%s)" % (user_agent, request.path, request.META))
data = {'error': 'User agent banned: %s' % user_agent, 'code': -1}
logging.user(
request, "~FB~SN~BBBanned UA: ~SB%s / %s (%s)" % (user_agent, request.path, request.META)
)
return HttpResponse(json.encode(data), status=403, content_type='text/json')
if request.user.is_authenticated and any(username == request.user.username for username in BANNED_USERNAMES):
data = {
'error': 'User banned: %s' % request.user.username,
'code': -1
}
logging.user(request, "~FB~SN~BBBanned Username: ~SB%s / %s (%s)" % (request.user, request.path, request.META))
if request.user.is_authenticated and any(
username == request.user.username for username in BANNED_USERNAMES
):
data = {'error': 'User banned: %s' % request.user.username, 'code': -1}
logging.user(
request,
"~FB~SN~BBBanned Username: ~SB%s / %s (%s)" % (request.user, request.path, request.META),
)
return HttpResponse(json.encode(data), status=403, content_type='text/json')
def __call__(self, request):
response = None
if hasattr(self, 'process_request'):
@ -418,4 +594,3 @@ class UserAgentBanMiddleware:
response = self.process_response(request, response)
return response

View file

@ -89,8 +89,8 @@ class Profile(models.Model):
self.secret_token = generate_secret_token(self.user.username, 12)
try:
super(Profile, self).save(*args, **kwargs)
except DatabaseError:
print(" ---> Profile not saved. Table isn't there yet.")
except DatabaseError as e:
print(f" ---> Profile not saved: {e}")
def delete_user(self, confirm=False, fast=False):
if not confirm:

View file

@ -25,7 +25,6 @@ from django.contrib.auth import logout as logout_user
from django.contrib.auth.models import User
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, Http404, UnreadablePostError
from django.conf import settings
from django.core.mail import mail_admins
from django.core.mail import EmailMultiAlternatives
from django.core.validators import validate_email
from django.contrib.sites.models import Site
@ -76,7 +75,7 @@ BANNED_URLS = [
ALLOWED_SUBDOMAINS = [
'dev',
'www',
'beta',
# 'beta', # Comment to redirect beta -> www, uncomment to allow beta -> staging (+ dns changes)
'staging',
'discovery',
'debug',
@ -1974,42 +1973,57 @@ def mark_story_as_unread(request):
@required_params('story_hash')
def mark_story_hash_as_unread(request):
r = redis.Redis(connection_pool=settings.REDIS_PUBSUB_POOL)
story_hash = request.POST.get('story_hash')
feed_id, _ = MStory.split_story_hash(story_hash)
story, _ = MStory.find_story(feed_id, story_hash)
if not story:
data = dict(code=-1, message="That story has been removed from the feed, no need to mark it unread.")
return data
message = RUserStory.story_can_be_marked_read_by_user(story, request.user)
if message:
data = dict(code=-1, message=message)
return data
# Also count on original subscription
usersubs = UserSubscription.objects.filter(user=request.user.pk, feed=feed_id)
if usersubs:
usersub = usersubs[0]
if not usersub.needs_unread_recalc:
usersub.needs_unread_recalc = True
usersub.save(update_fields=['needs_unread_recalc'])
data = usersub.invert_read_stories_after_unread_story(story, request)
r.publish(request.user.username, 'feed:%s' % feed_id)
story_hashes = request.POST.getlist('story_hash') or request.POST.getlist('story_hash[]')
is_list = len(story_hashes) > 1
datas = []
for story_hash in story_hashes:
feed_id, _ = MStory.split_story_hash(story_hash)
story, _ = MStory.find_story(feed_id, story_hash)
if not story:
data = dict(code=-1, message="That story has been removed from the feed, no need to mark it unread.", story_hash=story_hash)
if not is_list:
return data
else:
datas.append(data)
message = RUserStory.story_can_be_marked_read_by_user(story, request.user)
if message:
data = dict(code=-1, message=message, story_hash=story_hash)
if not is_list:
return data
else:
datas.append(data)
# Also count on original subscription
usersubs = UserSubscription.objects.filter(user=request.user.pk, feed=feed_id)
if usersubs:
usersub = usersubs[0]
if not usersub.needs_unread_recalc:
usersub.needs_unread_recalc = True
usersub.save(update_fields=['needs_unread_recalc'])
data = usersub.invert_read_stories_after_unread_story(story, request)
r.publish(request.user.username, 'feed:%s' % feed_id)
feed_id, friend_ids = RUserStory.mark_story_hash_unread(request.user, story_hash)
feed_id, friend_ids = RUserStory.mark_story_hash_unread(request.user, story_hash)
if friend_ids:
socialsubs = MSocialSubscription.objects.filter(
user_id=request.user.pk,
subscription_user_id__in=friend_ids)
for socialsub in socialsubs:
if not socialsub.needs_unread_recalc:
socialsub.needs_unread_recalc = True
socialsub.save()
r.publish(request.user.username, 'social:%s' % socialsub.subscription_user_id)
if friend_ids:
socialsubs = MSocialSubscription.objects.filter(
user_id=request.user.pk,
subscription_user_id__in=friend_ids)
for socialsub in socialsubs:
if not socialsub.needs_unread_recalc:
socialsub.needs_unread_recalc = True
socialsub.save()
r.publish(request.user.username, 'social:%s' % socialsub.subscription_user_id)
logging.user(request, "~FYUnread story in feed/socialsubs: %s/%s" % (feed_id, friend_ids))
logging.user(request, "~FYUnread story in feed/socialsubs: %s/%s" % (feed_id, friend_ids))
return dict(code=1, story_hash=story_hash, feed_id=feed_id, friend_user_ids=friend_ids)
data = dict(code=1, story_hash=story_hash, feed_id=feed_id, friend_user_ids=friend_ids)
if not is_list:
return data
else:
datas.append(data)
return datas
@ajax_login_required
@json.json_view
@ -2494,9 +2508,7 @@ def activate_premium_account(request):
sub.feed.count_subscribers()
sub.feed.schedule_feed_fetch_immediately()
except Exception as e:
subject = "Premium activation failed"
message = "%s -- %s\n\n%s" % (request.user, usersubs, e)
mail_admins(subject, message, fail_silently=True)
logging.user(request, "~BR~FWPremium activation failed: {e} {usersubs}")
request.user.profile.is_premium = True
request.user.profile.save()
@ -2535,97 +2547,113 @@ def _mark_story_as_starred(request):
code = 1
feed_id = int(request.POST.get('feed_id', 0))
story_id = request.POST.get('story_id', None)
story_hash = request.POST.get('story_hash', None)
user_tags = request.POST.getlist('user_tags') or request.POST.getlist('user_tags[]')
user_notes = request.POST.get('user_notes', None)
highlights = request.POST.getlist('highlights') or request.POST.getlist('highlights[]') or []
message = ""
if story_hash:
story, _ = MStory.find_story(story_hash=story_hash)
feed_id = story and story.story_feed_id
else:
story_hashes = request.POST.getlist('story_hash') or request.POST.getlist('story_hash[]')
is_list = len(story_hashes) > 1
datas = []
if not len(story_hashes):
story, _ = MStory.find_story(story_feed_id=feed_id, story_id=story_id)
if story:
story_hashes = [story.story_hash]
if not story:
if not len(story_hashes):
return {'code': -1, 'message': "Could not find story to save."}
story_db = dict([(k, v) for k, v in list(story._data.items())
if k is not None and v is not None])
# Pop all existing user-specific fields because we don't want to reuse them from the found story
# in case MStory.find_story uses somebody else's saved/shared story (because the original is deleted)
story_db.pop('user_id', None)
story_db.pop('starred_date', None)
story_db.pop('id', None)
story_db.pop('user_tags', None)
story_db.pop('highlights', None)
story_db.pop('user_notes', None)
now = datetime.datetime.now()
story_values = dict(starred_date=now, user_tags=user_tags, highlights=highlights, user_notes=user_notes, **story_db)
params = dict(story_guid=story.story_guid, user_id=request.user.pk)
starred_story = MStarredStory.objects(**params).limit(1)
created = False
changed_user_notes = False
removed_user_tags = []
removed_highlights = []
if not starred_story:
params.update(story_values)
if 'story_latest_content_z' in params:
params.pop('story_latest_content_z')
try:
starred_story = MStarredStory.objects.create(**params)
except OperationError as e:
logging.user(request, "~FCStarring ~FRfailed~FC: ~SB%s (~FM~SB%s~FC~SN)" % (story.story_title[:32], e))
return {'code': -1, 'message': "Could not save story due to: %s" % e}
for story_hash in story_hashes:
story, _ = MStory.find_story(story_hash=story_hash)
if not story:
logging.user(request, "~FCStarring ~FRfailed~FC: %s not found" % (story_hash))
datas.append({'code': -1, 'message': "Could not save story, not found", 'story_hash': story_hash})
continue
feed_id = story and story.story_feed_id
story_db = dict([(k, v) for k, v in list(story._data.items())
if k is not None and v is not None])
# Pop all existing user-specific fields because we don't want to reuse them from the found story
# in case MStory.find_story uses somebody else's saved/shared story (because the original is deleted)
story_db.pop('user_id', None)
story_db.pop('starred_date', None)
story_db.pop('id', None)
story_db.pop('user_tags', None)
story_db.pop('highlights', None)
story_db.pop('user_notes', None)
now = datetime.datetime.now()
story_values = dict(starred_date=now, user_tags=user_tags, highlights=highlights, user_notes=user_notes, **story_db)
params = dict(story_guid=story.story_guid, user_id=request.user.pk)
starred_story = MStarredStory.objects(**params).limit(1)
created = False
changed_user_notes = False
removed_user_tags = []
removed_highlights = []
if not starred_story:
params.update(story_values)
if 'story_latest_content_z' in params:
params.pop('story_latest_content_z')
try:
starred_story = MStarredStory.objects.create(**params)
except OperationError as e:
logging.user(request, "~FCStarring ~FRfailed~FC: ~SB%s (~FM~SB%s~FC~SN)" % (story.story_title[:32], e))
datas.append({'code': -1, 'message': "Could not save story due to: %s" % e, 'story_hash': story_hash})
created = True
MActivity.new_starred_story(user_id=request.user.pk,
story_title=story.story_title,
story_feed_id=feed_id,
story_id=starred_story.story_guid)
new_user_tags = user_tags
new_highlights = highlights
changed_user_notes = bool(user_notes)
MStarredStoryCounts.adjust_count(request.user.pk, feed_id=feed_id, amount=1)
else:
starred_story = starred_story[0]
new_user_tags = list(set(user_tags) - set(starred_story.user_tags or []))
removed_user_tags = list(set(starred_story.user_tags or []) - set(user_tags))
new_highlights = list(set(highlights) - set(starred_story.highlights or []))
removed_highlights = list(set(starred_story.highlights or []) - set(highlights))
changed_user_notes = bool(user_notes != starred_story.user_notes)
starred_story.user_tags = user_tags
starred_story.highlights = highlights
starred_story.user_notes = user_notes
starred_story.save()
if len(highlights) == 1 and len(new_highlights) == 1:
MStarredStoryCounts.adjust_count(request.user.pk, highlights=True, amount=1)
elif len(highlights) == 0 and len(removed_highlights):
MStarredStoryCounts.adjust_count(request.user.pk, highlights=True, amount=-1)
created = True
MActivity.new_starred_story(user_id=request.user.pk,
story_title=story.story_title,
story_feed_id=feed_id,
story_id=starred_story.story_guid)
new_user_tags = user_tags
new_highlights = highlights
changed_user_notes = bool(user_notes)
MStarredStoryCounts.adjust_count(request.user.pk, feed_id=feed_id, amount=1)
else:
starred_story = starred_story[0]
new_user_tags = list(set(user_tags) - set(starred_story.user_tags or []))
removed_user_tags = list(set(starred_story.user_tags or []) - set(user_tags))
new_highlights = list(set(highlights) - set(starred_story.highlights or []))
removed_highlights = list(set(starred_story.highlights or []) - set(highlights))
changed_user_notes = bool(user_notes != starred_story.user_notes)
starred_story.user_tags = user_tags
starred_story.highlights = highlights
starred_story.user_notes = user_notes
starred_story.save()
if len(highlights) == 1 and len(new_highlights) == 1:
MStarredStoryCounts.adjust_count(request.user.pk, highlights=True, amount=1)
elif len(highlights) == 0 and len(removed_highlights):
MStarredStoryCounts.adjust_count(request.user.pk, highlights=True, amount=-1)
for tag in new_user_tags:
MStarredStoryCounts.adjust_count(request.user.pk, tag=tag, amount=1)
for tag in removed_user_tags:
MStarredStoryCounts.adjust_count(request.user.pk, tag=tag, amount=-1)
for tag in new_user_tags:
MStarredStoryCounts.adjust_count(request.user.pk, tag=tag, amount=1)
for tag in removed_user_tags:
MStarredStoryCounts.adjust_count(request.user.pk, tag=tag, amount=-1)
if random.random() < 0.01:
MStarredStoryCounts.schedule_count_tags_for_user(request.user.pk)
MStarredStoryCounts.count_for_user(request.user.pk, total_only=True)
starred_counts, starred_count = MStarredStoryCounts.user_counts(request.user.pk, include_total=True)
if not starred_count and len(starred_counts):
starred_count = MStarredStory.objects(user_id=request.user.pk).count()
if not changed_user_notes:
r = redis.Redis(connection_pool=settings.REDIS_PUBSUB_POOL)
r.publish(request.user.username, 'story:starred:%s' % story.story_hash)
if created:
logging.user(request, "~FCStarring: ~SB%s (~FM~SB%s~FC~SN)" % (story.story_title[:32], starred_story.user_tags))
else:
logging.user(request, "~FCUpdating starred:~SN~FC ~SB%s~SN (~FM~SB%s~FC~SN/~FM%s~FC)" % (story.story_title[:32], starred_story.user_tags, starred_story.user_notes))
datas.append({'code': code, 'message': message, 'starred_count': starred_count, 'starred_counts': starred_counts})
if random.random() < 0.01:
MStarredStoryCounts.schedule_count_tags_for_user(request.user.pk)
MStarredStoryCounts.count_for_user(request.user.pk, total_only=True)
starred_counts, starred_count = MStarredStoryCounts.user_counts(request.user.pk, include_total=True)
if not starred_count and len(starred_counts):
starred_count = MStarredStory.objects(user_id=request.user.pk).count()
if not changed_user_notes:
r = redis.Redis(connection_pool=settings.REDIS_PUBSUB_POOL)
r.publish(request.user.username, 'story:starred:%s' % story.story_hash)
if created:
logging.user(request, "~FCStarring: ~SB%s (~FM~SB%s~FC~SN)" % (story.story_title[:32], starred_story.user_tags))
else:
logging.user(request, "~FCUpdating starred:~SN~FC ~SB%s~SN (~FM~SB%s~FC~SN/~FM%s~FC)" % (story.story_title[:32], starred_story.user_tags, starred_story.user_notes))
return {'code': code, 'message': message, 'starred_count': starred_count, 'starred_counts': starred_counts}
if len(datas) >= 2:
return datas
elif len(datas) == 1:
return datas[0]
return datas
@required_params('story_id')
@ajax_login_required
@ -2642,15 +2670,25 @@ def mark_story_hash_as_unstarred(request):
def _mark_story_as_unstarred(request):
code = 1
story_id = request.POST.get('story_id', None)
story_hash = request.POST.get('story_hash', None)
story_hashes = request.POST.getlist('story_hash') or request.POST.getlist('story_hash[]')
starred_counts = None
starred_story = None
if story_id:
starred_story = MStarredStory.objects(user_id=request.user.pk, story_guid=story_id)
if not story_id or not starred_story:
starred_story = MStarredStory.objects(user_id=request.user.pk, story_hash=story_hash or story_id)
if starred_story:
if starred_story:
starred_story = starred_story[0]
story_hashes = [starred_story.story_hash]
else:
story_hashes = [story_id]
datas = []
for story_hash in story_hashes:
starred_story = MStarredStory.objects(user_id=request.user.pk, story_hash=story_hash)
if not starred_story:
logging.user(request, "~FCUnstarring ~FRfailed~FC: %s not found" % (story_hash))
datas.append({'code': -1, 'message': "Could not unsave story, not found", 'story_hash': story_hash})
continue
starred_story = starred_story[0]
logging.user(request, "~FCUnstarring: ~SB%s" % (starred_story.story_title[:50]))
user_tags = starred_story.user_tags
@ -2677,12 +2715,12 @@ def _mark_story_as_unstarred(request):
r = redis.Redis(connection_pool=settings.REDIS_PUBSUB_POOL)
r.publish(request.user.username, 'story:unstarred:%s' % starred_story.story_hash)
else:
code = -1
return {'code': code, 'starred_counts': starred_counts}
if not story_hashes:
datas.append(dict(code=-1, message=f"Failed to find {story_hashes}"))
return {'code': code, 'starred_counts': starred_counts, 'messages': datas}
@ajax_login_required
@json.json_view
def starred_counts(request):

View file

@ -2496,6 +2496,17 @@ class MStory(mongo.Document):
def decoded_story_title(self):
return html.unescape(self.story_title)
@property
def story_content_str(self):
story_content = self.story_content
if not story_content and self.story_content_z:
story_content = smart_str(zlib.decompress(self.story_content_z))
else:
story_content = smart_str(story_content)
return story_content
def save(self, *args, **kwargs):
story_title_max = MStory._fields['story_title'].max_length
story_content_type_max = MStory._fields['story_content_type'].max_length
@ -2794,12 +2805,10 @@ class MStory(mongo.Document):
story_content = None
if not text:
story_content = self.story_content
if not story_content and self.story_content_z:
story_content = zlib.decompress(self.story_content_z)
story_content = self.story_content_str
elif text:
if self.original_text_z:
story_content = zlib.decompress(self.original_text_z)
story_content = smart_str(zlib.decompress(self.original_text_z))
if not story_content:
return
@ -2815,6 +2824,33 @@ class MStory(mongo.Document):
return
images = soup.findAll('img')
# Add youtube thumbnail and insert appropriately before/after images.
# Give the Youtube a bit of an edge.
video_thumbnails = soup.findAll('iframe', src=lambda x: x and any(y in x for y in ['youtube.com', 'ytimg.com']))
for video_thumbnail in video_thumbnails:
video_src = video_thumbnail.get('src')
video_id = re.search('.*?youtube.com/embed/([A-Za-z0-9\-_]+)', video_src)
if not video_id:
video_id = re.search('.*?youtube.com/v/([A-Za-z0-9\-_]+)', video_src)
if not video_id:
video_id = re.search('.*?ytimg.com/vi/([A-Za-z0-9\-_]+)', video_src)
if not video_id:
video_id = re.search('.*?youtube.com/watch\?v=([A-Za-z0-9\-_]+)', video_src)
if not video_id:
logging.debug(f" ***> Couldn't find youtube url in {video_thumbnail}: {video_src}")
continue
video_img_url = f"https://img.youtube.com/vi/{video_id.groups()[0]}/0.jpg"
iframe_index = story_content.index('<iframe')
try:
img_index = story_content.index('<img')*3
except ValueError:
img_index = None
if not img_index or iframe_index < img_index:
images.insert(0, video_img_url)
else:
images.append(video_img_url)
if not images:
if not text:
return self.extract_image_urls(force=force, text=True)
@ -2826,7 +2862,10 @@ class MStory(mongo.Document):
image_urls = []
for image in images:
image_url = image.get('src')
if isinstance(image, str):
image_url = image
else:
image_url = image.get('src')
if not image_url:
continue
if image_url and len(image_url) >= 1024:

View file

@ -0,0 +1,27 @@
---
layout: post
title: The Magazine view is a new perspective
tags: ['web']
---
Here's a nice new feature that brings a new perspective to your stories. It's called the Magazine view and you can access it next to the other story views:
<img src="/assets/magazine-views.png" style="width: 750px;">
<img src="/assets/magazine-light.png" style="width: 750px;">
<img src="/assets/magazine-dark.png" style="width: 750px;">
Loads of new features:
* The dashboard now has multiple, customizable rivers of news
* Image previews are now customizable by size and layout
* Story previews are also customizable by length
* Images are now full bleed on the web (edge-to-edge)
* Controls have been re-styled and made more accessible
* Sizes, spaces, and text have all been tweaked for a more legible read
* Upgraded backend: Python 2 to Python 3, latest Django and libraries, containerized infrastructure
* Both Android and iOS apps have been updated with the new design

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View file

@ -21,16 +21,10 @@ limit_request_fields = 1000
worker_tmp_dir = "/dev/shm"
reload = True
if GIGS_OF_MEMORY > NUM_CPUS:
workers = NUM_CPUS
else:
workers = int(NUM_CPUS / 2)
workers = max(int(math.floor(GIGS_OF_MEMORY * 2)), 3)
if workers <= 4:
workers = max(int(math.floor(GIGS_OF_MEMORY * 1000 / 512)), 3)
if workers > 8:
workers = 8
if workers > 4:
workers = 4
if os.environ.get('DOCKERBUILD', False):
workers = 2

View file

@ -24,7 +24,7 @@ django-cors-middleware==1.3.1
django-extensions==3.1.0
django-nose==1.4.7
django-oauth-toolkit==1.3.3
django-paypal==1.0.0
django-paypal==1.1.2
django-qurl==0.1.1
django-pipeline>=2,<3
django-redis-cache==3.0.0
@ -74,6 +74,7 @@ numpy==1.19.4
oauth2==1.9.0.post1
oauthlib==3.1.0
packaging==20.9
paypalrestsdk==1.13.1
pbr==5.6.0
Pillow==8.0.1
pluggy==0.13.1

View file

@ -59,6 +59,7 @@ frontend public
use_backend nginx if { path_beg /favicon }
use_backend nginx if { path_beg /crossdomain/ }
use_backend nginx if { path_beg /robots }
use_backend nginx if { hdr_sub(host) -i blog.localhost }
#use_backend self if { path_beg /munin/ }
# use_backend gunicorn_counts if is_unread_count

View file

@ -62,6 +62,10 @@ server {
location /static/ {
gzip_static on;
gzip on;
gzip_comp_level 6;
gzip_vary on;
gzip_types text/plain text/css application/json application/x-javascript application/javascript text/xml application/xml application/rss+xml text/javascript image/svg+xml application/vnd.ms-fontobject application/x-font-ttf font/opentype;
expires max;
keepalive_timeout 1;
root /srv/newsblur;

View file

@ -62,7 +62,7 @@ server {
keepalive_timeout 1;
root /srv/newsblur;
}
location /static/ {
gzip_static on;
expires max;
@ -114,3 +114,17 @@ server {
}
}
server {
listen 81;
server_name blog.localhost;
set_real_ip_from 0.0.0.0/0;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
root /srv/newsblur/blog/_site;
}

View file

@ -1398,7 +1398,7 @@ hr {
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;>>>
overflow: hidden;
}
.NB-feedbar .NB-feedbar-options-container {
float: right;
@ -1761,6 +1761,31 @@ hr {
font-size: 18px;
line-height: 22px;
}
.NB-theme-feed-size-xs .NB-layout-magazine .NB-story-title {
font-size: 14px;
line-height: 16px;
padding-top: 10px;
}
.NB-theme-feed-size-s .NB-layout-magazine .NB-story-title {
font-size: 15px;
line-height: 17px;
padding-top: 15px;
}
.NB-theme-feed-size-m .NB-layout-magazine .NB-story-title {
font-size: 16px;
line-height: 19px;
padding-top: 20px;
}
.NB-theme-feed-size-l .NB-layout-magazine .NB-story-title {
font-size: 18px;
line-height: 22px;
padding-top: 25px;
}
.NB-theme-feed-size-xl .NB-layout-magazine .NB-story-title {
font-size: 20px;
line-height: 24px;
padding-top: 30px;
}
.NB-story-pane-west .NB-story-title {
padding-right: 4px;
@ -1772,30 +1797,51 @@ hr {
.NB-view-river .NB-story-pane-west .NB-story-title {
padding-left: 56px;
}
.NB-image-preview-small-right:not(.NB-story-layout-grid) .NB-story-title.NB-has-image,
.NB-image-preview-large-right:not(.NB-story-layout-grid) .NB-story-title.NB-has-image {
.NB-image-preview-small-right .NB-story-title.NB-has-image,
.NB-image-preview-large-right .NB-story-title.NB-has-image {
padding-right: 242px;
}
.NB-image-preview-small-right:not(.NB-story-layout-grid) .NB-story-pane-west .NB-story-title.NB-has-image,
.NB-image-preview-large-right:not(.NB-story-layout-grid) .NB-story-pane-west .NB-story-title.NB-has-image {
.NB-image-preview-small-right .NB-story-pane-west .NB-story-title.NB-has-image,
.NB-image-preview-large-right .NB-story-pane-west .NB-story-title.NB-has-image {
padding-right: 90px;
}
/* Ragged right but not ragged left
.NB-image-preview-small-left .NB-story-title.NB-has-image,
.NB-image-preview-large-left .NB-story-title.NB-has-image */
.NB-image-preview-small-left:not(.NB-story-layout-grid) .NB-story-title,
.NB-image-preview-large-left:not(.NB-story-layout-grid) .NB-story-title {
.NB-image-preview-small-left .NB-story-title,
.NB-image-preview-large-left .NB-story-title {
padding-left: 116px;
}
.NB-image-preview-small-left:not(.NB-story-layout-grid) .NB-view-river .NB-story-title,
.NB-image-preview-large-left:not(.NB-story-layout-grid) .NB-view-river .NB-story-title {
.NB-image-preview-small-left .NB-story-title.NB-story-grid,
.NB-image-preview-large-left .NB-story-title.NB-story-grid {
padding-left: 0;
}
.NB-image-preview-small-left .NB-view-river .NB-story-title,
.NB-image-preview-large-left .NB-view-river .NB-story-title {
padding-left: 242px;
}
.NB-image-preview-small-left:not(.NB-story-layout-grid) .NB-view-river .NB-story-pane-west .NB-story-title,
.NB-image-preview-large-left:not(.NB-story-layout-grid) .NB-view-river .NB-story-pane-west .NB-story-title {
.NB-image-preview-small-left .NB-view-river .NB-story-pane-west .NB-story-title,
.NB-image-preview-large-left .NB-view-river .NB-story-pane-west .NB-story-title {
padding-left: 120px;
}
.NB-image-preview-small-left .NB-view-river .NB-story-title.NB-story-grid,
.NB-image-preview-large-left .NB-view-river .NB-story-title.NB-story-grid {
padding-left: 0;
}
.NB-image-preview-small-left .NB-layout-magazine .NB-story-title,
.NB-image-preview-large-left .NB-layout-magazine .NB-story-title,
.NB-view-river .NB-layout-magazine .NB-story-title {
padding-left: 400px;
min-height: 200px;
}
.NB-image-preview-none .NB-layout-magazine .NB-story-title {
padding-left: 56px;
}
.NB-image-preview-large-right .NB-layout-magazine .NB-story-title,
.NB-image-preview-small-right .NB-layout-magazine .NB-story-title {
padding-left: 48px;
padding-right: 400px;
}
.NB-story-title .NB-storytitles-feed-border-inner,
.NB-story-title .NB-storytitles-feed-border-outer {
position: absolute;
@ -1823,13 +1869,17 @@ hr {
.NB-story-pane-west .NB-story-title .NB-storytitles-sentiment {
left: 4px;
}
.NB-image-preview-small-left:not(.NB-story-layout-grid) .NB-story-title .NB-storytitles-sentiment,
.NB-image-preview-large-left:not(.NB-story-layout-grid) .NB-story-title .NB-storytitles-sentiment {
.NB-image-preview-small-left .NB-story-title .NB-storytitles-sentiment,
.NB-image-preview-large-left .NB-story-title .NB-storytitles-sentiment {
left: 89px;
}
.NB-layout-magazine .NB-story-title .NB-storytitles-sentiment {
left: -28px;
top: 4px;
}
.NB-image-preview-small-left:not(.NB-story-layout-grid) .NB-view-river .NB-story-pane-west .NB-story-title .NB-storytitles-sentiment,
.NB-image-preview-large-left:not(.NB-story-layout-grid) .NB-view-river .NB-story-pane-west .NB-story-title .NB-storytitles-sentiment {
.NB-image-preview-small-left .NB-view-river .NB-story-pane-west .NB-story-title .NB-storytitles-sentiment,
.NB-image-preview-large-left .NB-view-river .NB-story-pane-west .NB-story-title .NB-storytitles-sentiment {
top: 24px;
}
.NB-story-title.NB-story-positive .NB-storytitles-sentiment {
@ -1862,13 +1912,20 @@ hr {
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.NB-layout-magazine .NB-story-title .NB-storytitles-story-image {
background-size: 40%, contain;
}
.NB-story-title.read .NB-storytitles-story-image {
opacity: 0.6;
}
.NB-image-preview-large-left:not(.NB-story-layout-grid) .NB-story-title .NB-storytitles-story-image {
.NB-image-preview-large-left .NB-story-title .NB-storytitles-story-image {
right: inherit;
left: 8px;
}
.NB-image-preview-large-left .NB-layout-magazine .NB-story-title .NB-storytitles-story-image {
height: 100%;
top: 0;
}
.NB-story-title.NB-story-starred .NB-storytitles-star,
.NB-story-title.read.NB-story-starred .NB-storytitles-star {
width: 16px;
@ -2013,6 +2070,15 @@ hr {
.NB-content-preview-large .NB-storytitles-content-preview {
-webkit-line-clamp: 3;
}
.NB-content-preview-small .NB-layout-magazine .NB-storytitles-content-preview {
-webkit-line-clamp: 8;
}
.NB-content-preview-medium .NB-layout-magazine .NB-storytitles-content-preview {
-webkit-line-clamp: 12;
}
.NB-content-preview-large .NB-layout-magazine .NB-storytitles-content-preview {
-webkit-line-clamp: 16;
}
.NB-theme-feed-size-xs .NB-storytitles-content-preview {
font-size: 11px;
@ -2046,6 +2112,33 @@ hr {
.NB-story-layout-list .NB-selected .NB-storytitles-content-preview {
display: none;
}
.NB-theme-feed-size-xs .NB-layout-magazine .NB-storytitles-content-preview {
font-size: 12px;
line-height: 15px;
margin: 10px 0;
}
.NB-theme-feed-size-s .NB-layout-magazine .NB-storytitles-content-preview {
font-size: 13px;
line-height: 16px;
margin: 15px 0;
}
.NB-theme-feed-size-m .NB-layout-magazine .NB-storytitles-content-preview {
font-size: 14px;
line-height: 18px;
margin: 20px 0;
}
.NB-theme-feed-size-l .NB-layout-magazine .NB-storytitles-content-preview {
font-size: 16px;
line-height: 20px;
margin: 25px 0;
}
.NB-theme-feed-size-xl .NB-layout-magazine .NB-storytitles-content-preview {
font-size: 18px;
line-height: 22px;
margin: 30px 0;
}
.read .NB-storytitles-content-preview {
color: #b3b3b1;
}
@ -2086,6 +2179,13 @@ hr {
color: #707070;
position: absolute;
}
.NB-layout-magazine .NB-story-title .NB-story-feed {
position: relative;
top: auto;
left: auto;
width: 100%;
margin: 0 0 14px 4px;
}
.NB-story-pane-west .NB-story-title .NB-story-feed {
left: -22px;
top: 9px;
@ -2157,12 +2257,20 @@ hr {
.NB-story-pane-west .NB-story-title .NB-story-manage-icon {
left: 4px;
}
.NB-image-preview-small-left:not(.NB-story-layout-grid) .NB-story-title .NB-story-manage-icon,
.NB-image-preview-large-left:not(.NB-story-layout-grid) .NB-story-title .NB-story-manage-icon {
.NB-image-preview-small-left .NB-story-title .NB-story-manage-icon,
.NB-image-preview-large-left .NB-story-title .NB-story-manage-icon {
left: 90px;
}
.NB-image-preview-small-left:not(.NB-story-layout-grid) .NB-view-river .NB-story-pane-west .NB-story-title .NB-story-manage-icon,
.NB-image-preview-large-left:not(.NB-story-layout-grid) .NB-view-river .NB-story-pane-west .NB-story-title .NB-story-manage-icon {
.NB-layout-magazine .NB-story-title .NB-story-manage-icon {
left: -28px;
top: 4px;
}
.NB-layout-grid .NB-story-title .NB-story-manage-icon {
left: 0;
top: 2px;
}
.NB-image-preview-small-left .NB-view-river .NB-story-pane-west .NB-story-title .NB-story-manage-icon,
.NB-image-preview-large-left .NB-view-river .NB-story-pane-west .NB-story-title .NB-story-manage-icon {
top: 24px;
}
.NB-story-title:hover .NB-story-manage-icon {
@ -2367,20 +2475,20 @@ hr {
box-sizing: border-box;
}
.NB-layout-grid.NB-grid-height-xs .NB-story-grid {
height: 220px;
}
.NB-layout-grid.NB-grid-height-s .NB-story-grid {
height: 280px;
}
.NB-layout-grid.NB-grid-height-m .NB-story-grid {
.NB-layout-grid.NB-grid-height-s .NB-story-grid {
height: 360px;
}
.NB-layout-grid.NB-grid-height-l .NB-story-grid {
.NB-layout-grid.NB-grid-height-m .NB-story-grid {
height: 420px;
}
.NB-layout-grid.NB-grid-height-xl .NB-story-grid {
.NB-layout-grid.NB-grid-height-l .NB-story-grid {
height: 480px;
}
.NB-layout-grid.NB-grid-height-xl .NB-story-grid {
height: 540px;
}
.NB-story-grid.NB-story-title-hide-preview,
.NB-content-preview-title .NB-story-grid {
height: 296px;
@ -2412,7 +2520,7 @@ hr {
grid-template-columns: none;
grid-template-columns: repeat(4, 1fr);
}
.NB-view-river .NB-story-grid {
.NB-story-layout-grid .NB-view-river .NB-story-grid {
padding-left: 0;
border: none;
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
@ -2443,7 +2551,6 @@ hr {
line-height: 1.3em;
padding: 0;
word-break: break-word;
word-break: anywhere;
}
.NB-theme-feed-size-xs .NB-story-grid .NB-storytitles-title {
font-size: 14px;
@ -2465,23 +2572,37 @@ hr {
.NB-story-grid .NB-storytitles-story-image-container {
margin-left: 8px;
}
.NB-story-grid .NB-storytitles-story-image {
.NB-image-preview-small-left .NB-story-grid .NB-storytitles-story-image,
.NB-image-preview-large-left .NB-story-grid .NB-storytitles-story-image,
.NB-image-preview-small-right .NB-story-grid .NB-storytitles-story-image,
.NB-image-preview-large-right .NB-story-grid .NB-storytitles-story-image {
display: none;
position: inherit;
width: 100%;
height: 186px;
background-size: 64px 64px, cover;
background-position: 50% 60%, 50% 30%;
border-radius: 0;
background-repeat: no-repeat;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.NB-layout-grid.NB-grid-height-xs .NB-story-grid .NB-storytitles-story-image {
height: 86px;
.NB-image-preview-small-left .NB-grid-height-xs .NB-story-grid .NB-storytitles-story-image,
.NB-image-preview-small-right .NB-grid-height-xs .NB-story-grid .NB-storytitles-story-image {
height: 76px;
}
.NB-layout-grid.NB-grid-height-s .NB-story-grid .NB-storytitles-story-image {
height: 112px;
.NB-image-preview-large-left .NB-grid-height-xs .NB-story-grid .NB-storytitles-story-image,
.NB-image-preview-large-right .NB-grid-height-xs .NB-story-grid .NB-storytitles-story-image {
height: 106px;
}
.NB-image-preview-small-left .NB-grid-height-s .NB-story-grid .NB-storytitles-story-image,
.NB-image-preview-small-right .NB-grid-height-s .NB-story-grid .NB-storytitles-story-image {
height: 106px;
}
.NB-image-preview-large-left .NB-grid-height-s .NB-story-grid .NB-storytitles-story-image,
.NB-image-preview-large-right .NB-grid-height-s .NB-story-grid .NB-storytitles-story-image {
height: 146px;
}
.NB-image-preview-small-left .NB-grid-height-m .NB-story-grid .NB-storytitles-story-image,
.NB-image-preview-small-right .NB-grid-height-m .NB-story-grid .NB-storytitles-story-image {
@ -2489,23 +2610,23 @@ hr {
}
.NB-image-preview-large-left .NB-grid-height-m .NB-story-grid .NB-storytitles-story-image,
.NB-image-preview-large-right .NB-grid-height-m .NB-story-grid .NB-storytitles-story-image {
height: 204px;
height: 202px;
}
.NB-image-preview-small-left .NB-grid-height-l .NB-story-grid .NB-storytitles-story-image,
.NB-image-preview-small-right .NB-grid-height-l .NB-story-grid .NB-storytitles-story-image {
height: 152px;
height: 202px;
}
.NB-image-preview-large-left .NB-grid-height-l .NB-story-grid .NB-storytitles-story-image,
.NB-image-preview-large-right .NB-grid-height-l .NB-story-grid .NB-storytitles-story-image {
height: 264px;
height: 242px;
}
.NB-image-preview-small-left .NB-grid-height-xl .NB-story-grid .NB-storytitles-story-image,
.NB-image-preview-small-right .NB-grid-height-xl .NB-story-grid .NB-storytitles-story-image {
height: 202px;
height: 242px;
}
.NB-image-preview-large-left .NB-grid-height-xl .NB-story-grid .NB-storytitles-story-image,
.NB-image-preview-large-right .NB-grid-height-xl .NB-story-grid .NB-storytitles-story-image {
height: 324px;
height: 272px;
}
.NB-layout-grid.NB-grid-height-xs.NB-grid-columns-3 .NB-story-grid .NB-storytitles-author,
.NB-layout-grid.NB-grid-height-xs.NB-grid-columns-4 .NB-story-grid .NB-storytitles-author,
@ -2551,7 +2672,7 @@ hr {
.NB-story-grid.read .NB-storytitles-content-preview {
color: #D6D6DE;
}
.NB-story-grid.NB-story-title .NB-storytitles-grid-bottom {
.NB-story-title .NB-storytitles-grid-bottom {
position: absolute;
bottom: 0;
top: inherit;
@ -2567,6 +2688,16 @@ hr {
.NB-story-grid:not(.read):hover .NB-storytitles-grid-bottom {
background-color: #F7F7F6;
}
.NB-storytitles-magazine-bottom {
position: relative;
margin-bottom: 20px;
}
.NB-storytitles-magazine-bottom .story_date {
top: 0;
}
.NB-layout-magazine .NB-story-title .NB-storytitles-author {
padding-left: 4px;
}
.NB-story-grid.NB-story-title .NB-storytitles-author {
float: left;
display: block;
@ -2580,10 +2711,11 @@ hr {
display: block;
padding: 0 4px 0 0;
}
.NB-story-grid .NB-storytitles-sentiment,
.NB-story-grid .NB-story-manage-icon {
.NB-story-title.NB-story-grid .NB-storytitles-sentiment,
.NB-story-title.NB-story-grid .NB-story-manage-icon {
left: -28px;
}
.NB-story-magazine .NB-story-feed,
.NB-story-grid .NB-story-feed {
position: static;
margin: 10px 0;
@ -2593,11 +2725,13 @@ hr {
.NB-story-grid .story_title {
position: relative;
}
.NB-layout-magazine .NB-story-feed .feed_favicon,
.NB-story-grid .NB-story-feed .feed_favicon {
margin: 0 4px 0 0;
float: left;
position: static;
}
.NB-layout-magazine .NB-story-feed .feed_title,
.NB-story-grid .NB-story-feed .feed_title {
width: auto;
-webkit-line-clamp: 1;
@ -2851,20 +2985,64 @@ body {
display: none;
height: 0;
}
.NB-image-preview-small-left:not(.NB-story-layout-grid) .NB-storytitles-story-image {
.NB-image-preview-small-left .NB-storytitles-story-image {
width: 62px;
height: 70%;
left: 22px;
top: 7px;
border-radius: 6px;
}
.NB-image-preview-small-right:not(.NB-story-layout-grid) .NB-storytitles-story-image {
.NB-image-preview-small-right .NB-storytitles-story-image {
width: 62px;
height: 70%;
right: 12px;
top: 12px;
border-radius: 6px;
}
.NB-image-preview-small-left .NB-layout-magazine .NB-storytitles-story-image-container,
.NB-image-preview-large-left .NB-layout-magazine .NB-storytitles-story-image-container {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
width: 362px;
height: 100%;
top: 0;
left: 8px;
}
.NB-image-preview-small-left .NB-layout-magazine .NB-storytitles-story-image,
.NB-image-preview-small-right .NB-layout-magazine .NB-storytitles-story-image {
top: auto;
right: auto;
left: auto;
width: 70%;
height: 70%;
background-size: cover;
background-position: center;
border-radius: 12px;
}
.NB-image-preview-large-left .NB-layout-magazine .NB-storytitles-story-image,
.NB-image-preview-large-right .NB-layout-magazine .NB-storytitles-story-image {
top: auto;
right: auto;
left: auto;
width: 96%;
height: 100%;
background-size: cover;
background-position: center;
border-radius: 0;
}
.NB-image-preview-small-right .NB-layout-magazine .NB-storytitles-story-image-container,
.NB-image-preview-large-right .NB-layout-magazine .NB-storytitles-story-image-container {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
width: 362px;
height: 100%;
top: 0;
right: 8px;
}
.NB-image-preview-small-left.NB-story-layout-list .NB-storytitles-story-image,
.NB-image-preview-small-right.NB-story-layout-list .NB-storytitles-story-image,
.NB-image-preview-small-left.NB-story-layout-split .NB-story-pane-south .NB-storytitles-story-image,
@ -2987,7 +3165,7 @@ body {
}
.NB-feed-story .NB-feed-story-header-info {
padding: 0 206px 8px 28px;
padding: 8px 236px 8px 28px;
background-color: #EEF0E9;
border-bottom: 1px solid rgba(0, 0, 0, .2);
border-left: 3px solid transparent;
@ -3019,7 +3197,7 @@ body {
rgb(76,76,76) 36%,
rgb(55,55,55) 84%
);
padding: 2px 200px 2px 28px;
padding: 2px 236px 2px 28px;
position: sticky;
top: -1px;
border-bottom: 1px solid #000;
@ -3042,15 +3220,25 @@ body {
padding: 2px 0;
color: white;
font-size: 13px;
position: relative;
}
.NB-inverse .NB-feed-story-feed {
color: black;
}
.NB-pref-story-position-center .NB-feed-story-feed {
margin: 0 auto;
width: 700px;
}
.NB-pref-story-position-right .NB-feed-story-feed {
margin: 0 0 0 auto;
width: 700px;
}
.NB-feed-story-feed .feed_favicon {
position: absolute;
left: 6px;
top: 5px;
left: -22px;
top: 3px;
width: 16px;
height: 16px;
}
@ -3203,6 +3391,14 @@ body {
position: relative;
clear: both;
}
.NB-pref-story-position-center .NB-feed-story .NB-feed-story-title-container {
margin: 0 auto;
max-width: 700px;
}
.NB-pref-story-position-right .NB-feed-story .NB-feed-story-title-container {
margin: 0 0 0 auto;
max-width: 700px;
}
.NB-feed-story a.NB-feed-story-title {
display: block;
@ -3423,6 +3619,14 @@ body {
.NB-feed-story .NB-feed-story-header .NB-feed-story-date-line {
clear: left;
}
.NB-pref-story-position-center .NB-feed-story .NB-feed-story-header .NB-feed-story-date-line {
margin: 0 auto;
max-width: 700px;
}
.NB-pref-story-position-right .NB-feed-story .NB-feed-story-header .NB-feed-story-date-line {
margin: 0 0 0 auto;
max-width: 700px;
}
.NB-feed-story .NB-feed-story-header .NB-feed-story-show-changes {
float: left;
font-size: 12px;
@ -3457,14 +3661,21 @@ body {
.NB-feed-story .NB-feed-story-header .NB-feed-story-starred-date {
font-size: 12px;
background: transparent url('/media/embed/icons/circular/clock.png') no-repeat 0 center;
background-size: 16px;
padding-left: 20px;
/* text-shadow: 1px 1px 0 rgba(255, 255, 255, .5);*/
/* text-shadow: 1px 1px 0 rgba(255, 255, 255, .5);*/
color: #808080;
display: block;
clear: both;
margin: 6px 0;
margin: 6px 0 0;
}
.NB-feed-story .NB-feed-story-header .NB-feed-story-starred-date .NB-icon {
background: transparent url('/media/embed/icons/circular/clock.png') no-repeat 0 center;
background-size: 16px;
height: 16px;
margin-top: 6px;
display: inline-block;
vertical-align: text-bottom;
color: #9B9D97;
padding-left: 20px;
}
.NB-theme-size-xs .NB-feed-story-header .NB-feed-story-starred-date {
font-size: 10px;
@ -3478,13 +3689,27 @@ body {
.NB-theme-size-xl .NB-feed-story-header .NB-feed-story-starred-date {
font-size: 14px;
}
.NB-pref-story-position-center .NB-feed-story-header .NB-feed-story-starred-date {
margin: 6px auto 0;
max-width: 700px;
}
.NB-pref-story-position-right .NB-feed-story-header .NB-feed-story-starred-date {
margin: 6px 0 0 auto;
max-width: 700px;
}
.NB-feed-story .NB-feed-story-content {
padding: 12px 0 0;
max-width: 700px;
min-height: 12px;
}
.NB-pref-full-width-story .NB-feed-story .NB-feed-story-content {
.NB-pref-story-position-center .NB-feed-story .NB-feed-story-content {
margin: 0 auto;
}
.NB-pref-story-position-right .NB-feed-story .NB-feed-story-content {
margin: 0 0 0 auto;
}
.NB-pref-story-position-stretch .NB-feed-story .NB-feed-story-content {
max-width: none;
}
.NB-feed-story .NB-narrow-content .NB-feed-story-content {
@ -3667,15 +3892,20 @@ body {
/* ============ */
.NB-feed-story-comments {
margin: 0 236px 32px 28px;
padding: 0 236px 32px 28px;
max-width: 700px;
/* border-bottom: 1px solid #EAECE8;*/
clear: both;
background-color: white;
position: relative;
}
.NB-narrow-content .NB-feed-story-comments {
margin-right: 28px;
padding-right: 28px;
}
.NB-pref-story-position-center .NB-feed-story-comments {
margin: 0 auto;
}
.NB-pref-story-position-right .NB-feed-story-comments {
margin: 0 0 0 auto;
}
.NB-story-comment {
border-bottom: 1px solid #EAECE8;
@ -5126,6 +5356,16 @@ background: transparent;
background: transparent url('/media/img/icons/circular/nav_story_grid_active.png') no-repeat center center;
background-size: 15px;
}
.NB-taskbar .NB-task-layout-magazine .NB-task-image {
left: 12px;
background: transparent url('/media/img/icons/circular/nav_story_magazine.png') no-repeat center center;
background-size: 15px;
}
.NB-taskbar .NB-task-layout-magazine.NB-active .NB-task-image {
left: 12px;
background: transparent url('/media/img/icons/circular/nav_story_magazine_active.png') no-repeat center center;
background-size: 15px;
}
.NB-taskbar .NB-task-return .NB-task-image {
background: transparent url('/media/embed/icons/silk/arrow_undo.png') no-repeat center center;
@ -5372,6 +5612,37 @@ background: transparent;
.NB-options-story-titles-pane-south .NB-icon {
background: transparent url("/media/embed/reader/layout_bottom.png") no-repeat center center;
}
.NB-style-popover .NB-story-position-option .NB-icon {
width: 16px;
height: 16px;
display: inline-block;
margin: 0;
vertical-align: text-bottom;
}
.NB-options-story-position .NB-story-position-option {
width: auto;
font-size: 12px;
padding: 4px 15px;
}
.NB-options-story-position-stretch {
}
.NB-options-story-position-left .NB-icon {
background: transparent url("/media/embed/reader/position_left.svg") no-repeat center center;
background-size: 16px;
}
.NB-options-story-position-center .NB-icon {
background: transparent url("/media/embed/reader/position_center.svg") no-repeat center center;
background-size: 16px;
}
.NB-options-story-position-right .NB-icon {
background: transparent url("/media/embed/reader/position_right.svg") no-repeat center center;
background-size: 16px;
}
.NB-options-story-position-stretch .NB-icon {
background: transparent url("/media/embed/reader/position_stretch.png") no-repeat center center;
background-size: 16px;
}
.NB-style-popover .NB-options-single-story {
margin-top: 6px;
@ -7918,7 +8189,6 @@ form.opml_import_form input {
margin: 0 4px 0 0;
}
}
.NB-menu-manage .NB-menu-manage-story-share-confirm .NB-sideoption-share {
overflow: hidden;
border: none;

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 B

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="1200pt" height="1200pt" version="1.1" viewBox="0 0 1200 1200" xmlns="http://www.w3.org/2000/svg">
<path d="m1050 700v250c0 13.262-5.2695 25.98-14.645 35.355s-22.094 14.645-35.355 14.645h-350v100c0 17.863-9.5312 34.371-25 43.301-15.469 8.9336-34.531 8.9336-50 0-15.469-8.9297-25-25.438-25-43.301v-100h-350c-13.262 0-25.98-5.2695-35.355-14.645s-14.645-22.094-14.645-35.355v-250c0-13.262 5.2695-25.98 14.645-35.355s22.094-14.645 35.355-14.645h350v-100h-200c-13.262 0-25.98-5.2695-35.355-14.645s-14.645-22.094-14.645-35.355v-250c0-13.262 5.2695-25.98 14.645-35.355s22.094-14.645 35.355-14.645h200v-100c0-17.863 9.5312-34.371 25-43.301 15.469-8.9336 34.531-8.9336 50 0 15.469 8.9297 25 25.438 25 43.301v100h200c13.262 0 25.98 5.2695 35.355 14.645s14.645 22.094 14.645 35.355v250c0 13.262-5.2695 25.98-14.645 35.355s-22.094 14.645-35.355 14.645h-200v100h350c13.262 0 25.98 5.2695 35.355 14.645s14.645 22.094 14.645 35.355z" fill="#7f7f79"/>
</svg>

After

Width:  |  Height:  |  Size: 994 B

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="1200pt" height="1200pt" version="1.1" viewBox="0 0 1200 1200" xmlns="http://www.w3.org/2000/svg">
<path d="m100 1150c-13.262 0-25.98-5.2695-35.355-14.645s-14.645-22.094-14.645-35.355v-1e3c0-17.863 9.5312-34.371 25-43.301 15.469-8.9336 34.531-8.9336 50 0 15.469 8.9297 25 25.438 25 43.301v1e3c0 13.262-5.2695 25.98-14.645 35.355s-22.094 14.645-35.355 14.645zm1e3 -500h-800c-13.262 0-25.98 5.2695-35.355 14.645s-14.645 22.094-14.645 35.355v250c0 13.262 5.2695 25.98 14.645 35.355s22.094 14.645 35.355 14.645h800c13.262 0 25.98-5.2695 35.355-14.645s14.645-22.094 14.645-35.355v-250c0-13.262-5.2695-25.98-14.645-35.355s-22.094-14.645-35.355-14.645zm-800-100h500c13.262 0 25.98-5.2695 35.355-14.645s14.645-22.094 14.645-35.355v-250c0-13.262-5.2695-25.98-14.645-35.355s-22.094-14.645-35.355-14.645h-500c-13.262 0-25.98 5.2695-35.355 14.645s-14.645 22.094-14.645 35.355v250c0 13.262 5.2695 25.98 14.645 35.355s22.094 14.645 35.355 14.645z" fill="#7f7f79"/>
</svg>

After

Width:  |  Height:  |  Size: 1,009 B

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="1200pt" height="1200pt" version="1.1" viewBox="0 0 1200 1200" xmlns="http://www.w3.org/2000/svg">
<path d="m950 250v250c0 13.262-5.2695 25.98-14.645 35.355s-22.094 14.645-35.355 14.645h-500c-13.262 0-25.98-5.2695-35.355-14.645s-14.645-22.094-14.645-35.355v-250c0-13.262 5.2695-25.98 14.645-35.355s22.094-14.645 35.355-14.645h500c13.262 0 25.98 5.2695 35.355 14.645s14.645 22.094 14.645 35.355zm150-200c-13.262 0-25.98 5.2695-35.355 14.645s-14.645 22.094-14.645 35.355v1e3c0 17.863 9.5312 34.371 25 43.301 15.469 8.9336 34.531 8.9336 50 0 15.469-8.9297 25-25.438 25-43.301v-1e3c0-13.262-5.2695-25.98-14.645-35.355s-22.094-14.645-35.355-14.645zm-200 600h-800c-13.262 0-25.98 5.2695-35.355 14.645s-14.645 22.094-14.645 35.355v250c0 13.262 5.2695 25.98 14.645 35.355s22.094 14.645 35.355 14.645h800c13.262 0 25.98-5.2695 35.355-14.645s14.645-22.094 14.645-35.355v-250c0-13.262-5.2695-25.98-14.645-35.355s-22.094-14.645-35.355-14.645z" fill="#7f7f79"/>
</svg>

After

Width:  |  Height:  |  Size: 1,007 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -1026,6 +1026,8 @@ NEWSBLUR.AssetModel = Backbone.Router.extend({
refresh_feed: function(feed_id, callback) {
var self = this;
var feed = this.feeds.get(feed_id);
if (!feed) return;
var pre_callback = function(data) {
// NEWSBLUR.log(['refresh_feed pre_callback', data]);
@ -1037,7 +1039,7 @@ NEWSBLUR.AssetModel = Backbone.Router.extend({
this.make_request('/reader/feed/'+feed_id,
{
page: 0,
feed_address: this.feeds.get(feed_id).get('feed_address')
feed_address: feed.get('feed_address')
}, pre_callback,
null,
{
@ -1417,7 +1419,9 @@ NEWSBLUR.AssetModel = Backbone.Router.extend({
if (setting == 'read_filter' && _.string.contains(feed_id, 'river:')) {
default_setting = 'unread';
}
return feed && feed[s] || default_setting;
var view_setting = feed && feed[s] || default_setting;
// if (view_setting == "magazine") view_setting = "list";
return view_setting;
}
var view_settings = _.clone(NEWSBLUR.Preferences.view_settings[feed_id+'']) || {};
@ -1640,7 +1644,9 @@ NEWSBLUR.AssetModel = Backbone.Router.extend({
save_exception_retry: function(feed_id, callback, error_callback) {
var self = this;
var feed = this.feeds.get(feed_id);
if (!feed) return;
var pre_callback = function(data) {
// NEWSBLUR.log(['refresh_feed pre_callback', data]);
self.post_refresh_feeds(data, callback);
@ -1648,8 +1654,8 @@ NEWSBLUR.AssetModel = Backbone.Router.extend({
this.make_request('/rss_feeds/exception_retry', {
'feed_id': feed_id,
'reset_fetch': !!(this.feeds.get(feed_id).get('has_feed_exception') ||
this.feeds.get(feed_id).get('has_page_exception'))
'reset_fetch': !!(feed.get('has_feed_exception') ||
feed.get('has_page_exception'))
}, pre_callback, error_callback);
},

View file

@ -388,7 +388,7 @@ NEWSBLUR.Collections.Folders = Backbone.Collection.extend({
}
var remove_articles = function(str) {
words = str.split(" ");
var words = str.split(" ");
if (words.length <= 1) return str;
if (words[0] == 'the') return words.splice(1).join(" ");
return str;

View file

@ -45,12 +45,14 @@ NEWSBLUR.Models.Story = Backbone.Model.extend({
return score_name;
},
content_preview: function(attribute, length) {
content_preview: function(attribute, length, preserve_paragraphs) {
var content = this.get(attribute);
if (!attribute || !content) content = this.story_content();
// First do a naive strip, which is faster than rendering which makes network calls
content = content && content.replace(/<(?:.|\n)*?>/gm, ' ');
content = content && Inflector.stripTags(content);
content = content && content.replace(/<p(>| [^>]+>)/ig, '\n\n').replace(/(<([^>]+)>)/ig, ' ')
if (preserve_paragraphs) {
content = content && _.string.trim(content).replace(/\n{2,}/gm, '<br><br>').replace(/(<br\s*\/?>\s*){3,}/igm, '<br><br>');
}
content = content && content.replace(/[\u00a0\u200c]/g, ' '); // Invisible space, boo
content = content && content.replace(/\s+/gm, ' ');
@ -62,7 +64,6 @@ NEWSBLUR.Models.Story = Backbone.Model.extend({
if (this.get('image_urls').length >= index+1) {
var url = this.get('image_urls')[index];
if (window.location.protocol == 'https:' &&
NEWSBLUR.Globals.is_staff &&
_.str.startsWith(url, "http://")) {
var secure_url = this.get('secure_image_urls')[url];
if (secure_url) url = secure_url;

View file

@ -205,7 +205,7 @@
resize: true
});
if (_.contains(['split', 'list', 'grid'],
if (_.contains(['split', 'list', 'grid', 'magazine'],
NEWSBLUR.assets.view_setting(NEWSBLUR.reader.active_feed, 'layout'))) {
NEWSBLUR.app.story_titles.fill_out();
} else {
@ -397,7 +397,7 @@
contentLayoutOptions[story_anchor+'__onclose_start'] = $.rescope(this.toggle_story_titles_pane, this);
contentLayoutOptions[story_anchor+'__onopen_start'] = $.rescope(this.toggle_story_titles_pane, this);
this.layout.contentLayout = this.$s.$content_pane.layout(contentLayoutOptions);
} else if (_.contains(['list', 'grid'],
} else if (_.contains(['list', 'grid', 'magazine'],
NEWSBLUR.assets.view_setting(NEWSBLUR.reader.active_feed, 'layout'))) {
var rightLayoutOptions = {
resizeWhileDragging: true,
@ -529,13 +529,22 @@
add_body_classes: function() {
this.$s.$body.toggleClass('NB-is-premium', NEWSBLUR.Globals.is_premium);
this.$s.$body.toggleClass('NB-is-anonymous', NEWSBLUR.Globals.is_anonymous);
this.$s.$body.toggleClass('NB-is-authenticated', NEWSBLUR.Globals.is_authenticated);
this.$s.$body.toggleClass('NB-pref-full-width-story', !!this.model.preference('full_width_story'));
this.$s.$body.toggleClass('NB-is-authenticated', NEWSBLUR.Globals.is_authenticated);
if (!!this.model.preference('full_width_story')) {
this.model.preference('story_position', 'stretch');
this.model.preference('full_width_story', false); // Turn off to ignore and deprecate
}
this.$s.$body.removeClass('NB-pref-story-position-stretch')
.removeClass('NB-pref-story-position-left')
.removeClass('NB-pref-story-position-center')
.removeClass('NB-pref-story-position-right')
.toggleClass('NB-pref-story-position-' + this.model.preference('story_position'));
this.$s.$body.toggleClass('NB-dashboard-columns-single', this.model.preference('dashboard_columns') == 1);
this.$s.$body.removeClass('NB-story-layout-full')
.removeClass('NB-story-layout-split')
.removeClass('NB-story-layout-list')
.removeClass('NB-story-layout-grid')
.removeClass('NB-story-layout-magazine')
.addClass('NB-story-layout-'+NEWSBLUR.assets.view_setting(NEWSBLUR.reader.active_feed, 'layout'));
},
@ -590,7 +599,7 @@
var self = this;
percentage = percentage || 0;
seconds = parseFloat(Math.max(1, parseInt(seconds, 10)), 10);
var time;
if (percentage > 90) {
time = seconds;
} else if (percentage > 80) {
@ -942,7 +951,7 @@
page_in_story: function(amount, direction) {
amount = parseInt(amount, 10) / 100.0;
var page_height = this.$s.$story_pane.height();
if (_.contains(['list', 'grid'], NEWSBLUR.assets.view_setting(NEWSBLUR.reader.active_feed, 'layout'))) {
if (_.contains(['list', 'grid', 'magazine'], NEWSBLUR.assets.view_setting(NEWSBLUR.reader.active_feed, 'layout'))) {
page_height = this.$s.$story_titles.height();
}
var scroll_height = parseInt(page_height * amount, 10);
@ -983,7 +992,7 @@
}
}
}
} else if (_.contains(['list', 'grid'], NEWSBLUR.assets.view_setting(NEWSBLUR.reader.active_feed, 'layout'))) {
} else if (_.contains(['list', 'grid', 'magazine'], NEWSBLUR.assets.view_setting(NEWSBLUR.reader.active_feed, 'layout'))) {
var scroll_top = this.$s.$story_titles.scrollTop();
var $story = this.active_story.story_title_view.$el;
var story_height = $story.height();
@ -1005,7 +1014,7 @@
dir = '-';
}
// NEWSBLUR.log(['scroll_in_story', this.$s.$story_pane, direction, amount]);
if (_.contains(['list', 'grid'], NEWSBLUR.assets.view_setting(NEWSBLUR.reader.active_feed, 'layout'))) {
if (_.contains(['list', 'grid', 'magazine'], NEWSBLUR.assets.view_setting(NEWSBLUR.reader.active_feed, 'layout'))) {
this.$s.$story_titles.stop().scrollTo({
top: dir+'='+amount,
left:'+=0'
@ -1623,6 +1632,7 @@
var $split = $(".NB-task-layout-split");
var $list = $(".NB-task-layout-list");
var $grid = $(".NB-task-layout-grid");
var $magazine = $(".NB-task-layout-magazine");
var $full = $(".NB-task-layout-full");
var story_layout = NEWSBLUR.assets.view_setting(NEWSBLUR.reader.active_feed, 'layout');
this.$s.$story_titles[0].className = this.$s.$story_titles[0].className.replace(/ ?NB-layout-\w+/gi, '');
@ -1637,6 +1647,7 @@
$split.removeClass('NB-active');
$list.addClass('NB-active');
$grid.removeClass('NB-active');
$magazine.removeClass('NB-active');
} else if (story_layout == 'grid') {
$('.NB-taskbar-button.task_view_page').addClass('NB-hidden');
$('.NB-taskbar-button.task_view_feed').addClass('NB-first');
@ -1646,6 +1657,17 @@
$split.removeClass('NB-active');
$list.removeClass('NB-active');
$grid.addClass('NB-active');
$magazine.removeClass('NB-active');
} else if (story_layout == 'magazine') {
$('.NB-taskbar-button.task_view_page').addClass('NB-hidden');
$('.NB-taskbar-button.task_view_feed').addClass('NB-first');
$('.NB-taskbar-button.task_view_story').addClass('NB-hidden');
$('.NB-taskbar-button.task_view_text').addClass('NB-last');
$full.removeClass('NB-active');
$split.removeClass('NB-active');
$list.removeClass('NB-active');
$grid.removeClass('NB-active');
$magazine.addClass('NB-active');
} else if (story_layout == 'split') {
if (!this.flags.river_view) {
$('.NB-taskbar-button.task_view_page').removeClass('NB-hidden');
@ -1657,6 +1679,7 @@
$split.addClass('NB-active');
$list.removeClass('NB-active');
$grid.removeClass('NB-active');
$magazine.removeClass('NB-active');
} else if (story_layout == 'full') {
if (!this.flags.river_view) {
$('.NB-taskbar-button.task_view_page').removeClass('NB-hidden');
@ -1668,6 +1691,7 @@
$split.removeClass('NB-active');
$list.removeClass('NB-active');
$grid.removeClass('NB-active');
$magazine.removeClass('NB-active');
}
if (_.contains(['starred', 'read'], feed_id)) {
@ -1700,7 +1724,8 @@
this.set_correct_story_view_for_feed();
this.apply_resizable_layout({right_side: true});
if (original_layout == 'grid' || story_layout == 'grid') {
if (_.contains(['grid', 'magazine'], original_layout) ||
_.contains(['grid', 'magazine'], story_layout)) {
NEWSBLUR.app.story_titles.render();
}
if (story_layout == 'list') {
@ -1713,6 +1738,11 @@
this.active_story.story_title_view.toggle_selected();
}
NEWSBLUR.app.story_list.clear();
} else if (story_layout == 'magazine') {
if (this.active_story) {
this.active_story.story_title_view.toggle_selected();
}
NEWSBLUR.app.story_list.clear();
} else if (story_layout == 'split') {
NEWSBLUR.app.story_list.render();
if (this.active_story) {
@ -1739,7 +1769,7 @@
NEWSBLUR.app.story_titles.scroll_to_selected_story();
NEWSBLUR.app.story_list.scroll_to_selected_story();
NEWSBLUR.app.feed_list.scroll_to_selected();
if (_.contains(['split', 'list', 'grid'],
if (_.contains(['split', 'list', 'grid', 'magazine'],
NEWSBLUR.assets.view_setting(NEWSBLUR.reader.active_feed, 'layout'))) {
NEWSBLUR.app.story_titles.fill_out();
} else {
@ -2417,7 +2447,7 @@
this.switch_taskbar_view('feed', {skip_save_type: 'page'});
NEWSBLUR.app.story_list.show_stories_preference_in_feed_view();
}
} else if (_.contains(['list', 'grid'],
} else if (_.contains(['list', 'grid', 'magazine'],
NEWSBLUR.assets.view_setting(NEWSBLUR.reader.active_feed, 'layout')) &&
(this.story_view == 'page' || this.story_view == 'story')) {
this.switch_taskbar_view('feed', {skip_save_type: 'layout'});
@ -2769,7 +2799,7 @@
'fade': true
};
var $content_pane = this.$s.$content_pane;
feed_id = this.active_feed;
var feed_id = this.active_feed;
if (!feed_id) return;
if (this.flags['river_view']) {
var folder = this.active_folder;
@ -3114,7 +3144,7 @@
} else if ($('.task_view_'+view).hasClass('NB-disabled') ||
$('.task_view_'+view).hasClass('NB-hidden')) {
return;
} else if (_.contains(['list', 'grid'],
} else if (_.contains(['list', 'grid', 'magazine'],
NEWSBLUR.assets.view_setting(NEWSBLUR.reader.active_feed, 'layout')) &&
_.contains(['page', 'story'], view)) {
view = 'feed';
@ -3193,7 +3223,7 @@
NEWSBLUR.app.story_list.reset_story_positions();
if (!options.resize && this.active_story &&
_.contains(['list', 'grid'],
_.contains(['list', 'grid', 'magazine'],
NEWSBLUR.assets.view_setting(NEWSBLUR.reader.active_feed, 'layout'))) {
NEWSBLUR.app.text_tab_view.unload();
if (this.active_story.get('selected')) {
@ -3218,7 +3248,7 @@
}
} else if (!options.resize && this.active_story &&
this.active_story.get('selected') &&
_.contains(['list', 'grid'],
_.contains(['list', 'grid', 'magazine'],
NEWSBLUR.assets.view_setting(NEWSBLUR.reader.active_feed, 'layout'))) {
this.active_story.story_title_view.render_inline_story_detail();
}
@ -3244,7 +3274,7 @@
var $active = $('.NB-taskbar-view .NB-active');
var view;
if (_.contains(['list', 'grid'],
if (_.contains(['list', 'grid', 'magazine'],
NEWSBLUR.assets.view_setting(NEWSBLUR.reader.active_feed, 'layout'))) {
if (direction == -1) {
if ($active.hasClass('task_view_feed')) {
@ -5110,7 +5140,7 @@
NEWSBLUR.app.story_list.show_correct_explainer();
// NEWSBLUR.log(['Showing correct stories', this.story_view, unread_view_name, $stories_show.length, $stories_hide.length]);
if (_.contains(['split', 'list', 'grid'],
if (_.contains(['split', 'list', 'grid', 'magazine'],
NEWSBLUR.assets.view_setting(NEWSBLUR.reader.active_feed, 'layout'))) {
NEWSBLUR.app.story_titles.fill_out();
} else {
@ -5131,6 +5161,7 @@
var self = this;
feed_id = feed_id || this.active_feed;
var feed = this.model.get_feed(feed_id);
if (!feed) return;
feed.set({
fetched_once: false,
has_exception: false
@ -6733,6 +6764,10 @@
e.preventDefault();
self.switch_story_layout('grid');
});
$.targetIs(e, { tagSelector: '.NB-taskbar-button.NB-task-layout-magazine' }, function($t, $p){
e.preventDefault();
self.switch_story_layout('magazine');
});
$.targetIs(e, { tagSelector: '.NB-taskbar-options' }, function($t, $p){
e.preventDefault();
self.open_story_options_popover();

View file

@ -470,7 +470,7 @@ var classifier_prototype = {
// NEWSBLUR.log(['Make Story', story, feed]);
// HTML entities decoding.
story_title = _.string.trim($('<div/>').html(story.get('story_title')).text());
var story_title = _.string.trim($('<div/>').html(story.get('story_title')).text());
this.$modal = $.make('div', { className: 'NB-modal-classifiers NB-modal' }, [
$.make('div', { className: 'NB-modal-loading' }),

View file

@ -198,6 +198,13 @@ _.extend(NEWSBLUR.ReaderFeedException.prototype, {
$.make("img", { src: NEWSBLUR.Globals.MEDIA_URL+'/img/icons/circular/nav_story_grid_active.png' }),
$.make("div", { className: "NB-layout-title" }, "Grid")
])
]),
$.make('div', [
$.make('label', { 'for': 'NB-preference-layout-5' }, [
$.make('input', { id: 'NB-preference-layout-5', type: 'radio', name: 'story_layout', value: 'magazine' }),
$.make("img", { src: NEWSBLUR.Globals.MEDIA_URL+'/img/icons/circular/nav_story_magazine_active.png' }),
$.make("div", { className: "NB-layout-title" }, "Magazine")
])
])
])
])

View file

@ -382,6 +382,13 @@ _.extend(NEWSBLUR.ReaderPreferences.prototype, {
$.make("img", { src: NEWSBLUR.Globals.MEDIA_URL+'/img/icons/circular/nav_story_grid_active.png' }),
$.make("div", { className: "NB-layout-title" }, "Grid")
])
]),
$.make('div', [
$.make('label', { 'for': 'NB-preference-layout-5' }, [
$.make('input', { id: 'NB-preference-layout-5', type: 'radio', name: 'story_layout', value: 'magazine' }),
$.make("img", { src: NEWSBLUR.Globals.MEDIA_URL+'/img/icons/circular/nav_story_magazine_active.png' }),
$.make("div", { className: "NB-layout-title" }, "Magazine")
])
])
]),
$.make('div', { className: 'NB-preference-label'}, [
@ -759,25 +766,6 @@ _.extend(NEWSBLUR.ReaderPreferences.prototype, {
'Open links'
])
]),
$.make('div', { className: 'NB-preference NB-preference-fullwidthdstory' }, [
$.make('div', { className: 'NB-preference-options' }, [
$.make('div', [
$.make('input', { id: 'NB-preference-fullwidthdstory-1', type: 'radio', name: 'full_width_story', value: "false" }),
$.make('label', { 'for': 'NB-preference-fullwidthdstory-1' }, [
'Wrap story content at 700px to ease reading'
])
]),
$.make('div', [
$.make('input', { id: 'NB-preference-fullwidthdstory-2', type: 'radio', name: 'full_width_story', value: "true" }),
$.make('label', { 'for': 'NB-preference-fullwidthdstory-2' }, [
'Wrap story content at the edge of the screen'
])
])
]),
$.make('div', { className: 'NB-preference-label'}, [
'Story width'
])
]),
$.make('div', { className: 'NB-preference NB-preference-truncatestory' }, [
$.make('div', { className: 'NB-preference-options' }, [
$.make('div', [
@ -1131,12 +1119,6 @@ _.extend(NEWSBLUR.ReaderPreferences.prototype, {
return false;
}
});
$('input[name=full_width_story]', $modal).each(function() {
if ($(this).val() == ""+NEWSBLUR.Preferences.full_width_story) {
$(this).prop('checked', true);
return false;
}
});
$('input[name=truncate_story]', $modal).each(function() {
if ($(this).val() == NEWSBLUR.Preferences.truncate_story) {
$(this).prop('checked', true);

View file

@ -43,7 +43,7 @@ NEWSBLUR.Views.OriginalTabView = Backbone.View.extend({
find_story_in_feed_iframe: function(story) {
if (!story) return $([]);
$iframe = this.$el.contents();
var $iframe = this.$el.contents();
var $stories = $([]);
if (this.flags['iframe_story_locations_fetched'] || story.id in this.cache.iframe_stories) {
@ -240,7 +240,7 @@ NEWSBLUR.Views.OriginalTabView = Backbone.View.extend({
var last_story = this.cache.iframe_story_positions[last_story_position];
var $last_story;
if (last_story) {
$last_story = this.find_story_in_feed_iframe(last_story, $iframe);
$last_story = this.find_story_in_feed_iframe(last_story);
}
// NEWSBLUR.log(['last_story', last_story_index, last_story_position, last_story, $last_story]);
var last_story_same_position;
@ -258,7 +258,7 @@ NEWSBLUR.Views.OriginalTabView = Backbone.View.extend({
NEWSBLUR.assets.stories.any(_.bind(function(story, i) {
if (last_story_same_position && i < last_story_index) return true;
var $story = this.find_story_in_feed_iframe(story, $iframe);
var $story = this.find_story_in_feed_iframe(story);
// NEWSBLUR.log(['Pre-fetching', i, last_story_index, last_story_same_position, $story, story.get('story_title')]);
if (!$story ||
!$story.length ||
@ -308,9 +308,9 @@ NEWSBLUR.Views.OriginalTabView = Backbone.View.extend({
});
NEWSBLUR.assets.stories.any(_.bind(function(story, i) {
if (story.get('story_feed_id') == NEWSBLUR.reader.active_feed ||
"social:" + story.get('social_user_id') == NEWSBLUR.reader.active_feed) {
var $story = this.find_story_in_feed_iframe(story, $iframe);
if ((story.get('story_feed_id') == NEWSBLUR.reader.active_feed ||
"social:" + story.get('social_user_id') == NEWSBLUR.reader.active_feed)) {
var $story = this.find_story_in_feed_iframe(story);
// NEWSBLUR.log(['Fetching story', i, story.get('story_title'), $story]);
if (self.cache['story_misses'] > 5) {
@ -661,4 +661,4 @@ NEWSBLUR.Views.OriginalTabView = Backbone.View.extend({
}
}
});
});

View file

@ -250,7 +250,10 @@ NEWSBLUR.Views.StoryDetailView = Backbone.View.extend({
<% } %>\
</div>\
<% if (story.get("starred_date")) { %>\
<span class="NB-feed-story-starred-date"><%= story.get("starred_date") %></span>\
<div class="NB-feed-story-starred-date">\
<span class="NB-icon">Saved: </span>\
<%= story.get("starred_date") %>\
</div >\
<% } %>\
</div>\
</div>\
@ -690,7 +693,7 @@ NEWSBLUR.Views.StoryDetailView = Backbone.View.extend({
href = footnote_href;
var offset = $(href).offset().top;
var $scroll;
if (_.contains(['list', 'grid'], NEWSBLUR.assets.view_setting(NEWSBLUR.reader.active_feed, 'layout'))) {
if (_.contains(['list', 'grid', 'magazine'], NEWSBLUR.assets.view_setting(NEWSBLUR.reader.active_feed, 'layout'))) {
$scroll = NEWSBLUR.reader.$s.$story_titles;
} else if (NEWSBLUR.reader.flags['temporary_story_view'] ||
NEWSBLUR.reader.story_view == 'text') {
@ -998,7 +1001,7 @@ NEWSBLUR.Views.StoryDetailView = Backbone.View.extend({
},
scroll_to_comments: function() {
if (_.contains(['list', 'grid'], NEWSBLUR.assets.view_setting(NEWSBLUR.reader.active_feed, 'layout'))) {
if (_.contains(['list', 'grid', 'magazine'], NEWSBLUR.assets.view_setting(NEWSBLUR.reader.active_feed, 'layout'))) {
NEWSBLUR.app.story_titles.scroll_to_selected_story(this.model, {
scroll_to_comments: true,
scroll_offset: -50

View file

@ -23,7 +23,9 @@ NEWSBLUR.StoryOptionsPopover = NEWSBLUR.ReaderPopover.extend({
"click .NB-view-setting-option": "change_view_setting",
"click .NB-line-spacing-option": "change_line_spacing",
"click .NB-story-titles-pane-option": "change_story_titles_pane",
"click .NB-story-position-option": "change_story_position",
"click .NB-single-story-option": "change_single_story",
"click .NB-story-": "change_story_position",
"click .NB-grid-columns-option": "change_grid_columns",
"click .NB-grid-height-option": "change_grid_height",
"click .NB-premium-link": "open_premium_modal"
@ -74,8 +76,21 @@ NEWSBLUR.StoryOptionsPopover = NEWSBLUR.ReaderPopover.extend({
$.make('div', { className: 'NB-icon' }),
'Single Story'
])
]),
$.make('ul', { className: 'segmented-control NB-options-story-position' }, [
$.make('li', { className: 'NB-story-position-option NB-options-story-position-stretch', role: "button" }, [
'Full width'
]),
$.make('li', { className: 'NB-story-position-option NB-options-story-position-left', role: "button" }, [
$.make('div', { className: 'NB-icon' })
]),
$.make('li', { className: 'NB-story-position-option NB-options-story-position-center NB-active', role: "button" }, [
$.make('div', { className: 'NB-icon' })
]),
$.make('li', { className: 'NB-story-position-option NB-options-story-position-right NB-active', role: "button" }, [
$.make('div', { className: 'NB-icon' })
])
])
]),
$.make('div', { className: 'NB-popover-section' }, [
$.make('div', { className: 'NB-popover-section-title' }, 'Story Layout - Grid Columns'),
@ -138,8 +153,8 @@ NEWSBLUR.StoryOptionsPopover = NEWSBLUR.ReaderPopover.extend({
])
]),
(!NEWSBLUR.Globals.is_premium && $.make('div', { className: 'NB-premium-explainer' }, [
'Premium fonts require a ',
$.make('spam', { className: 'NB-splash-link NB-premium-link' }, 'premium account')
'Premium fonts require a ',
$.make('span', { className: 'NB-splash-link NB-premium-link' }, 'premium account')
]))
]),
$.make('div', { className: 'NB-popover-section' }, [
@ -204,6 +219,7 @@ NEWSBLUR.StoryOptionsPopover = NEWSBLUR.ReaderPopover.extend({
var grid_height = NEWSBLUR.assets.preference('grid_height');
var image_preview = NEWSBLUR.assets.preference('image_preview');
var content_preview = NEWSBLUR.assets.preference('show_content_preview');
var story_position = NEWSBLUR.assets.preference('story_position');
this.$('.NB-font-family-option').removeClass('NB-active');
this.$('.NB-options-font-family-'+font_family).addClass('NB-active');
@ -216,6 +232,8 @@ NEWSBLUR.StoryOptionsPopover = NEWSBLUR.ReaderPopover.extend({
this.$('.NB-line-spacing-option').removeClass('NB-active');
this.$('.NB-options-line-spacing-'+line_spacing).addClass('NB-active');
this.$('.NB-story-position-option').removeClass('NB-active');
this.$('.NB-options-story-position-'+story_position).addClass('NB-active');
this.$('.NB-story-titles-pane-option').removeClass('NB-active');
this.$('.NB-options-story-titles-pane-'+titles_layout_pane).addClass('NB-active');
this.$('.NB-single-story-option').removeClass('NB-active');
@ -373,6 +391,27 @@ NEWSBLUR.StoryOptionsPopover = NEWSBLUR.ReaderPopover.extend({
NEWSBLUR.assets.preference('story_titles_pane_size', pane_size);
NEWSBLUR.reader.apply_resizable_layout({right_side: true});
},
change_story_position: function(e) {
var $target = $(e.currentTarget);
if ($target.hasClass("NB-options-story-position-stretch")) {
this.update_story_position('stretch');
} else if ($target.hasClass("NB-options-story-position-left")) {
this.update_story_position('left');
} else if ($target.hasClass("NB-options-story-position-center")) {
this.update_story_position('center');
} else if ($target.hasClass("NB-options-story-position-right")) {
this.update_story_position('right');
}
this.show_correct_options();
},
update_story_position: function(setting) {
NEWSBLUR.assets.preference('story_position', setting);
NEWSBLUR.reader.add_body_classes();
},
change_single_story: function(e) {
var $target = $(e.currentTarget);

View file

@ -27,6 +27,7 @@ NEWSBLUR.Views.StoryTitleView = Backbone.View.extend({
render: function() {
var template_name = !this.model.get('selected') && this.options.is_grid ?
'grid_template' : 'template';
if (this.options.is_magazine) template_name = "magazine_template";
// console.log(['render story title', template_name, this.$el[0], this.options.is_grid, this.show_image_preview(), this.options.override_layout, NEWSBLUR.assets.get_feed(this.model.get('story_feed_id'))]);
this.$el.html(this[template_name]({
story : this.model,
@ -35,7 +36,7 @@ NEWSBLUR.Views.StoryTitleView = Backbone.View.extend({
NEWSBLUR.reader.flags.social_view) &&
NEWSBLUR.assets.get_feed(this.model.get('story_feed_id')),
options : this.options,
show_content_preview : this.show_content_preview(),
show_content_preview : this.show_content_preview(template_name),
show_image_preview : this.show_image_preview()
}));
this.$st = this.$(".NB-story-title");
@ -45,7 +46,7 @@ NEWSBLUR.Views.StoryTitleView = Backbone.View.extend({
this.load_youtube_embeds();
var story_layout = this.options.override_layout || NEWSBLUR.assets.view_setting(NEWSBLUR.reader.active_feed, 'layout');
if (this.options.is_grid) this.watch_grid_image();
if (_.contains(['list'], story_layout) && this.show_image_preview()) this.watch_grid_image();
if (_.contains(['list', 'magazine'], story_layout) && this.show_image_preview()) this.watch_grid_image();
if (_.contains(['split'], story_layout) && this.show_image_preview() && NEWSBLUR.assets.preference('feed_view_single_story')) this.watch_grid_image();
return this;
@ -97,7 +98,7 @@ NEWSBLUR.Views.StoryTitleView = Backbone.View.extend({
<div class="NB-storytitles-feed-border-outer"></div>\
<% if (story.image_url()) { %>\
<div class="NB-storytitles-story-image-container">\
<div class="NB-storytitles-story-image"></div>\
<div class="NB-storytitles-story-image" <% if (story.image_url()) { %>style="background-image: none, url(\'<%= story.image_url() %>\'); display: block;"<% } %>></div>\
</div>\
<% } %>\
<div class="NB-storytitles-content">\
@ -133,6 +134,48 @@ NEWSBLUR.Views.StoryTitleView = Backbone.View.extend({
<div class="NB-story-detail"></div>\
'),
magazine_template: _.template('\
<div class="NB-story-title <% if (!show_content_preview) { %>NB-story-title-hide-preview<% } %>">\
<div class="NB-storytitles-feed-border-inner"></div>\
<div class="NB-storytitles-feed-border-outer"></div>\
<% if (story.image_url()) { %>\
<div class="NB-storytitles-story-image-container">\
<div class="NB-storytitles-story-image" <% if (story.image_url()) { %>style="background-image: none, url(\'<%= story.image_url() %>\');"<% } %>></div>\
</div>\
<% } %>\
<div class="NB-storytitles-content">\
<% if (feed) { %>\
<div class="NB-story-feed">\
<img class="feed_favicon" src="<%= $.favicon(feed) %>">\
<span class="feed_title"><%= feed.get("feed_title") %></span>\
</div>\
<% } %>\
<a href="<%= story.get("story_permalink") %>" class="story_title NB-hidden-fade">\
<div class="NB-storytitles-star"></div>\
<div class="NB-storytitles-share"></div>\
<div class="NB-storytitles-sentiment"></div>\
<div class="NB-story-manage-icon" role="button"></div>\
<span class="NB-storytitles-title"><%= story.get("story_title") %></span>\
<% if (show_content_preview) { %>\
<div class="NB-storytitles-content-preview"><%= show_content_preview %></div>\
<% } %>\
</a>\
</div>\
<div class="NB-storytitles-magazine-bottom">\
<span class="NB-storytitles-author"><%= story.story_authors() %></span>\
<span class="story_date NB-hidden-fade"><%= story.formatted_short_date() %></span>\
</div>\
<% if (story.get("comment_count_friends")) { %>\
<div class="NB-storytitles-shares">\
<% _.each(story.get("commented_by_friends"), function(user_id) { %>\
<img class="NB-user-avatar" src="<%= NEWSBLUR.assets.user_profiles.find(user_id).get("photo_url") %>">\
<% }) %>\
</div>\
<% } %>\
</div>\
<div class="NB-story-detail"></div>\
'),
render_inline_story_detail: function(temporary_text) {
// console.log(['render_inline_story_detail', this.model.get('story_title')]);
if (NEWSBLUR.reader.story_view == 'text' || temporary_text) {
@ -159,6 +202,10 @@ NEWSBLUR.Views.StoryTitleView = Backbone.View.extend({
}
},
render_magazine_story_detail: function () {
this.render_inline_story_detail();
},
destroy: function() {
// console.log(["destroy story title", this.model.get('story_title')]);
if (this.text_view) {
@ -204,15 +251,15 @@ NEWSBLUR.Views.StoryTitleView = Backbone.View.extend({
}
},
show_content_preview: function() {
show_content_preview: function(template_name) {
var preference = NEWSBLUR.assets.preference('show_content_preview');
if (!preference) return preference;
var max_length = preference == 'small' ? 300 : preference == 'medium' ? 600 : 1000;
if (this.options.override_layout == 'grid' ||
NEWSBLUR.assets.view_setting(NEWSBLUR.reader.active_feed, 'layout') == 'grid') {
if (_.contains(['grid_template', 'magazine_template'], template_name)) {
max_length = preference == 'small' ? 500 : preference == 'medium' ? 1000 : 1500;
return this.model.content_preview('story_content', max_length) || " ";
var preserve_paragraphs = true;
return this.model.content_preview('story_content', max_length, preserve_paragraphs) || " ";
}
var pruned_description = this.model.content_preview('story_content', max_length) || " ";
var pruned_title = this.model.content_preview('story_title');
@ -232,7 +279,7 @@ NEWSBLUR.Views.StoryTitleView = Backbone.View.extend({
var story_layout = this.options.override_layout ||
NEWSBLUR.assets.view_setting(NEWSBLUR.reader.active_feed, 'layout');
var pane_anchor = this.options.override_layout ? "west" : NEWSBLUR.assets.preference('story_pane_anchor');
if (_.contains(['list', 'grid'], story_layout)) return true;
if (_.contains(['list', 'grid', 'magazine'], story_layout)) return true;
if (story_layout == 'split' && _.contains(['north', 'south'], pane_anchor)) return true;
return !!this.model.image_url();
@ -315,10 +362,10 @@ NEWSBLUR.Views.StoryTitleView = Backbone.View.extend({
this.select_regex(e, text) ||
this.select_regex(d, text);
if (i) {
this.$(".NB-storytitles-story-image").css({
'display': 'block',
'background-image': "url("+NEWSBLUR.Globals.MEDIA_URL+"img/reader/youtube_play.png), url(" + "https://img.youtube.com/vi/" + i + "/0.jpg" + ")"
});
// this.$(".NB-storytitles-story-image").css({
// 'display': 'block',
// 'background-image': "url("+NEWSBLUR.Globals.MEDIA_URL+"img/reader/youtube_play.png), url(" + "https://img.youtube.com/vi/" + i + "/0.jpg" + ")"
// });
return true;
}
},
@ -381,16 +428,19 @@ NEWSBLUR.Views.StoryTitleView = Backbone.View.extend({
}
},
toggle_selected: function(model, selected, options) {
toggle_selected: function (model, selected, options) {
var story_layout = this.options.override_layout ||
NEWSBLUR.assets.view_setting(NEWSBLUR.reader.active_feed, 'layout');
if (this.options.is_grid) this.render();
this.$st.toggleClass('NB-selected', !!this.model.get('selected'));
this.$el.toggleClass('NB-selected', !!this.model.get('selected'));
if (!!this.model.get('selected')) {
if (_.contains(['list', 'grid'], this.options.override_layout ||
NEWSBLUR.assets.view_setting(NEWSBLUR.reader.active_feed, 'layout'))) {
if (_.contains(['list', 'grid'], story_layout)) {
this.render_inline_story_detail();
} else if (_.contains(['magazine'], story_layout)) {
this.render_magazine_story_detail();
} else {
this.destroy_inline_story_detail();
}
@ -479,7 +529,7 @@ NEWSBLUR.Views.StoryTitleView = Backbone.View.extend({
return;
}
if (_.contains(['list', 'grid'], this.options.override_layout ||
if (_.contains(['list', 'grid', 'magazine'], this.options.override_layout ||
NEWSBLUR.assets.view_setting(NEWSBLUR.reader.active_feed, 'layout')) &&
this.model.get('selected')) {
this.collapse_story();

View file

@ -40,6 +40,7 @@ NEWSBLUR.Views.StoryTitlesView = Backbone.View.extend({
model: story,
collection: collection,
is_grid: story_layout == 'grid',
is_magazine: story_layout == 'magazine',
override_layout: override_layout,
on_dashboard: on_dashboard
}).render();
@ -71,6 +72,7 @@ NEWSBLUR.Views.StoryTitlesView = Backbone.View.extend({
model: story,
collection: collection,
is_grid: story_layout == 'grid',
is_magazine: story_layout == 'magazine',
override_layout: override_layout,
on_dashboard: on_dashboard
}).render();
@ -256,7 +258,7 @@ NEWSBLUR.Views.StoryTitlesView = Backbone.View.extend({
]);
var story_layout = this.options.override_layout ||
NEWSBLUR.assets.view_setting(NEWSBLUR.reader.active_feed, 'layout');
if (_.contains(['list', 'grid'], story_layout) || NEWSBLUR.assets.preference('mark_read_on_scroll_titles')) {
if (_.contains(['list', 'grid', 'magazine'], story_layout) || NEWSBLUR.assets.preference('mark_read_on_scroll_titles')) {
var pane_height = this.$story_titles.height();
var endbar_height = 20;
var last_story_height = 80;
@ -308,7 +310,7 @@ NEWSBLUR.Views.StoryTitlesView = Backbone.View.extend({
// console.log(["scroll_to_selected_story 1", story, options]);
var story_title_visisble = this.$story_titles.isScrollVisible(story_title_view.$el);
if (!story_title_visisble || options.force ||
_.contains(['list', 'grid'], story_layout)) {
_.contains(['list', 'grid', 'magazine'], story_layout)) {
var container_offset = this.$story_titles.position().top;
var scroll = story_title_view.$el.find('.NB-story-title').position().top;
if (options.scroll_to_comments) {
@ -318,7 +320,7 @@ NEWSBLUR.Views.StoryTitlesView = Backbone.View.extend({
var height = this.$story_titles.outerHeight();
var position = scroll+container-height/5;
// console.log(["scroll_to_selected_story 2", container_offset, scroll, container, height, position]);
if (_.contains(['list', 'grid'], story_layout)) {
if (_.contains(['list', 'grid', 'magazine'], story_layout)) {
position = scroll+container;
}
if (story_layout == 'grid') {

View file

@ -138,8 +138,8 @@ window.Inflector = {
var numberMatcher = /(\d+)(\d{3})/;
number += '';
var fragments = number.split('.');
whole = fragments[0];
decimal = fragments.length > 1 ? '.' + fragments[1] : '';
var whole = fragments[0];
var decimal = fragments.length > 1 ? '.' + fragments[1] : '';
while (numberMatcher.test(whole)) {
whole = whole.replace(numberMatcher, '$1,$2');
}
@ -175,4 +175,4 @@ window.Inflector = {
return s.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1');
}
};
};

File diff suppressed because one or more lines are too long

View file

@ -352,7 +352,7 @@ NEWSBLUR.log = function(msg) {
if(opts.cancelBubbling){
fails = true;
}else{
$tp = $t.closest(ts);
var $tp = $t.closest(ts);
if(!$tp.length){
fails = true;
}else{

View file

@ -26,7 +26,7 @@ DEBUG = True
# DEBUG_ASSETS controls JS/CSS asset packaging. Turning this off requires you to run
# `./manage.py collectstatic` first. Turn this on for development so you can see
# changes in your JS/CSS.
DEBUG_ASSETS = False # Make sure to run `./manage.py collectstatic` first
DEBUG_ASSETS = False # Make sure to run `./manage.py collectstatic` first
DEBUG_ASSETS = True
# DEBUG_QUERIES controls the output of the database query logs. Can be rather verbose

View file

@ -812,7 +812,7 @@ PIPELINE = {
'JS_COMPRESSOR': 'pipeline.compressors.closure.ClosureCompressor',
# 'CSS_COMPRESSOR': 'pipeline.compressors.NoopCompressor',
# 'JS_COMPRESSOR': 'pipeline.compressors.NoopCompressor',
'CLOSURE_BINARY': '/usr/bin/java -jar node_modules/google-closure-compiler-java/compiler.jar',
'CLOSURE_BINARY': '/usr/bin/java -jar /usr/local/bin/compiler.jar',
'CLOSURE_ARGUMENTS': '--language_in ECMASCRIPT_2016 --language_out ECMASCRIPT_2016 --warning_level DEFAULT',
'JAVASCRIPT': {
'common': {

View file

@ -86,6 +86,7 @@
'show_global_shared_stories': true,
'show_infrequent_site_stories': true,
'full_width_story' : false,
'story_position' : 'center',
'truncate_story' : 'social',
'autoopen_folder' : false,
'show_content_preview' : true,

View file

@ -266,6 +266,10 @@
<div class="NB-task-image"></div>
<span class="NB-task-title">List</span>
</li>
<li class="NB-taskbar-button NB-task-layout-magazine NB-tipsy" tipsy-title="Magazine">
<div class="NB-task-image"></div>
<span class="NB-task-title">Magazine</span>
</li>
<li class="NB-taskbar-button NB-task-layout-grid NB-tipsy" tipsy-title="Grid">
<div class="NB-task-image"></div>
<span class="NB-task-title">Grid</span>

View file

@ -48,8 +48,8 @@
endpoint to begin the OAuth authentication procedure. Use the <code>/oauth/token</code>
endpoint to receive your authorized token, which you can pass in with every request.</p>
<p>You can not required to use OAuth, in which case you will need to pass the <code>newsblur_sessionid</code>
cookie with every request. This cookie is set on login.</p>
<p>You are not required to use OAuth, in which case you will need to pass the <code>newsblur_sessionid</code>
cookie with every request. This cookie is set on <a href="#/api/login">login</a>.</p>
</div>
</div>

View file

@ -463,27 +463,13 @@
desc: "JSON serialized dictionary of user_ids to feed_ids to an array of story_ids."
required: true
example: "{<br>user_id: <br>{feed_id: ['story_id_1', 'story_id_2'],<br>24: ['story_id_3']<br>}<br>}"
- url: /reader/mark_story_as_unread
method: POST
short_desc: "Mark a story as unread."
long_desc:
- "Mark a story as unread."
params:
- key: story_id
desc: "Story id to mark unread."
required: true
example: "http://www.ofbrooklyn.com/story-title"
- key: feed_id
desc: "Feed id that the story is from."
required: true
example: "42"
- url: /reader/mark_story_hash_as_unread
method: POST
short_desc: "Mark a story as unread."
long_desc:
- "Mark a single story as unread using its unique story_hash."
- "Mark a single story or multiple stories as unread using its unique story_hash."
- "Multiple story hashes can be sent at once."
- "Premium users can mark up to 30 days as unread. Free users only have 15 days."
- >
If a story is too old to mark as read, look for (and display to the user) the
@ -499,21 +485,6 @@
example: >
story_hash=123:a1d62b
- url: /reader/mark_story_as_starred
method: POST
short_desc: "Mark a story as starred (saved)."
long_desc:
- "Mark a story as starred (saved)."
params:
- key: story_id
desc: "Story id to save."
required: true
example: "64"
- key: feed_id
desc: "Feed id that the story is from."
required: true
example: "42"
- url: /reader/mark_story_hash_as_starred
method: POST
short_desc: "Mark a story as starred (saved)."
@ -535,26 +506,12 @@
tips:
- "You can send a list for user_tags and highlights. <code>user_tags[]=blog&user_tags[]=cooking</code>"
- url: /reader/mark_story_as_unstarred
method: POST
short_desc: "Mark a story as unstarred (unsaved)."
long_desc:
- "Mark a story as unstarred (unsaved)."
params:
- key: story_id
desc: "Story id to unsave."
required: true
example: "64"
- key: feed_id
desc: "Feed id that the story is from."
required: true
example: "42"
- url: /reader/mark_story_hash_as_unstarred
method: POST
short_desc: "Mark a story as unstarred (unsaved)."
long_desc:
- "Mark a story as unstarred (unsaved)."
- "Multiple story hashes can be sent at once."
params:
- key: story_hash
desc: "Story to unsave, specified by hash."

File diff suppressed because it is too large Load diff

View file

@ -7,7 +7,6 @@ import urllib.request, urllib.parse, urllib.error
import urllib.parse
import random
import warnings
from django.core.mail import mail_admins
from django.utils.translation import ungettext
from django.utils.encoding import smart_str
from utils import log as logging
@ -41,8 +40,8 @@ def timelimit(timeout):
raise TimeoutError('took too long')
if c.error:
tb = ''.join(traceback.format_exception(c.exc_info[0], c.exc_info[1], c.exc_info[2]))
logging.debug(tb)
mail_admins('Error in timeout: %s' % c.exc_info[0], tb)
logging.debug(f" ***> Traceback timeout error: {tb}")
# mail_admins('Error in timeout: %s' % c.exc_info[0], tb)
raise c.error
return c.result
return _2
@ -221,8 +220,7 @@ def mail_feed_error_to_admin(feed, e, local_vars=None, subject=None):
pprint.pformat(feed.__dict__),
pprint.pformat(local_vars)
)
# print message
mail_admins(subject, message)
logging.debug(f" ***> Feed error, {subject}: {message}")
## {{{ http://code.activestate.com/recipes/576611/ (r11)
from operator import itemgetter

View file

@ -7,11 +7,11 @@ from decimal import Decimal
from django.core import serializers
from django.conf import settings
from django.http import HttpResponse, HttpResponseForbidden, Http404
from django.core.mail import mail_admins
from django.db.models.query import QuerySet
# from django.utils.deprecation import CallableBool
from mongoengine.queryset.queryset import QuerySet as MongoQuerySet
from bson.objectid import ObjectId
from utils import log as logging
import sys
import datetime
@ -161,7 +161,8 @@ def json_response(request, response=None):
'text': str(e)}
code = 500
if not settings.DEBUG:
mail_admins(subject, message, fail_silently=True)
logging.debug(f" ***> JSON exception {subject}: {message}")
logging.debug('\n'.join(traceback.format_exception(*exc_info)))
else:
print('\n'.join(traceback.format_exception(*exc_info)))

View file

@ -27,6 +27,7 @@ class MongoDumpMiddleware(object):
# save old methods
setattr(MongoClient, '_logging', True)
if hasattr(MongoClient, '_send_message_with_response'):
connection.queriesx = []
MongoClient._send_message_with_response = \
self._instrument(MongoClient._send_message_with_response)
MongoReplicaSetClient._send_message_with_response = \

View file

@ -19,6 +19,7 @@ class RedisDumpMiddleware(object):
if not getattr(Connection, '_logging', False):
# save old methods
setattr(Connection, '_logging', True)
connection.queriesx = []
Connection.pack_command = \
self._instrument(Connection.pack_command)

View file

@ -31,8 +31,8 @@ class DumpRequestMiddleware:
self.color_db(request.sql_times_elapsed['redis_pubsub'], '~FC'),
request.sql_times_elapsed['redis_pubsub'],
)
logging.user(request, " ---> %s~SN~FCDB times: ~FYsql: %s%.4f~SNs ~SN~FMmongo: %s%.5f~SNs ~SN~FCredis: %s" % (
self.elapsed_time(request),
logging.user(request, "~SN~FCDB times ~SB~FK%s~SN~FC: ~FYsql: %s%.4f~SNs ~SN~FMmongo: %s%.5f~SNs ~SN~FCredis: %s" % (
request.path,
self.color_db(request.sql_times_elapsed['sql'], '~FY'),
request.sql_times_elapsed['sql'],
self.color_db(request.sql_times_elapsed['mongo'], '~FM'),
@ -63,8 +63,8 @@ class DumpRequestMiddleware:
color = '~SB~FR'
elif seconds > .1:
color = '~FW'
elif seconds == 0:
color = '~FK'
# elif seconds == 0:
# color = '~FK~SB'
return color
def __init__(self, get_response=None):