From 4ed03d5940bb9a76b2c250f56169facf7b06b1dc Mon Sep 17 00:00:00 2001 From: abd-msyukyu-odoo Date: Wed, 25 May 2022 10:54:54 +0000 Subject: [PATCH] [IMP] knowledge: reorder functions alphabetically Small reorganisation of the code to order functions alphabetically in knowledge_controller and knowledge_renderer Task-2858428 X-original-commit: 874dc4a93af72c706ff756b29af96f7030a3423a Part-of: odoo/odoo#93088 --- .../static/src/js/knowledge_controller.js | 342 ++++++++-------- .../static/src/js/knowledge_renderers.js | 379 +++++++++--------- 2 files changed, 340 insertions(+), 381 deletions(-) diff --git a/addons/knowledge/static/src/js/knowledge_controller.js b/addons/knowledge/static/src/js/knowledge_controller.js index 3a4b31ee022..4d3132f1664 100644 --- a/addons/knowledge/static/src/js/knowledge_controller.js +++ b/addons/knowledge/static/src/js/knowledge_controller.js @@ -19,7 +19,6 @@ const KnowledgeArticleFormController = FormController.extend({ 'click i.o_toggle_favorite': '_onToggleFavorite', 'input .o_breadcrumb_article_name': '_adjustInputSize', }), - custom_events: Object.assign({}, FormController.prototype.custom_events, { create: '_onCreate', duplicate: '_onDuplicate', @@ -28,7 +27,6 @@ const KnowledgeArticleFormController = FormController.extend({ reload_tree: '_onReloadTree', emoji_click: '_onEmojiClick', }), - /** * Register the fact that the current @see FormController is one from * Knowledge in order not to try and search for a matching record for @@ -50,14 +48,28 @@ const KnowledgeArticleFormController = FormController.extend({ this._super.apply(this, arguments); this.onFieldSavedListeners = new Map(); }, - + /** + * @override + */ + saveRecord: async function () { + const modifiedFields = await this._super(...arguments); + const { data } = this.model.get(this.handle); + for (const field of modifiedFields) { + if (this.onFieldSavedListeners.has(field)) { + this.onFieldSavedListeners.get(field).forEach(listener => { + listener.call(this, data[field]); + }); + } + } + return modifiedFields; + }, /** * @override * @returns {Promise} */ start: function () { return this._super.apply(this, arguments).then(() => { - this.onFieldSaved('icon', unicode => { + this._onFieldSaved('icon', unicode => { const { id } = this.getState(); this.renderer._setEmoji(id, unicode); }); @@ -66,6 +78,18 @@ const KnowledgeArticleFormController = FormController.extend({ // Listeners: + /** + * @param {Event} event + */ + _adjustInputSize: function (event) { + event.target.setAttribute('size', event.target.value.length); + }, + _onAddCover: async function() { + if (this.mode === 'readonly') { + await this._setMode('edit'); + } + this.$('.o_input_file').click(); + }, _onAddRandomIcon: function() { this.trigger_up('field_changed', { dataPointID: this.handle, @@ -74,50 +98,13 @@ const KnowledgeArticleFormController = FormController.extend({ } }); }, - - _onAddCover: async function() { - if (this.mode === 'readonly') { - await this._setMode('edit'); - } - this.$('.o_input_file').click(); - }, - - /** - * When the user clicks on a field in readonly mode, a new 'quick_edit' event - * will be triggered. To prevent the view from switching to the edit mode when - * the article is locked, we will overwrite the `_onQuickEdit` handler. This - * function will now ignore the event if the article is locked. - * @override - */ - _onQuickEdit: function () { - const { data } = this.model.get(this.handle); - if (data.is_locked) { - return; - } - this._super.apply(this, arguments); - }, - - /** - * Callback function called when the user renames the active article. - * The function will update the name of the articles in the aside block. - * @param {Event} event - */ - _onRename: async function (event) { - var name = event.currentTarget.value; - if (name.length === 0) { - name = _t('New Article'); - } - const id = await this._getId(); - await this._rename(id, name); - }, - /** * When the user clicks on the name of the article, checks if the article * name hasn't been set yet. If it hasn't, it will look for a title in the * body of the article and set it as the name of the article. * @param {Event} event */ - _onArticleBreadcrumbClick: async function (event) { + _onArticleBreadcrumbClick: async function (event) { const name = event.currentTarget.value; if (name === _t('New Article')) { const $articleTitle = this.$('.o_knowledge_editor h1'); @@ -133,14 +120,12 @@ const KnowledgeArticleFormController = FormController.extend({ } } }, - /** - * @param {Event} event + * @param {OdooEvent} event */ - _adjustInputSize: function (event) { - event.target.setAttribute('size', event.target.value.length); + _onCreate: async function (event) { + await this._create(event.data); }, - /** * @param {OdooEvent} event */ @@ -151,21 +136,78 @@ const KnowledgeArticleFormController = FormController.extend({ additional_context: { res_id } }); }, - /** - * @param {OdooEvent} event + * @param {Event} event */ - _onCreate: async function (event) { - await this._create(event.data); + _onEmojiClick: async function (event) { + const { id } = this.getState(); + const { articleId, unicode } = event.data; + if (articleId === id) { + this.trigger_up('field_changed', { + dataPointID: this.handle, + changes: { + icon: unicode + } + }); + } else { + const result = await this._rpc({ + model: 'knowledge.article', + method: 'write', + args: [[articleId], { icon: unicode }], + }); + if (result) { + this.renderer._setEmoji(articleId, unicode); + } + } + }, + /** + * @override + * @param {Event} event + */ + _onFieldChanged: async function (event) { + this._super(...arguments); + const { changes } = event.data; + for (const field of this._getFieldsToForceSave()) { + if (changes.hasOwnProperty(field)) { + await this.saveRecord(this.handle, { + reload: false, + stayInEdit: true + }); + return; + } + } + }, + /** + * @param {String} name - field name + * @param {Function} callback + */ + _onFieldSaved: function (name, callback) { + if (this.onFieldSavedListeners.has(name)) { + this.onFieldSavedListeners.get(name).push(callback); + } else { + this.onFieldSavedListeners.set(name, [callback]); + } + }, + /** + * When the user clicks on a field in readonly mode, a new 'quick_edit' event + * will be triggered. To prevent the view from switching to the edit mode when + * the article is locked, we will overwrite the `_onQuickEdit` handler. This + * function will now ignore the event if the article is locked. + * @override + */ + _onQuickEdit: function () { + const { data } = this.model.get(this.handle); + if (data.is_locked) { + return; + } + this._super.apply(this, arguments); }, - /** * @param {Event} event */ _onMove: function (event) { this._confirmMove(event.data); }, - /** * Opens the "Move To" modal * @param {OdooEvent} event @@ -197,7 +239,6 @@ const KnowledgeArticleFormController = FormController.extend({ }); dialog.open(); }, - /** * @param {Event} event */ @@ -205,7 +246,19 @@ const KnowledgeArticleFormController = FormController.extend({ // TODO JBN: Create a widget for the tree and reload it without reloading the whole view. this.reload(); }, - + /** + * Callback function called when the user renames the active article. + * The function will update the name of the articles in the aside block. + * @param {Event} event + */ + _onRename: async function (event) { + var name = event.currentTarget.value; + if (name.length === 0) { + name = _t('New Article'); + } + const id = await this._getId(); + await this._rename(id, name); + }, /** * @param {Event} event */ @@ -215,7 +268,6 @@ const KnowledgeArticleFormController = FormController.extend({ searchValue: "?", }); }, - /** * @param {Event} event */ @@ -237,69 +289,10 @@ const KnowledgeArticleFormController = FormController.extend({ this.renderer._setTreeFavoriteListener(); this.renderer._renderEmojiPicker($dom); }); - }, - - /** - * @param {Event} event - */ - _onEmojiClick: async function (event) { - const { id } = this.getState(); - const { articleId, unicode } = event.data; - if (articleId === id) { - this.trigger_up('field_changed', { - dataPointID: this.handle, - changes: { - icon: unicode - } - }); - } else { - const result = await this._rpc({ - model: 'knowledge.article', - method: 'write', - args: [[articleId], { icon: unicode }], - }); - if (result) { - this.renderer._setEmoji(articleId, unicode); - } - } }, + // API calls: - /** - * @param {Object} data - * @param {String} data.category - * @param {integer} data.target_parent_id - */ - _create: async function (data) { - const articleId = await this._rpc({ - model: 'knowledge.article', - method: 'article_create', - args: [[]], - kwargs: { - is_private: data.category === 'private', - parent_id: data.target_parent_id ? data.target_parent_id : false - }, - }); - if (!articleId) { - return; - } - this.do_action('knowledge.ir_actions_server_knowledge_home_page', { - stackPosition: 'replaceCurrentAction', - additional_context: { - res_id: articleId - } - }); - }, - - /** - * @param {integer} id - Target id - * @param {string} name - Target Name - */ - _rename: async function (id, name) { - this.$(`.o_knowledge_tree .o_article[data-article-id="${id}"] > .o_article_handle > .o_article_name`).text(name); - this.$(`.o_breadcrumb_article_name`).val(name); - }, - /** * @param {Object} data * @param {integer} data.article_id @@ -348,7 +341,48 @@ const KnowledgeArticleFormController = FormController.extend({ }); } }, - + /** + * @param {Object} data + * @param {String} data.category + * @param {integer} data.target_parent_id + */ + _create: async function (data) { + const articleId = await this._rpc({ + model: 'knowledge.article', + method: 'article_create', + args: [[]], + kwargs: { + is_private: data.category === 'private', + parent_id: data.target_parent_id ? data.target_parent_id : false + }, + }); + if (!articleId) { + return; + } + this.do_action('knowledge.ir_actions_server_knowledge_home_page', { + stackPosition: 'replaceCurrentAction', + additional_context: { + res_id: articleId + } + }); + }, + /** + * @returns {Array[String]} + */ + _getFieldsToForceSave: function () { + return ['full_width', 'icon', 'cover']; + }, + /** + * @returns {integer} + */ + _getId: async function () { + let state = this.getState(); + if (typeof state.id === 'undefined') { + await this.saveRecord(this.handle); + state = this.getState(); + } + return state.id; + }, /** * @param {Object} data * @param {integer} data.article_id @@ -373,7 +407,14 @@ const KnowledgeArticleFormController = FormController.extend({ data.onReject(); }) }, - + /** + * @param {integer} id - Target id + * @param {string} name - Target Name + */ + _rename: async function (id, name) { + this.$(`.o_knowledge_tree .o_article[data-article-id="${id}"] > .o_article_handle > .o_article_name`).text(name); + this.$(`.o_breadcrumb_article_name`).val(name); + }, /** * @param {integer} id - article id * @returns {Promise} @@ -385,71 +426,6 @@ const KnowledgeArticleFormController = FormController.extend({ args: [id] }); }, - - /** - * @returns {Array[String]} - */ - _getFieldsToForceSave: function () { - return ['full_width', 'icon', 'cover']; - }, - - /** - * @override - * @param {Event} event - */ - _onFieldChanged: async function (event) { - this._super(...arguments); - const { changes } = event.data; - for (const field of this._getFieldsToForceSave()) { - if (changes.hasOwnProperty(field)) { - await this.saveRecord(this.handle, { - reload: false, - stayInEdit: true - }); - return; - } - } - }, - - /** - * @override - */ - saveRecord: async function () { - const modifiedFields = await this._super(...arguments); - const { data } = this.model.get(this.handle); - for (const field of modifiedFields) { - if (this.onFieldSavedListeners.has(field)) { - this.onFieldSavedListeners.get(field).forEach(listener => { - listener.call(this, data[field]); - }); - } - } - return modifiedFields; - }, - - /** - * @param {String} name - field name - * @param {Function} callback - */ - onFieldSaved: function (name, callback) { - if (this.onFieldSavedListeners.has(name)) { - this.onFieldSavedListeners.get(name).push(callback); - } else { - this.onFieldSavedListeners.set(name, [callback]); - } - }, - - /** - * @returns {integer} - */ - _getId: async function () { - let state = this.getState(); - if (typeof state.id === 'undefined') { - await this.saveRecord(this.handle); - state = this.getState(); - } - return state.id; - }, }); export { diff --git a/addons/knowledge/static/src/js/knowledge_renderers.js b/addons/knowledge/static/src/js/knowledge_renderers.js index 716477e42b8..ea7d987ca38 100644 --- a/addons/knowledge/static/src/js/knowledge_renderers.js +++ b/addons/knowledge/static/src/js/knowledge_renderers.js @@ -22,7 +22,6 @@ const KnowledgeArticleFormRenderer = FormRenderer.extend(KnowledgeTreePanelMixin 'click .o_knowledge_share_panel': '_preventDropdownClose', 'click .o_knowledge_more_options_panel': '_preventDropdownClose', }), - /** * @override */ @@ -30,7 +29,186 @@ const KnowledgeArticleFormRenderer = FormRenderer.extend(KnowledgeTreePanelMixin this._super(...arguments); this.breadcrumbs = params.breadcrumbs; }, - + /** + * @param {Event} event + */ + _onBreadcrumbItemClick: function (event) { + const $target = $(event.target); + this.trigger_up('breadcrumb_clicked', { + controllerID: $target.data('controller-id'), + }); + }, + /** + * Callback function called when the user clicks on the '+' sign of an article + * list item. The callback function will create a new article under the target article. + * @param {Event} event + */ + _onBtnArticleCreateClick: function (event) { + const $target = $(event.currentTarget); + const $li = $target.closest('li'); + this.trigger_up('create', { + target_parent_id: $li.data('article-id') + }); + }, + /** + * @param {Event} event + */ + _onBtnChatterClick: function (event) { + const $chatter = $('.o_knowledge_chatter'); + $chatter.toggleClass('d-none'); + $('.btn-chatter').toggleClass('active'); + }, + /** + * @param {Event} event + */ + _onBtnCreateClick: function (event) { + this.trigger_up('create', { + category: 'private' + }); + }, + /** + * @param {Event} event + */ + _onBtnDuplicateClick: function (event) { + event.preventDefault(); + this.trigger_up('duplicate', {}); + }, + /** + * @param {Event} event + */ + _onBtnMoveClick: function (event) { + event.preventDefault(); + this.trigger_up('open_move_to_modal', {}); + }, + /** + * Callback function called when the user clicks on the '+' sign of a section + * (workspace, private, shared). The callback function will create a new article + * on the root of the target section. + * @param {Event} event + */ + _onBtnSectionCreateClick: function (event) { + const $target = $(event.currentTarget); + const $section = $target.closest('.o_section'); + this.trigger_up('create', { + category: $section.data('section') + }); + }, + /** + * Opens the selected record. + * @param {Event} event + */ + _onOpen: async function (event) { + event.stopPropagation(); + const $li = $(event.target).closest('li'); + this.do_action('knowledge.ir_actions_server_knowledge_home_page', { + stackPosition: 'replaceCurrentAction', + additional_context: { + res_id: $li.data('article-id') + } + }); + }, + /** + * By default, Bootstrap closes automatically the dropdown menu when the user + * clicks inside it. To avoid that behavior, we will add a new event listener + * on the dropdown menu that will prevent the click event from bubbling up and + * triggering the listener closing the dropdown menu. + * @param {Event} event + */ + _preventDropdownClose: function (event) { + event.stopPropagation(); + }, + _renderArticleEmoji: function () { + const { data } = this.state; + const $dropdown = this.$('.o_knowledge_icon > .o_article_emoji_dropdown'); + $dropdown.attr('data-article-id', this.state.res_id); + $dropdown.find('.o_article_emoji').text(data.icon || ''); + }, + /** + * Renders the breadcrumb + */ + _renderBreadcrumb: function () { + const items = this.breadcrumbs.map(payload => { + return QWeb.render('knowledge.knowledge_breadcrumb_item', { payload }); + }); + this.$('.breadcrumb').prepend(items); + }, + /** + * Attaches an emoji picker widget to every emoji dropdown of the container. + * Note: The widget will be attached to the view when the user clicks on + * the dropdown menu for the first time. + * @param {JQuery} [$container] + */ + _renderEmojiPicker: function ($container) { + $container = $container || this.$el; + $container.find('.o_article_emoji_dropdown').one('click', event => { + const $dropdown = $(event.currentTarget); + const picker = new EmojiPickerWidget(this, { + article_id: $dropdown.data('article-id') + }); + picker.attachTo($dropdown); + }); + }, + /** + * Renders the permission panel + */ + _renderPermissionPanel: function () { + this.$('.btn-share').one('click', event => { + const $container = this.$('.o_knowledge_permission_panel'); + const panel = new PermissionPanelWidget(this, { + article_id: this.state.data.id, + user_permission: this.state.data.user_permission + }); + panel.attachTo($container); + }); + }, + /** + * @override + * @returns {Promise} + */ + _renderView: async function () { + const result = await this._super.apply(this, arguments); + this._renderBreadcrumb(); + await this._renderTree(this.state.res_id, '/knowledge/tree_panel'); + this._renderArticleEmoji(); + this._renderPermissionPanel(); + this._setResizeListener(); + return result; + }, + /** + * @param {integer} id - Article id + * @param {String} unicode + */ + _setEmoji: function (id, emoji) { + const emojis = this.$(`.o_article_emoji_dropdown[data-article-id="${id}"] > .o_article_emoji`); + emojis.text(emoji || ''); + }, + /** + * Enables the user to resize the aside block. + * Note: When the user grabs the resizer, a new listener will be attached + * to the document. The listener will be removed as soon as the user releases + * the resizer to free some resources. + */ + _setResizeListener: function () { + /** + * @param {PointerEvent} event + */ + const onPointerMove = _.throttle(event => { + event.preventDefault(); + this.el.style.setProperty('--default-sidebar-size', `${event.pageX}px`); + }, 100); + /** + * @param {PointerEvent} event + */ + const onPointerUp = event => { + $(document).off('pointermove', onPointerMove); + }; + const $resizer = this.$('.o_knowledge_article_form_resizer'); + $resizer.on('pointerdown', event => { + event.preventDefault(); + $(document).on('pointermove', onPointerMove); + $(document).one('pointerup', onPointerUp); + }); + }, /** * Initializes the drag-and-drop behavior of the tree listing all articles. * Once this function is called, the user will be able to move an article @@ -121,6 +299,7 @@ const KnowledgeArticleFormRenderer = FormRenderer.extend(KnowledgeTreePanelMixin }); // Allow drag and drop between sections: + this.$('section[data-section="workspace"] .o_tree').nestedSortable( 'option', 'connectWith', @@ -138,202 +317,6 @@ const KnowledgeArticleFormRenderer = FormRenderer.extend(KnowledgeTreePanelMixin 'section[data-section="workspace"] .o_tree, section[data-section="private"] .o_tree' ); }, - - /** - * Callback function called when the user clicks on the '+' sign of a section - * (workspace, private, shared). The callback function will create a new article - * on the root of the target section. - * @param {Event} event - */ - _onBtnSectionCreateClick: function (event) { - const $target = $(event.currentTarget); - const $section = $target.closest('.o_section'); - this.trigger_up('create', { - category: $section.data('section') - }); - }, - - /** - * Callback function called when the user clicks on the '+' sign of an article - * list item. The callback function will create a new article under the target article. - * @param {Event} event - */ - _onBtnArticleCreateClick: function (event) { - const $target = $(event.currentTarget); - const $li = $target.closest('li'); - this.trigger_up('create', { - target_parent_id: $li.data('article-id') - }); - }, - - /** - * Opens the selected record. - * @param {Event} event - */ - _onOpen: async function (event) { - event.stopPropagation(); - const $li = $(event.target).closest('li'); - this.do_action('knowledge.ir_actions_server_knowledge_home_page', { - stackPosition: 'replaceCurrentAction', - additional_context: { - res_id: $li.data('article-id') - } - }); - }, - - _renderArticleEmoji: function () { - const { data } = this.state; - const $dropdown = this.$('.o_knowledge_icon > .o_article_emoji_dropdown'); - $dropdown.attr('data-article-id', this.state.res_id); - $dropdown.find('.o_article_emoji').text(data.icon || ''); - }, - - /** - * @override - * @returns {Promise} - */ - _renderView: async function () { - const result = await this._super.apply(this, arguments); - this._renderBreadcrumb(); - await this._renderTree(this.state.res_id, '/knowledge/tree_panel'); - this._renderArticleEmoji(); - this._renderPermissionPanel(); - this._setResizeListener(); - return result; - }, - - /** - * Renders the breadcrumb - */ - _renderBreadcrumb: function () { - const items = this.breadcrumbs.map(payload => { - return QWeb.render('knowledge.knowledge_breadcrumb_item', { payload }); - }); - this.$('.breadcrumb').prepend(items); - }, - - /** - * Attaches an emoji picker widget to every emoji dropdown of the container. - * Note: The widget will be attached to the view when the user clicks on - * the dropdown menu for the first time. - * @param {JQuery} [$container] - */ - _renderEmojiPicker: function ($container) { - $container = $container || this.$el; - $container.find('.o_article_emoji_dropdown').one('click', event => { - const $dropdown = $(event.currentTarget); - const picker = new EmojiPickerWidget(this, { - article_id: $dropdown.data('article-id') - }); - picker.attachTo($dropdown); - }); - }, - - /** - * Renders the permission panel - */ - _renderPermissionPanel: function () { - this.$('.btn-share').one('click', event => { - const $container = this.$('.o_knowledge_permission_panel'); - const panel = new PermissionPanelWidget(this, { - article_id: this.state.data.id, - user_permission: this.state.data.user_permission - }); - panel.attachTo($container); - }); - }, - - /** - * Enables the user to resize the aside block. - * Note: When the user grabs the resizer, a new listener will be attached - * to the document. The listener will be removed as soon as the user releases - * the resizer to free some resources. - */ - _setResizeListener: function () { - /** - * @param {PointerEvent} event - */ - const onPointerMove = _.throttle(event => { - event.preventDefault(); - this.el.style.setProperty('--default-sidebar-size', `${event.pageX}px`); - }, 100); - /** - * @param {PointerEvent} event - */ - const onPointerUp = event => { - $(document).off('pointermove', onPointerMove); - }; - const $resizer = this.$('.o_knowledge_article_form_resizer'); - $resizer.on('pointerdown', event => { - event.preventDefault(); - $(document).on('pointermove', onPointerMove); - $(document).one('pointerup', onPointerUp); - }); - }, - - /** - * @param {integer} id - Article id - * @param {String} unicode - */ - _setEmoji: function (id, emoji) { - const emojis = this.$(`.o_article_emoji_dropdown[data-article-id="${id}"] > .o_article_emoji`); - emojis.text(emoji || ''); - }, - - /** - * @param {Event} event - */ - _onBreadcrumbItemClick: function (event) { - const $target = $(event.target); - this.trigger_up('breadcrumb_clicked', { - controllerID: $target.data('controller-id'), - }); - }, - - /** - * @param {Event} event - */ - _onBtnChatterClick: function (event) { - const $chatter = $('.o_knowledge_chatter'); - $chatter.toggleClass('d-none'); - $('.btn-chatter').toggleClass('active'); - }, - - /** - * @param {Event} event - */ - _onBtnCreateClick: function (event) { - this.trigger_up('create', { - category: 'private' - }); - }, - - /** - * @param {Event} event - */ - _onBtnDuplicateClick: function (event) { - event.preventDefault(); - this.trigger_up('duplicate', {}); - }, - - /** - * @param {Event} event - */ - _onBtnMoveClick: function (event) { - event.preventDefault(); - this.trigger_up('open_move_to_modal', {}); - }, - - /** - * By default, Bootstrap closes automatically the dropdown menu when the user - * clicks inside it. To avoid that behavior, we will add a new event listener - * on the dropdown menu that will prevent the click event from bubbling up and - * triggering the listener closing the dropdown menu. - * @param {Event} event - */ - _preventDropdownClose: function (event) { - event.stopPropagation(); - }, }); export {