mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-03 04:27:53 +00:00
feat: Update reply box UI 👀 (#1623)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
This commit is contained in:
committed by
GitHub
parent
2d5aa9d3bd
commit
fd181f18a1
@@ -96,9 +96,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.conversation-wrap {
|
.conversation-wrap {
|
||||||
@include background-gray;
|
|
||||||
@include margin(0);
|
@include margin(0);
|
||||||
@include border-normal-left;
|
@include border-normal-left;
|
||||||
|
background: var(--color-background-light);
|
||||||
|
|
||||||
.current-chat {
|
.current-chat {
|
||||||
@include flex;
|
@include flex;
|
||||||
@@ -140,8 +140,8 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
// Firefox flexbox fix
|
// Firefox flexbox fix
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin-bottom: $space-small;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
padding-bottom: var(--space-normal);
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +1,13 @@
|
|||||||
.reply-box {
|
.reply-box {
|
||||||
@include light-shadow;
|
|
||||||
border-bottom: 0;
|
|
||||||
border-radius: $space-small;
|
|
||||||
margin: $space-normal;
|
|
||||||
margin-top: 0;
|
|
||||||
max-height: $space-mega * 3;
|
|
||||||
transition: box-shadow .35s $swift-ease-out-function,
|
transition: box-shadow .35s $swift-ease-out-function,
|
||||||
height 2s $swift-ease-out-function;
|
height 2s $swift-ease-out-function;
|
||||||
|
|
||||||
|
|
||||||
&.is-focused {
|
&.is-focused {
|
||||||
@include shadow;
|
box-shadow: var(--shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
.reply-box__top {
|
.reply-box__top {
|
||||||
@include flex;
|
|
||||||
@include flex-align($x: left, $y: middle);
|
|
||||||
@include padding($space-one $space-normal);
|
|
||||||
@include background-white;
|
|
||||||
@include margin(0);
|
|
||||||
border-top-left-radius: $space-small;
|
|
||||||
border-top-right-radius: $space-small;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.canned {
|
.canned {
|
||||||
@include elegant-card;
|
@include elegant-card;
|
||||||
background: $color-white;
|
background: $color-white;
|
||||||
@@ -41,19 +26,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.is-active {
|
|
||||||
border-bottom-left-radius: $space-small;
|
|
||||||
border-bottom-right-radius: $space-small;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-private {
|
|
||||||
background: lighten($warning-color, 38%);
|
|
||||||
|
|
||||||
>input {
|
|
||||||
background: lighten($warning-color, 38%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
color: $medium-gray;
|
color: $medium-gray;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -65,9 +37,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-uploads>label {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.attachment {
|
.attachment {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -82,87 +51,45 @@
|
|||||||
// Override min-height : 50px in foundation
|
// Override min-height : 50px in foundation
|
||||||
//
|
//
|
||||||
max-height: $space-mega * 2.4;
|
max-height: $space-mega * 2.4;
|
||||||
min-height: 4rem;
|
min-height: 4.8rem;
|
||||||
|
padding: var(--space-normal) 0 0;
|
||||||
resize: none;
|
resize: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.reply-box__bottom {
|
&.is-private {
|
||||||
@include background-light;
|
background: var(--y-50);
|
||||||
@include flex;
|
|
||||||
@include flex-align($x: justify, $y: middle);
|
|
||||||
@include border-light-top;
|
|
||||||
border-bottom-left-radius: $space-small;
|
|
||||||
border-bottom-right-radius: $space-small;
|
|
||||||
|
|
||||||
.tabs {
|
.reply-box__top {
|
||||||
border: 0;
|
background: var(--y-50);
|
||||||
flex: 1;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
.tabs-title {
|
>input {
|
||||||
margin: 0;
|
background: var(--y-50);
|
||||||
transition: all .2s $swift-ease-out-function;
|
|
||||||
transition-property: color, background;
|
|
||||||
|
|
||||||
a {
|
|
||||||
font-weight: $font-weight-medium;
|
|
||||||
padding: $space-one $space-two;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-private.is-active {
|
|
||||||
background: lighten($warning-color, 38%);
|
|
||||||
|
|
||||||
a {
|
|
||||||
border-bottom-color: darken($warning-color, 15%);
|
|
||||||
color: darken($warning-color, 15%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs-title:first-child {
|
|
||||||
border-bottom-left-radius: $space-small;
|
|
||||||
|
|
||||||
&.is-active {
|
|
||||||
@include border-light-right;
|
|
||||||
border-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
border-bottom-left-radius: $space-small;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-active {
|
|
||||||
@include background-white;
|
|
||||||
@include border-light-left;
|
|
||||||
@include border-light-right;
|
|
||||||
margin-top: -1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-length {
|
|
||||||
float: right;
|
|
||||||
|
|
||||||
a {
|
|
||||||
font-size: $font-size-mini;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-error {
|
|
||||||
color: $input-error-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.send-button {
|
|
||||||
border-bottom-right-radius: $space-small;
|
|
||||||
height: 3.6rem;
|
|
||||||
padding-left: $space-two;
|
|
||||||
padding-right: $space-two;
|
|
||||||
padding-top: $space-small;
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
margin-left: $space-small;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.file-uploads>label {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover .button--emoji {
|
||||||
|
background: var(--b-200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-box .button--emoji.button--upload {
|
||||||
|
height: var(--space-large);
|
||||||
|
padding: 0;
|
||||||
|
width: var(--space-large);
|
||||||
|
|
||||||
|
.file-uploads {
|
||||||
|
height: 100%;
|
||||||
|
line-height: var(--space-large);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
padding: var(--space-small);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,9 @@ export default {
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.preview-item {
|
.preview-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 0 var(--space-small) var(--space-smaller);
|
padding: var(--space-slab) 0 0;
|
||||||
|
background: var(--color-background-light);
|
||||||
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.thumb-wrap {
|
.thumb-wrap {
|
||||||
@@ -89,13 +91,16 @@ export default {
|
|||||||
height: var(--space-medium);
|
height: var(--space-medium);
|
||||||
font-size: var(--font-size-medium);
|
font-size: var(--font-size-medium);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
top: -1px;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-name-wrap,
|
.file-name-wrap,
|
||||||
.file-size-wrap {
|
.file-size-wrap {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 var(--space-one);
|
padding: 0 var(--space-smaller);
|
||||||
|
|
||||||
> .item {
|
> .item {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -109,12 +114,20 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.file-name-wrap {
|
.file-name-wrap {
|
||||||
width: 100%;
|
max-width: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
.item {
|
||||||
|
height: var(--space-normal);
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-size-wrap {
|
.file-size-wrap {
|
||||||
width: 20%;
|
width: 20%;
|
||||||
justify-content: flex-end;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.remove-file-wrap {
|
.remove-file-wrap {
|
||||||
|
|||||||
@@ -0,0 +1,178 @@
|
|||||||
|
<template>
|
||||||
|
<div class="bottom-box" :class="wrapClass">
|
||||||
|
<div class="left-wrap">
|
||||||
|
<button class="button clear button--emoji" @click="toggleEmojiPicker">
|
||||||
|
<emoji-or-icon icon="ion-happy-outline" emoji="😊" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="showAttachButton"
|
||||||
|
class="button clear button--emoji button--upload"
|
||||||
|
>
|
||||||
|
<file-upload
|
||||||
|
:size="4096 * 4096"
|
||||||
|
accept="image/*, application/pdf, audio/mpeg, video/mp4, audio/ogg, text/csv"
|
||||||
|
@input-file="onFileUpload"
|
||||||
|
>
|
||||||
|
<emoji-or-icon icon="ion-android-attach" emoji="📎" />
|
||||||
|
</file-upload>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="right-wrap">
|
||||||
|
<button
|
||||||
|
class="button nice primary button--send"
|
||||||
|
:class="buttonClass"
|
||||||
|
@click="onSend"
|
||||||
|
>
|
||||||
|
{{ sendButtonText }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import FileUpload from 'vue-upload-component';
|
||||||
|
import EmojiOrIcon from 'shared/components/EmojiOrIcon';
|
||||||
|
|
||||||
|
import { REPLY_EDITOR_MODES } from './constants';
|
||||||
|
export default {
|
||||||
|
name: 'ReplyTopPanel',
|
||||||
|
components: { EmojiOrIcon, FileUpload },
|
||||||
|
props: {
|
||||||
|
mode: {
|
||||||
|
type: String,
|
||||||
|
default: REPLY_EDITOR_MODES.REPLY,
|
||||||
|
},
|
||||||
|
onSend: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
sendButtonText: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
showFileUpload: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
onFileUpload: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
showEmojiPicker: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
toggleEmojiPicker: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
isSendDisabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isNote() {
|
||||||
|
return this.mode === REPLY_EDITOR_MODES.NOTE;
|
||||||
|
},
|
||||||
|
wrapClass() {
|
||||||
|
return {
|
||||||
|
'is-note-mode': this.isNote,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
buttonClass() {
|
||||||
|
return {
|
||||||
|
'button--note': this.isNote,
|
||||||
|
'button--disabled': this.isSendDisabled,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
showAttachButton() {
|
||||||
|
return this.showFileUpload || this.isNote;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '~widget/assets/scss/variables.scss';
|
||||||
|
@import '~widget/assets/scss/mixins.scss';
|
||||||
|
|
||||||
|
.bottom-box {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: var(--space-slab) var(--space-normal);
|
||||||
|
|
||||||
|
&.is-note-mode {
|
||||||
|
background: var(--y-50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
padding: var(--space-one) var(--space-slab);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--w-300);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.button--emoji {
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
padding: var(--space-small);
|
||||||
|
border-radius: 9px;
|
||||||
|
background: var(--b-50);
|
||||||
|
border: 1px solid var(--color-border-light);
|
||||||
|
margin-right: var(--space-small);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--b-200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.button--note {
|
||||||
|
background: var(--y-800);
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--y-700);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.button--disabled {
|
||||||
|
background: var(--b-100);
|
||||||
|
color: var(--b-400);
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--b-100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-box.is-note-mode {
|
||||||
|
.button--emoji {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--reply {
|
||||||
|
border-right: 1px solid var(--color-border-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon--font {
|
||||||
|
color: var(--s-600);
|
||||||
|
font-size: var(--font-size-default);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
<template>
|
||||||
|
<div class="top-box">
|
||||||
|
<div class="mode-wrap button-group">
|
||||||
|
<button
|
||||||
|
class="button clear button--reply"
|
||||||
|
:class="replyButtonClass"
|
||||||
|
@click="handleReplyClick"
|
||||||
|
>
|
||||||
|
<emoji-or-icon icon="" emoji="💬" />
|
||||||
|
{{ $t('CONVERSATION.REPLYBOX.REPLY') }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="button clear button--note"
|
||||||
|
:class="noteButtonClass"
|
||||||
|
@click="handleNoteClick"
|
||||||
|
>
|
||||||
|
<emoji-or-icon icon="" emoji="📝" />
|
||||||
|
{{ $t('CONVERSATION.REPLYBOX.PRIVATE_NOTE') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="action-wrap"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { REPLY_EDITOR_MODES } from './constants';
|
||||||
|
import EmojiOrIcon from 'shared/components/EmojiOrIcon';
|
||||||
|
export default {
|
||||||
|
name: 'ReplyTopPanel',
|
||||||
|
components: {
|
||||||
|
EmojiOrIcon,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
mode: {
|
||||||
|
type: String,
|
||||||
|
default: REPLY_EDITOR_MODES.REPLY,
|
||||||
|
},
|
||||||
|
setReplyMode: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
replyButtonClass() {
|
||||||
|
return {
|
||||||
|
'is-active': this.mode === REPLY_EDITOR_MODES.REPLY,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
noteButtonClass() {
|
||||||
|
return {
|
||||||
|
'is-active': this.mode === REPLY_EDITOR_MODES.NOTE,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleReplyClick() {
|
||||||
|
this.setReplyMode(REPLY_EDITOR_MODES.REPLY);
|
||||||
|
},
|
||||||
|
handleNoteClick() {
|
||||||
|
this.setReplyMode(REPLY_EDITOR_MODES.NOTE);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.top-box {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
background: var(--b-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
border: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.button {
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
padding: var(--space-one) var(--space-normal);
|
||||||
|
margin: 0;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--reply {
|
||||||
|
border-right: 1px solid var(--color-border);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-right: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--note {
|
||||||
|
&.is-active {
|
||||||
|
border-right: 1px solid var(--color-border);
|
||||||
|
background: var(--y-50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--note {
|
||||||
|
color: var(--y-900);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export const REPLY_EDITOR_MODES = {
|
||||||
|
REPLY: 'REPLY',
|
||||||
|
NOTE: 'NOTE',
|
||||||
|
};
|
||||||
@@ -1,102 +1,61 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="reply-box" :class="replyBoxClass">
|
||||||
|
<reply-top-panel :mode="replyType" :set-reply-mode="setReplyMode" />
|
||||||
|
<div class="reply-box__top">
|
||||||
|
<canned-response
|
||||||
|
v-if="showCannedResponsesList"
|
||||||
|
v-on-clickaway="hideCannedResponse"
|
||||||
|
data-dropdown-menu
|
||||||
|
:on-keyenter="replaceText"
|
||||||
|
:on-click="replaceText"
|
||||||
|
/>
|
||||||
|
<emoji-input
|
||||||
|
v-if="showEmojiPicker"
|
||||||
|
v-on-clickaway="hideEmojiPicker"
|
||||||
|
:on-click="emojiOnClick"
|
||||||
|
/>
|
||||||
|
<resizable-text-area
|
||||||
|
ref="messageInput"
|
||||||
|
v-model="message"
|
||||||
|
class="input"
|
||||||
|
:placeholder="messagePlaceHolder"
|
||||||
|
:min-height="4"
|
||||||
|
@typing-off="onTypingOff"
|
||||||
|
@typing-on="onTypingOn"
|
||||||
|
@focus="onFocus"
|
||||||
|
@blur="onBlur"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div v-if="hasAttachments" class="attachment-preview-box">
|
<div v-if="hasAttachments" class="attachment-preview-box">
|
||||||
<attachment-preview
|
<attachment-preview
|
||||||
:attachments="attachedFiles"
|
:attachments="attachedFiles"
|
||||||
:remove-attachment="removeAttachment"
|
:remove-attachment="removeAttachment"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="reply-box" :class="replyBoxClass">
|
<reply-bottom-panel
|
||||||
<div class="reply-box__top" :class="{ 'is-private': isPrivate }">
|
:mode="replyType"
|
||||||
<canned-response
|
:send-button-text="replyButtonLabel"
|
||||||
v-if="showCannedResponsesList"
|
:on-file-upload="onFileUpload"
|
||||||
v-on-clickaway="hideCannedResponse"
|
:show-file-upload="showFileUpload"
|
||||||
data-dropdown-menu
|
:toggle-emoji-picker="toggleEmojiPicker"
|
||||||
:on-keyenter="replaceText"
|
:show-emoji-picker="showEmojiPicker"
|
||||||
:on-click="replaceText"
|
:on-send="sendMessage"
|
||||||
/>
|
:is-send-disabled="isReplyButtonDisabled"
|
||||||
<emoji-input
|
/>
|
||||||
v-if="showEmojiPicker"
|
|
||||||
v-on-clickaway="hideEmojiPicker"
|
|
||||||
:on-click="emojiOnClick"
|
|
||||||
/>
|
|
||||||
<resizable-text-area
|
|
||||||
ref="messageInput"
|
|
||||||
v-model="message"
|
|
||||||
class="input"
|
|
||||||
:placeholder="messagePlaceHolder"
|
|
||||||
:min-height="4"
|
|
||||||
@typing-off="onTypingOff"
|
|
||||||
@typing-on="onTypingOn"
|
|
||||||
@focus="onFocus"
|
|
||||||
@blur="onBlur"
|
|
||||||
/>
|
|
||||||
<file-upload
|
|
||||||
v-if="showFileUpload"
|
|
||||||
:size="4096 * 4096"
|
|
||||||
accept="image/*, application/pdf, audio/mpeg, video/mp4, audio/ogg, text/csv"
|
|
||||||
@input-file="onFileUpload"
|
|
||||||
>
|
|
||||||
<i class="icon ion-android-attach attachment" />
|
|
||||||
</file-upload>
|
|
||||||
<i
|
|
||||||
class="icon ion-happy-outline"
|
|
||||||
:class="{ active: showEmojiPicker }"
|
|
||||||
@click="toggleEmojiPicker"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="reply-box__bottom">
|
|
||||||
<ul class="tabs">
|
|
||||||
<li class="tabs-title" :class="{ 'is-active': !isPrivate }">
|
|
||||||
<a href="#" @click="setReplyMode">{{
|
|
||||||
$t('CONVERSATION.REPLYBOX.REPLY')
|
|
||||||
}}</a>
|
|
||||||
</li>
|
|
||||||
<li class="tabs-title is-private" :class="{ 'is-active': isPrivate }">
|
|
||||||
<a href="#" @click="setPrivateReplyMode">
|
|
||||||
{{ $t('CONVERSATION.REPLYBOX.PRIVATE_NOTE') }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li v-if="message.length" class="tabs-title message-length">
|
|
||||||
<a :class="{ 'message-error': isMessageLengthReachingThreshold }">
|
|
||||||
{{ characterCountIndicator }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="button send-button"
|
|
||||||
:disabled="isReplyButtonDisabled"
|
|
||||||
:class="{
|
|
||||||
disabled: isReplyButtonDisabled,
|
|
||||||
warning: isPrivate,
|
|
||||||
}"
|
|
||||||
@click="sendMessage"
|
|
||||||
>
|
|
||||||
{{ replyButtonLabel }}
|
|
||||||
<i
|
|
||||||
class="icon"
|
|
||||||
:class="{
|
|
||||||
'ion-android-send': !isPrivate,
|
|
||||||
'ion-android-lock': isPrivate,
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { mixin as clickaway } from 'vue-clickaway';
|
import { mixin as clickaway } from 'vue-clickaway';
|
||||||
import FileUpload from 'vue-upload-component';
|
|
||||||
|
|
||||||
import EmojiInput from 'shared/components/emoji/EmojiInput';
|
import EmojiInput from 'shared/components/emoji/EmojiInput';
|
||||||
import CannedResponse from './CannedResponse';
|
import CannedResponse from './CannedResponse';
|
||||||
import ResizableTextArea from 'shared/components/ResizableTextArea';
|
import ResizableTextArea from 'shared/components/ResizableTextArea';
|
||||||
import AttachmentPreview from 'dashboard/components/widgets/AttachmentsPreview';
|
import AttachmentPreview from 'dashboard/components/widgets/AttachmentsPreview';
|
||||||
|
import ReplyTopPanel from 'dashboard/components/widgets/WootWriter/ReplyTopPanel';
|
||||||
|
import ReplyBottomPanel from 'dashboard/components/widgets/WootWriter/ReplyBottomPanel';
|
||||||
|
import { REPLY_EDITOR_MODES } from 'dashboard/components/widgets/WootWriter/constants';
|
||||||
import {
|
import {
|
||||||
isEscape,
|
isEscape,
|
||||||
isEnter,
|
isEnter,
|
||||||
@@ -109,9 +68,10 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
EmojiInput,
|
EmojiInput,
|
||||||
CannedResponse,
|
CannedResponse,
|
||||||
FileUpload,
|
|
||||||
ResizableTextArea,
|
ResizableTextArea,
|
||||||
AttachmentPreview,
|
AttachmentPreview,
|
||||||
|
ReplyTopPanel,
|
||||||
|
ReplyBottomPanel,
|
||||||
},
|
},
|
||||||
mixins: [clickaway, inboxMixin],
|
mixins: [clickaway, inboxMixin],
|
||||||
props: {
|
props: {
|
||||||
@@ -123,18 +83,19 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
message: '',
|
message: '',
|
||||||
isPrivateTabActive: false,
|
|
||||||
isFocused: false,
|
isFocused: false,
|
||||||
showEmojiPicker: false,
|
showEmojiPicker: false,
|
||||||
showCannedResponsesList: false,
|
showCannedResponsesList: false,
|
||||||
attachedFiles: [],
|
attachedFiles: [],
|
||||||
|
isUploading: false,
|
||||||
|
replyType: REPLY_EDITOR_MODES.REPLY,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({ currentChat: 'getSelectedChat' }),
|
...mapGetters({ currentChat: 'getSelectedChat' }),
|
||||||
isPrivate() {
|
isPrivate() {
|
||||||
if (this.currentChat.can_reply) {
|
if (this.currentChat.can_reply) {
|
||||||
return this.isPrivateTabActive;
|
return this.replyType === REPLY_EDITOR_MODES.NOTE;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
@@ -207,6 +168,7 @@ export default {
|
|||||||
},
|
},
|
||||||
replyBoxClass() {
|
replyBoxClass() {
|
||||||
return {
|
return {
|
||||||
|
'is-private': this.isPrivate,
|
||||||
'is-focused': this.isFocused || this.hasAttachments,
|
'is-focused': this.isFocused || this.hasAttachments,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -216,10 +178,11 @@ export default {
|
|||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
currentChat(conversation) {
|
currentChat(conversation) {
|
||||||
if (conversation.can_reply) {
|
const { can_reply: canReply } = conversation;
|
||||||
this.isPrivateTabActive = false;
|
if (canReply) {
|
||||||
|
this.replyType = REPLY_EDITOR_MODES.REPLY;
|
||||||
} else {
|
} else {
|
||||||
this.isPrivateTabActive = true;
|
this.replyType = REPLY_EDITOR_MODES.NOTE;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
message(updatedMessage) {
|
message(updatedMessage) {
|
||||||
@@ -282,12 +245,10 @@ export default {
|
|||||||
this.message = message;
|
this.message = message;
|
||||||
}, 100);
|
}, 100);
|
||||||
},
|
},
|
||||||
setPrivateReplyMode() {
|
setReplyMode(mode = REPLY_EDITOR_MODES.REPLY) {
|
||||||
this.isPrivateTabActive = true;
|
const { can_reply: canReply } = this.currentChat;
|
||||||
this.$refs.messageInput.focus();
|
|
||||||
},
|
if (canReply) this.replyType = mode;
|
||||||
setReplyMode() {
|
|
||||||
this.isPrivateTabActive = false;
|
|
||||||
this.$refs.messageInput.focus();
|
this.$refs.messageInput.focus();
|
||||||
},
|
},
|
||||||
emojiOnClick(emoji) {
|
emojiOnClick(emoji) {
|
||||||
@@ -373,20 +334,45 @@ export default {
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss" scoped>
|
||||||
@import '~widget/assets/scss/mixins';
|
|
||||||
|
|
||||||
.send-button {
|
.send-button {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.attachment-preview-box {
|
.attachment-preview-box {
|
||||||
margin: 0 var(--space-normal);
|
padding: 0 var(--space-normal);
|
||||||
background: var(--white);
|
background: transparent;
|
||||||
margin-bottom: var(--space-minus-slab);
|
}
|
||||||
padding-top: var(--space-small);
|
|
||||||
padding-bottom: var(--space-normal);
|
.reply-box {
|
||||||
border-top-left-radius: var(--border-radius-medium);
|
border-top: 1px solid var(--color-border);
|
||||||
border-top-right-radius: var(--border-radius-medium);
|
background: white;
|
||||||
@include shadow;
|
|
||||||
|
&.is-private {
|
||||||
|
background: var(--y-50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.send-button {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-box__top {
|
||||||
|
padding: 0 var(--space-normal);
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
margin-top: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-dialog {
|
||||||
|
top: unset;
|
||||||
|
bottom: 12px;
|
||||||
|
left: -320px;
|
||||||
|
right: unset;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
right: -16px;
|
||||||
|
bottom: 10px;
|
||||||
|
transform: rotate(270deg);
|
||||||
|
filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.08));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
--g-800: #009000;
|
--g-800: #009000;
|
||||||
--g-900: #007000;
|
--g-900: #007000;
|
||||||
|
|
||||||
--y-50: #FFFEE8;
|
--y-50: #FFFCF4;
|
||||||
--y-100: #FFFAC5;
|
--y-100: #FFFAC5;
|
||||||
--y-200: #FFF69E;
|
--y-200: #FFF69E;
|
||||||
--y-300: #FEF176;
|
--y-300: #FEF176;
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ $font-size-medium: 18px;
|
|||||||
right: 0;
|
right: 0;
|
||||||
top: -22 * $space-one;
|
top: -22 * $space-one;
|
||||||
width: 32 * $space-one;
|
width: 32 * $space-one;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
@include arrow(bottom, $color-white, $space-slab);
|
@include arrow(bottom, $color-white, $space-slab);
|
||||||
|
|||||||
Reference in New Issue
Block a user