From 407eb21fc4f8e6c829128aa3f9cad0df51af7b82 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Tue, 2 Jun 2026 22:31:33 +0000 Subject: [PATCH] V3 --- backend/src/db/api/articles.js | 141 +-- .../Articles/configureArticlesCols.tsx | 357 ++---- frontend/src/pages/articles/articles-edit.tsx | 1103 ++--------------- frontend/src/pages/articles/articles-list.tsx | 16 +- frontend/src/pages/articles/articles-new.tsx | 773 ++---------- .../src/pages/articles/articles-table.tsx | 16 +- 6 files changed, 342 insertions(+), 2064 deletions(-) diff --git a/backend/src/db/api/articles.js b/backend/src/db/api/articles.js index 80baa95..0d0c20e 100644 --- a/backend/src/db/api/articles.js +++ b/backend/src/db/api/articles.js @@ -70,19 +70,6 @@ module.exports = class ArticlesDBApi { - await articles.setAffiliate_links(data.affiliate_links || [], { - transaction, - }); - - await articles.setMedia_assets(data.media_assets || [], { - transaction, - }); - - await articles.setArticle_bundle_items(data.article_bundle_items || [], { - transaction, - }); - - await FileDBApi.replaceRelationFiles( { @@ -214,25 +201,9 @@ module.exports = class ArticlesDBApi { await articles.update(updatePayload, {transaction}); - - + // Article source-content updates intentionally skip generated many-to-many + // affiliate/media/bundle sync. Those joins are handled later in Export Studio. - - - if (data.affiliate_links !== undefined) { - await articles.setAffiliate_links(data.affiliate_links, { transaction }); - } - - if (data.media_assets !== undefined) { - await articles.setMedia_assets(data.media_assets, { transaction }); - } - - if (data.article_bundle_items !== undefined) { - await articles.setArticle_bundle_items(data.article_bundle_items, { transaction }); - } - - - await FileDBApi.replaceRelationFiles( { belongsTo: db.articles.getTableName(), @@ -347,22 +318,6 @@ module.exports = class ArticlesDBApi { }); - output.affiliate_links = await articles.getAffiliate_links({ - transaction - }); - - - output.media_assets = await articles.getMedia_assets({ - transaction - }); - - - output.article_bundle_items = await articles.getArticle_bundle_items({ - transaction - }); - - - return output; } @@ -382,32 +337,10 @@ module.exports = class ArticlesDBApi { offset = currentPage * limit; let include = [ - - - { - model: db.affiliate_links, - as: 'affiliate_links', - required: false, - }, - - { - model: db.media_assets, - as: 'media_assets', - required: false, - }, - - { - model: db.article_bundle_items, - as: 'article_bundle_items', - required: false, - }, - - { model: db.file, as: 'featured_images', }, - ]; if (filter) { @@ -533,76 +466,6 @@ module.exports = class ArticlesDBApi { - if (filter.affiliate_links) { - const searchTerms = filter.affiliate_links.split('|'); - - include = [ - { - model: db.affiliate_links, - as: 'affiliate_links_filter', - required: searchTerms.length > 0, - where: searchTerms.length > 0 ? { - [Op.or]: [ - { id: { [Op.in]: searchTerms.map(term => Utils.uuid(term)) } }, - { - original_url: { - [Op.or]: searchTerms.map(term => ({ [Op.iLike]: `%${term}%` })) - } - } - ] - } : undefined - }, - ...include, - ] - } - - if (filter.media_assets) { - const searchTerms = filter.media_assets.split('|'); - - include = [ - { - model: db.media_assets, - as: 'media_assets_filter', - required: searchTerms.length > 0, - where: searchTerms.length > 0 ? { - [Op.or]: [ - { id: { [Op.in]: searchTerms.map(term => Utils.uuid(term)) } }, - { - file_name: { - [Op.or]: searchTerms.map(term => ({ [Op.iLike]: `%${term}%` })) - } - } - ] - } : undefined - }, - ...include, - ] - } - - if (filter.article_bundle_items) { - const searchTerms = filter.article_bundle_items.split('|'); - - include = [ - { - model: db.article_bundle_items, - as: 'article_bundle_items_filter', - required: searchTerms.length > 0, - where: searchTerms.length > 0 ? { - [Op.or]: [ - { id: { [Op.in]: searchTerms.map(term => Utils.uuid(term)) } }, - { - sort_order: { - [Op.or]: searchTerms.map(term => ({ [Op.iLike]: `%${term}%` })) - } - } - ] - } : undefined - }, - ...include, - ] - } - - if (filter.createdAtRange) { const [start, end] = filter.createdAtRange; diff --git a/frontend/src/components/Articles/configureArticlesCols.tsx b/frontend/src/components/Articles/configureArticlesCols.tsx index a7f872c..dc90624 100644 --- a/frontend/src/components/Articles/configureArticlesCols.tsx +++ b/frontend/src/components/Articles/configureArticlesCols.tsx @@ -1,272 +1,91 @@ -import React from 'react'; -import BaseIcon from '../BaseIcon'; -import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; -import axios from 'axios'; -import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, -} from '@mui/x-data-grid'; -import ImageField from '../ImageField'; -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter' -import DataGridMultiSelect from "../DataGridMultiSelect"; -import ListActionsPopover from '../ListActionsPopover'; +import React from 'react' +import { GridRowParams } from '@mui/x-data-grid' +import ListActionsPopover from '../ListActionsPopover' +import { hasPermission } from '../../helpers/userPermissions' -import {hasPermission} from "../../helpers/userPermissions"; +type Params = (id: string) => void -type Params = (id: string) => void; +export const loadColumns = async (onDelete: Params, _entityName: string, user) => { + const hasUpdatePermission = hasPermission(user, 'UPDATE_ARTICLES') -export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user - -) => { - async function callOptionsApi(entityName: string) { - - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } - } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_ARTICLES') - - return [ - - { - field: 'title', - headerName: 'Title', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'slug', - headerName: 'Slug', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'status', - headerName: 'Status', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'excerpt', - headerName: 'Excerpt', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'content_html', - headerName: 'ContentHTML', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'content_markdown', - headerName: 'ContentMarkdown', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'featured_images', - headerName: 'FeaturedImages', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: false, - sortable: false, - renderCell: (params: GridValueGetterParams) => ( - - ), - - }, - - { - field: 'source_url', - headerName: 'SourceURL', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'published_at', - headerName: 'PublishedAt', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.published_at), - - }, - - { - field: 'affiliate_links', - headerName: 'AffiliateLinks', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: false, - sortable: false, - type: 'singleSelect', - valueFormatter: ({ value }) => - dataFormatter.affiliate_linksManyListFormatter(value).join(', '), - renderEditCell: (params) => ( - - ), - - }, - - { - field: 'media_assets', - headerName: 'MediaAssets', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: false, - sortable: false, - type: 'singleSelect', - valueFormatter: ({ value }) => - dataFormatter.media_assetsManyListFormatter(value).join(', '), - renderEditCell: (params) => ( - - ), - - }, - - { - field: 'article_bundle_items', - headerName: 'ArticleBundleItems', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: false, - sortable: false, - type: 'singleSelect', - valueFormatter: ({ value }) => - dataFormatter.article_bundle_itemsManyListFormatter(value).join(', '), - renderEditCell: (params) => ( - - ), - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
- -
, - ] - }, - }, - ]; -}; + return [ + { + field: 'title', + headerName: 'Title', + flex: 1, + minWidth: 180, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + editable: hasUpdatePermission, + }, + { + field: 'status', + headerName: 'Status', + flex: 0.6, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + editable: hasUpdatePermission, + }, + { + field: 'excerpt', + headerName: 'Excerpt', + flex: 1.2, + minWidth: 220, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + editable: hasUpdatePermission, + }, + { + field: 'content_html', + headerName: 'HTML Content', + flex: 1.2, + minWidth: 220, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + editable: hasUpdatePermission, + }, + { + field: 'content_markdown', + headerName: 'Meta Paste', + flex: 1, + minWidth: 180, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + editable: hasUpdatePermission, + }, + { + field: 'source_url', + headerName: 'Source URL', + flex: 1, + minWidth: 180, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + editable: hasUpdatePermission, + }, + { + field: 'actions', + type: 'actions', + minWidth: 30, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + getActions: (params: GridRowParams) => [ +
+ +
, + ], + }, + ] +} diff --git a/frontend/src/pages/articles/articles-edit.tsx b/frontend/src/pages/articles/articles-edit.tsx index 22ff63b..b247134 100644 --- a/frontend/src/pages/articles/articles-edit.tsx +++ b/frontend/src/pages/articles/articles-edit.tsx @@ -1,989 +1,164 @@ -import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js' +import { mdiChartTimelineVariant } from '@mdi/js' import Head from 'next/head' -import React, { ReactElement, useEffect, useState } from 'react' -import DatePicker from "react-datepicker"; -import "react-datepicker/dist/react-datepicker.css"; -import dayjs from "dayjs"; +import React, { ReactElement, useEffect, useMemo, useState } from 'react' +import { Field, Form, Formik } from 'formik' +import { useRouter } from 'next/router' +import BaseButton from '../../components/BaseButton' +import BaseButtons from '../../components/BaseButtons' +import BaseDivider from '../../components/BaseDivider' import CardBox from '../../components/CardBox' -import LayoutAuthenticated from '../../layouts/Authenticated' +import FormField from '../../components/FormField' +import { RichTextField } from '../../components/RichTextField' import SectionMain from '../../components/SectionMain' import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton' +import LayoutAuthenticated from '../../layouts/Authenticated' import { getPageTitle } from '../../config' - -import { Field, Form, Formik } from 'formik' -import FormField from '../../components/FormField' -import BaseDivider from '../../components/BaseDivider' -import BaseButtons from '../../components/BaseButtons' -import BaseButton from '../../components/BaseButton' -import FormCheckRadio from '../../components/FormCheckRadio' -import FormCheckRadioGroup from '../../components/FormCheckRadioGroup' -import FormFilePicker from '../../components/FormFilePicker' -import FormImagePicker from '../../components/FormImagePicker' -import { SelectField } from "../../components/SelectField"; -import { SelectFieldMany } from "../../components/SelectFieldMany"; -import { SwitchField } from '../../components/SwitchField' -import {RichTextField} from "../../components/RichTextField"; - -import { update, fetch } from '../../stores/articles/articlesSlice' +import { fetch, update } from '../../stores/articles/articlesSlice' import { useAppDispatch, useAppSelector } from '../../stores/hooks' -import { useRouter } from 'next/router' -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter'; -import ImageField from "../../components/ImageField"; +const emptyValues = { + title: '', + status: 'draft', + excerpt: '', + content_html: '', + content_markdown: '', + source_url: '', +} +const normalizeArticle = (article: any) => { + if (!article || Array.isArray(article)) return emptyValues + + return { + title: article.title || '', + status: article.status || 'draft', + excerpt: article.excerpt || '', + content_html: article.content_html || '', + content_markdown: article.content_markdown || '', + source_url: article.source_url || '', + } +} + +const getErrorMessage = (error: any) => { + if (!error) return 'Article could not be saved. Please check the form and try again.' + if (typeof error === 'string') return error + if (error.message) return error.message + if (error.error) return error.error + return 'Article could not be saved. Please check the form and try again.' +} const EditArticlesPage = () => { const router = useRouter() - const dispatch = useAppDispatch() - const initVals = { - - - 'title': '', - - - - - - - - - - - - - - - - - - - - - - - - - - - - 'slug': '', - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - status: '', - - - - - - - - - - - - - - excerpt: '', - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - content_html: '', - - - - - - - - - - - - - - - - - - - - - - - - - - content_markdown: '', - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - featured_images: [], - - - - - - - - 'source_url': '', - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - published_at: new Date(), - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - affiliate_links: [], - - - - - - - - - - - - - - - - - - - - - - - - - - - - media_assets: [], - - - - - - - - - - - - - - - - - - - - - - - - - - - - article_bundle_items: [], - - - - } - const [initialValues, setInitialValues] = useState(initVals) - - const { articles } = useAppSelector((state) => state.articles) - - const { id } = router.query + const dispatch = useAppDispatch() + const { articles, loading } = useAppSelector((state) => state.articles) + const [submitError, setSubmitError] = useState('') useEffect(() => { - dispatch(fetch({ id: id })) - }, [id]) - - useEffect(() => { - if (typeof articles === 'object') { - setInitialValues(articles) + if (typeof id === 'string') { + dispatch(fetch({ id })) } - }, [articles]) + }, [dispatch, id]) - useEffect(() => { - if (typeof articles === 'object') { - const newInitialVal = {...initVals}; - Object.keys(initVals).forEach(el => newInitialVal[el] = (articles)[el]) - setInitialValues(newInitialVal); - } - }, [articles]) + const initialValues = useMemo(() => normalizeArticle(articles), [articles]) - const handleSubmit = async (data) => { - await dispatch(update({ id: id, data })) - await router.push('/articles/articles-list') + const handleSubmit = async (data, { setSubmitting }) => { + setSubmitError('') + + if (typeof id !== 'string') { + const message = 'Cannot update article because the article ID is missing.' + console.error(message, { id }) + setSubmitError(message) + setSubmitting(false) + return + } + + try { + await dispatch(update({ id, data })).unwrap() + await router.push('/articles/articles-list') + } catch (error) { + console.error('Article update failed:', error) + setSubmitError(getErrorMessage(error)) + } finally { + setSubmitting(false) + } } return ( <> - {getPageTitle('Edit articles')} + {getPageTitle('Edit Article')} - - {''} + + {''} - handleSubmit(values)} - > -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - setInitialValues({...initialValues, 'published_at': date})} - /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - router.push('/articles/articles-list')}/> - - + + {({ isSubmitting }) => ( +
+

+ Edit the source article here. Buyer-specific affiliate link replacement happens later in Export Studio. +

+ + {submitError && ( +
+ {submitError} +
+ )} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + router.push('/articles/articles-list')} + disabled={isSubmitting} + /> + + + )}
@@ -992,15 +167,7 @@ const EditArticlesPage = () => { } EditArticlesPage.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ) + return {page} } export default EditArticlesPage diff --git a/frontend/src/pages/articles/articles-list.tsx b/frontend/src/pages/articles/articles-list.tsx index 97feffe..9008156 100644 --- a/frontend/src/pages/articles/articles-list.tsx +++ b/frontend/src/pages/articles/articles-list.tsx @@ -34,13 +34,13 @@ const ArticlesTablesPage = () => { const dispatch = useAppDispatch(); - const [filters] = useState([{label: 'Title', title: 'title'},{label: 'Slug', title: 'slug'},{label: 'Excerpt', title: 'excerpt'},{label: 'ContentHTML', title: 'content_html'},{label: 'ContentMarkdown', title: 'content_markdown'},{label: 'SourceURL', title: 'source_url'}, - - - {label: 'PublishedAt', title: 'published_at', date: 'true'}, - - {label: 'AffiliateLinks', title: 'affiliate_links'},{label: 'MediaAssets', title: 'media_assets'},{label: 'ArticleBundleItems', title: 'article_bundle_items'}, - {label: 'Status', title: 'status', type: 'enum', options: ['draft','published','archived']}, + const [filters] = useState([ + { label: 'Title', title: 'title' }, + { label: 'Status', title: 'status', type: 'enum', options: ['draft', 'published', 'archived'] }, + { label: 'Excerpt', title: 'excerpt' }, + { label: 'HTML Content', title: 'content_html' }, + { label: 'Meta Paste', title: 'content_markdown' }, + { label: 'Source URL', title: 'source_url' }, ]); const hasCreatePermission = currentUser && hasPermission(currentUser, 'CREATE_ARTICLES'); @@ -94,7 +94,7 @@ const ArticlesTablesPage = () => { - {hasCreatePermission && } + {hasCreatePermission && } { + if (!error) return 'Article could not be saved. Please check the form and try again.' + if (typeof error === 'string') return error + if (error.message) return error.message + if (error.error) return error.error + return 'Article could not be saved. Please check the form and try again.' +} const ArticlesNew = () => { const router = useRouter() const dispatch = useAppDispatch() + const [submitError, setSubmitError] = useState('') - - + const handleSubmit = async (data, { setSubmitting }) => { + setSubmitError('') - const handleSubmit = async (data) => { - await dispatch(create(data)) - await router.push('/articles/articles-list') + try { + await dispatch(create(data)).unwrap() + await router.push('/articles/articles-list') + } catch (error) { + console.error('Article create failed:', error) + setSubmitError(getErrorMessage(error)) + } finally { + setSubmitting(false) + } } + return ( <> - {getPageTitle('New Item')} + {getPageTitle('New Article')} - - {''} + + {''} - handleSubmit(values)} - > -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - router.push('/articles/articles-list')}/> - - + + {({ isSubmitting }) => ( +
+

+ Add the source article here. Buyer-specific affiliate link replacement happens later in Export Studio. +

+ + {submitError && ( +
+ {submitError} +
+ )} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + router.push('/articles/articles-list')} + disabled={isSubmitting} + /> + + + )}
@@ -694,15 +131,7 @@ const ArticlesNew = () => { } ArticlesNew.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ) + return {page} } export default ArticlesNew diff --git a/frontend/src/pages/articles/articles-table.tsx b/frontend/src/pages/articles/articles-table.tsx index 75f54a7..c9c23c8 100644 --- a/frontend/src/pages/articles/articles-table.tsx +++ b/frontend/src/pages/articles/articles-table.tsx @@ -34,13 +34,13 @@ const ArticlesTablesPage = () => { const dispatch = useAppDispatch(); - const [filters] = useState([{label: 'Title', title: 'title'},{label: 'Slug', title: 'slug'},{label: 'Excerpt', title: 'excerpt'},{label: 'ContentHTML', title: 'content_html'},{label: 'ContentMarkdown', title: 'content_markdown'},{label: 'SourceURL', title: 'source_url'}, - - - {label: 'PublishedAt', title: 'published_at', date: 'true'}, - - {label: 'AffiliateLinks', title: 'affiliate_links'},{label: 'MediaAssets', title: 'media_assets'},{label: 'ArticleBundleItems', title: 'article_bundle_items'}, - {label: 'Status', title: 'status', type: 'enum', options: ['draft','published','archived']}, + const [filters] = useState([ + { label: 'Title', title: 'title' }, + { label: 'Status', title: 'status', type: 'enum', options: ['draft', 'published', 'archived'] }, + { label: 'Excerpt', title: 'excerpt' }, + { label: 'HTML Content', title: 'content_html' }, + { label: 'Meta Paste', title: 'content_markdown' }, + { label: 'Source URL', title: 'source_url' }, ]); const hasCreatePermission = currentUser && hasPermission(currentUser, 'CREATE_ARTICLES'); @@ -94,7 +94,7 @@ const ArticlesTablesPage = () => { - {hasCreatePermission && } + {hasCreatePermission && }