Lots of fixes to "media threads"
This commit is contained in:
parent
fe081edc62
commit
8bcceafcff
23 changed files with 867 additions and 333 deletions
|
|
@ -32,7 +32,7 @@
|
|||
:attachments="currentFileInputs"
|
||||
/>
|
||||
|
||||
<div v-if="!useVoiceMode && !useFileModeNonAdmin" class="chat-content flex-grow-1 flex-shrink-1" ref="chatContainer"
|
||||
<div v-if="!useVoiceMode && !useFileModeNonAdmin" :class="{'chat-content': true, 'flex-grow-1': true, 'flex-shrink-1': true, 'invisible': !initialLoadDone}" ref="chatContainer"
|
||||
v-on:scroll="onScroll" @click="closeContextMenusIfOpen">
|
||||
<div ref="messageOperationsStrut" class="message-operations-strut">
|
||||
<message-operations ref="messageOperations" :style="opStyle" :emojis="recentEmojis" v-on:close="
|
||||
|
|
@ -42,8 +42,8 @@
|
|||
v-on:addreply="addReply(selectedEvent)" v-on:edit="edit(selectedEvent)" v-on:redact="redact(selectedEvent)"
|
||||
v-on:download="download(selectedEvent)" v-on:more="
|
||||
isEmojiQuickReaction= true
|
||||
showMoreMessageOperations($event)
|
||||
" :originalEvent="selectedEvent" />
|
||||
showMoreMessageOperations({event: selectedEvent, anchor: $event.anchor})
|
||||
" :originalEvent="selectedEvent" :timelineSet="timelineSet" />
|
||||
</div>
|
||||
|
||||
<div ref="avatarOperationsStrut" class="avatar-operations-strut">
|
||||
|
|
@ -87,6 +87,7 @@
|
|||
isEmojiQuickReaction = true
|
||||
showMoreMessageOperations({event: event, anchor: $event.anchor})
|
||||
"
|
||||
v-on:layout-change="onLayoutChange"
|
||||
/>
|
||||
<!-- <div v-if="debugging" style="user-select:text">EventID: {{ event.getId() }}</div> -->
|
||||
<!-- <div v-if="debugging" style="user-select:text">Event: {{ JSON.stringify(event) }}</div> -->
|
||||
|
|
@ -114,6 +115,7 @@
|
|||
<div v-if="replyToContentType === 'm.text'" class="reply-text" :title="replyToEvent.getContent().body">
|
||||
{{ replyToEvent.getContent().body | latestReply }}
|
||||
</div>
|
||||
<div v-if="replyToContentType === 'm.thread'">{{ replyToThreadMessage }}</div>
|
||||
<div v-if="replyToContentType === 'm.image'">{{ $t("message.reply_image") }}</div>
|
||||
<div v-if="replyToContentType === 'm.audio'">{{ $t("message.reply_audio_message") }}</div>
|
||||
<div v-if="replyToContentType === 'm.video'">{{ $t("message.reply_video") }}</div>
|
||||
|
|
@ -533,7 +535,7 @@ export default {
|
|||
if (contentArr[0] === "") {
|
||||
contentArr.shift();
|
||||
}
|
||||
return contentArr[0].replace(/^> (<.*> )?/g, "");
|
||||
return (contentArr && contentArr.length > 0) ? contentArr[0].replace(/^> (<.*> )?/g, "") : "";
|
||||
},
|
||||
},
|
||||
|
||||
|
|
@ -792,6 +794,18 @@ export default {
|
|||
}
|
||||
}
|
||||
return "";
|
||||
},
|
||||
|
||||
/**
|
||||
* If we are replying to a (media) thread, this is the hint we show when replying.
|
||||
*/
|
||||
replyToThreadMessage() {
|
||||
if (this.replyToEvent && this.timelineSet) {
|
||||
return this.$t("message.sent_media", {count: this.timelineSet.relations
|
||||
.getAllChildEventsForEvent(this.replyToEvent.getId())
|
||||
.filter((e) => util.downloadableTypes().includes(e.getContent().msgtype)).length});
|
||||
}
|
||||
return "";
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -800,6 +814,8 @@ export default {
|
|||
immediate: true,
|
||||
handler(value, oldValue) {
|
||||
if (value && !oldValue) {
|
||||
this.events.filter(event => (event.threadRootId && !event.parentThread)).forEach(event => this.setParentThread(event));
|
||||
this.events.filter(event => (event.replyEventId && !event.replyEvent)).forEach(event => this.setReplyToEvent(event));
|
||||
console.log("Loading finished!");
|
||||
}
|
||||
}
|
||||
|
|
@ -842,7 +858,7 @@ export default {
|
|||
}
|
||||
});
|
||||
} else {
|
||||
this.initialLoadDone = true;
|
||||
this.setInitialLoadDone();
|
||||
return; // no room
|
||||
}
|
||||
},
|
||||
|
|
@ -889,6 +905,15 @@ export default {
|
|||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Set initialLoadDone to 'true'. First process all events, setting threadParent and replyEvent if needed.
|
||||
*/
|
||||
setInitialLoadDone() {
|
||||
this.events.filter(event => (event.threadRootId && !event.parentThread)).forEach(event => this.setParentThread(event));
|
||||
this.events.filter(event => (event.replyEventId && !event.replyEvent)).forEach(event => this.setReplyToEvent(event));
|
||||
this.initialLoadDone = true;
|
||||
console.log("Loading finished!");
|
||||
},
|
||||
windowNotificationPermission,
|
||||
onNotificationDialog() {
|
||||
if(this.windowNotificationPermission() === 'denied') {
|
||||
|
|
@ -943,9 +968,23 @@ export default {
|
|||
console.log("ERROR " + err);
|
||||
})
|
||||
.finally(() => {
|
||||
self.initialLoadDone = true;
|
||||
if (initialEventId && !this.showCreatedRoomWelcomeHeader) {
|
||||
self.scrollToEvent(initialEventId);
|
||||
// const [timelineEvents, threadedEvents, unknownRelations] =
|
||||
// this.room.partitionThreadedEvents(self.events);
|
||||
// this.$matrix.matrixClient.processAggregatedTimelineEvents(this.room, timelineEvents);
|
||||
// //room.addEventsToTimeline(timelineEvents, true, room.getLiveTimeline());
|
||||
// this.$matrix.matrixClient.processThreadEvents(this.room, threadedEvents, true);
|
||||
// unknownRelations.forEach((event) => this.room.relations.aggregateChildEvent(event));
|
||||
|
||||
this.setInitialLoadDone();
|
||||
if (initialEventId && !this.showCreatedRoomWelcomeHeader) {
|
||||
const event = this.room.findEventById(initialEventId);
|
||||
this.$nextTick(() => {
|
||||
if (event && event.parentThread) {
|
||||
self.scrollToEvent(event.parentThread.getId());
|
||||
} else {
|
||||
self.scrollToEvent(initialEventId);
|
||||
}
|
||||
});
|
||||
} else if (this.showCreatedRoomWelcomeHeader || this.showDirectChatWelcomeHeader) {
|
||||
self.onScroll();
|
||||
}
|
||||
|
|
@ -960,7 +999,7 @@ export default {
|
|||
} else {
|
||||
// Error. Done loading.
|
||||
this.events = this.timelineWindow.getEvents();
|
||||
this.initialLoadDone = true;
|
||||
this.setInitialLoadDone();
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
|
|
@ -1094,12 +1133,83 @@ export default {
|
|||
|
||||
this.restartRRTimer();
|
||||
},
|
||||
|
||||
setParentThread(event) {
|
||||
const parentEvent = this.timelineSet.findEventById(event.threadRootId) || this.room.findEventById(event.threadRootId);
|
||||
if (parentEvent) {
|
||||
Vue.set(parentEvent, "isMxThread", true);
|
||||
Vue.set(event, "parentThread", parentEvent);
|
||||
} else {
|
||||
// Try to load from server.
|
||||
this.$matrix.matrixClient.getEventTimeline(this.timelineSet, event.threadRootId).then((tl) => {
|
||||
if (tl) {
|
||||
const parentEvent = tl.getEvents().find((e) => e.getId() === event.threadRootId);
|
||||
if (parentEvent) {
|
||||
this.events = this.timelineWindow.getEvents();
|
||||
const fn = () => {
|
||||
Vue.set(parentEvent, "isMxThread", true);
|
||||
Vue.set(event, "parentThread", parentEvent);
|
||||
};
|
||||
if (this.initialLoadDone) {
|
||||
const sel = "[eventId=\"" + parentEvent.getId() + "\"]";
|
||||
const element = document.querySelector(sel);
|
||||
if (element) {
|
||||
this.onLayoutChange(fn, element);
|
||||
} else {
|
||||
fn();
|
||||
}
|
||||
} else {
|
||||
fn();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
setReplyToEvent(event) {
|
||||
const parentEvent = this.timelineSet.findEventById(event.replyEventId) || this.room.findEventById(event.replyEventId);
|
||||
if (parentEvent) {
|
||||
Vue.set(event, "replyEvent", parentEvent);
|
||||
} else {
|
||||
// Try to load from server.
|
||||
this.$matrix.matrixClient.getEventTimeline(this.timelineSet, event.replyEventId)
|
||||
.then((tl) => {
|
||||
if (tl) {
|
||||
const parentEvent = tl.getEvents().find((e) => e.getId() === event.replyEventId);
|
||||
if (parentEvent) {
|
||||
this.events = this.timelineWindow.getEvents();
|
||||
const fn = () => {Vue.set(event, "replyEvent", parentEvent);};
|
||||
if (this.initialLoadDone) {
|
||||
const sel = "[eventId=\"" + parentEvent.getId() + "\"]";
|
||||
const element = document.querySelector(sel);
|
||||
if (element) {
|
||||
this.onLayoutChange(fn, element);
|
||||
} else {
|
||||
fn();
|
||||
}
|
||||
} else {
|
||||
fn();
|
||||
}
|
||||
}
|
||||
}
|
||||
}).catch(e => console.error(e));
|
||||
}
|
||||
},
|
||||
|
||||
onEvent(event) {
|
||||
//console.log("OnEvent", JSON.stringify(event));
|
||||
if (event.getRoomId() !== this.roomId) {
|
||||
return; // Not for this room
|
||||
}
|
||||
|
||||
if (this.initialLoadDone && event.threadRootId && !event.parentThread) {
|
||||
this.setParentThread(event);
|
||||
}
|
||||
if (this.initialLoadDone && event.replyEventId && !event.replyEvent) {
|
||||
this.setReplyToEvent(event);
|
||||
}
|
||||
|
||||
const loadingDone = this.initialLoadDone;
|
||||
this.$matrix.matrixClient.decryptEventIfNeeded(event, {});
|
||||
|
||||
|
|
@ -1107,7 +1217,7 @@ export default {
|
|||
this.paginateBackIfNeeded();
|
||||
}
|
||||
|
||||
if (loadingDone && event.forwardLooking && !event.isRelation()) {
|
||||
if (loadingDone && event.forwardLooking && (!event.isRelation() || event.isMxThread || event.threadRootId || event.parentThread )) {
|
||||
// If we are at bottom, scroll to see new events...
|
||||
var scrollToSeeNew = event.getSender() == this.$matrix.currentUserId; // When we sent, scroll
|
||||
const container = this.chatContainer;
|
||||
|
|
@ -1315,6 +1425,28 @@ export default {
|
|||
this.cancelSendAttachment();
|
||||
},
|
||||
|
||||
/**
|
||||
* Called by message components that need to change their layout. This will avoid "jumping" in the UI, because
|
||||
* we remember scroll position, apply the layout change, then restore the scroll.
|
||||
* NOTE: we use "parentElement" below, because it is expected to be called with "element" set to the message component
|
||||
* and the message component in turn being wrapped by a "message-wrapper" element (see html above).
|
||||
* @param {} action A function that performs desired layout changes.
|
||||
* @param {*} element Root element for the chat message.
|
||||
*/
|
||||
onLayoutChange(action, element) {
|
||||
if (!element || !element.parentElemen || this.useVoiceMode || this.useFileModeNonAdmin) {
|
||||
action();
|
||||
return
|
||||
}
|
||||
const container = this.chatContainer;
|
||||
this.scrollPosition.prepareFor(element.parentElement.offsetTop >= container.scrollTop ? "down" : "up");
|
||||
action();
|
||||
this.$nextTick(() => {
|
||||
// restore scroll position!
|
||||
this.scrollPosition.restore();
|
||||
});
|
||||
},
|
||||
|
||||
handleScrolledToTop() {
|
||||
if (
|
||||
this.timelineWindow &&
|
||||
|
|
@ -1379,9 +1511,18 @@ export default {
|
|||
const container = this.chatContainer;
|
||||
const ref = this.$refs[eventId];
|
||||
if (container && ref) {
|
||||
const targetY = container.clientHeight / 2;
|
||||
const sourceY = ref[0].offsetTop;
|
||||
container.scrollTo(0, sourceY - targetY);
|
||||
const parent = container.getBoundingClientRect();
|
||||
const item = ref[0].getBoundingClientRect();
|
||||
let offsetY = (parent.bottom - parent.top) / 2;
|
||||
if (ref[0].clientHeight > offsetY) {
|
||||
offsetY = Math.max(0, (parent.bottom - parent.top) - ref[0].clientHeight);
|
||||
}
|
||||
const targetY = parent.top + offsetY;
|
||||
const currentY = item.top;
|
||||
const y = container.scrollTop + (currentY - targetY);
|
||||
this.$nextTick(() => {
|
||||
container.scrollTo(0, y);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -1433,7 +1574,11 @@ export default {
|
|||
addReply(event) {
|
||||
this.replyToEvent = event;
|
||||
this.$refs.messageInput.focus();
|
||||
this.replyToContentType = event.getContent().msgtype || 'm.poll';
|
||||
if (event.parentThread || event.isThreadRoot || event.isMxThread) {
|
||||
this.replyToContentType = 'm.thread';
|
||||
} else {
|
||||
this.replyToContentType = event.getContent().msgtype || 'm.poll';
|
||||
}
|
||||
this.setReplyToImage(event);
|
||||
},
|
||||
|
||||
|
|
@ -1455,7 +1600,12 @@ export default {
|
|||
},
|
||||
|
||||
download(event) {
|
||||
util.download(this.$matrix.matrixClient, event);
|
||||
if ((event.isThreadRoot || event.isMxThread) && this.timelineSet) {
|
||||
const children = this.timelineSet.relations.getAllChildEventsForEvent(event.getId()).filter(e => util.downloadableTypes().includes(e.getContent().msgtype));
|
||||
children.forEach(child => util.download(this.$matrix.matrixClient, child));
|
||||
} else {
|
||||
util.download(this.$matrix.matrixClient, event);
|
||||
}
|
||||
},
|
||||
|
||||
cancelEditReply() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue