mirror of
https://github.com/abhinavxd/libredesk.git
synced 2025-11-18 12:43:05 +00:00
refactor(editor): Remove unncessary props and simplify code for tiptap editor, update all editors for the same
This commit is contained in:
@@ -30,25 +30,23 @@
|
|||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@click.prevent="isBold = !isBold"
|
@click.prevent="editor?.chain().focus().toggleBold().run()"
|
||||||
:active="isBold"
|
:class="{ 'bg-gray-200 dark:bg-secondary': editor?.isActive('bold') }"
|
||||||
:class="{ 'bg-gray-200 dark:bg-secondary': isBold }"
|
|
||||||
>
|
>
|
||||||
<Bold size="14" />
|
<Bold size="14" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@click.prevent="isItalic = !isItalic"
|
@click.prevent="editor?.chain().focus().toggleItalic().run()"
|
||||||
:active="isItalic"
|
:class="{ 'bg-gray-200 dark:bg-secondary': editor?.isActive('italic') }"
|
||||||
:class="{ 'bg-gray-200 dark:bg-secondary': isItalic }"
|
|
||||||
>
|
>
|
||||||
<Italic size="14" />
|
<Italic size="14" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@click.prevent="toggleBulletList"
|
@click.prevent="editor?.chain().focus().toggleBulletList().run()"
|
||||||
:class="{ 'bg-gray-200 dark:bg-secondary': editor?.isActive('bulletList') }"
|
:class="{ 'bg-gray-200 dark:bg-secondary': editor?.isActive('bulletList') }"
|
||||||
>
|
>
|
||||||
<List size="14" />
|
<List size="14" />
|
||||||
@@ -57,7 +55,7 @@
|
|||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@click.prevent="toggleOrderedList"
|
@click.prevent="editor?.chain().focus().toggleOrderedList().run()"
|
||||||
:class="{ 'bg-gray-200 dark:bg-secondary': editor?.isActive('orderedList') }"
|
:class="{ 'bg-gray-200 dark:bg-secondary': editor?.isActive('orderedList') }"
|
||||||
>
|
>
|
||||||
<ListOrdered size="14" />
|
<ListOrdered size="14" />
|
||||||
@@ -91,7 +89,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watch, watchEffect, onUnmounted, computed } from 'vue'
|
import { ref, watch, onUnmounted } from 'vue'
|
||||||
import { useEditor, EditorContent, BubbleMenu } from '@tiptap/vue-3'
|
import { useEditor, EditorContent, BubbleMenu } from '@tiptap/vue-3'
|
||||||
import {
|
import {
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
@@ -121,21 +119,14 @@ import TableRow from '@tiptap/extension-table-row'
|
|||||||
import TableCell from '@tiptap/extension-table-cell'
|
import TableCell from '@tiptap/extension-table-cell'
|
||||||
import TableHeader from '@tiptap/extension-table-header'
|
import TableHeader from '@tiptap/extension-table-header'
|
||||||
|
|
||||||
const selectedText = defineModel('selectedText', { default: '' })
|
const textContent = defineModel('textContent', { default: '' })
|
||||||
const textContent = defineModel('textContent')
|
const htmlContent = defineModel('htmlContent', { default: '' })
|
||||||
const htmlContent = defineModel('htmlContent')
|
|
||||||
const isBold = defineModel('isBold')
|
|
||||||
const isItalic = defineModel('isItalic')
|
|
||||||
const cursorPosition = defineModel('cursorPosition', { default: 0 })
|
|
||||||
const showLinkInput = ref(false)
|
const showLinkInput = ref(false)
|
||||||
const linkUrl = ref('')
|
const linkUrl = ref('')
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
placeholder: String,
|
placeholder: String,
|
||||||
contentToSet: String,
|
|
||||||
setInlineImage: Object,
|
|
||||||
insertContent: String,
|
insertContent: String,
|
||||||
clearContent: Boolean,
|
|
||||||
autoFocus: {
|
autoFocus: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
@@ -150,8 +141,6 @@ const emit = defineEmits(['send', 'aiPromptSelected'])
|
|||||||
|
|
||||||
const emitPrompt = (key) => emit('aiPromptSelected', key)
|
const emitPrompt = (key) => emit('aiPromptSelected', key)
|
||||||
|
|
||||||
const getSelectionText = (from, to, doc) => doc.textBetween(from, to)
|
|
||||||
|
|
||||||
// To preseve the table styling in emails, need to set the table style inline.
|
// To preseve the table styling in emails, need to set the table style inline.
|
||||||
// Created these custom extensions to set the table style inline.
|
// Created these custom extensions to set the table style inline.
|
||||||
const CustomTable = Table.extend({
|
const CustomTable = Table.extend({
|
||||||
@@ -160,7 +149,7 @@ const CustomTable = Table.extend({
|
|||||||
...this.parent?.(),
|
...this.parent?.(),
|
||||||
style: {
|
style: {
|
||||||
parseHTML: (element) =>
|
parseHTML: (element) =>
|
||||||
(element.getAttribute('style') || '') + ' border: 1px solid #dee2e6 !important; width: 100%; margin:0; table-layout: fixed; border-collapse: collapse; position:relative; border-radius: 0.25rem;'
|
(element.getAttribute('style') || '') + '; border: 1px solid #dee2e6 !important; width: 100%; margin:0; table-layout: fixed; border-collapse: collapse; position:relative; border-radius: 0.25rem;'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -173,7 +162,7 @@ const CustomTableCell = TableCell.extend({
|
|||||||
style: {
|
style: {
|
||||||
parseHTML: (element) =>
|
parseHTML: (element) =>
|
||||||
(element.getAttribute('style') || '') +
|
(element.getAttribute('style') || '') +
|
||||||
' border: 1px solid #dee2e6 !important; box-sizing: border-box !important; min-width: 1em !important; padding: 6px 8px !important; vertical-align: top !important;'
|
'; border: 1px solid #dee2e6 !important; box-sizing: border-box !important; min-width: 1em !important; padding: 6px 8px !important; vertical-align: top !important;'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -186,26 +175,27 @@ const CustomTableHeader = TableHeader.extend({
|
|||||||
style: {
|
style: {
|
||||||
parseHTML: (element) =>
|
parseHTML: (element) =>
|
||||||
(element.getAttribute('style') || '') +
|
(element.getAttribute('style') || '') +
|
||||||
' background-color: #f8f9fa !important; color: #212529 !important; font-weight: bold !important; text-align: left !important; border: 1px solid #dee2e6 !important; padding: 6px 8px !important;'
|
'; background-color: #f8f9fa !important; color: #212529 !important; font-weight: bold !important; text-align: left !important; border: 1px solid #dee2e6 !important; padding: 6px 8px !important;'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const editorConfig = computed(() => ({
|
const isInternalUpdate = ref(false)
|
||||||
|
|
||||||
|
const editor = useEditor({
|
||||||
extensions: [
|
extensions: [
|
||||||
StarterKit.configure(),
|
StarterKit.configure(),
|
||||||
Image.configure({ HTMLAttributes: { class: 'inline-image' } }),
|
Image.configure({ HTMLAttributes: { class: 'inline-image' } }),
|
||||||
Placeholder.configure({ placeholder: () => props.placeholder }),
|
Placeholder.configure({ placeholder: () => props.placeholder }),
|
||||||
Link,
|
Link,
|
||||||
CustomTable.configure({
|
CustomTable.configure({ resizable: false }),
|
||||||
resizable: false
|
|
||||||
}),
|
|
||||||
TableRow,
|
TableRow,
|
||||||
CustomTableCell,
|
CustomTableCell,
|
||||||
CustomTableHeader
|
CustomTableHeader
|
||||||
],
|
],
|
||||||
autofocus: props.autoFocus,
|
autofocus: props.autoFocus,
|
||||||
|
content: htmlContent.value,
|
||||||
editorProps: {
|
editorProps: {
|
||||||
attributes: { class: 'outline-none' },
|
attributes: { class: 'outline-none' },
|
||||||
handleKeyDown: (view, event) => {
|
handleKeyDown: (view, event) => {
|
||||||
@@ -213,110 +203,30 @@ const editorConfig = computed(() => ({
|
|||||||
emit('send')
|
emit('send')
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if (event.ctrlKey && event.key.toLowerCase() === 'b') {
|
|
||||||
// Prevent outer listeners
|
|
||||||
event.stopPropagation()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}))
|
// To update state when user types.
|
||||||
|
onUpdate: ({ editor }) => {
|
||||||
const editor = ref(
|
isInternalUpdate.value = true
|
||||||
useEditor({
|
htmlContent.value = editor.getHTML()
|
||||||
...editorConfig.value,
|
textContent.value = editor.getText()
|
||||||
content: htmlContent.value,
|
isInternalUpdate.value = false
|
||||||
onSelectionUpdate: ({ editor }) => {
|
|
||||||
const { from, to } = editor.state.selection
|
|
||||||
selectedText.value = getSelectionText(from, to, editor.state.doc)
|
|
||||||
},
|
|
||||||
onUpdate: ({ editor }) => {
|
|
||||||
htmlContent.value = editor.getHTML()
|
|
||||||
textContent.value = editor.getText()
|
|
||||||
cursorPosition.value = editor.state.selection.from
|
|
||||||
},
|
|
||||||
onCreate: ({ editor }) => {
|
|
||||||
if (cursorPosition.value) {
|
|
||||||
editor.commands.setTextSelection(cursorPosition.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
const editorInstance = editor.value
|
|
||||||
if (!editorInstance) return
|
|
||||||
isBold.value = editorInstance.isActive('bold')
|
|
||||||
isItalic.value = editorInstance.isActive('italic')
|
|
||||||
})
|
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
const editorInstance = editor.value
|
|
||||||
if (!editorInstance) return
|
|
||||||
|
|
||||||
if (isBold.value !== editorInstance.isActive('bold')) {
|
|
||||||
isBold.value
|
|
||||||
? editorInstance.chain().focus().setBold().run()
|
|
||||||
: editorInstance.chain().focus().unsetBold().run()
|
|
||||||
}
|
|
||||||
if (isItalic.value !== editorInstance.isActive('italic')) {
|
|
||||||
isItalic.value
|
|
||||||
? editorInstance.chain().focus().setItalic().run()
|
|
||||||
: editorInstance.chain().focus().unsetItalic().run()
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.contentToSet,
|
htmlContent,
|
||||||
(newContentData) => {
|
(newContent) => {
|
||||||
if (!newContentData) return
|
if (!isInternalUpdate.value && editor.value && newContent !== editor.value.getHTML()) {
|
||||||
try {
|
editor.value.commands.setContent(newContent || '', false)
|
||||||
const parsedData = JSON.parse(newContentData)
|
textContent.value = editor.value.getText()
|
||||||
const content = parsedData.content
|
editor.value.commands.focus()
|
||||||
if (content === '') {
|
|
||||||
editor.value?.commands.clearContent()
|
|
||||||
} else {
|
|
||||||
editor.value?.commands.setContent(content, true)
|
|
||||||
}
|
|
||||||
editor.value?.commands.focus()
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Error parsing content data', e)
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
)
|
{ immediate: true }
|
||||||
|
|
||||||
watch(cursorPosition, (newPos, oldPos) => {
|
|
||||||
if (editor.value && newPos !== oldPos && newPos !== editor.value.state.selection.from) {
|
|
||||||
editor.value.commands.setTextSelection(newPos)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.clearContent,
|
|
||||||
() => {
|
|
||||||
if (!props.clearContent) return
|
|
||||||
editor.value?.commands.clearContent()
|
|
||||||
editor.value?.commands.focus()
|
|
||||||
// `onUpdate` is not called when clearing content, so need to reset the content here.
|
|
||||||
htmlContent.value = ''
|
|
||||||
textContent.value = ''
|
|
||||||
cursorPosition.value = 0
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.setInlineImage,
|
|
||||||
(val) => {
|
|
||||||
if (val) {
|
|
||||||
editor.value?.commands.setImage({
|
|
||||||
src: val.src,
|
|
||||||
alt: val.alt,
|
|
||||||
title: val.title
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Insert content at cursor position when insertContent prop changes.
|
||||||
watch(
|
watch(
|
||||||
() => props.insertContent,
|
() => props.insertContent,
|
||||||
(val) => {
|
(val) => {
|
||||||
@@ -328,18 +238,6 @@ onUnmounted(() => {
|
|||||||
editor.value?.destroy()
|
editor.value?.destroy()
|
||||||
})
|
})
|
||||||
|
|
||||||
const toggleBulletList = () => {
|
|
||||||
if (editor.value) {
|
|
||||||
editor.value.chain().focus().toggleBulletList().run()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggleOrderedList = () => {
|
|
||||||
if (editor.value) {
|
|
||||||
editor.value.chain().focus().toggleOrderedList().run()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const openLinkModal = () => {
|
const openLinkModal = () => {
|
||||||
if (editor.value?.isActive('link')) {
|
if (editor.value?.isActive('link')) {
|
||||||
linkUrl.value = editor.value.getAttributes('link').href
|
linkUrl.value = editor.value.getAttributes('link').href
|
||||||
|
|||||||
@@ -138,8 +138,7 @@ export const accountNavItems = [
|
|||||||
{
|
{
|
||||||
titleKey: 'globals.terms.profile',
|
titleKey: 'globals.terms.profile',
|
||||||
href: '/account/profile',
|
href: '/account/profile',
|
||||||
description: 'Update your profile'
|
},
|
||||||
}
|
|
||||||
]
|
]
|
||||||
|
|
||||||
export const contactNavItems = [
|
export const contactNavItems = [
|
||||||
|
|||||||
@@ -248,18 +248,10 @@ const fetchBusinessHours = async () => {
|
|||||||
})
|
})
|
||||||
businessHours.value = response.data.data
|
businessHours.value = response.data.data
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// If unauthorized (no permission), show a toast message.
|
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
|
||||||
if (error.response.status === 403) {
|
variant: 'destructive',
|
||||||
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
|
description: handleHTTPError(error).message
|
||||||
variant: 'destructive',
|
})
|
||||||
description: t('admin.businessHours.unauthorized')
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
|
|
||||||
variant: 'destructive',
|
|
||||||
description: handleHTTPError(error).message
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<Editor
|
<Editor
|
||||||
v-model:htmlContent="componentField.modelValue"
|
v-model:htmlContent="componentField.modelValue"
|
||||||
@update:htmlContent="(value) => componentField.onChange(value)"
|
@update:htmlContent="(value) => componentField.onChange(value)"
|
||||||
:placeholder="t('editor.newLine') + t('editor.send') + t('editor.cmdK')"
|
:placeholder="t('editor.newLine')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
<div class="w-full space-y-6 pb-8 relative">
|
<div class="w-full space-y-6 pb-8 relative">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<span class="text-xl font-semibold text-gray-900 dark:text-foreground">{{ $t('globals.terms.note', 2) }}</span>
|
<span class="text-xl font-semibold text-gray-900 dark:text-foreground">
|
||||||
|
{{ $t('globals.terms.note', 2) }}
|
||||||
|
</span>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -27,7 +29,7 @@
|
|||||||
<Editor
|
<Editor
|
||||||
v-model:htmlContent="newNote"
|
v-model:htmlContent="newNote"
|
||||||
@update:htmlContent="(value) => (newNote = value)"
|
@update:htmlContent="(value) => (newNote = value)"
|
||||||
:placeholder="t('editor.newLine') + t('editor.send') + t('editor.cmdK')"
|
:placeholder="t('editor.newLine') + t('editor.send')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-end space-x-3 pt-2">
|
<div class="flex justify-end space-x-3 pt-2">
|
||||||
@@ -64,7 +66,9 @@
|
|||||||
</AvatarFallback>
|
</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm font-medium text-gray-900 dark:text-foreground">{{ note.first_name }} {{ note.last_name }}</p>
|
<p class="text-sm font-medium text-gray-900 dark:text-foreground">
|
||||||
|
{{ note.first_name }} {{ note.last_name }}
|
||||||
|
</p>
|
||||||
<p class="text-xs text-muted-foreground flex items-center">
|
<p class="text-xs text-muted-foreground flex items-center">
|
||||||
<ClockIcon class="h-3 w-3 mr-1 inline-block opacity-70" />
|
<ClockIcon class="h-3 w-3 mr-1 inline-block opacity-70" />
|
||||||
{{ formatDate(note.created_at) }}
|
{{ formatDate(note.created_at) }}
|
||||||
@@ -115,7 +119,9 @@
|
|||||||
<div class="rounded-full bg-gray-100 dark:bg-foreground p-4 mb-2">
|
<div class="rounded-full bg-gray-100 dark:bg-foreground p-4 mb-2">
|
||||||
<MessageSquareIcon class="text-gray-400 dark:text-background" size="25" />
|
<MessageSquareIcon class="text-gray-400 dark:text-background" size="25" />
|
||||||
</div>
|
</div>
|
||||||
<h3 class="mt-2 text-base font-medium text-gray-900 dark:text-foreground">{{ $t('contact.notes.empty') }}</h3>
|
<h3 class="mt-2 text-base font-medium text-gray-900 dark:text-foreground">
|
||||||
|
{{ $t('contact.notes.empty') }}
|
||||||
|
</h3>
|
||||||
<p class="mt-1 text-sm text-muted-foreground max-w-sm mx-auto">
|
<p class="mt-1 text-sm text-muted-foreground max-w-sm mx-auto">
|
||||||
{{ $t('contact.notes.help') }}
|
{{ $t('contact.notes.help') }}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -161,9 +161,7 @@
|
|||||||
<Editor
|
<Editor
|
||||||
v-model:htmlContent="componentField.modelValue"
|
v-model:htmlContent="componentField.modelValue"
|
||||||
@update:htmlContent="(value) => componentField.onChange(value)"
|
@update:htmlContent="(value) => componentField.onChange(value)"
|
||||||
:contentToSet="contentToSet"
|
:placeholder="t('editor.newLine') + t('editor.cmdK')"
|
||||||
:placeholder="t('editor.newLine') + t('editor.send') + t('editor.cmdK')"
|
|
||||||
:clearContent="clearEditorContent"
|
|
||||||
:insertContent="insertContent"
|
:insertContent="insertContent"
|
||||||
:autoFocus="false"
|
:autoFocus="false"
|
||||||
class="w-full flex-1 overflow-y-auto p-2 box min-h-0"
|
class="w-full flex-1 overflow-y-auto p-2 box min-h-0"
|
||||||
@@ -199,6 +197,7 @@
|
|||||||
<ReplyBoxMenuBar
|
<ReplyBoxMenuBar
|
||||||
:handleFileUpload="handleFileUpload"
|
:handleFileUpload="handleFileUpload"
|
||||||
@emojiSelect="handleEmojiSelect"
|
@emojiSelect="handleEmojiSelect"
|
||||||
|
:showSendButton="false"
|
||||||
/>
|
/>
|
||||||
<Button type="submit" :disabled="isDisabled" :isLoading="loading">
|
<Button type="submit" :disabled="isDisabled" :isLoading="loading">
|
||||||
{{ $t('globals.buttons.submit') }}
|
{{ $t('globals.buttons.submit') }}
|
||||||
@@ -266,9 +265,6 @@ const emailQuery = ref('')
|
|||||||
const conversationStore = useConversationStore()
|
const conversationStore = useConversationStore()
|
||||||
const macroStore = useMacroStore()
|
const macroStore = useMacroStore()
|
||||||
let timeoutId = null
|
let timeoutId = null
|
||||||
|
|
||||||
const contentToSet = ref('')
|
|
||||||
const clearEditorContent = ref(false)
|
|
||||||
const insertContent = ref('')
|
const insertContent = ref('')
|
||||||
|
|
||||||
const handleEmojiSelect = (emoji) => {
|
const handleEmojiSelect = (emoji) => {
|
||||||
@@ -420,11 +416,7 @@ const createConversation = form.handleSubmit(async (values) => {
|
|||||||
watch(
|
watch(
|
||||||
() => conversationStore.getMacro('new-conversation').id,
|
() => conversationStore.getMacro('new-conversation').id,
|
||||||
() => {
|
() => {
|
||||||
// Setting timestamp, so the same macro can be set again.
|
form.setFieldValue('content', conversationStore.getMacro('new-conversation').message_content)
|
||||||
contentToSet.value = JSON.stringify({
|
|
||||||
content: conversationStore.getMacro('new-conversation').message_content,
|
|
||||||
timestamp: Date.now()
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
{ deep: true }
|
{ deep: true }
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<div
|
<div
|
||||||
v-for="action in actions"
|
v-for="action in actions"
|
||||||
:key="action.type"
|
:key="action.type"
|
||||||
class="flex items-center border border-gray-200 rounded shadow-sm transition-all duration-300 ease-in-out hover:shadow-md group gap-2"
|
class="flex items-center border bg-background border-gray-200 rounded shadow-sm transition-all duration-300 ease-in-out hover:shadow-md group gap-2"
|
||||||
>
|
>
|
||||||
<div class="flex items-center space-x-2 px-2">
|
<div class="flex items-center space-x-2 px-2">
|
||||||
<component
|
<component
|
||||||
|
|||||||
@@ -53,13 +53,8 @@
|
|||||||
:isSending="isSending"
|
:isSending="isSending"
|
||||||
:uploadingFiles="uploadingFiles"
|
:uploadingFiles="uploadingFiles"
|
||||||
:clearEditorContent="clearEditorContent"
|
:clearEditorContent="clearEditorContent"
|
||||||
:contentToSet="contentToSet"
|
|
||||||
v-model:htmlContent="htmlContent"
|
v-model:htmlContent="htmlContent"
|
||||||
v-model:textContent="textContent"
|
v-model:textContent="textContent"
|
||||||
v-model:selectedText="selectedText"
|
|
||||||
v-model:isBold="isBold"
|
|
||||||
v-model:isItalic="isItalic"
|
|
||||||
v-model:cursorPosition="cursorPosition"
|
|
||||||
v-model:to="to"
|
v-model:to="to"
|
||||||
v-model:cc="cc"
|
v-model:cc="cc"
|
||||||
v-model:bcc="bcc"
|
v-model:bcc="bcc"
|
||||||
@@ -88,14 +83,9 @@
|
|||||||
:isSending="isSending"
|
:isSending="isSending"
|
||||||
:uploadingFiles="uploadingFiles"
|
:uploadingFiles="uploadingFiles"
|
||||||
:clearEditorContent="clearEditorContent"
|
:clearEditorContent="clearEditorContent"
|
||||||
:contentToSet="contentToSet"
|
|
||||||
:uploadedFiles="mediaFiles"
|
:uploadedFiles="mediaFiles"
|
||||||
v-model:htmlContent="htmlContent"
|
v-model:htmlContent="htmlContent"
|
||||||
v-model:textContent="textContent"
|
v-model:textContent="textContent"
|
||||||
v-model:selectedText="selectedText"
|
|
||||||
v-model:isBold="isBold"
|
|
||||||
v-model:isItalic="isItalic"
|
|
||||||
v-model:cursorPosition="cursorPosition"
|
|
||||||
v-model:to="to"
|
v-model:to="to"
|
||||||
v-model:cc="cc"
|
v-model:cc="cc"
|
||||||
v-model:bcc="bcc"
|
v-model:bcc="bcc"
|
||||||
@@ -181,11 +171,6 @@ const emailErrors = ref([])
|
|||||||
const aiPrompts = ref([])
|
const aiPrompts = ref([])
|
||||||
const htmlContent = ref('')
|
const htmlContent = ref('')
|
||||||
const textContent = ref('')
|
const textContent = ref('')
|
||||||
const selectedText = ref('')
|
|
||||||
const isBold = ref(false)
|
|
||||||
const isItalic = ref(false)
|
|
||||||
const cursorPosition = ref(0)
|
|
||||||
const contentToSet = ref('')
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await fetchAiPrompts()
|
await fetchAiPrompts()
|
||||||
@@ -218,10 +203,7 @@ const handleAiPromptSelected = async (key) => {
|
|||||||
prompt_key: key,
|
prompt_key: key,
|
||||||
content: textContent.value
|
content: textContent.value
|
||||||
})
|
})
|
||||||
contentToSet.value = JSON.stringify({
|
htmlContent.value = resp.data.data.replace(/\n/g, '<br>')
|
||||||
content: resp.data.data.replace(/\n/g, '<br>'),
|
|
||||||
timestamp: Date.now()
|
|
||||||
})
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Check if user needs to enter OpenAI API key and has permission to do so.
|
// Check if user needs to enter OpenAI API key and has permission to do so.
|
||||||
if (error.response?.status === 400 && userStore.can('ai:manage')) {
|
if (error.response?.status === 400 && userStore.can('ai:manage')) {
|
||||||
@@ -348,11 +330,7 @@ const processSend = async () => {
|
|||||||
watch(
|
watch(
|
||||||
() => conversationStore.getMacro('reply').id,
|
() => conversationStore.getMacro('reply').id,
|
||||||
() => {
|
() => {
|
||||||
// Setting timestamp, so the same macro can be set again.
|
htmlContent.value = conversationStore.getMacro('reply').message_content
|
||||||
contentToSet.value = JSON.stringify({
|
|
||||||
content: conversationStore.getMacro('reply').message_content,
|
|
||||||
timestamp: Date.now()
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
{ deep: true }
|
{ deep: true }
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -84,21 +84,14 @@
|
|||||||
<!-- Main tiptap editor -->
|
<!-- Main tiptap editor -->
|
||||||
<div class="flex-grow flex flex-col overflow-hidden">
|
<div class="flex-grow flex flex-col overflow-hidden">
|
||||||
<Editor
|
<Editor
|
||||||
v-model:selectedText="selectedText"
|
|
||||||
v-model:isBold="isBold"
|
|
||||||
v-model:isItalic="isItalic"
|
|
||||||
v-model:htmlContent="htmlContent"
|
v-model:htmlContent="htmlContent"
|
||||||
v-model:textContent="textContent"
|
v-model:textContent="textContent"
|
||||||
v-model:cursorPosition="cursorPosition"
|
|
||||||
:placeholder="t('editor.newLine') + t('editor.send') + t('editor.cmdK')"
|
:placeholder="t('editor.newLine') + t('editor.send') + t('editor.cmdK')"
|
||||||
:aiPrompts="aiPrompts"
|
:aiPrompts="aiPrompts"
|
||||||
@aiPromptSelected="handleAiPromptSelected"
|
|
||||||
:contentToSet="contentToSet"
|
|
||||||
@send="handleSend"
|
|
||||||
:clearContent="clearEditorContent"
|
|
||||||
:setInlineImage="setInlineImage"
|
|
||||||
:insertContent="insertContent"
|
:insertContent="insertContent"
|
||||||
:autoFocus="true"
|
:autoFocus="true"
|
||||||
|
@aiPromptSelected="handleAiPromptSelected"
|
||||||
|
@send="handleSend"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -124,14 +117,9 @@
|
|||||||
class="mt-1 shrink-0"
|
class="mt-1 shrink-0"
|
||||||
:isFullscreen="isFullscreen"
|
:isFullscreen="isFullscreen"
|
||||||
:handleFileUpload="handleFileUpload"
|
:handleFileUpload="handleFileUpload"
|
||||||
:isBold="isBold"
|
|
||||||
:isItalic="isItalic"
|
|
||||||
:isSending="isSending"
|
:isSending="isSending"
|
||||||
@toggleBold="toggleBold"
|
|
||||||
@toggleItalic="toggleItalic"
|
|
||||||
:enableSend="enableSend"
|
:enableSend="enableSend"
|
||||||
:handleSend="handleSend"
|
:handleSend="handleSend"
|
||||||
:showSendButton="true"
|
|
||||||
@emojiSelect="handleEmojiSelect"
|
@emojiSelect="handleEmojiSelect"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -162,10 +150,6 @@ const showBcc = defineModel('showBcc', { default: false })
|
|||||||
const emailErrors = defineModel('emailErrors', { default: () => [] })
|
const emailErrors = defineModel('emailErrors', { default: () => [] })
|
||||||
const htmlContent = defineModel('htmlContent', { default: '' })
|
const htmlContent = defineModel('htmlContent', { default: '' })
|
||||||
const textContent = defineModel('textContent', { default: '' })
|
const textContent = defineModel('textContent', { default: '' })
|
||||||
const selectedText = defineModel('selectedText', { default: '' })
|
|
||||||
const isBold = defineModel('isBold', { default: false })
|
|
||||||
const isItalic = defineModel('isItalic', { default: false })
|
|
||||||
const cursorPosition = defineModel('cursorPosition', { default: 0 })
|
|
||||||
const macroStore = useMacroStore()
|
const macroStore = useMacroStore()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -185,14 +169,6 @@ const props = defineProps({
|
|||||||
type: Array,
|
type: Array,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
clearEditorContent: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
contentToSet: {
|
|
||||||
type: String,
|
|
||||||
default: null
|
|
||||||
},
|
|
||||||
uploadedFiles: {
|
uploadedFiles: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -212,9 +188,7 @@ const emit = defineEmits([
|
|||||||
const conversationStore = useConversationStore()
|
const conversationStore = useConversationStore()
|
||||||
const emitter = useEmitter()
|
const emitter = useEmitter()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
const insertContent = ref(null)
|
const insertContent = ref(null)
|
||||||
const setInlineImage = ref(null)
|
|
||||||
|
|
||||||
const toggleBcc = async () => {
|
const toggleBcc = async () => {
|
||||||
showBcc.value = !showBcc.value
|
showBcc.value = !showBcc.value
|
||||||
@@ -231,14 +205,6 @@ const toggleFullscreen = () => {
|
|||||||
emit('toggleFullscreen')
|
emit('toggleFullscreen')
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleBold = () => {
|
|
||||||
isBold.value = !isBold.value
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggleItalic = () => {
|
|
||||||
isItalic.value = !isItalic.value
|
|
||||||
}
|
|
||||||
|
|
||||||
const enableSend = computed(() => {
|
const enableSend = computed(() => {
|
||||||
return (
|
return (
|
||||||
(textContent.value.trim().length > 0 ||
|
(textContent.value.trim().length > 0 ||
|
||||||
|
|||||||
@@ -65,7 +65,10 @@ defineProps({
|
|||||||
isSending: Boolean,
|
isSending: Boolean,
|
||||||
enableSend: Boolean,
|
enableSend: Boolean,
|
||||||
handleSend: Function,
|
handleSend: Function,
|
||||||
showSendButton: Boolean,
|
showSendButton: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
handleFileUpload: Function,
|
handleFileUpload: Function,
|
||||||
handleInlineImageUpload: Function
|
handleInlineImageUpload: Function
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -640,7 +640,7 @@ export const useConversationStore = defineStore('conversation', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** Macros for new conversation or open conversation **/
|
/** Macros set for new conversation or an open conversation **/
|
||||||
async function setMacro (macro, context) {
|
async function setMacro (macro, context) {
|
||||||
macros.value[context] = macro
|
macros.value[context] = macro
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user