@eeacms/volto-n2k 1.1.12 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
+ ### [1.2.1](https://github.com/eea/volto-n2k/compare/1.2.0...1.2.1) - 3 February 2026
8
+
9
+ #### :bug: Bug Fixes
10
+
11
+ - fix: edit style [nileshgulia1 - [`a823420`](https://github.com/eea/volto-n2k/commit/a823420dfc8cd6fc4421d2888028faaf25d2fffa)]
12
+
13
+ ### [1.2.0](https://github.com/eea/volto-n2k/compare/1.1.12...1.2.0) - 3 February 2026
14
+
15
+ #### :rocket: New Features
16
+
17
+ - feat: add new variation for ImageGallery for Case Studies CT [nileshgulia1 - [`48173c5`](https://github.com/eea/volto-n2k/commit/48173c5626b94597e57af236ecb14a55383f63b1)]
18
+ - feat: add copyright text to tiles images refs-296409 [nileshgulia1 - [`614187b`](https://github.com/eea/volto-n2k/commit/614187b2d5c2cffcb1e2bf74ca72e88b2886520d)]
19
+
20
+ #### :hammer_and_wrench: Others
21
+
22
+ - bump v1.2.0 [nileshgulia1 - [`f94e2ff`](https://github.com/eea/volto-n2k/commit/f94e2ff9b244c126dff1930dcef265567712f4bc)]
7
23
  ### [1.1.12](https://github.com/eea/volto-n2k/compare/1.1.11...1.1.12) - 27 January 2026
8
24
 
9
25
  #### :hammer_and_wrench: Others
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-n2k",
3
- "version": "1.1.12",
3
+ "version": "1.2.1",
4
4
  "description": "volto-n2k: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: IDM2 A-Team",
@@ -1,19 +1,18 @@
1
1
  import React from 'react';
2
2
  import { SidebarPortal } from '@plone/volto/components';
3
- import InlineForm from '@plone/volto/components/manage/Form/InlineForm';
3
+ import BlockDataForm from '@plone/volto/components/manage/Form/BlockDataForm';
4
4
  import TilesImagesView from './View';
5
5
  import getSchema from './schema';
6
- import './styles.less';
7
6
 
8
7
  const Edit = (props) => {
9
- const { selected = false } = props;
10
- const schema = getSchema();
8
+ const { selected = false, data = {} } = props;
9
+ const schema = getSchema({ formData: data });
11
10
 
12
11
  return (
13
12
  <>
14
13
  <TilesImagesView {...props} mode="edit" />
15
14
  <SidebarPortal selected={selected}>
16
- <InlineForm
15
+ <BlockDataForm
17
16
  schema={schema}
18
17
  title={schema.title}
19
18
  onChangeField={(id, value) => {
@@ -22,6 +21,8 @@ const Edit = (props) => {
22
21
  [id]: value,
23
22
  });
24
23
  }}
24
+ onChangeBlock={props.onChangeBlock}
25
+ block={props.block}
25
26
  formData={props.data}
26
27
  />
27
28
  </SidebarPortal>
@@ -1,38 +1,16 @@
1
1
  import React from 'react';
2
- import { UniversalLink } from '@plone/volto/components';
3
- import cx from 'classnames';
4
- import './styles.less';
2
+ import { withBlockExtensions } from '@plone/volto/helpers/Extensions';
3
+ import config from '@plone/volto/registry';
5
4
 
6
5
  const View = (props) => {
7
- const { data = {}, mode = 'view' } = props;
8
- const images = data.images || [];
6
+ const { mode = 'view', variation } = props;
9
7
 
10
- return (
11
- <div className={cx('tiles-images', mode, data.theme || 'light')}>
12
- {mode === 'edit' && !images.length ? <p>Tiles images block</p> : ''}
13
- {images.map((image) => (
14
- <p
15
- key={`tile-${image.title}`}
16
- className={cx('p-image', {
17
- 'with-border': data.hasBorder ?? true,
18
- 'rounded-border': data.rounded ?? true,
19
- })}
20
- >
21
- <UniversalLink href={image.link || '#'} title={image.title}>
22
- <img
23
- src={`${image.image}/@@images/image/mini`}
24
- alt={image.title}
25
- style={
26
- data.size
27
- ? { width: `${data.size}px`, height: `${data.size}px` }
28
- : {}
29
- }
30
- />
31
- </UniversalLink>
32
- </p>
33
- ))}
34
- </div>
35
- );
8
+ const variations =
9
+ config.blocks?.blocksConfig['tiles_images']?.variations || [];
10
+ const defaultVariation = variations.filter((item) => item.isDefault)?.[0];
11
+ const Template = variation?.template ?? defaultVariation?.template ?? null;
12
+
13
+ return <Template {...props} mode={mode} />;
36
14
  };
37
15
 
38
- export default View;
16
+ export default withBlockExtensions(View);
@@ -1,4 +1,6 @@
1
1
  import TilesImagesEdit from './Edit';
2
+ import ImageGallery from './variations/ImageGallery/ImageGallery';
3
+ import DefaultView from './variations/Default/Default';
2
4
  import TilesImagesView from './View';
3
5
  import worldSVG from '@plone/volto/icons/world.svg';
4
6
 
@@ -18,6 +20,20 @@ export default function applyConfig(config) {
18
20
  addPermission: [],
19
21
  view: [],
20
22
  },
23
+ variations: [
24
+ {
25
+ id: 'default',
26
+ isDefault: true,
27
+ title: 'Default',
28
+ template: DefaultView,
29
+ },
30
+ {
31
+ id: 'imageGallery',
32
+ isDefault: false,
33
+ title: 'ImageGallery',
34
+ template: ImageGallery,
35
+ },
36
+ ],
21
37
  };
22
38
  return config;
23
39
  }
@@ -1,15 +1,20 @@
1
- const imageSchema = {
1
+ const imageSchema = ({ formData }) => ({
2
2
  title: 'Image',
3
3
  fieldsets: [
4
4
  {
5
5
  id: 'default',
6
6
  title: 'Default',
7
- fields: ['image', 'link', 'title'],
7
+ fields: [
8
+ 'image',
9
+ ...(formData?.variation !== 'imageGallery' ? ['link'] : []),
10
+ 'title',
11
+ 'copyright',
12
+ ],
8
13
  },
9
14
  ],
10
15
  properties: {
11
16
  image: {
12
- title: 'image',
17
+ title: 'Image',
13
18
  widget: 'object_by_path',
14
19
  },
15
20
  link: {
@@ -19,11 +24,14 @@ const imageSchema = {
19
24
  title: {
20
25
  title: 'Title',
21
26
  },
27
+ copyright: {
28
+ title: 'Copyright',
29
+ },
22
30
  },
23
- required: ['url', 'title'],
24
- };
31
+ required: [],
32
+ });
25
33
 
26
- export default function getSchema() {
34
+ export default function getSchema({ formData }) {
27
35
  return {
28
36
  title: 'Tiles images',
29
37
  fieldsets: [
@@ -32,11 +40,15 @@ export default function getSchema() {
32
40
  title: 'Default',
33
41
  fields: ['theme', 'images'],
34
42
  },
35
- {
36
- id: 'advanced',
37
- title: 'Advanced',
38
- fields: ['size', 'hasBorder', 'rounded'],
39
- },
43
+ ...(formData?.variation === 'default'
44
+ ? [
45
+ {
46
+ id: 'advanced',
47
+ title: 'Advanced',
48
+ fields: ['size', 'hasBorder', 'rounded'],
49
+ },
50
+ ]
51
+ : []),
40
52
  ],
41
53
  properties: {
42
54
  size: {
@@ -65,7 +77,7 @@ export default function getSchema() {
65
77
  images: {
66
78
  title: 'Images',
67
79
  widget: 'object_list',
68
- schema: imageSchema,
80
+ schema: imageSchema({ formData }),
69
81
  },
70
82
  },
71
83
  required: [],
@@ -0,0 +1,36 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ Copyright.propTypes = {
5
+ copyrightPosition: PropTypes.oneOf(['left', 'right']),
6
+ };
7
+
8
+ function Copyright({ children, ...rest }) {
9
+ return (
10
+ <div className={`eea copyright ${rest.copyrightPosition}`}>
11
+ <div className={'wrapper'}>{children}</div>
12
+ </div>
13
+ );
14
+ }
15
+
16
+ Copyright.Prefix = ({ children, ...rest }) =>
17
+ children ? (
18
+ <span {...rest} className={'icon-prefix'}>
19
+ {children}
20
+ </span>
21
+ ) : (
22
+ ''
23
+ );
24
+
25
+ Copyright.Icon = ({ children, ...rest }) => (
26
+ <span {...rest} className={'icon-wrapper'}>
27
+ {children}
28
+ </span>
29
+ );
30
+
31
+ Copyright.Text = ({ children, ...rest }) => (
32
+ <span {...rest} className={'icon-content'}>
33
+ {children}
34
+ </span>
35
+ );
36
+ export default Copyright;
@@ -0,0 +1,55 @@
1
+ import React from 'react';
2
+ import { UniversalLink } from '@plone/volto/components';
3
+ import cx from 'classnames';
4
+ import './styles.less';
5
+ import Copyright from './Copyright';
6
+
7
+ const DefaultView = (props) => {
8
+ const { mode = 'view', data = {} } = props;
9
+
10
+ const images = data.images || [];
11
+
12
+ return (
13
+ <div className={cx('tiles-images', mode, data.theme || 'light')}>
14
+ {mode === 'edit' && !images.length ? <p>Tiles images block</p> : ''}
15
+ {images.map((image) => {
16
+ const { copyright } = image;
17
+ return (
18
+ <p
19
+ key={`tile-${image.title}`}
20
+ className={cx('p-image', {
21
+ 'with-border': data.hasBorder ?? true,
22
+ 'rounded-border': data.rounded ?? true,
23
+ })}
24
+ >
25
+ <UniversalLink href={image.link || '#'} title={image.title}>
26
+ <>
27
+ <img
28
+ src={`${image.image}/@@images/image/mini`}
29
+ alt={image.title}
30
+ style={
31
+ data.size
32
+ ? { width: `${data.size}px`, height: `${data.size}px` }
33
+ : {}
34
+ }
35
+ />
36
+ <div className={`copyright-wrapper ${'left'}`}>
37
+ {copyright ? (
38
+ <Copyright copyrightPosition={'left'}>
39
+ <Copyright.Icon>@</Copyright.Icon>
40
+ <Copyright.Text>{copyright}</Copyright.Text>
41
+ </Copyright>
42
+ ) : (
43
+ ''
44
+ )}
45
+ </div>
46
+ </>
47
+ </UniversalLink>
48
+ </p>
49
+ );
50
+ })}
51
+ </div>
52
+ );
53
+ };
54
+
55
+ export default DefaultView;
@@ -0,0 +1,108 @@
1
+ import React from 'react';
2
+ import loadable from '@loadable/component';
3
+ import { Modal, Image } from 'semantic-ui-react';
4
+ import cx from 'classnames';
5
+ import 'slick-carousel/slick/slick.css';
6
+ import 'slick-carousel/slick/slick-theme.css';
7
+
8
+ import './styles.less';
9
+
10
+ const Slider = loadable(() => import('react-slick'));
11
+
12
+ const ImageGallery = (props) => {
13
+ const { data = {}, mode } = props;
14
+ const items = data.images || [];
15
+ const [open, setOpen] = React.useState(false);
16
+ const [slideIndex, setSlideIndex] = React.useState(0);
17
+
18
+ const [updateCount, setUpdateCount] = React.useState(0);
19
+ const sliderRef = React.useRef(null);
20
+
21
+ const carouselSettings = React.useMemo(
22
+ () => ({
23
+ afterChange: () => setUpdateCount(updateCount + 1),
24
+ beforeChange: (current, next) => setSlideIndex(next),
25
+ infinite: true,
26
+ slidesToShow: 1,
27
+ slidesToScroll: 1,
28
+ dots: false,
29
+ arrows: true,
30
+ adaptiveHeight: true,
31
+ autoplay: false,
32
+ fade: false,
33
+ useTransform: false,
34
+ initialSlide: slideIndex,
35
+ }),
36
+ [slideIndex, setSlideIndex, updateCount],
37
+ );
38
+
39
+ const handleClick = () => {
40
+ if (items.length) {
41
+ setSlideIndex(0);
42
+ setOpen(true);
43
+ }
44
+ };
45
+
46
+ const image = items[slideIndex];
47
+
48
+ return (
49
+ <div className="image-gallery">
50
+ <div
51
+ tabIndex={0}
52
+ role="button"
53
+ onKeyDown={handleClick}
54
+ onClick={handleClick}
55
+ >
56
+ <Image
57
+ src={`${items[0]?.image}/@@images/image/preview`}
58
+ alt={items[0]?.title}
59
+ className={cx('preview-image', {
60
+ 'image-gallery-preview': mode === 'edit',
61
+ })}
62
+ />
63
+ </div>
64
+ {mode === 'view' ? (
65
+ <Modal
66
+ closeIcon
67
+ open={open}
68
+ className="slider-modal"
69
+ onClose={() => setOpen(false)}
70
+ onOpen={() => setOpen(true)}
71
+ >
72
+ <Modal.Content>
73
+ <h3>{image?.title}</h3>
74
+ <p>{image?.description}</p>
75
+ <Slider {...carouselSettings} ref={sliderRef}>
76
+ {items.map((item, i) => {
77
+ return item.copyright ? (
78
+ <div key={i}>
79
+ <div className="image-slide">
80
+ <div className="image-rights">@ {item.copyright}</div>
81
+ <Image
82
+ src={`${item.image}/@@images/image/larger`}
83
+ alt={item?.title}
84
+ />
85
+ </div>
86
+ </div>
87
+ ) : (
88
+ <Image
89
+ key={i}
90
+ src={`${item.image}/@@images/image/larger`}
91
+ alt={item?.title}
92
+ />
93
+ );
94
+ })}
95
+ </Slider>
96
+ <div className="slide-image-count">
97
+ <strong>{slideIndex + 1}</strong> of {items.length}
98
+ </div>
99
+ </Modal.Content>
100
+ </Modal>
101
+ ) : (
102
+ <p>Save the block to preview the Image Gallery Carousel.</p>
103
+ )}
104
+ </div>
105
+ );
106
+ };
107
+
108
+ export default ImageGallery;
@@ -0,0 +1,21 @@
1
+ .image-slide {
2
+ position: relative;
3
+ display: inline-block;
4
+
5
+ .image-rights {
6
+ position: absolute;
7
+ z-index: 100;
8
+ right: 0em;
9
+ bottom: 0em;
10
+ left: 0em;
11
+ padding: 10px;
12
+ // background: rgba(0, 0, 0, 0.5); /* optional: semi-transparent background */
13
+ color: white; /* text color */
14
+ font-size: 14px; /* adjust font size as needed */
15
+ text-align: left; /* optional: center the text */
16
+ }
17
+ }
18
+
19
+ .image-gallery-preview {
20
+ width: auto !important;
21
+ }