Commit 720e6872 authored by Viacheslav Pavlov's avatar Viacheslav Pavlov Committed by Matija Obreza
Browse files

CMS Admin

- add new article
- summary field
- Ckeditor scripts load from local server
- blurb params (targetId, className) stored as path params, redesigned ArticleCard
- SSR
- fixed incorrect HtmlEditField value
parent cd926ecf
......@@ -281,7 +281,8 @@ module.exports = {
{ from: 'assets/images', to: 'images'},
{ from: 'assets/templates', to: 'templates'},
{ from: 'locales', to: 'locales'},
{ from: 'node_modules/leaflet/dist/images', to: 'images'}
{ from: 'node_modules/leaflet/dist/images', to: 'images'},
{ from: 'node_modules/ckeditor/', to: 'scripts/ckeditor'}
])
],
optimization: {
......
......@@ -247,6 +247,7 @@
"users": "Users",
"oAuthClients": "OAuth clients",
"requests": "Requests",
"content": "CMS",
"vocabulary": "Vocabulary",
"fileRepository": "File repository",
"cluster": "Cluster",
......@@ -497,6 +498,36 @@
}
}
,"cms": {
"admin": {
"c": {
"articleForm": {
"generalInformation": "General information",
"content": "Content",
"summary": "Summary",
"title": "Title",
"slug": "Slug",
"lang": "Language",
"isTemplate": "Is template"
}
},
"f": {
"filterTitle": "Filter articles",
"isTemplate": "Template",
"title": "Article title",
"meta": "Meta",
"slug": "Slug",
"lang": "Language"
},
"p": {
"browse": {
"title": "Browse articles",
"create": "Create new article"
},
"edit": {
"articleSaved": "Article saved"
}
}
},
"menu": {
"about": "About Genesys",
"contact": "Contact us",
......@@ -516,6 +547,9 @@
"toc": "Table of contents"
}
}
},
"common": {
"articleList": "Article list"
}
}
......
......@@ -996,7 +996,8 @@
"arity-n": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/arity-n/-/arity-n-1.0.4.tgz",
"integrity": "sha1-2edrEXM+CFacCEeuezmyhgswt0U="
"integrity": "sha1-2edrEXM+CFacCEeuezmyhgswt0U=",
"dev": true
},
"arr-diff": {
"version": "4.0.0",
......@@ -1407,7 +1408,6 @@
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz",
"integrity": "sha1-Of+DE7dci2Xc7/HzHTg+D/KkCKA=",
"dev": true,
"requires": {
"babel-runtime": "^6.26.0",
"babel-types": "^6.26.0",
......@@ -1594,14 +1594,12 @@
"babel-plugin-syntax-flow": {
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz",
"integrity": "sha1-TDqyCiryaqIM0lmVw5jE63AxDI0=",
"dev": true
"integrity": "sha1-TDqyCiryaqIM0lmVw5jE63AxDI0="
},
"babel-plugin-syntax-jsx": {
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz",
"integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=",
"dev": true
"integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY="
},
"babel-plugin-syntax-trailing-function-commas": {
"version": "6.22.0",
......@@ -1869,7 +1867,6 @@
"version": "6.22.0",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz",
"integrity": "sha1-hMtnKTXUNxT9wyvOhFaNh0Qc988=",
"dev": true,
"requires": {
"babel-plugin-syntax-flow": "^6.18.0",
"babel-runtime": "^6.22.0"
......@@ -1879,7 +1876,6 @@
"version": "6.25.0",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz",
"integrity": "sha1-Z+K/Hx6ck6sI25Z5LgU5K/LMKNE=",
"dev": true,
"requires": {
"babel-runtime": "^6.22.0"
}
......@@ -1888,7 +1884,6 @@
"version": "6.24.1",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz",
"integrity": "sha1-hAoCjn30YN/DotKfDA2R9jduZqM=",
"dev": true,
"requires": {
"babel-helper-builder-react-jsx": "^6.24.1",
"babel-plugin-syntax-jsx": "^6.8.0",
......@@ -1899,7 +1894,6 @@
"version": "6.22.0",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx-self/-/babel-plugin-transform-react-jsx-self-6.22.0.tgz",
"integrity": "sha1-322AqdomEqEh5t3XVYvL7PBuY24=",
"dev": true,
"requires": {
"babel-plugin-syntax-jsx": "^6.8.0",
"babel-runtime": "^6.22.0"
......@@ -1909,7 +1903,6 @@
"version": "6.22.0",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.22.0.tgz",
"integrity": "sha1-ZqwSFT9c0tF7PBkmj0vwGX9E7NY=",
"dev": true,
"requires": {
"babel-plugin-syntax-jsx": "^6.8.0",
"babel-runtime": "^6.22.0"
......@@ -1928,7 +1921,6 @@
"version": "6.23.0",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.23.0.tgz",
"integrity": "sha1-iEkNRGUC6puOfvsP4J7E2ZR5se4=",
"dev": true,
"requires": {
"babel-runtime": "^6.22.0"
}
......@@ -2028,7 +2020,6 @@
"version": "6.23.0",
"resolved": "https://registry.npmjs.org/babel-preset-flow/-/babel-preset-flow-6.23.0.tgz",
"integrity": "sha1-5xIYiHCFrpoktb5Baa/7WZgWxJ0=",
"dev": true,
"requires": {
"babel-plugin-transform-flow-strip-types": "^6.22.0"
}
......@@ -2037,7 +2028,6 @@
"version": "6.24.1",
"resolved": "https://registry.npmjs.org/babel-preset-react/-/babel-preset-react-6.24.1.tgz",
"integrity": "sha1-umnfrqRfw+xjm2pOzqbhdwLJE4A=",
"dev": true,
"requires": {
"babel-plugin-syntax-jsx": "^6.3.13",
"babel-plugin-transform-react-display-name": "^6.23.0",
......@@ -2116,7 +2106,6 @@
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz",
"integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=",
"dev": true,
"requires": {
"babel-runtime": "^6.26.0",
"esutils": "^2.0.2",
......@@ -2680,11 +2669,6 @@
"integrity": "sha512-9ZTaoBaePSCFvNlNGrsyI8ZVACP2svUtq0DkM7t4K2ClAa96sqOIRjAzDTc8zXzFt1cZR46rRzLTiHFSJ+Qw0g==",
"dev": true
},
"chickencurry": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/chickencurry/-/chickencurry-1.1.1.tgz",
"integrity": "sha1-AmVfKyazvC7hrh5TFoht4463lzg="
},
"chokidar": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz",
......@@ -2760,6 +2744,11 @@
"integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==",
"dev": true
},
"ckeditor": {
"version": "4.11.1",
"resolved": "https://registry.npmjs.org/ckeditor/-/ckeditor-4.11.1.tgz",
"integrity": "sha512-UhHe02cc/wWJquDQZysEgh0ohLMEMU56zDx+s8prDdjylY/aBDY2xdIiIpbgCBTXdjhrEPIAPyiDS9g3RxYXig=="
},
"class-utils": {
"version": "0.3.6",
"resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
......@@ -2999,14 +2988,6 @@
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
"integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY="
},
"compose-function": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/compose-function/-/compose-function-2.0.0.tgz",
"integrity": "sha1-5kL6fh2iFSlyADFHZ3b8JGkawLA=",
"requires": {
"arity-n": "^1.0.4"
}
},
"compressible": {
"version": "2.0.14",
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.14.tgz",
......@@ -4899,8 +4880,7 @@
"esutils": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
"dev": true
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs="
},
"etag": {
"version": "1.8.1",
......@@ -6708,15 +6688,15 @@
"dev": true
},
"html-to-react": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/html-to-react/-/html-to-react-1.3.3.tgz",
"integrity": "sha512-4Qi5/t8oBr6c1t1kBJKyxEeJu0lb7ctvq29oFZioiUHH0Wz88VWGwoXuH26HDt9v64bDHA4NMPNTH8bVrcaJWA==",
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/html-to-react/-/html-to-react-1.3.4.tgz",
"integrity": "sha512-/tWDdb/8Koi/QEP5YUY1653PcDpBnnMblXRhotnTuhFDjI1Fc6Wzox5d4sw73Xk5rM2OdM5np4AYjT/US/Wj7Q==",
"requires": {
"domhandler": "^2.3.0",
"domhandler": "^2.4.2",
"escape-string-regexp": "^1.0.5",
"htmlparser2": "^3.8.3",
"ramda": "^0.25.0",
"underscore.string.fp": "^1.0.4"
"htmlparser2": "^3.10.0",
"lodash.camelcase": "^4.3.0",
"ramda": "^0.26"
},
"dependencies": {
"domhandler": {
......@@ -6741,9 +6721,9 @@
}
},
"readable-stream": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.0.6.tgz",
"integrity": "sha512-9E1oLoOWfhSXHGv6QlwXJim7uNzd9EVlWK+21tCU9Ju/kR0/p2AZYPz4qSchgO8PlLIH4FpZYfzwS+rEksZjIg==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.1.0.tgz",
"integrity": "sha512-vpydAvIJvPODZNagCPuHG87O9JNPtvFEtjHHRVwNVsVVRBqemvPJkc2SYbxJsiZXawJdtZNmkmnsPuE3IgsG0A==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
......@@ -6751,9 +6731,9 @@
}
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz",
"integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==",
"requires": {
"safe-buffer": "~5.1.0"
}
......@@ -8066,6 +8046,11 @@
}
}
},
"load-script": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/load-script/-/load-script-1.0.0.tgz",
"integrity": "sha1-BJGTngvuVkPuSUp+PaPSuscMbKQ="
},
"loader-runner": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.0.tgz",
......@@ -8163,8 +8148,7 @@
"lodash.camelcase": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
"integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=",
"dev": true
"integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY="
},
"lodash.clonedeep": {
"version": "4.5.0",
......@@ -13791,9 +13775,9 @@
"dev": true
},
"ramda": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.25.0.tgz",
"integrity": "sha512-GXpfrYVPwx3K7RQ6aYT8KPS8XViSXUVJT1ONhoKPE9VAleW42YE+U+8VEyGWt41EnEQW7gwecYJriTI0pKoecQ=="
"version": "0.26.1",
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz",
"integrity": "sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ=="
},
"randomatic": {
"version": "3.1.0",
......@@ -13922,6 +13906,17 @@
"section-iterator": "^2.0.0"
}
},
"react-ckeditor-component": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/react-ckeditor-component/-/react-ckeditor-component-1.1.0.tgz",
"integrity": "sha512-dcptPsMKRDGvAGxokwdVDKPgZbTgwngknaQVD+SDTV6XSw57J6LD7cRaqEo6WGLwMZh39Up37fdJjugNd6vEAA==",
"requires": {
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-react": "^6.16.0",
"babel-runtime": "^6.11.6",
"load-script": "^1.0.0"
}
},
"react-day-picker": {
"version": "7.1.10",
"resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-7.1.10.tgz",
......@@ -15117,11 +15112,6 @@
"resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
"integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg=="
},
"reverse-arguments": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/reverse-arguments/-/reverse-arguments-1.0.0.tgz",
"integrity": "sha1-woCVo6khrHFdYYNN3s6QJ5kmZ80="
},
"rework": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/rework/-/rework-1.0.1.tgz",
......@@ -17060,8 +17050,7 @@
"to-fast-properties": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz",
"integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=",
"dev": true
"integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc="
},
"to-object-path": {
"version": "0.3.0",
......@@ -17436,22 +17425,6 @@
"integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=",
"dev": true
},
"underscore.string": {
"version": "3.0.3",
"resolved": "http://registry.npmjs.org/underscore.string/-/underscore.string-3.0.3.tgz",
"integrity": "sha1-Rhe4waJQz25QZPu7Nj0PqWzxRVI="
},
"underscore.string.fp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/underscore.string.fp/-/underscore.string.fp-1.0.4.tgz",
"integrity": "sha1-BUs/GEO8rlYShsh95eiHm0/Jg2Q=",
"requires": {
"chickencurry": "1.1.1",
"compose-function": "^2.0.0",
"reverse-arguments": "1.0.0",
"underscore.string": "3.0.3"
}
},
"unherit": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.0.tgz",
......
// actions
import { showSnackbar } from 'actions/snackbar';
import navigateTo from 'actions/navigation';
// constants
import { ADMIN_APPEND_ARTICLES, ADMIN_RECEIVE_ARTICLE, ADMIN_RECEIVE_ARTICLES, ADMIN_LOADING_ARTICLE } from 'cms/constants';
// model
import Article from 'model/cms/Article';
import ArticleFilter from 'model/cms/ArticleFilter';
import Page from 'model/Page';
import FilteredPage, { IPageRequest } from 'model/FilteredPage';
// service
import CmsService from 'service/genesys/CmsService';
const receiveArticles = (paged: FilteredPage<Article>, error = null) => ({
type: ADMIN_RECEIVE_ARTICLES,
payload: { paged, error },
});
const appendArticles = (paged: FilteredPage<Article>, error = null) => ({
type: ADMIN_APPEND_ARTICLES,
payload: { paged, error },
});
const receiveArticle = (slug: string, article: Article) => ({
type: ADMIN_RECEIVE_ARTICLE,
payload: {slug, article},
});
const loadingArticle = (slug: string) => ({
type: ADMIN_LOADING_ARTICLE,
payload: {slug},
});
export const loadArticle = (locale: string, slug: string, targetId: number, clazz: string) => (dispatch) => {
dispatch(loadingArticle(slug));
if (!targetId || !clazz) {
return CmsService.getArticleBySlugAndLang(locale, slug)
.then((article) => dispatch(receiveArticle(slug, article)));
} else {
return CmsService.getArticle(clazz, locale, slug, targetId)
.then((article) => dispatch(receiveArticle(slug, article)));
}
};
export const updateArticle = (article: Article) => (dispatch) => {
return CmsService.updateArticle(article)
.then((article) => {
dispatch(receiveArticle(article.slug, article));
dispatch(showSnackbar('cms.admin.p.edit.articleSaved'));
}).catch((error) => dispatch(showSnackbar(error.statusText)));
};
export const updateRoute = (paged: FilteredPage<Article>) => (dispatch) => {
dispatch(navigateTo(paged.filterCode ? `/admin/content/${paged.filterCode}` : '/admin/content'));
};
export const applyFilters = (filters: ArticleFilter, page: IPageRequest = { page: 0 }) => (dispatch) => {
console.log('Applying new filter', filters);
dispatch(showSnackbar('Applying filters...'));
return CmsService.listArticles(filters, page)
.then((paged) => {
dispatch(receiveArticles(paged));
dispatch(updateRoute(paged));
dispatch(showSnackbar(`Filters applied.`));
}).catch((error) => {
console.log(`API error`, error);
dispatch(receiveArticles(null, error));
dispatch(showSnackbar(`API error: ${error.statusText}`));
});
};
export const loadMoreArticles = (paged?: FilteredPage<Article>) => (dispatch) => {
return CmsService.listArticles(paged && paged.filterCode, Page.nextPage(paged))
.then((paged) => {
if (paged.number === 0) {
dispatch(receiveArticles(paged));
} else {
dispatch(appendArticles(paged));
}
dispatch(updateRoute(paged));
}).catch((error) => {
console.log(`API error`, error);
dispatch(receiveArticles(null, error));
});
};
export const createGlobalArticle = (article: Article) => (dispatch) => {
article = {
body: '<p></p>',
summary: '<p></p>',
lang: 'en',
...article,
};
return CmsService.createGlobalArticle(article)
.then((saved) => dispatch(navigateTo(`/admin/content/${saved.slug}/edit`)));
};
......@@ -2,3 +2,12 @@ export const RECEIVE_ARTICLE = 'cms/public/RECEIVE_ARTICLE';
export const RECEIVE_DOCUMENTATION = 'cms/public/RECEIVE_DOCUMENTATION';
export const LOADING_SLUG = 'cms/public/LOADING_SLUG';
export const RECEIVE_MENU = 'cms/public/RECEIVE_MENU';
// admin
export const ADMIN_RECEIVE_ARTICLE = 'cms/admin/RECEIVE_ARTICLE';
export const ADMIN_RECEIVE_ARTICLES = 'cms/admin/RECEIVE_ARTICLES';
export const ADMIN_APPEND_ARTICLES = 'cms/admin/APPEND_ARTICLES';
export const ADMIN_LOADING_ARTICLE = 'cms/admin/LOADING_ARTICLE';
export const ARTICLE_FORM = 'form/cms/admin/article';
import { ADMIN_RECEIVE_ARTICLE, ADMIN_RECEIVE_ARTICLES, ADMIN_APPEND_ARTICLES, ADMIN_LOADING_ARTICLE } from 'cms/constants';
import update from 'immutability-helper';
import Page from 'model/Page';
import Article from 'model/cms/Article';
const INITIAL_STATE: {
articles: any,
loading: any,
paged: Page<Article>,
pagedError: any,
} = {
articles: {},
loading: null,
paged: null,
pagedError: null,
};
export default function cmsAdmin(state = INITIAL_STATE, action: { type?: string, payload?: any } = {type: '', payload: {}}) {
switch (action.type) {
case ADMIN_LOADING_ARTICLE: {
const { slug } = action.payload;
return update(state, {
loading: { $set: slug },
});
}
case ADMIN_RECEIVE_ARTICLE: {
const { slug, article } = action.payload;
return update(state, {
articles: {
[slug]: { $set: article },
},
loading: { $set: null },
});
}
case ADMIN_RECEIVE_ARTICLES: {
const { paged, error } = action.payload;
return update(state, {
paged: { $set: paged },
pagedError: { $set: error },
});
}
case ADMIN_APPEND_ARTICLES: {
const { paged, error } = action.payload;
return !state.paged ? update(state, {
paged: { $set: paged },
pagedError: { $set: error },
}) :
update(state, {
paged: {
content: { $push: paged.content },
number: { $set: paged.number },
last: { $set: paged.last },
},
pagedError: { $set: error },
});
}
default:
return state;
}
}
import { combineReducers } from 'redux';
import admin from './admin';
import cmsPublic from './public';
const rootReducer = combineReducers({
public: cmsPublic,
admin,
});
export default rootReducer;
import ContentPage from 'cms/ui/ContentPage';
import EditPage from 'cms/ui/admin/EditPage';
import BrowsePage from 'cms/ui/admin/BrowsePage';
import DocumentationPage from 'cms/ui/DocumentationPage';
......@@ -17,4 +19,26 @@ const publicRoutes = [
},
];
export {publicRoutes as cmsPublicRoutes};
const adminRoutes = [
{
path: '/content/:slug/edit',
component: EditPage,
exact: true,
},
{
path: '/content/:slug/edit/:className/:targetId',
component: EditPage,
exact: true,
},
{
path: '/content/edit',
component: EditPage,
exact: true,
},
{
path: '/content/:filterCode(v.+)?',
component: BrowsePage,
},
];
export {publicRoutes as cmsPublicRoutes, adminRoutes as cmsAdminRoutes};
{
"admin": {
"c": {
"articleForm": {
"generalInformation": "General information",
"content": "Content",
"summary": "Summary",
"title": "Title",
"slug": "Slug",
"lang": "Language",
"isTemplate": "Is template"
}
},
"f": {
"filterTitle": "Filter articles",
"isTemplate": "Template",
"title": "Article title",
"meta": "Meta",
"slug": "Slug",
"lang": "Language"
},
"p": {
"browse": {
"title": "Browse articles",
"create": "Create new article"
},
"edit": {
"articleSaved": "Article saved"
}
}