mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-02 20:18:08 +00:00
* fix: Resolve and go next keyboard shortcuts doesn't work * refactor: use buildHotKeys instead of hasPressedCommandPlusAltAndEKey * feat: install tinykeys * refactor: use tinykeys * test: update buildKeyEvents * fix: remove stray import * feat: handle action list globally * feat: allow configuring `allowOnFocusedInput` * chore: Navigate chat list item * chore: Navigate dashboard * feat: Navigate editor top panel * feat: Toggle file upload * chore: More keyboard shortcuts * chore: Update mention selection mixin * chore: Phone input * chore: Clean up * chore: Clean up * chore: Dropdown and editor * chore: Enter key to send and clean up * chore: Rename mixin * chore: Review fixes * chore: Removed unused shortcut from modal * fix: Specs --------- Co-authored-by: iamsivin <iamsivin@gmail.com> Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
254 lines
5.9 KiB
Vue
254 lines
5.9 KiB
Vue
<template>
|
|
<div>
|
|
<div class="editor-root editor--article">
|
|
<input
|
|
ref="imageUploadInput"
|
|
type="file"
|
|
accept="image/png, image/jpeg, image/jpg, image/gif, image/webp"
|
|
hidden
|
|
@change="onFileChange"
|
|
/>
|
|
<div ref="editor" />
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import {
|
|
fullSchema,
|
|
buildEditor,
|
|
EditorView,
|
|
ArticleMarkdownSerializer,
|
|
ArticleMarkdownTransformer,
|
|
EditorState,
|
|
Selection,
|
|
} from '@chatwoot/prosemirror-schema';
|
|
import { checkFileSizeLimit } from 'shared/helpers/FileHelper';
|
|
import alertMixin from 'shared/mixins/alertMixin';
|
|
import keyboardEventListenerMixins from 'shared/mixins/keyboardEventListenerMixins';
|
|
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
|
|
|
|
const MAXIMUM_FILE_UPLOAD_SIZE = 4; // in MB
|
|
const createState = (
|
|
content,
|
|
placeholder,
|
|
// eslint-disable-next-line default-param-last
|
|
plugins = [],
|
|
// eslint-disable-next-line default-param-last
|
|
methods = {},
|
|
enabledMenuOptions
|
|
) => {
|
|
return EditorState.create({
|
|
doc: new ArticleMarkdownTransformer(fullSchema).parse(content),
|
|
plugins: buildEditor({
|
|
schema: fullSchema,
|
|
placeholder,
|
|
methods,
|
|
plugins,
|
|
enabledMenuOptions,
|
|
}),
|
|
});
|
|
};
|
|
|
|
export default {
|
|
mixins: [keyboardEventListenerMixins, uiSettingsMixin, alertMixin],
|
|
props: {
|
|
value: { type: String, default: '' },
|
|
editorId: { type: String, default: '' },
|
|
placeholder: { type: String, default: '' },
|
|
enabledMenuOptions: { type: Array, default: () => [] },
|
|
},
|
|
data() {
|
|
return {
|
|
editorView: null,
|
|
state: undefined,
|
|
plugins: [],
|
|
};
|
|
},
|
|
computed: {
|
|
contentFromEditor() {
|
|
if (this.editorView) {
|
|
return ArticleMarkdownSerializer.serialize(this.editorView.state.doc);
|
|
}
|
|
return '';
|
|
},
|
|
},
|
|
watch: {
|
|
value(newValue = '') {
|
|
if (newValue !== this.contentFromEditor) {
|
|
this.reloadState();
|
|
}
|
|
},
|
|
editorId() {
|
|
this.reloadState();
|
|
},
|
|
},
|
|
created() {
|
|
this.state = createState(
|
|
this.value,
|
|
this.placeholder,
|
|
this.plugins,
|
|
{ onImageUpload: this.openFileBrowser },
|
|
this.enabledMenuOptions
|
|
);
|
|
},
|
|
mounted() {
|
|
this.createEditorView();
|
|
|
|
this.editorView.updateState(this.state);
|
|
this.focusEditorInputField();
|
|
},
|
|
methods: {
|
|
openFileBrowser() {
|
|
this.$refs.imageUploadInput.click();
|
|
},
|
|
onFileChange() {
|
|
const file = this.$refs.imageUploadInput.files[0];
|
|
|
|
if (checkFileSizeLimit(file, MAXIMUM_FILE_UPLOAD_SIZE)) {
|
|
this.uploadImageToStorage(file);
|
|
} else {
|
|
this.showAlert(
|
|
this.$t('HELP_CENTER.ARTICLE_EDITOR.IMAGE_UPLOAD.ERROR_FILE_SIZE', {
|
|
size: MAXIMUM_FILE_UPLOAD_SIZE,
|
|
})
|
|
);
|
|
}
|
|
|
|
this.$refs.imageUploadInput.value = '';
|
|
},
|
|
async uploadImageToStorage(file) {
|
|
try {
|
|
const fileUrl = await this.$store.dispatch('articles/attachImage', {
|
|
portalSlug: this.$route.params.portalSlug,
|
|
file,
|
|
});
|
|
|
|
if (fileUrl) {
|
|
this.onImageUploadStart(fileUrl);
|
|
}
|
|
this.showAlert(
|
|
this.$t('HELP_CENTER.ARTICLE_EDITOR.IMAGE_UPLOAD.SUCCESS')
|
|
);
|
|
} catch (error) {
|
|
this.showAlert(
|
|
this.$t('HELP_CENTER.ARTICLE_EDITOR.IMAGE_UPLOAD.ERROR')
|
|
);
|
|
}
|
|
},
|
|
onImageUploadStart(fileUrl) {
|
|
const { selection } = this.editorView.state;
|
|
const from = selection.from;
|
|
const node = this.editorView.state.schema.nodes.image.create({
|
|
src: fileUrl,
|
|
});
|
|
const paragraphNode = this.editorView.state.schema.node('paragraph');
|
|
if (node) {
|
|
// Insert the image and the caption wrapped inside a paragraph
|
|
const tr = this.editorView.state.tr
|
|
.replaceSelectionWith(paragraphNode)
|
|
.insert(from + 1, node);
|
|
|
|
this.editorView.dispatch(tr.scrollIntoView());
|
|
this.focusEditorInputField();
|
|
}
|
|
},
|
|
reloadState() {
|
|
this.state = createState(
|
|
this.value,
|
|
this.placeholder,
|
|
this.plugins,
|
|
{ onImageUpload: this.openFileBrowser },
|
|
this.enabledMenuOptions
|
|
);
|
|
this.editorView.updateState(this.state);
|
|
this.focusEditorInputField();
|
|
},
|
|
createEditorView() {
|
|
this.editorView = new EditorView(this.$refs.editor, {
|
|
state: this.state,
|
|
dispatchTransaction: tx => {
|
|
this.state = this.state.apply(tx);
|
|
this.emitOnChange();
|
|
},
|
|
handleDOMEvents: {
|
|
keyup: () => {
|
|
this.onKeyup();
|
|
},
|
|
keydown: (view, event) => {
|
|
this.onKeydown(event);
|
|
},
|
|
focus: () => {
|
|
this.onFocus();
|
|
},
|
|
blur: () => {
|
|
this.onBlur();
|
|
},
|
|
},
|
|
});
|
|
},
|
|
|
|
handleKeyEvents() {},
|
|
focusEditorInputField() {
|
|
const { tr } = this.editorView.state;
|
|
const selection = Selection.atEnd(tr.doc);
|
|
|
|
this.editorView.dispatch(tr.setSelection(selection));
|
|
this.editorView.focus();
|
|
},
|
|
|
|
emitOnChange() {
|
|
this.editorView.updateState(this.state);
|
|
|
|
this.$emit('input', this.contentFromEditor);
|
|
},
|
|
|
|
onKeyup() {
|
|
this.$emit('keyup');
|
|
},
|
|
onKeydown() {
|
|
this.$emit('keydown');
|
|
},
|
|
onBlur() {
|
|
this.$emit('blur');
|
|
},
|
|
onFocus() {
|
|
this.$emit('focus');
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
@import '~@chatwoot/prosemirror-schema/src/styles/article.scss';
|
|
|
|
.ProseMirror-menubar-wrapper {
|
|
display: flex;
|
|
flex-direction: column;
|
|
|
|
> .ProseMirror {
|
|
padding: 0;
|
|
word-break: break-word;
|
|
}
|
|
}
|
|
|
|
.editor-root {
|
|
width: 100%;
|
|
}
|
|
|
|
.ProseMirror-woot-style {
|
|
min-height: 5rem;
|
|
max-height: 7.5rem;
|
|
overflow: auto;
|
|
}
|
|
|
|
.ProseMirror-prompt {
|
|
z-index: var(--z-index-highest);
|
|
background: var(--white);
|
|
box-shadow: var(--shadow-large);
|
|
border-radius: var(--border-radius-normal);
|
|
border: 1px solid var(--color-border);
|
|
min-width: 25rem;
|
|
}
|
|
</style>
|