Compare commits
17
Commits
v4.14.1
...
fix/hc-editor
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6928108b7a | ||
|
|
e7e3ed3916 | ||
|
|
b4d589fe24 | ||
|
|
370f890f1f | ||
|
|
a77ec8f5b5 | ||
|
|
450736c4fe | ||
|
|
cb075f794d | ||
|
|
4cc977b9ee | ||
|
|
6222a8d103 | ||
|
|
27d9142edf | ||
|
|
cb9e5806df | ||
|
|
af9762f04e | ||
|
|
ce56e3ed93 | ||
|
|
c50d00b48e | ||
|
|
2655a8c6b4 | ||
|
|
346d7a0375 | ||
|
|
e0449451f9 |
+19
-38
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { computed, ref, watch, onMounted } from 'vue';
|
||||
import { debounce } from '@chatwoot/utils';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { ARTICLE_EDITOR_MENU_OPTIONS } from 'dashboard/constants/editor';
|
||||
@@ -27,7 +27,6 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits([
|
||||
'saveArticle',
|
||||
'saveArticleAsync',
|
||||
'goBack',
|
||||
'setAuthor',
|
||||
'setCategory',
|
||||
@@ -35,52 +34,32 @@ const emit = defineEmits([
|
||||
]);
|
||||
|
||||
const { t } = useI18n();
|
||||
const articleTitle = ref('');
|
||||
|
||||
const isNewArticle = computed(() => !props.article?.id);
|
||||
|
||||
const saveAndSync = value => {
|
||||
const saveArticle = value => {
|
||||
emit('saveArticle', value);
|
||||
};
|
||||
|
||||
// this will only send the data to the backend
|
||||
// but will not update the local state preventing unnecessary re-renders
|
||||
// since the data is already saved and we keep the editor text as the source of truth
|
||||
const quickSave = debounce(
|
||||
value => emit('saveArticleAsync', value),
|
||||
400,
|
||||
false
|
||||
);
|
||||
|
||||
// 2.5 seconds is enough to know that the user has stopped typing and is taking a pause
|
||||
// so we can save the data to the backend and retrieve the updated data
|
||||
// this will update the local state with response data
|
||||
// Only use to save for existing articles
|
||||
const saveAndSyncDebounced = debounce(saveAndSync, 2500, false);
|
||||
|
||||
// Debounced save for new articles
|
||||
const quickSaveNewArticle = debounce(saveAndSync, 400, false);
|
||||
|
||||
const handleSave = value => {
|
||||
if (isNewArticle.value) {
|
||||
quickSaveNewArticle(value);
|
||||
} else {
|
||||
quickSave(value);
|
||||
saveAndSyncDebounced(value);
|
||||
}
|
||||
const saveContent = debounce(content => saveArticle({ content }), 200, false);
|
||||
const saveTitle = () => {
|
||||
if (articleTitle.value) saveArticle({ title: articleTitle.value });
|
||||
};
|
||||
|
||||
const articleTitle = computed({
|
||||
get: () => props.article.title,
|
||||
set: value => {
|
||||
handleSave({ title: value });
|
||||
watch(
|
||||
() => props.article,
|
||||
() => {
|
||||
articleTitle.value = props.article.title;
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
articleTitle.value = props.article.title;
|
||||
});
|
||||
|
||||
const articleContent = computed({
|
||||
get: () => props.article.content,
|
||||
set: content => {
|
||||
handleSave({ content });
|
||||
},
|
||||
set: saveContent,
|
||||
});
|
||||
|
||||
const onClickGoBack = () => {
|
||||
@@ -122,10 +101,11 @@ const previewArticle = () => {
|
||||
custom-text-area-wrapper-class="border-0 !bg-transparent dark:!bg-transparent !py-0 !px-0"
|
||||
placeholder="Title"
|
||||
autofocus
|
||||
@blur="saveTitle"
|
||||
/>
|
||||
<ArticleEditorControls
|
||||
:article="article"
|
||||
@save-article="saveAndSync"
|
||||
@save-article="saveArticle"
|
||||
@set-author="setAuthorId"
|
||||
@set-category="setCategoryId"
|
||||
/>
|
||||
@@ -138,6 +118,7 @@ const previewArticle = () => {
|
||||
"
|
||||
:enabled-menu-options="ARTICLE_EDITOR_MENU_OPTIONS"
|
||||
:autofocus="false"
|
||||
@blur="saveContent"
|
||||
/>
|
||||
</template>
|
||||
</HelpCenterLayout>
|
||||
|
||||
@@ -32,7 +32,7 @@ const props = defineProps({
|
||||
allowSignature: { type: Boolean, default: false }, // allowSignature is a kill switch, ensuring no signature methods are triggered except when this flag is true
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
const emit = defineEmits(['update:modelValue', 'blur']);
|
||||
|
||||
const textareaRef = ref(null);
|
||||
const isFocused = ref(false);
|
||||
@@ -106,6 +106,7 @@ const handleBlur = () => {
|
||||
if (!props.disabled) {
|
||||
isFocused.value = false;
|
||||
}
|
||||
emit('blur');
|
||||
};
|
||||
|
||||
// Watch for changes in modelValue to adjust height
|
||||
|
||||
@@ -67,10 +67,11 @@ export default {
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
modelValue(newValue = '') {
|
||||
if (newValue !== this.contentFromEditor()) {
|
||||
modelValue: {
|
||||
handler() {
|
||||
this.reloadState();
|
||||
}
|
||||
},
|
||||
once: true,
|
||||
},
|
||||
editorId() {
|
||||
this.reloadState();
|
||||
@@ -176,6 +177,7 @@ export default {
|
||||
{ onImageUpload: this.openFileBrowser },
|
||||
this.enabledMenuOptions
|
||||
);
|
||||
|
||||
editorView.updateState(state);
|
||||
this.focusEditorInputField();
|
||||
},
|
||||
@@ -229,6 +231,7 @@ export default {
|
||||
this.$emit('keydown');
|
||||
},
|
||||
onBlur() {
|
||||
this.$emit('update:modelValue', this.contentFromEditor());
|
||||
this.$emit('blur');
|
||||
},
|
||||
onFocus() {
|
||||
|
||||
+2
-8
@@ -40,11 +40,10 @@ const articleLink = computed(() => {
|
||||
);
|
||||
});
|
||||
|
||||
const saveArticle = async ({ ...values }, isAsync = false) => {
|
||||
const actionToDispatch = isAsync ? 'articles/updateAsync' : 'articles/update';
|
||||
const saveArticle = async ({ ...values }) => {
|
||||
isUpdating.value = true;
|
||||
try {
|
||||
await store.dispatch(actionToDispatch, {
|
||||
await store.dispatch('articles/update', {
|
||||
portalSlug,
|
||||
articleId: articleSlug,
|
||||
...values,
|
||||
@@ -62,10 +61,6 @@ const saveArticle = async ({ ...values }, isAsync = false) => {
|
||||
}
|
||||
};
|
||||
|
||||
const saveArticleAsync = async ({ ...values }) => {
|
||||
saveArticle({ ...values }, true);
|
||||
};
|
||||
|
||||
const isCategoryArticles = computed(() => {
|
||||
return (
|
||||
route.name === 'portals_categories_articles_index' ||
|
||||
@@ -112,7 +107,6 @@ onMounted(fetchArticleDetails);
|
||||
:is-updating="isUpdating"
|
||||
:is-saved="isSaved"
|
||||
@save-article="saveArticle"
|
||||
@save-article-async="saveArticleAsync"
|
||||
@preview-article="previewArticle"
|
||||
@go-back="goBackToArticles"
|
||||
/>
|
||||
|
||||
@@ -69,25 +69,6 @@ export const actions = {
|
||||
}
|
||||
},
|
||||
|
||||
updateAsync: async ({ commit }, { portalSlug, articleId, ...articleObj }) => {
|
||||
commit(types.UPDATE_ARTICLE_FLAG, {
|
||||
uiFlags: { isUpdating: true },
|
||||
articleId,
|
||||
});
|
||||
|
||||
try {
|
||||
await articlesAPI.updateArticle({ portalSlug, articleId, articleObj });
|
||||
return articleId;
|
||||
} catch (error) {
|
||||
return throwErrorMessage(error);
|
||||
} finally {
|
||||
commit(types.UPDATE_ARTICLE_FLAG, {
|
||||
uiFlags: { isUpdating: false },
|
||||
articleId,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
update: async ({ commit }, { portalSlug, articleId, ...articleObj }) => {
|
||||
commit(types.UPDATE_ARTICLE_FLAG, {
|
||||
uiFlags: {
|
||||
|
||||
@@ -58,7 +58,6 @@ class Article < ApplicationRecord
|
||||
validates :account_id, presence: true
|
||||
validates :author_id, presence: true
|
||||
validates :title, presence: true
|
||||
validates :content, presence: true
|
||||
|
||||
# ensuring that the position is always set correctly
|
||||
before_create :add_position_to_article
|
||||
|
||||
Reference in New Issue
Block a user