@eeacms/volto-tabs-block 4.0.1 → 5.0.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 +40 -0
- package/package.json +4 -2
- package/src/components/Edit.jsx +13 -17
- package/src/components/EditBlockWrapper.jsx +0 -4
- package/src/components/View.jsx +29 -1
- package/src/components/templates/accordion/View.jsx +15 -4
- package/src/components/templates/carousel/HorizontalView.jsx +4 -49
- package/src/components/templates/carousel/VerticalView.jsx +2 -48
- package/src/components/templates/default/Edit.jsx +12 -11
- package/src/components/templates/default/View.jsx +27 -57
- package/src/components/templates/horizontal-responsive/View.jsx +28 -66
- package/src/helpers.js +18 -0
- package/src/index.js +3 -7
- package/src/less/tabs.less +2 -2
- package/src/widgets/TabsWidget.jsx +4 -6
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,46 @@ 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
|
+
### [5.0.1](https://github.com/eea/volto-tabs-block/compare/5.0.0...5.0.1) - 15 May 2023
|
|
8
|
+
|
|
9
|
+
#### :bug: Bug Fixes
|
|
10
|
+
|
|
11
|
+
- fix: show properties for block inside tabs block [Mimi - [`5b4dd3d`](https://github.com/eea/volto-tabs-block/commit/5b4dd3d5f3a045126ead6c33cc8e2e470d691c6d)]
|
|
12
|
+
|
|
13
|
+
## [5.0.0](https://github.com/eea/volto-tabs-block/compare/4.0.1...5.0.0) - 24 April 2023
|
|
14
|
+
|
|
15
|
+
#### :rocket: New Features
|
|
16
|
+
|
|
17
|
+
- feat(SEO): Render content of all tabs initially for SEO optimization and use CSS to hide non-active ones - refs #250207 [dobri1408 - [`f3499f2`](https://github.com/eea/volto-tabs-block/commit/f3499f2f956bf144ae274f7e4277ae3842e3d206)]
|
|
18
|
+
|
|
19
|
+
#### :bug: Bug Fixes
|
|
20
|
+
|
|
21
|
+
- fix(tabs): scrollToElement on mount of View templates [nileshgulia1 - [`be6a6fb`](https://github.com/eea/volto-tabs-block/commit/be6a6fbba8fde9d5859f1a2408442c9814a72399)]
|
|
22
|
+
|
|
23
|
+
#### :nail_care: Enhancements
|
|
24
|
+
|
|
25
|
+
- refactor(code): Cleanup code as per sonarqube issues [Teodor Voicu - [`5b73ca6`](https://github.com/eea/volto-tabs-block/commit/5b73ca6b504a1b295f992f9536bda6d7a3448d29)]
|
|
26
|
+
- refactor(helpers): make getParentTabFromHash bulletproof refs#153601 [nileshgulia1 - [`890ac8c`](https://github.com/eea/volto-tabs-block/commit/890ac8c0ed4ad2bc38d46283bc945a5b58febbbf)]
|
|
27
|
+
|
|
28
|
+
#### :house: Internal changes
|
|
29
|
+
|
|
30
|
+
- chore(package.json): update package version from 4.0.1 to 5.0.0 [Miu Razvan - [`e0fee6f`](https://github.com/eea/volto-tabs-block/commit/e0fee6f42ed5111db62af156b61fdc5a519e8dc7)]
|
|
31
|
+
- chore: fix eslint refs #153601 [nileshgulia1 - [`79589b7`](https://github.com/eea/volto-tabs-block/commit/79589b792c9a7feabf4a942617398d1ae83277ae)]
|
|
32
|
+
|
|
33
|
+
#### :hammer_and_wrench: Others
|
|
34
|
+
|
|
35
|
+
- Merge pull request #35 from eea/fix-tab-anchors-scroll [Nilesh - [`532f05b`](https://github.com/eea/volto-tabs-block/commit/532f05b76dbbc0e6db7bafd270aad83e9e35a5d5)]
|
|
36
|
+
- clean up code [Miu Razvan - [`0228e2d`](https://github.com/eea/volto-tabs-block/commit/0228e2ddf700fe55db902b9c8b2cf0556b316ec5)]
|
|
37
|
+
- prettier fix [Miu Razvan - [`cb7564c`](https://github.com/eea/volto-tabs-block/commit/cb7564c4379adccf3a0a3880bcdb91fe54db8908)]
|
|
38
|
+
- fix onSelectBlock callback [Miu Razvan - [`5af17e6`](https://github.com/eea/volto-tabs-block/commit/5af17e6c734a5717b99685077a0825aa68e6867f)]
|
|
39
|
+
- revert breaking changes [Miu Razvan - [`ff1fc59`](https://github.com/eea/volto-tabs-block/commit/ff1fc596aee480c79fd42d0bb97570041f215ac8)]
|
|
40
|
+
- bump major version [nileshgulia1 - [`4fbae52`](https://github.com/eea/volto-tabs-block/commit/4fbae52ec06b3830c29b41385095a5b383b01e4b)]
|
|
41
|
+
- move slug check to volto-anchors [nileshgulia1 - [`1e33c22`](https://github.com/eea/volto-tabs-block/commit/1e33c229fac8e9d7ab65941fc7124a213d59e603)]
|
|
42
|
+
- fix scrolling onTabChange [nileshgulia1 - [`7051676`](https://github.com/eea/volto-tabs-block/commit/7051676673d395f142790ac92438c20f3d0b2006)]
|
|
43
|
+
- fix scroll to target [nileshgulia1 - [`02edb7b`](https://github.com/eea/volto-tabs-block/commit/02edb7b8699e9a39f8f8d302fd13e68910d685c4)]
|
|
44
|
+
- scrollToTarget method [nileshgulia1 - [`d6732e1`](https://github.com/eea/volto-tabs-block/commit/d6732e1d2d682010623d1efe38502642b8faa7de)]
|
|
45
|
+
- re add hocs, not a breaking change [nileshgulia1 - [`a1bb60b`](https://github.com/eea/volto-tabs-block/commit/a1bb60b2231d8dd335dc9cb8c3ec9b4f2580639b)]
|
|
46
|
+
- scroll to anchors computed from hash [nileshgulia1 - [`9a3da29`](https://github.com/eea/volto-tabs-block/commit/9a3da294ce0a35d1be2f06d2c4df39e51d49d895)]
|
|
7
47
|
### [4.0.1](https://github.com/eea/volto-tabs-block/compare/4.0.0...4.0.1) - 4 April 2023
|
|
8
48
|
|
|
9
49
|
#### :rocket: New Features
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eeacms/volto-tabs-block",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.1",
|
|
4
4
|
"description": "volto-tabs-block: Volto add-on",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"author": "European Environment Agency: IDM2 A-Team",
|
|
@@ -17,10 +17,12 @@
|
|
|
17
17
|
"url": "git@github.com:eea/volto-tabs-block.git"
|
|
18
18
|
},
|
|
19
19
|
"addons": [
|
|
20
|
-
"@eeacms/volto-block-style"
|
|
20
|
+
"@eeacms/volto-block-style",
|
|
21
|
+
"@eeacms/volto-anchors"
|
|
21
22
|
],
|
|
22
23
|
"dependencies": {
|
|
23
24
|
"@eeacms/volto-block-style": "*",
|
|
25
|
+
"@eeacms/volto-anchors": "*",
|
|
24
26
|
"react-responsive-tabs": "4.4.3",
|
|
25
27
|
"react-slick": "^0.24.0",
|
|
26
28
|
"slick-carousel": "^1.8.1"
|
package/src/components/Edit.jsx
CHANGED
|
@@ -111,7 +111,7 @@ const Edit = (props) => {
|
|
|
111
111
|
}
|
|
112
112
|
};
|
|
113
113
|
|
|
114
|
-
const onSelectBlock = (id,
|
|
114
|
+
const onSelectBlock = (id, isMultipleSelection, event) => {
|
|
115
115
|
let newMultiSelected = [];
|
|
116
116
|
let selected = id;
|
|
117
117
|
|
|
@@ -220,23 +220,19 @@ const Edit = (props) => {
|
|
|
220
220
|
) : (
|
|
221
221
|
''
|
|
222
222
|
)}
|
|
223
|
-
{!data?.readOnlySettings ? (
|
|
223
|
+
{!data?.readOnlySettings && !activeBlock ? (
|
|
224
224
|
<SidebarPortal selected={props.selected}>
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}}
|
|
237
|
-
formData={data}
|
|
238
|
-
/>
|
|
239
|
-
)}
|
|
225
|
+
<InlineForm
|
|
226
|
+
schema={schemaObject}
|
|
227
|
+
title={schemaObject.title}
|
|
228
|
+
onChangeField={(id, value) => {
|
|
229
|
+
onChangeBlock(block, {
|
|
230
|
+
...data,
|
|
231
|
+
[id]: value,
|
|
232
|
+
});
|
|
233
|
+
}}
|
|
234
|
+
formData={data}
|
|
235
|
+
/>
|
|
240
236
|
</SidebarPortal>
|
|
241
237
|
) : (
|
|
242
238
|
''
|
package/src/components/View.jsx
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import cx from 'classnames';
|
|
3
|
+
import { compose } from 'redux';
|
|
4
|
+
import { withRouter } from 'react-router';
|
|
3
5
|
import { StyleWrapperView } from '@eeacms/volto-block-style/StyleWrapper';
|
|
4
6
|
import { TABS_BLOCK } from '@eeacms/volto-tabs-block/constants';
|
|
7
|
+
import { getParentTabFromHash } from '@eeacms/volto-tabs-block/helpers';
|
|
5
8
|
import { DefaultView } from './templates/default';
|
|
9
|
+
import { withScrollToTarget } from '@eeacms/volto-tabs-block/hocs';
|
|
6
10
|
|
|
7
11
|
import config from '@plone/volto/registry';
|
|
8
12
|
|
|
@@ -27,6 +31,30 @@ const View = (props) => {
|
|
|
27
31
|
config.blocks.blocksConfig[TABS_BLOCK].templates?.[template]?.view ||
|
|
28
32
|
DefaultView;
|
|
29
33
|
|
|
34
|
+
React.useEffect(() => {
|
|
35
|
+
const urlHash = props.location.hash.substring(1) || '';
|
|
36
|
+
const parentTabId = getParentTabFromHash(data, urlHash);
|
|
37
|
+
const id = parentTabId;
|
|
38
|
+
const index = tabsList.indexOf(id);
|
|
39
|
+
const parentId = data.id || props.id;
|
|
40
|
+
const parent = document.getElementById(parentId);
|
|
41
|
+
const headerWrapper = document.querySelector('.header-wrapper');
|
|
42
|
+
const offsetHeight = headerWrapper?.offsetHeight || 0;
|
|
43
|
+
if (id !== parentId && index > -1 && parent) {
|
|
44
|
+
if (activeTabIndex !== index) {
|
|
45
|
+
setActiveTab(id);
|
|
46
|
+
}
|
|
47
|
+
setTimeout(() => {
|
|
48
|
+
const scrollToElement = document.getElementById(urlHash);
|
|
49
|
+
//TODO: volto now uses react-router-hash-link which automatically scrolls to offset 0
|
|
50
|
+
props.scrollToTarget(scrollToElement, offsetHeight);
|
|
51
|
+
}, 10);
|
|
52
|
+
} else if (id === parentId && parent) {
|
|
53
|
+
props.scrollToTarget(parent, offsetHeight);
|
|
54
|
+
}
|
|
55
|
+
/* eslint-disable-next-line */
|
|
56
|
+
}, []);
|
|
57
|
+
|
|
30
58
|
return (
|
|
31
59
|
<StyleWrapperView
|
|
32
60
|
{...props}
|
|
@@ -67,4 +95,4 @@ const View = (props) => {
|
|
|
67
95
|
);
|
|
68
96
|
};
|
|
69
97
|
|
|
70
|
-
export default View;
|
|
98
|
+
export default compose(withScrollToTarget)(withRouter(View));
|
|
@@ -9,6 +9,8 @@ import config from '@plone/volto/registry';
|
|
|
9
9
|
import { Icon as VoltoIcon, RenderBlocks } from '@plone/volto/components';
|
|
10
10
|
import { TABS_BLOCK } from '@eeacms/volto-tabs-block/constants';
|
|
11
11
|
import { withScrollToTarget } from '@eeacms/volto-tabs-block/hocs';
|
|
12
|
+
import { getParentTabFromHash } from '@eeacms/volto-tabs-block/helpers';
|
|
13
|
+
import noop from 'lodash/noop';
|
|
12
14
|
|
|
13
15
|
import 'react-responsive-tabs/styles.css';
|
|
14
16
|
import '@eeacms/volto-tabs-block/less/menu.less';
|
|
@@ -49,7 +51,7 @@ const View = (props) => {
|
|
|
49
51
|
tabsList = [],
|
|
50
52
|
tabs = {},
|
|
51
53
|
activeTabIndex = 0,
|
|
52
|
-
setActiveTab =
|
|
54
|
+
setActiveTab = noop,
|
|
53
55
|
id,
|
|
54
56
|
} = props;
|
|
55
57
|
|
|
@@ -59,6 +61,7 @@ const View = (props) => {
|
|
|
59
61
|
|
|
60
62
|
const tabsContainer = React.useRef();
|
|
61
63
|
const [mounted, setMounted] = React.useState(false);
|
|
64
|
+
const [hashTab, setHashTab] = React.useState(false);
|
|
62
65
|
const [initialWidth, setInitialWidth] = React.useState(transformWidth);
|
|
63
66
|
|
|
64
67
|
const schema = React.useMemo(
|
|
@@ -101,7 +104,7 @@ const View = (props) => {
|
|
|
101
104
|
{title || defaultTitle}{' '}
|
|
102
105
|
</>
|
|
103
106
|
),
|
|
104
|
-
|
|
107
|
+
content: (
|
|
105
108
|
<Tab {...props} tab={tab} content={tabs[tab]} aria-hidden={false} />
|
|
106
109
|
),
|
|
107
110
|
key: tab,
|
|
@@ -155,11 +158,19 @@ const View = (props) => {
|
|
|
155
158
|
ref={tabsContainer}
|
|
156
159
|
transformWidth={initialWidth}
|
|
157
160
|
selectedTabKey={tabsList[activeTabIndex]}
|
|
161
|
+
unmountOnExit={false}
|
|
158
162
|
items={items}
|
|
159
163
|
onChange={(tab) => {
|
|
160
|
-
|
|
164
|
+
const { blockWidth } = tabsContainer.current?.state || {};
|
|
165
|
+
const tabWithHash = getParentTabFromHash(
|
|
166
|
+
data,
|
|
167
|
+
props.location.hash.substring(1),
|
|
168
|
+
);
|
|
169
|
+
if (tabWithHash === tabsList[activeTabIndex] && !hashTab)
|
|
170
|
+
setHashTab(true);
|
|
171
|
+
else if (tab !== tabsList[activeTabIndex]) {
|
|
161
172
|
setActiveTab(tab);
|
|
162
|
-
} else {
|
|
173
|
+
} else if (blockWidth <= initialWidth) {
|
|
163
174
|
setActiveTab(null);
|
|
164
175
|
}
|
|
165
176
|
}}
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { connect } from 'react-redux';
|
|
3
|
-
import { compose } from 'redux';
|
|
4
2
|
import { withRouter } from 'react-router';
|
|
5
3
|
import loadable from '@loadable/component';
|
|
6
4
|
import cx from 'classnames';
|
|
7
5
|
import { Icon, RenderBlocks } from '@plone/volto/components';
|
|
8
|
-
import { withScrollToTarget } from '@eeacms/volto-tabs-block/hocs';
|
|
9
6
|
|
|
10
7
|
import rightArrowSVG from '@eeacms/volto-tabs-block/icons/right-arrow.svg';
|
|
11
8
|
import leftArrowSVG from '@eeacms/volto-tabs-block/icons/left-arrow.svg';
|
|
@@ -14,6 +11,8 @@ import 'slick-carousel/slick/slick.css';
|
|
|
14
11
|
import 'slick-carousel/slick/slick-theme.css';
|
|
15
12
|
import '@eeacms/volto-tabs-block/less/carousel.less';
|
|
16
13
|
|
|
14
|
+
import noop from 'lodash/noop';
|
|
15
|
+
|
|
17
16
|
const Slider = loadable(() => import('react-slick'));
|
|
18
17
|
|
|
19
18
|
const Dots = (props) => {
|
|
@@ -95,18 +94,15 @@ const ArrowsGroup = (props) => {
|
|
|
95
94
|
|
|
96
95
|
const View = (props) => {
|
|
97
96
|
const slider = React.useRef(null);
|
|
98
|
-
const [hashlinkOnMount, setHashlinkOnMount] = React.useState(false);
|
|
99
97
|
const blockId = props.id;
|
|
100
98
|
const {
|
|
101
99
|
activeTab = null,
|
|
102
100
|
data = {},
|
|
103
|
-
hashlink = {},
|
|
104
101
|
metadata = {},
|
|
105
102
|
tabsList = [],
|
|
106
103
|
tabs = {},
|
|
107
|
-
setActiveTab =
|
|
104
|
+
setActiveTab = noop,
|
|
108
105
|
} = props;
|
|
109
|
-
const activeTabIndex = tabsList.indexOf(activeTab);
|
|
110
106
|
const uiContainer = data.align === 'full' ? 'ui container' : false;
|
|
111
107
|
|
|
112
108
|
const settings = {
|
|
@@ -151,40 +147,6 @@ const View = (props) => {
|
|
|
151
147
|
carouselDiv.setAttribute('tabindex', '0');
|
|
152
148
|
}, []);
|
|
153
149
|
|
|
154
|
-
React.useEffect(() => {
|
|
155
|
-
const urlHash = props.location.hash.substring(1) || '';
|
|
156
|
-
if (
|
|
157
|
-
hashlink.counter > 0 ||
|
|
158
|
-
(hashlink.counter === 0 && urlHash && !hashlinkOnMount)
|
|
159
|
-
) {
|
|
160
|
-
const id = hashlink.hash || urlHash || '';
|
|
161
|
-
const index = tabsList.indexOf(id);
|
|
162
|
-
const parentId = data.id || props.id;
|
|
163
|
-
const parent = document.getElementById(parentId);
|
|
164
|
-
// TODO: Find the best way to add offset relative to header
|
|
165
|
-
// The header can be static on mobile and relative on > mobile
|
|
166
|
-
const headerWrapper = document.querySelector('.header-wrapper');
|
|
167
|
-
const offsetHeight = headerWrapper?.offsetHeight || 0;
|
|
168
|
-
if (
|
|
169
|
-
id !== parentId &&
|
|
170
|
-
parentId === hashlink.data.parentId &&
|
|
171
|
-
index > -1 &&
|
|
172
|
-
parent
|
|
173
|
-
) {
|
|
174
|
-
if (activeTabIndex !== index) {
|
|
175
|
-
slider.current.slickGoTo(index);
|
|
176
|
-
}
|
|
177
|
-
props.scrollToTarget(parent, offsetHeight);
|
|
178
|
-
} else if (id === parentId && parent) {
|
|
179
|
-
props.scrollToTarget(parent, offsetHeight);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
if (!hashlinkOnMount) {
|
|
183
|
-
setHashlinkOnMount(true);
|
|
184
|
-
}
|
|
185
|
-
/* eslint-disable-next-line */
|
|
186
|
-
}, [hashlink.counter]);
|
|
187
|
-
|
|
188
150
|
const panes = tabsList.map((tab, index) => {
|
|
189
151
|
return {
|
|
190
152
|
id: tab,
|
|
@@ -227,11 +189,4 @@ const View = (props) => {
|
|
|
227
189
|
);
|
|
228
190
|
};
|
|
229
191
|
|
|
230
|
-
export default
|
|
231
|
-
connect((state) => {
|
|
232
|
-
return {
|
|
233
|
-
hashlink: state.hashlink,
|
|
234
|
-
};
|
|
235
|
-
}),
|
|
236
|
-
withScrollToTarget,
|
|
237
|
-
)(withRouter(View));
|
|
192
|
+
export default withRouter(View);
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { connect } from 'react-redux';
|
|
3
|
-
import { compose } from 'redux';
|
|
4
2
|
import { withRouter } from 'react-router';
|
|
5
3
|
import loadable from '@loadable/component';
|
|
6
4
|
import cx from 'classnames';
|
|
7
5
|
import { RenderBlocks } from '@plone/volto/components';
|
|
8
|
-
import { withScrollToTarget } from '@eeacms/volto-tabs-block/hocs';
|
|
9
|
-
|
|
10
6
|
import 'slick-carousel/slick/slick.css';
|
|
11
7
|
import 'slick-carousel/slick/slick-theme.css';
|
|
12
8
|
import '@eeacms/volto-tabs-block/less/carousel.less';
|
|
@@ -15,14 +11,7 @@ const Slider = loadable(() => import('react-slick'));
|
|
|
15
11
|
|
|
16
12
|
const View = (props) => {
|
|
17
13
|
const slider = React.useRef(null);
|
|
18
|
-
const
|
|
19
|
-
const {
|
|
20
|
-
metadata = {},
|
|
21
|
-
data = {},
|
|
22
|
-
tabsList = [],
|
|
23
|
-
tabs = {},
|
|
24
|
-
hashlink = {},
|
|
25
|
-
} = props;
|
|
14
|
+
const { metadata = {}, data = {}, tabsList = [], tabs = {} } = props;
|
|
26
15
|
const theme = data.theme || 'light';
|
|
27
16
|
const uiContainer = data.align === 'full' ? 'ui container' : false;
|
|
28
17
|
|
|
@@ -38,34 +27,6 @@ const View = (props) => {
|
|
|
38
27
|
verticalSwiping: true,
|
|
39
28
|
};
|
|
40
29
|
|
|
41
|
-
React.useEffect(() => {
|
|
42
|
-
const urlHash = props.location.hash.substring(1) || '';
|
|
43
|
-
if (
|
|
44
|
-
hashlink.counter > 0 ||
|
|
45
|
-
(hashlink.counter === 0 && urlHash && !hashlinkOnMount)
|
|
46
|
-
) {
|
|
47
|
-
const id = hashlink.hash || urlHash || '';
|
|
48
|
-
const index = tabsList.indexOf(id);
|
|
49
|
-
const currentIndex = slider.current?.innerSlider?.state?.currentSlide;
|
|
50
|
-
const parentId = data.id || props.id;
|
|
51
|
-
const parent = document.getElementById(parentId);
|
|
52
|
-
const headerWrapper = document.querySelector('.header-wrapper');
|
|
53
|
-
const offsetHeight = headerWrapper?.offsetHeight || 0;
|
|
54
|
-
if (id !== parentId && index > -1 && parent) {
|
|
55
|
-
if (currentIndex !== index) {
|
|
56
|
-
slider.current.slickGoTo(index);
|
|
57
|
-
}
|
|
58
|
-
props.scrollToTarget(parent, offsetHeight);
|
|
59
|
-
} else if (id === parentId && parent) {
|
|
60
|
-
props.scrollToTarget(parent, offsetHeight);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
if (!hashlinkOnMount) {
|
|
64
|
-
setHashlinkOnMount(true);
|
|
65
|
-
}
|
|
66
|
-
/* eslint-disable-next-line */
|
|
67
|
-
}, [hashlink.counter]);
|
|
68
|
-
|
|
69
30
|
const panes = tabsList.map((tab, index) => {
|
|
70
31
|
return {
|
|
71
32
|
id: tab,
|
|
@@ -89,11 +50,4 @@ const View = (props) => {
|
|
|
89
50
|
);
|
|
90
51
|
};
|
|
91
52
|
|
|
92
|
-
export default
|
|
93
|
-
connect((state) => {
|
|
94
|
-
return {
|
|
95
|
-
hashlink: state.hashlink,
|
|
96
|
-
};
|
|
97
|
-
}),
|
|
98
|
-
withScrollToTarget,
|
|
99
|
-
)(withRouter(View));
|
|
53
|
+
export default withRouter(View);
|
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
|
|
16
16
|
import '@eeacms/volto-tabs-block/less/menu.less';
|
|
17
17
|
|
|
18
|
+
import noop from 'lodash/noop';
|
|
19
|
+
|
|
18
20
|
const MenuItem = (props) => {
|
|
19
21
|
const inputRef = React.useRef(null);
|
|
20
22
|
const {
|
|
@@ -30,11 +32,11 @@ const MenuItem = (props) => {
|
|
|
30
32
|
tabsDescription,
|
|
31
33
|
tabsList = [],
|
|
32
34
|
tabsTitle,
|
|
33
|
-
emptyTab =
|
|
34
|
-
setActiveBlock =
|
|
35
|
-
setActiveTab =
|
|
36
|
-
setEditingTab =
|
|
37
|
-
onChangeBlock =
|
|
35
|
+
emptyTab = noop,
|
|
36
|
+
setActiveBlock = noop,
|
|
37
|
+
setActiveTab = noop,
|
|
38
|
+
setEditingTab = noop,
|
|
39
|
+
onChangeBlock = noop,
|
|
38
40
|
} = props;
|
|
39
41
|
const { tab, index } = props;
|
|
40
42
|
const title = tabs[tab].title;
|
|
@@ -157,11 +159,11 @@ const Edit = (props) => {
|
|
|
157
159
|
tabs = {},
|
|
158
160
|
tabsData = {},
|
|
159
161
|
tabsList = [],
|
|
160
|
-
emptyTab =
|
|
161
|
-
onChangeBlock =
|
|
162
|
-
onChangeTabData =
|
|
163
|
-
onSelectBlock =
|
|
164
|
-
setEditingTab =
|
|
162
|
+
emptyTab = noop,
|
|
163
|
+
onChangeBlock = noop,
|
|
164
|
+
onChangeTabData = noop,
|
|
165
|
+
onSelectBlock = noop,
|
|
166
|
+
setEditingTab = noop,
|
|
165
167
|
} = props;
|
|
166
168
|
const menuPosition = getMenuPosition(data);
|
|
167
169
|
const isContainer = data.align === 'full';
|
|
@@ -242,7 +244,6 @@ const Edit = (props) => {
|
|
|
242
244
|
: false;
|
|
243
245
|
onSelectBlock(
|
|
244
246
|
id,
|
|
245
|
-
activeTab,
|
|
246
247
|
activeBlock === id ? false : isMultipleSelection,
|
|
247
248
|
e,
|
|
248
249
|
);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { connect } from 'react-redux';
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
3
2
|
import { compose } from 'redux';
|
|
4
3
|
import { withRouter } from 'react-router';
|
|
5
4
|
import cx from 'classnames';
|
|
@@ -15,11 +14,13 @@ import {
|
|
|
15
14
|
|
|
16
15
|
import '@eeacms/volto-tabs-block/less/menu.less';
|
|
17
16
|
|
|
17
|
+
import noop from 'lodash/noop';
|
|
18
|
+
|
|
18
19
|
const MenuItem = (props) => {
|
|
19
20
|
const {
|
|
20
21
|
activeTab = null,
|
|
21
22
|
tabs = {},
|
|
22
|
-
setActiveTab =
|
|
23
|
+
setActiveTab = noop,
|
|
23
24
|
tabsTitle,
|
|
24
25
|
tabsDescription,
|
|
25
26
|
blockId,
|
|
@@ -28,8 +29,22 @@ const MenuItem = (props) => {
|
|
|
28
29
|
const { tab, index } = props;
|
|
29
30
|
const title = tabs[tab].title;
|
|
30
31
|
const tabIndex = index + 1;
|
|
32
|
+
|
|
33
|
+
const [tabChanged, setTabChanged] = useState(false);
|
|
31
34
|
const defaultTitle = `Tab ${tabIndex}`;
|
|
32
35
|
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (
|
|
38
|
+
tabChanged === true &&
|
|
39
|
+
document?.getElementById(blockId)?.querySelector('#tab-pane-' + tab)
|
|
40
|
+
) {
|
|
41
|
+
document
|
|
42
|
+
.getElementById(blockId)
|
|
43
|
+
.querySelector('#tab-pane-' + tab)
|
|
44
|
+
.focus();
|
|
45
|
+
setTabChanged(false);
|
|
46
|
+
}
|
|
47
|
+
}, [tabChanged, tab, blockId]);
|
|
33
48
|
return (
|
|
34
49
|
<React.Fragment>
|
|
35
50
|
{index === 0 && (tabsTitle || tabsDescription) && (
|
|
@@ -50,19 +65,10 @@ const MenuItem = (props) => {
|
|
|
50
65
|
onKeyDown={(e) => {
|
|
51
66
|
if (e.key === 'Enter') {
|
|
52
67
|
e.preventDefault();
|
|
53
|
-
if (
|
|
54
|
-
document
|
|
55
|
-
.getElementById(blockId)
|
|
56
|
-
?.getElementsByClassName('active tab').length > 0
|
|
57
|
-
) {
|
|
58
|
-
const tabDiv = document
|
|
59
|
-
.getElementById(blockId)
|
|
60
|
-
.getElementsByClassName('active tab')[0];
|
|
61
|
-
tabDiv.focus();
|
|
62
|
-
}
|
|
63
68
|
if (activeTab !== tab) {
|
|
64
69
|
setActiveTab(tab);
|
|
65
70
|
}
|
|
71
|
+
setTabChanged(true);
|
|
66
72
|
}
|
|
67
73
|
}}
|
|
68
74
|
>
|
|
@@ -74,15 +80,12 @@ const MenuItem = (props) => {
|
|
|
74
80
|
};
|
|
75
81
|
|
|
76
82
|
const View = (props) => {
|
|
77
|
-
const [hashlinkOnMount, setHashlinkOnMount] = React.useState(false);
|
|
78
83
|
const {
|
|
79
84
|
metadata = {},
|
|
80
85
|
data = {},
|
|
81
86
|
tabsList = [],
|
|
82
87
|
tabs = {},
|
|
83
88
|
activeTabIndex = 0,
|
|
84
|
-
hashlink = {},
|
|
85
|
-
setActiveTab = () => {},
|
|
86
89
|
} = props;
|
|
87
90
|
const [menuPosition, setMenuPosition] = React.useState({});
|
|
88
91
|
|
|
@@ -115,33 +118,6 @@ const View = (props) => {
|
|
|
115
118
|
[schema, data],
|
|
116
119
|
);
|
|
117
120
|
|
|
118
|
-
React.useEffect(() => {
|
|
119
|
-
const urlHash = props.location.hash.substring(1) || '';
|
|
120
|
-
if (
|
|
121
|
-
hashlink.counter > 0 ||
|
|
122
|
-
(hashlink.counter === 0 && urlHash && !hashlinkOnMount)
|
|
123
|
-
) {
|
|
124
|
-
const id = hashlink.hash || urlHash || '';
|
|
125
|
-
const index = tabsList.indexOf(id);
|
|
126
|
-
const parentId = data.id || props.id;
|
|
127
|
-
const parent = document.getElementById(parentId);
|
|
128
|
-
const headerWrapper = document.querySelector('.header-wrapper');
|
|
129
|
-
const offsetHeight = headerWrapper?.offsetHeight || 0;
|
|
130
|
-
if (id !== parentId && index > -1 && parent) {
|
|
131
|
-
if (activeTabIndex !== index) {
|
|
132
|
-
setActiveTab(id);
|
|
133
|
-
}
|
|
134
|
-
props.scrollToTarget(parent, offsetHeight);
|
|
135
|
-
} else if (id === parentId && parent) {
|
|
136
|
-
props.scrollToTarget(parent, offsetHeight);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
if (!hashlinkOnMount) {
|
|
140
|
-
setHashlinkOnMount(true);
|
|
141
|
-
}
|
|
142
|
-
/* eslint-disable-next-line */
|
|
143
|
-
}, [hashlink.counter]);
|
|
144
|
-
|
|
145
121
|
const panes = tabsList.map((tab, index) => {
|
|
146
122
|
return {
|
|
147
123
|
id: tab,
|
|
@@ -156,13 +132,13 @@ const View = (props) => {
|
|
|
156
132
|
blockId={props?.id || ''}
|
|
157
133
|
/>
|
|
158
134
|
),
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
<
|
|
135
|
+
pane: (
|
|
136
|
+
<Tab.Pane as={isContainer ? Container : undefined}>
|
|
137
|
+
<div tabIndex={0} role="tabpanel" id={'tab-pane-' + tab}>
|
|
162
138
|
<RenderBlocks {...props} metadata={metadata} content={tabs[tab]} />
|
|
163
|
-
</
|
|
164
|
-
|
|
165
|
-
|
|
139
|
+
</div>
|
|
140
|
+
</Tab.Pane>
|
|
141
|
+
),
|
|
166
142
|
};
|
|
167
143
|
});
|
|
168
144
|
|
|
@@ -171,6 +147,7 @@ const View = (props) => {
|
|
|
171
147
|
<Tab
|
|
172
148
|
activeIndex={activeTabIndex}
|
|
173
149
|
className="default tabs tabs-accessibility"
|
|
150
|
+
renderActiveOnly={false}
|
|
174
151
|
menu={{
|
|
175
152
|
attached: menuPosition.attached,
|
|
176
153
|
borderless: getDataValue('menuBorderless'),
|
|
@@ -202,11 +179,4 @@ const View = (props) => {
|
|
|
202
179
|
);
|
|
203
180
|
};
|
|
204
181
|
|
|
205
|
-
export default compose(
|
|
206
|
-
connect((state) => {
|
|
207
|
-
return {
|
|
208
|
-
hashlink: state.hashlink,
|
|
209
|
-
};
|
|
210
|
-
}),
|
|
211
|
-
withScrollToTarget,
|
|
212
|
-
)(withRouter(View));
|
|
182
|
+
export default compose(withScrollToTarget)(withRouter(View));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
2
|
import { connect } from 'react-redux';
|
|
3
3
|
import { compose } from 'redux';
|
|
4
4
|
import { withRouter } from 'react-router';
|
|
@@ -7,7 +7,6 @@ import { Menu, Tab, Container, Dropdown, Button } from 'semantic-ui-react';
|
|
|
7
7
|
import config from '@plone/volto/registry';
|
|
8
8
|
import { RenderBlocks } from '@plone/volto/components';
|
|
9
9
|
import { TABS_BLOCK } from '@eeacms/volto-tabs-block/constants';
|
|
10
|
-
import { withScrollToTarget } from '@eeacms/volto-tabs-block/hocs';
|
|
11
10
|
import {
|
|
12
11
|
SimpleMarkdown,
|
|
13
12
|
getMenuPosition,
|
|
@@ -17,18 +16,27 @@ import {
|
|
|
17
16
|
|
|
18
17
|
import '@eeacms/volto-tabs-block/less/menu.less';
|
|
19
18
|
|
|
19
|
+
import noop from 'lodash/noop';
|
|
20
|
+
|
|
20
21
|
const MenuItem = (props) => {
|
|
21
|
-
const {
|
|
22
|
-
activeTab = null,
|
|
23
|
-
tabs = {},
|
|
24
|
-
setActiveTab = () => {},
|
|
25
|
-
blockId,
|
|
26
|
-
} = props;
|
|
22
|
+
const { activeTab = null, tabs = {}, setActiveTab = noop, blockId } = props;
|
|
27
23
|
const { tabsTitle, tabsDescription, tab, index } = props;
|
|
28
24
|
const title = tabs[tab].title;
|
|
29
25
|
const tabIndex = index + 1;
|
|
30
26
|
const defaultTitle = `Tab ${tabIndex}`;
|
|
31
|
-
|
|
27
|
+
const [tabChanged, setTabChanged] = useState(false);
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (
|
|
30
|
+
tabChanged === true &&
|
|
31
|
+
document?.getElementById(blockId)?.querySelector('#tab-pane-' + tab)
|
|
32
|
+
) {
|
|
33
|
+
document
|
|
34
|
+
.getElementById(blockId)
|
|
35
|
+
.querySelector('#tab-pane-' + tab)
|
|
36
|
+
.focus();
|
|
37
|
+
setTabChanged(false);
|
|
38
|
+
}
|
|
39
|
+
}, [tabChanged, tab, blockId]);
|
|
32
40
|
return (
|
|
33
41
|
<React.Fragment>
|
|
34
42
|
{index === 0 && (tabsTitle || tabsDescription) && (
|
|
@@ -73,19 +81,10 @@ const MenuItem = (props) => {
|
|
|
73
81
|
onKeyDown={(e) => {
|
|
74
82
|
if (e.key === 'Enter') {
|
|
75
83
|
e.preventDefault();
|
|
76
|
-
if (
|
|
77
|
-
document
|
|
78
|
-
.getElementById(blockId)
|
|
79
|
-
?.getElementsByClassName('active tab').length > 0
|
|
80
|
-
) {
|
|
81
|
-
const tabDiv = document
|
|
82
|
-
.getElementById(blockId)
|
|
83
|
-
.getElementsByClassName('active tab')[0];
|
|
84
|
-
tabDiv.focus();
|
|
85
|
-
}
|
|
86
84
|
if (activeTab !== tab) {
|
|
87
85
|
setActiveTab(tab);
|
|
88
86
|
}
|
|
87
|
+
setTabChanged(true);
|
|
89
88
|
}
|
|
90
89
|
}}
|
|
91
90
|
>
|
|
@@ -105,12 +104,12 @@ const MenuWrapper = (props) => {
|
|
|
105
104
|
screen = {},
|
|
106
105
|
tabs = {},
|
|
107
106
|
tabsList = [],
|
|
108
|
-
setActiveTab =
|
|
107
|
+
setActiveTab = noop,
|
|
109
108
|
} = props;
|
|
110
109
|
const [open, setOpen] = React.useState(false);
|
|
111
110
|
|
|
112
111
|
React.useEffect(() => {
|
|
113
|
-
if (
|
|
112
|
+
if (false || !node?.current) return;
|
|
114
113
|
const items = node.current.querySelectorAll(
|
|
115
114
|
'.ui.menu > .menu-wrapper > .item:not(.menu-title)',
|
|
116
115
|
);
|
|
@@ -182,16 +181,13 @@ const MenuWrapper = (props) => {
|
|
|
182
181
|
};
|
|
183
182
|
|
|
184
183
|
const View = (props) => {
|
|
185
|
-
const [hashlinkOnMount, setHashlinkOnMount] = React.useState(false);
|
|
186
184
|
const {
|
|
187
185
|
metadata = {},
|
|
188
186
|
data = {},
|
|
189
187
|
tabsList = [],
|
|
190
188
|
tabs = {},
|
|
191
189
|
activeTabIndex = 0,
|
|
192
|
-
hashlink = {},
|
|
193
190
|
screen,
|
|
194
|
-
setActiveTab = () => {},
|
|
195
191
|
} = props;
|
|
196
192
|
const menuPosition = getMenuPosition(data);
|
|
197
193
|
const isContainer = data.align === 'full';
|
|
@@ -217,33 +213,6 @@ const View = (props) => {
|
|
|
217
213
|
[schema, data],
|
|
218
214
|
);
|
|
219
215
|
|
|
220
|
-
React.useEffect(() => {
|
|
221
|
-
const urlHash = props.location.hash.substring(1) || '';
|
|
222
|
-
if (
|
|
223
|
-
hashlink.counter > 0 ||
|
|
224
|
-
(hashlink.counter === 0 && urlHash && !hashlinkOnMount)
|
|
225
|
-
) {
|
|
226
|
-
const id = hashlink.hash || urlHash || '';
|
|
227
|
-
const index = tabsList.indexOf(id);
|
|
228
|
-
const parentId = data.id || props.id;
|
|
229
|
-
const parent = document.getElementById(parentId);
|
|
230
|
-
const headerWrapper = document.querySelector('.header-wrapper');
|
|
231
|
-
const offsetHeight = headerWrapper?.offsetHeight || 0;
|
|
232
|
-
if (id !== parentId && index > -1 && parent) {
|
|
233
|
-
if (activeTabIndex !== index) {
|
|
234
|
-
setActiveTab(id);
|
|
235
|
-
}
|
|
236
|
-
props.scrollToTarget(parent, offsetHeight);
|
|
237
|
-
} else if (id === parentId && parent) {
|
|
238
|
-
props.scrollToTarget(parent, offsetHeight);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
if (!hashlinkOnMount) {
|
|
242
|
-
setHashlinkOnMount(true);
|
|
243
|
-
}
|
|
244
|
-
/* eslint-disable-next-line */
|
|
245
|
-
}, [hashlink.counter]);
|
|
246
|
-
|
|
247
216
|
const panes = tabsList.map((tab, index) => {
|
|
248
217
|
return {
|
|
249
218
|
id: tab,
|
|
@@ -260,19 +229,13 @@ const View = (props) => {
|
|
|
260
229
|
tabsDescription={tabsDescription}
|
|
261
230
|
/>
|
|
262
231
|
),
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
<
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
content={tabs[tab]}
|
|
271
|
-
/>
|
|
272
|
-
</Tab.Pane>
|
|
273
|
-
</>
|
|
274
|
-
);
|
|
275
|
-
},
|
|
232
|
+
pane: (
|
|
233
|
+
<Tab.Pane as={isContainer ? Container : undefined}>
|
|
234
|
+
<div tabIndex={0} role="tabpanel" id={'tab-pane-' + tab}>
|
|
235
|
+
<RenderBlocks {...props} metadata={metadata} content={tabs[tab]} />
|
|
236
|
+
</div>
|
|
237
|
+
</Tab.Pane>
|
|
238
|
+
),
|
|
276
239
|
};
|
|
277
240
|
});
|
|
278
241
|
|
|
@@ -281,6 +244,7 @@ const View = (props) => {
|
|
|
281
244
|
<Tab
|
|
282
245
|
activeIndex={activeTabIndex}
|
|
283
246
|
className="horizontal-responsive tabs"
|
|
247
|
+
renderActiveOnly={false}
|
|
284
248
|
menu={{
|
|
285
249
|
attached: menuPosition.attached,
|
|
286
250
|
borderless: getDataValue('menuBorderless'),
|
|
@@ -316,9 +280,7 @@ const View = (props) => {
|
|
|
316
280
|
export default compose(
|
|
317
281
|
connect((state) => {
|
|
318
282
|
return {
|
|
319
|
-
hashlink: state.hashlink,
|
|
320
283
|
screen: state.screen,
|
|
321
284
|
};
|
|
322
285
|
}),
|
|
323
|
-
withScrollToTarget,
|
|
324
286
|
)(withRouter(View));
|
package/src/helpers.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { v4 as uuid } from 'uuid';
|
|
2
2
|
import { emptyBlocksForm } from '@plone/volto/helpers';
|
|
3
|
+
import { visitBlocks, toSlug } from '@eeacms/volto-anchors/helpers';
|
|
3
4
|
|
|
4
5
|
export const empty = () => {
|
|
5
6
|
const tabId = uuid();
|
|
@@ -34,3 +35,20 @@ export const scrollToTarget = (target, offsetHeight = 0) => {
|
|
|
34
35
|
|
|
35
36
|
return;
|
|
36
37
|
};
|
|
38
|
+
|
|
39
|
+
export const getParentTabFromHash = (tabsBlockData, urlHash) => {
|
|
40
|
+
if (urlHash) {
|
|
41
|
+
const { data } = tabsBlockData;
|
|
42
|
+
let parentBlockId;
|
|
43
|
+
let isSlugPresent;
|
|
44
|
+
visitBlocks(data, ([id, data]) => {
|
|
45
|
+
if (data['@type'] === 'tab') parentBlockId = id;
|
|
46
|
+
if (toSlug(data.plaintext) === urlHash) {
|
|
47
|
+
isSlugPresent = true;
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
return isSlugPresent ? parentBlockId : null;
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
};
|
package/src/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { TabsEdit, TabsView } from '@eeacms/volto-tabs-block/components';
|
|
2
1
|
import {
|
|
3
2
|
DefaultEdit,
|
|
4
3
|
DefaultView,
|
|
@@ -11,8 +10,10 @@ import {
|
|
|
11
10
|
accordionSchema,
|
|
12
11
|
horizontalResponsiveSchema,
|
|
13
12
|
carouselSchema,
|
|
13
|
+
layoutSchema,
|
|
14
|
+
TabsEdit,
|
|
15
|
+
TabsView,
|
|
14
16
|
} from '@eeacms/volto-tabs-block/components';
|
|
15
|
-
import { layoutSchema } from '@eeacms/volto-tabs-block/components';
|
|
16
17
|
import { TABS_BLOCK } from './constants';
|
|
17
18
|
import { TabsWidget } from './widgets';
|
|
18
19
|
|
|
@@ -94,10 +95,5 @@ export default (config) => {
|
|
|
94
95
|
|
|
95
96
|
config.widgets.type.tabs = TabsWidget;
|
|
96
97
|
|
|
97
|
-
config.settings.hashlinkBlacklist = [
|
|
98
|
-
...(config.settings.hashlinkBlacklist || []),
|
|
99
|
-
TABS_BLOCK,
|
|
100
|
-
];
|
|
101
|
-
|
|
102
98
|
return config;
|
|
103
99
|
};
|
package/src/less/tabs.less
CHANGED
|
@@ -2,11 +2,9 @@ import React from 'react';
|
|
|
2
2
|
import { v4 as uuid } from 'uuid';
|
|
3
3
|
import { omit, without } from 'lodash';
|
|
4
4
|
import move from 'lodash-move';
|
|
5
|
-
import { Icon, FormFieldWrapper } from '@plone/volto/components';
|
|
6
|
-
import { DragDropList } from '@plone/volto/components';
|
|
5
|
+
import { Icon, FormFieldWrapper, DragDropList } from '@plone/volto/components';
|
|
7
6
|
import { emptyTab } from '@eeacms/volto-tabs-block/helpers';
|
|
8
7
|
import { StyleWrapperEdit } from '@eeacms/volto-block-style/StyleWrapper';
|
|
9
|
-
|
|
10
8
|
import dragSVG from '@plone/volto/icons/drag.svg';
|
|
11
9
|
import themeSVG from '@plone/volto/icons/theme.svg';
|
|
12
10
|
import trashSVG from '@plone/volto/icons/delete.svg';
|
|
@@ -30,9 +28,9 @@ const TabsWidget = (props) => {
|
|
|
30
28
|
const [activeTabId, setActiveTabId] = React.useState(0);
|
|
31
29
|
const { value = {}, id, onChange } = props;
|
|
32
30
|
const { blocks = {} } = value;
|
|
33
|
-
const tabsList = (value.blocks_layout?.items || []).map((
|
|
34
|
-
|
|
35
|
-
blocks[
|
|
31
|
+
const tabsList = (value.blocks_layout?.items || []).map((uid) => [
|
|
32
|
+
uid,
|
|
33
|
+
blocks[uid],
|
|
36
34
|
]);
|
|
37
35
|
const activeTabData = blocks[activeTabId] || {};
|
|
38
36
|
|