keanu-weblite/src/components/messages/composition/MessageThread.vue

292 lines
8.7 KiB
Vue

<template>
<component :is="rootComponent" ref="root" v-bind="{ ...$props, ...$attrs }" v-if="showMultiview">
<div class="bubble">
<div class="bubble-inset" v-if="showCCSummary">
<CCSummary :multiple="items.length > 1" :flags="proofHintFlags" />
</div>
<div class="original-message bubble-inset" v-if="inReplyToText">
<div class="original-message-sender">{{ inReplyToSender }}</div>
<div class="original-message-text" v-html="linkify($$sanitize(inReplyToText))" />
</div>
<div class="message">
<SwipeableThumbnailsView
:items="items"
v-if="event && !event.isRedacted() && room.displayType == ROOM_TYPE_CHANNEL"
v-bind="$attrs"
/>
<v-container v-else-if="event && !event.isRedacted()" fluid class="imageCollection">
<v-row class="pa-0 ma-0" wrap>
<v-col class="pa-0 ma-0" v-for="{ size, item } in layoutedItems" :key="item.event.getId()" :cols="size">
<ThumbnailView :item="item" :previewOnly="true" v-on:itemclick="onItemClick($event)" />
</v-col>
</v-row>
</v-container>
<div class="bubble-inset" v-if="showMessageText || event?.isRedacted() || event?.replacingEventId()">
<i v-if="event && event.isRedacted()" class="deleted-text">
<v-icon :color="isIncoming && senderIsAdminOrModerator(event) ? 'white' : ''" size="small">block</v-icon>
{{
redactedBySomeoneElse(event)
? $t("message.incoming_message_deleted_text")
: $t("message.outgoing_message_deleted_text")
}}
</i>
<span v-html="linkify($$sanitize(messageText))" v-else-if="messageText" />
<span class="edit-marker" v-if="event && event.replacingEventId() && !event.isRedacted()">
{{ t("message.edited") }}
</span>
</div>
</div>
</div>
<GalleryItemsView
:originalEvent="originalEvent"
:items="items"
:initialItem="showItem"
v-if="!!showItem"
v-on:close="showItem = undefined"
/>
</component>
<component
v-else-if="items.length == 1"
:is="$props.componentFn(items[0].event, false)"
v-bind="{ ...$props, ...$attrs }"
:originalEvent="items[0].event"
/>
</template>
<script setup lang="ts">
import MessageIncoming from "./MessageIncoming.vue";
import MessageOutgoing from "./MessageOutgoing.vue";
import { MessageProps, useMessage } from "./useMessage";
import util, { ROOM_TYPE_CHANNEL, ROOM_TYPE_FILE_MODE } from "@/plugins/utils";
import GalleryItemsView from "../../file_mode/GalleryItemsView.vue";
import ThumbnailView from "../../file_mode/ThumbnailView.vue";
import SwipeableThumbnailsView from "../channel/SwipeableThumbnailsView.vue";
import CCSummary from "@/components/content-credentials/CCSummary.vue";
import { computed, inject, onBeforeUnmount, ref, Ref, useTemplateRef, watch } from "vue";
import { EventAttachment } from "../../../models/eventAttachment";
import { useI18n } from "vue-i18n";
import { MatrixEvent, MatrixEventEvent } from "matrix-js-sdk";
import { useLazyLoad } from "./useLazyLoad";
import { ProofHintFlags } from "../../../models/proof";
const { t } = useI18n();
const $matrix: any = inject("globalMatrix");
const $$sanitize: any = inject("globalSanitize");
type RootType = InstanceType<typeof MessageOutgoing | typeof MessageIncoming>;
const rootRef = useTemplateRef<RootType>("root");
const emits = defineEmits<{
(event: "layout-change", value: { element: Element | undefined; action: () => void }): void;
}>();
const items: Ref<EventAttachment[]> = ref([]);
const showItem: Ref<EventAttachment | undefined> = ref(undefined);
const props = defineProps<MessageProps>();
const { room } = props;
const processThread = () => {
if (!event.value?.isRedacted()) {
const el = rootRef.value?.$el;
emits("layout-change", { element: el, action: _processThread });
}
};
const { isVisible } = useLazyLoad({ root: rootRef });
const {
event,
thread,
isIncoming,
senderIsAdminOrModerator,
inReplyToSender,
inReplyToText,
messageText,
redactedBySomeoneElse,
linkify,
} = useMessage($matrix, t, props, undefined, processThread);
const rootComponent = computed(() => {
return isIncoming.value ? MessageIncoming : MessageOutgoing;
});
const onRelationsCreated = () => {
if (event.value) {
thread.value = props.timelineSet.relations.getChildEventsForEvent(
event.value.getId() ?? "",
util.threadMessageType(),
"m.room.message"
);
event.value.off(MatrixEventEvent.RelationsCreated, onRelationsCreated);
}
};
watch(
event,
() => {
if (event.value) {
if (event.value.getContent().msgtype == "m.image") {
// Single image mode
items.value = [event.value].map((e: MatrixEvent) => {
let ea = $matrix.attachmentManager.getEventAttachment(e);
if (isVisible.value) {
ea.loadThumbnail();
}
return ea;
});
} else if (thread.value === undefined) {
thread.value = props.timelineSet.relations.getChildEventsForEvent(
event.value.getId() ?? "",
util.threadMessageType(),
"m.room.message"
);
}
if (!thread.value) {
event.value.on(MatrixEventEvent.RelationsCreated, onRelationsCreated);
}
}
},
{ immediate: true }
);
onBeforeUnmount(() => {
event.value?.off(MatrixEventEvent.RelationsCreated, onRelationsCreated);
});
const showMessageText = computed((): boolean => {
if (event.value?.getContent().msgtype == "m.image") {
return false;
}
return true;
});
const showMultiview = computed((): boolean => {
if (event.value?.getContent().msgtype == "m.image") {
return true;
}
return (
(isIncoming.value && props.room.displayType == ROOM_TYPE_FILE_MODE) ||
items.value?.length > 1 ||
(event.value && event.value.isRedacted()) ||
(props.room.displayType == ROOM_TYPE_CHANNEL &&
items.value.length == 1 &&
util.isFileTypePDF(items.value[0].event)) ||
messageText.value?.length > 0
);
});
const showCCSummary = computed(() => {
return items.value?.some((i) => i.proofHintFlags !== undefined);
});
const proofHintFlags = computed(() => {
return items.value.reduce((res: ProofHintFlags[], item) => {
if (item.proofHintFlags) {
res.push(item.proofHintFlags);
}
return res;
}, []);
});
watch(isVisible, (visible) => {
if (showMultiview.value && visible) {
items.value.forEach((a) => a.loadThumbnail());
}
});
const onItemClick = (event: any) => {
showItem.value = event.item;
};
const _processThread = () => {
const eventItems = props.timelineSet.relations
.getAllChildEventsForEvent(event.value?.getId() ?? "")
.filter((e: MatrixEvent) => !e.isRedacted() && util.downloadableTypes().includes(e.getContent().msgtype));
items.value = eventItems.map((e: MatrixEvent) => {
let ea = $matrix.attachmentManager.getEventAttachment(e);
if (showMultiview.value && isVisible.value) {
ea.loadThumbnail();
}
return ea;
});
};
const layoutedItems = computed(() => {
if (!items.value || items.value.length == 0) {
return [];
}
let array = items.value.slice(0);
let rows = [];
while (array.length > 0) {
if (array.length >= 7) {
rows.push({ size: 6, item: array[0] });
rows.push({ size: 6, item: array[1] });
rows.push({ size: 12, item: array[2] });
rows.push({ size: 3, item: array[3] });
rows.push({ size: 3, item: array[4] });
rows.push({ size: 3, item: array[5] });
rows.push({ size: 3, item: array[6] });
array = array.slice(7);
} else if (array.length >= 3) {
rows.push({ size: 12, item: array[0] });
rows.push({ size: 6, item: array[1] });
rows.push({ size: 6, item: array[2] });
array = array.slice(3);
} else if (array.length >= 2) {
rows.push({ size: 6, item: array[0] });
rows.push({ size: 6, item: array[1] });
array = array.slice(2);
} else {
rows.push({ size: 12, item: array[0] });
array = array.slice(1);
}
}
return rows;
});
</script>
<style lang="scss">
@use "@/assets/css/chat.scss" as *;
</style>
<style lang="scss" scoped>
.bubble {
width: 100%;
margin: 0 0 !important;
padding: 0 !important;
overflow: hidden;
}
.bubble-inset {
padding: 8px 8px;
}
.imageCollection {
width: unset;
max-width: unset;
margin: 0 0px !important;
padding: 0 !important;
overflow: hidden;
.v-row {
margin: -2px -2px 0 -2px !important;
}
.v-col {
padding: 2px !important;
background-color: transparent !important;
}
.file-item {
display: flex;
align-items: center;
justify-content: center;
font-size: 0.6rem;
flex-direction: column;
padding: 20px;
}
}
</style>