add modules elearning
@@ -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&autopause=0&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&autopause=0&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
|
||||
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 241 KiB |
|
After Width: | Height: | Size: 341 KiB |
|
After Width: | Height: | Size: 222 KiB |
|
After Width: | Height: | Size: 562 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 315 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 414 KiB |
|
After Width: | Height: | Size: 320 KiB |
|
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>
|
||||