Use flex-direction: column-reverse
On the `<converse-chat-content>` element. This removes the need for all the manual scrolling. Firefox finally supports this feature. Unfortunately Firefox ESR doesn't yet, but I can't wait anymore.
This commit is contained in:
parent
9bcf5f2947
commit
825e2643ae
@ -138,13 +138,6 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
converse-chat-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100%;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
converse-chat-message {
|
converse-chat-message {
|
||||||
.spinner {
|
.spinner {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -75,7 +75,6 @@ export default class ChatView extends BaseChatView {
|
|||||||
|
|
||||||
afterShown () {
|
afterShown () {
|
||||||
this.model.setChatState(_converse.ACTIVE);
|
this.model.setChatState(_converse.ACTIVE);
|
||||||
this.scrollDown();
|
|
||||||
this.maybeFocus();
|
this.maybeFocus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,6 @@ class HeadlinesView extends BaseChatView {
|
|||||||
|
|
||||||
await this.model.messages.fetched;
|
await this.model.messages.fetched;
|
||||||
this.model.maybeShow();
|
this.model.maybeShow();
|
||||||
this.scrollDown();
|
|
||||||
/**
|
/**
|
||||||
* Triggered once the {@link _converse.HeadlinesBoxView} has been initialized
|
* Triggered once the {@link _converse.HeadlinesBoxView} has been initialized
|
||||||
* @event _converse#headlinesBoxViewInitialized
|
* @event _converse#headlinesBoxViewInitialized
|
||||||
|
@ -21,15 +21,12 @@ export default class MUCView extends BaseChatView {
|
|||||||
|
|
||||||
this.listenTo(_converse, 'windowStateChanged', this.onWindowStateChanged);
|
this.listenTo(_converse, 'windowStateChanged', this.onWindowStateChanged);
|
||||||
this.listenTo(this.model, 'change:composing_spoiler', this.requestUpdateMessageForm);
|
this.listenTo(this.model, 'change:composing_spoiler', this.requestUpdateMessageForm);
|
||||||
this.listenTo(this.model, 'change:hidden', () => this.afterShown());
|
|
||||||
this.listenTo(this.model, 'change:minimized', () => this.afterShown());
|
|
||||||
this.listenTo(this.model, 'show', this.show);
|
this.listenTo(this.model, 'show', this.show);
|
||||||
this.listenTo(this.model.session, 'change:connection_status', this.updateAfterTransition);
|
this.listenTo(this.model.session, 'change:connection_status', this.updateAfterTransition);
|
||||||
this.listenTo(this.model.session, 'change:view', this.requestUpdate);
|
this.listenTo(this.model.session, 'change:view', this.requestUpdate);
|
||||||
|
|
||||||
this.updateAfterTransition();
|
this.updateAfterTransition();
|
||||||
this.model.maybeShow();
|
this.model.maybeShow();
|
||||||
this.scrollDown();
|
|
||||||
/**
|
/**
|
||||||
* Triggered once a { @link _converse.ChatRoomView } has been opened
|
* Triggered once a { @link _converse.ChatRoomView } has been opened
|
||||||
* @event _converse#chatRoomViewInitialized
|
* @event _converse#chatRoomViewInitialized
|
||||||
@ -43,17 +40,6 @@ export default class MUCView extends BaseChatView {
|
|||||||
return tpl_muc({ 'model': this.model });
|
return tpl_muc({ 'model': this.model });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback method that gets called after the chat has become visible.
|
|
||||||
* @private
|
|
||||||
* @method _converse.ChatRoomView#afterShown
|
|
||||||
*/
|
|
||||||
afterShown () {
|
|
||||||
if (!this.model.get('hidden') && !this.model.get('minimized')) {
|
|
||||||
this.scrollDown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes this chat, which implies leaving the MUC as well.
|
* Closes this chat, which implies leaving the MUC as well.
|
||||||
* @private
|
* @private
|
||||||
|
@ -6,6 +6,8 @@ import { html } from 'lit';
|
|||||||
import { onScrolledDown } from './utils.js';
|
import { onScrolledDown } from './utils.js';
|
||||||
import { safeSave } from '@converse/headless/utils/core.js';
|
import { safeSave } from '@converse/headless/utils/core.js';
|
||||||
|
|
||||||
|
import './styles/chat-content.scss';
|
||||||
|
|
||||||
|
|
||||||
export default class ChatContent extends CustomElement {
|
export default class ChatContent extends CustomElement {
|
||||||
|
|
||||||
@ -17,12 +19,11 @@ export default class ChatContent extends CustomElement {
|
|||||||
|
|
||||||
connectedCallback () {
|
connectedCallback () {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
this.debouncedMaintainScroll = debounce(this.maintainScrollPosition, 100);
|
|
||||||
this.markScrolled = debounce(this._markScrolled, 50);
|
this.markScrolled = debounce(this._markScrolled, 50);
|
||||||
|
|
||||||
this.model = _converse.chatboxes.get(this.jid);
|
this.model = _converse.chatboxes.get(this.jid);
|
||||||
this.listenTo(this.model, 'change:hidden_occupants', this.requestUpdate);
|
this.listenTo(this.model, 'change:hidden_occupants', this.requestUpdate);
|
||||||
this.listenTo(this.model, 'change:scrolled', this.requestUpdate);
|
this.listenTo(this.model, 'change:scrolled', this.scrollDown);
|
||||||
this.listenTo(this.model.messages, 'add', this.requestUpdate);
|
this.listenTo(this.model.messages, 'add', this.requestUpdate);
|
||||||
this.listenTo(this.model.messages, 'change', this.requestUpdate);
|
this.listenTo(this.model.messages, 'change', this.requestUpdate);
|
||||||
this.listenTo(this.model.messages, 'remove', this.requestUpdate);
|
this.listenTo(this.model.messages, 'remove', this.requestUpdate);
|
||||||
@ -34,55 +35,22 @@ export default class ChatContent extends CustomElement {
|
|||||||
if (this.model.occupants) {
|
if (this.model.occupants) {
|
||||||
this.listenTo(this.model.occupants, 'change', this.requestUpdate);
|
this.listenTo(this.model.occupants, 'change', this.requestUpdate);
|
||||||
}
|
}
|
||||||
// We jot down whether we were scrolled down before rendering, because when an
|
|
||||||
// image loads, it triggers 'scroll' and the chat will be marked as scrolled,
|
|
||||||
// which is technically true, but not what we want because the user
|
|
||||||
// didn't initiate the scrolling.
|
|
||||||
this.was_scrolled_up = this.model.get('scrolled');
|
|
||||||
this.addEventListener('imageLoaded', () => {
|
|
||||||
this.debouncedMaintainScroll();
|
|
||||||
});
|
|
||||||
this.addEventListener('scroll', () => this.markScrolled());
|
this.addEventListener('scroll', () => this.markScrolled());
|
||||||
this.initIntersectionObserver();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
// This element has "flex-direction: reverse", so elements here are
|
||||||
|
// shown in reverse order.
|
||||||
return html`
|
return html`
|
||||||
${ this.model.ui?.get('chat-content-spinner-top') ? html`<span class="spinner fa fa-spinner centered"></span>` : '' }
|
<div class="chat-content__notifications">${this.model.getNotificationsText()}</div>
|
||||||
<converse-message-history
|
<converse-message-history
|
||||||
.model=${this.model}
|
.model=${this.model}
|
||||||
.observer=${this.observer}
|
|
||||||
.messages=${[...this.model.messages.models]}>
|
.messages=${[...this.model.messages.models]}>
|
||||||
</converse-message-history>
|
</converse-message-history>
|
||||||
<div class="chat-content__notifications">${this.model.getNotificationsText()}</div>
|
${ this.model.ui?.get('chat-content-spinner-top') ? html`<span class="spinner fa fa-spinner centered"></span>` : '' }
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
updated () {
|
|
||||||
const scrolled = this.model.get('scrolled');
|
|
||||||
if (this.was_scrolled_up === scrolled) {
|
|
||||||
this.debouncedMaintainScroll();
|
|
||||||
} else {
|
|
||||||
this.was_scrolled_up = scrolled;
|
|
||||||
if (!this.scrolled) {
|
|
||||||
this.scrollDown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
initIntersectionObserver () {
|
|
||||||
if (this.observer) {
|
|
||||||
this.observer.disconnect();
|
|
||||||
} else {
|
|
||||||
const options = {
|
|
||||||
root: this,
|
|
||||||
threshold: [0.1]
|
|
||||||
}
|
|
||||||
const handler = ev => this.setAnchoredMessage(ev);
|
|
||||||
this.observer = new IntersectionObserver(handler, options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the chat content is scrolled up or down.
|
* Called when the chat content is scrolled up or down.
|
||||||
* We want to record when the user has scrolled away from
|
* We want to record when the user has scrolled away from
|
||||||
@ -95,11 +63,14 @@ export default class ChatContent extends CustomElement {
|
|||||||
*/
|
*/
|
||||||
_markScrolled () {
|
_markScrolled () {
|
||||||
let scrolled = true;
|
let scrolled = true;
|
||||||
const is_at_bottom = this.scrollTop + this.clientHeight >= this.scrollHeight;
|
const is_at_bottom = this.scrollTop === 0;
|
||||||
|
const is_at_top =
|
||||||
|
Math.ceil(this.clientHeight-this.scrollTop) >= (this.scrollHeight-Math.ceil(this.scrollHeight/20));
|
||||||
|
|
||||||
if (is_at_bottom) {
|
if (is_at_bottom) {
|
||||||
scrolled = false;
|
scrolled = false;
|
||||||
onScrolledDown(this.model);
|
onScrolledDown(this.model);
|
||||||
} else if (this.scrollTop === 0) {
|
} else if (is_at_top) {
|
||||||
/**
|
/**
|
||||||
* Triggered once the chat's message area has been scrolled to the top
|
* Triggered once the chat's message area has been scrolled to the top
|
||||||
* @event _converse#chatBoxScrolledUp
|
* @event _converse#chatBoxScrolledUp
|
||||||
@ -113,31 +84,15 @@ export default class ChatContent extends CustomElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setAnchoredMessage (entries) {
|
scrollDown () {
|
||||||
if (!this.model?.ui || this.model.ui.get('chat-content-spinner-top')) {
|
if (this.model.get('scrolled')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
entries = entries.filter(e => e.isIntersecting);
|
|
||||||
const current = entries.reduce((p, c) => c.boundingClientRect.y >= (p?.boundingClientRect.y || 0) ? c : p, null);
|
|
||||||
if (current) {
|
|
||||||
this.anchored_message = current.target;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
maintainScrollPosition () {
|
|
||||||
if (this.was_scrolled_up) {
|
|
||||||
this.anchored_message?.scrollIntoView(true);
|
|
||||||
} else {
|
|
||||||
this.scrollDown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
scrollDown () {
|
|
||||||
if (this.scrollTo) {
|
if (this.scrollTo) {
|
||||||
const behavior = this.scrollTop ? 'smooth' : 'auto';
|
const behavior = this.scrollTop ? 'smooth' : 'auto';
|
||||||
this.scrollTo({ 'top': this.scrollHeight, behavior });
|
this.scrollTo({ 'top': 0, behavior });
|
||||||
} else {
|
} else {
|
||||||
this.scrollTop = this.scrollHeight;
|
this.scrollTop = 0;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Triggered once the converse-chat-content element has been scrolled down to the bottom.
|
* Triggered once the converse-chat-content element has been scrolled down to the bottom.
|
||||||
|
@ -51,7 +51,6 @@ export default class MessageHistory extends CustomElement {
|
|||||||
static get properties () {
|
static get properties () {
|
||||||
return {
|
return {
|
||||||
model: { type: Object },
|
model: { type: Object },
|
||||||
observer: { type: Object },
|
|
||||||
messages: { type: Array }
|
messages: { type: Array }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -68,7 +67,6 @@ export default class MessageHistory extends CustomElement {
|
|||||||
const day = getDayIndicator(model);
|
const day = getDayIndicator(model);
|
||||||
const templates = day ? [day] : [];
|
const templates = day ? [day] : [];
|
||||||
const message = html`<converse-chat-message
|
const message = html`<converse-chat-message
|
||||||
.observer=${this.observer}
|
|
||||||
jid="${this.model.get('jid')}"
|
jid="${this.model.get('jid')}"
|
||||||
mid="${model.get('id')}"></converse-chat-message>`
|
mid="${model.get('id')}"></converse-chat-message>`
|
||||||
|
|
||||||
|
@ -24,8 +24,7 @@ export default class Message extends CustomElement {
|
|||||||
static get properties () {
|
static get properties () {
|
||||||
return {
|
return {
|
||||||
jid: { type: String },
|
jid: { type: String },
|
||||||
mid: { type: String },
|
mid: { type: String }
|
||||||
observer: { type: Object }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,10 +59,6 @@ export default class Message extends CustomElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
firstUpdated () {
|
|
||||||
this.observer.observe(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
getProps () {
|
getProps () {
|
||||||
return Object.assign(
|
return Object.assign(
|
||||||
this.model.toJSON(),
|
this.model.toJSON(),
|
||||||
|
8
src/shared/chat/styles/chat-content.scss
Normal file
8
src/shared/chat/styles/chat-content.scss
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
converse-chat-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
height: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user