[MERGE] website_slides: apply various fixes

PURPOSE

Purpose of this merge is to provide fixes and usability fixes or
improvements in eLearning.

SPECIFICATIONS

Improve "New content" modal: upload new course content should be revamped
-------------------------------------------------------------------------
* general layout: https://drive.google.com/a/odoo.com/file/d/1zbNO0Vg1262ijkXA6pwSuenMI0h4NsPm/view?usp=drivesdk
* remove picture tab;
* display as two column with preview on right on change of picture
or document;

Do not put odoobot responsible of demo courses
----------------------------------------------

Do not crash if there is any issue loading quiz
-----------------------------------------------

Do not allow fullscreen to bypass ACLs
--------------------------------------
Currently fullscreen takes all information from categorized slides to
display its menu. In order to avoid calls to server some information
is prepared in DOM to speedup loading. It means slide information is
available even when not being member of a course which leads to some content
leak.

This merge fixes that by correctly checking that a slide can be accessed
before allowing to have access to its information and embedded code. Access
of a slide is either member of a course, either course publisher.

Correctly set sequence of newly added content
---------------------------------------------

Purpose of this commit is to add new content at last position in frontend.
Indeed backend uses last position in embedded list view, frontend should
do the same. Moreover when having a lot of content, having everything at
the bottom of the screen at the same position of Add content / section
buttons makes sens.

Specifications

* when adding a new category: add it as last category;
* when adding a new slide in a category: add it as last slide of the
category;

Specific case of slide without category

* when adding a new slide without category: add it as last slide of the
channel (display it as last) but keep it on top in backend view
(display as first);

Improve content list display in frontend of eLearning

* contents without section are now at the bottom;
* fixed alignment of buttons icon (Add Content and Add Section)
* fixed broken design because of title too long

LINKS

Task 2049640 (website slides back2basics and fiximp)
Task 2058595 (eLearning v13 testing)
Task 2064112 (fullscreen bug report)
Task 2061672 (new content bug report)

Improve backend UI, views and user actions
------------------------------------------

Containing

* channel kanban view tweaking;
* channel form view: some spacing and renaming improvements. Also set
advanced field (new content email, share template) as group no one;

* slide kanban view tweaking;
* removed sections from kanban and list view for contents;
* contents form view: various improvements, with notably

* removed field website_url;
* set publishded date is now readonly, tracked and in group_no_one as
it is automatically set when publishing the lesson, and people should
not update it manually;
* removed the sum "Content" and replaced it by a stat button
* reword quiz constraint error messages;

* rating views improvements:
* revamped the kanban view of rating that were a bit broken;
* added a pivot view in Reporting/Reviews;
* updated graph view in Reporting/Reviews;

This commit also provides some usability fixes

* prevent sending email if new content is category;
* email: Prevent displaying an image if there is no image;

Fixes some display issues in eLearning frontend
-----------------------------------------------
* fix the share button in documentation. It was not working as model was
not correctly placed;
* fix top3 users banners broken in mobile;
* fix left menu of fullscreen being behind content on mobile (image, video);
* removed upload button in training channel aside as there are already
buttons available for that purpose;

Revamp "New Course" menu
------------------------
Purpose of this commit is to change the modal to make it more mobile friendly.
Notably remove the two columns.

Allow website publisher to create forum
---------------------------------------
Security file was buggy and not imported.

closes odoo/odoo#36348

Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
Odoo's Mergebot
2019-09-03 18:18:54 +02:00
committed by GitHub
30 changed files with 346 additions and 273 deletions
@@ -9,9 +9,10 @@ class Channel(models.Model):
enroll = fields.Selection(selection_add=[('payment', 'On payment')])
product_id = fields.Many2one('product.product', 'Product', index=True)
product_sale_revenues = fields.Float(
product_sale_revenues = fields.Monetary(
string='Total revenues', compute='_compute_product_sale_revenues',
groups="sales_team.group_sale_salesman")
currency_id = fields.Many2one(related='product_id.currency_id')
_sql_constraints = [
('product_id_check', "CHECK( enroll!='payment' OR product_id IS NOT NULL )", "Product is required for on payment channels.")
@@ -33,7 +33,8 @@
<xpath expr="//div[@name='info_total_time']" position="after">
<div class="d-flex" attrs="{'invisible': [('enroll', '!=', 'payment')]}">
<span class="mr-auto"><label for="product_sale_revenues" class="mb0">Sales</label></span>
<field name="product_sale_revenues"/>
<field name="product_sale_revenues" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<field name="currency_id" attrs="{'invisible': True}"/>
</div>
</xpath>
<xpath expr="//a[@name='action_channel_invite']" position="attributes">
@@ -831,7 +831,10 @@ class WebsiteSlides(WebsiteProfile):
else:
values.update({
'category_id': post['category_id'][0],
'sequence': request.env['slide.slide'].browse(post['category_id'][0]).sequence + 1
})
else:
values['sequence'] = -1
# create slide itself
try:
+2
View File
@@ -11,11 +11,13 @@
Hello<br/><br/>
There is something new in the course <strong>${object.channel_id.name}</strong> you are following:<br/><br/>
<center><strong>${object.name}</strong></center>
% if object.image_1024
<div style="margin: 16px 8px 16px 8px; text-align: center;">
<a href="${object.website_url}">
<img alt="${object.name}" src="${ctx['base_url']}/web/image/slide.slide/${object.id}/image_1024" style="height:auto; width:150px; margin: 16px;"/>
</a>
</div>
% endif
<div style="margin: 16px 8px 16px 8px; text-align: center;">
<a href="${object.website_url}"
style="background-color: #875a7b; padding: 8px 16px 8px 16px; text-decoration: none; color: #fff; border-radius: 5px;">View content</a>
@@ -3,6 +3,7 @@
<record id="slide_channel_demo_0_gard_0" model="slide.channel">
<field name="name">Basics of Gardening</field>
<field name="user_id" ref="base.user_admin"/>
<field name="enroll">public</field>
<field name="channel_type">training</field>
<field name="allow_comment" eval="True"/>
@@ -21,6 +22,7 @@
<!-- This channel will be set on payment -->
<record id="slide_channel_demo_1_gard1" model="slide.channel">
<field name="name">Taking care of Trees</field>
<field name="user_id" ref="base.user_admin"/>
<field name="enroll">public</field>
<field name="channel_type">training</field>
<field name="allow_comment" eval="True"/>
@@ -37,6 +39,7 @@
<record id="slide_channel_demo_2_gard2" model="slide.channel">
<field name="name">Trees, Wood and Gardens</field>
<field name="user_id" ref="base.user_admin"/>
<field name="enroll">public</field>
<field name="channel_type">documentation</field>
<field name="allow_comment" eval="True"/>
@@ -55,6 +58,7 @@
<record id="slide_channel_demo_3_furn0" model="slide.channel">
<field name="name">Choose your wood !</field>
<field name="user_id" ref="base.user_admin"/>
<field name="enroll">public</field>
<field name="channel_type">training</field>
<field name="allow_comment" eval="True"/>
@@ -74,6 +78,7 @@ will learn the basics of wood characteristics.</field>
<record id="slide_channel_demo_4_furn1" model="slide.channel">
<field name="name">Furniture Technical Specifications</field>
<field name="user_id" ref="base.user_admin"/>
<field name="enroll">invite</field>
<field name="channel_type">documentation</field>
<field name="allow_comment" eval="True"/>
@@ -89,6 +94,7 @@ will learn the basics of wood characteristics.</field>
<record id="slide_channel_demo_5_furn2" model="slide.channel">
<field name="name">Basics of Furniture Creation</field>
<field name="user_id" ref="base.user_admin"/>
<field name="enroll">invite</field>
<field name="channel_type">training</field>
<field name="allow_comment" eval="True"/>
@@ -107,6 +113,7 @@ will learn the basics of wood characteristics.</field>
<!-- This channel will be set on payment and receive certifications capabilities -->
<record id="slide_channel_demo_6_furn3" model="slide.channel">
<field name="name">DIY Furniture</field>
<field name="user_id" ref="base.user_admin"/>
<field name="enroll">invite</field>
<field name="channel_type">training</field>
<field name="allow_comment" eval="True"/>
+16 -16
View File
@@ -106,7 +106,7 @@ class Channel(models.Model):
('latest', 'Latest Published'),
('most_voted', 'Most Voted'),
('most_viewed', 'Most Viewed')],
string="Featuring Policy", default='latest', required=True)
string="Featured Content", default='latest', required=True)
access_token = fields.Char("Security Token", copy=False, default=_default_access_token)
nbr_presentation = fields.Integer('Presentations', compute='_compute_slides_statistics', store=True)
nbr_document = fields.Integer('Documents', compute='_compute_slides_statistics', store=True)
@@ -130,11 +130,11 @@ class Channel(models.Model):
help="Email template to send slide publication through email",
default=lambda self: self.env['ir.model.data'].xmlid_to_res_id('website_slides.slide_template_published'))
share_template_id = fields.Many2one(
'mail.template', string='Shared Template',
'mail.template', string='Share Template',
help="Email template used when sharing a slide",
default=lambda self: self.env['ir.model.data'].xmlid_to_res_id('website_slides.slide_template_shared'))
enroll = fields.Selection([
('public', 'Public'), ('invite', 'Invite')],
('public', 'Public'), ('invite', 'On Invitation')],
default='public', string='Enroll Policy', required=True,
help='Condition to enroll: everyone, on invite, on payment (sale bridge).')
enroll_msg = fields.Html(
@@ -142,7 +142,7 @@ class Channel(models.Model):
default=False, translate=tools.html_translate, sanitize_attributes=False)
enroll_group_ids = fields.Many2many('res.groups', string='Auto Enroll Groups', help="Members of those groups are automatically added as members of the channel.")
visibility = fields.Selection([
('public', 'Public'), ('members', 'Members')],
('public', 'Public'), ('members', 'Members Only')],
default='public', string='Visibility', required=True,
help='Applied directly as ACLs. Allow to hide channels and their content for non members.')
partner_ids = fields.Many2many(
@@ -154,7 +154,7 @@ class Channel(models.Model):
channel_partner_ids = fields.One2many('slide.channel.partner', 'channel_id', string='Members Information', groups='website.group_website_publisher', depends=['partner_ids'])
upload_group_ids = fields.Many2many(
'res.groups', 'rel_upload_groups', 'channel_id', 'group_id', string='Upload Groups',
help="Who can publish: responsible, members of upload_group_ids if defined or website publisher group members.")
help="Group of users allowed to publish contents on a documentation course.")
# not stored access fields, depending on each user
completed = fields.Boolean('Done', compute='_compute_user_statistics', compute_sudo=False)
completion = fields.Integer('Completion', compute='_compute_user_statistics', compute_sudo=False)
@@ -493,7 +493,7 @@ class Channel(models.Model):
def action_view_slides(self):
action = self.env.ref('website_slides.slide_slide_action').read()[0]
action['context'] = {'default_channel_id': self.id}
action['domain'] = [('channel_id', "=", self.id)]
action['domain'] = [('channel_id', "=", self.id), ('is_category', '=', False)]
return action
def action_view_ratings(self):
@@ -523,16 +523,7 @@ class Channel(models.Model):
all_slides = self.env['slide.slide'].sudo().search(base_domain, order=order)
category_data = []
# First add uncategorized slides
uncategorized_slides = all_slides.filtered(lambda slide: not slide.category_id)
if uncategorized_slides or force_void:
category_data.append({
'category': False, 'id': False,
'name': _('Uncategorized'), 'slug_name': _('Uncategorized'),
'total_slides': len(uncategorized_slides),
'slides': uncategorized_slides[(offset or 0):(offset + limit or len(uncategorized_slides))],
})
# Then all categories by natural order
# First add all categories by natural order
for category in all_categories:
category_slides = all_slides.filtered(lambda slide: slide.category_id == category)
if not category_slides and not force_void:
@@ -543,6 +534,15 @@ class Channel(models.Model):
'total_slides': len(category_slides),
'slides': category_slides[(offset or 0):(limit + offset or len(category_slides))],
})
# Then add uncategorized slides
uncategorized_slides = all_slides.filtered(lambda slide: not slide.category_id)
if uncategorized_slides or force_void:
category_data.append({
'category': False, 'id': False,
'name': _('Uncategorized'), 'slug_name': _('Uncategorized'),
'total_slides': len(uncategorized_slides),
'slides': uncategorized_slides[(offset or 0):(offset + limit or len(uncategorized_slides))],
})
return category_data
def _resequence_slides(self, slide):
@@ -27,19 +27,19 @@ class SlideQuestion(models.Model):
if answer.is_correct:
good_answer_count += 1
if good_answer_count > 1:
raise ValidationError(_('A question can only have one good answer'))
raise ValidationError(_('Question "%s" can only have one good answer') % question.question)
@api.constrains('answer_ids')
def _check_correct_answer(self):
for question in self:
if not any([answer.is_correct for answer in question.answer_ids]):
raise ValidationError(_("A question must at least have one good answer"))
raise ValidationError(_('Question "%s" must at least have one good answer') % question.question)
@api.constrains('answer_ids')
def _check_at_least_2_answers(self):
for question in self:
if len(question.answer_ids) < 2:
raise ValidationError(_("A question must at least have two possible answers"))
raise ValidationError(_('Question "%s" has no valid answer, please set one') % question.question)
@api.depends('slide_id')
def _compute_statistics(self):
+12 -6
View File
@@ -59,7 +59,7 @@ class SlideLink(models.Model):
slide_id = fields.Many2one('slide.slide', required=True, ondelete='cascade')
name = fields.Char('Title', required=True)
link = fields.Char('External Link', required=True)
link = fields.Char('Link', required=True)
class EmbeddedSlide(models.Model):
@@ -123,11 +123,11 @@ class Slide(models.Model):
description = fields.Text('Description', translate=True)
channel_id = fields.Many2one('slide.channel', string="Channel", required=True)
tag_ids = fields.Many2many('slide.tag', 'rel_slide_tag', 'slide_id', 'tag_id', string='Tags')
is_preview = fields.Boolean('Is Preview', default=False, help="The course is accessible by anyone : the users don't need to join the channel to access the content of the course.")
completion_time = fields.Float('Completion Time', digits=(10, 4), help="The estimated completion time for this slide")
is_preview = fields.Boolean('Allow Preview', default=False, help="The course is accessible by anyone : the users don't need to join the channel to access the content of the course.")
completion_time = fields.Float('Duration', digits=(10, 4), help="The estimated completion time for this slide")
# Categories
is_category = fields.Boolean('Is a category', default=False)
category_id = fields.Many2one('slide.slide', string="Category", compute="_compute_category_id", store=True)
category_id = fields.Many2one('slide.slide', string="Section", compute="_compute_category_id", store=True)
slide_ids = fields.One2many('slide.slide', "category_id", string="Slides")
# subscribers
partner_ids = fields.Many2many('res.partner', 'slide_slide_partner', 'slide_id', 'partner_id',
@@ -138,6 +138,7 @@ class Slide(models.Model):
help="Subscriber information for the current logged in user")
# Quiz related fields
question_ids = fields.One2many("slide.question", "slide_id", string="Questions")
questions_count = fields.Integer(string="Numbers of Questions", compute='_compute_questions_count')
quiz_first_attempt_reward = fields.Integer("First attempt reward", default=10)
quiz_second_attempt_reward = fields.Integer("Second attempt reward", default=7)
quiz_third_attempt_reward = fields.Integer("Third attempt reward", default=5,)
@@ -161,7 +162,7 @@ class Slide(models.Model):
html_content = fields.Html("HTML Content", help="Custom HTML content for slides of type 'Web Page'.", translate=True)
# website
website_id = fields.Many2one(related='channel_id.website_id', readonly=True)
date_published = fields.Datetime('Publish Date')
date_published = fields.Datetime('Publish Date', readonly=True, tracking=True)
likes = fields.Integer('Likes', compute='_compute_user_info', store=True, compute_sudo=False)
dislikes = fields.Integer('Dislikes', compute='_compute_user_info', store=True, compute_sudo=False)
user_vote = fields.Integer('User vote', compute='_compute_user_info', compute_sudo=False)
@@ -214,6 +215,11 @@ class Slide(models.Model):
elif slide.category_id != current_category:
slide.category_id = current_category.id
@api.depends('question_ids')
def _compute_questions_count(self):
for slide in self:
slide.questions_count = len(slide.question_ids)
@api.depends('website_message_ids.res_id', 'website_message_ids.model', 'website_message_ids.message_type')
def _compute_comments_count(self):
for slide in self:
@@ -393,7 +399,7 @@ class Slide(models.Model):
slide = super(Slide, self).create(values)
if slide.is_published:
if slide.is_published and not slide.is_category:
slide._post_publication()
return slide
@@ -257,10 +257,12 @@ odoo.define('website_slides.fullscreen', function (require) {
_onClickTab: function (ev) {
ev.stopPropagation();
var $elem = $(ev.currentTarget);
var isQuiz = $elem.data('isQuiz');
var slideID = parseInt($elem.data('id'));
var slide = findSlide(this.slideEntries, {id: slideID, isQuiz: isQuiz});
this.set('slideEntry', slide);
if ($elem.data('canAccess') === 'True') {
var isQuiz = $elem.data('isQuiz');
var slideID = parseInt($elem.data('id'));
var slide = findSlide(this.slideEntries, {id: slideID, isQuiz: isQuiz});
this.set('slideEntry', slide);
}
},
/**
* Actively changes the active tab in the sidebar so that it corresponds
@@ -710,7 +712,7 @@ odoo.define('website_slides.fullscreen', function (require) {
* Creates slides objects from every slide-list-cells attributes
*/
_getSlides: function (){
var $slides = this.$('.o_wslides_fs_sidebar_list_item');
var $slides = this.$('.o_wslides_fs_sidebar_list_item[data-can-access="True"]');
var slideList = [];
$slides.each(function () {
var slideData = $(this).data();
@@ -92,8 +92,9 @@ odoo.define('website_slides.quiz', function (require) {
}
else if (alert_code === 'slide_quiz_done') {
message = _t('This quiz is already done. Retaking it is not possible.');
} else if (alert_code === 'public_user') {
message = _t("You must be logged to submit the quiz.");
}
else if (alert_code === 'public_user') {
message = _t('You must be logged to submit the quiz.');
}
this.$('.o_wslides_js_lesson_quiz_error span:first').html(message);
this.$('.o_wslides_js_lesson_quiz_error').removeClass('d-none');
@@ -112,10 +113,10 @@ odoo.define('website_slides.quiz', function (require) {
}
}).then(function (quiz_data) {
self.quiz = {
questions: quiz_data.slide_questions,
quizAttemptsCount: quiz_data.quiz_attempts_count,
quizKarmaGain: quiz_data.quiz_karma_gain,
quizKarmaWon: quiz_data.quiz_karma_won
questions: quiz_data.slide_questions || [],
quizAttemptsCount: quiz_data.quiz_attempts_count || 0,
quizKarmaGain: quiz_data.quiz_karma_gain || 0,
quizKarmaWon: quiz_data.quiz_karma_won || 0,
};
});
},
@@ -74,13 +74,13 @@ var SlideUploadDialog = Dialog.extend({
this.$('.alert-warning').remove();
},
/**
* Category and tags management from select2
* Section and tags management from select2
*
* @private
*/
_bindSelect2Dropdown: function () {
var self = this;
this.$('#category_id').select2(this._select2Wrapper(_t('Category'), false,
this.$('#category_id').select2(this._select2Wrapper(_t('Section'), false,
function () {
return self._rpc({
route: '/slides/category/search_read',
@@ -350,6 +350,15 @@ var SlideUploadDialog = Dialog.extend({
}
};
},
/**
* Show the preview/right column and resize the modal
* @private
*/
_showPreviewColumn: function() {
this.$('#o_wslides_js_slide_upload_left_column').removeClass('col').addClass('col-md-6');
this.$('#o_wslides_js_slide_upload_preview_column').removeClass('d-none');
this.$modal.find('.modal-dialog').addClass('modal-lg');
},
/**
* @private
*/
@@ -389,6 +398,8 @@ var SlideUploadDialog = Dialog.extend({
} else {
this.set_title(_t("Upload a document"));
}
this.$modal.find('.modal-dialog').removeClass('modal-lg');
},
_onChangeCanSubmitForm: function (ev) {
if (this.get('can_submit_form')) {
@@ -426,6 +437,7 @@ var SlideUploadDialog = Dialog.extend({
}
buffer = buffer.split(',')[1];
self.file.data = buffer;
self._showPreviewColumn();
});
if (file.type === 'application/pdf') {
@@ -478,6 +490,7 @@ var SlideUploadDialog = Dialog.extend({
self.set('can_submit_form', true);
}
loaded = true;
self._showPreviewColumn();
});
});
});
@@ -510,6 +523,7 @@ var SlideUploadDialog = Dialog.extend({
self._formSetFieldValue('description', data.description);
self.isValidUrl = true;
self._showPreviewColumn();
}
});
},
@@ -7,5 +7,9 @@ $o-kanban-large-record-width: 350px;
.o_slide_rating_kanban_left {
min-width: 80px;
.o_slide_rating_value {
font-size: 4rem;
}
}
}
@@ -1,7 +0,0 @@
$o-kanban-large-record-width: 400px;
.o_kanban_view.o_slide_channel_kanban {
.o_kanban_record {
width: $o-kanban-large-record-width
}
}
@@ -1,6 +1,14 @@
$o-kanban-large-record-width: 400px;
.o_kanban_view.o_slide_kanban {
.o_kanban_record {
width: $o-kanban-large-record-width
}
}
table.o_section_list_view tr.o_data_row.o_is_section {
font-weight: bold;
background-color: #DDD;
border-top: 1px solid #BBB;
border-bottom: 1px solid #BBB;
}
}
@@ -35,6 +35,7 @@
.o_wslides_fs_sidebar {
background-image: linear-gradient(160deg, $o-wslides-color-dark1, $o-wslides-color-dark2);
position: relative;
z-index: $zindex-fixed;
.o_wslides_fs_sidebar_content {
min-width: $o-wslides-fs-side-width;
@@ -6,47 +6,37 @@
<div>
<form action="/slides/channel/add" method="POST" id="slide_channel_add_form">
<input type="hidden" name="csrf_token" t-att-value="csrf_token"/>
<div class="form-group row">
<label for="title" class="col-sm-3 col-form-label">Title</label>
<div class="col-sm-9">
<input type="text" class="form-control" name="name" id="title" placeholder="Course Title" required="1"/>
<p id="title-required" class="text-danger mt-1 mb-0 d-none">Please fill in this field</p>
</div>
<div class="form-group">
<label for="title" class="col-form-label">Title</label>
<input type="text" class="form-control" name="name" id="title" placeholder="Course Title" required="1"/>
<p id="title-required" class="text-danger mt-1 mb-0 d-none">Please fill in this field</p>
</div>
<div class="form-group row">
<label for="tag_ids" class="col-sm-3 col-form-label">Tags</label>
<div class="col-sm-9">
<input type="text" class="form-control" name="tag_ids" id="tag_ids" placeholder="Tags"/>
</div>
<div class="form-group">
<label for="tag_ids" class="col-form-label">Tags</label>
<input type="text" class="form-control" name="tag_ids" id="tag_ids" placeholder="Tags"/>
</div>
<fieldset class="form-group">
<div class="row">
<label for="channel_type" class="col-sm-3">Type</label>
<div class="col-sm-9">
<div class="form-check">
<input class="form-check-input" type="radio" name="channel_type" id="channel_type1" value="training" checked="checked"/>
<span class="form-check-label" for="channel_type1">
Training (with progress)
</span>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="channel_type" id="channel_type2" value="documentation"/>
<span class="form-check-label" for="channel_type2">
Documentation
</span>
</div>
</div>
<label for="channel_type">Type</label>
<div class="form-check">
<input class="form-check-input" type="radio" name="channel_type" id="channel_type1" value="training" checked="checked"/>
<span class="form-check-label" for="channel_type1">
Training (with progress)
</span>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="channel_type" id="channel_type2" value="documentation"/>
<span class="form-check-label" for="channel_type2">
Documentation
</span>
</div>
</fieldset>
<div class="form-group row">
<label for="title" class="col-sm-3">Description</label>
<div class="col-sm-9">
<textarea name="description" id="description" style="height:8em" class="form-control" placeholder="Write here a short description of your first course"/>
</div>
<div class="form-group">
<label for="title">Description</label>
<textarea name="description" id="description" style="height:8em" class="form-control" placeholder="Write here a short description of your first course"/>
</div>
<div class="form-group row">
<label id="communication-label" class="col-sm-3">Review</label>
<div class="col-sm-9 o_wslide_channel_communication_type">
<div class="form-group">
<label id="communication-label">Review</label>
<div class="o_wslide_channel_communication_type">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="allow_comment" name="allow_comment"/>
<span class="form-check-label" for="allow_comment">Allow review on Course</span>
@@ -58,28 +58,25 @@
Slide Type common form part template
-->
<t t-name="website.slide.upload.modal.common">
<div class="form-group row">
<label for="name" class="col-form-label col-md-3">Title</label>
<div class="col-md-9">
<input id="name" name="name" placeholder="Title" class="form-control" required="required"/>
</div>
<div class="form-group">
<label for="name" class="col-form-label">Title</label>
<input id="name" name="name" placeholder="Title" class="form-control" required="required"/>
</div>
<div t-if="!widget.defaultCategoryID" class="form-group row">
<label for="category_id" class="col-form-label col-md-3">Category</label>
<div class="controls col-md-9">
<input class="form-control" id="category_id"/>
</div>
<div t-if="!widget.defaultCategoryID" class="form-group">
<label for="category_id" class="col-form-label">Section</label>
<input class="form-control" id="category_id"/>
</div>
<div class="form-group row">
<label for="tag_ids" class="col-form-label col-md-3">Tags</label>
<div class="controls col-md-9">
<input id="tag_ids" name="tag_ids" type="hidden"/>
</div>
<div class="form-group">
<label for="tag_ids" class="col-form-label">Tags</label>
<input id="tag_ids" name="tag_ids" type="hidden"/>
</div>
<div class="form-group row">
<label for="duration" class="col-form-label col-md-3">Completion Time (minutes)</label>
<div class="col-md-9">
<div class="form-group">
<label for="duration" class="col-form-label">Duration</label>
<div class="input-group">
<input type="number" id="duration" name="duration" placeholder="Estimated slide completion time" class="form-control"/>
<div class="input-group-prepend">
<span class="input-group-text">Minutes</span>
</div>
</div>
</div>
</t>
@@ -90,27 +87,23 @@
<t t-name="website.slide.upload.modal.presentation">
<div>
<form class="clearfix">
<div class="form-group row">
<div class="col-md-4">
<div class="row">
<div id="o_wslides_js_slide_upload_left_column" class="col">
<div class="form-group">
<label for="upload" class="col-form-label">Choose a PDF or an Image</label>
<input id="upload" name="file" class="form-control" accept="image/*,application/pdf" type="file" required="required"/>
</div>
<canvas id="data_canvas" class="d-none"></canvas>
<t t-call="website.slide.upload.modal.common"/>
</div>
<div id="o_wslides_js_slide_upload_preview_column" class="col-md-6 d-none">
<div class="img-thumbnail">
<div class="o_slide_preview">
<img src="/website_slides/static/src/img/document.png" id="slide-image" title="Content Preview" alt="Content Preview" class="img-fluid"/>
</div>
</div>
</div>
<div class="col-md-8">
<ul class="list-group">
<li class="list-group-item">
<h5 class="list-group-item-heading">
<label for="upload" class="col-form-label">PDF or Image File</label>
</h5>
<input id="upload" name="file" class="form-control" accept="image/*,application/pdf" type="file" required="required"/>
</li>
</ul>
</div>
</div>
<canvas id="data_canvas" class="d-none"></canvas>
<t t-call="website.slide.upload.modal.common"/>
</form>
</div>
</t>
@@ -118,27 +111,23 @@
<t t-name="website.slide.upload.modal.webpage">
<div>
<form class="clearfix">
<t t-call="website.slide.upload.modal.common"/>
<div class="form-group row">
<div class="col-md-4">
<div class="row">
<div id="o_wslides_js_slide_upload_left_column" class="col">
<div class="form-group">
<label for="upload" class="col-form-label">Choose a Cover Image</label>
<input id="upload" name="file" class="form-control" accept="image/*" type="file" required="required"/>
</div>
<canvas id="data_canvas" class="d-none"></canvas>
<t t-call="website.slide.upload.modal.common"/>
</div>
<div id="o_wslides_js_slide_upload_preview_column" class="col-md-6 d-none">
<div class="img-thumbnail">
<div class="o_slide_preview">
<img src="/website_slides/static/src/img/document.png" id="slide-image" title="Content Preview" alt="Content Preview" class="img-fluid"/>
</div>
</div>
</div>
<div class="col-md-8">
<ul class="list-group">
<li class="list-group-item">
<h5 class="list-group-item-heading">
<label for="upload" class="col-form-label">Cover Image File</label>
</h5>
<input id="upload" name="file" class="form-control" accept="image/*" type="file" required="required" data-prevent-onchange="1"/>
</li>
</ul>
</div>
</div>
<canvas id="data_canvas" class="d-none"></canvas>
</form>
</div>
</t>
@@ -146,27 +135,23 @@
<t t-name="website.slide.upload.modal.video">
<div>
<form class="clearfix">
<div class="form-group row">
<div class="col-md-4">
<div class="row">
<div id="o_wslides_js_slide_upload_left_column" class="col">
<div class="form-group">
<label for="url" class="col-form-label">Youtube Link</label>
<input id="url" name="url" class="form-control" placeholder="Youtube Video URL" required="required"/>
</div>
<canvas id="data_canvas" class="d-none"></canvas>
<t t-call="website.slide.upload.modal.common"/>
</div>
<div id="o_wslides_js_slide_upload_preview_column" class="col-md-6 d-none">
<div class="img-thumbnail">
<div class="o_slide_preview">
<img src="/website_slides/static/src/img/document.png" id="slide-image" title="Content Preview" alt="Content Preview" class="img-fluid"/>
</div>
</div>
</div>
<div class="col-md-8">
<ul class="list-group">
<li class="list-group-item">
<h5 class="list-group-item-heading">
<label for="url" class="col-form-label">Youtube Link</label>
</h5>
<input id="url" name="url" class="form-control" placeholder="Youtube Video URL" required="required"/>
</li>
</ul>
</div>
</div>
<canvas id="data_canvas" class="d-none"></canvas>
<t t-call="website.slide.upload.modal.common"/>
</form>
</div>
</t>
@@ -23,7 +23,7 @@ var addVideoToSection = function (sectionName, saveAsDraft) {
var base_steps = [
{
content: 'eLearning: add content to section',
trigger: 'div.o_wslides_slide_list_category_header:contains("' + sectionName + '") a:contains("Add content")',
trigger: 'div.o_wslides_slide_list_category_header:contains("' + sectionName + '") a:contains("Add Content")',
}, {
content: 'eLearning: click on video',
trigger: 'a[data-slide-type=video]',
@@ -57,7 +57,7 @@ var addWebPageToSection = function (sectionName, pageName) {
return [
{
content: 'eLearning: add content to section',
trigger: 'div.o_wslides_slide_list_category_header:contains("' + sectionName + '") a:contains("Add content")',
trigger: 'div.o_wslides_slide_list_category_header:contains("' + sectionName + '") a:contains("Add Content")',
}, {
content: 'eLearning: click on webpage',
trigger: 'a[data-slide-type=webpage]',
+1 -2
View File
@@ -4,8 +4,7 @@
<template id="assets_backend" inherit_id="web.assets_backend" name="Slides Backend Assets">
<xpath expr="." position="inside">
<link rel="stylesheet" type="text/scss" href="/website_slides/static/src/scss/rating_rating_views.scss"/>
<link rel="stylesheet" type="text/scss" href="/website_slides/static/src/scss/slide_channel_views.scss"/>
<link rel="stylesheet" type="text/scss" href="/website_slides/static/src/scss/category_backend.scss" t-ignore="true"/>
<link rel="stylesheet" type="text/scss" href="/website_slides/static/src/scss/slide_views.scss"/>
<script type="text/javascript" src="/website_slides/static/src/js/slide_category_one2many.js"/>
<script type="text/javascript" src="/website_slides/static/src/js/rating_field_backend.js"/>
</xpath>
@@ -18,14 +18,15 @@
<t t-set="empty_star" t-value="5 - (val_integer + Math.ceil(val_decimal))"/>
<div class="oe_kanban_card oe_kanban_global_click">
<div class="d-flex flex-row">
<div class="o_slide_rating_kanban_left">
<div class="o_slide_rating_kanban_left mr-3">
<h1 class="o_slide_rating_value text-center text-primary" t-esc="val_stars"/>
<t t-foreach="_.range(0, val_integer)" t-as="num">
<i class="fa fa-star" role="img" aria-label="One star" title="One star"></i>
<i class="fa fa-star" role="img"></i>
</t>
<t t-if="val_decimal">
<i class="fa fa-star-half-o" role="img" aria-label="Half a star" title="Half a star"></i>
<i class="fa fa-star-half-o" role="img"></i>
</t>
<t t-foreach="_.range(0, empty_star)" t-as="num" role="img" t-attf-aria-label="#{empty_star} on 5" t-attf-title="#{empty_star} on 5">
<t t-foreach="_.range(0, empty_star)" t-as="num" role="img">
<i class="fa fa-star text-black-25"></i>
</t>
</div>
@@ -36,14 +37,14 @@
</div>
</div>
<div class="o_kanban_card_content mt0 d-flex flex-column">
<span>for
<span>
<i class="fa fa-folder mr-2"></i>
<a type="object" name="action_open_rated_object" t-att-title="record.res_name.raw_value">
<field name="res_name" />
</a>
</span>
<span><i class="fa fa-clock-o mr-2"/>on <field name="create_date" /></span>
<span><i class="fa fa-clock-o mr-2"/> <field name="create_date" /></span>
<div class="d-flex mt-2">
<i t-if="record.feedback.raw_value" class="fa fa-comment mr-2" t-att-title="record.feedback.raw_value" t-att-aria-label="record.feedback.raw_value" role="img"/>
<span t-esc="record.feedback.raw_value"/>
</div>
</div>
@@ -74,19 +75,32 @@
</record>
<record id="rating_rating_view_graph_slide_channel" model="ir.ui.view">
<field name="name">rating.rating.graph</field>
<field name="model">rating.rating</field>
<field name="priority">20</field>
<field name="arch" type="xml">
<graph string="Rating Average" type="bar" stacked="False">
<field name="name">rating.rating.view.graph.slides</field>
<field name="model">rating.rating</field>
<field name="priority">20</field>
<field name="arch" type="xml">
<graph string="Rating Average" type="bar">
<field name="res_name" type="row"/>
<field name="rating" type="col"/>
<field name="rating" type="measure"/>
</graph>
</field>
</record>
<record id="rating_rating_view_pivot_slide_channel" model="ir.ui.view">
<field name="name">rating.rating.view.pivot.slides</field>
<field name="model">rating.rating</field>
<field name="priority">20</field>
<field name="arch" type="xml">
<pivot>
<field name="res_name" type="row"/>
<field name="rating_text" type="col"/>
<field name="rating" type="measure"/>
</pivot>
</field>
</record>
<record id="rating_rating_action_slide_channel" model="ir.actions.act_window">
<field name="name">Courses Rating</field>
<field name="name">Rating</field>
<field name="res_model">rating.rating</field>
<field name="view_mode">kanban,tree,graph,pivot,form</field>
<field name="domain">[('consumed', '=', True), ('res_model', '=', 'slide.channel')]</field>
@@ -101,17 +115,33 @@
</record>
<record id="rating_rating_action_slide_channel_report" model="ir.actions.act_window">
<field name="name">Courses Rating</field>
<field name="name">Rating</field>
<field name="res_model">rating.rating</field>
<field name="view_mode">graph,tree</field>
<field name="domain">[('consumed', '=', True), ('res_model', '=', 'slide.channel')]</field>
<field name="context">{}</field>
<field name="search_view_id" ref="rating_rating_view_search_slide_channel"/>
<field name="view_id" ref="rating_rating_view_graph_slide_channel"/>
<field name="help" type="html">
<p class="o_view_nocontent_empty_folder">
There is no rating for these courses at the moment
</p>
</field>
</record>
<record id="rating_rating_action_slide_channel_report_view_graph" model="ir.actions.act_window.view">
<field name="act_window_id" ref="rating_rating_action_slide_channel_report"/>
<field name="sequence">1</field>
<field name="view_mode">graph</field>
<field name="view_id" ref="rating_rating_view_graph_slide_channel"/>
</record>
<record id="rating_rating_action_slide_channel_report_view_pivot" model="ir.actions.act_window.view">
<field name="act_window_id" ref="rating_rating_action_slide_channel_report"/>
<field name="sequence">2</field>
<field name="view_mode">pivot</field>
<field name="view_id" ref="rating_rating_view_pivot_slide_channel"/>
</record>
<record id="rating_rating_action_slide_channel_report_view_tree" model="ir.actions.act_window.view">
<field name="act_window_id" ref="rating_rating_action_slide_channel_report"/>
<field name="sequence">3</field>
<field name="view_mode">tree</field>
<field name="view_id" eval="False"/>
</record>
</odoo>
@@ -3,7 +3,7 @@
<data>
<!-- SLIDE.CHANNEL VIEWS -->
<record model="ir.ui.view" id="view_slide_channel_form">
<field name="name">slide.channel.form</field>
<field name="name">slide.channel.view.form</field>
<field name="model">slide.channel</field>
<field name="arch" type="xml">
<form string="Channels">
@@ -12,6 +12,16 @@
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button name="action_view_slides"
type="object"
icon="fa-files-o"
class="oe_stat_button"
groups="website.group_website_publisher">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value"><field name="total_slides" nolabel="1"/></span>
<span name="total_slides_label" class="o_stat_text">Contents</span>
</div>
</button>
<button name="action_redirect_to_done_members"
type="object"
icon="fa-trophy"
@@ -19,7 +29,7 @@
groups="website.group_website_publisher">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value"><field name="members_done_count" nolabel="1"/></span>
<span name="stat_label" class="o_stat_text">Finished</span>
<span name="members_done_count_label" class="o_stat_text">Finished</span>
</div>
</button>
<button name="action_redirect_to_members"
@@ -45,12 +55,11 @@
<label for="name" class="oe_edit_only" string="Course Title"/>
<h1><field name="name" default_focus="1" placeholder="Course Title"/></h1>
</div>
<group>
<group>
<field name="active" invisible="1"/>
<field name="tag_ids" widget="many2many_tags" placeholder="Tags"/>
</group>
</group>
<div>
<field name="active" invisible="1"/>
<label for="tag_ids"/>
<field name="tag_ids" widget="many2many_tags" placeholder="Tags"/>
</div>
<notebook colspan="4">
<page name="content" string="Content">
<field name="slide_ids" colspan="4" nolabel="1" widget="slide_category_one2many" context="{'default_channel_id': active_id}">
@@ -61,7 +70,6 @@
<field name="completion_time" attrs="{'invisible': [('slide_type', '=', 'category')]}"/>
<field name="total_views" attrs="{'invisible': [('slide_type', '=', 'category')]}"/>
<field name="is_category" invisible="1"/>
<control>
<create name="add_slide_section" string="Add Section" context="{'default_is_category': True}"/>
<create name="add_slide_lesson" string="Add Content"/>
@@ -81,15 +89,15 @@
</group>
<group name="access_rights" string="Access Rights">
<field name="enroll" widget="radio" options="{'horizontal': true}"/>
<field name="upload_group_ids" string="New Content" widget="many2many_tags"/>
<field name="upload_group_ids" widget="many2many_tags" groups="base.group_no_one"/>
<field name="enroll_group_ids" widget="many2many_tags" groups="base.group_no_one"/>
</group>
</group>
<group>
<group name="communication" string="Communication">
<field string="Allow Rating" name="allow_comment"/>
<field name="publish_template_id" domain="[('model','=','slide.slide')]"/>
<field name="share_template_id" domain="[('model','=','slide.slide')]"/>
<field name="publish_template_id" domain="[('model','=','slide.slide')]" groups="base.group_no_one"/>
<field name="share_template_id" domain="[('model','=','slide.slide')]" groups="base.group_no_one"/>
</group>
<group name="display" string="Display">
<field name="visibility" widget="radio"/>
@@ -119,8 +127,6 @@
<field name="nbr_infographic"/>
<field name="nbr_quiz" string="Quizzes"/>
<field name="nbr_webpage"/>
<separator/>
<field name="total_slides"/>
</group>
<group name="view_stats">
<field name="total_views" string="Visits"/>
@@ -202,7 +208,7 @@
<field name="name">slide.channel.view.kanban</field>
<field name="model">slide.channel</field>
<field name="arch" type="xml">
<kanban string="eLearning Overview" class="o_emphasize_colors o_kanban_dashboard o_slide_channel_kanban breadcrumb_item active" edit="false">
<kanban string="eLearning Overview" class="o_emphasize_colors o_kanban_dashboard o_slide_kanban breadcrumb_item active" edit="false">
<field name="color"/>
<field name="website_published"/>
<templates>
@@ -241,23 +247,21 @@
</div>
</div>
<div class="container o_kanban_card_content mt0">
<div class="row">
<div class="row mb16">
<div class="col-6 o_kanban_primary_left">
<button class="btn btn-primary" name="open_website_url" type="object">View course</button>
</div>
<div class="col-6 o_kanban_primary_right">
<div class="d-flex">
<span class="mr-auto"><label for="rating_avg" class="mb0">Rating</label></span>
<a name="action_view_ratings" type="object">
<field name="rating_count"/><span class="ml-2">reviews</span> (<field name="rating_avg_stars"/>/5)
</a>
<a name="action_view_ratings" type="object" class="mr-auto"><field name="rating_count"/> reviews</a>
<span><field name="rating_avg_stars"/> / 5</span>
</div>
<div class="d-flex">
<span class="mr-auto"><label for="total_views" class="mb0">Views</label></span>
<field name="total_views"/>
</div>
<div class="d-flex" name="info_total_time">
<span class="mr-auto"><label for="total_time" class="mb0">Completion Time</label></span>
<span class="mr-auto"><label for="total_time" class="mb0">Duration</label></span>
<field name="total_time" widget="float_time"/>
</div>
</div>
@@ -50,10 +50,10 @@
/>
<div class="oe_title">
<div>
<label for="name" string="Name"/>
<label for="name" string="Content Title" class="oe_edit_only"/>
</div>
<h1>
<field name="name" default_focus="1" placeholder="Title"/>
<field name="name" default_focus="1" placeholder="e.g. How to grow your business with Odoo?"/>
<field name="is_category" invisible="1"/>
</h1>
<field name="tag_ids" attrs="{'invisible': [('is_category', '=', True)]}" widget="many2many_tags" placeholder="Tags..."/>
@@ -63,6 +63,7 @@
<group>
<group name="lesson_details">
<field name="active" invisible="1"/>
<field name="channel_id"/>
<field name="slide_type"/>
<field name="url" attrs="{
'required': [('slide_type', 'in', ('video'))],
@@ -74,28 +75,26 @@
</group>
<group name="related_details">
<field name="user_id"/>
<field name="channel_id"/>
<field name="website_id" groups="website.group_multi_website"/>
<field name="website_url"/>
<field name="is_preview"/>
<label for="completion_time" string="Completion Time"/>
<label for="completion_time"/>
<div>
<field name="completion_time" widget="float_time" class="oe_inline"/>
<span> hours</span>
</div>
<field name="date_published" string="Published Date"/>
<field name="date_published" string="Published Date" attrs="{'invisible': [('date_published', '=', False)]}" groups="base.group_no_one"/>
</group>
</group>
</page>
<page name="description" string="Description">
<field name="description"/>
<field name="description" placeholder="e.g. In this video, we'll give you the keys on how Odoo can help you to grow your business. At the end, we'll propose you a quiz to test your knowledge."/>
</page>
<page string="External Links" name="external_links" >
<group>
<field name="link_ids" widget="one2many" nolabel="1">
<tree editable="top">
<field name="name"/>
<field name="link" widget="url"/>
<field name="link" widget="url" placeholder="e.g. https://www.odoo.com"/>
</tree>
</field>
</group>
@@ -123,15 +122,15 @@
<page name="statistics" string="Statistics">
<group>
<group name="view_statistics" string="Views">
<field string="Member views" name="slide_views"/>
<field string="Public views" name="public_views" readonly="1"/>
<field string="Total views" name="total_views"/>
<field string="Member" name="slide_views"/>
<field string="Public" name="public_views" readonly="1"/>
<field string="Total" name="total_views"/>
<hr attrs="{'invisible': [('channel_allow_comment', '!=', True), ('channel_type', '=', 'training')]}"/>
<field name="channel_type" invisible="1" readonly="1"/>
<field name="channel_allow_comment" invisible="1" readonly="1"/>
<field name="likes" attrs="{'invisible': [('channel_type', '=', 'training')]}"/>
<field name="dislikes" attrs="{'invisible': [('channel_type', '=', 'training')]}"/>
<field name="comments_count" attrs="{'invisible': [('channel_allow_comment', '!=', True)]}"/>
<field name="comments_count" string="Comments" attrs="{'invisible': [('channel_allow_comment', '!=', True)]}"/>
</group>
</group>
</page>
@@ -149,10 +148,11 @@
<field name="name">slide.slide.view.kanban</field>
<field name="model">slide.slide</field>
<field name="arch" type="xml">
<kanban edit="false">
<kanban edit="false" class="o_slide_kanban">
<field name="id"/>
<field name="channel_id"/>
<field name="slide_type"/>
<field name="user_id"/>
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click o_kanban_record_has_image_fill">
@@ -171,21 +171,23 @@
</div>
<div class="oe_kanban_details d-flex flex-column">
<strong class="o_kanban_record_title oe_partner_heading"><field name="display_name"/></strong>
<div class="o_kanban_tags_section">
<div class="text-mutex"><field name="channel_id"/></div>
<div class="o_kanban_tags_section mb-2">
<span class="oe_kanban_list_many2many">
<field name="tag_ids" widget="many2many_tags"/>
</span>
</div>
<span class="text-primary"><field name="channel_id"/></span>
<span class="text-primary"><field name="user_id"/></span>
<div class=" mt-auto d-flex justify-content-between">
<span class="badge badge-pill">
<div class="o_kanban_record_bottom mt-auto d-flex justify-content-between align-items-end">
<span>
<i class="fa fa-clock-o mr-2" aria-label="Duration" role="img" title="Duration"/><field name="completion_time" widget="float_time"/>
</span>
<span class="badge badge-pill">
<span>
<i class="fa fa-question mr-2" aria-label="Number of Questions" role="img" title="Number of Questions"/><field name="questions_count"/>
</span>
<span>
<i class="fa fa-eye mr-2" aria-label="Views" role="img" title="Views"/><field name="total_views"/>
</span>
<span class="badge badge-pill">
<span>
<t t-if="record.slide_type.raw_value == 'infographic'">
<i class="fa fa-file-image-o mr-2" aria-label="Infographic" role="img" title="Infographic"/>
</t>
@@ -201,6 +203,7 @@
<t t-else=""><i class="fa fa-file-pdf-o mr-2" aria-label="Document" role="img" title="Document"/></t>
<field name="slide_type"/>
</span>
<img t-att-src="kanban_image('res.users', 'image_64', record.user_id.raw_value)" t-att-title="record.user_id.value" t-att-alt="record.user_id.value" width="24" height="24" class="oe_kanban_avatar"/>
</div>
</div>
</div>
@@ -265,6 +268,7 @@
<field name="res_model">slide.slide</field>
<field name="view_mode">kanban,tree,form</field>
<field name="context"></field>
<field name="domain">[('is_category', '=', False)]</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Add a new lesson
@@ -237,6 +237,37 @@
</t>
</div>
</div>
<div class="modal fade" id="slideChannelShareModal" tabindex="-1" role="dialog" aria-labelledby="slideChannelShareModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="slideChannelShareModalLabel">
Share
</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span>&amp;times;</span>
</button>
</div>
<div class="modal-body">
<h5>Share on Social Networks</h5>
<t t-call="website_slides.slides_share">
<t t-set="record" t-value="channel"/>
</t>
<h5 class="mt16">Share Link</h5>
<div class="input-group">
<input type="text" class="form-control o_wslides_js_share_link" readonly="readonly" onclick="this.select();"
t-att-value="channel.website_url"/>
<div class="input-group-append">
<button class="btn btn-sm btn-primary o_clipboard_button" >
<span class="fa fa-clipboard"> Copy Text</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</t>
</template>
@@ -299,47 +330,6 @@
data-toggle="modal" data-target="#slideChannelShareModal">
<i class="fa fa-share-square fa-fw"/> Share
</button>
<div class="modal fade" id="slideChannelShareModal" tabindex="-1" role="dialog" aria-labelledby="slideChannelShareModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="slideChannelShareModalLabel">
Share
</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span>&amp;times;</span>
</button>
</div>
<div class="modal-body">
<h5>Share on Social Networks</h5>
<t t-call="website_slides.slides_share">
<t t-set="record" t-value="channel"/>
</t>
<h5 class="mt16">Share Link</h5>
<div class="input-group">
<input type="text" class="form-control o_wslides_js_share_link" readonly="readonly" onclick="this.select();"
t-att-value="channel.website_url"/>
<div class="input-group-append">
<button class="btn btn-sm btn-primary o_clipboard_button" >
<span class="fa fa-clipboard"> Copy Text</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div t-if="channel.can_upload" class="justify-content-center mt-1">
<a role="button" class="btn btn-link btn-block o_wslides_js_slide_upload" title="Upload Presentation"
aria-label="Upload Presentation" href="#"
t-att-data-modules-to-install="modules_to_install"
t-att-data-channel-id="channel.id"
t-att-data-can-upload="channel.can_upload"
t-att-data-can-publish="channel.can_publish">
<i class="fa fa-cloud-upload fa-fw"/> Upload
</a>
</div>
</div>
</div>
@@ -353,7 +343,7 @@
<t t-foreach="category_data" t-as="category">
<t t-set="category_id" t-value="category['id'] if category['id'] else None"/>
<li t-if="category['total_slides'] or channel.can_publish" t-att-class="'o_wslides_slide_list_category o_wslides_js_list_item mb-2' if category_id else 'mb-3'" t-att-data-slide-id="category_id" t-att-data-category-id="category_id">
<li t-if="category['total_slides'] or channel.can_publish" t-att-class="'o_wslides_slide_list_category o_wslides_js_list_item mb-2' if category_id else 'mt-4'" t-att-data-slide-id="category_id" t-att-data-category-id="category_id">
<div t-att-data-category-id="category_id"
t-att-class="'o_wslides_slide_list_category_header position-relative d-flex justify-content-between align-items-center %s' % ('o_wslides_js_category bg-white shadow-sm border-bottom-0' if category_id and channel.can_upload else 'border-0 py-1')">
<div t-att-class="'d-flex align-items-center pl-3 %s' % ('o_wslides_slides_list_drag' if channel.can_publish else '')">
@@ -374,7 +364,7 @@
t-att-data-category-id="category_id"
t-att-data-can-upload="channel.can_upload"
t-att-data-can-publish="channel.can_publish">
<i class="fa fa-plus mr-1"/> <span class="d-none d-md-inline-block">Add content</span>
<i class="fa fa-plus mr-1"/> <span class="d-none d-md-inline-block">Add Content</span>
</a>
</div>
</div>
@@ -399,10 +389,10 @@
t-att-data-modules-to-install="modules_to_install"
t-att-data-channel-id="channel.id"
t-att-data-can-upload="channel.can_upload"
t-att-data-can-publish="channel.can_publish"><i class="fa fa-plus mr-1 align-middle"/><span>Add Content</span></a>
t-att-data-can-publish="channel.can_publish"><i class="fa fa-plus mr-1"/><span>Add Content</span></a>
<a class="o_wslides_js_slide_section_add border btn btn-light bg-white" t-attf-channel_id="#{channel.id}"
href="#" role="button"
groups="website.group_website_publisher"><i class="fa fa-folder-o mr-1 align-middle"/><span>Add Section</span></a>
groups="website.group_website_publisher"><i class="fa fa-folder-o mr-1"/><span>Add Section</span></a>
</div>
</div>
<div t-field="channel.description_html"/>
@@ -416,14 +406,16 @@
<t t-call="website_slides.slide_icon">
<t t-set="icon_class" t-value="'py-2 mx-2'"/>
</t>
<a t-if="slide.is_preview or channel.is_member or channel.can_publish" class="o_wslides_js_slides_list_slide_link mr-auto" t-attf-href="/slides/slide/#{slug(slide)}">
<span t-field="slide.name"/>
</a>
<span t-else="" class="mr-auto">
<span t-esc="slide.name"/>
</span>
<div class="text-truncate mr-auto">
<a t-if="slide.is_preview or channel.is_member or channel.can_publish" class="o_wslides_js_slides_list_slide_link" t-attf-href="/slides/slide/#{slug(slide)}">
<span t-field="slide.name"/>
</a>
<span t-else="">
<span t-esc="slide.name"/>
</span>
</div>
<div class="d-flex flex-wrap">
<div class="d-flex flex-row">
<span t-if="slide.question_ids" t-att-class="'badge font-weight-bold px-2 py-1 m-1 %s' % ('badge-success' if channel_progress[slide.id].get('completed') else 'badge-warning')">
<i t-attf-class="fa fa-fw #{'fa-check' if channel_progress[slide.id].get('completed') else 'fa-flag'}"/>
<t t-esc="channel_progress[slide.id].get('quiz_karma_won', 0) if channel_progress[slide.id].get('completed') else channel_progress[slide.id].get('quiz_karma_gain', 0)"/> xp
@@ -435,12 +427,12 @@
<span t-elif="slide.is_preview and not channel.is_member" class="badge badge-success font-weight-normal px-2 py-1 m-1">Free preview</span>
</div>
<div t-if="channel.is_member or channel.can_publish" class="pt-2 pb-2 border-left ml-2 mr-2 pl-2 d-flex align-items-center o_wslides_slides_list_slide_controls">
<div t-if="channel.is_member or channel.can_publish" class="pt-2 pb-2 border-left ml-2 mr-2 pl-2 d-flex flex-row align-items-center o_wslides_slides_list_slide_controls">
<t t-if="channel.is_member">
<i t-if="not channel_progress[slide.id].get('completed')" class="check-done fa fa-circle-o text-500 px-2"></i>
<i t-else="" class="check-done text-success fa fa-check-circle px-2"></i>
</t>
<span t-if="channel.can_publish" class="d-none d-md-inline-block">
<span t-if="channel.can_publish" class="d-none d-md-flex">
<a t-if="slide.slide_type == 'webpage'" class="px-2 o_text_link text-primary" target="_blank" t-attf-href="/slides/slide/#{slug(slide)}?enable_editor=1"><span class="fa fa-pencil"/></a>
<a t-else="" class="px-2 o_text_link text-primary" target="_blank" t-attf-href="/web#id=#{slide.id}&amp;action=#{slide_action}&amp;model=slide.slide&amp;view_type=form"><span class="fa fa-pencil"/></a>
<a href="#" t-att-data-slide-id="slide.id" class="o_text_link text-danger px-2 o_wslides_js_slide_archive"><span class="fa fa-trash"/></a>
@@ -11,10 +11,10 @@
<a href="/profile/users" class="btn btn-primary btn-lg">Go meet them</a>
</div>
<div class="col-12 col-md-5" t-if="top3_users">
<div class="ml-4">
<div class="ml-4 ml-md-2 mt-3">
<t t-foreach="top3_users" t-as="user">
<img class="rounded-circle img-fluid shadow ml-n4"
style="border: 6px solid white"
style="border: 6px solid white; max-width: 40%"
t-attf-src="/web/image/res.users/#{user['id']}/image_128"/>
</t>
</div>
@@ -137,10 +137,13 @@
</a>
<ul class="collapse show p-0 m-0 list-unstyled" t-att-id="('collapse-%s') % (category.id if category else 0)" >
<t t-foreach="category_slide_ids" t-as="aside_slide">
<t t-set="slide_completed" t-value="channel_progress[aside_slide.id].get('completed')"/>
<t t-set="is_member" t-value="aside_slide.channel_id.is_member"/>
<t t-set="can_access" t-value="aside_slide.is_preview or is_member or aside_slide.channel_id.can_publish"/>
<li class="p-0 pb-1">
<a t-att-href="'/slides/slide/%s' % (slug(aside_slide))"
t-att-class="'o_wslides_lesson_aside_list_link d-flex align-items-top px-2 pt-1 text-decoration-none %s' % ('bg-100 py-1 active' if aside_slide == slide else '')">
<div t-if="aside_slide.channel_id.is_member" >
<a t-att-href="'/slides/slide/%s' % (slug(aside_slide)) if can_access else '#'"
t-att-class="'o_wslides_lesson_aside_list_link d-flex align-items-top px-2 pt-1 text-decoration-none %s%s' % (('bg-100 py-1 active' if aside_slide == slide else ''), 'text-muted' if not can_access else '')">
<div t-if="is_member" >
<i t-att-id="'o_wslides_lesson_aside_slide_check_%s' % (aside_slide.id)"
t-att-class="'mr-1 fa fa-fw %s' % ('text-success fa-check-circle' if channel_progress[aside_slide.id].get('completed') else 'text-600 fa-circle-o')">
</i>
@@ -160,15 +163,22 @@
<ul t-if="aside_slide.link_ids or aside_slide.question_ids" class="list-group px-2 mb-1 list-unstyled">
<t t-foreach="aside_slide.link_ids" t-as="resource">
<li class="pl-4">
<a t-attf-href="#{resource.link}" target="new" class="text-decoration-none small">
<a t-if="can_access" t-att-href="resource.link" target="new" class="text-decoration-none small">
<i class="fa fa-link mr-1"/><span t-field="resource.name"/>
</a>
<span t-else="" class="text-decoration-none text-muted small">
<i class="fa fa-link mr-1"/><span t-field="resource.name"/>
</span>
</li>
</t>
<li class="pl-4">
<a t-if="aside_slide.question_ids and aside_slide.slide_type != 'quiz'" t-att-href="'/slides/slide/%s#lessonQuiz' % (slug(aside_slide))" class="o_wslides_lesson_aside_list_link text-decoration-none small text-600">
<a t-if="can_access and aside_slide.question_ids and aside_slide.slide_type != 'quiz'" t-att-href="'/slides/slide/%s#lessonQuiz' % (slug(aside_slide))" class="o_wslides_lesson_aside_list_link text-decoration-none small text-600">
<i class="fa fa-flag text-warning"/> Quiz
</a>
<span t-elif="not can_access and aside_slide.question_ids and aside_slide.slide_type != 'quiz'"
class="o_wslides_lesson_aside_list_link text-decoration-none small text-600 text-muted">
<i class="fa fa-flag text-warning"/> Quiz
</span>
</li>
</ul>
</li>
@@ -82,8 +82,10 @@
<t t-foreach="slides" t-as="slide">
<t t-set="slide_completed" t-value="channel_progress[slide.id].get('completed')"/>
<t t-set="is_member" t-value="slide.channel_id.is_member"/>
<t t-set="can_access" t-value="slide.is_preview or is_member or slide.channel_id.can_publish"/>
<li t-att-class="'o_wslides_fs_sidebar_list_item d-flex align-items-top py-1 %s' % ('active' if slide.id == current_slide.id else '')"
t-att-data-id="slide.id"
t-att-data-can-access="can_access"
t-att-data-name="slide.name"
t-att-data-type="slide.slide_type"
t-att-data-slug="slug(slide)"
@@ -98,20 +100,30 @@
<i t-if="not slide_completed and is_member" class="fa fa-circle-thin fa-fw" t-att-data-slide-id="slide.id"/>
</span>
<div class="ml-2">
<a class="d-block pt-1" href="#">
<a t-if="can_access" class="d-block pt-1" href="#">
<div class="d-flex ">
<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 pt-1" href="#">
<div class="d-flex ">
<t t-call="website_slides.slide_icon"/>
<div class="o_wslides_fs_slide_name text-muted" t-esc="slide.name"/>
</div>
</span>
<ul class="list-unstyled w-100 pt-2 small" t-if="slide.link_ids or (slide.question_ids and not slide.slide_type =='quiz')" >
<li t-if="slide.link_ids" t-foreach="slide.link_ids" t-as="link" class="pl-0 mb-1">
<a class="o_wslides_fs_slide_link" t-att-href="link.link" target="_blank">
<a t-if="can_access" class="o_wslides_fs_slide_link" t-att-href="link.link" target="_blank">
<i class="fa fa-link mr-2"/><span t-esc="link.name"/>
</a>
<span t-else="" class="o_wslides_fs_slide_link text-muted">
<i class="fa fa-link mr-2"/><span t-esc="link.name"/>
</span>
</li>
<li class="o_wslides_fs_sidebar_list_item pl-0 mb-1" t-if="slide.question_ids and not slide.slide_type == 'quiz'"
t-att-data-id="slide.id"
t-att-data-can-access="can_access"
t-att-data-name="slide.name"
t-att-data-type="slide.slide_type"
t-att-data-slug="slug(slide)"
@@ -119,9 +131,12 @@
t-att-data-is-quiz="1"
t-att-data-completed="1 if slide_completed else 0"
t-att-data-readonly="not is_member">
<a class="o_wslides_fs_slide_quiz" href="#" t-att-index="i">
<a t-if="can_access" class="o_wslides_fs_slide_quiz" href="#" t-att-index="i">
<i class="fa fa-flag-checkered text-warning mr-2"/>Quiz
</a>
<span t-else="" class="text-muted">
<i class="fa fa-flag-checkered text-warning mr-2"/>Quiz
</span>
</li>
</ul>
</div>
@@ -12,6 +12,7 @@
'website_forum'
],
'data': [
'security/ir.model.access.csv',
'views/forum_views.xml',
'views/slide_channel_views.xml',
'views/website_slides_menu_views.xml',
@@ -1,2 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_forum_forum_website_publisher,access.forum.forum.website.publisher,model_forum_forum,base.group_website_publisher,1,1,1,0
access_forum_forum_website_publisher,access.forum.forum.website.publisher,model_forum_forum,website.group_website_publisher,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_forum_forum_website_publisher access.forum.forum.website.publisher model_forum_forum base.group_website_publisher website.group_website_publisher 1 1 1 0
@@ -2,7 +2,7 @@
<templates xml:space="preserve">
<t t-extend="website.slide.channel.create">
<t t-jquery="#communication-label" t-operation="replace">
<label id="communication-label" class="col-sm-3">Communication</label>
<label id="communication-label">Communication</label>
</t>
<t t-jquery=".o_wslide_channel_communication_type" t-operation="append">
<div class="form-check">
@@ -5,12 +5,12 @@
<field name="model">slide.channel</field>
<field name="inherit_id" ref="website_slides.view_slide_channel_form"/>
<field name="arch" type="xml">
<xpath expr="//span[@name='stat_label']" position="replace">
<xpath expr="//span[@name='members_done_count_label']" position="replace">
<span class="o_stat_text" attrs="{'invisible': [('nbr_certification', '>', 0)]}">Finished</span>
<span class="o_stat_text" attrs="{'invisible': [('nbr_certification', '=', 0)]}">Certified</span>
</xpath>
<xpath expr="//field[@name='nbr_webpage']" position="after">
<field string="Certifications" name="nbr_certification"/>
<field name="nbr_certification" string="Certifications"/>
</xpath>
<xpath expr="//field[@name='slide_type']" position="after">
<field name="survey_id"/>