Commit 5b8b26d0 authored by Oleksii Savran's avatar Oleksii Savran
Browse files

Merge branch '140-documentation-display' into 'master'

Resolve "Documentation display"

Closes #140

See merge request genesys-pgr/genesys-ui!135
parents e52c6bb0 5ef391cf
......@@ -497,6 +497,13 @@
"terms": "Terms and Conditions of Use",
"copying": "Copyright policy",
"privacy": "Privacy policy"
},
"public": {
"c": {
"documentationSection": {
"toc": "Table of contents"
}
}
}
}
......
// constants
import {RECEIVE_ARTICLE, RECEIVE_MENU} from 'cms/constants';
import { LOADING_SLUG, RECEIVE_ARTICLE, RECEIVE_DOCUMENTATION, RECEIVE_MENU } from 'cms/constants';
// model
import Article from 'model/cms/Article';
import Menu from 'model/cms/Menu';
import ADoc from 'model/cms/ADoc';
// service
import CmsService from 'service/genesys/CmsService';
import AsciiDocService from 'service/genesys/AsciiDocService';
const loadingSlug = (slug: string) => ({
type: LOADING_SLUG,
payload: {slug},
});
const receiveArticle = (slug: string, article: Article) => ({
type: RECEIVE_ARTICLE,
......@@ -12,10 +19,22 @@ const receiveArticle = (slug: string, article: Article) => ({
});
export const loadArticle = (locale: string, slug: string) => (dispatch) => {
CmsService.getArticleBySlugAndLang(locale, slug)
dispatch(loadingSlug(slug));
return CmsService.getArticleBySlugAndLang(locale, slug)
.then((article) => dispatch(receiveArticle(slug, article)));
};
const receiveDocumentation = (slug: string, documentation: ADoc) => ({
type: RECEIVE_DOCUMENTATION,
payload: {slug, documentation},
});
export const loadDocumentation = (slug: string) => (dispatch) => {
dispatch(loadingSlug(slug));
return AsciiDocService.viewDoc(slug)
.then((article) => dispatch(receiveDocumentation(slug, article)));
};
const receiveMenu = (menuKey: string, menuItem: Menu) => ({
type: RECEIVE_MENU,
payload: {menuKey, menuItem},
......
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';
import {RECEIVE_ARTICLE, RECEIVE_MENU} from 'cms/constants';
import { RECEIVE_ARTICLE, RECEIVE_DOCUMENTATION, RECEIVE_MENU, LOADING_SLUG } from 'cms/constants';
import update from 'immutability-helper';
const INITIAL_STATE: {
articles: any,
documentations: any,
loadingSlug: string,
menus: any,
} = {
articles: {},
documentations: {},
menus: {},
loadingSlug: null,
};
export default function listPublic(state = INITIAL_STATE, action: { type?: string, payload?: any } = {type: '', payload: {}}) {
switch (action.type) {
case LOADING_SLUG: {
const {slug} = action.payload;
return update(state, {
loadingSlug: {$set: slug},
});
}
case RECEIVE_ARTICLE: {
const {slug, article} = action.payload;
return update(state, {
articles: {
[slug]: {$set: article},
},
loadingSlug: {$set: null},
});
} case RECEIVE_DOCUMENTATION: {
const {slug, documentation} = action.payload;
return update(state, {
documentations: {
[slug]: {$set: documentation},
},
loadingSlug: {$set: null},
});
}
case RECEIVE_MENU: {
......
import ContentPage from 'cms/ui/ContentPage';
import DocumentationPage from 'cms/ui/DocumentationPage';
const publicRoutes = [
......@@ -10,6 +11,10 @@ const publicRoutes = [
path: '/content/:slug',
component: ContentPage,
},
{
path: '/documentation/:slug',
component: DocumentationPage,
},
];
export {publicRoutes as cmsPublicRoutes};
......@@ -11,5 +11,12 @@
"terms": "Terms and Conditions of Use",
"copying": "Copyright policy",
"privacy": "Privacy policy"
},
"public": {
"c": {
"documentationSection": {
"toc": "Table of contents"
}
}
}
}
import * as React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import { translate } from 'react-i18next';
// Actions
import { setPageTitle } from 'actions/pageTitle';
import {loadDocumentation} from 'cms/actions/public';
// Model
import ADoc from 'model/cms/ADoc';
// UI
import PageLayout from 'ui/layout/PageLayout';
import ContentHeader from 'ui/common/heading/ContentHeader';
import DocumentationSection from './c/DocumentationSection';
import Grid from '@material-ui/core/Grid';
interface IDocumentationPageProps {
slug: string;
loadingSlug: string;
t: any;
i18n: any;
documentation: ADoc;
loadDocumentation: (slug: string) => void;
setPageTitle: any;
}
class ContentPage extends React.Component<IDocumentationPageProps> {
protected static needs = [
({ params }) => loadDocumentation(params.slug),
];
public componentWillMount() {
const { slug, documentation, loadDocumentation, loadingSlug } = this.props;
if ((!documentation && !loadingSlug) || slug !== loadingSlug) {
loadDocumentation(slug);
}
}
public componentWillReceiveProps(nextProps) {
const { slug, documentation, loadDocumentation, setPageTitle, loadingSlug } = nextProps;
if ((!documentation && !loadingSlug) || (loadingSlug && slug !== loadingSlug)) {
loadDocumentation(slug);
} else {
if (documentation && documentation.title) {
setPageTitle(documentation.title);
}
}
}
public render() {
const {documentation} = this.props;
return (
<PageLayout withFooter>
{ documentation &&
<Grid container>
<ContentHeader title={ documentation.title } />
<DocumentationSection documentation={ documentation }/>
</Grid>
}
</PageLayout>
);
}
}
const mapStateToProps = (state, ownProps) => ({
slug: ownProps.match.params.slug,
documentation: state.cms.public.documentations[ownProps.match.params.slug],
loadingSlug: state.cms.public.loadingSlug,
});
const mapDispatchToProps = (dispatch) => bindActionCreators({
loadDocumentation,
setPageTitle,
}, dispatch);
export default translate()(connect(mapStateToProps, mapDispatchToProps)(ContentPage));
import * as React from 'react';
import {translate} from 'react-i18next';
import withStyles from '@material-ui/core/styles/withStyles';
import ADoc from 'model/cms/ADoc';
import Grid from '@material-ui/core/Grid';
import GridContainer from 'ui/layout/GridContainer';
const styles = (theme) => ({
/*tslint:disable*/
root: {
width: '100%',
minHeight: 'calc(100vh - 365px)',
margin: 0,
'& > p': {
fontFamily: 'Roboto-Light',
fontSize: '24px',
color: '#4d4c46',
lineHeight: '36px',
margin: 0,
},
'& > h3': {
fontSize: '24px',
marginTop: '28px',
paddingBottom: '6px',
},
'& > ul': {
marginTop: '30px',
padding: 0,
paddingLeft: '11px',
},
'& > ul > li': {
fontFamily: 'Roboto-Regular',
fontSize: '18px',
lineHeight: '30px',
},
'& > div': {
width: '100%',
}
},
white: {
backgroundColor: 'white',
},
header: {
fontSize: '75%',
marginBottom: '4em',
},
content: {
width: '100%',
boxSizing: 'border-box' as 'border-box',
fontFamily: 'arial, sans-serif',
fontSize: '16px',
lineHeight: 1.4285,
color: '#000',
'& p': {
margin: '0 0 11px',
color: '#4d4c46',
marginTop: '20px',
marginBottom: '0',
fontFamily: 'Roboto-Regular',
'&:first-child': {
[theme.breakpoints.down('sm')]: {
lineHeight: '26px',
fontSize: '20px',
marginTop: '33px',
paddingRight: 0,
},
},
},
'& h2': {
fontSize: '34px',
marginTop: '-78px',
},
'& h3': {
fontSize: '24px',
marginTop: '-89px',
},
'& h4': {
fontSize: '20px',
marginTop: '-72px',
},
'& h2, & h3, & h4': {
marginBottom: '11px',
fontWeight: 700,
lineHeight: '1.1',
color: 'inherit',
paddingTop: '100px',
},
'& img': {
display: 'inline-block' as 'inline-block',
verticalAlign: 'middle' as 'middle',
maxWidth: '100%',
height: 'auto' as 'auto',
textAlign: 'center' as 'center',
border: '0',
},
'& table': {
wordWrap: 'break-word' as 'break-word',
tableLayout: 'fixed' as 'fixed',
'& th': {
textAlign: 'left' as 'left',
}
},
'& caption': {
paddingTop: '8px',
paddingBottom: '8px',
color: '#777777',
textAlign: 'left' as 'left',
},
'& code': {
padding: '2px 4px',
fontSize: '90%',
color: '#c7254e',
backgroundColor: '#f9f2f4',
borderRadius: '4px',
fontFamily: 'Menlo, Monaco, Consolas, "Courier New", monospace',
},
'& pre': {
display: 'block' as 'block',
padding: '10.5px',
margin: '0 0 11px',
fontSize: '15px',
lineHeight: '1.428571429',
wordBreak: 'break-all' as 'break-all',
wordWrap: 'break-word' as 'break-word',
color: '#333333',
backgroundColor: '#f5f5f5',
border: 'none',
overflow: 'auto' as 'auto',
fontFamily: ' Menlo, Monaco, Consolas, "Courier New", monospace',
'& > code': {
padding: 0,
color: 'inherit' as 'inherit',
whiteSpace: 'pre-wrap' as 'pre-wrap',
backgroundColor: 'transparent' as 'transparent',
},
'& .key': {
color: '#808 !important',
'& .delimeter': {
color: '#606 !important',
},
},
'& .string': {
color: '#d20 !important',
'& .delimeter': {
color: '#d1 !important',
},
},
'& .value': {
color: '#088 !important',
},
'& .integer, & .float': {
color: '#099 !important',
},
},
'& > .sect1': {
'& > h2': {
marginTop: '-78px',
paddingTop: '100px',
overflowWrap: 'break-word' as 'break-word',
wordWrap: 'break-word' as 'break-word',
}
}
},
tocContainer: {
padding: '0 .5rem',
},
tocHeader: {
overflow: 'hidden' as 'hidden',
marginTop: 0,
marginBottom: '.8rem',
fontSize: '1.2em',
},
toc: {
width: 'calc(100% - 2.5rem)',
marginLeft: '1rem',
padding: '1.25em 1em',
'& ul': {
marginLeft: 0,
fontFamily: '"Open Sans","DejaVu Sans",sans-serif',
listStyleType: 'none' as 'none',
'& ul': {
paddingLeft: '1em',
},
},
'& > ul': {
fontSize: '15px',
lineHeight: '19px',
marginLeft: '.125em',
padding: 0,
},
'& a': {
textDecoration: 'none' as 'none',
fontFamily: 'Roboto-Regular',
color: '#006db4',
},
},
/*tslint:enable*/
});
interface IDocumentationSectionProps extends React.ClassAttributes<any> {
documentation: ADoc;
classes: any;
t: any;
}
class DocumentationSection extends React.Component<IDocumentationSectionProps> {
private replaceImgSrc = (content) => {
return content.replace('<img src="images', '<img src="/proxy/api/v1/cms/d/images');
}
public render() {
const {documentation, classes, t} = this.props;
return(
<GridContainer className={ `${classes.root} container-spacing-vertical` }>
<Grid item sm={ 12 } md={ 9 } className={ classes.white } >
<div className={ classes.header } dangerouslySetInnerHTML={ {__html: documentation.header} }/>
<div className={ classes.content } dangerouslySetInnerHTML={ {__html: this.replaceImgSrc(documentation.content)} }/>
</Grid>
<Grid item sm={ 12 } md={ 3 } className={ classes.tocContainer }>
<div className={ `${classes.toc} ${classes.white}` }>
<div className={ classes.tocHeader }>{ t('cms.public.c.documentationSection.toc') }</div>
<div className={ `${classes.toc} ${classes.white}` } dangerouslySetInnerHTML={ {__html: documentation.toc} }/>
</div>
</Grid>
</GridContainer>
);
}
}
export default translate()(withStyles(styles)(DocumentationSection));
/*
* Defined in Swagger as '#/definitions/ADoc'
*/
class ADoc {
public content: string;
public footer: string;
public header: string;
public title: string;
public toc: string;
}
export default ADoc;
import * as UrlTemplate from 'url-template';
import { axiosBackend } from 'utilities/requestUtils';
import ADoc from 'model/cms/ADoc';
const URL_GET_IMAGE = UrlTemplate.parse(`/api/v1/cms/d/images/{imageName}`);
const URL_VIEW_SECTION = UrlTemplate.parse(`/api/v1/cms/d/sections/{documentName}`);
const URL_VIEW_DOC = UrlTemplate.parse(`/api/v1/cms/d/{documentName}`);
/*
* Defined in Swagger as 'asciiDoc'
*/
class AsciiDocService {
/**
* getImage at /api/v1/cms/d/images/{imageName}
*
* @param imageName imageName
*/
public static getImage(imageName: string): Promise<string> {
const apiUrl = URL_GET_IMAGE.expand({imageName});
// console.log(`Fetching from ${apiUrl}`);
const content = { /* No content in request body */ };
return axiosBackend({
url: apiUrl,
method: 'GET',
...content,
}).then(({ data }) => data as string);
}
/**
* viewSection at /api/v1/cms/d/sections/{documentName}
*
* @param documentName documentName
*/
public static viewSection(documentName: string): Promise<ADoc> {
const apiUrl = URL_VIEW_SECTION.expand({documentName});
// console.log(`Fetching from ${apiUrl}`);
const content = { /* No content in request body */ };
return axiosBackend({
url: apiUrl,
method: 'GET',
...content,
}).then(({ data }) => data as ADoc);
}
/**
* viewDoc at /api/v1/cms/d/{documentName}
*
* @param documentName documentName
*/
public static viewDoc(documentName: string): Promise<ADoc> {
const apiUrl = URL_VIEW_DOC.expand({documentName});
// console.log(`Fetching from ${apiUrl}`);
const content = { /* No content in request body */ };
return axiosBackend({
url: apiUrl,
method: 'GET',
...content,
}).then(({ data }) => data as ADoc);
}
}
export default AsciiDocService;
......@@ -107,11 +107,11 @@ const PUBLIC_MENUS = [
],
},
{
to: '/documentation', // TODO
to: '/documentation',
label: 'public.menu.documentation',
subMenus: [
{
to: '/documentation/api', // TODO
to: '/documentation/apis',
label: 'public.menu.api',
},
{
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment