[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
This commit is contained in:
Nikunj Ladava
2022-09-02 14:17:40 +02:00
committed by Aurélien Warnon
parent 291e99b7f7
commit 7d66dcbf54
20 changed files with 392 additions and 206 deletions
+14 -3
View File
@@ -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
@@ -95,6 +95,39 @@
<field name="lang">{{ object.partner_id.lang }}</field>
</record>
<record id="mail_template_channel_shared" model="mail.template">
<field name="name">Channel Shared</field>
<field name="model_id" ref="model_slide_channel"/>
<field name="subject">{{ user.name }} shared a Course</field>
<field name="email_from">{{ user.email_formatted }}</field>
<field name="body_html" type="html">
<div style="margin: 0px; padding: 0px;">
<p style="margin: 0px; padding: 0px; font-size: 13px;">
Hello<br/><br/>
<t t-out="user.name or ''">Mitchell Admin</t> shared the <strong t-out="object.name or ''">document</strong> with you!
<div style="margin: 16px 8px 16px 8px; text-align: center;">
<a t-att-href="object.website_url">
<img t-att-alt="object.name"
t-attf-src="{{ ctx.get('base_url') }}/web/image/slide.channel/{{ object.id }}/image_256"
style="height:auto; width:150px; margin: 16px;"/>
</a>
</div>
<div style="padding: 16px 8px 16px 8px; text-align: center;">
<a t-att-href="object.website_url"
style="background-color: #875a7b; padding: 8px 16px 8px 16px;
text-decoration: none; color: #fff; border-radius: 5px;">
View <strong t-out="object.name or ''">Document</strong></a>
</div>
<t t-if="user.signature">
<br />
<t t-out="user.signature or ''">--<br/>Mitchell Admin</t>
</t>
</p>
</div>
</field>
<field name="auto_delete" eval="True"/>
</record>
<!-- Slide channel invite feature -->
<record id="mail_template_slide_channel_invite" model="mail.template">
<field name="name">Channel: Invite by email</field>
+22 -1
View File
@@ -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'] = {
+1 -1
View File
@@ -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(),
@@ -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('<div class="alert alert-info" role="alert">' + _t('<strong>Thank you!</strong> Mail has been sent.') + '</div>');
}).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
});
@@ -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($('<div class="alert alert-info" role="alert"><strong>Thank you!</strong> Mail has been sent.</div>'));
},
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();
}
@@ -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($('<div class="alert alert-info" role="alert">' + _t('<strong>Thank you!</strong> Mail has been sent.') + '</div>'));
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();
})
},
});
@@ -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;
}
}
}
}
@@ -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;
}
@@ -8,42 +8,66 @@
<t t-name="website.slide.share.socialmedia">
<div class="row">
<div class="col-12 col-lg-6 mb-4">
<h5 class="mt-0 mb-2">Share on Social Networks</h5>
<div class="btn-group" role="group">
<a t-attf-href="https://www.facebook.com/sharer/sharer.php?u=#{widget.slide.websiteShareUrl}" class="btn border bg-white o_wslides_js_social_share" social-key="facebook" aria-label="Share on Facebook" title="Share on Facebook"><i class="fa fa-facebook-square fa-fw"/></a>
<a t-attf-href="https://twitter.com/intent/tweet?text=#{widget.slide.name}&amp;url=#{widget.slide.websiteShareUrl}" class="btn border bg-white o_wslides_js_social_share" social-key="twitter" aria-label="Share on Twitter" title="Share on Twitter"><i class="fa fa-twitter fa-fw"/></a>
<a t-attf-href="http://www.linkedin.com/sharing/share-offsite/?url=#{widget.slide.websiteShareUrl}" social-key="linkedin" class="btn border bg-white o_wslides_js_social_share" aria-label="Share on LinkedIn" title="Share on LinkedIn"><i class="fa fa-linkedin fa-fw"/></a>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="col-12">
<h5 class="mt-0 mb-2">Share Link</h5>
<div class="input-group">
<input type="text" class="form-control o_wslides_js_share_link" t-att-value="widget.slide.websiteShareUrl" readonly="readonly" onClick="this.select();" />
<button class="btn btn-sm btn-primary o_clipboard_button" style="border-top-end-radius: 4px;border-bottom-end-radius: 4px;" >
<span class="fa fa-clipboard"> Copy Link</span>
<input type="text" class="form-control o_wslides_js_share_link text-center" t-att-value="widget.slide.websiteShareUrl" readonly="readonly" onClick="this.select();" />
<button class="btn btn-sm btn-primary o_clipboard_button" >
<i class="fa fa-clipboard"/> Copy Link
</button>
</div>
</div>
<div t-attf-class="col-12 col-lg-6">
<div class="col-12 mt-4">
<h5 class="mt-0 mb-2">Share on Social Media</h5>
<div class="btn-group" role="group">
<div class="s_share">
<a t-attf-href="https://www.facebook.com/sharer/sharer.php?u=#{widget.slide.websiteShareUrl}"
class="btn border bg-white o_wslides_js_social_share" social-key="facebook"
aria-label="Share on Facebook" title="Share on Facebook">
<i class="fa fa-facebook-square fa-fw"/>
</a>
<a t-attf-href="https://twitter.com/intent/tweet?text=#{widget.slide.name}&amp;url=#{widget.slide.websiteShareUrl}"
class="btn border bg-white o_wslides_js_social_share" social-key="twitter"
aria-label="Share on Twitter" title="Share on Twitter">
<i class="fa fa-twitter fa-fw"/>
</a>
<a t-attf-href="http://www.linkedin.com/sharing/share-offsite/?url=#{widget.slide.websiteShareUrl}"
class="btn border bg-white o_wslides_js_social_share" social-key="linkedin"
aria-label="Share on LinkedIn" title="Share on LinkedIn">
<i class="fa fa-linkedin fa-fw"/>
</a>
<a t-attf-href="https://wa.me/?text=#{window.location.href}" social-key="whatsapp"
class="btn border bg-white o_wslides_js_social_share"
aria-label="Share on Whatsapp" title="Share on Whatsapp">
<i class="fa fa-whatsapp fa-fw"/>
</a>
<a t-attf-href="http://pinterest.com/pin/create/button/?url=#{window.location.href}" social-key="pinterest"
class=" btn border bg-white o_wslides_js_social_share"
aria-label="Share on Pinterest" title="Share on Pinterest">
<i class="fa fa-pinterest fa-fw"/>
</a>
</div>
</div>
</div>
<div class="col-12">
<t t-call="website.slide.share.email"/>
</div>
</div>
</t>
<t t-name="website.slide.share.email">
<h5 class="mt-4">Share by mail</h5>
<h5 class="mt-4">Share by Email</h5>
<div t-if="!widget.session.is_website_user">
<form class="form-group o_wslides_js_share_email" role="form">
<div class="input-group">
<input type="email" class="form-control" placeholder="your-friend@domain.com"/>
<button class="btn btn-sm btn-primary" type="button"
<input type="text" class="form-control" placeholder="friend1@email.com, friend2@email.com"/>
<button class="btn btn-primary" type="button"
data-loading-text="Sending..."
t-attf-data-slide-id="#{widget.slide.id}"
style="border-top-end-radius: 4px;border-bottom-end-radius: 4px;">
t-attf-data-slide-id="#{widget.slide.id}">
<i class="fa fa-envelope-o"/> Send Email
</button>
</div>
<div class="alert alert-info d-none" role="alert"><strong>Sharing is caring!</strong> Email(s) sent. </div>
</form>
</div>
<div t-if="widget.session.is_website_user" class="alert alert-info d-inline-block">
@@ -12,10 +12,10 @@
<i t-if="slideCompleted" class="o_wslides_slide_completed fa fa-check-circle fa-fw text-success fa-lg" t-att-data-slide-id="slideId" title="Mark as not done"/>
<i t-if="!slideCompleted" class="fa fa-circle-thin fa-fw fa-lg" t-att-data-slide-id="slideId" title="Mark as done"/>
</button>
<div class="o_wslides_button_complete btn-sm" t-else="">
<button class="o_wslides_button_complete btn btn-sm" t-else="" disabled="1">
<i t-if="slideCompleted" class="o_wslides_slide_completed fa fa-check fa-fw text-success fa-lg" t-att-data-slide-id="slideId" title="Can not be marked as not done"/>
<i t-if="!slideCompleted" class="fa fa-circle-thin fa-fw fa-lg" t-att-data-slide-id="slideId" title="Can not be marked as done"/>
</div>
</button>
</div>
</t>
</templates>
@@ -109,7 +109,8 @@
<group>
<group name="communication" string="Communication">
<field string="Allow Reviews" name="allow_comment"/>
<field name="share_template_id" domain="[('model','=','slide.slide')]" groups="base.group_no_one"/>
<field name="share_slide_template_id" domain="[('model','=','slide.slide')]" groups="base.group_no_one"/>
<field name="share_channel_template_id" domain="[('model', '=', 'slide.channel')]" groups="base.group_no_one"/>
<field name="publish_template_id" placeholder="No Notification"/>
<field name="completed_template_id" placeholder="No Notification"/>
</group>
@@ -135,14 +135,6 @@
<div class="d-flex flex-column">
<h1 t-field="channel.name"/>
<div class="mb-0 mb-xl-3" t-field="channel.description"/>
<div t-if="channel.channel_type == 'documentation'" class="d-flex mb-md-5">
<button role="button" class="btn text-white ps-0" title="Share Channel"
aria-label="Share Channel"
data-bs-toggle="modal" t-att-data-bs-target="'#slideChannelShareModal_%s' % channel.id">
<i class="fa fa-share-square"></i> Share
</button>
</div>
</div>
<div class="d-flex flex-column justify-content-center h5 flex-grow-1 mb-md-5 o_not_editable" t-if="channel.allow_comment">
<t t-call="portal_rating.rating_stars_static_popup_composer">
@@ -169,12 +161,6 @@
</div>
</div>
<!-- Share modal : here to avoid having text-white from o_wslides_course_header -->
<t t-call="website_slides.slide_share_modal" t-if="channel.channel_type == 'documentation'">
<t t-set="record" t-value="channel"/>
<t t-set="website_share_url" t-value="channel.website_url"/>
</t>
<div class="o_wslides_course_main">
<t t-set="channel_frontend_tags" t-value="channel.tag_ids.filtered(lambda tag: tag.color)"/>
<div class="container mb-5">
@@ -243,11 +229,6 @@
</div>
</div>
</div>
<t t-call="website_slides.slide_share_modal">
<t t-set="record" t-value="channel"/>
<t t-set="website_share_url" t-value="channel.website_url"/>
</t>
</t>
</template>
@@ -283,9 +264,14 @@
<div class="mt-3 d-grid o_not_editable">
<button role="button" class="btn btn-link" title="Share Channel"
aria-label="Share Channel"
data-bs-toggle="modal" t-att-data-bs-target="'#slideChannelShareModal_%s' % channel.id">
<i class="fa fa-share-square fa-fw"/> Share
data-bs-toggle="modal" t-att-data-bs-target="'#slideShareModal_%s' % channel.id">
<i class="fa fa-share-alt"/> Share
</button>
<t t-call="website_slides.slide_share_modal">
<t t-set="record" t-value="channel"/>
<t t-set="slide_share_modal_title">Share This Course</t>
<t t-set="website_share_url" t-value="channel.website_url"/>
</t>
</div>
</div>
</div>
@@ -113,7 +113,7 @@
<!-- Slide: sidebar training mode -->
<template id="slide_aside_training" name="Slide: Sidebar in Training">
<div class="o_wslides_lesson_aside_list position-relative bg-white border-bottom mt-4">
<div class="bg-100 text-600 h6 my-0 text-decoration-none border-bottom d-flex align-items-center justify-content-between">
<div class="bg-100 text-1000 h6 my-0 text-decoration-none border-bottom d-flex align-items-center justify-content-between">
<span class="p-2">Course content</span>
<a href="#collapse_slide_aside" data-bs-toggle="collapse" class="d-lg-none p-2 text-decoration-none o_wslides_lesson_aside_collapse">
<i class="fa fa-chevron-down d-lg-none"/>
@@ -150,7 +150,7 @@
<t t-set="slide_completed" t-value="channel_progress[aside_slide.id].get('completed')"/>
<t t-set="can_access" t-value="aside_slide.is_preview or can_access_channel"/>
<li class="p-0 pb-1">
<div t-att-class="'o_wslides_lesson_aside_list_link d-flex align-items-center p-1 %s%s' % (('bg-100 active' if aside_slide == slide else ''), 'text-muted' if not can_access else '')"
<div t-att-class="'o_wslides_lesson_aside_list_link d-flex p-1 %s%s' % (('bg-100 active' if aside_slide == slide else ''), 'text-muted' if not can_access else '')"
t-att-data-id="slide.id"
t-att-data-completed="slide_completed">
<t t-call="website_slides.slide_sidebar_done_button">
@@ -158,21 +158,21 @@
<t t-set="slide_completed" t-value="channel_progress[aside_slide.id].get('completed')"/>
</t>
<a t-att-href="'/slides/slide/%s' % (slug(aside_slide)) if can_access else '#'"
t-attf-class="d-flex align-items-center text-decoration-none mw-100 overflow-hidden ms-2 #{'text-muted' if not can_access else ''}">
t-attf-class="d-flex text-decoration-none mw-100 overflow-hidden ms-2 #{'text-muted' if not can_access else ''}">
<t t-call="website_slides.slide_icon">
<t t-set="slide" t-value="aside_slide"/>
</t>
<div class="o_wslides_lesson_link_name text-truncate" t-att-title="aside_slide.name">
<span t-esc="aside_slide.name" class="me-2 text-truncate"/>
</div>
<div class="ms-auto" t-if="aside_slide.question_ids">
<span t-att-class="'badge rounded-pill %s' % ('text-bg-success' if channel_progress[aside_slide.id].get('completed') else 'text-bg-light text-600')">
<t t-esc="channel_progress[aside_slide.id].get('quiz_karma_won') if channel_progress[aside_slide.id].get('completed') else channel_progress[aside_slide.id].get('quiz_karma_gain')"/> xp
<div class="o_wslides_lesson_link_name" t-att-title="aside_slide.name">
<span t-esc="aside_slide.name"/>
<span class="align-items-end" t-if="aside_slide.question_ids">
<span t-att-class="'badge rounded-pill %s' % ('text-bg-success' if channel_progress[aside_slide.id].get('completed') else 'text-bg-light text-600')">
<t t-esc="channel_progress[aside_slide.id].get('quiz_karma_won') if channel_progress[aside_slide.id].get('completed') else channel_progress[aside_slide.id].get('quiz_karma_gain')"/> xp
</span>
</span>
</div>
</a>
</div>
<ul t-if="aside_slide._has_additional_resources() or aside_slide.question_ids" class="list-group px-2 mb-1 list-unstyled">
<ul t-if="aside_slide._has_additional_resources() or aside_slide.question_ids" class="list-group ps-5 mb-1 list-unstyled">
<t t-if="can_access_channel">
<t t-foreach="aside_slide.slide_resource_ids" t-as="resource" t-if="resource.resource_type == 'url'">
<li class="ps-4">
@@ -215,7 +215,7 @@
<!-- Slide: all its content, not fullscreen mode -->
<template id="slide_content_detailed" name="Slide: Detailed Content">
<div class="row align-items-start my-3 w-100">
<div class="d-flex align-items-start my-3 w-100">
<t t-set="slide_completed" t-value="channel_progress[slide.id].get('completed')"/>
<div class="col-12 col-md order-2 order-md-1 d-flex">
<div class="d-flex align-items-start">
@@ -283,6 +283,11 @@
<i class="fa fa-desktop me-2"/>
<span class="d-none d-sm-inline-block">Fullscreen</span>
</a>
<a class="btn btn-light border ms-2" role="button" data-bs-toggle="modal"
t-att-data-bs-target="'#slideShareModal_%s' % slide.id">
<i class="fa fa-share-alt me-2"/>
<span class="d-none d-sm-inline-block">Share</span>
</a>
</div>
</div>
<div t-if="slide.tag_ids" class="pb-2">
@@ -326,11 +331,6 @@
<i class="fa fa-bar-chart"></i> Statistics
</a>
</li>
<li class="nav-item">
<a href="#share" aria-controls="share" class="nav-link" role="tab" data-bs-toggle="tab">
<i class="fa fa-share-alt"></i> Share
</a>
</li>
</ul>
<div class="tab-content mt-3">
<div role="tabpanel" t-att-class="not comments and 'tab-pane fade in show active' or 'tab-pane fade'" id="about">
@@ -406,40 +406,6 @@
</div>
</div>
</div>
<div role="tabpanel" class="tab-pane fade" t-if="slide.website_published" id="share">
<h4 t-if="not slide.website_published"><i class="fa fa-info-circle"></i>
The social sharing module will be unlocked when a moderator will allow your publication.
</h4>
<t t-if="slide.website_published">
<div class="row">
<div class="col-12 col-lg-6">
<h5 class="mt16">Share on Social Networks</h5>
<t t-call="website_slides.slide_share_social">
<t t-set="record" t-value="slide"/>
<t t-set="website_share_url" t-value="slide.website_share_url"/>
</t>
</div>
<div class="col-12 col-lg-6">
<t t-call="website_slides.slide_share_link">
<t t-set="record" t-value="slide"/>
<t t-set="website_share_url" t-value="slide.website_share_url"/>
</t>
</div>
</div>
<div class="row">
<div t-attf-class="col-12 #{'col-lg-6' if slide.embed_code else ''}">
<t t-call="website_slides.slide_social_email">
<t t-set="slide" t-value="slide"/>
</t>
</div>
<div class="col-12 col-lg-6" t-if="slide.embed_code">
<t t-call="website_slides.slide_social_embed">
<t t-set="slide" t-value="slide"/>
</t>
</div>
</div>
</t>
</div>
</div>
</div>
<div class="o_wslides_js_quiz_container" t-att-data-slide-id="slide.id" id="quiz_container">
@@ -496,6 +462,11 @@
</t>
</t>
</div>
<t t-call="website_slides.slide_share_modal">
<t t-set="record" t-value="slide"/>
<t t-set="include_embed" t-value="bool(slide.embed_code)"/>
<t t-set="website_share_url" t-value="slide.website_share_url"/>
</t>
</template>
<!-- Slide sub-tempalte: render a quiz serverside. Should be sync with JS qweb template "slide.slide.quiz" -->
@@ -585,45 +556,4 @@
</div>
</template>
<!-- Slide sub-template: share: send by email -->
<template id='slide_social_email' name="Share by Email">
<h5 class="mt-4">Share by mail</h5>
<div t-if="not is_public_user" class="d-flex">
<form class="form-group oe_slide_js_share_email" role="form">
<div class="input-group">
<input type="email" class="form-control" placeholder="your-friend@domain.com"/>
<button class="btn btn-primary" type="button"
data-loading-text="Sending..."
t-attf-data-slide-id="#{slide.id}"
style="border-top-end-radius: 4px;border-bottom-end-radius: 4px;">
<i class="fa fa-envelope"/> Send Email
</button>
</div>
<span class="form-text text-muted d-block w-100">Send presentation through email</span>
</form>
</div>
<div t-if="is_public_user" class="alert alert-info d-inline-block">
<p class="mb-0">Please <a t-attf-href="/web?redirect=#{request.httprequest.url}" class="fw-bold"> login </a> to share this <t t-esc="slide.slide_category"/> by email.</p>
</div>
</template>
<!-- Slide sub-template: share: embed in your website -->
<template id="slide_social_embed" name="Share on Your Website">
<div class="oe_slide_js_embed_code_widget mt-4">
<h5 class="mt0">Embed in your website</h5>
<div class="form-group">
<textarea class="form-control slide_embed_code" readonly="readonly" onClick="this.select();">
<!-- Use the external embedding URL here, see python controller '_slide_embed' method for more details. -->
<t t-esc="slide.embed_code_external"/>
</textarea>
</div>
<div t-if="slide.slide_category == 'document' and not embed_hide_starting_page" class="form-group d-flex">
<div class="form-text p-0 col-xs-5 col-sm-5 col-md-5 col-lg-5">Select page to start with</div>
<div class="input-group col-xs-3 col-sm-2 col-md-2 col-lg-3">
<input type="number" value="1" class="form-control"/>
</div>
</div>
</div>
</template>
</data></odoo>
@@ -28,7 +28,7 @@
</div>
</div>
<div class="col flex-grow-0 d-flex text-nowrap small">
<a class="oe_slide_js_embed_option_link" href="#" data-bs-toggle="modal" t-attf-data-bs-target="#slideChannelShareModal_{{slide.id}}">
<a class="oe_slide_js_embed_option_link" href="#" data-bs-toggle="modal" t-attf-data-bs-target="#slideShareModal_{{slide.id}}">
<i class="fa fa-share-alt" aria-label="Share" title="Share"/>
Share
</a>
@@ -36,13 +36,15 @@
<t t-set="icon" t-value="'fa fa-pencil'"/>
<t t-set="_text_classes" t-value="'d-none d-md-inline-block'"/>
<t t-set="hide_rating_avg" t-value="True"/>
<t t-set="is_fullscreen" t-value="True"/>
</t>
</a>
<a class="o_wslides_fs_share d-flex align-items-center px-3" href="#" title="Share">
<i class="fa fa-share-alt"/><span class="d-none d-md-inline-block ms-1">Share</span>
</a>
</div>
<div class="d-flex ms-auto">
<a class="o_wslides_fs_share d-flex align-items-center px-3" href="#" title="Share">
<i class="fa fa-share-alt"/>
<span class="d-none d-md-inline-block ms-2">Share</span>
</a>
<a class="d-flex align-items-center px-3 o_wslides_fs_exit_fullscreen" t-attf-href="/slides/slide/#{slug(slide)}">
<i class="fa fa-sign-out"/><span class="d-none d-md-inline-block ms-1">Exit Fullscreen</span>
</a>
@@ -106,7 +108,7 @@
<t t-foreach="slides" t-as="slide">
<t t-set="slide_completed" t-value="channel_progress[slide.id].get('completed')"/>
<t t-set="can_access" t-value="can_access_channel or slide.is_preview"/>
<li t-attf-class="o_wslides_fs_sidebar_list_item d-flex align-items-center py-1 #{'active' if slide.id == current_slide.id else ''}"
<li t-attf-class="o_wslides_fs_sidebar_list_item d-flex py-1 #{'active' if slide.id == current_slide.id else ''}"
t-att-data-id="slide.id"
t-att-data-can-access="can_access"
t-att-data-name="slide.name"
@@ -123,22 +125,22 @@
t-att-data-session-answers="session_answers"
t-att-data-website-share-url="slide.website_share_url">
<t t-call="website_slides.slide_sidebar_done_button"/>
<div class="ms-2">
<div class="ms-2 o_wslides_sidebar_content">
<a t-if="can_access" class="d-block" href="#">
<div class="d-flex ">
<div class="d-flex">
<t t-set="icon_class" t-value="'me-2'"/>
<t t-call="website_slides.slide_icon"/>
<div class="o_wslides_fs_slide_name" t-esc="slide.name"/>
</div>
</a>
<span t-else="" class="d-block" href="#">
<div class="d-flex ">
<div class="d-flex">
<t t-set="icon_class" t-value="'me-2 text-600'"/>
<t t-call="website_slides.slide_icon"/>
<div class="o_wslides_fs_slide_name text-600" t-esc="slide.name"/>
</div>
</span>
<ul class="list-unstyled w-100 pt-2 small" t-if="slide._has_additional_resources() or (slide.question_ids and not slide.slide_category =='quiz')" >
<ul class="list-unstyled w-100 pt-2 small ps-4" t-if="slide._has_additional_resources() or (slide.question_ids and not slide.slide_category =='quiz')" >
<t t-if="can_access_channel">
<t t-set="links" t-value="slide.slide_resource_ids.filtered(lambda res: res.resource_type == 'url')"/>
<li t-foreach="links" t-as="link" class="ps-0 mb-1">
@@ -8,24 +8,84 @@
t-value="record.website_share_url if 'website_share_url' in record else record.website_url"/>
</t>
<div class="btn-group" role="group">
<a t-attf-href="https://www.facebook.com/sharer/sharer.php?u=#{website_share_url}" class="btn border bg-white o_wslides_js_social_share"
social-key="facebook" aria-label="Share on Facebook" title="Share on Facebook" target="_blank">
<i class="fa fa-facebook-square fa-fw"/>
</a>
<a t-attf-href="https://twitter.com/intent/tweet?text=#{record.name}&amp;url=#{website_share_url}" class="btn border bg-white o_wslides_js_social_share"
social-key="twitter" aria-label="Share on Twitter" title="Share on Twitter" target="_blank">
<i class="fa fa-twitter fa-fw"/>
</a>
<a t-attf-href="http://www.linkedin.com/sharing/share-offsite/?url=#{website_share_url}" class="btn border bg-white o_wslides_js_social_share"
social-key="linkedin" aria-label="Share on LinkedIn" title="Share on LinkedIn" target="_blank">
<i class="fa fa-linkedin fa-fw"/>
</a>
<div class="s_share">
<a t-attf-href="https://www.facebook.com/sharer/sharer.php?u=#{website_share_url}" class="btn border bg-white o_wslides_js_social_share"
social-key="facebook" aria-label="Share on Facebook" title="Share on Facebook" target="_blank">
<i class="fa fa-facebook-square fa-fw"/>
</a>
<a t-attf-href="https://twitter.com/intent/tweet?text=#{record.name}&amp;url=#{website_share_url}" class="btn border bg-white o_wslides_js_social_share"
social-key="twitter" aria-label="Share on Twitter" title="Share on Twitter" target="_blank">
<i class="fa fa-twitter fa-fw"/>
</a>
<a t-attf-href="http://www.linkedin.com/sharing/share-offsite/?url=#{website_share_url}" class="btn border bg-white o_wslides_js_social_share"
social-key="linkedin" aria-label="Share on LinkedIn" title="Share on LinkedIn" target="_blank">
<i class="fa fa-linkedin fa-fw"/>
</a>
<a t-attf-href="https://wa.me/?text=#{website_share_url}" class="btn border bg-white o_wslides_js_social_share"
social-key="whatsapp" aria-label="Share on Whatsapp" title="Share on Whatsapp" target="_blank">
<i class="fa fa-whatsapp fa-fw"/>
</a>
<a t-attf-href="http://pinterest.com/pin/create/button/?url=#{website_share_url}"
social-key="pinterest"
class="btn border bg-white o_wslides_js_social_share"
aria-label="Share on Pinterest" title="Share on Pinterest">
<i class="fa fa-pinterest fa-fw"/>
</a>
</div>
</div>
</template>
<!-- Slide sub-template: share: send by email -->
<template id='slide_social_email' name="Share by Email">
<h5 class="mt-4">Share by Email</h5>
<div t-if="not is_public_user">
<form class="oe_slide_js_share_email" role="form">
<div class="input-group">
<input type="text" class="form-control" placeholder="your-friend@domain.com, your-friend2@domain.com"/>
<button class="btn btn-primary" type="button"
data-loading-text="Sending..."
t-attf-data-slide-id="#{record.id if record._name == 'slide.slide' else False}"
t-attf-data-channel-id="#{record.id if record._name == 'slide.channel' else False}"
style="border-top-end-radius: 4px;border-bottom-end-radius: 4px;">
<i class="fa fa-envelope"/> Send Email
</button>
</div>
<div class="alert alert-info d-none" role="alert">
<strong>Sharing is caring!</strong> Email(s) sent.
</div>
<div class="alert alert-warning d-none" role="alert">Please enter valid email(s)</div>
</form>
</div>
<div t-if="is_public_user" class="alert alert-info d-inline-block">
<p class="mb-0">Please <a t-attf-href="/web?redirect=#{request.httprequest.url}" class="fw-bold"> login </a> to share this
<span t-field="record.slide_category" t-if="record._name == 'slide.slide'"/>
<span t-field="record.name" t-if="record._name == 'slide.channel'"/> by email.</p>
</div>
</template>
<!-- Slide sub-template: share: embed in your website -->
<template id="slide_social_embed" name="Share on Your Website">
<div class="oe_slide_js_embed_code_widget mt-4">
<h5 class="mt0">Embed in another Website</h5>
<div>
<textarea class="form-control slide_embed_code" readonly="readonly" onClick="this.select();" t-attf-id="wslides_share_embed_id_{{record.id}}">
<!-- Use the external embedding URL here, see python controller '_slide_embed' method for more details. -->
<t t-esc="slide.embed_code_external"/>
</textarea>
<button t-att-id="'share_embed_clipboard_button_id_%s' % record.id" class="btn btn-sm btn-primary o_embed_clipboard_button float-end mt-1 p-2" >
<i class="fa fa-clipboard"/> Copy Embed Code
</button>
</div>
<div t-if="slide.slide_category == 'document' and not embed_hide_starting_page" class="input-group mt-5">
<span class="input-group-text">Start at Page</span>
<input type="number" value="1" class="form-control"/>
</div>
</div>
</template>
<!-- Share: social media -->
<template id='slide_share_modal'>
<div class="modal fade" t-att-id="'slideChannelShareModal_%s' % record.id" tabindex="-1" role="dialog" aria-labelledby="slideChannelShareModalLabel" aria-hidden="true">
<div class="modal fade" t-att-id="'slideShareModal_%s' % record.id" tabindex="-1" role="dialog" aria-labelledby="slideShareModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<t t-call="website_slides.slide_share_modal_header"/>
@@ -37,8 +97,9 @@
<template id="slide_share_modal_header">
<div class="modal-header">
<h5 class="modal-title" id="slideChannelShareModalLabel">
Share
<h5 class="modal-title" id="slideShareModalLabel">
<t t-if="slide_share_modal_title" t-out="slide_share_modal_title"/>
<t t-else="">Share This Content</t>
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
@@ -46,9 +107,10 @@
<template id="slide_share_modal_body">
<div class="modal-body">
<h5>Share on Social Networks</h5>
<t t-call="website_slides.slide_share_social"/>
<t t-call="website_slides.slide_share_link"/>
<h5 class="mt-3">Share on Social Media</h5>
<t t-call="website_slides.slide_share_social"/>
<t t-call="website_slides.slide_social_email"/>
<t t-if="include_embed" t-call="website_slides.slide_social_embed"/>
</div>
</template>
@@ -58,12 +120,14 @@
<t t-set="website_share_url"
t-value="record.website_share_url if 'website_share_url' in record else record.website_url"/>
</t>
<h5 class="mt-3">Share Link</h5>
<h5>Share Link</h5>
<div class="input-group">
<input type="text" t-att-id="'wslides_share_link_id_%s' % record.id" class="form-control o_wslides_js_share_link" readonly="readonly" onclick="this.select();"
<input type="text" t-attf-id="wslides_share_link_id_{{record.id}}"
class="form-control o_wslides_js_share_link text-center"
readonly="readonly" onclick="this.select();"
t-att-value="website_share_url"/>
<button t-att-id="'share_link_clipboard_button_id_%s' % record.id" class="btn btn-sm btn-primary o_clipboard_button" >
<span class="fa fa-clipboard"> Copy Text</span>
<i class="fa fa-clipboard"/> Copy Link
</button>
</div>
</template>
@@ -76,7 +140,7 @@
<!-- Python equivalent of the JS template "website.slides.sidebar.done.button" -->
<template id="slide_sidebar_done_button" name="Sidebar Done Button">
<div t-if="is_member"
class="o_wslides_sidebar_done_button"
class="o_wslides_sidebar_done_button align-self-start"
t-att-data-id="slide.id"
t-att-data-completed="slide_completed"
t-att-data-can-self-mark-completed="slide.can_self_mark_completed"
@@ -86,10 +150,10 @@
<i t-if="slide_completed" class="o_wslides_slide_completed fa fa-check-circle fa-fw text-success fa-lg" t-att-data-slide-id="slide.id" title="Mark as not done"/>
<i t-else="" class="fa fa-circle-thin fa-fw fa-lg" t-att-data-slide-id="slide.id" title="Mark as done"/>
</button>
<div class="o_wslides_button_complete btn-sm" t-else="">
<button class="o_wslides_button_complete o_wslides_button_uncompleted btn btn-sm" t-else="" disabled="1">
<i t-if="not slide_completed" class="fa fa-circle-thin fa-fw fa-lg" t-att-data-slide-id="slide.id" title="Can not be marked as done"/>
<i t-else="" class="o_wslides_slide_completed fa fa-check fa-fw text-success fa-lg" t-att-data-slide-id="slide.id" title="Can not be marked as not done"/>
</div>
</button>
</div>
</template>
@@ -4,7 +4,7 @@
<template id="survey_fill_form_done_inherit_website_slides" inherit_id="survey.survey_fill_form_done">
<xpath expr="//div[hasclass('o_survey_finished')]//a[hasclass('btn-primary')][1]" position="after">
<t t-if="channel_id">
<a role="button" class="me-2 mt-2 mt-md-0 btn btn-secondary btn-lg" href="#" data-bs-toggle="modal" t-attf-data-bs-target="#slideChannelShareModal_{{channel_id.id}}">
<a role="button" class="me-2 mt-2 mt-md-0 btn btn-secondary btn-lg" href="#" data-bs-toggle="modal" t-attf-data-bs-target="#slideShareModal_{{channel_id.id}}">
<i class="fa fa-share-alt" aria-label="Share certification" title="Share certification"/>
Share your certification
</a>
@@ -78,7 +78,7 @@
<i class="fa fa-download" aria-label="Download certification" title="Download Certification"/>
</a>
<a role="button" class="me-2" href="#"
t-attf-onclick="event.stopPropagation(); $('#slideChannelShareModal_#{certificate.slide_id.channel_id.id}').modal('show');">
t-attf-onclick="event.stopPropagation(); $('#slideShareModal_#{certificate.slide_id.channel_id.id}').modal('show');">
<i class="fa fa-share-alt" aria-label="Share" title="Share"/>
</a>
</div>
@@ -2,7 +2,7 @@
<odoo>
<template id="slide_sidebar_done_button" inherit_id="website_slides.slide_sidebar_done_button">
<xpath expr="//div[hasclass('o_wslides_button_complete')]/i[1]" position="after">
<xpath expr="//button[hasclass('o_wslides_button_uncompleted')]/i[1]" position="after">
<i t-elif="slide_completed and slide.slide_category == 'certification'"
class="o_wslides_slide_completed fa fa-check fa-fw text-success fa-lg"
t-att-data-slide-id="slide.id"