mirror of
https://github.com/nextcloud/spreed.git
synced 2025-12-17 21:12:20 +01:00
Merge pull request #16475 from nextcloud/fix/noid/unread-marker
This commit is contained in:
commit
f8778c9734
4 changed files with 124 additions and 237 deletions
|
|
@ -7,7 +7,6 @@
|
||||||
<li
|
<li
|
||||||
:id="`message_${message.id}`"
|
:id="`message_${message.id}`"
|
||||||
:data-message-id="message.id"
|
:data-message-id="message.id"
|
||||||
:data-seen="seen"
|
|
||||||
:data-next-message-id="nextMessageId"
|
:data-next-message-id="nextMessageId"
|
||||||
:data-previous-message-id="previousMessageId"
|
:data-previous-message-id="previousMessageId"
|
||||||
class="message"
|
class="message"
|
||||||
|
|
@ -81,21 +80,6 @@
|
||||||
:message="message.message"
|
:message="message.message"
|
||||||
:rich-parameters="richParameters"
|
:rich-parameters="richParameters"
|
||||||
@close="isTranslateDialogOpen = false" />
|
@close="isTranslateDialogOpen = false" />
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="isLastReadMessage"
|
|
||||||
v-intersection-observer="lastReadMessageVisibilityChanged"
|
|
||||||
class="message-unread-marker">
|
|
||||||
<div class="message-unread-marker__wrapper">
|
|
||||||
<span class="message-unread-marker__text">{{ t('spreed', 'Unread messages') }}</span>
|
|
||||||
<NcAssistantButton
|
|
||||||
v-if="shouldShowSummaryOption"
|
|
||||||
:disabled="loading"
|
|
||||||
@click="generateSummary">
|
|
||||||
{{ t('spreed', 'Generate summary') }}
|
|
||||||
</NcAssistantButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -103,9 +87,7 @@
|
||||||
import { showError, showSuccess, showWarning, TOAST_DEFAULT_TIMEOUT } from '@nextcloud/dialogs'
|
import { showError, showSuccess, showWarning, TOAST_DEFAULT_TIMEOUT } from '@nextcloud/dialogs'
|
||||||
import { t } from '@nextcloud/l10n'
|
import { t } from '@nextcloud/l10n'
|
||||||
import { useIsSmallMobile } from '@nextcloud/vue/composables/useIsMobile'
|
import { useIsSmallMobile } from '@nextcloud/vue/composables/useIsMobile'
|
||||||
import { vIntersectionObserver as IntersectionObserver } from '@vueuse/components'
|
import { inject } from 'vue'
|
||||||
import { inject, ref } from 'vue'
|
|
||||||
import NcAssistantButton from '@nextcloud/vue/components/NcAssistantButton'
|
|
||||||
import MessageButtonsBar from './MessageButtonsBar/MessageButtonsBar.vue'
|
import MessageButtonsBar from './MessageButtonsBar/MessageButtonsBar.vue'
|
||||||
import MessageForwarder from './MessageButtonsBar/MessageForwarder.vue'
|
import MessageForwarder from './MessageButtonsBar/MessageForwarder.vue'
|
||||||
import MessageTranslateDialog from './MessageButtonsBar/MessageTranslateDialog.vue'
|
import MessageTranslateDialog from './MessageButtonsBar/MessageTranslateDialog.vue'
|
||||||
|
|
@ -120,15 +102,12 @@ import PollCard from './MessagePart/PollCard.vue'
|
||||||
import ReactionsWrapper from './MessagePart/ReactionsWrapper.vue'
|
import ReactionsWrapper from './MessagePart/ReactionsWrapper.vue'
|
||||||
import { useGetThreadId } from '../../../../composables/useGetThreadId.ts'
|
import { useGetThreadId } from '../../../../composables/useGetThreadId.ts'
|
||||||
import { CONVERSATION, MENTION, MESSAGE, PARTICIPANT } from '../../../../constants.ts'
|
import { CONVERSATION, MENTION, MESSAGE, PARTICIPANT } from '../../../../constants.ts'
|
||||||
import { getTalkConfig, hasTalkFeature } from '../../../../services/CapabilitiesManager.ts'
|
import { getTalkConfig } from '../../../../services/CapabilitiesManager.ts'
|
||||||
import { EventBus } from '../../../../services/EventBus.ts'
|
import { EventBus } from '../../../../services/EventBus.ts'
|
||||||
import { useActorStore } from '../../../../stores/actor.ts'
|
import { useActorStore } from '../../../../stores/actor.ts'
|
||||||
import { useChatExtrasStore } from '../../../../stores/chatExtras.ts'
|
import { useChatExtrasStore } from '../../../../stores/chatExtras.ts'
|
||||||
import { getItemTypeFromMessage } from '../../../../utils/getItemTypeFromMessage.ts'
|
import { getItemTypeFromMessage } from '../../../../utils/getItemTypeFromMessage.ts'
|
||||||
|
|
||||||
const canSummarizeChat = hasTalkFeature('local', 'chat-summary-api')
|
|
||||||
const summaryThreshold = getTalkConfig('local', 'chat', 'summary-threshold') ?? 0
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'MessageItem',
|
name: 'MessageItem',
|
||||||
|
|
||||||
|
|
@ -137,14 +116,9 @@ export default {
|
||||||
MessageButtonsBar,
|
MessageButtonsBar,
|
||||||
MessageForwarder,
|
MessageForwarder,
|
||||||
MessageTranslateDialog,
|
MessageTranslateDialog,
|
||||||
NcAssistantButton,
|
|
||||||
ReactionsWrapper,
|
ReactionsWrapper,
|
||||||
},
|
},
|
||||||
|
|
||||||
directives: {
|
|
||||||
IntersectionObserver,
|
|
||||||
},
|
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
message: {
|
message: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|
@ -188,11 +162,8 @@ export default {
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loading: false,
|
|
||||||
isHovered: false,
|
isHovered: false,
|
||||||
isDeleting: false,
|
isDeleting: false,
|
||||||
// whether the message was seen, only used if this was marked as last read message
|
|
||||||
seen: false,
|
|
||||||
isActionMenuOpen: false,
|
isActionMenuOpen: false,
|
||||||
// Right side bottom bar
|
// Right side bottom bar
|
||||||
isEmojiPickerOpen: false,
|
isEmojiPickerOpen: false,
|
||||||
|
|
@ -209,30 +180,6 @@ export default {
|
||||||
return this.message.timestamp === 0
|
return this.message.timestamp === 0
|
||||||
},
|
},
|
||||||
|
|
||||||
isLastMessage() {
|
|
||||||
// never displayed for the very last message
|
|
||||||
return !this.nextMessageId || this.message.id === this.conversation?.lastMessage?.id
|
|
||||||
},
|
|
||||||
|
|
||||||
visualLastLastReadMessageId() {
|
|
||||||
return this.$store.getters.getVisualLastReadMessageId(this.message.token)
|
|
||||||
},
|
|
||||||
|
|
||||||
isLastReadMessage() {
|
|
||||||
if (this.isLastMessage) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.message.id === this.visualLastLastReadMessageId
|
|
||||||
},
|
|
||||||
|
|
||||||
shouldShowSummaryOption() {
|
|
||||||
if (this.conversation.remoteServer || !canSummarizeChat || this.chatExtrasStore.hasChatSummaryTaskRequested(this.message.token)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return (this.conversation.unreadMessages >= summaryThreshold)
|
|
||||||
},
|
|
||||||
|
|
||||||
isDeletedMessage() {
|
isDeletedMessage() {
|
||||||
return this.message.messageType === MESSAGE.TYPE.COMMENT_DELETED
|
return this.message.messageType === MESSAGE.TYPE.COMMENT_DELETED
|
||||||
},
|
},
|
||||||
|
|
@ -365,11 +312,6 @@ export default {
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
t,
|
t,
|
||||||
lastReadMessageVisibilityChanged([{ isIntersecting }]) {
|
|
||||||
if (isIntersecting) {
|
|
||||||
this.seen = true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
handleMouseover() {
|
handleMouseover() {
|
||||||
if (!this.isHovered) {
|
if (!this.isHovered) {
|
||||||
|
|
@ -435,12 +377,6 @@ export default {
|
||||||
toggleFollowUpEmojiPicker() {
|
toggleFollowUpEmojiPicker() {
|
||||||
this.isFollowUpEmojiPickerOpen = !this.isFollowUpEmojiPickerOpen
|
this.isFollowUpEmojiPickerOpen = !this.isFollowUpEmojiPickerOpen
|
||||||
},
|
},
|
||||||
|
|
||||||
async generateSummary() {
|
|
||||||
this.loading = true
|
|
||||||
await this.chatExtrasStore.requestChatSummary(this.message.token, this.message.id)
|
|
||||||
this.loading = false
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -569,39 +505,6 @@ export default {
|
||||||
100% { background-color: rgba(var(--color-background-hover), 0); }
|
100% { background-color: rgba(var(--color-background-hover), 0); }
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-unread-marker {
|
|
||||||
position: relative;
|
|
||||||
margin: calc(4 * var(--default-grid-baseline));
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: '';
|
|
||||||
width: 100%;
|
|
||||||
border-top: 1px solid var(--color-border-maxcontrast);
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__wrapper {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: calc(3 * var(--default-grid-baseline));
|
|
||||||
margin-inline: auto;
|
|
||||||
padding-inline: calc(3 * var(--default-grid-baseline));
|
|
||||||
width: fit-content;
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
background-color: var(--color-main-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
&__text {
|
|
||||||
text-align: center;
|
|
||||||
white-space: nowrap;
|
|
||||||
font-weight: bold;
|
|
||||||
color: var(--color-main-text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-buttons-bar {
|
.message-buttons-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
inset-inline-end: 14px;
|
inset-inline-end: 14px;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
<li
|
<li
|
||||||
:id="`message_${message.id}`"
|
:id="`message_${message.id}`"
|
||||||
:data-message-id="message.id"
|
:data-message-id="message.id"
|
||||||
:data-seen="seen"
|
|
||||||
:data-next-message-id="nextMessageId"
|
:data-next-message-id="nextMessageId"
|
||||||
:data-previous-message-id="previousMessageId"
|
:data-previous-message-id="previousMessageId"
|
||||||
class="message">
|
class="message">
|
||||||
|
|
@ -40,28 +39,11 @@
|
||||||
</NcButton>
|
</NcButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="isLastReadMessage"
|
|
||||||
v-intersection-observer="lastReadMessageVisibilityChanged"
|
|
||||||
class="message-unread-marker">
|
|
||||||
<div class="message-unread-marker__wrapper">
|
|
||||||
<span class="message-unread-marker__text">{{ t('spreed', 'Unread messages') }}</span>
|
|
||||||
<NcAssistantButton
|
|
||||||
v-if="shouldShowSummaryOption"
|
|
||||||
:disabled="loading"
|
|
||||||
@click="generateSummary">
|
|
||||||
{{ t('spreed', 'Generate summary') }}
|
|
||||||
</NcAssistantButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { t } from '@nextcloud/l10n'
|
import { t } from '@nextcloud/l10n'
|
||||||
import { vIntersectionObserver as IntersectionObserver } from '@vueuse/components'
|
|
||||||
import NcAssistantButton from '@nextcloud/vue/components/NcAssistantButton'
|
|
||||||
import NcButton from '@nextcloud/vue/components/NcButton'
|
import NcButton from '@nextcloud/vue/components/NcButton'
|
||||||
import IconUnfoldLessHorizontal from 'vue-material-design-icons/UnfoldLessHorizontal.vue'
|
import IconUnfoldLessHorizontal from 'vue-material-design-icons/UnfoldLessHorizontal.vue'
|
||||||
import IconUnfoldMoreHorizontal from 'vue-material-design-icons/UnfoldMoreHorizontal.vue'
|
import IconUnfoldMoreHorizontal from 'vue-material-design-icons/UnfoldMoreHorizontal.vue'
|
||||||
|
|
@ -69,11 +51,6 @@ import DefaultParameter from './MessagePart/DefaultParameter.vue'
|
||||||
import MentionChip from './MessagePart/MentionChip.vue'
|
import MentionChip from './MessagePart/MentionChip.vue'
|
||||||
import MessageBody from './MessagePart/MessageBody.vue'
|
import MessageBody from './MessagePart/MessageBody.vue'
|
||||||
import { MENTION } from '../../../../constants.ts'
|
import { MENTION } from '../../../../constants.ts'
|
||||||
import { getTalkConfig, hasTalkFeature } from '../../../../services/CapabilitiesManager.ts'
|
|
||||||
import { useChatExtrasStore } from '../../../../stores/chatExtras.ts'
|
|
||||||
|
|
||||||
const canSummarizeChat = hasTalkFeature('local', 'chat-summary-api')
|
|
||||||
const summaryThreshold = getTalkConfig('local', 'chat', 'summary-threshold') ?? 0
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'MessageItem',
|
name: 'MessageItem',
|
||||||
|
|
@ -82,14 +59,9 @@ export default {
|
||||||
IconUnfoldLessHorizontal,
|
IconUnfoldLessHorizontal,
|
||||||
IconUnfoldMoreHorizontal,
|
IconUnfoldMoreHorizontal,
|
||||||
MessageBody,
|
MessageBody,
|
||||||
NcAssistantButton,
|
|
||||||
NcButton,
|
NcButton,
|
||||||
},
|
},
|
||||||
|
|
||||||
directives: {
|
|
||||||
IntersectionObserver,
|
|
||||||
},
|
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
message: {
|
message: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|
@ -138,49 +110,7 @@ export default {
|
||||||
|
|
||||||
emits: ['toggleCombinedSystemMessage'],
|
emits: ['toggleCombinedSystemMessage'],
|
||||||
|
|
||||||
setup() {
|
|
||||||
return {
|
|
||||||
chatExtrasStore: useChatExtrasStore(),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
loading: false,
|
|
||||||
// whether the message was seen, only used if this was marked as last read message
|
|
||||||
seen: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
isLastMessage() {
|
|
||||||
// never displayed for the very last message
|
|
||||||
return !this.nextMessageId || this.message.id === this.conversation?.lastMessage?.id
|
|
||||||
},
|
|
||||||
|
|
||||||
visualLastLastReadMessageId() {
|
|
||||||
return this.$store.getters.getVisualLastReadMessageId(this.message.token)
|
|
||||||
},
|
|
||||||
|
|
||||||
isLastReadMessage() {
|
|
||||||
if (this.isLastMessage) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.message.id === this.visualLastLastReadMessageId) {
|
|
||||||
return !this.isCollapsedSystemMessage || this.message.id !== this.lastCollapsedMessageId
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.isCombinedSystemMessage && this.lastCollapsedMessageId === this.visualLastLastReadMessageId
|
|
||||||
},
|
|
||||||
|
|
||||||
shouldShowSummaryOption() {
|
|
||||||
if (this.conversation.remoteServer || !canSummarizeChat || this.chatExtrasStore.hasChatSummaryTaskRequested(this.message.token)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return (this.conversation.unreadMessages >= summaryThreshold)
|
|
||||||
},
|
|
||||||
|
|
||||||
conversation() {
|
conversation() {
|
||||||
return this.$store.getters.conversation(this.message.token)
|
return this.$store.getters.conversation(this.message.token)
|
||||||
},
|
},
|
||||||
|
|
@ -210,21 +140,10 @@ export default {
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
t,
|
t,
|
||||||
lastReadMessageVisibilityChanged([{ isIntersecting }]) {
|
|
||||||
if (isIntersecting) {
|
|
||||||
this.seen = true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleCombinedSystemMessage() {
|
toggleCombinedSystemMessage() {
|
||||||
this.$emit('toggleCombinedSystemMessage')
|
this.$emit('toggleCombinedSystemMessage')
|
||||||
},
|
},
|
||||||
|
|
||||||
async generateSummary() {
|
|
||||||
this.loading = true
|
|
||||||
await this.chatExtrasStore.requestChatSummary(this.message.token, this.message.id)
|
|
||||||
this.loading = false
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -262,39 +181,6 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-unread-marker {
|
|
||||||
position: relative;
|
|
||||||
margin: calc(4 * var(--default-grid-baseline));
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: '';
|
|
||||||
width: 100%;
|
|
||||||
border-top: 1px solid var(--color-border-maxcontrast);
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__wrapper {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: calc(3 * var(--default-grid-baseline));
|
|
||||||
margin-inline: auto;
|
|
||||||
padding-inline: calc(3 * var(--default-grid-baseline));
|
|
||||||
width: fit-content;
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
background-color: var(--color-main-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
&__text {
|
|
||||||
text-align: center;
|
|
||||||
white-space: nowrap;
|
|
||||||
font-weight: bold;
|
|
||||||
color: var(--color-main-text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-buttons-bar {
|
.message-buttons-bar {
|
||||||
background-color: var(--color-main-background);
|
background-color: var(--color-main-background);
|
||||||
border-radius: var(--border-radius-element, calc(var(--default-clickable-area) / 2));
|
border-radius: var(--border-radius-element, calc(var(--default-clickable-area) / 2));
|
||||||
|
|
|
||||||
|
|
@ -91,12 +91,6 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
|
||||||
lastReadMessageId() {
|
|
||||||
return this.$store.getters.conversation(this.token)?.lastReadMessage
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
messages: {
|
messages: {
|
||||||
deep: true,
|
deep: true,
|
||||||
|
|
@ -106,10 +100,6 @@ export default {
|
||||||
this.updateCollapsedState()
|
this.updateCollapsedState()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
lastReadMessageId() {
|
|
||||||
this.updateCollapsedState()
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -188,11 +178,7 @@ export default {
|
||||||
|
|
||||||
updateCollapsedState() {
|
updateCollapsedState() {
|
||||||
for (const group of this.messagesGroupedBySystemMessage) {
|
for (const group of this.messagesGroupedBySystemMessage) {
|
||||||
const isLastReadInsideGroup = this.lastReadMessageId >= group.id && this.lastReadMessageId < group.lastId
|
if (this.groupIsCollapsed[group.id] !== undefined) {
|
||||||
if (isLastReadInsideGroup) {
|
|
||||||
// If the last read message is inside the group, we should show the group expanded
|
|
||||||
group.collapsed = false
|
|
||||||
} else if (this.groupIsCollapsed[group.id] !== undefined) {
|
|
||||||
// If the group was collapsed before, we should keep it collapsed
|
// If the group was collapsed before, we should keep it collapsed
|
||||||
group.collapsed = this.groupIsCollapsed[group.id]
|
group.collapsed = this.groupIsCollapsed[group.id]
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -51,14 +51,30 @@
|
||||||
role="heading"
|
role="heading"
|
||||||
aria-level="3" />
|
aria-level="3" />
|
||||||
</li>
|
</li>
|
||||||
<component
|
<template
|
||||||
:is="messagesGroupComponent[group.type]"
|
|
||||||
v-for="group in list"
|
v-for="group in list"
|
||||||
:key="group.id"
|
:key="group.id">
|
||||||
:token="token"
|
<component
|
||||||
:messages="group.messages"
|
:is="messagesGroupComponent[group.type]"
|
||||||
:previous-message-id="group.previousMessageId"
|
:token="token"
|
||||||
:next-message-id="group.nextMessageId" />
|
:messages="group.messages"
|
||||||
|
:previous-message-id="group.previousMessageId"
|
||||||
|
:next-message-id="group.nextMessageId" />
|
||||||
|
<div
|
||||||
|
v-if="isLastReadMessage(group)"
|
||||||
|
v-intersection-observer="lastReadMessageVisibilityChanged"
|
||||||
|
class="message-unread-marker">
|
||||||
|
<div class="message-unread-marker__wrapper">
|
||||||
|
<span class="message-unread-marker__text">{{ t('spreed', 'Unread messages') }}</span>
|
||||||
|
<NcAssistantButton
|
||||||
|
v-if="shouldShowSummaryOption"
|
||||||
|
:disabled="loadingSummary"
|
||||||
|
@click="generateSummary">
|
||||||
|
{{ t('spreed', 'Generate summary') }}
|
||||||
|
</NcAssistantButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<TransitionWrapper name="fade">
|
<TransitionWrapper name="fade">
|
||||||
|
|
@ -75,8 +91,10 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { n, t } from '@nextcloud/l10n'
|
import { n, t } from '@nextcloud/l10n'
|
||||||
|
import { vIntersectionObserver as IntersectionObserver } from '@vueuse/components'
|
||||||
import debounce from 'debounce'
|
import debounce from 'debounce'
|
||||||
import { computed, provide } from 'vue'
|
import { computed, provide, ref } from 'vue'
|
||||||
|
import NcAssistantButton from '@nextcloud/vue/components/NcAssistantButton'
|
||||||
import NcEmptyContent from '@nextcloud/vue/components/NcEmptyContent'
|
import NcEmptyContent from '@nextcloud/vue/components/NcEmptyContent'
|
||||||
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
|
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
|
||||||
import IconMessageOutline from 'vue-material-design-icons/MessageOutline.vue'
|
import IconMessageOutline from 'vue-material-design-icons/MessageOutline.vue'
|
||||||
|
|
@ -90,6 +108,7 @@ import { useGetMessages } from '../../composables/useGetMessages.ts'
|
||||||
import { useGetThreadId } from '../../composables/useGetThreadId.ts'
|
import { useGetThreadId } from '../../composables/useGetThreadId.ts'
|
||||||
import { ATTENDEE, CONVERSATION } from '../../constants.ts'
|
import { ATTENDEE, CONVERSATION } from '../../constants.ts'
|
||||||
import { CHAT_STYLE } from '../../constants.ts'
|
import { CHAT_STYLE } from '../../constants.ts'
|
||||||
|
import { getTalkConfig, hasTalkFeature } from '../../services/CapabilitiesManager.ts'
|
||||||
import { EventBus } from '../../services/EventBus.ts'
|
import { EventBus } from '../../services/EventBus.ts'
|
||||||
import { useChatStore } from '../../stores/chat.ts'
|
import { useChatStore } from '../../stores/chat.ts'
|
||||||
import { useChatExtrasStore } from '../../stores/chatExtras.ts'
|
import { useChatExtrasStore } from '../../stores/chatExtras.ts'
|
||||||
|
|
@ -98,6 +117,8 @@ import { convertToUnix } from '../../utils/formattedTime.ts'
|
||||||
|
|
||||||
const SCROLL_TOLERANCE = 10
|
const SCROLL_TOLERANCE = 10
|
||||||
const LOAD_HISTORY_THRESHOLD = 800
|
const LOAD_HISTORY_THRESHOLD = 800
|
||||||
|
const canSummarizeChat = hasTalkFeature('local', 'chat-summary-api')
|
||||||
|
const summaryThreshold = getTalkConfig('local', 'chat', 'summary-threshold') ?? 0
|
||||||
|
|
||||||
const messagesGroupComponent = {
|
const messagesGroupComponent = {
|
||||||
system: MessagesSystemGroup,
|
system: MessagesSystemGroup,
|
||||||
|
|
@ -110,11 +131,16 @@ export default {
|
||||||
IconMessageOutline,
|
IconMessageOutline,
|
||||||
LoadingPlaceholder,
|
LoadingPlaceholder,
|
||||||
NcEmptyContent,
|
NcEmptyContent,
|
||||||
|
NcAssistantButton,
|
||||||
NcLoadingIcon,
|
NcLoadingIcon,
|
||||||
StaticDateTime,
|
StaticDateTime,
|
||||||
TransitionWrapper,
|
TransitionWrapper,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
directives: {
|
||||||
|
IntersectionObserver,
|
||||||
|
},
|
||||||
|
|
||||||
provide() {
|
provide() {
|
||||||
return {
|
return {
|
||||||
getMessagesListScroller: () => this.$refs.scroller,
|
getMessagesListScroller: () => this.$refs.scroller,
|
||||||
|
|
@ -206,6 +232,10 @@ export default {
|
||||||
stickyDate: null,
|
stickyDate: null,
|
||||||
|
|
||||||
endScrollTimeout: () => {},
|
endScrollTimeout: () => {},
|
||||||
|
|
||||||
|
isUnreadMarkerSeen: false,
|
||||||
|
|
||||||
|
loadingSummary: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -255,6 +285,13 @@ export default {
|
||||||
currentDay() {
|
currentDay() {
|
||||||
return convertToUnix(new Date().setHours(0, 0, 0, 0))
|
return convertToUnix(new Date().setHours(0, 0, 0, 0))
|
||||||
},
|
},
|
||||||
|
|
||||||
|
shouldShowSummaryOption() {
|
||||||
|
if (this.conversation.remoteServer || !canSummarizeChat || this.chatExtrasStore.hasChatSummaryTaskRequested(this.token)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return (this.conversation.unreadMessages >= summaryThreshold)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
|
|
@ -277,6 +314,7 @@ export default {
|
||||||
token(newToken, oldToken) {
|
token(newToken, oldToken) {
|
||||||
// Expire older messages when navigating to another conversation
|
// Expire older messages when navigating to another conversation
|
||||||
this.$store.dispatch('easeMessageList', { token: oldToken })
|
this.$store.dispatch('easeMessageList', { token: oldToken })
|
||||||
|
this.isUnreadMarkerSeen = false
|
||||||
},
|
},
|
||||||
|
|
||||||
messagesList: {
|
messagesList: {
|
||||||
|
|
@ -323,6 +361,16 @@ export default {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
visualLastReadMessageId(newValue, oldValue) {
|
||||||
|
if (newValue === oldValue) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const newGroups = this.prepareMessagesGroups(this.messagesList)
|
||||||
|
this.softUpdateByDateGroups(this.messagesGroupedByDateByAuthor, newGroups)
|
||||||
|
this.isUnreadMarkerSeen = false
|
||||||
|
},
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|
@ -488,6 +536,12 @@ export default {
|
||||||
return false // No previous message
|
return false // No previous message
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If there is last read message visually, the group ends there
|
||||||
|
if ((message1.id === this.visualLastReadMessageId && message2.id > message1.id)
|
||||||
|
|| (message2.id === this.visualLastReadMessageId && message1.id > message2.id)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
if (!!message1.lastEditTimestamp || !!message2.lastEditTimestamp) {
|
if (!!message1.lastEditTimestamp || !!message2.lastEditTimestamp) {
|
||||||
return false // Edited messages are not grouped
|
return false // Edited messages are not grouped
|
||||||
}
|
}
|
||||||
|
|
@ -813,7 +867,7 @@ export default {
|
||||||
const lastReadMessageElement = this.getVisualLastReadMessageElement()
|
const lastReadMessageElement = this.getVisualLastReadMessageElement()
|
||||||
|
|
||||||
// first unread message has not been seen yet, so don't move it
|
// first unread message has not been seen yet, so don't move it
|
||||||
if (lastReadMessageElement && lastReadMessageElement.getAttribute('data-seen') !== 'true') {
|
if (!this.isUnreadMarkerSeen) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1036,6 +1090,31 @@ export default {
|
||||||
this.debounceHandleScroll({ skipHeightCheck: true })
|
this.debounceHandleScroll({ skipHeightCheck: true })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isLastReadMessage(group) {
|
||||||
|
if (!group.nextMessageId) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = group.messages.at(-1)
|
||||||
|
if (this.conversation.lastMessage && message.id >= this.conversation.lastMessage?.id) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return message.id === this.visualLastReadMessageId
|
||||||
|
},
|
||||||
|
|
||||||
|
lastReadMessageVisibilityChanged([{ isIntersecting }]) {
|
||||||
|
if (isIntersecting) {
|
||||||
|
this.isUnreadMarkerSeen = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async generateSummary() {
|
||||||
|
this.loadingSummary = true
|
||||||
|
await this.chatExtrasStore.requestChatSummary(this.token, this.visualLastReadMessageId)
|
||||||
|
this.loadingSummary = false
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -1139,4 +1218,37 @@ export default {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transition: opacity 0s;
|
transition: opacity 0s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-unread-marker {
|
||||||
|
position: relative;
|
||||||
|
margin: calc(4 * var(--default-grid-baseline));
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
width: 100%;
|
||||||
|
border-top: 1px solid var(--color-border-maxcontrast);
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: calc(3 * var(--default-grid-baseline));
|
||||||
|
margin-inline: auto;
|
||||||
|
padding-inline: calc(3 * var(--default-grid-baseline));
|
||||||
|
width: fit-content;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
background-color: var(--color-main-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__text {
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--color-main-text);
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue