chore: Update reply editor design (#10575)
# Pull Request Template ## Description This PR will update the reply message editor’s design. **Screen recording** https://github.com/user-attachments/assets/40f61903-6bf7-4031-9a36-9027dffc46aa --------- Co-authored-by: Pranav <pranavrajs@gmail.com> Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
This commit is contained in:
@@ -80,7 +80,7 @@ const toggleConversationLayout = () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="flex items-center justify-between gap-2 px-4 pb-0"
|
class="flex items-center justify-between gap-2 px-4 pb-0 mb-2"
|
||||||
:class="{
|
:class="{
|
||||||
'pb-3 border-b border-n-strong': hasAppliedFiltersOrActiveFolders,
|
'pb-3 border-b border-n-strong': hasAppliedFiltersOrActiveFolders,
|
||||||
'pt-2.5': showV4View,
|
'pt-2.5': showV4View,
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ import AIAssistanceModal from './AIAssistanceModal.vue';
|
|||||||
import { CMD_AI_ASSIST } from 'dashboard/helper/commandbar/events';
|
import { CMD_AI_ASSIST } from 'dashboard/helper/commandbar/events';
|
||||||
import AIAssistanceCTAButton from './AIAssistanceCTAButton.vue';
|
import AIAssistanceCTAButton from './AIAssistanceCTAButton.vue';
|
||||||
import { emitter } from 'shared/helpers/mitt';
|
import { emitter } from 'shared/helpers/mitt';
|
||||||
|
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
NextButton,
|
||||||
AIAssistanceModal,
|
AIAssistanceModal,
|
||||||
AICTAModal,
|
AICTAModal,
|
||||||
AIAssistanceCTAButton,
|
AIAssistanceCTAButton,
|
||||||
@@ -128,13 +130,13 @@ export default {
|
|||||||
v-if="shouldShowAIAssistCTAButton"
|
v-if="shouldShowAIAssistCTAButton"
|
||||||
@open="openAIAssist"
|
@open="openAIAssist"
|
||||||
/>
|
/>
|
||||||
<woot-button
|
<NextButton
|
||||||
v-else
|
v-else
|
||||||
v-tooltip.top-end="$t('INTEGRATION_SETTINGS.OPEN_AI.AI_ASSIST')"
|
v-tooltip.top-end="$t('INTEGRATION_SETTINGS.OPEN_AI.AI_ASSIST')"
|
||||||
icon="wand"
|
icon="i-ph-magic-wand"
|
||||||
color-scheme="secondary"
|
slate
|
||||||
variant="smooth"
|
faded
|
||||||
size="small"
|
sm
|
||||||
@click="openAIAssist"
|
@click="openAIAssist"
|
||||||
/>
|
/>
|
||||||
<woot-modal
|
<woot-modal
|
||||||
|
|||||||
@@ -1,27 +1,22 @@
|
|||||||
<script>
|
<script setup>
|
||||||
export default {
|
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||||
emits: ['open'],
|
|
||||||
|
|
||||||
methods: {
|
const emit = defineEmits(['open']);
|
||||||
onClick() {
|
|
||||||
this.$emit('open');
|
const onClick = () => {
|
||||||
},
|
emit('open');
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<woot-button
|
<NextButton
|
||||||
icon="wand"
|
class="cta-btn cta-btn-light dark:cta-btn-dark hover:cta-btn-light-hover dark:hover:cta-btn-dark-hover"
|
||||||
color-scheme="secondary"
|
:label="$t('INTEGRATION_SETTINGS.OPEN_AI.AI_ASSIST')"
|
||||||
variant="smooth"
|
icon="i-ph-magic-wand"
|
||||||
size="small"
|
sm
|
||||||
class-names="cta-btn cta-btn-light dark:cta-btn-dark hover:cta-btn-light-hover dark:hover:cta-btn-dark-hover"
|
|
||||||
@click="onClick"
|
@click="onClick"
|
||||||
>
|
/>
|
||||||
{{ $t('INTEGRATION_SETTINGS.OPEN_AI.AI_ASSIST') }}
|
|
||||||
</woot-button>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="radar-ping-animation absolute top-0 right-0 -mt-1 -mr-1 rounded-full w-3 h-3 bg-woot-500 dark:bg-woot-500"
|
class="radar-ping-animation absolute top-0 right-0 -mt-1 -mr-1 rounded-full w-3 h-3 bg-woot-500 dark:bg-woot-500"
|
||||||
@@ -34,22 +29,26 @@ export default {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
|
|
||||||
@layer components {
|
@layer components {
|
||||||
/* Gradient animation */
|
/* Gradient animation */
|
||||||
@keyframes gradient {
|
@keyframes gradient {
|
||||||
0% {
|
0% {
|
||||||
background-position: 0% 50%;
|
background-position: 0% 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
50% {
|
50% {
|
||||||
background-position: 100% 50%;
|
background-position: 100% 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
background-position: 0% 50%;
|
background-position: 0% 50%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.cta-btn {
|
.cta-btn {
|
||||||
animation: gradient 5s ease infinite;
|
animation: gradient 5s ease infinite;
|
||||||
border: 0;
|
@apply text-n-slate-12 border-0 text-xs;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cta-btn-light {
|
.cta-btn-light {
|
||||||
@@ -96,6 +95,7 @@ export default {
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.radar-ping-animation {
|
.radar-ping-animation {
|
||||||
animation: ping 1s ease infinite;
|
animation: ping 1s ease infinite;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,12 @@
|
|||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import DyteAPI from 'dashboard/api/integrations/dyte';
|
import DyteAPI from 'dashboard/api/integrations/dyte';
|
||||||
import { useAlert } from 'dashboard/composables';
|
import { useAlert } from 'dashboard/composables';
|
||||||
|
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
NextButton,
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
conversationId: {
|
conversationId: {
|
||||||
type: Number,
|
type: Number,
|
||||||
@@ -43,16 +47,15 @@ export default {
|
|||||||
|
|
||||||
<!-- eslint-disable-next-line vue/no-root-v-if -->
|
<!-- eslint-disable-next-line vue/no-root-v-if -->
|
||||||
<template>
|
<template>
|
||||||
<woot-button
|
<NextButton
|
||||||
v-if="isVideoIntegrationEnabled"
|
v-if="isVideoIntegrationEnabled"
|
||||||
v-tooltip.top-end="
|
v-tooltip.top-end="
|
||||||
$t('INTEGRATION_SETTINGS.DYTE.START_VIDEO_CALL_HELP_TEXT')
|
$t('INTEGRATION_SETTINGS.DYTE.START_VIDEO_CALL_HELP_TEXT')
|
||||||
"
|
"
|
||||||
icon="video"
|
icon="i-ph-video-camera"
|
||||||
:is-loading="isLoading"
|
slate
|
||||||
color-scheme="secondary"
|
faded
|
||||||
variant="smooth"
|
sm
|
||||||
size="small"
|
|
||||||
@click="onClick"
|
@click="onClick"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -735,7 +735,7 @@ useEmitter(BUS_EVENTS.INSERT_INTO_RICH_EDITOR, insertContentIntoEditor);
|
|||||||
|
|
||||||
.ProseMirror-menubar {
|
.ProseMirror-menubar {
|
||||||
min-height: var(--space-two) !important;
|
min-height: var(--space-two) !important;
|
||||||
@apply -ml-2.5 pb-0 bg-white dark:bg-slate-900 text-slate-700 dark:text-slate-100;
|
@apply -ml-2.5 pb-0 bg-transparent text-n-slate-11;
|
||||||
|
|
||||||
.ProseMirror-menu-active {
|
.ProseMirror-menu-active {
|
||||||
@apply bg-slate-75 dark:bg-slate-800;
|
@apply bg-slate-75 dark:bg-slate-800;
|
||||||
@@ -787,10 +787,6 @@ useEmitter(BUS_EVENTS.INSERT_INTO_RICH_EDITOR, insertContentIntoEditor);
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ProseMirror-menubar-wrapper {
|
.ProseMirror-menubar-wrapper {
|
||||||
.ProseMirror-menubar {
|
|
||||||
@apply bg-yellow-100 dark:bg-yellow-800 text-slate-700 dark:text-slate-25;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .ProseMirror {
|
> .ProseMirror {
|
||||||
@apply text-slate-800 dark:text-slate-25;
|
@apply text-slate-800 dark:text-slate-25;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { REPLY_EDITOR_MODES } from './constants';
|
||||||
|
|
||||||
|
import Icon from 'dashboard/components-next/icon/Icon.vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
mode: {
|
||||||
|
type: String,
|
||||||
|
default: REPLY_EDITOR_MODES.REPLY,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
defineEmits(['toggleMode']);
|
||||||
|
|
||||||
|
const isPrivate = computed(() => props.mode === REPLY_EDITOR_MODES.NOTE);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<button
|
||||||
|
class="flex items-center h-8 p-1 transition-all border rounded-full duration-600 bg-n-alpha-2 dark:bg-n-alpha-2 hover:bg-n-alpha-1 dark:hover:brightness-105 group relative transition-all duration-300 ease-in-out"
|
||||||
|
:class="[
|
||||||
|
isPrivate
|
||||||
|
? 'border-n-amber-12/10 dark:border-n-amber-3/30 w-[128px]'
|
||||||
|
: 'border-n-weak dark:border-n-weak ',
|
||||||
|
]"
|
||||||
|
@click="$emit('toggleMode')"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex absolute items-center justify-center w-6 transition-all duration-200 rounded-full bg-n-alpha-black1 size-6 transition-all duration-300 ease-in-out"
|
||||||
|
:class="[
|
||||||
|
isPrivate
|
||||||
|
? 'ltr:translate-x-[94px] rtl:-translate-x-[94px]'
|
||||||
|
: 'translate-x-0',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
:icon="
|
||||||
|
isPrivate
|
||||||
|
? 'i-material-symbols-lock'
|
||||||
|
: 'i-material-symbols-lock-open-rounded'
|
||||||
|
"
|
||||||
|
class="flex-shrink-0 size-3.5"
|
||||||
|
:class="[
|
||||||
|
isPrivate ? 'dark:text-n-amber-9 text-n-amber-11' : 'text-n-slate-10',
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
class="flex items-center text-sm font-medium transition-all duration-200 w-fit whitespace-nowrap"
|
||||||
|
:class="[
|
||||||
|
isPrivate
|
||||||
|
? 'text-n-amber-12 ltr:mr-7 ltr:ml-1.5 rtl:ml-7 rtl:mr-1.5'
|
||||||
|
: 'text-n-slate-12 ltr:ml-7 ltr:mr-1.5 rtl:mr-7 rtl:ml-1.5',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
isPrivate
|
||||||
|
? $t('CONVERSATION.REPLYBOX.PRIVATE_NOTE')
|
||||||
|
: $t('CONVERSATION.REPLYBOX.REPLY')
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
@@ -15,10 +15,11 @@ import VideoCallButton from '../VideoCallButton.vue';
|
|||||||
import AIAssistanceButton from '../AIAssistanceButton.vue';
|
import AIAssistanceButton from '../AIAssistanceButton.vue';
|
||||||
import { REPLY_EDITOR_MODES } from './constants';
|
import { REPLY_EDITOR_MODES } from './constants';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
|
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ReplyBottomPanel',
|
name: 'ReplyBottomPanel',
|
||||||
components: { FileUpload, VideoCallButton, AIAssistanceButton },
|
components: { NextButton, FileUpload, VideoCallButton, AIAssistanceButton },
|
||||||
mixins: [inboxMixin],
|
mixins: [inboxMixin],
|
||||||
props: {
|
props: {
|
||||||
mode: {
|
mode: {
|
||||||
@@ -207,13 +208,13 @@ export default {
|
|||||||
switch (this.recordingAudioState) {
|
switch (this.recordingAudioState) {
|
||||||
// playing paused recording stopped inactive destroyed
|
// playing paused recording stopped inactive destroyed
|
||||||
case 'playing':
|
case 'playing':
|
||||||
return 'microphone-pause';
|
return 'i-ph-pause';
|
||||||
case 'paused':
|
case 'paused':
|
||||||
return 'microphone-play';
|
return 'i-ph-play';
|
||||||
case 'stopped':
|
case 'stopped':
|
||||||
return 'microphone-play';
|
return 'i-ph-play';
|
||||||
default:
|
default:
|
||||||
return 'microphone-stop';
|
return 'i-ph-stop';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
showMessageSignatureButton() {
|
showMessageSignatureButton() {
|
||||||
@@ -253,15 +254,14 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="bottom-box" :class="wrapClass">
|
<div class="flex justify-between p-3" :class="wrapClass">
|
||||||
<div class="left-wrap">
|
<div class="left-wrap">
|
||||||
<woot-button
|
<NextButton
|
||||||
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_EMOJI_ICON')"
|
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_EMOJI_ICON')"
|
||||||
:title="$t('CONVERSATION.REPLYBOX.TIP_EMOJI_ICON')"
|
icon="i-ph-smiley-sticker"
|
||||||
icon="emoji"
|
slate
|
||||||
color-scheme="secondary"
|
faded
|
||||||
variant="smooth"
|
sm
|
||||||
size="small"
|
|
||||||
@click="toggleEmojiPicker"
|
@click="toggleEmojiPicker"
|
||||||
/>
|
/>
|
||||||
<FileUpload
|
<FileUpload
|
||||||
@@ -279,62 +279,59 @@ export default {
|
|||||||
}"
|
}"
|
||||||
@input-file="onFileUpload"
|
@input-file="onFileUpload"
|
||||||
>
|
>
|
||||||
<woot-button
|
<NextButton
|
||||||
v-if="showAttachButton"
|
v-if="showAttachButton"
|
||||||
class-names="button--upload"
|
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_ATTACH_ICON')"
|
||||||
:title="$t('CONVERSATION.REPLYBOX.TIP_ATTACH_ICON')"
|
icon="i-ph-paperclip"
|
||||||
icon="attach"
|
slate
|
||||||
color-scheme="secondary"
|
faded
|
||||||
variant="smooth"
|
sm
|
||||||
size="small"
|
|
||||||
/>
|
/>
|
||||||
</FileUpload>
|
</FileUpload>
|
||||||
<woot-button
|
<NextButton
|
||||||
v-if="showAudioRecorderButton"
|
v-if="showAudioRecorderButton"
|
||||||
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_AUDIORECORDER_ICON')"
|
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_AUDIORECORDER_ICON')"
|
||||||
:icon="!isRecordingAudio ? 'microphone' : 'microphone-off'"
|
:icon="!isRecordingAudio ? 'i-ph-microphone' : 'i-ph-microphone-slash'"
|
||||||
:color-scheme="!isRecordingAudio ? 'secondary' : 'alert'"
|
slate
|
||||||
variant="smooth"
|
faded
|
||||||
size="small"
|
sm
|
||||||
@click="toggleAudioRecorder"
|
@click="toggleAudioRecorder"
|
||||||
/>
|
/>
|
||||||
<woot-button
|
<NextButton
|
||||||
v-if="showEditorToggle"
|
v-if="showEditorToggle"
|
||||||
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_FORMAT_ICON')"
|
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_FORMAT_ICON')"
|
||||||
icon="quote"
|
icon="i-ph-quotes"
|
||||||
color-scheme="secondary"
|
slate
|
||||||
variant="smooth"
|
faded
|
||||||
size="small"
|
sm
|
||||||
@click="$emit('toggleEditor')"
|
@click="$emit('toggleEditor')"
|
||||||
/>
|
/>
|
||||||
<woot-button
|
<NextButton
|
||||||
v-if="showAudioPlayStopButton"
|
v-if="showAudioPlayStopButton"
|
||||||
|
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_FORMAT_ICON')"
|
||||||
:icon="audioRecorderPlayStopIcon"
|
:icon="audioRecorderPlayStopIcon"
|
||||||
color-scheme="secondary"
|
slate
|
||||||
variant="smooth"
|
faded
|
||||||
size="small"
|
sm
|
||||||
|
:label="recordingAudioDurationText"
|
||||||
@click="toggleAudioRecorderPlayPause"
|
@click="toggleAudioRecorderPlayPause"
|
||||||
>
|
/>
|
||||||
<span>{{ recordingAudioDurationText }}</span>
|
<NextButton
|
||||||
</woot-button>
|
|
||||||
<woot-button
|
|
||||||
v-if="showMessageSignatureButton"
|
v-if="showMessageSignatureButton"
|
||||||
v-tooltip.top-end="signatureToggleTooltip"
|
v-tooltip.top-end="signatureToggleTooltip"
|
||||||
icon="signature"
|
icon="i-ph-signature"
|
||||||
color-scheme="secondary"
|
slate
|
||||||
variant="smooth"
|
faded
|
||||||
size="small"
|
sm
|
||||||
:title="signatureToggleTooltip"
|
|
||||||
@click="toggleMessageSignature"
|
@click="toggleMessageSignature"
|
||||||
/>
|
/>
|
||||||
<woot-button
|
<NextButton
|
||||||
v-if="hasWhatsappTemplates"
|
v-if="hasWhatsappTemplates"
|
||||||
v-tooltip.top-end="$t('CONVERSATION.FOOTER.WHATSAPP_TEMPLATES')"
|
v-tooltip.top-end="$t('CONVERSATION.FOOTER.WHATSAPP_TEMPLATES')"
|
||||||
icon="whatsapp"
|
icon="i-ph-whatsapp"
|
||||||
color-scheme="secondary"
|
slate
|
||||||
variant="smooth"
|
faded
|
||||||
size="small"
|
sm
|
||||||
:title="$t('CONVERSATION.FOOTER.WHATSAPP_TEMPLATES')"
|
|
||||||
@click="$emit('selectWhatsappTemplate')"
|
@click="$emit('selectWhatsappTemplate')"
|
||||||
/>
|
/>
|
||||||
<VideoCallButton
|
<VideoCallButton
|
||||||
@@ -359,14 +356,13 @@ export default {
|
|||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
<woot-button
|
<NextButton
|
||||||
v-if="enableInsertArticleInReply"
|
v-if="enableInsertArticleInReply"
|
||||||
v-tooltip.top-end="$t('HELP_CENTER.ARTICLE_SEARCH.OPEN_ARTICLE_SEARCH')"
|
v-tooltip.top-end="$t('HELP_CENTER.ARTICLE_SEARCH.OPEN_ARTICLE_SEARCH')"
|
||||||
icon="document-text-link"
|
icon="i-ph-article-ny-times"
|
||||||
color-scheme="secondary"
|
slate
|
||||||
variant="smooth"
|
faded
|
||||||
size="small"
|
sm
|
||||||
:title="$t('HELP_CENTER.ARTICLE_SEARCH.OPEN_ARTICLE_SEARCH')"
|
|
||||||
@click="toggleInsertArticle"
|
@click="toggleInsertArticle"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -384,14 +380,6 @@ export default {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.bottom-box {
|
|
||||||
@apply flex justify-between py-3 px-4;
|
|
||||||
|
|
||||||
&.is-note-mode {
|
|
||||||
@apply bg-yellow-100 dark:bg-yellow-800;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.left-wrap {
|
.left-wrap {
|
||||||
@apply items-center flex gap-2;
|
@apply items-center flex gap-2;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
<script>
|
<script>
|
||||||
import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents';
|
import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents';
|
||||||
import { REPLY_EDITOR_MODES, CHAR_LENGTH_WARNING } from './constants';
|
import { REPLY_EDITOR_MODES, CHAR_LENGTH_WARNING } from './constants';
|
||||||
|
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||||
|
import EditorModeToggle from './EditorModeToggle.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ReplyTopPanel',
|
name: 'ReplyTopPanel',
|
||||||
|
components: {
|
||||||
|
NextButton,
|
||||||
|
EditorModeToggle,
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
mode: {
|
mode: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -17,10 +23,6 @@ export default {
|
|||||||
type: Number,
|
type: Number,
|
||||||
default: () => 0,
|
default: () => 0,
|
||||||
},
|
},
|
||||||
popoutReplyBox: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
emits: ['setReplyMode', 'togglePopout'],
|
emits: ['setReplyMode', 'togglePopout'],
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
@@ -33,6 +35,13 @@ export default {
|
|||||||
const handleNoteClick = () => {
|
const handleNoteClick = () => {
|
||||||
setReplyMode(REPLY_EDITOR_MODES.NOTE);
|
setReplyMode(REPLY_EDITOR_MODES.NOTE);
|
||||||
};
|
};
|
||||||
|
const handleModeToggle = () => {
|
||||||
|
const newMode =
|
||||||
|
props.mode === REPLY_EDITOR_MODES.REPLY
|
||||||
|
? REPLY_EDITOR_MODES.NOTE
|
||||||
|
: REPLY_EDITOR_MODES.REPLY;
|
||||||
|
setReplyMode(newMode);
|
||||||
|
};
|
||||||
const keyboardEvents = {
|
const keyboardEvents = {
|
||||||
'Alt+KeyP': {
|
'Alt+KeyP': {
|
||||||
action: () => handleNoteClick(),
|
action: () => handleNoteClick(),
|
||||||
@@ -46,8 +55,10 @@ export default {
|
|||||||
useKeyboardEvents(keyboardEvents);
|
useKeyboardEvents(keyboardEvents);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
handleModeToggle,
|
||||||
handleReplyClick,
|
handleReplyClick,
|
||||||
handleNoteClick,
|
handleNoteClick,
|
||||||
|
REPLY_EDITOR_MODES,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -74,27 +85,12 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex justify-between bg-black-50 dark:bg-slate-800">
|
<div class="flex justify-between h-[52px] gap-2 ltr:pl-3 rtl:pr-3">
|
||||||
<div class="button-group">
|
<EditorModeToggle
|
||||||
<woot-button
|
:mode="mode"
|
||||||
variant="clear"
|
class="mt-3"
|
||||||
class="button--reply"
|
@toggle-mode="handleModeToggle"
|
||||||
:class="replyButtonClass"
|
/>
|
||||||
@click="handleReplyClick"
|
|
||||||
>
|
|
||||||
{{ $t('CONVERSATION.REPLYBOX.REPLY') }}
|
|
||||||
</woot-button>
|
|
||||||
|
|
||||||
<woot-button
|
|
||||||
class="button--note"
|
|
||||||
variant="clear"
|
|
||||||
color-scheme="warning"
|
|
||||||
:class="noteButtonClass"
|
|
||||||
@click="handleNoteClick"
|
|
||||||
>
|
|
||||||
{{ $t('CONVERSATION.REPLYBOX.PRIVATE_NOTE') }}
|
|
||||||
</woot-button>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center mx-4 my-0">
|
<div class="flex items-center mx-4 my-0">
|
||||||
<div v-if="isMessageLengthReachingThreshold" class="text-xs">
|
<div v-if="isMessageLengthReachingThreshold" class="text-xs">
|
||||||
<span :class="charLengthClass">
|
<span :class="charLengthClass">
|
||||||
@@ -102,20 +98,10 @@ export default {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<woot-button
|
<NextButton
|
||||||
v-if="popoutReplyBox"
|
ghost
|
||||||
variant="clear"
|
class="ltr:rounded-bl-md rtl:rounded-br-md ltr:rounded-br-none rtl:rounded-bl-none ltr:rounded-tl-none rtl:rounded-tr-none text-n-slate-11 ltr:rounded-tr-[11px] rtl:rounded-tl-[11px]"
|
||||||
icon="dismiss"
|
icon="i-lucide-maximize-2"
|
||||||
color-scheme="secondary"
|
|
||||||
class-names="popout-button"
|
|
||||||
@click="$emit('togglePopout')"
|
|
||||||
/>
|
|
||||||
<woot-button
|
|
||||||
v-else
|
|
||||||
variant="clear"
|
|
||||||
icon="resize-large"
|
|
||||||
color-scheme="secondary"
|
|
||||||
class-names="popout-button"
|
|
||||||
@click="$emit('togglePopout')"
|
@click="$emit('togglePopout')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -127,28 +113,35 @@ export default {
|
|||||||
|
|
||||||
.button {
|
.button {
|
||||||
@apply text-sm font-medium py-2.5 px-4 m-0 relative z-10;
|
@apply text-sm font-medium py-2.5 px-4 m-0 relative z-10;
|
||||||
|
|
||||||
&.is-active {
|
&.is-active {
|
||||||
@apply bg-white dark:bg-slate-900;
|
@apply bg-white dark:bg-slate-900;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.button--reply {
|
.button--reply {
|
||||||
@apply border-r rounded-none border-b-0 border-l-0 border-t-0 border-slate-50 dark:border-slate-700;
|
@apply border-r rounded-none border-b-0 border-l-0 border-t-0 border-slate-50 dark:border-slate-700;
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus {
|
&:focus {
|
||||||
@apply border-r border-slate-50 dark:border-slate-700;
|
@apply border-r border-slate-50 dark:border-slate-700;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.button--note {
|
.button--note {
|
||||||
@apply border-l-0 rounded-none;
|
@apply border-l-0 rounded-none;
|
||||||
|
|
||||||
&.is-active {
|
&.is-active {
|
||||||
@apply border-r border-b-0 bg-yellow-100 dark:bg-yellow-800 border-t-0 border-slate-50 dark:border-slate-700;
|
@apply border-r border-b-0 bg-yellow-100 dark:bg-yellow-800 border-t-0 border-slate-50 dark:border-slate-700;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:active {
|
&:active {
|
||||||
@apply text-yellow-700 dark:text-yellow-700;
|
@apply text-yellow-700 dark:text-yellow-700;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.button--note {
|
.button--note {
|
||||||
@apply text-yellow-600 dark:text-yellow-600 bg-transparent dark:bg-transparent;
|
@apply text-yellow-600 dark:text-yellow-600 bg-transparent dark:bg-transparent;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -623,7 +623,11 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.reply-box {
|
.reply-box {
|
||||||
@apply border border-solid border-slate-75 dark:border-slate-600 max-w-[75rem] w-[70%];
|
@apply border border-n-weak max-w-[75rem] w-[70%];
|
||||||
|
|
||||||
|
&.is-private {
|
||||||
|
@apply dark:border-n-amber-3/30 border-n-amber-12/5;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.reply-box .reply-box__top {
|
.reply-box .reply-box__top {
|
||||||
|
|||||||
@@ -1242,28 +1242,12 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.reply-box {
|
.reply-box {
|
||||||
transition:
|
transition: height 2s cubic-bezier(0.37, 0, 0.63, 1);
|
||||||
box-shadow 0.35s cubic-bezier(0.37, 0, 0.63, 1),
|
|
||||||
height 2s cubic-bezier(0.37, 0, 0.63, 1);
|
|
||||||
|
|
||||||
@apply relative border-t border-slate-50 dark:border-slate-700 bg-white dark:bg-slate-900;
|
@apply relative mb-2 mx-2 border border-n-weak rounded-xl bg-n-solid-1;
|
||||||
|
|
||||||
&.is-focused {
|
|
||||||
box-shadow:
|
|
||||||
0 1px 3px 0 rgba(0, 0, 0, 0.1),
|
|
||||||
0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-private {
|
&.is-private {
|
||||||
@apply bg-yellow-100 dark:bg-yellow-800;
|
@apply bg-n-solid-amber dark:border-n-amber-3/10 border-n-amber-12/5;
|
||||||
|
|
||||||
.reply-box__top {
|
|
||||||
@apply bg-yellow-100 dark:bg-yellow-800;
|
|
||||||
|
|
||||||
> input {
|
|
||||||
@apply bg-yellow-100 dark:bg-yellow-800;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1272,7 +1256,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.reply-box__top {
|
.reply-box__top {
|
||||||
@apply relative py-0 px-4 -mt-px border-t border-solid border-slate-50 dark:border-slate-700;
|
@apply relative py-0 px-4 -mt-px;
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
@apply shadow-none border-transparent bg-transparent m-0 max-h-60 min-h-[3rem] pt-4 pb-0 px-0 resize-none;
|
@apply shadow-none border-transparent bg-transparent m-0 max-h-60 min-h-[3rem] pt-4 pb-0 px-0 resize-none;
|
||||||
|
|||||||
@@ -89,11 +89,11 @@ export default {
|
|||||||
<label class="input-group-label">
|
<label class="input-group-label">
|
||||||
{{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.TO') }}
|
{{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.TO') }}
|
||||||
</label>
|
</label>
|
||||||
<div class="rounded-none flex-1 min-w-0 m-0 whitespace-nowrap">
|
<div class="flex-1 min-w-0 m-0 rounded-none whitespace-nowrap">
|
||||||
<woot-input
|
<woot-input
|
||||||
v-model="v$.toEmailsVal.$model"
|
v-model="v$.toEmailsVal.$model"
|
||||||
type="text"
|
type="text"
|
||||||
class="[&>input]:!mb-0 [&>input]:border-transparent [&>input]:h-8 [&>input]:text-sm [&>input]:!border-0 [&>input]:border-none"
|
class="[&>input]:!mb-0 [&>input]:border-transparent [&>input]:h-8 [&>input]:text-sm [&>input]:!border-0 [&>input]:border-none [&>input]:!bg-transparent dark:[&>input]:!bg-transparent"
|
||||||
:class="{ error: v$.toEmailsVal.$error }"
|
:class="{ error: v$.toEmailsVal.$error }"
|
||||||
:placeholder="$t('CONVERSATION.REPLYBOX.EMAIL_HEAD.CC.PLACEHOLDER')"
|
:placeholder="$t('CONVERSATION.REPLYBOX.EMAIL_HEAD.CC.PLACEHOLDER')"
|
||||||
@blur="onBlur"
|
@blur="onBlur"
|
||||||
@@ -106,10 +106,10 @@ export default {
|
|||||||
<label class="input-group-label">
|
<label class="input-group-label">
|
||||||
{{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.CC.LABEL') }}
|
{{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.CC.LABEL') }}
|
||||||
</label>
|
</label>
|
||||||
<div class="rounded-none flex-1 min-w-0 m-0 whitespace-nowrap">
|
<div class="flex-1 min-w-0 m-0 rounded-none whitespace-nowrap">
|
||||||
<woot-input
|
<woot-input
|
||||||
v-model="v$.ccEmailsVal.$model"
|
v-model="v$.ccEmailsVal.$model"
|
||||||
class="[&>input]:!mb-0 [&>input]:border-transparent [&>input]:h-8 [&>input]:text-sm [&>input]:!border-0 [&>input]:border-none"
|
class="[&>input]:!mb-0 [&>input]:border-transparent [&>input]:h-8 [&>input]:text-sm [&>input]:!border-0 [&>input]:border-none [&>input]:!bg-transparent dark:[&>input]:!bg-transparent"
|
||||||
type="text"
|
type="text"
|
||||||
:class="{ error: v$.ccEmailsVal.$error }"
|
:class="{ error: v$.ccEmailsVal.$error }"
|
||||||
:placeholder="$t('CONVERSATION.REPLYBOX.EMAIL_HEAD.CC.PLACEHOLDER')"
|
:placeholder="$t('CONVERSATION.REPLYBOX.EMAIL_HEAD.CC.PLACEHOLDER')"
|
||||||
@@ -134,11 +134,11 @@ export default {
|
|||||||
<label class="input-group-label">
|
<label class="input-group-label">
|
||||||
{{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.BCC.LABEL') }}
|
{{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.BCC.LABEL') }}
|
||||||
</label>
|
</label>
|
||||||
<div class="rounded-none flex-1 min-w-0 m-0 whitespace-nowrap">
|
<div class="flex-1 min-w-0 m-0 rounded-none whitespace-nowrap">
|
||||||
<woot-input
|
<woot-input
|
||||||
v-model="v$.bccEmailsVal.$model"
|
v-model="v$.bccEmailsVal.$model"
|
||||||
type="text"
|
type="text"
|
||||||
class="[&>input]:!mb-0 [&>input]:border-transparent [&>input]:h-8 [&>input]:text-sm [&>input]:!border-0 [&>input]:border-none"
|
class="[&>input]:!mb-0 [&>input]:border-transparent [&>input]:h-8 [&>input]:text-sm [&>input]:!border-0 [&>input]:border-none [&>input]:!bg-transparent dark:[&>input]:!bg-transparent"
|
||||||
:class="{ error: v$.bccEmailsVal.$error }"
|
:class="{ error: v$.bccEmailsVal.$error }"
|
||||||
:placeholder="
|
:placeholder="
|
||||||
$t('CONVERSATION.REPLYBOX.EMAIL_HEAD.BCC.PLACEHOLDER')
|
$t('CONVERSATION.REPLYBOX.EMAIL_HEAD.BCC.PLACEHOLDER')
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
"@formkit/vue": "^1.6.7",
|
"@formkit/vue": "^1.6.7",
|
||||||
"@hcaptcha/vue3-hcaptcha": "^1.3.0",
|
"@hcaptcha/vue3-hcaptcha": "^1.3.0",
|
||||||
"@highlightjs/vue-plugin": "^2.1.0",
|
"@highlightjs/vue-plugin": "^2.1.0",
|
||||||
|
"@iconify-json/material-symbols": "^1.2.10",
|
||||||
"@june-so/analytics-next": "^2.0.0",
|
"@june-so/analytics-next": "^2.0.0",
|
||||||
"@lk77/vue3-color": "^3.0.6",
|
"@lk77/vue3-color": "^3.0.6",
|
||||||
"@radix-ui/colors": "^3.0.0",
|
"@radix-ui/colors": "^3.0.0",
|
||||||
|
|||||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@@ -40,6 +40,9 @@ importers:
|
|||||||
'@highlightjs/vue-plugin':
|
'@highlightjs/vue-plugin':
|
||||||
specifier: ^2.1.0
|
specifier: ^2.1.0
|
||||||
version: 2.1.0(highlight.js@11.10.0)(vue@3.5.12(typescript@5.6.2))
|
version: 2.1.0(highlight.js@11.10.0)(vue@3.5.12(typescript@5.6.2))
|
||||||
|
'@iconify-json/material-symbols':
|
||||||
|
specifier: ^1.2.10
|
||||||
|
version: 1.2.10
|
||||||
'@june-so/analytics-next':
|
'@june-so/analytics-next':
|
||||||
specifier: ^2.0.0
|
specifier: ^2.0.0
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
@@ -890,6 +893,9 @@ packages:
|
|||||||
'@iconify-json/lucide@1.2.11':
|
'@iconify-json/lucide@1.2.11':
|
||||||
resolution: {integrity: sha512-dqpbV7+g1qqxtZOHCZKwdKhtYYqEUjFhYiOg/+PcADbjtapoL+bwa1Brn12gAHq5r2K7Mf29xRHOTmZ3UHHOrw==}
|
resolution: {integrity: sha512-dqpbV7+g1qqxtZOHCZKwdKhtYYqEUjFhYiOg/+PcADbjtapoL+bwa1Brn12gAHq5r2K7Mf29xRHOTmZ3UHHOrw==}
|
||||||
|
|
||||||
|
'@iconify-json/material-symbols@1.2.10':
|
||||||
|
resolution: {integrity: sha512-GcZxhOFStM7Dk/oZvJSaW0tR/k6NwTq+KDzYgCNBDg52ktZuRa/gkjRiYooJm/8PAe9NBYxIx8XjS/wi4sasdQ==}
|
||||||
|
|
||||||
'@iconify-json/ph@1.2.1':
|
'@iconify-json/ph@1.2.1':
|
||||||
resolution: {integrity: sha512-x0DNfwWrS18dbsBYOq3XGiZnGz4CgRyC+YSl/TZvMQiKhIUl1woWqUbMYqqfMNUBzjyk7ulvaRovpRsIlqIf8g==}
|
resolution: {integrity: sha512-x0DNfwWrS18dbsBYOq3XGiZnGz4CgRyC+YSl/TZvMQiKhIUl1woWqUbMYqqfMNUBzjyk7ulvaRovpRsIlqIf8g==}
|
||||||
|
|
||||||
@@ -5784,6 +5790,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@iconify/types': 2.0.0
|
'@iconify/types': 2.0.0
|
||||||
|
|
||||||
|
'@iconify-json/material-symbols@1.2.10':
|
||||||
|
dependencies:
|
||||||
|
'@iconify/types': 2.0.0
|
||||||
|
|
||||||
'@iconify-json/ph@1.2.1':
|
'@iconify-json/ph@1.2.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@iconify/types': 2.0.0
|
'@iconify/types': 2.0.0
|
||||||
|
|||||||
@@ -227,7 +227,14 @@ const tailwindConfig = {
|
|||||||
iconsPlugin({
|
iconsPlugin({
|
||||||
collections: {
|
collections: {
|
||||||
woot: { icons },
|
woot: { icons },
|
||||||
...getIconCollections(['lucide', 'logos', 'ri', 'ph', 'teenyicons']),
|
...getIconCollections([
|
||||||
|
'lucide',
|
||||||
|
'logos',
|
||||||
|
'ri',
|
||||||
|
'ph',
|
||||||
|
'material-symbols',
|
||||||
|
'teenyicons',
|
||||||
|
]),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user