mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-20 17:52:01 +02:00
551 lines
18 KiB
JavaScript
551 lines
18 KiB
JavaScript
/** @odoo-module **/
|
|
|
|
import { registerModel } from '@mail/model/model_core';
|
|
import { attr, one } from '@mail/model/model_field';
|
|
import { clear, insert, link } from '@mail/model/model_field_command';
|
|
import { makeDeferred } from '@mail/utils/deferred';
|
|
|
|
const getThreadNextTemporaryId = (function () {
|
|
let tmpId = 0;
|
|
return () => {
|
|
tmpId -= 1;
|
|
return tmpId;
|
|
};
|
|
})();
|
|
|
|
const getMessageNextTemporaryId = (function () {
|
|
let tmpId = 0;
|
|
return () => {
|
|
tmpId -= 1;
|
|
return tmpId;
|
|
};
|
|
})();
|
|
|
|
registerModel({
|
|
name: 'Chatter',
|
|
recordMethods: {
|
|
focus() {
|
|
if (this.composerView) {
|
|
this.composerView.update({ doFocus: true });
|
|
}
|
|
},
|
|
async doSaveRecord() {
|
|
const saved = await this.saveRecord();
|
|
if (!saved) {
|
|
return saved;
|
|
}
|
|
let composerData = null;
|
|
if (this.composerView) {
|
|
const {
|
|
attachments,
|
|
isLog,
|
|
rawMentionedChannels,
|
|
rawMentionedPartners,
|
|
textInputContent,
|
|
textInputCursorEnd,
|
|
textInputCursorStart,
|
|
textInputSelectionDirection,
|
|
} = this.composerView.composer;
|
|
composerData = {
|
|
attachments,
|
|
isLog,
|
|
rawMentionedChannels,
|
|
rawMentionedPartners,
|
|
textInputContent,
|
|
textInputCursorEnd,
|
|
textInputCursorStart,
|
|
textInputSelectionDirection,
|
|
};
|
|
}
|
|
// Wait for next render from chatter_container,
|
|
// So that it changes to composer of new thread
|
|
this.update({
|
|
createNewRecordComposerData: composerData,
|
|
createNewRecordDeferred: composerData ? makeDeferred() : null,
|
|
});
|
|
await this.createNewRecordDeferred;
|
|
// Give some time to chatter model being updated by save.
|
|
await new Promise((resolve) => setTimeout(() => requestAnimationFrame(resolve)));
|
|
return saved;
|
|
},
|
|
onAttachmentsLoadingTimeout() {
|
|
this.update({
|
|
attachmentsLoaderTimer: clear(),
|
|
isShowingAttachmentsLoading: true,
|
|
});
|
|
},
|
|
/**
|
|
* Handles click on the attach button.
|
|
*/
|
|
async onClickButtonAddAttachments() {
|
|
if (this.isTemporary) {
|
|
const saved = await this.doSaveRecord();
|
|
if (!saved) {
|
|
return;
|
|
}
|
|
}
|
|
this.fileUploader.openBrowserFileUploader();
|
|
},
|
|
/**
|
|
* Handles click on the attachments button.
|
|
*/
|
|
async onClickButtonToggleAttachments() {
|
|
if (this.isTemporary) {
|
|
const saved = await this.doSaveRecord();
|
|
if (!saved) {
|
|
return;
|
|
}
|
|
}
|
|
this.update({ attachmentBoxView: this.attachmentBoxView ? clear() : {} });
|
|
if (this.attachmentBoxView) {
|
|
this.scrollPanelRef.el.scrollTop = 0;
|
|
}
|
|
},
|
|
/**
|
|
* Handles click on top bar close button.
|
|
*
|
|
* @param {MouseEvent} ev
|
|
*/
|
|
onClickChatterTopbarClose(ev) {
|
|
this.component.trigger('o-close-chatter');
|
|
},
|
|
/**
|
|
* Handles click on "log note" button.
|
|
*
|
|
* @param {MouseEvent} ev
|
|
*/
|
|
onClickLogNote() {
|
|
if (this.composerView && this.composerView.composer.isLog) {
|
|
this.update({ composerView: clear() });
|
|
} else {
|
|
this.showLogNote();
|
|
}
|
|
},
|
|
/**
|
|
* Handles click on "schedule activity" button.
|
|
*
|
|
* @param {MouseEvent} ev
|
|
*/
|
|
async onClickScheduleActivity(ev) {
|
|
if (this.isTemporary) {
|
|
const saved = await this.doSaveRecord();
|
|
if (!saved) {
|
|
return;
|
|
}
|
|
}
|
|
await this.messaging.openActivityForm({ thread: this.thread });
|
|
if (this.exists()) {
|
|
this.reloadParentView();
|
|
}
|
|
},
|
|
/**
|
|
* Handles click on "send message" button.
|
|
*
|
|
* @param {MouseEvent} ev
|
|
*/
|
|
onClickSendMessage(ev) {
|
|
if (this.composerView && !this.composerView.composer.isLog) {
|
|
this.update({ composerView: clear() });
|
|
} else {
|
|
this.showSendMessage();
|
|
}
|
|
},
|
|
/**
|
|
* Handles scroll on this scroll panel.
|
|
*
|
|
* @param {Event} ev
|
|
*/
|
|
onScrollScrollPanel(ev) {
|
|
if (!this.threadView || !this.threadView.messageListView || !this.threadView.messageListView.component) {
|
|
return;
|
|
}
|
|
this.threadView.messageListView.component.onScroll(ev);
|
|
},
|
|
openAttachmentBoxView() {
|
|
this.update({ attachmentBoxView: {} });
|
|
},
|
|
/**
|
|
* Open a dialog to add partners as followers.
|
|
*/
|
|
promptAddPartnerFollower() {
|
|
const action = {
|
|
type: 'ir.actions.act_window',
|
|
res_model: 'mail.wizard.invite',
|
|
view_mode: 'form',
|
|
views: [[false, 'form']],
|
|
name: this.env._t("Invite Follower"),
|
|
target: 'new',
|
|
context: {
|
|
default_res_model: this.thread.model,
|
|
default_res_id: this.thread.id,
|
|
},
|
|
};
|
|
this.env.services.action.doAction(
|
|
action,
|
|
{
|
|
onClose: async () => {
|
|
if (!this.exists() && !this.thread) {
|
|
return;
|
|
}
|
|
await this.thread.fetchData(['followers']);
|
|
if (this.exists() && this.hasParentReloadOnFollowersUpdate) {
|
|
this.reloadParentView();
|
|
}
|
|
},
|
|
}
|
|
);
|
|
},
|
|
async refresh() {
|
|
const requestData = ['activities', 'followers', 'suggestedRecipients'];
|
|
if (this.hasMessageList) {
|
|
requestData.push('attachments', 'messages');
|
|
}
|
|
this.thread.fetchData(requestData);
|
|
},
|
|
/**
|
|
* @param {Object} [param0={}]
|
|
* @param {string[]} [fieldNames]
|
|
*/
|
|
async reloadParentView({ fieldNames } = {}) {
|
|
if (this.saveRecord) {
|
|
await this.saveRecord();
|
|
}
|
|
if (this.webRecord) {
|
|
await this.webRecord.model.root.load({ resId: this.threadId }, { keepChanges: true });
|
|
this.webRecord.model.notify();
|
|
return;
|
|
}
|
|
if (this.component) {
|
|
const options = { keepChanges: true };
|
|
if (fieldNames) {
|
|
options.fieldNames = fieldNames;
|
|
}
|
|
this.component.trigger('reload', options);
|
|
}
|
|
},
|
|
showLogNote() {
|
|
this.update({ composerView: {} });
|
|
this.composerView.composer.update({ isLog: true });
|
|
this.focus();
|
|
},
|
|
showSendMessage() {
|
|
this.update({ composerView: {} });
|
|
this.composerView.composer.update({ isLog: false });
|
|
this.focus();
|
|
},
|
|
/**
|
|
* @private
|
|
*/
|
|
_onThreadIdOrThreadModelChanged() {
|
|
if (!this.threadModel) {
|
|
return;
|
|
}
|
|
if (this.threadId) {
|
|
if (this.thread && this.thread.isTemporary) {
|
|
this.thread.delete();
|
|
}
|
|
this.update({
|
|
attachmentBoxView: this.isAttachmentBoxVisibleInitially ? {} : clear(),
|
|
thread: insert({
|
|
// If the thread was considered to have the activity
|
|
// mixin once, it will have it forever.
|
|
hasActivities: this.hasActivities ? true : undefined,
|
|
id: this.threadId,
|
|
model: this.threadModel,
|
|
}),
|
|
});
|
|
} else if (!this.thread || !this.thread.isTemporary) {
|
|
const currentPartner = this.messaging.currentPartner;
|
|
const message = this.messaging.models['Message'].insert({
|
|
author: currentPartner,
|
|
body: this.env._t("Creating a new record..."),
|
|
id: getMessageNextTemporaryId(),
|
|
isTemporary: true,
|
|
});
|
|
const nextId = getThreadNextTemporaryId();
|
|
this.update({
|
|
attachmentBoxView: clear(),
|
|
thread: insert({
|
|
areAttachmentsLoaded: true,
|
|
id: nextId,
|
|
isTemporary: true,
|
|
model: this.threadModel,
|
|
}),
|
|
});
|
|
this.thread.cache.update({ temporaryMessages: link(message) });
|
|
}
|
|
// continuation of saving new record: restore composer state
|
|
if (this.createNewRecordComposerData) {
|
|
this.update({
|
|
composerView: {
|
|
composer: {
|
|
...this.createNewRecordComposerData,
|
|
thread: this.thread,
|
|
},
|
|
},
|
|
});
|
|
this.createNewRecordDeferred.resolve();
|
|
}
|
|
this.update({
|
|
createNewRecordComposerData: clear(),
|
|
createNewRecordDeferred: clear(),
|
|
});
|
|
},
|
|
/**
|
|
* @private
|
|
*/
|
|
_onThreadIsLoadingAttachmentsChanged() {
|
|
if (!this.thread || !this.thread.isLoadingAttachments) {
|
|
this.update({
|
|
attachmentsLoaderTimer: clear(),
|
|
isShowingAttachmentsLoading: false,
|
|
});
|
|
return;
|
|
}
|
|
if (this.isPreparingAttachmentsLoading || this.isShowingAttachmentsLoading) {
|
|
return;
|
|
}
|
|
this._prepareAttachmentsLoading();
|
|
},
|
|
/**
|
|
* @private
|
|
*/
|
|
_prepareAttachmentsLoading() {
|
|
this.update({ attachmentsLoaderTimer: {} });
|
|
},
|
|
},
|
|
fields: {
|
|
activityBoxView: one('ActivityBoxView', {
|
|
compute() {
|
|
if (this.thread && this.thread.hasActivities && this.thread.activities.length > 0) {
|
|
return {};
|
|
}
|
|
return clear();
|
|
},
|
|
inverse: 'chatter',
|
|
}),
|
|
attachmentBoxView: one('AttachmentBoxView', {
|
|
inverse: 'chatter',
|
|
}),
|
|
attachmentsLoaderTimer: one('Timer', {
|
|
inverse: 'chatterOwnerAsAttachmentsLoader',
|
|
}),
|
|
canPostMessage: attr({
|
|
compute() {
|
|
return Boolean(this.isTemporary || this.hasWriteAccess ||
|
|
(this.hasReadAccess && this.thread && this.thread.canPostOnReadonly));
|
|
},
|
|
}),
|
|
/**
|
|
* States the OWL Chatter component of this chatter.
|
|
*/
|
|
component: attr(),
|
|
/**
|
|
* Determines the composer view used to post in this chatter (if any).
|
|
*/
|
|
composerView: one('ComposerView', {
|
|
inverse: 'chatter',
|
|
}),
|
|
context: attr({
|
|
default: {},
|
|
}),
|
|
dropZoneView: one('DropZoneView', {
|
|
compute() {
|
|
if (!this.thread) {
|
|
return clear();
|
|
}
|
|
if (this.useDragVisibleDropZone.isVisible) {
|
|
return {};
|
|
}
|
|
return clear();
|
|
},
|
|
inverse: 'chatterOwner',
|
|
}),
|
|
fileUploader: one('FileUploader', {
|
|
compute() {
|
|
return this.thread ? {} : clear();
|
|
},
|
|
inverse: 'chatterOwner',
|
|
}),
|
|
followButtonView: one('FollowButtonView', {
|
|
compute() {
|
|
if (this.hasFollowers && this.thread && (!this.thread.channel || this.thread.channel.channel_type !== 'chat')) {
|
|
return {};
|
|
}
|
|
return clear();
|
|
},
|
|
inverse: 'chatterOwner',
|
|
}),
|
|
followerListMenuView: one('FollowerListMenuView', {
|
|
compute() {
|
|
if (this.hasFollowers && this.thread) {
|
|
return {};
|
|
}
|
|
return clear();
|
|
},
|
|
inverse: 'chatterOwner',
|
|
}),
|
|
/**
|
|
* Determines whether `this` should display an activity box.
|
|
*/
|
|
hasActivities: attr({
|
|
default: true,
|
|
}),
|
|
hasExternalBorder: attr({
|
|
default: true,
|
|
}),
|
|
/**
|
|
* Determines whether `this` should display followers menu.
|
|
*/
|
|
hasFollowers: attr({
|
|
default: true,
|
|
}),
|
|
/**
|
|
* Determines whether `this` should display a message list.
|
|
*/
|
|
hasMessageList: attr({
|
|
default: true,
|
|
}),
|
|
/**
|
|
* Whether the message list should manage its scroll.
|
|
* In particular, when the chatter is on the form view's side,
|
|
* then the scroll is managed by the message list.
|
|
* Also, the message list shoud not manage the scroll if it shares it
|
|
* with the rest of the page.
|
|
*/
|
|
hasMessageListScrollAdjust: attr({
|
|
default: false,
|
|
}),
|
|
hasParentReloadOnAttachmentsChanged: attr({
|
|
default: false,
|
|
}),
|
|
hasParentReloadOnFollowersUpdate: attr({
|
|
default: false,
|
|
}),
|
|
hasParentReloadOnMessagePosted: attr({
|
|
default: false,
|
|
}),
|
|
hasReadAccess: attr({
|
|
compute() {
|
|
return Boolean(this.thread && !this.thread.isTemporary && this.thread.hasReadAccess);
|
|
},
|
|
}),
|
|
/**
|
|
* Determines whether `this.thread` should be displayed.
|
|
*/
|
|
hasThreadView: attr({
|
|
compute() {
|
|
return Boolean(this.thread && this.hasMessageList);
|
|
},
|
|
}),
|
|
hasWriteAccess: attr({
|
|
compute() {
|
|
return Boolean(this.thread && !this.thread.isTemporary && this.thread.hasWriteAccess);
|
|
},
|
|
}),
|
|
hasTopbarCloseButton: attr({
|
|
default: false,
|
|
}),
|
|
/**
|
|
* States the id of this chatter. This id does not correspond to any
|
|
* specific value, it is just a unique identifier given by the creator
|
|
* of this record.
|
|
*/
|
|
id: attr({
|
|
identifying: true,
|
|
}),
|
|
/**
|
|
* Determiners whether the attachment box is visible initially.
|
|
*/
|
|
isAttachmentBoxVisibleInitially: attr({
|
|
default: false,
|
|
}),
|
|
isInFormSheetBg: attr({
|
|
default: false,
|
|
}),
|
|
isPreparingAttachmentsLoading: attr({
|
|
compute() {
|
|
return Boolean(this.attachmentsLoaderTimer);
|
|
},
|
|
default: false,
|
|
}),
|
|
isShowingAttachmentsLoading: attr({
|
|
default: false,
|
|
}),
|
|
isTemporary: attr({
|
|
compute() {
|
|
return Boolean(!this.thread || this.thread.isTemporary);
|
|
},
|
|
}),
|
|
saveRecord: attr(),
|
|
scrollPanelRef: attr(),
|
|
/**
|
|
* Determines whether the view should reload after file changed in this chatter,
|
|
* such as from a file upload.
|
|
*/
|
|
shouldReloadParentFromFileChanged: attr({
|
|
compute() {
|
|
return this.hasParentReloadOnAttachmentsChanged;
|
|
},
|
|
}),
|
|
/**
|
|
* Determines the `Thread` that should be displayed by `this`.
|
|
*/
|
|
thread: one('Thread'),
|
|
/**
|
|
* Determines the id of the thread that will be displayed by `this`.
|
|
*/
|
|
threadId: attr(),
|
|
/**
|
|
* Determines the model of the thread that will be displayed by `this`.
|
|
*/
|
|
threadModel: attr(),
|
|
/**
|
|
* States the `ThreadView` displaying `this.thread`.
|
|
*/
|
|
threadView: one('ThreadView', {
|
|
related: 'threadViewer.threadView',
|
|
}),
|
|
/**
|
|
* Determines the `ThreadViewer` managing the display of `this.thread`.
|
|
*/
|
|
threadViewer: one('ThreadViewer', {
|
|
compute() {
|
|
if (!this.thread) {
|
|
return clear();
|
|
}
|
|
return {
|
|
hasThreadView: this.hasThreadView,
|
|
order: 'desc',
|
|
thread: this.thread ? this.thread : clear(),
|
|
};
|
|
},
|
|
inverse: 'chatter',
|
|
}),
|
|
topbar: one('ChatterTopbar', {
|
|
compute() {
|
|
return this.thread ? {} : clear();
|
|
},
|
|
inverse: 'chatter',
|
|
}),
|
|
useDragVisibleDropZone: one('UseDragVisibleDropZone', {
|
|
default: {},
|
|
inverse: 'chatterOwner',
|
|
readonly: true,
|
|
required: true,
|
|
}),
|
|
webRecord: attr(),
|
|
createNewRecordComposerData: attr(),
|
|
createNewRecordDeferred: attr(),
|
|
},
|
|
onChanges: [
|
|
{
|
|
dependencies: ['threadId', 'threadModel'],
|
|
methodName: '_onThreadIdOrThreadModelChanged',
|
|
},
|
|
{
|
|
dependencies: ['thread.isLoadingAttachments'],
|
|
methodName: '_onThreadIsLoadingAttachmentsChanged',
|
|
},
|
|
],
|
|
});
|