From 84002938270d679be5369f2775c07a38f5d59a3e Mon Sep 17 00:00:00 2001 From: David Glick Date: Fri, 16 Aug 2024 09:10:23 -0700 Subject: [PATCH 01/11] Allow enhancing the ContentsPropertiesModal schema (#6248) Co-authored-by: Steve Piercy --- packages/volto/news/6248.feature | 2 + .../components/manage/Contents/Contents.jsx | 3 + .../Contents/ContentsPropertiesModal.jsx | 137 +++++++++++------- packages/volto/src/config/index.js | 1 + 4 files changed, 91 insertions(+), 52 deletions(-) create mode 100644 packages/volto/news/6248.feature diff --git a/packages/volto/news/6248.feature b/packages/volto/news/6248.feature new file mode 100644 index 0000000000..8f809c135e --- /dev/null +++ b/packages/volto/news/6248.feature @@ -0,0 +1,2 @@ +The schema for the `ContentsPropertiesModal` can be enhanced using the `contentPropertiesSchemaEnhancer` setting. +Also, the properties form is now prepopulated with values if all selected items share the same value. @davisagli diff --git a/packages/volto/src/components/manage/Contents/Contents.jsx b/packages/volto/src/components/manage/Contents/Contents.jsx index 089d1eb669..807ce7cbd3 100644 --- a/packages/volto/src/components/manage/Contents/Contents.jsx +++ b/packages/volto/src/components/manage/Contents/Contents.jsx @@ -1532,6 +1532,9 @@ class Contents extends Component { onCancel={this.onPropertiesCancel} onOk={this.onPropertiesOk} items={this.state.selected} + values={map(this.state.selected, (id) => + find(this.state.items, { '@id': id }), + )} /> {this.state.showWorkflow && ( { - const { onCancel, onOk, open, items } = props; + const { onCancel, onOk, open, items, values } = props; const intl = useIntl(); const dispatch = useDispatch(); const request = useSelector((state) => state.content.update); const prevrequestloading = usePrevious(request.loading); + const baseSchema = { + fieldsets: [ + { + id: 'default', + title: intl.formatMessage(messages.default), + fields: [ + 'effective', + 'expires', + 'rights', + 'creators', + 'exclude_from_nav', + ], + }, + ], + properties: { + effective: { + description: intl.formatMessage(messages.effectiveDescription), + title: intl.formatMessage(messages.effectiveTitle), + type: 'string', + widget: 'datetime', + }, + expires: { + description: intl.formatMessage(messages.expiresDescription), + title: intl.formatMessage(messages.expiresTitle), + type: 'string', + widget: 'datetime', + }, + rights: { + description: intl.formatMessage(messages.rightsDescription), + title: intl.formatMessage(messages.rightsTitle), + type: 'string', + widget: 'textarea', + }, + creators: { + description: intl.formatMessage(messages.creatorsDescription), + title: intl.formatMessage(messages.creatorsTitle), + type: 'array', + }, + exclude_from_nav: { + description: intl.formatMessage(messages.excludeFromNavDescription), + title: intl.formatMessage(messages.excludeFromNavTitle), + type: 'boolean', + }, + }, + required: [], + }; + const schemaEnhancer = config.settings.contentPropertiesSchemaEnhancer; + let schema = schemaEnhancer + ? schemaEnhancer({ + schema: cloneDeepSchema(baseSchema), + intl, + }) + : baseSchema; + + const initialData = {}; + if (values?.length) { + for (const name of Object.keys(schema.properties)) { + const firstValue = values[0][name]; + // should not show floor or ceiling dates + if ( + (name === 'effective' && firstValue && firstValue <= '1970') || + (name === 'expires' && firstValue && firstValue >= '2499') + ) { + continue; + } + if (values.every((item) => item[name] === firstValue)) { + initialData[name] = firstValue; + } + } + } + useEffect(() => { if (prevrequestloading && request.loaded) { onOk(); @@ -78,13 +151,19 @@ const ContentsPropertiesModal = (props) => { }, [onOk, prevrequestloading, request.loaded]); const onSubmit = (data) => { - if (isEmpty(data)) { + let changes = {}; + for (const name of Object.keys(data)) { + if (data[name] !== initialData[name]) { + changes[name] = data[name]; + } + } + if (isEmpty(changes)) { onOk(); } else { dispatch( updateContent( items, - map(items, () => data), + map(items, () => changes), ), ); } @@ -97,54 +176,8 @@ const ContentsPropertiesModal = (props) => { onSubmit={onSubmit} onCancel={onCancel} title={intl.formatMessage(messages.properties)} - schema={{ - fieldsets: [ - { - id: 'default', - title: intl.formatMessage(messages.default), - fields: [ - 'effective', - 'expires', - 'rights', - 'creators', - 'exclude_from_nav', - ], - }, - ], - properties: { - effective: { - description: intl.formatMessage(messages.effectiveDescription), - title: intl.formatMessage(messages.effectiveTitle), - type: 'string', - widget: 'datetime', - }, - expires: { - description: intl.formatMessage(messages.expiresDescription), - title: intl.formatMessage(messages.expiresTitle), - type: 'string', - widget: 'datetime', - }, - rights: { - description: intl.formatMessage(messages.rightsDescription), - title: intl.formatMessage(messages.rightsTitle), - type: 'string', - widget: 'textarea', - }, - creators: { - description: intl.formatMessage(messages.creatorsDescription), - title: intl.formatMessage(messages.creatorsTitle), - type: 'array', - }, - exclude_from_nav: { - description: intl.formatMessage( - messages.excludeFromNavDescription, - ), - title: intl.formatMessage(messages.excludeFromNavTitle), - type: 'boolean', - }, - }, - required: [], - }} + schema={schema} + formData={initialData} /> ) ); diff --git a/packages/volto/src/config/index.js b/packages/volto/src/config/index.js index ddce0abd9f..9933a3c32b 100644 --- a/packages/volto/src/config/index.js +++ b/packages/volto/src/config/index.js @@ -168,6 +168,7 @@ let config = { ], showSelfRegistration: false, contentMetadataTagsImageField: 'image', + contentPropertiesSchemaEnhancer: null, hasWorkingCopySupport: false, maxUndoLevels: 200, // undo history size for the main form addonsInfo: addonsInfo, From fdcc8740d821d6c6c5a6669bd4a572d2784fd203 Mon Sep 17 00:00:00 2001 From: David Glick Date: Fri, 16 Aug 2024 11:57:07 -0700 Subject: [PATCH 02/11] Fix edge case in ContentsPropertiesModal (#6258) --- packages/volto/news/6258.bugfix | 1 + packages/volto/src/components/manage/Contents/Contents.jsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 packages/volto/news/6258.bugfix diff --git a/packages/volto/news/6258.bugfix b/packages/volto/news/6258.bugfix new file mode 100644 index 0000000000..44a9fe4b9a --- /dev/null +++ b/packages/volto/news/6258.bugfix @@ -0,0 +1 @@ +Fix `TypeError: values[0] is undefined` in Contents properties modal. @davisagli diff --git a/packages/volto/src/components/manage/Contents/Contents.jsx b/packages/volto/src/components/manage/Contents/Contents.jsx index 807ce7cbd3..58431e2dce 100644 --- a/packages/volto/src/components/manage/Contents/Contents.jsx +++ b/packages/volto/src/components/manage/Contents/Contents.jsx @@ -1534,7 +1534,7 @@ class Contents extends Component { items={this.state.selected} values={map(this.state.selected, (id) => find(this.state.items, { '@id': id }), - )} + ).filter((item) => item)} /> {this.state.showWorkflow && ( Date: Tue, 20 Aug 2024 17:41:14 -0700 Subject: [PATCH 03/11] Fix redirect (#6262) --- docs/source/contributing/install-docker.md | 2 +- packages/volto/news/6262.documentation | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 packages/volto/news/6262.documentation diff --git a/docs/source/contributing/install-docker.md b/docs/source/contributing/install-docker.md index 2902fa1b76..444af46462 100644 --- a/docs/source/contributing/install-docker.md +++ b/docs/source/contributing/install-docker.md @@ -1,4 +1,4 @@ -Install [Docker Desktop](https://docs.docker.com/get-docker/) for your operating system. +Install [Docker Desktop](https://docs.docker.com/get-started/get-docker/) for your operating system. Docker Desktop includes all Docker tools. ```{note} diff --git a/packages/volto/news/6262.documentation b/packages/volto/news/6262.documentation new file mode 100644 index 0000000000..0018dd38ef --- /dev/null +++ b/packages/volto/news/6262.documentation @@ -0,0 +1 @@ +Fix redirect to Docker documentation. @stevepiercy From 913670f0d0140a941eb3031f612d7d13d91dec7c Mon Sep 17 00:00:00 2001 From: Tisha Soumya Date: Fri, 23 Aug 2024 10:14:12 +0530 Subject: [PATCH 04/11] Refactor Change password (#5044) --- packages/volto/news/5044.feature | 1 + .../manage/Preferences/ChangePassword.jsx | 266 +++++++----------- .../Preferences/ChangePassword.stories.jsx | 41 +++ 3 files changed, 136 insertions(+), 172 deletions(-) create mode 100644 packages/volto/news/5044.feature create mode 100644 packages/volto/src/components/manage/Preferences/ChangePassword.stories.jsx diff --git a/packages/volto/news/5044.feature b/packages/volto/news/5044.feature new file mode 100644 index 0000000000..60833498c6 --- /dev/null +++ b/packages/volto/news/5044.feature @@ -0,0 +1 @@ +Refactor Preference/Change Password from class to functional component. @Tishasoumya-02 diff --git a/packages/volto/src/components/manage/Preferences/ChangePassword.jsx b/packages/volto/src/components/manage/Preferences/ChangePassword.jsx index 994a6aa632..62c87e13b2 100644 --- a/packages/volto/src/components/manage/Preferences/ChangePassword.jsx +++ b/packages/volto/src/components/manage/Preferences/ChangePassword.jsx @@ -1,25 +1,16 @@ -/** - * Change password component. - * @module components/manage/Preferences/ChangePassword - */ - -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { Helmet } from '@plone/volto/helpers'; -import { connect } from 'react-redux'; -import { compose } from 'redux'; -import { Link, withRouter } from 'react-router-dom'; +import { useSelector, useDispatch, shallowEqual } from 'react-redux'; +import { Link, useHistory, useLocation } from 'react-router-dom'; +import { defineMessages, useIntl } from 'react-intl'; import { createPortal } from 'react-dom'; -import { defineMessages, injectIntl } from 'react-intl'; import { Container } from 'semantic-ui-react'; import jwtDecode from 'jwt-decode'; import { toast } from 'react-toastify'; -import { Icon, Toast, Toolbar } from '@plone/volto/components'; -import { Form } from '@plone/volto/components/manage/Form'; +import { Helmet } from '@plone/volto/helpers'; +import { useClient } from '@plone/volto/hooks'; +import { Form, Icon, Toast, Toolbar } from '@plone/volto/components'; import { updatePassword } from '@plone/volto/actions'; import { getBaseUrl } from '@plone/volto/helpers'; - import backSVG from '@plone/volto/icons/back.svg'; const messages = defineMessages({ @@ -70,169 +61,100 @@ const messages = defineMessages({ }, }); -/** - * ChangePassword class. - * @class ChangePassword - * @extends Component - */ -class ChangePassword extends Component { - /** - * Property types. - * @property {Object} propTypes Property types. - * @static - */ - static propTypes = { - userId: PropTypes.string.isRequired, - loading: PropTypes.bool.isRequired, - updatePassword: PropTypes.func.isRequired, - pathname: PropTypes.string.isRequired, - }; - - /** - * Constructor - * @method constructor - * @param {Object} props Component properties - * @constructs ChangePassword - */ - constructor(props) { - super(props); - this.onCancel = this.onCancel.bind(this); - this.onSubmit = this.onSubmit.bind(this); - this.state = { isClient: false }; - } +const ChangePassword = () => { + const intl = useIntl(); + const dispatch = useDispatch(); + const isClient = useClient(); - /** - * Component did mount - * @method componentDidMount - * @returns {undefined} - */ - componentDidMount() { - this.setState({ isClient: true }); - } + const userId = useSelector( + (state) => + state.userSession.token ? jwtDecode(state.userSession.token).sub : '', + shallowEqual, + ); + const loading = useSelector((state) => state.users.update_password.loading); + const { pathname } = useLocation(); + const history = useHistory(); - /** - * Submit handler - * @method onSubmit - * @param {object} data Form data. - * @returns {undefined} - */ - onSubmit(data) { + const onSubmit = (data) => { if (data.newPassword === data.newPasswordRepeat) { - this.props.updatePassword( - this.props.userId, - data.oldPassword, - data.newPassword, - ); - toast.success( - , + dispatch(updatePassword(userId, data.oldPassword, data.newPassword)).then( + () => { + toast.success( + , + ); + }, ); } - } + }; - /** - * Cancel handler - * @method onCancel - * @returns {undefined} - */ - onCancel() { - this.props.history.goBack(); - } + const onCancel = () => { + history.goBack(); + }; - /** - * Render method. - * @method render - * @returns {string} Markup for the component. - */ - render() { - return ( - - -
+ + - {this.state.isClient && - createPortal( - - - - } - />, - document.getElementById('toolbar'), - )} - - ); - } -} + ], + properties: { + oldPassword: { + description: intl.formatMessage(messages.oldPasswordDescription), + title: intl.formatMessage(messages.oldPasswordTitle), + type: 'string', + widget: 'password', + }, + newPassword: { + description: intl.formatMessage(messages.newPasswordDescription), + title: intl.formatMessage(messages.newPasswordTitle), + type: 'string', + widget: 'password', + }, + newPasswordRepeat: { + description: intl.formatMessage( + messages.newPasswordRepeatDescription, + ), + title: intl.formatMessage(messages.newPasswordRepeatTitle), + type: 'string', + widget: 'password', + }, + }, + required: ['oldPassword', 'newPassword', 'newPasswordRepeat'], + }} + onSubmit={onSubmit} + onCancel={onCancel} + loading={loading} + /> + {isClient && + createPortal( + + + + } + />, + document.getElementById('toolbar'), + )} + + ); +}; -export default compose( - withRouter, - injectIntl, - connect( - (state, props) => ({ - userId: state.userSession.token - ? jwtDecode(state.userSession.token).sub - : '', - loading: state.users.update_password.loading, - pathname: props.location.pathname, - }), - { updatePassword }, - ), -)(ChangePassword); +export default ChangePassword; diff --git a/packages/volto/src/components/manage/Preferences/ChangePassword.stories.jsx b/packages/volto/src/components/manage/Preferences/ChangePassword.stories.jsx new file mode 100644 index 0000000000..8a7b090aad --- /dev/null +++ b/packages/volto/src/components/manage/Preferences/ChangePassword.stories.jsx @@ -0,0 +1,41 @@ +import { injectIntl } from 'react-intl'; +import React from 'react'; +import ChangePasswordComponent from './ChangePassword'; +import { RealStoreWrapper as Wrapper } from '@plone/volto/storybook'; +const IntlChangePasswordComponent = injectIntl(ChangePasswordComponent); + +function StoryComponent(args) { + return ( + +
+ + + ); +} + +export const ChangePassword = StoryComponent.bind({}); + +export default { + title: 'Public components/ChangePassword', + component: ChangePassword, + decorators: [ + (Story) => ( +
+ +
+ ), + ], + argTypes: {}, +}; From 73ebaca2959f03f75f0cd0ab37d5231cbe132f3b Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 23 Aug 2024 16:07:14 -0700 Subject: [PATCH 05/11] Revert rename of .gitkeep for consistency (#6232) --- packages/volto/news/{.keep => .gitkeep} | 0 packages/volto/news/6232.internal | 1 + 2 files changed, 1 insertion(+) rename packages/volto/news/{.keep => .gitkeep} (100%) create mode 100644 packages/volto/news/6232.internal diff --git a/packages/volto/news/.keep b/packages/volto/news/.gitkeep similarity index 100% rename from packages/volto/news/.keep rename to packages/volto/news/.gitkeep diff --git a/packages/volto/news/6232.internal b/packages/volto/news/6232.internal new file mode 100644 index 0000000000..d9d32916c7 --- /dev/null +++ b/packages/volto/news/6232.internal @@ -0,0 +1 @@ +Revert rename of `.gitkeep`. @stevepiercy From a79ebb495af30ab4f2ec1fbf3ffb440d72fb2556 Mon Sep 17 00:00:00 2001 From: Wesley Barroso Lopes Date: Sat, 24 Aug 2024 01:29:19 -0300 Subject: [PATCH 06/11] Add setting unwantedControlPanelsFields and use it in the function filterControlPanelsSchema (#4819) Co-authored-by: Steve Piercy --- .../configuration/settings-reference.md | 8 +- packages/volto/news/4819.feature | 1 + packages/volto/src/config/ControlPanels.js | 91 ++++++++++--------- packages/volto/src/config/index.js | 2 + packages/volto/test-setup-config.jsx | 2 + 5 files changed, 60 insertions(+), 44 deletions(-) create mode 100644 packages/volto/news/4819.feature diff --git a/docs/source/configuration/settings-reference.md b/docs/source/configuration/settings-reference.md index b42797413c..54eaebef20 100644 --- a/docs/source/configuration/settings-reference.md +++ b/docs/source/configuration/settings-reference.md @@ -206,7 +206,13 @@ controlpanels The group can be one of the default groups 'General', 'Content', 'Security', 'Add-on Configuration', 'Users and Groups' or a custom group. filterControlPanelsSchema - A schema factory for a control panel. It is used internally, to tweak the schemas provided by the controlpanel endpoint, to make them fit for Volto. + A schema factory for a control panel. + It is used internally, to tweak the schemas provided by the `@controlpanels` endpoint, making them fit for Volto. + It uses the `unwantedControlPanelsFields` setting. + +unwantedControlPanelsFields + Control panels' fields that are not used in Volto. + It is used internally by the `filterControlPanelsSchema` function. errorHandlers A list of error handlers that will be called when there is an unhandled exception. Each error handler is a function that diff --git a/packages/volto/news/4819.feature b/packages/volto/news/4819.feature new file mode 100644 index 0000000000..fe12773f69 --- /dev/null +++ b/packages/volto/news/4819.feature @@ -0,0 +1 @@ +Add setting `unwantedControlPanelsFields` and use it in the function `filterControlPanelsSchema`. @wesleybl diff --git a/packages/volto/src/config/ControlPanels.js b/packages/volto/src/config/ControlPanels.js index eade003293..8b7dca7461 100644 --- a/packages/volto/src/config/ControlPanels.js +++ b/packages/volto/src/config/ControlPanels.js @@ -19,6 +19,7 @@ import rulesSVG from '@plone/volto/icons/content-existing.svg'; import undoControlPanelSVG from '@plone/volto/icons/undo-control-panel.svg'; import linkSVG from '@plone/volto/icons/link.svg'; import relationsSVG from '@plone/volto/icons/ahead.svg'; +import config from '@plone/volto/registry'; export const controlPanelsIcons = { default: settingsSVG, @@ -53,62 +54,66 @@ export const filterControlPanels = (controlpanels = []) => { ); }; +export const unwantedControlPanelsFields = { + language: ['display_flags', 'always_show_selector'], + search: ['enable_livesearch'], + site: [ + 'display_publication_date_in_byline', + 'icon_visibility', + 'thumb_visibility', + 'no_thumbs_portlet', + 'no_thumbs_lists', + 'no_thumbs_summary', + 'no_thumbs_tables', + 'thumb_scale_portlet', + 'thumb_scale_listing', + 'thumb_scale_table', + 'thumb_scale_summary', + 'toolbar_position', + 'toolbar_logo', + 'default_page', + 'site_favicon', + 'site_favicon_mimetype', + 'exposeDCMetaTags', + 'enable_sitemap', + 'robots_txt', + 'webstats_js', + ], + editing: ['available_editors', 'default_editor', 'ext_editor'], + imaging: [ + 'highpixeldensity_scales', + 'quality_2x', + 'quality_3x', + 'picture_variants', + 'image_captioning', + ], + navigation: [ + 'generate_tabs', + 'navigation_depth', + 'sort_tabs_on', + 'sort_tabs_reversed', + 'sitemap_depth', + ], +}; + // Filters props.controlpanel.schema to only valid/relevant fields export const filterControlPanelsSchema = (controlpanel) => { const panelType = controlpanel['@id'].split('/').pop(); - const unwantedSettings = { - language: ['display_flags', 'always_show_selector'], - search: ['enable_livesearch'], - site: [ - 'display_publication_date_in_byline', - 'icon_visibility', - 'thumb_visibility', - 'no_thumbs_portlet', - 'no_thumbs_lists', - 'no_thumbs_summary', - 'no_thumbs_tables', - 'thumb_scale_portlet', - 'thumb_scale_listing', - 'thumb_scale_table', - 'thumb_scale_summary', - 'toolbar_position', - 'toolbar_logo', - 'default_page', - 'site_favicon', - 'site_favicon_mimetype', - 'exposeDCMetaTags', - 'enable_sitemap', - 'robots_txt', - 'webstats_js', - ], - editing: ['available_editors', 'default_editor', 'ext_editor'], - imaging: [ - 'highpixeldensity_scales', - 'quality_2x', - 'quality_3x', - 'picture_variants', - 'image_captioning', - ], - navigation: [ - 'generate_tabs', - 'navigation_depth', - 'sort_tabs_on', - 'sort_tabs_reversed', - 'sitemap_depth', - ], - }; + const { unwantedControlPanelsFields } = config.settings; // Creates modified version of properties object const newPropertiesObj = Object.fromEntries( Object.entries(controlpanel.schema.properties).filter( - ([key, val]) => !(unwantedSettings[panelType] || []).includes(key), + ([key, _val]) => + !(unwantedControlPanelsFields[panelType] || []).includes(key), ), ); // Filters props.controlpanel.schema.fieldsets.fields to only valid/relevant fields const filterFields = (fields) => { return fields.filter( - (field) => !(unwantedSettings[panelType] || []).includes(field), + (field) => + !(unwantedControlPanelsFields[panelType] || []).includes(field), ); }; // Creates modified version of fieldsets array diff --git a/packages/volto/src/config/index.js b/packages/volto/src/config/index.js index 9933a3c32b..5d22ca8bab 100644 --- a/packages/volto/src/config/index.js +++ b/packages/volto/src/config/index.js @@ -25,6 +25,7 @@ import { controlPanelsIcons, filterControlPanels, filterControlPanelsSchema, + unwantedControlPanelsFields, } from './ControlPanels'; import applyAddonConfiguration, { addonsInfo } from 'load-volto-addons'; @@ -153,6 +154,7 @@ let config = { controlPanelsIcons, filterControlPanels, filterControlPanelsSchema, + unwantedControlPanelsFields, externalRoutes: [ // URL to be considered as external // { diff --git a/packages/volto/test-setup-config.jsx b/packages/volto/test-setup-config.jsx index b42bd47dc4..7057109d68 100644 --- a/packages/volto/test-setup-config.jsx +++ b/packages/volto/test-setup-config.jsx @@ -20,6 +20,7 @@ import { controlPanelsIcons, filterControlPanels, filterControlPanelsSchema, + unwantedControlPanelsFields, } from '@plone/volto/config/ControlPanels'; import ListingBlockSchema from '@plone/volto/components/manage/Blocks/Listing/schema'; @@ -49,6 +50,7 @@ config.set('settings', { controlPanelsIcons, filterControlPanels, filterControlPanelsSchema, + unwantedControlPanelsFields, apiExpanders: [], downloadableObjects: ['File'], viewableInBrowserObjects: [], From 84b7ee825d82019882a9f323b6ab184551a965b6 Mon Sep 17 00:00:00 2001 From: Wesley Barroso Lopes Date: Mon, 26 Aug 2024 13:38:45 -0300 Subject: [PATCH 07/11] Fix initialValue block setting (#5978) Co-authored-by: David Glick --- packages/volto/news/5971.bugfix | 1 + .../manage/Blocks/Block/EditBlockWrapper.jsx | 18 +++++++++++++++++- packages/volto/src/helpers/Blocks/Blocks.js | 19 ++++++++++++------- .../volto/src/helpers/Blocks/Blocks.test.js | 15 +++++++++++++++ packages/volto/src/helpers/index.js | 1 + 5 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 packages/volto/news/5971.bugfix diff --git a/packages/volto/news/5971.bugfix b/packages/volto/news/5971.bugfix new file mode 100644 index 0000000000..71d9814b79 --- /dev/null +++ b/packages/volto/news/5971.bugfix @@ -0,0 +1 @@ +Fix `initialValue` block setting. @wesleybl diff --git a/packages/volto/src/components/manage/Blocks/Block/EditBlockWrapper.jsx b/packages/volto/src/components/manage/Blocks/Block/EditBlockWrapper.jsx index f09006f33a..31508127bf 100644 --- a/packages/volto/src/components/manage/Blocks/Block/EditBlockWrapper.jsx +++ b/packages/volto/src/components/manage/Blocks/Block/EditBlockWrapper.jsx @@ -1,6 +1,8 @@ import React from 'react'; import { Icon } from '@plone/volto/components'; import { + applyBlockInitialValue, + getBlocksFieldname, blockHasValue, buildStyleClassNamesFromData, buildStyleObjectFromData, @@ -111,7 +113,21 @@ const EditBlockWrapper = (props) => { if (blockHasValue(data)) { onSelectBlock(onInsertBlock(id, value)); } else { - onChangeBlock(id, value); + const blocksFieldname = getBlocksFieldname(properties); + const newFormData = applyBlockInitialValue({ + id, + value, + blocksConfig, + formData: { + ...properties, + [blocksFieldname]: { + ...properties[blocksFieldname], + [id]: value || null, + }, + }, + }); + const newValue = newFormData[blocksFieldname][id]; + onChangeBlock(id, newValue); } }} onMutateBlock={onMutateBlock} diff --git a/packages/volto/src/helpers/Blocks/Blocks.js b/packages/volto/src/helpers/Blocks/Blocks.js index 9a2a5b405a..88f1a5ed15 100644 --- a/packages/volto/src/helpers/Blocks/Blocks.js +++ b/packages/volto/src/helpers/Blocks/Blocks.js @@ -161,7 +161,7 @@ export function addBlock(formData, type, index, blocksConfig) { return [ id, - _applyBlockInitialValue({ + applyBlockInitialValue({ id, value, blocksConfig, @@ -197,8 +197,12 @@ export function addBlock(formData, type, index, blocksConfig) { * to call `onChangeBlock` at their creation time, as this is prone to racing * issue on block data storage. */ -const _applyBlockInitialValue = ({ id, value, blocksConfig, formData }) => { - const blocksFieldname = getBlocksFieldname(formData); +export const applyBlockInitialValue = ({ + id, + value, + blocksConfig, + formData, +}) => { const type = value['@type']; blocksConfig = blocksConfig || config.blocks.blocksConfig; @@ -208,6 +212,7 @@ const _applyBlockInitialValue = ({ id, value, blocksConfig, formData }) => { value, formData, }); + const blocksFieldname = getBlocksFieldname(formData); formData[blocksFieldname][id] = value; } @@ -238,7 +243,7 @@ export function mutateBlock(formData, id, value, blocksConfig) { const trailId = formData[blocksLayoutFieldname].items[index]; if (trailId) { const block = formData[blocksFieldname][trailId]; - newFormData = _applyBlockInitialValue({ + newFormData = applyBlockInitialValue({ id, value, blocksConfig, @@ -256,7 +261,7 @@ export function mutateBlock(formData, id, value, blocksConfig) { } const idTrailingBlock = uuid(); - newFormData = _applyBlockInitialValue({ + newFormData = applyBlockInitialValue({ id, value, blocksConfig, @@ -307,8 +312,8 @@ export function insertBlock( }); const newBlockId = uuid(); - const newFormData = _applyBlockInitialValue({ - id, + const newFormData = applyBlockInitialValue({ + id: newBlockId, value, blocksConfig, formData: { diff --git a/packages/volto/src/helpers/Blocks/Blocks.test.js b/packages/volto/src/helpers/Blocks/Blocks.test.js index e97fbf4edf..900171717f 100644 --- a/packages/volto/src/helpers/Blocks/Blocks.test.js +++ b/packages/volto/src/helpers/Blocks/Blocks.test.js @@ -511,6 +511,21 @@ describe('Blocks', () => { }); }); + it('initializes data for new block with initialValue in insertBlock', () => { + const [newId, form] = insertBlock( + { + blocks: { a: { value: 1 }, b: { value: 2 } }, + blocks_layout: { items: ['a', 'b'] }, + }, + 'b', + { '@type': 'dummyText' }, + ); + expect(form.blocks[newId]).toStrictEqual({ + '@type': 'dummyText', + marker: true, + }); + }); + it('initializes data for new block based on schema defaults', () => { const [newId, form] = addBlock( { diff --git a/packages/volto/src/helpers/index.js b/packages/volto/src/helpers/index.js index a7f2625f09..6ec85dcec8 100644 --- a/packages/volto/src/helpers/index.js +++ b/packages/volto/src/helpers/index.js @@ -40,6 +40,7 @@ export { getLanguageIndependentFields, } from '@plone/volto/helpers/Content/Content'; export { + applyBlockInitialValue, addBlock, insertBlock, blockHasValue, From c8506159b0e25633572cf1c8169a7a5dc0494b9e Mon Sep 17 00:00:00 2001 From: Wesley Barroso Lopes Date: Mon, 26 Aug 2024 15:45:49 -0300 Subject: [PATCH 08/11] Move Tags component to the slot belowContent (#6269) Co-authored-by: David Glick --- docs/source/upgrade-guide/index.md | 3 ++ packages/volto/news/6269.breaking | 1 + .../volto/src/components/theme/Tags/Tags.jsx | 29 ++++++++++++------- .../src/components/theme/Tags/Tags.test.jsx | 20 ++++++------- .../Tags/__snapshots__/Tags.test.jsx.snap | 2 +- .../volto/src/components/theme/View/View.jsx | 6 ---- packages/volto/src/config/index.js | 13 +++++++++ packages/volto/src/config/slots.js | 12 ++++++++ packages/volto/test-setup-config.jsx | 1 + 9 files changed, 59 insertions(+), 28 deletions(-) create mode 100644 packages/volto/news/6269.breaking create mode 100644 packages/volto/src/config/slots.js diff --git a/docs/source/upgrade-guide/index.md b/docs/source/upgrade-guide/index.md index 8359867145..a21f21cf25 100644 --- a/docs/source/upgrade-guide/index.md +++ b/docs/source/upgrade-guide/index.md @@ -445,6 +445,9 @@ schema: { // rest of the form definition... ``` +### Tags in slot + +The Tags component has been moved to the `belowContent` slot. It now receives the `content` property instead of the `tags` property. (volto-upgrade-guide-17.x.x)= diff --git a/packages/volto/news/6269.breaking b/packages/volto/news/6269.breaking new file mode 100644 index 0000000000..a361dd27fc --- /dev/null +++ b/packages/volto/news/6269.breaking @@ -0,0 +1 @@ +Move `Tags` component to the slot `belowContent`. @wesleybl diff --git a/packages/volto/src/components/theme/Tags/Tags.jsx b/packages/volto/src/components/theme/Tags/Tags.jsx index 07d100f97b..4ff1584df6 100644 --- a/packages/volto/src/components/theme/Tags/Tags.jsx +++ b/packages/volto/src/components/theme/Tags/Tags.jsx @@ -3,19 +3,25 @@ * @module components/theme/Tags/Tags */ -import React from 'react'; import { Link } from 'react-router-dom'; import PropTypes from 'prop-types'; import { Container } from 'semantic-ui-react'; +import config from '@plone/registry'; /** - * Tags component class. + * Tags component. * @function Tags - * @param {array} tags Array of tags. - * @returns {string} Markup of the component. + * @param {Object} props Component properties. + * @param {Object} props.content Content object that may contain subjects. + * @param {Array} [props.content.subjects] Optional array of tags (subjects). + * @returns {JSX.Element|null} Markup of the component or null if no tags are available. */ -const Tags = ({ tags }) => - tags && tags.length > 0 ? ( +const Tags = ({ content }) => { + const tags = content?.subjects || []; + + if (!config.settings.showTags || !tags.length) return null; + + return ( {tags.map((tag) => ( @@ -23,9 +29,8 @@ const Tags = ({ tags }) => ))} - ) : ( - ); +}; /** * Property types. @@ -33,7 +38,9 @@ const Tags = ({ tags }) => * @static */ Tags.propTypes = { - tags: PropTypes.arrayOf(PropTypes.string), + content: PropTypes.shape({ + subjects: PropTypes.arrayOf(PropTypes.string), + }), }; /** @@ -42,7 +49,9 @@ Tags.propTypes = { * @static */ Tags.defaultProps = { - tags: null, + content: { + subjects: [], + }, }; export default Tags; diff --git a/packages/volto/src/components/theme/Tags/Tags.test.jsx b/packages/volto/src/components/theme/Tags/Tags.test.jsx index 0877336b8f..8e3710abaf 100644 --- a/packages/volto/src/components/theme/Tags/Tags.test.jsx +++ b/packages/volto/src/components/theme/Tags/Tags.test.jsx @@ -1,4 +1,3 @@ -import React from 'react'; import renderer from 'react-test-renderer'; import configureStore from 'redux-mock-store'; import { Provider } from 'react-intl-redux'; @@ -9,17 +8,22 @@ import Tags from './Tags'; const mockStore = configureStore(); describe('Tags', () => { - it('renders without tags', () => { - const store = mockStore({ + let store; + + beforeEach(() => { + store = mockStore({ intl: { locale: 'en', messages: {}, }, }); + }); + + it('renders without tags', () => { const component = renderer.create( - + , ); @@ -28,16 +32,10 @@ describe('Tags', () => { }); it('renders with tags', () => { - const store = mockStore({ - intl: { - locale: 'en', - messages: {}, - }, - }); const component = renderer.create( - + , ); diff --git a/packages/volto/src/components/theme/Tags/__snapshots__/Tags.test.jsx.snap b/packages/volto/src/components/theme/Tags/__snapshots__/Tags.test.jsx.snap index ce132b9dd1..e792d0bce4 100644 --- a/packages/volto/src/components/theme/Tags/__snapshots__/Tags.test.jsx.snap +++ b/packages/volto/src/components/theme/Tags/__snapshots__/Tags.test.jsx.snap @@ -21,4 +21,4 @@ exports[`Tags renders with tags 1`] = `
`; -exports[`Tags renders without tags 1`] = ``; +exports[`Tags renders without tags 1`] = `null`; diff --git a/packages/volto/src/components/theme/View/View.jsx b/packages/volto/src/components/theme/View/View.jsx index 36c0341cc5..6f4ff0f5e7 100644 --- a/packages/volto/src/components/theme/View/View.jsx +++ b/packages/volto/src/components/theme/View/View.jsx @@ -15,7 +15,6 @@ import qs from 'query-string'; import { ContentMetadataTags, Comments, - Tags, Toolbar, } from '@plone/volto/components'; import { listActions, getContent } from '@plone/volto/actions'; @@ -256,11 +255,6 @@ class View extends Component { history={this.props.history} /> - {config.settings.showTags && - this.props.content.subjects && - this.props.content.subjects.length > 0 && ( - - )} {this.props.content.allow_discussion && ( )} diff --git a/packages/volto/src/config/index.js b/packages/volto/src/config/index.js index 5d22ca8bab..5dea078810 100644 --- a/packages/volto/src/config/index.js +++ b/packages/volto/src/config/index.js @@ -18,6 +18,7 @@ import { import { components } from './Components'; import { loadables } from './Loadables'; import { workflowMapping } from './Workflows'; +import slots from './slots'; import { contentIcons } from './ContentIcons'; import { styleClassNameConverters, styleClassNameExtenders } from './Style'; @@ -245,6 +246,18 @@ ConfigRegistry.components = config.components; ConfigRegistry.slots = config.slots; ConfigRegistry.utilities = config.utilities; +// Register slots +Object.entries(slots).forEach(([slotName, components]) => { + components.forEach(({ name, component, predicates = [] }) => { + ConfigRegistry.registerSlotComponent({ + slot: slotName, + name, + component, + predicates, + }); + }); +}); + registerValidators(ConfigRegistry); applyAddonConfiguration(ConfigRegistry); diff --git a/packages/volto/src/config/slots.js b/packages/volto/src/config/slots.js new file mode 100644 index 0000000000..3e2411c08d --- /dev/null +++ b/packages/volto/src/config/slots.js @@ -0,0 +1,12 @@ +import { Tags } from '@plone/volto/components'; + +const slots = { + belowContent: [ + { + name: 'tags', + component: Tags, + }, + ], +}; + +export default slots; diff --git a/packages/volto/test-setup-config.jsx b/packages/volto/test-setup-config.jsx index 7057109d68..59cb64257f 100644 --- a/packages/volto/test-setup-config.jsx +++ b/packages/volto/test-setup-config.jsx @@ -32,6 +32,7 @@ config.set('settings', { defaultLanguage: 'en', supportedLanguages: ['en'], defaultPageSize: 25, + showTags: true, isMultilingual: false, nonContentRoutes, contentIcons: contentIcons, From 29f130cd2585749bfd5d4625e81695b3fecedba6 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Mon, 26 Aug 2024 14:14:23 -0700 Subject: [PATCH 09/11] Trivial MyST syntax enhancement --- docs/source/upgrade-guide/index.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/source/upgrade-guide/index.md b/docs/source/upgrade-guide/index.md index a21f21cf25..b199673aea 100644 --- a/docs/source/upgrade-guide/index.md +++ b/docs/source/upgrade-guide/index.md @@ -447,7 +447,8 @@ schema: { ### Tags in slot -The Tags component has been moved to the `belowContent` slot. It now receives the `content` property instead of the `tags` property. +The `Tags` component has been moved to the `belowContent` slot. +It now receives the `content` property instead of the `tags` property. (volto-upgrade-guide-17.x.x)= From 64b7d04f08275475aee01df47c137ac9a7511960 Mon Sep 17 00:00:00 2001 From: Tiberiu Ichim Date: Wed, 28 Aug 2024 01:13:53 +0300 Subject: [PATCH 10/11] When user changes location, set the userSession.token value based on cookie (#6079) Co-authored-by: ichim-david Co-authored-by: Steve Piercy Co-authored-by: David Glick --- packages/volto/news/6071.bugfix | 1 + packages/volto/src/middleware/api.js | 6 ++- packages/volto/src/middleware/index.js | 1 + .../volto/src/middleware/userSessionReset.js | 46 +++++++++++++++++++ packages/volto/src/store.js | 2 + 5 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 packages/volto/news/6071.bugfix create mode 100644 packages/volto/src/middleware/userSessionReset.js diff --git a/packages/volto/news/6071.bugfix b/packages/volto/news/6071.bugfix new file mode 100644 index 0000000000..a6ac978c83 --- /dev/null +++ b/packages/volto/news/6071.bugfix @@ -0,0 +1 @@ +When user changes location, set the `userSession.token` value based on cookie. This fixes the login status not being properly determined by the application. @tiberiu-ichim diff --git a/packages/volto/src/middleware/api.js b/packages/volto/src/middleware/api.js index fa23d37dbb..85465658f6 100644 --- a/packages/volto/src/middleware/api.js +++ b/packages/volto/src/middleware/api.js @@ -222,7 +222,8 @@ const apiMiddlewareFactory = } const { settings } = config; - if (getState().apierror.connectionRefused) { + const state = getState(); + if (state.apierror.connectionRefused) { next({ ...rest, type: RESET_APIERROR, @@ -232,7 +233,7 @@ const apiMiddlewareFactory = const lang = result?.language?.token; if ( lang && - getState().intl.locale !== toReactIntlLang(lang) && + state.intl.locale !== toReactIntlLang(lang) && !subrequest && config.settings.supportedLanguages.includes(lang) ) { @@ -244,6 +245,7 @@ const apiMiddlewareFactory = }); } } + if (type === LOGIN && settings.websockets) { const cookies = new Cookies(); cookies.set( diff --git a/packages/volto/src/middleware/index.js b/packages/volto/src/middleware/index.js index f8f6fae956..78d0eea124 100644 --- a/packages/volto/src/middleware/index.js +++ b/packages/volto/src/middleware/index.js @@ -11,3 +11,4 @@ export { protectLoadEnd, loadProtector, } from '@plone/volto/middleware/storeProtectLoadUtils'; +export { default as userSessionReset } from './userSessionReset'; diff --git a/packages/volto/src/middleware/userSessionReset.js b/packages/volto/src/middleware/userSessionReset.js new file mode 100644 index 0000000000..34598d432c --- /dev/null +++ b/packages/volto/src/middleware/userSessionReset.js @@ -0,0 +1,46 @@ +import Cookies from 'universal-cookie'; + +const LOCATION_CHANGE = '@@router/LOCATION_CHANGE'; + +const userSessionReset = + ({ dispatch, getState }) => + (next) => + (action) => { + if (typeof action === 'function') { + return next(action); + } + + switch (action.type) { + case LOCATION_CHANGE: + if (action.request?.subrequest || __SERVER__) { + return next(action); + } + + const cookies = new Cookies(); + const token = cookies.get('auth_token'); + const state = getState(); + + if (token && !state.userSession?.token) { + const loginAction = { + type: 'LOGIN_SUCCESS', + result: { + token, + }, + }; + dispatch(loginAction); + } else if (!token && state.userSession?.token) { + const logoutAction = { + type: 'LOGOUT_SUCCESS', + result: { + token, + }, + }; + dispatch(logoutAction); + } + return next(action); + default: + return next(action); + } + }; + +export default userSessionReset; diff --git a/packages/volto/src/store.js b/packages/volto/src/store.js index 6e40de35d7..7f0e33ce90 100644 --- a/packages/volto/src/store.js +++ b/packages/volto/src/store.js @@ -13,6 +13,7 @@ import { protectLoadStart, protectLoadEnd, loadProtector, + userSessionReset, } from '@plone/volto/middleware'; const configureStore = (initialState, history, apiHelper) => { @@ -22,6 +23,7 @@ const configureStore = (initialState, history, apiHelper) => { routerMiddleware(history), thunk, ...(apiHelper ? [api(apiHelper)] : []), + userSessionReset, protectLoadEnd, ...(__CLIENT__ ? [save({ states: config.settings.persistentReducers, debounce: 500 })] From a1120fb4933796354290fd7c3b9f605084f13d0b Mon Sep 17 00:00:00 2001 From: David Glick Date: Wed, 28 Aug 2024 10:59:45 -0700 Subject: [PATCH 11/11] Fix error in SortOn component when there is no sort selected. (#6273) --- packages/volto/news/6273.bugfix | 1 + .../src/components/manage/Blocks/Search/components/SortOn.jsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 packages/volto/news/6273.bugfix diff --git a/packages/volto/news/6273.bugfix b/packages/volto/news/6273.bugfix new file mode 100644 index 0000000000..6caa1e8e5e --- /dev/null +++ b/packages/volto/news/6273.bugfix @@ -0,0 +1 @@ +Fix error in `SortOn` component when no sort is selected. @davisagli diff --git a/packages/volto/src/components/manage/Blocks/Search/components/SortOn.jsx b/packages/volto/src/components/manage/Blocks/Search/components/SortOn.jsx index 54a0730e6f..08d268969b 100644 --- a/packages/volto/src/components/manage/Blocks/Search/components/SortOn.jsx +++ b/packages/volto/src/components/manage/Blocks/Search/components/SortOn.jsx @@ -60,7 +60,7 @@ const SortOn = (props) => { const showSelectField = sortOnOptions.length > 1; if (!showSelectField && !activeSortOn) { - return; + return null; } const value = { value: activeSortOn || intl.formatMessage(messages.noSelection),