Move history logic to MessageList, fix previews not keeping scroll
This commit is contained in:
parent
9926157683
commit
bb0450cb31
@ -54,25 +54,7 @@
|
||||
<div
|
||||
v-else
|
||||
class="chat-content">
|
||||
<div
|
||||
ref="chat"
|
||||
class="chat"
|
||||
>
|
||||
<div :class="['show-more', { show: channel.moreHistoryAvailable }]">
|
||||
<button
|
||||
ref="loadMoreButton"
|
||||
:disabled="channel.historyLoading || !$root.connected"
|
||||
class="btn"
|
||||
@click="onShowMoreClick"
|
||||
>
|
||||
<span v-if="channel.historyLoading">Loading…</span>
|
||||
<span v-else>Show older messages</span>
|
||||
</button>
|
||||
</div>
|
||||
<MessageList
|
||||
:channel="channel"
|
||||
@keepScrollPosition="keepScrollPosition"/>
|
||||
</div>
|
||||
<MessageList :channel="channel"/>
|
||||
<ChatUserList
|
||||
v-if="channel.type === 'channel'"
|
||||
:channel="channel"/>
|
||||
@ -87,8 +69,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
require("intersection-observer");
|
||||
const socket = require("../js/socket");
|
||||
import ParsedMessage from "./ParsedMessage.vue";
|
||||
import MessageList from "./MessageList.vue";
|
||||
import ChatInput from "./ChatInput.vue";
|
||||
@ -119,92 +99,5 @@ export default {
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
"channel.messages"() {
|
||||
this.keepScrollPosition();
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.$nextTick(() => {
|
||||
if (!this.$refs.chat) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (window.IntersectionObserver) {
|
||||
this.historyObserver = new window.IntersectionObserver(this.onLoadButtonObserved, {
|
||||
root: this.$refs.chat,
|
||||
});
|
||||
}
|
||||
|
||||
this.$refs.chat.scrollTop = this.$refs.chat.scrollHeight;
|
||||
});
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
if (this.historyObserver) {
|
||||
this.historyObserver.observe(this.$refs.loadMoreButton);
|
||||
}
|
||||
});
|
||||
},
|
||||
destroyed() {
|
||||
if (this.historyObserver) {
|
||||
this.historyObserver.disconnect();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onShowMoreClick() {
|
||||
let lastMessage = this.channel.messages[0];
|
||||
lastMessage = lastMessage ? lastMessage.id : -1;
|
||||
|
||||
this.$set(this.channel, "historyLoading", true);
|
||||
|
||||
socket.emit("more", {
|
||||
target: this.channel.id,
|
||||
lastId: lastMessage,
|
||||
});
|
||||
},
|
||||
onLoadButtonObserved(entries) {
|
||||
entries.forEach((entry) => {
|
||||
if (!entry.isIntersecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
entry.target.click();
|
||||
});
|
||||
},
|
||||
keepScrollPosition() {
|
||||
// If we are already waiting for the next tick to force scroll position,
|
||||
// we have no reason to perform more checks and set it again in the next tick
|
||||
if (this.isWaitingForNextTick) {
|
||||
return;
|
||||
}
|
||||
|
||||
const el = this.$refs.chat;
|
||||
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (el.scrollHeight - el.scrollTop - el.offsetHeight > 30) {
|
||||
if (this.channel.historyLoading) {
|
||||
const heightOld = el.scrollHeight - el.scrollTop;
|
||||
|
||||
this.isWaitingForNextTick = true;
|
||||
this.$nextTick(() => {
|
||||
this.isWaitingForNextTick = false;
|
||||
el.scrollTop = el.scrollHeight - heightOld;
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.isWaitingForNextTick = true;
|
||||
this.$nextTick(() => {
|
||||
this.isWaitingForNextTick = false;
|
||||
el.scrollTop = el.scrollHeight;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -67,20 +67,18 @@ export default {
|
||||
channel: Object,
|
||||
},
|
||||
watch: {
|
||||
"channel.pendingMessage": {
|
||||
handler: function() {
|
||||
const style = window.getComputedStyle(this.$refs.input);
|
||||
const lineHeight = parseFloat(style.lineHeight, 10) || 1;
|
||||
"channel.pendingMessage"() {
|
||||
const style = window.getComputedStyle(this.$refs.input);
|
||||
const lineHeight = parseFloat(style.lineHeight, 10) || 1;
|
||||
|
||||
// Start by resetting height before computing as scrollHeight does not
|
||||
// decrease when deleting characters
|
||||
resetInputHeight(this);
|
||||
// Start by resetting height before computing as scrollHeight does not
|
||||
// decrease when deleting characters
|
||||
resetInputHeight(this);
|
||||
|
||||
// Use scrollHeight to calculate how many lines there are in input, and ceil the value
|
||||
// because some browsers tend to incorrently round the values when using high density
|
||||
// displays or using page zoom feature
|
||||
this.$refs.input.style.height = Math.ceil(this.$refs.input.scrollHeight / lineHeight) * lineHeight + "px";
|
||||
},
|
||||
// Use scrollHeight to calculate how many lines there are in input, and ceil the value
|
||||
// because some browsers tend to incorrently round the values when using high density
|
||||
// displays or using page zoom feature
|
||||
this.$refs.input.style.height = Math.ceil(this.$refs.input.scrollHeight / lineHeight) * lineHeight + "px";
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
|
@ -125,6 +125,7 @@ export default {
|
||||
name: "LinkPreview",
|
||||
props: {
|
||||
link: Object,
|
||||
keepScrollPosition: Function,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -137,29 +138,36 @@ export default {
|
||||
return this.isContentShown ? "Less" : "More";
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
"link.type"() {
|
||||
this.onPreviewUpdate();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
// Don't display previews while they are loading on the server
|
||||
if (this.link.type === "loading") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Error don't have any media to render
|
||||
if (this.link.type === "error") {
|
||||
this.onPreviewReady();
|
||||
}
|
||||
|
||||
// If link doesn't have a thumbnail, render it
|
||||
if (this.link.type === "link" && !this.link.thumb) {
|
||||
this.onPreviewReady();
|
||||
}
|
||||
this.onPreviewUpdate();
|
||||
},
|
||||
methods: {
|
||||
onPreviewUpdate() {
|
||||
// Error don't have any media to render
|
||||
if (this.link.type === "error") {
|
||||
this.onPreviewReady();
|
||||
}
|
||||
|
||||
// If link doesn't have a thumbnail, render it
|
||||
if (this.link.type === "link" && !this.link.thumb) {
|
||||
this.onPreviewReady();
|
||||
}
|
||||
},
|
||||
onPreviewReady() {
|
||||
const options = require("../js/options");
|
||||
this.$set(this.link, "canDisplay", this.link.type !== "loading" && options.shouldOpenMessagePreview(this.link.type));
|
||||
|
||||
// parent 1 - message - parent 2 - messagelist
|
||||
this.$parent.$parent.$emit("keepScrollPosition");
|
||||
this.keepScrollPosition();
|
||||
|
||||
if (this.link.type !== "link") {
|
||||
return;
|
||||
|
@ -21,6 +21,17 @@
|
||||
:is="messageComponent"
|
||||
:message="message"/>
|
||||
</template>
|
||||
<template v-if="message.type === 'action'">
|
||||
<span class="from"/>
|
||||
<span class="content">
|
||||
<span class="text"><Username :user="message.from"/> <ParsedMessage :message="message"/></span>
|
||||
<LinkPreview
|
||||
v-for="preview in message.previews"
|
||||
:keep-scroll-position="keepScrollPosition"
|
||||
:key="preview.link"
|
||||
:link="preview"/>
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="from">
|
||||
<template v-if="message.from && message.from.nick">
|
||||
@ -29,9 +40,9 @@
|
||||
</span>
|
||||
<span class="content">
|
||||
<span class="text"><ParsedMessage :message="message"/></span>
|
||||
|
||||
<LinkPreview
|
||||
v-for="preview in message.previews"
|
||||
:keep-scroll-position="keepScrollPosition"
|
||||
:key="preview.link"
|
||||
:link="preview"/>
|
||||
</span>
|
||||
@ -54,6 +65,7 @@ export default {
|
||||
components: MessageTypes,
|
||||
props: {
|
||||
message: Object,
|
||||
keepScrollPosition: Function,
|
||||
},
|
||||
computed: {
|
||||
messageComponent() {
|
||||
|
@ -1,47 +1,66 @@
|
||||
<template>
|
||||
<div
|
||||
class="messages"
|
||||
role="log"
|
||||
aria-live="polite"
|
||||
aria-relevant="additions"
|
||||
@copy="onCopy"
|
||||
ref="chat"
|
||||
class="chat"
|
||||
>
|
||||
<template v-for="(message, id) in condensedMessages">
|
||||
<div
|
||||
v-if="shouldDisplayDateMarker(message, id)"
|
||||
:key="message.id + '-date'"
|
||||
:data-time="message.time"
|
||||
:aria-label="message.time | localedate"
|
||||
class="date-marker-container tooltipped tooltipped-s"
|
||||
<div :class="['show-more', { show: channel.moreHistoryAvailable }]">
|
||||
<button
|
||||
ref="loadMoreButton"
|
||||
:disabled="channel.historyLoading || !$root.connected"
|
||||
class="btn"
|
||||
@click="onShowMoreClick"
|
||||
>
|
||||
<div class="date-marker">
|
||||
<span
|
||||
:data-label="message.time | friendlydate"
|
||||
class="date-marker-text"/>
|
||||
<span v-if="channel.historyLoading">Loading…</span>
|
||||
<span v-else>Show older messages</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="messages"
|
||||
role="log"
|
||||
aria-live="polite"
|
||||
aria-relevant="additions"
|
||||
@copy="onCopy"
|
||||
>
|
||||
<template v-for="(message, id) in condensedMessages">
|
||||
<div
|
||||
v-if="shouldDisplayDateMarker(message, id)"
|
||||
:key="message.id + '-date'"
|
||||
:data-time="message.time"
|
||||
:aria-label="message.time | localedate"
|
||||
class="date-marker-container tooltipped tooltipped-s"
|
||||
>
|
||||
<div class="date-marker">
|
||||
<span
|
||||
:data-label="message.time | friendlydate"
|
||||
class="date-marker-text"/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="shouldDisplayUnreadMarker(id)"
|
||||
:key="message.id + '-unread'"
|
||||
class="unread-marker"
|
||||
>
|
||||
<span class="unread-marker-text"/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="shouldDisplayUnreadMarker(id)"
|
||||
:key="message.id + '-unread'"
|
||||
class="unread-marker"
|
||||
>
|
||||
<span class="unread-marker-text"/>
|
||||
</div>
|
||||
|
||||
<MessageCondensed
|
||||
v-if="message.type === 'condensed'"
|
||||
:key="message.id"
|
||||
:messages="message.messages"/>
|
||||
<Message
|
||||
v-else
|
||||
:message="message"
|
||||
:key="message.id"
|
||||
@linkPreviewToggle="onLinkPreviewToggle"/>
|
||||
</template>
|
||||
<MessageCondensed
|
||||
v-if="message.type === 'condensed'"
|
||||
:key="message.id"
|
||||
:messages="message.messages"/>
|
||||
<Message
|
||||
v-else
|
||||
:message="message"
|
||||
:key="message.id"
|
||||
:keep-scroll-position="keepScrollPosition"
|
||||
@linkPreviewToggle="onLinkPreviewToggle"/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
require("intersection-observer");
|
||||
|
||||
const constants = require("../js/constants");
|
||||
const clipboard = require("../js/clipboard");
|
||||
import socket from "../js/socket";
|
||||
@ -94,6 +113,38 @@ export default {
|
||||
return condensed;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
"channel.messages"() {
|
||||
this.keepScrollPosition();
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.$nextTick(() => {
|
||||
if (!this.$refs.chat) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (window.IntersectionObserver) {
|
||||
this.historyObserver = new window.IntersectionObserver(this.onLoadButtonObserved, {
|
||||
root: this.$refs.chat,
|
||||
});
|
||||
}
|
||||
|
||||
this.$refs.chat.scrollTop = this.$refs.chat.scrollHeight;
|
||||
});
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
if (this.historyObserver) {
|
||||
this.historyObserver.observe(this.$refs.loadMoreButton);
|
||||
}
|
||||
});
|
||||
},
|
||||
destroyed() {
|
||||
if (this.historyObserver) {
|
||||
this.historyObserver.disconnect();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
shouldDisplayDateMarker(message, id) {
|
||||
const previousMessage = this.condensedMessages[id - 1];
|
||||
@ -127,6 +178,59 @@ export default {
|
||||
shown: preview.shown,
|
||||
});
|
||||
},
|
||||
onShowMoreClick() {
|
||||
let lastMessage = this.channel.messages[0];
|
||||
lastMessage = lastMessage ? lastMessage.id : -1;
|
||||
|
||||
this.$set(this.channel, "historyLoading", true);
|
||||
|
||||
socket.emit("more", {
|
||||
target: this.channel.id,
|
||||
lastId: lastMessage,
|
||||
});
|
||||
},
|
||||
onLoadButtonObserved(entries) {
|
||||
entries.forEach((entry) => {
|
||||
if (!entry.isIntersecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
entry.target.click();
|
||||
});
|
||||
},
|
||||
keepScrollPosition() {
|
||||
// If we are already waiting for the next tick to force scroll position,
|
||||
// we have no reason to perform more checks and set it again in the next tick
|
||||
if (this.isWaitingForNextTick) {
|
||||
return;
|
||||
}
|
||||
|
||||
const el = this.$refs.chat;
|
||||
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (el.scrollHeight - el.scrollTop - el.offsetHeight > 30) {
|
||||
if (this.channel.historyLoading) {
|
||||
const heightOld = el.scrollHeight - el.scrollTop;
|
||||
|
||||
this.isWaitingForNextTick = true;
|
||||
this.$nextTick(() => {
|
||||
this.isWaitingForNextTick = false;
|
||||
el.scrollTop = el.scrollHeight - heightOld;
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.isWaitingForNextTick = true;
|
||||
this.$nextTick(() => {
|
||||
this.isWaitingForNextTick = false;
|
||||
el.scrollTop = el.scrollHeight;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -1,30 +0,0 @@
|
||||
<template>
|
||||
<span class="content">
|
||||
<Username :user="message.from"/>
|
||||
<span
|
||||
ref="text"
|
||||
class="text"><ParsedMessage :message="message"/></span>
|
||||
<LinkPreview
|
||||
v-for="preview in message.previews"
|
||||
:key="preview.link"
|
||||
:link="preview"/>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ParsedMessage from "../ParsedMessage.vue";
|
||||
import LinkPreview from "../LinkPreview.vue";
|
||||
import Username from "../Username.vue";
|
||||
|
||||
export default {
|
||||
name: "MessageTypeAction",
|
||||
components: {
|
||||
ParsedMessage,
|
||||
LinkPreview,
|
||||
Username,
|
||||
},
|
||||
props: {
|
||||
message: Object,
|
||||
},
|
||||
};
|
||||
</script>
|
@ -1,7 +1,6 @@
|
||||
"use strict";
|
||||
|
||||
const socket = require("../socket");
|
||||
const {shouldOpenMessagePreview} = require("../options");
|
||||
const {vueApp, findChannel} = require("../vue");
|
||||
|
||||
socket.on("msg:preview", function(data) {
|
||||
@ -15,8 +14,6 @@ socket.on("msg:preview", function(data) {
|
||||
const previewIndex = message.previews.findIndex((m) => m.link === data.preview.link);
|
||||
|
||||
if (previewIndex > -1) {
|
||||
data.preview.canDisplay = shouldOpenMessagePreview(data.preview.type);
|
||||
|
||||
vueApp.$set(message.previews, previewIndex, data.preview);
|
||||
}
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user