From d057fd6401e88a0616f3f27ffe21957b93db5f4f Mon Sep 17 00:00:00 2001 From: Steffen Lindner Date: Tue, 6 Jun 2023 20:46:36 +0200 Subject: [PATCH 01/19] Convert header class to function (#4767) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Katja Süss Co-authored-by: Victor Fernandez de Alba --- news/4767.bugfix | 1 + src/components/theme/Header/Header.jsx | 100 +++++++----------- src/components/theme/Header/Header.md | 27 ----- src/components/theme/Header/Header.test.jsx | 18 ++++ .../Header/__snapshots__/Header.test.jsx.snap | 44 ++++++++ src/hooks/userSession/useToken.js | 5 + 6 files changed, 105 insertions(+), 90 deletions(-) create mode 100644 news/4767.bugfix delete mode 100644 src/components/theme/Header/Header.md create mode 100644 src/hooks/userSession/useToken.js diff --git a/news/4767.bugfix b/news/4767.bugfix new file mode 100644 index 0000000000..d0e63b937f --- /dev/null +++ b/news/4767.bugfix @@ -0,0 +1 @@ +Convert header class to function. @gomez diff --git a/src/components/theme/Header/Header.jsx b/src/components/theme/Header/Header.jsx index 491f92d2ca..2cda868be9 100644 --- a/src/components/theme/Header/Header.jsx +++ b/src/components/theme/Header/Header.jsx @@ -1,12 +1,6 @@ -/** - * Header component. - * @module components/theme/Header/Header - */ - -import React, { Component } from 'react'; import { Container, Segment } from 'semantic-ui-react'; import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; +import { useToken } from '@plone/volto/hooks/userSession/useToken'; import { Anontools, @@ -16,65 +10,45 @@ import { SearchWidget, } from '@plone/volto/components'; -/** - * Header component class. - * @class Header - * @extends Component - */ -class Header extends Component { - /** - * Property types. - * @property {Object} propTypes Property types. - * @static - */ - static propTypes = { - token: PropTypes.string, - pathname: PropTypes.string.isRequired, - }; - - /** - * Default properties. - * @property {Object} defaultProps Default properties. - * @static - */ - static defaultProps = { - token: null, - }; +const Header = ({ pathname }) => { + const token = useToken(); - /** - * Render method. - * @method render - * @returns {string} Markup for the component. - */ - render() { - return ( - - -
-
-
- -
- + return ( + + +
+
+
+
-
- - {!this.props.token && ( -
- -
- )} -
- + +
+
+ + {!token && ( +
+
+ )} +
+
- - - ); - } -} +
+ + + ); +}; + +export default Header; + +Header.propTypes = { + token: PropTypes.string, + pathname: PropTypes.string.isRequired, + content: PropTypes.objectOf(PropTypes.any), +}; -export default connect((state) => ({ - token: state.userSession.token, -}))(Header); +Header.defaultProps = { + token: null, + content: null, +}; diff --git a/src/components/theme/Header/Header.md b/src/components/theme/Header/Header.md deleted file mode 100644 index b097be635d..0000000000 --- a/src/components/theme/Header/Header.md +++ /dev/null @@ -1,27 +0,0 @@ -Header example: - -```jsx static -
-``` - -Output: - -```jsx noeditor -const { Provider } = require('react-intl-redux'); -const configureStore = require('redux-mock-store').default; -const store = configureStore()({ - userSession: { - login: {}, - }, - intl: { - locale: 'en', - messages: {}, - }, -}); - -
- -
- -
; -``` diff --git a/src/components/theme/Header/Header.test.jsx b/src/components/theme/Header/Header.test.jsx index 8002a8cbba..4144f673bc 100644 --- a/src/components/theme/Header/Header.test.jsx +++ b/src/components/theme/Header/Header.test.jsx @@ -39,4 +39,22 @@ describe('Header', () => { const json = component.toJSON(); expect(json).toMatchSnapshot(); }); + + it('renders a header component - auth', () => { + const store = mockStore({ + userSession: { token: '1234567890' }, + intl: { + locale: 'en', + messages: {}, + }, + }); + + const component = renderer.create( + +
+ , + ); + const json = component.toJSON(); + expect(json).toMatchSnapshot(); + }); }); diff --git a/src/components/theme/Header/__snapshots__/Header.test.jsx.snap b/src/components/theme/Header/__snapshots__/Header.test.jsx.snap index 5cd7084418..43c47d0a58 100644 --- a/src/components/theme/Header/__snapshots__/Header.test.jsx.snap +++ b/src/components/theme/Header/__snapshots__/Header.test.jsx.snap @@ -1,5 +1,49 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Header renders a header component - auth 1`] = ` +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+`; + exports[`Header renders a header component 1`] = `
state.userSession.token, shallowEqual); +} From c0bec37581dc6827555128dee44cfada24932883 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89rico=20Andrei?= Date: Wed, 7 Jun 2023 12:58:56 -0300 Subject: [PATCH 02/19] Update Brazilian Portuguese translations (Fixes #4853) --- locales/pt_BR/LC_MESSAGES/volto.po | 86 +++++++++++++++--------------- news/4853.feature | 1 + 2 files changed, 44 insertions(+), 43 deletions(-) create mode 100644 news/4853.feature diff --git a/locales/pt_BR/LC_MESSAGES/volto.po b/locales/pt_BR/LC_MESSAGES/volto.po index dc69005027..899a0fa7e4 100644 --- a/locales/pt_BR/LC_MESSAGES/volto.po +++ b/locales/pt_BR/LC_MESSAGES/volto.po @@ -281,12 +281,12 @@ msgstr "Complemento atualizado com sucesso" #: components/manage/Blocks/Search/schema # defaultMessage: Advanced facet? msgid "Advanced facet?" -msgstr "" +msgstr "Facetas avançadas?" #: components/manage/Blocks/Search/schema # defaultMessage: Advanced facets are initially hidden and displayed on demand msgid "Advanced facets are initially hidden and displayed on demand" -msgstr "" +msgstr "Facetas avançadas ficam ocultas inicialmente e exibidas sob demanda" #: config/Views # defaultMessage: Album view @@ -478,7 +478,7 @@ msgstr "Navegação estrutural" #: components/manage/Controlpanels/Relations/BrokenRelations # defaultMessage: Broken relations msgid "Broken relations" -msgstr "" +msgstr "Relacionamentos rompidos" #: components/manage/Blocks/HeroImageLeft/Edit #: components/manage/Contents/ContentsUploadModal @@ -801,7 +801,7 @@ msgstr "Declaração de copyright ou outras informações de direitos sobre este #: helpers/MessageLabels/MessageLabels # defaultMessage: Create or delete relations to target msgid "Create or delete relations to target" -msgstr "" +msgstr "Crie ou remova relacionamentos para o destino" #: components/manage/Toolbar/More # defaultMessage: Create working copy @@ -1425,7 +1425,7 @@ msgstr "Nome do arquivo" #: helpers/MessageLabels/MessageLabels # defaultMessage: Filter msgid "Filter" -msgstr "" +msgstr "Filtrar" #: components/manage/Controlpanels/Rules/Rules # defaultMessage: Filter Rules: @@ -1455,7 +1455,7 @@ msgstr "Primeiro" #: helpers/MessageLabels/MessageLabels # defaultMessage: Fix relations msgid "Fix relations" -msgstr "" +msgstr "Consertar relacionamentos" #: components/manage/Blocks/Table/Edit # defaultMessage: Fixed width columns @@ -1596,7 +1596,7 @@ msgstr "Ocultar faceta?" #: components/manage/Blocks/Search/components/Facets # defaultMessage: Hide filters msgid "Hide filters" -msgstr "" +msgstr "Ocultar filtros" #: components/manage/History/History #: components/manage/Toolbar/More @@ -1669,7 +1669,7 @@ msgstr "Galeria de imagem" #: components/manage/Blocks/Teaser/schema # defaultMessage: Image override msgid "Image override" -msgstr "" +msgstr "Substituir imagem" #: components/manage/Blocks/Image/schema # defaultMessage: Image size @@ -1732,7 +1732,7 @@ msgstr "Inserir linha antes" #: helpers/MessageLabels/MessageLabels # defaultMessage: Inspect relations msgid "Inspect relations" -msgstr "" +msgstr "Inspecionar relacionamentos" #: components/manage/Controlpanels/AddonsControlpanel # defaultMessage: Install @@ -1897,7 +1897,7 @@ msgstr "Esquerda" #: components/manage/Blocks/Search/components/Facets # defaultMessage: Less filters msgid "Less filters" -msgstr "" +msgstr "Menos filtros" #: components/manage/Toolbar/Toolbar # defaultMessage: Link @@ -2029,7 +2029,7 @@ msgstr "Adicionado manual ou automaticamente?" #: helpers/MessageLabels/MessageLabels # defaultMessage: Many relations found. Please search. msgid "Many relations found. Please search." -msgstr "" +msgstr "Muitos relacionamentos foram encontrados. Por favor realize uma busca" #: components/manage/Blocks/Maps/MapsSidebar #: components/manage/Blocks/Maps/schema @@ -2060,7 +2060,7 @@ msgstr "Médio" #: helpers/MessageLabels/MessageLabels # defaultMessage: Membership updated msgid "Membership updated" -msgstr "" +msgstr "Participação atualizada" #: components/theme/ContactForm/ContactForm # defaultMessage: Message @@ -2111,7 +2111,7 @@ msgstr "Mais" #: components/manage/Blocks/Search/components/Facets # defaultMessage: More filters msgid "More filters" -msgstr "" +msgstr "Mais filtros" #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: More information about the upgrade procedure can be found in the documentation section of plone.org in the Upgrade Guide. @@ -2230,7 +2230,7 @@ msgstr "Nenhum complemento encontrado" #: components/manage/Controlpanels/Relations/RelationsMatrix # defaultMessage: No broken relations found. msgid "No broken relations found." -msgstr "" +msgstr "Nenhum relacionamento rompido foi encontrado" #: components/theme/RequestTimeout/RequestTimeout # defaultMessage: There is no connection to the server, due to a timeout o no network connection. @@ -2255,7 +2255,7 @@ msgstr "Nenhuma imagem definida no campo de imagem" #: components/manage/Blocks/Listing/GalleryNoResultsComponent # defaultMessage: No images found. msgid "No images found." -msgstr "" +msgstr "Nenhuma imagem foi encontrada" #: components/manage/Blocks/Listing/ListingBody # defaultMessage: No items found in this container. @@ -2288,7 +2288,7 @@ msgstr "Sem opções" #: helpers/MessageLabels/MessageLabels # defaultMessage: No relation found msgid "No relation found" -msgstr "" +msgstr "Nenhum relacionamento foi encontrado" #: components/manage/BlockChooser/BlockChooser #: components/theme/Search/Search @@ -2473,7 +2473,7 @@ msgstr "Pessoas responsáveis pela criação do conteúdo deste item. Por favor, #: components/manage/Blocks/Teaser/DefaultBody # defaultMessage: Please choose an existing content as source for this element msgid "Please choose an existing content as source for this element" -msgstr "" +msgstr "Por favor selecione um conteúdo existente como fonte para este elemento" #: components/manage/Controlpanels/Controlpanels # defaultMessage: Please continue with the upgrade. @@ -2513,7 +2513,7 @@ msgstr "Por favor, atualize para plone.restapi >= 8.24.0." #: components/manage/Controlpanels/Relations/Relations # defaultMessage: Please upgrade to plone.restapi >= 8.35.3. msgid "Please upgrade to plone.restapi >= 8.35.3." -msgstr "" +msgstr "Por favor atualize a plone.restapi para versão 8.35.3 ou superior." #: components/theme/Footer/Footer # defaultMessage: Plone Foundation @@ -2612,12 +2612,12 @@ msgstr "Leia Mais…" #: components/manage/Controlpanels/Relations/RelationsListing # defaultMessage: Read only for this type of relation. msgid "Read only for this type of relation." -msgstr "" +msgstr "Apenas leitura para este tipo de relacionamento." #: components/manage/Contents/Contents # defaultMessage: Rearrange items by… msgid "Rearrange items by…" -msgstr "Reorganize itens por…" +msgstr "Reorganizar itens por…" #: components/manage/Widgets/RecurrenceWidget/EndField # defaultMessage: Ends @@ -2660,24 +2660,24 @@ msgstr "Formulário de cadastro" #: helpers/MessageLabels/MessageLabels # defaultMessage: Relation msgid "Relation name" -msgstr "" +msgstr "Nome do relacionamento" #: components/manage/Controlpanels/Controlpanels #: components/manage/Controlpanels/Relations/Relations #: helpers/MessageLabels/MessageLabels # defaultMessage: Relations msgid "Relations" -msgstr "" +msgstr "Relacionamentos" #: components/manage/Controlpanels/Relations/RelationsListing # defaultMessage: Relations are editable with plone.api >= 2.0.3. msgid "Relations are editable with plone.api >= 2.0.3." -msgstr "" +msgstr "Relacionamentos são editáveis com uso da plone.api versão 2.0.3 ou superior." #: helpers/MessageLabels/MessageLabels # defaultMessage: Relations updated msgid "Relations updated" -msgstr "" +msgstr "Relacionamentos atualizados" #: components/theme/Search/Search # defaultMessage: Relevance @@ -2781,7 +2781,7 @@ msgstr "Redefinir o título do termo" #: components/manage/Blocks/Teaser/Data # defaultMessage: Reset the block msgid "Reset the block" -msgstr "" +msgstr "Redefinir o bloco" #: components/manage/Widgets/QuerystringWidget # defaultMessage: Results limit @@ -2890,7 +2890,7 @@ msgstr "Salvo" #: components/manage/Contents/ContentsItem # defaultMessage: Scheduled msgid "Scheduled" -msgstr "" +msgstr "Agendado" #: components/manage/Controlpanels/ContentTypesActions # defaultMessage: Schema @@ -2968,12 +2968,12 @@ msgstr "Resultados da pesquisa para {term}" #: helpers/MessageLabels/MessageLabels # defaultMessage: Search sources by title or path msgid "Search sources by title or path" -msgstr "" +msgstr "Pesquise fontes por título ou caminho" #: helpers/MessageLabels/MessageLabels # defaultMessage: Search targets by title or path msgid "Search targets by title or path" -msgstr "" +msgstr "Pesquise destinos por título ou caminho" #: helpers/MessageLabels/MessageLabels # defaultMessage: Search users… @@ -3016,7 +3016,7 @@ msgstr "Selecione colunas para mostrar" #: helpers/MessageLabels/MessageLabels # defaultMessage: Select relation msgid "Select relation" -msgstr "" +msgstr "Selecione um relacionamento" #: components/manage/Contents/ContentsWorkflowModal # defaultMessage: Select the transition to be used for modifying the items state. @@ -3026,7 +3026,7 @@ msgstr "Selecione a transição a ser usada para modificar o estado dos itens." #: helpers/MessageLabels/MessageLabels # defaultMessage: Selected msgid "Selected" -msgstr "" +msgstr "Selecionado" #: components/manage/Widgets/RecurrenceWidget/Occurences # defaultMessage: Selected dates @@ -3125,7 +3125,7 @@ msgstr "Mostrar respostas" #: components/manage/Blocks/Search/components/Facets # defaultMessage: Show filters msgid "Show filters" -msgstr "" +msgstr "Exibir filtros" #: helpers/MessageLabels/MessageLabels # defaultMessage: Show groups of users below @@ -3140,12 +3140,12 @@ msgstr "Mostrar item" #: components/manage/Controlpanels/Relations/RelationsMatrix # defaultMessage: Show potential sources. Not only objects that are source of some relation. msgid "Show potential sources. Not only objects that are source of some relation." -msgstr "" +msgstr "Exibir potenciais fontes. Não apenas os objetos que já são fonte de algum relacionamento." #: components/manage/Controlpanels/Relations/RelationsMatrix # defaultMessage: Show potential targets. Not only objects that are target of some relation. msgid "Show potential targets. Not only objects that are target of some relation." -msgstr "" +msgstr "Exibir potenciais destinos. Não apenas os objetos que já são destino de algum relacionamento." #: components/manage/Blocks/Search/schema # defaultMessage: Show search button? @@ -3217,7 +3217,7 @@ msgstr "Pequeno" #: components/manage/Controlpanels/Relations/Relations # defaultMessage: Some relations are broken. Please fix. msgid "Some relations are broken. Please fix." -msgstr "" +msgstr "Alguns relacionamentos estão rompidos. Por favor corrija-os." #: error # defaultMessage: Sorry, something went wrong with your request @@ -3430,7 +3430,7 @@ msgstr "O caminho da URL de destino deve começar com uma barra." #: components/manage/Blocks/Teaser/schema # defaultMessage: Teaser msgid "Teaser" -msgstr "" +msgstr "Destaque" #: components/manage/Widgets/SchemaWidget # defaultMessage: Text @@ -3626,7 +3626,7 @@ msgstr "Total de comentários" #: components/manage/Contents/Contents # defaultMessage: Total items to be deleted: msgid "Total items to be deleted:" -msgstr "" +msgstr "Total de itens a serem removidos:" #: components/manage/Controlpanels/DatabaseInformation # defaultMessage: Total number of objects in each cache @@ -4151,7 +4151,7 @@ msgstr "Você foi desconectado do site." #: components/manage/Controlpanels/Relations/Relations # defaultMessage: You have not the required permission for this control panel. msgid "You have not the required permission for this control panel." -msgstr "" +msgstr "Você não possui a permissão necessária para acessar esse painel de controle." #: components/theme/PasswordReset/RequestPasswordReset # defaultMessage: Your email is required for reset your password. @@ -4307,12 +4307,12 @@ msgstr "Onde" #: helpers/MessageLabels/MessageLabels # defaultMessage: flush intIds and rebuild relations msgid "flush intIds and rebuild relations" -msgstr "" +msgstr "descarrega os intIds e recontrua os relacionamentos" #: components/manage/Blocks/Teaser/schema # defaultMessage: Head title msgid "head_title" -msgstr "" +msgstr "Título principal" #: components/theme/PasswordReset/RequestPasswordReset # defaultMessage: Password reset confirmation sent @@ -4413,7 +4413,7 @@ msgstr "Mais usados" #: components/manage/Controlpanels/Relations/RelationsListing # defaultMessage: Found {sources} sources and {targets} targets. Narrow down to {max}! msgid "narrowDownRelations" -msgstr "" +msgstr "Encontradas {sources} fontes e {targets} destinos. Por favor, reduza ao máximo de {max}!" #: components/manage/Blocks/Search/components/DateRangeFacetFilterListEntry #: components/manage/Blocks/Search/components/ToggleFacetFilterListEntry @@ -4471,7 +4471,7 @@ msgstr "Selecionar" #: helpers/MessageLabels/MessageLabels # defaultMessage: rebuild relations msgid "rebuild relations" -msgstr "" +msgstr "reconstruir relacionamentos" #: components/theme/Search/Search # defaultMessage: results @@ -4676,7 +4676,7 @@ msgstr "ordenar" #: helpers/MessageLabels/MessageLabels # defaultMessage: sources path msgid "sources path" -msgstr "" +msgstr "caminho da fonte" #: config/Blocks # defaultMessage: Table @@ -4686,7 +4686,7 @@ msgstr "Tabela" #: helpers/MessageLabels/MessageLabels # defaultMessage: target path msgid "target path" -msgstr "" +msgstr "caminho do destino" #: config/Blocks # defaultMessage: Text diff --git a/news/4853.feature b/news/4853.feature new file mode 100644 index 0000000000..9bc839656a --- /dev/null +++ b/news/4853.feature @@ -0,0 +1 @@ +Update Brazilian Portuguese translations @ericof From 8d7459726446f4b774e7fa740171c877cd7e3bd6 Mon Sep 17 00:00:00 2001 From: Miu Razvan Date: Fri, 9 Jun 2023 13:23:12 +0300 Subject: [PATCH 03/19] feat(slate): Add css identifier to slate style menu options (#4847) Co-authored-by: Steve Piercy --- news/4846.feature | 1 + .../src/editor/plugins/StyleMenu/StyleMenu.jsx | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 news/4846.feature diff --git a/news/4846.feature b/news/4846.feature new file mode 100644 index 0000000000..90574e05fe --- /dev/null +++ b/news/4846.feature @@ -0,0 +1 @@ +Added a CSS identifier to the Slate style menu options. @razvanMiu \ No newline at end of file diff --git a/packages/volto-slate/src/editor/plugins/StyleMenu/StyleMenu.jsx b/packages/volto-slate/src/editor/plugins/StyleMenu/StyleMenu.jsx index af37a9f5f0..2771759873 100644 --- a/packages/volto-slate/src/editor/plugins/StyleMenu/StyleMenu.jsx +++ b/packages/volto-slate/src/editor/plugins/StyleMenu/StyleMenu.jsx @@ -28,13 +28,13 @@ const StyleMenuButton = ({ icon, active, ...props }) => ( ); -const MenuOpts = ({ editor, toSelect, option, ...rest }) => { +const MenuOpts = ({ editor, toSelect, option, type }) => { const isActive = toSelect.includes(option); return ( { @@ -118,7 +118,12 @@ const StylingsButton = (props) => { content={intl.formatMessage(messages.inlineStyle)} /> {inlineOpts.map((option, index) => ( - + ))} )} @@ -129,7 +134,12 @@ const StylingsButton = (props) => { content={intl.formatMessage(messages.paragraphStyle)} /> {blockOpts.map((option, index) => ( - + ))} )} From 3b4c31d01a557f7bcc12a3c9a19de2d372e4bb99 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Fri, 9 Jun 2023 12:45:55 +0200 Subject: [PATCH 04/19] Release 17.0.0-alpha.10 --- CHANGELOG.md | 17 +++++++++++++++++ news/4767.bugfix | 1 - news/4783.feature | 2 -- news/4801.feature | 1 - news/4841.bugfix | 1 - news/4846.feature | 1 - news/4849.feature | 1 - news/4853.feature | 1 - package.json | 2 +- packages/volto-slate/package.json | 2 +- 10 files changed, 19 insertions(+), 10 deletions(-) delete mode 100644 news/4767.bugfix delete mode 100644 news/4783.feature delete mode 100644 news/4801.feature delete mode 100644 news/4841.bugfix delete mode 100644 news/4846.feature delete mode 100644 news/4849.feature delete mode 100644 news/4853.feature diff --git a/CHANGELOG.md b/CHANGELOG.md index e7c8d246e5..32dc0f0731 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,23 @@ +## 17.0.0-alpha.10 (2023-06-09) + +### Feature + +- Search Block: Add support for advanced facets that are only displayed on demand. + [pbauer, razvanMiu, claudiaifrim] [#4783](https://github.com/plone/volto/issues/4783) +- Display PAS validation errors. [tschorr] [#4801](https://github.com/plone/volto/issues/4801) +- Added a CSS identifier to the Slate style menu options. @razvanMiu [#4846](https://github.com/plone/volto/issues/4846) +- Use a Container from the registry in the Form component and fallback to the Semantic UI one. @sneridagh [#4849](https://github.com/plone/volto/issues/4849) +- Update Brazilian Portuguese translations @ericof [#4853](https://github.com/plone/volto/issues/4853) + +### Bugfix + +- Convert header class to function. @gomez [#4767](https://github.com/plone/volto/issues/4767) +- Do not break validation on required number field with value 0 @cekk [#4841](https://github.com/plone/volto/issues/4841) + + ## 17.0.0-alpha.9 (2023-06-01) ### Bugfix diff --git a/news/4767.bugfix b/news/4767.bugfix deleted file mode 100644 index d0e63b937f..0000000000 --- a/news/4767.bugfix +++ /dev/null @@ -1 +0,0 @@ -Convert header class to function. @gomez diff --git a/news/4783.feature b/news/4783.feature deleted file mode 100644 index 531d971133..0000000000 --- a/news/4783.feature +++ /dev/null @@ -1,2 +0,0 @@ -Search Block: Add support for advanced facets that are only displayed on demand. -[pbauer, razvanMiu, claudiaifrim] diff --git a/news/4801.feature b/news/4801.feature deleted file mode 100644 index 6d0b273792..0000000000 --- a/news/4801.feature +++ /dev/null @@ -1 +0,0 @@ -Display PAS validation errors. [tschorr] diff --git a/news/4841.bugfix b/news/4841.bugfix deleted file mode 100644 index 65cdbba77d..0000000000 --- a/news/4841.bugfix +++ /dev/null @@ -1 +0,0 @@ -Do not break validation on required number field with value 0 @cekk diff --git a/news/4846.feature b/news/4846.feature deleted file mode 100644 index 90574e05fe..0000000000 --- a/news/4846.feature +++ /dev/null @@ -1 +0,0 @@ -Added a CSS identifier to the Slate style menu options. @razvanMiu \ No newline at end of file diff --git a/news/4849.feature b/news/4849.feature deleted file mode 100644 index 7eab003a6b..0000000000 --- a/news/4849.feature +++ /dev/null @@ -1 +0,0 @@ -Use a Container from the registry in the Form component and fallback to the Semantic UI one. @sneridagh diff --git a/news/4853.feature b/news/4853.feature deleted file mode 100644 index 9bc839656a..0000000000 --- a/news/4853.feature +++ /dev/null @@ -1 +0,0 @@ -Update Brazilian Portuguese translations @ericof diff --git a/package.json b/package.json index 4dc9c19184..ad387925ca 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ } ], "license": "MIT", - "version": "17.0.0-alpha.9", + "version": "17.0.0-alpha.10", "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 5cffb00466..61c8c193cf 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.9", + "version": "17.0.0-alpha.10", "description": "Slate.js integration with Volto", "main": "src/index.js", "author": "European Environment Agency: IDM2 A-Team", From b025c50cb7eacd9d22abbabf0a1255273408a516 Mon Sep 17 00:00:00 2001 From: Mikel Larreategi Date: Fri, 9 Jun 2023 12:46:20 +0200 Subject: [PATCH 05/19] Pagination with router params (#4698) Co-authored-by: bipoza Co-authored-by: ionlizarazu Co-authored-by: Unai --- cypress/support/commands.js | 20 + cypress/tests/core/blocks/blocks-listing.js | 384 ++++++++++++++++++ news/4159.bugfix | 1 + .../Blocks/Listing/ListingBody.test.jsx | 20 + .../__snapshots__/ListingBody.test.jsx.snap | 2 +- .../Blocks/Listing/withQuerystringResults.jsx | 12 +- .../manage/Blocks/Search/hocs/withSearch.jsx | 12 +- src/helpers/Utils/usePagination.js | 86 +++- src/helpers/Utils/usePagination.test.js | 115 ++++++ 9 files changed, 629 insertions(+), 23 deletions(-) create mode 100644 news/4159.bugfix create mode 100644 src/helpers/Utils/usePagination.test.js diff --git a/cypress/support/commands.js b/cypress/support/commands.js index cd2e6f23d4..70bd7683a8 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -849,3 +849,23 @@ Cypress.Commands.add('getTableSlate', (header = false) => { ); return slate; }); + +Cypress.Commands.add('configureListingWith', (contentType) => { + cy.get('.sidebar-container .tabs-wrapper .menu .item') + .contains('Block') + .click(); + cy.get('.querystring-widget .fields').contains('Add criteria').click(); + cy.get( + '.querystring-widget .fields:first-of-type .field:first-of-type .react-select__menu .react-select__option', + ) + .contains('Type') + .click(); + + //insert Page + cy.get('.querystring-widget .fields:first-of-type > .field').click(); + cy.get( + '.querystring-widget .fields:first-of-type > .field .react-select__menu .react-select__option', + ) + .contains(contentType) + .click(); +}); diff --git a/cypress/tests/core/blocks/blocks-listing.js b/cypress/tests/core/blocks/blocks-listing.js index bea65dac47..288909dc79 100644 --- a/cypress/tests/core/blocks/blocks-listing.js +++ b/cypress/tests/core/blocks/blocks-listing.js @@ -21,6 +21,42 @@ describe('Listing Block Tests', () => { cy.waitForResourceToLoad(''); }); + it('Add Listing block with no results', () => { + cy.intercept('PATCH', '/**/my-page').as('save'); + cy.intercept('GET', '/**/my-page').as('content'); + cy.intercept('GET', '/**/@types/Document').as('schema'); + + cy.createContent({ + contentType: 'Document', + contentId: 'my-page-test', + contentTitle: 'My Page Test', + path: 'my-page', + }); + + cy.navigate('/my-page'); + cy.wait('@content'); + + cy.navigate('/my-page/edit'); + cy.wait('@schema'); + + cy.clearSlateTitle().type('My title'); + + //add listing block + cy.addNewBlock('listing'); + + cy.configureListingWith('News Item'); + + //save + cy.get('#toolbar-save').click(); + cy.wait('@save'); + cy.wait('@content'); + + //test after save + cy.get('#page-document .block.listing.default .emptyListing').contains( + 'No results found.', + ); + }); + it('Add Listing block', () => { cy.intercept('PATCH', '/**/my-page').as('save'); cy.intercept('GET', '/**/my-page').as('content'); @@ -841,6 +877,354 @@ describe('Listing Block Tests', () => { cy.get('.listing-item h4').first().contains('My Folder 3'); }); + it('Navigates in listing block pagination and url clears on logo click', () => { + cy.intercept('PATCH', '/**/my-page').as('save'); + cy.intercept('GET', '/**/my-page').as('content'); + cy.intercept('GET', '/**/@types/Document').as('schema'); + + cy.createContent({ + contentType: 'Document', + contentId: 'my-folder', + contentTitle: 'My Folder', + path: 'my-page', + }); + cy.createContent({ + contentType: 'Document', + contentId: 'my-folder2', + contentTitle: 'My Folder 2', + path: 'my-page', + }); + cy.createContent({ + contentType: 'Document', + contentId: 'my-folder3', + contentTitle: 'My Folder 3', + path: 'my-page', + }); + + cy.navigate('/my-page'); + cy.wait('@content'); + + cy.navigate('/my-page/edit'); + cy.wait('@schema'); + + cy.clearSlateTitle().type('Listing block - navigate to second page'); + + //add listing block + cy.addNewBlock('listing'); + + //verify before save + cy.get(`.block.listing .listing-body:first-of-type`).contains('My Folder'); + + cy.get('.sidebar-container .tabs-wrapper .menu .item') + .contains('Block') + .click(); + cy.get('.querystring-widget .fields').contains('Add criteria').click(); + cy.get( + '.querystring-widget .fields:first-of-type .field:first-of-type .react-select__menu .react-select__option', + ) + .contains('Type') + .click(); + + //insert Page + cy.get('.querystring-widget .fields:first-of-type > .field').click(); + cy.get( + '.querystring-widget .fields:first-of-type > .field .react-select__menu .react-select__option', + ) + .contains('Page') + .click(); + + cy.get('#field-limit-3-querystring').click().type('2'); + + cy.get('#field-limit-3-querystring').click().clear().type('0'); + cy.get('#field-b_size-4-querystring').click().type('2'); + cy.get('.ui.pagination.menu a[value="2"]').first().click(); + cy.get('#toolbar-save').click(); + cy.wait('@save'); + cy.wait('@content'); + //test second pagination click + cy.get('.ui.pagination.menu a[value="2"]').first().click({ force: true }); + cy.url().should('include', '?page=2'); + //on logo click go to home page and remove ?page=2 from path + cy.get('.logo').first().click(); + cy.url().should('not.include', '?page=2'); + }); + + // reload url when ?page=2 + it('Reload path when listing page = 2', () => { + cy.intercept('PATCH', '/**/my-page').as('save'); + cy.intercept('GET', '/**/my-page').as('content'); + cy.intercept('GET', '/**/@types/Document').as('schema'); + + cy.createContent({ + contentType: 'Document', + contentId: 'my-folder', + contentTitle: 'My Folder', + path: 'my-page', + }); + cy.createContent({ + contentType: 'Document', + contentId: 'my-folder2', + contentTitle: 'My Folder 2', + path: 'my-page', + }); + cy.createContent({ + contentType: 'Document', + contentId: 'my-folder3', + contentTitle: 'My Folder 3', + path: 'my-page', + }); + + cy.navigate('/my-page'); + cy.wait('@content'); + + cy.navigate('/my-page/edit'); + cy.wait('@schema'); + + cy.clearSlateTitle().type( + 'Listing block - navigate to second page and reload path', + ); + + //add listing block + cy.addNewBlock('listing'); + + //verify before save + cy.get(`.block.listing .listing-body:first-of-type`).contains('My Folder'); + + cy.get('.sidebar-container .tabs-wrapper .menu .item') + .contains('Block') + .click(); + cy.get('.querystring-widget .fields').contains('Add criteria').click(); + cy.get( + '.querystring-widget .fields:first-of-type .field:first-of-type .react-select__menu .react-select__option', + ) + .contains('Type') + .click(); + + //insert Page + cy.get('.querystring-widget .fields:first-of-type > .field').click(); + cy.get( + '.querystring-widget .fields:first-of-type > .field .react-select__menu .react-select__option', + ) + .contains('Page') + .click(); + + cy.get('#field-limit-3-querystring').click().type('2'); + + //save + cy.get('#toolbar-save').click(); + cy.wait('@save'); + cy.wait('@content'); + + //test after save + cy.get('#page-document .listing-item:first-of-type a').should( + 'have.attr', + 'href', + '/my-page/my-folder', + ); + cy.get('.listing-item').should(($els) => { + expect($els).to.have.length(2); + }); + + cy.navigate('/my-page/edit'); + cy.wait('@schema'); + + cy.get('.block-editor-listing').click(); + cy.get('.sidebar-container .tabs-wrapper .menu .item') + .contains('Block') + .click(); + + cy.get('#field-limit-3-querystring').click().clear().type('0'); + cy.get('#field-b_size-4-querystring').click().type('2'); + cy.get('.ui.pagination.menu a[value="2"]').first().click(); + + cy.get('.listing-item h4').first().contains('My Folder 3'); + cy.get('#toolbar-save').click(); + cy.wait('@save'); + cy.wait('@content'); + //test second pagination click + cy.get('.ui.pagination.menu a[value="2"]').first().click(); + + //test f5 + cy.reload(); + cy.url().should('include', '?page=2'); + }); + + // different page in two listings on the same page + it('Navigate to different pages on two different listings', () => { + cy.intercept('PATCH', '/**/my-page').as('save'); + cy.intercept('GET', '/**/my-page').as('content'); + cy.intercept('GET', '/**/@types/Document').as('schema'); + + cy.createContent({ + contentType: 'Document', + contentId: 'my-folder', + contentTitle: 'My Folder', + path: 'my-page', + }); + cy.createContent({ + contentType: 'Document', + contentId: 'my-folder2', + contentTitle: 'My Folder 2', + path: 'my-page', + }); + cy.createContent({ + contentType: 'Document', + contentId: 'my-folder3', + contentTitle: 'My Folder 3', + path: 'my-page', + }); + + cy.navigate('/my-page'); + cy.wait('@content'); + + cy.navigate('/my-page/edit'); + cy.wait('@schema'); + + cy.clearSlateTitle().type( + 'Listing block - navigate to different pages on two different listings', + ); + + //add listing block + cy.addNewBlock('listing'); + + //verify before save + cy.get(`.block.listing .listing-body:first-of-type`).contains('My Folder'); + + cy.get('.sidebar-container .tabs-wrapper .menu .item') + .contains('Block') + .click(); + cy.get('.querystring-widget .fields').contains('Add criteria').click(); + cy.get( + '.querystring-widget .fields:first-of-type .field:first-of-type .react-select__menu .react-select__option', + ) + .contains('Type') + .click(); + + //insert Page + cy.get('.querystring-widget .fields:first-of-type > .field').click(); + cy.get( + '.querystring-widget .fields:first-of-type > .field .react-select__menu .react-select__option', + ) + .contains('Page') + .click(); + + cy.get('#field-limit-3-querystring').click().type('0'); + cy.get('#field-b_size-4-querystring').click().type('2'); + + cy.addNewBlock('listing'); + + //verify before save + cy.get(`.block.listing .listing-body:first-of-type`).contains('My Folder'); + + cy.get('.sidebar-container .tabs-wrapper .menu .item') + .contains('Block') + .click(); + cy.get('.querystring-widget .fields').contains('Add criteria').click(); + cy.get( + '.querystring-widget .fields:first-of-type .field:first-of-type .react-select__menu .react-select__option', + ) + .contains('Type') + .click(); + + //insert Page + cy.get('.querystring-widget .fields:first-of-type > .field').click(); + cy.get( + '.querystring-widget .fields:first-of-type > .field .react-select__menu .react-select__option', + ) + .contains('Page') + .click(); + + cy.get('#field-limit-3-querystring').click().type('0'); + cy.get('#field-b_size-4-querystring').click().type('1'); + cy.get('#toolbar-save').click(); + cy.wait('@save'); + cy.wait('@content'); + + // const listing1 = cy.get('.ui.pagination.menu').first(); + // cy.log('listing1', listing1); + //test second pagination click + cy.get('.ui.pagination.menu a[value="2"]').first().click(); + //test f5 + cy.reload(); + cy.url().should('include', '=2'); + // const listing2 = cy.get('.ui.pagination.menu').last(); + //test third pagination click on second listing + cy.get('.ui.pagination.menu a[value="3"]').first().click(); + //test f5 + cy.reload(); + cy.url().should('include', '=2'); + cy.url().should('include', '=3'); + //on logo click go to home page and remove ?page=2 from path + cy.get('.logo').first().click(); + cy.url().should('not.include', '=2'); + cy.url().should('not.include', '=3'); + //test back button + cy.navigate('/my-page'); + cy.wait('@content'); + cy.get('.ui.pagination.menu a[value="2"]').first().click({ force: true }); + cy.get('.ui.pagination.menu a[value="3"]').first().click({ force: true }); + cy.go(-1); + cy.url().should('not.include', '=3'); + cy.go(-1); + cy.url().should('not.include', '=2'); + cy.url().should('not.include', '=3'); + }); + + it('Add Listing block with no results, navigate to home, add a News Item, go to the listing', () => { + cy.intercept('PATCH', '/**/my-page').as('save'); + cy.intercept('GET', '/**/my-page').as('content'); + cy.intercept('GET', '/**/@types/Document').as('schema'); + + cy.createContent({ + contentType: 'Document', + contentId: 'my-page-test', + contentTitle: 'My Page Test', + path: 'my-page', + }); + + cy.navigate('/my-page'); + cy.wait('@content'); + + cy.navigate('/my-page/edit'); + cy.wait('@schema'); + + cy.clearSlateTitle().type('My title'); + + //add listing block + cy.addNewBlock('listing'); + + cy.configureListingWith('News Item'); + + //save + cy.get('#toolbar-save').click(); + cy.wait('@save'); + cy.wait('@content'); + + //test after save + cy.get('#page-document .block.listing.default .emptyListing').contains( + 'No results found.', + ); + + cy.get('.logo').first().click(); + + cy.createContent({ + contentType: 'News Item', + contentId: 'my-news-item-test', + contentTitle: 'My News Item', + path: 'my-page', + }); + cy.navigate('/my-page'); + + cy.get('#page-document .listing-body:first-of-type').contains( + 'My News Item', + ); + cy.get('#page-document .listing-item:first-of-type a').should( + 'have.attr', + 'href', + '/my-page/my-news-item-test', + ); + }); + // it('Listing block - Test Criteria: Location Navigation', () => { // /*not implemented because Navigation ui is not yet developed in Listing Block sidebar*/ // }); diff --git a/news/4159.bugfix b/news/4159.bugfix new file mode 100644 index 0000000000..d9b9f18dfa --- /dev/null +++ b/news/4159.bugfix @@ -0,0 +1 @@ +Added current page parameter to route in listing and search block pagination - Fix: #3868 @bipoza \ No newline at end of file diff --git a/src/components/manage/Blocks/Listing/ListingBody.test.jsx b/src/components/manage/Blocks/Listing/ListingBody.test.jsx index d52c7df814..8a5c359972 100644 --- a/src/components/manage/Blocks/Listing/ListingBody.test.jsx +++ b/src/components/manage/Blocks/Listing/ListingBody.test.jsx @@ -36,6 +36,26 @@ test('renders a ListingBody component', () => { content: { data: { is_folderish: true, + blocks: { + '839ee00b-013b-4f4a-9b10-8867938fdac3': { + '@type': 'listing', + block: '839ee00b-013b-4f4a-9b10-8867938fdac3', + headlineTag: 'h2', + query: [], + querystring: { + b_size: '2', + query: [ + { + i: 'path', + o: 'plone.app.querystring.operation.string.absolutePath', + v: '/', + }, + ], + sort_order: 'ascending', + }, + variation: 'default', + }, + }, }, }, intl: { diff --git a/src/components/manage/Blocks/Listing/__snapshots__/ListingBody.test.jsx.snap b/src/components/manage/Blocks/Listing/__snapshots__/ListingBody.test.jsx.snap index e81db3c83f..5fa1511b42 100644 --- a/src/components/manage/Blocks/Listing/__snapshots__/ListingBody.test.jsx.snap +++ b/src/components/manage/Blocks/Listing/__snapshots__/ListingBody.test.jsx.snap @@ -5,7 +5,7 @@ exports[`renders a ListingBody component 1`] = ` className="emptyListing" >
state.querystringsearch.subrequests, ); @@ -50,7 +52,7 @@ export default function withQuerystringResults(WrappedComponent) { const folderItems = content?.is_folderish ? content.items : []; const hasQuery = querystring?.query?.length > 0; - const hasLoaded = hasQuery ? !querystringResults?.[id]?.loading : true; + const hasLoaded = hasQuery ? querystringResults?.[id]?.loaded : true; const listingItems = querystring?.query?.length > 0 && querystringResults?.[id] @@ -109,6 +111,8 @@ export default function withQuerystringResults(WrappedComponent) { } else { dispatch(getContent(initialPath, null, null, currentPage)); } + adaptedQueryRef.current = adaptedQuery; + currentPageRef.current = currentPage; }, [ id, isImageGallery, diff --git a/src/components/manage/Blocks/Search/hocs/withSearch.jsx b/src/components/manage/Blocks/Search/hocs/withSearch.jsx index 332d6feb4f..b810cc282f 100644 --- a/src/components/manage/Blocks/Search/hocs/withSearch.jsx +++ b/src/components/manage/Blocks/Search/hocs/withSearch.jsx @@ -148,12 +148,16 @@ const getSearchFields = (searchData) => { }; /** - * A HOC that will mirror the search block state to a hash location + * A hook that will mirror the search block state to a hash location */ const useHashState = () => { const location = useLocation(); const history = useHistory(); + /** + * Required to maintain parameter compatibility. + With this we will maintain support for receiving hash (#) and search (?) type parameters. + */ const oldState = React.useMemo(() => { return { ...qs.parse(location.search), @@ -169,7 +173,7 @@ const useHashState = () => { const setSearchData = React.useCallback( (searchData) => { - const newParams = qs.parse(location.hash); + const newParams = qs.parse(location.search); let changed = false; @@ -186,11 +190,11 @@ const useHashState = () => { if (changed) { history.push({ - hash: qs.stringify(newParams), + search: qs.stringify(newParams), }); } }, - [history, oldState, location.hash], + [history, oldState, location.search], ); return [current, setSearchData]; diff --git a/src/helpers/Utils/usePagination.js b/src/helpers/Utils/usePagination.js index acb12d2ac5..083ec48682 100644 --- a/src/helpers/Utils/usePagination.js +++ b/src/helpers/Utils/usePagination.js @@ -1,25 +1,83 @@ -import React from 'react'; -import { isEqual } from 'lodash'; -import { usePrevious } from './usePrevious'; -import useDeepCompareEffect from 'use-deep-compare-effect'; +import React, { useRef, useEffect } from 'react'; +import { useHistory, useLocation } from 'react-router-dom'; +import qs from 'query-string'; +import { useSelector } from 'react-redux'; +import { slugify } from '@plone/volto/helpers/Utils/Utils'; + +/** + * @function useCreatePageQueryStringKey + * @description A hook that creates a key with an id if there are multiple blocks with pagination. + * @returns {string} Example: page || page_012345678 + */ +const useCreatePageQueryStringKey = (id) => { + const blockTypesWithPagination = ['search', 'listing']; + const blocks = useSelector((state) => state?.content?.data?.blocks) || []; + const blocksLayout = + useSelector((state) => state?.content?.data?.blocks_layout?.items) || []; + const displayedBlocks = blocksLayout?.map((item) => blocks[item]); + const hasMultiplePaginations = + displayedBlocks.filter((item) => + blockTypesWithPagination.includes(item['@type']), + ).length > 1 || false; + + return hasMultiplePaginations ? slugify(`page-${id}`) : 'page'; +}; + +const useGetBlockType = (id) => { + const blocks = useSelector((state) => state?.content?.data?.blocks) || []; + const block = blocks[id]; + return block ? block?.['@type'] : null; +}; /** * A pagination helper that tracks the query and resets pagination in case the * query changes. */ -export const usePagination = (query, defaultPage = 1) => { - const previousQuery = usePrevious(query); - const [currentPage, setCurrentPage] = React.useState(defaultPage); +export const usePagination = (id = null, defaultPage = 1) => { + const location = useLocation(); + const history = useHistory(); + const pageQueryStringKey = useCreatePageQueryStringKey(id); + const block_type = useGetBlockType(id); + const pageQueryParam = + qs.parse(location.search)[pageQueryStringKey] || defaultPage; + const [currentPage, setCurrentPageState] = React.useState( + parseInt(pageQueryParam), + ); + const setCurrentPage = (page) => { + setCurrentPageState(page); + const newParams = { + ...qs.parse(location.search), + [pageQueryStringKey]: page, + }; + history.push({ search: qs.stringify(newParams) }); + }; - useDeepCompareEffect(() => { - setCurrentPage(defaultPage); - }, [query, previousQuery, defaultPage]); + const queryRef = useRef(qs.parse(location.search)?.query); + useEffect(() => { + if ( + queryRef.current !== qs.parse(location.search)?.query && + block_type === 'search' + ) { + setCurrentPageState(defaultPage); + const newParams = { + ...qs.parse(location.search), + [pageQueryStringKey]: defaultPage, + }; + delete newParams[pageQueryStringKey]; + history.replace({ search: qs.stringify(newParams) }); + queryRef.current = qs.parse(location.search)?.query; + } else { + setCurrentPageState( + parseInt( + qs.parse(location.search)?.[pageQueryStringKey] || defaultPage, + ), + ); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [location.search, block_type]); return { - currentPage: - previousQuery && !isEqual(previousQuery, query) - ? defaultPage - : currentPage, + currentPage, setCurrentPage, }; }; diff --git a/src/helpers/Utils/usePagination.test.js b/src/helpers/Utils/usePagination.test.js new file mode 100644 index 0000000000..26924d78a0 --- /dev/null +++ b/src/helpers/Utils/usePagination.test.js @@ -0,0 +1,115 @@ +import { renderHook } from '@testing-library/react-hooks'; +import { usePagination } from './usePagination'; +import * as redux from 'react-redux'; +import routeData from 'react-router'; +import { slugify } from '@plone/volto/helpers/Utils/Utils'; + +const searchBlockId = '545b33de-92cf-4747-969d-68851837b317'; +const searchBlockId2 = '454b33de-92cf-4747-969d-68851837b713'; +const searchBlock = { + '@type': 'search', + query: { + b_size: '4', + query: [ + { + i: 'path', + o: 'plone.app.querystring.operation.string.relativePath', + v: '', + }, + ], + sort_order: 'ascending', + }, + showSearchInput: true, + showTotalResults: true, +}; +let state = { + content: { + data: { + blocks: { + [searchBlockId]: searchBlock, + }, + blocks_layout: { + items: [searchBlockId], + }, + }, + }, +}; + +let mockUseLocationValue = { + pathname: '/testroute', + search: '', +}; + +const setUp = (searchParam, numberOfSearches) => { + mockUseLocationValue.search = searchParam; + if (numberOfSearches > 1) { + state.content.data.blocks[searchBlockId2] = searchBlock; + state.content.data.blocks_layout.items.push(searchBlockId2); + } + return renderHook(({ id, defaultPage }) => usePagination(id, defaultPage), { + initialProps: { + id: searchBlockId, + defaultPage: 1, + }, + }); +}; + +describe(`Tests for usePagination, for the block ${searchBlockId}`, () => { + const useLocation = jest.spyOn(routeData, 'useLocation'); + const useHistory = jest.spyOn(routeData, 'useHistory'); + const useSelector = jest.spyOn(redux, 'useSelector'); + beforeEach(() => { + useLocation.mockReturnValue(mockUseLocationValue); + useHistory.mockReturnValue({ replace: jest.fn() }); + useSelector.mockImplementation((cb) => cb(state)); + }); + + it('1 paginated block with id and defaultPage 1 - shoud be 1', () => { + const { result } = setUp(); + expect(result.current.currentPage).toBe(1); + }); + + it('1 paginated block without params - shoud be 1', () => { + const { result } = setUp(); + expect(result.current.currentPage).toBe(1); + }); + + const param1 = '?page=2'; + it(`1 paginated block with params: ${param1} - shoud be 2`, () => { + const { result } = setUp(param1); + expect(result.current.currentPage).toBe(2); + }); + + const param2 = `?${slugify(`page-${searchBlockId}`)}=2`; + it(`2 paginated blocks with current block in the params: ${param2} - shoud be 2`, () => { + const { result } = setUp(param2, 2); + expect(result.current.currentPage).toBe(2); + }); + + const param3 = `?${slugify(`page-${searchBlockId2}`)}=2`; + it(`2 paginated blocks with the other block in the params: ${param3} - shoud be 1`, () => { + const { result } = setUp(param3, 2); + expect(result.current.currentPage).toBe(1); + }); + + const param4 = `?${slugify(`page-${searchBlockId}`)}=2&${slugify( + `page-${searchBlockId2}`, + )}=1`; + it(`2 paginated blocks with both blocks in the params, current 2: ${param4} - shoud be 2`, () => { + const { result } = setUp(param4, 2); + expect(result.current.currentPage).toBe(2); + }); + + const param5 = `?${slugify(`page-${searchBlockId}`)}=1&${slugify( + `page-${searchBlockId2}`, + )}=2`; + it(`2 paginated blocks with both blocks in the params, current 1: ${param5} - shoud be 1`, () => { + const { result } = setUp(param5, 2); + expect(result.current.currentPage).toBe(1); + }); + + it(`2 paginated blocks with wrong page param: ${param1} - shoud be 1`, () => { + const { result } = setUp(param1, 2); + expect(result.current.currentPage).toBe(1); + }); +}); From f5625c36468fc9e0580b21bc65ec61ed9237de76 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Fri, 9 Jun 2023 16:11:17 +0200 Subject: [PATCH 06/19] Release 17.0.0-alpha.11 --- CHANGELOG.md | 7 +++++++ news/4159.bugfix | 1 - package.json | 2 +- packages/volto-slate/package.json | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) delete mode 100644 news/4159.bugfix diff --git a/CHANGELOG.md b/CHANGELOG.md index 32dc0f0731..1dd70383e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,13 @@ +## 17.0.0-alpha.11 (2023-06-09) + +### Bugfix + +- Added current page parameter to route in listing and search block pagination - Fix: #3868 @bipoza [#4159](https://github.com/plone/volto/issues/4159) + + ## 17.0.0-alpha.10 (2023-06-09) ### Feature diff --git a/news/4159.bugfix b/news/4159.bugfix deleted file mode 100644 index d9b9f18dfa..0000000000 --- a/news/4159.bugfix +++ /dev/null @@ -1 +0,0 @@ -Added current page parameter to route in listing and search block pagination - Fix: #3868 @bipoza \ No newline at end of file diff --git a/package.json b/package.json index ad387925ca..f45ae9c1c5 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ } ], "license": "MIT", - "version": "17.0.0-alpha.10", + "version": "17.0.0-alpha.11", "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 61c8c193cf..cae353cb41 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.10", + "version": "17.0.0-alpha.11", "description": "Slate.js integration with Volto", "main": "src/index.js", "author": "European Environment Agency: IDM2 A-Team", From 28a753b4ecb424df469eadee0358e61194c038fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Katja=20S=C3=BCss?= Date: Wed, 14 Jun 2023 19:40:06 +0200 Subject: [PATCH 07/19] Allow to deselect color in ColorPickerWidget. (#4839) Please backport to Volto 16. --- news/4838.feature | 1 + src/components/manage/Widgets/ColorPickerWidget.jsx | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 news/4838.feature diff --git a/news/4838.feature b/news/4838.feature new file mode 100644 index 0000000000..df769019b9 --- /dev/null +++ b/news/4838.feature @@ -0,0 +1 @@ +Allow to deselect color in ColorPickerWidget. @ksuess \ No newline at end of file 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 From 7e3d530c13b9e329fc586feb35440556b64bf1ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Wed, 14 Jun 2023 22:27:52 +0200 Subject: [PATCH 08/19] Configurable Container component from registry for some key route views. (#4871) --- news/4871.feature | 1 + src/components/manage/Contents/Contents.jsx | 6 +++++- src/components/manage/History/History.jsx | 12 +++++++++++- src/components/manage/Sharing/Sharing.jsx | 6 +++++- src/components/theme/View/NewsItemView.jsx | 15 ++++++++++----- 5 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 news/4871.feature diff --git a/news/4871.feature b/news/4871.feature new file mode 100644 index 0000000000..53f9aba405 --- /dev/null +++ b/news/4871.feature @@ -0,0 +1 @@ +Configurable Container component from registry for some key route views. @sneridagh 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/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/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. From 36c3f6c1cad7f1db2361409d8b2b941c8561cb92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Wed, 14 Jun 2023 22:28:05 +0200 Subject: [PATCH 09/19] Fix regression in horizontal scroll in contents view, add it back (#4872) --- news/4872.bugfix | 1 + theme/themes/pastanaga/extras/contents.less | 1 + 2 files changed, 2 insertions(+) create mode 100644 news/4872.bugfix diff --git a/news/4872.bugfix b/news/4872.bugfix new file mode 100644 index 0000000000..bb38d05103 --- /dev/null +++ b/news/4872.bugfix @@ -0,0 +1 @@ +Fix regression in horizontal scroll in contents view, add it back @sneridagh 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%; From 47a44142d8a6f42d77e4a77f0e5b287663e7d9fd Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Wed, 14 Jun 2023 22:33:58 +0200 Subject: [PATCH 10/19] Release 17.0.0-alpha.12 --- CHANGELOG.md | 12 ++++++++++++ news/4838.feature | 1 - news/4871.feature | 1 - news/4872.bugfix | 1 - package.json | 2 +- packages/volto-slate/package.json | 2 +- 6 files changed, 14 insertions(+), 5 deletions(-) delete mode 100644 news/4838.feature delete mode 100644 news/4871.feature delete mode 100644 news/4872.bugfix diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dd70383e7..928ff80c2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,18 @@ +## 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/news/4838.feature b/news/4838.feature deleted file mode 100644 index df769019b9..0000000000 --- a/news/4838.feature +++ /dev/null @@ -1 +0,0 @@ -Allow to deselect color in ColorPickerWidget. @ksuess \ No newline at end of file diff --git a/news/4871.feature b/news/4871.feature deleted file mode 100644 index 53f9aba405..0000000000 --- a/news/4871.feature +++ /dev/null @@ -1 +0,0 @@ -Configurable Container component from registry for some key route views. @sneridagh diff --git a/news/4872.bugfix b/news/4872.bugfix deleted file mode 100644 index bb38d05103..0000000000 --- a/news/4872.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix regression in horizontal scroll in contents view, add it back @sneridagh diff --git a/package.json b/package.json index f45ae9c1c5..cfffa013ec 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ } ], "license": "MIT", - "version": "17.0.0-alpha.11", + "version": "17.0.0-alpha.12", "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 cae353cb41..9b9441e690 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.12", "description": "Slate.js integration with Volto", "main": "src/index.js", "author": "European Environment Agency: IDM2 A-Team", From c85a9e920eb5a8ef8b93d75bd81756c83f24b1da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Thu, 15 Jun 2023 20:21:36 +0200 Subject: [PATCH 11/19] Fix and improve the `addStyling` helper (#4880) --- news/4880.bugfix | 1 + .../Extensions/withBlockSchemaEnhancer.js | 26 ++-- .../withBlockSchemaEnhancer.test.js | 145 ++++++++++++++++++ 3 files changed, 161 insertions(+), 11 deletions(-) create mode 100644 news/4880.bugfix diff --git a/news/4880.bugfix b/news/4880.bugfix new file mode 100644 index 0000000000..f674f66aeb --- /dev/null +++ b/news/4880.bugfix @@ -0,0 +1 @@ +Fix and improve the `addStyling` helper @sneridagh 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: [], + }); + }); +}); From 2ddcdf28bf3f13ecd257b1107cf32ea81555e4d7 Mon Sep 17 00:00:00 2001 From: David Glick Date: Thu, 15 Jun 2023 11:22:10 -0700 Subject: [PATCH 12/19] Enforce max upload size (#4868) Co-authored-by: Timo Stollenwerk --- .../configuration/settings-reference.md | 4 +++ locales/ca/LC_MESSAGES/volto.po | 5 ++++ locales/de/LC_MESSAGES/volto.po | 7 ++++- locales/en/LC_MESSAGES/volto.po | 5 ++++ locales/es/LC_MESSAGES/volto.po | 5 ++++ locales/eu/LC_MESSAGES/volto.po | 5 ++++ locales/fi/LC_MESSAGES/volto.po | 5 ++++ locales/fr/LC_MESSAGES/volto.po | 5 ++++ locales/it/LC_MESSAGES/volto.po | 5 ++++ locales/ja/LC_MESSAGES/volto.po | 5 ++++ locales/nl/LC_MESSAGES/volto.po | 5 ++++ locales/pt/LC_MESSAGES/volto.po | 5 ++++ locales/pt_BR/LC_MESSAGES/volto.po | 5 ++++ locales/ro/LC_MESSAGES/volto.po | 5 ++++ locales/volto.pot | 7 ++++- locales/zh_CN/LC_MESSAGES/volto.po | 5 ++++ news/4868.feature | 1 + .../blocks/Text/DefaultTextBlockEditor.jsx | 11 +++++-- .../Text/extensions/withDeserializers.js | 4 ++- .../manage/Blocks/HeroImageLeft/Edit.jsx | 7 ++++- src/components/manage/Blocks/Image/Edit.jsx | 18 +++++++----- .../manage/Contents/ContentsUploadModal.jsx | 15 ++++++---- src/components/manage/Toast/Toast.jsx | 2 +- src/components/manage/Widgets/FileWidget.jsx | 3 +- src/config/index.js | 1 + src/helpers/FormValidation/FormValidation.js | 29 +++++++++++++++++++ src/helpers/MessageLabels/MessageLabels.js | 4 +++ src/helpers/index.js | 4 ++- 28 files changed, 160 insertions(+), 22 deletions(-) create mode 100644 news/4868.feature 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 e01b268d76..4bf4b148d1 100644 --- a/locales/ca/LC_MESSAGES/volto.po +++ b/locales/ca/LC_MESSAGES/volto.po @@ -4303,6 +4303,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 22c83cb8a6..f316a86a30 100644 --- a/locales/de/LC_MESSAGES/volto.po +++ b/locales/de/LC_MESSAGES/volto.po @@ -4300,10 +4300,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 c85490b44b..08c9c94371 100644 --- a/locales/en/LC_MESSAGES/volto.po +++ b/locales/en/LC_MESSAGES/volto.po @@ -4294,6 +4294,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 696ad123e6..6d18f20d8c 100644 --- a/locales/es/LC_MESSAGES/volto.po +++ b/locales/es/LC_MESSAGES/volto.po @@ -4305,6 +4305,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 b19c9ad4de..6f2565e6d5 100644 --- a/locales/eu/LC_MESSAGES/volto.po +++ b/locales/eu/LC_MESSAGES/volto.po @@ -4301,6 +4301,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 19345cfa05..791c210c1e 100644 --- a/locales/fi/LC_MESSAGES/volto.po +++ b/locales/fi/LC_MESSAGES/volto.po @@ -4305,6 +4305,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 cfcd5a0af4..c4a007b100 100644 --- a/locales/fr/LC_MESSAGES/volto.po +++ b/locales/fr/LC_MESSAGES/volto.po @@ -4311,6 +4311,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 64d87aadc6..d63d8a20f0 100644 --- a/locales/it/LC_MESSAGES/volto.po +++ b/locales/it/LC_MESSAGES/volto.po @@ -4294,6 +4294,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 9aef53b157..a8a6fbea17 100644 --- a/locales/ja/LC_MESSAGES/volto.po +++ b/locales/ja/LC_MESSAGES/volto.po @@ -4302,6 +4302,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 c563bf05bb..e575532f1a 100644 --- a/locales/nl/LC_MESSAGES/volto.po +++ b/locales/nl/LC_MESSAGES/volto.po @@ -4313,6 +4313,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 b9f0a4602b..2f445ad4fd 100644 --- a/locales/pt/LC_MESSAGES/volto.po +++ b/locales/pt/LC_MESSAGES/volto.po @@ -4302,6 +4302,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 899a0fa7e4..bd0ee20da5 100644 --- a/locales/pt_BR/LC_MESSAGES/volto.po +++ b/locales/pt_BR/LC_MESSAGES/volto.po @@ -4304,6 +4304,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 30009767d5..f2411286fc 100644 --- a/locales/ro/LC_MESSAGES/volto.po +++ b/locales/ro/LC_MESSAGES/volto.po @@ -4294,6 +4294,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 0524dad36f..7cb0cc5731 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-02T14:22:59.421Z\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" @@ -4296,6 +4296,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 e8f5457cdd..98779199d8 100644 --- a/locales/zh_CN/LC_MESSAGES/volto.po +++ b/locales/zh_CN/LC_MESSAGES/volto.po @@ -4300,6 +4300,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/news/4868.feature b/news/4868.feature new file mode 100644 index 0000000000..90113ac97a --- /dev/null +++ b/news/4868.feature @@ -0,0 +1 @@ +Add and enforce a new config setting, `maxFileUploadSize`. @davisagli 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/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/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/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/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/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 faac5f7b43..1a05433740 100644 --- a/src/helpers/index.js +++ b/src/helpers/index.js @@ -71,7 +71,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, From 453d36fc83af81363a54af63438f3d890cd9955a Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Thu, 15 Jun 2023 20:29:30 +0200 Subject: [PATCH 13/19] Release 17.0.0-alpha.13 --- CHANGELOG.md | 11 +++++++++++ news/4868.feature | 1 - news/4880.bugfix | 1 - package.json | 2 +- packages/volto-slate/package.json | 2 +- 5 files changed, 13 insertions(+), 4 deletions(-) delete mode 100644 news/4868.feature delete mode 100644 news/4880.bugfix diff --git a/CHANGELOG.md b/CHANGELOG.md index 928ff80c2e..e86b6b3198 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,17 @@ +## 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 diff --git a/news/4868.feature b/news/4868.feature deleted file mode 100644 index 90113ac97a..0000000000 --- a/news/4868.feature +++ /dev/null @@ -1 +0,0 @@ -Add and enforce a new config setting, `maxFileUploadSize`. @davisagli diff --git a/news/4880.bugfix b/news/4880.bugfix deleted file mode 100644 index f674f66aeb..0000000000 --- a/news/4880.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix and improve the `addStyling` helper @sneridagh diff --git a/package.json b/package.json index cfffa013ec..d09b21cb0c 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ } ], "license": "MIT", - "version": "17.0.0-alpha.12", + "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 9b9441e690..506c771cc4 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.12", + "version": "17.0.0-alpha.13", "description": "Slate.js integration with Volto", "main": "src/index.js", "author": "European Environment Agency: IDM2 A-Team", From 7e5783bde5627f505857e0a15cac3614bd6ec02a Mon Sep 17 00:00:00 2001 From: David Glick Date: Sat, 17 Jun 2023 17:37:02 -0700 Subject: [PATCH 14/19] Update to plone-backend 6.0.5 (#4897) --- Makefile | 4 ++-- README.md | 4 ++-- api/buildout.cfg | 2 +- docker-compose.yml | 2 +- news/4897.bugfix | 1 + .../generator-volto/generators/addon/templates/Makefile | 8 ++++---- 6 files changed, 11 insertions(+), 10 deletions(-) create mode 100644 news/4897.bugfix diff --git a/Makefile b/Makefile index 17d412f4aa..a094073869 100644 --- a/Makefile +++ b/Makefile @@ -13,8 +13,8 @@ MAKEFLAGS+=--no-builtin-rules # Project settings INSTANCE_PORT=8080 -DOCKER_IMAGE=plone/server-dev:6.0.4 -DOCKER_IMAGE_ACCEPTANCE=plone/server-acceptance:6.0.4 +DOCKER_IMAGE=plone/server-dev:6.0.5 +DOCKER_IMAGE_ACCEPTANCE=plone/server-acceptance:6.0.5 KGS= NODEBIN = ./node_modules/.bin SCRIPTSPACKAGE = ./packages/scripts diff --git a/README.md b/README.md index 7c32093b7f..c86d75746b 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ cd myvoltoproject You can bootstrap a ready Docker Plone container with all the dependencies and ready for Volto use. We recommend to use the Plone docker builds based in `pip` [plone/plone-backend](https://github.com/plone/plone-backend) image: ```shell -docker run -it --rm --name=plone -p 8080:8080 -e SITE=Plone -e PROFILES="plone.volto:default-homepage" plone/plone-backend:6.0.1 +docker run -it --rm --name=plone -p 8080:8080 -e SITE=Plone -e PROFILES="plone.volto:default-homepage" plone/plone-backend:6.0.5 ``` or as an alternative if you have experience with Plone and you have all the @@ -266,7 +266,7 @@ yarn Either using a Docker command: ```shell -docker run -it --rm --name=plone -p 8080:8080 -e SITE=Plone -e PROFILES="plone.volto:default-homepage" plone/plone-backend:6.0.1 +docker run -it --rm --name=plone -p 8080:8080 -e SITE=Plone -e PROFILES="plone.volto:default-homepage" plone/plone-backend:6.0.5 ``` or using the convenience makefile command: diff --git a/api/buildout.cfg b/api/buildout.cfg index ead796cfcb..f3d12ae947 100644 --- a/api/buildout.cfg +++ b/api/buildout.cfg @@ -1,7 +1,7 @@ [buildout] index = https://pypi.org/simple/ extends = - http://dist.plone.org/release/6.0.4/versions.cfg + http://dist.plone.org/release/6.0.5/versions.cfg version-constraints.cfg versions.cfg parts = instance plonesite site-packages test robot-server diff --git a/docker-compose.yml b/docker-compose.yml index 82a49830b7..ba7948726a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ version: '3.3' services: backend: - image: plone/plone-backend:6.0.1 + image: plone/plone-backend:6.0.5 # Plone 5.2 series can be used too # image: plone/plone-backend:5.2.10 ports: diff --git a/news/4897.bugfix b/news/4897.bugfix new file mode 100644 index 0000000000..58beb4357d --- /dev/null +++ b/news/4897.bugfix @@ -0,0 +1 @@ +Update to version 6.0.5 of Plone backend. @davisagli diff --git a/packages/generator-volto/generators/addon/templates/Makefile b/packages/generator-volto/generators/addon/templates/Makefile index 59c4f32e52..be8d5fb29c 100644 --- a/packages/generator-volto/generators/addon/templates/Makefile +++ b/packages/generator-volto/generators/addon/templates/Makefile @@ -11,15 +11,15 @@ MAKEFLAGS+=--warn-undefined-variables MAKEFLAGS+=--no-builtin-rules # Project settings -# Update the versions depending on your project requirements | Last Updated 2022-12-23 -DOCKER_IMAGE=plone/server-dev:6.0.2 -DOCKER_IMAGE_ACCEPTANCE=plone/server-acceptance:6.0.2 +# Update the versions depending on your project requirements | Last Updated 2023-06-17 +DOCKER_IMAGE=plone/server-dev:6.0.5 +DOCKER_IMAGE_ACCEPTANCE=plone/server-acceptance:6.0.5 KGS= NODEBIN = ./node_modules/.bin # Plone 5 legacy DOCKER_IMAGE5=plone/plone-backend:5.2.10 -KGS5=plone.restapi==8.35.1 plone.volto==4.0.7 plone.rest==3.0.0 +KGS5=plone.restapi==8.37.0 plone.volto==4.0.8 plone.rest==3.0.0 TESTING_ADDONS=plone.app.robotframework==2.0.0 plone.app.testing==7.0.0 DIR=$(shell basename $$(pwd)) From ac3c11905709e0bb3cf75b2a592e13d743ff7234 Mon Sep 17 00:00:00 2001 From: Tisha Soumya Date: Mon, 19 Jun 2023 13:39:43 +0530 Subject: [PATCH 15/19] Refactor Anontools (#4845) Co-authored-by: Victor Fernandez de Alba Co-authored-by: Nilesh --- news/4845.feature | 1 + src/components/theme/Anontools/Anontools.jsx | 117 +++++++----------- .../theme/Anontools/Anontools.test.jsx | 18 ++- src/hooks/content/useContent.js | 31 +++++ 4 files changed, 93 insertions(+), 74 deletions(-) create mode 100644 news/4845.feature create mode 100644 src/hooks/content/useContent.js diff --git a/news/4845.feature b/news/4845.feature new file mode 100644 index 0000000000..e8599a0811 --- /dev/null +++ b/news/4845.feature @@ -0,0 +1 @@ +Refactored Anontools components. @Tishasoumya-02 diff --git a/src/components/theme/Anontools/Anontools.jsx b/src/components/theme/Anontools/Anontools.jsx index 2b9e9fb5b6..ccff3638b0 100644 --- a/src/components/theme/Anontools/Anontools.jsx +++ b/src/components/theme/Anontools/Anontools.jsx @@ -1,83 +1,56 @@ -/** - * Anontools component. - * @module components/theme/Anontools/Anontools - */ - -import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; import { Link } from 'react-router-dom'; import { Menu } from 'semantic-ui-react'; import { FormattedMessage } from 'react-intl'; +import { flattenToAppURL } from '@plone/volto/helpers'; +import { useToken } from '@plone/volto/hooks/userSession/useToken'; +import { useContent } from '@plone/volto/hooks/content/useContent'; import config from '@plone/volto/registry'; -/** - * Anontools container class. - */ -export class Anontools extends Component { - /** - * Property types. - * @property {Object} propTypes Property types. - * @static - */ - static propTypes = { - token: PropTypes.string, - content: PropTypes.shape({ - '@id': PropTypes.string, - }), - }; - - /** - * Default properties. - * @property {Object} defaultProps Default properties. - * @static - */ - static defaultProps = { - token: null, - content: { - '@id': null, - }, - }; +const Anontools = () => { + const token = useToken(); + const { data: content } = useContent(); - /** - * Render method. - * @method render - * @returns {string} Markup for the component. - */ - render() { - const { settings } = config; - return ( - !this.props.token && ( - + const { settings } = config; + return ( + !token && ( + + + + + + + {settings.showSelfRegistration && ( - - + + - {settings.showSelfRegistration && ( - - - - - - )} - - ) - ); - } -} + )} + + ) + ); +}; + +export default Anontools; + +Anontools.propTypes = { + token: PropTypes.string, + content: PropTypes.shape({ + '@id': PropTypes.string, + }), +}; -export default connect((state) => ({ - token: state.userSession.token, - content: state.content.data, -}))(Anontools); +Anontools.defaultProps = { + token: null, + content: { + '@id': null, + }, +}; diff --git a/src/components/theme/Anontools/Anontools.test.jsx b/src/components/theme/Anontools/Anontools.test.jsx index 628e214925..28f54f9544 100644 --- a/src/components/theme/Anontools/Anontools.test.jsx +++ b/src/components/theme/Anontools/Anontools.test.jsx @@ -12,7 +12,14 @@ describe('Anontools', () => { it('renders an anontools component when no token is specified', () => { const store = mockStore({ userSession: { token: null }, - content: { data: { '@id': 'myid' } }, + content: { + data: { '@id': 'myid' }, + get: { + loading: false, + loaded: true, + error: null, + }, + }, intl: { locale: 'en', messages: {}, @@ -32,7 +39,14 @@ describe('Anontools', () => { it('should not render an anontools component when a token is specified', () => { const store = mockStore({ userSession: { token: '1234' }, - content: { data: {} }, + content: { + data: {}, + get: { + loading: false, + loaded: true, + error: null, + }, + }, intl: { locale: 'en', messages: {}, diff --git a/src/hooks/content/useContent.js b/src/hooks/content/useContent.js new file mode 100644 index 0000000000..3001c1d7a4 --- /dev/null +++ b/src/hooks/content/useContent.js @@ -0,0 +1,31 @@ +import { useSelector, shallowEqual } from 'react-redux'; + +/** + * useContent hook + * + * This hook returns the current content that is stored in the Redux store in the + * `content` reducer, and returns it along with the related state (loading/loaded/error). + * + * @export + * @return {{ data: ContentData, loading: boolean, loaded: boolean, error: Error }} + */ +export function useContent() { + const data = useSelector((state) => state.content.data, shallowEqual); + const loading = useSelector((state) => state.content.get.loading); + const loaded = useSelector((state) => state.content.get.loaded); + const error = useSelector((state) => state.content.get.error, shallowEqual); + + return { data, loading, loaded, error }; +} + +// For reference purposes: Potential future useQuery version +// export function useContent() { +// // the cache will need to know the current location +// const pathname = useLocation(); +// const query = useQuery(getContentQuery({ path })) + +// // This might not be needed if we rename the properties +// const {isLoading: loading, isSuccess: loaded, ...rest} = query; + +// return { loading, loaded, ...rest }; +// } From 10eaab14d9f650638db3807c49f03d47890aee3c Mon Sep 17 00:00:00 2001 From: "Leonardo J. Caballero G" Date: Thu, 22 Jun 2023 03:12:26 -0400 Subject: [PATCH 16/19] Spanish translation (#4896) --- CHANGELOG.md | 1 + locales/es/LC_MESSAGES/volto.po | 88 ++++++++++++++++----------------- 2 files changed, 45 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e86b6b3198..0cfc61862e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1256,6 +1256,7 @@ See https://6.docs.plone.org/volto/upgrade-guide/index.html for more information - Add support for loading core add-ons from the `packages` folder defined in Volto's `package.json` @sneridagh - Implement the Upgrade Control Panel @ericof - Allow addons to customize modules from the project root, via the `@root` namespace and folder @tiberiuichim +- Updated Spanish translation @macagua ### Bugfix diff --git a/locales/es/LC_MESSAGES/volto.po b/locales/es/LC_MESSAGES/volto.po index 6d18f20d8c..db43208f5c 100644 --- a/locales/es/LC_MESSAGES/volto.po +++ b/locales/es/LC_MESSAGES/volto.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: Plone\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2019-12-03 03:20-0400\n" -"PO-Revision-Date: 2022-12-02 11:45+0100\n" +"PO-Revision-Date: 2023-06-20 14:02-0400\n" "Last-Translator: Leonardo J. Caballero G. \n" "Language: es\n" "Language-Team: ES \n" @@ -15,7 +15,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "MIME-Version: 1.0\n" -"X-Generator: Poedit 2.3\n" +"X-Generator: Poedit 3.3.1\n" "Language-Code: es\n" "Language-Name: Español\n" "Preferred-Encodings: utf-8\n" @@ -282,12 +282,12 @@ msgstr "Complemento actualizado con éxito" #: components/manage/Blocks/Search/schema # defaultMessage: Advanced facet? msgid "Advanced facet?" -msgstr "" +msgstr "¿Faceta avanzada?" #: components/manage/Blocks/Search/schema # defaultMessage: Advanced facets are initially hidden and displayed on demand msgid "Advanced facets are initially hidden and displayed on demand" -msgstr "" +msgstr "Las facetas avanzadas se ocultan inicialmente y se muestran cuando se solicitan" #: config/Views # defaultMessage: Album view @@ -479,7 +479,7 @@ msgstr "Barra de ruta" #: components/manage/Controlpanels/Relations/BrokenRelations # defaultMessage: Broken relations msgid "Broken relations" -msgstr "" +msgstr "Relaciones rotas" #: components/manage/Blocks/HeroImageLeft/Edit #: components/manage/Contents/ContentsUploadModal @@ -802,7 +802,7 @@ msgstr "Información del copyright o otros derechos en este elemento." #: helpers/MessageLabels/MessageLabels # defaultMessage: Create or delete relations to target msgid "Create or delete relations to target" -msgstr "" +msgstr "Crear o eliminar relaciones con el destino" #: components/manage/Toolbar/More # defaultMessage: Create working copy @@ -1426,7 +1426,7 @@ msgstr "Nombre del archivo" #: helpers/MessageLabels/MessageLabels # defaultMessage: Filter msgid "Filter" -msgstr "" +msgstr "Filtrar" #: components/manage/Controlpanels/Rules/Rules # defaultMessage: Filter Rules: @@ -1456,7 +1456,7 @@ msgstr "Primero" #: helpers/MessageLabels/MessageLabels # defaultMessage: Fix relations msgid "Fix relations" -msgstr "" +msgstr "Corregir relaciones" #: components/manage/Blocks/Table/Edit # defaultMessage: Fixed width columns @@ -1597,7 +1597,7 @@ msgstr "¿Ocultar la faceta?" #: components/manage/Blocks/Search/components/Facets # defaultMessage: Hide filters msgid "Hide filters" -msgstr "" +msgstr "Ocultar filtros" #: components/manage/History/History #: components/manage/Toolbar/More @@ -1670,7 +1670,7 @@ msgstr "Galería de imágenes" #: components/manage/Blocks/Teaser/schema # defaultMessage: Image override msgid "Image override" -msgstr "" +msgstr "Reemplazar imagen" #: components/manage/Blocks/Image/schema # defaultMessage: Image size @@ -1733,7 +1733,7 @@ msgstr "Insertar fila antes" #: helpers/MessageLabels/MessageLabels # defaultMessage: Inspect relations msgid "Inspect relations" -msgstr "" +msgstr "Inspeccionar relaciones" #: components/manage/Controlpanels/AddonsControlpanel # defaultMessage: Install @@ -1779,7 +1779,7 @@ msgstr "Intervalo anual" #: components/theme/View/RenderBlocks # defaultMessage: Invalid block - Will be removed on saving msgid "Invalid Block" -msgstr "Bloque no válido" +msgstr "Bloque no válido: se eliminará al guardar" #: components/manage/Widgets/QuerystringWidget # defaultMessage: Item batch size @@ -1898,7 +1898,7 @@ msgstr "Izquierda" #: components/manage/Blocks/Search/components/Facets # defaultMessage: Less filters msgid "Less filters" -msgstr "" +msgstr "Menos filtros" #: components/manage/Toolbar/Toolbar # defaultMessage: Link @@ -2030,7 +2030,7 @@ msgstr "¿Añadidos de forma manual o automática?" #: helpers/MessageLabels/MessageLabels # defaultMessage: Many relations found. Please search. msgid "Many relations found. Please search." -msgstr "" +msgstr "Muchas relaciones encontradas. Por favor, busque." #: components/manage/Blocks/Maps/MapsSidebar #: components/manage/Blocks/Maps/schema @@ -2112,7 +2112,7 @@ msgstr "Más" #: components/manage/Blocks/Search/components/Facets # defaultMessage: More filters msgid "More filters" -msgstr "" +msgstr "Más filtros" #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: More information about the upgrade procedure can be found in the documentation section of plone.org in the Upgrade Guide. @@ -2231,7 +2231,7 @@ msgstr "No se han encontrado complementos" #: components/manage/Controlpanels/Relations/RelationsMatrix # defaultMessage: No broken relations found. msgid "No broken relations found." -msgstr "" +msgstr "No se encontraron relaciones rotas." #: components/theme/RequestTimeout/RequestTimeout # defaultMessage: There is no connection to the server, due to a timeout o no network connection. @@ -2289,7 +2289,7 @@ msgstr "No hay opciones" #: helpers/MessageLabels/MessageLabels # defaultMessage: No relation found msgid "No relation found" -msgstr "" +msgstr "No se encontró relación" #: components/manage/BlockChooser/BlockChooser #: components/theme/Search/Search @@ -2474,7 +2474,7 @@ msgstr "Las personas responsables de la creación del contenido de este elemento #: components/manage/Blocks/Teaser/DefaultBody # defaultMessage: Please choose an existing content as source for this element msgid "Please choose an existing content as source for this element" -msgstr "" +msgstr "Elija un contenido existente como origen para este elemento" #: components/manage/Controlpanels/Controlpanels # defaultMessage: Please continue with the upgrade. @@ -2514,7 +2514,7 @@ msgstr "Actualice plone.restapi a la versión 8.24.0 o superior." #: components/manage/Controlpanels/Relations/Relations # defaultMessage: Please upgrade to plone.restapi >= 8.35.3. msgid "Please upgrade to plone.restapi >= 8.35.3." -msgstr "" +msgstr "Por favor, actualice a plone.restapi >= 8.35.3." #: components/theme/Footer/Footer # defaultMessage: Plone Foundation @@ -2613,7 +2613,7 @@ msgstr "Leer Más..." #: components/manage/Controlpanels/Relations/RelationsListing # defaultMessage: Read only for this type of relation. msgid "Read only for this type of relation." -msgstr "" +msgstr "Solo lectura para este tipo de relación." #: components/manage/Contents/Contents # defaultMessage: Rearrange items by… @@ -2661,24 +2661,24 @@ msgstr "Formulario de registro" #: helpers/MessageLabels/MessageLabels # defaultMessage: Relation msgid "Relation name" -msgstr "" +msgstr "Relación" #: components/manage/Controlpanels/Controlpanels #: components/manage/Controlpanels/Relations/Relations #: helpers/MessageLabels/MessageLabels # defaultMessage: Relations msgid "Relations" -msgstr "" +msgstr "Relaciones" #: components/manage/Controlpanels/Relations/RelationsListing # defaultMessage: Relations are editable with plone.api >= 2.0.3. msgid "Relations are editable with plone.api >= 2.0.3." -msgstr "" +msgstr "Las relaciones son editables con plone.api >= 2.0.3." #: helpers/MessageLabels/MessageLabels # defaultMessage: Relations updated msgid "Relations updated" -msgstr "" +msgstr "Relaciones actualizadas" #: components/theme/Search/Search # defaultMessage: Relevance @@ -2782,7 +2782,7 @@ msgstr "Limpiar el título del término" #: components/manage/Blocks/Teaser/Data # defaultMessage: Reset the block msgid "Reset the block" -msgstr "" +msgstr "Restablecer el bloque" #: components/manage/Widgets/QuerystringWidget # defaultMessage: Results limit @@ -2891,7 +2891,7 @@ msgstr "Guardado" #: components/manage/Contents/ContentsItem # defaultMessage: Scheduled msgid "Scheduled" -msgstr "" +msgstr "Programado" #: components/manage/Controlpanels/ContentTypesActions # defaultMessage: Schema @@ -2969,12 +2969,12 @@ msgstr "Resultados de búsqueda para {term}" #: helpers/MessageLabels/MessageLabels # defaultMessage: Search sources by title or path msgid "Search sources by title or path" -msgstr "" +msgstr "Buscar orígenes por título o ruta" #: helpers/MessageLabels/MessageLabels # defaultMessage: Search targets by title or path msgid "Search targets by title or path" -msgstr "" +msgstr "Buscar destinos por título o ruta" #: helpers/MessageLabels/MessageLabels # defaultMessage: Search users… @@ -3017,7 +3017,7 @@ msgstr "Seleccionar columnas a mostrar" #: helpers/MessageLabels/MessageLabels # defaultMessage: Select relation msgid "Select relation" -msgstr "" +msgstr "Seleccionar relación" #: components/manage/Contents/ContentsWorkflowModal # defaultMessage: Select the transition to be used for modifying the items state. @@ -3027,7 +3027,7 @@ msgstr "Seleccione la transición a ser efectuada al cambiar el estado del conte #: helpers/MessageLabels/MessageLabels # defaultMessage: Selected msgid "Selected" -msgstr "" +msgstr "Seleccionado" #: components/manage/Widgets/RecurrenceWidget/Occurences # defaultMessage: Selected dates @@ -3126,7 +3126,7 @@ msgstr "Mostrar respuestas" #: components/manage/Blocks/Search/components/Facets # defaultMessage: Show filters msgid "Show filters" -msgstr "" +msgstr "Mostrar filtros" #: helpers/MessageLabels/MessageLabels # defaultMessage: Show groups of users below @@ -3141,12 +3141,12 @@ msgstr "Mostrar elemento" #: components/manage/Controlpanels/Relations/RelationsMatrix # defaultMessage: Show potential sources. Not only objects that are source of some relation. msgid "Show potential sources. Not only objects that are source of some relation." -msgstr "" +msgstr "Mostrar orígenes potenciales. No solo objetos que son orígenes de alguna relación." #: components/manage/Controlpanels/Relations/RelationsMatrix # defaultMessage: Show potential targets. Not only objects that are target of some relation. msgid "Show potential targets. Not only objects that are target of some relation." -msgstr "" +msgstr "Mostrar destinos potenciales. No solo objetos que son destino de alguna relación." #: components/manage/Blocks/Search/schema # defaultMessage: Show search button? @@ -3218,7 +3218,7 @@ msgstr "Pequeño" #: components/manage/Controlpanels/Relations/Relations # defaultMessage: Some relations are broken. Please fix. msgid "Some relations are broken. Please fix." -msgstr "" +msgstr "Algunas relaciones están rotas. Por favor, arréglelas." #: error # defaultMessage: Sorry, something went wrong with your request @@ -3431,7 +3431,7 @@ msgstr "El camino de destino debe comenzar con /." #: components/manage/Blocks/Teaser/schema # defaultMessage: Teaser msgid "Teaser" -msgstr "" +msgstr "Destacado" #: components/manage/Widgets/SchemaWidget # defaultMessage: Text @@ -3627,7 +3627,7 @@ msgstr "Comentarios totales" #: components/manage/Contents/Contents # defaultMessage: Total items to be deleted: msgid "Total items to be deleted:" -msgstr "" +msgstr "Total de elementos a eliminar:" #: components/manage/Controlpanels/DatabaseInformation # defaultMessage: Total number of objects in each cache @@ -4152,7 +4152,7 @@ msgstr "Ha sido salido del sitio." #: components/manage/Controlpanels/Relations/Relations # defaultMessage: You have not the required permission for this control panel. msgid "You have not the required permission for this control panel." -msgstr "" +msgstr "No tiene el permiso requerido para este panel de control." #: components/theme/PasswordReset/RequestPasswordReset # defaultMessage: Your email is required for reset your password. @@ -4308,17 +4308,17 @@ msgstr "Dónde" #: helpers/MessageLabels/MessageLabels # defaultMessage: This website does not accept files larger than {limit} msgid "fileTooLarge" -msgstr "" +msgstr "Este sitio web no acepta archivos de más de {limit}" #: helpers/MessageLabels/MessageLabels # defaultMessage: flush intIds and rebuild relations msgid "flush intIds and rebuild relations" -msgstr "" +msgstr "vaciar intIds y reconstruir relaciones" #: components/manage/Blocks/Teaser/schema # defaultMessage: Head title msgid "head_title" -msgstr "" +msgstr "Encabezado" #: components/theme/PasswordReset/RequestPasswordReset # defaultMessage: Password reset confirmation sent @@ -4419,7 +4419,7 @@ msgstr "Más usado" #: components/manage/Controlpanels/Relations/RelationsListing # defaultMessage: Found {sources} sources and {targets} targets. Narrow down to {max}! msgid "narrowDownRelations" -msgstr "" +msgstr "Se encontraron {sources} orígenes y {targets} destinos. ¡Reduzca a {max}!" #: components/manage/Blocks/Search/components/DateRangeFacetFilterListEntry #: components/manage/Blocks/Search/components/ToggleFacetFilterListEntry @@ -4477,7 +4477,7 @@ msgstr "Seleccione..." #: helpers/MessageLabels/MessageLabels # defaultMessage: rebuild relations msgid "rebuild relations" -msgstr "" +msgstr "reconstruir relaciones" #: components/theme/Search/Search # defaultMessage: results @@ -4682,7 +4682,7 @@ msgstr "Ordenar" #: helpers/MessageLabels/MessageLabels # defaultMessage: sources path msgid "sources path" -msgstr "" +msgstr "ruta de origen" #: config/Blocks # defaultMessage: Table @@ -4692,7 +4692,7 @@ msgstr "Tabla" #: helpers/MessageLabels/MessageLabels # defaultMessage: target path msgid "target path" -msgstr "" +msgstr "ruta de destino" #: config/Blocks # defaultMessage: Text From 37fce9295a1624d86cd16ce483a15c0a6bbf3be0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Fri, 23 Jun 2023 13:00:15 +0200 Subject: [PATCH 17/19] Release notes for 16.20.8 16.21.0 16.21.1 (#4910) --- CHANGELOG.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cfc61862e..07e07830be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -285,6 +285,44 @@ - Use a universal static path for both documentation and volto repos. @stevepiercy [#4376](https://github.com/plone/volto/issues/4376) +## 16.21.1 (2023-06-23) + +### Bugfix + +- Added current page parameter to route in listing and search block pagination - Fix: #3868 @bipoza [#4159](https://github.com/plone/volto/issues/4159) + + +## 16.21.0 (2023-06-16) + +### Feature + +- Display PAS validation errors. [tschorr] [#4801](https://github.com/plone/volto/issues/4801) +- Allow to deselect color in ColorPickerWidget. @ksuess [#4838](https://github.com/plone/volto/issues/4838) +- Added a CSS identifier to the Slate style menu options. @razvanMiu [#4846](https://github.com/plone/volto/issues/4846) +- Use a Container from the registry in the Form component and fallback to the Semantic UI one. @sneridagh [#4849](https://github.com/plone/volto/issues/4849) +- Add and enforce a new config setting, `maxFileUploadSize`. @davisagli [#4868](https://github.com/plone/volto/issues/4868) +- Configurable Container component from registry for some key route views. @sneridagh [#4871](https://github.com/plone/volto/issues/4871) + +### Bugfix + +- Do not break validation on required number field with value 0 @cekk [#4841](https://github.com/plone/volto/issues/4841) +- Fix regression in horizontal scroll in contents view, add it back @sneridagh [#4872](https://github.com/plone/volto/issues/4872) +- Fix and improve the `addStyling` helper @sneridagh [#4880](https://github.com/plone/volto/issues/4880) + + +## 16.20.8 (2023-06-01) + +### Bugfix + +- Fix special characters in request urls @pnicolli @mamico @luca-bellenghi @cekk [#4826](https://github.com/plone/volto/issues/4826) +- Fix block is undefined in StyleWrapper helper when building classnames @sneridagh [#4827](https://github.com/plone/volto/issues/4827) +- Fix navigation sections in 404 pages @sneridagh [#4836](https://github.com/plone/volto/issues/4836) + +### Documentation + +- Fix glossary warning due to lack of empty line before a term. @stevepiercy [#4820](https://github.com/plone/volto/issues/4820) + + ## 16.20.7 (2023-05-24) ### Bugfix From e2ed2fcd2b2c550697a0fd733a3bc46d6eb9f53d Mon Sep 17 00:00:00 2001 From: Tiberiu Ichim Date: Fri, 23 Jun 2023 15:18:53 +0300 Subject: [PATCH 18/19] Linked headlines (#3540) Co-authored-by: Alin Voinea Co-authored-by: nileshgulia1 Co-authored-by: Timo Stollenwerk --- cypress/tests/core/blocks/block-anchors.js | 71 +++++++++++++++++ cypress/tests/core/guillotina/blocks-text.js | 4 +- .../05-block-slate-format-basics.js | 4 +- cypress/tests/guillotina/blocks-text.js | 4 +- .../volto-slate/configuration-settings.md | 13 ++++ locales/ca/LC_MESSAGES/volto.po | 5 ++ locales/de/LC_MESSAGES/volto.po | 5 ++ locales/en/LC_MESSAGES/volto.po | 5 ++ locales/es/LC_MESSAGES/volto.po | 5 ++ locales/eu/LC_MESSAGES/volto.po | 5 ++ locales/fi/LC_MESSAGES/volto.po | 5 ++ locales/fr/LC_MESSAGES/volto.po | 5 ++ locales/it/LC_MESSAGES/volto.po | 5 ++ locales/ja/LC_MESSAGES/volto.po | 5 ++ locales/nl/LC_MESSAGES/volto.po | 5 ++ locales/pt/LC_MESSAGES/volto.po | 5 ++ locales/pt_BR/LC_MESSAGES/volto.po | 5 ++ locales/ro/LC_MESSAGES/volto.po | 5 ++ locales/volto.pot | 5 ++ locales/zh_CN/LC_MESSAGES/volto.po | 5 ++ news/4287.feature | 1 + package.json | 1 + .../src/blocks/Text/TextBlockView.jsx | 36 +++++---- packages/volto-slate/src/editor/config.jsx | 9 ++- .../volto-slate/src/editor/less/slate.less | 28 +++++++ packages/volto-slate/src/editor/render.jsx | 76 +++++++++++++++++-- .../manage/Blocks/Listing/ListingBody.jsx | 38 ++++++++-- src/components/manage/Blocks/Title/View.jsx | 20 +++-- .../manage/Blocks/Title/View.test.jsx | 17 ++++- .../Title/__snapshots__/View.test.jsx.snap | 22 ++++++ src/components/manage/Blocks/ToC/View.jsx | 9 ++- .../ToC/variations/DefaultTocRenderer.jsx | 21 ++++- .../Blocks/ToC/variations/HorizontalMenu.jsx | 8 +- src/helpers/MessageLabels/MessageLabels.js | 4 + src/helpers/ScrollToTop/ScrollToTop.jsx | 8 +- src/hooks/clipboard/useClipboard.js | 26 +++++++ src/hooks/index.js | 2 + yarn.lock | 8 ++ 38 files changed, 447 insertions(+), 58 deletions(-) create mode 100644 cypress/tests/core/blocks/block-anchors.js create mode 100644 news/4287.feature create mode 100644 packages/volto-slate/src/editor/less/slate.less create mode 100644 src/hooks/clipboard/useClipboard.js create mode 100644 src/hooks/index.js diff --git a/cypress/tests/core/blocks/block-anchors.js b/cypress/tests/core/blocks/block-anchors.js new file mode 100644 index 0000000000..1166b0ce26 --- /dev/null +++ b/cypress/tests/core/blocks/block-anchors.js @@ -0,0 +1,71 @@ +import { slateBeforeEach } from '../../../support/volto-slate'; + +describe('Block Tests: Anchors', () => { + beforeEach(slateBeforeEach); + + it('Add Block: Links', () => { + // Change page title + cy.clearSlateTitle(); + cy.getSlateTitle().type('Slate Heading Anchors'); + cy.getSlate().click(); + + // Add TOC block + cy.get('.ui.basic.icon.button.block-add-button').first().click(); + cy.get(".blocks-chooser .ui.form .field.searchbox input[type='text']").type( + 'table of contents', + ); + cy.get('.button.toc').click(); + + // Save page + cy.get('#toolbar-save').click(); + cy.url().should('eq', Cypress.config().baseUrl + '/my-page'); + cy.get('h1.documentFirstHeading') + .trigger('mouseover', { eventConstructor: 'MouseEvent' }) + .children() + .should('have.length', 1); + }); + + it('Add Block: add content to TOC', () => { + // Change page title + cy.clearSlateTitle(); + cy.getSlateTitle().type('Slate Heading Anchors'); + cy.getSlate().click(); + + // Add TOC block + cy.get('.ui.basic.icon.button.block-add-button').first().click(); + cy.get(".blocks-chooser .ui.form .field.searchbox input[type='text']").type( + 'table of contents', + ); + cy.get('.button.toc').click(); + + // Add headings + cy.get('.ui.drag.block.inner.slate').click().type('Title 1').click(); + cy.get('.ui.drag.block.inner.slate span span span').setSelection('Title 1'); + cy.get('.slate-inline-toolbar .button-wrapper a[title="Title"]').click({ + force: true, + }); + cy.get('.ui.drag.block.inner.slate').click().type('{enter}'); + + cy.get('.ui.drag.block.inner.slate').eq(1).click().type('Title 2').click(); + cy.get('.ui.drag.block.inner.slate span span span') + .eq(1) + .setSelection('Title 2'); + cy.get('.slate-inline-toolbar .button-wrapper a[title="Title"]').click({ + force: true, + }); + cy.get('.ui.drag.block.inner.slate').eq(1).click().type('{enter}'); + + // Save page + cy.get('#toolbar-save').click(); + cy.url().should('eq', Cypress.config().baseUrl + '/my-page'); + + // Check if the page contains the TOC and scrolls to each entry on click + cy.contains('Slate Heading Anchors'); + cy.get('h2[id="title-1"]').contains('Title 1'); + cy.get('h2[id="title-2"]').contains('Title 2'); + cy.get('a[href="#title-1"]').click(); + cy.get('h2[id="title-1"]').scrollIntoView().should('be.visible'); + cy.get('a[href="#title-2"]').click(); + cy.get('h2[id="title-2"]').scrollIntoView().should('be.visible'); + }); +}); diff --git a/cypress/tests/core/guillotina/blocks-text.js b/cypress/tests/core/guillotina/blocks-text.js index 8ca7b815b8..334c3b3c32 100644 --- a/cypress/tests/core/guillotina/blocks-text.js +++ b/cypress/tests/core/guillotina/blocks-text.js @@ -53,7 +53,7 @@ describe('Text Block Tests', () => { // then the page view should contain a link cy.contains('Colorless green ideas sleep furiously.'); - cy.get('#page-document a') + cy.get('#page-document p a') .should('have.attr', 'href') .and('include', 'https://google.com'); }); @@ -81,7 +81,7 @@ describe('Text Block Tests', () => { // then the page view should contain a mailto link cy.contains('Colorless green ideas sleep furiously.'); - cy.get('#page-document a') + cy.get('#page-document p a') .should('have.attr', 'href') .and('include', 'mailto:hello@example.com'); }); diff --git a/cypress/tests/core/volto-slate/05-block-slate-format-basics.js b/cypress/tests/core/volto-slate/05-block-slate-format-basics.js index 413d43a21b..3d32b822e4 100644 --- a/cypress/tests/core/volto-slate/05-block-slate-format-basics.js +++ b/cypress/tests/core/volto-slate/05-block-slate-format-basics.js @@ -135,7 +135,7 @@ describe('Block Tests: Basic text format', () => { cy.toolbarSave(); // then the page view should contain our changes - cy.get('[id="page-document"] h2').children().should('have.length', 0); + cy.get('[id="page-document"] h2').children().should('have.length', 1); cy.get('[id="page-document"] h2').contains('Colorless'); }); @@ -177,7 +177,7 @@ describe('Block Tests: Basic text format', () => { cy.toolbarSave(); // then the page view should contain our changes - cy.get('[id="page-document"] h3').children().should('have.length', 0); + cy.get('[id="page-document"] h3').children().should('have.length', 1); cy.get('[id="page-document"] h3').contains('Colorless'); }); diff --git a/cypress/tests/guillotina/blocks-text.js b/cypress/tests/guillotina/blocks-text.js index 8ca7b815b8..334c3b3c32 100644 --- a/cypress/tests/guillotina/blocks-text.js +++ b/cypress/tests/guillotina/blocks-text.js @@ -53,7 +53,7 @@ describe('Text Block Tests', () => { // then the page view should contain a link cy.contains('Colorless green ideas sleep furiously.'); - cy.get('#page-document a') + cy.get('#page-document p a') .should('have.attr', 'href') .and('include', 'https://google.com'); }); @@ -81,7 +81,7 @@ describe('Text Block Tests', () => { // then the page view should contain a mailto link cy.contains('Colorless green ideas sleep furiously.'); - cy.get('#page-document a') + cy.get('#page-document p a') .should('have.attr', 'href') .and('include', 'mailto:hello@example.com'); }); diff --git a/docs/source/configuration/volto-slate/configuration-settings.md b/docs/source/configuration/volto-slate/configuration-settings.md index a6da06c3b2..97276b804f 100644 --- a/docs/source/configuration/volto-slate/configuration-settings.md +++ b/docs/source/configuration/volto-slate/configuration-settings.md @@ -239,6 +239,19 @@ They are not persisted in the final value, so they are useful, for example, to h slate.runtimeDecorators = [([node, path], ranges) => ranges]; ``` +(editor-configuration-slate-useLinkedHeadings-label)= + +## `slate.useLinkedHeadings` + +The setting `slate.useLinkedHeadings` controls whether `volto-slate` creates anchors for headings, such as `h1` and `h2`, in the editor. + +The default setting is `true`. + +You can opt out of this feature by setting its value to `false`. + +```js +slate.useLinkedHeadings = false +``` (editor-configuration-blocks-initialBlocksFocus-label)= diff --git a/locales/ca/LC_MESSAGES/volto.po b/locales/ca/LC_MESSAGES/volto.po index 4bf4b148d1..db73f21323 100644 --- a/locales/ca/LC_MESSAGES/volto.po +++ b/locales/ca/LC_MESSAGES/volto.po @@ -1903,6 +1903,11 @@ msgstr "" msgid "Link" msgstr "Enllaç" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Link copied to clipboard +msgid "Link copied to clipboard" +msgstr "" + #: components/manage/Blocks/HeroImageLeft/schema #: components/manage/Blocks/Listing/schema # defaultMessage: Link more diff --git a/locales/de/LC_MESSAGES/volto.po b/locales/de/LC_MESSAGES/volto.po index f316a86a30..4d37f47225 100644 --- a/locales/de/LC_MESSAGES/volto.po +++ b/locales/de/LC_MESSAGES/volto.po @@ -1900,6 +1900,11 @@ msgstr "" msgid "Link" msgstr "Link" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Link copied to clipboard +msgid "Link copied to clipboard" +msgstr "" + #: components/manage/Blocks/HeroImageLeft/schema #: components/manage/Blocks/Listing/schema # defaultMessage: Link more diff --git a/locales/en/LC_MESSAGES/volto.po b/locales/en/LC_MESSAGES/volto.po index 08c9c94371..7783ecbea7 100644 --- a/locales/en/LC_MESSAGES/volto.po +++ b/locales/en/LC_MESSAGES/volto.po @@ -1894,6 +1894,11 @@ msgstr "" msgid "Link" msgstr "" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Link copied to clipboard +msgid "Link copied to clipboard" +msgstr "" + #: components/manage/Blocks/HeroImageLeft/schema #: components/manage/Blocks/Listing/schema # defaultMessage: Link more diff --git a/locales/es/LC_MESSAGES/volto.po b/locales/es/LC_MESSAGES/volto.po index db43208f5c..e532261061 100644 --- a/locales/es/LC_MESSAGES/volto.po +++ b/locales/es/LC_MESSAGES/volto.po @@ -1905,6 +1905,11 @@ msgstr "Menos filtros" msgid "Link" msgstr "Enlace" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Link copied to clipboard +msgid "Link copied to clipboard" +msgstr "" + #: components/manage/Blocks/HeroImageLeft/schema #: components/manage/Blocks/Listing/schema # defaultMessage: Link more diff --git a/locales/eu/LC_MESSAGES/volto.po b/locales/eu/LC_MESSAGES/volto.po index 6f2565e6d5..85aa21e868 100644 --- a/locales/eu/LC_MESSAGES/volto.po +++ b/locales/eu/LC_MESSAGES/volto.po @@ -1901,6 +1901,11 @@ msgstr "" msgid "Link" msgstr "Esteka" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Link copied to clipboard +msgid "Link copied to clipboard" +msgstr "" + #: components/manage/Blocks/HeroImageLeft/schema #: components/manage/Blocks/Listing/schema # defaultMessage: Link more diff --git a/locales/fi/LC_MESSAGES/volto.po b/locales/fi/LC_MESSAGES/volto.po index 791c210c1e..e3d3b2cc3f 100644 --- a/locales/fi/LC_MESSAGES/volto.po +++ b/locales/fi/LC_MESSAGES/volto.po @@ -1905,6 +1905,11 @@ msgstr "" msgid "Link" msgstr "Linkki" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Link copied to clipboard +msgid "Link copied to clipboard" +msgstr "" + #: components/manage/Blocks/HeroImageLeft/schema #: components/manage/Blocks/Listing/schema # defaultMessage: Link more diff --git a/locales/fr/LC_MESSAGES/volto.po b/locales/fr/LC_MESSAGES/volto.po index c4a007b100..8862a6e1ac 100644 --- a/locales/fr/LC_MESSAGES/volto.po +++ b/locales/fr/LC_MESSAGES/volto.po @@ -1911,6 +1911,11 @@ msgstr "" msgid "Link" msgstr "Lien" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Link copied to clipboard +msgid "Link copied to clipboard" +msgstr "" + #: components/manage/Blocks/HeroImageLeft/schema #: components/manage/Blocks/Listing/schema # defaultMessage: Link more diff --git a/locales/it/LC_MESSAGES/volto.po b/locales/it/LC_MESSAGES/volto.po index d63d8a20f0..c081ae1612 100644 --- a/locales/it/LC_MESSAGES/volto.po +++ b/locales/it/LC_MESSAGES/volto.po @@ -1894,6 +1894,11 @@ msgstr "" msgid "Link" msgstr "Link" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Link copied to clipboard +msgid "Link copied to clipboard" +msgstr "" + #: components/manage/Blocks/HeroImageLeft/schema #: components/manage/Blocks/Listing/schema # defaultMessage: Link more diff --git a/locales/ja/LC_MESSAGES/volto.po b/locales/ja/LC_MESSAGES/volto.po index a8a6fbea17..81b4228ce9 100644 --- a/locales/ja/LC_MESSAGES/volto.po +++ b/locales/ja/LC_MESSAGES/volto.po @@ -1902,6 +1902,11 @@ msgstr "" msgid "Link" msgstr "リンク" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Link copied to clipboard +msgid "Link copied to clipboard" +msgstr "" + #: components/manage/Blocks/HeroImageLeft/schema #: components/manage/Blocks/Listing/schema # defaultMessage: Link more diff --git a/locales/nl/LC_MESSAGES/volto.po b/locales/nl/LC_MESSAGES/volto.po index e575532f1a..24d58061a5 100644 --- a/locales/nl/LC_MESSAGES/volto.po +++ b/locales/nl/LC_MESSAGES/volto.po @@ -1913,6 +1913,11 @@ msgstr "" msgid "Link" msgstr "" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Link copied to clipboard +msgid "Link copied to clipboard" +msgstr "" + #: components/manage/Blocks/HeroImageLeft/schema #: components/manage/Blocks/Listing/schema # defaultMessage: Link more diff --git a/locales/pt/LC_MESSAGES/volto.po b/locales/pt/LC_MESSAGES/volto.po index 2f445ad4fd..b5ecc4dd7d 100644 --- a/locales/pt/LC_MESSAGES/volto.po +++ b/locales/pt/LC_MESSAGES/volto.po @@ -1902,6 +1902,11 @@ msgstr "" msgid "Link" msgstr "" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Link copied to clipboard +msgid "Link copied to clipboard" +msgstr "" + #: components/manage/Blocks/HeroImageLeft/schema #: components/manage/Blocks/Listing/schema # defaultMessage: Link more diff --git a/locales/pt_BR/LC_MESSAGES/volto.po b/locales/pt_BR/LC_MESSAGES/volto.po index bd0ee20da5..19cfd33d8d 100644 --- a/locales/pt_BR/LC_MESSAGES/volto.po +++ b/locales/pt_BR/LC_MESSAGES/volto.po @@ -1904,6 +1904,11 @@ msgstr "Menos filtros" msgid "Link" msgstr "Link" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Link copied to clipboard +msgid "Link copied to clipboard" +msgstr "" + #: components/manage/Blocks/HeroImageLeft/schema #: components/manage/Blocks/Listing/schema # defaultMessage: Link more diff --git a/locales/ro/LC_MESSAGES/volto.po b/locales/ro/LC_MESSAGES/volto.po index f2411286fc..e3e7c2ae0c 100644 --- a/locales/ro/LC_MESSAGES/volto.po +++ b/locales/ro/LC_MESSAGES/volto.po @@ -1894,6 +1894,11 @@ msgstr "" msgid "Link" msgstr "Legătură" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Link copied to clipboard +msgid "Link copied to clipboard" +msgstr "" + #: components/manage/Blocks/HeroImageLeft/schema #: components/manage/Blocks/Listing/schema # defaultMessage: Link more diff --git a/locales/volto.pot b/locales/volto.pot index 7cb0cc5731..4ef602b388 100644 --- a/locales/volto.pot +++ b/locales/volto.pot @@ -1896,6 +1896,11 @@ msgstr "" msgid "Link" msgstr "" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Link copied to clipboard +msgid "Link copied to clipboard" +msgstr "" + #: components/manage/Blocks/HeroImageLeft/schema #: components/manage/Blocks/Listing/schema # defaultMessage: Link more diff --git a/locales/zh_CN/LC_MESSAGES/volto.po b/locales/zh_CN/LC_MESSAGES/volto.po index 98779199d8..0355a55f65 100644 --- a/locales/zh_CN/LC_MESSAGES/volto.po +++ b/locales/zh_CN/LC_MESSAGES/volto.po @@ -1900,6 +1900,11 @@ msgstr "" msgid "Link" msgstr "链接" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Link copied to clipboard +msgid "Link copied to clipboard" +msgstr "" + #: components/manage/Blocks/HeroImageLeft/schema #: components/manage/Blocks/Listing/schema # defaultMessage: Link more diff --git a/news/4287.feature b/news/4287.feature new file mode 100644 index 0000000000..e676eb12b7 --- /dev/null +++ b/news/4287.feature @@ -0,0 +1 @@ +Added slug-based linked headings in `volto-slate`. @tiberiuichim, @nileshgulia1 diff --git a/package.json b/package.json index d09b21cb0c..8882758ccf 100644 --- a/package.json +++ b/package.json @@ -289,6 +289,7 @@ "eslint-plugin-react-hooks": "4.0.2", "express": "4.17.3", "filesize": "6", + "github-slugger": "1.4.0", "glob": "7.1.6", "history": "4.10.1", "hoist-non-react-statics": "3.3.2", diff --git a/packages/volto-slate/src/blocks/Text/TextBlockView.jsx b/packages/volto-slate/src/blocks/Text/TextBlockView.jsx index 5e8accb82c..7f9f623fbd 100644 --- a/packages/volto-slate/src/blocks/Text/TextBlockView.jsx +++ b/packages/volto-slate/src/blocks/Text/TextBlockView.jsx @@ -1,26 +1,30 @@ -import { serializeNodes } from '@plone/volto-slate/editor/render'; +import { + serializeNodes, + serializeNodesToText, +} from '@plone/volto-slate/editor/render'; import config from '@plone/volto/registry'; +import { isEqual } from 'lodash'; +import Slugger from 'github-slugger'; const TextBlockView = (props) => { const { id, data, styling = {} } = props; const { value, override_toc } = data; const metadata = props.metadata || props.properties; - return serializeNodes( - value, - (node, path) => { - const res = { ...styling }; - if (node.type) { - if ( - config.settings.slate.topLevelTargetElements.includes(node.type) || - override_toc - ) { - res.id = id; - } + const { topLevelTargetElements } = config.settings.slate; + + const getAttributes = (node, path) => { + const res = { ...styling }; + if (node.type && isEqual(path, [0])) { + if (topLevelTargetElements.includes(node.type) || override_toc) { + const text = serializeNodesToText(node?.children || []); + const slug = Slugger.slug(text); + res.id = slug || id; } - return res; - }, - { metadata: metadata }, - ); + } + return res; + }; + + return serializeNodes(value, getAttributes, { metadata: metadata }); }; export default TextBlockView; diff --git a/packages/volto-slate/src/editor/config.jsx b/packages/volto-slate/src/editor/config.jsx index d97bb021d7..cdacc04daa 100644 --- a/packages/volto-slate/src/editor/config.jsx +++ b/packages/volto-slate/src/editor/config.jsx @@ -43,6 +43,7 @@ import { bTagDeserializer, codeTagDeserializer, } from './deserialize'; +import { renderLinkElement } from './render'; // Registry of available buttons export const buttons = { @@ -234,10 +235,10 @@ export const defaultBlockType = 'p'; export const elements = { default: ({ attributes, children }) =>

{children}

, - h1: ({ attributes, children }) =>

{children}

, - h2: ({ attributes, children }) =>

{children}

, - h3: ({ attributes, children }) =>

{children}

, - h4: ({ attributes, children }) =>

{children}

, + h1: renderLinkElement('h1'), + h2: renderLinkElement('h2'), + h3: renderLinkElement('h3'), + h4: renderLinkElement('h4'), li: ({ attributes, children }) =>
  • {children}
  • , ol: ({ attributes, children }) =>
      {children}
    , diff --git a/packages/volto-slate/src/editor/less/slate.less b/packages/volto-slate/src/editor/less/slate.less new file mode 100644 index 0000000000..954ee09ab2 --- /dev/null +++ b/packages/volto-slate/src/editor/less/slate.less @@ -0,0 +1,28 @@ +h1, +h2, +h3, +h4 { + &:hover { + a.anchor { + svg { + opacity: 1; + transform: rotate(15deg); + } + } + } + + a.anchor { + position: absolute; + display: inline-block; + margin-left: 5px; + vertical-align: middle; + + svg { + width: 1.6ch; + fill: #42526e; + opacity: 0; + transform: rotate(15deg) translate(-8px, 2px); + transition: opacity 0.2s ease 0s, transform 0.2s ease 0s; + } + } +} diff --git a/packages/volto-slate/src/editor/render.jsx b/packages/volto-slate/src/editor/render.jsx index bfc34cb66e..45ab42ca75 100644 --- a/packages/volto-slate/src/editor/render.jsx +++ b/packages/volto-slate/src/editor/render.jsx @@ -1,9 +1,18 @@ import React from 'react'; import { renderToStaticMarkup } from 'react-dom/server'; +import { useLocation } from 'react-router-dom'; +import { toast } from 'react-toastify'; +import { useIntl } from 'react-intl'; import { Node, Text } from 'slate'; import cx from 'classnames'; -import { isEmpty, isEqual, omit } from 'lodash'; +import { isEmpty, omit } from 'lodash'; +import { UniversalLink, Toast } from '@plone/volto/components'; +import { messages, addAppURL } from '@plone/volto/helpers'; +import useClipboard from '@plone/volto/hooks/clipboard/useClipboard'; import config from '@plone/volto/registry'; +import linkSVG from '@plone/volto/icons/link.svg'; + +import './less/slate.less'; const OMITTED = ['editor', 'path']; @@ -106,13 +115,7 @@ export const serializeNodes = (nodes, getAttributes, extras = {}) => { mode="view" key={path} data-slate-data={node.data ? serializeData(node) : null} - attributes={ - isEqual(path, [0]) - ? getAttributes - ? getAttributes(node, path) - : null - : null - } + attributes={getAttributes ? getAttributes(node, path) : null} extras={extras} > {_serializeNodes(Array.from(Node.children(editor, path)))} @@ -153,3 +156,60 @@ export const serializeNodesToText = (nodes) => { export const serializeNodesToHtml = (nodes) => renderToStaticMarkup(serializeNodes(nodes)); + +export const renderLinkElement = (tagName) => { + function LinkElement({ + attributes, + children, + mode = 'edit', + className = null, + }) { + const { slate = {} } = config.settings; + const Tag = tagName; + const slug = attributes.id || ''; + const location = useLocation(); + const appPathname = addAppURL(location.pathname); + // eslint-disable-next-line no-unused-vars + const [copied, copy, setCopied] = useClipboard( + appPathname.concat(`#${slug}`), + ); + const intl = useIntl(); + + return slate.useLinkedHeadings === false ? ( + + {children} + + ) : ( + + {children} + {mode === 'view' && slug && ( + + )} + + ); + } + LinkElement.displayName = `${tagName}LinkElement`; + return LinkElement; +}; diff --git a/src/components/manage/Blocks/Listing/ListingBody.jsx b/src/components/manage/Blocks/Listing/ListingBody.jsx index 0d8e54003f..227b473c8f 100644 --- a/src/components/manage/Blocks/Listing/ListingBody.jsx +++ b/src/components/manage/Blocks/Listing/ListingBody.jsx @@ -1,14 +1,35 @@ -import React, { createRef } from 'react'; +import React, { createRef, useMemo } from 'react'; import { FormattedMessage, injectIntl } from 'react-intl'; import cx from 'classnames'; import { Pagination, Dimmer, Loader } from 'semantic-ui-react'; +import Slugger from 'github-slugger'; import { Icon } from '@plone/volto/components'; +import { renderLinkElement } from '@plone/volto-slate/editor/render'; import config from '@plone/volto/registry'; import withQuerystringResults from './withQuerystringResults'; import paginationLeftSVG from '@plone/volto/icons/left-key.svg'; import paginationRightSVG from '@plone/volto/icons/right-key.svg'; +const Headline = ({ headlineTag, id, data = {}, listingItems, isEditMode }) => { + let attr = { id }; + const slug = Slugger.slug(data.headline); + attr.id = slug || id; + const LinkedHeadline = useMemo(() => renderLinkElement(headlineTag), [ + headlineTag, + ]); + return ( + 0, + })} + /> + ); +}; + const ListingBody = withQuerystringResults((props) => { const { data = {}, @@ -22,6 +43,7 @@ const ListingBody = withQuerystringResults((props) => { nextBatch, isFolderContentsListing, hasLoaded, + id, } = props; let ListingBodyTemplate; @@ -50,13 +72,13 @@ const ListingBody = withQuerystringResults((props) => { return ( <> {data.headline && ( - 0, - })} - > - {data.headline} - + )} {listingItems?.length > 0 ? (
    diff --git a/src/components/manage/Blocks/Title/View.jsx b/src/components/manage/Blocks/Title/View.jsx index b5836f8109..8486785fe7 100644 --- a/src/components/manage/Blocks/Title/View.jsx +++ b/src/components/manage/Blocks/Title/View.jsx @@ -3,19 +3,29 @@ * @module volto-slate/blocks/Title/TitleBlockView */ -import React from 'react'; +import React, { useMemo } from 'react'; import PropTypes from 'prop-types'; +import Slugger from 'github-slugger'; +import { renderLinkElement } from '@plone/volto-slate/editor/render'; /** * View title block component. * @class View * @extends Component */ -const TitleBlockView = ({ properties, metadata }) => { +const TitleBlockView = ({ properties, metadata, id, children }) => { + let attr = { id }; + const title = (properties || metadata)['title']; + const slug = Slugger.slug(title); + attr.id = slug || id; + const LinkedTitle = useMemo(() => renderLinkElement('h1'), []); return ( -

    - {(metadata || properties)['title'] || ''} -

    + ); }; diff --git a/src/components/manage/Blocks/Title/View.test.jsx b/src/components/manage/Blocks/Title/View.test.jsx index 80683d24c9..9c2c53d29e 100644 --- a/src/components/manage/Blocks/Title/View.test.jsx +++ b/src/components/manage/Blocks/Title/View.test.jsx @@ -1,10 +1,25 @@ import React from 'react'; import renderer from 'react-test-renderer'; +import configureStore from 'redux-mock-store'; +import { Provider } from 'react-intl-redux'; +import { MemoryRouter } from 'react-router-dom'; import View from './View'; +const mockStore = configureStore(); + test('renders a view title component', () => { + const store = mockStore({ + intl: { + locale: 'en', + messages: {}, + }, + }); const component = renderer.create( - , + + + + + , ); const json = component.toJSON(); expect(json).toMatchSnapshot(); diff --git a/src/components/manage/Blocks/Title/__snapshots__/View.test.jsx.snap b/src/components/manage/Blocks/Title/__snapshots__/View.test.jsx.snap index 4e31d1dc1b..cc62ae1b9c 100644 --- a/src/components/manage/Blocks/Title/__snapshots__/View.test.jsx.snap +++ b/src/components/manage/Blocks/Title/__snapshots__/View.test.jsx.snap @@ -3,7 +3,29 @@ exports[`renders a view title component 1`] = `

    My Title +

    `; diff --git a/src/components/manage/Blocks/ToC/View.jsx b/src/components/manage/Blocks/ToC/View.jsx index 92f0a5f033..a0cd337f53 100644 --- a/src/components/manage/Blocks/ToC/View.jsx +++ b/src/components/manage/Blocks/ToC/View.jsx @@ -56,7 +56,14 @@ const View = (props) => { const items = []; if (!level || !levels.includes(level)) return; tocEntriesLayout.push(id); - tocEntries[id] = { level, title: title || block.plaintext, items, id }; + tocEntries[id] = { + level, + title: title || block.plaintext, + items, + id, + override_toc: block.override_toc, + plaintext: block.plaintext, + }; if (level < rootLevel) { rootLevel = level; } diff --git a/src/components/manage/Blocks/ToC/variations/DefaultTocRenderer.jsx b/src/components/manage/Blocks/ToC/variations/DefaultTocRenderer.jsx index 97fdbbcd10..148a8d88e3 100644 --- a/src/components/manage/Blocks/ToC/variations/DefaultTocRenderer.jsx +++ b/src/components/manage/Blocks/ToC/variations/DefaultTocRenderer.jsx @@ -8,15 +8,27 @@ import PropTypes from 'prop-types'; import { map } from 'lodash'; import { List } from 'semantic-ui-react'; import { FormattedMessage, injectIntl } from 'react-intl'; +import { useHistory } from 'react-router-dom'; import AnchorLink from 'react-anchor-link-smooth-scroll'; +import Slugger from 'github-slugger'; -const RenderListItems = ({ items, data }) => { +const RenderListItems = ({ items, data, history }) => { return map(items, (item) => { - const { id, level, title } = item; + const { id, level, title, override_toc, plaintext } = item; + const slug = override_toc + ? Slugger.slug(plaintext) + : Slugger.slug(title) || id; return ( item && ( - {title} + { + history.push({ hash: slug }); + }} + > + {title} + {item.items?.length > 0 && ( { * @extends Component */ const View = ({ data, tocEntries }) => { + const history = useHistory(); return ( <> {data.title && !data.hide_title ? ( @@ -57,7 +70,7 @@ const View = ({ data, tocEntries }) => { bulleted={!data.ordered} as={data.ordered ? 'ol' : 'ul'} > - + ); diff --git a/src/components/manage/Blocks/ToC/variations/HorizontalMenu.jsx b/src/components/manage/Blocks/ToC/variations/HorizontalMenu.jsx index 8239a96e97..0bd1ccf8b0 100644 --- a/src/components/manage/Blocks/ToC/variations/HorizontalMenu.jsx +++ b/src/components/manage/Blocks/ToC/variations/HorizontalMenu.jsx @@ -9,15 +9,19 @@ import { map } from 'lodash'; import { Menu } from 'semantic-ui-react'; import { FormattedMessage, injectIntl } from 'react-intl'; import AnchorLink from 'react-anchor-link-smooth-scroll'; +import Slugger from 'github-slugger'; const RenderMenuItems = ({ items }) => { return map(items, (item) => { - const { id, level, title } = item; + const { id, level, title, override_toc, plaintext } = item; + const slug = override_toc + ? Slugger.slug(plaintext) + : Slugger.slug(title) || id; return ( item && ( - {title} + {title} {item.items?.length > 0 && } diff --git a/src/helpers/MessageLabels/MessageLabels.js b/src/helpers/MessageLabels/MessageLabels.js index a341f329b7..2e1c4f6b90 100644 --- a/src/helpers/MessageLabels/MessageLabels.js +++ b/src/helpers/MessageLabels/MessageLabels.js @@ -260,6 +260,10 @@ export const messages = defineMessages({ id: 'Show groups of users below', defaultMessage: 'Show groups of users below', }, + urlClipboardCopy: { + id: 'Link copied to clipboard', + defaultMessage: 'Link copied to clipboard', + }, inspectRelations: { id: 'Inspect relations', defaultMessage: 'Inspect relations', diff --git a/src/helpers/ScrollToTop/ScrollToTop.jsx b/src/helpers/ScrollToTop/ScrollToTop.jsx index 6d4a87bcc7..8122fbea63 100644 --- a/src/helpers/ScrollToTop/ScrollToTop.jsx +++ b/src/helpers/ScrollToTop/ScrollToTop.jsx @@ -28,15 +28,17 @@ class ScrollToTop extends React.Component { * @memberof ScrollToTop */ componentDidUpdate(prevProps) { + const { location } = this.props; const noInitialBlocksFocus = // Do not scroll on /edit config.blocks?.initialBlocksFocus === null ? this.props.location?.pathname.slice(-5) !== '/edit' : true; + + const isHash = location?.hash || location?.pathname.hash; if ( - !this.props.location?.hash && - !this.props.location?.pathname.hash && + !isHash && noInitialBlocksFocus && - this.props.location?.pathname !== prevProps.location?.pathname + location?.pathname !== prevProps.location?.pathname ) { window.scrollTo(0, 0); } diff --git a/src/hooks/clipboard/useClipboard.js b/src/hooks/clipboard/useClipboard.js new file mode 100644 index 0000000000..eaabbf7a15 --- /dev/null +++ b/src/hooks/clipboard/useClipboard.js @@ -0,0 +1,26 @@ +import { useState, useRef, useEffect, useCallback } from 'react'; + +export default function useClipboard(clipboardText = '') { + const stringToCopy = useRef(clipboardText); + const [copied, setCopied] = useState(false); + + //synchronous: window.clipboardData.setData(options.format || "text", text); + const copyToClipboard = async (text) => { + if ('clipboard' in navigator) { + return await navigator.clipboard.writeText(text); + } else { + return document.execCommand('copy', true, text); + } + }; + + const copyAction = useCallback(() => { + const copiedString = copyToClipboard(stringToCopy.current); + setCopied(copiedString); + }, [stringToCopy]); + + useEffect(() => { + stringToCopy.current = clipboardText; + }, [clipboardText]); + + return [copied, copyAction, setCopied]; +} diff --git a/src/hooks/index.js b/src/hooks/index.js new file mode 100644 index 0000000000..ab9a86a472 --- /dev/null +++ b/src/hooks/index.js @@ -0,0 +1,2 @@ +export useClipboard from '@plone/volto/hooks/clipboard/useClipboard'; +export useToken from '@plone/volto/hooks/userSession/useToken'; diff --git a/yarn.lock b/yarn.lock index c4a6999d35..3188760ebf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2690,6 +2690,7 @@ __metadata: express: 4.17.3 filesize: 6 full-icu: 1.4.0 + github-slugger: 1.4.0 glob: 7.1.6 history: 4.10.1 hoist-non-react-statics: 3.3.2 @@ -12198,6 +12199,13 @@ __metadata: languageName: node linkType: hard +"github-slugger@npm:1.4.0": + version: 1.4.0 + resolution: "github-slugger@npm:1.4.0" + checksum: 4f52e7a21f5c6a4c5328f01fe4fe13ae8881fea78bfe31f9e72c4038f97e3e70d52fb85aa7633a52c501dc2486874474d9abd22aa61cbe9b113099a495551c6b + languageName: node + linkType: hard + "github-slugger@npm:^1.0.0": version: 1.5.0 resolution: "github-slugger@npm:1.5.0" From e9ca69974c5f3dc6c7d972ee1e91f3c1b5cf9c4b Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Fri, 23 Jun 2023 14:21:16 +0200 Subject: [PATCH 19/19] Release 17.0.0-alpha.14 --- CHANGELOG.md | 12 ++++++++++++ news/4287.feature | 1 - news/4845.feature | 1 - news/4897.bugfix | 1 - package.json | 2 +- packages/volto-slate/package.json | 2 +- 6 files changed, 14 insertions(+), 5 deletions(-) delete mode 100644 news/4287.feature delete mode 100644 news/4845.feature delete mode 100644 news/4897.bugfix diff --git a/CHANGELOG.md b/CHANGELOG.md index 07e07830be..8b7aed4fb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,18 @@ +## 17.0.0-alpha.14 (2023-06-23) + +### Feature + +- Added slug-based linked headings in `volto-slate`. @tiberiuichim, @nileshgulia1 [#4287](https://github.com/plone/volto/issues/4287) +- Refactored Anontools components. @Tishasoumya-02 [#4845](https://github.com/plone/volto/issues/4845) + +### Bugfix + +- Update to version 6.0.5 of Plone backend. @davisagli [#4897](https://github.com/plone/volto/issues/4897) + + ## 17.0.0-alpha.13 (2023-06-15) ### Feature diff --git a/news/4287.feature b/news/4287.feature deleted file mode 100644 index e676eb12b7..0000000000 --- a/news/4287.feature +++ /dev/null @@ -1 +0,0 @@ -Added slug-based linked headings in `volto-slate`. @tiberiuichim, @nileshgulia1 diff --git a/news/4845.feature b/news/4845.feature deleted file mode 100644 index e8599a0811..0000000000 --- a/news/4845.feature +++ /dev/null @@ -1 +0,0 @@ -Refactored Anontools components. @Tishasoumya-02 diff --git a/news/4897.bugfix b/news/4897.bugfix deleted file mode 100644 index 58beb4357d..0000000000 --- a/news/4897.bugfix +++ /dev/null @@ -1 +0,0 @@ -Update to version 6.0.5 of Plone backend. @davisagli diff --git a/package.json b/package.json index 8882758ccf..d2126596b8 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ } ], "license": "MIT", - "version": "17.0.0-alpha.13", + "version": "17.0.0-alpha.14", "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 506c771cc4..7d599abd35 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.13", + "version": "17.0.0-alpha.14", "description": "Slate.js integration with Volto", "main": "src/index.js", "author": "European Environment Agency: IDM2 A-Team",