[FIX] mail: more consistent async handling of scroll feature
task-2333535 task-2388676 closes odoo/odoo#64859 X-original-commit: 981a6e063d3b64efb45cfdf104a618d92ee26d7b Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com> Signed-off-by: Sébastien Theys (seb) <seb@odoo.com>
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
odoo.define('mail/static/src/component_hooks/use_rendered_values/use_rendered_values.js', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { Component } = owl;
|
||||
const { onMounted, onPatched } = owl.hooks;
|
||||
|
||||
/**
|
||||
* This hooks provides support for accessing the values returned by the given
|
||||
* selector at the time of the last render. The values will be updated after
|
||||
* every mount/patch.
|
||||
*
|
||||
* @param {function} selector function that will be executed at the time of the
|
||||
* render and of which the result will be stored for future reference.
|
||||
* @returns {function} function to call to retrieve the last rendered values.
|
||||
*/
|
||||
function useRenderedValues(selector) {
|
||||
const component = Component.current;
|
||||
let renderedValues;
|
||||
let patchedValues;
|
||||
|
||||
const __render = component.__render.bind(component);
|
||||
component.__render = function () {
|
||||
renderedValues = selector();
|
||||
return __render(...arguments);
|
||||
};
|
||||
onMounted(onUpdate);
|
||||
onPatched(onUpdate);
|
||||
function onUpdate() {
|
||||
patchedValues = renderedValues;
|
||||
}
|
||||
return () => patchedValues;
|
||||
}
|
||||
|
||||
return useRenderedValues;
|
||||
|
||||
});
|
||||
@@ -5,6 +5,7 @@ const components = {
|
||||
Message: require('mail/static/src/components/message/message.js'),
|
||||
};
|
||||
const useRefs = require('mail/static/src/component_hooks/use_refs/use_refs.js');
|
||||
const useRenderedValues = require('mail/static/src/component_hooks/use_rendered_values/use_rendered_values.js');
|
||||
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
|
||||
const useUpdate = require('mail/static/src/component_hooks/use_update/use_update.js');
|
||||
|
||||
@@ -37,17 +38,14 @@ class MessageList extends Component {
|
||||
},
|
||||
});
|
||||
this._getRefs = useRefs();
|
||||
useUpdate({ func: () => this._update() });
|
||||
/**
|
||||
* Determine whether the auto-scroll on load is active or not. This
|
||||
* is useful to disable some times, such as when mounting message list
|
||||
* in ASC order: the initial scroll position is at the top of the
|
||||
* conversation, and most of the time the expected initial scroll
|
||||
* position should be at the bottom of the thread. During this time,
|
||||
* the programmatical scrolling should not trigger auto-load messages
|
||||
* on scroll.
|
||||
* States whether there was at least one programmatic scroll since the
|
||||
* last scroll event was handled (which is particularly async due to
|
||||
* throttled behavior).
|
||||
* Useful to avoid loading more messages or to incorrectly disabling the
|
||||
* auto-scroll feature when the scroll was not made by the user.
|
||||
*/
|
||||
this._isAutoLoadOnScrollActive = true;
|
||||
this._isLastScrollProgrammatic = false;
|
||||
/**
|
||||
* Reference of the "load more" item. Useful to trigger load more
|
||||
* on scroll when it becomes visible.
|
||||
@@ -58,6 +56,29 @@ class MessageList extends Component {
|
||||
*/
|
||||
this._willPatchSnapshot = undefined;
|
||||
this._onScrollThrottled = _.throttle(this._onScrollThrottled.bind(this), 100);
|
||||
/**
|
||||
* State used by the component at the time of the render. Useful to
|
||||
* properly handle async code.
|
||||
*/
|
||||
this._lastRenderedValues = useRenderedValues(() => {
|
||||
const threadView = this.threadView;
|
||||
const thread = threadView && threadView.thread;
|
||||
return {
|
||||
componentHintList: threadView ? [...threadView.componentHintList] : [],
|
||||
hasAutoScrollOnMessageReceived: threadView && threadView.hasAutoScrollOnMessageReceived,
|
||||
hasScrollAdjust: this.props.hasScrollAdjust,
|
||||
mainCache: thread && thread.mainCache,
|
||||
order: this.props.order,
|
||||
threadCache: threadView && threadView.threadCache,
|
||||
threadCacheInitialScrollHeight: threadView && threadView.threadCacheInitialScrollHeight,
|
||||
threadCacheInitialScrollPosition: threadView && threadView.threadCacheInitialScrollPosition,
|
||||
threadView,
|
||||
threadViewer: threadView && threadView.threadViewer,
|
||||
};
|
||||
});
|
||||
// useUpdate must be defined after useRenderedValues to guarantee proper
|
||||
// call order
|
||||
useUpdate({ func: () => this._update() });
|
||||
}
|
||||
|
||||
willPatch() {
|
||||
@@ -103,37 +124,33 @@ class MessageList extends Component {
|
||||
* component is patched. This is acceptable when <ThreadView/> has a
|
||||
* fixed height, which is the case for the moment. task-2358066
|
||||
*/
|
||||
async adjustFromComponentHints() {
|
||||
if (!this.threadView) {
|
||||
return;
|
||||
}
|
||||
if (!this.el) {
|
||||
return;
|
||||
}
|
||||
for (const hint of this.threadView.componentHintList) {
|
||||
adjustFromComponentHints() {
|
||||
const { componentHintList, threadView } = this._lastRenderedValues();
|
||||
for (const hint of componentHintList) {
|
||||
switch (hint.type) {
|
||||
case 'change-of-thread-cache':
|
||||
this._adjustFromChangeOfThreadCache(hint);
|
||||
break;
|
||||
case 'home-menu-hidden':
|
||||
this._adjustFromHomeMenuHidden(hint);
|
||||
break;
|
||||
case 'home-menu-shown':
|
||||
this._adjustFromHomeMenuShown(hint);
|
||||
break;
|
||||
case 'messages-loaded':
|
||||
this.threadView.markComponentHintProcessed(hint);
|
||||
// thread just became visible, the goal is to restore its
|
||||
// saved position if it exists or scroll to the end
|
||||
this._adjustScrollFromModel();
|
||||
break;
|
||||
case 'message-received':
|
||||
this._adjustFromMessageReceived(hint);
|
||||
case 'messages-loaded':
|
||||
case 'new-messages-loaded':
|
||||
// messages have been added at the end, either scroll to the
|
||||
// end or keep the current position
|
||||
this._adjustScrollForExtraMessagesAtTheEnd();
|
||||
break;
|
||||
case 'more-messages-loaded':
|
||||
this._adjustFromMoreMessagesLoaded(hint);
|
||||
break;
|
||||
case 'new-messages-loaded':
|
||||
this.threadView.markComponentHintProcessed(hint);
|
||||
// messages have been added at the start, keep the current
|
||||
// position
|
||||
this._adjustScrollForExtraMessagesAtTheStart();
|
||||
break;
|
||||
}
|
||||
if (threadView && threadView.exists()) {
|
||||
threadView.markComponentHintProcessed(hint);
|
||||
}
|
||||
}
|
||||
this._willPatchSnapshot = undefined;
|
||||
}
|
||||
@@ -174,7 +191,8 @@ class MessageList extends Component {
|
||||
* @returns {mail/static/src/components/message/message.js|undefined}
|
||||
*/
|
||||
get mostRecentMessageRef() {
|
||||
if (this.props.order === 'desc') {
|
||||
const { order } = this._lastRenderedValues();
|
||||
if (order === 'desc') {
|
||||
return this.messageRefs[0];
|
||||
}
|
||||
const { length: l, [l - 1]: mostRecentMessageRef } = this.messageRefs;
|
||||
@@ -213,6 +231,7 @@ class MessageList extends Component {
|
||||
* @returns {mail/static/src/components/message/message.js[]}
|
||||
*/
|
||||
get messageRefs() {
|
||||
const { order } = this._lastRenderedValues();
|
||||
const refs = this._getRefs();
|
||||
const ascOrderedMessageRefs = Object.entries(refs)
|
||||
.filter(([refId, ref]) => (
|
||||
@@ -225,7 +244,7 @@ class MessageList extends Component {
|
||||
)
|
||||
.map(([refId, ref]) => ref)
|
||||
.sort((ref1, ref2) => (ref1.message.id < ref2.message.id ? -1 : 1));
|
||||
if (this.props.order === 'desc') {
|
||||
if (order === 'desc') {
|
||||
return ascOrderedMessageRefs.reverse();
|
||||
}
|
||||
return ascOrderedMessageRefs;
|
||||
@@ -245,11 +264,12 @@ class MessageList extends Component {
|
||||
/**
|
||||
* @param {integer} value
|
||||
*/
|
||||
async setScrollTop(value) {
|
||||
this._isAutoLoadOnScrollActive = false;
|
||||
setScrollTop(value) {
|
||||
if (this.el.scrollTop === value) {
|
||||
return;
|
||||
}
|
||||
this._isLastScrollProgrammatic = true;
|
||||
this.el.scrollTop = value;
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
this._isAutoLoadOnScrollActive = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -316,158 +336,88 @@ class MessageList extends Component {
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} hint
|
||||
*/
|
||||
async _adjustFromChangeOfThreadCache(hint) {
|
||||
const threadCache = this.threadView.threadCache;
|
||||
if (!threadCache.isLoaded) {
|
||||
_adjustScrollForExtraMessagesAtTheEnd() {
|
||||
const {
|
||||
hasAutoScrollOnMessageReceived,
|
||||
hasScrollAdjust,
|
||||
order,
|
||||
} = this._lastRenderedValues();
|
||||
if (!this.el || !hasScrollAdjust) {
|
||||
return;
|
||||
}
|
||||
let isProcessed = false;
|
||||
if (threadCache.messages.length > 0) {
|
||||
if (this.threadView.threadCacheInitialScrollPosition !== undefined) {
|
||||
if (this.props.hasScrollAdjust) {
|
||||
if (this.el.scrollHeight === this.threadView.threadCacheInitialScrollHeight) {
|
||||
this.el.scrollTop = this.threadView.threadCacheInitialScrollPosition;
|
||||
isProcessed = true;
|
||||
}
|
||||
} else {
|
||||
isProcessed = true;
|
||||
}
|
||||
} else {
|
||||
const lastMessage = threadCache.lastMessage;
|
||||
if (this.messageRefFromId(lastMessage.id)) {
|
||||
if (this.props.hasScrollAdjust) {
|
||||
this._scrollToMostRecentMessage();
|
||||
}
|
||||
isProcessed = true;
|
||||
}
|
||||
if (!hasAutoScrollOnMessageReceived) {
|
||||
if (order === 'desc' && this._willPatchSnapshot) {
|
||||
const { scrollHeight, scrollTop } = this._willPatchSnapshot;
|
||||
this.setScrollTop(this.el.scrollHeight - scrollHeight + scrollTop);
|
||||
}
|
||||
} else {
|
||||
isProcessed = true;
|
||||
}
|
||||
if (isProcessed) {
|
||||
this.threadView.markComponentHintProcessed(hint);
|
||||
return;
|
||||
}
|
||||
this._scrollToEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} hint
|
||||
*/
|
||||
_adjustFromChatWindowUnfolded(hint) {
|
||||
if (this._adjustScrollFromModel()) {
|
||||
this.threadView.markComponentHintProcessed(hint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} hint
|
||||
*/
|
||||
_adjustFromHomeMenuHidden(hint) {
|
||||
if (this._adjustScrollFromModel()) {
|
||||
this.threadView.markComponentHintProcessed(hint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} hint
|
||||
*/
|
||||
_adjustFromHomeMenuShown(hint) {
|
||||
if (this._adjustScrollFromModel()) {
|
||||
this.threadView.markComponentHintProcessed(hint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} hint
|
||||
*/
|
||||
async _adjustFromMessageReceived(hint) {
|
||||
const threadCache = this.threadView.threadCache;
|
||||
if (!threadCache.isLoaded) {
|
||||
return;
|
||||
}
|
||||
const { message } = hint.data;
|
||||
if (!threadCache.messages.includes(message)) {
|
||||
return;
|
||||
}
|
||||
if (!this.messageRefFromId(message.id)) {
|
||||
return;
|
||||
}
|
||||
if (!this.props.hasScrollAdjust) {
|
||||
this.threadView.markComponentHintProcessed(hint);
|
||||
return;
|
||||
}
|
||||
if (!this.threadView.hasAutoScrollOnMessageReceived) {
|
||||
this.threadView.markComponentHintProcessed(hint);
|
||||
return;
|
||||
}
|
||||
_adjustScrollForExtraMessagesAtTheStart() {
|
||||
const {
|
||||
hasScrollAdjust,
|
||||
order,
|
||||
} = this._lastRenderedValues();
|
||||
if (
|
||||
this.threadView.lastVisibleMessage &&
|
||||
(message.id < this.threadView.lastVisibleMessage.id)
|
||||
!this.el ||
|
||||
!hasScrollAdjust ||
|
||||
!this._willPatchSnapshot ||
|
||||
order === 'desc'
|
||||
) {
|
||||
this.threadView.markComponentHintProcessed(hint);
|
||||
return;
|
||||
}
|
||||
await this._scrollToMessage(message.id);
|
||||
this.threadView.markComponentHintProcessed(hint);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} hint
|
||||
*/
|
||||
_adjustFromMoreMessagesLoaded(hint) {
|
||||
if (!this._willPatchSnapshot) {
|
||||
this.threadView.markComponentHintProcessed(hint);
|
||||
return;
|
||||
}
|
||||
const { scrollHeight, scrollTop } = this._willPatchSnapshot;
|
||||
if (this.props.order === 'asc' && this.props.hasScrollAdjust) {
|
||||
this.el.scrollTop = this.el.scrollHeight - scrollHeight + scrollTop;
|
||||
}
|
||||
this.threadView.markComponentHintProcessed(hint);
|
||||
this.setScrollTop(this.el.scrollHeight - scrollHeight + scrollTop);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {boolean} whether the adjustment should be considered processed
|
||||
*/
|
||||
_adjustScrollFromModel() {
|
||||
if (
|
||||
this.threadView.threadCacheInitialScrollPosition !== undefined &&
|
||||
this.props.hasScrollAdjust
|
||||
) {
|
||||
if (this.el.scrollHeight === this.threadView.threadCacheInitialScrollHeight) {
|
||||
this.el.scrollTop = this.threadView.threadCacheInitialScrollPosition;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
const {
|
||||
hasScrollAdjust,
|
||||
threadCacheInitialScrollHeight,
|
||||
threadCacheInitialScrollPosition,
|
||||
} = this._lastRenderedValues();
|
||||
if (!this.el || !hasScrollAdjust) {
|
||||
return;
|
||||
}
|
||||
return true;
|
||||
if (
|
||||
threadCacheInitialScrollPosition !== undefined &&
|
||||
this.el.scrollHeight === threadCacheInitialScrollHeight
|
||||
) {
|
||||
this.setScrollTop(threadCacheInitialScrollPosition);
|
||||
return;
|
||||
}
|
||||
this._scrollToEnd();
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_checkMostRecentMessageIsVisible() {
|
||||
if (!this.threadView) {
|
||||
const {
|
||||
mainCache,
|
||||
threadCache,
|
||||
threadView,
|
||||
} = this._lastRenderedValues();
|
||||
if (!threadView || !threadView.exists()) {
|
||||
return;
|
||||
}
|
||||
const thread = this.threadView.thread;
|
||||
const threadCache = this.threadView.threadCache;
|
||||
const lastMessageIsVisible =
|
||||
threadCache &&
|
||||
threadCache.messages.length > 0 &&
|
||||
this.mostRecentMessageRef &&
|
||||
threadCache === thread.mainCache &&
|
||||
threadCache === mainCache &&
|
||||
this.mostRecentMessageRef.isPartiallyVisible();
|
||||
if (lastMessageIsVisible) {
|
||||
this.threadView.handleVisibleMessage(this.mostRecentMessageRef.message);
|
||||
threadView.handleVisibleMessage(this.mostRecentMessageRef.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,55 +440,29 @@ class MessageList extends Component {
|
||||
* @private
|
||||
*/
|
||||
_loadMore() {
|
||||
this.threadView.threadCache.loadMoreMessages();
|
||||
const { threadCache } = this._lastRenderedValues();
|
||||
if (!threadCache || !threadCache.exists()) {
|
||||
return;
|
||||
}
|
||||
threadCache.loadMoreMessages();
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls to the end of the list.
|
||||
*
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async _scrollToMostRecentMessage() {
|
||||
if (!this.mostRecentMessageRef) {
|
||||
return;
|
||||
}
|
||||
this._isAutoLoadOnScrollActive = false;
|
||||
await this.mostRecentMessageRef.scrollIntoView();
|
||||
if (!this.el) {
|
||||
this._isAutoLoadOnScrollActive = true;
|
||||
return;
|
||||
}
|
||||
this.el.scrollTop = this.props.order === 'asc'
|
||||
? this.el.scrollTop + 15
|
||||
: this.el.scrollTop - 15;
|
||||
this._isAutoLoadOnScrollActive = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {integer} messageId
|
||||
*/
|
||||
async _scrollToMessage(messageId) {
|
||||
const messageRef = this.messageRefFromId(messageId);
|
||||
if (!messageRef) {
|
||||
return;
|
||||
}
|
||||
this._isAutoLoadOnScrollActive = false;
|
||||
await messageRef.scrollIntoView();
|
||||
if (!this.el) {
|
||||
this._isAutoLoadOnScrollActive = true;
|
||||
return;
|
||||
}
|
||||
this.el.scrollTop = this.props.order === 'asc'
|
||||
? this.el.scrollTop + 15
|
||||
: this.el.scrollTop - 15;
|
||||
this._isAutoLoadOnScrollActive = true;
|
||||
_scrollToEnd() {
|
||||
const { order } = this._lastRenderedValues();
|
||||
this.setScrollTop(order === 'asc' ? this.el.scrollHeight - this.el.clientHeight : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_update() {
|
||||
this.adjustFromComponentHints();
|
||||
this._checkMostRecentMessageIsVisible();
|
||||
this.adjustFromComponentHints();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -559,14 +483,6 @@ class MessageList extends Component {
|
||||
* @param {ScrollEvent} ev
|
||||
*/
|
||||
onScroll(ev) {
|
||||
if (!this.threadView) {
|
||||
return;
|
||||
}
|
||||
// Clear pending hints to prevent them from potentially overriding the
|
||||
// new scroll position.
|
||||
for (const hint of this.threadView.componentHintList) {
|
||||
this.threadView.markComponentHintProcessed(hint);
|
||||
}
|
||||
this._onScrollThrottled(ev);
|
||||
}
|
||||
|
||||
@@ -575,35 +491,41 @@ class MessageList extends Component {
|
||||
* @param {ScrollEvent} ev
|
||||
*/
|
||||
_onScrollThrottled(ev) {
|
||||
const {
|
||||
order,
|
||||
threadCache,
|
||||
threadView,
|
||||
threadViewer,
|
||||
} = this._lastRenderedValues();
|
||||
if (!this.el) {
|
||||
// could be unmounted in the meantime (due to throttled behavior)
|
||||
return;
|
||||
}
|
||||
if (!this.threadView || !this.threadView.threadViewer) {
|
||||
return;
|
||||
}
|
||||
const scrollTop = this.el.scrollTop;
|
||||
this.env.messagingBus.trigger('o-component-message-list-scrolled', {
|
||||
scrollTop,
|
||||
threadViewer: this.threadView.threadViewer,
|
||||
threadViewer,
|
||||
});
|
||||
// Margin to compensate for inaccurate scrolling to bottom.
|
||||
const margin = 4;
|
||||
// Automatically scroll to new received messages only when the list is
|
||||
// currently fully scrolled.
|
||||
const hasAutoScrollOnMessageReceived = (this.props.order === 'asc')
|
||||
? scrollTop >= this.el.scrollHeight - this.el.clientHeight - margin
|
||||
: scrollTop <= margin;
|
||||
this.threadView.update({ hasAutoScrollOnMessageReceived });
|
||||
this.threadView.threadViewer.saveThreadCacheScrollHeightAsInitial(this.el.scrollHeight);
|
||||
this.threadView.threadViewer.saveThreadCacheScrollPositionsAsInitial(scrollTop);
|
||||
if (!this._isAutoLoadOnScrollActive) {
|
||||
return;
|
||||
if (!this._isLastScrollProgrammatic && threadView && threadView.exists()) {
|
||||
// Margin to compensate for inaccurate scrolling to bottom and height
|
||||
// flicker due height change of composer area.
|
||||
const margin = 30;
|
||||
// Automatically scroll to new received messages only when the list is
|
||||
// currently fully scrolled.
|
||||
const hasAutoScrollOnMessageReceived = (order === 'asc')
|
||||
? scrollTop >= this.el.scrollHeight - this.el.clientHeight - margin
|
||||
: scrollTop <= margin;
|
||||
threadView.update({ hasAutoScrollOnMessageReceived });
|
||||
}
|
||||
if (this._isLoadMoreVisible()) {
|
||||
if (threadViewer && threadViewer.exists()) {
|
||||
threadViewer.saveThreadCacheScrollHeightAsInitial(this.el.scrollHeight, threadCache);
|
||||
threadViewer.saveThreadCacheScrollPositionsAsInitial(scrollTop, threadCache);
|
||||
}
|
||||
if (!this._isLastScrollProgrammatic && this._isLoadMoreVisible()) {
|
||||
this._loadMore();
|
||||
}
|
||||
this._checkMostRecentMessageIsVisible();
|
||||
this._isLastScrollProgrammatic = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -45,17 +45,8 @@ function factory(dependencies) {
|
||||
* @param {Object} hint
|
||||
*/
|
||||
markComponentHintProcessed(hint) {
|
||||
let filterFun;
|
||||
switch (hint.type) {
|
||||
case 'message-received':
|
||||
filterFun = h => h.type !== hint.type && h.message !== hint.data.message;
|
||||
break;
|
||||
default:
|
||||
filterFun = h => h.type !== hint.type;
|
||||
break;
|
||||
}
|
||||
this.update({
|
||||
componentHintList: this.componentHintList.filter(filterFun),
|
||||
componentHintList: this.componentHintList.filter(h => h !== hint),
|
||||
});
|
||||
this.env.messagingBus.trigger('o-thread-view-hint-processed', {
|
||||
hint,
|
||||
@@ -143,6 +134,8 @@ function factory(dependencies) {
|
||||
* @private
|
||||
*/
|
||||
_onThreadCacheChanged() {
|
||||
// clear obsolete hints
|
||||
this.update({ componentHintList: clear() });
|
||||
this.addComponentHint('change-of-thread-cache');
|
||||
if (this.threadCache) {
|
||||
this.threadCache.update({ isCacheRefreshRequested: true });
|
||||
@@ -236,7 +229,9 @@ function factory(dependencies) {
|
||||
* a new message. Detection of new message is done through the component
|
||||
* hint `message-received`.
|
||||
*/
|
||||
hasAutoScrollOnMessageReceived: attr(),
|
||||
hasAutoScrollOnMessageReceived: attr({
|
||||
default: true,
|
||||
}),
|
||||
/**
|
||||
* Last message in the context of the currently displayed thread cache.
|
||||
*/
|
||||
|
||||
@@ -14,9 +14,11 @@ function factory(dependencies) {
|
||||
|
||||
/**
|
||||
* @param {integer} scrollHeight
|
||||
* @param {mail.thread_cache} threadCache
|
||||
*/
|
||||
saveThreadCacheScrollHeightAsInitial(scrollHeight) {
|
||||
if (!this.threadCache) {
|
||||
saveThreadCacheScrollHeightAsInitial(scrollHeight, threadCache) {
|
||||
threadCache = threadCache || this.threadCache;
|
||||
if (!threadCache) {
|
||||
return;
|
||||
}
|
||||
if (this.chatter) {
|
||||
@@ -27,16 +29,18 @@ function factory(dependencies) {
|
||||
}
|
||||
this.update({
|
||||
threadCacheInitialScrollHeights: Object.assign({}, this.threadCacheInitialScrollHeights, {
|
||||
[this.threadCache.localId]: scrollHeight,
|
||||
[threadCache.localId]: scrollHeight,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {integer} scrollTop
|
||||
* @param {mail.thread_cache} threadCache
|
||||
*/
|
||||
saveThreadCacheScrollPositionsAsInitial(scrollTop) {
|
||||
if (!this.threadCache) {
|
||||
saveThreadCacheScrollPositionsAsInitial(scrollTop, threadCache) {
|
||||
threadCache = threadCache || this.threadCache;
|
||||
if (!threadCache) {
|
||||
return;
|
||||
}
|
||||
if (this.chatter) {
|
||||
@@ -47,7 +51,7 @@ function factory(dependencies) {
|
||||
}
|
||||
this.update({
|
||||
threadCacheInitialScrollPositions: Object.assign({}, this.threadCacheInitialScrollPositions, {
|
||||
[this.threadCache.localId]: scrollTop,
|
||||
[threadCache.localId]: scrollTop,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
<script type="text/javascript" src="/mail/static/src/bugfix/bugfix.js"></script>
|
||||
<script type="text/javascript" src="/mail/static/src/component_hooks/use_drag_visible_dropzone/use_drag_visible_dropzone.js"></script>
|
||||
<script type="text/javascript" src="/mail/static/src/component_hooks/use_refs/use_refs.js"></script>
|
||||
<script type="text/javascript" src="/mail/static/src/component_hooks/use_rendered_values/use_rendered_values.js"/>
|
||||
<script type="text/javascript" src="/mail/static/src/component_hooks/use_store/use_store.js"></script>
|
||||
<script type="text/javascript" src="/mail/static/src/component_hooks/use_update/use_update.js"/>
|
||||
<script type="text/javascript" src="/mail/static/src/components/activity/activity.js"></script>
|
||||
|
||||
Reference in New Issue
Block a user