diff --git a/addons/website_twitter/__init__.py b/addons/website_twitter/__init__.py
new file mode 100644
index 00000000000..396c76fe87a
--- /dev/null
+++ b/addons/website_twitter/__init__.py
@@ -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
index 00000000000..96e17ff9356
--- /dev/null
+++ b/addons/website_twitter/__openerp__.py
@@ -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
index 00000000000..8ee9bae18d9
--- /dev/null
+++ b/addons/website_twitter/controllers/__init__.py
@@ -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
index 00000000000..afd636a26c4
--- /dev/null
+++ b/addons/website_twitter/controllers/main.py
@@ -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
index 00000000000..b7ca4281504
--- /dev/null
+++ b/addons/website_twitter/data/twitter_data.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
diff --git a/addons/website_twitter/models/__init__.py b/addons/website_twitter/models/__init__.py
new file mode 100644
index 00000000000..8c34d4f968e
--- /dev/null
+++ b/addons/website_twitter/models/__init__.py
@@ -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
index 00000000000..7c1c417040b
--- /dev/null
+++ b/addons/website_twitter/models/twitter.py
@@ -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
index 00000000000..3330a5b0424
--- /dev/null
+++ b/addons/website_twitter/models/twitter_config.py
@@ -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
index 00000000000..eb88063e9c5
--- /dev/null
+++ b/addons/website_twitter/security/ir.model.access.csv
@@ -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
index 00000000000..21d0e6cdc39
--- /dev/null
+++ b/addons/website_twitter/static/src/css/Makefile
@@ -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
index 00000000000..b44bf800bd7
--- /dev/null
+++ b/addons/website_twitter/static/src/css/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;
+ }
+}
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
index 00000000000..9eca59eb6ce
--- /dev/null
+++ b/addons/website_twitter/static/src/css/website.twitter.sass
@@ -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
index 00000000000..8f53f9649fa
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
index 00000000000..f8a0a6179bd
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
index 00000000000..b8d602d519a
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
index 00000000000..bb19fd5e66a
--- /dev/null
+++ b/addons/website_twitter/static/src/js/website.twitter.animation.js
@@ -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;
+ }
+ }
+ });
+ $("
").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 = $("", {
+ 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 = $('
');
+ var scrollableArea = $('
');
+ 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
index 00000000000..69a1896695a
--- /dev/null
+++ b/addons/website_twitter/static/src/js/website.twitter.editor.js
@@ -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
index 00000000000..25c6121e166
--- /dev/null
+++ b/addons/website_twitter/static/src/xml/website.twitter.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Reload
+
+
+
+
+
diff --git a/addons/website_twitter/views/twitter_snippet.xml b/addons/website_twitter/views/twitter_snippet.xml
new file mode 100644
index 00000000000..bc039d71c61
--- /dev/null
+++ b/addons/website_twitter/views/twitter_snippet.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
diff --git a/addons/website_twitter/views/twitter_view.xml b/addons/website_twitter/views/twitter_view.xml
new file mode 100644
index 00000000000..8600ceca039
--- /dev/null
+++ b/addons/website_twitter/views/twitter_view.xml
@@ -0,0 +1,56 @@
+
+
+
+
+ Twitter settings
+ website.config.settings
+
+
+
+
+
+
Set your Twitter API access below to be able to use the Twitter Scroller Website snippet.
+ You can get your API credentials from https://apps.twitter.com/app/new
+
+
+
+
+
+ How to configure the Twitter API access
+
+ Create a new Twitter application on https://apps.twitter.com/app/new
+ with the following values:
+
+ Name: OpenERP Tweet Scroller
+ Description: OpenERP Tweet Scroller
+ Website: http://www.openerp.com
+ Callback URL: leave it blank
+ Accept terms of use and click on the Create button at the bottom
+
+
+ Switch to the API Keys tab:
+
+
+ Copy/Paste API Key and Secret below
+ Enter the screen name from which you want to load favorite Tweets (does not need to be the same as the API keys)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+