@eeacms/volto-embed 6.0.1 → 7.0.0

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,26 @@ 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
+ ### [7.0.0](https://github.com/eea/volto-embed/compare/6.0.1...7.0.0) - 6 November 2023
8
+
9
+ #### :rocket: New Features
10
+
11
+ - feat: add toolbar + refactor [Miu Razvan - [`d439f87`](https://github.com/eea/volto-embed/commit/d439f87045cfea1554319fe05ec2a9d8b0c141cf)]
12
+
13
+ #### :house: Internal changes
14
+
15
+ - style: Automated code fix [eea-jenkins - [`c34d6f4`](https://github.com/eea/volto-embed/commit/c34d6f47fd33f728d40254a12c05fe385b27f2e9)]
16
+ - chore: husky, lint-staged use fixed versions [valentinab25 - [`31169f7`](https://github.com/eea/volto-embed/commit/31169f73887837463daee76d63b6516046ad49aa)]
17
+
18
+ #### :hammer_and_wrench: Others
19
+
20
+ - update tests [Miu Razvan - [`c906b79`](https://github.com/eea/volto-embed/commit/c906b79149e853f718d924871ce2e633e5e427c8)]
21
+ - update tests [Miu Razvan - [`11625b6`](https://github.com/eea/volto-embed/commit/11625b65846388a846d1bdf7ebb2e1f8bd103c91)]
22
+ - Release 7.0.0 [Alin Voinea - [`cbc5c79`](https://github.com/eea/volto-embed/commit/cbc5c79f1b66daf0d599d9de5a1bc7d137bafed8)]
23
+ - add act in unit tests [Miu Razvan - [`ecf5b2f`](https://github.com/eea/volto-embed/commit/ecf5b2fe553d99ad6a11570d3d34852d557fbe85)]
24
+ - update [Miu Razvan - [`049cc14`](https://github.com/eea/volto-embed/commit/049cc140bb6ffea4eb3f1555e693e9973ef63d0e)]
25
+ - update [Miu Razvan - [`124acea`](https://github.com/eea/volto-embed/commit/124acea1784d25c562d3271cba295927d38de8f8)]
26
+ - update unit tests [Miu Razvan - [`52c3be5`](https://github.com/eea/volto-embed/commit/52c3be5b0b56ba5b17fd88e3ff99949b3636e727)]
7
27
  ### [6.0.1](https://github.com/eea/volto-embed/compare/6.0.0...6.0.1) - 12 October 2023
8
28
 
9
29
  #### :house: Internal changes
package/cypress.config.js CHANGED
@@ -2,12 +2,12 @@ const { defineConfig } = require('cypress');
2
2
 
3
3
  module.exports = defineConfig({
4
4
  viewportWidth: 1280,
5
- defaultCommandTimeout: 5000,
5
+ defaultCommandTimeout: 8888,
6
6
  chromeWebSecurity: false,
7
7
  reporter: 'junit',
8
- video: true,
8
+ video: false,
9
9
  retries: {
10
- runMode: 1,
10
+ runMode: 2,
11
11
  openMode: 0,
12
12
  },
13
13
  reporterOptions: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-embed",
3
- "version": "6.0.1",
3
+ "version": "7.0.0",
4
4
  "description": "Embed external content",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: IDM2 A-Team",
@@ -27,8 +27,8 @@
27
27
  "@cypress/code-coverage": "^3.10.0",
28
28
  "@plone/scripts": "*",
29
29
  "babel-plugin-transform-class-properties": "^6.24.1",
30
- "husky": "*",
31
- "lint-staged": "*",
30
+ "husky": "^8.0.3",
31
+ "lint-staged": "^14.0.1",
32
32
  "md5": "^2.3.0"
33
33
  },
34
34
  "lint-staged": {
@@ -72,4 +72,4 @@
72
72
  "cypress:open": "make cypress-open",
73
73
  "prepare": "husky install"
74
74
  }
75
- }
75
+ }
@@ -9,17 +9,15 @@ import PropTypes from 'prop-types';
9
9
  import { Button, Input, Message } from 'semantic-ui-react';
10
10
  import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
11
11
  import cx from 'classnames';
12
-
12
+ import { isEqual } from 'lodash';
13
+ import { withBlockExtensions } from '@plone/volto/helpers';
14
+ import { compose } from 'redux';
13
15
  import { Icon, SidebarPortal } from '@plone/volto/components';
14
- import InlineForm from '@plone/volto/components/manage/Form/InlineForm';
15
16
  import clearSVG from '@plone/volto/icons/clear.svg';
16
17
  import aheadSVG from '@plone/volto/icons/ahead.svg';
17
18
  import mapsBlockSVG from '@plone/volto/components/manage/Blocks/Maps/block-maps.svg';
18
- import schema from './schema';
19
- import {
20
- PrivacyProtection,
21
- addPrivacyProtectionToSchema,
22
- } from '../PrivacyProtection';
19
+ import PrivacyProtection from '@eeacms/volto-embed/PrivacyProtection/PrivacyProtection';
20
+ import MapsSidebar from './MapsSidebar';
23
21
 
24
22
  const messages = defineMessages({
25
23
  MapsBlockInputPlaceholder: {
@@ -90,6 +88,19 @@ class Edit extends Component {
90
88
  this.onKeyDownVariantMenuForm = this.onKeyDownVariantMenuForm.bind(this);
91
89
  }
92
90
 
91
+ /**
92
+ * @param {*} nextProps
93
+ * @returns {boolean}
94
+ * @memberof Edit
95
+ */
96
+ shouldComponentUpdate(nextProps) {
97
+ return (
98
+ this.props.selected ||
99
+ nextProps.selected ||
100
+ !isEqual(this.props.data, nextProps.data)
101
+ );
102
+ }
103
+
93
104
  /**
94
105
  * Backward compatibility
95
106
  * @method componentDidMount
@@ -196,6 +207,9 @@ class Edit extends Component {
196
207
  * @returns {string} Markup for the component.
197
208
  */
198
209
  render() {
210
+ const placeholder =
211
+ this.props.data.placeholder ||
212
+ this.props.intl.formatMessage(messages.MapsBlockInputPlaceholder);
199
213
  return (
200
214
  <div
201
215
  className={cx(
@@ -224,6 +238,11 @@ class Edit extends Component {
224
238
  className="google-map"
225
239
  frameBorder="0"
226
240
  allowFullScreen
241
+ style={
242
+ this.props.data.height
243
+ ? { height: this.props.data.height }
244
+ : {}
245
+ }
227
246
  />
228
247
  </PrivacyProtection>
229
248
  </div>
@@ -235,9 +254,7 @@ class Edit extends Component {
235
254
  <Input
236
255
  onKeyDown={this.onKeyDownVariantMenuForm}
237
256
  onChange={this.onChangeUrl}
238
- placeholder={this.props.intl.formatMessage(
239
- messages.MapsBlockInputPlaceholder,
240
- )}
257
+ placeholder={placeholder}
241
258
  value={this.state.url}
242
259
  // Prevents propagation to the Dropzone and the opening
243
260
  // of the upload browser dialog
@@ -287,22 +304,13 @@ class Edit extends Component {
287
304
  </center>
288
305
  </Message>
289
306
  )}
307
+ {!this.props.selected && <div className="map-overlay" />}
290
308
  <SidebarPortal selected={this.props.selected}>
291
- <InlineForm
292
- schema={addPrivacyProtectionToSchema(schema)}
293
- title={schema.title}
294
- onChangeField={(id, value) => {
295
- this.props.onChangeBlock(this.props.block, {
296
- ...this.props.data,
297
- [id]: value,
298
- });
299
- }}
300
- formData={this.props.data}
301
- />
309
+ <MapsSidebar {...this.props} resetSubmitUrl={this.resetSubmitUrl} />
302
310
  </SidebarPortal>
303
311
  </div>
304
312
  );
305
313
  }
306
314
  }
307
315
 
308
- export default injectIntl(Edit);
316
+ export default compose(injectIntl, withBlockExtensions)(Edit);
@@ -0,0 +1,119 @@
1
+ /* eslint-disable no-unused-vars */
2
+ import React from 'react';
3
+ import config from '@plone/volto/registry';
4
+ import { render, fireEvent, screen } from '@testing-library/react';
5
+ import { Provider } from 'react-intl-redux';
6
+ import configureStore from 'redux-mock-store';
7
+ import Edit from './Edit';
8
+ import '@testing-library/jest-dom/extend-expect';
9
+
10
+ const mockStore = configureStore();
11
+
12
+ const store = mockStore(() => ({
13
+ connected_data_parameters: {},
14
+ router: {
15
+ location: {
16
+ pathname: '',
17
+ },
18
+ },
19
+ intl: {
20
+ locale: 'en',
21
+ messages: {},
22
+ },
23
+ }));
24
+
25
+ config.blocks.blocksConfig = {
26
+ ...config.blocks.blocksConfig,
27
+ maps: {
28
+ id: 'maps',
29
+ title: 'Maps',
30
+ group: 'media',
31
+ extensions: {},
32
+ variations: [],
33
+ restricted: false,
34
+ mostUsed: true,
35
+ sidebarTab: 1,
36
+ security: {
37
+ addPermission: [],
38
+ view: [],
39
+ },
40
+ },
41
+ };
42
+
43
+ jest.mock(
44
+ '@eeacms/volto-embed/PrivacyProtection/PrivacyProtection',
45
+ () => ({ children }) => children,
46
+ );
47
+
48
+ describe('Edit', () => {
49
+ it('renders without crashing', () => {
50
+ render(
51
+ <Provider store={store}>
52
+ <Edit
53
+ selected={false}
54
+ block="block"
55
+ index={1}
56
+ data={{
57
+ '@type': 'maps',
58
+ url:
59
+ 'https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3027.7835278268726!2d14.38842915203974!3d40.634655679238854!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x133b994881d943cb%3A0x6ab93db57d3272f0!2sHotel+Mediterraneo+Sorrento!5e0!3m2!1sen!2ses!4v1550168740166',
60
+ }}
61
+ pathname="/"
62
+ onChangeBlock={() => {}}
63
+ onSelectBlock={() => {}}
64
+ onDeleteBlock={() => {}}
65
+ onFocusPreviousBlock={() => {}}
66
+ onFocusNextBlock={() => {}}
67
+ handleKeyDown={() => {}}
68
+ />
69
+ </Provider>,
70
+ );
71
+ });
72
+
73
+ it('submits url when button is clicked', async () => {
74
+ const url =
75
+ 'https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3027.7835278268726!2d14.38842915203974!3d40.634655679238854!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x133b994881d943cb%3A0x6ab93db57d3272f0!2sHotel+Mediterraneo+Sorrento!5e0!3m2!1sen!2ses!4v1550168740166';
76
+
77
+ const { container, getByPlaceholderText } = render(
78
+ <Provider store={store}>
79
+ <Edit
80
+ selected={false}
81
+ block="block"
82
+ index={1}
83
+ data={{
84
+ '@type': 'maps',
85
+ dataprotection: {
86
+ privacy_statement: 'test',
87
+ },
88
+ }}
89
+ pathname="/"
90
+ onChangeBlock={() => {}}
91
+ onSelectBlock={() => {}}
92
+ onDeleteBlock={() => {}}
93
+ onFocusPreviousBlock={() => {}}
94
+ onFocusNextBlock={() => {}}
95
+ handleKeyDown={() => {}}
96
+ />
97
+ </Provider>,
98
+ );
99
+
100
+ const input = getByPlaceholderText('Enter map Embed Code');
101
+
102
+ fireEvent.click(input);
103
+ fireEvent.change(input, { target: { value: url } });
104
+ // screen.debug();
105
+ // expect(input.value).toBe(url);
106
+
107
+ // fireEvent.click(container.querySelector('button.cancel'));
108
+
109
+ // fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
110
+ // fireEvent.keyDown(input, { key: 'Escape', code: 'Escape' });
111
+ // fireEvent.keyDown(input, { key: 'KeyA', code: 'KeyA' });
112
+
113
+ // fireEvent.change(input, { target: { value: '<iframe src="test"/>' } });
114
+ // fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
115
+
116
+ // const button = container.querySelector('button.ui.basic.primary.button');
117
+ // fireEvent.click(button);
118
+ });
119
+ });
@@ -0,0 +1,52 @@
1
+ import React from 'react';
2
+ import { BlockDataForm } from '@plone/volto/components';
3
+ import { useIntl, defineMessages } from 'react-intl';
4
+ import globeSVG from '@plone/volto/icons/globe.svg';
5
+ import { Icon } from '@plone/volto/components';
6
+ import { Segment } from 'semantic-ui-react';
7
+ import { addPrivacyProtectionToSchema } from '@eeacms/volto-embed';
8
+ import { MapsSchema } from './schema';
9
+
10
+ const messages = defineMessages({
11
+ Maps: {
12
+ id: 'Maps',
13
+ defaultMessage: 'Maps',
14
+ },
15
+ NoMaps: {
16
+ id: 'No map selected',
17
+ defaultMessage: 'No map selected',
18
+ },
19
+ });
20
+
21
+ const MapsSidebar = (props) => {
22
+ const { data, block, onChangeBlock } = props;
23
+ const intl = useIntl();
24
+ const schema = addPrivacyProtectionToSchema(MapsSchema({ ...props, intl }));
25
+
26
+ return (
27
+ <>
28
+ {!data.url ? (
29
+ <Segment className="sidebar-metadata-container" secondary>
30
+ {props.intl.formatMessage(messages.NoMaps)}
31
+ <Icon name={globeSVG} size="100px" color="#b8c6c8" />
32
+ </Segment>
33
+ ) : (
34
+ <BlockDataForm
35
+ schema={schema}
36
+ title={intl.formatMessage(messages.Maps)}
37
+ onChangeField={(id, value) => {
38
+ onChangeBlock(block, {
39
+ ...data,
40
+ [id]: value,
41
+ });
42
+ }}
43
+ onChangeBlock={onChangeBlock}
44
+ formData={data}
45
+ block={block}
46
+ />
47
+ )}
48
+ </>
49
+ );
50
+ };
51
+
52
+ export default MapsSidebar;
@@ -7,7 +7,9 @@ import React from 'react';
7
7
  import { defineMessages, injectIntl } from 'react-intl';
8
8
  import PropTypes from 'prop-types';
9
9
  import cx from 'classnames';
10
- import { PrivacyProtection } from '../PrivacyProtection';
10
+ import { compose } from 'redux';
11
+ import { withBlockExtensions } from '@plone/volto/helpers';
12
+ import PrivacyProtection from '@eeacms/volto-embed/PrivacyProtection/PrivacyProtection';
11
13
 
12
14
  const messages = defineMessages({
13
15
  EmbededGoogleMaps: {
@@ -32,17 +34,11 @@ const View = ({ data, intl, id }) => {
32
34
  },
33
35
  data.align,
34
36
  )}
35
- style={
36
- data.align === 'full'
37
- ? { position: 'static', height: data.height }
38
- : { height: data.height }
39
- }
40
37
  >
41
38
  <div
42
- className={cx({
43
- 'full-width-block': data.align === 'full',
39
+ className={cx('maps-inner', {
40
+ 'full-width': data.align === 'full',
44
41
  })}
45
- style={{ height: '100%' }}
46
42
  >
47
43
  <PrivacyProtection data={data} id={id}>
48
44
  <iframe
@@ -51,7 +47,7 @@ const View = ({ data, intl, id }) => {
51
47
  className="google-map"
52
48
  frameBorder="0"
53
49
  allowFullScreen
54
- style={{ height: data.height }}
50
+ style={data.height ? { height: data.height } : {}}
55
51
  />
56
52
  </PrivacyProtection>
57
53
  </div>
@@ -68,4 +64,4 @@ View.propTypes = {
68
64
  data: PropTypes.objectOf(PropTypes.any).isRequired,
69
65
  };
70
66
 
71
- export default injectIntl(View);
67
+ export default compose(injectIntl, withBlockExtensions)(View);
@@ -0,0 +1,12 @@
1
+ import Edit from './Edit';
2
+ import View from './View';
3
+
4
+ export default function installHeroBlock(config) {
5
+ config.blocks.blocksConfig.maps = {
6
+ ...config.blocks.blocksConfig.maps,
7
+ edit: Edit,
8
+ view: View,
9
+ };
10
+
11
+ return config;
12
+ }
@@ -0,0 +1,67 @@
1
+ import { defineMessages } from 'react-intl';
2
+
3
+ const messages = defineMessages({
4
+ Maps: {
5
+ id: 'Maps',
6
+ defaultMessage: 'Maps',
7
+ },
8
+ AltText: {
9
+ id: 'Alt text',
10
+ defaultMessage: 'Alt text',
11
+ },
12
+ MapsURL: {
13
+ id: 'Maps URL',
14
+ defaultMessage: 'Maps URL',
15
+ },
16
+ Alignment: {
17
+ id: 'Alignment',
18
+ defaultMessage: 'Alignment',
19
+ },
20
+ CSSHeight: {
21
+ id: 'CSSHeight',
22
+ defineMessages: 'CSS height',
23
+ },
24
+ CSSHeightDescription: {
25
+ id: 'CSSHeightDescription',
26
+ defineMessages: 'Iframe height',
27
+ },
28
+ });
29
+
30
+ export const MapsSchema = (props) => ({
31
+ title: props.intl.formatMessage(messages.Maps),
32
+ block: 'Maps',
33
+ fieldsets: [
34
+ {
35
+ id: 'default',
36
+ title: 'Default',
37
+ fields: ['url', 'title', 'align', 'height'],
38
+ },
39
+ ],
40
+
41
+ properties: {
42
+ url: {
43
+ title: props.intl.formatMessage(messages.MapsURL),
44
+ widget: 'url',
45
+ },
46
+ title: {
47
+ title: props.intl.formatMessage(messages.AltText),
48
+ },
49
+ align: {
50
+ title: props.intl.formatMessage(messages.Alignment),
51
+ widget: 'align',
52
+ },
53
+ height: {
54
+ title: (
55
+ <a
56
+ rel="noreferrer"
57
+ target="_blank"
58
+ href="https://developer.mozilla.org/en-US/docs/Web/CSS/height"
59
+ >
60
+ {props.intl.formatMessage(messages.CSSHeight)}
61
+ </a>
62
+ ),
63
+ description: props.intl.formatMessage(messages.CSSHeightDescription),
64
+ },
65
+ },
66
+ required: [],
67
+ });
@@ -0,0 +1,5 @@
1
+ import installMaps from './Maps';
2
+
3
+ export default function installBlocks(config) {
4
+ return [installMaps].reduce((acc, apply) => apply(acc), config);
5
+ }
@@ -1,4 +1,5 @@
1
1
  import React, { useState } from 'react';
2
+ import cx from 'classnames';
2
3
  import { compose } from 'redux';
3
4
  import { useSelector, useDispatch } from 'react-redux';
4
5
  import VisibilitySensor from 'react-visibility-sensor';
@@ -77,6 +78,7 @@ const CookieWatcher = (domain_key, cookies, pollingRate = 250) => {
77
78
 
78
79
  const PrivacyProtection = (props) => {
79
80
  const {
81
+ className,
80
82
  children,
81
83
  data = {},
82
84
  id,
@@ -168,7 +170,7 @@ const PrivacyProtection = (props) => {
168
170
  >
169
171
  {visible ? (
170
172
  <div
171
- className="privacy-protection-wrapper"
173
+ className={cx('privacy-protection-wrapper', className)}
172
174
  style={{
173
175
  position: 'relative',
174
176
  height: height && (!enabled || !show) ? `${height}px` : 'auto',
@@ -17,17 +17,12 @@
17
17
  text-align: center;
18
18
 
19
19
  .wrapped {
20
- // position: absolute;
21
- // top: 50%;
22
- // left: 50%;
23
20
  width: 300px;
24
21
  padding: 1.4rem;
25
22
  margin: 0 auto;
26
23
  background: white;
27
24
  border-radius: 5px;
28
25
  opacity: 1;
29
- // -ms-transform: translate(-50%, -50%);
30
- // transform: translate(-50%, -50%);
31
26
  }
32
27
 
33
28
  .discreet,
@@ -0,0 +1,29 @@
1
+ import React, { useState } from 'react';
2
+ import { Modal } from 'semantic-ui-react';
3
+
4
+ const EnlargeWidget = ({ children }) => {
5
+ const [isOpen, setIsOpen] = useState(false);
6
+
7
+ return (
8
+ <div className="enlarge">
9
+ <button className="trigger-button" onClick={() => setIsOpen(true)}>
10
+ <i className="ri-fullscreen-line" />
11
+ Enlarge
12
+ </button>
13
+ <Modal
14
+ open={isOpen}
15
+ closeIcon={
16
+ <span className="close icon">
17
+ <i class="ri-close-line" />
18
+ </span>
19
+ }
20
+ onClose={() => setIsOpen(false)}
21
+ className="enlarge-modal"
22
+ >
23
+ <Modal.Content>{children}</Modal.Content>
24
+ </Modal>
25
+ </div>
26
+ );
27
+ };
28
+
29
+ export default EnlargeWidget;
@@ -0,0 +1,45 @@
1
+ import React from 'react';
2
+ import { Popup } from 'semantic-ui-react';
3
+ import { isArray, isString } from 'lodash';
4
+ import cx from 'classnames';
5
+ import {
6
+ serializeNodes,
7
+ serializeNodesToText,
8
+ } from '@plone/volto-slate/editor/render';
9
+
10
+ export const serializeText = (notes) => {
11
+ const content = isArray(notes) ? serializeNodes(notes) : notes;
12
+ const text = isArray(notes)
13
+ ? serializeNodesToText(notes)
14
+ : isString(notes)
15
+ ? notes
16
+ : '';
17
+ if (!text) return <p>There are no notes set for this visualization</p>;
18
+ return content;
19
+ };
20
+
21
+ export default function FigureNote({ notes = [] }) {
22
+ const [open, setOpen] = React.useState(false);
23
+
24
+ return (
25
+ <Popup
26
+ popper={{ id: 'vis-toolbar-popup', className: 'figure-note-popup' }}
27
+ position="bottom left"
28
+ on="click"
29
+ open={open}
30
+ onClose={() => {
31
+ setOpen(false);
32
+ }}
33
+ onOpen={() => {
34
+ setOpen(true);
35
+ }}
36
+ trigger={
37
+ <div className="figure-note">
38
+ <button className={cx('trigger-button', { open })}>Note</button>
39
+ </div>
40
+ }
41
+ >
42
+ <Popup.Content>{serializeText(notes)}</Popup.Content>
43
+ </Popup>
44
+ );
45
+ }
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+ import { UniversalLink } from '@plone/volto/components';
3
+
4
+ const Link = ({ children, ...props }) => {
5
+ if (props.href) {
6
+ return <UniversalLink {...props}>{children}</UniversalLink>;
7
+ }
8
+ return <span {...props}>{children}</span>;
9
+ };
10
+
11
+ export default function MoreInfo({ href }) {
12
+ return (
13
+ <div className="more-info">
14
+ <Link href={href} className="trigger-button">
15
+ <span>More info</span>
16
+ <i class="ri-external-link-line" />
17
+ </Link>
18
+ </div>
19
+ );
20
+ }