@plone/volto 19.1.5 → 19.1.6
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 +20 -0
- package/locales/af/LC_MESSAGES/volto.po +13 -11
- package/locales/af.json +1 -1
- package/locales/ar/LC_MESSAGES/volto.po +13 -11
- package/locales/ar.json +1 -1
- package/locales/bg/LC_MESSAGES/volto.po +13 -11
- package/locales/bg.json +1 -1
- package/locales/bn/LC_MESSAGES/volto.po +13 -11
- package/locales/bn.json +1 -1
- package/locales/ca/LC_MESSAGES/volto.po +13 -11
- package/locales/ca.json +1 -1
- package/locales/cs/LC_MESSAGES/volto.po +13 -11
- package/locales/cs.json +1 -1
- package/locales/cy/LC_MESSAGES/volto.po +13 -11
- package/locales/cy.json +1 -1
- package/locales/da/LC_MESSAGES/volto.po +13 -11
- package/locales/da.json +1 -1
- package/locales/de/LC_MESSAGES/volto.po +13 -11
- package/locales/de.json +1 -1
- package/locales/el/LC_MESSAGES/volto.po +13 -11
- package/locales/el.json +1 -1
- package/locales/en/LC_MESSAGES/volto.po +13 -11
- package/locales/en.json +1 -1
- package/locales/en_AU/LC_MESSAGES/volto.po +13 -11
- package/locales/en_AU.json +1 -1
- package/locales/en_GB/LC_MESSAGES/volto.po +13 -11
- package/locales/en_GB.json +1 -1
- package/locales/eo/LC_MESSAGES/volto.po +13 -11
- package/locales/eo.json +1 -1
- package/locales/es/LC_MESSAGES/volto.po +13 -11
- package/locales/es.json +1 -1
- package/locales/et/LC_MESSAGES/volto.po +13 -11
- package/locales/et.json +1 -1
- package/locales/eu/LC_MESSAGES/volto.po +13 -11
- package/locales/eu.json +1 -1
- package/locales/fa/LC_MESSAGES/volto.po +13 -11
- package/locales/fa.json +1 -1
- package/locales/fi/LC_MESSAGES/volto.po +13 -11
- package/locales/fi.json +1 -1
- package/locales/fr/LC_MESSAGES/volto.po +13 -11
- package/locales/fr.json +1 -1
- package/locales/fu/LC_MESSAGES/volto.po +13 -11
- package/locales/fu.json +1 -1
- package/locales/gl/LC_MESSAGES/volto.po +13 -11
- package/locales/gl.json +1 -1
- package/locales/he/LC_MESSAGES/volto.po +13 -11
- package/locales/he.json +1 -1
- package/locales/hi/LC_MESSAGES/volto.po +13 -11
- package/locales/hi.json +1 -1
- package/locales/hr/LC_MESSAGES/volto.po +13 -11
- package/locales/hr.json +1 -1
- package/locales/hu/LC_MESSAGES/volto.po +13 -11
- package/locales/hu.json +1 -1
- package/locales/hy/LC_MESSAGES/volto.po +13 -11
- package/locales/hy.json +1 -1
- package/locales/id/LC_MESSAGES/volto.po +13 -11
- package/locales/id.json +1 -1
- package/locales/it/LC_MESSAGES/volto.po +13 -11
- package/locales/it.json +1 -1
- package/locales/ja/LC_MESSAGES/volto.po +13 -11
- package/locales/ja.json +1 -1
- package/locales/ka/LC_MESSAGES/volto.po +13 -11
- package/locales/ka.json +1 -1
- package/locales/kn/LC_MESSAGES/volto.po +13 -11
- package/locales/kn.json +1 -1
- package/locales/ko/LC_MESSAGES/volto.po +13 -11
- package/locales/ko.json +1 -1
- package/locales/lt/LC_MESSAGES/volto.po +13 -11
- package/locales/lt.json +1 -1
- package/locales/lv/LC_MESSAGES/volto.po +13 -11
- package/locales/lv.json +1 -1
- package/locales/mi/LC_MESSAGES/volto.po +13 -11
- package/locales/mi.json +1 -1
- package/locales/mk/LC_MESSAGES/volto.po +13 -11
- package/locales/mk.json +1 -1
- package/locales/my/LC_MESSAGES/volto.po +13 -11
- package/locales/my.json +1 -1
- package/locales/nb_NO/LC_MESSAGES/volto.po +13 -11
- package/locales/nb_NO.json +1 -1
- package/locales/nl/LC_MESSAGES/volto.po +13 -11
- package/locales/nl.json +1 -1
- package/locales/nn/LC_MESSAGES/volto.po +13 -11
- package/locales/nn.json +1 -1
- package/locales/pl/LC_MESSAGES/volto.po +13 -11
- package/locales/pl.json +1 -1
- package/locales/pt/LC_MESSAGES/volto.po +13 -11
- package/locales/pt.json +1 -1
- package/locales/pt_BR/LC_MESSAGES/volto.po +13 -11
- package/locales/pt_BR.json +1 -1
- package/locales/rm/LC_MESSAGES/volto.po +13 -11
- package/locales/rm.json +1 -1
- package/locales/ro/LC_MESSAGES/volto.po +13 -11
- package/locales/ro.json +1 -1
- package/locales/ru/LC_MESSAGES/volto.po +13 -11
- package/locales/ru.json +1 -1
- package/locales/sk/LC_MESSAGES/volto.po +13 -11
- package/locales/sk.json +1 -1
- package/locales/sl/LC_MESSAGES/volto.po +13 -11
- package/locales/sl.json +1 -1
- package/locales/sm/LC_MESSAGES/volto.po +13 -11
- package/locales/sm.json +1 -1
- package/locales/sq/LC_MESSAGES/volto.po +13 -11
- package/locales/sq.json +1 -1
- package/locales/sr/LC_MESSAGES/volto.po +13 -11
- package/locales/sr.json +1 -1
- package/locales/sr@cyrl/LC_MESSAGES/volto.po +13 -11
- package/locales/sr@cyrl.json +1 -1
- package/locales/sr@latn/LC_MESSAGES/volto.po +13 -11
- package/locales/sr@latn.json +1 -1
- package/locales/sv/LC_MESSAGES/volto.po +13 -11
- package/locales/sv.json +1 -1
- package/locales/ta/LC_MESSAGES/volto.po +13 -11
- package/locales/ta.json +1 -1
- package/locales/te/LC_MESSAGES/volto.po +13 -11
- package/locales/te.json +1 -1
- package/locales/th/LC_MESSAGES/volto.po +13 -11
- package/locales/th.json +1 -1
- package/locales/to/LC_MESSAGES/volto.po +13 -11
- package/locales/to.json +1 -1
- package/locales/tr/LC_MESSAGES/volto.po +13 -11
- package/locales/tr.json +1 -1
- package/locales/uk/LC_MESSAGES/volto.po +13 -11
- package/locales/uk.json +1 -1
- package/locales/vi/LC_MESSAGES/volto.po +13 -11
- package/locales/vi.json +1 -1
- package/locales/volto.pot +12 -10
- package/locales/zh_CN/LC_MESSAGES/volto.po +13 -11
- package/locales/zh_CN.json +1 -1
- package/locales/zh_Hant/LC_MESSAGES/volto.po +13 -11
- package/locales/zh_Hant.json +1 -1
- package/locales/zh_Hant_HK/LC_MESSAGES/volto.po +13 -11
- package/locales/zh_Hant_HK.json +1 -1
- package/package.json +6 -7
- package/src/components/manage/Controlpanels/BlockTypes.test.ts +132 -0
- package/src/components/manage/Controlpanels/BlockTypes.tsx +32 -11
- package/src/components/manage/Sidebar/ObjectBrowserBody.jsx +23 -2
- package/src/components/manage/Toolbar/More.jsx +13 -0
- package/src/components/manage/Widgets/FileWidget.jsx +19 -9
- package/src/components/manage/Widgets/RegistryImageWidget.jsx +5 -3
- package/theme/themes/pastanaga/extras/focus.less +33 -0
- package/theme/themes/pastanaga/extras/main.less +1 -0
- package/theme/themes/pastanaga/extras/skiplinks.less +0 -2
- package/theme/themes/pastanaga/globals/site.variables +7 -0
- package/types/components/manage/Controlpanels/BlockTypes.d.ts +48 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { createIntl, createIntlCache } from 'react-intl';
|
|
2
|
+
import type { BlocksConfigData } from '@plone/types';
|
|
3
|
+
import { sortConfigBlocks } from './BlockTypes';
|
|
4
|
+
|
|
5
|
+
const intl = createIntl(
|
|
6
|
+
{ locale: 'en', messages: { Teaser: 'Destaque' } },
|
|
7
|
+
createIntlCache(),
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
// `BlockConfigBase` types id and title as required, so a malformed entry
|
|
11
|
+
// cannot be expressed through it. That mismatch between the type and the
|
|
12
|
+
// configuration is the bug these tests cover, so the fixtures go around it.
|
|
13
|
+
const asBlocksConfig = (config: Record<string, unknown>) =>
|
|
14
|
+
config as unknown as BlocksConfigData;
|
|
15
|
+
|
|
16
|
+
describe('sortConfigBlocks', () => {
|
|
17
|
+
it('lists a registered block', () => {
|
|
18
|
+
const blocks = sortConfigBlocks(
|
|
19
|
+
asBlocksConfig({ image: { id: 'image', title: 'Image' } }),
|
|
20
|
+
intl,
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
expect(blocks).toEqual([{ id: 'image', title: 'Image' }]);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('translates the title', () => {
|
|
27
|
+
const blocks = sortConfigBlocks(
|
|
28
|
+
asBlocksConfig({ teaser: { id: 'teaser', title: 'Teaser' } }),
|
|
29
|
+
intl,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
expect(blocks[0].title).toEqual('Destaque');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('sorts by the translated title, not by the config order', () => {
|
|
36
|
+
const blocks = sortConfigBlocks(
|
|
37
|
+
asBlocksConfig({
|
|
38
|
+
zebra: { id: 'zebra', title: 'Zebra' },
|
|
39
|
+
apple: { id: 'apple', title: 'Apple' },
|
|
40
|
+
// 'Teaser' translates to 'Destaque', so it sorts before 'Zebra'.
|
|
41
|
+
teaser: { id: 'teaser', title: 'Teaser' },
|
|
42
|
+
}),
|
|
43
|
+
intl,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
expect(blocks.map((block) => block.title)).toEqual([
|
|
47
|
+
'Apple',
|
|
48
|
+
'Destaque',
|
|
49
|
+
'Zebra',
|
|
50
|
+
]);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('keeps the rest of the block config', () => {
|
|
54
|
+
const blocks = sortConfigBlocks(
|
|
55
|
+
asBlocksConfig({
|
|
56
|
+
image: {
|
|
57
|
+
id: 'image',
|
|
58
|
+
title: 'Image',
|
|
59
|
+
icon: 'image.svg',
|
|
60
|
+
group: 'media',
|
|
61
|
+
},
|
|
62
|
+
}),
|
|
63
|
+
intl,
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
expect(blocks[0]).toMatchObject({ icon: 'image.svg', group: 'media' });
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('entries that are not blocks', () => {
|
|
70
|
+
// An add-on that configures a block which was never registered leaves an
|
|
71
|
+
// entry behind carrying only what it set. Listing it would link to a block
|
|
72
|
+
// that does not exist, and a row with no id crashes the table.
|
|
73
|
+
it('drops an entry with neither an id nor a title', () => {
|
|
74
|
+
const blocks = sortConfigBlocks(
|
|
75
|
+
asBlocksConfig({
|
|
76
|
+
image: { id: 'image', title: 'Image' },
|
|
77
|
+
accordion: { addPermission: [] },
|
|
78
|
+
}),
|
|
79
|
+
intl,
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
expect(blocks).toEqual([{ id: 'image', title: 'Image' }]);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('drops an entry with an id but no title', () => {
|
|
86
|
+
const blocks = sortConfigBlocks(
|
|
87
|
+
asBlocksConfig({ accordion: { id: 'accordion' } }),
|
|
88
|
+
intl,
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
expect(blocks).toEqual([]);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('drops an entry with a title but no id', () => {
|
|
95
|
+
const blocks = sortConfigBlocks(
|
|
96
|
+
asBlocksConfig({ accordion: { title: 'Accordion' } }),
|
|
97
|
+
intl,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
expect(blocks).toEqual([]);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('does not fall back to the config key for a missing id', () => {
|
|
104
|
+
const blocks = sortConfigBlocks(
|
|
105
|
+
asBlocksConfig({ accordion: { title: 'Accordion' } }),
|
|
106
|
+
intl,
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
expect(blocks.map((block) => block.id)).not.toContain('accordion');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('gives every listed block an id, so the table can key its rows', () => {
|
|
113
|
+
const blocks = sortConfigBlocks(
|
|
114
|
+
asBlocksConfig({
|
|
115
|
+
image: { id: 'image', title: 'Image' },
|
|
116
|
+
accordion: { addPermission: [] },
|
|
117
|
+
teaser: { id: 'teaser' },
|
|
118
|
+
}),
|
|
119
|
+
intl,
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
expect(blocks.length).toBeGreaterThan(0);
|
|
123
|
+
for (const block of blocks) {
|
|
124
|
+
expect(block.id).toBeTruthy();
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('returns nothing when no block is configured', () => {
|
|
130
|
+
expect(sortConfigBlocks(asBlocksConfig({}), intl)).toEqual([]);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
@@ -5,6 +5,7 @@ import { useClient } from '@plone/volto/hooks';
|
|
|
5
5
|
import config from '@plone/volto/registry';
|
|
6
6
|
import { createPortal } from 'react-dom';
|
|
7
7
|
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
|
8
|
+
import type { IntlShape } from 'react-intl';
|
|
8
9
|
import { Link } from 'react-router-dom';
|
|
9
10
|
import { useEffect } from 'react';
|
|
10
11
|
import { getBlockTypes } from '@plone/volto/actions/blockTypes/blockTypes';
|
|
@@ -15,6 +16,7 @@ import UniversalLink from '@plone/volto/components/manage/UniversalLink/Universa
|
|
|
15
16
|
|
|
16
17
|
import backSVG from '@plone/volto/icons/back.svg';
|
|
17
18
|
import type { Location } from 'history';
|
|
19
|
+
import type { BlocksConfigData } from '@plone/types';
|
|
18
20
|
|
|
19
21
|
const messages = defineMessages({
|
|
20
22
|
back: {
|
|
@@ -53,6 +55,35 @@ type SelectorState = {
|
|
|
53
55
|
blockTypes: BlockTypesState;
|
|
54
56
|
};
|
|
55
57
|
|
|
58
|
+
/**
|
|
59
|
+
* List the configured blocks, by title.
|
|
60
|
+
*
|
|
61
|
+
* An add-on can configure a block that was never registered — enhancing one
|
|
62
|
+
* that ships with another add-on, say — which leaves an entry behind that is
|
|
63
|
+
* not a block at all, with no id or no title. `BlockConfigBase` says both are
|
|
64
|
+
* always there, but the config disagrees, so drop those entries rather than
|
|
65
|
+
* list a block that does not exist.
|
|
66
|
+
* @function sortConfigBlocks
|
|
67
|
+
* @param {BlocksConfigData} blocksConfig The blocks configuration.
|
|
68
|
+
* @param {IntlShape} intl Used to translate each block title.
|
|
69
|
+
* @returns {Array} The configured blocks, sorted by translated title.
|
|
70
|
+
*/
|
|
71
|
+
export function sortConfigBlocks(
|
|
72
|
+
blocksConfig: BlocksConfigData,
|
|
73
|
+
intl: IntlShape,
|
|
74
|
+
) {
|
|
75
|
+
return Object.entries(blocksConfig)
|
|
76
|
+
.filter(([, blockConfig]) => blockConfig.id && blockConfig.title)
|
|
77
|
+
.map(([, blockConfig]) => ({
|
|
78
|
+
...blockConfig,
|
|
79
|
+
title: intl.formatMessage({
|
|
80
|
+
id: blockConfig.title,
|
|
81
|
+
defaultMessage: blockConfig.title,
|
|
82
|
+
}),
|
|
83
|
+
}))
|
|
84
|
+
.sort((a, b) => (a.title === b.title ? 0 : a.title > b.title ? 1 : -1));
|
|
85
|
+
}
|
|
86
|
+
|
|
56
87
|
const BlockTypesControlpanel = (props: RouteProps) => {
|
|
57
88
|
const { location } = props;
|
|
58
89
|
const intl = useIntl();
|
|
@@ -62,17 +93,7 @@ const BlockTypesControlpanel = (props: RouteProps) => {
|
|
|
62
93
|
const pathname = location.pathname;
|
|
63
94
|
const isClient = useClient();
|
|
64
95
|
|
|
65
|
-
const blocks =
|
|
66
|
-
.map(([key, blockConfig]) => ({
|
|
67
|
-
...blockConfig,
|
|
68
|
-
title: blockConfig.title
|
|
69
|
-
? intl.formatMessage({
|
|
70
|
-
id: blockConfig.title,
|
|
71
|
-
defaultMessage: blockConfig.title,
|
|
72
|
-
})
|
|
73
|
-
: blockConfig.id ?? key,
|
|
74
|
-
}))
|
|
75
|
-
.sort((a, b) => (a.title === b.title ? 0 : a.title > b.title ? 1 : -1));
|
|
96
|
+
const blocks = sortConfigBlocks(blocksConfig, intl);
|
|
76
97
|
|
|
77
98
|
useEffect(() => {
|
|
78
99
|
dispatch(getBlockTypes());
|
|
@@ -5,6 +5,7 @@ import { connect } from 'react-redux';
|
|
|
5
5
|
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
|
6
6
|
import { Input, Segment, Breadcrumb } from 'semantic-ui-react';
|
|
7
7
|
|
|
8
|
+
import debounce from 'lodash/debounce';
|
|
8
9
|
import join from 'lodash/join';
|
|
9
10
|
|
|
10
11
|
// These absolute imports (without using the corresponding centralized index.js) are required
|
|
@@ -159,6 +160,22 @@ class ObjectBrowserBody extends Component {
|
|
|
159
160
|
view: this.props.mode === 'image' ? 'icons' : 'list',
|
|
160
161
|
};
|
|
161
162
|
this.searchInputRef = React.createRef();
|
|
163
|
+
// Debounce the live-search dispatch so rapid keystrokes do not produce a
|
|
164
|
+
// burst of overlapping requests whose responses may resolve out of order
|
|
165
|
+
// (an older, less-filtered response arriving after the latest one would
|
|
166
|
+
// visually "reset" the list to look unfiltered).
|
|
167
|
+
this.debouncedSearch = debounce(this.doSearch, 300);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Component will unmount
|
|
172
|
+
* @method componentWillUnmount
|
|
173
|
+
* @returns {undefined}
|
|
174
|
+
*/
|
|
175
|
+
componentWillUnmount() {
|
|
176
|
+
if (this.debouncedSearch) {
|
|
177
|
+
this.debouncedSearch.cancel();
|
|
178
|
+
}
|
|
162
179
|
}
|
|
163
180
|
|
|
164
181
|
/**
|
|
@@ -248,8 +265,7 @@ class ObjectBrowserBody extends Component {
|
|
|
248
265
|
view: prevState.view === 'icons' ? 'list' : 'icons',
|
|
249
266
|
}));
|
|
250
267
|
|
|
251
|
-
|
|
252
|
-
const text = flattenToAppURL(e.target.value);
|
|
268
|
+
doSearch = (text) => {
|
|
253
269
|
if (text.startsWith('/')) {
|
|
254
270
|
this.setState({ currentFolder: text });
|
|
255
271
|
this.props.searchContent(
|
|
@@ -286,6 +302,11 @@ class ObjectBrowserBody extends Component {
|
|
|
286
302
|
}
|
|
287
303
|
};
|
|
288
304
|
|
|
305
|
+
onSearch = (e) => {
|
|
306
|
+
const text = flattenToAppURL(e.target.value);
|
|
307
|
+
this.debouncedSearch(text);
|
|
308
|
+
};
|
|
309
|
+
|
|
289
310
|
onSelectItem = (item) => {
|
|
290
311
|
const url = item['@id'];
|
|
291
312
|
const { block, data, mode, dataName, onChangeBlock } = this.props;
|
|
@@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
|
|
|
4
4
|
import { useDispatch, useSelector, shallowEqual } from 'react-redux';
|
|
5
5
|
import { Link, useHistory } from 'react-router-dom';
|
|
6
6
|
import find from 'lodash/find';
|
|
7
|
+
import { Dimmer, Loader } from 'semantic-ui-react';
|
|
7
8
|
import { toast } from 'react-toastify';
|
|
8
9
|
|
|
9
10
|
import Toast from '@plone/volto/components/manage/Toast/Toast';
|
|
@@ -103,6 +104,10 @@ const messages = defineMessages({
|
|
|
103
104
|
id: 'workingCopyGenericError',
|
|
104
105
|
defaultMessage: 'An error occurred while performing this operation.',
|
|
105
106
|
},
|
|
107
|
+
Loading: {
|
|
108
|
+
id: 'Loading',
|
|
109
|
+
defaultMessage: 'Loading',
|
|
110
|
+
},
|
|
106
111
|
});
|
|
107
112
|
|
|
108
113
|
const More = (props) => {
|
|
@@ -123,6 +128,8 @@ const More = (props) => {
|
|
|
123
128
|
const workingCopyApply = workingCopy?.apply.loading;
|
|
124
129
|
const workingCopyCreate = workingCopy?.create.loading;
|
|
125
130
|
const workingCopyRemove = workingCopy?.remove.loading;
|
|
131
|
+
const isWorkingCopyLoading =
|
|
132
|
+
workingCopyApply || workingCopyCreate || workingCopyRemove;
|
|
126
133
|
|
|
127
134
|
const prevWorkingCopyApplyLoading = usePrevious(workingCopyApply);
|
|
128
135
|
const prevWorkingCopyCreateLoading = usePrevious(workingCopyCreate);
|
|
@@ -216,6 +223,9 @@ const More = (props) => {
|
|
|
216
223
|
: null,
|
|
217
224
|
}}
|
|
218
225
|
>
|
|
226
|
+
<Dimmer active={isWorkingCopyLoading} inverted>
|
|
227
|
+
<Loader>{intl.formatMessage(messages.Loading)}</Loader>
|
|
228
|
+
</Dimmer>
|
|
219
229
|
<header>
|
|
220
230
|
<h2>{content.title}</h2>
|
|
221
231
|
<button
|
|
@@ -335,6 +345,7 @@ const More = (props) => {
|
|
|
335
345
|
<li>
|
|
336
346
|
<button
|
|
337
347
|
aria-label={intl.formatMessage(messages.CreateWorkingCopy)}
|
|
348
|
+
disabled={workingCopyCreate}
|
|
338
349
|
onClick={() => {
|
|
339
350
|
dispatch(createWorkingCopy(path)).then((response) => {
|
|
340
351
|
history.push(flattenToAppURL(response['@id']));
|
|
@@ -354,6 +365,7 @@ const More = (props) => {
|
|
|
354
365
|
<li>
|
|
355
366
|
<button
|
|
356
367
|
aria-label={intl.formatMessage(messages.applyWorkingCopy)}
|
|
368
|
+
disabled={workingCopyApply}
|
|
357
369
|
onClick={() => {
|
|
358
370
|
dispatch(applyWorkingCopy(path)).then((response) => {
|
|
359
371
|
history.push(flattenToAppURL(content.working_copy_of['@id']));
|
|
@@ -395,6 +407,7 @@ const More = (props) => {
|
|
|
395
407
|
<li>
|
|
396
408
|
<button
|
|
397
409
|
aria-label={intl.formatMessage(messages.removeWorkingCopy)}
|
|
410
|
+
disabled={workingCopyRemove}
|
|
398
411
|
onClick={() => {
|
|
399
412
|
dispatch(removeWorkingCopy(path)).then((response) => {
|
|
400
413
|
history.push(flattenToAppURL(content.working_copy_of['@id']));
|
|
@@ -35,12 +35,14 @@ const messages = defineMessages({
|
|
|
35
35
|
defaultMessage: 'Drop files here ...',
|
|
36
36
|
},
|
|
37
37
|
editFile: {
|
|
38
|
-
id: 'Drop
|
|
39
|
-
defaultMessage:
|
|
38
|
+
id: 'Drop file here to replace the existing file',
|
|
39
|
+
defaultMessage:
|
|
40
|
+
'File upload area. Press Enter or click to replace the existing file.',
|
|
40
41
|
},
|
|
41
42
|
fileDrag: {
|
|
42
|
-
id: 'Drop
|
|
43
|
-
defaultMessage:
|
|
43
|
+
id: 'Drop file here to upload a new file',
|
|
44
|
+
defaultMessage:
|
|
45
|
+
'File upload area. Press Enter or click to open the file browser.',
|
|
44
46
|
},
|
|
45
47
|
replaceFile: {
|
|
46
48
|
id: 'Replace existing file',
|
|
@@ -60,8 +62,14 @@ const messages = defineMessages({
|
|
|
60
62
|
defaultMessage: 'File is not of the accepted type {accept}',
|
|
61
63
|
},
|
|
62
64
|
dragAndDropActionA11y: {
|
|
63
|
-
id: '
|
|
64
|
-
defaultMessage:
|
|
65
|
+
id: 'Press Enter to browse files from your computer.',
|
|
66
|
+
defaultMessage:
|
|
67
|
+
'File upload area. Press Enter or click to open the file browser.',
|
|
68
|
+
},
|
|
69
|
+
dragAndDropReplaceA11y: {
|
|
70
|
+
id: 'File upload area. Press Enter or click to replace the existing file',
|
|
71
|
+
defaultMessage:
|
|
72
|
+
'File upload area. Press Enter or click to replace the existing file.',
|
|
65
73
|
},
|
|
66
74
|
requiredField: {
|
|
67
75
|
id: 'This field is required.',
|
|
@@ -180,9 +188,11 @@ const FileWidget = (props) => {
|
|
|
180
188
|
};
|
|
181
189
|
|
|
182
190
|
const statusTextA11y = [
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
191
|
+
value
|
|
192
|
+
? intl.formatMessage(messages.dragAndDropReplaceA11y)
|
|
193
|
+
: intl.formatMessage(messages.dragAndDropActionA11y),
|
|
194
|
+
props.required && intl.formatMessage(messages.requiredField),
|
|
195
|
+
value?.filename,
|
|
186
196
|
]
|
|
187
197
|
.filter(Boolean)
|
|
188
198
|
.join('. ');
|
|
@@ -33,12 +33,14 @@ const messages = defineMessages({
|
|
|
33
33
|
defaultMessage: 'Drop files here ...',
|
|
34
34
|
},
|
|
35
35
|
editFile: {
|
|
36
|
-
id: 'Drop
|
|
37
|
-
defaultMessage:
|
|
36
|
+
id: 'Drop file here to replace the existing file',
|
|
37
|
+
defaultMessage:
|
|
38
|
+
'File upload area. Press Enter or click to replace the existing file.',
|
|
38
39
|
},
|
|
39
40
|
fileDrag: {
|
|
40
41
|
id: 'Drop file here to upload a new file',
|
|
41
|
-
defaultMessage:
|
|
42
|
+
defaultMessage:
|
|
43
|
+
'File upload area. Press Enter or click to open the file browser.',
|
|
42
44
|
},
|
|
43
45
|
replaceFile: {
|
|
44
46
|
id: 'Replace existing file',
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
.universal-focus-style() {
|
|
2
|
+
box-shadow: 0 0 0 2px @innerFocusShadow !important;
|
|
3
|
+
outline: @outerFocusOutline auto 2px !important;
|
|
4
|
+
outline-offset: 2px;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// Global focus styles for all elements that support :focus-visible
|
|
8
|
+
// Exclude contenteditable elements (slate editors, rich text inputs) — focus is
|
|
9
|
+
// already implied by the text cursor; an outline ring is noisy and confusing.
|
|
10
|
+
:focus-visible:not([contenteditable]):not([data-slate-editor]) {
|
|
11
|
+
.universal-focus-style();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
//inputs modifications
|
|
15
|
+
input:focus-visible {
|
|
16
|
+
border-color: @outerFocusOutline !important;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Semantic UI checkbox focus styles
|
|
20
|
+
.ui.checkbox input:focus-visible ~ label::before {
|
|
21
|
+
.universal-focus-style();
|
|
22
|
+
outline-offset: 4px;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.ui.checkbox input:focus-visible ~ label::before {
|
|
26
|
+
transition: none !important;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Skiplinks focus styles
|
|
30
|
+
.skiplinks-wrapper a:focus-visible {
|
|
31
|
+
border: 2px solid @linkColor;
|
|
32
|
+
outline: 3px solid @linkColor;
|
|
33
|
+
}
|
|
@@ -1,7 +1,55 @@
|
|
|
1
|
+
import type { IntlShape } from 'react-intl';
|
|
1
2
|
import type { Location } from 'history';
|
|
3
|
+
import type { BlocksConfigData } from '@plone/types';
|
|
2
4
|
type RouteProps = {
|
|
3
5
|
history: History;
|
|
4
6
|
location: Location;
|
|
5
7
|
};
|
|
8
|
+
/**
|
|
9
|
+
* List the configured blocks, by title.
|
|
10
|
+
*
|
|
11
|
+
* An add-on can configure a block that was never registered — enhancing one
|
|
12
|
+
* that ships with another add-on, say — which leaves an entry behind that is
|
|
13
|
+
* not a block at all, with no id or no title. `BlockConfigBase` says both are
|
|
14
|
+
* always there, but the config disagrees, so drop those entries rather than
|
|
15
|
+
* list a block that does not exist.
|
|
16
|
+
* @function sortConfigBlocks
|
|
17
|
+
* @param {BlocksConfigData} blocksConfig The blocks configuration.
|
|
18
|
+
* @param {IntlShape} intl Used to translate each block title.
|
|
19
|
+
* @returns {Array} The configured blocks, sorted by translated title.
|
|
20
|
+
*/
|
|
21
|
+
export declare function sortConfigBlocks(blocksConfig: BlocksConfigData, intl: IntlShape): {
|
|
22
|
+
title: string;
|
|
23
|
+
id: string;
|
|
24
|
+
icon: string;
|
|
25
|
+
group: string;
|
|
26
|
+
category?: string;
|
|
27
|
+
blockModel?: number;
|
|
28
|
+
view?: React.ComponentType<import("@plone/types").BlockViewProps>;
|
|
29
|
+
edit?: React.ComponentType<import("@plone/types").BlockEditProps>;
|
|
30
|
+
schema?: object;
|
|
31
|
+
blockSchema: import("@plone/types").JSONSchema | ((args?: import("@plone/types").BlockSchemaArgs) => import("@plone/types").JSONSchema);
|
|
32
|
+
dataAdapter?: ({ block, data, id, onChangeBlock, value, }: {
|
|
33
|
+
block: string;
|
|
34
|
+
data: import("@plone/types").BlocksFormData;
|
|
35
|
+
id: string;
|
|
36
|
+
onChangeBlock: (id: string, newData: any) => void;
|
|
37
|
+
value: any;
|
|
38
|
+
}) => void;
|
|
39
|
+
restricted: ((args: {
|
|
40
|
+
properties: import("@plone/types").Content;
|
|
41
|
+
block: import("@plone/types").BlockConfigBase;
|
|
42
|
+
navRoot: import("@plone/types").Content;
|
|
43
|
+
contentType: string;
|
|
44
|
+
user: import("@plone/types").User;
|
|
45
|
+
}) => boolean) | boolean;
|
|
46
|
+
mostUsed: boolean;
|
|
47
|
+
blockHasOwnFocusManagement?: boolean;
|
|
48
|
+
sidebarTab: boolean | 0 | 1;
|
|
49
|
+
schemaEnhancer?: (args: import("@plone/types").SchemaEnhancerArgs) => import("@plone/types").JSONSchema;
|
|
50
|
+
variations?: import("@plone/types").BlockExtension[];
|
|
51
|
+
extensions?: Record<string, import("@plone/types").BlockExtension>;
|
|
52
|
+
blocksConfig?: Partial<BlocksConfigData>;
|
|
53
|
+
}[];
|
|
6
54
|
declare const BlockTypesControlpanel: (props: RouteProps) => import("react/jsx-runtime").JSX.Element;
|
|
7
55
|
export default BlockTypesControlpanel;
|