Compare commits

...
Author SHA1 Message Date
Shivam MishraandGitHub 6928108b7a Merge branch 'develop' into fix/hc-editor 2025-10-13 12:30:50 +05:30
Muhsin KelothandGitHub e7e3ed3916 Merge branch 'develop' into fix/hc-editor 2025-09-22 16:38:21 +05:30
Shivam MishraandGitHub b4d589fe24 Merge branch 'develop' into fix/hc-editor 2025-08-14 17:15:05 +05:30
Muhsin KelothandGitHub 370f890f1f Merge branch 'develop' into fix/hc-editor 2025-08-14 14:33:53 +05:30
Muhsin KelothandGitHub a77ec8f5b5 Merge branch 'develop' into fix/hc-editor 2025-08-14 13:56:49 +05:30
Shivam MishraandGitHub 450736c4fe Merge branch 'develop' into fix/hc-editor 2025-02-10 20:02:49 +05:30
Shivam MishraandGitHub cb075f794d Merge branch 'develop' into fix/hc-editor 2025-01-31 13:53:53 +05:30
Shivam Mishra 4cc977b9ee fix: focus 2025-01-30 14:22:12 +05:30
Shivam Mishra 6222a8d103 feat: remove cursor manipulation
not needed anymore
2025-01-30 14:20:45 +05:30
Shivam MishraandGitHub 27d9142edf Merge branch 'develop' into fix/hc-editor 2025-01-30 14:20:31 +05:30
Shivam MishraandGitHub cb9e5806df Merge branch 'develop' into fix/hc-editor 2025-01-29 16:39:33 +05:30
Shivam Mishra af9762f04e feat: it's okay to just start with a title 2025-01-29 16:37:29 +05:30
Shivam Mishra ce56e3ed93 feat: update content one last time on blur 2025-01-29 16:37:16 +05:30
Shivam Mishra c50d00b48e feat: update title only on blur 2025-01-29 16:37:04 +05:30
Shivam Mishra 2655a8c6b4 feat: remove async saving 2025-01-29 15:59:59 +05:30
Shivam Mishra 346d7a0375 fix: model reload only once 2025-01-29 15:58:50 +05:30
Shivam Mishra e0449451f9 fix: always save 2025-01-29 15:00:25 +05:30
6 changed files with 29 additions and 70 deletions
@@ -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() {
@@ -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: {
-1
View File
@@ -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