diff --git a/.eslintrc.js b/.eslintrc.js
index 0cbd65cc..52469170 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -54,7 +54,7 @@ const defaultConfig = {
allowReferrer: true,
},
],
- }
+ },
};
const config = addonExtenders.reduce(
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b2dbcdd3..59b9fda8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,25 @@ All notable changes to this project will be documented in this file. Dates are d
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
+### [3.4.0](https://github.com/eea/volto-eea-website-theme/compare/3.3.0...3.4.0) - 11 December 2024
+
+#### :bug: Bug Fixes
+
+- fix(UniversaLink): use download prop in calculating anchor for downloadable files refs#281622 [nileshgulia1 - [`239b492`](https://github.com/eea/volto-eea-website-theme/commit/239b4928d39a9584fdc9ce3ef3f012dee23f840e)]
+- fix(ContextNavigation): Add memoization for View that triggered fetch on Edit page modifications [David Ichim - [`48fdf60`](https://github.com/eea/volto-eea-website-theme/commit/48fdf6089e21aeefacf927310b9a41ea9cd0e2be)]
+- fix(context-navigation): contentTypes choice list when creating a new object [David Ichim - [`d1ccc75`](https://github.com/eea/volto-eea-website-theme/commit/d1ccc7523b681da6aef04f91447cc5190d1c8bbf)]
+- fix(number-widget): from Volto core to parse values to int avoiding passing wrong values to restapi code such as context navigation [David Ichim - [`0d67686`](https://github.com/eea/volto-eea-website-theme/commit/0d6768652201d2b1dbf8e478613049e654b7476e)]
+- fix(context-navigation): missing content types on layout or inside tabs [David Ichim - [`0ab2a04`](https://github.com/eea/volto-eea-website-theme/commit/0ab2a049f2c94aaf6256611309b1bd6b7ff8a610)]
+- fix(report-navigation): use report-navigation class instead of smart-toc [David Ichim - [`f4d7f56`](https://github.com/eea/volto-eea-website-theme/commit/f4d7f56ae4e0dbdc04425cc86cfbafb9d527dd85)]
+- fix(report-navigation): remove unnecessary context navigation header fallback [David Ichim - [`877e520`](https://github.com/eea/volto-eea-website-theme/commit/877e520f78b8624b5f887b421979ef753a4d3c9e)]
+
+#### :house: Internal changes
+
+- chore: fix eslint config lint warning and avoid warning for active property within report navigation block list items [David Ichim - [`9b3b03c`](https://github.com/eea/volto-eea-website-theme/commit/9b3b03c36626ee5d4bb7b64b6413217ee8903873)]
+
+#### :hammer_and_wrench: Others
+
+- Update package.json [Ichim David - [`b14f4c4`](https://github.com/eea/volto-eea-website-theme/commit/b14f4c46c6fc6c99ec70072f260616136c85f095)]
### [3.3.0](https://github.com/eea/volto-eea-website-theme/compare/3.2.0...3.3.0) - 28 November 2024
#### :bug: Bug Fixes
diff --git a/package.json b/package.json
index feb59872..1a03bbe2 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@eeacms/volto-eea-website-theme",
- "version": "3.3.0",
+ "version": "3.4.0",
"description": "@eeacms/volto-eea-website-theme: Volto add-on",
"main": "src/index.js",
"author": "European Environment Agency: IDM2 A-Team",
diff --git a/src/components/manage/Blocks/ContextNavigation/ContextNavigationEdit.jsx b/src/components/manage/Blocks/ContextNavigation/ContextNavigationEdit.jsx
index 95795d95..06b90648 100644
--- a/src/components/manage/Blocks/ContextNavigation/ContextNavigationEdit.jsx
+++ b/src/components/manage/Blocks/ContextNavigation/ContextNavigationEdit.jsx
@@ -5,8 +5,21 @@ import BlockDataForm from '@plone/volto/components/manage/Form/BlockDataForm';
import ContextNavigationView from './ContextNavigationView';
+import { useSelector, shallowEqual } from 'react-redux';
+
+function arePropsEqual(oldProps, newProps) {
+ return (
+ newProps.selected === oldProps.selected &&
+ newProps.data === oldProps.data &&
+ newProps.id === oldProps.id
+ );
+}
+
const ContextNavigationFillEdit = (props) => {
- const contentTypes = props.properties?.['@components']?.types;
+ const contentTypes = useSelector(
+ (state) => state.types?.types || [],
+ shallowEqual,
+ );
const availableTypes = React.useMemo(
() => contentTypes?.map((type) => [type.id, type.title || type.name]),
[contentTypes],
@@ -42,4 +55,4 @@ const ContextNavigationFillEdit = (props) => {
);
};
-export default ContextNavigationFillEdit;
+export default React.memo(ContextNavigationFillEdit, arePropsEqual);
diff --git a/src/components/manage/Blocks/ContextNavigation/ContextNavigationView.jsx b/src/components/manage/Blocks/ContextNavigation/ContextNavigationView.jsx
index ebaea7ca..92d447e5 100644
--- a/src/components/manage/Blocks/ContextNavigation/ContextNavigationView.jsx
+++ b/src/components/manage/Blocks/ContextNavigation/ContextNavigationView.jsx
@@ -2,13 +2,27 @@ import React from 'react';
import { flattenToAppURL, withBlockExtensions } from '@plone/volto/helpers';
import DefaultTemplate from './variations/Default';
-const ContextNavigationView = (props = {}) => {
+function arePropsEqual(prevProps, nextProps) {
+ // check if component should be re-rendered
+ return (
+ prevProps.mode === nextProps.mode &&
+ prevProps.id === nextProps.id &&
+ prevProps.path === nextProps.path &&
+ JSON.stringify(prevProps.data) === JSON.stringify(nextProps.data)
+ );
+}
+
+const ContextNavigationView = React.memo((props = {}) => {
const { variation, data = {} } = props;
- const navProps = { ...data };
- const root_path = data?.root_node?.[0]?.['@id'];
- if (root_path) navProps['root_path'] = flattenToAppURL(root_path);
+ const navProps = React.useMemo(() => {
+ const props = { ...data };
+ const root_path = data?.root_node?.[0]?.['@id'];
+ if (root_path) props['root_path'] = flattenToAppURL(root_path);
+ return props;
+ }, [data]);
+
const Renderer = variation?.view ?? DefaultTemplate;
return ;
-};
+}, arePropsEqual);
export default withBlockExtensions(ContextNavigationView);
diff --git a/src/components/manage/Blocks/ContextNavigation/variations/ReportNavigation.jsx b/src/components/manage/Blocks/ContextNavigation/variations/ReportNavigation.jsx
index bbb8bab1..6476f4a4 100644
--- a/src/components/manage/Blocks/ContextNavigation/variations/ReportNavigation.jsx
+++ b/src/components/manage/Blocks/ContextNavigation/variations/ReportNavigation.jsx
@@ -4,19 +4,11 @@ import { Link as RouterLink } from 'react-router-dom';
import cx from 'classnames';
import { compose } from 'redux';
import { withRouter } from 'react-router';
-import { defineMessages, useIntl } from 'react-intl';
import { flattenToAppURL } from '@plone/volto/helpers';
import { UniversalLink, MaybeWrap } from '@plone/volto/components';
import { withContentNavigation } from '@plone/volto/components/theme/Navigation/withContentNavigation';
-const messages = defineMessages({
- navigation: {
- id: 'Navigation',
- defaultMessage: 'Navigation',
- },
-});
-
/**
* Handles click on summary links and closes parent details elements
* @param {Event} e - Click event
@@ -49,8 +41,7 @@ function renderNode(node, parentLevel) {
return (
- {navigation.has_custom_name ? (
+
diff --git a/src/customizations/volto/components/manage/UniversalLink/UniversalLink.jsx b/src/customizations/volto/components/manage/UniversalLink/UniversalLink.jsx
index d108be3f..036b11a1 100644
--- a/src/customizations/volto/components/manage/UniversalLink/UniversalLink.jsx
+++ b/src/customizations/volto/components/manage/UniversalLink/UniversalLink.jsx
@@ -68,7 +68,8 @@ const UniversalLink = ({
}
const isExternal = !isInternalURL(url);
- const isDownload = !isExternal && url && url.includes('@@download');
+ const isDownload =
+ (!isExternal && url && url.includes('@@download')) || download;
const isDisplayFile =
(!isExternal && url.includes('@@display-file')) || false;
diff --git a/src/customizations/volto/components/manage/Widgets/NumberWidget.jsx b/src/customizations/volto/components/manage/Widgets/NumberWidget.jsx
new file mode 100644
index 00000000..0e71575e
--- /dev/null
+++ b/src/customizations/volto/components/manage/Widgets/NumberWidget.jsx
@@ -0,0 +1,102 @@
+/**
+ * NumberWidget component.
+ * @module components/manage/Widgets/PassswordWidget
+ */
+
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Input } from 'semantic-ui-react';
+import { FormFieldWrapper } from '@plone/volto/components';
+import { injectIntl } from 'react-intl';
+
+/**
+ * NumberWidget component class.
+ *
+ * To use it, in schema properties, declare a field like:
+ *
+ * ```jsx
+ * {
+ * title: "Number",
+ * type: 'number',
+ * }
+ * ```
+ */
+const NumberWidget = (props) => {
+ const {
+ id,
+ value,
+ onChange,
+ onBlur,
+ onClick,
+ isDisabled,
+ maximum,
+ minimum,
+ placeholder,
+ step,
+ } = props;
+ return (
+
+
+ onChange(
+ id,
+ target.value === '' ? undefined : window.parseInt(target.value),
+ )
+ }
+ onBlur={({ target }) =>
+ onBlur(
+ id,
+ target.value === '' ? undefined : window.parseInt(target.value),
+ )
+ }
+ onClick={() => onClick()}
+ />
+
+ );
+};
+
+/**
+ * Property types.
+ * @property {Object} propTypes Property types.
+ * @static
+ */
+NumberWidget.propTypes = {
+ id: PropTypes.string.isRequired,
+ title: PropTypes.string.isRequired,
+ description: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
+ required: PropTypes.bool,
+ error: PropTypes.arrayOf(PropTypes.string),
+ value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ onChange: PropTypes.func.isRequired,
+ wrapped: PropTypes.bool,
+ maximum: PropTypes.number,
+ minimum: PropTypes.number,
+ step: PropTypes.number,
+ placeholder: PropTypes.string,
+};
+
+/**
+ * Default properties.
+ * @property {Object} defaultProps Default properties.
+ * @static
+ */
+NumberWidget.defaultProps = {
+ description: null,
+ required: false,
+ error: [],
+ value: null,
+ onChange: () => {},
+ onBlur: () => {},
+ onClick: () => {},
+};
+
+export default injectIntl(NumberWidget);
diff --git a/src/customizations/volto/components/manage/Widgets/NumberWidget.test.jsx b/src/customizations/volto/components/manage/Widgets/NumberWidget.test.jsx
new file mode 100644
index 00000000..d5494010
--- /dev/null
+++ b/src/customizations/volto/components/manage/Widgets/NumberWidget.test.jsx
@@ -0,0 +1,120 @@
+import React from 'react';
+import { render, screen, fireEvent } from '@testing-library/react';
+import { Provider } from 'react-intl-redux';
+import configureStore from 'redux-mock-store';
+import '@testing-library/jest-dom/extend-expect';
+
+import NumberWidget from './NumberWidget';
+
+const mockStore = configureStore();
+
+const store = mockStore({
+ intl: {
+ locale: 'en',
+ messages: {},
+ },
+});
+
+describe('NumberWidget', () => {
+ it('renders a number widget component', () => {
+ const onChange = jest.fn();
+ render(
+
+
+ ,
+ );
+
+ expect(screen.getByRole('spinbutton')).toBeInTheDocument();
+ });
+
+ describe('onChange behavior', () => {
+ it('converts string value to number on change', () => {
+ const onChange = jest.fn();
+ render(
+
+
+ ,
+ );
+
+ const input = screen.getByRole('spinbutton');
+ fireEvent.change(input, { target: { value: '42' } });
+
+ expect(onChange).toHaveBeenCalledWith('my-field', 42);
+ });
+
+ it('handles empty value correctly', () => {
+ const onChange = jest.fn();
+ render(
+
+
+ ,
+ );
+
+ const input = screen.getByRole('spinbutton');
+ fireEvent.change(input, { target: { value: '' } });
+
+ expect(onChange).toHaveBeenCalledWith('my-field', undefined);
+ });
+ });
+
+ describe('onBlur behavior', () => {
+ it('calls onBlur with the current value', () => {
+ const onBlur = jest.fn();
+ render(
+
+
+ ,
+ );
+
+ const input = screen.getByRole('spinbutton');
+ fireEvent.blur(input);
+
+ expect(onBlur).toHaveBeenCalled();
+ });
+ });
+
+ describe('validation constraints', () => {
+ it('respects minimum and maximum values', () => {
+ render(
+
+
+ ,
+ );
+
+ const input = screen.getByRole('spinbutton');
+ expect(input).toHaveAttribute('min', '1');
+ expect(input).toHaveAttribute('max', '100');
+ });
+
+ it('handles step attribute', () => {
+ render(
+
+
+ ,
+ );
+
+ const input = screen.getByRole('spinbutton');
+ expect(input).toHaveAttribute('step', '0.5');
+ });
+ });
+});
diff --git a/src/customizations/volto/components/manage/Widgets/README.md b/src/customizations/volto/components/manage/Widgets/README.md
index b52d0898..cdd5331d 100644
--- a/src/customizations/volto/components/manage/Widgets/README.md
+++ b/src/customizations/volto/components/manage/Widgets/README.md
@@ -1 +1,6 @@
-Customized ObjectBrowserWidget to preserve anchor links in the manually pasted internal URL.
+### Customizations
+
+- Customized ObjectBrowserWidget to preserve anchor links in the manually pasted internal URL.
+
+- Customized NumberWidget to parse the number input and convert it to a number.
+ [ichim-david refs #280463]