[IMP] website_forum, *: redesign forum's frontend

* = website_slides_forum, website_mail, website_profile

In `website_(slides)_forum`:
All forums page:
Course forums are displayed on the same level as regular forums (with
a tag "Course" to differentiate them).
We don't see this page if there is only one forum.

This new design resembles that of Discourse with a table for the list
of questions and a sidebar on the left.

Other pages such as the Tags, FAQ, and Moderation pages have also been
revamped. Some of the functionality comes in the following commits.

Notes: The table rows have been made fully clickable, but by doing so
other clickable or hoverable elements lose their function.
In order to counteract this, a `position-relative` and
`z-index-1` classes are added to them.

The image_url definition wasn't working properly: When a user didn't have
and avatar, a placeholder with their initial is generated. But it did
not take the size option defined in the widget. This has been
corrected by changing it to `request.website.image_url(...)`

In the posts' page, the answer box now comes up from the bottom of the
screen.

In the FAQ pages, the accordion has been replaced by BS accordion
component using `accordion-item` instead of cards. The web editor
however is still using the old system.

In `website_mail`:
A new template for the follow button has been created in order to
redesign it for the forum without disrupting this element in other
modules (ie: website_mail, blog).

Task-3083657
Part of Task-3316555

Part-of: odoo/odoo#123709
Co-authored-by: Florian Charlier <flch@odoo.com>
This commit is contained in:
Elisabeth Dickinson
2023-07-18 16:58:43 +02:00
committed by Florian Charlier
co-authored by Florian Charlier
parent ae7a0603e0
commit 21531bd9b9
27 changed files with 1705 additions and 1518 deletions
+2
View File
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'Forum',
'category': 'Website/Website',
@@ -41,6 +42,7 @@ Ask questions, get answers, no distractions
'views/forum_forum_templates_moderation.xml',
'views/forum_forum_templates_post.xml',
'views/forum_forum_templates_tools.xml',
'views/forum_templates_mail.xml',
'views/website_profile_templates.xml',
'views/snippets/snippets.xml',
@@ -501,6 +501,23 @@ class WebsiteForum(WebsiteProfile):
return request.render("website_forum.moderation_queue", values)
@http.route('/forum/<model("forum.forum"):forum>/closed_posts', type='http', auth="user", website=True)
def closed_posts(self, forum, **kwargs):
if request.env.user.karma < forum.karma_moderate:
raise werkzeug.exceptions.NotFound()
closed_posts_ids = request.env['forum.post'].search(
[('forum_id', '=', forum.id), ('state', '=', 'close')],
order='write_date DESC, id DESC',
)
values = self._prepare_user_values(forum=forum)
values.update({
'posts_ids': closed_posts_ids,
'queue_type': 'close',
})
return request.render("website_forum.moderation_queue", values)
@http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/validate', type='http', auth="user", website=True)
def post_accept(self, forum, post, **kwargs):
url = "/forum/%s/validation_queue" % (slug(forum))
@@ -508,6 +525,8 @@ class WebsiteForum(WebsiteProfile):
url = "/forum/%s/flagged_queue" % (slug(forum))
elif post.state == 'offensive':
url = "/forum/%s/offensive_posts" % (slug(forum))
elif post.state == 'close':
url = "/forum/%s/closed_posts" % (slug(forum))
post.validate()
return request.redirect(url)
@@ -3,6 +3,7 @@
<record id="forum_help" model="forum.forum">
<field name="name">Help</field>
<field name="image_1920" type="base64" file="website_forum/static/src/img/help.jpg"/>
<field name="description">This community is for professionals and enthusiasts of our products and services. Share and discuss the best content and new marketing ideas, build your professional profile and become a better marketer together.</field>
</record>
@@ -5,15 +5,19 @@
<field name="type">qweb</field>
<field name="key">website_forum.faq_accordion</field>
<field name="arch" type="xml">
<section class="s_faq_collapse pt32 pb32">
<t t-set="_page_name" t-value="'guidelines'"/>
<t t-set="_page_name_label">Guidelines</t>
<section class="s_faq_collapse mb-5">
<div class="container">
<div id="myCollapse" class="accordion" role="tablist">
<div class="card bg-white" data-name="Item">
<a href="#" role="tab" data-bs-toggle="collapse" aria-expanded="false" class="collapsed card-header" data-bs-target="#collapse1">
<b>What kinds of questions can I ask here?</b>
</a>
<div id="collapse1" class="collapse" data-bs-parent="#myCollapse" role="tabpanel">
<div class="card-body">
<div id="myCollapse" class="accordion rounded border" role="tablist">
<div class="accordion-item" data-name="Item">
<h2 class="accordion-header" id="headingOne">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapse1">
What kind of questions can I ask here?
</button>
</h2>
<div id="collapse1" class="accordion-collapse collapse show" data-bs-parent="#myCollapse" aria-labelledby="headingOne">
<div class="accordion-body bg-light">
<p>This community is for professional and enthusiast users, partners and programmers. You can ask questions about:</p>
<ul>
<li>how to install Odoo on a specific infrastructure,</li>
@@ -27,12 +31,14 @@
</div>
</div>
</div>
<div class="card bg-white" data-name="Item">
<a href="#" role="tab" data-bs-toggle="collapse" aria-expanded="false" class="collapsed card-header" data-bs-target="#collapse2">
<b>What should I avoid in my questions?</b>
</a>
<div id="collapse2" class="collapse" data-bs-parent="#myCollapse" role="tabpanel">
<div class="card-body">
<div class="accordion-item" data-name="Item">
<h2 class="accordion-header" id="headingTwo">
<button class="collapsed accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapse2">
What should I avoid in my questions?
</button>
</h2>
<div id="collapse2" class="collapse" data-bs-parent="#myCollapse" aria-labelledby="headingTwo">
<div class="accordion-body bg-light">
<p>You should only ask practical, answerable questions based on actual problems that you face. Chatty, open-ended questions diminish the usefulness of this site and push other questions off the front page.</p>
<p>To prevent your question from being flagged and possibly removed, avoid asking subjective questions where …</p>
<ul>
@@ -54,28 +60,32 @@
</div>
</div>
</div>
<div class="card bg-white" data-name="Item">
<a href="#" role="tab" data-bs-toggle="collapse" aria-expanded="false" class="collapsed card-header" data-bs-target="#collapse3">
<b>What should I avoid in my answers?</b>
</a>
<div id="collapse3" class="collapse" data-bs-parent="#myCollapse" role="tabpanel">
<div class="card-body">
<div class="accordion-item" data-name="Item">
<h2 class="accordion-header" id="headingThree">
<button class="collapsed accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapse3">
What should I avoid in my answers?
</button>
</h2>
<div id="collapse3" class="collapse" data-bs-parent="#myCollapse" aria-labelledby="headingThree">
<div class="accordion-body bg-light">
<p><b>Answers should not add or expand questions</b>. Instead, either edit the question or add a comment.</p>
<p><b>Answers should not comment other answers</b>. Instead add a comment on the other answers.</p>
<p><b>Answers shouldn't just point to other questions</b>.Instead add a comment indicating <i>"Possible duplicate of..."</i>. However, it's fine to include links to other questions or answers providing relevant additional information.</p>
<p> <b>Answers shouldn't just provide a link a solution</b>. Instead provide the solution description text in your answer, even if it's just a copy/paste. Links are welcome, but should be complementary to answer, referring sources or additional reading.</p>
<p><b>Answers shouldn't just provide a link a solution</b>. Instead provide the solution description text in your answer, even if it's just a copy/paste. Links are welcome, but should be complementary to answer, referring sources or additional reading.</p>
<p><b>Answers should not start debates</b> This community Q&amp;A is not a discussion group. Please avoid holding debates in your answers as they tend to dilute the essence of questions and answers. For brief discussions please use commenting facility.</p>
<p>When a question or answer is upvoted, the user who posted them will gain some points, which are called "karma points". These points serve as a rough measure of the community trust to him/her. Various moderation tasks are gradually assigned to the users based on those points.</p>
<p>For example, if you ask an interesting question or give a helpful answer, your input will be upvoted. On the other hand if the answer is misleading - it will be downvoted. Each vote in favor will generate 10 points, each vote against will subtract 2 points. There is a limit of 200 points that can be accumulated for a question or answer per day. The table given at the end explains reputation point requirements for each type of moderation task.</p>
</div>
</div>
</div>
<div class="card bg-white" data-name="Item">
<a href="#" role="tab" data-bs-toggle="collapse" aria-expanded="false" class="collapsed card-header" data-bs-target="#collapse4">
<b>Why can other people edit my questions/answers?</b>
</a>
<div id="collapse4" class="collapse" data-bs-parent="#myCollapse" role="tabpanel">
<div class="card-body">
<div class="accordion-item" data-name="Item">
<h2 class="accordion-header" id="headingFour">
<button class="collapsed accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapse4">
Why can other people edit my questions/answers?
</button>
</h2>
<div id="collapse4" class="collapse" data-bs-parent="#myCollapse" aria-labelledby="headingFour">
<div class="accordion-body bg-light">
<p>The goal of this site is create a relevant knowledge base that would answer questions related to Odoo.</p>
<p>Therefore questions and answers can be edited like wiki pages by experienced users of this site in order to improve the overall quality of the knowledge base content. Such privileges are granted based on user karma level: you will be able to do the same once your karma gets high enough.</p>
<p>If this approach is not for you, please respect the community.</p>
@@ -89,4 +99,4 @@
</field>
</record>
</data>
</odoo>
</odoo>
+29 -19
View File
@@ -3,6 +3,8 @@
import textwrap
from markupsafe import Markup
from odoo import _, api, fields, models
from odoo.tools.translate import html_translate
from odoo.addons.http_routing.models.ir_http import slug
@@ -21,25 +23,26 @@ class Forum(models.Model):
_order = "sequence"
def _get_default_welcome_message(self):
return """
<section>
<div class="container py-5">
<div class="row">
<div class="col-lg-12">
<h1 class="text-center">Welcome!</h1>
<p class="text-400 text-center">%(message_intro)s<br/>%(message_post)s</p>
</div>
<div class="col text-center mt-3">
<a href="#" class="js_close_intro btn btn-outline-light mr-2">%(hide_text)s</a>
<a class="btn btn-light forum_register_url" href="/web/login">%(register_text)s</a>
</div>
</div>
</div>
</section>""" % {
'message_intro': _("This community is for professionals and enthusiasts of our products and services."),
'message_post': _("Share and discuss the best content and new marketing ideas, build your professional profile and become a better marketer together."),
'hide_text': _('Hide Intro'),
'register_text': _('Register')}
return Markup("""
<h1 style="text-align: center;clear-both"><font style="font-size: 62px; font-weight: bold;">%(message_intro)s</font></h1>
<div class="text-white">
<p class="lead o_default_snippet_text" style="text-align: center;">%(message_post)s</p>
<p style="text-align: center;">
<a class="btn btn-primary forum_register_url" href="/web/login">%(register_text)s</a>
<button type="button" class="btn btn-light js_close_intro" aria-label="Dismiss message">
%(hide_text)s
</button>
</p>
</div>
""") % {
'message_intro': _("Welcome!"),
'message_post': _(
"Share and discuss the best content and new marketing ideas, build your professional profile and become"
" a better marketer together."
),
'hide_text': _('Dismiss'),
'register_text': _('Sign up'),
}
# description and use
name = fields.Char('Forum Name', required=True, translate=True)
@@ -131,6 +134,7 @@ class Forum(models.Model):
karma_post = fields.Integer(string='Ask questions without validation', default=100)
karma_moderate = fields.Integer(string='Moderate posts', default=1000)
has_pending_post = fields.Boolean(string='Has pending post', compute='_compute_has_pending_post')
can_moderate = fields.Boolean(string="Is a moderator", compute="_compute_can_moderate")
@api.depends_context('uid')
def _compute_has_pending_post(self):
@@ -146,6 +150,12 @@ class Forum(models.Model):
pending_forums.has_pending_post = True
(self - pending_forums).has_pending_post = False
@api.depends_context('uid')
@api.depends('karma_moderate')
def _compute_can_moderate(self):
for forum in self:
forum.can_moderate = self.env.user.karma >= forum.karma_moderate
@api.depends('description')
def _compute_teaser(self):
for forum in self:
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 203.0829 243.7154"><g id="c"><g id="d"><polygon points="101.5414 238.2685 20.0382 190.7423 20.0382 101.8237 101.5414 148.9972 101.5414 238.2685" style="fill:#c1dbf6;"/><polygon points="101.5414 238.2685 183.044 191.095 183.044 101.8237 101.5414 148.9972 101.5414 238.2685" style="fill:#fff;"/><polygon points="183.044 101.8237 183.044 158.6371 111.3156 202.3055 101.5414 148.9972 183.044 101.8237" style="fill:#c1dbf6;"/><polygon points="20.8288 101.9859 101.5414 148.9972 101.5414 148.6905 101.5414 54.625 20.8288 101.9859" style="fill:#c1dbf6;"/><polygon points="101.5414 54.625 101.5414 148.9972 183.044 101.8237 101.5414 54.625" style="fill:#374874;"/><path d="m155.6613,118.1373l-23.2882-13.2862c-2.5378-1.4928-4.1799-4.1799-4.1799-7.1656h0c0-3.2842,3.5828-5.3742,6.4192-3.7321l24.6317,14.1819c2.09,1.1943,3.4335,3.5828,3.4335,5.9713h0c.1493,3.5828-3.8814,5.822-7.0163,4.0306Z" style="fill:#fff;"/><path d="m101.5414,54.625l81.5026,47.1988,14.3455,43.1314-14.3455,8.2813v37.8586l-81.5025,47.1734-81.5032-47.5262v-32.0983l-.0114-.007v-5.4006l-14.3455-8.2813,14.3455-43.1314,81.5146-47.1987m0-5.4541L17.6841,97.7379l-1.5591.894-.5672,1.7053L1.2123,143.4687l-1.2123,3.6449,3.3267,1.9204,11.9903,6.9217v5.3231l.0114.007v32.1621l2.3372,1.3629,83.8758,48.9045,83.8617-48.5441,2.3506-1.3604v-37.8551l11.9903-6.9217,3.3388-1.9273-1.2296-3.6538-14.4874-43.0493-.5615-1.6686-1.5184-.8911-83.7445-48.673h0Z" style="fill:#374874;"/><polygon points="183.044 101.8237 101.5414 54.625 20.2749 101.9673 20.0269 101.8237 5.6814 144.9551 87.3993 192.1287 101.5414 148.9972 24.9857 104.6936 101.5414 60.2454 178.3204 104.5575 101.5414 148.9972 115.6715 192.1287 197.3895 144.9551 183.044 101.8237" style="fill:#fff;"/><path d="m49.8818,64.403c-.6002,0-1.2004-.2277-1.6592-.6842l-24.5309-24.3699c-.9233-.9164-.9279-2.4078-.0115-3.33.9176-.9222,2.4101-.9268,3.33-.0115l24.5309,24.3699c.9233.9164.9279,2.4078.0115,3.33-.4599.4634-1.0659.6957-1.6707.6957Z" style="fill:#374874;"/><path d="m150.4164,64.403c-.6048,0-1.2108-.2323-1.6707-.6957-.9164-.9222-.9118-2.4135.0115-3.33l24.5309-24.3699c.9199-.9176,2.4124-.913,3.33.0115.9164.9222.9118,2.4135-.0115,3.33l-24.5309,24.3699c-.4588.4565-1.059.6842-1.6592.6842Z" style="fill:#374874;"/><path d="m101.5353,41.5659c-1.3005,0-2.3549-1.0544-2.3549-2.3549V2.3549c0-1.3005,1.0544-2.3549,2.3549-2.3549s2.3549,1.0544,2.3549,2.3549v36.8561c0,1.3005-1.0544,2.3549-2.3549,2.3549Z" style="fill:#374874;"/></g></g></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.4 KiB

@@ -9,7 +9,7 @@
wTourUtils.registerBackendAndFrontendTour("question", {
url: '/forum/1',
}, [{
trigger: ".o_forum_ask_btn",
trigger: ".o_wforum_ask_btn",
position: "left",
content: _t("Create a new post in this forum by clicking on the button."),
}, {
@@ -26,11 +26,12 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({
'click .o_wforum_flag_validator': '_onFlagValidatorClick',
'click .o_wforum_flag_mark_as_offensive': '_onFlagMarkAsOffensiveClick',
'click .vote_up:not(.karma_required), .vote_down:not(.karma_required)': '_onVotePostClick',
'click .o_js_validation_queue a[href*="/validate"]': '_onValidationQueueClick',
'click .o_wforum_validation_queue a[href*="/validate"]': '_onValidationQueueClick',
'click .o_wforum_validate_toggler:not(.karma_required)': '_onAcceptAnswerClick',
'click .o_wforum_favourite_toggle': '_onFavoriteQuestionClick',
'click .comment_delete': '_onDeleteCommentClick',
'click .js_close_intro': '_onCloseIntroClick',
'click .answer_collapse': '_onExpandAnswerClick',
'submit .js_wforum_submit_form:has(:not(.karma_required).o_wforum_submit_post)': '_onSubmitForm',
},
@@ -225,6 +226,21 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({
}, 0);
}
},
/**
* @private
* @param {Event} ev
*/
_onExpandAnswerClick: function (ev) {
const expandableWindow = ev.currentTarget;
if (ev.target.matches('.o_wforum_expand_toggle')) {
expandableWindow.classList.toggle('o_expand')
expandableWindow.classList.toggle('min-vh-100');
expandableWindow.classList.toggle('w-lg-50');
} else if (ev.target.matches('.o_wforum_discard_btn')){
expandableWindow.classList.remove('o_expand', 'min-vh-100');
expandableWindow.classList.add('w-lg-50');
}
},
/**
* @private
* @param {Event} ev
@@ -243,7 +259,8 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({
};
if (session.is_website_user) {
notifOptions.title = _t("Access Denied");
notifOptions.message = _t("Sorry you must be logged in to perform this action");
notifOptions.message = Markup`
<p>Oh no! Please <a href='/web/login'>sign in</a> to vote</p>`;
} else {
notifOptions.title = _t("Karma Error");
// FIXME this translation is bad, the number should be part of the
@@ -327,6 +344,7 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({
elem.prepend(child);
if (countFlaggedPosts) {
countFlaggedPosts.classList.remove('bg-light');
countFlaggedPosts.classList.remove('d-none');
countFlaggedPosts.classList.add('bg-danger');
countFlaggedPosts.innerText = parseInt(countFlaggedPosts.innerText, 10) + 1;
}
@@ -356,7 +374,8 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({
if (data.error === 'own_post') {
message = _t('Sorry, you cannot vote for your own posts');
} else if (data.error === 'anonymous_user') {
message = _t('Sorry you must be logged to vote');
message = Markup`
<p>Oh no! Please <a href='/web/login'>sign in</a> to vote</p>`;
}
this.displayNotification({
message: message,
@@ -401,62 +420,96 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({
});
},
/**
* Call the route to moderate/validate the post, then hide the validated post
* and decrement the count in the appropriate queue badge of the sidebar on success.
*
* @private
* @param {Event} ev
*/
_onValidationQueueClick: function (ev) {
_onValidationQueueClick: async function (ev) {
ev.preventDefault();
var $link = $(ev.currentTarget);
$link.parents('.post_to_validate').hide();
$.get($link.attr('href')).then(() => {
var left = $('.o_js_validation_queue:visible').length;
var type = $('h2.o_page_header a.active').data('type');
$('#count_post').text(left);
$('#moderation_tools a[href*="/' + type + '_"]').find('strong').text(left);
if (!left) {
this.$('.o_caught_up_alert').removeClass('d-none');
}
}, function () {
$link.parents('.o_js_validation_queue > div').addClass('bg-danger text-white').css('background-color', '#FAA');
$link.parents('.post_to_validate').show();
});
const approvalLink = ev.currentTarget;
const postBeingValidated = this._findParent(approvalLink, '.post_to_validate');
if (!postBeingValidated) {
return;
}
postBeingValidated.classList.add('d-none');
let ok;
try {
ok = (await fetch(approvalLink.href)).ok;
} catch {
// Calling the endpoint like this returns an HTML page. As we can't
// extract the error message from that, we disregard it and simply
// restore the post's visibility. This __should__ be improved.
}
if (!ok) {
postBeingValidated.classList.remove('d-none');
return;
}
const nbLeftInQueue = Array.from(document.querySelectorAll('.post_to_validate'))
.filter(e => window.getComputedStyle(e).display !== 'none')
.length;
const queueType = document.querySelector('#queue_type').dataset.queueType;
const queueCountBadge = document.querySelector(`#count_posts_queue_${queueType}`);
queueCountBadge.innerText = nbLeftInQueue;
if (!nbLeftInQueue) {
document.querySelector('.o_caught_up_alert').classList.remove('d-none');
queueCountBadge.classList.add('d-none');
}
},
_findParent: function (el, selector) {
while (el.parentElement && !el.matches(selector)) {
el = el.parentElement;
}
return el.matches(selector) ? el : null;
},
/**
* @private
* @param {Event} ev
*/
_onAcceptAnswerClick: function (ev) {
_onAcceptAnswerClick: async function (ev) {
ev.preventDefault();
var $link = $(ev.currentTarget);
var target = $link.data('target');
const $link = $(ev.currentTarget);
const target = $link.data('target');
this._rpc({
route: $link.data('href'),
}).then(data => {
if (data.error) {
if (data.error === 'anonymous_user') {
var message = _t("Sorry, anonymous users cannot choose correct answer.");
}
this.displayNotification({
message: message,
title: _t("Access Denied"),
sticky: false,
type: "warning",
});
} else {
this.$('.forum_answer').toArray().forEach((answer) => {
var $answer = $(answer);
var isCorrect = $answer.is(target) ? data : false;
var $toggler = $answer.find('.o_wforum_validate_toggler');
var newHelper = isCorrect ? $toggler.data('helper-decline') : $toggler.data('helper-accept');
$answer.toggleClass('o_wforum_answer_correct', isCorrect);
$toggler.tooltip('dispose')
.attr('data-bs-original-title', newHelper)
.tooltip({delay: 0});
});
const data = await this._rpc({route: $link.data('href')});
if (data.error) {
let message;
if (data.error === 'anonymous_user') {
message = _t('Sorry, anonymous users cannot choose correct answer.');
} else if (data.error === 'own_post') {
message = _t('Sorry, you cannot vote for your own posts');
}
});
this.displayNotification({
message: message || data.error,
title: _t('Access Denied'),
sticky: false,
type: 'warning',
});
} else {
this.$('.o_wforum_answer').toArray().forEach((answer) => {
const $answer = $(answer);
const isCorrect = $answer.is(target) ? data : false;
const $toggler = $answer.find('.o_wforum_validate_toggler');
const $togglerIcon = $toggler.find('.fa');
const $correctBadge = $answer.find('.o_wforum_answer_correct_badge');
const newHelper = isCorrect ? $toggler.data('helper-decline') : $toggler.data('helper-accept');
$answer.toggleClass(
"o_wforum_answer_correct my-2 mx-n3 mx-lg-n2 mx-xl-n3 py-3 px-3 px-lg-2 px-xl-3",
isCorrect
);
$answer.find('div .border-start')
.toggleClass('border-success', isCorrect);
$toggler.tooltip('dispose')
.attr('data-bs-original-title', newHelper)
.tooltip({delay: 0})
.toggleClass('opacity-50', !isCorrect);
$togglerIcon
.toggleClass('fa-check-circle text-success', isCorrect)
.toggleClass('fa-check-circle-o', !isCorrect);
$correctBadge.toggleClass('d-inline', isCorrect).toggleClass('d-none', !isCorrect);
});
}
},
/**
* @private
@@ -465,11 +518,13 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({
_onFavoriteQuestionClick: function (ev) {
ev.preventDefault();
var $link = $(ev.currentTarget);
var $link_icon = $link.find('.fa');
this._rpc({
route: $link.data('href'),
}).then(function (data) {
$link.toggleClass('o_wforum_gold fa-star', data)
.toggleClass('fa-star-o text-muted', !data);
$link.toggleClass('opacity-50 opacity-100-hover', !data);
$link_icon.toggleClass('o_wforum_gold fa-star', data)
.toggleClass('fa-star-o', !data);
});
},
/**
@@ -516,16 +571,16 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({
method: currentTarget.dataset.action,
args: [parseInt(currentTarget.dataset.postId)],
});
currentTarget.parentElement.querySelectorAll('.flag_validator').forEach((element) => element.classList.toggle('d-none'));
this._findParent(currentTarget, '.o_wforum_flag_alert')?.classList.toggle('d-none');
const flaggedButton = currentTarget.parentElement.firstElementChild,
child = flaggedButton.firstElementChild,
countFlaggedPosts = this.el.querySelector('#count_flagged_posts'),
countFlaggedPosts = this.el.querySelector('#count_posts_queue_flagged'),
count = parseInt(countFlaggedPosts.innerText, 10) - 1;
flaggedButton.innerText = _t(' Flag');
flaggedButton.prepend(child);
if (count === 0) {
countFlaggedPosts.classList.add("bg-light");
countFlaggedPosts.classList.add('bg-light');
}
countFlaggedPosts.innerText = count;
},
@@ -545,7 +600,7 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({
renderFooter: false,
}).open();
dialog.opened().then(() => {
dialog.$(".btn-light:contains('Discard')").click((ev) => {
dialog.$(".btn-link:contains('Discard')").click((ev) => {
ev.preventDefault();
dialog.close();
});
@@ -1,42 +1,103 @@
$gold: #eca801;
$silver: #cccccc;
$bronze: #eea91e;
$silver: #acacac;
$bronze: #ba651f;
$-forum-sidebar-width: map-get($container-max-widths, sm) / 2;
.website_forum, .o_wprofile_body {
--o-avatar-height: 1.875rem;
.o_wforum_avatar {
width: var(--o-avatar-height);
height: var(--o-avatar-height);
}
.o_wforum_author_box {
&.o_show_info {
line-height: 1.2;
}
&.o_compact {
--o-avatar-height: 1.4rem;
line-height: 1;
}
}
}
.website_forum {
.o_forum_ask_btn {
@include media-breakpoint-up(md) {
box-shadow: $box-shadow;
width: 200px;
--o-post-header-height: var(--o-avatar-height);
.o_wforum_wrapper {
@include media-breakpoint-up(xl) {
grid-template-columns: $-forum-sidebar-width minmax(0, auto);
}
@include media-breakpoint-up(xxl) {
max-width: 100%;
}
}
.o_wforum_nav .nav-link {
@include o-hover-text-color($body-color, $link-color);
line-height: 1;
.o_wforum_content_wrapper {
@include media-breakpoint-up(xxl) {
margin-left: -$-forum-sidebar-width;
.fa {
opacity: 0.5;
}
&:hover, &.active {
.fa {
opacity: 1;
> div {
max-width: map-get($grid-breakpoints, xl) - $-forum-sidebar-width - 100px;
@include make-container();
}
}
}
&.active {
background-color: rgba(map-get($theme-colors, 'info'), 0.1);
color: darken(map-get($theme-colors, 'info'), 15%)!important;
aside.o_wforum_sidebar {
min-width: $-forum-sidebar-width;
.btn.w-100:hover {
background-color: darken(map-get($o-color-palette, 'o-color-3'), 4%);
}
}
.o_wforum_table {
--o-avatar-height: 2.25rem;
img.o_wforum_avatar {
border: 2px solid var(--o-color-4);
}
img.o_forum_avatar {
width: 30px;
height: 30px;
.o_wforum_reply_count, .o_wforum_view_count, .o_wforum_last_activity {
width: 100px;
}
&> :not(caption) > * > * {
@include media-breakpoint-down(lg) {
padding-left: 0;
padding-right: $table-cell-padding-x-sm;
}
}
tbody {
@include media-breakpoint-down(lg) {
border-top: 0;
}
tr:hover {
@if ($body-bg and color-contrast($body-bg) != $color-contrast-dark) {
background-color: lighten($body-bg, 10%);
} @else {
background-color: darken($body-bg, 2%);
}
}
}
}
.o_wforum_index_entry_title a:visited span {
color: lighten($body-color, 50%);
}
.btn[aria-expanded="true"] {
opacity: 1 !important;
}
// Single Post
.o_wforum_post, .note-editable.panel-body {
.o_wforum_post, .note-editable {
word-wrap: break-word;
pre {
@@ -49,22 +110,61 @@ $bronze: #eea91e;
blockquote {
position: relative;
padding-left: 1em;
border-left: .25em solid map-get($grays, '500');
padding-left: 1em;
color: map-get($grays, '600');
}
}
#post_reply {
img.o_forum_avatar {
width: 24px;
height: 24px;
#post_reply {
z-index: $zindex-modal;
min-height: 0;
max-height: 0;
border-radius: $border-radius $border-radius 0 0;
transition: max-#{$transition-collapse}, min-#{$transition-collapse}, #{$transition-collapse}, #{$transition-collapse-width};
&.show {
max-height: 100%;
&.o_expand {
.o_wforum_expand_toggle {
content:'\f066';
}
.odoo-editor-editable {
transition: $transition-collapse;
flex-grow: 1;
}
form.js_website_submit_form {
height: calc(100vh - 100px);
}
}
.o_wysiwyg_textarea_wrapper {
display: flex;
flex-flow: column;
flex-grow: 1;
.odoo-editor {
height: 200px !important;
display: flex;
flex-grow: 1;
flex-flow: column;
.note-editable {
flex-grow: 1;
}
}
}
}
}
.o_wforum_readable {
max-width: 700px;
.o_wforum_post_header, .o_wforum_answer_header {
min-height: var(--o-post-header-height);
}
.o_wforum_readable {
p {
margin-bottom: 0.5rem;
}
@@ -99,61 +199,25 @@ $bronze: #eea91e;
}
}
.o_wforum_author_box {
&.o_show_info {
line-height: 1.2;
img {
width: 2em;
height: 2em;
}
}
&.o_compact {
line-height: 1;
img {
width: 1.4em;
height: 1.4em;
}
}
}
.forum_answer {
.o_wforum_answer_correct_badge {
display: none;
}
.o_wforum_author_box_check {
width: 1em;
height: 1em;
display: none;
top: 0;
right: -3px;
box-shadow: 0 0 0 2px white;
line-height: .8;
}
.o_wforum_validate_toggler {
@include o-hover-text-color(map-get($grays, '400'), lighten(map-get($theme-colors, 'success'), 20%));
}
.o_wforum_answer {
transition: $transition-base;
&.o_wforum_answer_correct {
.o_wforum_answer_correct_badge, .o_wforum_author_box .o_wforum_author_box_check {
display: inline;
// This hack is to have either a light or dark success color
// depending on the background color's contrast
$-cc1-bg: o-color(map-get($o-color-palette, o-cc1-bg));
$-isBright: color-contrast($-cc1-bg) == $color-contrast-dark;
@if ($-isBright) {
background-color: mix($color-contrast-light, $success, 90%);
} @else {
$-success-light: lighten(saturate($success, 70), 30);
border: 1px solid $-success-light;
background-color: mix($color-contrast-dark, $-success-light, 80%);
}
.o_wforum_validate_toggler {
@include o-hover-text-color(map-get($theme-colors, 'success'), map-get($theme-colors, 'warning'));
&:hover .fa.fa-check:before {
content: '\f00d';
}
}
.o_wforum_answer_header .o_wforum_author_pic {
border: 2px solid $success;
padding: 3px;
.o_wforum_author_pic img {
box-shadow: 0 0 0 2px $success;
}
}
}
@@ -177,7 +241,6 @@ $bronze: #eea91e;
.website_forum, .o_wforum_profile_tab {
.vote {
.vote_count {
line-height: 1;
animation-play-state: paused;
&.o_forum_vote_animate {
@@ -185,62 +248,13 @@ $bronze: #eea91e;
}
}
.vote_down, .vote_up {
line-height: 0.5;
font-size: 1.1em;
@include o-hover-text-color(rgba($text-muted, 0.5), map-get($theme-colors, 'success'));
.vote_up {
@include o-hover-text-color(inherit, $success);
}
.vote_down {
@include o-hover-text-color(rgba($text-muted, 0.5), map-get($theme-colors, 'danger'));
@include o-hover-text-color(inherit, $danger);
}
&.o_wforum_vote_vertical {
@include media-breakpoint-up(sm) {
font-size: 1.2em;
}
}
}
}
.o_js_forum_tag_follow {
position: relative;
.badge {
font-size: 100%;
}
.o_forum_tag_follow_box {
@include o-position-absolute(100%, auto, auto, 0);
display: none;
z-index: 1;
.card {
padding: ($grid-gutter-width*0.5) - 1px;
}
}
}
.o_profile_main {
overflow: hidden;
}
img.o_forum_avatar {
width: 40px;
height: 40px;
object-fit: cover;
}
img.o_forum_avatar_big {
width: 75px;
height: 75px;
object-fit: cover;
}
.o_wprofile_email_validation_container {
img.o_forum_avatar {
width: 16px;
height: 16px;
}
}
@@ -250,6 +264,7 @@ img.o_forum_avatar_big {
display: flex;
align-items: center;
}
span[data-oe-model="res.country"] {
margin-left: 10px;
}
@@ -258,31 +273,37 @@ img.o_forum_avatar_big {
.o_wforum_bio_popover_info .css_editable_mode_hidden > div:last-child > .o_forum_tooltip_line {
margin-top: -0.5rem; // compensate parent 'mt-2' class
}
.o_wforum_bio_popover_bio p {
margin-top: 8px;
margin-bottom: 0;
}
}
.o_wforum_elearning_navtabs_container {
@include media-breakpoint-up(md) {
background-color: map-get($theme-colors, 'secondary');
}
}
.website_forum {
margin-bottom: $spacer;
}
.popover.o_wforum_bio_popover_container {
box-shadow: 0 2px 5px $gray-400;
.popover-header {
background-color: tint-color($primary, 80%);
border-bottom: 0;
color: $primary;
}
max-width: 552px;
}
.o_wforum_forum_card_bg {
background-image: linear-gradient(99deg, map-get($theme-colors, 'secondary') 10%, darken(map-get($theme-colors, 'secondary'), 10%) 90%);
.o-text-gold {
color: $gold;
}
#o_wforum_forums_index_list & {
min-height: 100px;
.o-text-silver {
color: $silver;
}
.o-text-bronze {
color: $bronze;
}
}
#o_wforum_forums_index_list .card {
border-radius: .75rem;
&:hover {
transform: translateY(-5px);
box-shadow: $box-shadow !important;
}
}
@@ -3,7 +3,7 @@
<div role="dialog" class="modal fade" id="oe_social_share_modal">
<div class="modal-dialog" role="document">
<div class="modal-content">
<header class="modal-header alert alert-info mb0" role="status">
<header class="modal-header mb0" role="status">
<h4 class="modal-title">Thanks for posting!</h4>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</header>
@@ -1,13 +1,14 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
registry.category("web_tour.tours").add('forum_question', {
test: true,
url: '/forum/help-1',
steps: [{
import { registry } from "@web/core/registry";
registry.category("web_tour.tours").add('forum_question', {
test: true,
url: '/forum/help-1',
steps: [
{
content: "Ask the question in this forum by clicking on the button.",
trigger: '.o_forum_ask_btn',
trigger: '.o_wforum_ask_btn',
}, {
content: "Give your question content.",
trigger: 'input[name=post_name]',
@@ -52,11 +53,9 @@
content: "Close modal once modal animation is done.",
extra_trigger: 'div.modal.modal_shown',
trigger: ".modal-header button.btn-close",
}, {
content: "Click here to accept this answer.",
extra_trigger: '#wrap:has(".o_wforum_validate_toggler")',
trigger: '.o_wforum_validate_toggler[data-karma]:first',
}, {
content: "Congratulations! You just created and post your first question and answer.",
trigger: '#wrap:has(".o_wforum_answer_correct")',
}]});
trigger: '.o_wforum_validate_toggler',
isCheck: true,
}]
});
@@ -15,11 +15,11 @@
<div t-if="options.get('badges')" style="display: inline-block">
<t t-esc="separator"/>
<b>|</b>
<span class="fa fa-trophy bg-gold ms-2" role="img" aria-label="Gold badge" title="Gold badge"/>
<span class="fa fa-trophy o-text-gold ms-2" role="img" aria-label="Gold badge" title="Gold badge"/>
<t t-esc="object.gold_badge"/>
<span class="fa fa-trophy bg-silver ms-2" role="img" aria-label="Silver badge" title="Silver badge"/>
<span class="fa fa-trophy o-text-silver ms-2" role="img" aria-label="Silver badge" title="Silver badge"/>
<t t-esc="object.silver_badge"/>
<span class="fa fa-trophy bg-bronze ms-2" role="img" aria-label="Bronze badge" title="Bronze badge"/>
<span class="fa fa-trophy o-text-bronze ms-2" role="img" aria-label="Bronze badge" title="Bronze badge"/>
<t t-esc="object.bronze_badge"/>
</div>
<t t-out="0"/>
@@ -4,183 +4,46 @@
<!-- Specific Forum Layout -->
<template id="forum_index" name="Forum">
<t t-set="no_filters" t-value="filters not in ('solved', 'unsolved', 'unanswered')"/>
<t t-if="my == 'mine'">
<t t-set="_page_name" t-value="'my_posts'"/>
<t t-set="_page_name_label">My Posts</t>
</t>
<t t-elif="my == 'favourites'">
<t t-set="_page_name" t-value="'my_favourites'"/>
<t t-set="_page_name_label">My Favorites</t>
</t>
<t t-else="" t-set="_page_name" t-value="'list_questions'"/>
<t t-call="website_forum.header">
<div class="row g-0">
<div t-attf-class="d-flex justify-content-end flex-md-grow-1 #{(search or tag or my) and 'col-12 flex-column-reverse flex-md-row mb-3' or 'col-md-auto order-md-3'}">
<div t-if="search or tag or my" class="d-flex flex-wrap align-items-center flex-grow-1">
<span t-if="search" class="w-100 w-md-auto mb-2 mb-md-0 border rounded ps-2 d-inline-flex align-items-center justify-content-between">
<em class="bg-light px-2" t-esc="search"/>
<a t-att-href="url_for('') + '?' + keep_query( 'filters', 'sorting', 'my')" class="btn py-1">&#215;</a>
</span>
<span t-if="my" t-attf-class="w-100 w-md-auto mb-2 mb-md-0 border rounded ps-2 d-inline-flex align-items-center justify-content-between #{search and 'ms-md-2'}">
<div>
<img t-if="uid" class="o_forum_avatar rounded-circle me-1" t-att-src="website.image_url(user, 'avatar_128', '16x16')" alt="Avatar"/>
<span t-if="my == 'favourites'"> My <b>Favourites</b></span>
<span t-elif="my == 'followed'"> I'm <b>Following</b></span>
<span t-elif="my == 'mine'"> My <b>Posts</b></span>
<span t-elif="my == 'tagged'"> <b>Tags</b> I Follow</span>
</div>
<a t-att-href="url_for('') + '?' + keep_query( 'search', 'filters', 'sorting')" class="btn py-1">&#215;</a>
</span>
<span t-if="tag" t-attf-class="w-100 w-md-auto mb-2 mb-md-0 border rounded ps-2 d-inline-flex align-items-center justify-content-between #{(search or my) and 'ms-md-2'}">
<div>
<span class="fa fa-tag text-muted me-1"/>
<span t-esc="tag.name"/>
</div>
<a t-att-href="url_for('/forum') + '/' + slug(forum) + '?' + keep_query( 'search', 'sorting', 'filters', 'my')" class="btn py-1">&#215;</a>
</span>
</div>
<div t-if="uid and forum and forum.has_pending_post"
title="You already have a pending post"
data-bs-toggle="popover" data-bs-trigger="hover" data-bs-content="Please wait for a moderator to validate your previous post before continuing.">
<a class="disabled btn btn-secondary btn-block mb-3 mb-md-0" t-attf-href="/forum/#{slug(forum)}/ask">New Post</a>
</div>
<a t-else="" role="button" type="button" class="btn btn-primary btn-block o_forum_ask_btn mb-3 mb-md-0" t-att-href="uid and '/forum/' + slug(forum) + '/ask' or '/web/login'">New Post</a>
</div>
<t t-set="no_filters" t-value="not filters in ('solved', 'unsolved', 'unanswered')"/>
<t t-if="not no_filters or (no_filters and question_count)">
<!-- Filter post by type (mobile only) -->
<div class="col-6 col-md-auto d-lg-none d-flex align-items-center">
<div class="dropdown"> Show
<a href="#" class="dropdown-toggle" data-bs-toggle="dropdown">
<t t-if="no_filters"> All</t>
<t t-elif="filters == 'solved'"> Solved</t>
<t t-elif="filters == 'unsolved'"> Unsolved</t>
<t t-elif="filters == 'unanswered'"> Unanswered</t>
<!-- List questions or search/filters result -->
<table t-if="question_count != 0" class="o_wforum_table table mb-5" height="1">
<thead>
<tr class="d-none d-lg-table-row">
<th class="o_wforum_table_title"></th>
<th class="o_wforum_table_posters"></th>
<th class="o_wforum_table_replies small fw-normal text-center">
<a class="text-muted" t-attf-href="?#{ keep_query('search', 'filters', sorting='child_count asc' if sorting == 'child_count desc' else 'child_count desc') }">
Replies <i t-attf-class="fa fa-caret-#{ 'down' if sorting == 'child_count desc' else 'up' } #{ not sorting.startswith('child_count') and 'd-none ' }"></i>
</a>
<div class="dropdown-menu" role="menu">
<a t-att-href="url_for('') + '?' + keep_query( 'search', 'sorting', filters='all')"
class="dropdown-item">
All
</a>
<div class="dropdown-divider"/>
<t t-if="forum.mode == 'questions'">
<a t-att-href="url_for('') + '?' + keep_query( 'search', 'sorting', filters='solved')"
class="dropdown-item">Solved
</a>
<a t-att-href="url_for('') + '?' + keep_query( 'search', 'sorting', filters='unsolved')"
class="dropdown-item">Unsolved
</a>
</t>
<a t-att-href="url_for('') + '?' + keep_query( 'search', 'sorting', filters='unanswered')"
class="dropdown-item">Unanswered
</a>
</div>
</div>
</div>
<!-- Filter post by type (desktop) -->
<div class="d-none d-lg-flex align-items-center col-auto flex-grow-md-1 flex-grow-lg-0">
<nav class="o_wforum_nav nav nav-pills justify-content-around">
<a t-att-href="url_for('') + '?' + keep_query('search', 'sorting', 'my', filters='all')"
t-attf-class="nav-link py-1 rounded-pill #{no_filters and 'active fw-bold' or 'ps-0 pe-2'}">
All
</th>
<th class="o_wforum_table_views small fw-normal text-center">
<a class="text-muted" t-attf-href="?#{ keep_query('search', 'filters', sorting='views asc' if sorting == 'views desc' else 'views desc') }">
Views <i t-attf-class="fa fa-caret-#{'down' if sorting == 'views desc' else 'up'} #{ not sorting.startswith('views') and 'd-none ' }"></i>
</a>
<t t-if="forum.mode == 'questions'">
<span class="mx-1 text-400 d-none d-lg-block">|</span>
<a t-att-href="url_for('') + '?' + keep_query( 'search', 'sorting', 'my', filters='solved')"
t-attf-class="nav-link py-1 rounded-pill #{filters == 'solved' and 'active fw-bold' or 'px-2'}">Solved
</a>
<a t-att-href="url_for('') + '?' + keep_query( 'search', 'sorting', 'my', filters='unsolved')"
t-attf-class="d-none d-lg-block nav-link py-1 rounded-pill #{filters == 'unsolved' and 'active fw-bold' or 'px-2'}">Unsolved
</a>
</t>
<span class="mx-1 text-400 d-none d-lg-block">|</span>
<a t-att-href="url_for('') + '?' + keep_query( 'search', 'sorting', 'my', filters='unanswered')"
t-attf-class="nav-link py-1 rounded-pill #{filters == 'unanswered' and 'active fw-bold' or 'px-2'}">Unanswered
</th>
<th class="o_wforum_table_activity small fw-normal text-center">
<a class="text-muted" t-att-href="sorting=='write_date desc' and url_for('') + '?' + keep_query( 'search', 'filters', sorting='write_date asc') or url_for('') + '?' + keep_query( 'search', 'filters', sorting='write_date desc')">
Activity <i t-attf-class="fa fa-caret-#{sorting=='write_date desc' and 'down' or 'up'} #{sorting == 'write_date desc' or sorting == 'write_date asc' and ' ' or 'd-none'}"></i>
</a>
</nav>
</div>
</th>
</tr>
</thead>
<t t-foreach="question_ids" t-as="question" t-call="website_forum.display_post">
<t t-set="show_author_avatar" t-value="true"/>
</t>
<!-- Order by -->
<div t-if="question_count > 1"
t-attf-class="col-6 col-md-auto d-flex align-items-center justify-content-end #{uid and 'mt-lg-0'}">
<span class="mx-3 mx-lg-2 text-400 d-none d-md-inline">|</span>
<span class="dropdown">
Order by
<a href="#" class="dropdown-toggle" data-bs-toggle="dropdown">
<t t-if="sorting == 'relevancy desc'"> trending</t>
<t t-elif="sorting == 'create_date desc'"> newest</t>
<t t-elif="sorting == 'write_date desc'"> activity date</t>
<t t-elif="sorting == 'child_count desc'"> most answered</t>
<t t-elif="sorting == 'vote_count desc'"> most voted</t>
</a>
<div class="dropdown-menu dropdown-menu-end" role="menu">
<a role="menuitem" t-att-href="url_for('') + '?' + keep_query( 'search', 'filters', sorting='relevancy desc')" t-attf-class="dropdown-item#{sorting == 'relevancy desc' and ' active'}">Trending</a>
<a role="menuitem" t-att-href="url_for('') + '?' + keep_query( 'search', 'filters', sorting='write_date desc')" t-attf-class="dropdown-item#{sorting == 'write_date desc' and ' active'}">Last activity date</a>
<a role="menuitem" t-att-href="url_for('') + '?' + keep_query( 'search', 'filters', sorting='create_date desc')" t-attf-class="dropdown-item#{sorting == 'create_date desc' and ' active'}">Newest</a>
<a role="menuitem" t-att-href="url_for('') + '?' + keep_query( 'search', 'filters', sorting='child_count desc')" t-attf-class="dropdown-item#{sorting == 'child_count desc' and ' active'}">Most answered</a>
<a role="menuitem" t-att-href="url_for('') + '?' + keep_query( 'search', 'filters', sorting='vote_count desc')" t-attf-class="dropdown-item#{sorting == 'vote_count desc' and ' active'}">Most voted</a>
</div>
</span>
</div>
</div>
<div class="row mt-4">
<!-- List questions or search/filters result -->
<div t-if="question_count != 0" class="col">
<t t-foreach="question_ids" t-as="question">
<t t-call="website_forum.display_post">
<t t-set="show_author_avatar" t-value="true"/>
</t>
</t>
</div>
<!-- No posts or search/filters result -->
<div t-if="question_count == 0 or original_search" class="col">
<div t-if="search or tag or (not no_filters)" class="alert alert-info">
<t t-set="_filters_str">
<t t-if="filters == 'unanswered'">unanswered</t>
<t t-elif="filters == 'solved'">solved</t>
<t t-elif="filters == 'unsolved'">unsolved</t>
</t>
<t t-set="_my_str">
<t t-if="my == 'favourites'">in your favourites</t>
<t t-if="my == 'followed'">in your followed list</t>
<t t-if="my == 'mine'">in your posts</t>
</t>
<t t-set="_search_str"><t t-if="search">matching "<em class="fw-bold" t-esc="original_search or search"/>"</t></t>
<t t-set="_search_and_tag_str"><t t-if="search and tag">&amp;nbsp;and&amp;nbsp;</t></t>
<t t-set="_tag_str"><t t-if="tag">using the <span class="badge text-bg-light" t-esc="tag.name"/> tag</t></t>
<t t-set="result_msg">
Sorry, we could not find any <b>%s</b> result <b>%s</b> %s%s%s.
</t>
<span t-out="result_msg % (_filters_str.strip(), _my_str.strip(), _search_str.strip(), _search_and_tag_str.strip(), _tag_str.strip())"/>
<span t-if="original_search">Showing results for <em class="fw-bold" t-esc="search"/> instead.</span>
</div>
<t t-elif="not tag and not search and no_filters">
<div t-if="my == 'followed'" class="alert border">
You're not following any topic in this forum (yet).<br/>
<a t-attf-href="/forum/#{slug(forum)}/">Browse All</a>
</div>
<div t-elif="my == 'favourites'" class="alert border">
No favourite questions in this forum (yet).<br/>
<a t-attf-href="/forum/#{slug(forum)}/">Browse All</a>
</div>
<div t-elif="my == 'mine'" class="alert border">You have no posts in this forum (yet).</div>
</t>
<div t-elif="filters == 'unanswered'" class="alert alert-info">Amazing! There are no unanswered questions left!</div>
<div t-elif="no_filters and not search and not tag and not my" class="alert alert-info">
<b>This forum is empty.</b><br/>
Be the first one asking a question
</div>
<t t-if="search and question_count == 0">
<h4>Search Tips</h4>
<ul>
<li>Check your spelling and try again</li>
<li>Try searching for one or two words</li>
<li>Be less specific in your wording for a wider search result</li>
</ul>
</t>
</div>
</div>
</table>
<t t-if="question_count == 0 or original_search" t-call="website_forum.no_results_message"/>
<t t-call="website.pager"/>
</t>
</template>
@@ -191,49 +54,57 @@
<div t-if="forum and forum.has_pending_post" class="alert border" role="alert">
<b>You already have a pending post.</b><br/>
<p>Please wait for a moderator to validate your previous post before continuing.</p>
<a t-attf-href="/forum/#{ slug(forum) }" title="All Topics"><i class="oi oi-chevron-left me-2"/>Back to All Topics</a>
<a t-attf-href="/forum/#{ slug(forum) }" title="All Topics"><i class="oi oi-chevron-left me-2"/>Back to All Posts</a>
</div>
<form t-else="" t-attf-action="/forum/#{slug(forum)}/new" method="post" role="form" class="tag_text js_website_submit_form js_wforum_submit_form o_wforum_readable">
<div class="mb-3">
<label class="form-label" for="content">Title</label>
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<input type="text" name="post_name" required="required" pattern=".*\S.*" t-attf-value="#{post_name}"
class="form-control form-control-lg" placeholder="A clear, explicit and concise title" title="Title must not be empty"/>
<input type="hidden" name="karma" t-attf-value="#{user.karma}" id="karma"/>
<div class="form-text small text-muted">
<a data-bs-toggle="collapse" href="#newQuestionExample" role="button" aria-expanded="false" aria-controls="newQuestionExample">
Example
<i class="fa fa-question-circle"/>
</a>
<div class="collapse" id="newQuestionExample">
<div class="text-success mt-2">
<i class="fa fa-check"/> How to configure TPS and TVQ's canadian taxes?
</div>
<div class="text-danger">
<i class="fa fa-times"/> Good morning to all! Please, can someone help solve my tax computation problem in Canada? Thanks!
<form t-else="" t-attf-action="/forum/#{slug(forum)}/new" method="post" role="form" class="tag_text js_website_submit_form js_wforum_submit_form o_wforum_readable mt-lg-3">
<div class="row mb-3">
<label class="form-label col-lg-2" for="content">Title</label>
<div class="col">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<input type="text" name="post_name" required="required" pattern=".*\S.*" t-att-value="post_name"
class="form-control" placeholder="Write a clear, explicit and concise title" title="Title must not be empty"/>
<input type="hidden" name="karma" t-att-value="user.karma" id="karma"/>
<div class="form-text small text-muted">
<a data-bs-toggle="collapse" href="#newQuestionExample" role="button" aria-expanded="false" aria-controls="newQuestionExample">
Example
<i class="fa fa-question-circle"/>
</a>
<div class="collapse" id="newQuestionExample">
<div class="mt-2 text-success">
<i class="fa fa-check"/> How to configure TPS and TVQ's canadian taxes?
</div>
<div class="text-danger">
<i class="fa fa-times"/> Good morning to all! Please, can someone help solve my tax computation problem in Canada? Thanks!
</div>
</div>
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label" for="content">Description</label>
<div class="small form-text text-muted d-inline">, consider <b>adding an example</b>. </div>
<textarea name="content" required="required" id="content" class="form-control o_wysiwyg_loader" t-att-data-karma="forum.karma_editor">
<t t-esc="question_content"/>
</textarea>
<div class="row mb-3">
<label class="form-label col-lg-2" for="content">Description</label>
<div class="col">
<textarea name="content" required="required" id="content" class="form-control o_wysiwyg_loader" t-att-data-karma="forum.karma_editor">
<t t-out="question_content"/>
</textarea>
<span class="form-text d-inline small text-muted"><i class="fa fa-lightbulb-o"></i> Tip: consider adding an example. </span>
</div>
</div>
<div class="mb-3">
<label class="form-label" for="post_tags">Tags</label>
<input type="hidden" name="karma_tag_create" t-attf-value="#{forum.karma_tag_create}" id="karma_tag_create"/>
<input type="hidden" name="karma_edit_retag" t-attf-value="#{forum.karma_edit_retag}" id="karma_edit_retag"/>
<input type="hidden" name="post_tags" placeholder="Tags" class="form-control js_select2"/>
<div class="row mb-2">
<label class="form-label col-lg-2" for="post_tags">Tags</label>
<div class="col">
<input type="hidden" name="karma_tag_create" t-att-value="forum.karma_tag_create" id="karma_tag_create"/>
<input type="hidden" name="karma_edit_retag" t-att-value="forum.karma_edit_retag" id="karma_edit_retag"/>
<input type="hidden" name="post_tags" placeholder="Tags" class="form-control js_select2"/>
</div>
</div>
<div class="mb-5">
<button type="submit" t-attf-class="btn btn-primary o_wforum_submit_post #{forum.allow_share and 'oe_social_share_call'} #{(user.karma &lt; forum.karma_ask) and 'karma_required'}"
t-att-data-karma="forum.karma_ask"
data-hashtags="#question" data-social-target-type="question">Post Your Question</button>
<a class="btn btn-secondary" title="Back to Question" t-attf-href="/forum/#{ slug(forum) }"> Discard</a>
<div class="row mb-5">
<div class="col offset-lg-2">
<button type="submit" t-attf-class="o_wforum_submit_post #{forum.allow_share and 'oe_social_share_call'} btn btn-primary #{user.karma &lt; forum.karma_ask and 'karma_required'}"
t-att-data-karma="forum.karma_ask"
data-hashtags="#question" data-social-target-type="question">Post Your Question</button>
<a class="btn btn-link" title="Back to Question" t-attf-href="/forum/#{ slug(forum) }">Discard</a>
</div>
</div>
</form>
</t>
@@ -242,68 +113,81 @@
<!-- Edition: edit a post -->
<template id="edit_post" name="Edit Post">
<t t-call="website_forum.header">
<div t-if="is_answer" class="fw-bold mb-1">Question by <t t-esc="post.parent_id.create_uid.sudo().name"/></div>
<article t-if="is_answer" class="alert border pb-0 o_wforum_readable">
<h5 class="mb-1 text-muted" t-esc="post.parent_id.name"/>
<div t-field="post.parent_id.content" class="o_wforum_post_content text-muted oe_no_empty"/>
</article>
<div t-if="is_answer" class="row g-0">
<div class="col-lg-2 pt-3">
<label class="fw-bold">Question</label>
</div>
<article class="o_wforum_readable alert col bg-light">
<i class="text-muted fw-bold" t-out="post.parent_id.name"/>
<i t-field="post.parent_id.content" class="o_wforum_post_content oe_no_empty pb-3 text-muted text-break"/>
<small class="text-end text-muted">by <t t-out="post.parent_id.create_uid.sudo().name" /></small>
</article>
</div>
<form t-attf-action="/forum/#{slug(forum)}/post/#{slug(post)}/save" method="post" role="form" class="tag_text js_website_submit_form js_wforum_submit_form o_wforum_readable">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<div t-if="not is_answer" class="mb-3">
<label class="form-label" for="post_name">Title</label>
<input type="text" name="post_name" required="required" pattern=".*\S.*" t-attf-value="#{post.name}"
class="form-control form-control-lg" placeholder="Edit your Post" title="Title must not be empty"/>
<div class="form-text small text-muted">
<a data-bs-toggle="collapse" href="#newQuestionExample" role="button" aria-expanded="false" aria-controls="newQuestionExample">
Example
<i class="fa fa-question-circle"/>
</a>
<div class="collapse" id="newQuestionExample">
<div class="my-2">Use a clear, explicit and concise title</div>
<div class="text-success">
<i class="fa fa-check"/> How to configure TPS and TVQ's canadian taxes?
</div>
<div class="text-danger">
<i class="fa fa-times"/> Good morning to all! Please, can someone help solve my tax computation problem in Canada? Thanks!
<div t-if="not is_answer" class="row mb-3">
<label class="form-label col-lg-2" for="post_name">Title</label>
<div class="col">
<input type="text" name="post_name" required="required" pattern=".*\S.*" t-att-value="post.name"
class="form-control" placeholder="Edit your Post" title="Title must not be empty"/>
<div class="form-text small text-muted">
<a data-bs-toggle="collapse" href="#newQuestionExample" role="button" aria-expanded="false" aria-controls="newQuestionExample">
Example
<i class="fa fa-question-circle"/>
</a>
<div class="collapse" id="newQuestionExample">
<div class="my-2">Use a clear, explicit and concise title</div>
<div class="text-success">
<i class="fa fa-check"/> How to configure TPS and TVQ's canadian taxes?
</div>
<div class="text-danger">
<i class="fa fa-times"/> Good morning to all! Please, can someone help solve my tax computation problem in Canada? Thanks!
</div>
</div>
</div>
</div>
</div>
<div class="mb-3">
<label t-if="not is_answer" class="form-label" for="content">Description</label>
<label t-else="" class="form-label">Your Answer</label>
<div t-if="not is_answer" class="small form-text text-muted d-inline">, consider <b>adding an example</b>. </div>
<textarea name="content" id="content" required="required" class="form-control o_wysiwyg_loader" t-att-data-karma="forum.karma_editor">
<t t-esc="post.content"/>
</textarea>
<div class="row">
<label t-if="not is_answer" class="form-label col-lg-2" for="content">Description</label>
<label t-else="" class="form-label col-lg-2">Your Answer</label>
<div class="col">
<textarea name="content" id="content" required="required" class="form-control o_wysiwyg_loader" t-att-data-karma="forum.karma_editor" t-out="post.content"/>
<span t-if="not is_answer" class="form-text d-inline small text-muted"><i class="fa fa-lightbulb-o"/> Tip: consider adding an example. </span>
</div>
</div>
<input type="hidden" name="karma" t-attf-value="#{user.karma}" id="karma"/>
<div t-if="not is_answer" class="mb-3">
<label class="form-label" for="post_tags">Tags</label>
<input type="hidden" name="karma_tag_create" t-attf-value="#{forum.karma_tag_create}" id="karma_tag_create"/>
<input type="hidden" name="karma_edit_retag" t-attf-value="#{forum.karma_edit_retag}" id="karma_edit_retag"/>
<t t-set="edit_tags_karma_fail" t-value="user.karma &lt; forum.karma_edit_retag"/>
<t t-set="edit_tags_karma_error_message">You need to have sufficient karma to edit tags</t>
<input type="text" name="post_tags" class="form-control js_select2" placeholder="Tags" t-attf-data-init-value="#{tags}" value="Tags"
t-att-readonly="edit_tags_karma_fail and 'readonly'"
t-att-title="edit_tags_karma_fail and edit_tags_karma_error_message"/>
<input type="hidden" name="karma" t-att-value="user.karma" id="karma"/>
<div t-if="not is_answer" class="row mb-2">
<label class="form-label col-lg-2" for="post_tags">Tags</label>
<div class="col">
<input type="hidden" name="karma_tag_create" t-att-value="forum.karma_tag_create" id="karma_tag_create"/>
<input type="hidden" name="karma_edit_retag" t-att-value="forum.karma_edit_retag" id="karma_edit_retag"/>
<t t-set="edit_tags_karma_fail" t-value="user.karma &lt; forum.karma_edit_retag"/>
<t t-set="edit_tags_karma_error_message">You need to have sufficient karma to edit tags</t>
<input type="text" name="post_tags" class="form-control js_select2" placeholder="Tags" t-attf-data-init-value="#{tags}" value="Tags"
t-att-readonly="edit_tags_karma_fail and 'readonly'"
t-att-title="edit_tags_karma_fail and edit_tags_karma_error_message"/>
</div>
</div>
<div class="mb-4">
<button type="submit" class="btn btn-primary o_wforum_submit_post">Save Changes</button>
<a class="btn btn-secondary" title="Back to Question" t-attf-href="/forum/#{ slug(forum) }/#{ slug(post)}">
Discard
</a>
<div class="row mb-5">
<div class="col offset-lg-2">
<button type="submit" class="o_wforum_submit_post btn btn-primary">Save Changes</button>
<a class="btn btn-link" title="Back to Question" t-attf-href="/forum/#{ slug(forum) }/#{ slug(post)}">
Discard
</a>
</div>
</div>
</form>
</t>
</template>
<template id="tag" name="Forum Tags">
<template id="forum_index_tags" name="Forum Tags">
<t t-set="_page_name" t-valuef="tags"/>
<t t-set="_page_name_label">Tags</t>
<t t-call="website_forum.header">
<nav t-if="pager_tag_chars" class="navbar navbar-light bg-light justify-content-start">
<t t-if="len(pager_tag_chars) &gt; 1">
<t t-if="len(pager_tag_chars) &lt; 11">
<span class="navbar-text me-3">Show Tags Starting By</span>
<ul class="pagination mt0 mb0">
<ul class="pagination overflow-auto mt0 mb0">
<t t-foreach="pager_tag_chars" t-as="tuple_char">
<t t-if="tuple_char_index &lt; 11">
<li t-attf-class="page-item #{active_char_tag == tuple_char[1] and 'active'}"><a t-attf-href="/forum/#{slug(forum)}/tag/#{quote_plus(tuple_char[1])}" class="page-link"><t t-esc="tuple_char[0]"/></a></li>
@@ -312,43 +196,47 @@
</ul>
</t>
<div t-else="" class="d-flex align-items-center" role="toolbar" aria-label="Toolbar with button groups">
<label for="filter" class="me-2">Show Tags Starting By</label>
<label for="filter" class="me-2">Show Tags Starting with</label>
<select name="filter" class="form-select w-auto" onchange="location = this.value;">
<t t-foreach="pager_tag_chars" t-as="tuple_char">
<option t-if="active_char_tag == tuple_char[1]" selected="selected" value="" t-esc="tuple_char[0]"/>
<option t-if="active_char_tag == tuple_char[1]" selected="selected" value="" t-out="tuple_char[0]"/>
<option t-else="" t-attf-value="/forum/#{slug(forum)}/tag/#{quote_plus(tuple_char[1])}" t-esc="tuple_char[0]"/>
</t>
</select>
</div>
</nav>
</t>
<div class="row mb-5" t-if="tags">
<div class="col-md-3 mt16 o_js_forum_tag_follow" t-foreach="tags" t-as="tag">
<span t-attf-class="badge border px-2 #{tag.message_is_follower and 'border-success text-success' or 'text-bg-light text-600'}">
<i class="fa fa-tag small"/>
<t t-esc="tag.name"/>
<b class="small align-top">(<t t-esc="tag.posts_count"/>)</b>
</span>
<div class="o_forum_tag_follow_box text-center">
<div class="card shadow mt-2">
<a t-attf-href="/forum/#{ slug(forum) }/tag/#{ slug(tag) }/questions?{{ keep_query( filters='tag') }}"
class="btn btn-light">
See <t t-esc="tag.posts_count"/> post<t t-if="tag.posts_count > 1">s</t>
</a>
<em class="d-block mb-2">or</em>
<div class="input-group">
<t t-call="website_mail.follow">
<t t-set="email" t-value="user_id.email"/>
<t t-set="object" t-value="tag"/>
</t>
</div>
</div>
<div class="row g-0 mt-3 mb-5" t-if="tags">
<div class="col-md-3 o_js_forum_tag_follow" t-foreach="tags" t-as="tag">
<div t-attf-class="#{ 'd-block' if request.env.user._is_public() else 'd-flex'}">
<a t-attf-class="#{len(tag.post_ids) == 0 and 'disabled'} btn btn-link pe-0 text-decoration-none text-nowrap #{tag.message_is_follower and 'text-dark fw-bold'}"
t-attf-href="/forum/#{ slug(forum) }/tag/#{ slug(tag) }/questions?#{ keep_query(filters='tag') }}">
<t t-out="tag.name"/>
<span class="small">(<t t-out="tag.posts_count"/>)</span>
</a>
<t t-call="website_forum.follow">
<t t-set="email" t-value="user_id.email"/>
<t t-set="object" t-value="tag"/>
<t t-set="icons_design" t-value="True"/>
<t t-set="div_class" t-value="'d-inline'"/>
</t>
</div>
</div>
</div>
<div t-else="" class="alert border text-center">
No tags
</div>
<t t-else="">
<t t-set="tag_filter" t-value="keep_query('filters') and keep_query('filters').split('=')[1] or ''"/>
<t t-call="website_forum.no_results_message">
<t t-if="tag_filter and tag_filter != 'all'" t-set="results_msg">
There are no tags matching the selected filter.
</t>
<t t-if="search" t-set="results_msg">
There are no tags matching the selected search.
</t>
<t t-else="" t-set="results_msg">
There are no tags being used in this forum.
</t>
</t>
</t>
</t>
</template>
@@ -5,25 +5,21 @@
<!-- All Forums Layout -->
<template id="forum_all" name="Forum Navigation">
<t t-set="col_class" t-valuef="mb-3 col-sm-6"/>
<t t-set="col_class" t-valuef="col-md col-xl-3 col-xxl-2 mb-3"/>
<t t-set="img_class" t-valuef="col-md-4"/>
<t t-set="content_class" t-valuef="col-md-8"/>
<t t-set="last_post_class" t-valuef="col-md-12 pe-md-3 pt-3"/>
<t t-set="nb_post_class" t-valuef="col-md-12 pe-md-3 pt-2"/>
<t t-call="website.layout">
<t t-set="pageName" t-value="'website_forum'"/>
<div id="wrap">
<div class="oe_structure oe_empty" id="oe_structure_forum_all_top"/>
<div id="o_wforum_forums_index_list" class="container pt-4 pb-5">
<div t-if="forums" class="row">
<t t-call="website_forum.forum_all_all_entries">
<div id="o_wforum_forums_index_list" class="container pt-lg-4 pb-5">
<div class="row justify-content-center">
<t t-if="forums" t-call="website_forum.forum_all_all_entries">
<t t-set="_forums" t-value="forums"/>
</t>
<div t-else="" class="alert alert-info">No forum is available yet.</div>
</div>
<t t-else="">
<div class="alert alert-info">No forum is available yet.</div>
</t>
</div>
<div class="oe_structure oe_empty" id="oe_structure_forum_all_bottom"/>
</div>
@@ -39,45 +35,33 @@
<t t-foreach="sorted_forums" t-as="forum">
<t t-set="has_desc" t-value="forum.description"/>
<div t-attf-class="#{col_class}">
<div class="row py-4 bg-200 mx-1 h-100 o_forum_row">
<div t-attf-class="o_forum_image_container pe-md-0 h-100 #{img_class}">
<a t-attf-href="/forum/#{slug(forum)}">
<div t-attf-class="h-100 w-100 #{not forum.image_1920 and 'rounded o_wforum_forum_card_bg shadow-sm flex-shrink-0'}">
<div t-if="forum.image_1920 or editable" t-attf-class="h-100"
t-field="forum.image_1920" t-options="{'widget': 'image', 'preview_image': 'image_256', 'class': 'w-100 h-100 o_object_fit_cover rounded'}" />
</div>
</a>
<a t-attf-href="/forum/#{slug(forum)}"
class="card oe_img_bg h-100 justify-content-end shadow-sm border-0 p-3 pt-5 text-reset text-decoration-none transition-base"
t-attf-style="background-image: url('#{forum.image_1920 and website.image_url(forum, 'image_1920') or ''}')"
>
<div class="o_we_bg_filter" style="background-image: linear-gradient(0deg, rgba(255, 255, 255, .75) 30%, rgba(255, 255, 255, 0) 100%);"></div>
<div t-if="forum_badge" class="badge position-absolute top-0 end-0 m-3 border text-bg-light" t-out="forum_badge"></div>
<div class="mt-5 pt-5">
<h3 class="mb-0 fw-bold" t-field="forum.name"/>
<t t-if="is_view_active('website_forum.opt_post_count')">
<span t-if="forum.total_posts > 0" class="badge text-bg-dark">
<span t-out="forum.total_posts" class="me-1"/>
<t t-if="forum.total_posts > 1">posts</t>
<t t-else="">post</t>
</span>
<span t-else="" class="badge text-dark">No posts yet</span>
</t>
</div>
<div t-attf-class="#{content_class} mt-2 mt-md-0 d-flex flex-column h-100">
<a t-attf-href="/forum/#{slug(forum)}" class="text-reset" t-att-title="forum.name">
<h3 class="h4" t-field="forum.name"/>
</a>
<p class="m-0 flex-grow-1"
placeholder="Description"
t-field="forum.teaser"/>
<t t-if="is_view_active('website_forum.opt_post_count') or is_view_active('website_forum.opt_last_post')" t-call="website_forum.forum_post_options"/>
</div>
</div>
</a>
</div>
</t>
</template>
<template id="forum_post_options">
<div class="row">
<div t-attf-class="#{last_post_class}">
<div t-if="is_view_active('website_forum.opt_last_post') and forum.post_ids" class="text-truncate">Last Post: <a t-attf-href="/forum/#{slug(forum)}/#{slug(forum.last_post_id)}"><t t-esc="forum.last_post_id.name"/></a></div>
</div>
<div t-attf-class="#{nb_post_class}">
<div t-if="is_view_active('website_forum.opt_post_count')">Posts: <strong><t t-esc="forum.total_posts"/></strong></div>
</div>
</div>
</template>
<!-- (Options) Forum : List View
Display forums as a list -->
<template name="List View" id="website_forum.opt_list_view" inherit_id="website_forum.forum_all" active="False">
<xpath expr="//t[@t-set='col_class']" position="attributes">
<attribute name="t-valuef">mb-3 col-sm-12</attribute>
<attribute name="t-valuef">col-8 mb-3</attribute>
</xpath>
<xpath expr="//t[@t-set='img_class']" position="attributes">
<attribute name="t-valuef">col-md-3</attribute>
@@ -85,12 +69,6 @@
<xpath expr="//t[@t-set='content_class']" position="attributes">
<attribute name="t-valuef">col-md-9</attribute>
</xpath>
<xpath expr="//t[@t-set='last_post_class']" position="attributes">
<attribute name="t-valuef">col-md-10 pe-md-0 pt-3</attribute>
</xpath>
<xpath expr="//t[@t-set='nb_post_class']" position="attributes">
<attribute name="t-valuef">col-md-2 pe-md-3 pt-3</attribute>
</xpath>
</template>
<!-- (Options) Forum : Show Post Count
@@ -106,18 +84,13 @@
<template id="forum_all_oe_structure_forum_all_top" inherit_id="website_forum.forum_all" name="Forum Navigation (oe_structure_forum_all_top)">
<xpath expr="//*[hasclass('oe_structure')][@id='oe_structure_forum_all_top']" position="replace">
<div class="oe_structure oe_empty" id="oe_structure_forum_all_top">
<section class="s_cover parallax s_parallax_is_fixed bg-black-50 py-5" data-scroll-background-ratio="1" data-snippet="s_cover">
<span class="s_parallax_bg oe_img_bg" style="background-image: url('/web/image/website.s_cover_default_image'); background-position: 50% 0;"/>
<div class="o_we_bg_filter bg-black-50"/>
<div class="container">
<div class="row s_nb_column_fixed">
<div class="col-lg-12">
<h1 class="o_default_snippet_text text-center">Our forums</h1>
<p class="lead o_default_snippet_text mb-0" style="text-align: center;">
This community is for professional and enthusiast users, partners and programmers.
</p>
</div>
</div>
<section class="s_cover o_colored_level s_parallax_no_overflow_hidden pt96 pb48" data-scroll-background-ratio="0" data-snippet="s_cover" data-name="Cover">
<div class="s_allow_columns container">
<h1 style="text-align: center;"><font style="font-size: 62px; font-weight: bold;">Community Forums</font></h1>
<p class="lead" style="text-align: center;">Tap into the collective knowledge of our community by asking your questions in our forums,<br/> where helpful members are ready to assist you.</p>
<p style="text-align: center;">
<a class="mb-2" href="/profile/users?forum_origin=#{request.httprequest.path}" data-bs-original-title="" title="" target="_blank">Meet our community members</a>
</p>
</div>
</section>
</div>
@@ -17,82 +17,197 @@
<!-- PAGE INDEX -->
<!-- ============================================================ -->
<template id="forum_model_nav">
<!-- allows to be overridden such as in website_slides_forum -->
<t t-set="breadcrumb_kind" t-valuef="base"/>
<nav id="o_wforum_nav" t-attf-class="navbar d-flex align-items-center gap-2 #{_page_name == 'single_question' and 'mw-xl-75 mw-xxl-100'} px-0" aria-label="breadcrumb">
<t t-if="_page_name == 'single_question'">
<div class="flex-grow-1">
<div class="o_wforum_breadcrumb_root_single row g-0">
<div t-if="breadcrumb_kind == 'base'" class="col-10">
<a class="btn btn-lg btn-link px-0 pb-2" t-attf-href="/forum/#{ slug(forum) }">
<i class="d-inline-block oi oi-chevron-left me-1 small lh-50"/><t t-out="forum.name"/>
</a>
</div>
<div class="d-lg-none col-2 text-end">
<button class="btn position-relative ms-auto" data-bs-toggle="offcanvas" data-bs-target="#o_wforum_offcanvas">
<i class="fa fa-navicon"/>
</button>
</div>
</div>
<div class="d-flex gap-2 align-items-baseline">
<h3 t-attf-class="col-lg-10 mb-0" t-out="question.name"/>
<div class="col d-flex justify-content-end align-items-center">
<i t-if="question.state == 'close'" class="fa fa-lock ms-2 fs-4" title="Closed" data-bs-toggle="tooltip" data-bs-placement="top"/>
<span t-elif="not question.active" class="badge bg-danger">
<t t-if="question.state!='offensive'">Deleted</t>
<t t-if="question.state=='offensive'">Offensive</t>
<t t-if="question.state=='offensive' and question.closed_reason_id">
<t t-out="question.closed_reason_id.name.capitalize()"/>
</t>
</span>
<a t-elif="uid" type="button" aria-label="Favorite"
t-attf-data-href="/forum/#{slug(question.forum_id)}/question/#{slug(question)}/toggle_favourite"
t-attf-class="o_wforum_favourite_toggle btn btn-lg #{ not question.user_favourite and 'opacity-50' } opacity-hover-100 px-1">
<i t-attf-class="position-relative fa #{question.user_favourite and 'o_wforum_gold fa-star ' or 'fa-star-o '}"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="Favorite"/>
</a>
<t t-if="question.state == 'active'" t-call="website_forum.follow">
<t t-set="object" t-value="question"/>
<t t-set="icons_design" t-value="True"/>
<t t-set="btn_classes" t-value="'btn-lg opacity-50 opacity-100-hover' "/>
</t>
</div>
</div>
</div>
</t>
<t t-elif="_page_name == 'list_questions' or is_edit">
<t t-set="target" t-value="post.parent_id if is_answer else post"/>
<div class="o_wforum_breadcrumb_root_list_or_edit d-flex">
<div t-if="breadcrumb_kind=='base'" class="col-10 col-lg flex-grow-1">
<h5 t-if="not is_edit" class="fw-bold mb-0" t-out="forum.name"/>
<h5 t-elif="not is_answer" class="fw-bold mb-0">Edit Question</h5>
<h5 t-else="is_answer" class="fw-bold mb-0">Edit Answer</h5>
</div>
</div>
</t>
<ol t-else="" class="breadcrumb col-10 col-lg flex-grow-1 mb-0 p-0">
<li class="o_wforum_breadcrumb_root breadcrumb-item text-nowrap">
<h5 class="mb-0"><a t-attf-href="/forum/#{ slug(forum) }" t-out="forum.name"/></h5>
</li>
<li t-if="queue_type" class="breadcrumb-item text-nowrap d-none d-lg-flex">
<h5 class="mb-0">Moderation</h5>
</li>
<li class="breadcrumb-item text-nowrap">
<h5 class="fw-bold mb-0" t-out="_page_name_label"/>
</li>
</ol>
<t t-if="_page_name != 'single_question'">
<div class="d-lg-none text-end">
<button class="btn position-relative ms-auto" data-bs-toggle="offcanvas" data-bs-target="#o_wforum_offcanvas">
<i class="fa fa-navicon"/>
</button>
</div>
<div class="d-flex justify-content-lg-end gap-2 flex-grow-1 mw-lg-50">
<span t-if="tag and not tags" class="btn btn-light rounded ps-2">
<span t-if="tag"><i class="fa fa-tag me-1 text-muted"/><t t-out="tag.name"></t></span>
<a t-attf-href="#{ url_for('/forum') }/#{ slug(forum) }?#{ keep_query('search', 'sorting', 'my') }"
class="p-1 text-decoration-none text-muted"><i class="oi oi-close d-inline-block"/></a>
</span>
<span t-if="search" class="btn btn-light rounded ps-2">
<em t-if="search" class="text-muted">"<t t-out="search"/>"</em>
<a t-attf-href="?#{ keep_query('sorting', 'my') }" class="p-1 text-decoration-none text-muted">
<i class="oi oi-close d-inline-block"/>
</a>
</span>
<div t-if="question_count != 0 or tags" class="dropdown ms-lg-auto">
<t t-if="_page_name == 'tags'" t-set="tag_filter" t-value="keep_query('filters') and keep_query('filters').split('=')[1] or ''"/>
<a href="#" class="btn btn-light dropdown-toggle" data-bs-toggle="dropdown">
<!-- Foreach tag_filter/ Foreach filters -->
<t t-if="filters == 'all' or tag_filter == 'all' or not tag_filter and not filters">All</t>
<t t-if="_page_name == 'tags'">
<t t-if="tag_filter == 'followed'">Followed Tags</t>
<t t-elif="tag_filter == 'unused'">Unused Tags</t>
<t t-elif="tag_filter == 'most_used'">Most used Tags</t>
</t>
<t t-else="">
<t t-if="filters == 'solved'">Solved</t>
<t t-elif="filters == 'unsolved'">Unsolved</t>
<t t-elif="filters == 'unanswered'">Unanswered</t>
</t>
</a>
<div class="dropdown-menu" role="menu">
<a t-attf-href="?#{ keep_query('search', 'sorting', filters='all') }"
class="dropdown-item">
All
</a>
<div class="dropdown-divider"/>
<t t-if="_page_name == 'tags'">
<a t-if="not request.env.user._is_public()"
t-attf-href="?#{ keep_query('search', 'sorting', 'my', filters='followed') }"
class="dropdown-item">Followed Tags
</a>
<t t-if="forum.can_moderate">
<a t-attf-href="?#{ keep_query('search', 'sorting', 'my', filters='unused') }"
class="dropdown-item">Unused Tags
</a>
</t>
<a t-attf-href="?#{ keep_query('search', 'sorting', 'my', filters='most_used') }"
class="dropdown-item">Most Used Tags
</a>
</t>
<t t-else="">
<t t-if="forum.mode == 'questions'">
<a t-attf-href="?#{ keep_query('search', 'sorting', 'my', filters='solved') }"
class="dropdown-item">Solved
</a>
<a t-attf-href="?#{ keep_query('search', 'sorting', 'my', filters='unsolved') }"
class="dropdown-item">Unsolved
</a>
</t>
<a t-attf-href="?#{ keep_query('search', 'sorting', 'my', filters='unanswered') }"
class="dropdown-item">Unanswered
</a>
</t>
</div>
</div>
<!-- 'Search Box' [TODO] tags-->
<t t-if="question_count != 0" t-call="website.website_search_box_input">
<t t-if="_page_name == 'Tags'" t-set="search_type" t-valuef="forum_tags_only"/>
<t t-else="" t-set="search_type" t-valuef="forums"/>
<t t-if="_page_name == 'Tags'" t-set="action" t-value="'/forum/%s/tag' % (slug(forum))"/>
<t t-else="" t-set="action" t-value="'/forum/%s%s' % (slug(forum), tag and ('/tag/%s/questions' % slug(tag)) or '')"/>
<t t-set="display_description" t-valuef="true"/>
<t t-set="display_detail" t-valuef="true"/>
<t t-set="_form_classes" t-valuef="flex-grow-1"/>
<t t-set="_input_classes" t-valuef="border-0 bg-light"/>
<t t-set="_submit_classes" t-valuef="btn-light"/>
<input t-if="filters" type="hidden" name="filters" t-att-value="filters"/>
<input t-if="my" type="hidden" name="my" t-att-value="my"/>
<input t-if="sorting" type="hidden" name="sorting" t-att-value="sorting"/>
</t>
<t t-if="_page_name == 'list_questions'">
<t t-set="popover_title">You already have a pending post</t>
<t t-set="popover_content">Please wait for a moderator to validate your previous post before continuing.</t>
<div t-if="uid and forum and forum.has_pending_post"
data-bs-toggle="popover"
t-att-data-bs-title="popover_title"
t-att-data-bs-content="popover_content">
<a class="o_wforum_ask_btn disabled btn btn-primary mb-3 mb-md-0" t-attf-href="/forum/#{ slug(forum) }/ask">New Post</a>
</div>
<a t-elif="uid" role="button" type="button" t-attf-class="o_wforum_ask_btn btn btn-primary #{ user.karma &lt; forum.karma_ask and 'karma_required' }"
t-att-data-karma="forum.karma_ask" t-attf-href="/forum/#{slug(forum)}/ask">New Post</a>
</t>
<button t-if="website_forum_action and not queue_type == 'close' and question_count != 0" type="button" class="btn btn-secondary"
data-bs-toggle="modal" data-bs-target="#markAllAsSpam">
<i class="fa fa-bug me-1"/>Filter Tool
</button>
</div>
</t>
</nav>
</template>
<template id="header" name="Forum Index">
<t t-if="forum.active" t-call="website_forum.layout">
<section t-attf-class="s_cover parallax s_parallax_is_fixed py-3 #{forum.image_1920 and 'bg-black-50' or 'o_wforum_forum_card_bg text-white'}" data-scroll-background-ratio="1" data-snippet="s_cover">
<span t-if="forum.image_1920" class="s_parallax_bg oe_img_bg" t-attf-style="background-image: url('#{website.image_url(forum, 'image_1920')}'); background-position: center;"/>
<div t-if="forum.image_1920" class="o_we_bg_filter bg-black-50"/>
<div class="container">
<div class="row s_nb_column_fixed">
<div class="col-lg-12">
<h1 class="o_default_snippet_text text-center"><t t-esc="forum.name"></t></h1>
</div>
</div>
<div t-if="editable or (is_public_user and not forum_welcome_message)" t-att-class="'css_non_editable_mode_hidden' if editable else 'forum_intro'">
<div t-field="forum.welcome_message"/>
</div>
</div>
</section>
<div class="o_forum_nav_header_container mb-2 mb-md-4">
<t t-call="website_forum.forum_nav_header"></t>
</div>
<div id="wrap" t-attf-class="container #{website_forum_action}">
<div class="row">
<div class="col o_wprofile_email_validation_container mb16">
<div class="oe_structure" id="oe_structure_website_forum_header_1"/>
<div id="wrap" t-attf-class="o_wforum_wrapper position-relative container d-flex d-xl-grid px-lg-0 #{website_forum_action}">
<t t-set="_forum_path" t-value="url_for('/forum') + '/' + slug(forum)"/>
<t t-call="website_forum.user_sidebar"/>
<t t-call="website_forum.user_sidebar_mobile"/>
<div class="o_wforum_content_wrapper w-100 w-xl-auto px-lg-3">
<div class="o_wprofile_email_validation_container row g-0 justify-content-center mb-3 mb-lg-5 pt-2 pt-lg-3">
<t t-call="website_profile.email_validation_banner">
<t t-set="redirect_url" t-value="'/forum/%s' % forum.id"/>
<t t-set="additional_validation_email_message"> and join this Forum</t>
<t t-set="additional_validated_email_message"> You may now participate in our forums.</t>
</t>
<div class="row">
<div class="col">
<nav t-if="header.get('is_guidelines') or queue_type or new_question or is_edit or tags or reasons" aria-label="breadcrumb">
<ol class="breadcrumb p-0 bg-white">
<li class="breadcrumb-item">
<a t-attf-href="/forum/#{ slug(forum) }" t-esc="forum.name"/>
</li>
<t t-if="header.get('is_guidelines')">
<li class="breadcrumb-item">
<a t-if="header.get('is_karma')" t-attf-href="/forum/#{ slug(forum) }/faq">Guidelines</a>
<t t-else="">
Guidelines
</t>
</li>
<li t-if="header.get('is_karma')" class="breadcrumb-item">Karma</li>
</t>
<li t-if="queue_type" class="breadcrumb-item">Moderation</li>
<li t-if="queue_type == 'validation'" class="breadcrumb-item">To Validate</li>
<li t-if="queue_type == 'flagged'" class="breadcrumb-item">Flagged</li>
<li t-if="queue_type == 'offensive'" class="breadcrumb-item">Offensive</li>
<li t-if="reasons and offensive" class="breadcrumb-item">Offensive Post</li>
<li t-if="reasons and not offensive" class="breadcrumb-item">Close Post</li>
<li t-if="new_question" class="breadcrumb-item">New Post</li>
<t t-if="is_edit">
<t t-set="target" t-value="post.parent_id if is_answer else post"/>
<li class="breadcrumb-item text-truncate" style="max-width:150px">
<a t-attf-href="/forum/#{ slug(forum) }/#{ slug(target)}" title="Back to Question">
<t t-esc="target.name"/>
</a>
</li>
<li t-if="not is_answer" class="breadcrumb-item">Edit Question</li>
<li t-if="is_answer" class="breadcrumb-item">Edit Answer</li>
</t>
<li t-elif="tags" class="breadcrumb-item">All Tags</li>
</ol>
</nav>
<t t-out="0"/>
</div>
<aside t-if="uid" class="d-none d-lg-flex justify-content-end col-auto">
<t t-call="website_forum.user_sidebar"/>
</aside>
</div>
<t t-call="website_forum.forum_model_nav"/>
<t t-out="0"/>
</div>
</div>
</div>
<div class="oe_structure" id="oe_structure_website_forum_header_1"/>
</t>
<t t-else="" t-call="website_forum.layout">
<t t-set="head">
@@ -104,183 +219,177 @@
</t>
</template>
<template id="forum_nav_header">
<div class="navbar navbar-expand-sm navbar-light">
<div class="container flex-wrap flex-md-nowrap">
<a t-if="has_back_button_url" class="btn btn-light border me-2 o_back_button" title="Back">
<i class="oi oi-chevron-left me-1"/>Back
<!-- User sidebar -->
<template id="user_sidebar">
<aside class="o_wforum_sidebar z-index-1 d-none d-lg-flex flex-column h-100 o_cc o_cc2">
<div class="flex-grow-1 px-2">
<t t-call="website_forum.user_sidebar_header"/>
<t t-call="website_forum.user_sidebar_body"/>
</div>
<div class="o_wforum_sidebar_footer mt-3 px-3 pb-2 text-center">
<a class="btn btn-sm btn-link" t-attf-href="/forum/#{slug(forum)}/faq">
<i class="fa fa-info-circle fa-fw"/> About this forum
</a>
<!-- Desktop -->
<ul class="navbar-nav me-auto d-none d-lg-flex">
<li class="nav-item">
<a t-if="request.website.forum_count > 1" class="nav-link" href="/forum/" title="All forums">
All Forums
</a>
</li>
<li class="nav-item">
<a t-attf-href="/forum/#{ slug(forum) }" t-attf-class="nav-link #{question_count and 'active'}">Topics</a>
</li>
<li class="nav-item">
<a t-attf-href="/profile/users?forum_origin=#{request.httprequest.path}"
t-attf-class="nav-link #{searches.get('users') and 'active'}">People</a>
</li>
<li class="nav-item">
<a t-attf-href="/forum/#{ slug(forum) }/tag" t-attf-class="nav-link #{searches.get('tags') and 'active'}">Tags</a>
</li>
<li class="nav-item">
<a t-attf-href="/profile/ranks_badges?badge_category=forum&amp;url_origin=#{request.httprequest.path}&amp;name_origin=#{forum.name}"
t-attf-class="nav-link #{searches.get('badges') and 'active'}">Badges</a>
</li>
<li class="nav-item">
<a t-attf-href="/forum/#{ slug(forum) }/faq" t-attf-class="nav-link #{header.get('is_guidelines') and 'active'}">About</a>
</li>
</ul>
</div>
</aside>
</template>
<!-- Mobile -->
<ul class="navbar-nav d-lg-none flex-row flex-grow-1 justify-content-between">
<span class="navbar-text me-1">Go to:</span>
<li class="nav-item dropdown me-auto">
<a class="nav-link active dropdown-toggle" type="button" data-bs-toggle="dropdown">
<t t-if="searches.get('users')">People</t>
<t t-elif="searches.get('tags')">Tags</t>
<t t-elif="searches.get('badges')">Badges</t>
<t t-elif="header.get('is_guidelines')">About</t>
<t t-elif="uid and my == 'favourites'">Favourites</t>
<t t-elif="uid and my == 'mine'">My Posts</t>
<t t-elif="uid and my == 'followed'">Following</t>
<t t-elif="question">Question</t>
<t t-else="">All Topics</t>
</a>
<div class="dropdown-menu position-absolute">
<a t-if="searches or my or question" t-attf-href="/forum/#{ slug(forum) }" class="dropdown-item">All Topics</a>
<a t-if="not searches.get('users')" t-attf-href="/profile/users?forum_origin=#{request.httprequest.path}" class="dropdown-item">People</a>
<a t-if="not searches.get('tags')" t-attf-href="/forum/#{slug(forum)}/tag" class="dropdown-item">Tags</a>
<a t-if="not searches.get('badges')" t-attf-href="/profile/ranks_badges?badge_category=forum&amp;url_origin=#{request.httprequest.path}&amp;name_origin=#{forum.name}" class="dropdown-item">Badges</a>
<a t-if="not header.get('is_guidelines')" t-attf-href="/forum/#{ slug(forum) }/faq" class="dropdown-item">About</a>
<t t-if="uid">
<div class="dropdown-divider"/>
<a t-att-href="'/forum/%s/user/%s?forum_origin=%s' % (slug(forum), uid, request.httprequest.path)"
class="dropdown-item">My profile</a>
<a t-if="my != 'mine'" t-att-href="url_for('') + '?' + keep_query( 'search', 'sorting', 'filters', my='mine')" class="dropdown-item">My Posts</a>
<a t-if="my != 'favourites'" t-att-href="url_for('') + '?' + keep_query( 'search', 'sorting', 'filters', my='favourites')" class="dropdown-item">My Favourites</a>
<a t-if="my != 'followed'" t-att-href="url_for('') + '?' + keep_query( 'search', 'sorting', 'filters', my='followed')" class="dropdown-item">I'm Following</a>
<a t-if="my != 'tagged'" t-att-href="url_for('') + '?' + keep_query( 'search', 'sorting', 'filters', my='tagged')" class="dropdown-item">Tags I Follow</a>
</t>
<div groups="base.group_erp_manager" class="dropdown-divider"/>
<a groups="base.group_erp_manager" t-attf-href="/web#id=#{forum.id}&amp;view_type=form&amp;model=forum.forum" class="dropdown-item">Edit Forum in Backend</a>
</div>
</li>
<t t-if="user.karma>=forum.karma_moderate">
<li t-if="forum.count_posts_waiting_validation" class="nav-item">
<a class="nav-link" t-attf-href="/forum/#{slug(forum)}/validation_queue">
<i class="fa fa-check-square-o fa-fw text-warning"/>
<b t-esc="forum.count_posts_waiting_validation" class="text-800"/>
</a>
</li>
<li t-if="forum.count_flagged_posts" class="nav-item ms-2">
<a class="nav-link" t-attf-href="/forum/#{slug(forum)}/flagged_queue">
<i class="fa fa-flag fa-fw text-danger"/>
<b t-esc="forum.count_flagged_posts" class="text-800"/>
</a>
</li>
</t>
<!-- Mobile 'Search Box' toggler-->
<li class="nav-item ms-4">
<a data-bs-toggle="collapse" href="#o_wforum_search" class="nav-link"><i class="fa fa-search"/></a>
</li>
</ul>
<!-- 'Search Box' -->
<t t-call="website.website_search_box_input">
<t t-set="_classes" t-valuef="w-100 w-md-auto pt-2 pt-md-0 d-md-flex"/>
<t t-set="search_type" t-valuef="forums"/>
<t t-set="action" t-value="'/forum/%s%s' % (slug(forum), tag and ('/tag/%s/questions' % slug(tag)) or '')"/>
<t t-set="display_description" t-valuef="true"/>
<t t-set="display_detail" t-valuef="true"/>
<input t-if="filters" type="hidden" name="filters" t-att-value="filters"/>
<input t-if="my" type="hidden" name="my" t-att-value="my"/>
<input t-if="sorting" type="hidden" name="sorting" t-att-value="sorting"/>
</t>
<!-- Off canvas user sidebar on mobile -->
<template id="user_sidebar_mobile">
<div id="o_wforum_offcanvas" class="o_website_offcanvas offcanvas offcanvas-end d-lg-none mw-75 p-0 overflow-visible">
<button type="button" class="btn-close mt-3 ms-auto me-3" data-bs-dismiss="offcanvas" aria-label="Close"/>
<div class="offcanvas-header align-items-start px-0" t-call="website_forum.user_sidebar_header"/>
<div class="offcanvas-body d-flex flex-column ps-0 py-0">
<t t-call="website_forum.user_sidebar_body"/>
<div class="mb-2 d-flex justify-content-center align-items-end flex-grow-1">
<a class="btn btn-sm btn-link" t-att-href="_forum_path + '/faq'">
<i class="fa fa-info-circle fa-fw"/> About this forum
</a>
</div>
</div>
</div>
</template>
<template id="website_forum.user_sidebar">
<nav t-if="uid" class="o_wforum_nav nav nav-pills flex-column ms-4">
<a t-attf-href="/forum/#{slug(forum)}/user/#{uid}?forum_origin=#{request.httprequest.path}"
class="nav-link d-flex align-items-center rounded-pill text-reset mb-2"
data-bs-toggle="tooltip"
data-trigger="hover"
<!-- User sidebar elements -->
<template id="user_sidebar_header">
<t t-set="_location" t-value="('/tag/' + slug(tag) + '/questions?') if tag else '?' "/>
<div t-if="not uid" class="o_wforum_sidebar_section mt-4 text-center">
<div class="alert alert-info mb-2"><span>You need to be registered to interact with the community.</span>
<a t-if="is_public_user and forum_welcome_message" href="/web/login" class="btn btn-primary mt-2">Sign up</a>
</div>
</div>
<div t-if="uid" class="o_wforum_sidebar_section mt-4 mb-2">
<a t-attf-href="#{ _forum_path }/user/#{ uid }?forum_origin=#{ request.httprequest.path }"
class="btn w-100 py-1 text-start d-flex align-items-center" data-bs-toggle="tooltip" data-trigger="hover"
title="My profile">
<img class="o_forum_avatar rounded-circle me-1" t-att-src="website.image_url(user, 'avatar_128', '30x30')" alt="Avatar"/>
<div>
<h6 class="my-0" t-esc="user_id.name"/>
<small class="text-muted fw-bold"><t t-esc="user_id.karma"/>xp</small>
<img class="o_wforum_avatar rounded-circle" t-att-src="request.website.image_url(user, 'avatar_128', '60x60')" alt="Avatar"/>
<div class="d-flex flex-column justify-content-center ms-2">
<h6 class="mb-1" t-out="user_id.name"/>
<small class="text-muted"><t t-out="user_id.karma"/> XP</small>
</div>
</a>
</div>
</template>
<template id="user_sidebar_body">
<div class="o_wforum_sidebar_section">
<t t-set="location" t-value="url_for('/forum') + '/' + slug(forum) + ( ('/tag/' + slug(tag) + '/questions?') if tag else '?' )"/>
<!-- My Posts -->
<span t-if="my == 'mine'" class="nav-link rounded-pill mb-2 active fw-bold">
<i class="fa fa-question-circle-o fa-fw"/> My Posts
<a class="text-reset pull-right no-decoration" t-att-href="location + keep_query('search', 'filters', 'sorting')">&#215;</a>
</span>
<a t-else="" class="nav-link rounded-pill mb-2 text-reset" t-att-href="location + keep_query('search', 'filters', 'sorting', my='mine')">
<i class="fa fa-question-circle-o fa-fw"/> My Posts
<!-- All Posts -->
<a t-attf-class="nav-link my-1 py-1 #{ 'bg-primary-light text-primary disabled' if request.httprequest.path == _forum_path and no_filters and not any([my, queue_type, tags]) else 'text-reset'}" t-att-href="location">
<i t-attf-class="fa fa-list fa-fw #{ 'opacity-50' if request.httprequest.path != _forum_path or my or queue_type else '' }"/> Posts
</a>
<!-- My Favourites -->
<span t-if="my == 'favourites'" class="nav-link rounded-pill mb-2 active fw-bold">
<i class="fa fa-star fa-fw"/> Favourites
<a class="text-reset pull-right no-decoration" t-att-href="location + keep_query( 'search', 'filters', 'sorting')">&#215;</a>
</span>
<a t-else="" t-attf-class="nav-link rounded-pill mb-2 text-reset" t-att-href="location + keep_query( 'search', 'filters', 'sorting', my='favourites')">
<i class="fa fa-star fa-fw"/> Favourites
</a>
<!-- My Followed posts -->
<span t-if="my == 'followed'" class="nav-link rounded-pill mb-2 active fw-bold">
<i class="fa fa-bell fa-fw"/> Followed Posts
<a class="text-reset pull-right no-decoration" t-att-href="location + keep_query( 'search', 'filters', 'sorting')">&#215;</a>
</span>
<a t-else="" class="nav-link rounded-pill mb-2 text-reset" t-att-href="location + keep_query( 'search', 'filters', 'sorting', my='followed')">
<i class="fa fa-bell fa-fw"/> Followed Posts
</a>
<!-- My Followed tags -->
<span t-if="my == 'tagged'" class="nav-link rounded-pill mb-2 active fw-bold">
<i class="fa fa-tags fa-fw"/> Followed Tags
<a class="text-reset pull-right no-decoration" t-att-href="location + keep_query( 'search', 'filters', 'sorting')">&#215;</a>
</span>
<a t-else="" class="nav-link rounded-pill mb-2 text-reset" t-att-href="location + keep_query( 'search', 'filters', 'sorting', my='tagged')">
<i class="fa fa-tags fa-fw"/> Followed Tags
</a>
<!-- Moderation Tools -->
<t t-if="user.karma>=forum.karma_moderate or queue_type">
<span class="nav-link disabled mt-3">
<div class="pb-1 border-bottom text-muted">Moderation tools</div>
</span>
<span t-if="queue_type == 'validation'" class="nav-link rounded-pill mb-2 active fw-bold">
<i class="fa fa-check-square-o fa-fw"/> To Validate
<a class="text-reset pull-right no-decoration" t-attf-href="/forum/#{ slug(forum) }">&#215;</a>
</span>
<a t-else="" class="nav-link rounded-pill text-reset" t-attf-href="/forum/#{slug(forum)}/validation_queue">
<i class="fa fa-check-square-o fa-fw"/> To Validate
<span t-attf-class="badge pull-right #{forum.count_posts_waiting_validation > 0 and 'text-bg-warning' or 'text-bg-light'}" t-esc="forum.count_posts_waiting_validation"/>
<t t-if="uid">
<!-- My Posts -->
<a t-attf-class="nav-link my-1 py-1 #{my == 'mine' and 'bg-primary-light text-primary disabled' or 'text-reset'}" t-att-href="location + keep_query('search', 'filters', 'sorting', my='mine')">
<i t-attf-class="fa fa-user-circle fa-fw #{my != 'mine' and 'opacity-50'}"/> My Posts
</a>
<span t-if="queue_type == 'offensive' or queue_type == 'flagged'" class="nav-link rounded-pill mb-2 active fw-bold">
<i class="fa fa-flag fa-fw"/> Flagged
<a class="text-reset pull-right no-decoration" t-attf-href="/forum/#{ slug(forum) }">&#215;</a>
</span>
<a t-else="" class="nav-link rounded-pill text-reset" t-attf-href="/forum/#{slug(forum)}/flagged_queue">
<i class="fa fa-flag fa-fw"/> Flagged
<span id="count_flagged_posts" t-attf-class="badge pull-right #{forum.count_flagged_posts > 0 and 'text-bg-danger' or 'text-bg-light'}" t-esc="forum.count_flagged_posts"/>
<!-- My Favourites -->
<a t-attf-class="nav-link my-1 py-1 #{my == 'favourites' and 'bg-primary-light text-primary disabled' or 'text-reset'}" t-att-href="location + keep_query( 'search', 'filters', 'sorting', my='favourites')">
<i t-attf-class="fa fa-star fa-fw #{my != 'favourites' and 'opacity-50'}"/> Favourites
</a>
</t>
</nav>
<!-- People -->
<a t-attf-class="nav-link my-1 py-1 text-reset" t-attf-href="/profile/users?forum_origin=#{request.httprequest.path}">
<i class="fa fa-users fa-fw opacity-50"/> People
</a>
<!-- Badges -->
<a t-attf-class="nav-link my-1 py-1 text-reset" t-attf-href="/profile/ranks_badges?forum_origin=#{request.httprequest.path}">
<i class="fa fa-shield fa-fw opacity-50"/> Badges
</a>
</div>
<div t-if="user.karma >= forum.karma_moderate" class="o_wforum_sidebar_section pt-3">
<!-- Moderation Tools -->
<div class="px-3 pb-1 fw-bold">Moderation tools</div>
<a t-attf-class="nav-link my-1 py-1 #{queue_type == 'validation' and 'bg-primary-light text-primary disabled' or 'text-reset'}" t-attf-href="/forum/#{slug(forum)}/validation_queue">
<i t-attf-class="fa fa-check-square-o fa-fw #{queue_type != 'validation' and 'opacity-50'}"/> To Validate
<span id="count_posts_queue_validation" t-attf-class="badge #{forum.count_posts_waiting_validation > 0 and 'text-bg-warning' or 'd-none'}" t-out="forum.count_posts_waiting_validation"/>
</a>
<a t-attf-class="nav-link my-1 py-1 #{queue_type == 'offensive' or queue_type == 'flagged' and 'bg-primary-light text-primary disabled' or 'text-reset'}" t-attf-href="/forum/#{slug(forum)}/flagged_queue">
<i t-attf-class="fa fa-flag fa-fw #{queue_type != 'flagged' and 'opacity-50'}"/> Flagged
<span id="count_posts_queue_flagged" t-attf-class="badge #{forum.count_flagged_posts > 0 and 'text-bg-danger' or 'd-none'}" t-out="forum.count_flagged_posts"/>
</a>
<a t-attf-class="nav-link my-1 py-1 #{queue_type == 'close' and 'bg-primary-light text-primary disabled' or 'text-reset'}" t-attf-href="/forum/#{slug(forum)}/closed_posts">
<i t-attf-class="fa fa-window-close fa-fw #{queue_type != 'close' and 'opacity-50'}"/> Closed
</a>
</div>
<div t-if="forum.post_ids.tag_ids" class="o_wforum_sidebar_section pt-3">
<div class="d-flex align-items-center px-3 pb-1 fw-bold">Tags
<a class="ms-auto px-0 fw-normal" t-att-href="_forum_path + '/tag'">
<small>View all</small>
</a>
</div>
<t t-foreach="forum.post_ids.tag_ids" t-as="tag">
<a t-attf-href="#{ _forum_path }/tag/#{ slug(tag) }/questions?#{ keep_query( 'search', 'sorting', 'my', filters='tag') }"
t-attf-class="nav-link my-1 py-1 text-reset">
<i class="fa fa-tag fa-fw small opacity-50"/>
<t t-out="tag.name"/>
</a>
</t>
</div>
</template>
<template id="header_welcome_message" inherit_id="website_forum.header" name="Forum Welcome Message (oe_structure_forum_top)">
<xpath expr="//*[hasclass('oe_structure')][@id='oe_structure_website_forum_header_1']" position="replace">
<div class="oe_structure oe_empty" id="oe_structure_website_forum_header_1">
<section t-if="editable or (is_public_user and not forum_welcome_message)" t-attf-class="s_cover parallax s_parallax_is_fixed bg-black-50 pt48 pb48 #{'css_non_editable_mode_hidden' if editable else 'forum_intro'}" data-scroll-background-ratio="1" data-snippet="s_cover">
<span t-if="forum.image_1920" class="s_parallax_bg oe_img_bg" t-attf-style="background-image: url('#{request.website.image_url(forum, 'image_1920')}'); background-position: center;"/>
<div t-if="forum.image_1920" class="o_we_bg_filter bg-black-50"/>
<div class="container s_allow_columns">
<div class="row" data-row-count="5">
<div class="o_colored_level offset-lg-3 col-lg-6">
<div t-field="forum.welcome_message" class="container s_allow_columns"/>
</div>
</div>
</div>
</section>
</div>
</xpath>
</template>
<template id="no_results_message">
<t t-set="_filters_str">
<t t-if="filters == 'unanswered'">&amp;nbsp;unanswered</t>
<t t-elif="filters == 'solved'">&amp;nbsp;solved</t>
<t t-elif="filters == 'unsolved'">&amp;nbsp;unsolved</t>
</t>
<t t-set="_my_str">
<t t-if="my == 'favourites'">&amp;nbsp;in your favourites</t>
<t t-elif="my == 'mine'">&amp;nbsp;in your posts</t>
</t>
<t t-set="_search_str">&amp;nbsp;<t t-if="search">matching "<em class="fw-bold text-break" t-out="original_search or search"/>"</t></t>
<t t-set="_search_and_tag_str" t-value="'&amp;nbsp;and&amp;nbsp;' if search and tag else ''"/>
<t t-set="_tag_str"><t t-if="tag">using the <span class="px-2 py-1 rounded bg-300" t-out="tag.name"/> tag</t></t>
<t t-if="queue_type">
<t t-set="_no_results_title">You've Completely Caught&amp;nbsp;Up!</t>
<t t-set="result_msg">Go enjoy a cup of coffee.</t>
</t>
<t t-else="">
<t t-set="_no_results_title">Oops!</t>
<t t-set="result_msg">Sorry, we could not find any<b>%s</b> results<b>%s</b>%s%s%s.</t>
<t t-set="result_msg" t-value="result_msg % (_filters_str.strip(), _my_str.strip(), _search_str.strip(), _search_and_tag_str.strip(), _tag_str.strip())"></t>
</t>
<div t-attf-class="#{queue_type and 'o_caught_up_alert'} row g-0 justify-content-center #{'d-none' if start_hidden else ''}">
<div class="p-3 d-flex flex-column align-items-center">
<img t-attf-src="/website_forum/static/src/img/#{'tasks' if queue_type else 'empty'}.svg" class="img img-fluid mb-3" width="150" alt="Checked boxes"/>
<h5 t-out="_no_results_title"/>
<span t-out="result_msg"/>
<span t-if="original_search">Showing results for <em class="fw-bold" t-out="search"/> instead.</span>
<div t-if="question_count == 0 and forum.total_posts != 0" class="mt-3 text-start">
<p><i class="fa fa-check fa-fw me-1"/>Check your spelling and try again.</p>
<p><i class="fa fa-check fa-fw me-1"/>Try searching for one or two words.</p>
<p><i class="fa fa-check fa-fw me-1"/>Be less specific in your wording for a wider search result.</p>
</div>
<span t-elif="forum.total_posts == 0">Actually, there are no posts in this forum yet.</span>
<div class="mt-3">
<a t-if="uid and forum.total_posts == 0" role="button" type="button" class="o_forum_ask_btn btn btn-primary ms-2" t-attf-href="/forum/#{slug(forum)}/ask">Start by creating a post</a>
<a t-else="" role="button" type="button" class="btn btn-primary mt-2" t-attf-href="/forum/#{slug(forum)}/">Go back to questions list</a>
</div>
</div>
</div>
</template>
<!-- ERROR MANAGEMENT -->
@@ -292,7 +401,7 @@
<h1 class="mt-4">Question not found!</h1>
<p>Sorry, this question is not available anymore.</p>
<p>
<a t-attf-href="/forum">Return to the question list.</a>
<a class="btn-link" t-attf-href="/forum"><i class="oi oi-arrow-right display-inline-block"/> Return to questions list</a>
</p>
</t>
</template>
@@ -12,62 +12,42 @@
</t>
</template>
<template id="moderation_display_post_answer">
<div class="clearfix">
<div class="question-name">
<a style="font-size: 15px;" t-attf-href="/forum/#{ slug(answer.forum_id) }/#{ answer.parent_id.id }/#answer-#{ answer.id }" t-esc="answer.parent_id.name"/>
<b>[Answer]</b>
<span t-if="not answer.active and answer.state=='offensive'"><b> [Offensive]</b></span>
<span t-if="not answer.active and answer.state=='offensive' and answer.closed_reason_id"><b> [<t t-esc="answer.closed_reason_id.name[0].upper() + answer.closed_reason_id.name[1:]"/>]</b></span>
<t t-if="answer.state == 'flagged'">
<small class="text-muted">
Flagged
</small>
</t>
<t t-if="len(answer.website_message_ids)&gt;0">
(<t t-esc="len(answer.website_message_ids)"/>
<t t-if="len(answer.website_message_ids)&gt;1"> Comments</t>
<t t-if="len(answer.website_message_ids)&lt;=1"> Comment</t>)
</t>
</div>
<div class="clearfix"><span t-field="answer.content" class="oe_no_empty"/></div>
</div>
</template>
<!-- Moderation: close a post -->
<template id="mark_as_offensive">
<div class="o_mark_offensive">
<p class="text-muted" t-if="not offensive">
<div class="alert bg-light text-muted" t-if="not offensive">
If you close this post, it will be hidden for most users. Only
users having a high karma can see closed posts to moderate
them.
</p>
<p class="text-muted" t-if="offensive">
</div>
<div class="alert bg-light text-muted" t-if="offensive">
If you mark this post as offensive, it will be hidden for most users. Only
users having a high karma can see offensive posts to moderate
them.
</p>
<form t-attf-action="/forum/#{ slug(forum) }/#{offensive and 'post' or 'question'}/#{slug(question)}/#{offensive and 'mark_as_offensive' or 'close'}" method="post" role="form" class="mt32 js_website_submit_form">
</div>
<form t-attf-action="/forum/#{ slug(forum) }/#{offensive and 'post' or 'question'}/#{slug(question)}/#{offensive and 'mark_as_offensive' or 'close'}" method="post" role="form" class="js_website_submit_form">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<input name="post_id" t-att-value="question.id" type="hidden"/>
<div class="mb-3">
<label class="form-label" for="post">Post:</label>
<input type="text" disabled="True" class="form-control-plaintext" name="post" t-att-value="question.name if not question.parent_id else question.parent_id.name"/>
<div class="row g-0 mb-3">
<label class="form-label col-lg-2" for="post">Post:</label>
<input type="text" disabled="True" class="form-control-plaintext col" name="post" t-att-value="question.name if not question.parent_id else question.parent_id.name"/>
</div>
<div class="mb-4">
<label class="form-label" for="reason"><t t-if="offensive">Offensive</t><t t-if="not offensive">Closing</t> Reason:</label>
<select class="form-select form-select form-select-lg" name="reason_id">
<div class="row g-0 mb-4">
<label class="form-label col-lg-2" for="reason"><t t-if="offensive">Offensive</t><t t-if="not offensive">Closing</t> Reason:</label>
<select class="form-select form-select col" name="reason_id">
<t t-foreach="reasons or []" t-as="reason">
<option t-att-value="reason.id" t-att-selected="reason.id == question.closed_reason_id.id"><t t-esc="reason.name"/></option>
<option t-att-value="reason.id" t-att-selected="reason.id == question.closed_reason_id.id"><t t-out="reason.name"/></option>
</t>
</select>
</div>
<div class="mb-3">
<button type="submit" class="btn btn-danger">
<t t-if="offensive">Mark as offensive</t>
<t t-if="not offensive">Close post</t>
</button>
<a role="button" class="btn btn-light border ms-2" t-attf-href="/forum/#{ slug(forum) }/#{ slug(question) }">Discard</a>
<div class="row g-0 mb-3">
<div class="col offset-lg-2">
<button type="submit" class="btn btn-danger">
<t t-if="offensive">Mark as offensive</t>
<t t-if="not offensive">Close post</t>
</button>
<a role="button" class="btn btn-link ms-2" t-attf-href="/forum/#{ slug(forum) }/#{ slug(question) }">Discard</a>
</div>
</div>
</form>
</div>
@@ -80,23 +60,22 @@
</template>
<template id="moderation_queue" name="Forum Moderation Queue">
<t t-set="_page_name" t-value="queue_type"/>
<t t-set="_page_name_label" t-if="queue_type == 'validation'">To Validate</t>
<t t-set="_page_name_label" t-elif="queue_type == 'flagged'">Flagged</t>
<t t-set="_page_name_label" t-elif="queue_type == 'offensive'">Offensive Posts</t>
<t t-set="_page_name_label" t-elif="queue_type == 'close'">Closed Posts</t>
<t t-set="website_forum_action" t-valuef="o_wforum_moderation_queue"/>
<t t-call="website_forum.header">
<t t-set="website_forum_action" t-value="'o_wforum_moderation_queue'"/>
<div t-if="len(posts_ids) > 0" class="mb-2 text-end">
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#markAllAsSpam"><i class="fa fa-bug"></i> Filter Tool</button>
</div>
<div t-attf-class="o_caught_up_alert alert text-center #{len(posts_ids) and 'd-none'}">
<i class="fa fa-check text-success d-block display-2"></i>
<b>You've Completely Caught Up!</b><br/>
<t t-if="queue_type == 'validation'">No post to be validated</t>
<t t-if="queue_type == 'flagged'">No flagged posts</t>
</div>
<t t-call="website_forum.no_results_message"><t t-set="hide_alert" t-value="len(posts_ids) > 0"/></t>
<div id="queue_type" t-att-data-queue-type="queue_type"/>
<div class="modal fade" t-att-data-spam-ids="str(posts_ids.ids)" id="markAllAsSpam" tabindex="-1" role="dialog" aria-labelledby="markAllAsSpam" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header d-flex align-items-center pb-0">
<div class="text-muted me-2">Filter by:</div>
<div class="me-2 text-muted">Filter by:</div>
<ul class="nav nav-tabs border-bottom-0" id="myTab" role="tablist">
<li class="nav-item">
<a class="nav-link active spam_menu" id="user-tab" data-bs-toggle="tab" href="#spam_user" role="tab" aria-controls="user" aria-selected="true"><i class="fa fa-user"/> User</a>
@@ -118,10 +97,10 @@
<div class="card mb-2">
<div class="card-body py-2">
<div class="form-check">
<input type="checkbox" t-att-value="create_user.id" class="form-check-input" t-attf-id="user_#{create_user.id}"/>
<label class="form-check-label" t-attf-for="user_#{create_user.id}">
<img class="d-inline img o_forum_avatar" t-att-src="website.image_url(create_user, 'avatar_128', '40x40')" alt="Avatar"/>
<span t-esc="create_user.name" class="d-inline"/>
<input type="checkbox" t-att-value="create_user.id" class="form-check-input" t-attf-id="user_#{ create_user.id }"/>
<label class="form-check-label" t-attf-for="user_#{ create_user.id }">
<img class="d-inline img o_wforum_avatar" t-att-src="request.website.image_url(create_user, 'avatar_128', '40x40')" alt="Avatar"/>
<span t-out="create_user.name" class="d-inline"/>
</label>
</div>
</div>
@@ -138,7 +117,7 @@
<input type="checkbox" class="form-check-input" t-attf-id="country_#{country.id}" t-att-value="country.id"/>
<label class="form-check-label" t-attf-for="country_#{country.id}">
<span t-field="country.image_url" t-options='{"widget": "image_url", "class": "country_flag"}' class="me-2"/>
<span t-esc="country.name"/>
<span t-out="country.name"/>
</label>
</div>
</div>
@@ -160,32 +139,86 @@
</div>
</div>
<div t-foreach="posts_ids" t-as="question" class="post_to_validate card mb-3">
<div class="card-header">
<div class="row align-items-baseline">
<h6 class="col-md-2 mb-0">
<b t-if="question.parent_id">Answer:</b>
<b t-else="">Question:</b>
</h6>
<div class="col d-flex align-items-center justify-content-between">
<a t-attf-href="/forum/#{slug(question.forum_id)}/#{slug(question)}#{answer and ('/#answer-%s' % answer.id)}"
t-attf-title="Read: #{question.name}"
t-attf-class="text-reset"
t-out="question.name"/>
</div>
<div class="col-lg-3 d-lg-flex">
<span t-if="not question.active and question.state=='offensive'" class="badge bg-warning text-wrap flex-grow-1">Offensive</span>
<span t-if="not question.active and question.state=='offensive' and question.closed_reason_id" class="badge bg-warning text-wrap flex-grow-1" t-out="question.closed_reason_id.name.capitalize()"/>
<span t-if="question.closed_reason_id" class="badge bg-danger text-wrap flex-grow-1" t-out="question.closed_reason_id.name.capitalize()"/>
</div>
</div>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-2 col-sm-3 o_js_validation_queue border-end">
<div class="text-center d-flex align-items-end flex-sm-column justify-content-end align-items-sm-stretch">
<a t-attf-href="/forum/#{slug(forum)}/post/#{slug(question)}/validate" title="Validate" aria-label="Validate" data-bs-toggle="tooltip" class="btn border-success m-1 bg-white"><i class="fa fa-check text-success"/></a>
<a t-if="queue_type == 'validation'" t-attf-href="/forum/#{slug(forum)}/post/#{slug(question)}/refuse" data-bs-toggle="tooltip" title="Refuse" aria-label="Refuse" class="btn border-danger m-1 bg-white"><i class="fa fa-times text-danger"/></a>
<a t-if="queue_type == 'flagged'" t-attf-href="/forum/#{slug(forum)}/post/#{slug(question)}/ask_for_mark_as_offensive" data-bs-toggle="tooltip" aria-label="Mark as offensive" title="Mark as offensive" class="btn border-danger m-1 bg-white"><i class="fa fa-times text-danger"/></a>
<a href="#" t-if="queue_type == 'offensive'" disabled="True" aria-label="Offensive" title="Offensive" data-bs-toggle="tooltip" class="btn border-danger bg-white"><i class="fa fa-times m-1 text-danger"/></a>
</div>
</div>
<t t-if="question.parent_id">
<div t-foreach="question" t-as="answer" class="col-md-10 col-sm-8">
<t t-call="website_forum.moderation_display_post_answer"/>
<small class="text-muted">
<i t-attf-class="fa fa-user" role="img" aria-label="Question" />
<span>By </span><span t-field="question.create_uid" t-options='{"widget": "contact", "country_image": True, "fields": ["name", "country_id"]}' style="display: inline-block;"/>
<i class="ms-4 mr4 fa fa-calendar"/><span t-field="question.write_date" t-options='{"format":"short"}'/>
<span t-if="question.state == 'flagged'" class="text-black">
<i class="fa fa-flag ms-4 mr4"/>
Flagged
</span>
</small>
<div t-foreach="question" t-as="answer" class="d-flex flex-column col-md-9 col-sm-8 mb-2">
<span t-field="answer.content" class="oe_no_empty flex-grow-1 mb-2"/>
<div class="mt-auto">
<t t-call="website_forum.author_box">
<t t-set="object" t-value="question"/>
<t t-set="_image_classes" t-value="'rounded-circle'"/>
<t t-set="allow_biography" t-value="True"/>
<t t-set="show_image" t-value="True"/>
<t t-set="show_name" t-value="True"/>
<t t-set="compact" t-value="True"/>
</t>
<small class="text-muted">
<i class="ms-4 mr4 fa fa-calendar"/><span t-field="question.write_date" t-options='{"format":"short"}'/>
</small>
</div>
</div>
</t>
<div t-if="not question.parent_id" class="col">
<t t-call="website_forum.moderation_display_post_question_block"/>
<div t-if="not question.parent_id" class="d-flex flex-column col">
<span t-field="question.content" class="oe_no_empty flex-grow-1"/>
<div class="mt-auto">
<t t-call="website_forum.author_box">
<t t-set="object" t-value="question"/>
<t t-set="_image_classes" t-value="'rounded-circle'"/>
<t t-set="allow_biography" t-value="True"/>
<t t-set="show_image" t-value="True"/>
<t t-set="show_name" t-value="True"/>
<t t-set="compact" t-value="True"/>
</t>
<small class="text-muted">
<i class="fa fa-calendar ms-4 me-1"/><span t-field="question.write_date" t-options='{"format":"short"}'/>
</small>
</div>
</div>
<t t-set="post_url" t-valuef="/forum/#{ slug(forum) }/post/#{ slug(question) }"/>
<div class="col-sm-3 o_wforum_validation_queue">
<div class="text-center d-flex flex-row gap-2 flex-lg-column align-items-stretch mt-2 mt-lg-0">
<t t-if="queue_type == 'close'" t-call="website_forum.link_button">
<t t-set="url" t-valuef="/forum/#{ slug(forum) }/question/#{ slug(question) }/reopen"/>
<t t-set="label">Reopen</t>
<t t-set="icon" t-value="'fa fa-folder-open'"/>
<t t-set="karma" t-value="not question.can_close and question.karma_close or 0"/>
<t t-set="form_classes" t-value="'d-flex'"/>
<t t-set="classes" t-value="'btn-outline-primary flex-grow-1'"/>
</t>
<t t-if="queue_type == 'close'" t-call="website_forum.link_button">
<t t-set="url" t-valuef="#{ post_url }/delete'"/>
<t t-set="label">Delete</t>
<t t-set="icon" t-value="'fa fa-trash'"/>
<t t-set="karma" t-value="not question.can_unlink and question.karma_unlink or 0"/>
<t t-set="form_classes" t-value="'d-flex'"/>
<t t-set="classes" t-value="'btn-outline-danger flex-grow-1'"/>
</t>
<a t-else="" t-attf-href="#{ post_url }/validate" title="Validate" aria-label="Validate" class="btn btn-outline-success flex-grow-1"><i class="fa fa-check"/><span class="ms-2">Accept</span></a>
<a t-if="queue_type == 'validation'" t-attf-href="#{ post_url }/refuse" title="Refuse" aria-label="Refuse" class="btn btn-outline-danger flex-grow-1"><i class="fa fa-times"/><span class="ms-2">Reject</span></a>
<a t-if="queue_type == 'flagged'" t-attf-href="#{ post_url }/ask_for_mark_as_offensive" aria-label="Mark as offensive" title="Mark as offensive" class="btn btn-outline-danger flex-grow-1"><i class="fa fa-times"/><span class="ms-2">Offensive</span></a>
<a t-if="queue_type == 'offensive'" href="#" disabled="disabled" aria-label="Offensive" title="Offensive" class="btn btn-outline-danger flex-grow-1"><i class="fa fa-times"/><span class="ms-2">Offensive</span></a>
</div>
</div>
</div>
</div>
File diff suppressed because it is too large Load Diff
@@ -6,24 +6,22 @@
<!-- ============================================================ -->
<template id="show_flag_validator">
<a href="#" t-att-data-post-id="object.id" t-attf-data-action="validate" t-attf-class="#{'' if object.state == 'flagged' else 'd-none'} flag_validator badge text-bg-light my-2 me-2 o_wforum_flag_validator" title="Validate" data-bs-toggle="tooltip">
<i class="fa fa-check text-success"/>
<a href="#" t-att-data-post-id="object.id" t-attf-data-action="validate" t-attf-class="o_wforum_flag_validator flag_validator btn btn-success #{'' if object.state == 'flagged' else 'd-none'} my-2 me-2" title="Validate" data-bs-toggle="tooltip" data-bs-placement="top">
<i class="fa fa-check"/> Accept
</a>
<a href="#" t-attf-data-action="/forum/#{object.id}/ask_for_mark_as_offensive" t-attf-class="#{'' if object.state == 'flagged' else 'd-none'} flag_validator badge text-bg-light my-2 me-2 o_wforum_flag_mark_as_offensive" title="Mark as Offensive" data-bs-toggle="tooltip">
<i class="fa fa-times text-danger"/>
<a href="#" t-attf-data-action="/forum/#{object.id}/ask_for_mark_as_offensive" t-attf-class="o_wforum_flag_mark_as_offensive flag_validator #{'' if object.state == 'flagged' else 'd-none'} btn btn-danger my-2 me-2" title="Mark as Offensive" data-bs-toggle="tooltip" data-bs-placement="top">
<i class="fa fa-times"/> Reject
</a>
</template>
<template id="link_button">
<form t-attf-method="#{form_method or 'POST'}" t-att-action="url" t-attf-class="#{form_classes} #{not inDropdown and 'btn btn-sm border'}">
<form t-attf-method="#{form_method or 'POST'}" t-att-action="url" t-attf-class="#{form_classes} #{not inDropdown and ''}">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<div class="input-group">
<button t-attf-class="#{icon and not label and ('fa ' + icon)} #{inDropdown and 'dropdown-item ps-3' or 'btn btn-sm p-0'} #{classes} #{karma and 'karma_required text-muted'}" t-attf-data-karma="#{karma}" t-att-title="title">
<i t-if="icon and label" t-attf-class="fa fa-fw text-muted #{icon} #{inDropdown and 'me-1'}"/>
<t t-esc="label"/>
</button>
<t t-if="flag_validator" t-call="website_forum.show_flag_validator"/>
</div>
<button t-if="label" t-attf-class="#{icon and not label and ('fa ' + icon)} #{inDropdown and 'dropdown-item ps-3' or 'btn'} #{classes}#{karma > 0 and ' karma_required text-muted'}" t-attf-data-karma="#{karma}" t-att-title="title">
<i t-if="icon and label" t-attf-class="fa fa-fw #{icon} #{inDropdown and 'me-1'}"/>
<t t-out="label"/>
</button>
<t t-if="flag_validator" t-call="website_forum.show_flag_validator"/>
</form>
</template>
@@ -100,67 +98,70 @@
<template id="vote">
<t t-set="own_vote" t-value="post.user_vote"/>
<t t-set="forum" t-value="post.forum_id"/>
<!-- can_upvote returns "true" for unregistered users -->
<t t-set="can_upvote" t-value="post.can_upvote"/>
<t t-set="can_downvote" t-value="post.can_downvote"/>
<div t-attf-class="vote text-center d-inline-flex align-items-center #{vertical and 'o_wforum_vote_vertical flex-md-column'} #{classes}">
<div t-attf-class="vote d-inline-flex align-items-center ms-n2 #{classes} text-muted text-center">
<button type="button" t-attf-data-href="/forum/#{slug(forum)}/post/#{slug(post)}/upvote"
t-attf-class="btn btn-link vote_up fa fa-caret-up ps-0 #{vertical and 'px-md-0 pt-md-0' or 'pe-2'} #{own_vote == 1 and 'text-success' or 'text-muted'} #{not can_upvote and 'karma_required'}"
t-attf-class="btn vote_up #{own_vote == 1 and 'text-success'} #{not can_upvote and 'karma_required text-reset opacity-25'}#{post_id.create_uid.id == uid and ' text-300'}"
t-att-disabled="own_vote == 1 and 'disabled'"
t-att-data-karma="forum.karma_upvote"
t-att-data-can-upvote="can_upvote"
aria-label="Positive vote" title="Positive vote"/>
<b t-attf-class="vote_count #{own_vote == 1 and 'text-success' or (own_vote == -1 and 'text-danger' or 'text-muted')}"
t-esc="post.vote_count"/>
aria-label="Upvote" title="Upvote">
<i class="fa fa-caret-up" data-bs-toggle="tooltip" data-bs-placement="top" title="Upvote"/>
</button>
<small t-attf-class="vote_count #{own_vote == 1 and 'text-success' or (own_vote == -1 and 'text-danger') or post.vote_count == 0 and not own_vote and 'text-muted opacity-75'}"
t-out="post.vote_count"/>
<button type="button" t-attf-data-href="/forum/#{slug(forum)}/post/#{slug(post)}/downvote"
t-attf-class="btn btn-link vote_down fa fa-caret-down #{vertical and 'px-md-0' or 'px-2'} #{own_vote == -1 and 'text-danger' or 'text-muted'} #{not can_downvote and 'karma_required'}"
t-attf-class="btn vote_down #{own_vote == -1 and 'text-danger'} #{not can_downvote and 'karma_required text-reset opacity-25'}#{post_id.create_uid.id == uid and ' text-300'}"
t-att-disabled="own_vote == -1 and 'disabled'"
t-att-data-karma="forum.karma_downvote"
t-att-data-can-downvote="can_downvote"
aria-label="Negative vote" title="Negative vote"/>
aria-label="Downvote" title="Downvote">
<i class="fa fa-caret-down" data-bs-toggle="tooltip" data-bs-placement="top" title="Downvote"/>
</button>
<t t-out="0"/>
</div>
</template>
<template id="website_forum.author_box">
<template id="author_box">
<t t-set="display_info" t-value="show_name or show_date or show_karma"/>
<t t-if="allow_biography and object.can_display_biography" t-set="bio_popover_data">
<div class="d-flex o_wforum_bio_popover_wrap">
<img class="o_forum_avatar_big flex-shrink-0 me-3" t-att-src="website.image_url(object.create_uid, 'avatar_128', '75x75')" alt="Avatar"/>
<div class="o_wforum_bio_popover_wrap d-flex transition-fade">
<div class="d-flex flex-column">
<img class="o_wforum_avatar flex-shrink-0 rounded me-3" t-att-src="request.website.image_url(object.create_uid, 'avatar_128', '60x60')" alt="Avatar"/>
</div>
<div>
<h5 class="o_wforum_bio_popover_name mb-0" t-field="object.create_uid" t-options='{"widget": "contact", "country_image": True, "fields": ["name", "country_id"]}'/>
<h5 class="o_wforum_bio_popover_name mb-0" t-field="object.create_uid" t-options='{"widget": "contact", "country_image": True, "fields": ["name"]}'/>
<span class="o_wforum_bio_popover_info" t-field="object.create_uid" t-options='{"widget": "contact", "UserBio": True, "badges": True, "fields": ["karma"]}'/>
<div class="o_wforum_bio_popover_bio" t-field="object.create_uid" t-options='{"widget": "contact", "website_description": True, "fields": ["partner_id"]}'/>
<div class="o_wforum_bio_popover_bio mt-1 mb-0" t-field="object.create_uid" t-options='{"widget": "contact", "website_description": True, "fields": ["partner_id"]}'/>
</div>
</div>
</t>
<div t-attf-class="o_wforum_author_box d-inline-flex #{display_info and 'o_show_info'} #{compact and 'o_compact align-items-center'} #{bio_popover_data and 'o_wforum_bio_popover'}"
t-att-data-bs-content="bio_popover_data">
<t t-set="user_profile_url" t-valuef="#"/>
<t t-if="object.create_uid.id == request.session.uid or object.create_uid.sudo().website_published">
<t t-set="user_profile_url" t-value="'/forum/%s/user/%s' % (slug(forum), object.create_uid.id) + '?forum_origin=' + request.httprequest.path"/>
<div t-attf-class="o_wforum_author_box d-inline-flex #{display_info and 'o_show_info '}#{compact and 'o_compact align-items-center '}#{bio_popover_data and 'o_wforum_bio_popover '}#{_box_classes}"
t-att-data-bs-content="bio_popover_data"
data-bs-placement="top">
<t t-set="user_profile_url" t-value="False"/>
<t t-if="'forum_id' in object and (object.create_uid.id == request.session.uid or object.create_uid.sudo().website_published)">
<t t-set="user_profile_url" t-valuef="/forum/#{ slug(object.forum_id) }/user/#{ object.create_uid.id }?forum_origin=#{ request.httprequest.path }"/>
</t>
<a t-att-href="user_profile_url" class="o_wforum_author_pic position-relative rounded-circle">
<span t-if="object_validable" class="o_wforum_author_box_check rounded-circle bg-success position-absolute">
<i class="fa fa-check fa-fw small text-white"/>
</span>
<img t-attf-class="rounded-circle o_forum_avatar #{not display_info and 'shadow'}" t-att-src="website.image_url(object.create_uid, 'avatar_128', '40x40')" alt="Avatar"/>
<a t-if="show_image" t-att-href="user_profile_url or '#'" t-attf-class="o_wforum_author_pic position-relative #{ not user_profile_url and 'pe-none' }">
<img t-attf-class="o_wforum_avatar rounded-circle #{'me-2' if show_name or show_date or show_karma else '' } #{_image_classes}" t-att-src="website.image_url(object.create_uid, 'avatar_128', '40x40')" alt="Avatar"/>
</a>
<div t-if="show_name or show_date or show_karma" t-attf-class="d-flex #{compact and 'align-items-baseline' or 'flex-column justify-content-around'} #{show_image and 'ms-2'}">
<a t-att-href="user_profile_url" t-attf-class="my-0 text-reset h6 #{compact and 'small'}" t-field="object.create_uid" t-options='{"widget": "contact", "fields": ["name"]}'/>
<small t-if="show_karma and show_date" class="text-muted fw-bold"> - <t t-out="object.create_uid.karma"/>xp</small>
<div t-if="show_name or show_date or show_karma" t-attf-class="d-flex #{compact and 'align-items-baseline ms-1' or 'flex-column justify-content-around ms-2'}">
<a t-att-href="user_profile_url" class="h6 my-0 text-reset" t-field="object.create_uid" t-options='{"widget": "contact", "fields": ["name"]}'/>
<small t-if="show_karma and show_date" class="text-muted fw-bold"> - <t t-esc="object.create_uid.karma"/>xp</small>
<div t-attf-class="text-muted small fw-bold #{compact and 'd-flex align-items-baseline'}">
<span t-if="compact" class="mx-1"> - </span>
<time t-if="show_date" class="d-block text-muted fw-bold" t-field="object.create_date" t-options='{"format": "d MMMM y"}'/>
<span t-if="show_karma and not show_date" class="text-muted fw-bold"><t t-esc="object.create_uid.karma"/>xp</span>
<div t-if="show_date or show_karma" t-attf-class="text-muted small #{compact and 'd-flex align-items-baseline'}">
<div t-if="show_date">
<time class="text-muted" t-field="object.create_date" t-options="{ 'format': 'd MMMM y ' }"/>
at
<time class="text-muted" t-field="object.create_date" t-options="{ 'format': 'HH:mm ' }"/>
</div>
<span t-if="show_karma and not show_date" class="text-muted"><t t-out="object.create_uid.karma"/>xp</span>
</div>
</div>
</div>
</template>
</data>
</odoo>
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<template id="follow">
<div t-attf-class="js_follow #{div_class}"
t-att-data-id="object.id"
t-att-data-object="object._name"
t-att-data-follow="object.id and object.message_is_follower and 'on' or 'off'"
t-att-data-unsubscribe="'unsubscribe' if 'unsubscribe' in request.params else None">
<div t-if="icons_design and not request.env.user.has_group('base.group_public')" class="js_follow_icons_container">
<button t-attf-class="btn js_unfollow_btn #{btn_classes}">
<i t-attf-class="fa fa-fw fa-minus-circle #{icons_classes}" data-bs-toggle="tooltip" data-bs-placement="top" title="Unfollow"/>
</button>
<button t-attf-class="btn js_follow_btn #{btn_classes}">
<i t-attf-class="fa fa-fw fa-plus-circle #{icons_classes}" data-bs-toggle="tooltip" data-bs-placement="top" title="Follow"/>
</button>
</div>
<span t-elif="request.env.user.has_group('base.group_public') and icons_design" class="js_follow_icons_container">
<button t-attf-class="btn js_unfollow_btn #{btn_classes}">
<i t-attf-class="fa fa-fw fa-minus-circle #{icons_classes}" data-bs-toggle="tooltip" data-bs-placement="top" title="Unfollow"/>
</button>
<button t-attf-class="btn follow_btn #{btn_classes}" data-bs-toggle="modal" data-bs-target="#o_wmail_follow_modal">
<i t-attf-class="fa fa-fw fa-plus-circle #{icons_classes}" data-bs-toggle="tooltip" data-bs-placement="top" title="Follow"/>
</button>
</span>
<t t-else="">
<button href="#" t-attf-class="btn btn-primary js_unfollow_btn #{btn_classes}"><i class="fa fa-fw fa-check me-1"/>Following</button>
<button href="#" t-attf-class="btn btn-outline-primary js_follow_btn #{btn_classes}">Follow</button>
</t>
<div role="dialog" class="modal fade" id="o_wmail_follow_modal" aria-hidden="True">
<div class="modal-dialog mw-lg-25" role="document">
<div class="modal-content">
<header class="modal-header" role="status">
<h4 class="modal-title">Subscribe</h4>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</header>
<main class="modal-body">
<p t-if="object == question">Get notified when there's activity on this post</p>
<p t-if="object == tag">Get notified when this tag is used</p>
<input type="email" name="email"
class="js_follow_email form-control mb-2"
placeholder="your email..."
groups="base.group_public"/>
<button href="#" t-attf-class="btn btn-primary js_follow_btn">Subscribe</button>
<button href="#" t-attf-class="btn btn-secondary js_unfollow_btn"><i class="fa fa-fw fa-check me-1"/>Following</button>
</main>
</div>
</div>
</div>
</div>
</template>
</odoo>
@@ -4,7 +4,7 @@
<template id="private_profile" inherit_id="website_profile.private_profile">
<xpath expr="//div[@id='private_profile_return_link_container']" position="inside">
<t t-if="request.params.get('forum_id')">
<a t-attf-href="/forum/#{request.params.get('forum_id')}">Return to the forum.</a>
<a t-attf-href="/forum/#{request.params.get('forum_id')}" class="btn btn-primary">Return to the forum</a>
</t>
</xpath>
</template>
@@ -28,9 +28,9 @@
<td>
<span>
<i class="fa fa-thumbs-up text-success" role="img" aria-label="Positive votes" title="Positive votes"/>
<span class="fw-bold" t-esc="up_votes"/>
<span class="fw-bold" t-out="up_votes"/>
<i class="fa fa-thumbs-down text-danger ms-3" role="img" aria-label="Negative votes" title="Negative votes"/>
<span class="fw-bold" t-esc="down_votes"/>
<span class="fw-bold" t-out="down_votes"/>
</span>
</td>
</tr>
@@ -39,10 +39,10 @@
<xpath expr="//ul[@id='profile_extra_info_tablist']" position="inside">
<t t-if="forum">
<li class="nav-item">
<a role="tab" aria-controls="questions" href="#questions" class="nav-link o_wprofile_navlink" data-bs-toggle="tab"><t t-esc="count_questions"/> Questions</a>
<a role="tab" aria-controls="questions" href="#questions" class="nav-link o_wprofile_navlink" data-bs-toggle="tab"><t t-out="count_questions"/> Questions</a>
</li>
<li class="nav-item">
<a role="tab" aria-controls="answers" href="#answers" class="nav-link o_wprofile_navlink" data-bs-toggle="tab"><t t-esc="count_answers"/> Answers</a>
<a role="tab" aria-controls="answers" href="#answers" class="nav-link o_wprofile_navlink" data-bs-toggle="tab"><t t-out="count_answers"/> Answers</a>
</li>
<li t-if="uid == user.id" class="nav-item">
<a role="tab" aria-controls="activity" href="#activity" class="nav-link o_wprofile_navlink" data-bs-toggle="tab">Activity</a>
@@ -54,6 +54,7 @@
</xpath>
<xpath expr="//div[@id='profile_extra_info_tabcontent']" position="inside">
<t t-if="forum">
<t t-set="is_profile" t-value="true"/>
<div role="tabpanel" class="o_wforum_profile_tab tab-pane" id="questions">
<div class="mb-4">
<h5 class="border-bottom pb-1">
@@ -65,8 +66,18 @@
<t t-call="website_forum.display_post"/>
</div>
</t>
<div t-elif="my_profile" class="text-muted">
You have not posted any questions yet. <br />
<a href="/forum/" class="btn btn-link px-0">
<i class="oi oi-arrow-right d-inline-block"/> Go to Forums
</a>
</div>
<t t-else="">
<span class="fw-bold">No question posted yet.</span>
<div class="text-muted">This user hasn't posted any questions yet.<br/>
<a href="/forum/" class="btn btn-link px-0">
<i class="oi oi-arrow-right d-inline-block"/> Go to Forums
</a>
</div>
</t>
</div>
<t t-if="favourite">
@@ -100,9 +111,18 @@
<t t-call="website_forum.display_post_answer"/>
</div>
</t>
<t t-else="">
<span class="fw-bold">No answer posted yet.</span>
</t>
<div t-elif="my_profile" class="text-muted">
You have not answered any questions yet. <br />
<a href="/forum/" class="btn btn-link px-0">
<i class="oi oi-arrow-right d-inline-block"/> Go to Forums
</a>
</div>
<div t-else="" class="text-muted">
This user hasn't answered any questions yet. <br/>
<a href="/forum/" class="btn btn-link px-0">
<i class="oi oi-arrow-right d-inline-block"/> Go to Forums
</a>
</div>
</div>
</div>
<div role="tabpanel" class="o_wforum_profile_tab tab-pane" id="votes" t-if="uid == user.id">
@@ -126,10 +146,10 @@
<template id="forum_filter_tag" name="Filtering Forum Tag">
<t t-if="forum_filtered">
<span class="align-items-baseline border d-inline-flex ps-2 rounded mb-1 ms-4">
<i class="fa fa-filter me-2 text-muted"/>
<t t-esc="forum_filtered"/>
<a t-attf-href="/profile/user/#{uid}" class="btn border-0 py-1">&#215;</a>
<span class="d-inline-flex align-items-center gap-2 rounded py-1 ps-2 pe-0 bg-200 small">
<i class="fa fa-filter text-muted"/>
<t t-out="forum_filtered"/>
<a t-attf-href="/profile/user/#{uid}" class="oi oi-close btn m-n1 text-muted"/>
</span>
</t>
</template>
@@ -138,41 +158,41 @@
<t t-if="activities">
<div t-foreach="activities" t-as="activity" class="card mb-2">
<div class="card-body">
<span t-esc="activity.subtype_id.name" class="badge text-bg-info me-2 mt-1"/>
<span t-out="activity.subtype_id.name" class="badge text-bg-info me-2 mt-1"/>
<span t-field="activity.date" t-options='{"format": "short"}' class="me-2"/>
<t t-set="post" t-value="posts[activity.res_id]"/>
<span t-if="post[1]">
<a t-attf-href="/forum/#{ slug(post[0].forum_id) }/#{ slug(post[0]) }#answer-#{ str(post[1].id) }">
<span t-esc="post[0].name"/>
<span t-out="post[0].name"/>
</a>
</span>
<span t-if="not post[1]">
<a t-attf-href="/forum/#{ slug(post[0].forum_id) }/#{ slug(post[0]) }">
<span t-esc="post[0].name"/>
<span t-out="post[0].name"/>
</a>
</span>
</div>
</div>
</t>
<t t-else="">
<p class="text-muted">No activities yet!</p>
<span class="text-muted">There is no activity yet.</span>
</t>
</template>
<template id="user_votes" name="Forum User Votes">
<div t-foreach="vote_post" t-as="vote">
<span t-field="vote.post_id.create_date"/>
<span t-if="vote.vote == '1'" class="fa fa-thumbs-up text-success" style="margin-left:30px" role="img" aria-label="Positive vote" title="Positive vote"/>
<span t-if="vote.vote == '-1'" class="fa fa-thumbs-down text-warning" style="margin-left:30px" role="img" aria-label="Negative vote" title="Negative vote"/>
<t t-if="vote.post_id.parent_id">
<a t-attf-href="/forum/#{ slug(vote.post_id.forum_id) }/#{ vote.post_id.parent_id.id }/#answer-#{ vote.post_id.id }" t-esc="vote.post_id.parent_id.name" style="margin-left:10px"/>
</t>
<t t-if="not vote.post_id.parent_id">
<a t-attf-href="/forum/#{ slug(vote.post_id.forum_id) }/#{ vote.post_id.id }" style=" color:black;margin-left:10px" t-esc="vote.post_id.name"/>
</t>
<div t-foreach="vote_post" t-as="vote" class="card mb-2">
<div class="card-body">
<span t-field="vote.post_id.create_date"/>
<span t-if="vote.vote == '1'" class="fa fa-thumbs-up text-success" style="margin-left:30px" role="img" aria-label="Positive vote" title="Positive vote"/>
<span t-if="vote.vote == '-1'" class="fa fa-thumbs-down text-warning" style="margin-left:30px" role="img" aria-label="Negative vote" title="Negative vote"/>
<a t-if="vote.post_id.parent_id" class="ms-2" t-attf-href="/forum/#{ slug(vote.post_id.forum_id) }/#{ vote.post_id.parent_id.id }/#answer-#{ vote.post_id.id }"
t-out="vote.post_id.parent_id.name"/>
<a t-else="" class="ms-2 text-black" t-attf-href="/forum/#{ slug(vote.post_id.forum_id) }/#{ vote.post_id.id }"
t-out="vote.post_id.name"/>
</div>
</div>
<div class="mb16" t-if="not vote_post">
<p class="text-muted">No vote given by you yet!</p>
<p class="text-muted">You haven't given any votes yet.</p>
</div>
</template>
</data></odoo>
@@ -1,10 +1,14 @@
.js_follow[data-follow='on'] .js_follow_btn ,
.js_follow[data-follow='on'] .js_follow_btn, .js_follow[data-follow='on'] .follow_btn,
.js_follow[data-follow='off'] .js_unfollow_btn {
display: none;
}
.js_follow[data-follow='on'] .js_unfollow_btn {
opacity: 1 !important;
}
.js_follow_icons_container {
.js_follow_btn, .js_unfollow_btn {
.js_follow_btn, .js_unfollow_btn, .follow_btn {
animation: js_follow_fade 1s ease forwards;
opacity: 0;
@@ -18,12 +22,11 @@
transition-duration: 1s;
opacity: 1;
}
};
}
}
.fa:before {
content: "\f0f3";
color: $text-muted;
}
.js_follow_btn:hover .fa:before {
@@ -31,12 +34,12 @@
}
.js_unfollow_btn .fa:before {
color: map-get($theme-colors, 'primary');
color: $primary;
}
.js_unfollow_btn:hover .fa:before {
content: "\f1f6";
color: map-get($theme-colors, 'danger');
color: $danger;
}
}
@@ -96,7 +96,7 @@ publicWidget.registry.websiteProfileEditor = publicWidget.Widget.extend({
var reader = new window.FileReader();
reader.readAsDataURL(ev.currentTarget.files[0]);
reader.onload = function (ev) {
$form.find('.o_forum_avatar_img').attr('src', ev.target.result);
$form.find('.o_wforum_avatar_img').attr('src', ev.target.result);
};
$form.find('#forum_clear_image').remove();
},
@@ -106,7 +106,7 @@ publicWidget.registry.websiteProfileEditor = publicWidget.Widget.extend({
*/
_onProfilePicClearClick: function (ev) {
var $form = $(ev.currentTarget).closest('form');
$form.find('.o_forum_avatar_img').attr('src', '/web/static/img/placeholder.png');
$form.find('.o_wforum_avatar_img').attr('src', '/web/static/img/placeholder.png');
$form.append($('<input/>', {
name: 'clear_image',
id: 'forum_clear_image',
@@ -89,7 +89,7 @@
<div class="col-3">
<div class="card o_card_people">
<div class="card-body">
<img class="o_forum_avatar_img w-100 mb-3" t-att-src="website.image_url(user, 'avatar_128')"/>
<img class="o_wforum_avatar_img w-100 mb-3" t-att-src="website.image_url(user, 'avatar_128')"/>
<div class="text-center">
<a href="#" class="o_forum_profile_pic_edit btn btn-primary" aria-label="Edit">
<i class="fa fa-pencil fa-1g float-sm-none float-md-start" title="Edit"></i>
@@ -10,6 +10,7 @@ class Forum(models.Model):
slide_channel_ids = fields.One2many('slide.channel', 'forum_id', 'Courses', help="Edit the course linked to this forum on the course form.")
slide_channel_id = fields.Many2one('slide.channel', 'Course', compute='_compute_slide_channel_id', store=True)
visibility = fields.Selection(related='slide_channel_id.visibility', help="Forum linked to a Course, the visibility is the one applied on the course.")
image_1920 = fields.Image('Image', compute='_compute_image_1920', store=True, readonly=False)
@api.depends('slide_channel_ids')
def _compute_slide_channel_id(self):
@@ -18,3 +19,8 @@ class Forum(models.Model):
forum.slide_channel_id = forum.slide_channel_ids[0]
else:
forum.slide_channel_id = None
@api.depends('slide_channel_id', 'slide_channel_id.image_1920')
def _compute_image_1920(self):
for forum in self.filtered(lambda f: not f.image_1920 and f.slide_channel_id.image_1920):
forum.image_1920 = forum.slide_channel_id.image_1920
@@ -1,81 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo><data>
<template id="website_slides_forum_header" inherit_id="website_forum.header">
<xpath expr="//div[hasclass('o_forum_nav_header_container')]" position="before">
<t t-if="forum.slide_channel_id">
<div class="o_wforum_elearning_navtabs_container">
<div class="container">
<ul class="nav nav-tabs o_wprofile_nav_tabs mt-0 flex-nowrap" role="tablist" id="profile_extra_info_tablist">
<li class="nav-item">
<a t-att-href="'/slides/%s%s' % (slug(forum.slide_channel_id), '/%s' % slug(category) if category else '')"
t-att-class="'nav-link o_wprofile_navlink'" style="border-left: 0px">
<i class="fa fa-home"/> Course</a>
</li>
<li t-if="forum.slide_channel_id.allow_comment" class="nav-item">
<a t-att-href="'/slides/%s?active_tab=review' % (slug(forum.slide_channel_id))"
t-att-class="'nav-link o_wprofile_navlink'" style="border-left: 0px">
Reviews<t t-if="forum.slide_channel_id.rating_count"> (<t t-esc="forum.slide_channel_id.rating_count"/>)</t>
</a>
</li>
<li class="nav-item">
<a t-att-href="'/forum/%s' % (slug(forum))" t-att-class="'nav-link active o_wprofile_navlink'" style="border-left: 0px">Forum</a>
</li>
</ul>
</div>
</div>
</t>
</xpath>
</template>
<template name="Split regular forums/courses" id="website_slides_forum_index" inherit_id="website_forum.forum_all" active="True">
<xpath expr="//div[@id='o_wforum_forums_index_list']/*[@t-if='forums']" position="replace">
<xpath expr="//div[@id='o_wforum_forums_index_list']//*[@t-if='forums']" position="replace">
<t t-if="forums">
<t t-set="courses_discussions" t-value="forums.filtered(lambda f: f.slide_channel_id)"/>
<t t-set="regular_forums" t-value="forums - courses_discussions"/>
<div class="row mb-4">
<t t-if="len(regular_forums) == 1">
<t t-set="forum" t-value="regular_forums"/>
<div class="col-md-10 col-lg-8 mb-2">
<div class="row align-items-start">
<div class="col-12 col-sm-4">
<a t-attf-href="/forum/#{slug(forum)}">
<div t-attf-class="h-100 w-100 o_editable #{not forum.image_1920 and 'rounded o_wforum_forum_card_bg shadow-sm flex-shrink-0'}">
<div t-if="forum.image_1920 or editable" t-attf-class="h-100"
t-field="forum.image_1920" t-options="{'widget': 'image', 'preview_image': 'image_256', 'class': 'w-100 h-100 o_object_fit_cover rounded'}" />
</div>
</a>
</div>
<div class="col-12 col-sm-7 mt-2 mt-sm-0">
<a t-attf-href="/forum/#{slug(forum)}" class="text-reset" t-att-title="forum.name">
<h3 class="h2" t-field="forum.name"/>
</a>
<p t-attf-class="m-0 lead #{not forum.description and 'css_non_editable_mode_hidden'}"
placeholder="Description"
t-field="forum.teaser"/>
</div>
</div>
</div>
</t>
<t t-else="">
<t t-call="website_forum.forum_all_all_entries">
<t t-set="_forums" t-value="regular_forums"/>
</t>
</t>
</div>
<div class="d-flex border-bottom pb-1 pt-3 mb-3">
<h2 class="h4 mb-0 text-muted">Courses Discussions</h2>
<div class="flex-grow-1 text-end">
<a href="/slides" title="See all Courses">Check our Courses <i class="oi oi-chevron-right"/></a>
</div>
</div>
<div class="row">
<t t-call="website_forum.forum_all_all_entries">
<t t-set="_forums" t-value="courses_discussions"/>
</t>
</div>
<t t-call="website_forum.forum_all_all_entries">
<t t-set="_forums" t-value="regular_forums"/>
</t>
<t t-call="website_forum.forum_all_all_entries">
<t t-set="_forums" t-value="courses_discussions"/>
</t>
</t>
</xpath>
</template>
</data></odoo>
<template name="Replace Breadcrumb Root" id="website_slides_forum_breadcrumb" inherit_id="website_forum.forum_model_nav" active="True">
<xpath expr="//nav[@id='o_wforum_nav']" position="before">
<t t-if="forum.slide_channel_id" t-set="breadcrumb_kind" t-value="'slides'"/>
</xpath>
<xpath expr="//div[hasclass('o_wforum_breadcrumb_root_single')]" postition="inside">
<ol t-if="breadcrumb_kind == 'slides'" class="breadcrumb col-10 col-lg-6 mb-0 p-0">
<li class="breadcrumb-item">
<h5 class="mb-0">
<a t-attf-href="/slides/#{slug(forum.slide_channel_id)}#{'/' + slug(category) if category else ''}" t-out="forum.name"/>
</h5>
</li>
<li class="breadcrumb-item text-nowrap">
<h5 class="mb-0"><a t-attf-href="/forum/#{ slug(forum) }">Forum</a></h5>
</li>
</ol>
</xpath>
<xpath expr="//div[hasclass('o_wforum_breadcrumb_root_list_or_edit')]" postition="inside">
<ol t-if="breadcrumb_kind == 'slides'" class="breadcrumb col-10 col-lg flex-grow-1 mb-0 p-0">
<li class="breadcrumb-item text-nowrap">
<h5 class="mb-0">
<a t-attf-href="/slides/#{slug(forum.slide_channel_id)}#{'/' + slug(category) if category else ''}" t-out="forum.name"/>
</h5>
</li>
<li class="breadcrumb-item text-nowrap">
<h5 class="mb-0"><strong>Forum</strong></h5>
</li>
</ol>
</xpath>
</template>
<template name="Add course badge" id="forum_all_all_entries" inherit_id="website_forum.forum_all_all_entries">
<xpath expr="//t[@t-foreach='sorted_forums']" position="before">
<t t-if="_forums == courses_discussions">
<t t-set="forum_badge">Course</t>
</t>
</xpath>
<xpath expr="//t[@t-foreach='sorted_forums']" position="after">
<div t-if="_forums == courses_discussions" class="d-flex justify-content-center">
<a href="/slides" class="btn btn-link">Check out our courses <i class="fa fa-long-arrow-right"/></a>
</div>
</xpath>
</template>
</data>
</odoo>