summaryrefslogtreecommitdiff
path: root/addons/website_forum/static/src
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/website_forum/static/src
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/website_forum/static/src')
-rw-r--r--addons/website_forum/static/src/js/tours/website_forum.js67
-rw-r--r--addons/website_forum/static/src/js/website_forum.editor.js144
-rw-r--r--addons/website_forum/static/src/js/website_forum.js593
-rw-r--r--addons/website_forum/static/src/js/website_forum.share.js99
-rw-r--r--addons/website_forum/static/src/scss/website_forum.scss272
-rw-r--r--addons/website_forum/static/src/xml/website_forum_share_templates.xml75
-rw-r--r--addons/website_forum/static/src/xml/website_forum_templates.xml53
7 files changed, 1303 insertions, 0 deletions
diff --git a/addons/website_forum/static/src/js/tours/website_forum.js b/addons/website_forum/static/src/js/tours/website_forum.js
new file mode 100644
index 00000000..55f57457
--- /dev/null
+++ b/addons/website_forum/static/src/js/tours/website_forum.js
@@ -0,0 +1,67 @@
+odoo.define("website_forum.tour_forum", function (require) {
+ "use strict";
+
+ var core = require("web.core");
+ var tour = require("web_tour.tour");
+
+ var _t = core._t;
+
+ tour.register("question", {
+ url: "/forum/1",
+ }, [{
+ trigger: ".o_forum_ask_btn",
+ position: "left",
+ content: _t("Create a new post in this forum by clicking on the button."),
+ }, {
+ trigger: "input[name=post_name]",
+ position: "top",
+ content: _t("Give your post title."),
+ }, {
+ trigger: ".note-editable p",
+ extra_trigger: "input[name=post_name]:not(:propValue(\"\"))",
+ content: _t("Put your question here."),
+ position: "bottom",
+ run: "text",
+ }, {
+ trigger: ".select2-choices",
+ extra_trigger: ".note-editable p:not(:containsExact(\"<br>\"))",
+ content: _t("Insert tags related to your question."),
+ position: "top",
+ run: function (actions) {
+ actions.auto("input[id=s2id_autogen2]");
+ },
+ }, {
+ trigger: "button:contains(\"Post\")",
+ extra_trigger: "input[id=s2id_autogen2]:not(:propValue(\"Tags\"))",
+ content: _t("Click to post your question."),
+ position: "bottom",
+ }, {
+ 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",
+ run: "text",
+ }, {
+ trigger: "button:contains(\"Post Answer\")",
+ extra_trigger: ".note-editable p:not(:containsExact(\"<br>\"))",
+ content: _t("Click to post your answer."),
+ position: "bottom",
+ }, {
+ extra_trigger: 'div.modal.modal_shown',
+ trigger: ".modal-header button.close",
+ auto: true,
+ }, {
+ trigger: ".o_wforum_validate_toggler[data-karma=\"20\"]:first",
+ content: _t("Click here to accept this answer."),
+ position: "right",
+ }]);
+});
diff --git a/addons/website_forum/static/src/js/website_forum.editor.js b/addons/website_forum/static/src/js/website_forum.editor.js
new file mode 100644
index 00000000..74072c59
--- /dev/null
+++ b/addons/website_forum/static/src/js/website_forum.editor.js
@@ -0,0 +1,144 @@
+odoo.define('website_forum.editor', function (require) {
+"use strict";
+
+var core = require('web.core');
+var WebsiteNewMenu = require('website.newMenu');
+var Dialog = require('web.Dialog');
+
+var _t = core._t;
+
+var ForumCreateDialog = Dialog.extend({
+ xmlDependencies: Dialog.prototype.xmlDependencies.concat(
+ ['/website_forum/static/src/xml/website_forum_templates.xml']
+ ),
+ template: 'website_forum.add_new_forum',
+ events: _.extend({}, Dialog.prototype.events, {
+ 'change input[name="privacy"]': '_onPrivacyChanged',
+ }),
+
+ /**
+ * @override
+ * @param {Object} parent
+ * @param {Object} options
+ */
+ init: function (parent, options) {
+ options = _.defaults(options || {}, {
+ title: _t("New Forum"),
+ size: 'medium',
+ buttons: [
+ {
+ text: _t("Create"),
+ classes: 'btn-primary',
+ click: this.onCreateClick.bind(this),
+ },
+ {
+ text: _t("Discard"),
+ close: true
+ },
+ ]
+ });
+ this._super(parent, options);
+ },
+ start: function () {
+ var self = this;
+ return this._super.apply(this, arguments).then(function () {
+ var $input = self.$('#group_id');
+ $input.select2({
+ width: '100%',
+ allowClear: true,
+ formatNoMatches: false,
+ multiple: false,
+ selection_data: false,
+ fill_data: function (query, data) {
+ var that = this;
+ var tags = {results: []};
+ _.each(data, function (obj) {
+ if (that.matcher(query.term, obj.display_name)) {
+ tags.results.push({id: obj.id, text: obj.display_name});
+ }
+ });
+ query.callback(tags);
+ },
+ query: function (query) {
+ var that = this;
+ // fetch data only once and store it
+ if (!this.selection_data) {
+ self._rpc({
+ model: 'res.groups',
+ method: 'search_read',
+ args: [[], ['display_name']],
+ }).then(function (data) {
+ that.fill_data(query, data);
+ that.selection_data = data;
+ });
+ } else {
+ this.fill_data(query, this.selection_data);
+ }
+ }
+ });
+ });
+ },
+ onCreateClick: function () {
+ var $dialog = this.$el;
+ var $forumName = $dialog.find('input[name=forum_name]');
+ if (!$forumName.val()) {
+ $forumName.addClass('border-danger');
+ return;
+ }
+ var $forumPrivacyGroup = $dialog.find('input[name=group_id]');
+ var forumPrivacy = $dialog.find('input:radio[name=privacy]:checked').val();
+ if (forumPrivacy === 'private' && !$forumPrivacyGroup.val()) {
+ this.$("#group-required").removeClass('d-none');
+ return;
+ }
+ var addMenu = ($dialog.find('input[type="checkbox"]').is(':checked'));
+ var forumMode = $dialog.find('input:radio[name=mode]:checked').val();
+ return this._rpc({
+ route: '/forum/new',
+ params: {
+ forum_name: $forumName.val(),
+ forum_mode: forumMode,
+ forum_privacy: forumPrivacy,
+ forum_privacy_group: $forumPrivacyGroup.val(),
+ add_menu: addMenu || "",
+ },
+ }).then(function (url) {
+ window.location.href = url;
+ return new Promise(function () {});
+ });
+ },
+ /**
+ * @private
+ */
+ _onPrivacyChanged: function (ev) {
+ this.$('.show_visibility_group').toggleClass('d-none', ev.target.value !== 'private');
+ },
+});
+
+WebsiteNewMenu.include({
+ actions: _.extend({}, WebsiteNewMenu.prototype.actions || {}, {
+ new_forum: '_createNewForum',
+ }),
+
+ //--------------------------------------------------------------------------
+ // Actions
+ //--------------------------------------------------------------------------
+
+ /**
+ * Asks the user information about a new forum to create, then creates it
+ * and redirects the user to this new forum.
+ *
+ * @private
+ * @returns {Promise} Unresolved if there is a redirection
+ */
+ _createNewForum: function () {
+ var self = this;
+ var def = new Promise(function (resolve) {
+ var dialog = new ForumCreateDialog(self, {});
+ dialog.open();
+ dialog.on('closed', self, resolve);
+ });
+ return def;
+ },
+});
+});
diff --git a/addons/website_forum/static/src/js/website_forum.js b/addons/website_forum/static/src/js/website_forum.js
new file mode 100644
index 00000000..e65d8944
--- /dev/null
+++ b/addons/website_forum/static/src/js/website_forum.js
@@ -0,0 +1,593 @@
+odoo.define('website_forum.website_forum', function (require) {
+'use strict';
+
+const dom = require('web.dom');
+var core = require('web.core');
+var weDefaultOptions = require('web_editor.wysiwyg.default_options');
+var wysiwygLoader = require('web_editor.loader');
+var publicWidget = require('web.public.widget');
+var session = require('web.session');
+var qweb = core.qweb;
+
+var _t = core._t;
+
+publicWidget.registry.websiteForum = publicWidget.Widget.extend({
+ selector: '.website_forum',
+ xmlDependencies: ['/website_forum/static/src/xml/website_forum_share_templates.xml'],
+ events: {
+ 'click .karma_required': '_onKarmaRequiredClick',
+ 'mouseenter .o_js_forum_tag_follow': '_onTagFollowBoxMouseEnter',
+ 'mouseleave .o_js_forum_tag_follow': '_onTagFollowBoxMouseLeave',
+ 'mouseenter .o_forum_user_info': '_onUserInfoMouseEnter',
+ 'mouseleave .o_forum_user_info': '_onUserInfoMouseLeave',
+ 'mouseleave .o_forum_user_bio_expand': '_onUserBioExpandMouseLeave',
+ 'click .flag:not(.karma_required)': '_onFlagAlertClick',
+ 'click .vote_up:not(.karma_required), .vote_down:not(.karma_required)': '_onVotePostClick',
+ 'click .o_js_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',
+ 'submit .js_wforum_submit_form:has(:not(.karma_required).o_wforum_submit_post)': '_onSubmitForm',
+ },
+
+ /**
+ * @override
+ */
+ start: function () {
+ var self = this;
+
+ this.lastsearch = [];
+
+ // float-left class messes up the post layout OPW 769721
+ $('span[data-oe-model="forum.post"][data-oe-field="content"]').find('img.float-left').removeClass('float-left');
+
+ // welcome message action button
+ var forumLogin = _.string.sprintf('%s/web?redirect=%s',
+ window.location.origin,
+ escape(window.location.href)
+ );
+ $('.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({
+ tags: true,
+ tokenSeparators: [',', ' ', '_'],
+ maximumInputLength: 35,
+ minimumInputLength: 2,
+ maximumSelectionSize: 5,
+ lastsearch: [],
+ createSearchChoice: function (term) {
+ if (_.filter(self.lastsearch, function (s) {
+ return s.text.localeCompare(term) === 0;
+ }).length === 0) {
+ //check Karma
+ if (parseInt($('#karma').val()) >= parseInt($('#karma_edit_retag').val())) {
+ return {
+ id: '_' + $.trim(term),
+ text: $.trim(term) + ' *',
+ isNew: true,
+ };
+ }
+ }
+ },
+ formatResult: function (term) {
+ if (term.isNew) {
+ return '<span class="badge badge-primary">New</span> ' + _.escape(term.text);
+ } else {
+ return _.escape(term.text);
+ }
+ },
+ ajax: {
+ url: '/forum/get_tags',
+ dataType: 'json',
+ data: function (term) {
+ return {
+ query: term,
+ limit: 50,
+ };
+ },
+ results: function (data) {
+ var ret = [];
+ _.each(data, function (x) {
+ ret.push({
+ id: x.id,
+ text: x.name,
+ isNew: false,
+ });
+ });
+ self.lastsearch = ret;
+ return {results: ret};
+ }
+ },
+ // Take default tags from the input value
+ initSelection: function (element, callback) {
+ var data = [];
+ _.each(element.data('init-value'), function (x) {
+ data.push({id: x.id, text: x.name, isNew: false});
+ });
+ element.val('');
+ callback(data);
+ },
+ });
+
+ _.each($('textarea.o_wysiwyg_loader'), function (textarea) {
+ var $textarea = $(textarea);
+ var editorKarma = $textarea.data('karma') || 0; // default value for backward compatibility
+ var $form = $textarea.closest('form');
+ var hasFullEdit = parseInt($("#karma").val()) >= editorKarma;
+ // Warning: Do not activate any option that adds inline style.
+ // Because the style is deleted after save.
+ var toolbar = [
+ ['style', ['style']],
+ ['font', ['bold', 'italic', 'underline', 'clear']],
+ ['para', ['ul', 'ol', 'paragraph']],
+ ['table', ['table']],
+ ];
+ if (hasFullEdit) {
+ toolbar.push(['insert', ['link', 'picture']]);
+ }
+ toolbar.push(['history', ['undo', 'redo']]);
+
+ var options = {
+ height: 200,
+ minHeight: 80,
+ toolbar: toolbar,
+ styleWithSpan: false,
+ styleTags: _.without(weDefaultOptions.styleTags, 'h1', 'h2', 'h3'),
+ recordInfo: {
+ context: self._getContext(),
+ res_model: 'forum.post',
+ res_id: +window.location.pathname.split('-').pop(),
+ },
+ disableFullMediaDialog: true,
+ disableResizeImage: true,
+ };
+ if (!hasFullEdit) {
+ options.plugins = {
+ LinkPlugin: false,
+ MediaPlugin: false,
+ };
+ }
+ wysiwygLoader.load(self, $textarea[0], options).then(wysiwyg => {
+ // float-left class messes up the post layout OPW 769721
+ $form.find('.note-editable').find('img.float-left').removeClass('float-left');
+ // o_we_selected_image has not always been removed when
+ // saving a post so we need the line below to remove it if it is present.
+ $form.find('.note-editable').find('img.o_we_selected_image').removeClass('o_we_selected_image');
+ $form.on('click', 'button, .a-submit', () => {
+ $form.find('.note-editable').find('img.o_we_selected_image').removeClass('o_we_selected_image');
+ wysiwyg.save();
+ });
+ });
+ });
+
+ _.each(this.$('.o_wforum_bio_popover'), authorBox => {
+ $(authorBox).popover({
+ trigger: 'hover',
+ offset: 10,
+ animation: false,
+ html: true,
+ }).popover('hide').data('bs.popover').tip.classList.add('o_wforum_bio_popover_container');
+ });
+
+ this.$('#post_reply').on('shown.bs.collapse', function (e) {
+ const replyEl = document.querySelector('#post_reply');
+ const scrollingElement = dom.closestScrollable(replyEl.parentNode);
+ dom.scrollTo(replyEl, {
+ forcedOffset: $(scrollingElement).innerHeight() - $(replyEl).innerHeight(),
+ });
+ });
+
+ return this._super.apply(this, arguments);
+ },
+
+ //--------------------------------------------------------------------------
+ // 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] && $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
+ */
+ _onKarmaRequiredClick: function (ev) {
+ var $karma = $(ev.currentTarget);
+ var karma = $karma.data('karma');
+ var forum_id = $('#wrapwrap').data('forum_id');
+ if (!karma) {
+ return;
+ }
+ ev.preventDefault();
+ var msg = karma + ' ' + _t("karma is required to perform this action. ");
+ var title = _t("Karma Error");
+ if (forum_id) {
+ msg += '<a class="alert-link" href="/forum/' + forum_id + '/faq">' + _t("Read the guidelines to know how to gain karma.") + '</a>';
+ }
+ if (session.is_website_user) {
+ msg = _t("Sorry you must be logged in to perform this action");
+ title = _t("Access Denied");
+ }
+ this.call('crash_manager', 'show_warning', {
+ message: msg,
+ title: title,
+ }, {
+ sticky: false,
+ });
+ },
+ /**
+ * @private
+ * @param {Event} ev
+ */
+ _onTagFollowBoxMouseEnter: function (ev) {
+ $(ev.currentTarget).find('.o_forum_tag_follow_box').stop().fadeIn().css('display', 'block');
+ },
+ /**
+ * @private
+ * @param {Event} ev
+ */
+ _onTagFollowBoxMouseLeave: function (ev) {
+ $(ev.currentTarget).find('.o_forum_tag_follow_box').stop().fadeOut().css('display', 'none');
+ },
+ /**
+ * @private
+ * @param {Event} ev
+ */
+ _onUserInfoMouseEnter: function (ev) {
+ $(ev.currentTarget).parent().find('.o_forum_user_bio_expand').delay(500).toggle('fast');
+ },
+ /**
+ * @private
+ * @param {Event} ev
+ */
+ _onUserInfoMouseLeave: function (ev) {
+ $(ev.currentTarget).parent().find('.o_forum_user_bio_expand').clearQueue();
+ },
+ /**
+ * @private
+ * @param {Event} ev
+ */
+ _onUserBioExpandMouseLeave: function (ev) {
+ $(ev.currentTarget).fadeOut('fast');
+ },
+ /**
+ * @private
+ * @param {Event} ev
+ */
+ _onFlagAlertClick: function (ev) {
+ var self = this;
+ ev.preventDefault();
+ var $link = $(ev.currentTarget);
+ this._rpc({
+ route: $link.data('href') || ($link.attr('href') !== '#' && $link.attr('href')) || $link.closest('form').attr('action'),
+ }).then(function (data) {
+ if (data.error) {
+ var message;
+ if (data.error === 'anonymous_user') {
+ message = _t("Sorry you must be logged to flag a post");
+ } else if (data.error === 'post_already_flagged') {
+ message = _t("This post is already flagged");
+ } else if (data.error === 'post_non_flaggable') {
+ message = _t("This post can not be flagged");
+ }
+ self.call('crash_manager', 'show_warning', {
+ message: message,
+ title: _t("Access Denied"),
+ }, {
+ sticky: false,
+ });
+ } else if (data.success) {
+ var elem = $link;
+ if (data.success === 'post_flagged_moderator') {
+ elem.data('href') && elem.html(' Flagged');
+ var c = parseInt($('#count_flagged_posts').html(), 10);
+ c++;
+ $('#count_flagged_posts').html(c);
+ } else if (data.success === 'post_flagged_non_moderator') {
+ elem.data('href') && elem.html(' Flagged');
+ var forumAnswer = elem.closest('.forum_answer');
+ forumAnswer.fadeIn(1000);
+ forumAnswer.slideUp(1000);
+ }
+ }
+ });
+ },
+ /**
+ * @private
+ * @param {Event} ev
+ */
+ _onVotePostClick: function (ev) {
+ var self = this;
+ ev.preventDefault();
+ var $btn = $(ev.currentTarget);
+ this._rpc({
+ route: $btn.data('href'),
+ }).then(function (data) {
+ if (data.error) {
+ var message;
+ 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');
+ }
+ self.call('crash_manager', 'show_warning', {
+ message: message,
+ title: _t("Access Denied"),
+ }, {
+ sticky: false,
+ });
+ } else {
+ var $container = $btn.closest('.vote');
+ var $items = $container.children();
+ var $voteUp = $items.filter('.vote_up');
+ var $voteDown = $items.filter('.vote_down');
+ var $voteCount = $items.filter('.vote_count');
+ var userVote = parseInt(data['user_vote']);
+
+ $voteUp.prop('disabled', userVote === 1);
+ $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');
+ $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');
+ }
+ });
+ },
+ /**
+ * @private
+ * @param {Event} ev
+ */
+ _onValidationQueueClick: 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();
+ });
+ },
+ /**
+ * @private
+ * @param {Event} ev
+ */
+ _onAcceptAnswerClick: function (ev) {
+ ev.preventDefault();
+ var $link = $(ev.currentTarget);
+ var 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.call('crash_manager', 'show_warning', {
+ message: message,
+ title: _t("Access Denied"),
+ }, {
+ sticky: false,
+ });
+ } else {
+ _.each(this.$('.forum_answer'), 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-original-title', newHelper)
+ .tooltip({delay: 0});
+ });
+ }
+ });
+ },
+ /**
+ * @private
+ * @param {Event} ev
+ */
+ _onFavoriteQuestionClick: function (ev) {
+ ev.preventDefault();
+ var $link = $(ev.currentTarget);
+ this._rpc({
+ route: $link.data('href'),
+ }).then(function (data) {
+ $link.toggleClass('o_wforum_gold fa-star', data)
+ .toggleClass('fa-star-o text-muted', !data);
+ });
+ },
+ /**
+ * @private
+ * @param {Event} ev
+ */
+ _onDeleteCommentClick: function (ev) {
+ ev.preventDefault();
+ var $link = $(ev.currentTarget);
+ var $container = $link.closest('.o_wforum_post_comments_container');
+
+ this._rpc({
+ route: $link.closest('form').attr('action'),
+ }).then(function () {
+ $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();
+ }
+ });
+ },
+ /**
+ * @private
+ * @param {Event} ev
+ */
+ _onCloseIntroClick: function (ev) {
+ ev.preventDefault();
+ document.cookie = 'forum_welcome_message = false';
+ $('.forum_intro').slideUp();
+ return true;
+ },
+});
+
+publicWidget.registry.websiteForumSpam = publicWidget.Widget.extend({
+ selector: '.o_wforum_moderation_queue',
+ xmlDependencies: ['/website_forum/static/src/xml/website_forum_share_templates.xml'],
+ events: {
+ 'click .o_wforum_select_all_spam': '_onSelectallSpamClick',
+ 'click .o_wforum_mark_spam': 'async _onMarkSpamClick',
+ 'input #spamSearch': '_onSpamSearchInput',
+ },
+
+ /**
+ * @override
+ */
+ start: function () {
+ this.spamIDs = this.$('.modal').data('spam-ids');
+ return this._super.apply(this, arguments);
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ * @param {Event} ev
+ */
+ _onSelectallSpamClick: function (ev) {
+ var $spamInput = this.$('.modal .tab-pane.active input');
+ $spamInput.prop('checked', true);
+ },
+
+ /**
+ * @private
+ * @param {Event} ev
+ */
+ _onSpamSearchInput: function (ev) {
+ var self = this;
+ var toSearch = $(ev.currentTarget).val();
+ return this._rpc({
+ model: 'forum.post',
+ method: 'search_read',
+ args: [
+ [['id', 'in', self.spamIDs],
+ '|',
+ ['name', 'ilike', toSearch],
+ ['content', 'ilike', toSearch]],
+ ['name', 'content']
+ ],
+ kwargs: {}
+ }).then(function (o) {
+ _.each(o, function (r) {
+ r.content = $('<p>' + $(r.content).html() + '</p>').text().substring(0, 250);
+ });
+ self.$('div.post_spam').html(qweb.render('website_forum.spam_search_name', {
+ posts: o,
+ }));
+ });
+ },
+
+ /**
+ * @private
+ * @param {Event} ev
+ */
+ _onMarkSpamClick: function (ev) {
+ var key = this.$('.modal .tab-pane.active').data('key');
+ var $inputs = this.$('.modal .tab-pane.active input.custom-control-input:checked');
+ var values = _.map($inputs, function (o) {
+ return parseInt(o.value);
+ });
+ return this._rpc({model: 'forum.post',
+ method: 'mark_as_offensive_batch',
+ args: [this.spamIDs, key, values],
+ }).then(function () {
+ window.location.reload();
+ });
+ },
+});
+
+});
diff --git a/addons/website_forum/static/src/js/website_forum.share.js b/addons/website_forum/static/src/js/website_forum.share.js
new file mode 100644
index 00000000..68a2b8f3
--- /dev/null
+++ b/addons/website_forum/static/src/js/website_forum.share.js
@@ -0,0 +1,99 @@
+odoo.define('website_forum.share', function (require) {
+'use strict';
+
+var core = require('web.core');
+var publicWidget = require('web.public.widget');
+
+var qweb = core.qweb;
+
+// FIXME There is no reason to inherit from socialShare here
+var ForumShare = publicWidget.registry.socialShare.extend({
+ selector: '',
+ xmlDependencies: publicWidget.registry.socialShare.prototype.xmlDependencies
+ .concat(['/website_forum/static/src/xml/website_forum_share_templates.xml']),
+ events: {},
+
+ /**
+ * @override
+ * @param {Object} parent
+ * @param {Object} options
+ * @param {string} targetType
+ */
+ init: function (parent, options, targetType) {
+ this._super.apply(this, arguments);
+ this.targetType = targetType;
+ },
+ /**
+ * @override
+ */
+ start: function () {
+ var def = this._super.apply(this, arguments);
+ this._onMouseEnter();
+ return def;
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ */
+ _bindSocialEvent: function () {
+ this._super.apply(this, arguments);
+ $('.oe_share_bump').click($.proxy(this._postBump, this));
+ },
+ /**
+ * @private
+ */
+ _render: function () {
+ var $question = this.$('article.question');
+ if (!this.targetType) {
+ this._super.apply(this, arguments);
+ } else if (this.targetType === 'social-alert') {
+ $question.before(qweb.render('website.social_alert', {medias: this.socialList}));
+ } else {
+ $('body').append(qweb.render('website.social_modal', {
+ medias: this.socialList,
+ target_type: this.targetType,
+ state: $question.data('state'),
+ }));
+ $('#oe_social_share_modal').modal('show');
+ }
+ },
+ /**
+ * @private
+ */
+ _postBump: function () {
+ this._rpc({ // FIXME
+ route: '/forum/post/bump',
+ params: {
+ post_id: this.element.data('id'),
+ },
+ });
+ },
+});
+
+publicWidget.registry.websiteForumShare = publicWidget.Widget.extend({
+ selector: '.website_forum',
+
+ /**
+ * @override
+ */
+ start: function () {
+ // Retrieve stored social data
+ if (sessionStorage.getItem('social_share')) {
+ var socialData = JSON.parse(sessionStorage.getItem('social_share'));
+ (new ForumShare(this, false, socialData.targetType)).attachTo($(document.body));
+ sessionStorage.removeItem('social_share');
+ }
+ // Display an alert if post has no reply and is older than 10 days
+ var $questionContainer = $('.oe_js_bump');
+ if ($questionContainer.length) {
+ new ForumShare(this, false, 'social-alert').attachTo($questionContainer);
+ }
+
+ return this._super.apply(this, arguments);
+ },
+});
+});
diff --git a/addons/website_forum/static/src/scss/website_forum.scss b/addons/website_forum/static/src/scss/website_forum.scss
new file mode 100644
index 00000000..12ad1ac3
--- /dev/null
+++ b/addons/website_forum/static/src/scss/website_forum.scss
@@ -0,0 +1,272 @@
+$gold: #eca801;
+$silver: #cccccc;
+$bronze: #eea91e;
+
+.website_forum {
+ .o_forum_ask_btn {
+ @include media-breakpoint-up(md) {
+ box-shadow: $box-shadow;
+ width: 200px;
+ }
+ }
+
+ .o_wforum_nav .nav-link {
+ @include o-hover-text-color($body-color, $link-color);
+ line-height: 1;
+
+ .fa {
+ opacity: 0.5;
+ }
+
+ &:hover, &.active {
+ .fa {
+ opacity: 1;
+ }
+ }
+
+ &.active {
+ background-color: rgba(theme-color('info'), 0.1);
+ color: darken(theme-color('info'), 15%)!important;
+ }
+
+ img.o_forum_avatar {
+ @include size(30px);
+ }
+ }
+
+ // 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 {
+ position: relative;
+ padding-left: 1em;
+ border-left: .25em solid gray('500');
+ color: gray('600');
+ }
+
+ #post_reply {
+ img.o_forum_avatar {
+ @include size(24px);
+ }
+ }
+ }
+
+ .o_wforum_readable {
+ max-width: 700px;
+
+ p {
+ margin-bottom: 0.5rem;
+ }
+
+ img {
+ max-width: 100%;
+ }
+
+ &::after {
+ content: "";
+ display: table;
+ clear: both;
+ }
+ }
+
+ 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;
+ }
+
+ .o_wforum_author_box_check {
+ @include size(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(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;
+ }
+
+ .o_wforum_validate_toggler {
+ @include o-hover-text-color(theme-color('success'), theme-color('warning'));
+
+ &:hover .fa.fa-check:before {
+ content: '\f00d';
+ }
+ }
+
+ .o_wforum_answer_header .o_wforum_author_pic {
+ border: 2px solid $success;
+ padding: 3px;
+ }
+ }
+ }
+
+ .o_wforum_gold {
+ color: $gold;
+ }
+
+ a.no-decoration {
+ cursor: pointer;
+ text-decoration: none !important;
+ }
+}
+
+.website_forum, .o_wforum_profile_tab {
+ .vote {
+ .vote_count {
+ line-height: 1;
+ animation-play-state: paused;
+
+ &.o_forum_vote_animate {
+ animation: bounceIn 0.3s ease running;
+ }
+ }
+
+ .vote_down, .vote_up {
+ line-height: 0.5;
+ 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;
+ }
+ }
+ }
+}
+
+.o_js_forum_tag_follow {
+ .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 {
+ @include size(40px);
+ object-fit: cover;
+}
+
+img.o_forum_avatar_big {
+ @include size(75px);
+ object-fit: cover;
+}
+
+.o_wprofile_email_validation_container {
+ img.o_forum_avatar {
+ @include size(16px);
+ }
+}
+
+.o_wforum_bio_popover_wrap {
+ .o_wforum_bio_popover_name {
+ address > div, span[data-oe-model="res.country"] {
+ display: flex;
+ align-items: center;
+ }
+ span[data-oe-model="res.country"] {
+ margin-left: 10px;
+ }
+ }
+
+ .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: theme-color('secondary');
+ }
+}
+
+.website_forum {
+ margin-bottom: $spacer;
+}
+
+.popover.o_wforum_bio_popover_container {
+ max-width: 552px;
+}
+
+.o_wforum_forum_card_bg {
+ background-image: linear-gradient(99deg, theme-color('secondary') 10%, darken(theme-color('secondary'), 10%) 90%);
+
+ #o_wforum_forums_index_list & {
+ min-height: 100px;
+ }
+}
diff --git a/addons/website_forum/static/src/xml/website_forum_share_templates.xml b/addons/website_forum/static/src/xml/website_forum_share_templates.xml
new file mode 100644
index 00000000..e1ef8d75
--- /dev/null
+++ b/addons/website_forum/static/src/xml/website_forum_share_templates.xml
@@ -0,0 +1,75 @@
+<templates id="template" xml:space="preserve">
+ <t t-name="website.social_modal">
+ <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">
+ <h4 class="modal-title">Thanks for posting!</h4>
+ <button type="button" class="close" data-dismiss="modal">
+ <span role="img" aria-label="Close">x</span>
+ </button>
+ </header>
+ <main class="modal-body">
+ <t t-if="target_type == 'question'" t-call="website_forum.social_message_question"/>
+ <t t-if="target_type == 'answer'" t-call="website_forum.social_message_answer"/>
+ <t t-if="target_type == 'default'" t-call="website_forum.social_message_default"/>
+ <div t-if="state != 'pending'" class="share-icons text-center text-primary">
+ <t t-foreach="medias" t-as="media">
+ <a style="cursor: pointer" t-attf-class="fa-stack fa-lg share #{media}" t-attf-aria-label="Share on #{media}" t-attf-title="Share on #{media}">
+ <span class="fa fa-square fa-stack-2x"></span>
+ <span t-attf-class="oe_social_#{media} fa fa-#{media} fa-stack-1x fa-inverse"></span>
+ </a>
+ </t>
+ </div>
+ </main>
+ </div>
+ </div>
+ </div>
+ </t>
+ <t t-name="website_forum.spam_search_name">
+ <t t-foreach="posts" t-as="post">
+ <div class="card mb-1 o_spam_character">
+ <div class="card-body py-2">
+ <div class="custom-control custom-checkbox">
+ <input type="checkbox" class="custom-control-input" t-attf-id="post_#{post.id}" t-att-value='post.id' checked='checked'/>
+ <label class="custom-control-label" t-attf-for="post_#{post.id}">
+ <b><t t-esc="post.name" /></b>
+ <p class='text-muted'><t t-esc="post.content" /></p>
+ </label>
+ </div>
+ </div>
+ </div>
+ </t>
+ </t>
+ <t t-name="website_forum.social_message_question">
+ <p>On average, <b>45% of questions shared</b> on social networks get an answer within
+ 5 hours. Questions shared on two social networks have <b>65% more chance to get an
+ answer</b> !</p>
+ <p t-if="state == 'pending'">You can share your question once it has been validated</p>
+ </t>
+ <t t-name="website_forum.social_message_answer">
+ <p>By sharing you answer, you will get additional <b>karma points</b> if your
+ answer is selected as the right one. See what you can do with karma
+ <a href="/forum/help-1/faq" target="_blank">here</a>.</p>
+ </t>
+ <t t-name="website_forum.social_message_default">
+ <p>Share this content to increase your chances to be featured on the front page and attract more visitors.</p>
+ </t>
+
+ <t t-name="website.social_alert">
+ <div class="alert alert-info alert-dismissable" role="status">
+ <button type="button" class="close" data-dismiss="alert">
+ <span role="img" aria-label="Close">&#215;</span>
+ </button>
+ <p>Move this question to the top of the list by sharing it on social networks.</p><br/>
+ <div>
+ <t t-foreach="medias" t-as="media">
+ <a style="cursor: pointer" t-attf-class="fa-stack fa-lg share oe_share_bump #{media}" t-attf-aria-label="Share on #{media}" t-attf-title="Share on #{media}">
+ <span class="fa fa-square fa-stack-2x"></span>
+ <span t-attf-class="oe_social_#{media} fa fa-#{media} fa-stack-1x fa-inverse"></span>
+ </a>
+ </t>
+ </div>
+ </div>
+ </t>
+</templates>
diff --git a/addons/website_forum/static/src/xml/website_forum_templates.xml b/addons/website_forum/static/src/xml/website_forum_templates.xml
new file mode 100644
index 00000000..aedc6223
--- /dev/null
+++ b/addons/website_forum/static/src/xml/website_forum_templates.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates xml:space="preserve">
+ <t t-name="website_forum.add_new_forum">
+ <form id="editor_new_forum">
+ <div class="form-group row">
+ <label for="page-name" class="col-md-4 col-form-label">Forum Name</label>
+ <div class="col-md-8">
+ <input type="text" name="forum_name" class="form-control" required="required"/>
+ <div class="custom-control custom-checkbox mt-2">
+ <input type="checkbox" class="custom-control-input" id="add_to_menu" required="required"/>
+ <label class="custom-control-label" for="add_to_menu">Add to menu</label>
+ </div>
+ </div>
+ </div>
+ <div class="form-group row mt-2">
+ <label class="col-md-4 col-form-label" data-toggle="tooltip" data-placement="bottom"
+ title="Questions and Answers mode: only one answer allowed\n Discussions mode: multiple answers allowed">Forum Mode</label>
+ <div class="col-md-8">
+ <div class="custom-control custom-radio">
+ <input type="radio" id="questions" name="mode" class="custom-control-input" value="questions" checked="checked"/>
+ <label class="custom-control-label" for="questions">Questions and Answers</label>
+ </div>
+ <div class="custom-control custom-radio">
+ <input type="radio" id="discussions" name="mode" class="custom-control-input" value="discussions"/>
+ <label class="custom-control-label" for="discussions">Discussions</label>
+ </div>
+ </div>
+ </div>
+ <div class="form-group row mt-2">
+ <label class="col-md-4 col-form-label" data-toggle="tooltip" data-placement="bottom"
+ title="Public: Forum is public\nSigned In: Forum is visible for signed in users\nSome users: Forum and their content are hidden for non members of selected group">Privacy</label>
+ <div class="col-md-8">
+ <div class="custom-control custom-radio">
+ <input type="radio" id="public" name="privacy" class="custom-control-input" value="public" checked="checked"/>
+ <label class="custom-control-label" for="public">Public</label>
+ </div>
+ <div class="custom-control custom-radio">
+ <input type="radio" id="connected" name="privacy" class="custom-control-input" value="connected"/>
+ <label class="custom-control-label" for="connected">Signed In</label>
+ </div>
+ <div class="custom-control custom-radio">
+ <input type="radio" id="private" name="privacy" class="custom-control-input" value="private"/>
+ <label class="custom-control-label" for="private">Some Users</label>
+ </div>
+ <div class="form-group show_visibility_group d-none">
+ <input type="text" class="form-control" name="group_id" id="group_id" placeholder="Select Authorized Group"/>
+ <p id="group-required" class="text-danger mt-1 mb-0 d-none">Please fill in this field</p>
+ </div>
+ </div>
+ </div>
+ </form>
+ </t>
+</templates>