diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dd70383e7..e86b6b3198 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,29 @@ +## 17.0.0-alpha.13 (2023-06-15) + +### Feature + +- Add and enforce a new config setting, `maxFileUploadSize`. @davisagli [#4868](https://github.com/plone/volto/issues/4868) + +### Bugfix + +- Fix and improve the `addStyling` helper @sneridagh [#4880](https://github.com/plone/volto/issues/4880) + + +## 17.0.0-alpha.12 (2023-06-14) + +### Feature + +- Allow to deselect color in ColorPickerWidget. @ksuess [#4838](https://github.com/plone/volto/issues/4838) +- Configurable Container component from registry for some key route views. @sneridagh [#4871](https://github.com/plone/volto/issues/4871) + +### Bugfix + +- Fix regression in horizontal scroll in contents view, add it back @sneridagh [#4872](https://github.com/plone/volto/issues/4872) + + ## 17.0.0-alpha.11 (2023-06-09) ### Bugfix diff --git a/docs/source/configuration/settings-reference.md b/docs/source/configuration/settings-reference.md index 779d7f1417..c201810b22 100644 --- a/docs/source/configuration/settings-reference.md +++ b/docs/source/configuration/settings-reference.md @@ -83,6 +83,10 @@ maxResponseSize You can edit this limit in the `settings` object setting a new value in bytes (for example, to set 500 mb you need to write 5000000000). +maxFileUploadSize + The maximum allowed size of file uploads (in bytes). + Default: `null` (no limit enforced by Volto). + initialReducersBlacklist The initial state passed from server to browser needs to be minimal in order to optimize the resultant html generated. This state gets stored in `window.__data` and received in client. diff --git a/locales/ca/LC_MESSAGES/volto.po b/locales/ca/LC_MESSAGES/volto.po index f66e060712..b2d90627de 100644 --- a/locales/ca/LC_MESSAGES/volto.po +++ b/locales/ca/LC_MESSAGES/volto.po @@ -4344,6 +4344,11 @@ msgstr "quan" msgid "event_where" msgstr "On?" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: This website does not accept files larger than {limit} +msgid "fileTooLarge" +msgstr "" + #: helpers/MessageLabels/MessageLabels # defaultMessage: flush intIds and rebuild relations msgid "flush intIds and rebuild relations" diff --git a/locales/de/LC_MESSAGES/volto.po b/locales/de/LC_MESSAGES/volto.po index 1cd4e36247..47a5f4712b 100644 --- a/locales/de/LC_MESSAGES/volto.po +++ b/locales/de/LC_MESSAGES/volto.po @@ -4341,10 +4341,15 @@ msgstr "Datum" msgid "event_where" msgstr "Ort" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: This website does not accept files larger than {limit} +msgid "fileTooLarge" +msgstr "Dateien, die größer sind als {limit}, dürfen nicht hochgeladen werden." + #: helpers/MessageLabels/MessageLabels # defaultMessage: flush intIds and rebuild relations msgid "flush intIds and rebuild relations" -msgstr "TODO Translation. IntIds neu generieren und Relationen neu katalogisieren" +msgstr "IntIds neu generieren und Relationen neu katalogisieren" #: components/manage/Blocks/Teaser/schema # defaultMessage: Head title diff --git a/locales/en/LC_MESSAGES/volto.po b/locales/en/LC_MESSAGES/volto.po index 4412c7770d..4d58ae3ae7 100644 --- a/locales/en/LC_MESSAGES/volto.po +++ b/locales/en/LC_MESSAGES/volto.po @@ -4335,6 +4335,11 @@ msgstr "" msgid "event_where" msgstr "" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: This website does not accept files larger than {limit} +msgid "fileTooLarge" +msgstr "" + #: helpers/MessageLabels/MessageLabels # defaultMessage: flush intIds and rebuild relations msgid "flush intIds and rebuild relations" diff --git a/locales/es/LC_MESSAGES/volto.po b/locales/es/LC_MESSAGES/volto.po index f4119bbb55..3bad762d19 100644 --- a/locales/es/LC_MESSAGES/volto.po +++ b/locales/es/LC_MESSAGES/volto.po @@ -4346,6 +4346,11 @@ msgstr "Cuándo" msgid "event_where" msgstr "Dónde" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: This website does not accept files larger than {limit} +msgid "fileTooLarge" +msgstr "" + #: helpers/MessageLabels/MessageLabels # defaultMessage: flush intIds and rebuild relations msgid "flush intIds and rebuild relations" diff --git a/locales/eu/LC_MESSAGES/volto.po b/locales/eu/LC_MESSAGES/volto.po index b6f2cbeddd..78e86c7c24 100644 --- a/locales/eu/LC_MESSAGES/volto.po +++ b/locales/eu/LC_MESSAGES/volto.po @@ -4342,6 +4342,11 @@ msgstr "Noiz" msgid "event_where" msgstr "Non" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: This website does not accept files larger than {limit} +msgid "fileTooLarge" +msgstr "" + #: helpers/MessageLabels/MessageLabels # defaultMessage: flush intIds and rebuild relations msgid "flush intIds and rebuild relations" diff --git a/locales/fi/LC_MESSAGES/volto.po b/locales/fi/LC_MESSAGES/volto.po index bbe380fac5..af159edbda 100644 --- a/locales/fi/LC_MESSAGES/volto.po +++ b/locales/fi/LC_MESSAGES/volto.po @@ -4346,6 +4346,11 @@ msgstr "Milloin" msgid "event_where" msgstr "Missä" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: This website does not accept files larger than {limit} +msgid "fileTooLarge" +msgstr "" + #: helpers/MessageLabels/MessageLabels # defaultMessage: flush intIds and rebuild relations msgid "flush intIds and rebuild relations" diff --git a/locales/fr/LC_MESSAGES/volto.po b/locales/fr/LC_MESSAGES/volto.po index 8dcddb2bfc..f0f7181742 100644 --- a/locales/fr/LC_MESSAGES/volto.po +++ b/locales/fr/LC_MESSAGES/volto.po @@ -4352,6 +4352,11 @@ msgstr "Quand" msgid "event_where" msgstr "Où" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: This website does not accept files larger than {limit} +msgid "fileTooLarge" +msgstr "" + #: helpers/MessageLabels/MessageLabels # defaultMessage: flush intIds and rebuild relations msgid "flush intIds and rebuild relations" diff --git a/locales/it/LC_MESSAGES/volto.po b/locales/it/LC_MESSAGES/volto.po index 53e4e24b48..d14413e008 100644 --- a/locales/it/LC_MESSAGES/volto.po +++ b/locales/it/LC_MESSAGES/volto.po @@ -4335,6 +4335,11 @@ msgstr "Quando" msgid "event_where" msgstr "Dove" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: This website does not accept files larger than {limit} +msgid "fileTooLarge" +msgstr "" + #: helpers/MessageLabels/MessageLabels # defaultMessage: flush intIds and rebuild relations msgid "flush intIds and rebuild relations" diff --git a/locales/ja/LC_MESSAGES/volto.po b/locales/ja/LC_MESSAGES/volto.po index dac6b03dfc..ae6255d021 100644 --- a/locales/ja/LC_MESSAGES/volto.po +++ b/locales/ja/LC_MESSAGES/volto.po @@ -4343,6 +4343,11 @@ msgstr "日時" msgid "event_where" msgstr "場所" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: This website does not accept files larger than {limit} +msgid "fileTooLarge" +msgstr "" + #: helpers/MessageLabels/MessageLabels # defaultMessage: flush intIds and rebuild relations msgid "flush intIds and rebuild relations" diff --git a/locales/nl/LC_MESSAGES/volto.po b/locales/nl/LC_MESSAGES/volto.po index a4b6a427ba..9cd0501a95 100644 --- a/locales/nl/LC_MESSAGES/volto.po +++ b/locales/nl/LC_MESSAGES/volto.po @@ -4354,6 +4354,11 @@ msgstr "" msgid "event_where" msgstr "" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: This website does not accept files larger than {limit} +msgid "fileTooLarge" +msgstr "" + #: helpers/MessageLabels/MessageLabels # defaultMessage: flush intIds and rebuild relations msgid "flush intIds and rebuild relations" diff --git a/locales/pt/LC_MESSAGES/volto.po b/locales/pt/LC_MESSAGES/volto.po index a3a7558455..1bc50bc851 100644 --- a/locales/pt/LC_MESSAGES/volto.po +++ b/locales/pt/LC_MESSAGES/volto.po @@ -4343,6 +4343,11 @@ msgstr "" msgid "event_where" msgstr "" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: This website does not accept files larger than {limit} +msgid "fileTooLarge" +msgstr "" + #: helpers/MessageLabels/MessageLabels # defaultMessage: flush intIds and rebuild relations msgid "flush intIds and rebuild relations" diff --git a/locales/pt_BR/LC_MESSAGES/volto.po b/locales/pt_BR/LC_MESSAGES/volto.po index 191fd4d835..2538655cf8 100644 --- a/locales/pt_BR/LC_MESSAGES/volto.po +++ b/locales/pt_BR/LC_MESSAGES/volto.po @@ -4345,6 +4345,11 @@ msgstr "Quando" msgid "event_where" msgstr "Onde" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: This website does not accept files larger than {limit} +msgid "fileTooLarge" +msgstr "" + #: helpers/MessageLabels/MessageLabels # defaultMessage: flush intIds and rebuild relations msgid "flush intIds and rebuild relations" diff --git a/locales/ro/LC_MESSAGES/volto.po b/locales/ro/LC_MESSAGES/volto.po index 640fb85024..aed1a126d4 100644 --- a/locales/ro/LC_MESSAGES/volto.po +++ b/locales/ro/LC_MESSAGES/volto.po @@ -4335,6 +4335,11 @@ msgstr "Data" msgid "event_where" msgstr "Locație" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: This website does not accept files larger than {limit} +msgid "fileTooLarge" +msgstr "" + #: helpers/MessageLabels/MessageLabels # defaultMessage: flush intIds and rebuild relations msgid "flush intIds and rebuild relations" diff --git a/locales/volto.pot b/locales/volto.pot index 823dbbd84e..4e287384a8 100644 --- a/locales/volto.pot +++ b/locales/volto.pot @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Plone\n" -"POT-Creation-Date: 2023-06-09T09:09:49.024Z\n" +"POT-Creation-Date: 2023-06-13T20:41:58.672Z\n" "Last-Translator: Plone i18n \n" "Language-Team: Plone i18n \n" "MIME-Version: 1.0\n" @@ -39,7 +39,7 @@ msgid "Action changed" msgstr "" #: components/manage/Controlpanels/Rules/ConfigureRule -# defaultMessage: Action: +# defaultMessage: Action: msgid "Action: " msgstr "" @@ -686,7 +686,7 @@ msgid "Condition changed" msgstr "" #: components/manage/Controlpanels/Rules/ConfigureRule -# defaultMessage: Condition: +# defaultMessage: Condition: msgid "Condition: " msgstr "" @@ -4337,6 +4337,11 @@ msgstr "" msgid "event_where" msgstr "" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: This website does not accept files larger than {limit} +msgid "fileTooLarge" +msgstr "" + #: helpers/MessageLabels/MessageLabels # defaultMessage: flush intIds and rebuild relations msgid "flush intIds and rebuild relations" diff --git a/locales/zh_CN/LC_MESSAGES/volto.po b/locales/zh_CN/LC_MESSAGES/volto.po index 68a0a4828e..a96ef19ceb 100644 --- a/locales/zh_CN/LC_MESSAGES/volto.po +++ b/locales/zh_CN/LC_MESSAGES/volto.po @@ -4341,6 +4341,11 @@ msgstr "" msgid "event_where" msgstr "" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: This website does not accept files larger than {limit} +msgid "fileTooLarge" +msgstr "" + #: helpers/MessageLabels/MessageLabels # defaultMessage: flush intIds and rebuild relations msgid "flush intIds and rebuild relations" diff --git a/package.json b/package.json index f45ae9c1c5..d09b21cb0c 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ } ], "license": "MIT", - "version": "17.0.0-alpha.11", + "version": "17.0.0-alpha.13", "repository": { "type": "git", "url": "git@github.com:plone/volto.git" diff --git a/packages/volto-slate/package.json b/packages/volto-slate/package.json index fa4a4142e4..a558b0887b 100644 --- a/packages/volto-slate/package.json +++ b/packages/volto-slate/package.json @@ -1,6 +1,6 @@ { "name": "@plone/volto-slate", - "version": "17.0.0-alpha.11", + "version": "17.0.0-alpha.13", "description": "Slate.js integration with Volto", "main": "src/index.js", "author": "European Environment Agency: IDM2 A-Team", diff --git a/packages/volto-slate/src/blocks/Text/DefaultTextBlockEditor.jsx b/packages/volto-slate/src/blocks/Text/DefaultTextBlockEditor.jsx index bdd3f42926..4bdcf09756 100644 --- a/packages/volto-slate/src/blocks/Text/DefaultTextBlockEditor.jsx +++ b/packages/volto-slate/src/blocks/Text/DefaultTextBlockEditor.jsx @@ -6,7 +6,11 @@ import { defineMessages, useIntl } from 'react-intl'; import { useInView } from 'react-intersection-observer'; import { Dimmer, Loader, Message, Segment } from 'semantic-ui-react'; -import { flattenToAppURL, getBaseUrl } from '@plone/volto/helpers'; +import { + flattenToAppURL, + getBaseUrl, + validateFileUploadSize, +} from '@plone/volto/helpers'; import config from '@plone/volto/registry'; import { BlockDataForm, @@ -71,6 +75,7 @@ export const DefaultTextBlockEditor = (props) => { const { slate } = config.settings; const { textblockExtensions } = slate; const { value } = data; + const intl = useIntl(); // const [addNewBlockOpened, setAddNewBlockOpened] = React.useState(); const [showDropzone, setShowDropzone] = React.useState(false); @@ -106,6 +111,7 @@ export const DefaultTextBlockEditor = (props) => { files.forEach((file) => { const [mime] = file.type.split('/'); if (mime !== 'image') return; + if (!validateFileUploadSize(file, intl.formatMessage)) return; readAsDataURL(file).then((data) => { const fields = data.match(/^data:(.*);(.*),(.*)$/); @@ -127,7 +133,7 @@ export const DefaultTextBlockEditor = (props) => { }); setShowDropzone(false); }, - [pathname, uploadContent, block], + [pathname, uploadContent, block, intl.formatMessage], ); const { loaded, loading } = uploadRequest; @@ -178,7 +184,6 @@ export const DefaultTextBlockEditor = (props) => { instructions = formDescription; } - const intl = useIntl(); const placeholder = data.placeholder || formTitle || intl.formatMessage(messages.text); const schema = TextBlockSchema(data); diff --git a/packages/volto-slate/src/blocks/Text/extensions/withDeserializers.js b/packages/volto-slate/src/blocks/Text/extensions/withDeserializers.js index 3dab777f0e..f4f8eaf08d 100644 --- a/packages/volto-slate/src/blocks/Text/extensions/withDeserializers.js +++ b/packages/volto-slate/src/blocks/Text/extensions/withDeserializers.js @@ -1,7 +1,7 @@ import isUrl from 'is-url'; import imageExtensions from 'image-extensions'; import { blockTagDeserializer } from '@plone/volto-slate/editor/deserialize'; -import { getBaseUrl } from '@plone/volto/helpers'; +import { getBaseUrl, validateFileUploadSize } from '@plone/volto/helpers'; import { v4 as uuid } from 'uuid'; import { Transforms } from 'slate'; @@ -66,7 +66,9 @@ export const withDeserializers = (editor) => { ...editor.dataTransferHandlers, files: (files) => { const unprocessed = []; + const { intl } = editor.getBlockProps(); for (const file of files) { + if (!validateFileUploadSize(file, intl.formatMessage)) return; const reader = new FileReader(); const [mime] = file.type.split('/'); if (mime === 'image') { diff --git a/src/components/manage/Blocks/HeroImageLeft/Edit.jsx b/src/components/manage/Blocks/HeroImageLeft/Edit.jsx index a93014fd28..422769a376 100644 --- a/src/components/manage/Blocks/HeroImageLeft/Edit.jsx +++ b/src/components/manage/Blocks/HeroImageLeft/Edit.jsx @@ -14,7 +14,11 @@ import { defineMessages, injectIntl } from 'react-intl'; import cx from 'classnames'; import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable'; -import { flattenToAppURL, getBaseUrl } from '@plone/volto/helpers'; +import { + flattenToAppURL, + getBaseUrl, + validateFileUploadSize, +} from '@plone/volto/helpers'; import { createContent } from '@plone/volto/actions'; import { Icon, SidebarPortal, LinkMore } from '@plone/volto/components'; @@ -275,6 +279,7 @@ class EditComponent extends Component { */ onUploadImage({ target }) { const file = target.files[0]; + if (!validateFileUploadSize(file, this.props.intl.formatMessage)) return; this.setState({ uploading: true, }); diff --git a/src/components/manage/Blocks/Image/Edit.jsx b/src/components/manage/Blocks/Image/Edit.jsx index 63087dabf2..2e7548f19b 100644 --- a/src/components/manage/Blocks/Image/Edit.jsx +++ b/src/components/manage/Blocks/Image/Edit.jsx @@ -21,6 +21,7 @@ import { flattenToAppURL, getBaseUrl, isInternalURL, + validateFileUploadSize, } from '@plone/volto/helpers'; import imageBlockSVG from '@plone/volto/components/manage/Blocks/Image/block-image.svg'; @@ -125,6 +126,7 @@ class Edit extends Component { onUploadImage = (e) => { e.stopPropagation(); const file = e.target.files[0]; + if (!validateFileUploadSize(file, this.props.intl.formatMessage)) return; this.setState({ uploading: true, }); @@ -178,23 +180,25 @@ class Edit extends Component { * @param {array} files File objects * @returns {undefined} */ - onDrop = (file) => { - this.setState({ - uploading: true, - }); + onDrop = (files) => { + if (!validateFileUploadSize(files[0], this.props.intl.formatMessage)) { + this.setState({ dragging: false }); + return; + } + this.setState({ uploading: true }); - readAsDataURL(file[0]).then((data) => { + readAsDataURL(files[0]).then((data) => { const fields = data.match(/^data:(.*);(.*),(.*)$/); this.props.createContent( getBaseUrl(this.props.pathname), { '@type': 'Image', - title: file[0].name, + title: files[0].name, image: { data: fields[3], encoding: fields[2], 'content-type': fields[1], - filename: file[0].name, + filename: files[0].name, }, }, this.props.block, diff --git a/src/components/manage/Contents/Contents.jsx b/src/components/manage/Contents/Contents.jsx index 3b3097d387..e4289c8e9c 100644 --- a/src/components/manage/Contents/Contents.jsx +++ b/src/components/manage/Contents/Contents.jsx @@ -12,7 +12,7 @@ import { Link } from 'react-router-dom'; import { Button, Confirm, - Container, + Container as SemanticContainer, Divider, Dropdown, Menu, @@ -70,6 +70,7 @@ import { import { Helmet, getBaseUrl } from '@plone/volto/helpers'; import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable'; +import config from '@plone/volto/registry'; import backSVG from '@plone/volto/icons/back.svg'; import cutSVG from '@plone/volto/icons/cut.svg'; @@ -1177,6 +1178,9 @@ class Contents extends Component { (this.props.orderRequest?.loading && !this.props.orderRequest?.error) || (this.props.searchRequest?.loading && !this.props.searchRequest?.error); + const Container = + config.getComponent({ name: 'Container' }).component || SemanticContainer; + return this.props.token && this.props.objectActions?.length > 0 ? ( <> {folderContentsAction ? ( diff --git a/src/components/manage/Contents/ContentsUploadModal.jsx b/src/components/manage/Contents/ContentsUploadModal.jsx index 8733c05826..f393f3f118 100644 --- a/src/components/manage/Contents/ContentsUploadModal.jsx +++ b/src/components/manage/Contents/ContentsUploadModal.jsx @@ -25,6 +25,7 @@ import { readAsDataURL } from 'promise-file-reader'; import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; import { FormattedRelativeDate } from '@plone/volto/components'; import { createContent } from '@plone/volto/actions'; +import { validateFileUploadSize } from '@plone/volto/helpers'; const Dropzone = loadable(() => import('react-dropzone')); @@ -121,14 +122,18 @@ class ContentsUploadModal extends Component { * @returns {undefined} */ onDrop = async (files) => { + const validFiles = []; for (let i = 0; i < files.length; i++) { - await readAsDataURL(files[i]).then((data) => { - const fields = data.match(/^data:(.*);(.*),(.*)$/); - files[i].preview = fields[0]; - }); + if (validateFileUploadSize(files[i], this.props.intl.formatMessage)) { + await readAsDataURL(files[i]).then((data) => { + const fields = data.match(/^data:(.*);(.*),(.*)$/); + files[i].preview = fields[0]; + }); + validFiles.push(files[i]); + } } this.setState({ - files: concat(this.state.files, files), + files: concat(this.state.files, validFiles), }); }; diff --git a/src/components/manage/History/History.jsx b/src/components/manage/History/History.jsx index fb420bbadb..5fa61001a4 100644 --- a/src/components/manage/History/History.jsx +++ b/src/components/manage/History/History.jsx @@ -9,7 +9,13 @@ import { Helmet } from '@plone/volto/helpers'; import { Link } from 'react-router-dom'; import { connect } from 'react-redux'; import { compose } from 'redux'; -import { Container, Dropdown, Icon, Segment, Table } from 'semantic-ui-react'; +import { + Container as SemanticContainer, + Dropdown, + Icon, + Segment, + Table, +} from 'semantic-ui-react'; import { concat, map, reverse, find } from 'lodash'; import { Portal } from 'react-portal'; import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; @@ -24,6 +30,7 @@ import { } from '@plone/volto/components'; import { getHistory, revertHistory, listActions } from '@plone/volto/actions'; import { getBaseUrl } from '@plone/volto/helpers'; +import config from '@plone/volto/registry'; import backSVG from '@plone/volto/icons/back.svg'; @@ -147,6 +154,9 @@ class History extends Component { }); const entries = this.processHistoryEntries(); + const Container = + config.getComponent({ name: 'Container' }).component || SemanticContainer; + return !historyAction ? ( <> {this.props.token ? ( diff --git a/src/components/manage/Sharing/Sharing.jsx b/src/components/manage/Sharing/Sharing.jsx index 943f236ddb..047c706b93 100644 --- a/src/components/manage/Sharing/Sharing.jsx +++ b/src/components/manage/Sharing/Sharing.jsx @@ -14,7 +14,7 @@ import { Portal } from 'react-portal'; import { Button, Checkbox, - Container, + Container as SemanticContainer, Form, Icon as IconOld, Input, @@ -28,6 +28,7 @@ import { updateSharing, getSharing } from '@plone/volto/actions'; import { getBaseUrl } from '@plone/volto/helpers'; import { Icon, Toolbar, Toast } from '@plone/volto/components'; import { toast } from 'react-toastify'; +import config from '@plone/volto/registry'; import aheadSVG from '@plone/volto/icons/ahead.svg'; import clearSVG from '@plone/volto/icons/clear.svg'; @@ -288,6 +289,9 @@ class SharingComponent extends Component { * @returns {string} Markup for the component. */ render() { + const Container = + config.getComponent({ name: 'Container' }).component || SemanticContainer; + return ( diff --git a/src/components/manage/Toast/Toast.jsx b/src/components/manage/Toast/Toast.jsx index bd3421519e..96f4355656 100644 --- a/src/components/manage/Toast/Toast.jsx +++ b/src/components/manage/Toast/Toast.jsx @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { Icon } from '@plone/volto/components'; +import Icon from '@plone/volto/components/theme/Icon/Icon'; import successSVG from '@plone/volto/icons/ready.svg'; import infoSVG from '@plone/volto/icons/info.svg'; diff --git a/src/components/manage/Widgets/ColorPickerWidget.jsx b/src/components/manage/Widgets/ColorPickerWidget.jsx index 929c0095e7..97c88c99be 100644 --- a/src/components/manage/Widgets/ColorPickerWidget.jsx +++ b/src/components/manage/Widgets/ColorPickerWidget.jsx @@ -51,7 +51,12 @@ const ColorPickerWidget = (props) => { onClick={(e) => { e.preventDefault(); e.stopPropagation(); - onChange(id, color.name); + onChange( + id, + value === color.name + ? props.missing_value + : color.name, + ); }} active={value === color.name} circular diff --git a/src/components/manage/Widgets/FileWidget.jsx b/src/components/manage/Widgets/FileWidget.jsx index 046437c33e..34db736933 100644 --- a/src/components/manage/Widgets/FileWidget.jsx +++ b/src/components/manage/Widgets/FileWidget.jsx @@ -11,7 +11,7 @@ import { injectIntl } from 'react-intl'; import deleteSVG from '@plone/volto/icons/delete.svg'; import { Icon, FormFieldWrapper } from '@plone/volto/components'; import loadable from '@loadable/component'; -import { flattenToAppURL } from '@plone/volto/helpers'; +import { flattenToAppURL, validateFileUploadSize } from '@plone/volto/helpers'; import { defineMessages, useIntl } from 'react-intl'; const imageMimetypes = [ @@ -95,6 +95,7 @@ const FileWidget = (props) => { */ const onDrop = (files) => { const file = files[0]; + if (!validateFileUploadSize(file, intl.formatMessage)) return; readAsDataURL(file).then((data) => { const fields = data.match(/^data:(.*);(.*),(.*)$/); onChange(id, { diff --git a/src/components/theme/View/NewsItemView.jsx b/src/components/theme/View/NewsItemView.jsx index 0e16994429..fe3287233a 100644 --- a/src/components/theme/View/NewsItemView.jsx +++ b/src/components/theme/View/NewsItemView.jsx @@ -5,13 +5,14 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { Container, Image } from 'semantic-ui-react'; +import { Container as SemanticContainer, Image } from 'semantic-ui-react'; import { hasBlocksData, flattenToAppURL, flattenHTMLToAppURL, } from '@plone/volto/helpers'; import RenderBlocks from '@plone/volto/components/theme/View/RenderBlocks'; +import config from '@plone/volto/registry'; /** * NewsItemView view component class. @@ -19,11 +20,14 @@ import RenderBlocks from '@plone/volto/components/theme/View/RenderBlocks'; * @params {object} content Content object. * @returns {string} Markup of the component. */ -const NewsItemView = ({ content }) => - hasBlocksData(content) ? ( -
+const NewsItemView = ({ content }) => { + const Container = + config.getComponent({ name: 'Container' }).component || SemanticContainer; + + return hasBlocksData(content) ? ( + -
+
) : ( {content.title && ( @@ -57,6 +61,7 @@ const NewsItemView = ({ content }) => )} ); +}; /** * Property types. diff --git a/src/config/index.js b/src/config/index.js index 3496ae2083..5047b221eb 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -150,6 +150,7 @@ let config = { }, appExtras: [], maxResponseSize: 2000000000, // This is superagent default (200 mb) + maxFileUploadSize: null, serverConfig, storeExtenders: [], showTags: true, diff --git a/src/helpers/Extensions/withBlockSchemaEnhancer.js b/src/helpers/Extensions/withBlockSchemaEnhancer.js index dbd60aa252..ddd497aee7 100644 --- a/src/helpers/Extensions/withBlockSchemaEnhancer.js +++ b/src/helpers/Extensions/withBlockSchemaEnhancer.js @@ -1,6 +1,7 @@ import React from 'react'; import { defineMessages } from 'react-intl'; import { useIntl } from 'react-intl'; +import { find, isEmpty } from 'lodash'; import config from '@plone/volto/registry'; import { cloneDeepSchema } from '@plone/volto/helpers/Utils/Utils'; @@ -291,20 +292,23 @@ export const EMPTY_STYLES_SCHEMA = { }; /** - * Creates the `styles` field and fieldset in a schema + * Adds the `styles` field and 'styling' fieldset in a given schema */ export const addStyling = ({ schema, formData, intl }) => { - schema.fieldsets.push({ - id: 'styling', - title: intl.formatMessage(messages.styling), - fields: ['styles'], - }); + if (isEmpty(find(schema.fieldsets, { id: 'styling' }))) { + schema.fieldsets.push({ + id: 'styling', + title: intl.formatMessage(messages.styling), + fields: ['styles'], + }); + + schema.properties.styles = { + widget: 'object', + title: intl.formatMessage(messages.styling), + schema: cloneDeepSchema(EMPTY_STYLES_SCHEMA), + }; + } - schema.properties.styles = { - widget: 'object', - title: intl.formatMessage(messages.styling), - schema: EMPTY_STYLES_SCHEMA, - }; return schema; }; diff --git a/src/helpers/Extensions/withBlockSchemaEnhancer.test.js b/src/helpers/Extensions/withBlockSchemaEnhancer.test.js index e02a533d16..d5e29f4105 100644 --- a/src/helpers/Extensions/withBlockSchemaEnhancer.test.js +++ b/src/helpers/Extensions/withBlockSchemaEnhancer.test.js @@ -2,6 +2,7 @@ import { addExtensionFieldToSchema, applySchemaEnhancer, composeSchema, + addStyling, } from './withBlockSchemaEnhancer'; import config from '@plone/volto/registry'; @@ -246,3 +247,147 @@ describe('composeSchema', () => { expect(res).toStrictEqual([6, 9]); }); }); + +describe('addStyling', () => { + it('returns an enhanced schema with the styling wrapper object on it', () => { + const intl = { formatMessage: () => 'Styling' }; + + const schema = { + fieldsets: [ + { + id: 'default', + title: 'Default', + fields: [], + }, + ], + properties: {}, + required: [], + }; + + const result = addStyling({ schema, intl }); + + expect(result).toStrictEqual({ + fieldsets: [ + { id: 'default', title: 'Default', fields: [] }, + { id: 'styling', title: 'Styling', fields: ['styles'] }, + ], + properties: { + styles: { + widget: 'object', + title: 'Styling', + schema: { + fieldsets: [ + { + fields: [], + id: 'default', + title: 'Default', + }, + ], + properties: {}, + required: [], + }, + }, + }, + required: [], + }); + }); + + it('multiple schema enhancers', () => { + const intl = { formatMessage: () => 'Styling' }; + + const schema1 = { + fieldsets: [ + { + id: 'default', + title: 'Default', + fields: [], + }, + ], + properties: {}, + required: [], + }; + + const schema2 = { + fieldsets: [ + { + id: 'default', + title: 'Default', + fields: [], + }, + ], + properties: {}, + required: [], + }; + + const result = addStyling({ schema: schema1, intl }); + + // We add some fields to the styling schema + result.properties.styles.schema.properties.align = { + widget: 'align', + title: 'align', + actions: ['left', 'right', 'center'], + default: 'left', + }; + + result.properties.styles.schema.fieldsets[0].fields = ['align']; + + const result2 = addStyling({ schema: schema2, intl }); + + expect(result).toStrictEqual({ + fieldsets: [ + { id: 'default', title: 'Default', fields: [] }, + { id: 'styling', title: 'Styling', fields: ['styles'] }, + ], + properties: { + styles: { + widget: 'object', + title: 'Styling', + schema: { + fieldsets: [ + { + fields: ['align'], + id: 'default', + title: 'Default', + }, + ], + properties: { + align: { + widget: 'align', + title: 'align', + actions: ['left', 'right', 'center'], + default: 'left', + }, + }, + required: [], + }, + }, + }, + required: [], + }); + + expect(result2).toStrictEqual({ + fieldsets: [ + { id: 'default', title: 'Default', fields: [] }, + { id: 'styling', title: 'Styling', fields: ['styles'] }, + ], + properties: { + styles: { + widget: 'object', + title: 'Styling', + schema: { + fieldsets: [ + { + fields: [], + id: 'default', + title: 'Default', + }, + ], + properties: {}, + required: [], + }, + }, + }, + required: [], + }); + }); +}); diff --git a/src/helpers/FormValidation/FormValidation.js b/src/helpers/FormValidation/FormValidation.js index 1f34ca05d5..52941afa75 100644 --- a/src/helpers/FormValidation/FormValidation.js +++ b/src/helpers/FormValidation/FormValidation.js @@ -1,5 +1,8 @@ import { map, uniq, keys, intersection, isEmpty } from 'lodash'; import { messages } from '../MessageLabels/MessageLabels'; +import config from '@plone/volto/registry'; +import { toast } from 'react-toastify'; +import Toast from '@plone/volto/components/manage/Toast/Toast'; /** * Will return the intl message if invalid @@ -369,3 +372,29 @@ class FormValidation { } export default FormValidation; + +/** + * Check if a file upload is within the maximum size limit. + * @param {File} file + * @param {Function} intlFunc + * @returns {Boolean} + */ +export const validateFileUploadSize = (file, intlFunc) => { + const isValid = + !config.settings.maxFileUploadSize || + file.size <= config.settings.maxFileUploadSize; + if (!isValid) { + toast.error( + , + ); + } + return isValid; +}; diff --git a/src/helpers/MessageLabels/MessageLabels.js b/src/helpers/MessageLabels/MessageLabels.js index 69c8d3cfe9..a341f329b7 100644 --- a/src/helpers/MessageLabels/MessageLabels.js +++ b/src/helpers/MessageLabels/MessageLabels.js @@ -332,4 +332,8 @@ export const messages = defineMessages({ id: 'Filter', defaultMessage: 'Filter', }, + fileTooLarge: { + id: 'fileTooLarge', + defaultMessage: 'This website does not accept files larger than {limit}', + }, }); diff --git a/src/helpers/index.js b/src/helpers/index.js index 937b6d2940..89092e61b1 100644 --- a/src/helpers/index.js +++ b/src/helpers/index.js @@ -72,7 +72,9 @@ export { export langmap from './LanguageMap/LanguageMap'; export Helmet from './Helmet/Helmet'; -export FormValidation from './FormValidation/FormValidation'; +export FormValidation, { + validateFileUploadSize, +} from './FormValidation/FormValidation'; export { difference, getColor, diff --git a/theme/themes/pastanaga/extras/contents.less b/theme/themes/pastanaga/extras/contents.less index 149384714a..c4cea98911 100644 --- a/theme/themes/pastanaga/extras/contents.less +++ b/theme/themes/pastanaga/extras/contents.less @@ -94,6 +94,7 @@ .contents-table-wrapper { width: 100%; + overflow-x: auto; .ui.attached.table { width: 100%;