Skip to content

Commit

Permalink
fix: View.jsx customization for redirects
Browse files Browse the repository at this point in the history
  • Loading branch information
nileshgulia1 committed Feb 13, 2024
1 parent e6b9cb0 commit 6d5a6f2
Show file tree
Hide file tree
Showing 2 changed files with 300 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/customizations/volto/components/theme/View/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This cusomization needs to be removed in volto 17.
299 changes: 299 additions & 0 deletions src/customizations/volto/components/theme/View/View.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
/**
* View container.
* @module components/theme/View/View
*/

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { compose } from 'redux';
import { Redirect } from 'react-router-dom';
import { Portal } from 'react-portal';
import { injectIntl } from 'react-intl';
import qs from 'query-string';

import {
ContentMetadataTags,
Comments,
Tags,
Toolbar,
} from '@plone/volto/components';
import { listActions, getContent } from '@plone/volto/actions';
import {
BodyClass,
getBaseUrl,
flattenToAppURL,
getLayoutFieldname,
hasApiExpander,
} from '@plone/volto/helpers';

import config from '@plone/volto/registry';

/**
* View container class.
* @class View
* @extends Component
*/
class View extends Component {
/**
* Property types.
* @property {Object} propTypes Property types.
* @static
*/
static propTypes = {
actions: PropTypes.shape({
object: PropTypes.arrayOf(PropTypes.object),
object_buttons: PropTypes.arrayOf(PropTypes.object),
user: PropTypes.arrayOf(PropTypes.object),
}),
listActions: PropTypes.func.isRequired,
/**
* Action to get the content
*/
getContent: PropTypes.func.isRequired,
/**
* Pathname of the object
*/
pathname: PropTypes.string.isRequired,
location: PropTypes.shape({
search: PropTypes.string,
pathname: PropTypes.string,
}).isRequired,
/**
* Version id of the object
*/
versionId: PropTypes.string,
/**
* Content of the object
*/
content: PropTypes.shape({
/**
* Layout of the object
*/
layout: PropTypes.string,
/**
* Allow discussion of the object
*/
allow_discussion: PropTypes.bool,
/**
* Title of the object
*/
title: PropTypes.string,
/**
* Description of the object
*/
description: PropTypes.string,
/**
* Type of the object
*/
'@type': PropTypes.string,
/**
* Subjects of the object
*/
subjects: PropTypes.arrayOf(PropTypes.string),
is_folderish: PropTypes.bool,
}),
error: PropTypes.shape({
/**
* Error type
*/
status: PropTypes.number,
}),
};

/**
* Default properties.
* @property {Object} defaultProps Default properties.
* @static
*/
static defaultProps = {
actions: null,
content: null,
versionId: null,
error: null,
};

state = {
hasObjectButtons: null,
isClient: false,
};

componentDidMount() {
// Do not trigger the actions action if the expander is present
if (!hasApiExpander('actions', getBaseUrl(this.props.pathname))) {
this.props.listActions(getBaseUrl(this.props.pathname));
}

this.props.getContent(
getBaseUrl(this.props.pathname),
this.props.versionId,
);
this.setState({ isClient: true });
}

/**
* Component will receive props
* @method componentWillReceiveProps
* @param {Object} nextProps Next properties
* @returns {undefined}
*/
UNSAFE_componentWillReceiveProps(nextProps) {
if (nextProps.pathname !== this.props.pathname) {
// Do not trigger the actions action if the expander is present
if (!hasApiExpander('actions', getBaseUrl(nextProps.pathname))) {
this.props.listActions(getBaseUrl(nextProps.pathname));
}

this.props.getContent(
getBaseUrl(nextProps.pathname),
this.props.versionId,
);
}

if (nextProps.actions.object_buttons) {
const objectButtons = nextProps.actions.object_buttons;
this.setState({
hasObjectButtons: !!objectButtons.length,
});
}
}

/**
* Default fallback view
* @method getViewDefault
* @returns {string} Markup for component.
*/
getViewDefault = () => config.views.defaultView;

/**
* Get view by content type
* @method getViewByType
* @returns {string} Markup for component.
*/
getViewByType = () =>
config.views.contentTypesViews[this.props.content['@type']] || null;

/**
* Get view by content layout property
* @method getViewByLayout
* @returns {string} Markup for component.
*/
getViewByLayout = () =>
config.views.layoutViews[
this.props.content[getLayoutFieldname(this.props.content)]
] || null;

/**
* Cleans the component displayName (specially for connected components)
* which have the Connect(componentDisplayName)
* @method cleanViewName
* @param {string} dirtyDisplayName The displayName
* @returns {string} Clean displayName (no Connect(...)).
*/
cleanViewName = (dirtyDisplayName) =>
dirtyDisplayName
.replace('Connect(', '')
.replace('injectIntl(', '')
.replace(')', '')
.replace('connect(', '')
.toLowerCase();

/**
* Render method.
* @method render
* @returns {string} Markup for the component.
*/
render() {
const { views } = config;
if (this.props.error && this.props.error.code === 301) {
const redirect = flattenToAppURL(this.props.error.url).split('?')[0];
return <Redirect to={`${redirect}${this.props.location.search}`} />;
} else if (this.props.error && !this.props.connectionRefused) {
let FoundView;
if (this.props.error.status === undefined) {
// For some reason, while development and if CORS is in place and the
// requested resource is 404, it returns undefined as status, then the
// next statement will fail
FoundView = views.errorViews.corsError;
} else {
FoundView = views.errorViews[this.props.error.status.toString()];
}
if (!FoundView) {
FoundView = views.errorViews['404']; // default to 404
}
return (
<div id="view">
<FoundView {...this.props} />
</div>
);
}
if (!this.props.content) {
return <span />;
}
const RenderedView =
this.getViewByLayout() || this.getViewByType() || this.getViewDefault();

return (
<div id="view">
<ContentMetadataTags content={this.props.content} />
{/* Body class if displayName in component is set */}
<BodyClass
className={
RenderedView.displayName
? `view-${this.cleanViewName(RenderedView.displayName)}`
: null
}
/>
<RenderedView
key={this.props.content['@id']}
content={this.props.content}
location={this.props.location}
token={this.props.token}
history={this.props.history}
/>
{config.settings.showTags &&
this.props.content.subjects &&
this.props.content.subjects.length > 0 && (
<Tags tags={this.props.content.subjects} />
)}
{/* Add opt-in social sharing if required, disabled by default */}
{/* In the future this might be parameterized from the app config */}
{/* <SocialSharing
url={typeof window === 'undefined' ? '' : window.location.href}
title={this.props.content.title}
description={this.props.content.description || ''}
/> */}
{this.props.content.allow_discussion && (
<Comments pathname={this.props.pathname} />
)}
{this.state.isClient && (
<Portal node={document.getElementById('toolbar')}>
<Toolbar pathname={this.props.pathname} inner={<span />} />
</Portal>
)}
</div>
);
}
}

export default compose(
injectIntl,
connect(
(state, props) => ({
actions: state.actions.actions,
token: state.userSession.token,
content: state.content.data,
error: state.content.get.error,
apiError: state.apierror.error,
connectionRefused: state.apierror.connectionRefused,
pathname: props.location.pathname,
versionId:
qs.parse(props.location.search) &&
qs.parse(props.location.search).version,
}),
{
listActions,
getContent,
},
),
)(View);

0 comments on commit 6d5a6f2

Please sign in to comment.