Compare commits
17
Commits
| 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>
|
<script setup>
|
||||||
import { computed } from 'vue';
|
import { computed, ref, watch, onMounted } from 'vue';
|
||||||
import { debounce } from '@chatwoot/utils';
|
import { debounce } from '@chatwoot/utils';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { ARTICLE_EDITOR_MENU_OPTIONS } from 'dashboard/constants/editor';
|
import { ARTICLE_EDITOR_MENU_OPTIONS } from 'dashboard/constants/editor';
|
||||||
@@ -27,7 +27,6 @@ const props = defineProps({
|
|||||||
|
|
||||||
const emit = defineEmits([
|
const emit = defineEmits([
|
||||||
'saveArticle',
|
'saveArticle',
|
||||||
'saveArticleAsync',
|
|
||||||
'goBack',
|
'goBack',
|
||||||
'setAuthor',
|
'setAuthor',
|
||||||
'setCategory',
|
'setCategory',
|
||||||
@@ -35,52 +34,32 @@ const emit = defineEmits([
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
const articleTitle = ref('');
|
||||||
|
|
||||||
const isNewArticle = computed(() => !props.article?.id);
|
const saveArticle = value => {
|
||||||
|
|
||||||
const saveAndSync = value => {
|
|
||||||
emit('saveArticle', value);
|
emit('saveArticle', value);
|
||||||
};
|
};
|
||||||
|
|
||||||
// this will only send the data to the backend
|
const saveContent = debounce(content => saveArticle({ content }), 200, false);
|
||||||
// but will not update the local state preventing unnecessary re-renders
|
const saveTitle = () => {
|
||||||
// since the data is already saved and we keep the editor text as the source of truth
|
if (articleTitle.value) saveArticle({ title: articleTitle.value });
|
||||||
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 articleTitle = computed({
|
watch(
|
||||||
get: () => props.article.title,
|
() => props.article,
|
||||||
set: value => {
|
() => {
|
||||||
handleSave({ title: value });
|
articleTitle.value = props.article.title;
|
||||||
},
|
},
|
||||||
|
{ immediate: true, deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
articleTitle.value = props.article.title;
|
||||||
});
|
});
|
||||||
|
|
||||||
const articleContent = computed({
|
const articleContent = computed({
|
||||||
get: () => props.article.content,
|
get: () => props.article.content,
|
||||||
set: content => {
|
set: saveContent,
|
||||||
handleSave({ content });
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const onClickGoBack = () => {
|
const onClickGoBack = () => {
|
||||||
@@ -122,10 +101,11 @@ const previewArticle = () => {
|
|||||||
custom-text-area-wrapper-class="border-0 !bg-transparent dark:!bg-transparent !py-0 !px-0"
|
custom-text-area-wrapper-class="border-0 !bg-transparent dark:!bg-transparent !py-0 !px-0"
|
||||||
placeholder="Title"
|
placeholder="Title"
|
||||||
autofocus
|
autofocus
|
||||||
|
@blur="saveTitle"
|
||||||
/>
|
/>
|
||||||
<ArticleEditorControls
|
<ArticleEditorControls
|
||||||
:article="article"
|
:article="article"
|
||||||
@save-article="saveAndSync"
|
@save-article="saveArticle"
|
||||||
@set-author="setAuthorId"
|
@set-author="setAuthorId"
|
||||||
@set-category="setCategoryId"
|
@set-category="setCategoryId"
|
||||||
/>
|
/>
|
||||||
@@ -138,6 +118,7 @@ const previewArticle = () => {
|
|||||||
"
|
"
|
||||||
:enabled-menu-options="ARTICLE_EDITOR_MENU_OPTIONS"
|
:enabled-menu-options="ARTICLE_EDITOR_MENU_OPTIONS"
|
||||||
:autofocus="false"
|
:autofocus="false"
|
||||||
|
@blur="saveContent"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</HelpCenterLayout>
|
</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
|
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 textareaRef = ref(null);
|
||||||
const isFocused = ref(false);
|
const isFocused = ref(false);
|
||||||
@@ -106,6 +106,7 @@ const handleBlur = () => {
|
|||||||
if (!props.disabled) {
|
if (!props.disabled) {
|
||||||
isFocused.value = false;
|
isFocused.value = false;
|
||||||
}
|
}
|
||||||
|
emit('blur');
|
||||||
};
|
};
|
||||||
|
|
||||||
// Watch for changes in modelValue to adjust height
|
// Watch for changes in modelValue to adjust height
|
||||||
|
|||||||
@@ -67,10 +67,11 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
modelValue(newValue = '') {
|
modelValue: {
|
||||||
if (newValue !== this.contentFromEditor()) {
|
handler() {
|
||||||
this.reloadState();
|
this.reloadState();
|
||||||
}
|
},
|
||||||
|
once: true,
|
||||||
},
|
},
|
||||||
editorId() {
|
editorId() {
|
||||||
this.reloadState();
|
this.reloadState();
|
||||||
@@ -176,6 +177,7 @@ export default {
|
|||||||
{ onImageUpload: this.openFileBrowser },
|
{ onImageUpload: this.openFileBrowser },
|
||||||
this.enabledMenuOptions
|
this.enabledMenuOptions
|
||||||
);
|
);
|
||||||
|
|
||||||
editorView.updateState(state);
|
editorView.updateState(state);
|
||||||
this.focusEditorInputField();
|
this.focusEditorInputField();
|
||||||
},
|
},
|
||||||
@@ -229,6 +231,7 @@ export default {
|
|||||||
this.$emit('keydown');
|
this.$emit('keydown');
|
||||||
},
|
},
|
||||||
onBlur() {
|
onBlur() {
|
||||||
|
this.$emit('update:modelValue', this.contentFromEditor());
|
||||||
this.$emit('blur');
|
this.$emit('blur');
|
||||||
},
|
},
|
||||||
onFocus() {
|
onFocus() {
|
||||||
|
|||||||
+2
-8
@@ -40,11 +40,10 @@ const articleLink = computed(() => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const saveArticle = async ({ ...values }, isAsync = false) => {
|
const saveArticle = async ({ ...values }) => {
|
||||||
const actionToDispatch = isAsync ? 'articles/updateAsync' : 'articles/update';
|
|
||||||
isUpdating.value = true;
|
isUpdating.value = true;
|
||||||
try {
|
try {
|
||||||
await store.dispatch(actionToDispatch, {
|
await store.dispatch('articles/update', {
|
||||||
portalSlug,
|
portalSlug,
|
||||||
articleId: articleSlug,
|
articleId: articleSlug,
|
||||||
...values,
|
...values,
|
||||||
@@ -62,10 +61,6 @@ const saveArticle = async ({ ...values }, isAsync = false) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveArticleAsync = async ({ ...values }) => {
|
|
||||||
saveArticle({ ...values }, true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const isCategoryArticles = computed(() => {
|
const isCategoryArticles = computed(() => {
|
||||||
return (
|
return (
|
||||||
route.name === 'portals_categories_articles_index' ||
|
route.name === 'portals_categories_articles_index' ||
|
||||||
@@ -112,7 +107,6 @@ onMounted(fetchArticleDetails);
|
|||||||
:is-updating="isUpdating"
|
:is-updating="isUpdating"
|
||||||
:is-saved="isSaved"
|
:is-saved="isSaved"
|
||||||
@save-article="saveArticle"
|
@save-article="saveArticle"
|
||||||
@save-article-async="saveArticleAsync"
|
|
||||||
@preview-article="previewArticle"
|
@preview-article="previewArticle"
|
||||||
@go-back="goBackToArticles"
|
@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 }) => {
|
update: async ({ commit }, { portalSlug, articleId, ...articleObj }) => {
|
||||||
commit(types.UPDATE_ARTICLE_FLAG, {
|
commit(types.UPDATE_ARTICLE_FLAG, {
|
||||||
uiFlags: {
|
uiFlags: {
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ class Article < ApplicationRecord
|
|||||||
validates :account_id, presence: true
|
validates :account_id, presence: true
|
||||||
validates :author_id, presence: true
|
validates :author_id, presence: true
|
||||||
validates :title, presence: true
|
validates :title, presence: true
|
||||||
validates :content, presence: true
|
|
||||||
|
|
||||||
# ensuring that the position is always set correctly
|
# ensuring that the position is always set correctly
|
||||||
before_create :add_position_to_article
|
before_create :add_position_to_article
|
||||||
|
|||||||
Reference in New Issue
Block a user