diff --git a/addons/website_slides/controllers/main.py b/addons/website_slides/controllers/main.py index 52c254fb9e2..3047b06678a 100644 --- a/addons/website_slides/controllers/main.py +++ b/addons/website_slides/controllers/main.py @@ -4,7 +4,7 @@ import logging import werkzeug from openerp.addons.web import http -from openerp.exceptions import AccessError +from openerp.exceptions import AccessError, UserError from openerp.http import request from openerp.tools.translate import _ @@ -127,6 +127,7 @@ class website_slides(http.Controller): 'user': user, 'pager': pager, 'is_public_user': user == request.website.user_id, + 'display_channel_settings': not request.httprequest.cookies.get('slides_channel_%s' % (channel.id), False) and channel.can_see_full, } if search: values['search'] = search @@ -312,7 +313,7 @@ class website_slides(http.Controller): # otherwise client slide create dialog box continue processing even server fail to create a slide. try: slide_id = request.env['slide.slide'].create(values) - except AccessError as e: + except (UserError, AccessError) as e: _logger.error(e) return {'error': e.name} except Exception as e: diff --git a/addons/website_slides/models/slides.py b/addons/website_slides/models/slides.py index ceeeeaf58f3..503d89e19d7 100644 --- a/addons/website_slides/models/slides.py +++ b/addons/website_slides/models/slides.py @@ -135,7 +135,7 @@ class Category(models.Model): _order = "sequence, id" name = fields.Char('Name', translate=True, required=True) - channel_id = fields.Many2one('slide.channel', string="Channel", required=True) + channel_id = fields.Many2one('slide.channel', string="Channel", required=True, ondelete='cascade') sequence = fields.Integer(default=10, help='Display order') slide_ids = fields.One2many('slide.slide', 'category_id', string="Slides") nbr_presentations = fields.Integer("Number of Presentations", compute='_count_presentations', store=True) @@ -239,8 +239,8 @@ class Slide(models.Model): def _get_image(self): for record in self: if record.image: - record.image_medium = image.crop_image(record.image, thumbnail_ratio=3) - record.image_thumb = image.crop_image(record.image, thumbnail_ratio=4) + record.image_medium = image.crop_image(record.image, type='top', ratio=(4, 3), thumbnail_ratio=4) + record.image_thumb = image.crop_image(record.image, type='top', ratio=(4, 3), thumbnail_ratio=6) else: record.image_medium = False record.iamge_thumb = False @@ -314,7 +314,12 @@ class Slide(models.Model): def _website_url(self, name, arg): res = super(Slide, self)._website_url(name, arg) base_url = self.env['ir.config_parameter'].get_param('web.base.url') - res.update({(slide.id, '%s/slides/slide/%s' % (base_url, slug(slide))) for slide in self}) + #link_tracker is not in dependencies, so use it to shorten url only if installed. + if self.env.registry.get('link.tracker'): + LinkTracker = self.env['link.tracker'] + res.update({(slide.id, LinkTracker.sudo().create({'url': '%s/slides/slide/%s' % (base_url, slug(slide))}).short_url) for slide in self}) + else: + res.update({(slide.id, '%s/slides/slide/%s' % (base_url, slug(slide))) for slide in self}) return res diff --git a/addons/website_slides/static/description/index.html b/addons/website_slides/static/description/index.html index 1dff078834f..f6cd3ebc780 100644 --- a/addons/website_slides/static/description/index.html +++ b/addons/website_slides/static/description/index.html @@ -5,14 +5,15 @@

Publish your presentations, videos, documents, infographics, etc.

-
- +
+

+

Odoo presentations lets you upload files privately or publicly in the following file formats: PowerPoint, PDF, Keynote, Video or OpenDocument presentations.

-

This module originally meant to be used for businesses to share slides among employees more easily. It also provides users the ability to rate, comment, and share the uploaded content. +

This module originally meant to be used for businesses to share slides among employees more easily. It also provides users the ability to rate, comment, share the uploaded content and see detailed statistics like total views, web-views, embedded-views, likes and dislikes, total comments, total social-shares and shares on individual social site.

Contact Us diff --git a/addons/website_slides/static/description/slides1.png b/addons/website_slides/static/description/slides1.png index aad8a869efc..91eb5288c33 100644 Binary files a/addons/website_slides/static/description/slides1.png and b/addons/website_slides/static/description/slides1.png differ diff --git a/addons/website_slides/static/description/slides2.png b/addons/website_slides/static/description/slides2.png index 0dbd8550d16..bf5487587cb 100644 Binary files a/addons/website_slides/static/description/slides2.png and b/addons/website_slides/static/description/slides2.png differ diff --git a/addons/website_slides/static/description/slides3.png b/addons/website_slides/static/description/slides3.png index e804143d9b1..ecbbf338297 100644 Binary files a/addons/website_slides/static/description/slides3.png and b/addons/website_slides/static/description/slides3.png differ diff --git a/addons/website_slides/static/description/slides4.png b/addons/website_slides/static/description/slides4.png new file mode 100644 index 00000000000..02f309f59c8 Binary files /dev/null and b/addons/website_slides/static/description/slides4.png differ diff --git a/addons/website_slides/static/lib/pdfslidesviewer/PDFSlidesViewer.js b/addons/website_slides/static/lib/pdfslidesviewer/PDFSlidesViewer.js index 85abc0c8233..a6d533e8cd0 100644 --- a/addons/website_slides/static/lib/pdfslidesviewer/PDFSlidesViewer.js +++ b/addons/website_slides/static/lib/pdfslidesviewer/PDFSlidesViewer.js @@ -148,6 +148,23 @@ var PDFSlidesViewer = (function(){ PDFSlidesViewer.prototype.toggleFullScreen = function(){ var el = this.canvas; + var $PDFViewer = $("div#PDFSlideViewer"); + var $navigation_footer = $('div#PDFViewer > div.navbar-fixed-bottom'); + + $(document).unbind('webkitfullscreenchange mozfullscreenchange fullscreenchange') + .bind('webkitfullscreenchange mozfullscreenchange fullscreenchange', function() { + var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || document.msFullScreen; + if (!state) { + $navigation_footer.removeAttr('style'); + $PDFViewer.unbind('click'); + } else { + $navigation_footer.css('z-index', 2147483647); + $PDFViewer.on('click', function() { + $navigation_footer.fadeToggle(); + }); + } + }); + var isFullscreenAvailable = document.fullscreenEnabled || document.mozFullScreenEnabled || document.webkitFullscreenEnabled || document.msFullscreenEnabled || false; if(isFullscreenAvailable){ // Full screen supported // get the actual element in FullScreen mode (Null if no element) diff --git a/addons/website_slides/static/src/js/slides.js b/addons/website_slides/static/src/js/slides.js index fe46ed826b0..f898e45ee76 100644 --- a/addons/website_slides/static/src/js/slides.js +++ b/addons/website_slides/static/src/js/slides.js @@ -29,6 +29,14 @@ $(document).ready(function () { $(el).text(display_str); }); + // To prevent showing channel settings alert box once user closed it. + $('.o_slides_hide_channel_settings').on('click', function(ev) { + var channel_id = $(this).data("channelId"); + ev.preventDefault(); + document.cookie = "slides_channel_" + channel_id + " = closed"; + return true; + }); + /* * Like/Dislike Buttons Widget */ @@ -140,6 +148,13 @@ $(document).ready(function () { * Social Sharing Statistics Widget */ if ($('div#statistic').length) { + var slide_url = $("div#statistic").attr('slide-url'); + var social_urls = { + 'linkedin': 'https://www.linkedin.com/countserv/count/share?url=', + 'twitter': 'https://cdn.api.twitter.com/1/urls/count.json?url=', + 'fb': 'https://graph.facebook.com/?id=', + 'gplus': 'https://clients6.google.com/rpc' + } var socialgatter = function (app_url, url, callback) { $.ajax({ url: app_url + url, @@ -147,43 +162,77 @@ $(document).ready(function () { success: callback }); }; - var current_url = window.location.origin + window.location.pathname; - socialgatter('https://www.linkedin.com/countserv/count/share?url=', current_url, function (data) { - $('#linkedin-badge').text(data.count || 0); - $('#total-share').text(parseInt($('#total-share').text()) + parseInt($('#linkedin-badge').text())); - }); - socialgatter('https://cdn.api.twitter.com/1/urls/count.json?url=', current_url, function (data) { - $('#twitter-badge').text(data.count || 0); - $('#total-share').text(parseInt($('#total-share').text()) + parseInt($('#twitter-badge').text())); - }); - socialgatter('https://graph.facebook.com/?id=', current_url, function (data) { - $('#facebook-badge').text(data.shares || 0); - $('#total-share').text(parseInt($('#total-share').text()) + parseInt($('#facebook-badge').text())); + var update_statistics = function(social_site, slide_url) { + if (social_site == 'linkedin') { + socialgatter(social_urls['linkedin'], slide_url, function(data) { + $('#linkedin-badge').text(data.count || 0); + $('#total-share').text(parseInt($('#total-share').text()) + parseInt($('#linkedin-badge').text())); + }); + } + if (social_site == 'twitter') { + socialgatter(social_urls['twitter'], slide_url, function(data) { + $('#twitter-badge').text(data.count || 0); + $('#total-share').text(parseInt($('#total-share').text()) + parseInt($('#twitter-badge').text())); + }); + } + if (social_site == 'fb') { + socialgatter(social_urls['fb'], slide_url, function(data) { + $('#facebook-badge').text(data.shares || 0); + $('#total-share').text(parseInt($('#total-share').text()) + parseInt($('#facebook-badge').text())); + }); + } + if (social_site == 'gplus') { + $.ajax({ + url: social_urls['gplus'], + type: "POST", + dataType: 'json', + contentType: 'application/json', + data: JSON.stringify([{ + "method": "pos.plusones.get", + "id": "p", + "params": { + "nolog": true, + "id": slide_url, + "source": "widget", + "userId": "@viewer", + "groupId": "@self" + }, + // TDE NOTE: should there be a key here ? + "jsonrpc": "2.0", + "apiVersion": "v1" + }]), + success: function(data) { + $('#google-badge').text(data[0].result.metadata.globalCounts.count || 0); + $('#total-share').text(parseInt($('#total-share').text()) + parseInt($('#google-badge').text())); + }, + }); + } + }; + + $.each(social_urls, function(key, value) { + update_statistics(key, slide_url) }); - $.ajax({ - url: 'https://clients6.google.com/rpc', - type: "POST", - dataType: 'json', - contentType: 'application/json', - data: JSON.stringify([{ - "method": "pos.plusones.get", - "id": "p", - "params": { - "nolog": true, - "id": current_url, - "source": "widget", - "userId": "@viewer", - "groupId": "@self" - }, - // TDE NOTE: should there be a key here ? - "jsonrpc": "2.0", - "apiVersion": "v1" - }]), - success: function (data) { - $('#google-badge').text(data[0].result.metadata.globalCounts.count || 0); - $('#total-share').text(parseInt($('#total-share').text()) + parseInt($('#google-badge').text())); - }, + $("a.o_slides_social_share").on('click', function(ev) { + ev.preventDefault(); + var key = $(ev.currentTarget).attr('social-key'); + var win_url = $(ev.currentTarget).attr('href'); + var win = window.open( + win_url, + 'Share Dialog', + 'width=626,height=436'); + var interval = window.setInterval(function() { + try { + if (win.closed) { + window.clearInterval(interval); + setTimeout(function() { + update_statistics(key, slide_url); + }, 3000); + } + } catch (e) { + window.clearInterval(interval); + } + }, 1000); }); } }); diff --git a/addons/website_slides/static/src/js/slides_upload.js b/addons/website_slides/static/src/js/slides_upload.js index aa8d3c339bb..91a4b1dadb1 100644 --- a/addons/website_slides/static/src/js/slides_upload.js +++ b/addons/website_slides/static/src/js/slides_upload.js @@ -198,7 +198,7 @@ var SlideDialog = Widget.extend({ width: '100%', placeholder: tag, allowClear: true, - formatNoMatches: _.str.sprintf(_t("No matches found. Type to create new %s"), tag), + formatNoMatches: false, multiple: multi, selection_data: false, fetch_rpc_fnc : fetch_fnc, @@ -208,14 +208,22 @@ var SlideDialog = Widget.extend({ } return data.text; }, - createSearchChoice: function (term) { - return { - id: _.uniqueId('tag_'), - create: true, - tag: term, - text: _.str.sprintf(_t("Create New %s '%s'"), tag, term) - }; + check_unique_tag: function(term, tags) { + return _.filter(tags, function(tag) { + return tag.text.toLowerCase() === term.toLowerCase() + }).length === 0; }, + createSearchChoice: function(term, data) { + var added_tags = $(this.opts.element).select2('data'); + if (this.opts.check_unique_tag(term, _.union(added_tags, data))) { + return { + id: _.uniqueId('tag_'), + create: true, + tag: term, + text: _.str.sprintf(_t("Create New %s '%s'"), tag, term) + }; + } + }, fill_data: function (query, data) { var that = this, tags = {results: []}; @@ -290,6 +298,14 @@ var SlideDialog = Widget.extend({ }); return res; }, + //Python PIL does not support SVG, so converting SVG to PNG + svg_to_png: function() { + var img = this.$el.find("img#slide-image")[0]; + var canvas = document.createElement("canvas"); + canvas.width = img.width, canvas.height = img.height; + canvas.getContext("2d").drawImage(img, 0, 0); + return canvas.toDataURL("image/png").split(',')[1]; + }, // Values and save get_value: function () { var canvas = this.$('#data_canvas')[0], @@ -313,8 +329,8 @@ var SlideDialog = Widget.extend({ if (/^image\/.*/.test(this.file.type)) { _.extend(values, { 'slide_type': 'infographic', - 'mime_type': this.file.type, - 'datas': this.file.data + 'mime_type': this.file.type === 'image/svg+xml' ? 'image/png' : this.file.type, + 'datas': this.file.type === 'image/svg+xml' ? this.svg_to_png() : this.file.data }); } return values; @@ -364,4 +380,4 @@ var SlideDialog = Widget.extend({ slides.page_widgets['upload_dialog'] = new SlideDialog(this, channel_id).appendTo(document.body); }); -}); \ No newline at end of file +}); diff --git a/addons/website_slides/static/src/less/website_slides.less b/addons/website_slides/static/src/less/website_slides.less index ae1dd9a2551..a9348336a3b 100644 --- a/addons/website_slides/static/src/less/website_slides.less +++ b/addons/website_slides/static/src/less/website_slides.less @@ -86,6 +86,10 @@ img.oe_slides_promote_image { color: @link-color; } +textarea.o_slides_comment { + resize: vertical; +} + // Thumbnails .oe_slides_thumbnail_container{ height: 300px @@ -103,6 +107,11 @@ img.oe_slides_promote_image { text-overflow: ellipsis } +.o_slide_preview { + max-height: 160px; + overflow: hidden; +} + // Suggested slides grid .oe_slides_suggestion_caption { position:absolute; @@ -154,3 +163,17 @@ img.oe_slides_promote_image { .oe_slides_share_bar{ padding: 10px 0; } + +.oe_slides_panel_footer #page_number { + width: 45px; +} +// Display Slide Navigation Footer bit clear on mobile preview +@media screen and (max-width: 300px) { + .o_slide_navigation_buttons { + margin-left: 15px; + margin-right: -15px; + .col-xs-4 { + display: none; + } + } +} diff --git a/addons/website_slides/static/src/xml/website_slides.xml b/addons/website_slides/static/src/xml/website_slides.xml index 0c5c62c3466..270d3acb9c1 100644 --- a/addons/website_slides/static/src/xml/website_slides.xml +++ b/addons/website_slides/static/src/xml/website_slides.xml @@ -15,7 +15,11 @@
- +
+
+ +
+
-