[MERGE] website slides: add course tags to eLearning frontend

PURPOSE

Add more visibility to course tags as well as the ability to add new ones via
the front-end.

SPECIFICATIONS

Course tags are only viewable in the courses page. In this merge we add more
course tag functionality by:

- making course tag creation more intuitive via backend
- making course tags viewable within a course's specific webpage
- making it possible for those with the correct access rights to add new
  course tags via the course's specific webpage (via a "+ Add Tag" button)
- adding the ability to assign colors to course tags
- note that coloring should not show when courses page is filtered by course
  tag to allow for easier identification of tag filters.

See sub commits for more details.

LINKS

Task: 2128166
Migration PR: odoo/upgrade#862

closes odoo/odoo#44365

Related: odoo/upgrade#862
Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
Odoo's Mergebot
2020-03-06 15:48:03 +01:00
committed by GitHub
15 changed files with 590 additions and 31 deletions
+63
View File
@@ -549,6 +549,69 @@ class WebsiteSlides(WebsiteProfile):
'can_create': can_create,
}
@http.route(['/slides/channel/tag/group/search_read'], type='json', auth='user', methods=['POST'], website=True)
def slide_channel_tag_group_search_read(self, fields, domain):
can_create = request.env['slide.channel.tag.group'].check_access_rights('create', raise_exception=False)
return {
'read_results': request.env['slide.channel.tag.group'].search_read(domain, fields),
'can_create': can_create,
}
@http.route('/slides/channel/tag/add', type='json', auth='user', methods=['POST'], website=True)
def slide_channel_tag_add(self, channel_id, tag_id=None, group_id=None):
""" Adds a slide channel tag to the specified slide channel.
:param integer channel_id: Channel ID
:param list tag_id: Channel Tag ID as first value of list. If id=0, then this is a new tag to
generate and expects a second list value of the name of the new tag.
:param list group_id: Channel Tag Group ID as first value of list. If id=0, then this is a new
tag group to generate and expects a second list value of the name of the
new tag group. This value is required for when a new tag is being created.
tag_id and group_id values are provided by a Select2. Default "None" values allow for
graceful failures in exceptional cases when values are not provided.
:return: channel's course page
"""
# handle exception during addition of course tag and send error notification to the client
# otherwise client slide create dialog box continue processing even server fail to create a slide
try:
channel = request.env['slide.channel'].browse(int(channel_id))
can_upload = channel.can_upload
can_publish = channel.can_publish
except (UserError, AccessError) as e:
_logger.error(e)
return {'error': e.name}
else:
if not can_upload or not can_publish:
return {'error': _('You cannot add tags to this course.')}
if tag_id:
# handle creation of new channel tag
if tag_id[0] == 0:
if group_id:
# handle creation of new channel tag group
if group_id[0] == 0:
tag_group = request.env['slide.channel.tag.group'].create({
'name': group_id[1]['name'],
})
group_id = tag_group.id
# use existing channel tag group
else:
group_id = group_id[0]
else:
return {'error': _('Missing "Tag Group" for creating a new "Tag".')}
request.env['slide.channel.tag'].create({'name': tag_id[1]['name'],
'channel_ids': [channel.id],
'group_id': group_id,
})
else:
# use existing channel tag
request.env['slide.channel.tag'].browse(tag_id[0]).write({'channel_ids': [(4, channel.id, 0)]})
return {'url': "/slides/%s" % (slug(channel))}
@http.route(['/slides/channel/subscribe'], type='json', auth='user', website=True)
def slide_channel_subscribe(self, channel_id):
return request.env['slide.channel'].browse(channel_id).message_subscribe(partner_ids=[request.env.user.partner_id.id])
@@ -6,14 +6,17 @@
</record>
<record id="slide_channel_tag_level_basic" model="slide.channel.tag">
<field name="name">Basic</field>
<field name="color">10</field>
<field name="group_id" ref="website_slides.slide_channel_tag_group_level"/>
</record>
<record id="slide_channel_tag_level_intermediate" model="slide.channel.tag">
<field name="name">Intermediate</field>
<field name="color">3</field>
<field name="group_id" ref="website_slides.slide_channel_tag_group_level"/>
</record>
<record id="slide_channel_tag_level_advanced" model="slide.channel.tag">
<field name="name">Advanced</field>
<field name="color">1</field>
<field name="group_id" ref="website_slides.slide_channel_tag_group_level"/>
</record>
@@ -23,14 +26,17 @@
</record>
<record id="slide_channel_tag_role_gardener" model="slide.channel.tag">
<field name="name">Gardener</field>
<field name="color">10</field>
<field name="group_id" ref="website_slides.slide_channel_tag_group_role"/>
</record>
<record id="slide_channel_tag_role_carpenter" model="slide.channel.tag">
<field name="name">Carpenter</field>
<field name="color">11</field>
<field name="group_id" ref="website_slides.slide_channel_tag_group_role"/>
</record>
<record id="slide_channel_tag_role_furniture" model="slide.channel.tag">
<field name="name">Furniture Designer</field>
<field name="color">3</field>
<field name="group_id" ref="website_slides.slide_channel_tag_group_role"/>
</record>
@@ -40,10 +46,11 @@
</record>
<record id="slide_channel_tag_other_1" model="slide.channel.tag">
<field name="name">Certification</field>
<field name="color">7</field>
<field name="group_id" ref="website_slides.slide_channel_tag_group_data_other"/>
</record>
<record id="slide_channel_tag_other_2" model="slide.channel.tag">
<field name="name">Dog Friendly</field>
<field name="group_id" ref="website_slides.slide_channel_tag_group_data_other"/>
</record>
</data></odoo>
</data></odoo>
@@ -6,11 +6,11 @@ from odoo import fields, models
class SlideChannelTagGroup(models.Model):
_name = 'slide.channel.tag.group'
_description = 'Channel/Course Tag'
_description = 'Channel/Course Groups'
_inherit = 'website.published.mixin'
_order = 'sequence asc'
name = fields.Char('Name', required=True, translate=True)
name = fields.Char('Group Name', required=True, translate=True)
sequence = fields.Integer('Sequence', default=10, index=True, required=True)
tag_ids = fields.One2many('slide.channel.tag', 'group_id', string='Tags')
@@ -30,3 +30,4 @@ class SlideChannelTag(models.Model):
'Group sequence', related='group_id.sequence',
index=True, readonly=True, store=True)
channel_ids = fields.Many2many('slide.channel', 'slide_channel_tag_rel', 'tag_id', 'channel_id', string='Channels')
color = fields.Integer(string='Color Index', help="Color to apply to this tag (including in website).")
@@ -0,0 +1,330 @@
odoo.define('website_slides.channel_tag.add', function (require) {
'use strict';
var core = require('web.core');
var Dialog = require('web.Dialog');
var publicWidget = require('web.public.widget');
var _t = core._t;
var TagCourseDialog = Dialog.extend({
template: 'website.slides.tag.add',
events: _.extend({}, Dialog.prototype.events, {
'change input#tag_id' : '_onChangeTag',
}),
/**
* @override
* @param {Object} parent
* @param {Object} options holding channelId
*
*/
init: function (parent, options) {
options = _.defaults(options || {}, {
title: _t("Add a tag"),
size: 'medium',
buttons: [{
text: _t("Add"),
classes: 'btn-primary',
click: this._onClickFormSubmit.bind(this)
}, {
text: _t("Discard"),
close: true
}]
});
this.channelID = parseInt(options.channelId, 10);
this.tagIds = options.channelTagIds || [];
this._super(parent, options);
},
start: function () {
var self = this;
return this._super.apply(this, arguments).then(function () {
self._bindSelect2Dropdown();
self._hideTagGroup();
});
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* 'Tag' and 'Tag Group' management for select2
*
* @private
*/
_bindSelect2Dropdown: function () {
var self = this;
this.$('#tag_id').select2(this._select2Wrapper(_t('Tag'),
function () {
return self._rpc({
route: '/slides/channel/tag/search_read',
params: {
fields: ['name'],
domain: [['id','not in',self.tagIds]],
}
});
})
);
this.$('#tag_group_id').select2(this._select2Wrapper(_t('Tag Group (required for new tags)'),
function () {
return self._rpc({
route: '/slides/channel/tag/group/search_read',
params: {
fields: ['name'],
domain: [],
}
});
})
);
},
/**
* Wrapper for select2 load data from server at once and store it.
*
* @private
* @param {String} Placeholder for element.
* @param {Function} Function to fetch data from remote location should return a Promise
* resolved data should be array of object with id and name. eg. [{'id': id, 'name': 'text'}, ...]
* @param {String} [nameKey='name'] (optional) the name key of the returned record
* ('name' if not provided)
* @returns {Object} select2 wrapper object
*/
_select2Wrapper: function (tag, fetchFNC, nameKey) {
nameKey = nameKey || 'name';
var values = {
width: '100%',
placeholder: tag,
allowClear: true,
formatNoMatches: false,
selection_data: false,
fetch_rpc_fnc: fetchFNC,
formatSelection: function (data) {
if (data.tag) {
data.text = data.tag;
}
return data.text;
},
createSearchChoice: function (term, data) {
var addedTags = $(this.opts.element).select2('data');
if (_.filter(_.union(addedTags, data), function (tag) {
return tag.text.toLowerCase().localeCompare(term.toLowerCase()) === 0;
}).length === 0) {
if (this.opts.can_create) {
return {
id: _.uniqueId('tag_'),
create: true,
tag: term,
text: _.str.sprintf(_t("Create new %s '%s'"), tag, term),
};
} else {
return undefined;
}
}
},
fill_data: function (query, data) {
var that = this,
tags = {results: []};
_.each(data, function (obj) {
if (that.matcher(query.term, obj[nameKey])) {
tags.results.push({id: obj.id, text: obj[nameKey]});
}
});
query.callback(tags);
},
query: function (query) {
var that = this;
// fetch data only once and store it
if (!this.selection_data) {
this.fetch_rpc_fnc().then(function (data) {
that.can_create = data.can_create;
that.fill_data(query, data.read_results);
that.selection_data = data.read_results;
});
} else {
this.fill_data(query, this.selection_data);
}
}
};
return values;
},
/**
* Get value for tag_id and [when appropriate] tag_group_id to send to server
*
* @private
*/
_getSelect2DropdownValues: function () {
var result = {};
var tag = this.$('#tag_id').select2('data');
if (tag) {
if (tag.create) {
// new tag
var group = this.$('#tag_group_id').select2('data');
if(group) {
result['tag_id'] = [0, {'name': tag.text}]
if (group.create) {
// new tag group
result['group_id'] = [0, {'name': group.text}];
} else {
result['group_id'] = [group.id];
}
}
} else {
result['tag_id'] = [tag.id];
}
}
return result;
},
/**
* Select2 fields makes the "required" input hidden on the interface.
* Therefore we need to make a method to visually provide this requirement
* feedback to users. "tag group" field should only need this when a new tag
* is created.
*
* @private
*/
_formValidate: function ($form) {
$form.addClass('was-validated');
var result = $form[0].checkValidity();
var $tagInput = this.$('#tag_id');
if ($tagInput.length !== 0){
var $tagSelect2Container = $tagInput
.closest('.form-group')
.find('.select2-container');
$tagSelect2Container.removeClass('is-invalid is-valid');
if ($tagInput.is(':invalid')) {
$tagSelect2Container.addClass('is-invalid');
} else if ($tagInput.is(':valid')) {
$tagSelect2Container.addClass('is-valid');
var $tagGroupInput = this.$('#tag_group_id');
if ($tagGroupInput.length !== 0){
var $tagGroupSelect2Container = $tagGroupInput
.closest('.form-group')
.find('.select2-container');
if ($tagGroupInput.is(':invalid')) {
$tagGroupSelect2Container.addClass('is-invalid');
} else if ($tagGroupInput.is(':valid')) {
$tagGroupSelect2Container.addClass('is-valid');
}
}
}
}
return result;
},
_alertDisplay: function (message) {
this._alertRemove();
$('<div/>', {
"class": 'alert alert-warning',
role: 'alert'
}).text(message).insertBefore(this.$('form'));
},
_alertRemove: function () {
this.$('.alert-warning').remove();
},
/**
* When the user IS NOT creating a new tag, this function hides the group tag field
* and makes it not required. Since the select2 field makes an extra container, this
* needs to be hidden along with the group tag input field and its label.
*
* @private
*/
_hideTagGroup: function () {
var $tag_group_id = this.$('#tag_group_id');
var $tagGroupSelect2Container = $tag_group_id.closest('.form-group');
$tagGroupSelect2Container.hide();
$tag_group_id.removeAttr("required");
$tag_group_id.select2("val", "");
},
/**
* When the user IS creating a new tag, this function shows the field and
* makes it required. Since the select2 field makes an extra container, this
* needs to be shown along with the group input field and its label.
*
* @private
*/
_showTagGroup: function () {
var $tag_group_id = this.$('#tag_group_id');
var $tagGroupSelect2Container = $tag_group_id.closest('.form-group');
$tagGroupSelect2Container.show();
$tag_group_id.attr("required", "required");
},
//--------------------------------------------------------------------------
// Handler
//--------------------------------------------------------------------------
_onClickFormSubmit: function () {
var self = this;
var $form = this.$('#slides_channel_tag_add_form');
if (this._formValidate($form)) {
var values = this._getSelect2DropdownValues();
return this._rpc({
route: '/slides/channel/tag/add',
params: {'channel_id': this.channelID,
'tag_id': values.tag_id,
'group_id': values.group_id},
}).then(function (data) {
if (data.error) {
self._alertDisplay(data.error);
} else {
window.location = data.url;
}
});
}
},
_onChangeTag: function (ev) {
var self = this;
var tag = $(ev.currentTarget).select2('data');
if (tag && tag.create) {
self._showTagGroup();
} else {
self._hideTagGroup();
}
},
});
publicWidget.registry.websiteSlidesTag = publicWidget.Widget.extend({
selector: '.o_wslides_js_channel_tag_add',
xmlDependencies: ['/website_slides/static/src/xml/website_slides_channel_tag.xml'],
events: {
'click': '_onAddTagClick',
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
_openDialog: function ($element) {
var data = $element.data();
return new TagCourseDialog(this, data).open();
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* @private
* @param {Event} ev
*/
_onAddTagClick: function (ev) {
ev.preventDefault();
this._openDialog($(ev.currentTarget));
},
});
return {
TagCourseDialog: TagCourseDialog,
websiteSlidesTag: publicWidget.registry.websiteSlidesTag
};
});
@@ -58,6 +58,36 @@ $o-wslides-fs-side-width: 300px;
}
}
// Color tags according to assigned background color.
.o_wslides_channel_tag {
vertical-align: middle;
@for $size from 1 through length($o-colors) {
&.o_tag_color_#{$size - 1} {
$background-color: white;
// no color selected
@if $size == 1 {
& {
color: black;
background-color: $background-color;
box-shadow: inset 0 0 0 1px nth($o-colors, $size);
}
} @else {
$background-color: nth($o-colors, $size);
& {
color: white;
background-color: $background-color;
}
}
@at-root a#{&} {
&:hover {
color: color-yiq($background-color);
background-color: darken($background-color, 10%);
}
}
}
}
}
.o_wslides_body {
background-color: $o-wslides-color-bg;
@@ -163,7 +193,7 @@ $o-wslides-fs-side-width: 300px;
@each $limit in $truncate-limits {
.o_wslides_desc_truncate_#{$limit} {
$line-height: 1.3;
max-height: $limit * $line-height * 1em;
max-height: $limit * $line-height * 1.2em;
overflow: hidden;
text-overflow: ellipsis;
white-space: normal;
@@ -75,6 +75,8 @@ tour.register('course_publisher', {
run: function () {} // check membership
}
].concat(
slidesTourTools.addExistingCourseTag(),
slidesTourTools.addNewCourseTag('The Most Awesome Course'),
slidesTourTools.addSection('Introduction'),
slidesTourTools.addVideoToSection('Introduction'),
[{
@@ -73,7 +73,7 @@ var addWebPageToSection = function (sectionName, pageName) {
content: 'eLearning: click on tags',
trigger: 'ul.select2-choices:first',
}, {
content: 'eLearning: select gardener tag',
content: 'eLearning: select Theory tag',
trigger: 'div.select2-result-label:contains("Theory")',
in_modal: false,
}, {
@@ -83,10 +83,59 @@ var addWebPageToSection = function (sectionName, pageName) {
}];
};
var addExistingCourseTag = function () {
return [
{
content: 'eLearning: click on Add Tag',
trigger: 'a.o_wslides_js_channel_tag_add',
}, {
content: 'eLearning: click on tag dropdown',
trigger: 'a.select2-choice:first',
}, {
content: 'eLearning: select advanced tag',
trigger: 'div.select2-result-label:contains("Advanced")',
in_modal: false,
}, {
content: 'eLearning: add existing course tag',
trigger: 'footer.modal-footer button:contains("Add")'
}];
};
var addNewCourseTag = function (courseTagName) {
return [
{
content: 'eLearning: click on Add Tag',
trigger: 'a.o_wslides_js_channel_tag_add',
}, {
content: 'eLearning: click on tag dropdown',
trigger: 'a.select2-choice:first',
}, {
content: 'eLearning: add a new course tag',
trigger: 'a.select2-choice:first',
run: function () {
// directly add new tag since we can assume select2 works correctly
$('#tag_id').select2('data', {id:'123', text: courseTagName, create: true});
$('#tag_id').trigger('change');
}
}, {
content: 'eLearning: click on tag group dropdown',
trigger: 'a.select2-choice:last',
}, {
content: 'eLearning: select Tags tag group',
trigger: 'div.select2-result-label:contains("Tags")',
in_modal: false,
}, {
content: 'eLearning: add new course tag',
trigger: 'footer.modal-footer button:contains("Add")'
}];
};
return {
addSection: addSection,
addVideoToSection: addVideoToSection,
addWebPageToSection: addWebPageToSection,
addExistingCourseTag: addExistingCourseTag,
addNewCourseTag: addNewCourseTag,
};
});
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="website.slides.tag.add">
<div class="form-group">
<form action="/slides/channel/tag/add" method="POST" id="slides_channel_tag_add_form">
<div class="form-group">
<label for="tag_id" class="col-form-label">Tag</label>
<input class="form-control" id="tag_id" required="required"/>
</div>
<div class="form-group">
<label id="tag_group_label" for="tag_group_id" class="col-form-label">Tag Group</label>
<input class="form-control" id="tag_group_id"/>
</div>
</form>
</div>
</t>
</templates>
+1
View File
@@ -30,6 +30,7 @@
<script type="text/javascript" src="/website_slides/static/src/js/slides_course_quiz.js"/>
<script type="text/javascript" src="/website_slides/static/src/js/slides_course_quiz_question_form.js"/>
<script type="text/javascript" src="/website_slides/static/src/js/slides_course_quiz_finish.js"/>
<script type="text/javascript" src="/website_slides/static/src/js/slides_course_tag_add.js"/>
<script type="text/javascript" src="/website_slides/static/src/js/slides_course_unsubscribe.js"/>
</xpath>
</template>
@@ -39,7 +39,7 @@
</tree>
</field>
</record>
<record id="slide_channel_tag_action" model="ir.actions.act_window">
<field name="name">Course Tags</field>
<field name="res_model">slide.channel.tag</field>
@@ -64,9 +64,23 @@
<form string="Course Tag Group">
<sheet>
<group>
<field name="name"/>
<field name="is_published" string="Menu entry"/>
<field name="tag_ids"/>
<div class="oe_title">
<label for="name" class="oe_edit_only" string="Group Name"/>
<h1><field name="name" default_focus="1" placeholder="Group Name"/></h1>
<label for="is_published" string="Menu Entry"/>
<field name="is_published"/><br/>
<field name="tag_ids">
<tree editable="bottom">
<field name="sequence" widget="handle"/>
<field name="group_sequence" invisible="1"/>
<field name="name" string="Tag Name"/>
<field name="color" string="Color" widget="color_picker"/>
<control>
<create string="Add a tag"/>
</control>
</tree>
</field>
</div>
</group>
</sheet>
</form>
@@ -86,7 +100,7 @@
</record>
<record id="slide_channel_tag_group_action" model="ir.actions.act_window">
<field name="name">Course Tags</field>
<field name="name">Course Groups</field>
<field name="res_model">slide.channel.tag.group</field>
<field name="view_mode">tree,form</field>
</record>
@@ -57,7 +57,7 @@
</div>
<div>
<field name="active" invisible="1"/>
<field name="tag_ids" widget="many2many_tags" placeholder="Tags"/>
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}" placeholder="Tags"/>
</div>
<notebook colspan="4">
<page name="content" string="Content">
@@ -67,8 +67,8 @@
sequence="1"
action="website_slides_action_settings"
groups="base.group_system"/>
<menuitem name="Course Tags"
id="website_slides_menu_config_course_tags"
<menuitem name="Course Groups"
id="website_slides_menu_config_course_groups"
parent="website_slides_menu_configuration"
sequence="2"
action="slide_channel_tag_group_action"/>
@@ -78,4 +78,4 @@
sequence="3"
action="action_slide_tag"/>
</odoo>
</odoo>
@@ -193,6 +193,22 @@
<div class="tab-content py-4 o_wslides_tabs_content mb-4" id="courseMainTabContent">
<div t-att-class="'tab-pane fade %s' % ('show active' if active_tab == 'home' else '')" id="home" role="tabpanel" aria-labelledby="home-tab">
<div class="mb-2 pt-1">
<t t-if="channel.tag_ids">
<t t-foreach="channel.tag_ids" t-as="channel_tag">
<span t-attf-class="badge o_wslides_channel_tag #{'o_tag_color_'+str(channel_tag.color)}" t-esc="channel_tag.name"/>
</t>
</t>
<a t-if="channel.can_upload"
class="o_wslides_js_channel_tag_add border badge badge-light font-weight-normal py-1 m-1"
role="button"
aria-label="Add Tag"
href="#"
t-att-data-channel-id="channel.id"
t-att-data-channel-tag-ids="channel.tag_ids.ids">
<i class="fa fa-plus mr-1"/><span>Add Tag</span>
</a>
</div>
<t t-if="channel.channel_type == 'training'" t-call="website_slides.course_slides_list"/>
</div>
<div t-if="channel.allow_comment" t-att-class="'tab-pane fade %s' % ('show active' if active_tab == 'review' else '')" id="review" role="tabpanel" aria-labelledby="review-tab">
@@ -572,21 +588,42 @@
</div>
</div>
<div t-if="channel.can_upload" class="text-right container pb-2">
<a class="btn btn-primary py-1 o_wslides_js_slide_upload"
title="Upload Presentation" role="button"
aria-label="Upload Presentation" href="#"
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 mr-1"/>Upload new content
</a>
<a class="btn btn-secondary py-1 o_wslides_js_slide_section_add"
title="Add Section" role="button"
aria-label="Add Section" href="#"
t-att-channel_id="channel.id">
<i class="fa fa-folder-o mr-1"/>Add a section
</a>
<div class="container">
<div class="row">
<div class="mb-2 pt-1 text-left col">
<t t-if="channel.tag_ids">
<t t-foreach="channel.tag_ids" t-as="channel_tag">
<span t-attf-class="badge o_wslides_channel_tag #{'o_tag_color_'+str(channel_tag.color)}" t-esc="channel_tag.name"/>
</t>
</t>
<a t-if="channel.can_upload"
class="o_wslides_js_channel_tag_add border badge badge-light font-weight-normal py-1 m-1"
role="button"
aria-label="Add Tag"
href="#"
t-att-data-channel-id="channel.id"
t-att-data-channel-tag-ids="channel.tag_ids.ids">
<i class="fa fa-plus mr-1"/><span>Add Tag</span>
</a>
</div>
<div t-if="channel.can_upload" class="text-right pb-2 col-auto">
<a class="btn btn-primary py-1 o_wslides_js_slide_upload"
title="Upload Presentation" role="button"
aria-label="Upload Presentation" href="#"
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 mr-1"/>Upload new content
</a>
<a class="btn btn-secondary py-1 o_wslides_js_slide_section_add"
title="Add Section" role="button"
aria-label="Add Section" href="#"
t-att-channel_id="channel.id">
<i class="fa fa-folder-o mr-1"/>Add a section
</a>
</div>
</div>
</div>
<!-- Featured lesson -->
<t t-call="website_slides.course_promoted_slide"/>
@@ -360,7 +360,12 @@
<div class="font-weight-light o_wslides_desc_truncate_3" t-field="channel.description_short"/>
<div t-if="channel.tag_ids" class="mt-2 pt-1 o_wslides_desc_truncate_2">
<t t-foreach="channel.tag_ids" t-as="tag">
<a t-att-href="'/slides/all?%s' % keep_query('*', tags=str((tag | search_tags).ids))" t-attf-class="badge #{'badge-primary' if tag in search_tags else 'badge-light'}" t-esc="tag.name"/>
<t t-if="search_tags">
<a t-att-href="'/slides/all?%s' % keep_query('*', tags=str((tag | search_tags).ids))" t-attf-class="badge #{'badge-primary' if tag in search_tags else 'o_wslides_channel_tag o_tag_color_0'}" t-esc="tag.name"/>
</t>
<t t-else="">
<a t-att-href="'/slides/all?%s' % keep_query('*', tags=str((tag | search_tags).ids))" t-attf-class="badge o_wslides_channel_tag #{'o_tag_color_'+str(tag.color)}" t-esc="tag.name"/>
</t>
</t>
</div>
</div>
@@ -50,7 +50,7 @@
<div class="overflow-hidden mb-1" style="height:24px">
<t t-foreach="course.channel_id.tag_ids" t-as="tag">
<a t-att-href="'/slides/all?channel_tag_id=%s' % tag.id" class="badge badge-light" t-esc="tag.name"/>
<a t-att-href="'/slides/all?channel_tag_id=%s' % tag.id" t-attf-class="badge o_wslides_channel_tag #{'o_tag_color_'+str(tag.color)}" t-esc="tag.name"/>
</t>
</div>