UNPKG

@plone/volto

Version:
459 lines (438 loc) 15 kB
import React, { useState, useEffect } from 'react'; import { defineMessages, useIntl } from 'react-intl'; import PropTypes from 'prop-types'; import { useDispatch, useSelector, shallowEqual } from 'react-redux'; import { Link, useHistory } from 'react-router-dom'; import find from 'lodash/find'; import { toast } from 'react-toastify'; import Toast from '@plone/volto/components/manage/Toast/Toast'; import { Pluggable, Plug } from '@plone/volto/components/manage/Pluggable'; import FormattedDate from '@plone/volto/components/theme/FormattedDate/FormattedDate'; import Icon from '@plone/volto/components/theme/Icon/Icon'; import Display from '@plone/volto/components/manage/Display/Display'; import Workflow from '@plone/volto/components/manage/Workflow/Workflow'; import { applyWorkingCopy, createWorkingCopy, removeWorkingCopy, } from '@plone/volto/actions/workingcopy/workingcopy'; import { flattenToAppURL, getBaseUrl } from '@plone/volto/helpers/Url/Url'; import { usePrevious } from '@plone/volto/helpers/Utils/usePrevious'; import config from '@plone/volto/registry'; import rightArrowSVG from '@plone/volto/icons/right-key.svg'; import userSVG from '@plone/volto/icons/user.svg'; import applySVG from '@plone/volto/icons/ready.svg'; import removeSVG from '@plone/volto/icons/circle-dismiss.svg'; const messages = defineMessages({ personalTools: { id: 'Personal tools', defaultMessage: 'Personal tools', }, history: { id: 'History', defaultMessage: 'History', }, sharing: { id: 'Sharing', defaultMessage: 'Sharing', }, rules: { id: 'Rules', defaultMessage: 'Rules', }, aliases: { id: 'URL Management', defaultMessage: 'URL Management', }, linkstoitem: { id: 'Links and references', defaultMessage: 'Links and references', }, ManageTranslations: { id: 'Manage Translations', defaultMessage: 'Manage Translations', }, manageContent: { id: 'Manage content…', defaultMessage: 'Manage content…', }, CreateWorkingCopy: { id: 'Create working copy', defaultMessage: 'Create working copy', }, applyWorkingCopy: { id: 'Apply working copy', defaultMessage: 'Apply working copy', }, removeWorkingCopy: { id: 'Remove working copy', defaultMessage: 'Remove working copy', }, viewWorkingCopy: { id: 'View working copy', defaultMessage: 'View working copy', }, workingAppliedTitle: { id: 'Changes applied.', defaultMessage: 'Changes applied', }, workingCopyAppliedBy: { id: 'Made by {creator} on {date}. This is not a working copy anymore, but the main content.', defaultMessage: 'Made by {creator} on {date}. This is not a working copy anymore, but the main content.', }, workingCopyRemovedTitle: { id: 'The working copy was discarded', defaultMessage: 'The working copy was discarded', }, Unauthorized: { id: 'Unauthorized', defaultMessage: 'Unauthorized', }, workingCopyErrorUnauthorized: { id: 'workingCopyErrorUnauthorized', defaultMessage: 'You are not authorized to perform this operation.', }, Error: { id: 'Error', defaultMessage: 'Error', }, workingCopyGenericError: { id: 'workingCopyGenericError', defaultMessage: 'An error occurred while performing this operation.', }, }); const More = (props) => { const dispatch = useDispatch(); const intl = useIntl(); const history = useHistory(); const [, setPushed] = useState(false); const pathname = props.pathname; const content = useSelector((state) => state.content?.data, shallowEqual); const workingCopy = useSelector((state) => state.workingCopy, shallowEqual); const actions = useSelector((state) => state.actions.actions, shallowEqual); const workingCopyApply = workingCopy?.apply.loading; const workingCopyCreate = workingCopy?.create.loading; const workingCopyRemove = workingCopy?.remove.loading; const prevWorkingCopyApplyLoading = usePrevious(workingCopyApply); const prevWorkingCopyCreateLoading = usePrevious(workingCopyCreate); const prevWorkingCopyRemoveLoading = usePrevious(workingCopyRemove); const push = (selector) => { setPushed(true); props.loadComponent(selector); document.removeEventListener('mousedown', props.handleClickOutside, false); }; useEffect(() => { let erroredAction = ''; if (prevWorkingCopyApplyLoading && workingCopy.apply.error) { erroredAction = 'apply'; } else if (prevWorkingCopyCreateLoading && workingCopy.create.error) { erroredAction = 'create'; } else if (prevWorkingCopyRemoveLoading && workingCopy.remove.error) { erroredAction = 'remove'; } if (erroredAction) { const errorStatus = workingCopy[erroredAction].error.status; if (errorStatus === 401 || errorStatus === 403) { toast.error( <Toast error title={intl.formatMessage(messages.Unauthorized)} content={intl.formatMessage(messages.workingCopyErrorUnauthorized)} />, { toastId: 'workingCopyErrorUnauthorized', autoClose: 10000, }, ); } else { toast.error( <Toast error title={intl.formatMessage(messages.Error)} content={intl.formatMessage(messages.workingCopyGenericError)} />, { toastId: 'workingCopyGenericError', autoClose: 10000, }, ); } } }, [ workingCopy, prevWorkingCopyApplyLoading, prevWorkingCopyCreateLoading, prevWorkingCopyRemoveLoading, intl, ]); const path = getBaseUrl(pathname); const editAction = find(actions.object, { id: 'edit' }); const historyAction = find(actions.object, { id: 'history' }); const sharingAction = find(actions.object, { id: 'local_roles', }); const rulesAction = find(actions.object, { id: 'contentrules', }); const aliasesAction = find(actions.object_buttons, { id: 'redirection', }); const workingCopyCheckoutAction = find(actions.object_buttons, { id: 'iterate_checkout', }); const workingCopyCheckinAction = find(actions.object_buttons, { id: 'iterate_checkin', }); const dateOptions = { year: 'numeric', month: 'long', day: 'numeric', }; return ( <div className="menu-more pastanaga-menu" style={{ flex: props.theToolbar.current ? `0 0 ${props.theToolbar.current.getBoundingClientRect().width}px` : null, }} > <header> <h2>{content.title}</h2> <button className="more-user" aria-label={intl.formatMessage(messages.personalTools)} onClick={() => push('personalTools')} tabIndex={0} > <Icon name={userSVG} size="30px" title={intl.formatMessage(messages.personalTools)} /> </button> </header> <div className="pastanaga-menu-list"> <ul> <Pluggable name="toolbar-more-menu-list" /> <Plug pluggable="toolbar-more-menu-list" id="state"> {content['@type'] !== 'Plone Site' && ( // Plone Site does not have workflow <li className="state-select"> <Workflow pathname={path} /> </li> )} </Plug> <Plug pluggable="toolbar-more-menu-list" id="view"> {content['@type'] !== 'Plone Site' && ( // Plone Site does not have view (yet) <li className="display-select"> {editAction && <Display pathname={path} />} </li> )} </Plug> <Plug pluggable="toolbar-more-menu-list" id="history"> {content['@type'] !== 'Plone Site' && ( // Plone Site does not have history (yet) <li> <Link to={`${path}/historyview`}> <div> <span className="pastanaga-menu-label"> {historyAction?.title || intl.formatMessage(messages.history)} </span> <span className="pastanaga-menu-value" /> </div> <Icon name={rightArrowSVG} size="24px" /> </Link> </li> )} </Plug> <Plug pluggable="toolbar-more-menu-list" id="sharing"> {sharingAction && ( <li> <Link to={`${path}/sharing`}> {intl.formatMessage(messages.sharing)} <Icon name={rightArrowSVG} size="24px" /> </Link> </li> )} </Plug> <Plug pluggable="toolbar-more-menu-list" id="aliases"> {aliasesAction && ( <li> <Link to={`${path}/aliases`}> {intl.formatMessage(messages.aliases)} <Icon name={rightArrowSVG} size="24px" /> </Link> </li> )} </Plug> {path !== '' && !config.settings.excludeLinksAndReferencesMenuItem && ( <Plug pluggable="toolbar-more-menu-list" id="linkstoitems"> <li> <Link to={`${path}/links-to-item`}> {intl.formatMessage(messages.linkstoitem)} <Icon name={rightArrowSVG} size="24px" /> </Link> </li> </Plug> )} <Plug pluggable="toolbar-more-menu-list" id="rules"> {rulesAction && ( <li> <Link to={`${path}/rules`}> {intl.formatMessage(messages.rules)} <Icon name={rightArrowSVG} size="24px" /> </Link> </li> )} </Plug> </ul> </div> <Pluggable name="toolbar-more-manage-content"> {(pluggables) => ( <> {pluggables.length > 0 && ( <> <header> <h2>{intl.formatMessage(messages.manageContent)}</h2> </header> <div className="pastanaga-menu-list"> <ul> {pluggables.map((p, index) => ( <React.Fragment key={index}>{p()}</React.Fragment> ))} </ul> </div> </> )} </> )} </Pluggable> {workingCopyCheckoutAction && ( <Plug pluggable="toolbar-more-manage-content" id="workingcopy"> <li> <button aria-label={intl.formatMessage(messages.CreateWorkingCopy)} onClick={() => { dispatch(createWorkingCopy(path)).then((response) => { history.push(flattenToAppURL(response['@id'])); props.closeMenu(); }); }} > {intl.formatMessage(messages.CreateWorkingCopy)} <Icon name={rightArrowSVG} size="24px" /> </button> </li> </Plug> )} {workingCopyCheckinAction && ( <Plug pluggable="toolbar-more-manage-content" id="workingcopy"> <li> <button aria-label={intl.formatMessage(messages.applyWorkingCopy)} onClick={() => { dispatch(applyWorkingCopy(path)).then((response) => { history.push(flattenToAppURL(content.working_copy_of['@id'])); props.closeMenu(); toast.info( <Toast info title={intl.formatMessage(messages.workingAppliedTitle)} content={intl.formatMessage( messages.workingCopyAppliedBy, { creator: content.working_copy?.creator_name, date: ( <FormattedDate date={content.working_copy?.created} format={dateOptions} /> ), }, )} />, { toastId: 'workingcopyapplyinfo', autoClose: 10000, }, ); }); }} > {intl.formatMessage(messages.applyWorkingCopy)} <Icon name={applySVG} size="24px" title={intl.formatMessage(messages.applyWorkingCopy)} /> </button> </li> <li> <button aria-label={intl.formatMessage(messages.removeWorkingCopy)} onClick={() => { dispatch(removeWorkingCopy(path)).then((response) => { history.push(flattenToAppURL(content.working_copy_of['@id'])); props.closeMenu(); toast.info( <Toast info title={intl.formatMessage( messages.workingCopyRemovedTitle, )} />, { toastId: 'workingcopyremovednotice', autoClose: 10000, }, ); }); }} > {intl.formatMessage(messages.removeWorkingCopy)} <Icon name={removeSVG} size="24px" color="#e40166" title={intl.formatMessage(messages.removeWorkingCopy)} /> </button> </li> </Plug> )} {content.working_copy && !content.working_copy_of && ( <Plug pluggable="toolbar-more-manage-content" id="workingcopy"> <li> <Link to={flattenToAppURL(content.working_copy['@id'])} onClick={() => props.closeMenu()} > {intl.formatMessage(messages.viewWorkingCopy)} <Icon name={rightArrowSVG} size="24px" /> </Link> </li> </Plug> )} {editAction && config.settings.isMultilingual && ( <Plug pluggable="toolbar-more-manage-content" id="multilingual"> <li> <Link to={`${path}/manage-translations`}> {intl.formatMessage(messages.ManageTranslations)} <Icon name={rightArrowSVG} size="24px" /> </Link> </li> </Plug> )} </div> ); }; More.propTypes = { loadComponent: PropTypes.func.isRequired, closeMenu: PropTypes.func.isRequired, }; export default More;