@eeacms/volto-clms-theme 1.1.178 → 1.1.180

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,30 @@ 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.1.180](https://github.com/eea/volto-clms-theme/compare/1.1.179...1.1.180) - 29 August 2024
8
+
9
+ #### :rocket: New Features
10
+
11
+ - feat: modify the tabs block so that it can include a direct link within the tab itself -refs #273221 [ana-oprea - [`6816ec6`](https://github.com/eea/volto-clms-theme/commit/6816ec62f6b260a491ba8ef3f5964beb5917e1a5)]
12
+
13
+ #### :hammer_and_wrench: Others
14
+
15
+ - increase size of title column based on the number of selected columns -refs #274031 [ana-oprea - [`6c5cf75`](https://github.com/eea/volto-clms-theme/commit/6c5cf75e89c01b33d44b6d02c9fff86c1f93ee67)]
16
+ - increase size of title column on contents -refs #274031 [ana-oprea - [`5af9200`](https://github.com/eea/volto-clms-theme/commit/5af9200e9109261df92a89ec7fc01e237f611ed0)]
17
+ ### [1.1.179](https://github.com/eea/volto-clms-theme/compare/1.1.178...1.1.179) - 28 August 2024
18
+
19
+ #### :house: Internal changes
20
+
21
+ - style: Automated code fix [eea-jenkins - [`c668e51`](https://github.com/eea/volto-clms-theme/commit/c668e51c7592ed244b8fa486e785ced43ca5cb5f)]
22
+
23
+ #### :hammer_and_wrench: Others
24
+
25
+ - Code cleanup [Tiberiu Ichim - [`4b3d00f`](https://github.com/eea/volto-clms-theme/commit/4b3d00fa7e02a422a7be14e95c2ddc23f07a60cf)]
26
+ - Fix parenting [Tiberiu Ichim - [`bf536b8`](https://github.com/eea/volto-clms-theme/commit/bf536b85715471db0f373315a8bca84cbde2aec1)]
27
+ - Expand context navigation on /en/faq [Tiberiu Ichim - [`37b8eb1`](https://github.com/eea/volto-clms-theme/commit/37b8eb129651e781e0ced8c79237bdf7b0bb55e1)]
28
+ - WIP [Tiberiu Ichim - [`dbf9e58`](https://github.com/eea/volto-clms-theme/commit/dbf9e58a74442005933356e875965ad8f6dd9cd0)]
29
+ - Rewrite for lisibility [Tiberiu Ichim - [`e8c8a20`](https://github.com/eea/volto-clms-theme/commit/e8c8a2037a81832ae901892120e74c11757ebf55)]
30
+ - Rewrite for lisibility [Tiberiu Ichim - [`9fe7896`](https://github.com/eea/volto-clms-theme/commit/9fe78963411c231cbf8271a29209d51c5b7c4345)]
7
31
  ### [1.1.178](https://github.com/eea/volto-clms-theme/compare/1.1.177...1.1.178) - 26 August 2024
8
32
 
9
33
  #### :house: Internal changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-clms-theme",
3
- "version": "1.1.178",
3
+ "version": "1.1.180",
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",
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import CclFAQBlockView from './CclFAQBlockView';
3
3
 
4
4
  const CclFAQBlockEdit = (props) => {
5
- return <CclFAQBlockView props={props} isEditMode={true} />;
5
+ return <CclFAQBlockView {...props} isEditMode={true} />;
6
6
  };
7
7
 
8
8
  export default CclFAQBlockEdit;
@@ -1,3 +1,4 @@
1
+ import cx from 'classnames';
1
2
  import React, { useState } from 'react';
2
3
  import AnimateHeight from 'react-animate-height';
3
4
  import { useDispatch, useSelector } from 'react-redux';
@@ -6,18 +7,46 @@ import { Accordion, Segment } from 'semantic-ui-react';
6
7
  import { getContextNavigation } from '@plone/volto/actions';
7
8
  import { Icon, UniversalLink } from '@plone/volto/components';
8
9
  import RenderBlocks from '@plone/volto/components/theme/View/RenderBlocks';
9
- import { hasBlocksData } from '@plone/volto/helpers';
10
+ import { hasBlocksData, getBaseUrl } from '@plone/volto/helpers';
11
+
12
+ import { CclTabs } from '@eeacms/volto-clms-theme/components/CclTab';
13
+
10
14
  import penSVG from '@plone/volto/icons/pen.svg';
11
15
  import config from '@plone/volto/registry';
12
- import { CclTabs } from '@eeacms/volto-clms-theme/components/CclTab';
13
- // import { StringToHTML } from '@eeacms/volto-clms-theme/components/CclUtils';
16
+
17
+ const flattenCN = (cn_items) =>
18
+ cn_items.reduce(
19
+ (acc, curr) => [
20
+ ...acc,
21
+ curr,
22
+ ...curr.items
23
+ .filter((i) => i.type === 'document')
24
+ .map((item) => ({ ...item, isSubtab: true })),
25
+ ],
26
+ [],
27
+ );
28
+
29
+ // this is just to highlight that the way CclTabs is working is misleading
30
+ // It reads random properties from <div> children, which is unexpected
31
+ function Tab(props) {
32
+ return <div {...props} />;
33
+ }
14
34
 
15
35
  const CclFAQBlockView = (props) => {
16
- const { isEditMode } = props;
36
+ const { isEditMode, content } = props;
37
+ const pathname = getBaseUrl(props.pathname || props.path);
38
+ const cn_key = `${pathname}/@contextnavigation`;
39
+
40
+ // this are the accordions that are opened
41
+ const [activeIndex, setActiveIndex] = useState([]);
42
+
17
43
  const dispatch = useDispatch();
18
- const path = useSelector((state) => state.router.location.pathname);
19
- const contextNavigation = useSelector((state) => state.contextNavigation);
20
- const cn_key = `${path.replace('/edit', '')}/@contextnavigation`;
44
+ const contextNavigationItems = useSelector(
45
+ (state) =>
46
+ state.contextNavigation?.[cn_key]?.data?.items ||
47
+ content['@components']?.['contextnavigation']?.items,
48
+ );
49
+
21
50
  const handleClick = ({ index }) => {
22
51
  const newIndex =
23
52
  activeIndex.indexOf(index) === -1
@@ -27,128 +56,93 @@ const CclFAQBlockView = (props) => {
27
56
  setActiveIndex(newIndex);
28
57
  };
29
58
 
30
- const flattenCN = (cn_items) => {
31
- return cn_items.reduce((prev, curr) => {
32
- prev.push(curr);
33
- if (curr.items.filter((i) => i.type === 'document').length > 0) {
34
- curr.items
35
- .filter((i) => i.type === 'document')
36
- .forEach((i_i) => prev.push({ ...i_i, isSubtab: true }));
37
- }
38
- return prev;
39
- }, []);
40
- };
41
59
  const flatCN = flattenCN(
42
- contextNavigation?.[cn_key]?.data?.items
43
- ? contextNavigation?.[cn_key]?.data?.items.filter(
44
- (i) => i.type === 'document',
45
- )
46
- : [],
60
+ contextNavigationItems?.filter((i) => i.type === 'document') || [],
47
61
  );
48
- const [activeIndex, setActiveIndex] = useState([]);
62
+
49
63
  React.useEffect(() => {
50
- dispatch(getContextNavigation(path.replace('/edit', '')));
51
- }, [path, dispatch]);
64
+ isEditMode && dispatch(getContextNavigation(pathname));
65
+ }, [pathname, dispatch, isEditMode]);
66
+
52
67
  React.useEffect(() => {
53
- let indexes = [];
54
- // eslint-disable-next-line no-unused-expressions
55
- contextNavigation?.[cn_key]?.data?.items &&
56
- contextNavigation?.[cn_key]?.data?.items.forEach((i) => {
57
- if (i.items.length > 0) {
58
- indexes.push(i.items[0].normalized_id);
59
- }
60
- if (i.type === 'document') {
61
- i.items.forEach((i_i) => {
62
- if (i_i.items.length > 0) {
63
- indexes.push(i_i.items[0].normalized_id);
64
- }
65
- });
66
- }
67
- });
68
+ let indexes = (contextNavigationItems || []).reduce(
69
+ (acc, cur) => [
70
+ ...acc,
71
+ ...(cur.items?.length ? [cur.items[0].normalized_id] : []),
72
+ ...cur.items
73
+ ?.map((item) => item.items?.[0]?.normalized_id)
74
+ .filter((id) => !!id),
75
+ ],
76
+ [],
77
+ );
68
78
  setActiveIndex(indexes);
69
- // eslint-disable-next-line react-hooks/exhaustive-deps
70
- }, [contextNavigation?.[cn_key]?.data?.items]);
79
+ }, [contextNavigationItems]);
71
80
 
72
81
  const titleIcons = config.blocks?.blocksConfig?.accordion?.titleIcons;
73
82
 
74
83
  return (
75
84
  <div id="faq-listing" className="ccl-container tab-container">
76
- {contextNavigation?.[cn_key]?.loaded ? (
77
- contextNavigation?.[cn_key]?.data?.items?.length > 0 && (
78
- <CclTabs routing={true}>
79
- {flatCN
80
- .filter((cn) => cn.type === 'document')
81
- .map((cn, key) => (
82
- <div
83
- key={key}
84
- tabTitle={cn.title}
85
- className={
86
- cn.isSubtab
87
- ? 'subcard'
88
- : cn.items.filter((i) => i.type === 'document').length > 0
89
- }
90
- parent={
91
- cn.items.filter((i) => i.type === 'document').length > 0
92
- }
93
- >
94
- <div className="accordion-block">
95
- {cn.items
96
- .filter((item) => item.type === 'faq')
97
- .map((item, item_key) => {
98
- return (
99
- <Accordion fluid styled key={item_key}>
100
- <Accordion.Title
101
- as={'h2'}
102
- onClick={() =>
103
- handleClick({ index: item.normalized_id })
104
- }
105
- className={'accordion-title align-arrow-right'}
106
- >
107
- {activeIndex.includes(item.normalized_id) ? (
108
- <Icon name={titleIcons.opened.rightPosition} />
109
- ) : (
110
- <Icon name={titleIcons.closed.rightPosition} />
111
- )}
112
- {isEditMode && (
113
- <UniversalLink
114
- openLinkInNewTab={true}
115
- href={`${item['@id']}/edit`}
116
- >
117
- <Icon
118
- name={penSVG}
119
- className="circled"
120
- title={'Edit'}
121
- />
122
- </UniversalLink>
123
- )}
124
- <span>{item.title}</span>
125
- </Accordion.Title>
126
- <Accordion.Content
127
- active={activeIndex.includes(item.normalized_id)}
128
- >
129
- <AnimateHeight
130
- animateOpacity
131
- duration={500}
132
- height={'auto'}
133
- >
134
- {/* <StringToHTML
135
- string={item.text ? item.text.data : ''}
136
- /> */}
137
- {hasBlocksData(item) && (
138
- <RenderBlocks content={item} />
139
- )}
140
- </AnimateHeight>
141
- </Accordion.Content>
142
- </Accordion>
143
- );
144
- })}
145
- </div>
146
- </div>
147
- ))}
148
- </CclTabs>
149
- )
85
+ {contextNavigationItems?.length > 0 ? (
86
+ <CclTabs routing={true}>
87
+ {flatCN.map((cn, key) => (
88
+ <Tab
89
+ key={key}
90
+ tabTitle={cn.title}
91
+ className={cx({ subcard: cn.isSubtab })}
92
+ isParent={!!cn.items.filter((i) => i.type === 'document').length}
93
+ >
94
+ <div className="accordion-block">
95
+ {cn.items
96
+ .filter((item) => item.type === 'faq')
97
+ .map((item, item_key) => (
98
+ <Accordion fluid styled key={item_key}>
99
+ <Accordion.Title
100
+ as={'h2'}
101
+ onClick={() =>
102
+ handleClick({ index: item.normalized_id })
103
+ }
104
+ className={'accordion-title align-arrow-right'}
105
+ >
106
+ {activeIndex.includes(item.normalized_id) ? (
107
+ <Icon name={titleIcons.opened.rightPosition} />
108
+ ) : (
109
+ <Icon name={titleIcons.closed.rightPosition} />
110
+ )}
111
+ {isEditMode && (
112
+ <UniversalLink
113
+ openLinkInNewTab={true}
114
+ href={`${item['@id']}/edit`}
115
+ >
116
+ <Icon
117
+ name={penSVG}
118
+ className="circled"
119
+ title={'Edit'}
120
+ />
121
+ </UniversalLink>
122
+ )}
123
+ <span>{item.title}</span>
124
+ </Accordion.Title>
125
+ <Accordion.Content
126
+ active={activeIndex.includes(item.normalized_id)}
127
+ >
128
+ <AnimateHeight
129
+ animateOpacity
130
+ duration={500}
131
+ height={'auto'}
132
+ >
133
+ {hasBlocksData(item) && (
134
+ <RenderBlocks content={item} />
135
+ )}
136
+ </AnimateHeight>
137
+ </Accordion.Content>
138
+ </Accordion>
139
+ ))}
140
+ </div>
141
+ </Tab>
142
+ ))}
143
+ </CclTabs>
150
144
  ) : (
151
- <Segment loading={contextNavigation?.[cn_key]?.loading}></Segment>
145
+ <Segment loading={true}></Segment>
152
146
  )}
153
147
  </div>
154
148
  );
@@ -7,6 +7,7 @@ import { compose } from 'redux';
7
7
  import { RenderBlocks } from '@plone/volto/components';
8
8
  import { withScrollToTarget } from '@eeacms/volto-tabs-block/hocs';
9
9
  import { useLocation } from 'react-router-dom';
10
+ import { flattenToAppURL } from '@plone/volto/helpers/Url/Url';
10
11
 
11
12
  import { slugify } from '../../utils';
12
13
  import './fontawesome';
@@ -53,6 +54,7 @@ const TabsComponent = (props) => {
53
54
  {tabsList.map((tab, index) => {
54
55
  const title = tabs[tab].title;
55
56
  const subTab = tabs[tab]?.subTab?.subtab || false;
57
+ const redirectURL = tabs[tab]?.subTab?.redirectURL || false;
56
58
  const tabIndex = index + 1;
57
59
  const nextSubTab = tabs[tabsList[tabIndex]]?.subTab?.subtab || false;
58
60
  const defaultTitle = `Tab ${tabIndex}`;
@@ -71,7 +73,9 @@ const TabsComponent = (props) => {
71
73
  <span>{title || defaultTitle}</span>
72
74
  ) : (
73
75
  <NavLink
74
- to={'?' + tabHash}
76
+ to={`${
77
+ redirectURL ? flattenToAppURL(redirectURL) : '?' + tabHash
78
+ }`}
75
79
  className="collapsed"
76
80
  onClick={(e) => {
77
81
  handleClick(
@@ -4,7 +4,7 @@ export const subTabSchema = () => ({
4
4
  {
5
5
  id: 'default',
6
6
  title: 'Default',
7
- fields: ['subtab'],
7
+ fields: ['subtab', 'redirectURL'],
8
8
  },
9
9
  ],
10
10
  properties: {
@@ -14,6 +14,11 @@ export const subTabSchema = () => ({
14
14
  type: 'boolean',
15
15
  default: false,
16
16
  },
17
+ redirectURL: {
18
+ title: 'Redirect URL',
19
+ description: 'Redirect URL',
20
+ widget: 'url',
21
+ },
17
22
  },
18
23
  required: [],
19
24
  });
@@ -43,7 +43,7 @@ const CclTabs = (props) => {
43
43
  if (possible_hashes.includes(hash)) setActiveTab(hash);
44
44
  } else if (
45
45
  children.filter((item) => !!item?.props?.tabTitle).length > 1 &&
46
- firstTab.props?.parent
46
+ firstTab.props?.isParent
47
47
  ) {
48
48
  setActiveTab(
49
49
  slugify(
@@ -72,7 +72,7 @@ const CclTabs = (props) => {
72
72
  const currentTab = children.filter(
73
73
  (item) => slugify(item?.props?.tabTitle) === tabId,
74
74
  )[0];
75
- if (currentTab?.props?.parent) {
75
+ if (currentTab?.props?.isParent) {
76
76
  hasSubtab = true;
77
77
  }
78
78
  return (
@@ -92,6 +92,7 @@ import sortUpSVG from '@plone/volto/icons/sort-up.svg';
92
92
  import downKeySVG from '@plone/volto/icons/down-key.svg';
93
93
  import moreSVG from '@plone/volto/icons/more.svg';
94
94
  import clearSVG from '@plone/volto/icons/clear.svg';
95
+ import cx from 'classnames';
95
96
 
96
97
  const messages = defineMessages({
97
98
  back: {
@@ -1187,6 +1188,13 @@ class Contents extends Component {
1187
1188
  const Container =
1188
1189
  config.getComponent({ name: 'Container' }).component || SemanticContainer;
1189
1190
 
1191
+ const columnCountClass = cx({
1192
+ 'many-columns': this.state.index.selectedCount > 4,
1193
+ 'three-columns': this.state.index.selectedCount === 4,
1194
+ 'two-columns': this.state.index.selectedCount === 3,
1195
+ 'one-column': this.state.index.selectedCount === 2,
1196
+ });
1197
+
1190
1198
  return this.props.token && this.props.objectActions?.length > 0 ? (
1191
1199
  <>
1192
1200
  {folderContentsAction ? (
@@ -1879,7 +1887,13 @@ class Contents extends Component {
1879
1887
  </Dropdown>
1880
1888
  </Segment>
1881
1889
  <div className="contents-table-wrapper">
1882
- <Table selectable compact singleLine attached>
1890
+ <Table
1891
+ selectable
1892
+ compact
1893
+ singleLine
1894
+ attached
1895
+ className={columnCountClass}
1896
+ >
1883
1897
  <Table.Header>
1884
1898
  <Table.Row>
1885
1899
  <Table.HeaderCell>
@@ -2069,8 +2083,11 @@ class Contents extends Component {
2069
2083
  </Popup>
2070
2084
  </Table.HeaderCell>
2071
2085
  <Table.HeaderCell
2072
- width={Math.ceil(
2073
- 16 / this.state.index.selectedCount,
2086
+ width={Math.max(
2087
+ 4,
2088
+ Math.ceil(
2089
+ 10 - this.state.index.selectedCount - 1,
2090
+ ),
2074
2091
  )}
2075
2092
  >
2076
2093
  <FormattedMessage
@@ -2084,9 +2101,7 @@ class Contents extends Component {
2084
2101
  this.state.index.values[index].selected && (
2085
2102
  <ContentsIndexHeader
2086
2103
  key={index}
2087
- width={Math.ceil(
2088
- 16 / this.state.index.selectedCount,
2089
- )}
2104
+ width={2}
2090
2105
  label={
2091
2106
  this.state.index.values[index].label
2092
2107
  }
@@ -161,16 +161,17 @@ export const ContentsItemComponent = ({
161
161
  title={item['Type'] || item['@type']}
162
162
  />
163
163
  <span title={item.title} className="content-title-force-one-line">
164
+ {item.ExpirationDate !== 'None' &&
165
+ new Date(item.ExpirationDate).getTime() <
166
+ new Date().getTime() && (
167
+ <Button className="button-margin" size="mini">
168
+ <FormattedMessage id="Expired" defaultMessage="Expired" />
169
+ </Button>
170
+ )}
164
171
  {item.title}
165
172
  </span>
166
173
  </div>
167
- {item.ExpirationDate !== 'None' &&
168
- new Date(item.ExpirationDate).getTime() <
169
- new Date().getTime() && (
170
- <Button className="button-margin" size="mini">
171
- <FormattedMessage id="Expired" defaultMessage="Expired" />
172
- </Button>
173
- )}
174
+
174
175
  {item.EffectiveDate !== 'None' &&
175
176
  new Date(item.EffectiveDate).getTime() > new Date().getTime() && (
176
177
  <Button className="button-margin effective-future" size="mini">
package/src/index.js CHANGED
@@ -201,6 +201,12 @@ const applyConfig = (config) => {
201
201
  'navroot',
202
202
  ],
203
203
  },
204
+ {
205
+ match: '/en/faq',
206
+ exact: false,
207
+ GET_CONTENT: ['contextnavigation'],
208
+ },
209
+
204
210
  {
205
211
  match: '',
206
212
  GET_CONTENT: ['navigation'],
@@ -1,6 +1,27 @@
1
+ .folder-contents .ui.table .icon-align-name {
2
+ max-width: unset;
3
+ }
4
+
5
+ .ui.table td,
6
+ .ui.table th,
1
7
  .folder-contents .ui.table .icon-align-name span.content-title-force-one-line {
2
- max-width: 250px;
3
8
  white-space: nowrap;
4
9
  overflow: hidden;
5
10
  text-overflow: ellipsis;
6
11
  }
12
+
13
+ .ui.table.many-columns .icon-align-name span.content-title-force-one-line {
14
+ max-width: 400px;
15
+ }
16
+
17
+ .ui.table.three-columns .icon-align-name span.content-title-force-one-line {
18
+ width: 440px;
19
+ }
20
+
21
+ .ui.table.two-columns .icon-align-name span.content-title-force-one-line {
22
+ width: 600px;
23
+ }
24
+
25
+ .ui.table.one-column .icon-align-name span.content-title-force-one-line {
26
+ width: 770px;
27
+ }