io-sanita-theme 2.11.0 → 2.11.2

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
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.11.2](https://github.com/RedTurtle/io-sanita-theme/compare/2.11.1...2.11.2) (2025-04-08)
4
+
5
+ ### Bug Fixes
6
+
7
+ * fixed title tag for a11y in Attachments component not displayed as section ([#79](https://github.com/RedTurtle/io-sanita-theme/issues/79)) ([96bed38](https://github.com/RedTurtle/io-sanita-theme/commit/96bed38f4441dde74e3e059d7c3a05ad138a7f24))
8
+ * table block sorting ([#78](https://github.com/RedTurtle/io-sanita-theme/issues/78)) ([2e1dfca](https://github.com/RedTurtle/io-sanita-theme/commit/2e1dfcad4faaafddc4991b96deb58078b56fd9be))
9
+
10
+ ## [2.11.1](https://github.com/RedTurtle/io-sanita-theme/compare/2.11.0...2.11.1) (2025-03-31)
11
+
12
+ ### Bug Fixes
13
+
14
+ * a11y Table block ([#76](https://github.com/RedTurtle/io-sanita-theme/issues/76)) ([773d4d9](https://github.com/RedTurtle/io-sanita-theme/commit/773d4d9da80563ef531bcff4d546dd79cbedce00))
15
+ * display none to -newsletterSubscribe on main footer settings ([f2d61ea](https://github.com/RedTurtle/io-sanita-theme/commit/f2d61eabd36d66ea8e5bb146762c5d9bbec595e5))
16
+ * ImageWidget submit form . Backport of voltopr ([2c6c873](https://github.com/RedTurtle/io-sanita-theme/commit/2c6c873042661006f0ba3d60159630d68b07ed20))
17
+ * locales ([b21cbbf](https://github.com/RedTurtle/io-sanita-theme/commit/b21cbbfa51927914081f912e22a801cc9400073f))
18
+ * remove newsletter subscribe option in footer configuration form ([#77](https://github.com/RedTurtle/io-sanita-theme/issues/77)) ([f6b5bf4](https://github.com/RedTurtle/io-sanita-theme/commit/f6b5bf4c7f7b862c2b7ed06908ee6ac2c0be0413))
19
+ * table template view taxonomy ([1175082](https://github.com/RedTurtle/io-sanita-theme/commit/11750828a8cbf7b414fbd8358204af9aa23e24d6))
20
+
3
21
  ## [2.11.0](https://github.com/RedTurtle/io-sanita-theme/compare/2.10.0...2.11.0) (2025-03-25)
4
22
 
5
23
  ### Features
package/RELEASE.md CHANGED
@@ -41,6 +41,15 @@
41
41
  - ...
42
42
  -->
43
43
 
44
+ ## Versione 2.11.1 (31/03/2025)
45
+
46
+ ### Fix
47
+
48
+ - sistemato un problema di perdita di dati durante il caricamento di una nuova immagine nel form di inseriemento dati di un ct con i campi a blocchi.
49
+ - sistemata la visualizzazione delle tassonomie nel template 'Tabella' del blocco elenco
50
+ - sistemata l'accessibilità per il blocco Tabella
51
+ - rimosso il flag di configurazione 'Mostra la form di iscrizione' nel footer.
52
+
44
53
  ## Versione 2.11.0 (25/03/2025)
45
54
 
46
55
  ### Novità
@@ -1077,6 +1077,11 @@ msgstr ""
1077
1077
  msgid "appStoreLink"
1078
1078
  msgstr ""
1079
1079
 
1080
+ #. Default: "ascending"
1081
+ #: overrideTranslations
1082
+ msgid "ascendingTableSort"
1083
+ msgstr ""
1084
+
1080
1085
  #. Default: "Allegato"
1081
1086
  #: components/Cards/CardFile/CardFile
1082
1087
  msgid "attachment"
@@ -1727,6 +1732,11 @@ msgstr ""
1727
1732
  msgid "delete"
1728
1733
  msgstr ""
1729
1734
 
1735
+ #. Default: "descending"
1736
+ #: overrideTranslations
1737
+ msgid "descendingTableSort"
1738
+ msgstr ""
1739
+
1730
1740
  #. Default: "Testo per il link al dettaglio"
1731
1741
  #: config/blocks/listing/ListingOptions/utils
1732
1742
  msgid "detail_link_label"
@@ -1072,6 +1072,11 @@ msgstr "Opening date"
1072
1072
  msgid "appStoreLink"
1073
1073
  msgstr ""
1074
1074
 
1075
+ #. Default: "ascending"
1076
+ #: overrideTranslations
1077
+ msgid "ascendingTableSort"
1078
+ msgstr ""
1079
+
1075
1080
  #. Default: "Allegato"
1076
1081
  #: components/Cards/CardFile/CardFile
1077
1082
  msgid "attachment"
@@ -1722,6 +1727,11 @@ msgstr "Start date"
1722
1727
  msgid "delete"
1723
1728
  msgstr ""
1724
1729
 
1730
+ #. Default: "descending"
1731
+ #: overrideTranslations
1732
+ msgid "descendingTableSort"
1733
+ msgstr ""
1734
+
1725
1735
  #. Default: "Testo per il link al dettaglio"
1726
1736
  #: config/blocks/listing/ListingOptions/utils
1727
1737
  msgid "detail_link_label"
@@ -1079,6 +1079,11 @@ msgstr ""
1079
1079
  msgid "appStoreLink"
1080
1080
  msgstr ""
1081
1081
 
1082
+ #. Default: "ascending"
1083
+ #: overrideTranslations
1084
+ msgid "ascendingTableSort"
1085
+ msgstr ""
1086
+
1082
1087
  #. Default: "Allegato"
1083
1088
  #: components/Cards/CardFile/CardFile
1084
1089
  msgid "attachment"
@@ -1729,6 +1734,11 @@ msgstr ""
1729
1734
  msgid "delete"
1730
1735
  msgstr ""
1731
1736
 
1737
+ #. Default: "descending"
1738
+ #: overrideTranslations
1739
+ msgid "descendingTableSort"
1740
+ msgstr ""
1741
+
1732
1742
  #. Default: "Testo per il link al dettaglio"
1733
1743
  #: config/blocks/listing/ListingOptions/utils
1734
1744
  msgid "detail_link_label"
@@ -1079,6 +1079,11 @@ msgstr ""
1079
1079
  msgid "appStoreLink"
1080
1080
  msgstr ""
1081
1081
 
1082
+ #. Default: "ascending"
1083
+ #: overrideTranslations
1084
+ msgid "ascendingTableSort"
1085
+ msgstr ""
1086
+
1082
1087
  #. Default: "Allegato"
1083
1088
  #: components/Cards/CardFile/CardFile
1084
1089
  msgid "attachment"
@@ -1729,6 +1734,11 @@ msgstr ""
1729
1734
  msgid "delete"
1730
1735
  msgstr ""
1731
1736
 
1737
+ #. Default: "descending"
1738
+ #: overrideTranslations
1739
+ msgid "descendingTableSort"
1740
+ msgstr ""
1741
+
1732
1742
  #. Default: "Testo per il link al dettaglio"
1733
1743
  #: config/blocks/listing/ListingOptions/utils
1734
1744
  msgid "detail_link_label"
@@ -1072,6 +1072,11 @@ msgstr ""
1072
1072
  msgid "appStoreLink"
1073
1073
  msgstr ""
1074
1074
 
1075
+ #. Default: "ascending"
1076
+ #: overrideTranslations
1077
+ msgid "ascendingTableSort"
1078
+ msgstr "ordine crescente"
1079
+
1075
1080
  #. Default: "Allegato"
1076
1081
  #: components/Cards/CardFile/CardFile
1077
1082
  msgid "attachment"
@@ -1722,6 +1727,11 @@ msgstr ""
1722
1727
  msgid "delete"
1723
1728
  msgstr "elimina"
1724
1729
 
1730
+ #. Default: "descending"
1731
+ #: overrideTranslations
1732
+ msgid "descendingTableSort"
1733
+ msgstr "ordine discendente"
1734
+
1725
1735
  #. Default: "Testo per il link al dettaglio"
1726
1736
  #: config/blocks/listing/ListingOptions/utils
1727
1737
  msgid "detail_link_label"
package/locales/volto.pot CHANGED
@@ -1,7 +1,7 @@
1
1
  msgid ""
2
2
  msgstr ""
3
3
  "Project-Id-Version: Plone\n"
4
- "POT-Creation-Date: 2025-03-25T11:31:34.827Z\n"
4
+ "POT-Creation-Date: 2025-03-31T11:23:16.064Z\n"
5
5
  "Last-Translator: Plone i18n <plone-i18n@lists.sourceforge.net>\n"
6
6
  "Language-Team: Plone i18n <plone-i18n@lists.sourceforge.net>\n"
7
7
  "Content-Type: text/plain; charset=utf-8\n"
@@ -1074,6 +1074,11 @@ msgstr ""
1074
1074
  msgid "appStoreLink"
1075
1075
  msgstr ""
1076
1076
 
1077
+ #. Default: "ascending"
1078
+ #: overrideTranslations
1079
+ msgid "ascendingTableSort"
1080
+ msgstr ""
1081
+
1077
1082
  #. Default: "Allegato"
1078
1083
  #: components/Cards/CardFile/CardFile
1079
1084
  msgid "attachment"
@@ -1724,6 +1729,11 @@ msgstr ""
1724
1729
  msgid "delete"
1725
1730
  msgstr ""
1726
1731
 
1732
+ #. Default: "descending"
1733
+ #: overrideTranslations
1734
+ msgid "descendingTableSort"
1735
+ msgstr ""
1736
+
1727
1737
  #. Default: "Testo per il link al dettaglio"
1728
1738
  #: config/blocks/listing/ListingOptions/utils
1729
1739
  msgid "detail_link_label"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "io-sanita-theme",
3
- "version": "2.11.0",
3
+ "version": "2.11.2",
4
4
  "description": "io-sanita-theme: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "license": "MIT",
@@ -42,9 +42,16 @@ const TableTemplate = (props) => {
42
42
  const ct_schemas = useSelector((state) => state.ct_schema?.subrequests);
43
43
 
44
44
  useEffect(() => {
45
- (columns ?? []).forEach((c) => {
46
- if (!ct_schemas[c.ct]) {
47
- dispatch(getCTSchema(c.ct));
45
+ const cts = columns.reduce((acc, c) => {
46
+ if (acc.indexOf(c.ct) < 0) {
47
+ acc.push(c.ct);
48
+ }
49
+ return acc;
50
+ }, []);
51
+
52
+ cts.forEach((c) => {
53
+ if (!ct_schemas[c]) {
54
+ dispatch(getCTSchema(c));
48
55
  }
49
56
  });
50
57
  }, [columns]);
@@ -100,7 +107,9 @@ const TableTemplate = (props) => {
100
107
 
101
108
  let Widget = views?.getWidget(field);
102
109
 
103
- let widget_props = {};
110
+ let widget_props = {
111
+ behavior: field_properties.behavior,
112
+ };
104
113
  switch (c.field) {
105
114
  case 'apertura_bando':
106
115
  case 'chiusura_procedimento_bando':
@@ -116,6 +125,7 @@ const TableTemplate = (props) => {
116
125
  widget_props.vocabulary =
117
126
  field_properties.vocabulary['@id'];
118
127
  }
128
+
119
129
  render_value = (
120
130
  <Widget value={item[c.field]} {...widget_props} />
121
131
  );
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import cx from 'classnames';
3
+ import { render } from 'react-dom';
4
+
5
+ const SelectWidget = ({ value, children, className, behavior }) => {
6
+ let render_value = children
7
+ ? children(value?.title || value?.token || value)
8
+ : value?.title || value?.token || value;
9
+ if (behavior?.startsWith('collective.taxonomy')) {
10
+ render_value = render_value.split('»').reverse()[0];
11
+ }
12
+ return value ? (
13
+ <span className={cx(className, 'select', 'widget')}>{render_value}</span>
14
+ ) : (
15
+ ''
16
+ );
17
+ };
18
+ export default SelectWidget;
@@ -98,7 +98,7 @@ const Attachments = ({
98
98
  </RichTextSection>
99
99
  ) : (
100
100
  <div className="mb-5 mt-3">
101
- {title && <h4 className="h5">{title}</h4>}
101
+ {title && <h3 className="h5">{title}</h3>}
102
102
  {attachments.length > 0 && attachments_view}
103
103
  {searchResults?.[key]?.loading && !searchResults?.[key]?.loaded && <></>}
104
104
  </div>
@@ -54,8 +54,8 @@ import { component } from 'design-react-kit/dist/types/Icon/assets/ItAndroidSqua
54
54
  export const AGGREGATION_PAGE_ARGOMENTO = '/argomento/';
55
55
  export const AGGREGATION_PAGE_TIPOLOGIA_UTENTE = '/tipologia-utente/';
56
56
 
57
- const ReleaseLog = loadable(() =>
58
- import('io-sanita-theme/components/ReleaseLog/ReleaseLog'),
57
+ const ReleaseLog = loadable(
58
+ () => import('io-sanita-theme/components/ReleaseLog/ReleaseLog'),
59
59
  );
60
60
 
61
61
  const messages = defineMessages({
@@ -223,7 +223,7 @@ export default function applyConfig(config) {
223
223
 
224
224
  'volto-editablefooter': {
225
225
  ...config.settings['volto-editablefooter'],
226
- options: { socials: true, newsletterSubscribe: true },
226
+ options: { socials: true, newsletterSubscribe: false },
227
227
  },
228
228
 
229
229
  'volto-form-block-italia': {
@@ -49,7 +49,11 @@ const BlocksViewWidget = loadable(() =>
49
49
  /* webpackChunkName: "ISWidgetView" */ 'io-sanita-theme/components/View/Widgets/BlocksViewWidget'
50
50
  ),
51
51
  );
52
-
52
+ const SelectViewWidget = loadable(() =>
53
+ import(
54
+ /* webpackChunkName: "ISWidgetView" */ 'io-sanita-theme/components/View/Widgets/SelectViewWidget'
55
+ ),
56
+ );
53
57
  const PanelsWidget = loadable(() =>
54
58
  import(
55
59
  /* webpackChunkName: "ISManage" */ 'io-sanita-theme/components/manage/Widgets/PanelsWidget/PanelsWidget'
@@ -99,7 +103,12 @@ const getIoSanitaWidgets = (config) => {
99
103
  views: {
100
104
  ...config.widgets.views,
101
105
  id: { ...config.widgets.views.id, parliamo_di: ParliamoDiWidgetView },
102
- widget: { ...config.widgets.views.widget, blocks: BlocksViewWidget },
106
+ widget: {
107
+ ...config.widgets.views.widget,
108
+ blocks: BlocksViewWidget,
109
+ choices: SelectViewWidget,
110
+ },
111
+ choices: SelectViewWidget,
103
112
  },
104
113
  type: {
105
114
  ...config.widgets.type,
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Slate Table block's View component.
3
+ * @module volto-slate/blocks/Table/View
4
+ * Customizations:
5
+ * - aggiunto aria-sort per indicare la direzione dell’ordinamento (ascending o descending). Se la colonna non è ordinata, usa aria-sort="none".
6
+ * - role="columnheader": Aggiunge il ruolo di intestazione di colonna.
7
+ * - Accessibilità tastiera (onKeyDown) per permette l’ordinamento con Enter o Spazio.
8
+ * - tabIndex={0} per rende la cella interattiva tramite tastiera.
9
+ */
10
+
11
+ import React, { useState, useMemo } from 'react';
12
+ import PropTypes from 'prop-types';
13
+ import { Table } from 'semantic-ui-react';
14
+ import {
15
+ serializeNodes,
16
+ serializeNodesToText,
17
+ } from '@plone/volto-slate/editor/render';
18
+ import { Node } from 'slate';
19
+ import { useIntl, defineMessages } from 'react-intl';
20
+
21
+ const messages = defineMessages({
22
+ ascendingTableSort: {
23
+ id: 'ascendingTableSort',
24
+ defaultMessage: 'ascending',
25
+ },
26
+ descendingTableSort: {
27
+ id: 'descendingTableSort',
28
+ defaultMessage: 'descending',
29
+ },
30
+ });
31
+
32
+ /**
33
+ * Slate Table block's View class.
34
+ * @class View
35
+ * @param {object} data The table data to render as a table.
36
+ */
37
+ const View = ({ data }) => {
38
+ const [state, setState] = useState({
39
+ column: null,
40
+ direction: null,
41
+ });
42
+
43
+ const intl = useIntl();
44
+
45
+ const { table } = data;
46
+ const {
47
+ rows,
48
+ fixed,
49
+ compact,
50
+ basic,
51
+ celled,
52
+ inverted,
53
+ striped,
54
+ sortable,
55
+ hideHeaders,
56
+ } = table;
57
+
58
+ const headers = useMemo(() => rows?.[0]?.cells || [], [rows]);
59
+ const rowsData = useMemo(() => {
60
+ return (
61
+ rows?.slice(1).map((row) =>
62
+ row.cells.map((cell) => ({
63
+ ...cell,
64
+ value:
65
+ cell.value && Node.string({ children: cell.value }).length > 0
66
+ ? serializeNodes(cell.value)
67
+ : '\u00A0',
68
+ valueText:
69
+ cell.value && Node.string({ children: cell.value }).length > 0
70
+ ? serializeNodesToText(cell.value)
71
+ : '\u00A0',
72
+ })),
73
+ ) || []
74
+ );
75
+ }, [rows]);
76
+
77
+ const sortedRows = useMemo(() => {
78
+ if (state.column === null) return rowsData;
79
+ return [...rowsData].sort((a, b) => {
80
+ const aText = a[state.column].valueText;
81
+ const bText = b[state.column].valueText;
82
+ const isAscending = state.direction === 'ascending';
83
+ if (isAscending ? aText < bText : aText > bText) return -1;
84
+ if (isAscending ? aText > bText : aText < bText) return 1;
85
+ return 0;
86
+ });
87
+ }, [state, rowsData]);
88
+
89
+ const handleSort = (index) => {
90
+ if (!sortable) return;
91
+ setState({
92
+ column: index,
93
+ direction:
94
+ state.column !== index
95
+ ? 'ascending'
96
+ : state.direction === 'ascending'
97
+ ? 'descending'
98
+ : 'ascending',
99
+ sortLabel:
100
+ state.column !== index
101
+ ? intl.formatMessage(messages.ascendingTableSort)
102
+ : state.direction === 'ascending'
103
+ ? intl.formatMessage(messages.descendingTableSort)
104
+ : intl.formatMessage(messages.ascendingTableSort),
105
+ });
106
+ };
107
+
108
+ return (
109
+ <>
110
+ {table && (
111
+ <Table
112
+ fixed={fixed}
113
+ compact={compact}
114
+ basic={basic ? 'very' : false}
115
+ celled={celled}
116
+ inverted={inverted}
117
+ striped={striped}
118
+ sortable={sortable}
119
+ className="slate-table-block"
120
+ >
121
+ {!hideHeaders && (
122
+ <Table.Header>
123
+ <Table.Row>
124
+ {headers.map((cell, index) => (
125
+ <Table.HeaderCell
126
+ key={index}
127
+ textAlign="left"
128
+ verticalAlign="middle"
129
+ sorted={state.column === index ? state.direction : null}
130
+ aria-sort={
131
+ state.column === index ? state.sortLabel : 'none'
132
+ }
133
+ role="columnheader"
134
+ onClick={() => handleSort(index)}
135
+ onKeyDown={(e) => {
136
+ if (e.key === 'Enter' || e.key === ' ') {
137
+ e.preventDefault();
138
+ handleSort(index);
139
+ }
140
+ }}
141
+ tabIndex={0}
142
+ >
143
+ {cell.value &&
144
+ Node.string({ children: cell.value }).length > 0
145
+ ? serializeNodes(cell.value)
146
+ : '\u00A0'}
147
+ </Table.HeaderCell>
148
+ ))}
149
+ </Table.Row>
150
+ </Table.Header>
151
+ )}
152
+ <Table.Body>
153
+ {sortedRows.map((row, rowIndex) => (
154
+ <Table.Row key={rowIndex}>
155
+ {row.map((cell, cellIndex) => (
156
+ <Table.Cell
157
+ key={cellIndex}
158
+ textAlign="left"
159
+ verticalAlign="middle"
160
+ >
161
+ {cell.value}
162
+ </Table.Cell>
163
+ ))}
164
+ </Table.Row>
165
+ ))}
166
+ </Table.Body>
167
+ </Table>
168
+ )}
169
+ </>
170
+ );
171
+ };
172
+
173
+ /**
174
+ * Property types.
175
+ * @property {Object} propTypes Property types.
176
+ * @static
177
+ */
178
+ View.propTypes = {
179
+ data: PropTypes.objectOf(PropTypes.any).isRequired,
180
+ };
181
+
182
+ export default View;
@@ -0,0 +1,343 @@
1
+ /*
2
+ customizations: backport of: https://github.com/plone/volto/pull/6879
3
+
4
+ */
5
+ import React, { useEffect, useRef } from 'react';
6
+ import { Button, Dimmer, Loader, Message } from 'semantic-ui-react';
7
+ import { useIntl, defineMessages } from 'react-intl';
8
+ import { useDispatch } from 'react-redux';
9
+ import { useLocation } from 'react-router-dom';
10
+ import loadable from '@loadable/component';
11
+ import { connect } from 'react-redux';
12
+ import { compose } from 'redux';
13
+ import useLinkEditor from '@plone/volto/components/manage/AnchorPlugin/useLinkEditor';
14
+ import withObjectBrowser from '@plone/volto/components/manage/Sidebar/ObjectBrowser';
15
+
16
+ import {
17
+ flattenToAppURL,
18
+ getBaseUrl,
19
+ isInternalURL,
20
+ } from '@plone/volto/helpers/Url/Url';
21
+ import { validateFileUploadSize } from '@plone/volto/helpers/FormValidation/FormValidation';
22
+ import { usePrevious } from '@plone/volto/helpers/Utils/usePrevious';
23
+ import { createContent } from '@plone/volto/actions/content/content';
24
+ import { readAsDataURL } from 'promise-file-reader';
25
+ import FormFieldWrapper from '@plone/volto/components/manage/Widgets/FormFieldWrapper';
26
+ import Icon from '@plone/volto/components/theme/Icon/Icon';
27
+
28
+ import imageBlockSVG from '@plone/volto/components/manage/Blocks/Image/block-image.svg';
29
+ import clearSVG from '@plone/volto/icons/clear.svg';
30
+ import navTreeSVG from '@plone/volto/icons/nav.svg';
31
+ import linkSVG from '@plone/volto/icons/link.svg';
32
+ import uploadSVG from '@plone/volto/icons/upload.svg';
33
+
34
+ const Dropzone = loadable(() => import('react-dropzone'));
35
+
36
+ export const ImageToolbar = ({ className, data, id, onChange, selected }) => (
37
+ <div className="image-upload-widget-toolbar">
38
+ <Button.Group>
39
+ <Button icon basic onClick={() => onChange(id, null)}>
40
+ <Icon className="circled" name={clearSVG} size="24px" color="#e40166" />
41
+ </Button>
42
+ </Button.Group>
43
+ </div>
44
+ );
45
+
46
+ const messages = defineMessages({
47
+ addImage: {
48
+ id: 'Browse the site, drop an image, or type a URL',
49
+ defaultMessage: 'Browse the site, drop an image, or use a URL',
50
+ },
51
+ pickAnImage: {
52
+ id: 'pickAnImage',
53
+ defaultMessage: 'Pick an existing image',
54
+ },
55
+ uploadAnImage: {
56
+ id: 'uploadAnImage',
57
+ defaultMessage: 'Upload an image from your computer',
58
+ },
59
+ linkAnImage: {
60
+ id: 'linkAnImage',
61
+ defaultMessage: 'Enter a URL to an image',
62
+ },
63
+ uploadingImage: {
64
+ id: 'Uploading image',
65
+ defaultMessage: 'Uploading image',
66
+ },
67
+ });
68
+
69
+ const UnconnectedImageInput = (props) => {
70
+ const {
71
+ id,
72
+ onChange,
73
+ onFocus,
74
+ openObjectBrowser,
75
+ value,
76
+ imageSize = 'teaser',
77
+ selected = true,
78
+ hideLinkPicker = false,
79
+ hideObjectBrowserPicker = false,
80
+ restrictFileUpload = false,
81
+ objectBrowserPickerType = 'image',
82
+ description,
83
+ placeholderLinkInput = '',
84
+ onSelectItem,
85
+ } = props;
86
+ const imageValue = value?.[0]?.['@id'] || value;
87
+
88
+ const intl = useIntl();
89
+ const linkEditor = useLinkEditor();
90
+ const location = useLocation();
91
+ const dispatch = useDispatch();
92
+ const contextUrl = location.pathname;
93
+
94
+ const [uploading, setUploading] = React.useState(false);
95
+ const [dragging, setDragging] = React.useState(false);
96
+
97
+ const imageUploadInputRef = useRef(null);
98
+
99
+ const requestId = `image-upload-${id}`;
100
+
101
+ const loaded = props.request.loaded;
102
+ const { content } = props;
103
+ const imageId = content?.['@id'];
104
+ const image = content?.image;
105
+ let loading = false;
106
+
107
+ useEffect(() => {
108
+ if (uploading && loading && loaded) {
109
+ setUploading(false);
110
+ onChange(id, imageId, {
111
+ image_field: 'image',
112
+ image_scales: { image: [image] },
113
+ });
114
+ }
115
+ }, [loading, loaded, uploading, imageId, image, id, onChange]); // Explicitly list all dependencies
116
+
117
+ loading = usePrevious(props.request?.loading);
118
+
119
+ const handleUpload = React.useCallback(
120
+ (eventOrFile) => {
121
+ if (restrictFileUpload === true) return;
122
+ eventOrFile.target && eventOrFile.stopPropagation();
123
+
124
+ setUploading(true);
125
+ const file = eventOrFile.target
126
+ ? eventOrFile.target.files[0]
127
+ : eventOrFile[0];
128
+ if (!validateFileUploadSize(file, intl.formatMessage)) return;
129
+ readAsDataURL(file).then((fileData) => {
130
+ const fields = fileData.match(/^data:(.*);(.*),(.*)$/);
131
+ dispatch(
132
+ createContent(
133
+ getBaseUrl(contextUrl),
134
+ {
135
+ '@type': 'Image',
136
+ title: file.name,
137
+ image: {
138
+ data: fields[3],
139
+ encoding: fields[2],
140
+ 'content-type': fields[1],
141
+ filename: file.name,
142
+ },
143
+ },
144
+ props.block || requestId,
145
+ ),
146
+ );
147
+ });
148
+ },
149
+ [
150
+ restrictFileUpload,
151
+ intl.formatMessage,
152
+ dispatch,
153
+ props,
154
+ contextUrl,
155
+ requestId,
156
+ ],
157
+ );
158
+
159
+ const onDragEnter = React.useCallback(() => {
160
+ if (restrictFileUpload === false) setDragging(true);
161
+ }, [restrictFileUpload]);
162
+ const onDragLeave = React.useCallback(() => setDragging(false), []);
163
+
164
+ return imageValue ? (
165
+ <div
166
+ className="image-upload-widget-image"
167
+ onClick={onFocus}
168
+ onKeyDown={onFocus}
169
+ role="toolbar"
170
+ >
171
+ {selected && <ImageToolbar {...props} />}
172
+ <img
173
+ className={props.className}
174
+ src={
175
+ isInternalURL(imageValue)
176
+ ? `${flattenToAppURL(imageValue)}/@@images/image/${imageSize}`
177
+ : imageValue
178
+ }
179
+ alt=""
180
+ />
181
+ </div>
182
+ ) : (
183
+ <div
184
+ className="image-upload-widget"
185
+ onClick={onFocus}
186
+ onKeyDown={onFocus}
187
+ role="toolbar"
188
+ >
189
+ <Dropzone
190
+ noClick
191
+ onDrop={handleUpload}
192
+ onDragEnter={onDragEnter}
193
+ onDragLeave={onDragLeave}
194
+ className="dropzone"
195
+ >
196
+ {({ getRootProps, getInputProps }) => (
197
+ <div {...getRootProps()}>
198
+ <Message>
199
+ {dragging && <Dimmer active></Dimmer>}
200
+ {uploading && (
201
+ <Dimmer active>
202
+ <Loader indeterminate>
203
+ {intl.formatMessage(messages.uploadingImage)}
204
+ </Loader>
205
+ </Dimmer>
206
+ )}
207
+ <img src={imageBlockSVG} alt="" className="placeholder" />
208
+ <p>{description || intl.formatMessage(messages.addImage)}</p>
209
+ <div className="toolbar-wrapper">
210
+ <div className="toolbar-inner" ref={linkEditor.anchorNode}>
211
+ {hideObjectBrowserPicker === false && (
212
+ <Button.Group>
213
+ <Button
214
+ aria-label={intl.formatMessage(messages.pickAnImage)}
215
+ icon
216
+ basic
217
+ onClick={(e) => {
218
+ onFocus && onFocus();
219
+ e.preventDefault();
220
+ openObjectBrowser({
221
+ mode: objectBrowserPickerType,
222
+ onSelectItem: onSelectItem
223
+ ? onSelectItem
224
+ : (url, { title, image_field, image_scales }) => {
225
+ onChange(props.id, flattenToAppURL(url), {
226
+ title,
227
+ image_field,
228
+ image_scales,
229
+ });
230
+ },
231
+ currentPath: contextUrl,
232
+ });
233
+ }}
234
+ type="button"
235
+ >
236
+ <Icon name={navTreeSVG} size="24px" />
237
+ </Button>
238
+ </Button.Group>
239
+ )}
240
+ {restrictFileUpload === false && (
241
+ <Button.Group>
242
+ <Button
243
+ aria-label={intl.formatMessage(messages.uploadAnImage)}
244
+ icon
245
+ basic
246
+ compact
247
+ onClick={() => {
248
+ imageUploadInputRef.current.click();
249
+ }}
250
+ type="button"
251
+ >
252
+ <Icon name={uploadSVG} size="24px" />
253
+ </Button>
254
+ <input
255
+ {...getInputProps({
256
+ type: 'file',
257
+ ref: imageUploadInputRef,
258
+ onChange: handleUpload,
259
+ style: { display: 'none' },
260
+ })}
261
+ />
262
+ </Button.Group>
263
+ )}
264
+
265
+ {hideLinkPicker === false && (
266
+ <Button.Group>
267
+ <Button
268
+ icon
269
+ basic
270
+ aria-label={intl.formatMessage(messages.linkAnImage)}
271
+ onClick={(e) => {
272
+ !props.selected && onFocus && onFocus();
273
+ linkEditor.show();
274
+ }}
275
+ type="button"
276
+ >
277
+ <Icon name={linkSVG} circled size="24px" />
278
+ </Button>
279
+ </Button.Group>
280
+ )}
281
+ </div>
282
+ {linkEditor.anchorNode && (
283
+ <linkEditor.LinkEditor
284
+ value={imageValue}
285
+ placeholder={
286
+ placeholderLinkInput ||
287
+ intl.formatMessage(messages.linkAnImage)
288
+ }
289
+ objectBrowserPickerType={objectBrowserPickerType}
290
+ onChange={(_, e) =>
291
+ onChange(
292
+ props.id,
293
+ isInternalURL(e) ? flattenToAppURL(e) : e,
294
+ {},
295
+ )
296
+ }
297
+ id={id}
298
+ />
299
+ )}
300
+ </div>
301
+ </Message>
302
+ </div>
303
+ )}
304
+ </Dropzone>
305
+ </div>
306
+ );
307
+ };
308
+
309
+ export const ImageInput = compose(
310
+ connect(
311
+ (state, ownProps) => {
312
+ const requestId = `image-upload-${ownProps.id}`;
313
+ return {
314
+ request: state.content.subrequests[ownProps.block || requestId] || {},
315
+ content: state.content.subrequests[ownProps.block || requestId]?.data,
316
+ };
317
+ },
318
+ { createContent },
319
+ ),
320
+ )(withObjectBrowser(UnconnectedImageInput));
321
+
322
+ const ImageUploadWidget = (props) => {
323
+ const { fieldSet, id, title } = props;
324
+ return (
325
+ <FormFieldWrapper
326
+ {...props}
327
+ columns={1}
328
+ className="block image-upload-widget"
329
+ >
330
+ <div className="wrapper">
331
+ <label
332
+ id={`fieldset-${fieldSet}-field-label-${id}`}
333
+ htmlFor={`field-${id}`}
334
+ >
335
+ {title}
336
+ </label>
337
+ </div>
338
+ <ImageInput {...props} />
339
+ </FormFieldWrapper>
340
+ );
341
+ };
342
+
343
+ export default ImageUploadWidget;
@@ -170,4 +170,12 @@ defineMessages({
170
170
  id: 'mainMenu',
171
171
  defaultMessage: 'Menù principale',
172
172
  },
173
+ ascendingTableSort: {
174
+ id: 'ascendingTableSort',
175
+ defaultMessage: 'ascending',
176
+ },
177
+ descendingTableSort: {
178
+ id: 'descendingTableSort',
179
+ defaultMessage: 'descending',
180
+ },
173
181
  });