# ZeenChatMessages
Компонент Сообщений чата
# Примеры:
# Props
Название | Тип | Обязательный | По умолчанию |
---|---|---|---|
isReply | Boolean | - | false |
replyText | String | - | null |
replyName | String | - | null |
text | String | + | null |
admin | Object | - | {} |
time | String | + | null |
user | Object | - | null |
toSpeaker | Boolean | - | false |
# Source Code - исходный код компонента
<template>
<div class="message" :class="messageClass">
<div class="message__header">
<div :class="{'message__admin--container': isAdmin}">
<div class="text" :class="{'message__name-user-ask': askToSpeaker}" @click="openBadge">
<p class="message__name">
{{ authorName }}
</p>
<p class="message__name company" v-if="!hideCompany">
{{ authorCompany }}
</p>
<div class="message__status" v-if="hasAnswer">
<svg width="12" height="13" viewBox="0 0 12 13" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M6 1.875C3.44568 1.875 1.375 3.94568 1.375 6.5C1.375 7.24053 1.54876 7.9394 1.8574 8.55905C1.98127 8.80775 2.02897 9.10513 1.95082 9.39723L1.65301 10.5103C1.59832 10.7147 1.78533 10.9017 1.98975 10.847L3.10276 10.5492C3.39486 10.471 3.69225 10.5187 3.94095 10.6426C4.5606 10.9512 5.25947 11.125 6 11.125C8.55432 11.125 10.625 9.05432 10.625 6.5C10.625 3.94568 8.55432 1.875 6 1.875ZM0.625 6.5C0.625 3.53147 3.03147 1.125 6 1.125C8.96853 1.125 11.375 3.53147 11.375 6.5C11.375 9.46853 8.96853 11.875 6 11.875C5.14085 11.875 4.32776 11.6731 3.60657 11.3139C3.49895 11.2603 3.38907 11.249 3.29662 11.2737L2.1836 11.5715C1.42168 11.7754 0.724637 11.0783 0.928498 10.3164L1.2263 9.20338C1.25104 9.11093 1.23967 9.00105 1.18606 8.89343C0.826853 8.17224 0.625 7.35915 0.625 6.5ZM7.76516 5.23483C7.91161 5.38128 7.91161 5.61872 7.76516 5.76516L5.76516 7.76516C5.62083 7.9095 5.38755 7.91188 5.2403 7.77052L4.2403 6.81052C4.0909 6.66709 4.08605 6.4297 4.22948 6.2803C4.37291 6.1309 4.6103 6.12605 4.7597 6.26948L5.49464 6.97503L7.23484 5.23483C7.38128 5.08839 7.61872 5.08839 7.76516 5.23483Z"
/>
</svg>
{{ answered }}
</div>
</div>
<p v-if="askToSpeaker" class="message__speaker-name">
<span>
<svg width="8" height="9" viewBox="0 0 8 9" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M1 0.272461V3.66841C1 5.32526 2.34315 6.66841 4 6.66841H6.97103M6.97103 6.66841L4.98069 4.77331M6.97103 6.66841L4.98069 8.56351"
stroke="var(--zeen-chat-general-color)"
/>
</svg>
</span>
{{ speakerName }}
</p>
</div>
<div class="text message__time" v-if="statusPending">
{{ onModeration }}
</div>
<div class="text message__time message__time_reject" v-else-if="statusRejected">
{{ rejected }}
</div>
<p class="text message__time" v-else>{{ message.time }}</p>
</div>
<div class="message__body">
<div
v-if="message.is_reply"
class="message__reply"
:class="{message__reply_my: message.reply_my}"
@mouseover="showTooltip"
@mouseleave="hideTooltip"
@click="showTooltip"
v-click-outside="hideTooltip"
>
<p class="text message__name message__reply-name">{{ message.reply_name }}</p>
<div class="text message__text message__reply-text">
{{ questionText }}
<div class="message__tooltip" v-show="tooltipHandler">
<div class="message__tooltip-wrapper">
<div class="text">{{ message.text }}</div>
</div>
</div>
</div>
</div>
<p v-if="!isLink" class="text message__text">{{ messageText }}</p>
<template v-else>
<div>
<template v-for="(message, index) in messageWithLinkArray">
<span v-if="!message.url" class="text message__text" :key="index">{{ message }}</span>
<a v-else :href="message.href" class="text message__text message__link" :key="index" target="_blank">
{{ message.url }}
</a>
</template>
</div>
</template>
<img v-if="picture" class="message__img" :src="picture" alt="img" @click.prevent="openPicture" />
</div>
<div class="message__footer">
<slot name="message-footer" />
</div>
</div>
</template>
<script>
import isURL from 'validator/lib/isURL'
export default {
name: 'Message',
props: {
onModeration: {
type: String,
default: 'На модерации',
},
rejected: {
type: String,
default: 'Отклонено',
},
maxQuestionLength: {
type: Number,
default: 100,
},
hideCompany: {
type: Boolean,
default: true,
},
message: {
type: Object,
default: () => ({
status: null,
isMy: null,
reply_my: null,
is_reply: null,
reply_text: null,
reply_name: null,
text: null,
admin: null,
speaker: null,
time: null,
user: null,
picture: null,
is_answered: null,
}),
},
answered: {
type: String,
default: 'отвечен в эфире',
},
showAnswer: {
type: Boolean,
default: true,
},
},
data() {
return {
tooltip: false,
}
},
methods: {
openBadge() {
if (this.message.user) {
this.$emit('openBadge', this.message.user)
}
},
showTooltip() {
this.tooltip = true
},
hideTooltip() {
this.tooltip = false
},
openPicture() {
this.$emit('open-picture', this.picture)
},
linkValidate(link) {
return isURL(link, {require_protocol: true}) ? link : `https://${link}`
},
},
computed: {
author() {
if (!this.message.user && !this.message.admin && this.message.speaker) {
return this.message.speaker
}
if (this.message.user && !this.message.admin && !this.message.speaker) {
return this.message.user
}
if (this.message.admin && !this.message.speaker) {
return this.message.admin
}
return this.message.user
},
authorName() {
return this.author?.name ?? ''
},
authorCompany() {
return this.author?.company ?? ''
},
speakerName() {
return this.message.speaker?.name ?? ''
},
isAdmin() {
return this.message.admin
},
askToSpeaker() {
return this.message.user && this.message.speaker
},
questionText() {
const text = this.message.text
if (text.length > this.maxQuestionLength) {
const textResize = text.slice(0, this.maxQuestionLength).split(' ')
const textDeleteLast = textResize.length > 1 ? textResize.slice(0, -1) : textResize
return `${textDeleteLast.join(' ')}...`
}
return text
},
messageText() {
return this.message.is_reply ? this.message.reply_text : this.message.text
},
arrayFromMessage() {
return this.messageText.split(' ')
},
isLink() {
if (!this.isAdmin) return false
return this.arrayFromMessage.some((text) => isURL(text))
},
messageWithLinkArray() {
if (!this.isLink) return []
let messageArray = []
let textArray = []
this.arrayFromMessage.forEach((text) => {
if (isURL(text)) {
textArray = textArray.join(' ')
messageArray.push(textArray)
const linkObj = {
url: text,
href: this.linkValidate(text),
}
messageArray.push(linkObj)
textArray = []
} else {
textArray.push(text)
}
})
textArray = textArray.join(' ')
messageArray.push(textArray)
return messageArray
},
picture() {
return this.message?.picture?.url ?? ''
},
tooltipHandler() {
return this.message.text.length > this.maxQuestionLength && this.tooltip
},
messageClass() {
return {
message_my: this.message.isMy && !this.message.is_reply,
message_replay: this.message.is_reply,
}
},
statusPending() {
return this.message.status === 'pending'
},
statusRejected() {
return this.message.status === 'rejected'
},
hasAnswer() {
return this.message?.is_answered && this.showAnswer
},
},
}
</script>
<style lang="scss">
:root {
/* Размеры */
--zeen-message-border-width: 1px;
--zeen-message-radius: 30px;
--zeen-message-line-height: 1.5;
--zeen-message-small-gap: 5px;
--zeen-message-gap: calc(var(--zeen-message-small-gap) * 2);
--zeen-message-font-size: var(--main-smallest-text);
--zeen-message-small-font-size: var(--main-input-label-size);
--zeen-message-border: var(--zeen-message-border-width) solid var(--zeen-chat-general-color);
--zeen-message-margin: var(--zeen-chat-md-gap) 0;
--zeen-chat-tooltip-padding-vertical: 5px;
--zeen-chat-tooltip-padding-horizontal: 10px;
--zeen-chat-tooltip-border-radius: 5px;
--zeen-chat-tooltip-max-height: 200px;
--zeen-message-img-width: 200px;
--zeen-message-img-height: 150px;
--zeen-message-status-font-size: 10px;
--zeen-message-status-padding: 2px 6px;
--zeen-message-status-border-radius: 5px;
--zeen-message-status-icon-size: 12px;
/* Цвета */
--zeen-message-text-color-secondary: var(--gray-4);
--zeen-message-text-color: var(--main-positive-color);
--zeen-chat-tooltip-background: var(--dark-1);
--zeen-chat-tooltip-color: var(--main-light);
--zeen-message-img-border-radius: 15px;
--zeen-message-img-margin-top: 5px;
--zeen-message-status-color: #27bd7e;
--zeen-message-status-background: rgba(39, 189, 126, 0.1);
}
</style>
<style lang="scss" scoped>
.text {
font-size: var(--zeen-message-font-size);
line-height: var(--zeen-message-line-height);
color: var(--zeen-message-text-color);
margin: 0;
}
.message {
margin-top: var(--zeen-chat-lg-gap);
&__name {
display: inline;
font-weight: bold;
}
&__status {
display: inline-flex;
align-items: center;
color: var(--zeen-message-status-color);
font-size: var(--zeen-message-status-font-size);
padding: var(--zeen-message-status-padding);
border-radius: var(--zeen-message-status-border-radius);
background: var(--zeen-message-status-background);
margin-left: 10px;
& svg {
margin-right: 6px;
fill: currentColor;
width: var(--zeen-message-status-icon-size);
height: var(--zeen-message-status-icon-size);
flex-shrink: 0;
}
}
&__header {
display: flex;
justify-content: space-between;
cursor: pointer;
}
&__speaker-name {
margin: 0;
font-size: var(--zeen-message-small-font-size);
color: var(--zeen-chat-general-color);
}
&__name-user-ask {
font-weight: bold;
}
&__admin--container {
.text {
color: var(--zeen-chat-white);
background: var(--zeen-chat-general-color);
border-radius: var(--zeen-message-radius);
padding: 0 var(--zeen-chat-md-gap);
}
}
&__text {
position: relative;
}
&__link {
color: var(--zeen-chat-general-color);
}
&__tooltip {
position: absolute;
display: flex;
justify-content: center;
padding: var(--zeen-chat-tooltip-padding-vertical) var(--zeen-chat-tooltip-padding-horizontal);
top: 100%;
left: 0;
width: 100%;
max-height: var(--zeen-chat-tooltip-max-height);
background: var(--zeen-chat-tooltip-background);
border-radius: var(--zeen-chat-tooltip-border-radius);
z-index: 10;
.text {
color: var(--zeen-chat-tooltip-color);
}
&::after {
content: '';
position: absolute;
bottom: 100%;
left: 10%;
margin-left: -5px;
border-width: 5px;
border-style: solid;
border-color: transparent transparent var(--zeen-chat-tooltip-background) transparent;
}
}
&__tooltip-wrapper {
overflow-y: auto;
padding-right: 5px;
&::-webkit-scrollbar {
width: var(--zeen-chat-scrollbar-width);
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: transparent;
border-radius: var(--zeen-chat-scrollbar-thumb-borderradius);
}
&::-webkit-scrollbar-button {
background: transparent;
height: 3px;
}
&:hover {
&::-webkit-scrollbar-thumb {
background: var(--zeen-chat-general-color);
}
}
}
&__time {
color: var(--zeen-message-text-color);
&_reject {
color: var(--main-danger-color);
}
}
&__body {
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex-direction: column;
margin-top: var(--zeen-chat-md-gap);
.message__reply {
margin: var(--zeen-message-margin);
padding-left: var(--zeen-chat-md-gap);
border-left: var(--zeen-message-border);
.message__reply-name {
color: var(--zeen-chat-general-color);
}
.message__reply-text {
color: var(--zeen-message-text-color-secondary);
margin-top: var(--zeen-chat-sm-gap);
}
}
}
&__footer {
margin-top: var(--zeen-message-gap);
}
&__img {
max-width: var(--zeen-message-img-width);
max-height: var(--zeen-message-img-height);
border-radius: var(--zeen-message-img-border-radius);
margin-top: var(--zeen-message-img-margin-top);
cursor: pointer;
}
}
</style>