feat: Adds the ability to edit article (#5232)
This commit is contained in:
parent
b5e497a6a2
commit
b71291619c
19 changed files with 326 additions and 130 deletions
|
@ -42,7 +42,8 @@ class Api::V1::Accounts::ArticlesController < Api::V1::Accounts::BaseController
|
|||
|
||||
def article_params
|
||||
params.require(:article).permit(
|
||||
:title, :content, :description, :position, :category_id, :author_id, :associated_article_id, :status
|
||||
:title, :content, :description, :position, :category_id, :author_id, :associated_article_id, :status, meta: [:title, :description,
|
||||
{ tags: [] }]
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -21,6 +21,17 @@ class ArticlesAPI extends PortalsAPI {
|
|||
if (category_slug) baseUrl += `&category_slug=${category_slug}`;
|
||||
return axios.get(baseUrl);
|
||||
}
|
||||
|
||||
getArticle({ id, portalSlug }) {
|
||||
return axios.get(`${this.url}/${portalSlug}/articles/${id}`);
|
||||
}
|
||||
|
||||
updateArticle({ portalSlug, articleId, articleObj }) {
|
||||
return axios.patch(
|
||||
`${this.url}/${portalSlug}/articles/${articleId}`,
|
||||
articleObj
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default new ArticlesAPI();
|
||||
|
|
|
@ -26,4 +26,30 @@ describe('#PortalAPI', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
describeWithAPIMock('API calls', context => {
|
||||
it('#getArticle', () => {
|
||||
articlesAPI.getArticle({
|
||||
id: 1,
|
||||
portalSlug: 'room-rental',
|
||||
});
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v1/portals/room-rental/articles/1'
|
||||
);
|
||||
});
|
||||
});
|
||||
describeWithAPIMock('API calls', context => {
|
||||
it('#updateArticle', () => {
|
||||
articlesAPI.updateArticle({
|
||||
articleId: 1,
|
||||
portalSlug: 'room-rental',
|
||||
articleObj: { title: 'Update shipping address' },
|
||||
});
|
||||
expect(context.axiosMock.patch).toHaveBeenCalledWith(
|
||||
'/api/v1/portals/room-rental/articles/1',
|
||||
{
|
||||
title: 'Update shipping address',
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -18,13 +18,14 @@
|
|||
}
|
||||
},
|
||||
"EDIT_HEADER": {
|
||||
"ALL_ARTICLES": "All Articles",
|
||||
"PUBLISH_BUTTON": "Publish",
|
||||
"PREVIEW": "Preview",
|
||||
"ADD_TRANSLATION": "Add translation",
|
||||
"OPEN_SIDEBAR": "Open sidebar",
|
||||
"CLOSE_SIDEBAR": "Close sidebar",
|
||||
"SAVING": "Draft saving...",
|
||||
"SAVED": "Draft saved"
|
||||
"SAVING": "Saving...",
|
||||
"SAVED": "Saved"
|
||||
},
|
||||
"ARTICLE_SETTINGS": {
|
||||
"TITLE": "Article Settings",
|
||||
|
@ -175,8 +176,12 @@
|
|||
}
|
||||
},
|
||||
"EDIT_ARTICLE": {
|
||||
"LOADING": "Loading article...",
|
||||
"TITLE_PLACEHOLDER": "Article title goes here",
|
||||
"CONTENT_PLACEHOLDER": "Write your article here"
|
||||
"CONTENT_PLACEHOLDER": "Write your article here",
|
||||
"API": {
|
||||
"ERROR": "Error while saving article"
|
||||
}
|
||||
},
|
||||
"SIDEBAR": {
|
||||
"SEARCH": {
|
||||
|
|
|
@ -25,7 +25,9 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { debounce } from '@chatwoot/utils';
|
||||
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
WootMessageEditor,
|
||||
|
@ -49,6 +51,13 @@ export default {
|
|||
mounted() {
|
||||
this.articleTitle = this.article.title;
|
||||
this.articleContent = this.article.content;
|
||||
this.saveArticle = debounce(
|
||||
values => {
|
||||
this.$emit('save-article', values);
|
||||
},
|
||||
300,
|
||||
false
|
||||
);
|
||||
},
|
||||
methods: {
|
||||
onFocus() {
|
||||
|
@ -58,10 +67,10 @@ export default {
|
|||
this.$emit('blur');
|
||||
},
|
||||
onTitleInput() {
|
||||
this.$emit('titleInput', this.articleTitle);
|
||||
this.saveArticle({ title: this.articleTitle });
|
||||
},
|
||||
onContentInput() {
|
||||
this.$emit('contentInput', this.articleContent);
|
||||
this.saveArticle({ content: this.articleContent });
|
||||
},
|
||||
},
|
||||
};
|
|
@ -6,6 +6,7 @@
|
|||
</div>
|
||||
<div class="header-right--wrap">
|
||||
<woot-button
|
||||
v-if="shouldShowSettings"
|
||||
class-names="article--buttons"
|
||||
icon="filter"
|
||||
color-scheme="secondary"
|
||||
|
@ -16,6 +17,7 @@
|
|||
{{ $t('HELP_CENTER.HEADER.FILTER') }}
|
||||
</woot-button>
|
||||
<woot-button
|
||||
v-if="shouldShowSettings"
|
||||
class-names="article--buttons"
|
||||
icon="arrow-sort"
|
||||
color-scheme="secondary"
|
||||
|
@ -68,6 +70,7 @@
|
|||
</woot-dropdown-menu>
|
||||
</div>
|
||||
<woot-button
|
||||
v-if="shouldShowSettings"
|
||||
v-tooltip.top-end="$t('HELP_CENTER.HEADER.SETTINGS_BUTTON')"
|
||||
icon="settings"
|
||||
class-names="article--buttons"
|
||||
|
@ -113,6 +116,10 @@ export default {
|
|||
type: String,
|
||||
default: '',
|
||||
},
|
||||
shouldShowSettings: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
@ -12,9 +12,10 @@
|
|||
</woot-button>
|
||||
</div>
|
||||
<div class="header-right--wrap">
|
||||
<span v-if="showDraftStatus" class="draft-status">
|
||||
{{ draftStatusText }}
|
||||
<span v-if="isUpdating || isSaved" class="draft-status">
|
||||
{{ statusText }}
|
||||
</span>
|
||||
|
||||
<woot-button
|
||||
class-names="article--buttons"
|
||||
icon="globe"
|
||||
|
@ -73,9 +74,13 @@ export default {
|
|||
type: String,
|
||||
default: '',
|
||||
},
|
||||
draftState: {
|
||||
type: String,
|
||||
default: '',
|
||||
isUpdating: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isSaved: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
|
@ -84,20 +89,10 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
isDraftStatusSavingOrSaved() {
|
||||
return this.draftState === 'saving' || 'saved';
|
||||
},
|
||||
draftStatusText() {
|
||||
if (this.draftState === 'saving') {
|
||||
return this.$t('HELP_CENTER.EDIT_HEADER.SAVING');
|
||||
}
|
||||
if (this.draftState === 'saved') {
|
||||
return this.$t('HELP_CENTER.EDIT_HEADER.SAVED');
|
||||
}
|
||||
return '';
|
||||
},
|
||||
showDraftStatus() {
|
||||
return this.isDraftStatusSavingOrSaved;
|
||||
statusText() {
|
||||
return this.isUpdating
|
||||
? this.$t('HELP_CENTER.EDIT_HEADER.SAVING')
|
||||
: this.$t('HELP_CENTER.EDIT_HEADER.SAVED');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
@ -150,5 +145,14 @@ export default {
|
|||
color: var(--s-400);
|
||||
align-items: center;
|
||||
font-size: var(--font-size-mini);
|
||||
animation: fadeIn 1s;
|
||||
@keyframes fadeIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -186,6 +186,7 @@ export default {
|
|||
portalSlug: this.selectedPortalSlug,
|
||||
});
|
||||
});
|
||||
this.$store.dispatch('agents/get');
|
||||
},
|
||||
toggleKeyShortcutModal() {
|
||||
this.showShortcutModal = true;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { action } from '@storybook/addon-actions';
|
||||
import EditArticle from './EditArticle.vue';
|
||||
import ArticleEditor from './ArticleEditor.vue';
|
||||
|
||||
export default {
|
||||
title: 'Components/Help Center',
|
||||
component: EditArticle,
|
||||
component: ArticleEditor,
|
||||
argTypes: {
|
||||
article: {
|
||||
defaultValue: {},
|
||||
|
@ -16,9 +16,9 @@ export default {
|
|||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { EditArticle },
|
||||
components: { ArticleEditor },
|
||||
template:
|
||||
'<edit-article v-bind="$props" @focus="onFocus" @blur="onBlur"></edit-article>',
|
||||
'<article-editor v-bind="$props" @focus="onFocus" @blur="onBlur"></-article>',
|
||||
});
|
||||
|
||||
export const EditArticleView = Template.bind({});
|
|
@ -8,7 +8,7 @@
|
|||
<label>
|
||||
{{ $t('HELP_CENTER.ARTICLE_SETTINGS.FORM.CATEGORY.LABEL') }}
|
||||
<multiselect-dropdown
|
||||
:options="categoryList"
|
||||
:options="categories"
|
||||
:selected-item="selectedCategory"
|
||||
:has-thumbnail="false"
|
||||
:multiselector-title="
|
||||
|
@ -31,7 +31,7 @@
|
|||
<label>
|
||||
{{ $t('HELP_CENTER.ARTICLE_SETTINGS.FORM.AUTHOR.LABEL') }}
|
||||
<multiselect-dropdown
|
||||
:options="authorList"
|
||||
:options="agents"
|
||||
:selected-item="assignedAuthor"
|
||||
:multiselector-title="
|
||||
$t('HELP_CENTER.ARTICLE_SETTINGS.FORM.AUTHOR.TITLE')
|
||||
|
@ -51,18 +51,19 @@
|
|||
<label>
|
||||
{{ $t('HELP_CENTER.ARTICLE_SETTINGS.FORM.META_TITLE.LABEL') }}
|
||||
<textarea
|
||||
v-model="title"
|
||||
v-model="metaTitle"
|
||||
rows="3"
|
||||
type="text"
|
||||
:placeholder="
|
||||
$t('HELP_CENTER.ARTICLE_SETTINGS.FORM.META_TITLE.PLACEHOLDER')
|
||||
"
|
||||
@input="onChangeMetaInput"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
{{ $t('HELP_CENTER.ARTICLE_SETTINGS.FORM.META_DESCRIPTION.LABEL') }}
|
||||
<textarea
|
||||
v-model="description"
|
||||
v-model="metaDescription"
|
||||
rows="3"
|
||||
type="text"
|
||||
:placeholder="
|
||||
|
@ -70,19 +71,20 @@
|
|||
'HELP_CENTER.ARTICLE_SETTINGS.FORM.META_DESCRIPTION.PLACEHOLDER'
|
||||
)
|
||||
"
|
||||
@input="onChangeMetaInput"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
{{ $t('HELP_CENTER.ARTICLE_SETTINGS.FORM.META_TAGS.LABEL') }}
|
||||
<multiselect
|
||||
ref="tagInput"
|
||||
v-model="values"
|
||||
v-model="metaTags"
|
||||
:placeholder="
|
||||
$t('HELP_CENTER.ARTICLE_SETTINGS.FORM.META_TAGS.PLACEHOLDER')
|
||||
"
|
||||
label="name"
|
||||
:options="metaOptions"
|
||||
track-by="name"
|
||||
:options="options"
|
||||
:multiple="true"
|
||||
:taggable="true"
|
||||
@tag="addTagValue"
|
||||
|
@ -115,60 +117,88 @@
|
|||
|
||||
<script>
|
||||
import MultiselectDropdown from 'shared/components/ui/MultiselectDropdown';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { debounce } from '@chatwoot/utils';
|
||||
import { isEmptyObject } from 'dashboard/helper/commons.js';
|
||||
export default {
|
||||
components: {
|
||||
MultiselectDropdown,
|
||||
},
|
||||
props: {
|
||||
article: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// Dummy value
|
||||
categoryList: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Getting started',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Features',
|
||||
},
|
||||
],
|
||||
selectedCategory: {
|
||||
id: 1,
|
||||
name: 'Features',
|
||||
},
|
||||
authorList: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'John Doe',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Jane Doe',
|
||||
},
|
||||
],
|
||||
assignedAuthor: {
|
||||
id: 1,
|
||||
name: 'John Doe',
|
||||
},
|
||||
title: '',
|
||||
description: '',
|
||||
values: [],
|
||||
options: [],
|
||||
metaTitle: '',
|
||||
metaDescription: '',
|
||||
metaTags: [],
|
||||
metaOptions: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
categories: 'categories/allCategories',
|
||||
agents: 'agents/getAgents',
|
||||
}),
|
||||
assignedAuthor() {
|
||||
return this.article?.author;
|
||||
},
|
||||
selectedCategory() {
|
||||
return this.article?.category;
|
||||
},
|
||||
allTags() {
|
||||
return this.metaTags.map(item => item.name);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (!isEmptyObject(this.article.meta)) {
|
||||
const {
|
||||
meta: { title = '', description = '', tags = [] },
|
||||
} = this.article;
|
||||
this.metaTitle = title;
|
||||
this.metaDescription = description;
|
||||
this.metaTags = this.formattedTags({ tags });
|
||||
}
|
||||
|
||||
this.saveArticle = debounce(
|
||||
() => {
|
||||
this.$emit('save-article', {
|
||||
meta: {
|
||||
title: this.metaTitle,
|
||||
description: this.metaDescription,
|
||||
tags: this.allTags,
|
||||
},
|
||||
});
|
||||
},
|
||||
1000,
|
||||
false
|
||||
);
|
||||
},
|
||||
methods: {
|
||||
formattedTags({ tags }) {
|
||||
return tags.map(tag => ({
|
||||
name: tag,
|
||||
}));
|
||||
},
|
||||
addTagValue(tagValue) {
|
||||
const tag = {
|
||||
name: tagValue,
|
||||
};
|
||||
this.values.push(tag);
|
||||
this.metaTags.push(tag);
|
||||
this.$refs.tagInput.$el.focus();
|
||||
this.saveArticle();
|
||||
},
|
||||
onClickSelectCategory() {
|
||||
this.$emit('select-category');
|
||||
onClickSelectCategory({ id }) {
|
||||
this.$emit('save-article', { category_id: id });
|
||||
},
|
||||
onClickAssignAuthor() {
|
||||
this.$emit('assign-author');
|
||||
onClickAssignAuthor({ id }) {
|
||||
this.$emit('save-article', { author_id: id });
|
||||
},
|
||||
onChangeMetaInput() {
|
||||
this.saveArticle();
|
||||
},
|
||||
onClickArchiveArticle() {
|
||||
this.$emit('archive-article');
|
||||
|
|
|
@ -1,39 +1,131 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<edit-article-header
|
||||
back-button-label="All Articles"
|
||||
draft-state="saved"
|
||||
@back="onClickGoBack"
|
||||
<div class="article-container">
|
||||
<div
|
||||
class="edit-article--container"
|
||||
:class="{ 'is-sidebar-open': showArticleSettings }"
|
||||
>
|
||||
<edit-article-header
|
||||
:back-button-label="$t('HELP_CENTER.HEADER.TITLES.ALL_ARTICLES')"
|
||||
:is-updating="isUpdating"
|
||||
:is-saved="isSaved"
|
||||
@back="onClickGoBack"
|
||||
@open="openArticleSettings"
|
||||
@close="closeArticleSettings"
|
||||
/>
|
||||
<div v-if="isFetching" class="text-center p-normal fs-default h-full">
|
||||
<spinner size="" />
|
||||
<span>{{ $t('HELP_CENTER.EDIT_ARTICLE.LOADING') }}</span>
|
||||
</div>
|
||||
<article-editor
|
||||
v-else
|
||||
:is-settings-sidebar-open="showArticleSettings"
|
||||
:article="article"
|
||||
@save-article="saveArticle"
|
||||
/>
|
||||
</div>
|
||||
<article-settings
|
||||
v-if="showArticleSettings"
|
||||
:article="article"
|
||||
@save-article="saveArticle"
|
||||
/>
|
||||
<edit-article-field :article="article" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import EditArticleHeader from 'dashboard/routes/dashboard/helpcenter/components/Header/EditArticleHeader';
|
||||
import EditArticleField from 'dashboard/components/helpCenter/EditArticle';
|
||||
import { mapGetters } from 'vuex';
|
||||
import EditArticleHeader from '../../components/Header/EditArticleHeader.vue';
|
||||
import ArticleEditor from '../../components/ArticleEditor.vue';
|
||||
import ArticleSettings from './ArticleSettings.vue';
|
||||
import Spinner from 'shared/components/Spinner';
|
||||
import portalMixin from '../../mixins/portalMixin';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
export default {
|
||||
components: {
|
||||
EditArticleHeader,
|
||||
EditArticleField,
|
||||
ArticleEditor,
|
||||
Spinner,
|
||||
ArticleSettings,
|
||||
},
|
||||
props: {
|
||||
article: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
mixins: [portalMixin, alertMixin],
|
||||
data() {
|
||||
return {
|
||||
isUpdating: false,
|
||||
isSaved: false,
|
||||
showArticleSettings: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
isFetching: 'articles/isFetching',
|
||||
articles: 'articles/articles',
|
||||
}),
|
||||
article() {
|
||||
return this.$store.getters['articles/articleById'](this.articleId);
|
||||
},
|
||||
articleId() {
|
||||
return this.$route.params.articleSlug;
|
||||
},
|
||||
selectedPortalSlug() {
|
||||
return this.portalSlug || this.selectedPortal?.slug;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchArticleDetails();
|
||||
},
|
||||
methods: {
|
||||
onClickGoBack() {
|
||||
this.$router.push({ name: 'list_all_locale_articles' });
|
||||
},
|
||||
fetchArticleDetails() {
|
||||
this.$store.dispatch('articles/show', {
|
||||
id: this.articleId,
|
||||
portalSlug: this.selectedPortalSlug,
|
||||
});
|
||||
},
|
||||
async saveArticle({ ...values }) {
|
||||
this.isUpdating = true;
|
||||
try {
|
||||
await this.$store.dispatch('articles/update', {
|
||||
portalSlug: this.selectedPortalSlug,
|
||||
articleId: this.articleId,
|
||||
...values,
|
||||
});
|
||||
} catch (error) {
|
||||
this.alertMessage =
|
||||
error?.message ||
|
||||
this.$t('HELP_CENTER.EDIT_ARTICLE.API.ERROR_MESSAGE');
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
this.isUpdating = false;
|
||||
this.isSaved = true;
|
||||
}, 1500);
|
||||
}
|
||||
},
|
||||
openArticleSettings() {
|
||||
this.showArticleSettings = true;
|
||||
},
|
||||
closeArticleSettings() {
|
||||
this.showArticleSettings = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.container {
|
||||
.article-container {
|
||||
display: flex;
|
||||
padding: var(--space-small) var(--space-normal);
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
overflow: scroll;
|
||||
|
||||
.edit-article--container {
|
||||
flex: 1;
|
||||
flex-shrink: 0;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.is-sidebar-open {
|
||||
flex: 0.7;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,35 +1,21 @@
|
|||
<template>
|
||||
<div class="article-container">
|
||||
<div
|
||||
class="edit-article--container"
|
||||
:class="{ 'is-sidebar-open': showArticleSettings }"
|
||||
>
|
||||
<edit-article-header
|
||||
back-button-label="All Articles"
|
||||
draft-state="saved"
|
||||
@back="onClickGoBack"
|
||||
@open="openArticleSettings"
|
||||
@close="closeArticleSettings"
|
||||
/>
|
||||
<edit-article-field
|
||||
:is-settings-sidebar-open="showArticleSettings"
|
||||
@titleInput="titleInput"
|
||||
@contentInput="contentInput"
|
||||
/>
|
||||
</div>
|
||||
<article-settings v-if="showArticleSettings" />
|
||||
<div class="container">
|
||||
<edit-article-header
|
||||
back-button-label="All Articles"
|
||||
draft-state="saved"
|
||||
@back="onClickGoBack"
|
||||
/>
|
||||
<article-editor @titleInput="titleInput" @contentInput="contentInput" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import EditArticleHeader from 'dashboard/routes/dashboard/helpcenter/components/Header/EditArticleHeader';
|
||||
import EditArticleField from 'dashboard/components/helpCenter/EditArticle';
|
||||
import ArticleSettings from 'dashboard/routes/dashboard/helpcenter/pages/articles/ArticleSettings';
|
||||
import ArticleEditor from '../../components/ArticleEditor.vue';
|
||||
export default {
|
||||
components: {
|
||||
EditArticleHeader,
|
||||
EditArticleField,
|
||||
ArticleSettings,
|
||||
ArticleEditor,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
@ -62,18 +62,24 @@ export const actions = {
|
|||
commit(types.SET_UI_FLAG, { isFetching: false });
|
||||
}
|
||||
},
|
||||
update: async ({ commit }, params) => {
|
||||
const articleId = params.id;
|
||||
update: async ({ commit }, { portalSlug, articleId, ...articleObj }) => {
|
||||
commit(types.ADD_ARTICLE_FLAG, {
|
||||
uiFlags: {
|
||||
isUpdating: true,
|
||||
},
|
||||
articleId,
|
||||
});
|
||||
try {
|
||||
const { data } = await articlesAPI.update(params);
|
||||
|
||||
commit(types.UPDATE_ARTICLE, data);
|
||||
try {
|
||||
const {
|
||||
data: { payload },
|
||||
} = await articlesAPI.updateArticle({
|
||||
portalSlug,
|
||||
articleId,
|
||||
articleObj,
|
||||
});
|
||||
|
||||
commit(types.UPDATE_ARTICLE, payload);
|
||||
|
||||
return articleId;
|
||||
} catch (error) {
|
||||
|
|
|
@ -19,7 +19,7 @@ export const mutations = {
|
|||
[types.CLEAR_ARTICLES]: $state => {
|
||||
Vue.set($state.articles, 'byId', {});
|
||||
Vue.set($state.articles, 'allIds', []);
|
||||
Vue.set($state.articles, 'uiFlags', {});
|
||||
Vue.set($state.articles, 'uiFlags.byId', {});
|
||||
},
|
||||
[types.ADD_MANY_ARTICLES]($state, articles) {
|
||||
const allArticles = { ...$state.articles.byId };
|
||||
|
@ -55,7 +55,6 @@ export const mutations = {
|
|||
},
|
||||
[types.UPDATE_ARTICLE]($state, article) {
|
||||
const articleId = article.id;
|
||||
|
||||
if (!$state.articles.allIds.includes(articleId)) return;
|
||||
|
||||
Vue.set($state.articles.byId, articleId, {
|
||||
|
|
|
@ -89,8 +89,15 @@ describe('#actions', () => {
|
|||
|
||||
describe('#update', () => {
|
||||
it('sends correct actions if API is success', async () => {
|
||||
axios.patch.mockResolvedValue({ data: articleList[0] });
|
||||
await actions.update({ commit }, articleList[0]);
|
||||
axios.patch.mockResolvedValue({ data: { payload: articleList[0] } });
|
||||
await actions.update(
|
||||
{ commit },
|
||||
{
|
||||
portalSlug: 'room-rental',
|
||||
articleId: 1,
|
||||
title: 'Documents are required to complete KYC',
|
||||
}
|
||||
);
|
||||
expect(commit.mock.calls).toEqual([
|
||||
[
|
||||
types.default.ADD_ARTICLE_FLAG,
|
||||
|
@ -105,9 +112,17 @@ describe('#actions', () => {
|
|||
});
|
||||
it('sends correct actions if API is error', async () => {
|
||||
axios.patch.mockRejectedValue({ message: 'Incorrect header' });
|
||||
await expect(actions.update({ commit }, articleList[0])).rejects.toThrow(
|
||||
Error
|
||||
);
|
||||
await expect(
|
||||
actions.update(
|
||||
{ commit },
|
||||
{
|
||||
portalSlug: 'room-rental',
|
||||
articleId: 1,
|
||||
title: 'Documents are required to complete KYC',
|
||||
}
|
||||
)
|
||||
).rejects.toThrow(Error);
|
||||
|
||||
expect(commit.mock.calls).toEqual([
|
||||
[
|
||||
types.default.ADD_ARTICLE_FLAG,
|
||||
|
|
|
@ -106,7 +106,11 @@ describe('#mutations', () => {
|
|||
mutations[types.CLEAR_ARTICLES](state);
|
||||
expect(state.articles.allIds).toEqual([]);
|
||||
expect(state.articles.byId).toEqual({});
|
||||
expect(state.articles.uiFlags).toEqual({});
|
||||
expect(state.articles.uiFlags).toEqual({
|
||||
byId: {
|
||||
'1': { isFetching: false, isUpdating: true, isDeleting: false },
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@ json.description article.description
|
|||
json.status article.status
|
||||
json.account_id article.account_id
|
||||
json.updated_at article.updated_at.to_i
|
||||
|
||||
json.meta article.meta
|
||||
json.category do
|
||||
json.id article.category_id
|
||||
json.name article.category.name
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
"dependencies": {
|
||||
"@braid/vue-formulate": "^2.5.2",
|
||||
"@chatwoot/prosemirror-schema": "https://github.com/chatwoot/prosemirror-schema.git#7e8acadd10d7b932c0dc0bd0a18f804434f83517",
|
||||
"@chatwoot/utils": "^0.0.6",
|
||||
"@chatwoot/utils": "^0.0.10",
|
||||
"@hcaptcha/vue-hcaptcha": "^0.3.2",
|
||||
"@rails/actioncable": "6.1.3",
|
||||
"@rails/webpacker": "5.3.0",
|
||||
|
|
|
@ -1406,10 +1406,10 @@
|
|||
prosemirror-state "^1.3.3"
|
||||
prosemirror-view "^1.17.2"
|
||||
|
||||
"@chatwoot/utils@^0.0.6":
|
||||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@chatwoot/utils/-/utils-0.0.6.tgz#76d7b17d692b5b656c565b9b714b98e0f2bc1324"
|
||||
integrity sha512-fCvULfJSFSylDAiGh1cPAX5nQkVsmG5ASGm/E6YBYg8cox/2JU179JFstdtTxrIJg/YeHukcaq85Gc+/16ShPQ==
|
||||
"@chatwoot/utils@^0.0.10":
|
||||
version "0.0.10"
|
||||
resolved "https://registry.npmjs.org/@chatwoot/utils/-/utils-0.0.10.tgz#59f68cc28d8718b261ebed8b9c94d2c493b6c67f"
|
||||
integrity sha512-Zd+wQTblWKUV1mhcXoabcfoLygx/Ock5pP0JQdfqW64lubhjYaRR4gCutEgqUcQB4nuOUH7MZ7BTzdZm4RoM/g==
|
||||
dependencies:
|
||||
date-fns "^2.22.1"
|
||||
|
||||
|
|
Loading…
Reference in a new issue