[IMP] mail: move the activity handler inside a model view
part of task-2579306 closes odoo/odoo#79538 Related: odoo/enterprise#22506 Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com> Co-authored-by: Sébastien Theys <seb@odoo.com>
This commit is contained in:
committed by
Sébastien Theys
co-authored by
Sébastien Theys
parent
c53ebf8244
commit
84adbcaffa
@@ -39,8 +39,7 @@ If you need to manage your meetings, you should install the CRM module.
|
||||
'auto_install': False,
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
'calendar/static/src/models/activity/activity.js',
|
||||
'calendar/static/src/components/activity/activity.js',
|
||||
'calendar/static/src/models/*/*.js',
|
||||
'calendar/static/src/scss/calendar.scss',
|
||||
'calendar/static/src/js/base_calendar.js',
|
||||
'calendar/static/src/js/calendar_renderer.js',
|
||||
@@ -55,7 +54,7 @@ If you need to manage your meetings, you should install the CRM module.
|
||||
],
|
||||
'web.assets_qweb': [
|
||||
'calendar/static/src/xml/base_calendar.xml',
|
||||
'calendar/static/src/components/activity/activity.xml',
|
||||
'calendar/static/src/components/*/*.xml',
|
||||
],
|
||||
},
|
||||
'license': 'LGPL-3',
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { Activity } from '@mail/components/activity/activity';
|
||||
import { patch } from 'web.utils';
|
||||
import Dialog from 'web.Dialog';
|
||||
import core from 'web.core';
|
||||
const _t = core._t;
|
||||
|
||||
patch(Activity.prototype, 'calendar/static/src/components/activity/activity.js', {
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Small override that asks for confirmation in case there is a meeting linked to this activity.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_onClickCancel(ev) {
|
||||
const superMethod = this._super;
|
||||
if (!this.activity.calendar_event_id){
|
||||
this._super(ev);
|
||||
} else {
|
||||
Dialog.confirm(
|
||||
this,
|
||||
_t("The activity is linked to a meeting. Deleting it will remove the meeting as well. Do you want to proceed ?"), {
|
||||
confirm_callback: function () {
|
||||
superMethod(ev);
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -3,11 +3,11 @@
|
||||
|
||||
<t t-inherit="mail.Activity" t-inherit-mode="extension">
|
||||
<xpath expr="//button[hasclass('o_Activity_editButton')]" position="attributes">
|
||||
<attribute name="t-if">!activity.calendar_event_id</attribute>
|
||||
<attribute name="t-if">!activityView.activity.calendar_event_id</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//button[hasclass('o_Activity_editButton')]" position="after">
|
||||
<t t-if="activity.calendar_event_id">
|
||||
<button class="o_Activity_toolButton o_Activity_editButton btn btn-link" t-on-click="_onClickEdit">
|
||||
<t t-if="activityView.activity.calendar_event_id">
|
||||
<button class="o_Activity_toolButton o_Activity_editButton btn btn-link" t-on-click="activityView.onClickEdit">
|
||||
<i class="fa fa-calendar"/> Reschedule
|
||||
</button>
|
||||
</t>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { patchRecordMethods } from '@mail/model/model_core';
|
||||
// ensure that the model definition is loaded before the patch
|
||||
import '@mail/models/activity_view/activity_view';
|
||||
|
||||
import Dialog from 'web.Dialog';
|
||||
|
||||
patchRecordMethods('ActivityView', {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
async onClickCancel(ev) {
|
||||
if (this.activity.calendar_event_id) {
|
||||
await new Promise(resolve => {
|
||||
Dialog.confirm(
|
||||
this,
|
||||
this.env._t("The activity is linked to a meeting. Deleting it will remove the meeting as well. Do you want to proceed?"),
|
||||
{ confirm_callback: resolve },
|
||||
);
|
||||
});
|
||||
}
|
||||
if (!this.exists()) {
|
||||
return;
|
||||
}
|
||||
await this._super();
|
||||
}
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
import { useRefToModel } from '@mail/component_hooks/use_ref_to_model/use_ref_to_model';
|
||||
|
||||
import {
|
||||
auto_str_to_date,
|
||||
@@ -8,8 +9,7 @@ import {
|
||||
getLangDatetimeFormat,
|
||||
} from 'web.time';
|
||||
|
||||
const { Component, useState } = owl;
|
||||
const { useRef } = owl.hooks;
|
||||
const { Component } = owl;
|
||||
|
||||
export class Activity extends Component {
|
||||
|
||||
@@ -18,15 +18,7 @@ export class Activity extends Component {
|
||||
*/
|
||||
setup() {
|
||||
super.setup();
|
||||
this.state = useState({
|
||||
areDetailsVisible: false,
|
||||
});
|
||||
/**
|
||||
* Reference of the file uploader.
|
||||
* Useful to programmatically prompts the browser file uploader.
|
||||
*/
|
||||
this._fileUploaderRef = useRef('fileUploader');
|
||||
this._onAttachmentCreated = this._onAttachmentCreated.bind(this);
|
||||
useRefToModel({ fieldName: 'fileUploaderRef', modelName: 'ActivityView', propNameAsRecordLocalId: 'activityViewLocalId', refName: 'fileUploader' });
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -34,17 +26,17 @@ export class Activity extends Component {
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @returns {Activity}
|
||||
* @returns {ActivityView}
|
||||
*/
|
||||
get activity() {
|
||||
return this.messaging && this.messaging.models['Activity'].get(this.props.activityLocalId);
|
||||
get activityView() {
|
||||
return this.messaging && this.messaging.models['ActivityView'].get(this.props.activityViewLocalId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
get assignedUserText() {
|
||||
return _.str.sprintf(this.env._t("for %s"), this.activity.assignee.nameOrDisplayName);
|
||||
return _.str.sprintf(this.env._t("for %s"), this.activityView.activity.assignee.nameOrDisplayName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,7 +44,7 @@ export class Activity extends Component {
|
||||
*/
|
||||
get delayLabel() {
|
||||
const today = moment().startOf('day');
|
||||
const momentDeadlineDate = moment(auto_str_to_date(this.activity.dateDeadline));
|
||||
const momentDeadlineDate = moment(auto_str_to_date(this.activityView.activity.dateDeadline));
|
||||
// true means no rounding
|
||||
const diff = momentDeadlineDate.diff(today, 'days', true);
|
||||
if (diff === 0) {
|
||||
@@ -72,7 +64,7 @@ export class Activity extends Component {
|
||||
* @returns {string}
|
||||
*/
|
||||
get formattedCreateDatetime() {
|
||||
const momentCreateDate = moment(auto_str_to_date(this.activity.dateCreate));
|
||||
const momentCreateDate = moment(auto_str_to_date(this.activityView.activity.dateCreate));
|
||||
const datetimeFormat = getLangDatetimeFormat();
|
||||
return momentCreateDate.format(datetimeFormat);
|
||||
}
|
||||
@@ -81,7 +73,7 @@ export class Activity extends Component {
|
||||
* @returns {string}
|
||||
*/
|
||||
get formattedDeadlineDate() {
|
||||
const momentDeadlineDate = moment(auto_str_to_date(this.activity.dateDeadline));
|
||||
const momentDeadlineDate = moment(auto_str_to_date(this.activityView.activity.dateDeadline));
|
||||
const datetimeFormat = getLangDateFormat();
|
||||
return momentDeadlineDate.format(datetimeFormat);
|
||||
}
|
||||
@@ -97,79 +89,14 @@ export class Activity extends Component {
|
||||
* @returns {string}
|
||||
*/
|
||||
get summary() {
|
||||
return _.str.sprintf(this.env._t("“%s”"), this.activity.summary);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} detail
|
||||
* @param {Attachment} detail.attachment
|
||||
*/
|
||||
_onAttachmentCreated(detail) {
|
||||
this.activity.markAsDone({ attachments: [detail.attachment] });
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClick(ev) {
|
||||
if (
|
||||
ev.target.tagName === 'A' &&
|
||||
ev.target.dataset.oeId &&
|
||||
ev.target.dataset.oeModel
|
||||
) {
|
||||
this.messaging.openProfile({
|
||||
id: Number(ev.target.dataset.oeId),
|
||||
model: ev.target.dataset.oeModel,
|
||||
});
|
||||
// avoid following dummy href
|
||||
ev.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
async _onClickCancel(ev) {
|
||||
ev.preventDefault();
|
||||
await this.activity.deleteServerRecord();
|
||||
this.trigger('reload', { keepChanges: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onClickDetailsButton() {
|
||||
this.state.areDetailsVisible = !this.state.areDetailsVisible;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickEdit(ev) {
|
||||
this.activity.edit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickUploadDocument(ev) {
|
||||
this._fileUploaderRef.comp.openBrowserFileUploader();
|
||||
return _.str.sprintf(this.env._t("“%s”"), this.activityView.activity.summary);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(Activity, {
|
||||
props: {
|
||||
activityLocalId: String,
|
||||
activityViewLocalId: String,
|
||||
},
|
||||
template: 'mail.Activity',
|
||||
});
|
||||
|
||||
@@ -2,21 +2,21 @@
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.Activity" owl="1">
|
||||
<div class="o_Activity" t-attf-class="{{ className }}" t-on-click="_onClick" t-ref="root">
|
||||
<t t-if="activity">
|
||||
<div class="o_Activity" t-attf-class="{{ className }}" t-on-click="activityView.onClickActivity" t-ref="root">
|
||||
<t t-if="activityView">
|
||||
<div class="o_Activity_sidebar">
|
||||
<div class="o_Activity_user">
|
||||
<t t-if="activity.assignee">
|
||||
<img class="o_Activity_userAvatar" t-attf-src="/web/image/res.users/{{ activity.assignee.id }}/avatar_128" t-att-alt="activity.assignee.nameOrDisplayName"/>
|
||||
<t t-if="activityView.activity.assignee">
|
||||
<img class="o_Activity_userAvatar" t-attf-src="/web/image/res.users/{{ activityView.activity.assignee.id }}/avatar_128" t-att-alt="activityView.activity.assignee.nameOrDisplayName"/>
|
||||
</t>
|
||||
<div class="o_Activity_iconContainer"
|
||||
t-att-class="{
|
||||
'bg-success': activity.state === 'planned',
|
||||
'bg-warning': activity.state === 'today',
|
||||
'bg-danger': activity.state === 'overdue',
|
||||
'bg-success': activityView.activity.state === 'planned',
|
||||
'bg-warning': activityView.activity.state === 'today',
|
||||
'bg-danger': activityView.activity.state === 'overdue',
|
||||
}"
|
||||
>
|
||||
<i class="o_Activity_icon fa" t-attf-class="{{ activity.icon }}"/>
|
||||
<i class="o_Activity_icon fa" t-attf-class="{{ activityView.activity.icon }}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -24,68 +24,68 @@
|
||||
<div class="o_Activity_info">
|
||||
<div class="o_Activity_dueDateText"
|
||||
t-att-class="{
|
||||
'o-default': activity.state === 'default',
|
||||
'o-overdue': activity.state === 'overdue',
|
||||
'o-planned': activity.state === 'planned',
|
||||
'o-today': activity.state === 'today',
|
||||
'o-default': activityView.activity.state === 'default',
|
||||
'o-overdue': activityView.activity.state === 'overdue',
|
||||
'o-planned': activityView.activity.state === 'planned',
|
||||
'o-today': activityView.activity.state === 'today',
|
||||
}"
|
||||
>
|
||||
<t t-esc="delayLabel"/>
|
||||
</div>
|
||||
<t t-if="activity.summary">
|
||||
<t t-if="activityView.activity.summary">
|
||||
<div class="o_Activity_summary">
|
||||
<t t-esc="summary"/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-elif="activity.type">
|
||||
<t t-elif="activityView.activity.type">
|
||||
<div class="o_Activity_summary o_Activity_type">
|
||||
<t t-esc="activity.type.displayName"/>
|
||||
<t t-esc="activityView.activity.type.displayName"/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="activity.assignee">
|
||||
<t t-if="activityView.activity.assignee">
|
||||
<div class="o_Activity_userName">
|
||||
<t t-esc="assignedUserText"/>
|
||||
</div>
|
||||
</t>
|
||||
<a class="o_Activity_detailsButton btn btn-link" t-on-click="_onClickDetailsButton" role="button">
|
||||
<a class="o_Activity_detailsButton btn btn-link" t-on-click="activityView.onClickDetailsButton" role="button">
|
||||
<i class="fa fa-info-circle" role="img" title="Info"/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<t t-if="state.areDetailsVisible">
|
||||
<t t-if="activityView.areDetailsVisible">
|
||||
<div class="o_Activity_details">
|
||||
<dl class="dl-horizontal">
|
||||
<t t-if="activity.type">
|
||||
<t t-if="activityView.activity.type">
|
||||
<dt>Activity type</dt>
|
||||
<dd class="o_Activity_type">
|
||||
<t t-esc="activity.type.displayName"/>
|
||||
<t t-esc="activityView.activity.type.displayName"/>
|
||||
</dd>
|
||||
</t>
|
||||
<t t-if="activity.creator">
|
||||
<t t-if="activityView.activity.creator">
|
||||
<dt>Created</dt>
|
||||
<dd class="o_Activity_detailsCreation">
|
||||
<t t-esc="formattedCreateDatetime"/>
|
||||
<img class="o_Activity_detailsUserAvatar o_Activity_detailsCreatorAvatar" t-attf-src="/web/image/res.users/{{ activity.creator.id }}/avatar_128" t-att-title="activity.creator.nameOrDisplayName" t-att-alt="activity.creator.nameOrDisplayName"/>
|
||||
<img class="o_Activity_detailsUserAvatar o_Activity_detailsCreatorAvatar" t-attf-src="/web/image/res.users/{{ activityView.activity.creator.id }}/avatar_128" t-att-title="activityView.activity.creator.nameOrDisplayName" t-att-alt="activityView.activity.creator.nameOrDisplayName"/>
|
||||
<span class="o_Activity_detailsCreator">
|
||||
<t t-esc="activity.creator.nameOrDisplayName"/>
|
||||
<t t-esc="activityView.activity.creator.nameOrDisplayName"/>
|
||||
</span>
|
||||
</dd>
|
||||
</t>
|
||||
<t t-if="activity.assignee">
|
||||
<t t-if="activityView.activity.assignee">
|
||||
<dt>Assigned to</dt>
|
||||
<dd class="o_Activity_detailsAssignation">
|
||||
<img class="o_Activity_detailsUserAvatar o_Activity_detailsAssignationUserAvatar" t-attf-src="/web/image/res.users/{{ activity.assignee.id }}/avatar_128" t-att-title="activity.assignee.nameOrDisplayName" t-att-alt="activity.assignee.nameOrDisplayName"/>
|
||||
<t t-esc="activity.assignee.nameOrDisplayName"/>
|
||||
<img class="o_Activity_detailsUserAvatar o_Activity_detailsAssignationUserAvatar" t-attf-src="/web/image/res.users/{{ activityView.activity.assignee.id }}/avatar_128" t-att-title="activityView.activity.assignee.nameOrDisplayName" t-att-alt="activityView.activity.assignee.nameOrDisplayName"/>
|
||||
<t t-esc="activityView.activity.assignee.nameOrDisplayName"/>
|
||||
</dd>
|
||||
</t>
|
||||
<dt>Due on</dt>
|
||||
<dd class="o_Activity_detailsDueDate">
|
||||
<span class="o_Activity_deadlineDateText"
|
||||
t-att-class="{
|
||||
'o-default': activity.state === 'default',
|
||||
'o-overdue': activity.state === 'overdue',
|
||||
'o-planned': activity.state === 'planned',
|
||||
'o-today': activity.state === 'today',
|
||||
'o-default': activityView.activity.state === 'default',
|
||||
'o-overdue': activityView.activity.state === 'overdue',
|
||||
'o-planned': activityView.activity.state === 'planned',
|
||||
'o-today': activityView.activity.state === 'today',
|
||||
}"
|
||||
>
|
||||
<t t-esc="formattedDeadlineDate"/>
|
||||
@@ -95,51 +95,51 @@
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-if="activity.note">
|
||||
<t t-if="activityView.activity.note">
|
||||
<div class="o_Activity_note">
|
||||
<t t-raw="activity.note"/>
|
||||
<t t-raw="activityView.activity.note"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-if="activity.mailTemplates.length > 0">
|
||||
<t t-if="activityView.activity.mailTemplates.length > 0">
|
||||
<div class="o_Activity_mailTemplates">
|
||||
<t t-foreach="activity.mailTemplates" t-as="mailTemplate" t-key="mailTemplate.localId">
|
||||
<t t-foreach="activityView.activity.mailTemplates" t-as="mailTemplate" t-key="mailTemplate.localId">
|
||||
<MailTemplate
|
||||
className="'o_Activity_mailTemplate'"
|
||||
activityLocalId="activity.localId"
|
||||
activityLocalId="activityView.activity.localId"
|
||||
mailTemplateLocalId="mailTemplate.localId"
|
||||
/>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-if="activity.canWrite">
|
||||
<t t-if="activityView.activity.canWrite">
|
||||
<div name="tools" class="o_Activity_tools">
|
||||
<t t-if="activity.category !== 'upload_file'">
|
||||
<t t-if="activityView.activity.category !== 'upload_file'">
|
||||
<Popover position="'right'" title="MARK_DONE">
|
||||
<button class="o_Activity_toolButton o_Activity_markDoneButton btn btn-link" t-att-title="MARK_DONE">
|
||||
<i class="fa fa-check"/> Mark Done
|
||||
</button>
|
||||
<t t-set="opened">
|
||||
<ActivityMarkDonePopover activityLocalId="props.activityLocalId"/>
|
||||
<ActivityMarkDonePopover activityLocalId="activityView.activity.localId"/>
|
||||
</t>
|
||||
</Popover>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<button class="o_Activity_toolButton o_Activity_uploadButton btn btn-link" t-on-click="_onClickUploadDocument">
|
||||
<button class="o_Activity_toolButton o_Activity_uploadButton btn btn-link" t-on-click="activityView.onClickUploadDocument">
|
||||
<i class="fa fa-upload"/> Upload Document
|
||||
</button>
|
||||
<FileUploader
|
||||
t-if="activity.thread"
|
||||
threadLocalId="activity.thread.localId"
|
||||
onAttachmentCreated="_onAttachmentCreated"
|
||||
t-if="activityView.activity.thread"
|
||||
threadLocalId="activityView.activity.thread.localId"
|
||||
onAttachmentCreated="activityView.onAttachmentCreated"
|
||||
t-ref="fileUploader"
|
||||
/>
|
||||
</t>
|
||||
<button class="o_Activity_toolButton o_Activity_editButton btn btn-link" t-on-click="_onClickEdit">
|
||||
<button class="o_Activity_toolButton o_Activity_editButton btn btn-link" t-on-click="activityView.onClickEdit">
|
||||
<i class="fa fa-pencil"/> Edit
|
||||
</button>
|
||||
<button class="o_Activity_toolButton o_Activity_cancelButton btn btn-link" t-on-click="_onClickCancel" >
|
||||
<button class="o_Activity_toolButton o_Activity_cancelButton btn btn-link" t-on-click="activityView.onClickCancel" >
|
||||
<i class="fa fa-times"/> Cancel
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -557,7 +557,7 @@ QUnit.test('activity details layout', async function (assert) {
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_Activity_details .o_Activity_type').textContent,
|
||||
"Email",
|
||||
"activity details type should be 'Fake type'"
|
||||
"activity details type should be 'Email'"
|
||||
);
|
||||
assert.strictEqual(
|
||||
document.querySelectorAll('.o_Activity_detailsCreation').length,
|
||||
|
||||
@@ -14,7 +14,7 @@ export class ActivityBox extends Component {
|
||||
* @returns {Chatter}
|
||||
*/
|
||||
get activityBoxView() {
|
||||
return this.messaging.models['ActivityBoxView'].get(this.props.activityBoxViewLocalId);
|
||||
return this.messaging && this.messaging.models['ActivityBoxView'].get(this.props.activityBoxViewLocalId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
</a>
|
||||
<t t-if="activityBoxView.isActivityListVisible">
|
||||
<div class="o_ActivityBox_activityList">
|
||||
<t t-foreach="activityBoxView.chatter.thread.activities" t-as="activity" t-key="activity.localId">
|
||||
<Activity className="'o_ActivityBox_activity'" activityLocalId="activity.localId"/>
|
||||
<t t-foreach="activityBoxView.activityViews" t-as="activityView" t-key="activityView.localId">
|
||||
<Activity className="'o_ActivityBox_activity'" activityViewLocalId="activityView.localId"/>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
import { useComponentToModel } from '@mail/component_hooks/use_component_to_model/use_component_to_model';
|
||||
import { useUpdate } from '@mail/component_hooks/use_update/use_update';
|
||||
import { useRefToModel } from '@mail/component_hooks/use_ref_to_model/use_ref_to_model';
|
||||
|
||||
@@ -15,6 +16,7 @@ export class Chatter extends Component {
|
||||
setup() {
|
||||
super.setup();
|
||||
useUpdate({ func: () => this._update() });
|
||||
useComponentToModel({ fieldName: 'component', modelName: 'Chatter', propNameAsRecordLocalId: 'chatterLocalId' });
|
||||
useRefToModel({ fieldName: 'threadRef', modelName: 'Chatter', propNameAsRecordLocalId: 'chatterLocalId', refName: 'thread' });
|
||||
/**
|
||||
* Reference of the scroll Panel (Real scroll element). Useful to pass the Scroll element to
|
||||
|
||||
@@ -1,24 +1,11 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
import { useComponentToModel } from '@mail/component_hooks/use_component_to_model/use_component_to_model';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export class ChatterTopbar extends Component {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
setup() {
|
||||
super.setup();
|
||||
useComponentToModel({ fieldName: 'componentChatterTopbar', modelName: 'Chatter', propNameAsRecordLocalId: 'chatterLocalId' });
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @returns {Chatter}
|
||||
*/
|
||||
|
||||
@@ -3,20 +3,22 @@
|
||||
|
||||
<t t-name="mail.FollowButton" owl="1">
|
||||
<div class="o_FollowButton" t-attf-class="{{ className }}" t-ref="root">
|
||||
<t t-if="thread.isCurrentPartnerFollowing">
|
||||
<button class="o_FollowButton_unfollow btn btn-link" t-att-class="{ 'o-following': !state.isUnfollowButtonHighlighted, 'o-unfollow': state.isUnfollowButtonHighlighted }" t-att-disabled="props.isDisabled" t-on-click="_onClickUnfollow" t-on-mouseenter="_onMouseEnterUnfollow" t-on-mouseleave="_onMouseLeaveUnfollow">
|
||||
<t t-if="state.isUnfollowButtonHighlighted">
|
||||
<i class="fa fa-times"/> Unfollow
|
||||
</t>
|
||||
<t t-else="">
|
||||
<i class="fa fa-check"/> Following
|
||||
</t>
|
||||
</button>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<button class="o_FollowButton_follow btn btn-link" t-att-disabled="props.isDisabled" t-on-click="_onClickFollow">
|
||||
Follow
|
||||
</button>
|
||||
<t t-if="thread">
|
||||
<t t-if="thread.isCurrentPartnerFollowing">
|
||||
<button class="o_FollowButton_unfollow btn btn-link" t-att-class="{ 'o-following': !state.isUnfollowButtonHighlighted, 'o-unfollow': state.isUnfollowButtonHighlighted }" t-att-disabled="props.isDisabled" t-on-click="_onClickUnfollow" t-on-mouseenter="_onMouseEnterUnfollow" t-on-mouseleave="_onMouseLeaveUnfollow">
|
||||
<t t-if="state.isUnfollowButtonHighlighted">
|
||||
<i class="fa fa-times"/> Unfollow
|
||||
</t>
|
||||
<t t-else="">
|
||||
<i class="fa fa-check"/> Following
|
||||
</t>
|
||||
</button>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<button class="o_FollowButton_follow btn btn-link" t-att-disabled="props.isDisabled" t-on-click="_onClickFollow">
|
||||
Follow
|
||||
</button>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
@@ -3,32 +3,34 @@
|
||||
|
||||
<t t-name="mail.FollowerListMenu" owl="1">
|
||||
<div class="o_FollowerListMenu" t-attf-class="{{ className }}" t-on-keydown="_onKeydown" t-ref="root">
|
||||
<div class="o_FollowerListMenu_followers" t-ref="dropdown">
|
||||
<button class="o_FollowerListMenu_buttonFollowers btn btn-link" t-att-disabled="props.isDisabled" t-on-click="_onClickFollowersButton" title="Show Followers">
|
||||
<i class="fa fa-user"/>
|
||||
<span class="o_FollowerListMenu_buttonFollowersCount pl-1" t-esc="thread.followers.length"/>
|
||||
</button>
|
||||
<t t-if="thread">
|
||||
<div class="o_FollowerListMenu_followers" t-ref="dropdown">
|
||||
<button class="o_FollowerListMenu_buttonFollowers btn btn-link" t-att-disabled="props.isDisabled" t-on-click="_onClickFollowersButton" title="Show Followers">
|
||||
<i class="fa fa-user"/>
|
||||
<span class="o_FollowerListMenu_buttonFollowersCount pl-1" t-esc="thread.followers.length"/>
|
||||
</button>
|
||||
|
||||
<t t-if="state.isDropdownOpen">
|
||||
<div class="o_FollowerListMenu_dropdown dropdown-menu dropdown-menu-right" role="menu">
|
||||
<t t-if="thread.channel_type !== 'chat'">
|
||||
<a class="o_FollowerListMenu_addFollowersButton dropdown-item" href="#" role="menuitem" t-on-click="_onClickAddFollowers">
|
||||
Add Followers
|
||||
</a>
|
||||
</t>
|
||||
<t t-if="thread.followers.length > 0">
|
||||
<div role="separator" class="dropdown-divider"/>
|
||||
<t t-foreach="thread.followers" t-as="follower" t-key="follower.localId">
|
||||
<Follower
|
||||
className="'o_FollowerMenu_follower dropdown-item'"
|
||||
followerLocalId="follower.localId"
|
||||
onClick="_onClickFollower"
|
||||
/>
|
||||
<t t-if="state.isDropdownOpen">
|
||||
<div class="o_FollowerListMenu_dropdown dropdown-menu dropdown-menu-right" role="menu">
|
||||
<t t-if="thread.channel_type !== 'chat'">
|
||||
<a class="o_FollowerListMenu_addFollowersButton dropdown-item" href="#" role="menuitem" t-on-click="_onClickAddFollowers">
|
||||
Add Followers
|
||||
</a>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
<t t-if="thread.followers.length > 0">
|
||||
<div role="separator" class="dropdown-divider"/>
|
||||
<t t-foreach="thread.followers" t-as="follower" t-key="follower.localId">
|
||||
<Follower
|
||||
className="'o_FollowerMenu_follower dropdown-item'"
|
||||
followerLocalId="follower.localId"
|
||||
onClick="_onClickFollower"
|
||||
/>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ export function addLifecycleHooks(modelName, hooks) {
|
||||
assertIsFunction(handler);
|
||||
assertIsValidHookName(name);
|
||||
assertSectionDoesNotHaveKey('lifecycleHooks', name, definition);
|
||||
}, `Cannot add lifecycle hook "${name}" to model "${modelName}":`);
|
||||
}, `Cannot add lifecycle hook "${name}" to model "${modelName}": `);
|
||||
definition.get('lifecycleHooks').set(name, handler);
|
||||
}
|
||||
}
|
||||
@@ -281,6 +281,7 @@ export function patchLifecycleHooks(modelName, hooks) {
|
||||
for (const [name, handler] of Object.entries(hooks)) {
|
||||
addContextToErrors(() => {
|
||||
assertIsFunction(handler);
|
||||
assertIsValidHookName(name);
|
||||
assertSectionHasKey('lifecycleHooks', name, definition);
|
||||
}, `Cannot patch lifecycle hook "${name}" on model "${modelName}": `);
|
||||
const hookBeforePatch = definition.get('lifecycleHooks').get(name);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerModel } from '@mail/model/model_core';
|
||||
import { attr, many2many, many2one } from '@mail/model/model_field';
|
||||
import { attr, many2many, many2one, one2many } from '@mail/model/model_field';
|
||||
import { clear, insert, unlink, unlinkAll } from '@mail/model/model_field_command';
|
||||
|
||||
registerModel({
|
||||
@@ -232,6 +232,10 @@ registerModel({
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
activityViews: one2many('ActivityView', {
|
||||
inverse: 'activity',
|
||||
isCausal: true,
|
||||
}),
|
||||
assignee: many2one('User'),
|
||||
attachments: many2many('Attachment', {
|
||||
inverse: 'activities',
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerModel } from '@mail/model/model_core';
|
||||
import { attr, one2one } from '@mail/model/model_field';
|
||||
import { attr, one2many, one2one } from '@mail/model/model_field';
|
||||
import { insertAndReplace, replace } from '@mail/model/model_field_command';
|
||||
|
||||
registerModel({
|
||||
name: 'ActivityBoxView',
|
||||
@@ -19,8 +20,22 @@ registerModel({
|
||||
onClickActivityBoxTitle() {
|
||||
this.update({ isActivityListVisible: !this.isActivityListVisible });
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {FieldCommand}
|
||||
*/
|
||||
_computeActivityViews() {
|
||||
return insertAndReplace(this.chatter.thread.activities.map(activity => {
|
||||
return { activity: replace(activity) };
|
||||
}));
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
activityViews: one2many('ActivityView', {
|
||||
compute: '_computeActivityViews',
|
||||
inverse: 'activityBoxView',
|
||||
isCausal: true,
|
||||
}),
|
||||
chatter: one2one('Chatter', {
|
||||
inverse: 'activityBoxView',
|
||||
readonly: true,
|
||||
@@ -29,6 +44,6 @@ registerModel({
|
||||
isActivityListVisible: attr({
|
||||
default: true,
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerModel } from '@mail/model/model_core';
|
||||
import { attr, many2one } from '@mail/model/model_field';
|
||||
|
||||
registerModel({
|
||||
name: 'ActivityView',
|
||||
identifyingFields: ['activityBoxView', 'activity'],
|
||||
lifecycleHooks: {
|
||||
_created() {
|
||||
this.onAttachmentCreated = this.onAttachmentCreated.bind(this);
|
||||
this.onClickActivity = this.onClickActivity.bind(this);
|
||||
this.onClickCancel = this.onClickCancel.bind(this);
|
||||
this.onClickDetailsButton = this.onClickDetailsButton.bind(this);
|
||||
this.onClickEdit = this.onClickEdit.bind(this);
|
||||
this.onClickUploadDocument = this.onClickUploadDocument.bind(this);
|
||||
}
|
||||
},
|
||||
recordMethods: {
|
||||
/**
|
||||
* @param {Object} detail
|
||||
* @param {Attachment} detail.attachment
|
||||
*/
|
||||
onAttachmentCreated(detail) {
|
||||
this.activity.markAsDone({ attachments: [detail.attachment] });
|
||||
},
|
||||
/**
|
||||
* Handles the click on a link inside the activity.
|
||||
*
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
onClickActivity(ev) {
|
||||
if (
|
||||
ev.target.tagName === 'A' &&
|
||||
ev.target.dataset.oeId &&
|
||||
ev.target.dataset.oeModel
|
||||
) {
|
||||
this.messaging.openProfile({
|
||||
id: Number(ev.target.dataset.oeId),
|
||||
model: ev.target.dataset.oeModel,
|
||||
});
|
||||
// avoid following dummy href
|
||||
ev.preventDefault();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Handles the click on the cancel button
|
||||
*/
|
||||
async onClickCancel() {
|
||||
const { chatter } = this.activityBoxView; // save value before deleting activity
|
||||
await this.activity.deleteServerRecord();
|
||||
chatter.reloadParentView();
|
||||
},
|
||||
/**
|
||||
* Handles the click on the detail button
|
||||
*/
|
||||
onClickDetailsButton() {
|
||||
this.update({ areDetailsVisible: !this.areDetailsVisible });
|
||||
},
|
||||
/**
|
||||
* Handles the click on the edit button
|
||||
*/
|
||||
onClickEdit() {
|
||||
this.activity.edit();
|
||||
},
|
||||
/**
|
||||
* Handles the click on the upload document button. This open the file
|
||||
* explorer for upload.
|
||||
*/
|
||||
onClickUploadDocument() {
|
||||
this.fileUploaderRef.comp.openBrowserFileUploader();
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
activity: many2one('Activity', {
|
||||
inverse: 'activityViews',
|
||||
required: true,
|
||||
readonly: true,
|
||||
}),
|
||||
activityBoxView: many2one('ActivityBoxView', {
|
||||
inverse: 'activityViews',
|
||||
readonly: true,
|
||||
required: true,
|
||||
}),
|
||||
/**
|
||||
* Determines whether the details are visible.
|
||||
*/
|
||||
areDetailsVisible: attr({
|
||||
default: false,
|
||||
}),
|
||||
/**
|
||||
* States the OWL FileUploader component of this activity view.
|
||||
*/
|
||||
fileUploaderRef: attr(),
|
||||
},
|
||||
});
|
||||
@@ -60,7 +60,7 @@ registerModel({
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
onClickChatterTopbarClose(ev) {
|
||||
this.componentChatterTopbar.trigger('o-close-chatter');
|
||||
this.component.trigger('o-close-chatter');
|
||||
},
|
||||
/**
|
||||
* Handles click on "log note" button.
|
||||
@@ -96,12 +96,7 @@ registerModel({
|
||||
return this.env.bus.trigger('do-action', {
|
||||
action,
|
||||
options: {
|
||||
on_close: () => {
|
||||
if (!this.componentChatterTopbar) {
|
||||
return;
|
||||
}
|
||||
this.componentChatterTopbar.trigger('reload', { keepChanges: true });
|
||||
},
|
||||
on_close: () => this.reloadParentView(),
|
||||
},
|
||||
});
|
||||
},
|
||||
@@ -140,6 +135,11 @@ registerModel({
|
||||
this.thread.refresh();
|
||||
}
|
||||
},
|
||||
reloadParentView() {
|
||||
if (this.component) {
|
||||
this.component.trigger('reload', { keepChanges: true });
|
||||
}
|
||||
},
|
||||
showLogNote() {
|
||||
this.update({ composerView: insertAndReplace() });
|
||||
this.composerView.composer.update({ isLog: true });
|
||||
@@ -296,9 +296,9 @@ registerModel({
|
||||
readonly: true,
|
||||
}),
|
||||
/**
|
||||
* States the OWL component of this chatter top bar.
|
||||
* States the OWL Chatter component of this chatter.
|
||||
*/
|
||||
componentChatterTopbar: attr(),
|
||||
component: attr(),
|
||||
/**
|
||||
* Determines the composer view used to post in this chatter (if any).
|
||||
*/
|
||||
|
||||
@@ -67,9 +67,9 @@ Featuring
|
||||
'web.assets_backend': [
|
||||
'website_slides/static/src/scss/rating_rating_views.scss',
|
||||
'website_slides/static/src/scss/slide_views.scss',
|
||||
'website_slides/static/src/components/activity/activity.js',
|
||||
'website_slides/static/src/js/slide_category_one2many.js',
|
||||
'website_slides/static/src/js/rating_field_backend.js',
|
||||
'website_slides/static/src/models/*/*.js',
|
||||
],
|
||||
'web.assets_frontend': [
|
||||
'website_slides/static/src/scss/website_slides.scss',
|
||||
@@ -114,10 +114,10 @@ Featuring
|
||||
'website_slides/static/src/tests/helpers/mock_models.js',
|
||||
],
|
||||
'web.qunit_suite_tests': [
|
||||
'website_slides/static/src/components/activity/activity_tests.js',
|
||||
'website_slides/static/src/components/*/tests/*.js',
|
||||
],
|
||||
'web.assets_qweb': [
|
||||
'website_slides/static/src/components/activity/activity.xml',
|
||||
'website_slides/static/src/components/*/*.xml',
|
||||
],
|
||||
},
|
||||
'license': 'LGPL-3',
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { Activity } from '@mail/components/activity/activity';
|
||||
|
||||
import { patch } from 'web.utils';
|
||||
|
||||
patch(Activity.prototype, 'website_slides/static/src/components/activity/activity.js', {
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
async _onGrantAccess(ev) {
|
||||
await this.env.services.rpc({
|
||||
model: 'slide.channel',
|
||||
method: 'action_grant_access',
|
||||
args: [[this.activity.thread.id]],
|
||||
kwargs: { partner_id: this.activity.requestingPartner.id },
|
||||
});
|
||||
if (this.activity) {
|
||||
this.activity.delete();
|
||||
}
|
||||
this.trigger('reload', { keepChanges: true });
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
async _onRefuseAccess(ev) {
|
||||
await this.env.services.rpc({
|
||||
model: 'slide.channel',
|
||||
method: 'action_refuse_access',
|
||||
args: [[this.activity.thread.id]],
|
||||
kwargs: { partner_id: this.activity.requestingPartner.id },
|
||||
});
|
||||
if (this.activity) {
|
||||
this.activity.delete();
|
||||
}
|
||||
this.trigger('reload', { keepChanges: true });
|
||||
},
|
||||
});
|
||||
@@ -2,12 +2,12 @@
|
||||
<templates xml:space="preserve">
|
||||
<t t-inherit="mail.Activity" t-inherit-mode="extension">
|
||||
<xpath expr="//*[@name='tools']" position="replace">
|
||||
<t t-if="activity.requestingPartner and activity.thread.model === 'slide.channel'">
|
||||
<t t-if="activityView.activity.requestingPartner and activityView.activity.thread.model === 'slide.channel'">
|
||||
<div class="o_Activity_tools">
|
||||
<button class="o_Activity_toolButton o_Activity_grantAccessButton btn btn-link" t-on-click="_onGrantAccess">
|
||||
<button class="o_Activity_toolButton o_Activity_grantAccessButton btn btn-link" t-on-click="activityView.onGrantAccess">
|
||||
<i class="fa fa-check"/> Grant Access
|
||||
</button>
|
||||
<button class="o_Activity_toolButton o_Activity_refuseAccessButton btn btn-link" t-on-click="_onRefuseAccess">
|
||||
<button class="o_Activity_toolButton o_Activity_refuseAccessButton btn btn-link" t-on-click="activityView.onRefuseAccess">
|
||||
<i class="fa fa-times"/> Refuse Access
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { addRecordMethods, patchLifecycleHooks } from '@mail/model/model_core';
|
||||
// ensure that the model definition is loaded before the patch
|
||||
import '@mail/models/activity_view/activity_view';
|
||||
|
||||
patchLifecycleHooks('ActivityView', {
|
||||
_created() {
|
||||
this._super();
|
||||
this.onGrantAccess = this.onGrantAccess.bind(this);
|
||||
this.onRefuseAccess = this.onRefuseAccess.bind(this);
|
||||
},
|
||||
});
|
||||
|
||||
addRecordMethods('ActivityView', {
|
||||
/**
|
||||
* Handles click on the "grant access" button.
|
||||
*/
|
||||
async onGrantAccess(ev) {
|
||||
const { chatter } = this.activityBoxView; // save value before deleting activity
|
||||
await this.env.services.rpc({
|
||||
model: 'slide.channel',
|
||||
method: 'action_grant_access',
|
||||
args: [[this.activity.thread.id]],
|
||||
kwargs: { partner_id: this.activity.requestingPartner.id },
|
||||
});
|
||||
if (this.activity) {
|
||||
this.activity.delete();
|
||||
}
|
||||
chatter.reloadParentView();
|
||||
},
|
||||
/**
|
||||
* Handles click on the "refuse access" button.
|
||||
*/
|
||||
async onRefuseAccess(ev) {
|
||||
const { chatter } = this.activityBoxView; // save value before deleting activity
|
||||
await this.env.services.rpc({
|
||||
model: 'slide.channel',
|
||||
method: 'action_refuse_access',
|
||||
args: [[this.activity.thread.id]],
|
||||
kwargs: { partner_id: this.activity.requestingPartner.id },
|
||||
});
|
||||
if (this.activity) {
|
||||
this.activity.delete();
|
||||
}
|
||||
chatter.reloadParentView();
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user