feat: Adds the ability to edit article (#5232)

This commit is contained in:
Muhsin Keloth 2022-08-16 17:55:34 +05:30 committed by GitHub
parent b5e497a6a2
commit b71291619c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 326 additions and 130 deletions

View file

@ -42,7 +42,8 @@ class Api::V1::Accounts::ArticlesController < Api::V1::Accounts::BaseController
def article_params def article_params
params.require(:article).permit( 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 end

View file

@ -21,6 +21,17 @@ class ArticlesAPI extends PortalsAPI {
if (category_slug) baseUrl += `&category_slug=${category_slug}`; if (category_slug) baseUrl += `&category_slug=${category_slug}`;
return axios.get(baseUrl); 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(); export default new ArticlesAPI();

View file

@ -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',
}
);
});
});
}); });

View file

@ -18,13 +18,14 @@
} }
}, },
"EDIT_HEADER": { "EDIT_HEADER": {
"ALL_ARTICLES": "All Articles",
"PUBLISH_BUTTON": "Publish", "PUBLISH_BUTTON": "Publish",
"PREVIEW": "Preview", "PREVIEW": "Preview",
"ADD_TRANSLATION": "Add translation", "ADD_TRANSLATION": "Add translation",
"OPEN_SIDEBAR": "Open sidebar", "OPEN_SIDEBAR": "Open sidebar",
"CLOSE_SIDEBAR": "Close sidebar", "CLOSE_SIDEBAR": "Close sidebar",
"SAVING": "Draft saving...", "SAVING": "Saving...",
"SAVED": "Draft saved" "SAVED": "Saved"
}, },
"ARTICLE_SETTINGS": { "ARTICLE_SETTINGS": {
"TITLE": "Article Settings", "TITLE": "Article Settings",
@ -175,8 +176,12 @@
} }
}, },
"EDIT_ARTICLE": { "EDIT_ARTICLE": {
"LOADING": "Loading article...",
"TITLE_PLACEHOLDER": "Article title goes here", "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": { "SIDEBAR": {
"SEARCH": { "SEARCH": {

View file

@ -25,7 +25,9 @@
</template> </template>
<script> <script>
import { debounce } from '@chatwoot/utils';
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vue'; import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vue';
export default { export default {
components: { components: {
WootMessageEditor, WootMessageEditor,
@ -49,6 +51,13 @@ export default {
mounted() { mounted() {
this.articleTitle = this.article.title; this.articleTitle = this.article.title;
this.articleContent = this.article.content; this.articleContent = this.article.content;
this.saveArticle = debounce(
values => {
this.$emit('save-article', values);
},
300,
false
);
}, },
methods: { methods: {
onFocus() { onFocus() {
@ -58,10 +67,10 @@ export default {
this.$emit('blur'); this.$emit('blur');
}, },
onTitleInput() { onTitleInput() {
this.$emit('titleInput', this.articleTitle); this.saveArticle({ title: this.articleTitle });
}, },
onContentInput() { onContentInput() {
this.$emit('contentInput', this.articleContent); this.saveArticle({ content: this.articleContent });
}, },
}, },
}; };

View file

@ -6,6 +6,7 @@
</div> </div>
<div class="header-right--wrap"> <div class="header-right--wrap">
<woot-button <woot-button
v-if="shouldShowSettings"
class-names="article--buttons" class-names="article--buttons"
icon="filter" icon="filter"
color-scheme="secondary" color-scheme="secondary"
@ -16,6 +17,7 @@
{{ $t('HELP_CENTER.HEADER.FILTER') }} {{ $t('HELP_CENTER.HEADER.FILTER') }}
</woot-button> </woot-button>
<woot-button <woot-button
v-if="shouldShowSettings"
class-names="article--buttons" class-names="article--buttons"
icon="arrow-sort" icon="arrow-sort"
color-scheme="secondary" color-scheme="secondary"
@ -68,6 +70,7 @@
</woot-dropdown-menu> </woot-dropdown-menu>
</div> </div>
<woot-button <woot-button
v-if="shouldShowSettings"
v-tooltip.top-end="$t('HELP_CENTER.HEADER.SETTINGS_BUTTON')" v-tooltip.top-end="$t('HELP_CENTER.HEADER.SETTINGS_BUTTON')"
icon="settings" icon="settings"
class-names="article--buttons" class-names="article--buttons"
@ -113,6 +116,10 @@ export default {
type: String, type: String,
default: '', default: '',
}, },
shouldShowSettings: {
type: Boolean,
default: false,
},
}, },
data() { data() {
return { return {

View file

@ -12,9 +12,10 @@
</woot-button> </woot-button>
</div> </div>
<div class="header-right--wrap"> <div class="header-right--wrap">
<span v-if="showDraftStatus" class="draft-status"> <span v-if="isUpdating || isSaved" class="draft-status">
{{ draftStatusText }} {{ statusText }}
</span> </span>
<woot-button <woot-button
class-names="article--buttons" class-names="article--buttons"
icon="globe" icon="globe"
@ -73,9 +74,13 @@ export default {
type: String, type: String,
default: '', default: '',
}, },
draftState: { isUpdating: {
type: String, type: Boolean,
default: '', default: false,
},
isSaved: {
type: Boolean,
default: false,
}, },
}, },
data() { data() {
@ -84,20 +89,10 @@ export default {
}; };
}, },
computed: { computed: {
isDraftStatusSavingOrSaved() { statusText() {
return this.draftState === 'saving' || 'saved'; return this.isUpdating
}, ? this.$t('HELP_CENTER.EDIT_HEADER.SAVING')
draftStatusText() { : this.$t('HELP_CENTER.EDIT_HEADER.SAVED');
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;
}, },
}, },
methods: { methods: {
@ -150,5 +145,14 @@ export default {
color: var(--s-400); color: var(--s-400);
align-items: center; align-items: center;
font-size: var(--font-size-mini); font-size: var(--font-size-mini);
animation: fadeIn 1s;
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
} }
</style> </style>

View file

@ -186,6 +186,7 @@ export default {
portalSlug: this.selectedPortalSlug, portalSlug: this.selectedPortalSlug,
}); });
}); });
this.$store.dispatch('agents/get');
}, },
toggleKeyShortcutModal() { toggleKeyShortcutModal() {
this.showShortcutModal = true; this.showShortcutModal = true;

View file

@ -1,9 +1,9 @@
import { action } from '@storybook/addon-actions'; import { action } from '@storybook/addon-actions';
import EditArticle from './EditArticle.vue'; import ArticleEditor from './ArticleEditor.vue';
export default { export default {
title: 'Components/Help Center', title: 'Components/Help Center',
component: EditArticle, component: ArticleEditor,
argTypes: { argTypes: {
article: { article: {
defaultValue: {}, defaultValue: {},
@ -16,9 +16,9 @@ export default {
const Template = (args, { argTypes }) => ({ const Template = (args, { argTypes }) => ({
props: Object.keys(argTypes), props: Object.keys(argTypes),
components: { EditArticle }, components: { ArticleEditor },
template: 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({}); export const EditArticleView = Template.bind({});

View file

@ -8,7 +8,7 @@
<label> <label>
{{ $t('HELP_CENTER.ARTICLE_SETTINGS.FORM.CATEGORY.LABEL') }} {{ $t('HELP_CENTER.ARTICLE_SETTINGS.FORM.CATEGORY.LABEL') }}
<multiselect-dropdown <multiselect-dropdown
:options="categoryList" :options="categories"
:selected-item="selectedCategory" :selected-item="selectedCategory"
:has-thumbnail="false" :has-thumbnail="false"
:multiselector-title=" :multiselector-title="
@ -31,7 +31,7 @@
<label> <label>
{{ $t('HELP_CENTER.ARTICLE_SETTINGS.FORM.AUTHOR.LABEL') }} {{ $t('HELP_CENTER.ARTICLE_SETTINGS.FORM.AUTHOR.LABEL') }}
<multiselect-dropdown <multiselect-dropdown
:options="authorList" :options="agents"
:selected-item="assignedAuthor" :selected-item="assignedAuthor"
:multiselector-title=" :multiselector-title="
$t('HELP_CENTER.ARTICLE_SETTINGS.FORM.AUTHOR.TITLE') $t('HELP_CENTER.ARTICLE_SETTINGS.FORM.AUTHOR.TITLE')
@ -51,18 +51,19 @@
<label> <label>
{{ $t('HELP_CENTER.ARTICLE_SETTINGS.FORM.META_TITLE.LABEL') }} {{ $t('HELP_CENTER.ARTICLE_SETTINGS.FORM.META_TITLE.LABEL') }}
<textarea <textarea
v-model="title" v-model="metaTitle"
rows="3" rows="3"
type="text" type="text"
:placeholder=" :placeholder="
$t('HELP_CENTER.ARTICLE_SETTINGS.FORM.META_TITLE.PLACEHOLDER') $t('HELP_CENTER.ARTICLE_SETTINGS.FORM.META_TITLE.PLACEHOLDER')
" "
@input="onChangeMetaInput"
/> />
</label> </label>
<label> <label>
{{ $t('HELP_CENTER.ARTICLE_SETTINGS.FORM.META_DESCRIPTION.LABEL') }} {{ $t('HELP_CENTER.ARTICLE_SETTINGS.FORM.META_DESCRIPTION.LABEL') }}
<textarea <textarea
v-model="description" v-model="metaDescription"
rows="3" rows="3"
type="text" type="text"
:placeholder=" :placeholder="
@ -70,19 +71,20 @@
'HELP_CENTER.ARTICLE_SETTINGS.FORM.META_DESCRIPTION.PLACEHOLDER' 'HELP_CENTER.ARTICLE_SETTINGS.FORM.META_DESCRIPTION.PLACEHOLDER'
) )
" "
@input="onChangeMetaInput"
/> />
</label> </label>
<label> <label>
{{ $t('HELP_CENTER.ARTICLE_SETTINGS.FORM.META_TAGS.LABEL') }} {{ $t('HELP_CENTER.ARTICLE_SETTINGS.FORM.META_TAGS.LABEL') }}
<multiselect <multiselect
ref="tagInput" ref="tagInput"
v-model="values" v-model="metaTags"
:placeholder=" :placeholder="
$t('HELP_CENTER.ARTICLE_SETTINGS.FORM.META_TAGS.PLACEHOLDER') $t('HELP_CENTER.ARTICLE_SETTINGS.FORM.META_TAGS.PLACEHOLDER')
" "
label="name" label="name"
:options="metaOptions"
track-by="name" track-by="name"
:options="options"
:multiple="true" :multiple="true"
:taggable="true" :taggable="true"
@tag="addTagValue" @tag="addTagValue"
@ -115,60 +117,88 @@
<script> <script>
import MultiselectDropdown from 'shared/components/ui/MultiselectDropdown'; 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 { export default {
components: { components: {
MultiselectDropdown, MultiselectDropdown,
}, },
props: {
article: {
type: Object,
required: true,
},
},
data() { data() {
return { return {
// Dummy value metaTitle: '',
categoryList: [ metaDescription: '',
{ metaTags: [],
id: 1, metaOptions: [],
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: [],
}; };
}, },
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: { methods: {
formattedTags({ tags }) {
return tags.map(tag => ({
name: tag,
}));
},
addTagValue(tagValue) { addTagValue(tagValue) {
const tag = { const tag = {
name: tagValue, name: tagValue,
}; };
this.values.push(tag); this.metaTags.push(tag);
this.$refs.tagInput.$el.focus(); this.$refs.tagInput.$el.focus();
this.saveArticle();
}, },
onClickSelectCategory() { onClickSelectCategory({ id }) {
this.$emit('select-category'); this.$emit('save-article', { category_id: id });
}, },
onClickAssignAuthor() { onClickAssignAuthor({ id }) {
this.$emit('assign-author'); this.$emit('save-article', { author_id: id });
},
onChangeMetaInput() {
this.saveArticle();
}, },
onClickArchiveArticle() { onClickArchiveArticle() {
this.$emit('archive-article'); this.$emit('archive-article');

View file

@ -1,39 +1,131 @@
<template> <template>
<div class="container"> <div class="article-container">
<edit-article-header <div
back-button-label="All Articles" class="edit-article--container"
draft-state="saved" :class="{ 'is-sidebar-open': showArticleSettings }"
@back="onClickGoBack" >
<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> </div>
</template> </template>
<script> <script>
import EditArticleHeader from 'dashboard/routes/dashboard/helpcenter/components/Header/EditArticleHeader'; import { mapGetters } from 'vuex';
import EditArticleField from 'dashboard/components/helpCenter/EditArticle'; 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 { export default {
components: { components: {
EditArticleHeader, EditArticleHeader,
EditArticleField, ArticleEditor,
Spinner,
ArticleSettings,
}, },
props: { mixins: [portalMixin, alertMixin],
article: { data() {
type: Object, return {
default: () => {}, 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: { methods: {
onClickGoBack() { onClickGoBack() {
this.$router.push({ name: 'list_all_locale_articles' }); 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> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.container { .article-container {
display: flex;
padding: var(--space-small) var(--space-normal); padding: var(--space-small) var(--space-normal);
width: 100%; width: 100%;
flex: 1;
overflow: scroll;
.edit-article--container {
flex: 1;
flex-shrink: 0;
overflow: scroll;
}
.is-sidebar-open {
flex: 0.7;
}
} }
</style> </style>

View file

@ -1,35 +1,21 @@
<template> <template>
<div class="article-container"> <div class="container">
<div <edit-article-header
class="edit-article--container" back-button-label="All Articles"
:class="{ 'is-sidebar-open': showArticleSettings }" draft-state="saved"
> @back="onClickGoBack"
<edit-article-header />
back-button-label="All Articles" <article-editor @titleInput="titleInput" @contentInput="contentInput" />
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> </div>
</template> </template>
<script> <script>
import EditArticleHeader from 'dashboard/routes/dashboard/helpcenter/components/Header/EditArticleHeader'; import EditArticleHeader from 'dashboard/routes/dashboard/helpcenter/components/Header/EditArticleHeader';
import EditArticleField from 'dashboard/components/helpCenter/EditArticle'; import ArticleEditor from '../../components/ArticleEditor.vue';
import ArticleSettings from 'dashboard/routes/dashboard/helpcenter/pages/articles/ArticleSettings';
export default { export default {
components: { components: {
EditArticleHeader, EditArticleHeader,
EditArticleField, ArticleEditor,
ArticleSettings,
}, },
data() { data() {
return { return {

View file

@ -62,18 +62,24 @@ export const actions = {
commit(types.SET_UI_FLAG, { isFetching: false }); commit(types.SET_UI_FLAG, { isFetching: false });
} }
}, },
update: async ({ commit }, params) => { update: async ({ commit }, { portalSlug, articleId, ...articleObj }) => {
const articleId = params.id;
commit(types.ADD_ARTICLE_FLAG, { commit(types.ADD_ARTICLE_FLAG, {
uiFlags: { uiFlags: {
isUpdating: true, isUpdating: true,
}, },
articleId, 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; return articleId;
} catch (error) { } catch (error) {

View file

@ -19,7 +19,7 @@ export const mutations = {
[types.CLEAR_ARTICLES]: $state => { [types.CLEAR_ARTICLES]: $state => {
Vue.set($state.articles, 'byId', {}); Vue.set($state.articles, 'byId', {});
Vue.set($state.articles, 'allIds', []); Vue.set($state.articles, 'allIds', []);
Vue.set($state.articles, 'uiFlags', {}); Vue.set($state.articles, 'uiFlags.byId', {});
}, },
[types.ADD_MANY_ARTICLES]($state, articles) { [types.ADD_MANY_ARTICLES]($state, articles) {
const allArticles = { ...$state.articles.byId }; const allArticles = { ...$state.articles.byId };
@ -55,7 +55,6 @@ export const mutations = {
}, },
[types.UPDATE_ARTICLE]($state, article) { [types.UPDATE_ARTICLE]($state, article) {
const articleId = article.id; const articleId = article.id;
if (!$state.articles.allIds.includes(articleId)) return; if (!$state.articles.allIds.includes(articleId)) return;
Vue.set($state.articles.byId, articleId, { Vue.set($state.articles.byId, articleId, {

View file

@ -89,8 +89,15 @@ describe('#actions', () => {
describe('#update', () => { describe('#update', () => {
it('sends correct actions if API is success', async () => { it('sends correct actions if API is success', async () => {
axios.patch.mockResolvedValue({ data: articleList[0] }); axios.patch.mockResolvedValue({ data: { payload: articleList[0] } });
await actions.update({ commit }, articleList[0]); await actions.update(
{ commit },
{
portalSlug: 'room-rental',
articleId: 1,
title: 'Documents are required to complete KYC',
}
);
expect(commit.mock.calls).toEqual([ expect(commit.mock.calls).toEqual([
[ [
types.default.ADD_ARTICLE_FLAG, types.default.ADD_ARTICLE_FLAG,
@ -105,9 +112,17 @@ describe('#actions', () => {
}); });
it('sends correct actions if API is error', async () => { it('sends correct actions if API is error', async () => {
axios.patch.mockRejectedValue({ message: 'Incorrect header' }); axios.patch.mockRejectedValue({ message: 'Incorrect header' });
await expect(actions.update({ commit }, articleList[0])).rejects.toThrow( await expect(
Error actions.update(
); { commit },
{
portalSlug: 'room-rental',
articleId: 1,
title: 'Documents are required to complete KYC',
}
)
).rejects.toThrow(Error);
expect(commit.mock.calls).toEqual([ expect(commit.mock.calls).toEqual([
[ [
types.default.ADD_ARTICLE_FLAG, types.default.ADD_ARTICLE_FLAG,

View file

@ -106,7 +106,11 @@ describe('#mutations', () => {
mutations[types.CLEAR_ARTICLES](state); mutations[types.CLEAR_ARTICLES](state);
expect(state.articles.allIds).toEqual([]); expect(state.articles.allIds).toEqual([]);
expect(state.articles.byId).toEqual({}); expect(state.articles.byId).toEqual({});
expect(state.articles.uiFlags).toEqual({}); expect(state.articles.uiFlags).toEqual({
byId: {
'1': { isFetching: false, isUpdating: true, isDeleting: false },
},
});
}); });
}); });
}); });

View file

@ -5,7 +5,7 @@ json.description article.description
json.status article.status json.status article.status
json.account_id article.account_id json.account_id article.account_id
json.updated_at article.updated_at.to_i json.updated_at article.updated_at.to_i
json.meta article.meta
json.category do json.category do
json.id article.category_id json.id article.category_id
json.name article.category.name json.name article.category.name

View file

@ -20,7 +20,7 @@
"dependencies": { "dependencies": {
"@braid/vue-formulate": "^2.5.2", "@braid/vue-formulate": "^2.5.2",
"@chatwoot/prosemirror-schema": "https://github.com/chatwoot/prosemirror-schema.git#7e8acadd10d7b932c0dc0bd0a18f804434f83517", "@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", "@hcaptcha/vue-hcaptcha": "^0.3.2",
"@rails/actioncable": "6.1.3", "@rails/actioncable": "6.1.3",
"@rails/webpacker": "5.3.0", "@rails/webpacker": "5.3.0",

View file

@ -1406,10 +1406,10 @@
prosemirror-state "^1.3.3" prosemirror-state "^1.3.3"
prosemirror-view "^1.17.2" prosemirror-view "^1.17.2"
"@chatwoot/utils@^0.0.6": "@chatwoot/utils@^0.0.10":
version "0.0.6" version "0.0.10"
resolved "https://registry.yarnpkg.com/@chatwoot/utils/-/utils-0.0.6.tgz#76d7b17d692b5b656c565b9b714b98e0f2bc1324" resolved "https://registry.npmjs.org/@chatwoot/utils/-/utils-0.0.10.tgz#59f68cc28d8718b261ebed8b9c94d2c493b6c67f"
integrity sha512-fCvULfJSFSylDAiGh1cPAX5nQkVsmG5ASGm/E6YBYg8cox/2JU179JFstdtTxrIJg/YeHukcaq85Gc+/16ShPQ== integrity sha512-Zd+wQTblWKUV1mhcXoabcfoLygx/Ock5pP0JQdfqW64lubhjYaRR4gCutEgqUcQB4nuOUH7MZ7BTzdZm4RoM/g==
dependencies: dependencies:
date-fns "^2.22.1" date-fns "^2.22.1"