mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-21 03:32:05 +02:00
Initial commit: Core packages
This commit is contained in:
commit
12c29a983b
9512 changed files with 8379910 additions and 0 deletions
551
odoo-bringout-oca-ocb-mail/mail/static/src/models/chatter.js
Normal file
551
odoo-bringout-oca-ocb-mail/mail/static/src/models/chatter.js
Normal file
|
|
@ -0,0 +1,551 @@
|
|||
/** @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',
|
||||
},
|
||||
],
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue