Custom CSS and Custom JavaScript, saved on the server.

This commit is contained in:
Samuel Clay 2017-05-18 16:59:35 -07:00
parent c914d55abf
commit 6c24e09a78
6 changed files with 117 additions and 30 deletions

View file

@ -5,7 +5,7 @@ from vendor.zebra.forms import StripePaymentForm
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.contrib.auth import authenticate from django.contrib.auth import authenticate
from django.contrib.auth.models import User from django.contrib.auth.models import User
from apps.profile.models import change_password, blank_authenticate, MGiftCode from apps.profile.models import change_password, blank_authenticate, MGiftCode, MCustomStyling
from apps.social.models import MSocialProfile from apps.social.models import MSocialProfile
PLANS = [ PLANS = [
@ -113,7 +113,12 @@ class AccountSettingsForm(forms.Form):
old_password = forms.CharField(widget=forms.PasswordInput(attrs={'class': 'NB-input'}), old_password = forms.CharField(widget=forms.PasswordInput(attrs={'class': 'NB-input'}),
label='password', label='password',
required=False) required=False)
# error_messages={'required': 'Please enter a password.'}) custom_js = forms.CharField(widget=forms.TextInput(attrs={'class': 'NB-input'}),
label='custom_js',
required=False)
custom_css = forms.CharField(widget=forms.TextInput(attrs={'class': 'NB-input'}),
label='custom_css',
required=False)
def __init__(self, user, *args, **kwargs): def __init__(self, user, *args, **kwargs):
self.user = user self.user = user
@ -161,6 +166,8 @@ class AccountSettingsForm(forms.Form):
new_password = self.cleaned_data.get('new_password', None) new_password = self.cleaned_data.get('new_password', None)
old_password = self.cleaned_data.get('old_password', None) old_password = self.cleaned_data.get('old_password', None)
email = self.cleaned_data.get('email', None) email = self.cleaned_data.get('email', None)
custom_css = self.cleaned_data.get('custom_css', None)
custom_js = self.cleaned_data.get('custom_js', None)
if username and self.user.username != username: if username and self.user.username != username:
change_password(self.user, self.user.username, username) change_password(self.user, self.user.username, username)
@ -178,6 +185,8 @@ class AccountSettingsForm(forms.Form):
if old_password or new_password: if old_password or new_password:
change_password(self.user, old_password, new_password) change_password(self.user, old_password, new_password)
MCustomStyling.save_user(self.user.pk, custom_css, custom_js)
class RedeemCodeForm(forms.Form): class RedeemCodeForm(forms.Form):
gift_code = forms.CharField(widget=forms.TextInput(), gift_code = forms.CharField(widget=forms.TextInput(),
label="Gift code", label="Gift code",

View file

@ -7,7 +7,6 @@ import re
import redis import redis
import uuid import uuid
import mongoengine as mongo import mongoengine as mongo
from pprint import pprint
from django.db import models from django.db import models
from django.db import IntegrityError from django.db import IntegrityError
from django.db.utils import DatabaseError from django.db.utils import DatabaseError
@ -16,7 +15,6 @@ from django.db.models import Sum, Avg, Count
from django.conf import settings from django.conf import settings
from django.contrib.auth import authenticate from django.contrib.auth import authenticate
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.mail import mail_admins
from django.core.mail import EmailMultiAlternatives from django.core.mail import EmailMultiAlternatives
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.template.loader import render_to_string from django.template.loader import render_to_string
@ -515,7 +513,6 @@ class Profile(models.Model):
@classmethod @classmethod
def count_all_feed_subscribers_for_user(self, user): def count_all_feed_subscribers_for_user(self, user):
SUBSCRIBER_EXPIRE = datetime.datetime.now() - datetime.timedelta(days=settings.SUBSCRIBER_EXPIRE)
r = redis.Redis(connection_pool=settings.REDIS_FEED_SUB_POOL) r = redis.Redis(connection_pool=settings.REDIS_FEED_SUB_POOL)
if not isinstance(user, User): if not isinstance(user, User):
user = User.objects.get(pk=user) user = User.objects.get(pk=user)
@ -604,12 +601,12 @@ class Profile(models.Model):
params = dict(receiver_user_id=self.user.pk, email_type='first_share') params = dict(receiver_user_id=self.user.pk, email_type='first_share')
try: try:
sent_email = MSentEmail.objects.get(**params) MSentEmail.objects.get(**params)
if not force: if not force:
# Return if email already sent # Return if email already sent
return return
except MSentEmail.DoesNotExist: except MSentEmail.DoesNotExist:
sent_email = MSentEmail.objects.create(**params) MSentEmail.objects.create(**params)
social_profile = MSocialProfile.objects.get(user_id=self.user.pk) social_profile = MSocialProfile.objects.get(user_id=self.user.pk)
params = { params = {
@ -630,14 +627,14 @@ class Profile(models.Model):
logging.user(self.user, "~BB~FM~SBSending first share to blurblog email to: %s" % self.user.email) logging.user(self.user, "~BB~FM~SBSending first share to blurblog email to: %s" % self.user.email)
def send_new_premium_email(self, force=False): def send_new_premium_email(self, force=False):
subs = UserSubscription.objects.filter(user=self.user) # subs = UserSubscription.objects.filter(user=self.user)
message = """Woohoo! # message = """Woohoo!
#
User: %(user)s # User: %(user)s
Feeds: %(feeds)s # Feeds: %(feeds)s
#
Sincerely, # Sincerely,
NewsBlur""" % {'user': self.user.username, 'feeds': subs.count()} # NewsBlur""" % {'user': self.user.username, 'feeds': subs.count()}
# mail_admins('New premium account', message, fail_silently=True) # mail_admins('New premium account', message, fail_silently=True)
if not self.user.email or not self.send_emails: if not self.user.email or not self.send_emails:
@ -645,12 +642,12 @@ NewsBlur""" % {'user': self.user.username, 'feeds': subs.count()}
params = dict(receiver_user_id=self.user.pk, email_type='new_premium') params = dict(receiver_user_id=self.user.pk, email_type='new_premium')
try: try:
sent_email = MSentEmail.objects.get(**params) MSentEmail.objects.get(**params)
if not force: if not force:
# Return if email already sent # Return if email already sent
return return
except MSentEmail.DoesNotExist: except MSentEmail.DoesNotExist:
sent_email = MSentEmail.objects.create(**params) MSentEmail.objects.create(**params)
user = self.user user = self.user
text = render_to_string('mail/email_new_premium.txt', locals()) text = render_to_string('mail/email_new_premium.txt', locals())
@ -692,12 +689,12 @@ NewsBlur""" % {'user': self.user.username, 'feeds': subs.count()}
params = dict(receiver_user_id=self.user.pk, email_type='new_user_queue') params = dict(receiver_user_id=self.user.pk, email_type='new_user_queue')
try: try:
sent_email = MSentEmail.objects.get(**params) MSentEmail.objects.get(**params)
if not force: if not force:
# Return if email already sent # Return if email already sent
return return
except MSentEmail.DoesNotExist: except MSentEmail.DoesNotExist:
sent_email = MSentEmail.objects.create(**params) MSentEmail.objects.create(**params)
user = self.user user = self.user
text = render_to_string('mail/email_new_user_queue.txt', locals()) text = render_to_string('mail/email_new_user_queue.txt', locals())
@ -769,13 +766,13 @@ NewsBlur""" % {'user': self.user.username, 'feeds': subs.count()}
params = dict(receiver_user_id=self.user.pk, email_type='launch_social') params = dict(receiver_user_id=self.user.pk, email_type='launch_social')
try: try:
sent_email = MSentEmail.objects.get(**params) MSentEmail.objects.get(**params)
if not force: if not force:
# Return if email already sent # Return if email already sent
logging.user(self.user, "~FM~SB~FRNot~FM sending launch social email for user, sent already: %s" % self.user.email) logging.user(self.user, "~FM~SB~FRNot~FM sending launch social email for user, sent already: %s" % self.user.email)
return return
except MSentEmail.DoesNotExist: except MSentEmail.DoesNotExist:
sent_email = MSentEmail.objects.create(**params) MSentEmail.objects.create(**params)
delta = datetime.datetime.now() - self.last_seen_on delta = datetime.datetime.now() - self.last_seen_on
months_ago = delta.days / 30 months_ago = delta.days / 30
@ -799,13 +796,13 @@ NewsBlur""" % {'user': self.user.username, 'feeds': subs.count()}
params = dict(receiver_user_id=self.user.pk, email_type='launch_turntouch') params = dict(receiver_user_id=self.user.pk, email_type='launch_turntouch')
try: try:
sent_email = MSentEmail.objects.get(**params) MSentEmail.objects.get(**params)
if not force: if not force:
# Return if email already sent # Return if email already sent
logging.user(self.user, "~FM~SB~FRNot~FM sending launch social email for user, sent already: %s" % self.user.email) logging.user(self.user, "~FM~SB~FRNot~FM sending launch social email for user, sent already: %s" % self.user.email)
return return
except MSentEmail.DoesNotExist: except MSentEmail.DoesNotExist:
sent_email = MSentEmail.objects.create(**params) MSentEmail.objects.create(**params)
delta = datetime.datetime.now() - self.last_seen_on delta = datetime.datetime.now() - self.last_seen_on
months_ago = delta.days / 30 months_ago = delta.days / 30
@ -829,13 +826,13 @@ NewsBlur""" % {'user': self.user.username, 'feeds': subs.count()}
params = dict(receiver_user_id=self.user.pk, email_type='launch_turntouch_end') params = dict(receiver_user_id=self.user.pk, email_type='launch_turntouch_end')
try: try:
sent_email = MSentEmail.objects.get(**params) MSentEmail.objects.get(**params)
if not force: if not force:
# Return if email already sent # Return if email already sent
logging.user(self.user, "~FM~SB~FRNot~FM sending launch TT end email for user, sent already: %s" % self.user.email) logging.user(self.user, "~FM~SB~FRNot~FM sending launch TT end email for user, sent already: %s" % self.user.email)
return return
except MSentEmail.DoesNotExist: except MSentEmail.DoesNotExist:
sent_email = MSentEmail.objects.create(**params) MSentEmail.objects.create(**params)
delta = datetime.datetime.now() - self.last_seen_on delta = datetime.datetime.now() - self.last_seen_on
months_ago = delta.days / 30 months_ago = delta.days / 30
@ -1278,6 +1275,52 @@ class MRedeemedCode(mongo.Document):
logging.user(user, "~FG~BBRedeeming gift code: %s~FW" % gift_code) logging.user(user, "~FG~BBRedeeming gift code: %s~FW" % gift_code)
class MCustomStyling(mongo.Document):
user_id = mongo.IntField(unique=True)
custom_css = mongo.StringField()
custom_js = mongo.StringField()
updated_date = mongo.DateTimeField(default=datetime.datetime.now)
meta = {
'collection': 'custom_styling',
'allow_inheritance': False,
'indexes': ['user_id'],
}
def __unicode__(self):
return "%s custom style %s/%s %s" % (self.user_id, len(self.custom_css) if self.custom_css else "-",
len(self.custom_js) if self.custom_js else "-", self.updated_date)
def canonical(self):
return {
'css': self.custom_css,
'js': self.custom_js,
}
@classmethod
def get_user(cls, user_id):
try:
styling = cls.objects.get(user_id=user_id)
except cls.DoesNotExist:
return None
return styling
@classmethod
def save_user(cls, user_id, css, js):
styling = cls.get_user(user_id)
if not css and not js:
if styling:
styling.delete()
return
if not styling:
styling = cls.objects.create(user_id=user_id)
styling.custom_css = css
styling.custom_js = js
styling.save()
class RNewUserQueue: class RNewUserQueue:
KEY = "new_user_queue" KEY = "new_user_queue"

View file

@ -32,7 +32,7 @@ from apps.analyzer.models import MClassifierTitle, MClassifierAuthor, MClassifie
from apps.analyzer.models import apply_classifier_titles, apply_classifier_feeds from apps.analyzer.models import apply_classifier_titles, apply_classifier_feeds
from apps.analyzer.models import apply_classifier_authors, apply_classifier_tags from apps.analyzer.models import apply_classifier_authors, apply_classifier_tags
from apps.analyzer.models import get_classifiers_for_user, sort_classifiers_by_feed from apps.analyzer.models import get_classifiers_for_user, sort_classifiers_by_feed
from apps.profile.models import Profile from apps.profile.models import Profile, MCustomStyling
from apps.reader.models import UserSubscription, UserSubscriptionFolders, RUserStory, Feature from apps.reader.models import UserSubscription, UserSubscriptionFolders, RUserStory, Feature
from apps.reader.forms import SignupForm, LoginForm, FeatureForm from apps.reader.forms import SignupForm, LoginForm, FeatureForm
from apps.rss_feeds.models import MFeedIcon, MStarredStoryCounts, MSavedSearch from apps.rss_feeds.models import MFeedIcon, MStarredStoryCounts, MSavedSearch
@ -102,6 +102,7 @@ def dashboard(request, **kwargs):
).select_related('feed')[:2] ).select_related('feed')[:2]
statistics = MStatistics.all() statistics = MStatistics.all()
social_profile = MSocialProfile.get_user(user.pk) social_profile = MSocialProfile.get_user(user.pk)
custom_styling = MCustomStyling.get_user(user.pk)
start_import_from_google_reader = request.session.get('import_from_google_reader', False) start_import_from_google_reader = request.session.get('import_from_google_reader', False)
if start_import_from_google_reader: if start_import_from_google_reader:
@ -117,6 +118,7 @@ def dashboard(request, **kwargs):
return { return {
'user_profile' : user.profile, 'user_profile' : user.profile,
'feed_count' : feed_count, 'feed_count' : feed_count,
'custom_styling' : custom_styling,
'account_images' : range(1, 4), 'account_images' : range(1, 4),
'recommended_feeds' : recommended_feeds, 'recommended_feeds' : recommended_feeds,
'unmoderated_feeds' : unmoderated_feeds, 'unmoderated_feeds' : unmoderated_feeds,

View file

@ -11249,12 +11249,14 @@ form.opml_import_form input {
border: 4px solid #8F1F00; border: 4px solid #8F1F00;
margin: 2px 3px; margin: 2px 3px;
} }
.NB-modal-profile-editor .NB-profile-blurblog-css { .NB-modal-profile-editor .NB-profile-blurblog-css,
.NB-modal-account .NB-account-custom-css,
.NB-modal-account .NB-account-custom-javascript {
width: 100%; width: 100%;
font-family: Courier, monospace; font-family: Courier, monospace;
font-size: 13px; font-size: 13px;
padding: 6px; padding: 6px;
height: 54px; height: 94px;
-webkit-box-sizing: border-box; -webkit-box-sizing: border-box;
-moz-box-sizing: border-box; -moz-box-sizing: border-box;
box-sizing: border-box; box-sizing: border-box;

View file

@ -43,7 +43,8 @@ _.extend(NEWSBLUR.ReaderAccount.prototype, {
$.make('div', { className: 'NB-modal-loading' }), $.make('div', { className: 'NB-modal-loading' }),
$.make('div', { className: 'NB-modal-tab NB-active NB-modal-tab-account' }, 'Account'), $.make('div', { className: 'NB-modal-tab NB-active NB-modal-tab-account' }, 'Account'),
$.make('div', { className: 'NB-modal-tab NB-modal-tab-premium' }, 'Payments'), $.make('div', { className: 'NB-modal-tab NB-modal-tab-premium' }, 'Payments'),
$.make('div', { className: 'NB-modal-tab NB-modal-tab-emails' }, 'Emails') $.make('div', { className: 'NB-modal-tab NB-modal-tab-emails' }, 'Emails'),
$.make('div', { className: 'NB-modal-tab NB-modal-tab-custom' }, 'Custom CSS/JavaScript')
]), ]),
$.make('h2', { className: 'NB-modal-title' }, [ $.make('h2', { className: 'NB-modal-title' }, [
$.make('div', { className: 'NB-icon' }), $.make('div', { className: 'NB-icon' }),
@ -207,6 +208,20 @@ _.extend(NEWSBLUR.ReaderAccount.prototype, {
]) ])
]) ])
]), ]),
$.make('div', { className: 'NB-tab NB-tab-custom' }, [
$.make('fieldset', [
$.make('legend', 'Custom CSS'),
$.make('div', { className: 'NB-modal-section NB-profile-editor-blurblog-custom-css'}, [
$.make('textarea', { 'className': 'NB-account-custom-css', name: 'custom_css' }, _.string.trim($("#NB-custom-css").text()))
])
]),
$.make('fieldset', [
$.make('legend', 'Custom JavaScript'),
$.make('div', { className: 'NB-modal-section NB-profile-editor-blurblog-custom-js'}, [
$.make('textarea', { 'className': 'NB-account-custom-javascript', name: 'custom_js' }, _.string.trim($("#NB-custom-js").text()))
])
])
]),
$.make('div', { className: 'NB-modal-submit' }, [ $.make('div', { className: 'NB-modal-submit' }, [
$.make('input', { type: 'submit', disabled: 'true', className: 'NB-modal-submit-button NB-modal-submit-green NB-disabled', value: 'Change what you like above...' }) $.make('input', { type: 'submit', disabled: 'true', className: 'NB-modal-submit-button NB-modal-submit-green NB-disabled', value: 'Change what you like above...' })
]) ])
@ -387,7 +402,7 @@ _.extend(NEWSBLUR.ReaderAccount.prototype, {
serialize_preferences: function() { serialize_preferences: function() {
var preferences = {}; var preferences = {};
$('input[type=radio]:checked, select, input[type=text], input[type=password]', this.$modal).each(function() { $('input[type=radio]:checked, select, textarea, input[type=text], input[type=password]', this.$modal).each(function() {
var name = $(this).attr('name'); var name = $(this).attr('name');
var preference = preferences[name] = $(this).val(); var preference = preferences[name] = $(this).val();
if (preference == 'true') preferences[name] = true; if (preference == 'true') preferences[name] = true;
@ -464,6 +479,8 @@ _.extend(NEWSBLUR.ReaderAccount.prototype, {
newtab = 'premium'; newtab = 'premium';
} else if ($t.hasClass('NB-modal-tab-emails')) { } else if ($t.hasClass('NB-modal-tab-emails')) {
newtab = 'emails'; newtab = 'emails';
} else if ($t.hasClass('NB-modal-tab-custom')) {
newtab = 'custom';
} }
self.switch_tab(newtab); self.switch_tab(newtab);
}); });
@ -502,6 +519,8 @@ _.extend(NEWSBLUR.ReaderAccount.prototype, {
handle_change: function() { handle_change: function() {
$('input[type=radio],input[type=checkbox],select,input', this.$modal).bind('change', _.bind(this.enable_save, this)); $('input[type=radio],input[type=checkbox],select,input', this.$modal).bind('change', _.bind(this.enable_save, this));
$('input', this.$modal).bind('keydown', _.bind(this.enable_save, this)); $('input', this.$modal).bind('keydown', _.bind(this.enable_save, this));
$('.NB-tab-custom', this.$modal).delegate('input[type=text],textarea', 'keydown', _.bind(this.enable_save, this));
$('.NB-tab-custom', this.$modal).delegate('input,textarea', 'change', _.bind(this.enable_save, this));
}, },
enable_save: function() { enable_save: function() {

View file

@ -22,6 +22,18 @@
}); });
</script> </script>
{% endif %} {% endif %}
{% if custom_styling and custom_styling.custom_js %}
<script id="NB-custom-js">
{{ custom_styling.custom_js|safe }}
</script>
{% endif %}
{% if custom_styling and custom_styling.custom_css %}
<style id="NB-custom-css">
{{ custom_styling.custom_css|safe }}
</style>
{% endif %}
{% endblock %} {% endblock %}
{% block bodyclass %}NB-body-main{% endblock %} {% block bodyclass %}NB-body-main{% endblock %}