[IMP] website_forum, *: revamp design

* web_editor, website_profile

Part of https://github.com/odoo/odoo/pull/35749
task-2024197

Co-authored-by: Jérémy Kersten <jke@odoo.com>
Co-authored-by: qsm-odoo <qsm@odoo.com>
Co-authored-by: fja-odoo <fja@odoo.com>
This commit is contained in:
sri-odoo
2019-10-01 16:52:20 +00:00
committed by qsm-odoo
co-authored by Jérémy Kersten qsm-odoo fja-odoo
parent 83284a962c
commit c8c8eb3d56
16 changed files with 1497 additions and 995 deletions
@@ -3,6 +3,7 @@ odoo.define('web_editor.summernote', function (require) {
var core = require('web.core'); var core = require('web.core');
require('summernote/summernote'); // wait that summernote is loaded require('summernote/summernote'); // wait that summernote is loaded
var weDefaultOptions = require('web_editor.wysiwyg.default_options');
var _t = core._t; var _t = core._t;
@@ -1044,7 +1045,7 @@ options.keyMap.mac['ESCAPE'] = 'cancel';
options.keyMap.mac['UP'] = 'up'; options.keyMap.mac['UP'] = 'up';
options.keyMap.mac['DOWN'] = 'down'; options.keyMap.mac['DOWN'] = 'down';
options.styleTags = ['p', 'pre', 'small', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote']; options.styleTags = weDefaultOptions.styleTags;
$.summernote.pluginEvents.insertTable = function (event, editor, layoutInfo, sDim) { $.summernote.pluginEvents.insertTable = function (event, editor, layoutInfo, sDim) {
var $editable = layoutInfo.editable(); var $editable = layoutInfo.editable();
@@ -2083,7 +2084,7 @@ eventHandler.modules.editor.currentStyle = function (target) {
return styleInfo; return styleInfo;
}; };
options.fontSizes = [_t('Default'), 8, 9, 10, 11, 12, 14, 18, 24, 36, 48, 62]; options.fontSizes = weDefaultOptions.fontSizes;
$.summernote.pluginEvents.applyFont = function (event, editor, layoutInfo, color, bgcolor, size) { $.summernote.pluginEvents.applyFont = function (event, editor, layoutInfo, color, bgcolor, size) {
var r = range.create(); var r = range.create();
if (!r) return; if (!r) return;
@@ -2486,7 +2487,7 @@ function mouseDownChecklist (e) {
$lis.addClass('o_checked'); $lis.addClass('o_checked');
} while ($lis.length); } while ($lis.length);
} }
}; }
var fn_attach = eventHandler.attach; var fn_attach = eventHandler.attach;
eventHandler.attach = function (oLayoutInfo, options) { eventHandler.attach = function (oLayoutInfo, options) {
@@ -60,3 +60,25 @@ var WysiwygRoot = Widget.extend({
return WysiwygRoot; return WysiwygRoot;
}); });
odoo.define('web_editor.wysiwyg.default_options', function (require) {
'use strict';
/**
* TODO this should be refactored to be done another way, same as the 'root'
* module that should be done another way.
*
* This allows to have access to default options that are used in the summernote
* editor so that they can be tweaked (instead of entirely replaced) when using
* the editor on an editable content.
*/
var core = require('web.core');
var _lt = core._lt;
return {
styleTags: ['p', 'pre', 'small', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote'],
fontSizes: [_lt('Default'), 8, 9, 10, 11, 12, 14, 18, 24, 36, 48, 62],
};
});
+24 -10
View File
@@ -73,7 +73,7 @@ class WebsiteForum(WebsiteProfile):
'''/forum/<model("forum.forum"):forum>/tag/<model("forum.tag"):tag>/questions''', '''/forum/<model("forum.forum"):forum>/tag/<model("forum.tag"):tag>/questions''',
'''/forum/<model("forum.forum"):forum>/tag/<model("forum.tag"):tag>/questions/page/<int:page>''', '''/forum/<model("forum.forum"):forum>/tag/<model("forum.tag"):tag>/questions/page/<int:page>''',
], type='http', auth="public", website=True, sitemap=sitemap_forum) ], type='http', auth="public", website=True, sitemap=sitemap_forum)
def questions(self, forum, tag=None, page=1, filters='all', sorting=None, search='', **post): def questions(self, forum, tag=None, page=1, filters='all', my=None, sorting=None, search='', **post):
if not forum.can_access_from_current_website(): if not forum.can_access_from_current_website():
raise werkzeug.exceptions.NotFound() raise werkzeug.exceptions.NotFound()
@@ -86,8 +86,21 @@ class WebsiteForum(WebsiteProfile):
domain += [('tag_ids', 'in', tag.id)] domain += [('tag_ids', 'in', tag.id)]
if filters == 'unanswered': if filters == 'unanswered':
domain += [('child_ids', '=', False)] domain += [('child_ids', '=', False)]
elif filters == 'followed': elif filters == 'solved':
domain += [('message_partner_ids', '=', request.env.user.partner_id.id)] domain += [('has_validated_answer', '=', True)]
elif filters == 'unsolved':
domain += [('has_validated_answer', '=', False)]
user = request.env.user
if my == 'mine':
domain += [('create_uid', '=', user.id)]
elif my == 'followed':
domain += [('message_partner_ids', '=', user.partner_id.id)]
elif my == 'tagged':
domain += [('tag_ids.message_partner_ids', '=', user.partner_id.id)]
elif my == 'favourites':
domain += [('favourite_ids', '=', user.id)]
if sorting: if sorting:
# check that sorting is valid # check that sorting is valid
@@ -128,6 +141,7 @@ class WebsiteForum(WebsiteProfile):
'pager': pager, 'pager': pager,
'tag': tag, 'tag': tag,
'filters': filters, 'filters': filters,
'my': my,
'sorting': sorting, 'sorting': sorting,
'search': search, 'search': search,
}) })
@@ -153,10 +167,9 @@ class WebsiteForum(WebsiteProfile):
first_char_tag = forum.get_tags_first_char() first_char_tag = forum.get_tags_first_char()
first_char_list = [(t, t.lower()) for t in first_char_tag if t.isalnum()] first_char_list = [(t, t.lower()) for t in first_char_tag if t.isalnum()]
first_char_list.insert(0, (_('All'), 'all')) first_char_list.insert(0, (_('All'), 'all'))
# get active first char tag
active_char_tag = first_char_list[1][1] if len(first_char_list) > 1 else 'all' active_char_tag = tag_char and tag_char.lower() or 'all'
if tag_char:
active_char_tag = tag_char.lower()
# generate domain for searched tags # generate domain for searched tags
domain = [('forum_id', '=', forum.id), ('posts_count', '>', 0)] domain = [('forum_id', '=', forum.id), ('posts_count', '>', 0)]
order_by = 'name' order_by = 'name'
@@ -282,7 +295,7 @@ class WebsiteForum(WebsiteProfile):
user = request.env.user user = request.env.user
if not user.email or not tools.single_email_re.match(user.email): if not user.email or not tools.single_email_re.match(user.email):
return werkzeug.utils.redirect("/forum/%s/user/%s/edit?email_required=1" % (slug(forum), request.session.uid)) return werkzeug.utils.redirect("/forum/%s/user/%s/edit?email_required=1" % (slug(forum), request.session.uid))
values = self._prepare_user_values(forum=forum, searches={}, header={'ask_hide': True}) values = self._prepare_user_values(forum=forum, searches={}, header={'ask_hide': True}, new_question=True)
return request.render("website_forum.new_question", values) return request.render("website_forum.new_question", values)
@http.route(['/forum/<model("forum.forum"):forum>/new', @http.route(['/forum/<model("forum.forum"):forum>/new',
@@ -349,6 +362,7 @@ class WebsiteForum(WebsiteProfile):
values.update({ values.update({
'tags': tags, 'tags': tags,
'post': post, 'post': post,
'is_edit': True,
'is_answer': bool(post.parent_id), 'is_answer': bool(post.parent_id),
'searches': kwargs, 'searches': kwargs,
'content': post.name, 'content': post.name,
@@ -518,8 +532,8 @@ class WebsiteForum(WebsiteProfile):
# ----------------------------------- # -----------------------------------
@http.route(['/forum/<model("forum.forum"):forum>/user/<int:user_id>'], type='http', auth="public", website=True) @http.route(['/forum/<model("forum.forum"):forum>/user/<int:user_id>'], type='http', auth="public", website=True)
def view_user_forum_profile(self, forum, user_id, **post): def view_user_forum_profile(self, forum, user_id, forum_origin, **post):
return werkzeug.utils.redirect('/profile/user/' + str(user_id) + '?forum_id=' + str(forum.id)) return werkzeug.utils.redirect('/profile/user/' + str(user_id) + '?forum_id=' + str(forum.id) + '&forum_origin=' + str(forum_origin))
def _prepare_user_profile_values(self, user, **post): def _prepare_user_profile_values(self, user, **post):
values = super(WebsiteForum, self)._prepare_user_profile_values(user, **post) values = super(WebsiteForum, self)._prepare_user_profile_values(user, **post)
+1
View File
@@ -3,6 +3,7 @@
<data noupdate="1"> <data noupdate="1">
<record id="forum_help" model="forum.forum"> <record id="forum_help" model="forum.forum">
<field name="name">Help</field> <field name="name">Help</field>
<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> </record>
<record id="menu_website_forums" model="website.menu"> <record id="menu_website_forums" model="website.menu">
@@ -1,5 +1,3 @@
<h1 class="o_page_header">Guidelines</h1>
<div id="accordion"> <div id="accordion">
<div class="card mb-2 bg-100"> <div class="card mb-2 bg-100">
<a href="#collapseOne" class="card-header" id="headingOne" data-toggle="collapse" aria-expanded="false" aria-controls="collapseOne"> <a href="#collapseOne" class="card-header" id="headingOne" data-toggle="collapse" aria-expanded="false" aria-controls="collapseOne">
+2 -1
View File
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from . import forum
from . import gamification from . import gamification
from . import res_users from . import res_users
from . import forum from . import website
+3 -8
View File
@@ -36,16 +36,11 @@ class Forum(models.Model):
help='Questions mode: only one answer allowed\n Discussions mode: multiple answers allowed') help='Questions mode: only one answer allowed\n Discussions mode: multiple answers allowed')
active = fields.Boolean(default=True) active = fields.Boolean(default=True)
faq = fields.Html('Guidelines', default=_get_default_faq, translate=html_translate, sanitize=False) faq = fields.Html('Guidelines', default=_get_default_faq, translate=html_translate, sanitize=False)
description = fields.Text( description = fields.Text('Description', translate=True)
'Description',
translate=True,
default=lambda s: _('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.'))
welcome_message = fields.Html( welcome_message = fields.Html(
'Welcome Message', 'Welcome Message',
translate=True, translate=True,
default="""<section class="bg-info shadow"> default="""<section>
<div class="container py-5"> <div class="container py-5">
<div class="row"> <div class="row">
<div class="col-lg-12"> <div class="col-lg-12">
@@ -56,7 +51,7 @@ class Forum(models.Model):
</p> </p>
</div> </div>
<div class="col text-center mt-3"> <div class="col text-center mt-3">
<a href="#" class="js_close_intro btn btn-outline-light">Hide Intro</a> <a href="#" class="js_close_intro btn btn-outline-light mr-2">Hide Intro</a>
<a class="btn btn-light forum_register_url" href="/web/login">Register</a> <a class="btn btn-light forum_register_url" href="/web/login">Register</a>
</div> </div>
</div> </div>
+15
View File
@@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models, fields
class Website(models.Model):
_inherit = 'website'
def _compute_field_forums_count(self):
Forum = self.env['forum.forum']
for website in self:
website.forums_count = Forum.search_count(website.website_domain())
forums_count = fields.Integer(compute=_compute_field_forums_count)
@@ -22,7 +22,7 @@ odoo.define("website_forum.tour_forum", function (require) {
content: _t("Click <em>Continue</em> to create the forum."), content: _t("Click <em>Continue</em> to create the forum."),
position: "right", position: "right",
}, { }, {
trigger: ".btn-lg.btn-block", trigger: ".o_forum_ask_btn",
position: "left", position: "left",
content: _t("Ask the question in this forum by clicking on the button."), content: _t("Ask the question in this forum by clicking on the button."),
}, { }, {
@@ -67,7 +67,7 @@ odoo.define("website_forum.tour_forum", function (require) {
trigger: ".modal-header button.close", trigger: ".modal-header button.close",
auto: true, auto: true,
}, { }, {
trigger: "a[data-karma=\"20\"]:first", trigger: ".o_wforum_validate_toggler[data-karma=\"20\"]:first",
content: _t("Click here to accept this answer."), content: _t("Click here to accept this answer."),
position: "right", position: "right",
}]); }]);
@@ -2,6 +2,7 @@ odoo.define('website_forum.website_forum', function (require) {
'use strict'; 'use strict';
var core = require('web.core'); var core = require('web.core');
var weDefaultOptions = require('web_editor.wysiwyg.default_options');
var wysiwygLoader = require('web_editor.loader'); var wysiwygLoader = require('web_editor.loader');
var publicWidget = require('web.public.widget'); var publicWidget = require('web.public.widget');
var session = require('web.session'); var session = require('web.session');
@@ -22,11 +23,8 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({
'click .flag:not(.karma_required)': '_onFlagAlertClick', 'click .flag:not(.karma_required)': '_onFlagAlertClick',
'click .vote_up:not(.karma_required), .vote_down:not(.karma_required)': '_onVotePostClick', 'click .vote_up:not(.karma_required), .vote_down:not(.karma_required)': '_onVotePostClick',
'click .o_js_validation_queue a[href*="/validate"]': '_onValidationQueueClick', 'click .o_js_validation_queue a[href*="/validate"]': '_onValidationQueueClick',
'click .accept_answer:not(.karma_required)': '_onAcceptAnswerClick', 'click .o_wforum_validate_toggler:not(.karma_required)': '_onAcceptAnswerClick',
'click .validate_answer [data-href]': '_onAcceptAnswerClick', 'click .o_wforum_favourite_toggle': '_onFavoriteQuestionClick',
'mouseenter .validate_answer [data-href]': '_onRemoveValidAnswerMouse',
'mouseleave .validate_answer [data-href]': '_onRemoveValidAnswerMouse',
'click .favourite_question': '_onFavoriteQuestionClick',
'click .comment_delete': '_onDeleteCommentClick', 'click .comment_delete': '_onDeleteCommentClick',
'click .js_close_intro': '_onCloseIntroClick', 'click .js_close_intro': '_onCloseIntroClick',
}, },
@@ -49,6 +47,10 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({
); );
$('.forum_register_url').attr('href', forumLogin); $('.forum_register_url').attr('href', forumLogin);
// Initialize forum's tooltips
this.$('[data-toggle="tooltip"]').tooltip({delay: 0});
this.$('[data-toggle="popover"]').popover({offset: 8});
$('input.js_select2').select2({ $('input.js_select2').select2({
tags: true, tags: true,
tokenSeparators: [',', ' ', '_'], tokenSeparators: [',', ' ', '_'],
@@ -127,9 +129,11 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({
toolbar.push(['history', ['undo', 'redo']]); toolbar.push(['history', ['undo', 'redo']]);
var options = { var options = {
height: 150, height: 200,
minHeight: 80,
toolbar: toolbar, toolbar: toolbar,
styleWithSpan: false, styleWithSpan: false,
styleTags: _.without(weDefaultOptions.styleTags, 'h1', 'h2', 'h3'),
recordInfo: { recordInfo: {
context: self._getContext(), context: self._getContext(),
res_model: 'forum.post', res_model: 'forum.post',
@@ -151,12 +155,13 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({
}); });
}); });
// Check all answer status to add text-success on answer if needed _.each(this.$('.o_wforum_bio_popover'), authorBox => {
_.each($('.accept_answer'), function (el) { $(authorBox).popover({
var $el = $(el); trigger: 'hover',
if ($el.hasClass('oe_answer_true')) { offset: 10,
$el.addClass('text-success'); animation: false,
} html: true,
});
}); });
return this._super.apply(this, arguments); return this._super.apply(this, arguments);
@@ -278,9 +283,9 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({
_onVotePostClick: function (ev) { _onVotePostClick: function (ev) {
var self = this; var self = this;
ev.preventDefault(); ev.preventDefault();
var $link = $(ev.currentTarget); var $btn = $(ev.currentTarget);
this._rpc({ this._rpc({
route: $link.data('href'), route: $btn.data('href'),
}).then(function (data) { }).then(function (data) {
if (data.error) { if (data.error) {
var message; var message;
@@ -296,17 +301,28 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({
sticky: false, sticky: false,
}); });
} else { } else {
$link.parent().find('.vote_count').html(data.vote_count); var $container = $btn.closest('.vote');
if (data.user_vote === 0) { var $items = $container.children();
$link.parent().find('.text-success').removeClass('text-success'); var $voteUp = $items.filter('.vote_up');
$link.parent().find('.text-warning').removeClass('text-warning'); var $voteDown = $items.filter('.vote_down');
} else { var $voteCount = $items.filter('.vote_count');
if (data.user_vote === 1) { var userVote = parseInt(data['user_vote']);
$link.addClass('text-success');
} else { $voteUp.prop('disabled', userVote === 1);
$link.addClass('text-warning'); $voteDown.prop('disabled', userVote === -1);
}
$items.removeClass('text-success text-danger text-muted o_forum_vote_animate');
void $container[0].offsetWidth; // Force a refresh
if (userVote === 1) {
$voteUp.addClass('text-success');
$voteCount.addClass('text-success');
} }
if (userVote === -1) {
$voteDown.addClass('text-danger');
$voteCount.addClass('text-danger');
}
$voteCount.html(data['vote_count']).addClass('o_forum_vote_animate');
} }
}); });
}, },
@@ -318,11 +334,14 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({
ev.preventDefault(); ev.preventDefault();
var $link = $(ev.currentTarget); var $link = $(ev.currentTarget);
$link.parents('.post_to_validate').hide(); $link.parents('.post_to_validate').hide();
$.get($link.attr('href')).then(function () { $.get($link.attr('href')).then(() => {
var left = $('.o_js_validation_queue:visible').length; var left = $('.o_js_validation_queue:visible').length;
var type = $('h2.o_page_header a.active').data('type'); var type = $('h2.o_page_header a.active').data('type');
$('#count_post').text(left); $('#count_post').text(left);
$('#moderation_tools a[href*="/' + type + '_"]').find('strong').text(left); $('#moderation_tools a[href*="/' + type + '_"]').find('strong').text(left);
if (!left) {
this.$('.o_caught_up_alert').removeClass('d-none');
}
}, function () { }, function () {
$link.parents('.o_js_validation_queue > div').addClass('bg-danger text-white').css('background-color', '#FAA'); $link.parents('.o_js_validation_queue > div').addClass('bg-danger text-white').css('background-color', '#FAA');
$link.parents('.post_to_validate').show(); $link.parents('.post_to_validate').show();
@@ -333,61 +352,38 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({
* @param {Event} ev * @param {Event} ev
*/ */
_onAcceptAnswerClick: function (ev) { _onAcceptAnswerClick: function (ev) {
var self = this;
ev.preventDefault(); ev.preventDefault();
var self = this;
var $acceptAnswerLinks = this.$('.accept_answer');
var $link = $(ev.currentTarget); var $link = $(ev.currentTarget);
var target = $link.data('target');
this._rpc({ this._rpc({
route: $link.data('href'), route: $link.data('href'),
}).then(function (data) { }).then(data => {
if (data.error) { if (data.error) {
if (data.error === 'anonymous_user') { if (data.error === 'anonymous_user') {
var message = _t("Sorry, anonymous users cannot choose correct answer."); var message = _t("Sorry, anonymous users cannot choose correct answer.");
} }
self.call('crash_manager', 'show_warning', { this.call('crash_manager', 'show_warning', {
message: message, message: message,
title: _t("Access Denied"), title: _t("Access Denied"),
}, { }, {
sticky: false, sticky: false,
}); });
} else { } else {
$acceptAnswerLinks.removeClass('oe_answer_true') _.each(this.$('.forum_answer'), answer => {
.addClass('oe_answer_false'); var $answer = $(answer);
$link.toggleClass('oe_answer_true', !!data) var isCorrect = $answer.is(target) ? data : false;
.toggleClass('oe_answer_false', !data); var $toggler = $answer.find('.o_wforum_validate_toggler');
var newHelper = isCorrect ? $toggler.data('helper-decline') : $toggler.data('helper-accept');
// TODO in master, review the utility of this function... $answer.toggleClass('o_wforum_answer_correct', isCorrect);
self._onCheckAnswerStatus(ev); $toggler.tooltip('dispose')
.attr('data-original-title', newHelper)
// If we are removing an accepted answer, reload the page as the .tooltip({delay: 0});
// design is quite different with or without an accepted answer. });
if ($link.closest('.validate_answer').length) {
window.location.reload();
}
} }
}); });
}, },
/**
* @private
* @param {Event} ev
*/
_onRemoveValidAnswerMouse: function (ev) {
var hover = (ev.type === 'mouseenter');
$(ev.currentTarget).find('.fa')
.toggleClass('fa-times-circle text-danger', hover)
.toggleClass('fa-check-circle text-success', !hover);
},
/**
* @private
* @param {Event} ev
*/
_onCheckAnswerStatus: function (ev) {
_.each(this.$('.accept_answer'), function (link) {
var $link = $(link);
$link.toggleClass('text-success', $link.hasClass('oe_answer_true'));
});
},
/** /**
* @private * @private
* @param {Event} ev * @param {Event} ev
@@ -398,7 +394,8 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({
this._rpc({ this._rpc({
route: $link.data('href'), route: $link.data('href'),
}).then(function (data) { }).then(function (data) {
$link.toggleClass('forum_favourite_question', !!data); $link.toggleClass('o_wforum_gold fa-star', data)
.toggleClass('fa-star-o text-muted', !data);
}); });
}, },
/** /**
@@ -408,10 +405,19 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({
_onDeleteCommentClick: function (ev) { _onDeleteCommentClick: function (ev) {
ev.preventDefault(); ev.preventDefault();
var $link = $(ev.currentTarget); var $link = $(ev.currentTarget);
var $container = $link.closest('.o_wforum_post_comments_container');
this._rpc({ this._rpc({
route: $link.closest('form').attr('action'), route: $link.closest('form').attr('action'),
}).then(function () { }).then(function () {
$link.parents('.comment').first().remove(); $link.closest('.o_wforum_post_comment').remove();
var count = $container.find('.o_wforum_post_comment').length;
if (count) {
$container.find('.o_wforum_comments_count').text(count);
} else {
$container.find('.o_wforum_comments_count_header').remove();
}
}); });
}, },
/** /**
@@ -1,223 +1,229 @@
$gold: #ffcc00; $gold: #eca801;
$silver: #cccccc; $silver: #cccccc;
$bronze: #eea91e; $bronze: #eea91e;
.website_forum { .website_forum {
.o_forum_ask_btn {
.question-block .question-name { @include media-breakpoint-up(md) {
max-width: 80%; box-shadow: $box-shadow;
width: 200px;
}
} }
.o_post_content { .o_wforum_nav .nav-link {
pre { @include o-hover-text-color($body-color, $link-color);
padding: 1.5rem; line-height: 1;
border-radius: 5px;
color: $white; .fa {
background-color: $gray-700; opacity: 0.5;
} }
&:hover, &.active {
.fa {
opacity: 1;
}
}
&.active {
background-color: rgba(theme-color('info'), 0.1);
color: darken(theme-color('info'), 15%)!important;
}
}
// Single Post
.o_wforum_post, .note-editable.panel-body {
word-wrap: break-word;
pre {
color: color-yiq(gray('100'));
border-radius: $border-radius;
padding: 1rem;
background-color: gray('100');
white-space: pre-wrap;
}
blockquote { blockquote {
position: relative; position: relative;
padding-left: 3rem; padding-left: 1em;
border-left: 2px solid $gray-500; border-left: .25em solid gray('500');
font-style: italic; color: gray('600');
font-weight: lighter;
}
word-wrap: break-word;
}
.vote {
min-width: 38px;
margin-right: 12px;
.vote_count {
font-weight: bold;
line-height: 12px;
font-size: 160%;
}
.vote_down,
.vote_up {
font-size: 130%;
} }
} }
.post_to_validate { .o_wforum_readable {
overflow: hidden; max-width: 700px;
.validate_answer {
p {
margin-bottom: 0.5rem;
}
}
textarea.o_wysiwyg_loader + .note-editor {
border: 0;
.note-toolbar {
@include border-top-radius($border-radius);
height: 32px;
}
.note-editable, .note-statusbar {
border: 1px solid $border-color;
border-width: 0 1px;
}
.note-statusbar {
display: block;
border-bottom-width: 1px;
}
}
.o_wforum_author_box {
&.o_show_info {
line-height: 1.2;
img {
@include size(2em);
}
}
&.o_compact {
line-height: 1;
img {
@include size(1.4em);
}
}
}
.forum_answer {
.o_wforum_answer_correct_badge {
display: none; display: none;
} }
}
.validate_answer { .o_wforum_author_box_check {
@include o-position-absolute(0, auto, 0, 0); @include size(1em);
width: 4px; display: none;
height: 100%; top: 0;
z-index: 1; // On top of card content if an alert is displayed inside right: -3px;
box-shadow: 0 0 0 2px white;
> .bg-success { line-height: .8;
@include o-position-absolute(0, 0, 0, 0);
} }
> span {
@include o-position-absolute(10px, 50%);
transform: translateX(50%);
font-size: 20px;
&[data-href] { .o_wforum_validate_toggler {
cursor: pointer; @include o-hover-text-color(gray('400'), lighten(theme-color('success'), 20%));
}
&.o_wforum_answer_correct {
.o_wforum_answer_correct_badge, .o_wforum_author_box .o_wforum_author_box_check {
display: inline;
} }
i { .o_wforum_validate_toggler {
padding: 2px 3px; @include o-hover-text-color(theme-color('success'), theme-color('warning'));
border-radius: 100%;
&:hover .fa.fa-check:before {
content: '\f00d';
}
}
.o_wforum_answer_header .o_wforum_author_pic {
border: 2px solid $success;
padding: 3px;
} }
} }
} }
.user_name_people { .o_wforum_gold {
font-weight: bold; color: $gold;
max-width: 60%;
} }
.oe_answer_true {
color: theme-color('success');
&:hover {
color: lighten(theme-color('success'), 20%);
}
}
.oe_answer_false {
color: $gray-400;
&:hover {
color: $gray-600;
}
}
.favourite_question {
color: $gray-400 !important;
&.forum_favourite_question {
color: $gold !important;
}
}
.oe_grey {
background-color: #eeeeee;
}
a.no-decoration { a.no-decoration {
cursor: pointer; cursor: pointer;
text-decoration: none !important; text-decoration: none !important;
} }
.close.comment_delete {
font-size: 0.875rem;
float: none;
opacity: 1;
font-weight: 300;
line-height: 1.5;
}
.country_flag {
display: inline-block;
margin-left: 2px;
max-height: 13px;
width: auto !important;
}
.forum_answer img, .question-block img { .forum_answer img, .question-block img {
max-width: 100% max-width: 100%
} }
}
img.country_flag_people { .website_forum, .o_wforum_profile_tab {
@include o-position-absolute(0,0); .vote {
max-height: 25px; .vote_count {
width: auto; line-height: 1;
} animation-play-state: paused;
.tag_text .text-core .text-wrap { &.o_forum_vote_animate {
.text-dropdown, animation: bounceIn 0.3s ease running;
.text-prompt, }
input,
textarea {
font: 1.2em "Helvetica Neue", Helvetica, Arial, sans-serif !important;
} }
.text-tags .text-tag .text-button { .vote_down, .vote_up {
font: 1.2em "Helvetica Neue", Helvetica, Arial, sans-serif !important; line-height: 0.5;
height: 1.2em !important; font-size: 1.1em;
@include o-hover-text-color(rgba($text-muted, 0.5), theme-color('success'));
}
.vote_down {
@include o-hover-text-color(rgba($text-muted, 0.5), theme-color('danger'));
}
&.o_wforum_vote_vertical {
@include media-breakpoint-up(sm) {
font-size: 1.2em;
}
} }
} }
}
.oe_forum_email_required { .o_js_forum_tag_follow {
position: absolute; .badge {
margin-top: 155px; font-size: 100%;
margin-left: 500px;
margin-right: 100px;
z-index: 5;
} }
button.btn-link.text-muted { .o_forum_tag_follow_box {
color: $gray-600; @include o-position-absolute(100%, auto, auto, 0);
display: none;
z-index: 1;
.card {
padding: ($grid-gutter-width*0.5) - 1px;
}
} }
} }
.o_forum_tag_follow_box {
position: absolute;
display: none;
z-index: 1;
text-align: center;
opacity: 0.9;
}
.forum_answer {
position: relative;
}
.o_wforum_post_count_answer_count {
border-left: 3px solid;
}
.o_profile_main { .o_profile_main {
overflow: hidden; overflow: hidden;
} }
img.o_forum_avatar { img.o_forum_avatar {
max-height: 40px; @include size(40px);
margin-right: 10px; object-fit: cover;
width: auto;
} }
img.o_forum_avatar_big { img.o_forum_avatar_big {
max-width: 75px; @include size(75px);
margin-right: 10px; object-fit: cover;
width: auto;
} }
.o_forum_user_bio_expand { .o_wforum_bio_popover_wrap {
position: absolute; .o_wforum_bio_popover_name {
display: none; address > div, span[data-oe-model="res.country"] {
z-index: 200; display: flex;
} align-items: center;
}
.o_forum_tooltip_heading { span[data-oe-model="res.country"] {
padding: 0; margin-left: 10px;
margin: 0; }
font-size: 18px;
}
.o_forum_user_bio {
color: $black;
width: 318px;
min-height: 100px;
cursor: auto;
border: 1px solid darkgray;
background-color: whitesmoke;
line-height: 13px;
font-size: 11px;
word-wrap: break-word;
.o_forum_tooltip_line {
min-height: 13px;
} }
.o_forum_tooltip_header {
min-height: 75px; .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;
} }
} }
@@ -8,7 +8,7 @@ odoo.define('website_forum.tour_forum_question', function (require) {
url: '/forum/help-1', url: '/forum/help-1',
}, [{ }, [{
content: "Ask the question in this forum by clicking on the button.", content: "Ask the question in this forum by clicking on the button.",
trigger: '.btn-lg.btn-block', trigger: '.o_forum_ask_btn',
}, { }, {
content: "Give your question content.", content: "Give your question content.",
trigger: 'input[name=post_name]', trigger: 'input[name=post_name]',
@@ -41,13 +41,13 @@ odoo.define('website_forum.tour_forum_question', function (require) {
trigger: 'button:contains("Post Answer")', trigger: 'button:contains("Post Answer")',
}, { }, {
content: "This page contain new created question and its answer.", content: "This page contain new created question and its answer.",
extra_trigger: '#wrap:has(".fa-check-circle")', extra_trigger: '#wrap:has(".o_wforum_validate_toggler")',
trigger: 'button[data-dismiss="modal"]', trigger: 'button[data-dismiss="modal"]',
}, { }, {
content: "Click here to accept this answer.", content: "Click here to accept this answer.",
trigger: 'a[data-karma="20"]:first', trigger: '.o_wforum_validate_toggler[data-karma="20"]:first',
}, { }, {
content: "Congratulations! You just created and post your first question and answer.", content: "Congratulations! You just created and post your first question and answer.",
trigger: '#wrap:has(".oe_answer_true")', trigger: '#wrap:has(".o_wforum_answer_correct")',
}]); }]);
}); });
File diff suppressed because it is too large Load Diff
@@ -9,6 +9,17 @@
</xpath> </xpath>
</template> </template>
<template id="user_profile_sub_nav" inherit_id="website_profile.user_profile_sub_nav">
<xpath expr="//nav" position="before">
<div t-if="request.params.get('forum_origin')" class="o_wprofile_all_users_nav_btn_container col pr-0 flex-grow-0">
<a t-att-href="request.params.get('forum_origin')"
class="o_wprofile_all_users_nav_btn btn text-nowrap">
<i class="fa fa-chevron-left small"/> Back
</a>
</div>
</xpath>
</template>
<template id="user_profile_content" inherit_id="website_profile.user_profile_content"> <template id="user_profile_content" inherit_id="website_profile.user_profile_content">
<xpath expr="//table[@id='o_wprofile_sidebar_table']//tr[last()]" position="after"> <xpath expr="//table[@id='o_wprofile_sidebar_table']//tr[last()]" position="after">
<t t-if="forum and (up_votes or down_votes)"> <t t-if="forum and (up_votes or down_votes)">
@@ -43,11 +54,11 @@
</xpath> </xpath>
<xpath expr="//div[@id='profile_extra_info_tabcontent']" position="inside"> <xpath expr="//div[@id='profile_extra_info_tabcontent']" position="inside">
<t t-if="forum"> <t t-if="forum">
<div role="tabpanel" class="tab-pane" id="questions"> <div role="tabpanel" class="o_wforum_profile_tab tab-pane" id="questions">
<div class="mb32"> <div class="mb32">
<h5 class="border-bottom pb-1">Questions</h5> <h5 class="border-bottom pb-1">Questions</h5>
<t t-if="questions"> <t t-if="questions">
<div class="mb16" t-foreach="questions" t-as="question"> <div class="mb4" t-foreach="questions" t-as="question">
<t t-call="website_forum.display_post"/> <t t-call="website_forum.display_post"/>
</div> </div>
</t> </t>
@@ -56,15 +67,17 @@
</t> </t>
</div> </div>
<t t-if="favourite"> <t t-if="favourite">
<div class="mb32"> <div class="mb4">
<h5 class="border-bottom pb-1">Favourite Questions</h5> <h5 class="border-bottom pb-1">Favourite Questions</h5>
<div t-foreach="favourite" t-as="question"> <div t-foreach="favourite" t-as="question">
<t t-call="website_forum.display_post"/> <t t-call="website_forum.display_post">
<t t-set="hide_fav_icon" t-value="True"/>
</t>
</div> </div>
</div> </div>
</t> </t>
<t t-if="followed"> <t t-if="followed">
<div class="mb32"> <div class="mb4">
<h5 class="border-bottom pb-1">Followed Questions</h5> <h5 class="border-bottom pb-1">Followed Questions</h5>
<div t-foreach="followed" t-as="question" class="mb16"> <div t-foreach="followed" t-as="question" class="mb16">
<t t-call="website_forum.display_post"/> <t t-call="website_forum.display_post"/>
@@ -72,7 +85,7 @@
</div> </div>
</t> </t>
</div> </div>
<div role="tabpanel" class="tab-pane" id="answers"> <div role="tabpanel" class="o_wforum_profile_tab tab-pane" id="answers">
<div class="mb32"> <div class="mb32">
<h5 class="border-bottom pb-1">Answers</h5> <h5 class="border-bottom pb-1">Answers</h5>
<t t-if="answers"> <t t-if="answers">
@@ -85,13 +98,13 @@
</t> </t>
</div> </div>
</div> </div>
<div role="tabpanel" class="tab-pane" id="votes" t-if="uid == user.id"> <div role="tabpanel" class="o_wforum_profile_tab tab-pane" id="votes" t-if="uid == user.id">
<div class="mb32"> <div class="mb32">
<h5 class="border-bottom pb-1">Votes</h5> <h5 class="border-bottom pb-1">Votes</h5>
<t t-call="website_forum.user_votes"/> <t t-call="website_forum.user_votes"/>
</div> </div>
</div> </div>
<div role="tabpanel" class="tab-pane" id="activity" t-if="uid == user.id"> <div role="tabpanel" class="o_wforum_profile_tab tab-pane" id="activity" t-if="uid == user.id">
<div class="mb32"> <div class="mb32">
<h5 class="border-bottom pb-1">Activities</h5> <h5 class="border-bottom pb-1">Activities</h5>
<t t-call="website_forum.display_activities"/> <t t-call="website_forum.display_activities"/>
@@ -133,16 +133,43 @@ $owprofile-color-bg: mix($body-bg, #efeff4);
.o_wprofile_all_users_nav { .o_wprofile_all_users_nav {
border-width: 1px 0; border-width: 1px 0;
&, .o_wprofile_course_nav_search { &, .o_wprofile_course_nav_search, .o_wprofile_all_users_nav_btn {
background-color: rgba(white, 0.05); background-color: rgba(white, 0.05);
border-color: rgba(white, 0.1); border-color: rgba(white, 0.1);
border-style: solid; border-style: solid;
} }
.o_wprofile_course_nav_search { .o_wprofile_course_nav_search, .o_wprofile_all_users_nav_btn {
border-width: 0 1px; border-width: 0 1px;
} }
.o_wprofile_all_users_nav_btn {
@include media-breakpoint-up(md) {
@include o-hover-text-color(white, $gray-800);
margin-top: -1px;
border-radius: 0;
min-height: 35px;
&:hover {
background-color: white;
}
}
}
.o_wprofile_all_users_nav_btn_container {
@include media-breakpoint-down(sm) {
~ .o_wprofile_user_profile_sub_nav_mobile_col {
padding-left: 0;
}
.o_wprofile_all_users_nav_btn {
@include o-hover-text-color(white, white);
border-radius: $btn-border-radius;
background-color: rgba(black, 0.25);
}
}
}
.breadcrumb-item.active a, .breadcrumb-item a:hover { .breadcrumb-item.active a, .breadcrumb-item a:hover {
color: white; color: white;
} }
@@ -41,7 +41,7 @@
</div> </div>
<!-- Mobile Mode --> <!-- Mobile Mode -->
<div class="col d-md-none py-1"> <div class="col d-md-none py-1 o_wprofile_user_profile_sub_nav_mobile_col">
<div class="btn-group w-100 position-relative" role="group" aria-label="Mobile sub-nav"> <div class="btn-group w-100 position-relative" role="group" aria-label="Mobile sub-nav">
<div class="btn-group w-100 ml-2"> <div class="btn-group w-100 ml-2">
<a class="btn bg-black-25 text-white dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Nav</a> <a class="btn bg-black-25 text-white dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Nav</a>