mirror of
https://github.com/lingble/chatwoot.git
synced 2025-10-30 02:32:29 +00:00
chore: Replace plain reply editor with rich editor for multiple canned responses
This commit is contained in:
@@ -78,10 +78,6 @@ export default {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showEditorToggle: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isOnPrivateNote: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
@@ -130,7 +126,6 @@ export default {
|
||||
emits: [
|
||||
'replaceText',
|
||||
'toggleInsertArticle',
|
||||
'toggleEditor',
|
||||
'selectWhatsappTemplate',
|
||||
'selectContentTemplate',
|
||||
'toggleQuotedReply',
|
||||
@@ -325,18 +320,8 @@ export default {
|
||||
sm
|
||||
@click="toggleAudioRecorder"
|
||||
/>
|
||||
<NextButton
|
||||
v-if="showEditorToggle"
|
||||
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_FORMAT_ICON')"
|
||||
icon="i-ph-quotes"
|
||||
slate
|
||||
faded
|
||||
sm
|
||||
@click="$emit('toggleEditor')"
|
||||
/>
|
||||
<NextButton
|
||||
v-if="showAudioPlayStopButton"
|
||||
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_FORMAT_ICON')"
|
||||
:icon="audioRecorderPlayStopIcon"
|
||||
slate
|
||||
faded
|
||||
|
||||
@@ -9,7 +9,6 @@ import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
||||
|
||||
import CannedResponse from './CannedResponse.vue';
|
||||
import ReplyToMessage from './ReplyToMessage.vue';
|
||||
import ResizableTextArea from 'shared/components/ResizableTextArea.vue';
|
||||
import AttachmentPreview from 'dashboard/components/widgets/AttachmentsPreview.vue';
|
||||
import ReplyTopPanel from 'dashboard/components/widgets/WootWriter/ReplyTopPanel.vue';
|
||||
import ReplyEmailHead from './ReplyEmailHead.vue';
|
||||
@@ -45,8 +44,6 @@ import fileUploadMixin from 'dashboard/mixins/fileUploadMixin';
|
||||
import {
|
||||
appendSignature,
|
||||
removeSignature,
|
||||
replaceSignature,
|
||||
extractTextFromMarkdown,
|
||||
} from 'dashboard/helper/editorHelper';
|
||||
|
||||
import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage';
|
||||
@@ -69,7 +66,6 @@ export default {
|
||||
ReplyEmailHead,
|
||||
ReplyToMessage,
|
||||
ReplyTopPanel,
|
||||
ResizableTextArea,
|
||||
ContentTemplates,
|
||||
WhatsappTemplates,
|
||||
WootMessageEditor,
|
||||
@@ -86,7 +82,6 @@ export default {
|
||||
setup() {
|
||||
const {
|
||||
uiSettings,
|
||||
updateUISettings,
|
||||
isEditorHotKeyEnabled,
|
||||
fetchSignatureFlagFromUISettings,
|
||||
setQuotedReplyFlagForInbox,
|
||||
@@ -97,7 +92,6 @@ export default {
|
||||
|
||||
return {
|
||||
uiSettings,
|
||||
updateUISettings,
|
||||
isEditorHotKeyEnabled,
|
||||
fetchSignatureFlagFromUISettings,
|
||||
setQuotedReplyFlagForInbox,
|
||||
@@ -115,7 +109,6 @@ export default {
|
||||
isRecordingAudio: false,
|
||||
recordingAudioState: '',
|
||||
recordingAudioDurationText: '',
|
||||
isUploading: false,
|
||||
replyType: REPLY_EDITOR_MODES.REPLY,
|
||||
mentionSearchKey: '',
|
||||
hasSlashCommand: false,
|
||||
@@ -159,20 +152,6 @@ export default {
|
||||
!this.is360DialogWhatsAppChannel
|
||||
);
|
||||
},
|
||||
showRichContentEditor() {
|
||||
if (this.isOnPrivateNote || this.isRichEditorEnabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isAPIInbox) {
|
||||
const {
|
||||
display_rich_content_editor: displayRichContentEditor = false,
|
||||
} = this.uiSettings;
|
||||
return displayRichContentEditor;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
showWhatsappTemplates() {
|
||||
return this.isAWhatsAppCloudChannel && !this.isPrivate;
|
||||
},
|
||||
@@ -288,9 +267,6 @@ export default {
|
||||
hasAttachments() {
|
||||
return this.attachedFiles.length;
|
||||
},
|
||||
isRichEditorEnabled() {
|
||||
return this.isAWebWidgetInbox || this.isAnEmailChannel;
|
||||
},
|
||||
showAudioRecorder() {
|
||||
return !this.isOnPrivateNote && this.showFileUpload;
|
||||
},
|
||||
@@ -330,21 +306,11 @@ export default {
|
||||
return !this.isPrivate && this.sendWithSignature;
|
||||
},
|
||||
isSignatureAvailable() {
|
||||
return !!this.signatureToApply;
|
||||
return !!this.messageSignature;
|
||||
},
|
||||
sendWithSignature() {
|
||||
return this.fetchSignatureFlagFromUISettings(this.channelType);
|
||||
},
|
||||
editorMessageKey() {
|
||||
const { editor_message_key: isEnabled } = this.uiSettings;
|
||||
return isEnabled;
|
||||
},
|
||||
commandPlusEnterToSendEnabled() {
|
||||
return this.editorMessageKey === 'cmd_enter';
|
||||
},
|
||||
enterToSendEnabled() {
|
||||
return this.editorMessageKey === 'enter';
|
||||
},
|
||||
conversationId() {
|
||||
return this.currentChat.id;
|
||||
},
|
||||
@@ -371,12 +337,6 @@ export default {
|
||||
});
|
||||
return variables;
|
||||
},
|
||||
// ensure that the signature is plain text depending on `showRichContentEditor`
|
||||
signatureToApply() {
|
||||
return this.showRichContentEditor
|
||||
? this.messageSignature
|
||||
: extractTextFromMarkdown(this.messageSignature);
|
||||
},
|
||||
connectedPortalSlug() {
|
||||
const { help_center: portal = {} } = this.inbox;
|
||||
const { slug = '' } = portal;
|
||||
@@ -473,13 +433,13 @@ export default {
|
||||
// Check if the message starts with a slash.
|
||||
const bodyWithoutSignature = removeSignature(
|
||||
updatedMessage,
|
||||
this.signatureToApply
|
||||
this.messageSignature
|
||||
);
|
||||
const startsWithSlash = bodyWithoutSignature.startsWith('/');
|
||||
|
||||
// Determine if the user is potentially typing a slash command.
|
||||
// This is true if the message starts with a slash and the rich content editor is not active.
|
||||
this.hasSlashCommand = startsWithSlash && !this.showRichContentEditor;
|
||||
// This is true if the message starts with a slash.
|
||||
this.hasSlashCommand = startsWithSlash;
|
||||
this.showMentions = this.hasSlashCommand;
|
||||
|
||||
// If a slash command is active, extract the command text after the slash.
|
||||
@@ -500,7 +460,7 @@ export default {
|
||||
mounted() {
|
||||
this.getFromDraft();
|
||||
// Don't use the keyboard listener mixin here as the events here are supposed to be
|
||||
// working even if input/textarea is focussed.
|
||||
// working even if the editor is focussed.
|
||||
document.addEventListener('paste', this.onPaste);
|
||||
document.addEventListener('keydown', this.handleKeyEvents);
|
||||
this.setCCAndToEmailsFromLastChat();
|
||||
@@ -537,45 +497,17 @@ export default {
|
||||
methods: {
|
||||
handleInsert(article) {
|
||||
const { url, title } = article;
|
||||
if (this.isRichEditorEnabled) {
|
||||
// Removing empty lines from the title
|
||||
const lines = title.split('\n');
|
||||
const nonEmptyLines = lines.filter(line => line.trim() !== '');
|
||||
const filteredMarkdown = nonEmptyLines.join(' ');
|
||||
emitter.emit(
|
||||
BUS_EVENTS.INSERT_INTO_RICH_EDITOR,
|
||||
`[${filteredMarkdown}](${url})`
|
||||
);
|
||||
} else {
|
||||
this.addIntoEditor(
|
||||
`${this.$t('CONVERSATION.REPLYBOX.INSERT_READ_MORE')} ${url}`
|
||||
);
|
||||
}
|
||||
// Removing empty lines from the title
|
||||
const lines = title.split('\n');
|
||||
const nonEmptyLines = lines.filter(line => line.trim() !== '');
|
||||
const filteredMarkdown = nonEmptyLines.join(' ');
|
||||
emitter.emit(
|
||||
BUS_EVENTS.INSERT_INTO_RICH_EDITOR,
|
||||
`[${filteredMarkdown}](${url})`
|
||||
);
|
||||
|
||||
useTrack(CONVERSATION_EVENTS.INSERT_ARTICLE_LINK);
|
||||
},
|
||||
toggleRichContentEditor() {
|
||||
this.updateUISettings({
|
||||
display_rich_content_editor: !this.showRichContentEditor,
|
||||
});
|
||||
|
||||
const plainTextSignature = extractTextFromMarkdown(this.messageSignature);
|
||||
|
||||
if (!this.showRichContentEditor && this.messageSignature) {
|
||||
// remove the old signature -> extract text from markdown -> attach new signature
|
||||
let message = removeSignature(this.message, this.messageSignature);
|
||||
message = extractTextFromMarkdown(message);
|
||||
message = appendSignature(message, plainTextSignature);
|
||||
|
||||
this.message = message;
|
||||
} else {
|
||||
this.message = replaceSignature(
|
||||
this.message,
|
||||
plainTextSignature,
|
||||
this.messageSignature
|
||||
);
|
||||
}
|
||||
},
|
||||
toggleQuotedReply() {
|
||||
if (!this.isAnEmailChannel) {
|
||||
return;
|
||||
@@ -643,8 +575,8 @@ export default {
|
||||
}
|
||||
|
||||
return this.sendWithSignature
|
||||
? appendSignature(message, this.signatureToApply)
|
||||
: removeSignature(message, this.signatureToApply);
|
||||
? appendSignature(message, this.messageSignature)
|
||||
: removeSignature(message, this.messageSignature);
|
||||
},
|
||||
removeFromDraft() {
|
||||
if (this.conversationIdByRoute) {
|
||||
@@ -703,9 +635,6 @@ export default {
|
||||
},
|
||||
onPaste(e) {
|
||||
const data = e.clipboardData.files;
|
||||
if (!this.showRichContentEditor && data.length !== 0) {
|
||||
this.$refs.messageInput.$el.blur();
|
||||
}
|
||||
if (!data.length || !data[0]) {
|
||||
return;
|
||||
}
|
||||
@@ -839,7 +768,7 @@ export default {
|
||||
// if signature is enabled, append it to the message
|
||||
// appendSignature ensures that the signature is not duplicated
|
||||
// so we don't need to check if the signature is already present
|
||||
message = appendSignature(message, this.signatureToApply);
|
||||
message = appendSignature(message, this.messageSignature);
|
||||
}
|
||||
|
||||
const updatedMessage = replaceVariablesInMessage({
|
||||
@@ -862,40 +791,22 @@ export default {
|
||||
mode,
|
||||
});
|
||||
if (canReply || this.isAWhatsAppChannel) this.replyType = mode;
|
||||
if (this.showRichContentEditor) {
|
||||
if (this.isRecordingAudio) {
|
||||
this.toggleAudioRecorder();
|
||||
}
|
||||
return;
|
||||
if (this.isRecordingAudio) {
|
||||
this.toggleAudioRecorder();
|
||||
}
|
||||
this.$nextTick(() => this.$refs.messageInput.focus());
|
||||
},
|
||||
clearEditorSelection() {
|
||||
this.updateEditorSelectionWith = '';
|
||||
},
|
||||
insertIntoTextEditor(text, selectionStart, selectionEnd) {
|
||||
const { message } = this;
|
||||
const newMessage =
|
||||
message.slice(0, selectionStart) +
|
||||
text +
|
||||
message.slice(selectionEnd, message.length);
|
||||
this.message = newMessage;
|
||||
},
|
||||
addIntoEditor(content) {
|
||||
if (this.showRichContentEditor) {
|
||||
this.updateEditorSelectionWith = content;
|
||||
this.onFocus();
|
||||
}
|
||||
if (!this.showRichContentEditor) {
|
||||
const { selectionStart, selectionEnd } = this.$refs.messageInput.$el;
|
||||
this.insertIntoTextEditor(content, selectionStart, selectionEnd);
|
||||
}
|
||||
this.updateEditorSelectionWith = content;
|
||||
this.onFocus();
|
||||
},
|
||||
clearMessage() {
|
||||
this.message = '';
|
||||
if (this.sendWithSignature && !this.isPrivate) {
|
||||
// if signature is enabled, append it to the message
|
||||
this.message = appendSignature(this.message, this.signatureToApply);
|
||||
this.message = appendSignature(this.message, this.messageSignature);
|
||||
}
|
||||
this.attachedFiles = [];
|
||||
this.isRecordingAudio = false;
|
||||
@@ -913,19 +824,15 @@ export default {
|
||||
},
|
||||
toggleAudioRecorder() {
|
||||
this.isRecordingAudio = !this.isRecordingAudio;
|
||||
this.isRecorderAudioStopped = !this.isRecordingAudio;
|
||||
if (!this.isRecordingAudio) {
|
||||
this.resetAudioRecorderInput();
|
||||
}
|
||||
},
|
||||
toggleAudioRecorderPlayPause() {
|
||||
if (!this.isRecordingAudio) {
|
||||
return;
|
||||
}
|
||||
if (!this.isRecorderAudioStopped) {
|
||||
this.isRecorderAudioStopped = true;
|
||||
if (!this.$refs.audioRecorderInput) return;
|
||||
if (!this.recordingAudioState) {
|
||||
this.$refs.audioRecorderInput.stopRecording();
|
||||
} else if (this.isRecorderAudioStopped) {
|
||||
} else {
|
||||
this.$refs.audioRecorderInput.playPause();
|
||||
}
|
||||
},
|
||||
@@ -1213,23 +1120,7 @@ export default {
|
||||
@play="recordingAudioState = 'playing'"
|
||||
@pause="recordingAudioState = 'paused'"
|
||||
/>
|
||||
<ResizableTextArea
|
||||
v-else-if="!showRichContentEditor"
|
||||
ref="messageInput"
|
||||
v-model="message"
|
||||
class="rounded-none input"
|
||||
:placeholder="messagePlaceHolder"
|
||||
:min-height="4"
|
||||
:signature="signatureToApply"
|
||||
allow-signature
|
||||
:send-with-signature="sendWithSignature"
|
||||
@typing-off="onTypingOff"
|
||||
@typing-on="onTypingOn"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
/>
|
||||
<WootMessageEditor
|
||||
v-else
|
||||
v-model="message"
|
||||
:editor-id="editorStateId"
|
||||
class="input"
|
||||
@@ -1239,7 +1130,7 @@ export default {
|
||||
:min-height="4"
|
||||
enable-variables
|
||||
:variables="messageVariables"
|
||||
:signature="signatureToApply"
|
||||
:signature="messageSignature"
|
||||
allow-signature
|
||||
:channel-type="channelType"
|
||||
@typing-off="onTypingOff"
|
||||
@@ -1289,7 +1180,6 @@ export default {
|
||||
:recording-audio-state="recordingAudioState"
|
||||
:send-button-text="replyButtonLabel"
|
||||
:show-audio-recorder="showAudioRecorder"
|
||||
:show-editor-toggle="isAPIInbox && !isOnPrivateNote"
|
||||
:show-emoji-picker="showEmojiPicker"
|
||||
:show-file-upload="showFileUpload"
|
||||
:show-quoted-reply-toggle="shouldShowQuotedReplyToggle"
|
||||
@@ -1302,7 +1192,6 @@ export default {
|
||||
:new-conversation-modal-active="newConversationModalActive"
|
||||
@select-whatsapp-template="openWhatsappTemplateModal"
|
||||
@select-content-template="openContentTemplateModal"
|
||||
@toggle-editor="toggleRichContentEditor"
|
||||
@replace-text="replaceText"
|
||||
@toggle-insert-article="toggleInsertArticle"
|
||||
@toggle-quoted-reply="toggleQuotedReply"
|
||||
@@ -1356,10 +1245,6 @@ export default {
|
||||
|
||||
.reply-box__top {
|
||||
@apply relative py-0 px-4 -mt-px;
|
||||
|
||||
textarea {
|
||||
@apply shadow-none outline-none border-transparent bg-transparent m-0 max-h-60 min-h-[3rem] pt-4 pb-0 px-0 resize-none;
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-dialog {
|
||||
|
||||
@@ -196,7 +196,6 @@
|
||||
"INSERT_READ_MORE": "Read more",
|
||||
"DISMISS_REPLY": "Dismiss reply",
|
||||
"REPLYING_TO": "Replying to:",
|
||||
"TIP_FORMAT_ICON": "Show rich text editor",
|
||||
"TIP_EMOJI_ICON": "Show emoji selector",
|
||||
"TIP_ATTACH_ICON": "Attach files",
|
||||
"TIP_AUDIORECORDER_ICON": "Record audio",
|
||||
|
||||
Reference in New Issue
Block a user