From 7d66dcbf544215b23033e85a6c3a0cf5e8ffe3e4 Mon Sep 17 00:00:00 2001 From: Nikunj Ladava Date: Fri, 29 Jul 2022 05:45:03 +0000 Subject: [PATCH] [IMP] website_slides: improve design of share pages in elearning PURPOSE Globally improve user experience when sharing content from the e-learning app. This includes new sharing options, allowing to share to multiple emails, split the sharing template from slide and course, etc... SPECIFICATION - added two more options 'WhatsApp' and 'Pinterest' to share snippets. - we converted the email type of input for 'Share by email' to text, so we can enter multiple emails to share and also add validation in the controller for this input text input. - We improved the design of the share modal by making sure that the full URL for sharing link is visible and rearranged the UI fields of 'Share' nav-tab on non-fullscreen content (if there is embed code, the sharing by email and embed code will still be on the single row). - Improved the share modal of the channel by adding an email field in it to Share the channel through the mail. - New field name 'share_channel_template_id' for channel template in 'slide.channel' modal and also added _send_share_email method in it and email template for sharing channel through the mail, and also added the dedicated route for sharing the channel. - New share button is added on the top of documents in the slide (which previously was in the tab below the content). task- 2627364 Part-of: odoo/odoo#78139 --- addons/website_slides/controllers/main.py | 17 ++- .../data/mail_template_data.xml | 33 ++++++ addons/website_slides/models/slide_channel.py | 23 +++- addons/website_slides/models/slide_slide.py | 2 +- .../src/js/slides_course_fullscreen_player.js | 47 ++++++-- .../static/src/js/slides_embed.js | 21 ++-- .../static/src/js/slides_share.js | 84 +++++++++++-- .../src/scss/slides_slide_fullscreen.scss | 17 ++- .../static/src/scss/website_slides.scss | 11 +- .../static/src/xml/website_slides_share.xml | 60 +++++++--- .../static/src/xml/website_slides_sidebar.xml | 4 +- .../views/slide_channel_views.xml | 3 +- .../views/website_slides_templates_course.xml | 28 ++--- .../views/website_slides_templates_lesson.xml | 112 ++++-------------- .../website_slides_templates_lesson_embed.xml | 2 +- ...ite_slides_templates_lesson_fullscreen.xml | 18 +-- .../views/website_slides_templates_utils.xml | 110 +++++++++++++---- .../views/survey_templates.xml | 2 +- .../views/website_profile.xml | 2 +- .../views/website_slides_templates_utils.xml | 2 +- 20 files changed, 392 insertions(+), 206 deletions(-) diff --git a/addons/website_slides/controllers/main.py b/addons/website_slides/controllers/main.py index 9764cdde19e..db4088d193f 100644 --- a/addons/website_slides/controllers/main.py +++ b/addons/website_slides/controllers/main.py @@ -17,6 +17,7 @@ from odoo.addons.website_profile.controllers.main import WebsiteProfile from odoo.exceptions import AccessError, ValidationError, UserError, MissingError from odoo.http import request from odoo.osv import expression +from odoo.tools import email_split _logger = logging.getLogger(__name__) @@ -730,6 +731,14 @@ class WebsiteSlides(WebsiteProfile): return {'url': "/slides/%s" % (slug(channel))} + @http.route(['/slides/channel/send_share_email'], type='json', auth='user', website=True) + def slide_channel_send_share_email(self, channel_id, emails): + if not email_split(emails): + return False + channel = request.env['slide.channel'].browse(int(channel_id)) + channel._send_share_email(emails) + return True + @http.route(['/slides/channel/subscribe'], type='json', auth='user', website=True) def slide_channel_subscribe(self, channel_id): return request.env['slide.channel'].browse(channel_id).message_subscribe(partner_ids=[request.env.user.partner_id.id]) @@ -921,10 +930,12 @@ class WebsiteSlides(WebsiteProfile): return slide.is_preview @http.route(['/slides/slide/send_share_email'], type='json', auth='user', website=True) - def slide_send_share_email(self, slide_id, email, fullscreen=False): + def slide_send_share_email(self, slide_id, emails, fullscreen=False): + if not email_split(emails): + return False slide = request.env['slide.slide'].browse(int(slide_id)) - result = slide._send_share_email(email, fullscreen) - return result + slide._send_share_email(emails, fullscreen) + return True # -------------------------------------------------- # TAGS SECTION diff --git a/addons/website_slides/data/mail_template_data.xml b/addons/website_slides/data/mail_template_data.xml index ce7f59e01e3..52e5fd00184 100644 --- a/addons/website_slides/data/mail_template_data.xml +++ b/addons/website_slides/data/mail_template_data.xml @@ -95,6 +95,39 @@ {{ object.partner_id.lang }} + + Channel Shared + + {{ user.name }} shared a Course + {{ user.email_formatted }} + +
+

+ Hello

+ Mitchell Admin shared the document with you! +

+ + + +
+ + +
+ --
Mitchell Admin
+
+

+
+
+ +
+ Channel: Invite by email diff --git a/addons/website_slides/models/slide_channel.py b/addons/website_slides/models/slide_channel.py index d7be60fe5d3..f445d146aa8 100644 --- a/addons/website_slides/models/slide_channel.py +++ b/addons/website_slides/models/slide_channel.py @@ -251,7 +251,11 @@ class Channel(models.Model): help="Defines the email your Attendees will receive each time you upload new content.", default=lambda self: self.env['ir.model.data']._xmlid_to_res_id('website_slides.slide_template_published'), domain=[('model', '=', 'slide.slide')]) - share_template_id = fields.Many2one( + share_channel_template_id = fields.Many2one( + 'mail.template', string='Channel Share Template', + help='Email template used when sharing a channel', + default=lambda self: self.env['ir.model.data']._xmlid_to_res_id('website_slides.mail_template_channel_shared')) + share_slide_template_id = fields.Many2one( 'mail.template', string='Share Template', help="Email template used when sharing a slide", default=lambda self: self.env['ir.model.data']._xmlid_to_res_id('website_slides.slide_template_shared')) @@ -752,6 +756,23 @@ class Channel(models.Model): if removed_channel_partner_domain: self.env['slide.channel.partner'].sudo().search(removed_channel_partner_domain).unlink() + def _send_share_email(self, emails): + """ Share channel through emails.""" + mail_ids = [] + for record in self: + template = record.share_channel_template_id.with_context( + user=self.env.user, + email=emails, + base_url=record.get_base_url(), + ) + email_values = {'email_to': emails} + if self.env.user.has_group('base.group_portal'): + template = template.sudo() + email_values['email_from'] = self.env.company.catchall_formatted or self.env.company.email_formatted + + mail_ids.append(template.send_mail(record.id, email_layout_xmlid='mail.mail_notification_light', email_values=email_values)) + return mail_ids + def action_view_slides(self): action = self.env["ir.actions.actions"]._for_xml_id("website_slides.slide_slide_action") action['context'] = { diff --git a/addons/website_slides/models/slide_slide.py b/addons/website_slides/models/slide_slide.py index 31fa287f9c0..ac3bc91b808 100644 --- a/addons/website_slides/models/slide_slide.py +++ b/addons/website_slides/models/slide_slide.py @@ -804,7 +804,7 @@ class Slide(models.Model): # TDE FIXME: template to check mail_ids = [] for record in self: - template = record.channel_id.share_template_id.with_context( + template = record.channel_id.share_slide_template_id.with_context( user=self.env.user, email=email, base_url=record.get_base_url(), diff --git a/addons/website_slides/static/src/js/slides_course_fullscreen_player.js b/addons/website_slides/static/src/js/slides_course_fullscreen_player.js index 32433f09a60..8f33cb85dac 100644 --- a/addons/website_slides/static/src/js/slides_course_fullscreen_player.js +++ b/addons/website_slides/static/src/js/slides_course_fullscreen_player.js @@ -368,12 +368,13 @@ 'click .o_wslides_js_share_email button': '_onShareByEmailClick', 'click a.o_wslides_js_social_share': '_onSlidesSocialShare', 'click .o_clipboard_button': '_onShareLinkCopy', + 'keypress .o_wslides_js_share_email input': '_onKeypress', }, init: function (parent, options, slide) { options = _.defaults(options || {}, { - title: "Share", - buttons: [{text: "Cancel", close: true}], + title: _t("Share This Content"), + buttons: [{text: "Close", close: true}], size: 'medium', }); this._super(parent, options); @@ -381,23 +382,48 @@ this.session = session; }, - _onShareByEmailClick: function() { + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * Send the email(s) on 'Enter' key + * + * @private + * @param {Event} ev + */ + _onKeypress: function (ev) { + if (ev.keyCode === $.ui.keyCode.ENTER) { + ev.preventDefault(); + this._onShareByEmailClick(); + } + }, + + _onShareByEmailClick: function () { var form = this.$('.o_wslides_js_share_email'); var input = form.find('input'); - var slideID = form.find('button').data('slide-id'); - if (input.val() && input[0].checkValidity()) { + if (input.val()) { form.removeClass('o_has_error').find('.form-control, .form-select').removeClass('is-invalid'); + var slideID = form.find('button').data('slide-id'); this._rpc({ route: '/slides/slide/send_share_email', params: { slide_id: slideID, - email: input.val(), + emails: input.val(), fullscreen: true }, - }).then(function () { - form.html(''); + }).then((action) => { + if (action) { + form.find('.alert-info').removeClass('d-none'); + form.find('.input-group').addClass('d-none'); + } else { + this.displayNotification({ message: _t('Please enter valid email(s)'), type: 'danger' }); + form.addClass('o_has_error').find('.form-control, .form-select').addClass('is-invalid'); + input.focus(); + } }); } else { + this.displayNotification({ message: _t('Please enter valid email(s)'), type: 'danger' }); form.addClass('o_has_error').find('.form-control, .form-select').addClass('is-invalid'); input.focus(); } @@ -405,6 +431,7 @@ _onSlidesSocialShare: function (ev) { ev.preventDefault(); + ev.stopPropagation(); var popUpURL = $(ev.currentTarget).attr('href'); window.open(popUpURL, 'Share Dialog', 'width=626,height=436'); }, @@ -506,6 +533,10 @@ var self = this; this.on('change:slide', this, this._onChangeSlide); this._toggleSidebar(); + const backendNavEl = document.querySelector('.o_frontend_to_backend_nav'); + if (backendNavEl) { + backendNavEl.remove(); + } return this._super.apply(this, arguments).then(function () { return self._onChangeSlide(); // trigger manually once DOM ready, since slide content is not rendered server side }); diff --git a/addons/website_slides/static/src/js/slides_embed.js b/addons/website_slides/static/src/js/slides_embed.js index 52356ce5d56..0083884a95b 100644 --- a/addons/website_slides/static/src/js/slides_embed.js +++ b/addons/website_slides/static/src/js/slides_embed.js @@ -227,22 +227,29 @@ $(function () { var widget = $('.oe_slide_js_share_email'); var input = widget.find('input'); var slideID = widget.find('button').data('slide-id'); - if (input.val() && input[0].checkValidity()) { + if (input.val()) { widget.removeClass('o_has_error').find('.form-control, .form-select').removeClass('is-invalid'); $.ajax({ type: "POST", dataType: 'json', url: '/slides/slide/send_share_email', contentType: "application/json; charset=utf-8", - data: JSON.stringify({'jsonrpc': "2.0", 'method': "call", "params": {'slide_id': slideID, 'email': input.val()}}), - success: function () { - widget.html($('')); - }, - error: function (data) { - console.error("ERROR ", data); + data: JSON.stringify({'jsonrpc': "2.0", 'method': "call", "params": {'slide_id': slideID, 'emails': input.val()}}), + success: function (action) { + if (action.result) { + widget.find('.alert-info').removeClass('d-none'); + widget.find('.input-group').addClass('d-none'); + } else { + widget.find('.alert-warning').removeClass('d-none'); + widget.find('.input-group').addClass('d-none'); + widget.addClass('o_has_error').find('.form-control, .form-select').addClass('is-invalid'); + input.focus(); + } }, }); } else { + widget.find('.alert-warning').removeClass('d-none'); + widget.find('.input-group').addClass('d-none'); widget.addClass('o_has_error').find('.form-control, .form-select').addClass('is-invalid'); input.focus(); } diff --git a/addons/website_slides/static/src/js/slides_share.js b/addons/website_slides/static/src/js/slides_share.js index e5f8966dc0a..80ca5361e07 100644 --- a/addons/website_slides/static/src/js/slides_share.js +++ b/addons/website_slides/static/src/js/slides_share.js @@ -7,31 +7,61 @@ import { _t } from 'web.core'; var ShareMail = publicWidget.Widget.extend({ events: { 'click button': '_sendMail', + 'keypress input': '_onKeypress', }, //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- + /** + * Send the email(s) on 'Enter' key + * + * @private + * @param {Event} ev + */ + _onKeypress: function (ev) { + if (ev.keyCode === $.ui.keyCode.ENTER) { + ev.preventDefault(); + this._sendMail(); + } + }, + /** * @private */ _sendMail: function () { - var self = this; - var input = this.$('input'); - var slideID = this.$('button').data('slide-id'); - if (input.val() && input[0].checkValidity()) { + const input = this.$('input'); + if (input.val()) { + const slideID = this.$('button').data('slide-id'); + const channelID = this.$('button').data('channel-id'); + const params = { + emails: input.val(), + }; + let route; + if (slideID) { + route = '/slides/slide/send_share_email'; + params.slide_id = slideID; + } else if (channelID) { + route = '/slides/channel/send_share_email'; + params.channel_id = channelID; + } this.$el.removeClass('o_has_error').find('.form-control, .form-select').removeClass('is-invalid'); this._rpc({ - route: '/slides/slide/send_share_email', - params: { - slide_id: slideID, - email: input.val(), - }, - }).then(function () { - self.$el.html($('')); + route, + params + }).then((action) => { + if (action) { + this.$('.alert-info').removeClass('d-none'); + this.$('.input-group').addClass('d-none'); + } else { + this.displayNotification({ message: _t('Please enter valid email(s)'), type: 'danger' }); + this.$el.addClass('o_has_error').find('.form-control, .form-select').addClass('is-invalid'); + input.focus(); + } }); } else { + this.displayNotification({ message: _t('Please enter valid email(s)'), type: 'danger' }); this.$el.addClass('o_has_error').find('.form-control, .form-select').addClass('is-invalid'); input.focus(); } @@ -66,6 +96,7 @@ publicWidget.registry.websiteSlidesShare = publicWidget.Widget.extend({ */ _onSlidesSocialShare: function (ev) { ev.preventDefault(); + ev.stopPropagation(); var popUpURL = $(ev.currentTarget).attr('href'); var popUp = window.open(popUpURL, 'Share Dialog', 'width=626,height=436'); $(window).on('focus', function () { @@ -100,3 +131,34 @@ publicWidget.registry.websiteSlidesShare = publicWidget.Widget.extend({ }) }, }); + +publicWidget.registry.websiteSlidesEmbedShare = publicWidget.Widget.extend({ + selector: '.oe_slide_js_embed_code_widget', + events: { + 'click .o_embed_clipboard_button': '_onShareLinkCopy', + }, + + _onShareLinkCopy: function (ev) { + ev.preventDefault(); + const $clipboardBtn = $(ev.currentTarget); + $clipboardBtn.tooltip({title: "Copied !", trigger: "manual", placement: "bottom"}); + const clipboard = new ClipboardJS('#' + $clipboardBtn[0].id, { + target: () => { + const share_embed_el = this.$('#wslides_share_embed_id_' + $clipboardBtn[0].id.split('id_')[1]); + return share_embed_el[0]; + }, + container: this.el + }); + clipboard.on('success', function () { + clipboard.destroy(); + $clipboardBtn.tooltip('show'); + _.delay(function () { + $clipboardBtn.tooltip("hide"); + }, 800); + }); + clipboard.on('error', function (e) { + console.log(e); + clipboard.destroy(); + }) + }, +}); diff --git a/addons/website_slides/static/src/scss/slides_slide_fullscreen.scss b/addons/website_slides/static/src/scss/slides_slide_fullscreen.scss index 689e0bf33d7..4fb858435f8 100644 --- a/addons/website_slides/static/src/scss/slides_slide_fullscreen.scss +++ b/addons/website_slides/static/src/scss/slides_slide_fullscreen.scss @@ -80,7 +80,6 @@ .o_wslides_fs_sidebar_section_slides li { color: rgba(white, 0.8); - line-height: 1.3; &.active { box-shadow: inset 2px 0 0 map-get($theme-colors, 'primary'); @@ -91,8 +90,20 @@ } } - .o_wslides_fs_slide_name { - line-height: 1; + .o_wslides_sidebar_content { + line-height: unset; + i { + line-height: unset; + } + } + + .o_wslides_sidebar_done_button { + line-height: unset; + + button { + padding-top: 0; + border-top: 0; + } } } } diff --git a/addons/website_slides/static/src/scss/website_slides.scss b/addons/website_slides/static/src/scss/website_slides.scss index b0093963dc7..4dff653aa63 100644 --- a/addons/website_slides/static/src/scss/website_slides.scss +++ b/addons/website_slides/static/src/scss/website_slides.scss @@ -536,14 +536,17 @@ $line-height-truncate: 1.25em; .o_wslides_lesson_aside_list_link { @include o-hover-text-color($gray-600, $headings-color ); - .o_wslides_lesson_link_name { - line-height: 1.2; - } - &.active { box-shadow:inset 2px 0 map-get($theme-colors, 'primary'); } } + button { + padding-top: 0; + border-top: 0; + } + i { + line-height: unset; + } .o_wslides_button_complete { margin-left: 5px; } diff --git a/addons/website_slides/static/src/xml/website_slides_share.xml b/addons/website_slides/static/src/xml/website_slides_share.xml index 954868550ae..e82888b9039 100644 --- a/addons/website_slides/static/src/xml/website_slides_share.xml +++ b/addons/website_slides/static/src/xml/website_slides_share.xml @@ -8,42 +8,66 @@
-
-
Share on Social Networks
-
- - - -
-
-
+
Share Link
- -
-
+
+
Share on Social Media
+ +
+
-
Share by mail
+
Share by Email
- -
+
diff --git a/addons/website_slides/static/src/xml/website_slides_sidebar.xml b/addons/website_slides/static/src/xml/website_slides_sidebar.xml index 7b9e9ee0c67..7dca825271e 100644 --- a/addons/website_slides/static/src/xml/website_slides_sidebar.xml +++ b/addons/website_slides/static/src/xml/website_slides_sidebar.xml @@ -12,10 +12,10 @@ -
+
+
diff --git a/addons/website_slides/views/slide_channel_views.xml b/addons/website_slides/views/slide_channel_views.xml index 733139928d0..0a5a95c6d0d 100644 --- a/addons/website_slides/views/slide_channel_views.xml +++ b/addons/website_slides/views/slide_channel_views.xml @@ -109,7 +109,8 @@ - + + diff --git a/addons/website_slides/views/website_slides_templates_course.xml b/addons/website_slides/views/website_slides_templates_course.xml index 22c1456a232..55cd4211215 100644 --- a/addons/website_slides/views/website_slides_templates_course.xml +++ b/addons/website_slides/views/website_slides_templates_course.xml @@ -135,14 +135,6 @@

- -
- -
@@ -169,12 +161,6 @@

- - - - - -
@@ -243,11 +229,6 @@
- - - - - @@ -283,9 +264,14 @@
+ + + Share This Course + +
diff --git a/addons/website_slides/views/website_slides_templates_lesson.xml b/addons/website_slides/views/website_slides_templates_lesson.xml index f86a9d64b4a..6557239d599 100644 --- a/addons/website_slides/views/website_slides_templates_lesson.xml +++ b/addons/website_slides/views/website_slides_templates_lesson.xml @@ -113,7 +113,7 @@ - - - - - - diff --git a/addons/website_slides/views/website_slides_templates_lesson_embed.xml b/addons/website_slides/views/website_slides_templates_lesson_embed.xml index 55c47fa46bf..4b16990e068 100644 --- a/addons/website_slides/views/website_slides_templates_lesson_embed.xml +++ b/addons/website_slides/views/website_slides_templates_lesson_embed.xml @@ -28,7 +28,7 @@
- + Share diff --git a/addons/website_slides/views/website_slides_templates_lesson_fullscreen.xml b/addons/website_slides/views/website_slides_templates_lesson_fullscreen.xml index 4f57bda0dc8..63341c1d1c9 100644 --- a/addons/website_slides/views/website_slides_templates_lesson_fullscreen.xml +++ b/addons/website_slides/views/website_slides_templates_lesson_fullscreen.xml @@ -36,13 +36,15 @@ + - - Share -
+ + + Share + Exit Fullscreen @@ -106,7 +108,7 @@ -
  • -
    +
    -
    +
    -
    +
    -
      +
      • diff --git a/addons/website_slides/views/website_slides_templates_utils.xml b/addons/website_slides/views/website_slides_templates_utils.xml index a7efdcea705..bae724f3cfa 100644 --- a/addons/website_slides/views/website_slides_templates_utils.xml +++ b/addons/website_slides/views/website_slides_templates_utils.xml @@ -8,24 +8,84 @@ t-value="record.website_share_url if 'website_share_url' in record else record.website_url"/> + + + + + + +