зеркало из https://github.com/mozilla/bedrock.git
Fix Bug 965823 - Add a custom Twitter timeline widget to the Student Ambassadors landing page
This commit is contained in:
Родитель
940b814505
Коммит
98b3a042d3
|
@ -43,3 +43,6 @@
|
|||
[submodule "vendor-local/src/raven"]
|
||||
path = vendor-local/src/raven
|
||||
url = git://github.com/getsentry/raven-python.git
|
||||
[submodule "vendor-local/src/tweepy"]
|
||||
path = vendor-local/src/tweepy
|
||||
url = https://github.com/tweepy/tweepy.git
|
||||
|
|
|
@ -280,3 +280,79 @@
|
|||
{% macro twitter_share_url(url, tweet_text) -%}
|
||||
{{ 'https://www.twitter.com/intent/tweet?url=%s&text=%s'|format(url|urlencode, tweet_text|urlencode)|e }}
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro twitter_follow_button(account_name, account_id, show_account_id=True) -%}
|
||||
{%- if show_account_id -%}
|
||||
{% set label = _('Follow @%s')|format(account_id) %}
|
||||
{%- else -%}
|
||||
{% set label = _('Follow') %}
|
||||
{%- endif -%}
|
||||
<a href="https://twitter.com/{{ account_id }}" title="{{ _('Follow %s on Twitter')|format(account_name) }}" class="twitter-follow-button">{{ label }}</a>
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro twitter_timeline_widget(title='', heading_level=3, max_length=0) -%}
|
||||
{% if tweets -%}
|
||||
<section id="twitter-timeline-widget" itemscope itemtype="http://schema.org/Blog">
|
||||
<header>
|
||||
<h{{ heading_level }} itemprop="name">
|
||||
{%- if title -%}
|
||||
{{ title }}
|
||||
{%- else -%}
|
||||
{{ _('Twitter Timeline of %s')|format(tweets[0].user.name) }}
|
||||
{%- endif -%}
|
||||
</h{{ heading_level }}>
|
||||
<p>{{ twitter_follow_button(tweets[0].user.name, tweets[0].user.screen_name, False) }}</p>
|
||||
</header>
|
||||
<div class="tweets">
|
||||
{% for _tweet in tweets %}
|
||||
{%- if _tweet.retweeted_status -%}
|
||||
{% set retweet = true %}
|
||||
{% set tweet = _tweet.retweeted_status %}
|
||||
{%- else -%}
|
||||
{% set retweet = false %}
|
||||
{% set tweet = _tweet %}
|
||||
{%- endif -%}
|
||||
<article itemprop="blogPost" itemscope itemtype="http://schema.org/BlogPosting">
|
||||
<header>
|
||||
<h{{ heading_level + 1 }} class="timestamp">
|
||||
<a href="https://twitter.com/{{ tweet.user.screen_name }}/status/{{ tweet.id }}" class="post">
|
||||
{{ format_tweet_timestamp(tweet)|safe }}
|
||||
</a>
|
||||
</h{{ heading_level + 1 }}>
|
||||
<div itemprop="author" itemscope itemtype="http://schema.org/Person">
|
||||
<a href="https://twitter.com/{{ tweet.user.screen_name }}" class="author">
|
||||
<img src="{{ tweet.user.profile_image_url_https }}" alt="" itemprop="image">
|
||||
<span itemprop="name">{{ tweet.user.name }}</span>
|
||||
<span itemprop="alternateName">@{{ tweet.user.screen_name }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
<div>
|
||||
<p itemprop="articleBody">{{ format_tweet_body(tweet)|safe }}</p>
|
||||
{% if retweet -%}
|
||||
<p class="retweet-credit">{{ _('Retweeted by %s')|format('<a href="%s" class="credit">%s</a>'|format('https://twitter.com/'+_tweet.user.screen_name, _tweet.user.name)|safe) }}</p>
|
||||
{% endif -%}
|
||||
{% if tweet.entities.media -%}
|
||||
{% for medium in tweet.entities.media -%}
|
||||
{% if medium.type == 'photo' -%}
|
||||
<p class="media"><a href="{{ medium.expanded_url }}" class="image"><img src="{{ medium.media_url_https }}" alt="" itemprop="image"></a></p>
|
||||
{% endif -%}
|
||||
{% endfor -%}
|
||||
{% endif -%}
|
||||
</div>
|
||||
<footer>
|
||||
<ul class="actions">
|
||||
<li><a class="reply" href="https://twitter.com/intent/tweet?in_reply_to={{ tweet.id }}">{{ _('Reply') }}</a></li>
|
||||
<li><a class="retweet" href="https://twitter.com/intent/retweet?tweet_id={{ tweet.id }}">{{ _('Retweet') }}</a></li>
|
||||
<li><a class="favorite" href="https://twitter.com/intent/favorite?tweet_id={{ tweet.id }}">{{ _('Favorite') }}</a></li>
|
||||
</ul>
|
||||
</footer>
|
||||
</article>
|
||||
{% if max_length > 0 and loop.index == max_length -%}
|
||||
{% break %}
|
||||
{% endif -%}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif -%}
|
||||
</section>
|
||||
{%- endmacro %}
|
||||
|
|
|
@ -7,6 +7,8 @@ import feedparser
|
|||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
|
||||
from bedrock.mozorg.util import TwitterAPI
|
||||
|
||||
|
||||
@cronjobs.register
|
||||
def update_feeds():
|
||||
|
@ -15,3 +17,16 @@ def update_feeds():
|
|||
# Cache for a year (it will be set by the cron job no matter
|
||||
# what on a set interval)
|
||||
cache.set('feeds-%s' % name, feed_info, 60 * 60 * 24 * 365)
|
||||
|
||||
|
||||
@cronjobs.register
|
||||
def update_tweets():
|
||||
for account in settings.TWITTER_ACCOUNTS:
|
||||
try:
|
||||
tweets = TwitterAPI(account).user_timeline(screen_name=account)
|
||||
except:
|
||||
tweets = []
|
||||
|
||||
# Cache for a year (it will be set by the cron job no matter
|
||||
# what on a set interval)
|
||||
cache.set('tweets-%s' % account, tweets, 60 * 60 * 24 * 365)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# flake8: noqa
|
||||
import download_buttons
|
||||
import misc
|
||||
import social_widgets
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
from datetime import datetime
|
||||
import urllib
|
||||
|
||||
import jingo
|
||||
from lib.l10n_utils.dotlang import _
|
||||
|
||||
|
||||
@jingo.register.function
|
||||
def format_tweet_body(tweet):
|
||||
"""
|
||||
Return a tweet in an HTML format.
|
||||
|
||||
@param tweet: A Tweepy Status object retrieved with the Twitter REST API.
|
||||
|
||||
See the developer document for details:
|
||||
https://dev.twitter.com/docs/platform-objects/tweets
|
||||
"""
|
||||
text = tweet.text
|
||||
entities = tweet.entities
|
||||
|
||||
# Hashtags (#something)
|
||||
for hashtags in entities['hashtags']:
|
||||
hash = hashtags['text']
|
||||
text = text.replace('#' + hash,
|
||||
('<a href="https://twitter.com/search?q=%s&src=hash" class="hash">#%s</a>'
|
||||
% ('%23' + urllib.quote(hash), hash)))
|
||||
|
||||
# Mentions (@someone)
|
||||
for user in entities['user_mentions']:
|
||||
name = user['screen_name']
|
||||
text = text.replace('@' + name,
|
||||
('<a href="https://twitter.com/%s" class="mention">@%s</a>'
|
||||
% (urllib.quote(name), name)))
|
||||
|
||||
# URLs
|
||||
for url in entities['urls']:
|
||||
text = text.replace(url['url'],
|
||||
('<a href="%s" title="%s">%s</a>'
|
||||
% (url['url'], url['expanded_url'], url['display_url'])))
|
||||
|
||||
# Media
|
||||
if entities.get('media'):
|
||||
for medium in entities['media']:
|
||||
text = text.replace(medium['url'],
|
||||
('<a href="%s" title="%s" class="media">%s</a>'
|
||||
% (medium['url'], medium['expanded_url'], medium['display_url'])))
|
||||
|
||||
return text
|
||||
|
||||
|
||||
@jingo.register.function
|
||||
def format_tweet_timestamp(tweet):
|
||||
"""
|
||||
Return an HTML time element filled with a tweet timestamp.
|
||||
|
||||
@param tweet: A Tweepy Status object retrieved with the Twitter REST API.
|
||||
|
||||
For a tweet posted within the last 24 hours, the timestamp label should be
|
||||
a relative format like "20s", "3m" or 5h", otherwise it will be a simple
|
||||
date like "6 Jun". See the Display Requirements for details:
|
||||
https://dev.twitter.com/terms/display-requirements
|
||||
"""
|
||||
now = datetime.utcnow()
|
||||
created = tweet.created_at # A datetime object
|
||||
diff = now - created # A timedelta Object
|
||||
|
||||
if diff.days == 0:
|
||||
if diff.seconds < 60:
|
||||
label = _('%ds') % diff.seconds
|
||||
elif diff.seconds < 60 * 60:
|
||||
label = _('%dm') % round(diff.seconds / 60)
|
||||
else:
|
||||
label = _('%dh') % round(diff.seconds / 60 / 60)
|
||||
else:
|
||||
label = created.strftime("%-d %b")
|
||||
|
||||
full = created.strftime("%Y-%m-%d %H:%M")
|
||||
|
||||
return ('<time datetime="%s" title="%s" itemprop="dateCreated">%s '
|
||||
'<span class="full">(%s)</span></time>'
|
||||
% (created.isoformat(), full, label, full))
|
|
@ -1,3 +1,4 @@
|
|||
{% from "macros.html" import twitter_timeline_widget with context %}
|
||||
{% extends "mozorg/base-resp.html" %}
|
||||
|
||||
{% block facebook_id %}107747252595375{# facebook.com/Firefox.Student.Ambassadors #}{% endblock %}
|
||||
|
@ -90,7 +91,18 @@
|
|||
</section>
|
||||
</div>
|
||||
</div>
|
||||
{% if tweets %}
|
||||
<div class="row">
|
||||
<div class="col-full">
|
||||
{{ twitter_timeline_widget(title=_('See What We’re Up To'), heading_level=2, max_length=6) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block email_form %}{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ js('contribute-studentambassadors-landing') }}
|
||||
{% endblock %}
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -0,0 +1,51 @@
|
|||
# coding: utf-8
|
||||
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import json
|
||||
import os.path
|
||||
|
||||
from django.test.client import RequestFactory
|
||||
|
||||
import tweepy
|
||||
|
||||
from bedrock.mozorg.tests import TestCase
|
||||
from bedrock.mozorg.helpers.social_widgets import * # noqa
|
||||
|
||||
|
||||
TEST_FILES_ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
||||
'test_files')
|
||||
|
||||
|
||||
class TestFormatTweet(TestCase):
|
||||
rf = RequestFactory()
|
||||
|
||||
with open(os.path.join(TEST_FILES_ROOT, 'data', 'tweets.json')) as file:
|
||||
tweets = json.load(file)
|
||||
|
||||
# For test, select and parse a tweet containing a hashtag, mention and URL
|
||||
tweet = tweepy.models.Status.parse(tweepy.api, tweets[5])
|
||||
|
||||
def test_format_tweet_body(self):
|
||||
"""Should return a tweet in an HTML format"""
|
||||
# Note that … is a non-ASCII character. That's why the UTF-8 encoding is
|
||||
# specified at the top of the file.
|
||||
expected = (
|
||||
u'Want more information about the <a href="https://twitter.com/'
|
||||
u'mozstudents" class="mention">@mozstudents</a> program? Sign-up '
|
||||
u'and get a monthly newsletter in your in-box <a href="http://t.co/'
|
||||
u'0thqsyksC3" title="http://www.mozilla.org/en-US/contribute/'
|
||||
u'universityambassadors/">mozilla.org/en-US/contribu…</a> <a href='
|
||||
u'"https://twitter.com/search?q=%23students&src=hash" class='
|
||||
u'"hash">#students</a>')
|
||||
self.assertEqual(format_tweet_body(self.tweet), expected)
|
||||
|
||||
def test_format_tweet_timestamp(self):
|
||||
"""Should return a timestamp in an HTML format"""
|
||||
expected = (
|
||||
u'<time datetime="2014-01-16T19:28:24" title="2014-01-16 19:28" '
|
||||
u'itemprop="dateCreated">16 Jan <span class="full">(2014-01-16 '
|
||||
u'19:28)</span></time>')
|
||||
self.assertEqual(format_tweet_timestamp(self.tweet), expected)
|
|
@ -164,8 +164,9 @@ urlpatterns = patterns('',
|
|||
name='mozorg.contribute_embed',
|
||||
kwargs={'template': 'mozorg/contribute-embed.html',
|
||||
'return_to_form': False}),
|
||||
page('contribute/studentambassadors',
|
||||
'mozorg/contribute/studentambassadors/landing.html'),
|
||||
url('^contribute/studentambassadors/$',
|
||||
views.contribute_studentambassadors_landing,
|
||||
name='mozorg.contribute.studentambassadors.landing'),
|
||||
url('^contribute/studentambassadors/join/$',
|
||||
views.contribute_studentambassadors_join,
|
||||
name='mozorg.contribute.studentambassadors.join'),
|
||||
|
|
|
@ -10,6 +10,7 @@ from django.conf.urls import url
|
|||
from django.http import HttpResponse
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
import tweepy
|
||||
import commonware.log
|
||||
from lib import l10n_utils
|
||||
from lib.l10n_utils.dotlang import lang_file_has_tag
|
||||
|
@ -122,3 +123,17 @@ def get_fb_like_locale(request_locale):
|
|||
lang = 'en_US'
|
||||
|
||||
return lang
|
||||
|
||||
|
||||
def TwitterAPI(account):
|
||||
"""
|
||||
Connect to the Twitter REST API using the Tweepy library.
|
||||
|
||||
https://dev.twitter.com/docs/api/1.1
|
||||
http://pythonhosted.org/tweepy/html/
|
||||
"""
|
||||
keys = settings.TWITTER_APP_KEYS[account]
|
||||
auth = tweepy.OAuthHandler(keys['CONSUMER_KEY'], keys['CONSUMER_SECRET'])
|
||||
auth.set_access_token(keys['ACCESS_TOKEN'], keys['ACCESS_TOKEN_SECRET'])
|
||||
|
||||
return tweepy.API(auth)
|
||||
|
|
|
@ -6,6 +6,7 @@ import re
|
|||
import json
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.core.context_processors import csrf
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.views.decorators.csrf import csrf_exempt, csrf_protect
|
||||
|
@ -208,6 +209,13 @@ def plugincheck(request, template='mozorg/plugincheck.html'):
|
|||
return l10n_utils.render(request, template, data)
|
||||
|
||||
|
||||
@xframe_allow
|
||||
def contribute_studentambassadors_landing(request):
|
||||
return l10n_utils.render(request,
|
||||
'mozorg/contribute/studentambassadors/landing.html',
|
||||
{'tweets': cache.get('tweets-mozstudents') or []})
|
||||
|
||||
|
||||
@csrf_protect
|
||||
def contribute_studentambassadors_join(request):
|
||||
form = ContributeStudentAmbassadorForm(request.POST or None)
|
||||
|
|
|
@ -148,6 +148,7 @@ MINIFY_BUNDLES = {
|
|||
'css/mozorg/contribute-page.less',
|
||||
),
|
||||
'contribute-studentambassadors-landing': (
|
||||
'css/base/social-widgets.less',
|
||||
'css/mozorg/contribute/studentambassadors/landing.less',
|
||||
),
|
||||
'contribute-studentambassadors-join': (
|
||||
|
@ -444,6 +445,9 @@ MINIFY_BUNDLES = {
|
|||
'js/mozorg/contribute-form.js',
|
||||
'js/base/mozilla-input-placeholder.js',
|
||||
),
|
||||
'contribute-studentambassadors-landing': (
|
||||
'js/base/social-widgets.js',
|
||||
),
|
||||
'contribute-studentambassadors-join': (
|
||||
'js/mozorg/contribute-studentambassadors-join.js',
|
||||
'js/base/mozilla-input-placeholder.js',
|
||||
|
@ -815,6 +819,11 @@ FEEDS = {
|
|||
'mozilla': 'https://blog.mozilla.org/feed/'
|
||||
}
|
||||
|
||||
# Twitter accounts to retrieve tweets with the API
|
||||
TWITTER_ACCOUNTS = (
|
||||
'mozstudents',
|
||||
)
|
||||
|
||||
BASKET_URL = 'http://basket.mozilla.com'
|
||||
|
||||
# This prefixes /b/ on all URLs generated by `reverse` so that links
|
||||
|
|
|
@ -28,3 +28,13 @@ GA_ACCOUNT_CODE = ''
|
|||
SESSION_COOKIE_SECURE = False
|
||||
|
||||
USE_GRUNT_LIVERELOAD = False
|
||||
|
||||
# Twitter apps' consumer key/secret and access token/secret
|
||||
TWITTER_APP_KEYS = {
|
||||
'mozstudents': {
|
||||
'CONSUMER_KEY': '',
|
||||
'CONSUMER_SECRET': '',
|
||||
'ACCESS_TOKEN': '',
|
||||
'ACCESS_TOKEN_SECRET': ''
|
||||
},
|
||||
}
|
||||
|
|
|
@ -0,0 +1,230 @@
|
|||
@import "../sandstone/lib.less";
|
||||
|
||||
@font-face {
|
||||
font-family: 'Font Awesome';
|
||||
src: url('/media/fonts/fontawesome-webfont.eot?#iefix') format('embedded-opentype'),
|
||||
url('/media/fonts/fontawesome-webfont.woff') format('woff'),
|
||||
url('/media/fonts/fontawesome-webfont.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
// Twitter Follow button
|
||||
|
||||
a.twitter-follow-button {
|
||||
.inline-block;
|
||||
border: 1px solid #CCC;
|
||||
border-radius: 3px;
|
||||
padding: 0 5px;
|
||||
color: #333;
|
||||
#gradient > .vertical(#FFFFFF, #DEDEDE);
|
||||
font-size: @smallFontSize;
|
||||
line-height: 1.4;
|
||||
font-weight: bold;
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover, &:focus, &:active {
|
||||
color: #333;
|
||||
#gradient > .vertical(#F8F8F8, #D9D9D9);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: #0089CB;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: #EFEFEF;
|
||||
.box-shadow(inset 0 3px 5px rgba(0, 0, 0, .1));
|
||||
}
|
||||
|
||||
&:before {
|
||||
.inline-block;
|
||||
color: #55ACEE;
|
||||
font-size: @baseFontSize;
|
||||
font-family: 'Font Awesome', sans-serif;
|
||||
vertical-align: middle;
|
||||
content: '\0F099\00A0';
|
||||
}
|
||||
}
|
||||
|
||||
// Twitter timeline widget
|
||||
|
||||
#twitter-timeline-widget {
|
||||
header {
|
||||
a.twitter-follow-button {
|
||||
float: right;
|
||||
margin: -32px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
article {
|
||||
overflow: hidden;
|
||||
padding: 10px 10px 10px 68px;
|
||||
line-height: @baseLine;
|
||||
|
||||
header {
|
||||
.timestamp {
|
||||
float: right;
|
||||
margin: 0 0 0 10px;
|
||||
font-size: @smallFontSize;
|
||||
line-height: @baseLine;
|
||||
letter-spacing: 0;
|
||||
|
||||
a {
|
||||
color: @textColorTertiary;
|
||||
}
|
||||
|
||||
.full {
|
||||
.visually-hidden();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[itemprop="author"] {
|
||||
a {
|
||||
color: inherit;
|
||||
|
||||
&:hover, &:focus, &:active {
|
||||
text-decoration: none;
|
||||
|
||||
[itemprop="name"] {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
float: left;
|
||||
margin: 0 0 0 -58px;
|
||||
border-radius: 5px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
[itemprop="name"] {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
[itemprop="alternateName"] {
|
||||
font-size: @smallFontSize;
|
||||
color: @textColorTertiary;
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
p {
|
||||
margin: 0;
|
||||
line-height: @baseLine;
|
||||
}
|
||||
|
||||
img {
|
||||
margin: 5px 0;
|
||||
width: 100%;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.retweet-credit {
|
||||
font-size: @smallFontSize;
|
||||
color: @textColorLight;
|
||||
|
||||
&:before {
|
||||
.inline-block;
|
||||
font-family: 'Font Awesome', sans-serif;
|
||||
content: '\0F079\00A0';
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
overflow: hidden;
|
||||
|
||||
.actions {
|
||||
float: right;
|
||||
margin: 0;
|
||||
|
||||
li {
|
||||
.inline-block;
|
||||
margin: 0 0 0 8px;
|
||||
padding: 0;
|
||||
font-size: @smallFontSize;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: @textColorTertiary;
|
||||
|
||||
&:before {
|
||||
.inline-block;
|
||||
font-family: 'Font Awesome', sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
.reply:before {
|
||||
content: '\0F112\00A0';
|
||||
}
|
||||
|
||||
.retweet:before {
|
||||
content: '\0F079\00A0';
|
||||
}
|
||||
|
||||
.favorite:before {
|
||||
content: '\0F005\00A0';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.html-rtl {
|
||||
#twitter-timeline-widget {
|
||||
header a.twitter-follow-button,
|
||||
article header .timestamp,
|
||||
footer .actions {
|
||||
float: left;
|
||||
}
|
||||
|
||||
article {
|
||||
padding: 10px 68px 10px 10px;
|
||||
|
||||
header .timestamp {
|
||||
margin: 0 10px 0 0;
|
||||
}
|
||||
|
||||
[itemprop="author"] img {
|
||||
float: right;
|
||||
margin: 0 -58px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
footer .actions li {
|
||||
margin: 0 8px 0 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: @breakMobileLandscape) {
|
||||
#twitter-timeline-widget {
|
||||
header a.twitter-follow-button {
|
||||
float: none;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.html-rtl {
|
||||
#twitter-timeline-widget {
|
||||
header a.twitter-follow-button {
|
||||
float: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -98,6 +98,10 @@
|
|||
.span(6)
|
||||
}
|
||||
|
||||
.col-full {
|
||||
margin: 0 @gridGutterWidth/2;
|
||||
}
|
||||
|
||||
section {
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
@ -156,6 +160,16 @@
|
|||
}
|
||||
}
|
||||
|
||||
#twitter-timeline-widget {
|
||||
.tweets {
|
||||
.multi-column(auto, 2, 20px);
|
||||
|
||||
article {
|
||||
.multi-column-avoid-break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: @breakDesktop) {
|
||||
#main-feature header {
|
||||
h1, p {
|
||||
|
@ -215,6 +229,12 @@
|
|||
#main-content section {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
#twitter-timeline-widget {
|
||||
.tweets {
|
||||
.multi-column-clear;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: @breakMobileLandscape) and (max-width: @breakTablet) {
|
||||
|
|
|
@ -344,9 +344,35 @@
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
// Add chevron character after text (with space)
|
||||
.trailing-arrow() {
|
||||
&:after {
|
||||
content: " »"; /* nbsp raquo */
|
||||
}
|
||||
}
|
||||
|
||||
.multi-column(@width: auto, @count: auto, @gap: normal) {
|
||||
-webkit-column-width: @width;
|
||||
-moz-column-width: @width;
|
||||
-o-column-width: @width;
|
||||
column-width: @width;
|
||||
-webkit-column-count: @count;
|
||||
-moz-column-count: @count;
|
||||
-o-column-count: @count;
|
||||
column-count: @count;
|
||||
-webkit-column-gap: @gap;
|
||||
-moz-column-gap: @gap;
|
||||
-o-column-gap: @gap;
|
||||
column-gap: @gap;
|
||||
}
|
||||
|
||||
.multi-column-clear {
|
||||
.multi-column(auto, auto, normal);
|
||||
}
|
||||
|
||||
.multi-column-avoid-break {
|
||||
page-break-inside: avoid;
|
||||
-webkit-column-break-inside: avoid;
|
||||
-moz-column-break-inside: avoid;
|
||||
-o-column-break-inside: avoid;
|
||||
column-break-inside: avoid;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
$(function() {
|
||||
// GA event tracking
|
||||
function _track(event, cmd) {
|
||||
if (event.target.target === '_blank' || event.metaKey || event.ctrlKey) {
|
||||
// New tab
|
||||
gaTrack(cmd);
|
||||
} else {
|
||||
// Current tab
|
||||
event.preventDefault();
|
||||
gaTrack(cmd, function() { window.location.href = event.currentTarget.href; });
|
||||
}
|
||||
};
|
||||
|
||||
// Twitter Follow button
|
||||
$('.twitter-follow-button').on('click', function(event) {
|
||||
_track(event, ['_trackEvent', 'Social Interactions', 'Twitter Follow']);
|
||||
});
|
||||
|
||||
// Twitter timeline widget
|
||||
$('#twitter-timeline-widget').on('click', 'a', function(event) {
|
||||
if ($(this).hasClass('twitter-follow-button')) {
|
||||
return; // Tracking will be done by the function above
|
||||
}
|
||||
|
||||
_track(event, ['_trackEvent', 'Social Interactions', 'Twitter ' + ({
|
||||
'post': 'Post Link Exit',
|
||||
'author': 'Author Link Exit',
|
||||
'credit': 'Retweet Credit Link Exit',
|
||||
'image': 'Preview Image Exit',
|
||||
'hash': 'Hashtag Link Exit',
|
||||
'mention': 'Mention Link Exit',
|
||||
'media': 'Media Link Exit',
|
||||
'reply': 'Reply',
|
||||
'retweet': 'Retweet',
|
||||
'favorite': 'Favorite',
|
||||
}[$(this).attr('class')] || 'General Link Exit')]);
|
||||
});
|
||||
});
|
|
@ -11,6 +11,7 @@ GitPython==0.1.7
|
|||
-e git://github.com/jsocol/commonware.git#egg=commonware
|
||||
-e git://github.com/mozilla/nuggets.git#egg=nuggets
|
||||
-e git://github.com/kurtmckee/feedparser#egg=feedparser
|
||||
-e git://github.com/tweepy/tweepy.git#egg=tweepy
|
||||
|
||||
# Security
|
||||
-e git://github.com/fwenzel/django-sha2.git#egg=django-sha2
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 3941489457db86710b770bc120409d6947904afa
|
|
@ -16,3 +16,4 @@ src/raven
|
|||
src/requests
|
||||
src/rna
|
||||
src/six
|
||||
src/tweepy
|
||||
|
|
Загрузка…
Ссылка в новой задаче