@eeacms/volto-clms-theme 1.0.156 → 1.0.158

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,11 +4,40 @@ 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.0.156](https://github.com/eea/volto-clms-theme/compare/1.0.155...1.0.156) - 12 December 2022
7
+ ### [1.0.158](https://github.com/eea/volto-clms-theme/compare/1.0.157...1.0.158) - 22 December 2022
8
+
9
+ #### :bug: Bug Fixes
10
+
11
+ - fix: add b_size to the FAQ search request and remove duplicates from categories [ionlizarazu - [`94c0b19`](https://github.com/eea/volto-clms-theme/commit/94c0b19b9c990e675125b45522d15d896ee53792)]
12
+
13
+ ### [1.0.157](https://github.com/eea/volto-clms-theme/compare/1.0.156...1.0.157) - 22 December 2022
8
14
 
9
15
  #### :rocket: New Features
10
16
 
11
- - feat: sort UseCases shown in related listings alphabetically. CLMS-1618 [Mikel Larreategi - [`87cd56d`](https://github.com/eea/volto-clms-theme/commit/87cd56d0a0adb78b70049aa67dc033dd3000b2a2)]
17
+ - feat: copy to customize [Mikel Larreategi - [`84bb8d6`](https://github.com/eea/volto-clms-theme/commit/84bb8d6de43acccf70dd009088f91a09168259af)]
18
+ - feat: use volto-addon-ci from 15.x tag [Mikel Larreategi - [`f21deaa`](https://github.com/eea/volto-clms-theme/commit/f21deaaf20b8873886f10b05c42470c9e12d0686)]
19
+ - feat: do not show View in the mapviewer link if this item will not bee shown [Mikel Larreategi - [`fbda05f`](https://github.com/eea/volto-clms-theme/commit/fbda05f25c62e5dd9a4a66a1bdc53170e93ca0a2)]
20
+ - feat: show dataset names in the download pop-up when a custom selection and prepackaged are requested to be downloaded [Mikel Larreategi - [`cdb2564`](https://github.com/eea/volto-clms-theme/commit/cdb25648d6ae44d5c0892a4ea3cddb5c03b04e4c)]
21
+ - feat: FAQ Block [Mikel Larreategi - [`8ffabf7`](https://github.com/eea/volto-clms-theme/commit/8ffabf7c1f1353020a40b3f2c14ee979872ed607)]
22
+
23
+ #### :bug: Bug Fixes
24
+
25
+ - fix: add import/no-resolver ignore [Mikel Larreategi - [`ba527ba`](https://github.com/eea/volto-clms-theme/commit/ba527ba3bbedbcf44a70ef68ee641d5bc97d50f3)]
26
+ - fix: use Grid instead of Table [Mikel Larreategi - [`4d9bed0`](https://github.com/eea/volto-clms-theme/commit/4d9bed05301b6e4361e0ab27ae5650e44f703cde)]
27
+
28
+ #### :house: Internal changes
29
+
30
+ - chore: fix [Mikel Larreategi - [`c12a7c3`](https://github.com/eea/volto-clms-theme/commit/c12a7c3aaae93a37776de1eefadda523941de82f)]
31
+ - chore: fix [Mikel Larreategi - [`3ac089a`](https://github.com/eea/volto-clms-theme/commit/3ac089a9ba82c444104f8b43fb7e2e16ff7a7be3)]
32
+
33
+ #### :hammer_and_wrench: Others
34
+
35
+ - add edit button [ionlizarazu - [`d84f322`](https://github.com/eea/volto-clms-theme/commit/d84f3229c33dd41ae6741d46109cbafe25e01c54)]
36
+ - faq block [ionlizarazu - [`db09537`](https://github.com/eea/volto-clms-theme/commit/db095378f91b839cc8cdc40e395e8110aa6d19a7)]
37
+ - add dep [Mikel Larreategi - [`408b499`](https://github.com/eea/volto-clms-theme/commit/408b499a1caa7a1f1a48b18f37df7dc96aeec78b)]
38
+ - fix [Mikel Larreategi - [`840b896`](https://github.com/eea/volto-clms-theme/commit/840b89671d722e04c3b060bc527bdc68c25390cc)]
39
+ - remove console.log [Mikel Larreategi - [`bee5ceb`](https://github.com/eea/volto-clms-theme/commit/bee5ceb3273f0983c25a99782abff4490adbed5d)]
40
+ ### [1.0.156](https://github.com/eea/volto-clms-theme/compare/1.0.155...1.0.156) - 12 December 2022
12
41
 
13
42
  ### [1.0.155](https://github.com/eea/volto-clms-theme/compare/1.0.154...1.0.155) - 12 December 2022
14
43
 
package/Jenkinsfile CHANGED
@@ -41,19 +41,22 @@ pipeline {
41
41
 
42
42
  "ES lint": {
43
43
  node(label: 'docker') {
44
- sh '''docker run -i --rm --name="$BUILD_TAG-eslint" -e NAMESPACE="$NAMESPACE" -e GIT_NAME=$GIT_NAME -e GIT_BRANCH="$BRANCH_NAME" -e GIT_CHANGE_ID="$CHANGE_ID" plone/volto-addon-ci eslint'''
44
+ sh '''docker pull plone/volto-addon-ci:15.x'''
45
+ sh '''docker run -i --rm --name="$BUILD_TAG-eslint" -e VOLTO=$VOLTO -e NAMESPACE="$NAMESPACE" -e GIT_NAME=$GIT_NAME -e GIT_BRANCH="$BRANCH_NAME" -e GIT_CHANGE_ID="$CHANGE_ID" plone/volto-addon-ci:15.x eslint'''
45
46
  }
46
47
  },
47
48
 
48
49
  "Style lint": {
49
50
  node(label: 'docker') {
50
- sh '''docker run -i --rm --name="$BUILD_TAG-stylelint" -e NAMESPACE="$NAMESPACE" -e GIT_NAME=$GIT_NAME -e GIT_BRANCH="$BRANCH_NAME" -e GIT_CHANGE_ID="$CHANGE_ID" plone/volto-addon-ci stylelint'''
51
+ sh '''docker pull plone/volto-addon-ci:15.x'''
52
+ sh '''docker run -i --rm --name="$BUILD_TAG-stylelint" -e VOLTO=$VOLTO -e NAMESPACE="$NAMESPACE" -e GIT_NAME=$GIT_NAME -e GIT_BRANCH="$BRANCH_NAME" -e GIT_CHANGE_ID="$CHANGE_ID" plone/volto-addon-ci:15.x stylelint'''
51
53
  }
52
54
  },
53
55
 
54
56
  "Prettier": {
55
57
  node(label: 'docker') {
56
- sh '''docker run -i --rm --name="$BUILD_TAG-prettier" -e NAMESPACE="$NAMESPACE" -e GIT_NAME=$GIT_NAME -e GIT_BRANCH="$BRANCH_NAME" -e GIT_CHANGE_ID="$CHANGE_ID" plone/volto-addon-ci prettier'''
58
+ sh '''docker pull plone/volto-addon-ci:15.x'''
59
+ sh '''docker run -i --rm --name="$BUILD_TAG-prettier" -e VOLTO=$VOLTO -e NAMESPACE="$NAMESPACE" -e GIT_NAME=$GIT_NAME -e GIT_BRANCH="$BRANCH_NAME" -e GIT_CHANGE_ID="$CHANGE_ID" plone/volto-addon-ci:15.x prettier'''
57
60
  }
58
61
  }
59
62
  )
@@ -77,8 +80,8 @@ pipeline {
77
80
  node(label: 'docker') {
78
81
  script {
79
82
  try {
80
- sh '''docker pull plone/volto-addon-ci'''
81
- sh '''docker run -i --name="$BUILD_TAG-volto" -e NAMESPACE="$NAMESPACE" -e VOLTO=$VOLTO -e GIT_NAME=$GIT_NAME -e GIT_BRANCH="$BRANCH_NAME" -e GIT_CHANGE_ID="$CHANGE_ID" plone/volto-addon-ci'''
83
+ sh '''docker pull plone/volto-addon-ci:15.x'''
84
+ sh '''docker run -i --name="$BUILD_TAG-volto" -e NAMESPACE="$NAMESPACE" -e VOLTO=$VOLTO -e GIT_NAME=$GIT_NAME -e GIT_BRANCH="$BRANCH_NAME" -e GIT_CHANGE_ID="$CHANGE_ID" plone/volto-addon-ci:15.x'''
82
85
  sh '''rm -rf xunit-reports'''
83
86
  sh '''mkdir -p xunit-reports'''
84
87
  sh '''docker cp $BUILD_TAG-volto:/opt/frontend/my-volto-project/coverage xunit-reports/'''
@@ -126,7 +129,7 @@ pipeline {
126
129
  script {
127
130
  try {
128
131
  sh '''docker pull plone; docker run -d --rm --name="$BUILD_TAG-plone" -e SITE="Plone" -e PROFILES="profile-plone.restapi:blocks" plone fg'''
129
- sh '''docker pull plone/volto-addon-ci; docker run -i --name="$BUILD_TAG-cypress" --link $BUILD_TAG-plone:plone -e VOLTO=$VOLTO -e NAMESPACE="$NAMESPACE" -e GIT_NAME=$GIT_NAME -e GIT_BRANCH="$BRANCH_NAME" -e GIT_CHANGE_ID="$CHANGE_ID" -e DEPENDENCIES="$DEPENDENCIES" -e NODE_ENV=test plone/volto-addon-ci cypress'''
132
+ sh '''docker pull plone/volto-addon-ci:15.x; docker run -i --name="$BUILD_TAG-cypress" --link $BUILD_TAG-plone:plone -e VOLTO=$VOLTO -e NAMESPACE="$NAMESPACE" -e GIT_NAME=$GIT_NAME -e GIT_BRANCH="$BRANCH_NAME" -e GIT_CHANGE_ID="$CHANGE_ID" -e DEPENDENCIES="$DEPENDENCIES" -e NODE_ENV=test plone/volto-addon-ci:15.x cypress'''
130
133
  } finally {
131
134
  try {
132
135
  sh '''rm -rf cypress-reports cypress-results cypress-coverage'''
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-clms-theme",
3
- "version": "1.0.156",
3
+ "version": "1.0.158",
4
4
  "description": "volto-clms-theme: Volto theme for CLMS site",
5
5
  "main": "src/index.js",
6
6
  "author": "CodeSyntax for the European Environment Agency",
@@ -60,7 +60,8 @@
60
60
  "volto-form-block": "2.8.0",
61
61
  "react-input-range": "^1.3.0",
62
62
  "lightgallery": "^2.4.0",
63
- "validator": "13.7.0"
63
+ "validator": "13.7.0",
64
+ "connected-react-router": "6.8.0"
64
65
  },
65
66
  "devDependencies": {
66
67
  "@cypress/code-coverage": "^3.9.5",
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ import CclFAQBlockView from './CclFAQBlockView';
3
+
4
+ const CclFAQBlockEdit = (props) => {
5
+ return <CclFAQBlockView props={props} isEditMode={true} />;
6
+ };
7
+
8
+ export default CclFAQBlockEdit;
@@ -0,0 +1,149 @@
1
+ import React from 'react';
2
+ import { CclTabs } from '@eeacms/volto-clms-theme/components/CclTab';
3
+ import { useDispatch, useSelector } from 'react-redux';
4
+ import { searchContent } from '@plone/volto/actions';
5
+ import config from '@plone/volto/registry';
6
+ import { Accordion, Segment } from 'semantic-ui-react';
7
+ import { Icon, UniversalLink } from '@plone/volto/components';
8
+ import AnimateHeight from 'react-animate-height';
9
+ import { StringToHTML } from '@eeacms/volto-clms-theme/components/CclUtils';
10
+ import penSVG from '@plone/volto/icons/pen.svg';
11
+
12
+ const CclFAQBlockView = (props) => {
13
+ const { isEditMode } = props;
14
+ const dispatch = useDispatch();
15
+ const path = useSelector((state) => state.router.location.pathname);
16
+ const search = useSelector((state) => state.search);
17
+ const handleClick = ({ index }) => {
18
+ const newIndex =
19
+ activeIndex.indexOf(index) === -1
20
+ ? [...activeIndex, index]
21
+ : activeIndex.filter((item) => item !== index);
22
+
23
+ setActiveIndex(newIndex);
24
+ };
25
+ React.useEffect(() => {
26
+ dispatch(
27
+ searchContent(path.replace('/edit', ''), {
28
+ fullobjects: 1,
29
+ portal_type: 'FAQ',
30
+ b_size: 999,
31
+ }),
32
+ );
33
+ }, [path, dispatch]);
34
+ let categories =
35
+ search.items.length > 0
36
+ ? [
37
+ ...new Set(
38
+ search.items.map((item) => item.taxonomy_faqcategories).flat(),
39
+ ),
40
+ ]
41
+ .map((cat) => {
42
+ const cat_family = cat.title?.split(' » ');
43
+ if (cat_family.length > 1) {
44
+ cat['subTab'] = true;
45
+ }
46
+ return cat;
47
+ })
48
+ .filter(
49
+ (thing, index, self) =>
50
+ index === self.findIndex((t) => t.token === thing.token),
51
+ )
52
+ .sort((a, b) => {
53
+ if (a.title < b.title) {
54
+ return -1;
55
+ } else if (a.title > b.title) {
56
+ return 1;
57
+ }
58
+ return 0;
59
+ })
60
+ : [];
61
+ categories.forEach((cat, index) => {
62
+ if (cat.subTab && !categories[index - 1].subTab) {
63
+ categories[index - 1]['parent'] = true;
64
+ }
65
+ });
66
+ const [activeIndex, setActiveIndex] = React.useState([0]);
67
+ const titleIcons = config.blocks?.blocksConfig?.accordion?.titleIcons;
68
+
69
+ return (
70
+ <div id="faq-listing" className="ccl-container tab-container">
71
+ {search.loaded ? (
72
+ search.items?.length > 0 &&
73
+ categories.length > 0 && (
74
+ <CclTabs routing={true}>
75
+ {categories.map((cat, key) => {
76
+ const cat_family = cat.title.split(' » ');
77
+ const cat_title =
78
+ cat_family.length > 1
79
+ ? cat_family[1].split('#')[1]
80
+ : cat_family[0].split('#')[1];
81
+ return (
82
+ <div
83
+ key={key}
84
+ tabTitle={cat_title}
85
+ className={cat_family.length > 1 ? 'subcard' : ''}
86
+ parent={cat.parent}
87
+ >
88
+ <div className="accordion-block">
89
+ {search.items.map((item, item_key) => {
90
+ return (
91
+ item.taxonomy_faqcategories.filter((faq_cat) =>
92
+ faq_cat.title.includes(cat.title),
93
+ ).length > 0 && (
94
+ <Accordion fluid styled key={item_key}>
95
+ <Accordion.Title
96
+ as={'h2'}
97
+ onClick={() => handleClick({ index: item_key })}
98
+ className={'accordion-title align-arrow-right'}
99
+ >
100
+ {activeIndex.includes(item_key) ? (
101
+ <Icon name={titleIcons.opened.rightPosition} />
102
+ ) : (
103
+ <Icon name={titleIcons.closed.rightPosition} />
104
+ )}
105
+ {isEditMode && (
106
+ <UniversalLink
107
+ openLinkInNewTab={true}
108
+ href={`${item['@id']}/edit`}
109
+ >
110
+ <Icon
111
+ name={penSVG}
112
+ className="circled"
113
+ title={'Edit'}
114
+ />
115
+ </UniversalLink>
116
+ )}
117
+ <span>{item.title}</span>
118
+ </Accordion.Title>
119
+ <Accordion.Content
120
+ active={activeIndex.includes(item_key)}
121
+ >
122
+ <AnimateHeight
123
+ animateOpacity
124
+ duration={500}
125
+ height={'auto'}
126
+ >
127
+ <StringToHTML
128
+ string={item.text ? item.text.data : ''}
129
+ />
130
+ </AnimateHeight>
131
+ </Accordion.Content>
132
+ </Accordion>
133
+ )
134
+ );
135
+ })}
136
+ </div>
137
+ </div>
138
+ );
139
+ })}
140
+ </CclTabs>
141
+ )
142
+ ) : (
143
+ <Segment loading={search.loading}></Segment>
144
+ )}
145
+ </div>
146
+ );
147
+ };
148
+
149
+ export default CclFAQBlockView;
@@ -58,6 +58,8 @@ import TextLinkCarouselEdit from '@eeacms/volto-clms-theme/components/Blocks/Ccl
58
58
  import TextLinkCarouselView from '@eeacms/volto-clms-theme/components/Blocks/CclTextLinkCarouselBlock/CclTextLinkCarouselView';
59
59
  import SubscriptionBlockView from '@eeacms/volto-clms-theme/components/Blocks/CclSubscriptionBlock/SubscriptionView';
60
60
  import SubscriptionBlockEdit from '@eeacms/volto-clms-theme/components/Blocks/CclSubscriptionBlock/SubscriptionEdit';
61
+ import CclFAQBlockEdit from '@eeacms/volto-clms-theme/components/Blocks/CclFAQBlock/CclFAQBlockEdit';
62
+ import CclFAQBlockView from '@eeacms/volto-clms-theme/components/Blocks/CclFAQBlock/CclFAQBlockView';
61
63
  import containerSVG from '@plone/volto/icons/apps.svg';
62
64
  import {
63
65
  customIdFieldSchema,
@@ -590,6 +592,22 @@ const customBlocks = (config) => ({
590
592
  ...config.blocks.blocksConfig.maps,
591
593
  restricted: false,
592
594
  },
595
+ cclFAQ: {
596
+ id: 'cclFAQ', // The name (id) of the block
597
+ title: 'FAQ Block', // The display name of the block
598
+ icon: containerSVG, // The icon used in the block chooser
599
+ group: 'ccl_blocks', // The group (blocks can be grouped, displayed in the chooser)
600
+ view: CclFAQBlockView, // The view mode component
601
+ edit: CclFAQBlockEdit, // The edit mode component
602
+ restricted: false, // If the block is restricted, it won't show in the chooser
603
+ mostUsed: false, // A meta group `most used`, appearing at the top of the chooser
604
+ blockHasOwnFocusManagement: false, // Set this to true if the block manages its own focus
605
+ sidebarTab: 1, // The sidebar tab you want to be selected when selecting the block
606
+ security: {
607
+ addPermission: [], // Future proof (not implemented yet) add user permission role(s)
608
+ view: [], // Future proof (not implemented yet) view user role(s)
609
+ },
610
+ },
593
611
  });
594
612
 
595
613
  export default customBlocks;
@@ -28,7 +28,9 @@ export const getPanels = (data) => {
28
28
 
29
29
  export const slugify = (string) => {
30
30
  return string
31
- .toLowerCase()
32
- .replace(/[\s-]+/g, '_')
33
- .replace(/[^\w]+/g, '');
31
+ ? string
32
+ .toLowerCase()
33
+ .replace(/[\s-]+/g, '_')
34
+ .replace(/[^\w]+/g, '')
35
+ : '';
34
36
  };
@@ -64,6 +64,12 @@ const CLMSDatasetDetailView = ({ content, token }) => {
64
64
  const [open, setOpen] = React.useState({});
65
65
  const locale = useSelector((state) => state.intl.locale);
66
66
 
67
+ const isAuxiliary = content.mapviewer_viewservice
68
+ .toLowerCase()
69
+ .startsWith(
70
+ 'https://trial.discomap.eea.europa.eu/arcgis/services/clms/worldcountries/mapserver/wmsserver',
71
+ );
72
+
67
73
  return (
68
74
  <div className="ccl-container ">
69
75
  <h1 className="page-title">{content.title}</h1>
@@ -422,7 +428,7 @@ const CLMSDatasetDetailView = ({ content, token }) => {
422
428
  />
423
429
  </div>
424
430
  )}
425
- {content?.mapviewer_viewservice?.length > 0 && (
431
+ {content?.mapviewer_viewservice?.length > 0 && !isAuxiliary && (
426
432
  <div className="menu-detail-button">
427
433
  <CclButton
428
434
  url={'/' + locale + '/map-viewer?dataset=' + content.UID}
@@ -461,12 +461,18 @@ const CLMSCartContent = (props) => {
461
461
  }
462
462
  <br />
463
463
  <br />
464
- <strong>Selected pre-packaged files:</strong>
464
+ <strong>Selected pre-packaged files from:</strong>
465
465
  <ul>
466
- {getSelectedCartItems()
467
- .filter((item) => item.file_id)
466
+ {[
467
+ ...new Set(
468
+ getSelectedCartItems()
469
+ .filter((item) => item.file_id)
470
+ .map((item) => item.name),
471
+ ),
472
+ ]
473
+ .sort()
468
474
  .map((item, key) => (
469
- <li key={key}>{item.area || item.file || item.title}</li>
475
+ <li key={key}>{item}</li>
470
476
  ))}
471
477
  </ul>
472
478
  <br />
@@ -17,6 +17,8 @@ function CclTab(props) {
17
17
  routing,
18
18
  redirect,
19
19
  loginRequired,
20
+ className = '',
21
+ hasSubtab = false,
20
22
  } = props;
21
23
  const token = useSelector((state) => state.userSession?.token);
22
24
  function onTabClick() {
@@ -25,22 +27,24 @@ function CclTab(props) {
25
27
  const [redirecting, setRedirecting] = React.useState(false);
26
28
  return (
27
29
  <div
28
- className={cx('card', activeTab === tabId ? 'active' : '')}
30
+ className={cx('card ' + className, activeTab === tabId ? 'active ' : '')}
29
31
  onClick={(e) => {
30
32
  !loginRequired
31
- ? onTabClick(e)
33
+ ? !hasSubtab && onTabClick(e)
32
34
  : loginRequired && token && onTabClick(e);
33
35
  }}
34
36
  onKeyDown={(e) => {
35
37
  !loginRequired
36
- ? onTabClick(e)
38
+ ? !hasSubtab && onTabClick(e)
37
39
  : loginRequired && token && onTabClick(e);
38
40
  }}
39
41
  tabIndex="0"
40
42
  role="button"
41
43
  id={tabId}
42
44
  >
43
- {loginRequired && !token ? (
45
+ {hasSubtab ? (
46
+ <span>{tabTitle}</span>
47
+ ) : loginRequired && !token ? (
44
48
  <CclLoginModal
45
49
  otherPath={redirect ? redirect : undefined}
46
50
  triggerComponent={() => (
@@ -4,6 +4,7 @@ import React, { useState } from 'react';
4
4
 
5
5
  import CclTab from './CclTab';
6
6
  import PropTypes from 'prop-types';
7
+ import { slugify } from '../Blocks/utils';
7
8
 
8
9
  /**
9
10
  * Tabs component documentation.
@@ -23,8 +24,7 @@ import PropTypes from 'prop-types';
23
24
  const CclTabs = (props) => {
24
25
  let { children, routing = false } = props;
25
26
  let [activeTab, setActiveTab] = useState(
26
- props.children[0].props.tabId ||
27
- props.children[0].props.tabTitle.split(' ').join('-'),
27
+ props.children[0].props.tabId || slugify(props.children[0].props.tabTitle),
28
28
  );
29
29
 
30
30
  function onClickTabItem(tab) {
@@ -35,11 +35,11 @@ const CclTabs = (props) => {
35
35
  const firstTab = children.filter((item) => !!item?.props?.tabTitle)[0];
36
36
  if (routing) {
37
37
  if (hash.startsWith('b_size')) {
38
- setActiveTab(firstTab.props?.tabTitle?.split(' ').join('-'));
38
+ setActiveTab(slugify(firstTab.props?.tabTitle));
39
39
  } else if (hash) {
40
40
  setActiveTab(hash);
41
41
  } else {
42
- setActiveTab(firstTab.props?.tabTitle?.split(' ').join('-'));
42
+ setActiveTab(slugify(firstTab.props?.tabTitle));
43
43
  }
44
44
  }
45
45
  }, [children, routing]);
@@ -53,7 +53,14 @@ const CclTabs = (props) => {
53
53
  .filter((item) => !!item?.props?.tabTitle)
54
54
  .map((child, key) => {
55
55
  const { tabTitle, redirect } = child.props;
56
- const tabId = tabTitle?.split(' ').join('-');
56
+ const tabId = slugify(tabTitle);
57
+ let hasSubtab = false;
58
+ const currentTab = children.filter(
59
+ (item) => slugify(item?.props?.tabTitle) === tabId,
60
+ )[0];
61
+ if (currentTab?.props?.parent) {
62
+ hasSubtab = true;
63
+ }
57
64
  return (
58
65
  <CclTab
59
66
  activeTab={activeTab}
@@ -63,6 +70,7 @@ const CclTabs = (props) => {
63
70
  tabTitle={tabTitle}
64
71
  onClick={onClickTabItem}
65
72
  redirect={redirect}
73
+ hasSubtab={hasSubtab}
66
74
  {...child.props}
67
75
  />
68
76
  );
@@ -85,8 +93,7 @@ const CclTabs = (props) => {
85
93
  .flat()
86
94
  .filter((item) => !!item?.props?.tabTitle)
87
95
  .map((child, index) => {
88
- return child.props?.tabTitle?.split(' ').join('-') !==
89
- activeTab ? (
96
+ return slugify(child.props?.tabTitle) !== activeTab ? (
90
97
  <div key={index} className="deactivate-content">
91
98
  {child.props.children}
92
99
  </div>
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Diff field component.
3
+ * @module components/manage/Diff/DiffField
4
+ */
5
+
6
+ import React from 'react';
7
+ // import { diffWords as dWords } from 'diff';
8
+ import { join, map } from 'lodash';
9
+ import PropTypes from 'prop-types';
10
+ import { Grid } from 'semantic-ui-react';
11
+ import ReactDOMServer from 'react-dom/server';
12
+ import { Provider } from 'react-intl-redux';
13
+ import { createBrowserHistory } from 'history';
14
+ // eslint-disable-next-line import/no-unresolved
15
+ import { ConnectedRouter } from 'connected-react-router';
16
+ import { useSelector } from 'react-redux';
17
+
18
+ import { Api } from '@plone/volto/helpers';
19
+ import configureStore from '@plone/volto/store';
20
+ import { DefaultView } from '@plone/volto/components/';
21
+
22
+ import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';
23
+
24
+ /**
25
+ * Enhanced diff words utility
26
+ * @function diffWords
27
+ * @param oneStr Field one
28
+ * @param twoStr Field two
29
+ */
30
+
31
+ /**
32
+ * Diff field component.
33
+ * @function DiffField
34
+ * @param {*} one Field one
35
+ * @param {*} two Field two
36
+ * @param {Object} schema Field schema
37
+ * @returns {string} Markup of the component.
38
+ */
39
+ const DiffField = ({
40
+ one,
41
+ two,
42
+ contentOne,
43
+ contentTwo,
44
+ view,
45
+ schema,
46
+ diffLib,
47
+ }) => {
48
+ const language = useSelector((state) => state.intl.locale);
49
+ const readable_date_format = {
50
+ dateStyle: 'full',
51
+ timeStyle: 'short',
52
+ };
53
+ const diffWords = (oneStr, twoStr) => {
54
+ return diffLib.diffWords(String(oneStr), String(twoStr));
55
+ };
56
+
57
+ let parts, oneArray, twoArray;
58
+ if (schema.widget) {
59
+ switch (schema.widget) {
60
+ case 'richtext':
61
+ parts = diffWords(one?.data, two?.data);
62
+ break;
63
+ case 'datetime':
64
+ parts = diffWords(
65
+ new Intl.DateTimeFormat(language, readable_date_format).format(
66
+ new Date(one),
67
+ ),
68
+ new Intl.DateTimeFormat(language, readable_date_format).format(
69
+ new Date(two),
70
+ ),
71
+ );
72
+ break;
73
+ case 'json':
74
+ const api = new Api();
75
+ const history = createBrowserHistory();
76
+ const store = configureStore(window.__data, history, api);
77
+ parts = diffWords(
78
+ ReactDOMServer.renderToStaticMarkup(
79
+ <Provider store={store}>
80
+ <ConnectedRouter history={history}>
81
+ <DefaultView content={contentOne} />
82
+ </ConnectedRouter>
83
+ </Provider>,
84
+ ),
85
+ ReactDOMServer.renderToStaticMarkup(
86
+ <Provider store={store}>
87
+ <ConnectedRouter history={history}>
88
+ <DefaultView content={contentTwo} />
89
+ </ConnectedRouter>
90
+ </Provider>,
91
+ ),
92
+ );
93
+ break;
94
+ case 'textarea':
95
+ default:
96
+ parts = diffWords(one, two);
97
+ break;
98
+ }
99
+ } else if (schema.type === 'object') {
100
+ parts = diffWords(one?.filename || one, two?.filename || two);
101
+ } else if (schema.type === 'array') {
102
+ oneArray = (one || []).map((i) => i?.title || i);
103
+ twoArray = (two || []).map((j) => j?.title || j);
104
+ parts = diffWords(oneArray, twoArray);
105
+ } else {
106
+ parts = diffWords(one?.title || one, two?.title || two);
107
+ }
108
+ return (
109
+ <Grid compact data-testid="DiffField">
110
+ <Grid.Row inverted>
111
+ <Grid.Column width={12}>{schema.title}</Grid.Column>
112
+ </Grid.Row>
113
+ {view === 'split' && (
114
+ <Grid.Row>
115
+ <Grid.Column width={6} verticalAlign="top">
116
+ <span
117
+ dangerouslySetInnerHTML={{
118
+ __html: join(
119
+ map(
120
+ parts,
121
+ (part) =>
122
+ (part.removed &&
123
+ `<span class="deletion">${part.value}</span>`) ||
124
+ (!part.added && `<span>${part.value}</span>`) ||
125
+ '',
126
+ ),
127
+ '',
128
+ ),
129
+ }}
130
+ />
131
+ </Grid.Column>
132
+ <Grid.Column width={6} verticalAlign="top">
133
+ <span
134
+ dangerouslySetInnerHTML={{
135
+ __html: join(
136
+ map(
137
+ parts,
138
+ (part) =>
139
+ (part.added &&
140
+ `<span class="addition">${part.value}</span>`) ||
141
+ (!part.removed && `<span>${part.value}</span>`) ||
142
+ '',
143
+ ),
144
+ '',
145
+ ),
146
+ }}
147
+ />
148
+ </Grid.Column>
149
+ </Grid.Row>
150
+ )}
151
+ {view === 'unified' && (
152
+ <Grid.Row>
153
+ <Grid.Column width={16} verticalAlign="top">
154
+ <span
155
+ dangerouslySetInnerHTML={{
156
+ __html: join(
157
+ map(
158
+ parts,
159
+ (part) =>
160
+ (part.removed &&
161
+ `<span class="deletion">${part.value}</span>`) ||
162
+ (part.added &&
163
+ `<span class="addition">${part.value}</span>`) ||
164
+ (!part.added && `<span>${part.value}</span>`),
165
+ ),
166
+ '',
167
+ ),
168
+ }}
169
+ />
170
+ </Grid.Column>
171
+ </Grid.Row>
172
+ )}
173
+ </Grid>
174
+ );
175
+ };
176
+
177
+ /**
178
+ * Property types.
179
+ * @property {Object} propTypes Property types.
180
+ * @static
181
+ */
182
+ DiffField.propTypes = {
183
+ one: PropTypes.any.isRequired,
184
+ two: PropTypes.any.isRequired,
185
+ contentOne: PropTypes.any,
186
+ contentTwo: PropTypes.any,
187
+ view: PropTypes.string.isRequired,
188
+ schema: PropTypes.shape({
189
+ widget: PropTypes.string,
190
+ type: PropTypes.string,
191
+ title: PropTypes.string,
192
+ }).isRequired,
193
+ };
194
+
195
+ export default injectLazyLibs('diffLib')(DiffField);