Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(MessageGroup) - group system messages #9777

Merged
merged 9 commits into from
Aug 8, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,7 @@ describe('Message.vue', () => {

test('renders author if first message', async () => {
messageProps.isFirstMessage = true
messageProps.showAuthor = true
const wrapper = shallowMount(Message, {
localVue,
store,
Expand Down
79 changes: 70 additions & 9 deletions src/components/MessagesList/MessagesGroup/Message/Message.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ the main body of the message as well as a quote.
@animationend="isHighlighted = false"
@mouseover="handleMouseover"
@mouseleave="handleMouseleave">
<div :class="{'normal-message-body': !isSystemMessage && !isDeletedMessage, 'system' : isSystemMessage}"
<div :class="{'normal-message-body': !isSystemMessage && !isDeletedMessage,
'system' : isSystemMessage,
'combined-system': isCombinedSystemMessage}"
class="message-body">
<div v-if="isFirstMessage && showAuthor"
class="message-body__author"
Expand Down Expand Up @@ -184,6 +186,7 @@ the main body of the message as well as a quote.
<div class="message-body__scroll">
<MessageButtonsBar v-if="showMessageButtonsBar"
ref="messageButtonsBar"
class="message-buttons-bar"
:is-translation-available="isTranslationAvailable"
:is-action-menu-open.sync="isActionMenuOpen"
:is-emoji-picker-open.sync="isEmojiPickerOpen"
Expand All @@ -201,6 +204,18 @@ the main body of the message as well as a quote.
:sent-icon-tooltip="sentIconTooltip"
@show-translate-dialog="isTranslateDialogOpen = true"
@delete="handleDelete" />
<div v-else-if="showCombinedSystemMessageToggle"
class="message-buttons-bar">
<NcButton type="tertiary"
:aria-label="t('spreed', 'Show or collapse system messages')"
:title="t('spreed', 'Show or collapse system messages')"
@click="toggleCombinedSystemMessage">
<template #icon>
<UnfoldMore v-if="isCombinedSystemMessageCollapsed" />
<UnfoldLess v-else />
</template>
</NcButton>
</div>
</div>

<MessageTranslateDialog v-if="isTranslateDialogOpen"
Expand All @@ -225,6 +240,8 @@ import Check from 'vue-material-design-icons/Check.vue'
import CheckAll from 'vue-material-design-icons/CheckAll.vue'
import EmoticonOutline from 'vue-material-design-icons/EmoticonOutline.vue'
import Reload from 'vue-material-design-icons/Reload.vue'
import UnfoldLess from 'vue-material-design-icons/UnfoldLessHorizontal.vue'
import UnfoldMore from 'vue-material-design-icons/UnfoldMoreHorizontal.vue'

import { getCapabilities } from '@nextcloud/capabilities'
import { showError, showSuccess, showWarning, TOAST_DEFAULT_TIMEOUT } from '@nextcloud/dialogs'
Expand Down Expand Up @@ -276,6 +293,8 @@ export default {
CheckAll,
EmoticonOutline,
Reload,
UnfoldLess,
UnfoldMore,
},

mixins: [
Expand Down Expand Up @@ -341,22 +360,22 @@ export default {
*/
showAuthor: {
type: Boolean,
default: true,
default: false,
},
/**
* Specifies if the message is temporary in order to display the spinner instead
* of the message time.
*/
isTemporary: {
type: Boolean,
required: true,
default: false,
},
/**
* Specifies if the message is the first of a group of same-author messages.
*/
isFirstMessage: {
type: Boolean,
required: true,
default: false,
},
/**
* Specifies if the message can be replied to.
Expand All @@ -379,6 +398,20 @@ export default {
type: String,
required: true,
},
/**
* Specifies if the message is a combined system message.
*/
isCombinedSystemMessage: {
type: Boolean,
default: false,
},
/**
* Specifies whether the combined system message is collapsed.
*/
isCombinedSystemMessageCollapsed: {
type: Boolean,
default: undefined,
},
/**
* The type of the message.
*/
Expand Down Expand Up @@ -426,6 +459,8 @@ export default {
},
},

emits: ['toggle-combined-system-message'],

setup() {
const isInCall = useIsInCall()
return { isInCall, isTranslationAvailable }
Expand Down Expand Up @@ -613,6 +648,11 @@ export default {
|| this.isReactionsMenuOpen || this.isForwarderOpen || this.isTranslateDialogOpen)
},

showCombinedSystemMessageToggle() {
return this.isSystemMessage && !this.isDeletedMessage && !this.isTemporary
&& this.isCombinedSystemMessage && (this.isHovered || !this.isCombinedSystemMessageCollapsed)
},

isTemporaryUpload() {
return this.isTemporary && this.messageParameters.file
},
Expand Down Expand Up @@ -851,24 +891,29 @@ export default {

return displayName
},

toggleCombinedSystemMessage() {
this.$emit('toggle-combined-system-message')
},
},
}
</script>

<style lang="scss" scoped>
@import '../../../../assets/variables';

.message:hover .normal-message-body {
border-radius: 8px;
background-color: var(--color-background-hover);
}

.message {
position: relative;

&__last {
margin-bottom: 12px;
}

&:hover .normal-message-body,
&:hover .combined-system {
border-radius: 8px;
background-color: var(--color-background-hover);
}
}

.message-body {
Expand Down Expand Up @@ -1025,6 +1070,22 @@ export default {
padding: 8px;
}

.message-buttons-bar {
display: flex;
right: 14px;
top: 8px;
position: sticky;
background-color: var(--color-main-background);
border-radius: calc(var(--default-clickable-area) / 2);
box-shadow: 0 0 4px 0 var(--color-box-shadow);
height: 44px;
z-index: 1;

& h6 {
margin-left: auto;
}
}

.message-body__main__text--markdown {
:deep(.rich-text--wrapper) {
h1 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@

<template>
<!-- Message Actions -->
<div v-click-outside="handleClickOutside"
class="message-buttons-bar">
<div v-click-outside="handleClickOutside">
<template v-if="!isReactionsMenuOpen">
<NcButton v-if="canReact"
type="tertiary"
Expand Down Expand Up @@ -578,24 +577,3 @@ export default {
},
}
</script>

<style lang="scss" scoped>
@import '../../../../../assets/variables';

.message-buttons-bar {
display: flex;
right: 14px;
top: 8px;
position: sticky;
background-color: var(--color-main-background);
border-radius: calc(var(--default-clickable-area) / 2);
box-shadow: 0 0 4px 0 var(--color-box-shadow);
height: 44px;
z-index: 1;

& h6 {
margin-left: auto;
}
}

</style>
87 changes: 45 additions & 42 deletions src/components/MessagesList/MessagesGroup/MessagesGroup.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { cloneDeep } from 'lodash'
import Vuex from 'vuex'

import MessagesGroup from './MessagesGroup.vue'
import MessagesSystemGroup from './MessagesSystemGroup.vue'

import { ATTENDEE } from '../../../constants.js'
import storeConfig from '../../../store/storeConfig.js'
Expand Down Expand Up @@ -123,7 +124,33 @@ describe('MessagesGroup.vue', () => {
})

test('renders grouped system messages', () => {
const wrapper = shallowMount(MessagesGroup, {
const MESSAGES = [{
id: 100,
token: TOKEN,
actorId: 'actor-1',
actorDisplayName: 'actor one',
actorType: ATTENDEE.ACTOR_TYPE.USERS,
message: 'Actor entered the scene',
messageType: 'comment',
messageParameters: {},
systemMessage: 'call_started',
timestamp: 100,
isReplyable: false,
}, {
id: 110,
token: TOKEN,
actorId: 'actor-1',
actorDisplayName: 'actor one',
actorType: ATTENDEE.ACTOR_TYPE.USERS,
message: 'Actor left the scene',
messageType: 'comment',
messageParameters: {},
systemMessage: 'call_stopped',
timestamp: 200,
isReplyable: false,
}]

const wrapper = shallowMount(MessagesSystemGroup, {
localVue,
store,
propsData: {
Expand All @@ -132,31 +159,7 @@ describe('MessagesGroup.vue', () => {
dateSeparator: '<date separator>',
previousMessageId: 90,
nextMessageId: 200,
messages: [{
id: 100,
token: TOKEN,
actorId: 'actor-1',
actorDisplayName: 'actor one',
actorType: ATTENDEE.ACTOR_TYPE.USERS,
message: 'Actor entered the scene',
messageType: 'comment',
messageParameters: {},
systemMessage: 'call_started',
timestamp: 100,
isReplyable: false,
}, {
id: 110,
token: TOKEN,
actorId: 'actor-1',
actorDisplayName: 'actor one',
actorType: ATTENDEE.ACTOR_TYPE.USERS,
message: 'Actor left the scene',
messageType: 'comment',
messageParameters: {},
systemMessage: 'call_stopped',
timestamp: 200,
isReplyable: false,
}],
messages: MESSAGES,
},
})

Expand All @@ -169,24 +172,24 @@ describe('MessagesGroup.vue', () => {
const messagesEl = wrapper.findAllComponents({ name: 'Message' })
// TODO: date separator
let message = messagesEl.at(0)
expect(message.attributes('id')).toBe('100')
expect(message.attributes('message')).toBe('Actor entered the scene')
expect(message.attributes('actorid')).toBe('actor-1')
expect(message.attributes('actordisplayname')).toBe('actor one')
expect(message.attributes('previousmessageid')).toBe('90')
expect(message.attributes('nextmessageid')).toBe('110')
expect(message.attributes('isfirstmessage')).toBe('true')
expect(message.attributes('showauthor')).not.toBeDefined()
expect(message.props('id')).toBe(MESSAGES[0].id)
expect(message.props('message')).toBe(MESSAGES[0].message)
expect(message.props('actorid')).toBe(MESSAGES[0].actorid)
expect(message.props('actordisplayname')).toBe(MESSAGES[0].actordisplayname)
expect(message.props('previousmessageid')).toBe(MESSAGES[0].previousmessageid)
expect(message.props('nextmessageid')).toBe(MESSAGES[0].nextmessageid)
expect(message.props('isfirstmessage')).toBe(MESSAGES[0].isfirstmessage)
expect(message.props('showauthor')).not.toBeDefined()

message = messagesEl.at(1)
expect(message.attributes('id')).toBe('110')
expect(message.attributes('message')).toBe('Actor left the scene')
expect(message.attributes('actorid')).toBe('actor-1')
expect(message.attributes('actordisplayname')).toBe('actor one')
expect(message.attributes('previousmessageid')).toBe('100')
expect(message.attributes('nextmessageid')).toBe('200')
expect(message.attributes('isfirstmessage')).not.toBeDefined()
expect(message.attributes('showauthor')).not.toBeDefined()
expect(message.props('id')).toBe(MESSAGES[1].id)
expect(message.props('message')).toBe(MESSAGES[1].message)
expect(message.props('actorid')).toBe(MESSAGES[1].actorid)
expect(message.props('actordisplayname')).toBe(MESSAGES[1].actordisplayname)
expect(message.props('previousmessageid')).toBe(MESSAGES[1].previousmessageid)
expect(message.props('nextmessageid')).toBe(MESSAGES[1].nextmessageid)
expect(message.props('isfirstmessage')).not.toBeDefined()
expect(message.props('showauthor')).not.toBeDefined()
})

test('renders guest display name', () => {
Expand Down
20 changes: 4 additions & 16 deletions src/components/MessagesList/MessagesGroup/MessagesGroup.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,8 @@
role="heading"
aria-level="3">{{ dateSeparator }}</span>
</div>
<div class="wrapper"
:class="{'wrapper--system': isSystemMessage}">
<div v-if="!isSystemMessage" class="messages__avatar">
<div class="wrapper">
<div class="messages__avatar">
<AuthorAvatar :author-type="actorType"
:author-id="actorId"
:display-name="actorDisplayName" />
Expand All @@ -40,13 +39,13 @@
ref="message"
v-bind="message"
:is-first-message="index === 0"
:is-temporary="message.timestamp === 0"
:next-message-id="(messages[index + 1] && messages[index + 1].id) || nextMessageId"
:previous-message-id="(index > 0 && messages[index - 1].id) || previousMessageId"
:actor-type="actorType"
:actor-id="actorId"
:actor-display-name="actorDisplayName"
:show-author="!isSystemMessage"
:is-temporary="message.timestamp === 0" />
show-author />
</ul>
</div>
</div>
Expand Down Expand Up @@ -138,14 +137,6 @@ export default {

return displayName
},
/**
* Whether the given message is a system message
*
* @return {boolean}
*/
isSystemMessage() {
return this.messages[0].systemMessage.length !== 0
},
},

methods: {
Expand Down Expand Up @@ -188,9 +179,6 @@ export default {
display: flex;
margin: auto;
padding: 0;
&--system {
padding-left: $clickable-area + 8px;
}
&:focus {
background-color: rgba(47, 47, 47, 0.068);
}
Expand Down
Loading
Loading