add modules elearning

This commit is contained in:
XuanHuyen
2025-04-28 16:35:45 +07:00
parent 480b7fdae3
commit 4f8478d1e7
319 changed files with 2910 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import controllers
from . import models
@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
{
'name': 'eLearning Plus',
'version': '18.0.0.2',
'sequence': 10,
'summary': 'Added extra features to enhance learning.',
'website': 'https://www.manprax.com',
'author': 'ManpraX Software LLP',
'category': 'Website/eLearning',
'description': """
Extended feature of elearning.
""",
'depends': [
'website_slides',
],
'data': [
'views/slide_view.xml',
'views/website_slides_templates_course.xml',
],
'assets': {
'web.assets_backend': [
],
'web.assets_frontend': [
'mx_elearning_plus/static/src/js/slides_course.js',
'mx_elearning_plus/static/src/js/slides_course_extend.js',
'mx_elearning_plus/static/src/js/slides_course_rating_fullscreen.js',
'mx_elearning_plus/static/src/js/slide_comment_composer_fullscreen.js',
'mx_elearning_plus/static/src/xml/comment_composer.xml',
'mx_elearning_plus/static/src/xml/comment_button_composer_extend.xml',
],
},
'demo': [],
'qweb': [],
'images': ["static/description/images/app_banner_plus.png"],
'installable': True,
'application': True,
'license': 'AGPL-3',
}
@@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import main
@@ -0,0 +1,73 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import http, tools, _
from odoo.http import request
from odoo.tools import plaintext2html
from odoo.addons.website_slides.controllers.main import WebsiteSlides
class SlideController(http.Controller):
@http.route(['/website/publish/slide'], type='json', auth="user", website=True)
def publish(self, id):
slide_id = request.env['slide.slide'].browse(id)
return bool(slide_id.website_published)
@http.route(['/slides/slide/mx/like'], type='json', auth="public", website=True)
def slide_like_dislike(self, slide_id):
slide = request.env['slide.slide'].browse(slide_id)
return {
'user_vote': slide.user_vote,
'likes': tools.misc.format_decimalized_number(slide.likes),
'dislikes': tools.misc.format_decimalized_number(slide.dislikes),
}
def _portal_post_has_content(self, res_model, res_id, message, attachment_ids=None, **kw):
""" Tells if we can effectively post on the model based on content. """
return bool(message) or bool(attachment_ids)
@http.route(['/mail/slide/comment'], type='json', methods=['POST'], auth='public', website=True)
def portal_chatter_post(self, res_model, res_id, message, attachment_ids=None, attachment_tokens=None, **kw):
"""Create a new `mail.message` with the given `message` and/or `attachment_ids` and return new message values."""
if not self._portal_post_has_content(res_model, res_id, message,
attachment_ids=attachment_ids, attachment_tokens=attachment_tokens,
**kw):
return
res_id = int(res_id)
result = {'default_message': message}
# message is received in plaintext and saved in html
if message:
message = plaintext2html(message)
vals = ({
'email_from': request.env.user.email_formatted,
'author_id': request.env.user.partner_id.id,
'message_type':'comment',
'body':message if message else '',
'subtype_id': 1,
'model':res_model,
'res_id': res_id,
# 'attachment_ids': False,
'record_name': (request.env[str(res_model)].browse(res_id)).name
})
message=request.env['mail.message'].sudo().create(vals)
result.update({'default_message_id': message.id})
if attachment_ids:
# sudo write the attachment to bypass the read access
# verification in mail message
record = request.env[res_model].browse(res_id)
message_values = {'res_id': res_id, 'model': res_model}
attachments = record._message_post_process_attachments([], attachment_ids, message_values)
if attachments.get('attachment_ids'):
message.sudo().write(attachments)
result.update({'default_attachment_ids': message.attachment_ids.sudo().read(['id', 'name', 'mimetype', 'file_size', 'access_token'])})
return result
class WebsiteSlideController(WebsiteSlides):
@http.route('/slides/slide/like', type='json', auth="public", website=True)
def slide_like(self, slide_id, upvote):
res = super(WebsiteSlideController, self).slide_like(slide_id, upvote)
return res
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import slide_slide
@@ -0,0 +1,161 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
import re
import requests
from werkzeug import urls
from markupsafe import Markup
from odoo import models, fields, api, _
_logger = logging.getLogger(__name__)
class Slide(models.Model):
_inherit = 'slide.slide'
# is_hide = fields.Boolean(string="Allow Hide")
def action_publish(self):
if not self.id:
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'type': 'warning',
'message': _("Please save the course to publish it.")
}
}
else:
self.is_published = True
def action_unpublish(self):
self.is_published = False
class Channel(models.Model):
_inherit = 'slide.channel'
description_short = fields.Html('Short Description', help="The description that is displayed on the course card")
description = fields.Html('Description', help="The description that is displayed on top of the course page, just below the title")
YOUTUBE_VIDEO_ID_REGEX_PRO = r'^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*'
GOOGLE_DRIVE_DOCUMENT_ID_REGEX_PRO = r'(^https:\/\/docs.google.com|^https:\/\/drive.google.com).*\/d\/([^\/]*)'
VIMEO_VIDEO_ID_REGEX_PRO = r'\/\/(player.)?vimeo.com\/(?:[a-z]*\/)*([0-9]{6,11})\/?([0-9a-z]{6,11})?[?]?.*'
intro_video_type = fields.Selection([
('youtube_video', 'YouTube Video'),
('google_drive_video', 'Google Drive Video'),
('vimeo_video', 'Vimeo Video')],
string="Slide Type", compute='_compute_intro_video_type', store=True, readonly=False,
help="Subtype of the video category, allows more precision on the actual file type / source type.")
intro_url = fields.Char('External URL', help="URL of the Google Drive file or URL of the YouTube video")
intro_video_url = fields.Char('Introduction Video Link', related='intro_url', readonly=False,
help="Link of the video (we support YouTube, Google Drive and Vimeo as sources)")
intro_video_source_type = fields.Selection([
('youtube', 'YouTube'),
('google_drive', 'Google Drive'),
('vimeo', 'Vimeo')],
string='Video Source', compute="_compute_intro_video_source_type")
video_image_1920 = fields.Image(store=True, readonly=False)
intro_youtube_id = fields.Char('Video YouTube ID', compute='_compute_intro_youtube_id')
intro_vimeo_id = fields.Char('Video Vimeo ID', compute='_compute_intro_vimeo_id')
intro_google_drive_id = fields.Char('Google Drive ID of the external URL', compute='_compute_intro_google_drive_id')
video_embed_code = fields.Html('Embed Code', readonly=True, compute='_compute_video_embed_code', sanitize=False)
@api.depends('intro_video_url', 'intro_google_drive_id', 'intro_video_source_type', 'intro_youtube_id')
def _compute_video_embed_code(self):
for course in self:
video_embed_code = False
if course.intro_video_url :
if course.intro_video_source_type == 'youtube':
query_params = urls.url_parse(course.intro_video_url).query
query_params = query_params + '&theme=light' if query_params else 'theme=light'
video_embed_code = Markup('<iframe src="//www.youtube-nocookie.com/embed/%s?%s" allowFullScreen="true" frameborder="0" style="height:-webkit-fill-available; width:-webkit-fill-available;"></iframe>') % (course.intro_youtube_id, query_params)
elif course.intro_video_source_type == 'google_drive':
video_embed_code = Markup('<iframe src="//drive.google.com/file/d/%s/preview" allowFullScreen="true" frameborder="0" style="height:-webkit-fill-available; width:-webkit-fill-available;"></iframe>') % (course.intro_google_drive_id)
elif course.intro_video_source_type == 'vimeo':
if '/' in course.intro_vimeo_id:
# in case of privacy 'with URL only', vimeo adds a token after the video ID
# the embed url needs to receive that token as a "h" parameter
[vimeo_id, vimeo_token] = course.intro_vimeo_id.split('/')
video_embed_code = Markup("""
<iframe src="https://player.vimeo.com/video/%s?h=%s&badge=0&amp;autopause=0&amp;player_id=0"
frameborder="0" style="height:-webkit-fill-available; width:-webkit-fill-available;" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe>""") % (
vimeo_id, vimeo_token)
else:
video_embed_code = Markup("""
<iframe src="https://player.vimeo.com/video/%s?badge=0&amp;autopause=0&amp;player_id=0"
frameborder="0" style="height:-webkit-fill-available; width:-webkit-fill-available;" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe>""") % (course.intro_vimeo_id)
course.video_embed_code = video_embed_code
@api.depends('intro_video_type', 'intro_video_source_type')
def _compute_intro_video_type(self):
""" For 'local content' or specific slide categories, the slide type is directly derived
from the slide category.
For external content, the slide type is determined from the metadata and the mime_type.
(See #_fetch_google_drive_metadata() for more details)."""
for course in self:
if course.intro_video_url and course.intro_video_source_type == 'youtube':
course.intro_video_type = 'youtube_video'
elif course.intro_video_url and course.intro_video_source_type == 'google_drive':
course.intro_video_type = 'google_drive_video'
elif course.intro_video_url and course.intro_video_source_type == 'vimeo':
course.intro_video_type = 'vimeo_video'
else:
course.intro_video_type = False
@api.depends('intro_video_url')
def _compute_intro_video_source_type(self):
for course in self:
intro_video_source_type = False
youtube_match = re.match(self.YOUTUBE_VIDEO_ID_REGEX_PRO, course.intro_video_url) if course.intro_video_url else False
if youtube_match and len(youtube_match.groups()) == 2 and len(youtube_match.group(2)) == 11:
intro_video_source_type = 'youtube'
if course.intro_video_url and not intro_video_source_type and re.match(self.GOOGLE_DRIVE_DOCUMENT_ID_REGEX_PRO, course.intro_video_url):
intro_video_source_type = 'google_drive'
vimeo_match = re.search(self.VIMEO_VIDEO_ID_REGEX_PRO, course.intro_video_url) if course.intro_video_url else False
if not intro_video_source_type and vimeo_match and len(vimeo_match.groups()) == 3:
intro_video_source_type = 'vimeo'
course.intro_video_source_type = intro_video_source_type
@api.depends('intro_video_url', 'intro_video_source_type')
def _compute_intro_youtube_id(self):
for course in self:
if course.intro_video_url and course.intro_video_source_type == 'youtube':
match = re.match(self.YOUTUBE_VIDEO_ID_REGEX_PRO, course.intro_video_url)
if match and len(match.groups()) == 2 and len(match.group(2)) == 11:
course.intro_youtube_id = match.group(2)
else:
course.intro_youtube_id = False
else:
course.intro_youtube_id = False
@api.depends('intro_video_url', 'intro_video_source_type')
def _compute_intro_vimeo_id(self):
for course in self:
if course.intro_video_url and course.intro_video_source_type == 'vimeo':
match = re.search(self.VIMEO_VIDEO_ID_REGEX_PRO, course.intro_video_url)
if match and len(match.groups()) == 3:
if match.group(3):
# in case of privacy 'with URL only', vimeo adds a token after the video ID
# the share url is then 'vimeo_id/token'
# the token will be captured in the third group of the regex (if any)
course.intro_vimeo_id = '%s/%s' % (match.group(2), match.group(3))
else:
# regular video, we just capture the vimeo_id
course.intro_vimeo_id = match.group(2)
else:
course.intro_vimeo_id = False
@api.depends('intro_video_url')
def _compute_intro_google_drive_id(self):
""" Extracts the Google Drive ID from the url based on the slide category. """
for course in self:
url = course.intro_video_url
intro_google_drive_id = False
if url:
match = re.match(self.GOOGLE_DRIVE_DOCUMENT_ID_REGEX_PRO, url)
if match and len(match.groups()) == 2:
intro_google_drive_id = match.group(2)
course.intro_google_drive_id = intro_google_drive_id
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 562 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 506 KiB

@@ -0,0 +1,211 @@
<div class="row"
style="margin: 0;position: relative;color: #000;background-position: center;background: #ffffff;border-bottom: 1px solid #e4e4e4;text-align: center; margin: auto; display: flex;justify-content: center;">
<a href="https://www.manprax.com/" target="_blank"><img src="images/manprax.png"
style=" width: 293px; padding: 1rem 0rem; margin: auto" alt="manprax-logo"></a>
</div>
<div class="row"
style="margin:75px 0;position: relative;color: #000;background-position: center;background: #ffffff;border-bottom: 1px solid #e4e4e4; padding-bottom: 30px;">
<div class="col-md-7 col-sm-12 col-xs-12" style="padding: 0px">
<div
style=" margin: 0 0 0px;padding: 20px 0 10;font-size: 23px;line-height: 35px;font-weight: 400;color: #000;border-top: 1px solid rgba(255,255,255,0.1);border-bottom: 1px solid rgba(255,255,255,0.11);text-align: left;">
<h1 style="font-size: 39px;font-weight: 600;margin: 0px !important;">Elearning Plus </h1>
</h3>
</div>
<h2 style="font-weight: 600;font-size: 1.8rem;margin-top: 15px;">Key Highlights</h2>
<ul style=" padding: 0 1px; list-style: none; ">
<li style="display: flex;align-items: center;padding: 8px 0;font-size: 18px;"><i class="fa fa-check-circle-o"
style="width:40px; color:#00438b"></i>
Collapse/Expand Feature in course content list.
</li>
<li style="display: flex;align-items: center;padding: 8px 0;font-size: 18px;"><i class="fa fa-check-circle-o"
style="width:40px; color:#00438b"></i>
Fix Bug of Publish/Unpublish Button on course Fullscreen.
</li>
<li style="display: flex;align-items: center;padding: 8px 0;font-size: 18px;"><i class="fa fa-check-circle-o"
style="width:40px; color:#00438b"></i>
Publish slides directly from backend.
</li>
<li style="display: flex;align-items: center;padding: 8px 0;font-size: 18px;"><i class="fa fa-check-circle-o"
style="width:40px; color:#00438b"></i>
Add Description with HTML editor.
</li>
<li style="display: flex;align-items: center;padding: 8px 0;font-size: 18px;"><i class="fa fa-check-circle-o"
style="width:40px; color:#00438b"></i>
Short Introduction video for course.
</li>
<li style="display: flex;align-items: center;padding: 8px 0;font-size: 18px;"><i class="fa fa-check-circle-o"
style="width:40px; color:#00438b"></i>
Like/Dislike option for content in fullscreen.
</li>
<li style="display: flex;align-items: center;padding: 8px 0;font-size: 18px;"><i class="fa fa-check-circle-o"
style="width:40px; color:#00438b"></i>
Comment option for content in fullscreen.
</li>
</ul>
</div>
</div>
<div>
<h2 class="text-center">Screenshots</h2>
<section class="oe_container">
<div>
<div>
<div>
<div class="col-xs-12 col-sm-12 col-md-12 mb16 mt16" style="float: left;">
<h3 class="alert"
style="font-weight:400;color: #091E42;background: #fff;text-align: left;border-radius: 0; font-size: 18px;">
<i class="fa fa-check-circle-o" style="width:40px; color:#00438b"></i>
Collapse/Expand Feature in course content list.
</h3>
<div style="text-align: center;"><img class="img img-responsive center-block"
style="border-top-left-radius: 10px;border-top-right-radius: 10px; width: 80%;"
src="images/screen1.png"></div>
</div>
</div>
<div style="min-height: 0px;">
<div class="col-xs-12 col-sm-12 col-md-12 mb16 mt16" style="float: left;">
<h3 class="alert"
style="font-weight:400;color: #091E42;background: #fff;text-align: left;border-radius: 0; font-size: 18px;">
<i class="fa fa-check-circle-o" style="width:40px; color:#00438b"></i>
Fix Bug of Publish/Unpublish Button on course Fullscreen.
<br>
</h3>
<div style="text-align: center;"><img class="img img-responsive center-block"
style="border-top-left-radius: 10px;border-top-right-radius: 10px;width: 80%;"
src="images/screen2.png"></div>
</div>
</div>
<div style="min-height: 0px;">
<div class="col-xs-12 col-sm-12 col-md-12 mb16 mt16" style="float: left;">
<h3 class="alert"
style="font-weight:400;color: #091E42;background: #fff;text-align: left;border-radius: 0; font-size: 18px;">
<i class="fa fa-check-circle-o" style="width:40px; color:#00438b"></i>
Publish slides directly from backend.
<br>
</h3>
<div style="text-align: center;"><img class="img img-responsive center-block"
style="border-top-left-radius: 10px;border-top-right-radius: 10px;width: 80%;"
src="images/screen3.png"></div>
</div>
</div>
<div style="min-height: 0px;">
<div class="col-xs-12 col-sm-12 col-md-12 mb16 mt16" style="float: left;">
<h3 class="alert"
style="font-weight:400;color: #091E42;background: #fff;text-align: left;border-radius: 0; font-size: 18px;">
<i class="fa fa-check-circle-o" style="width:40px; color:#00438b"></i>
Add Description for course in HTML Editor.
<br>
</h3>
<div style="text-align: center;"><img class="img img-responsive center-block mb-5"
style="border-top-left-radius: 10px;border-top-right-radius: 10px;width: 80%;"
src="images/screen4.png"></div>
<div style="text-align: center;"><img class="img img-responsive center-block mb-5"
style="border-top-left-radius: 10px;border-top-right-radius: 10px;width: 80%;"
src="images/screen5.png"></div>
</div>
</div>
<div style="min-height: 0px;">
<div class="col-xs-12 col-sm-12 col-md-12 mb16 mt16" style="float: left;">
<h3 class="alert"
style="font-weight:400;color: #091E42;background: #fff;text-align: left;border-radius: 0; font-size: 18px;">
<i class="fa fa-check-circle-o" style="width:40px; color:#00438b"></i>
Add Youtube Video URL or Google Drive Video URL.
<br>
</h3>
<div style="text-align: center;"><img class="img img-responsive center-block mb-5"
style="border-top-left-radius: 10px;border-top-right-radius: 10px;width: 80%;"
src="images/screen6.png"></div>
<div style="text-align: center;"><img class="img img-responsive center-block mb-5"
style="border-top-left-radius: 10px;border-top-right-radius: 10px;width: 80%;"
src="images/screen7.png"></div>
<div style="text-align: center;"><img class="img img-responsive center-block mb-5"
style="border-top-left-radius: 10px;border-top-right-radius: 10px;width: 80%;"
src="images/screen8.png"></div>
</div>
</div>
<div style="min-height: 0px;">
<div class="col-xs-12 col-sm-12 col-md-12 mb16 mt16" style="float: left;">
<h3 class="alert"
style="font-weight:400;color: #091E42;background: #fff;text-align: left;border-radius: 0; font-size: 18px;">
<i class="fa fa-check-circle-o" style="width:40px; color:#00438b"></i>
Like/Dislike option for content in fullscreen.
<br>
</h3>
<div style="text-align: center;"><img class="img img-responsive center-block mb-5"
style="border-top-left-radius: 10px;border-top-right-radius: 10px;width: 80%;"
src="images/screen9.png"></div>
</div>
</div>
<div style="min-height: 0px;">
<div class="col-xs-12 col-sm-12 col-md-12 mb16 mt16" style="float: left;">
<h3 class="alert"
style="font-weight:400;color: #091E42;background: #fff;text-align: left;border-radius: 0; font-size: 18px;">
<i class="fa fa-check-circle-o" style="width:40px; color:#00438b"></i>
Comment option for content in fullscreen.
<br>
</h3>
<div style="text-align: center;"><img class="img img-responsive center-block mb-5"
style="border-top-left-radius: 10px;border-top-right-radius: 10px;width: 80%;"
src="images/screen10.png"></div>
<div style="text-align: center;"><img class="img img-responsive center-block mb-5"
style="border-top-left-radius: 10px;border-top-right-radius: 10px;width: 80%;"
src="images/screen11.png"></div>
</div>
</div>
</div>
</div>
</section>
</div>
<div>
<div style="max-width:1540px; margin: 0 auto; ">
<div class="col-lg-12 mb-4">
<h4 class="text-center">Need Help?</h4>
<hr style="border-width: 4px; border-color:#875A7B; width: 80px;">
</div>
</div>
<!-- Contact Cards -->
<div class="row d-flex justify-content-center align-items-center" style="max-width:1540px; margin: 0 auto;">
<div class="col-lg-3 shadow mt-2"
style="padding: 5rem 2rem 2rem; border-radius: 10px; margin-right: 3rem; border-top: 7px solid #546E7A; height: 180px;">
<h5 class="font-weight-bold" style="font-family: Roboto, 'sans-serif';text-align: center;">Visit us</h5>
<a href="https://manprax.com" target="_blank" class="btn btn-block mb-2 deep_hover"
style="text-decoration: none; background-color: #546E7A; color: #FFF; border-radius: 4px;">ManpraX Software LLP</a>
</div>
<div class="col-lg-3 shadow mt-2"
style="padding: 5rem 2rem 2rem; border-radius: 10px; margin-right: 3rem; border-top: 7px solid #546E7A; height: 180px;">
<h5 class="font-weight-bold" style="font-family: Roboto, 'sans-serif';text-align: center;">Bite Sized Conversation</h5>
<a href="https://wa.me/+918448440100" target="_blank" class="btn btn-block mb-2 deep_hover"
style="text-decoration: none; background-color: #546E7A; color: #FFF; border-radius: 4px;">WhatsApp</a>
</div>
</div>
</div>
<div class="mt-5">
<div style="max-width:1540px; margin: 0 auto; ">
<div class="col-lg-12 mb-4">
<h4 class="text-center">Enquires</h4>
<hr style="border-width: 4px; border-color:#875A7B; width: 80px;">
</div>
</div>
<!-- Contact Cards -->
<div class="row d-flex justify-content-center align-items-center" style="max-width:1540px; margin: 0 auto;">
<div class="col-lg-3 shadow mt-2"
style="padding: 5rem 2rem 2rem; border-radius: 10px; margin-right: 3rem; border-top: 7px solid #546E7A; height: 180px;">
<h5 class="font-weight-bold" style="font-family: Roboto, 'sans-serif';text-align: center;">Business Enquiry</h5>
<a href="mailto:sales@manprax.com" target="_blank" class="btn btn-block mb-2 deep_hover"
style="text-decoration: none; background-color: #546E7A; color: #FFF; border-radius: 4px;">Click Here</a>
</div>
<div class="col-lg-3 shadow mt-2"
style="padding: 5rem 2rem 2rem; border-radius: 10px; margin-right: 3rem; border-top: 7px solid #546E7A; height: 180px;">
<h5 class="font-weight-bold" style="font-family: Roboto, 'sans-serif';text-align: center;">General Enquiry</h5>
<a href="mailto:info@manprax.com" target="_blank" class="btn btn-block mb-2 deep_hover"
style="text-decoration: none; background-color: #546E7A; color: #FFF; border-radius: 4px;">Click Here</a>
</div>
<div class="col-lg-3 shadow mt-2"
style="padding: 5rem 2rem 2rem; border-radius: 10px; margin-right: 3rem; border-top: 7px solid #546E7A; height: 180px;">
<h5 class="font-weight-bold" style="font-family: Roboto, 'sans-serif';text-align: center;">Join the MX Team</h5>
<a href="mailto:apply@manprax.com" target="_blank" class="btn btn-block mb-2 deep_hover"
style="text-decoration: none; background-color: #546E7A; color: #FFF; border-radius: 4px;">Click Here</a>
</div>
</div>
</div>
@@ -0,0 +1,144 @@
/** @odoo-module **/
import publicWidget from '@web/legacy/js/public/public_widget';
import portalComposer from "@portal/js/portal_composer";
import { renderToElement } from "@web/core/utils/render";
import { session } from "@web/session";
const PortalComposer = portalComposer.PortalComposer;
PortalComposer.include({
/**
* @private
* @param {Event} ev
*/
async _onSubmitButtonClick(ev) {
await this._super(...arguments).then((result) => {
const $modal = this.$el.closest('#commentpopupcomposer');
$modal.on('hidden.bs.modal', () => {
this.trigger_up('reload_comment_popup_composer', result);
});
$modal.modal('hide');
});
},
/**
* Prepare message data for submission.
*
* @private
* @returns {Object} The data object to be sent to the backend.
*/
_prepareMessageData() {
const messageBody = this.$('textarea[name="message"]').val();
const attachmentIds = this.attachments.map((a) => a.id);
const attachmentTokens = this.attachments.map((a) => a.access_token);
if (this.options.is_comment_fullscreen) {
const resId = $('.o_wslides_fs_sidebar_list_item.active').data('id');
return {
thread_model: this.options.res_model,
thread_id: resId,
post_data: {
body: messageBody,
attachment_ids: attachmentIds,
message_type: "comment",
subtype_xmlid: "mail.mt_comment",
},
attachment_tokens: attachmentTokens,
};
} else {
return {
thread_model: this.options.res_model,
thread_id: this.options.res_id,
post_data: {
body: messageBody,
attachment_ids: attachmentIds,
message_type: "comment",
subtype_xmlid: "mail.mt_comment",
},
attachment_tokens: attachmentTokens,
message_id: this.options.default_message_id,
};
}
},
});
/**
* CommentPopupComposer
*
* Open a popup with the portal composer when clicking on it.
**/
const CommentPopupComposer = publicWidget.Widget.extend({
selector: '.o_comment_popup_composer',
custom_events: {
reload_comment_popup_composer: '_onReloadCommentPopupComposer',
},
willStart: function (parent) {
const def = this._super.apply(this, arguments);
const options = this.$el.data();
this.options = Object.assign({
'token': false,
'res_model': false,
'res_id': false,
'pid': 0,
'csrf_token': odoo.csrf_token,
'user_id': session.user_id,
}, options, {});
return def;
},
/**
* @override
*/
start: function () {
return Promise.all([
this._super.apply(this, arguments),
this._reloadCommentPopupComposer(),
]);
},
/**
* Destroy existing commentPopup and insert new commentPopup widget
*
* @private
* @param {Object} data
*/
_reloadCommentPopupComposer: function () {
// Append the modal
const modal = renderToElement(
'mx_elearning_plus.PopupCommentComposer', {
inline_mode: true,
widget: this,
});
this.$('.o_comment_popup_composer_modal').html(modal);
if (this._composer) {
this._composer.destroy();
}
// Instantiate the "Portal Composer" widget and insert it into the modal
this._composer = new PortalComposer(this, this.options);
return this._composer.appendTo(this.$('.o_comment_popup_composer_modal .o_portal_chatter_composer'))
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* @private
* @param {OdooEvent} event
*/
_onReloadCommentPopupComposer: function (event) {
const data = event.data;
this.options = Object.assign(this.options, data);
this._reloadCommentPopupComposer();
}
});
publicWidget.registry.CommentPopupComposer = CommentPopupComposer;
export default CommentPopupComposer;
@@ -0,0 +1,101 @@
/** @odoo-module **/
import Fullscreen from "@website_slides/js/slides_course_fullscreen_player";
import { _t } from "@web/core/l10n/translation";
import { rpc } from "@web/core/network/rpc";
Fullscreen.include({
events: Object.assign({}, Fullscreen.prototype.events, {
'click .o_btn_comment_unable': '_onClickCommentUnable',
'click .o_btn_comment_public': '_onClickCommentPublic',
'click .o_btn_no_comment_karma': '_onClickNoCommentKarma',
'click .o_btn_comment_karma': '_onClickCommentKarma',
}),
_onClickCommentUnable: function () {
var message = ('Commenting is not enabled on this course.');
this.displayNotification({
type: 'warning',
message: message,
sticky: true
});
},
_onClickCommentPublic: function () {
var message = ('There are no comments for now. Join Course to be the first to leave a comment.');
this.displayNotification({
type: 'warning',
message: message,
sticky: true
});
},
_onClickNoCommentKarma: function () {
var message = ('There are no comments for now. Earn more Karma to be the first to leave a comment.');
this.displayNotification({
type: 'warning',
message: message,
sticky: true
});
},
_onClickCommentKarma: function () {
var message = ('Earn more Karma to leave a comment.');
this.displayNotification({
type: 'warning',
message: message,
sticky: true
});
},
_renderSlide: function () {
this._super.apply(this, arguments);
const slide = this._slideValue;
rpc('/slides/slide/mx/like', {
slide_id: slide.id,
}).then(function (data) {
if (! data.error) {
const $likesBtn = self.$('span.o_wslides_js_slide_like_up_mx');
const $likesIcon = $likesBtn.find('i.fa');
const $dislikesBtn = self.$('span.o_wslides_js_slide_like_down_mx');
const $dislikesIcon = $dislikesBtn.find('i.fa');
// update 'thumbs-up' button with latest state
$likesBtn.data('user-vote', data.user_vote);
$likesBtn.find('span').text(data.likes);
$likesIcon.toggleClass("fa-thumbs-up", data.user_vote === 1);
$likesIcon.toggleClass("fa-thumbs-o-up", data.user_vote !== 1);
// update 'thumbs-down' button with latest state
$dislikesBtn.data('user-vote', data.user_vote);
$dislikesBtn.find('span').text(data.dislikes);
$dislikesIcon.toggleClass("fa-thumbs-down", data.user_vote === -1);
$dislikesIcon.toggleClass("fa-thumbs-o-down", data.user_vote !== -1);
$('.o_wslides_js_slide_like_up_mx').data('slide-id', slide.id);
$('.o_wslides_js_slide_like_down_mx').data('slide-id', slide.id);
$('.o_wslides_js_slide_like_up_mx span').text(data.likes);
$('.o_wslides_js_slide_like_down_mx span').text(data.dislikes);
}
});
},
_onChangeSlideRequest: function (ev){
this._super.apply(this, arguments);
var slideData = ev.data;
var $data = this.$el.prevObject.find('.js_publish_btn:visible').parents(".js_publish_management:first");
rpc('/website/publish/slide', {
id: slideData.id,
}).then(function (result) {
if (result){
$data.removeClass("css_unpublished");
$data.addClass("css_published");
$data.find('input').prop("checked", result);
$data.parents("[data-publish]").attr("data-publish", +result ? 'on' : 'off');
}else{
$data.removeClass("css_published");
$data.addClass("css_unpublished");
$data.find('input').prop("checked", result);
$data.parents("[data-publish]").attr("data-publish", +result ? 'on' : 'off');
}
})
}
});
@@ -0,0 +1,26 @@
/** @odoo-module **/
import publicWidget from '@web/legacy/js/public/public_widget';
publicWidget.registry.websiteCourseExtended = publicWidget.Widget.extend({
selector: '.o_course_extended',
/**
* @override
*/
start: function () {
$('.o_course_extended').on('click','#collapse_div',function() {
var id = this.dataset.target.split('-')[1]
if($('#slide-'+id).is(":visible")) {
$('#slide-'+id).hide()
$(this).children().first().children().removeClass('fa-minus');
$(this).children().first().children().addClass('fa-plus');
}else {
$('#slide-'+id).show()
$(this).children().first().children().removeClass('fa-plus');
$(this).children().first().children().addClass('fa-minus');
}
});
}
})
@@ -0,0 +1,118 @@
/** @odoo-module **/
import publicWidget from '@web/legacy/js/public/public_widget';
import { rpc } from "@web/core/network/rpc";
import { sprintf } from '@web/core/utils/strings';
import { _t } from "@web/core/l10n/translation";
import '@website_slides/js/slides';
var FullscreenSlideLikeWidget = publicWidget.Widget.extend({
events: {
'click .o_wslides_js_slide_like_up_mx': '_onClickUpMx',
'click .o_wslides_js_slide_like_down_mx': '_onClickDownMx',
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @private
* @param {Object} $el
* @param {String} message
*/
_popoverAlert: function ($el, message) {
$el.popover({
trigger: 'focus',
delay: {'hide': 300},
placement: 'bottom',
container: 'body',
html: true,
content: function () {
return message;
}
}).popover('show');
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* @private
*/
_onClickMx: function (slideId, voteType) {
var self = this;
rpc('/slides/slide/like', {
slide_id: slideId,
upvote: voteType === 'like',
}).then(function (data) {
if (! data.error) {
const $likesBtn = self.$('span.o_wslides_js_slide_like_up_mx');
const $likesIcon = $likesBtn.find('i.fa');
const $dislikesBtn = self.$('span.o_wslides_js_slide_like_down_mx');
const $dislikesIcon = $dislikesBtn.find('i.fa');
// update 'thumbs-up' button with latest state
$likesBtn.data('user-vote', data.user_vote);
$likesBtn.find('span').text(data.likes);
$likesIcon.toggleClass("fa-thumbs-up", data.user_vote === 1);
$likesIcon.toggleClass("fa-thumbs-o-up", data.user_vote !== 1);
// update 'thumbs-down' button with latest state
$dislikesBtn.data('user-vote', data.user_vote);
$dislikesBtn.find('span').text(data.dislikes);
$dislikesIcon.toggleClass("fa-thumbs-down", data.user_vote === -1);
$dislikesIcon.toggleClass("fa-thumbs-o-down", data.user_vote !== -1);
} else {
if (data.error === 'public_user') {
const message = data.error_signup_allowed ?
_t('Please <a href="/web/login?redirect=%s">login</a> or <a href="/web/signup?redirect=%s">create an account</a> to vote for this lesson') :
_t('Please <a href="/web/login?redirect=%s">login</a> to vote for this lesson');
self._popoverAlert(self.$el, sprintf(message, document.URL, document.URL));
} else if (data.error === 'slide_access') {
self._popoverAlert(self.$el, _t('You don\'t have access to this lesson'));
} else if (data.error === 'channel_membership_required') {
self._popoverAlert(self.$el, _t('You must be member of this course to vote'));
} else if (data.error === 'channel_comment_disabled') {
self._popoverAlert(self.$el, _t('Votes and comments are disabled for this course'));
} else if (data.error === 'channel_karma_required') {
self._popoverAlert(self.$el, _t('You don\'t have enough karma to vote'));
} else {
self._popoverAlert(self.$el, _t('Unknown error'));
}
}
});
},
_onClickUpMx: function (ev) {
var slideId = $(ev.currentTarget).data('slide-id');
return this._onClickMx(slideId, 'like');
},
_onClickDownMx: function (ev) {
var slideId = $(ev.currentTarget).data('slide-id');
return this._onClickMx(slideId, 'dislike');
},
});
publicWidget.registry.FullscreenWebsiteSlidesSlideLike = publicWidget.Widget.extend({
selector: '#wrapwrap',
/**
* @override
* @param {Object} parent
*/
start: function () {
var self = this;
var defs = [this._super.apply(this, arguments)];
$('.o_wslides_js_slide_like_mx').each(function () {
defs.push(new FullscreenSlideLikeWidget(self).attachTo($(this)));
});
return Promise.all(defs);
},
});
export default {
FullscreenSlideLikeWidget: FullscreenSlideLikeWidget,
FullscreenWebsiteSlidesSlideLike: publicWidget.registry.FullscreenWebsiteSlidesSlideLike
};
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="mx_elearning_plus.portalComposer" t-inherit="portal.Composer" t-inherit-mode="primary">
<xpath expr="//button[hasclass('o_portal_chatter_composer_btn')]" position="replace">
<t t-if="widget.options['is_comment_fullscreen']">
<button t-attf-data-action="/mail/slide/comment" class="o_portal_chatter_composer_btn btn btn-primary" type="submit">Send</button>
</t>
<t t-else="">
<button t-attf-data-action="/mail/message/post" class="o_portal_chatter_composer_btn btn btn-primary" type="submit">Send</button>
</t>
</xpath>
</t>
</templates>
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<!--
Popup Comment Composer Widget
-->
<t t-name="mx_elearning_plus.PopupCommentComposer">
<div t-if="widget.options['display_composer']" class="modal fade" id="commentpopupcomposer" tabindex="-1" role="dialog" aria-labelledby="commentpopupcomposerlabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title o_comment_popup_composer_label" style="color: black;" id="commentpopupcomposerlabel">
Add your comment
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="o_portal_chatter_composer"/>
</div>
</div>
</div>
</div>
</t>
</templates>
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_slide_slide_form_inherit_publish" model="ir.ui.view">
<field name="name">slide.slide.form</field>
<field name="model">slide.slide</field>
<field name="inherit_id" ref="website_slides.view_slide_slide_form"/>
<field name="arch" type="xml">
<xpath expr="//sheet" position="before">
<header>
<button name="action_publish" type="object" string="Publish" class="oe_highlight" invisible="is_published"/>
<button name="action_unpublish" type="object" string="Unpublish" class="oe_highlight" invisible="not is_published"/>
</header>
</xpath>
<!-- <field name="slide_resource_downloadable" position="after">
<field name="is_hide" />
</field> -->
</field>
</record>
<record id="mx_plus_view_slide_channel_form_inherit" model="ir.ui.view">
<field name="name">slide.channel.form</field>
<field name="model">slide.channel</field>
<field name="inherit_id" ref="website_slides.view_slide_channel_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='user_id']" position="after">
<field name="intro_video_url" placeholder='e.g "www.youtube.com/watch?v=ebBez6bcSEc"' widget="url"/>
</xpath>
</field>
</record>
</data>
</odoo>
@@ -0,0 +1,247 @@
<?xml version="1.0" ?>
<odoo><data>
<template id="course_slides_list_collapse" inherit_id="website_slides.course_slides_list">
<div t-att-data-channel-id="channel.id" position="attributes">
<attribute name="class" separator=" " add="o_course_extended"/>
</div>
<div t-att-class="'d-flex align-items-center me-auto ps-3 %s' % ('o_wslides_slides_list_drag' if channel.can_publish else '')" position="attributes">
<attribute name="data-toggle">collapse</attribute>
<attribute name="t-att-data-target">'%s%s' % ('#slide-', category_id)</attribute>
<attribute name="aria-expanded">false</attribute>
<attribute name="t-att-aria-controls">category_id</attribute>
<attribute name="id">collapse_div</attribute>
</div>
<xpath expr="//div[@t-if='channel.can_publish and category_id']/i" position="replace">
<i class="fa fa-plus"/>
</xpath>
<ul class="list-unstyled pb-1 border-top" position="replace">
<t t-if="category_id">
<div class="collapse" t-att-id="'slide-%s' % (category_id)">
<ul t-att-data-category-id="category_id" class="list-unstyled pb-1 border-top">
<li class="o_wslides_slides_list_slide o_not_editable border-0"/>
<li class="o_wslides_js_slides_list_empty border-0"/>
<t t-foreach="category['slides']" t-as="slide">
<!-- <t t-if="not slide.is_hide"> -->
<t t-call="website_slides.course_slides_list_slide" />
<!-- </t> -->
<t t-set="j" t-value="j+1"/>
</t>
</ul>
</div>
</t>
<t t-if="not category_id">
<ul t-att-data-category-id="category_id" class="list-unstyled pb-1 border-top">
<li class="o_wslides_slides_list_slide o_not_editable border-0"/>
<li class="o_wslides_js_slides_list_empty border-0"/>
<t t-foreach="category['slides']" t-as="slide">
<t t-call="website_slides.course_slides_list_slide" />
<t t-set="j" t-value="j+1"/>
</t>
</ul>
</t>
</ul>
</template>
<!-- introduction video in courses home page template -->
<template id="course_card_inherit" inherit_id="website_slides.course_card">
<div class="o_wslides_background_image h-100" position="replace">
<t t-if="channel.image_1920">
<div t-field="channel.image_1920" t-options="{'widget': 'image'}" class="o_wslides_background_image h-100">
<t t-if="channel.partner_has_new_content" t-call="website_slides.course_card_information"/>
</div>
</t>
<t t-elif="channel.video_embed_code">
<div t-out="channel.video_embed_code" t-options='{"widget": "video_preview", "class": "embed-responsive-item"}' class="o_wslides_background_image h-100">
<t t-if="channel.partner_has_new_content" t-call="website_slides.course_card_information"/>
</div>
</t>
<t t-else="">
<div t-field="channel.image_1920" t-options="{'widget': 'image', 'preview_image': 'image_512'}" class="o_wslides_background_image h-100">
<t t-if="channel.partner_has_new_content" t-call="website_slides.course_card_information"/>
</div>
</t>
</div>
</template>
<!-- introduction video in course main page template -->
<template id="mx_course_slides_course_main" inherit_id="website_slides.course_main">
<div class="d-flex align-items-end justify-content-around h-100" position="replace">
<div class="d-flex align-items-end justify-content-around h-100">
<t t-if="channel.image_1920">
<div t-field="channel.image_1920" t-options='{"widget": "image", "class": "o_wslides_course_pict d-inline-block mb-2 mt-3 my-md-0"}' class="h-100"/>
</t>
<t t-elif="channel.video_embed_code" >
<div t-out="channel.video_embed_code" t-options='{"widget": "video_preview", "class": "o_wslides_course_pict embed-responsive-item d-inline-block mb-2 mt-3 my-md-0"}' class="h-100" style="width:-webkit-fill-available;"/>
</t>
<t t-else="">
<div t-field="channel.image_1920"
t-options="{'widget': 'image', 'class': 'o_wslides_course_pict d-inline-block mb-2 mt-3 my-md-0', 'preview_image': 'image_1024'}"
class="h-100"/>
</t>
</div>
</div>
</template>
<template id="mx_plus_slide_fullscreen_inherit" inherit_id="website_slides.slide_fullscreen">
<a class="o_wslides_fs_share d-flex align-items-center px-3" position="before">
<div t-if="slide.channel_id.allow_comment" class="o_wslides_js_slide_like_mx d-flex align-items-center px-3">
<span t-attf-class="o_wslides_js_slide_like_up_mx #{'disabled' if not slide.channel_id.can_vote else ''}" tabindex="0" data-bs-toggle="popover" t-att-data-slide-id="slide.id" t-att-data-user-vote="slide.user_vote">
<i t-attf-class="fa fa-1x #{'fa-thumbs-up' if slide.user_vote == 1 else 'fa-thumbs-o-up'}" role="img" aria-label="Likes" title="Like" style="cursor:pointer;"/>
<span t-field="slide.likes" t-options="{'format_decimalized_number': True}"/>
</span>
<span t-attf-class="o_wslides_js_slide_like_down_mx ms-3 #{'disabled' if not slide.channel_id.can_vote else ''}" tabindex="0" data-bs-toggle="popover" t-att-data-slide-id="slide.id" t-att-data-user-vote="slide.user_vote">
<i t-attf-class="fa fa-1x #{'fa-thumbs-down' if slide.user_vote == -1 else 'fa-thumbs-o-down'}" role="img" aria-label="Dislikes" title="Dislike" style="cursor:pointer;"/>
<span t-field="slide.dislikes" t-options="{'format_decimalized_number': True}"/>
</span>
</div>
</a>
<a class="o_wslides_fs_review d-flex align-items-center" position="after">
<a class="o_wslides_fs_comment d-flex align-items-center" title="Comments">
<t t-call="mx_elearning_plus.mx_fullscreen_comment">
<t t-set="enable_slide_comments" t-value="0"/>
<t t-set="allow_comment" t-value="slide.channel_id.allow_comment"/>
<t t-set="channel_type" t-value="slide.channel_id.channel_type"/>
<t t-set="can_access_channel" t-value="can_access_channel"/>
<t t-set="comment_count" t-value="slide.comments_count"/>
<t t-set="enroll" t-value="slide.channel_id.enroll"/>
<t t-set="can_comment" t-value="slide.channel_id.can_comment"/>
<t t-set="token" t-value="channel.access_token"/>
<t t-set="hash" t-value="message_post_hash"/>
<t t-set="pid" t-value="message_post_pid"/>
<t t-set="default_message_id" t-value="False"/>
<t t-set="disable_composer" t-value="not (slide.channel_id.can_comment and slide.channel_id.allow_comment)"/>
<t t-set="_link_btn_classes" t-value="'d-inline-block text-white fw-light shadow-none'"/>
<t t-set="icon" t-value="'fa fa-comments'"/>
<t t-set="_text_classes" t-value="'d-none d-md-inline-block'"/>
<t t-set="object" t-value="slide"/>
<t t-set="is_fullscreen" t-value="True"/>
<t t-set="is_comment_fullscreen" t-value="True"/>
</t>
</a>
</a>
</template>
<template id="mx_fullscreen_comment">
<t t-set="display_composer" t-value="not disable_composer and not (request.session.uid and env.user._is_public())"/>
<div class="d-print-none o_comment_popup_composer o_not_editable p-0"
contenteditable="false"
t-att-data-allow_comment="slide.channel_id.allow_comment"
t-att-data-channel_type="slide.channel_id.channel_type"
t-att-data-comments_count="slide.comments_count"
t-att-data-enroll="slide.channel_id.enroll"
t-att-data-can_comment="slide.channel_id.can_comment"
t-att-data-can_access_channel="can_access_channel"
t-att-data-comments="comments"
t-att-data-res_model="object._name"
t-att-data-res_id="object.id"
t-att-data-pid="pid"
t-att-data-hash="hash"
t-att-data-token="token"
t-att-data-partner_id="request.env.user.partner_id.id"
t-att-data-disable_composer="disable_composer"
t-att-data-display_composer="display_composer"
t-att-data-link_btn_classes="_link_btn_classes"
t-att-data-icon="icon"
t-att-data-text_classes="_text_classes"
t-att-data-allow_composer="'0' if disable_composer else '1'"
t-att-data-is_fullscreen="is_fullscreen"
t-att-data-is_comment_fullscreen="is_comment_fullscreen">
<div class="d-flex flex-wrap align-items-center">
<t t-if="not (slide.channel_id.allow_comment and slide.channel_id.channel_type == 'training')">
<button type="button" t-att-class="'o_btn_comment_unable btn ' + _link_btn_classes or 'btn-primary'">
<i t-if="icon" t-att-class="icon"/>
<span t-attf-class="#{_text_classes}">
<t t-if="is_fullscreen">Comments</t>
<!-- <t t-if="slide.comments_count"> (<t t-out="slide.comments_count"/>)</t> -->
</span>
</button>
</t>
<t t-elif="not slide.comments_count">
<t t-if="not can_access_channel and slide.channel_id.enroll != 'public'">
<button type="button" t-att-class="'o_btn_comment_public btn ' + _link_btn_classes or 'btn-primary'">
<i t-if="icon" t-att-class="icon"/>
<span t-attf-class="#{_text_classes}">
<t t-if="is_fullscreen">Comments</t>
<!-- <t t-if="slide.comments_count"> (<t t-out="slide.comments_count"/>)</t> -->
</span>
</button>
</t>
<t t-if="not slide.channel_id.can_comment">
<button type="button" t-att-class="'o_btn_no_comment_karma btn ' + _link_btn_classes or 'btn-primary'">
<i t-if="icon" t-att-class="icon"/>
<span t-attf-class="#{_text_classes}">
<t t-if="is_fullscreen">Comments</t>
<!-- <t t-if="slide.comments_count"> (<t t-out="slide.comments_count"/>)</t> -->
</span>
</button>
</t>
</t>
<t t-else="">
<t t-if="not slide.comments_count and can_access_channel">
<button type="button" t-att-class="'o_btn_comment_karma btn ' + _link_btn_classes or 'btn-primary'">
<i t-if="icon" t-att-class="icon"/>
<span t-attf-class="#{_text_classes}">
<t t-if="is_fullscreen">Comments</t>
<!-- <t t-if="slide.comments_count"> (<t t-out="slide.comments_count"/>)</t> -->
</span>
</button>
</t>
</t>
<button t-if="display_composer" type="button"
t-att-class="'btn ' + _link_btn_classes or 'btn-primary'"
data-bs-toggle="modal" data-bs-target="#commentpopupcomposer">
<i t-if="icon" t-att-class="icon"/>
<span t-attf-class="#{_text_classes}">
<t t-if="is_fullscreen">Comments</t>
<!-- <t t-if="slide.comments_count"> (<t t-out="slide.comments_count"/>)</t> -->
</span>
</button>
<div class="o_comment_popup_composer_modal"/>
</div>
</div>
</template>
<!-- <template id="mx_plus_slide_aside_training_category_inherit" inherit_id="website_slides.slide_aside_training_category">
<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 '')" position="attributes">
<attribute name="t-if">not aside_slide.is_hide</attribute>
</div>
<ul class="list-group ps-5 mb-1 list-unstyled" position="attributes">
<attribute name="t-if">(aside_slide._has_additional_resources() or aside_slide.question_ids) and not aside_slide.is_hide</attribute>
</ul>
</template> -->
<template id="mx_plus_slide_fullscreen_sidebar_category_inherit" inherit_id="website_slides.slide_fullscreen_sidebar_category">
<li t-attf-class="o_wslides_fs_sidebar_list_item d-flex py-1 #{'active' if slide.id == current_slide.id else ''}" position="attributes">
<attribute name="t-att-data-allow-comment">slide.channel_id.allow_comment</attribute>
<attribute name="t-att-data-can-vote">slide.channel_id.can_vote</attribute>
<attribute name="t-att-data-user-vote">slide.user_vote if slide.user_vote else 0</attribute>
<attribute name="t-att-data-likes">slide.likes if slide.likes else 0</attribute>
<attribute name="t-att-data-dislikes">slide.dislikes if slide.dislikes else 0</attribute>
<attribute name="t-att-data-channel-type">slide.channel_id.channel_type</attribute>
<attribute name="t-att-data-comments-count">slide.comments_count</attribute>
<attribute name="t-att-data-enroll">slide.channel_id.enroll</attribute>
<attribute name="t-att-data-can-comment">slide.channel_id.can_comment</attribute>
<attribute name="t-att-data-can-access-channel">can_access_channel</attribute>
<attribute name="t-att-data-comments">comments</attribute>
</li>
<!-- <xpath expr="//span[@class='d-block']/div[@class='d-flex']" position="replace">
</xpath>
<a t-if="can_access" class="d-block" href="#" position="replace">
<t t-if="not slide.is_hide">
<a t-if="can_access" class="d-block" href="#">
<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">
<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>
</t>
</a>
<ul class="list-unstyled w-100 pt-2 small ps-4" position="attributes">
<attribute name="t-if">(slide._has_additional_resources() and not slide.is_hide) or (slide.question_ids and not slide.slide_category =='quiz' and not slide.is_hide)</attribute>
</ul> -->
</template>
</data></odoo>