[MERGE] [ADD] website_twitter: add twitter scroller snippet
authorBarad Mahendra <mba@tinyerp.com>
Thu, 10 Apr 2014 17:46:22 +0000 (19:46 +0200)
committerOlivier Dony <odo@openerp.com>
Thu, 10 Apr 2014 17:46:22 +0000 (19:46 +0200)
bzr revid: odo@openerp.com-20140410174622-j2waid38nk3fqitt

20 files changed:
addons/website_twitter/__init__.py [new file with mode: 0644]
addons/website_twitter/__openerp__.py [new file with mode: 0644]
addons/website_twitter/controllers/__init__.py [new file with mode: 0644]
addons/website_twitter/controllers/main.py [new file with mode: 0644]
addons/website_twitter/data/twitter_data.xml [new file with mode: 0644]
addons/website_twitter/models/__init__.py [new file with mode: 0644]
addons/website_twitter/models/twitter.py [new file with mode: 0644]
addons/website_twitter/models/twitter_config.py [new file with mode: 0644]
addons/website_twitter/security/ir.model.access.csv [new file with mode: 0644]
addons/website_twitter/static/src/css/Makefile [new file with mode: 0644]
addons/website_twitter/static/src/css/website.twitter.css [new file with mode: 0644]
addons/website_twitter/static/src/css/website.twitter.sass [new file with mode: 0644]
addons/website_twitter/static/src/img/api_key.png [new file with mode: 0644]
addons/website_twitter/static/src/img/loadtweet.gif [new file with mode: 0644]
addons/website_twitter/static/src/img/twitter_scroll.png [new file with mode: 0644]
addons/website_twitter/static/src/js/website.twitter.animation.js [new file with mode: 0644]
addons/website_twitter/static/src/js/website.twitter.editor.js [new file with mode: 0644]
addons/website_twitter/static/src/xml/website.twitter.xml [new file with mode: 0644]
addons/website_twitter/views/twitter_snippet.xml [new file with mode: 0644]
addons/website_twitter/views/twitter_view.xml [new file with mode: 0644]

diff --git a/addons/website_twitter/__init__.py b/addons/website_twitter/__init__.py
new file mode 100644 (file)
index 0000000..396c76f
--- /dev/null
@@ -0,0 +1,2 @@
+import models
+import controllers
diff --git a/addons/website_twitter/__openerp__.py b/addons/website_twitter/__openerp__.py
new file mode 100644 (file)
index 0000000..96e17ff
--- /dev/null
@@ -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,
+}
diff --git a/addons/website_twitter/controllers/__init__.py b/addons/website_twitter/controllers/__init__.py
new file mode 100644 (file)
index 0000000..8ee9bae
--- /dev/null
@@ -0,0 +1 @@
+import main
diff --git a/addons/website_twitter/controllers/main.py b/addons/website_twitter/controllers/main.py
new file mode 100644 (file)
index 0000000..afd636a
--- /dev/null
@@ -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]
diff --git a/addons/website_twitter/data/twitter_data.xml b/addons/website_twitter/data/twitter_data.xml
new file mode 100644 (file)
index 0000000..b7ca428
--- /dev/null
@@ -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>
+
diff --git a/addons/website_twitter/models/__init__.py b/addons/website_twitter/models/__init__.py
new file mode 100644 (file)
index 0000000..8c34d4f
--- /dev/null
@@ -0,0 +1,3 @@
+import twitter
+import twitter_config
+
diff --git a/addons/website_twitter/models/twitter.py b/addons/website_twitter/models/twitter.py
new file mode 100644 (file)
index 0000000..7c1c417
--- /dev/null
@@ -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
+    }
diff --git a/addons/website_twitter/models/twitter_config.py b/addons/website_twitter/models/twitter_config.py
new file mode 100644 (file)
index 0000000..3330a5b
--- /dev/null
@@ -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
\ No newline at end of file
diff --git a/addons/website_twitter/security/ir.model.access.csv b/addons/website_twitter/security/ir.model.access.csv
new file mode 100644 (file)
index 0000000..eb88063
--- /dev/null
@@ -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
diff --git a/addons/website_twitter/static/src/css/Makefile b/addons/website_twitter/static/src/css/Makefile
new file mode 100644 (file)
index 0000000..21d0e6c
--- /dev/null
@@ -0,0 +1,3 @@
+sass:
+       sass -t expanded --compass --unix-newlines --watch website.twitter.sass:website.twitter.css
+        
diff --git a/addons/website_twitter/static/src/css/website.twitter.css b/addons/website_twitter/static/src/css/website.twitter.css
new file mode 100644 (file)
index 0000000..b44bf80
--- /dev/null
@@ -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;
+  }
+}
diff --git a/addons/website_twitter/static/src/css/website.twitter.sass b/addons/website_twitter/static/src/css/website.twitter.sass
new file mode 100644 (file)
index 0000000..9eca59e
--- /dev/null
@@ -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
diff --git a/addons/website_twitter/static/src/img/api_key.png b/addons/website_twitter/static/src/img/api_key.png
new file mode 100644 (file)
index 0000000..8f53f96
Binary files /dev/null and b/addons/website_twitter/static/src/img/api_key.png differ
diff --git a/addons/website_twitter/static/src/img/loadtweet.gif b/addons/website_twitter/static/src/img/loadtweet.gif
new file mode 100644 (file)
index 0000000..f8a0a61
Binary files /dev/null and b/addons/website_twitter/static/src/img/loadtweet.gif differ
diff --git a/addons/website_twitter/static/src/img/twitter_scroll.png b/addons/website_twitter/static/src/img/twitter_scroll.png
new file mode 100644 (file)
index 0000000..b8d602d
Binary files /dev/null and b/addons/website_twitter/static/src/img/twitter_scroll.png differ
diff --git a/addons/website_twitter/static/src/js/website.twitter.animation.js b/addons/website_twitter/static/src/js/website.twitter.animation.js
new file mode 100644 (file)
index 0000000..bb19fd5
--- /dev/null
@@ -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);
+            }
+        },
+    });
+
+})();
diff --git a/addons/website_twitter/static/src/js/website.twitter.editor.js b/addons/website_twitter/static/src/js/website.twitter.editor.js
new file mode 100644 (file)
index 0000000..69a1896
--- /dev/null
@@ -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();
+        },
+    });
+
+})();
diff --git a/addons/website_twitter/static/src/xml/website.twitter.xml b/addons/website_twitter/static/src/xml/website.twitter.xml
new file mode 100644 (file)
index 0000000..25c6121
--- /dev/null
@@ -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>
diff --git a/addons/website_twitter/views/twitter_snippet.xml b/addons/website_twitter/views/twitter_snippet.xml
new file mode 100644 (file)
index 0000000..bc039d7
--- /dev/null
@@ -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>
diff --git a/addons/website_twitter/views/twitter_view.xml b/addons/website_twitter/views/twitter_view.xml
new file mode 100644 (file)
index 0000000..8600cec
--- /dev/null
@@ -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>