@eeacms/volto-eea-website-theme 1.28.2 → 1.29.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,10 +4,35 @@ 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.28.2](https://github.com/eea/volto-eea-website-theme/compare/1.28.1...1.28.2) - 29 February 2024
7
+ ### [1.29.0](https://github.com/eea/volto-eea-website-theme/compare/1.28.3...1.29.0) - 7 March 2024
8
+
9
+ #### :rocket: New Features
10
+
11
+ - feat: set isPrint redux state when window.print() is triggered [laszlocseh - [`4d07b9a`](https://github.com/eea/volto-eea-website-theme/commit/4d07b9a76af0e43340e100db10ad1051c61f3a89)]
8
12
 
9
13
  #### :bug: Bug Fixes
10
14
 
15
+ - fix: smaller timeout in window.print [laszlocseh - [`808d7e9`](https://github.com/eea/volto-eea-website-theme/commit/808d7e9ef406f38e008a718fd4883fe735c296de)]
16
+
17
+ #### :house: Internal changes
18
+
19
+ - chore: cleanup unused RemoveSchema code [laszlocseh - [`6d8de0a`](https://github.com/eea/volto-eea-website-theme/commit/6d8de0a27dd6bfd5645bb730d2c66f2d4784c158)]
20
+ - chore: cleanup unused RemoveSchema code [laszlocseh - [`c1f2650`](https://github.com/eea/volto-eea-website-theme/commit/c1f2650f0f7ffb27c168399fabcbf69d0299235b)]
21
+
22
+ #### :hammer_and_wrench: Others
23
+
24
+ - test: TokenWidget.test.jsx and TopicsWidget.test.jsx cover more conditions [laszlocseh - [`dd154bb`](https://github.com/eea/volto-eea-website-theme/commit/dd154bb520237f458be563280cac1545eb381bdf)]
25
+ - Bump version to 1.29.0 from 1.28.4 [Claudia Ifrim - [`7bc8eab`](https://github.com/eea/volto-eea-website-theme/commit/7bc8eabd4b0462fc5afc07ed131d074af642cc89)]
26
+ - test: added TokenWidget.test.jsx and TopicsWidget.test.jsx [laszlocseh - [`f7292bb`](https://github.com/eea/volto-eea-website-theme/commit/f7292bb426591f758dcc7bc159b5dbd75b7afb36)]
27
+ - test: added HomePageView.test.jsx and HomePageInverseView.test.jsx [laszlocseh - [`2076651`](https://github.com/eea/volto-eea-website-theme/commit/2076651820693825b5e20b0a7774307cd78eeb57)]
28
+ - test: fix in Logo.test.jsx [laszlocseh - [`6d552f8`](https://github.com/eea/volto-eea-website-theme/commit/6d552f89d5aff6e47ef3032d4862a1bcbe364c01)]
29
+ - test: add Logo.test.jsx [laszlocseh - [`752c562`](https://github.com/eea/volto-eea-website-theme/commit/752c5629840d906382858f088b97d33147684ca8)]
30
+ - add isPrint missing files [laszlocseh - [`d995789`](https://github.com/eea/volto-eea-website-theme/commit/d995789f337a00455d45b5ec26de9c4c0c898ce6)]
31
+ ### [1.28.3](https://github.com/eea/volto-eea-website-theme/compare/1.28.2...1.28.3) - 5 March 2024
32
+
33
+ #### :bug: Bug Fixes
34
+
35
+ - fix(slate): fix copy/paste in slate from Microsoft Word Desktop App - refs #265782 [Miu Razvan - [`f7a128c`](https://github.com/eea/volto-eea-website-theme/commit/f7a128c4ef02207bf6dfd7adf38cb8df8f073d14)]
11
36
  - fix(event): add permalink in customization to the original volto component [kreafox - [`c662aae`](https://github.com/eea/volto-eea-website-theme/commit/c662aae7c271fa1170dd2dbd8cc9a374fed59a2f)]
12
37
 
13
38
  #### :nail_care: Enhancements
@@ -17,6 +42,8 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
17
42
  #### :hammer_and_wrench: Others
18
43
 
19
44
  - Allow hit Enter in the LayoutSettings block to create a new block [Tiberiu Ichim - [`ecde522`](https://github.com/eea/volto-eea-website-theme/commit/ecde52263ce6f989936962bda7bc1213b2750d7d)]
45
+ ### [1.28.2](https://github.com/eea/volto-eea-website-theme/compare/1.28.1...1.28.2) - 29 February 2024
46
+
20
47
  ### [1.28.1](https://github.com/eea/volto-eea-website-theme/compare/1.28.0...1.28.1) - 19 February 2024
21
48
 
22
49
  #### :bug: Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-eea-website-theme",
3
- "version": "1.28.2",
3
+ "version": "1.29.0",
4
4
  "description": "@eeacms/volto-eea-website-theme: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: IDM2 A-Team",
@@ -1 +1 @@
1
- export * from './schema';
1
+ export * from './print';
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Print action.
3
+ * @module actions/print
4
+ */
5
+
6
+ import { SET_ISPRINT } from '@eeacms/volto-eea-website-theme/constants/ActionTypes';
7
+
8
+ export const setIsPrint = (data) => {
9
+ return {
10
+ type: SET_ISPRINT,
11
+ payload: data,
12
+ };
13
+ };
@@ -1,11 +1,11 @@
1
1
  import React, { useCallback, useMemo, useRef } from 'react';
2
2
  import { Helmet } from '@plone/volto/helpers';
3
3
  import { compose } from 'redux';
4
- import { connect } from 'react-redux';
4
+ import { connect, useDispatch } from 'react-redux';
5
5
  import { withRouter } from 'react-router';
6
6
  import { defineMessages, injectIntl } from 'react-intl';
7
7
  import startCase from 'lodash/startCase';
8
- import { Icon } from 'semantic-ui-react';
8
+ import { Icon, Loader } from 'semantic-ui-react';
9
9
  import Popup from '@eeacms/volto-eea-design-system/ui/Popup/Popup';
10
10
  import config from '@plone/volto/registry';
11
11
  import Banner from '@eeacms/volto-eea-design-system/ui/Banner/Banner';
@@ -14,7 +14,8 @@ import {
14
14
  sharePage,
15
15
  } from '@eeacms/volto-eea-design-system/ui/Banner/Banner';
16
16
  import Copyright from '@eeacms/volto-eea-design-system/ui/Copyright/Copyright';
17
-
17
+ import { setIsPrint } from '@eeacms/volto-eea-website-theme/actions/print';
18
+ import cx from 'classnames';
18
19
  import './styles.less';
19
20
 
20
21
  const messages = defineMessages({
@@ -65,6 +66,7 @@ const Title = ({ config = {}, properties }) => {
65
66
  };
66
67
 
67
68
  const View = (props) => {
69
+ const dispatch = useDispatch();
68
70
  const { banner = {}, intl } = props;
69
71
  const metadata = props.metadata || props.properties;
70
72
  const popupRef = useRef(null);
@@ -164,14 +166,95 @@ const View = (props) => {
164
166
  </>
165
167
  )}
166
168
  {!hideDownloadButton && (
167
- <Banner.Action
168
- icon="ri-download-2-fill"
169
- title={intl.formatMessage(messages.download)}
170
- className="download"
171
- onClick={() => {
172
- window.print();
173
- }}
174
- />
169
+ <>
170
+ <Banner.Action
171
+ icon="ri-download-2-fill"
172
+ title={intl.formatMessage(messages.download)}
173
+ className="download"
174
+ onClick={() => {
175
+ // set tabs to be visible
176
+ const tabs = document.getElementsByClassName('ui tab');
177
+ Array.from(tabs).forEach((tab) => {
178
+ tab.style.display = 'block';
179
+ });
180
+
181
+ dispatch(setIsPrint(true));
182
+ // display loader
183
+ const printLoader = document.getElementById(
184
+ 'download-print-loader',
185
+ );
186
+ printLoader.style.display = 'flex';
187
+
188
+ let timeoutValue = 1000;
189
+ // if we have plotlycharts increase timeout
190
+ setTimeout(() => {
191
+ const plotlyCharts = document.getElementsByClassName(
192
+ 'visualization-wrapper',
193
+ );
194
+ if (plotlyCharts.length > 0) {
195
+ timeoutValue = timeoutValue + 1000;
196
+ }
197
+ }, timeoutValue);
198
+
199
+ // scroll to iframes to make them be in the viewport
200
+ // use timeout to wait for load
201
+ setTimeout(() => {
202
+ const iframes = document.getElementsByTagName('iframe');
203
+ if (iframes.length > 0) {
204
+ timeoutValue = timeoutValue + 2000;
205
+ Array.from(iframes).forEach((element, index) => {
206
+ setTimeout(() => {
207
+ element.scrollIntoView({
208
+ behavior: 'instant',
209
+ block: 'nearest',
210
+ inline: 'center',
211
+ });
212
+ }, timeoutValue);
213
+ timeoutValue = timeoutValue + 3000;
214
+ });
215
+ timeoutValue = timeoutValue + 1000;
216
+ }
217
+
218
+ setTimeout(() => {
219
+ window.scrollTo({
220
+ top: 0,
221
+ });
222
+ Array.from(tabs).forEach((tab) => {
223
+ tab.style.display = '';
224
+ });
225
+ printLoader.style.display = 'none';
226
+ dispatch(setIsPrint(false));
227
+ window.print();
228
+ }, timeoutValue);
229
+ }, timeoutValue);
230
+ }}
231
+ />
232
+ <div
233
+ id="download-print-loader"
234
+ className={cx('ui warning message')}
235
+ style={{
236
+ position: 'fixed',
237
+ left: '40%',
238
+ right: '40%',
239
+ backgroundColor: '#fff',
240
+ padding: '1em',
241
+ display: 'none',
242
+ flexDirection: 'column',
243
+ alignItems: 'center',
244
+ top: '40%',
245
+ zIndex: '9999',
246
+ }}
247
+ >
248
+ <Loader
249
+ disabled={false}
250
+ indeterminate
251
+ active
252
+ inline
253
+ size="medium"
254
+ ></Loader>
255
+ <div>Preparing download</div>
256
+ </div>
257
+ </>
175
258
  )}
176
259
  {rssLinks?.map((rssLink, index) => (
177
260
  <>
@@ -253,6 +336,7 @@ export default compose(
253
336
  connect((state) => {
254
337
  return {
255
338
  types: state.types.types,
339
+ isPrint: state.isPrint,
256
340
  };
257
341
  }),
258
342
  )(View);
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import HomePageInverseView from './HomePageInverseView';
4
+ import '@testing-library/jest-dom/extend-expect';
5
+
6
+ describe('HomePageInverseView Component', () => {
7
+ it('renders without crashing', () => {
8
+ const mockContent = (
9
+ <body>
10
+ <div>Mock content</div>
11
+ </body>
12
+ );
13
+ const { container } = render(<HomePageInverseView content={mockContent} />);
14
+ expect(container).toBeTruthy();
15
+ });
16
+ });
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import HomePageView from './HomePageView';
4
+ import '@testing-library/jest-dom/extend-expect';
5
+
6
+ describe('HomePageView Component', () => {
7
+ it('renders without crashing', () => {
8
+ const mockContent = (
9
+ <body>
10
+ <div>Mock content</div>
11
+ </body>
12
+ );
13
+ const { container } = render(<HomePageView content={mockContent} />);
14
+ expect(container).toBeTruthy();
15
+ });
16
+ });
@@ -0,0 +1,31 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import { Provider } from 'react-intl-redux';
4
+ import configureStore from 'redux-mock-store';
5
+ import { Router } from 'react-router-dom';
6
+ import { createMemoryHistory } from 'history';
7
+ import EEALogo from './Logo';
8
+
9
+ const mockStore = configureStore();
10
+ let history = createMemoryHistory();
11
+
12
+ describe('EEALogo Component', () => {
13
+ it('renders without crashing', () => {
14
+ const store = mockStore({
15
+ intl: {
16
+ locale: 'en',
17
+ messages: {},
18
+ },
19
+ });
20
+
21
+ const { container } = render(
22
+ <Provider store={store}>
23
+ <Router history={history}>
24
+ <EEALogo />
25
+ </Router>
26
+ </Provider>,
27
+ );
28
+
29
+ expect(container).toBeTruthy();
30
+ });
31
+ });
@@ -0,0 +1,54 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import { Provider } from 'react-intl-redux';
4
+ import configureStore from 'redux-mock-store';
5
+ import { Router } from 'react-router-dom';
6
+ import { createMemoryHistory } from 'history';
7
+ import { TokenWidget } from './TokenWidget';
8
+
9
+ const mockStore = configureStore();
10
+ let history = createMemoryHistory();
11
+
12
+ describe('TokenWidget Component', () => {
13
+ it('renders without crashing', () => {
14
+ const store = mockStore({
15
+ intl: {
16
+ locale: 'en',
17
+ messages: {},
18
+ },
19
+ });
20
+
21
+ const { container } = render(
22
+ <Provider store={store}>
23
+ <Router history={history}>
24
+ <TokenWidget
25
+ value={['Value1', 'Value2']}
26
+ children={''}
27
+ className={'test'}
28
+ />
29
+ </Router>
30
+ </Provider>,
31
+ );
32
+
33
+ expect(container).toBeTruthy();
34
+ });
35
+
36
+ it('renders without crashing, without value', () => {
37
+ const store = mockStore({
38
+ intl: {
39
+ locale: 'en',
40
+ messages: {},
41
+ },
42
+ });
43
+
44
+ const { container } = render(
45
+ <Provider store={store}>
46
+ <Router history={history}>
47
+ <TokenWidget value={null} children={''} className={'test'} />
48
+ </Router>
49
+ </Provider>,
50
+ );
51
+
52
+ expect(container).toBeTruthy();
53
+ });
54
+ });
@@ -0,0 +1,54 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import { Provider } from 'react-intl-redux';
4
+ import configureStore from 'redux-mock-store';
5
+ import { Router } from 'react-router-dom';
6
+ import { createMemoryHistory } from 'history';
7
+ import { TopicsWidget } from './TopicsWidget';
8
+
9
+ const mockStore = configureStore();
10
+ let history = createMemoryHistory();
11
+
12
+ describe('TopicsWidget Component', () => {
13
+ it('renders without crashing, with value', () => {
14
+ const store = mockStore({
15
+ intl: {
16
+ locale: 'en',
17
+ messages: {},
18
+ },
19
+ });
20
+
21
+ const { container } = render(
22
+ <Provider store={store}>
23
+ <Router history={history}>
24
+ <TopicsWidget
25
+ value={['Value1', 'Value2']}
26
+ children={''}
27
+ className={'test'}
28
+ />
29
+ </Router>
30
+ </Provider>,
31
+ );
32
+
33
+ expect(container).toBeTruthy();
34
+ });
35
+
36
+ it('renders without crashing, without value', () => {
37
+ const store = mockStore({
38
+ intl: {
39
+ locale: 'en',
40
+ messages: {},
41
+ },
42
+ });
43
+
44
+ const { container } = render(
45
+ <Provider store={store}>
46
+ <Router history={history}>
47
+ <TopicsWidget value={null} children={''} className={'test'} />
48
+ </Router>
49
+ </Provider>,
50
+ );
51
+
52
+ expect(container).toBeTruthy();
53
+ });
54
+ });
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Action types.
3
+ * @module constants/ActionTypes
4
+ */
5
+
6
+ export const SET_ISPRINT = 'SET_ISPRINT';
@@ -0,0 +1,372 @@
1
+ /* eslint no-console: ["error", { allow: ["error", "warn"] }] */
2
+ import { Editor, Transforms, Text } from 'slate'; // Range, RangeRef
3
+ import config from '@plone/volto/registry';
4
+ import {
5
+ getBlocksFieldname,
6
+ getBlocksLayoutFieldname,
7
+ } from '@plone/volto/helpers';
8
+ import _ from 'lodash';
9
+ import { makeEditor } from '@plone/volto-slate/utils/editor';
10
+
11
+ // case sensitive; first in an inner array is the default and preffered format
12
+ // in that array of formats
13
+ const formatAliases = [
14
+ ['strong', 'b'],
15
+ ['em', 'i'],
16
+ ['del', 's'],
17
+ ];
18
+
19
+ /**
20
+ * Excerpt from Slate documentation, kept here for posterity:
21
+ * See https://docs.slatejs.org/concepts/11-normalizing#built-in-constraints
22
+
23
+ ## Built-in Constraints
24
+
25
+ Slate editors come with a few built-in constraints out of the box. These
26
+ constraints are there to make working with content much more predictable than
27
+ standard contenteditable. All of the built-in logic in Slate depends on these
28
+ constraints, so unfortunately you cannot omit them. They are...
29
+
30
+ - All Element nodes must contain at least one Text descendant. If an element node
31
+ does not contain any children, an empty text node will be added as its only
32
+ child. This constraint exists to ensure that the selection's anchor and focus
33
+ points (which rely on referencing text nodes) can always be placed inside any
34
+ node. With this, empty elements (or void elements) wouldn't be selectable.
35
+
36
+ - Two adjacent texts with the same custom properties will be merged. If two
37
+ adjacent text nodes have the same formatting, they're merged into a single text
38
+ node with a combined text string of the two. This exists to prevent the text
39
+ nodes from only ever expanding in count in the document, since both adding and
40
+ removing formatting results in splitting text nodes.
41
+
42
+ - Block nodes can only contain other blocks, or inline and text nodes. For
43
+ example, a paragraph block cannot have another paragraph block element and
44
+ a link inline element as children at the same time. The type of children
45
+ allowed is determined by the first child, and any other non-conforming children
46
+ are removed. This ensures that common richtext behaviors like "splitting
47
+ a block in two" function consistently.
48
+
49
+ - Inline nodes cannot be the first or last child of a parent block, nor can it
50
+ be next to another inline node in the children array. If this is the case, an
51
+ empty text node will be added to correct this to be in compliance with the
52
+ constraint.
53
+
54
+ - The top-level editor node can only contain block nodes. If any of the
55
+ top-level children are inline or text nodes they will be removed. This ensures
56
+ that there are always block nodes in the editor so that behaviors like
57
+ "splitting a block in two" work as expected.
58
+
59
+ - These default constraints are all mandated because they make working with
60
+ Slate documents much more predictable.
61
+
62
+ Although these constraints are the best we've come up with now, we're always
63
+ looking for ways to have Slate's built-in constraints be less constraining if
64
+ possible—as long as it keeps standard behaviors easy to reason about. If you
65
+ come up with a way to reduce or remove a built-in constraint with a different
66
+ approach, we're all ears!
67
+ *
68
+ */
69
+
70
+ export const normalizeExternalData = (editor, nodes) => {
71
+ let fakeEditor = makeEditor({ extensions: editor._installedPlugins });
72
+ // set the nodes to the fakeEditor
73
+ let currentIndex = 0;
74
+ fakeEditor.children = nodes.reduce((nodes, node) => {
75
+ if (Editor.isBlock(fakeEditor, node) && !!node.type) {
76
+ nodes.push(node);
77
+ currentIndex++;
78
+ return nodes;
79
+ }
80
+ if (!nodes[currentIndex]) {
81
+ nodes[currentIndex] = { type: 'p', children: [] };
82
+ }
83
+ nodes[currentIndex].children.push(node);
84
+ return nodes;
85
+ }, []);
86
+
87
+ Editor.normalize(fakeEditor, { force: true });
88
+
89
+ return fakeEditor.children;
90
+ };
91
+
92
+ /**
93
+ * Is it text? Is it whitespace (space, newlines, tabs) ?
94
+ *
95
+ */
96
+ export const isWhitespace = (c) => {
97
+ return (
98
+ typeof c === 'string' &&
99
+ c.replace(/\s/g, '').replace(/\t/g, '').replace(/\n/g, '').length === 0
100
+ );
101
+ };
102
+
103
+ export function createDefaultBlock(children) {
104
+ return {
105
+ type: config.settings.slate.defaultBlockType,
106
+ children: children || [{ text: '' }],
107
+ };
108
+ }
109
+
110
+ export function createBlock(type, children) {
111
+ return {
112
+ type,
113
+ children: children || [{ text: '' }],
114
+ };
115
+ }
116
+
117
+ export function createEmptyParagraph() {
118
+ // TODO: rename to createEmptyBlock
119
+ return {
120
+ type: config.settings.slate.defaultBlockType,
121
+ children: [{ text: '' }],
122
+ };
123
+ }
124
+
125
+ export function createParagraph(text) {
126
+ return {
127
+ type: config.settings.slate.defaultBlockType,
128
+ children: [{ text }],
129
+ };
130
+ }
131
+
132
+ export const isSingleBlockTypeActive = (editor, format) => {
133
+ const [match] = Editor.nodes(editor, {
134
+ match: (n) => n.type === format,
135
+ });
136
+
137
+ return !!match;
138
+ };
139
+
140
+ export const isBlockActive = (editor, format) => {
141
+ const aliasList = _.find(formatAliases, (x) => _.includes(x, format));
142
+
143
+ if (aliasList) {
144
+ const aliasFound = _.some(aliasList, (y) => {
145
+ return isSingleBlockTypeActive(editor, y);
146
+ });
147
+
148
+ if (aliasFound) {
149
+ return true;
150
+ }
151
+ }
152
+
153
+ return isSingleBlockTypeActive(editor, format);
154
+ };
155
+
156
+ export const getBlockTypeContextData = (editor, format) => {
157
+ let isActive, defaultFormat, matcher;
158
+
159
+ const aliasList = _.find(formatAliases, (x) => _.includes(x, format));
160
+
161
+ if (aliasList) {
162
+ const aliasFound = _.some(aliasList, (y) => {
163
+ return isSingleBlockTypeActive(editor, y);
164
+ });
165
+
166
+ if (aliasFound) {
167
+ isActive = true;
168
+ defaultFormat = _.first(aliasList);
169
+ matcher = (n) => _.includes(aliasList, n.type);
170
+
171
+ return { isActive, defaultFormat, matcher };
172
+ }
173
+ }
174
+
175
+ isActive = isBlockActive(editor, format);
176
+ defaultFormat = format;
177
+ matcher = (n) => n.type === format;
178
+
179
+ return { isActive, defaultFormat, matcher };
180
+ };
181
+
182
+ export const toggleInlineFormat = (editor, format) => {
183
+ const { isActive, defaultFormat, matcher } = getBlockTypeContextData(
184
+ editor,
185
+ format,
186
+ );
187
+
188
+ if (isActive) {
189
+ const rangeRef = Editor.rangeRef(editor, editor.selection);
190
+
191
+ Transforms.unwrapNodes(editor, {
192
+ match: matcher,
193
+ split: false,
194
+ });
195
+
196
+ const newSel = JSON.parse(JSON.stringify(rangeRef.current));
197
+
198
+ Transforms.select(editor, newSel);
199
+ editor.setSavedSelection(newSel);
200
+ // editor.savedSelection = newSel;
201
+ return;
202
+ }
203
+
204
+ const exclusiveElements = config.settings.slate.exclusiveElements;
205
+ const matchedElements = exclusiveTags(exclusiveElements, format);
206
+ let alreadyOneIsActive =
207
+ !!matchedElements &&
208
+ (matchedElements.indexOf(format) === 0
209
+ ? isBlockActive(editor, matchedElements[1])
210
+ : isBlockActive(editor, matchedElements[0]));
211
+
212
+ if (alreadyOneIsActive) {
213
+ Transforms.unwrapNodes(editor, {
214
+ match: (n) => matchedElements.includes(n.type),
215
+ split: false,
216
+ });
217
+
218
+ const block = { type: format, children: [] };
219
+ Transforms.wrapNodes(editor, block, { split: true });
220
+ return;
221
+ }
222
+
223
+ // `children` property is added automatically as an empty array then
224
+ // normalized
225
+ const block = { type: defaultFormat };
226
+ Transforms.wrapNodes(editor, block, { split: true });
227
+ };
228
+
229
+ const exclusiveTags = (exclusiveElements, format) => {
230
+ let elements = null;
231
+ for (const item of exclusiveElements) {
232
+ if (item.includes(format)) {
233
+ elements = item;
234
+ break;
235
+ }
236
+ }
237
+
238
+ return elements;
239
+ };
240
+
241
+ export const toggleBlock = (editor, format, allowedChildren) => {
242
+ // We have 6 boolean variables which need to be accounted for.
243
+ // See https://docs.google.com/spreadsheets/d/1mVeMuqSTMABV2BhoHPrPAFjn7zUksbNgZ9AQK_dcd3U/edit?usp=sharing
244
+ const { slate } = config.settings;
245
+ const { listTypes } = slate;
246
+
247
+ const isListItem = isBlockActive(editor, slate.listItemType);
248
+ const isActive = isBlockActive(editor, format);
249
+ const wantsList = listTypes.includes(format);
250
+
251
+ if (isListItem && !wantsList) {
252
+ toggleFormatAsListItem(editor, format);
253
+ } else if (isListItem && wantsList && !isActive) {
254
+ switchListType(editor, format);
255
+ } else if (!isListItem && wantsList) {
256
+ changeBlockToList(editor, format);
257
+ } else if (!isListItem && !wantsList) {
258
+ toggleFormat(editor, format, allowedChildren);
259
+ } else if (isListItem && wantsList && isActive) {
260
+ clearFormatting(editor);
261
+ } else {
262
+ console.warn('toggleBlock case not covered, please examine:', {
263
+ wantsList,
264
+ isActive,
265
+ isListItem,
266
+ });
267
+ }
268
+ };
269
+
270
+ /*
271
+ * Applies a block format to a list item. Will split the list
272
+ */
273
+ export const toggleFormatAsListItem = (editor, format) => {
274
+ Transforms.setNodes(editor, {
275
+ type: format,
276
+ });
277
+
278
+ Editor.normalize(editor);
279
+ };
280
+
281
+ /*
282
+ * Toggles between list types by exploding the block
283
+ */
284
+ export const switchListType = (editor, format) => {
285
+ const { slate } = config.settings;
286
+ Transforms.unwrapNodes(editor, {
287
+ match: (n) => slate.listTypes.includes(n.type),
288
+ split: true,
289
+ });
290
+ const block = { type: format, children: [] };
291
+ Transforms.wrapNodes(editor, block);
292
+ };
293
+
294
+ export const changeBlockToList = (editor, format) => {
295
+ const { slate } = config.settings;
296
+ const [match] = Editor.nodes(editor, {
297
+ match: (n) => n.type === slate.listItemType,
298
+ });
299
+
300
+ if (!match) {
301
+ Transforms.setNodes(editor, {
302
+ type: slate.listItemType,
303
+ // id: nanoid(8),
304
+ });
305
+ }
306
+
307
+ // `children` property is added automatically as an empty array then
308
+ // normalized
309
+ const block = { type: format };
310
+ Transforms.wrapNodes(editor, block);
311
+ };
312
+
313
+ export const toggleFormat = (editor, format, allowedChildren) => {
314
+ const { slate } = config.settings;
315
+ const isActive = isBlockActive(editor, format);
316
+ const type = isActive ? slate.defaultBlockType : format;
317
+ Transforms.setNodes(editor, {
318
+ type,
319
+ });
320
+ allowedChildren?.length &&
321
+ Transforms.unwrapNodes(editor, {
322
+ mode: 'all',
323
+ at: [0],
324
+ match: (n, path) => {
325
+ const isMatch =
326
+ path.length > 1 && // we don't deal with the parent nodes
327
+ !(Text.isText(n) || allowedChildren.includes(n?.type));
328
+ return isMatch;
329
+ },
330
+ });
331
+ };
332
+
333
+ /**
334
+ * @param {object} properties A prop received by the View component
335
+ * which is read by the `getBlocksFieldname` and
336
+ * `getBlocksLayoutFieldname` Volto helpers to produce the return value.
337
+ * @returns {Array} All the blocks data taken from the Volto form.
338
+ */
339
+ export const getAllBlocks = (properties, blocks) => {
340
+ const blocksFieldName = getBlocksFieldname(properties);
341
+ const blocksLayoutFieldname = getBlocksLayoutFieldname(properties);
342
+
343
+ for (const n of properties?.[blocksLayoutFieldname]?.items || []) {
344
+ const block = properties[blocksFieldName][n];
345
+ // TODO Make this configurable via block config getBlocks
346
+ if (
347
+ block?.data?.[blocksLayoutFieldname] &&
348
+ block?.data?.[blocksFieldName]
349
+ ) {
350
+ getAllBlocks(block.data, blocks);
351
+ } else if (block?.[blocksLayoutFieldname] && block?.[blocksFieldName]) {
352
+ getAllBlocks(block, blocks);
353
+ }
354
+ blocks.push({
355
+ id: n,
356
+ ...block,
357
+ });
358
+ }
359
+ return blocks;
360
+ };
361
+
362
+ export const clearFormatting = (editor) => {
363
+ const { slate } = config.settings;
364
+ Transforms.setNodes(editor, {
365
+ type: slate.defaultBlockType,
366
+ });
367
+ Transforms.unwrapNodes(editor, {
368
+ match: (n) => n.type && n.type !== slate.defaultBlockType,
369
+ mode: 'all',
370
+ split: false,
371
+ });
372
+ };
package/src/index.js CHANGED
@@ -27,7 +27,7 @@ import contentBoxSVG from './icons/content-box.svg';
27
27
  import okMiddleware from './middleware/ok';
28
28
  import voltoCustomMiddleware from './middleware/voltoCustom';
29
29
  import installSlate from './slate';
30
-
30
+ import { print } from './reducers';
31
31
  import { nanoid } from '@plone/volto-slate/utils';
32
32
  import { v4 as uuid } from 'uuid';
33
33
 
@@ -534,6 +534,12 @@ const applyConfig = (config) => {
534
534
  };
535
535
  }
536
536
 
537
+ // addonReducers
538
+ config.addonReducers = {
539
+ ...(config.addonReducers || {}),
540
+ print,
541
+ };
542
+
537
543
  // Breadcrumbs
538
544
  config.settings.apiExpanders.push({
539
545
  match: '',
package/src/index.test.js CHANGED
@@ -215,6 +215,11 @@ describe('applyConfig', () => {
215
215
  title: 'Horizontal',
216
216
  isDefault: false,
217
217
  },
218
+ {
219
+ id: 'accordion',
220
+ title: 'Accordion responsive',
221
+ isDefault: false,
222
+ },
218
223
  ],
219
224
  },
220
225
  columnsBlock: {},
@@ -0,0 +1 @@
1
+ export print from './print';
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Print reducer.
3
+ * @module reducers/print
4
+ */
5
+
6
+ import { SET_ISPRINT } from '@eeacms/volto-eea-website-theme/constants/ActionTypes';
7
+
8
+ const initialState = {
9
+ isPrint: false,
10
+ };
11
+
12
+ export default function print(state = initialState, action) {
13
+ if (action.type === SET_ISPRINT) {
14
+ return {
15
+ ...state,
16
+ isPrint: action.payload,
17
+ };
18
+ } else {
19
+ return state;
20
+ }
21
+ }
@@ -1,5 +0,0 @@
1
- export function removeSchema() {
2
- return {
3
- type: 'REMOVE_SCHEMA',
4
- };
5
- }