fix: Avoid editor formatting issues when a canned response is edited (#5533)
This commit is contained in:
parent
7b54990ae6
commit
705d06ac3c
4 changed files with 85 additions and 57 deletions
|
@ -28,7 +28,7 @@ import {
|
||||||
suggestionsPlugin,
|
suggestionsPlugin,
|
||||||
triggerCharacters,
|
triggerCharacters,
|
||||||
} from '@chatwoot/prosemirror-schema/src/mentions/plugin';
|
} from '@chatwoot/prosemirror-schema/src/mentions/plugin';
|
||||||
import { EditorState } from 'prosemirror-state';
|
import { EditorState, Selection } from 'prosemirror-state';
|
||||||
import { defaultMarkdownParser } from 'prosemirror-markdown';
|
import { defaultMarkdownParser } from 'prosemirror-markdown';
|
||||||
import { wootWriterSetup } from '@chatwoot/prosemirror-schema';
|
import { wootWriterSetup } from '@chatwoot/prosemirror-schema';
|
||||||
|
|
||||||
|
@ -61,23 +61,28 @@ export default {
|
||||||
mixins: [eventListenerMixins],
|
mixins: [eventListenerMixins],
|
||||||
props: {
|
props: {
|
||||||
value: { type: String, default: '' },
|
value: { type: String, default: '' },
|
||||||
|
editorId: { type: String, default: '' },
|
||||||
placeholder: { type: String, default: '' },
|
placeholder: { type: String, default: '' },
|
||||||
isPrivate: { type: Boolean, default: false },
|
isPrivate: { type: Boolean, default: false },
|
||||||
isFormatMode: { type: Boolean, default: false },
|
|
||||||
enableSuggestions: { type: Boolean, default: true },
|
enableSuggestions: { type: Boolean, default: true },
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
lastValue: null,
|
|
||||||
showUserMentions: false,
|
showUserMentions: false,
|
||||||
showCannedMenu: false,
|
showCannedMenu: false,
|
||||||
mentionSearchKey: '',
|
mentionSearchKey: '',
|
||||||
cannedSearchTerm: '',
|
cannedSearchTerm: '',
|
||||||
editorView: null,
|
editorView: null,
|
||||||
range: null,
|
range: null,
|
||||||
|
state: undefined,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
contentFromEditor() {
|
||||||
|
return addMentionsToMarkdownSerializer(
|
||||||
|
defaultMarkdownSerializer
|
||||||
|
).serialize(this.editorView.state.doc);
|
||||||
|
},
|
||||||
plugins() {
|
plugins() {
|
||||||
if (!this.enableSuggestions) {
|
if (!this.enableSuggestions) {
|
||||||
return [];
|
return [];
|
||||||
|
@ -102,7 +107,6 @@ export default {
|
||||||
onExit: () => {
|
onExit: () => {
|
||||||
this.mentionSearchKey = '';
|
this.mentionSearchKey = '';
|
||||||
this.showUserMentions = false;
|
this.showUserMentions = false;
|
||||||
this.editorView = null;
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
onKeyDown: ({ event }) => {
|
onKeyDown: ({ event }) => {
|
||||||
|
@ -131,7 +135,6 @@ export default {
|
||||||
onExit: () => {
|
onExit: () => {
|
||||||
this.cannedSearchTerm = '';
|
this.cannedSearchTerm = '';
|
||||||
this.showCannedMenu = false;
|
this.showCannedMenu = false;
|
||||||
this.editorView = null;
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
onKeyDown: ({ event }) => {
|
onKeyDown: ({ event }) => {
|
||||||
|
@ -149,28 +152,33 @@ export default {
|
||||||
this.$emit('toggle-canned-menu', !this.isPrivate && updatedValue);
|
this.$emit('toggle-canned-menu', !this.isPrivate && updatedValue);
|
||||||
},
|
},
|
||||||
value(newValue = '') {
|
value(newValue = '') {
|
||||||
if (newValue !== this.lastValue) {
|
if (newValue !== this.contentFromEditor) {
|
||||||
const { tr } = this.state;
|
this.reloadState();
|
||||||
if (this.isFormatMode) {
|
|
||||||
this.state = createState(
|
|
||||||
newValue,
|
|
||||||
this.placeholder,
|
|
||||||
this.plugins,
|
|
||||||
this.isFormatMode
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
tr.insertText(newValue, 0, tr.doc.content.size);
|
|
||||||
this.state = this.view.state.apply(tr);
|
|
||||||
}
|
|
||||||
this.view.updateState(this.state);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
editorId() {
|
||||||
|
this.reloadState();
|
||||||
|
},
|
||||||
|
isPrivate() {
|
||||||
|
this.reloadState();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.state = createState(this.value, this.placeholder, this.plugins);
|
this.state = createState(this.value, this.placeholder, this.plugins);
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.view = new EditorView(this.$refs.editor, {
|
this.createEditorView();
|
||||||
|
this.editorView.updateState(this.state);
|
||||||
|
this.focusEditorInputField();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
reloadState() {
|
||||||
|
this.state = createState(this.value, this.placeholder, this.plugins);
|
||||||
|
this.editorView.updateState(this.state);
|
||||||
|
this.focusEditorInputField();
|
||||||
|
},
|
||||||
|
createEditorView() {
|
||||||
|
this.editorView = new EditorView(this.$refs.editor, {
|
||||||
state: this.state,
|
state: this.state,
|
||||||
dispatchTransaction: tx => {
|
dispatchTransaction: tx => {
|
||||||
this.state = this.state.apply(tx);
|
this.state = this.state.apply(tx);
|
||||||
|
@ -194,9 +202,7 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.focusEditorInputField();
|
|
||||||
},
|
},
|
||||||
methods: {
|
|
||||||
handleKeyEvents(e) {
|
handleKeyEvents(e) {
|
||||||
if (hasPressedAltAndPKey(e)) {
|
if (hasPressedAltAndPKey(e)) {
|
||||||
this.focusEditorInputField();
|
this.focusEditorInputField();
|
||||||
|
@ -206,47 +212,59 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
focusEditorInputField() {
|
focusEditorInputField() {
|
||||||
this.$refs.editor.querySelector('div.ProseMirror-woot-style').focus();
|
const { tr } = this.editorView.state;
|
||||||
|
const selection = Selection.atEnd(tr.doc);
|
||||||
|
|
||||||
|
this.editorView.dispatch(tr.setSelection(selection));
|
||||||
|
this.editorView.focus();
|
||||||
},
|
},
|
||||||
insertMentionNode(mentionItem) {
|
insertMentionNode(mentionItem) {
|
||||||
if (!this.view) {
|
if (!this.editorView) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const node = this.view.state.schema.nodes.mention.create({
|
const node = this.editorView.state.schema.nodes.mention.create({
|
||||||
userId: mentionItem.key,
|
userId: mentionItem.key,
|
||||||
userFullName: mentionItem.label,
|
userFullName: mentionItem.label,
|
||||||
});
|
});
|
||||||
|
|
||||||
const tr = this.view.state.tr.replaceWith(
|
const tr = this.editorView.state.tr.replaceWith(
|
||||||
this.range.from,
|
this.range.from,
|
||||||
this.range.to,
|
this.range.to,
|
||||||
node
|
node
|
||||||
);
|
);
|
||||||
this.state = this.view.state.apply(tr);
|
this.state = this.editorView.state.apply(tr);
|
||||||
return this.emitOnChange();
|
return this.emitOnChange();
|
||||||
},
|
},
|
||||||
|
|
||||||
insertCannedResponse(cannedItem) {
|
insertCannedResponse(cannedItem) {
|
||||||
if (!this.view) {
|
if (!this.editorView) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tr = this.view.state.tr.insertText(
|
const tr = this.editorView.state.tr.insertText(
|
||||||
cannedItem,
|
cannedItem,
|
||||||
this.range.from,
|
this.range.from,
|
||||||
this.range.to
|
this.range.to
|
||||||
);
|
);
|
||||||
this.state = this.view.state.apply(tr);
|
this.state = this.editorView.state.apply(tr);
|
||||||
return this.emitOnChange();
|
this.emitOnChange();
|
||||||
|
|
||||||
|
// Hacky fix for #5501
|
||||||
|
this.state = createState(
|
||||||
|
this.contentFromEditor,
|
||||||
|
this.placeholder,
|
||||||
|
this.plugins
|
||||||
|
);
|
||||||
|
this.editorView.updateState(this.state);
|
||||||
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
emitOnChange() {
|
emitOnChange() {
|
||||||
this.view.updateState(this.state);
|
this.editorView.updateState(this.state);
|
||||||
this.lastValue = addMentionsToMarkdownSerializer(
|
|
||||||
defaultMarkdownSerializer
|
this.$emit('input', this.contentFromEditor);
|
||||||
).serialize(this.state.doc);
|
|
||||||
this.$emit('input', this.lastValue);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
hideMentions() {
|
hideMentions() {
|
||||||
this.showUserMentions = false;
|
this.showUserMentions = false;
|
||||||
},
|
},
|
||||||
|
|
|
@ -56,6 +56,7 @@
|
||||||
<woot-message-editor
|
<woot-message-editor
|
||||||
v-else
|
v-else
|
||||||
v-model="message"
|
v-model="message"
|
||||||
|
:editor-id="editorStateId"
|
||||||
class="input"
|
class="input"
|
||||||
:is-private="isOnPrivateNote"
|
:is-private="isOnPrivateNote"
|
||||||
:placeholder="messagePlaceHolder"
|
:placeholder="messagePlaceHolder"
|
||||||
|
@ -429,6 +430,13 @@ export default {
|
||||||
profilePath() {
|
profilePath() {
|
||||||
return frontendURL(`accounts/${this.accountId}/profile/settings`);
|
return frontendURL(`accounts/${this.accountId}/profile/settings`);
|
||||||
},
|
},
|
||||||
|
conversationId() {
|
||||||
|
return this.currentChat.id;
|
||||||
|
},
|
||||||
|
editorStateId() {
|
||||||
|
const key = `draft-${this.conversationIdByRoute}-${this.replyType}`;
|
||||||
|
return key;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
currentChat(conversation) {
|
currentChat(conversation) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
export const LOCAL_STORAGE_KEYS = {
|
export const LOCAL_STORAGE_KEYS = {
|
||||||
DISMISSED_UPDATES: 'dismissedUpdates',
|
DISMISSED_UPDATES: 'dismissedUpdates',
|
||||||
WIDGET_BUILDER: 'widgetBubble_',
|
WIDGET_BUILDER: 'widgetBubble_',
|
||||||
|
DRAFT_MESSAGES: 'draftMessages',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LocalStorage = {
|
export const LocalStorage = {
|
||||||
|
|
|
@ -46,6 +46,7 @@ export default {
|
||||||
return {
|
return {
|
||||||
articleTitle: '',
|
articleTitle: '',
|
||||||
articleContent: '',
|
articleContent: '',
|
||||||
|
saveArticle: () => {},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|
Loading…
Reference in a new issue