[MERGE] [ADD] website_twitter: add twitter scroller snippet
bzr revid: odo@openerp.com-20140410174622-j2waid38nk3fqitt
This commit is contained in:
parent
7c75b5e004
commit
0152bea512
|
@ -0,0 +1,2 @@
|
|||
import models
|
||||
import controllers
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
'name': 'Twitter Roller',
|
||||
'category': 'Website',
|
||||
'summary': 'Add twitter scroller snippet in website builder',
|
||||
'version': '1.0',
|
||||
'description': """
|
||||
Display best tweets
|
||||
========================
|
||||
|
||||
""",
|
||||
'author': 'OpenERP SA',
|
||||
'depends': ['website'],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'data/twitter_data.xml',
|
||||
'views/twitter_view.xml',
|
||||
'views/twitter_snippet.xml'
|
||||
],
|
||||
'demo': [],
|
||||
'qweb': [],
|
||||
'js': [],
|
||||
'css': [],
|
||||
'installable': True,
|
||||
'application': True,
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
import main
|
|
@ -0,0 +1,32 @@
|
|||
from openerp.addons.web import http
|
||||
from openerp.addons.web.http import request
|
||||
from openerp.tools.translate import _
|
||||
|
||||
import json
|
||||
|
||||
class Twitter(http.Controller):
|
||||
@http.route(['/twitter_reload'], type='json', auth="user", website=True)
|
||||
def twitter_reload(self):
|
||||
return request.website.fetch_favorite_tweets()
|
||||
|
||||
@http.route(['/get_favorites'], type='json', auth="public", website=True)
|
||||
def get_tweets(self, limit=20):
|
||||
key = request.website.twitter_api_key
|
||||
secret = request.website.twitter_api_secret
|
||||
screen_name = request.website.twitter_screen_name
|
||||
if not key or not secret:
|
||||
return {"error": _("Please set the Twitter API Key and Secret in the Website Settings.")}
|
||||
if not screen_name:
|
||||
return {"error": _("Please set a Twitter screen name to load favorites from, "
|
||||
"in the Website Settings (it does not have to be yours)")}
|
||||
twitter_tweets = request.registry['website.twitter.tweet']
|
||||
tweets = twitter_tweets.search_read(
|
||||
request.cr, request.uid,
|
||||
[('website_id','=', request.website.id),
|
||||
('screen_name','=', screen_name)],
|
||||
['tweet'], limit=int(limit), order="tweet_id desc", context=request.context)
|
||||
if len(tweets) < 12:
|
||||
return {"error": _("Twitter user @%(username)s has less than 12 favorite tweets. "
|
||||
"Please add more or choose a different screen name.") % \
|
||||
{'username': screen_name}}
|
||||
return [json.loads(tweet['tweet']) for tweet in tweets]
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<record id="ir_cron_twitter_actions" model="ir.cron">
|
||||
<field name="name">Fetch new Twitter favorites</field>
|
||||
<field name="interval_number">2</field>
|
||||
<field name="interval_type">hours</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field name="doall" eval="False"/>
|
||||
<field name="model">website</field>
|
||||
<field name="function">_refresh_favorite_tweets</field>
|
||||
<field name="args">()</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
import twitter
|
||||
import twitter_config
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
from urllib2 import urlopen, Request, HTTPError
|
||||
|
||||
import base64
|
||||
import json
|
||||
import logging
|
||||
import werkzeug
|
||||
|
||||
from openerp.osv import fields, osv
|
||||
|
||||
API_ENDPOINT = 'https://api.twitter.com'
|
||||
API_VERSION = '1.1'
|
||||
REQUEST_TOKEN_URL = '%s/oauth2/token' % API_ENDPOINT
|
||||
REQUEST_FAVORITE_LIST_URL = '%s/%s/favorites/list.json' % (API_ENDPOINT, API_VERSION)
|
||||
URLOPEN_TIMEOUT = 10
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class TwitterClient(osv.osv):
|
||||
_inherit = "website"
|
||||
|
||||
_columns = {
|
||||
'twitter_api_key': fields.char('Twitter API key', help="Twitter API Key"),
|
||||
'twitter_api_secret': fields.char('Twitter API secret', help="Twitter API Secret"),
|
||||
'twitter_screen_name': fields.char('Get favorites from this screen name'),
|
||||
}
|
||||
|
||||
def _request(self, website, url, params=None):
|
||||
"""Send an authenticated request to the Twitter API."""
|
||||
access_token = self._get_access_token(website)
|
||||
if params:
|
||||
params = werkzeug.url_encode(params)
|
||||
url = url + '?' + params
|
||||
try:
|
||||
request = Request(url)
|
||||
request.add_header('Authorization', 'Bearer %s' % access_token)
|
||||
return json.load(urlopen(request, timeout=URLOPEN_TIMEOUT))
|
||||
except HTTPError, e:
|
||||
_logger.debug("Twitter API request failed with code: %r, msg: %r, content: %r",
|
||||
e.code, e.msg, e.fp.read())
|
||||
raise
|
||||
|
||||
def _refresh_favorite_tweets(self, cr, uid, context=None):
|
||||
''' called by cron job '''
|
||||
website = self.pool['website']
|
||||
ids = self.pool['website'].search(cr, uid, [('twitter_api_key', '!=', False),
|
||||
('twitter_api_secret', '!=', False),
|
||||
('twitter_screen_name', '!=', False)],
|
||||
context=context)
|
||||
_logger.debug("Refreshing tweets for website IDs: %r", ids)
|
||||
website.fetch_favorite_tweets(cr, uid, ids, context=context)
|
||||
|
||||
def fetch_favorite_tweets(self, cr, uid, ids, context=None):
|
||||
website_tweets = self.pool['website.twitter.tweet']
|
||||
tweet_ids = []
|
||||
for website in self.browse(cr, uid, ids, context=context):
|
||||
if not all((website.twitter_api_key, website.twitter_api_secret,
|
||||
website.twitter_screen_name)):
|
||||
_logger.debug("Skip fetching favorite tweets for unconfigured website %s",
|
||||
website)
|
||||
continue
|
||||
params = {'screen_name': website.twitter_screen_name}
|
||||
last_tweet = website_tweets.search_read(
|
||||
cr, uid, [('website_id', '=', website.id),
|
||||
('screen_name', '=', website.twitter_screen_name)],
|
||||
['tweet_id'],
|
||||
limit=1, order='tweet_id desc', context=context)
|
||||
if last_tweet:
|
||||
params['since_id'] = int(last_tweet[0]['tweet_id'])
|
||||
_logger.debug("Fetching favorite tweets using params %r", params)
|
||||
response = self._request(website, REQUEST_FAVORITE_LIST_URL, params=params)
|
||||
for tweet_dict in response:
|
||||
tweet_id = tweet_dict['id'] # unsigned 64-bit snowflake ID
|
||||
tweet_ids = website_tweets.search(cr, uid, [('tweet_id', '=', tweet_id)])
|
||||
if not tweet_ids:
|
||||
new_tweet = website_tweets.create(
|
||||
cr, uid,
|
||||
{
|
||||
'website_id': website.id,
|
||||
'tweet': json.dumps(tweet_dict),
|
||||
'tweet_id': tweet_id, # stored in NUMERIC PG field
|
||||
'screen_name': website.twitter_screen_name,
|
||||
},
|
||||
context=context)
|
||||
_logger.debug("Found new favorite: %r, %r", tweet_id, tweet_dict)
|
||||
tweet_ids.append(new_tweet)
|
||||
return tweet_ids
|
||||
|
||||
def _get_access_token(self, website):
|
||||
"""Obtain a bearer token."""
|
||||
bearer_token_cred = '%s:%s' % (website.twitter_api_key, website.twitter_api_secret)
|
||||
encoded_cred = base64.b64encode(bearer_token_cred)
|
||||
request = Request(REQUEST_TOKEN_URL)
|
||||
request.add_header('Content-Type',
|
||||
'application/x-www-form-urlencoded;charset=UTF-8')
|
||||
request.add_header('Authorization',
|
||||
'Basic %s' % encoded_cred)
|
||||
request.add_data('grant_type=client_credentials')
|
||||
data = json.load(urlopen(request, timeout=URLOPEN_TIMEOUT))
|
||||
access_token = data['access_token']
|
||||
return access_token
|
||||
|
||||
class WebsiteTwitterTweet(osv.osv):
|
||||
_name = "website.twitter.tweet"
|
||||
_description = "Twitter Tweets"
|
||||
_columns = {
|
||||
'website_id': fields.many2one('website', string="Website"),
|
||||
'screen_name': fields.char("Screen Name"),
|
||||
'tweet': fields.text('Tweets'),
|
||||
|
||||
# Twitter IDs are 64-bit unsigned ints, so we need to store them in
|
||||
# unlimited precision NUMERIC columns, which can be done with a
|
||||
# float field. Used digits=(0,0) to indicate unlimited.
|
||||
# Using VARCHAR would work too but would have sorting problems.
|
||||
'tweet_id': fields.float("Tweet ID", digits=(0,0)), # Twitter
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
import logging
|
||||
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools.translate import _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class twitter_config_settings(osv.osv_memory):
|
||||
_inherit = 'website.config.settings'
|
||||
|
||||
_columns = {
|
||||
'twitter_api_key': fields.related(
|
||||
'website_id', 'twitter_api_key', type="char",
|
||||
string='Twitter API Key',
|
||||
help="Twitter API key you can get it from https://apps.twitter.com/app/new"),
|
||||
'twitter_api_secret': fields.related(
|
||||
'website_id', 'twitter_api_secret', type="char",
|
||||
string='Twitter API secret',
|
||||
help="Twitter API secret you can get it from https://apps.twitter.com/app/new"),
|
||||
'twitter_tutorial': fields.dummy(
|
||||
type="boolean", string="Show me how to obtain the Twitter API Key and Secret"),
|
||||
'twitter_screen_name': fields.related(
|
||||
'website_id', 'twitter_screen_name',
|
||||
type="char", string='Get favorites from this screen name',
|
||||
help="Screen Name of the Twitter Account from which you want to load favorites."
|
||||
"It does not have to match the API Key/Secret."),
|
||||
}
|
||||
|
||||
def _check_twitter_authorization(self, cr, uid, config_id, context=None):
|
||||
website_obj = self.pool['website']
|
||||
website_config = self.browse(cr, uid, config_id, context=context)
|
||||
try:
|
||||
website_obj.fetch_favorite_tweets(cr, uid, [website_config.website_id.id], context=context)
|
||||
except Exception:
|
||||
_logger.warning('Failed to verify twitter API authorization', exc_info=True)
|
||||
raise osv.except_osv(_('Twitter authorization error!'), _('Please double-check your Twitter API Key and Secret'))
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
res_id = super(twitter_config_settings, self).create(cr, uid, vals, context=context)
|
||||
if vals.get('twitter_api_key') and vals.get('twitter_api_secret'):
|
||||
self._check_twitter_authorization(cr, uid, res_id, context=context)
|
||||
return res_id
|
|
@ -0,0 +1,2 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_website_twitter_tweet_public,access of twitter snippet,website_twitter.model_website_twitter_tweet,,1,0,0,0
|
|
|
@ -0,0 +1,3 @@
|
|||
sass:
|
||||
sass -t expanded --compass --unix-newlines --watch website.twitter.sass:website.twitter.css
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
.wrap-row {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
height: 310px;
|
||||
}
|
||||
.wrap-row .twitter-row {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
.wrap-row .twitter-row div.scrollWrapper {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.wrap-row .twitter-row div.scrollableArea {
|
||||
position: relative;
|
||||
width: auto;
|
||||
height: 100%;
|
||||
}
|
||||
.wrap-row .twitter-row div .tweet {
|
||||
border: 1px solid #cccccc;
|
||||
max-width: 500px;
|
||||
width: 500px;
|
||||
font-size: 0.8em;
|
||||
padding-top: 12px;
|
||||
padding-right: 10px;
|
||||
padding-bottom: 12px;
|
||||
padding-left: 10px;
|
||||
float: left;
|
||||
display: block;
|
||||
margin: 6px;
|
||||
max-height: 90px;
|
||||
height: 90px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
.wrap-row .twitter-row div .tweet h4, .wrap-row .twitter-row div .tweet p {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.wrap-row .twitter-row div .tweet .left {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 80px;
|
||||
}
|
||||
.wrap-row .twitter-row div .tweet .left img {
|
||||
width: 65px;
|
||||
height: auto;
|
||||
float: left;
|
||||
display: block;
|
||||
margin: 0px 5px 0px -5px;
|
||||
}
|
||||
.wrap-row .twitter-row div .tweet .right {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 470px;
|
||||
}
|
||||
.wrap-row .twitter-row div .tweet .right .top {
|
||||
height: 20px;
|
||||
}
|
||||
.wrap-row .twitter-row div .tweet h4 {
|
||||
font-size: 14px;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-weight: bold;
|
||||
color: black;
|
||||
float: left;
|
||||
display: block;
|
||||
position: relative;
|
||||
margin-left: 70px;
|
||||
margin-top: -65px;
|
||||
}
|
||||
.wrap-row .twitter-row div .tweet h4 span {
|
||||
color: #cccccc;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
}
|
||||
.wrap-row .twitter-row div .tweet p {
|
||||
line-height: 1.5em;
|
||||
float: left;
|
||||
position: relative;
|
||||
display: block;
|
||||
}
|
||||
.wrap-row .twitter-row div .tweet .date {
|
||||
float: right;
|
||||
line-height: 0.5em;
|
||||
margin-top: -60px;
|
||||
margin-right: -10px;
|
||||
}
|
||||
.wrap-row .twitter-row div .tweet .right .bottom p {
|
||||
margin-top: -65px;
|
||||
margin-left: 70px;
|
||||
font-size: 12px;
|
||||
word-break: break-word;
|
||||
}
|
||||
.wrap-row .twitter-row div .tweet:hover {
|
||||
-webkit-box-shadow: 0.5px 0.5px 0.5px 1px #428bca;
|
||||
-moz-box-shadow: 0.5px 0.5px 0.5px 1px #428bca;
|
||||
box-shadow: 0.5px 0.5px 0.5px 1px #428bca;
|
||||
cursor: pointer;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 580px) {
|
||||
.wrap-row {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
height: 100px;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
.wrap-row
|
||||
position: relative
|
||||
overflow: hidden
|
||||
height: 310px
|
||||
.twitter-row
|
||||
position: absolute
|
||||
width: 100%
|
||||
height: auto
|
||||
div
|
||||
&.scrollWrapper
|
||||
position: relative
|
||||
overflow: hidden
|
||||
width: 100%
|
||||
height: 100%
|
||||
&.scrollableArea
|
||||
position: relative
|
||||
width: auto
|
||||
height: 100%
|
||||
.tweet
|
||||
border: 1px solid #ccc
|
||||
max-width: 500px
|
||||
width: 500px
|
||||
font-size: 0.8em
|
||||
padding-top: 12px
|
||||
padding-right: 10px
|
||||
padding-bottom: 12px
|
||||
padding-left: 10px
|
||||
float: left
|
||||
display: block
|
||||
margin: 6px
|
||||
max-height: 90px
|
||||
height: 90px
|
||||
opacity: 0.6
|
||||
h4, p
|
||||
padding: 0
|
||||
margin: 0
|
||||
.left
|
||||
display: block
|
||||
float: left
|
||||
width: 80px
|
||||
img
|
||||
width: 65px
|
||||
height: auto
|
||||
float: left
|
||||
display: block
|
||||
margin: 0px 5px 0px -5px
|
||||
.right
|
||||
display: block
|
||||
float: left
|
||||
width: 470px
|
||||
.top
|
||||
height: 20px
|
||||
h4
|
||||
font-size: 14px
|
||||
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif
|
||||
font-weight: bold
|
||||
color: #000
|
||||
float: left
|
||||
display: block
|
||||
position: relative
|
||||
margin-left: 70px
|
||||
margin-top: -65px
|
||||
span
|
||||
color: #ccc
|
||||
font-weight: bold
|
||||
font-size: 14px
|
||||
p
|
||||
line-height: 1.5em
|
||||
float: left
|
||||
position: relative
|
||||
display: block
|
||||
&.date
|
||||
float: right
|
||||
line-height: 0.5em
|
||||
margin-top: -60px
|
||||
margin-right: -10px
|
||||
.right .bottom p
|
||||
margin-top: -65px
|
||||
margin-left: 70px
|
||||
font-size: 12px
|
||||
word-break: break-word
|
||||
&:hover
|
||||
-webkit-box-shadow: 0.5px 0.5px 0.5px 1px #428BCA
|
||||
-moz-box-shadow: 0.5px 0.5px 0.5px 1px #428BCA
|
||||
box-shadow: 0.5px 0.5px 0.5px 1px #428BCA
|
||||
cursor: pointer
|
||||
opacity: 1
|
||||
|
||||
|
||||
@media screen and (max-width: 580px)
|
||||
.wrap-row
|
||||
position: relative
|
||||
overflow: hidden
|
||||
height: 100px
|
Binary file not shown.
After Width: | Height: | Size: 38 KiB |
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
|
@ -0,0 +1,144 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
var website = openerp.website,
|
||||
qweb = openerp.qweb;
|
||||
|
||||
qweb.add_template('/website_twitter/static/src/xml/website.twitter.xml');
|
||||
if (!website.snippet) website.snippet = {};
|
||||
website.snippet.animationRegistry.twitter = website.snippet.Animation.extend({
|
||||
selector: ".twitter",
|
||||
start: function () {
|
||||
var self = this;
|
||||
var timeline = this.$target.find(".twitter_timeline");
|
||||
|
||||
this.$target.on('click', '.twitter_timeline .tweet', function($event) {
|
||||
if ($event.target.tagName.toLowerCase() !== "a") {
|
||||
var url = $($event.currentTarget).data('url');
|
||||
if (url) {
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
else {
|
||||
debugger;
|
||||
}
|
||||
}
|
||||
});
|
||||
$("<center><div><img src='/website_twitter/static/src/img/loadtweet.gif'></div></center>").appendTo(timeline);
|
||||
openerp.jsonRpc('/get_favorites','call', {}).then(function(data) {
|
||||
self.$target.find(".twitter_timeline").empty();
|
||||
if (data.error) {
|
||||
self.error(data);
|
||||
}
|
||||
else {
|
||||
self.render(data);
|
||||
self.setupMouseEvents();
|
||||
}
|
||||
});
|
||||
},
|
||||
stop: function() {
|
||||
$(this).find('.scrollWrapper').each(function(index, el){
|
||||
self.stop_scrolling($(el));
|
||||
});
|
||||
this.clearMouseEvents();
|
||||
},
|
||||
error: function(data){
|
||||
var $error = $(qweb.render("website.Twitter.Error", {'data': data}));
|
||||
$error.appendTo(this.$target.find(".twitter_timeline"));
|
||||
},
|
||||
parse_tweet: function (tweet) {
|
||||
var create_link = function (url, text) {
|
||||
var c = $("<a>", {
|
||||
text: text,
|
||||
href: url,
|
||||
target: "_blank"
|
||||
});
|
||||
return c.prop("outerHTML");
|
||||
};
|
||||
return tweet.text.replace(/[A-Za-z]+:\/\/[A-Za-z0-9-_]+\.[A-Za-z0-9-_:%&~\?\/.=]+/g,
|
||||
function (url) { return create_link(url, url) })
|
||||
.replace(/[@]+[A-Za-z0-9_]+/g,
|
||||
function (screen_name) { return create_link("http://twitter.com/" + screen_name.replace("@",""), screen_name); })
|
||||
.replace(/[#]+[A-Za-z0-9_]+/g,
|
||||
function (hashtag) { return create_link("http://twitter.com/search?q="+hashtag.replace("#",""), hashtag); });
|
||||
},
|
||||
parse_date: function(tweet) {
|
||||
var d = new Date(tweet.created_at);
|
||||
return d.toDateString();
|
||||
},
|
||||
setupMouseEvents: function() {
|
||||
var self = this;
|
||||
this.$scroller.mouseenter(function() {
|
||||
$(this).find('.scrollWrapper').each(function(index, el){
|
||||
self.stop_scrolling($(el));
|
||||
});
|
||||
}).mouseleave(function() {
|
||||
$(this).find('.scrollWrapper').each(function(index, el){
|
||||
self.start_scrolling($(el));
|
||||
});
|
||||
});
|
||||
},
|
||||
clearMouseEvents: function() {
|
||||
if (this.$scroller) {
|
||||
this.$scroller.off('mouseenter')
|
||||
.off('mouseleave');
|
||||
}
|
||||
},
|
||||
render: function(data){
|
||||
var self = this;
|
||||
var timeline = this.$target.find(".twitter_timeline");
|
||||
var tweets = [];
|
||||
$.each(data, function (e, tweet) {
|
||||
tweet.created_at = self.parse_date(tweet);
|
||||
tweet.text = self.parse_tweet(tweet);
|
||||
tweets.push(qweb.render("website.Twitter.Tweet", {'tweet': tweet}));
|
||||
});
|
||||
|
||||
var f = Math.floor(tweets.length / 3);
|
||||
var tweet_slice = [tweets.slice(0, f).join(" "), tweets.slice(f, f * 2).join(" "), tweets.slice(f * 2, tweets.length).join(" ")];
|
||||
|
||||
this.$scroller = $(qweb.render("website.Twitter.Scroller"));
|
||||
this.$scroller.appendTo(this.$target.find(".twitter_timeline"));
|
||||
this.$scroller.find("div[id^='scroller']").each(function(index, element){
|
||||
var scrollWrapper = $('<div class="scrollWrapper"></div>');
|
||||
var scrollableArea = $('<div class="scrollableArea"></div>');
|
||||
scrollWrapper.append(scrollableArea)
|
||||
.data('scrollableArea', scrollableArea);
|
||||
scrollableArea.append(tweet_slice[index]);
|
||||
$(element).append(scrollWrapper);
|
||||
scrollableArea.width(self.get_wrapper_width(scrollableArea));
|
||||
scrollWrapper.scrollLeft(index*180);
|
||||
self.start_scrolling(scrollWrapper);
|
||||
});
|
||||
},
|
||||
get_wrapper_width: function(wrapper){
|
||||
var total_width = 0;
|
||||
wrapper.children().each(function(){
|
||||
total_width += $(this).outerWidth(true);
|
||||
});
|
||||
return total_width;
|
||||
},
|
||||
start_scrolling: function(wrapper){
|
||||
var self = this;
|
||||
wrapper.data("getNextElementWidth", true);
|
||||
wrapper.data("autoScrollingInterval", setInterval(function () {
|
||||
wrapper.scrollLeft(wrapper.scrollLeft() + 1);
|
||||
self.swap_right(wrapper);
|
||||
}, 20));
|
||||
},
|
||||
stop_scrolling: function(wrapper){
|
||||
clearInterval(wrapper.data('autoScrollingInterval'));
|
||||
},
|
||||
swap_right: function(wrapper){
|
||||
if (wrapper.data("getNextElementWidth")) {
|
||||
wrapper.data("swapAt", wrapper.data("scrollableArea").children(":first").outerWidth(true));
|
||||
wrapper.data("getNextElementWidth", false);
|
||||
}
|
||||
if (wrapper.data("swapAt") <= wrapper.scrollLeft()){
|
||||
var swap_el = wrapper.data("scrollableArea").children(":first").detach();
|
||||
wrapper.data("scrollableArea").append(swap_el);
|
||||
wrapper.scrollLeft(wrapper.scrollLeft() - swap_el.outerWidth(true));
|
||||
wrapper.data("getNextElementWidth", true);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
})();
|
|
@ -0,0 +1,51 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
var website = openerp.website,
|
||||
qweb = openerp.qweb;
|
||||
|
||||
website.snippet.options["twitter"] = website.snippet.options.marginAndResize.extend({
|
||||
start: function(){
|
||||
this._super();
|
||||
this.make_hover_config();
|
||||
this.$target.find('.lnk_configure').click(function(e){
|
||||
window.location = e.target.href;
|
||||
});
|
||||
if (this.$target.data("snippet-view")) {
|
||||
this.$target.data("snippet-view").stop();
|
||||
}
|
||||
},
|
||||
twitter_reload: function(){
|
||||
openerp.jsonRpc('/twitter_reload','call', {});
|
||||
},
|
||||
make_hover_config: function(){
|
||||
var self = this;
|
||||
var $configuration = $(qweb.render("website.Twitter.Reload")).hide().appendTo(document.body).click(function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
self.twitter_reload();
|
||||
});
|
||||
this.$target.on('mouseover', '', function () {
|
||||
var $selected = $(this);
|
||||
var position = $selected.offset();
|
||||
$configuration.show().offset({
|
||||
top: $selected.outerHeight() / 2
|
||||
+ position.top
|
||||
- $configuration.outerHeight() / 2,
|
||||
left: $selected.outerWidth() / 2
|
||||
+ position.left
|
||||
- $configuration.outerWidth() / 2,
|
||||
})
|
||||
}).on('mouseleave', '', function (e) {
|
||||
var current = document.elementFromPoint(e.clientX, e.clientY);
|
||||
if (current === $configuration[0]) {
|
||||
return;
|
||||
}
|
||||
$configuration.hide();
|
||||
});
|
||||
},
|
||||
clean_for_save: function () {
|
||||
this.$target.find(".twitter_timeline").empty();
|
||||
},
|
||||
});
|
||||
|
||||
})();
|
|
@ -0,0 +1,45 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="website.Twitter.Tweet">
|
||||
<div class="tweet" t-attf-data-url="http://twitter.com/#{tweet.user.screen_name}/status/#{tweet.id_str}" t-attf-data-tweet-id="#{tweet.id_str}">
|
||||
<div class="left">
|
||||
<img t-att-src="tweet.user.profile_image_url"/>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="top">
|
||||
<h4>
|
||||
<t t-esc="tweet.user.name"/>
|
||||
<span>
|
||||
<a t-att-href="'https://twitter.com/' + tweet.user.screen_name" target="_blank"><t t-esc="'@' + tweet.user.screen_name "/></a>
|
||||
</span>
|
||||
</h4>
|
||||
<a class="date" target="_blank" t-attf-href="http://twitter.com/#{tweet.user.screen_name}/status/#{tweet.id_str}"><t t-esc="tweet.created_at"/></a>
|
||||
</div>
|
||||
<div class="bottom"><p><t t-raw="tweet.text"/></p></div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="website.Twitter.Scroller">
|
||||
<div class="wrap-row" contenteditable="false">
|
||||
<div class="twitter-row">
|
||||
<div class="twitter-scroller">
|
||||
<div id="scroller1" ></div>
|
||||
<div id="scroller2" ></div>
|
||||
<div id="scroller3" ></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="website.Twitter.Reload">
|
||||
<button type="button" contenteditable="false" class="btn btn-primary hover-edition-button">Reload</button>
|
||||
</t>
|
||||
<t t-name="website.Twitter.Error">
|
||||
<div class="alert alert-info" contenteditable="false">
|
||||
<t t-esc="data.error"/>
|
||||
<i t-if='!data.nodata' class="fa fa-plus-circle">
|
||||
<a class="lnk_configure" href="/web#action=website.action_website_configuration">Twitter Configuration</a>
|
||||
</i>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<template id="website_twitter_snippet" name="Twitter snippet" inherit_id="website.snippets">
|
||||
<xpath expr="//div[@id='snippet_feature']" position="inside">
|
||||
<div>
|
||||
<div class="oe_snippet_thumbnail">
|
||||
<img class="oe_snippet_thumbnail_img" src="/website_twitter/static/src/img/twitter_scroll.png"/>
|
||||
<span class="oe_snippet_thumbnail_title">Twitter Scroller</span>
|
||||
</div>
|
||||
<section class="oe_snippet_body twitter" data-screen-name="OpenERP" data-limit="15">
|
||||
<div class="twitter_timeline" contenteditable="false"></div>
|
||||
</section>
|
||||
</div>
|
||||
</xpath>
|
||||
</template>
|
||||
<template id="website_twitter_options" name="Twitter Options" inherit_id="website.snippet_options">
|
||||
<xpath expr="//div" position="after">
|
||||
<div data-snippet-option-id='twitter'
|
||||
data-selector=".twitter"
|
||||
data-selector-children=".oe_structure, [data-oe-type=html]">
|
||||
</div>
|
||||
</xpath>
|
||||
</template>
|
||||
<template id="twitter" inherit_id="website.layout" name="Twitter Snippet">
|
||||
<xpath expr="//head" position="inside">
|
||||
<link rel="stylesheet" href="/website_twitter/static/src/css/website.twitter.css" type="text/css"/>
|
||||
<script type="text/javascript" src="/website_twitter/static/src/js/website.twitter.animation.js"></script>
|
||||
<script type="text/javascript" src="/website_twitter/static/src/js/website.twitter.editor.js" groups="base.group_website_publisher"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
</data>
|
||||
</openerp>
|
|
@ -0,0 +1,56 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<record id="view_website_config_settings" model="ir.ui.view">
|
||||
<field name="name">Twitter settings</field>
|
||||
<field name="model">website.config.settings</field>
|
||||
<field name="inherit_id" ref="website.view_website_config_settings"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@name='social_twitter']" position="after">
|
||||
<label for="id" string="Twitter API"/>
|
||||
<div>
|
||||
<p>Set your Twitter API access below to be able to use the Twitter Scroller Website snippet.<br/>
|
||||
You can get your API credentials from <a href="https://apps.twitter.com/app/new" target="new">https://apps.twitter.com/app/new</a>
|
||||
</p>
|
||||
<div>
|
||||
<field name="twitter_tutorial" class="oe_inline"/><label for="twitter_tutorial"/>
|
||||
|
||||
<blockquote class="small" attrs="{'invisible':[('twitter_tutorial','=',False)]}">
|
||||
<h2>How to configure the Twitter API access</h2>
|
||||
<ol>
|
||||
<li>Create a new Twitter application on <a href="https://apps.twitter.com/app/new" target="new">https://apps.twitter.com/app/new</a>
|
||||
with the following values:
|
||||
<ul>
|
||||
<li><strong>Name: </strong> OpenERP Tweet Scroller</li>
|
||||
<li><strong>Description: </strong> OpenERP Tweet Scroller </li>
|
||||
<li><strong>Website: </strong> <tt>http://www.openerp.com</tt></li>
|
||||
<li><strong>Callback URL: </strong> leave it blank</li>
|
||||
<li>Accept terms of use and click on the Create button at the bottom</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Switch to the API Keys tab: <br/>
|
||||
<img src='/website_twitter/static/src/img/api_key.png'/>
|
||||
</li>
|
||||
<li>Copy/Paste API Key and Secret below</li>
|
||||
<li>Enter the screen name from which you want to load favorite Tweets (does not need to be the same as the API keys)</li>
|
||||
</ol>
|
||||
</blockquote>
|
||||
</div>
|
||||
<div>
|
||||
<label for="twitter_api_key"/>
|
||||
<field name="twitter_api_key" class="oe_inline"/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="twitter_api_secret"/>
|
||||
<field name="twitter_api_secret" class="oe_inline"/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="twitter_screen_name"/>
|
||||
<field name="twitter_screen_name" class="oe_inline"/>
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
Loading…
Reference in New Issue