From 645056554ee38b5b96aeb53b6ed59cdb8e41b74c Mon Sep 17 00:00:00 2001 From: "Okan SUMER (osu)" Date: Thu, 19 Mar 2020 09:42:59 +0000 Subject: [PATCH] [IMP] website_forum: frontend improvements and corrections This commit adds new features and corrects some others in the frontend of website_forum app. - In the "about" section of a forum, use the s_accordion instead of a custom one for the first 3 elements, the last one is on a new page accessible from a link below the accordion - Cut the description after a specific amount of characters in the page where all forum are listed - In that same view, create options that show the number of post and the last post a forum has. Create an option to render forums in list view - Allow the user to cancel his vote (down or up) - Hide the answer box in the post view. Make it appear when the user click on the "answer" button - Use a JS public widget to validate a Post form (new, save, comment) - Enable the modification of a forum's image from the frontend - Hide Solved/Unsolved categories in "Discussion" forums - Disable the possibility to add snippets to an answer - Corrections of tours that broke after these modifications Part of https://github.com/odoo/odoo/pull/46634 task-2201708 --- addons/website_forum/__manifest__.py | 1 + addons/website_forum/controllers/main.py | 10 +- .../website_forum/data/forum_default_faq.html | 63 ------ .../website_forum/data/forum_default_faq.xml | 92 +++++++++ addons/website_forum/models/forum.py | 45 +++-- .../static/src/js/tours/website_forum.js | 8 +- .../static/src/js/website_forum.js | 70 +++++++ .../static/src/js/website_forum.share.js | 18 -- .../tests/tours/website_forum_question.js | 8 +- addons/website_forum/views/website_forum.xml | 184 +++++++++++++----- .../views/website_slides_forum_templates.xml | 8 +- 11 files changed, 358 insertions(+), 149 deletions(-) delete mode 100644 addons/website_forum/data/forum_default_faq.html create mode 100644 addons/website_forum/data/forum_default_faq.xml diff --git a/addons/website_forum/__manifest__.py b/addons/website_forum/__manifest__.py index e7e3423035c..19e72d59585 100644 --- a/addons/website_forum/__manifest__.py +++ b/addons/website_forum/__manifest__.py @@ -17,6 +17,7 @@ Ask questions, get answers, no distractions 'website_profile', ], 'data': [ + 'data/forum_default_faq.xml', 'data/forum_data.xml', 'views/forum.xml', 'views/res_users_views.xml', diff --git a/addons/website_forum/controllers/main.py b/addons/website_forum/controllers/main.py index b309a644be0..51a4c00b0c1 100644 --- a/addons/website_forum/controllers/main.py +++ b/addons/website_forum/controllers/main.py @@ -45,7 +45,10 @@ class WebsiteForum(WebsiteProfile): forums = request.env['forum.forum'].search(domain) if len(forums) == 1: return werkzeug.utils.redirect('/forum/%s' % slug(forums[0]), code=302) - return request.render("website_forum.forum_all", {'forums': forums}) + + return request.render("website_forum.forum_all", { + 'forums': forums + }) @http.route('/forum/new', type='json', auth="user", methods=['POST'], website=True) def forum_create(self, forum_name="New Forum", forum_mode="questions", forum_privacy="public", forum_privacy_group=False, add_menu=False): @@ -164,6 +167,11 @@ class WebsiteForum(WebsiteProfile): values = self._prepare_user_values(forum=forum, searches=dict(), header={'is_guidelines': True}, **post) return request.render("website_forum.faq", values) + @http.route(['/forum//faq/karma'], type='http', auth="public", website=True, sitemap=False) + def forum_faq_karma(self, forum, **post): + values = self._prepare_user_values(forum=forum, header={'is_guidelines': True, 'is_karma': True}, **post) + return request.render("website_forum.faq_karma", values) + @http.route('/forum/get_tags', type='http', auth="public", methods=['GET'], website=True, sitemap=False) def tag_read(self, query='', limit=25, **post): data = request.env['forum.tag'].search_read( diff --git a/addons/website_forum/data/forum_default_faq.html b/addons/website_forum/data/forum_default_faq.html deleted file mode 100644 index 43f4489c67b..00000000000 --- a/addons/website_forum/data/forum_default_faq.html +++ /dev/null @@ -1,63 +0,0 @@ -
-
- -
-
-

This community is for professional and enthusiast users, partners and programmers. You can ask questions about:

-
    -
  • how to install Odoo on a specific infrastructure,
  • -
  • how to configure or customize Odoo to specific business needs,
  • -
  • what's the best way to use Odoo for a specific business need,
  • -
  • how to develop modules for your own need,
  • -
  • specific questions about Odoo service offers, etc.
  • -
-

Before you ask - please make sure to search for a similar question. You can search questions by their title or tags. It’s also OK to answer your own question.

-

Please avoid asking questions that are too subjective and argumentative or not relevant to this community.

-
-
-
-
- -
-
-

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.

-

To prevent your question from being flagged and possibly removed, avoid asking subjective questions where …

-
    -
  • every answer is equally valid: “What’s your favorite ______?”
  • -
  • your answer is provided along with the question, and you expect more answers: “I use ______ for ______, what do you use?”
  • -
  • there is no actual problem to be solved: “I’m curious if other people feel like I do.”
  • -
  • we are being asked an open-ended, hypothetical question: “What if ______ happened?”
  • -
  • it is a rant disguised as a question: “______ sucks, am I right?”
  • -
-

If you fit in one of these example or if your motivation for asking the question is “I would like to participate in a discussion about ______”, then you should not be asking here but on our mailing lists. However, if your motivation is “I would like others to explain ______ to me”, then you are probably OK.

-

(The above section was adapted from Stackoverflow’s FAQ.)

-

More over:

-
    -
  • Answers should not add or expand questions. Instead either edit the question or add a question comment.
  • -
  • Answers should not comment other answers. Instead add a comment on the other answers.
  • -
  • Answers shouldn't just point to other Questions. Instead add a question comment indication "Possible duplicate of...". However, it's ok to include links to other questions or answers providing relevant additional information.
  • -
  • Answers shouldn't just provide a link a solution. 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.
  • -
-
-
-
-
- -
-
-

Answers should not add or expand questions. Insteadeither edit the question or add a comment.

-

Answers should not comment other answers. Instead add a comment on the other answers.

-

Answers shouldn't just point to other questions.Instead add a comment indicating "Possible duplicate of...". However, it's fine to include links to other questions or answers providing relevant additional information.

-

Answers shouldn't just provide a link a solution. 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.

-

Answers should not start debates This community Q&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.

-

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.

-

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 10 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.

-
-
-
diff --git a/addons/website_forum/data/forum_default_faq.xml b/addons/website_forum/data/forum_default_faq.xml new file mode 100644 index 00000000000..71406580d45 --- /dev/null +++ b/addons/website_forum/data/forum_default_faq.xml @@ -0,0 +1,92 @@ + + + + Faq Accordion + qweb + website_forum.faq_accordion + +
+
+
+
+ +
+
+

This community is for professional and enthusiast users, partners and programmers. You can ask questions about:

+
    +
  • how to install Odoo on a specific infrastructure,
  • +
  • how to configure or customize Odoo to specific business needs,
  • +
  • what's the best way to use Odoo for a specific business need,
  • +
  • how to develop modules for your own need,
  • +
  • specific questions about Odoo service offers, etc.
  • +
+

Before you ask - please make sure to search for a similar question. You can search questions by their title or tags. It’s also OK to answer your own question.

+

Please avoid asking questions that are too subjective and argumentative or not relevant to this community.

+
+
+
+
+ +
+
+

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.

+

To prevent your question from being flagged and possibly removed, avoid asking subjective questions where …

+
    +
  • every answer is equally valid: “What’s your favorite ______?”
  • +
  • your answer is provided along with the question, and you expect more answers: “I use ______ for ______, what do you use?”
  • +
  • there is no actual problem to be solved: “I’m curious if other people feel like I do.”
  • +
  • we are being asked an open-ended, hypothetical question: “What if ______ happened?”
  • +
  • it is a rant disguised as a question: “______ sucks, am I right?”
  • +
+

If you fit in one of these example or if your motivation for asking the question is “I would like to participate in a discussion about ______”, then you should not be asking here but on our mailing lists. However, if your motivation is “I would like others to explain ______ to me”, then you are probably OK.

+

(The above section was adapted from Stackoverflow’s FAQ.)

+

More over:

+
    +
  • Answers should not add or expand questions. Instead either edit the question or add a question comment.
  • +
  • Answers should not comment other answers. Instead add a comment on the other answers.
  • +
  • Answers shouldn't just point to other Questions. Instead add a question comment indication "Possible duplicate of...". However, it's ok to include links to other questions or answers providing relevant additional information.
  • +
  • Answers shouldn't just provide a link a solution. 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.
  • +
+
+
+
+
+ +
+
+

Answers should not add or expand questions. Insteadeither edit the question or add a comment.

+

Answers should not comment other answers. Instead add a comment on the other answers.

+

Answers shouldn't just point to other questions.Instead add a comment indicating "Possible duplicate of...". However, it's fine to include links to other questions or answers providing relevant additional information.

+

Answers shouldn't just provide a link a solution. 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.

+

Answers should not start debates This community Q&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.

+

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.

+

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 10 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.

+
+
+
+
+ +
+
+

The goal of this site is create a relevant knowledge base that would answer questions related to Odoo.

+

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.

+

If this approach is not for you, please respect the community.

+ Here a table with the privileges and the karma level +
+
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/addons/website_forum/models/forum.py b/addons/website_forum/models/forum.py index 7468411efeb..0fe44dbe9c7 100644 --- a/addons/website_forum/models/forum.py +++ b/addons/website_forum/models/forum.py @@ -22,11 +22,6 @@ class Forum(models.Model): _inherit = ['mail.thread', 'image.mixin', 'website.seo.metadata', 'website.multi.mixin'] _order = "sequence" - @api.model - def _get_default_faq(self): - with misc.file_open('website_forum/data/forum_default_faq.html', 'r') as f: - return f.read() - # description and use name = fields.Char('Forum Name', required=True, translate=True) sequence = fields.Integer('Sequence', default=1) @@ -44,8 +39,9 @@ class Forum(models.Model): authorized_group_id = fields.Many2one('res.groups', 'Authorized Group') menu_id = fields.Many2one('website.menu', 'Menu', copy=False) active = fields.Boolean(default=True) - faq = fields.Html('Guidelines', default=_get_default_faq, translate=html_translate, sanitize=False) + faq = fields.Html('Guidelines', translate=html_translate, sanitize=False) description = fields.Text('Description', translate=True) + teaser = fields.Text('Teaser', compute='_compute_teaser', store=True) welcome_message = fields.Html( 'Welcome Message', translate=True, @@ -85,10 +81,11 @@ class Forum(models.Model): 'of the forum content.') # posts statistics post_ids = fields.One2many('forum.post', 'forum_id', string='Posts') - total_posts = fields.Integer('Posts', compute='_compute_forum_statistics') - total_views = fields.Integer('Views', compute='_compute_forum_statistics') - total_answers = fields.Integer('Answers', compute='_compute_forum_statistics') - total_favorites = fields.Integer('Favorites', compute='_compute_forum_statistics') + last_post_id = fields.Many2one('forum.post', compute='_compute_last_post') + total_posts = fields.Integer('# Posts', compute='_compute_forum_statistics') + total_views = fields.Integer('# Views', compute='_compute_forum_statistics') + total_answers = fields.Integer('# Answers', compute='_compute_forum_statistics') + total_favorites = fields.Integer('# Favorites', compute='_compute_forum_statistics') count_posts_waiting_validation = fields.Integer(string="Number of posts waiting for validation", compute='_compute_count_posts_waiting_validation') count_flagged_posts = fields.Integer(string='Number of flagged posts', compute='_compute_count_flagged_posts') # karma generation @@ -129,6 +126,23 @@ class Forum(models.Model): karma_post = fields.Integer(string='Ask questions without validation', default=100) karma_moderate = fields.Integer(string='Moderate posts', default=1000) + @api.depends('post_ids') + def _compute_last_post(self): + for forum in self: + forum.last_post_id = forum.post_ids.search([('forum_id', '=', forum.id), ('parent_id', '=', False), ('state', '=', 'active')], order='create_date desc', limit=1) + + @api.depends('description') + def _compute_teaser(self): + for forum in self: + if forum.description: + desc = forum.description.replace('\n', ' ') + if len(forum.description) > 180: + forum.teaser = desc[:180] + '...' + else: + forum.teaser = forum.description + else: + forum.teaser = "" + @api.depends('post_ids.state', 'post_ids.views', 'post_ids.child_count', 'post_ids.favourite_count') def _compute_forum_statistics(self): result = dict((cid, dict(total_posts=0, total_views=0, total_answers=0, total_favorites=0)) for cid in self.ids) @@ -157,9 +171,14 @@ class Forum(models.Model): domain = [('forum_id', '=', forum.id), ('state', '=', 'flagged')] forum.count_flagged_posts = self.env['forum.post'].search_count(domain) + def _set_default_faq(self): + self.faq = self.env['ir.ui.view'].render_template('website_forum.faq_accordion', {"forum": self}).decode('utf-8') + @api.model def create(self, values): - return super(Forum, self.with_context(mail_create_nolog=True, mail_create_nosubscribe=True)).create(values) + res = super(Forum, self.with_context(mail_create_nolog=True, mail_create_nosubscribe=True)).create(values) + res._set_default_faq() + return res def write(self, vals): if 'privacy' in vals: @@ -411,8 +430,8 @@ class Post(models.Model): post.can_edit = is_admin or user.karma >= post.karma_edit post.can_close = is_admin or user.karma >= post.karma_close post.can_unlink = is_admin or user.karma >= post.karma_unlink - post.can_upvote = is_admin or user.karma >= post.forum_id.karma_upvote - post.can_downvote = is_admin or user.karma >= post.forum_id.karma_downvote + post.can_upvote = is_admin or user.karma >= post.forum_id.karma_upvote or post.user_vote == -1 + post.can_downvote = is_admin or user.karma >= post.forum_id.karma_downvote or post.user_vote == 1 post.can_comment = is_admin or user.karma >= post.karma_comment post.can_comment_convert = is_admin or user.karma >= post.karma_comment_convert post.can_view = is_admin or user.karma >= post.karma_close or (post_sudo.create_uid.karma > 0 and (post_sudo.active or post_sudo.create_uid == user)) diff --git a/addons/website_forum/static/src/js/tours/website_forum.js b/addons/website_forum/static/src/js/tours/website_forum.js index b8bd5c67a11..55f57457ae6 100644 --- a/addons/website_forum/static/src/js/tours/website_forum.js +++ b/addons/website_forum/static/src/js/tours/website_forum.js @@ -39,7 +39,13 @@ odoo.define("website_forum.tour_forum", function (require) { extra_trigger: 'div.modal.modal_shown', trigger: ".modal-header button.close", auto: true, - }, { + }, + { + trigger: "a:contains(\"Answer\").collapsed", + content: _t("Click to answer."), + position: "bottom", + }, + { trigger: ".note-editable p", content: _t("Put your answer here."), position: "bottom", diff --git a/addons/website_forum/static/src/js/website_forum.js b/addons/website_forum/static/src/js/website_forum.js index 5b22fa03c01..dcbca2e364a 100644 --- a/addons/website_forum/static/src/js/website_forum.js +++ b/addons/website_forum/static/src/js/website_forum.js @@ -27,6 +27,7 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({ 'click .o_wforum_favourite_toggle': '_onFavoriteQuestionClick', 'click .comment_delete': '_onDeleteCommentClick', 'click .js_close_intro': '_onCloseIntroClick', + 'submit .js_wforum_submit_form:has(:not(.karma_required).o_wforum_submit_post)': '_onSubmitForm', }, /** @@ -164,6 +165,12 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({ }); }); + this.$('#post_reply').on('shown.bs.collapse', function (e) { + $('html').animate({ + scrollTop: $('#post_reply').offset().top - ($(window).innerHeight() - $('#post_reply').innerHeight()) + }, 500); + }); + return this._super.apply(this, arguments); }, @@ -171,6 +178,59 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({ // Handlers //-------------------------------------------------------------------------- + /** + * + * @override + * @param {Event} ev + */ + _onSubmitForm: function (ev) { + let validForm = true; + + let $form = $(ev.currentTarget); + let $title = $form.find('input[name=post_name]'); + let $textarea = $form.find('textarea[name=content]'); + // It's not really in the textarea that the user write at first + let textareaContent = $form.find('.o_wysiwyg_wrapper .note-editable.panel-body').text().trim(); + + if ($title.length && $title[0].required) { + if ($title.val()) { + $title.removeClass('is-invalid'); + } else { + $title.addClass('is-invalid'); + validForm = false; + } + } + + // Because the textarea is hidden, we add the red or green border to its container + if ($textarea[0].required) { + let $textareaContainer = $form.find('.o_wysiwyg_wrapper .note-editor.panel.panel-default'); + if (!textareaContent.length) { + $textareaContainer.addClass('border border-danger rounded-top'); + validForm = false; + } else { + $textareaContainer.removeClass('border border-danger rounded-top'); + } + } + + if (validForm) { + // Stores social share data to display modal on next page. + if ($form.has('.oe_social_share_call').length) { + sessionStorage.setItem('social_share', JSON.stringify({ + targetType: $(ev.currentTarget).find('.o_wforum_submit_post').data('social-target-type'), + })); + } + } else { + ev.preventDefault(); + setTimeout(function() { + var $buttons = $(ev.currentTarget).find('button[type="submit"], a.a-submit'); + _.each($buttons, function (btn) { + let $btn = $(btn); + $btn.find('i').remove(); + $btn.prop('disabled', false); + }); + }, 0); + } + }, /** * @private * @param {Event} ev @@ -317,10 +377,20 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({ if (userVote === 1) { $voteUp.addClass('text-success'); $voteCount.addClass('text-success'); + $voteDown.removeClass('karma_required'); } if (userVote === -1) { $voteDown.addClass('text-danger'); $voteCount.addClass('text-danger'); + $voteUp.removeClass('karma_required'); + } + if (userVote === 0) { + if (!$voteDown.data('can-downvote')) { + $voteDown.addClass('karma_required'); + } + if (!$voteUp.data('can-upvote')) { + $voteUp.addClass('karma_required'); + } } $voteCount.html(data['vote_count']).addClass('o_forum_vote_animate'); } diff --git a/addons/website_forum/static/src/js/website_forum.share.js b/addons/website_forum/static/src/js/website_forum.share.js index 0545d775a81..68a2b8f3b72 100644 --- a/addons/website_forum/static/src/js/website_forum.share.js +++ b/addons/website_forum/static/src/js/website_forum.share.js @@ -76,9 +76,6 @@ var ForumShare = publicWidget.registry.socialShare.extend({ publicWidget.registry.websiteForumShare = publicWidget.Widget.extend({ selector: '.website_forum', - events: { - 'click :not(.karma_required).oe_social_share_call': '_onShareCallClick', - }, /** * @override @@ -98,20 +95,5 @@ publicWidget.registry.websiteForumShare = publicWidget.Widget.extend({ return this._super.apply(this, arguments); }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * Stores social share data to display modal on next page. - * - * @private - */ - _onShareCallClick: function (ev) { - sessionStorage.setItem('social_share', JSON.stringify({ - targetType: $(ev.currentTarget).data('social-target-type'), - })); - }, }); }); diff --git a/addons/website_forum/static/tests/tours/website_forum_question.js b/addons/website_forum/static/tests/tours/website_forum_question.js index b2f549aed83..be292067db6 100644 --- a/addons/website_forum/static/tests/tours/website_forum_question.js +++ b/addons/website_forum/static/tests/tours/website_forum_question.js @@ -35,7 +35,13 @@ odoo.define('website_forum.tour_forum_question', function (require) { content: "Close modal once modal animation is done.", extra_trigger: 'div.modal.modal_shown', trigger: ".modal-header button.close", - }, { + }, + { + trigger: "a:contains(\"Answer\").collapsed", + content: "Click to answer.", + position: "bottom", + }, + { content: "Put your answer here.", trigger: '.note-editable p', run: 'text First Answer', diff --git a/addons/website_forum/views/website_forum.xml b/addons/website_forum/views/website_forum.xml index 7bf8a313732..9dc01f8fdf9 100644 --- a/addons/website_forum/views/website_forum.xml +++ b/addons/website_forum/views/website_forum.xml @@ -83,7 +83,15 @@ - + + + + @@ -362,12 +370,18 @@